From iLab Cookbook

Toolkits: VideoSlitScanning

<< Back to the EasyImage Toolkit page



Tutorial - Slit Scanning

Slit scanning is a photographic technique where a photo is taken through a slit over time. This example shows a variation of slit-scanning applied to video. See our paper and video for more information on how this idea works and how it can be applied.

What you will learn

This tutorial is a companion to the How-To: Understanding Bitmaps. Read that first, and also go through the VideoSlitScanning tutorial before trying to understand this more advanced bitmap manipulation example. This tutorial shows:

In the above picture,

Download

While we recommend you create this program from scratch, you can also download the source and executables.

Source and Executables of this and other examples: EasyImagesExamples.zip
  • Unzip all files into a single folder.
  • Try the executable; a shortcut is in the top-level directory, or look in the debug directory
  • You will need a web camera attached to your computer.

Step 1. Preconditions

Including EasyImages in your new Visual Studio 2005 C# project. This was described in Minimal Camera Example. Make sure to include the using EasyImages; line in your project.

Step 2. Creating GUI controls

Add the following GUI controls to your form window. Thei locations and purpose should be obvious if you check the image at the top of this page.

Step 3. Writing the program

The complete program is listed below. Its long, but it does alot!

using System;
using System.Drawing;
using System.Windows.Forms;
using System.Drawing.Imaging; //Needed
using EasyImages;
namespace VideoManipulatingBits
{
  public partial class Form1 : Form
  {
    Bitmap bmSlitScan; //A bitmap that we will use to hold the slit-scanned image
    EasyImages.CameraClient camera;
    private delegate void SetPictureBoxImage(PictureBox pbox, Bitmap image);

    public Form1()
    {
      InitializeComponent();
    }

   //As the GUI is loaded...
    private void Form1_Load(object sender, EventArgs e)
    {
      //Create a new camera object and set its initial properties
      camera = new EasyImages.CameraClient("DefaultCamera");
      camera.FramesPerSecond = 10;
      camera.ReceivedFrame += new EasyImages.CameraClient.ReceivedFrameEventHandler(camera_ReceivedFrame);
      camera.Start();

      // Create and display blank black bitmap which will eventually hold the slit scanned image
      bmSlitScan = new Bitmap(pbSlitScan.Width, pbSlitScan.Height);
      BitmapColor( (Bitmap) bmSlitScan, 0, 0, 0);
      DisplayImageInPictureBox (pbSlitScan, bmSlitScan);
    }

    // As we receive each frame...
    void camera_ReceivedFrame(object sender, EasyImages.CameraEventArgs e)
    {
      // Shift the slit-scanned bitmap left by one column
      BitmapShiftLeft((Bitmap)bmSlitScan);

      // Copy the column at the given location in the source frame to the end of the slit-scanned bitmap
      BitmapCopyColumn(e.Bitmap, tbColumn.Value, bmSlitScan, bmSlitScan.Width - 1);

      // Display both bitmaps
      DisplayImageInPictureBox(this.pbOriginal, e.Bitmap);
      DisplayImageInPictureBox(this.pbSlitScan, bmSlitScan);
    }

    //Display the image in the picture box in the correct thread
    private void DisplayImageInPictureBox(PictureBox pbox, Image image)
    {
      if (pbox.InvokeRequired) // We are in the wrong thread. Call ourselves in the correct thread
      {
        SetPictureBoxImage theDelegate = new SetPictureBoxImage(DisplayImageInPictureBox);
        BeginInvoke(theDelegate, new object[] { pbox, image });
      }
      else // we are in the correct thread, so assign the image
      {
        pbox.Image = image;
      }
    }

    ///
    /// BITMAP Manipulation Routines
    ///

