Video Slit Scanning
<< 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:
- how to create a slit-scanned image, where a column from a source frame is appended to the end of a destination bitmap.
- specifically, it shows how to color a bitmap, how to effeciently move bitmap regions, and how to copy pixels from a source bitmap to a destination bitmap.
- note that this is not really a tutorial on EasyImages, but knowing how you can manipulate successive frames at a low level is really useful if you need a novel effect.
In the above picture,
- the image on the right is the result of appending a column from video frame on the left (marked with a red line) to the end of the image.
- the user can move the column being copied via the trackbar
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
|
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.
- 2 PictureBoxes
- Name = pbOriginal, pbSlitScan
- Size = 320,240 ; 640, 240 (the Width can vary, but the height must be the same)
- 1 Label
- Text = the text seen in the figure
- '''1 Trackbar
- Name = tbColumn
- Minimum = 0
- Maximum = 319 (the width of the original video frame - 1)
- Value = 160 (the middle of the video frame)
Step 3. Writing the program
The complete program is listed below. Its long, but it does alot!
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:
- BitmapColor gets a bitmap, walks through it pixel by pixel and byte by byte, and colors each pixel by the R,G or B value to give the bitmap a uniform color. This program sets the background to Black. (RGB = 0, 0, 0)
- BitmapShiftLeft moves the entire bitmap image 1 column to the left. To do this efficiently, we use the MoveMemory function.
- BitmapCopyColumn copies the column located at the source bitmap's column number to the column at the destination bitmap's column number. At the same time, it draws a red line on the bottom quarter of the source column so we know what column is being copied.