    //For a given bitmap, color all the bits to the provided RGB byte values   
    public static void BitmapColor(Bitmap bm, byte R, byte B, byte G)
    {
      // Lock the bitmap so we can manipulate it
      BitmapData data = bm.LockBits(new Rectangle(0, 0, bm.Width, bm.Height),
               ImageLockMode.ReadWrite,
               PixelFormat.Format24bppRgb);
      unsafe
      {
        int bheight = data.Height; // putting these data variables into local variables makes the loop run faster
        int bwidth = data.Width;
        int bstride = data.Stride;
        byte* imgPtr = (byte*)(data.Scan0)// the first Byte of the bitmap structure

        // Walk through each pixel in the bitmap by row and column
        for (int row = 0; row < bheight; row++)
        {
          for (int col = 0; col < bwidth; col++)
          {
            // Set each byte's color to the RGB colors supplied (each pixel is 3 bytes of color)
            *imgPtr++ = G;
            *imgPtr++ = B;
            *imgPtr++ = R;
          }
          imgPtr += bstride - bwidth * 3// Go to the next row.
        }
      }
      bm.UnlockBits(data);       // Our work is done, so we can unlock the bitmap.
    }

    //For a given bitmap, move the entire bitmap image 1 column to the left
    //To do this efficiently, we use the MoveMemory function
    [System.Runtime.InteropServices.DllImport("kernel32", EntryPoint = "RtlMoveMemory")]
    private unsafe static extern void MoveMemory(void* desination, void* source, int length);
    public static void BitmapShiftLeft(Bitmap bm)
    {
      // Lock the bitmap so we can manipulate it
      BitmapData data = bm.LockBits(new Rectangle(0, 0, bm.Width, bm.Height),
               ImageLockMode.ReadWrite,
               PixelFormat.Format24bppRgb);
      unsafe
      {
        IntPtr imgPtr = data.Scan0; // the first Byte of the bitmap structure
        int bstride = data.Stride;

        //Shift the entire bitmap left one pixel; we do this row by row
        int copy_length = 3 * (bm.Width - 1);
        int height = data.Height - 1;
        for (int row = height; row >= 0; row--)  //for each row
        {
          byte* inpix = ((byte*)((void*)imgPtr)) + row * bstride;
          MoveMemory(inpix, inpix + 3, copy_length);
        }
      }
      bm.UnlockBits(data);
    }

    // Copy the column located at the source bitmap's column number to the column at the destination bitmap's column number
    // At the same time, draw a red line on the bottom quarter of the source column so we know what column is being copied
    public static void BitmapCopyColumn(Bitmap source, int sourceColumnNumber, Bitmap destination, int destinationColumnNumber)
    {
      /* Lock both bitmaps so we can manipulate it */
      BitmapData sourceData = source.LockBits(new Rectangle(0, 0, source.Width, source.Height), ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb);
      BitmapData destData = destination.LockBits(new Rectangle(0, 0, destination.Width, destination.Height), ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb);

      unsafe
      {
        // Create a pointer to the first Byte of each bitmap
        IntPtr inp = sourceData.Scan0;
        IntPtr outp = destData.Scan0;

        // Get the stride for each one; for better performance in the loop, we save them as  local variables
        int sourceStride = sourceData.Stride;
        int destinationStride = destData.Stride;

        // Get the height of the source bitmap; again, for better performance in the loop, we save it as s local variable
        int height = sourceData.Height - 1;
        int quarterHeight = (height / 4) * 3 ;

        // For each row
        for (int row = height; row >= 0; row--)
        {
          // Move to the appropriate columns in each bitmap
          byte* inpix = ((byte*)((void*)inp)) + row * sourceStride + sourceColumnNumber * 3;
          byte* outpix = ((byte*)((void*)outp)) + row * destinationStride + destinationColumnNumber * 3;

          // Copy the 3 bytes that make up this row/column pixel in the source to the destination
          outpix[0] = inpix[0];
          outpix[1] = inpix[1];
          outpix[2] = inpix[2];

          // color the bottom half of the source column red, so the person will know what column has been copied
          if (row > quarterHeight)
          {
            inpix[0] = 0;
            inpix[1] = 0;
            inpix[2] = 255;
          }
        }
      }
      source.UnlockBits(sourceData);
      destination.UnlockBits(destData);
    }
  }
}

Explanation

The critical code are in the several methods found in under the Bitmap Manipulation Routines banner. All these manipulate bits in one or both bitmaps. To understand these, make sure you read the How-To: Understanding Bitmaps. The key differences are:

Retrieved from http://grouplab.cpsc.ucalgary.ca/cookbook/index.php/Toolkits/VideoSlitScanning
Page last modified on July 17, 2007, at 07:54 PM