Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Fast Image Scrolling in C#

0.00/5 (No votes)
22 Apr 2012 1  
Fast Image Scrolling in C#

Introduction

This is a sample code to see one of the options for fast image scrolling in C#. The standard functions provided by C# libraries are slow and do not provide smooth image scrolling.

Background

I was making a code which would display images in a scrollable box but found that the Image.Copy (or other methods) is slow and does not allow smooth scrolling of the image. It, however, turned out that it wasn't as easy as I initially thought it to be. Even though there may be multiple options for scolling images, in a nut shell, all demand the image to be unpacked into its R,G,B components, making a data array of these RGB components and then transferring blocks of byte data when ever scroll is performed. We will see how can this be achieved in the code below.

Using the code

Before we see the code, we need to know that images can be stored in many formats and with different compression schemes. They may be 1 bit to 32 bit in color resolution. In this code, I have tried a general approach to handling any type of input image using the default C# functions.

Here is a quick understanding of what the code does:

  1. Create a Windows Form with a picture box and vertical scroll bar.
  2. Read the Image you wish to see in the picture box. Create an empty image of similar size but of 24 bit pixel resolution. Plot your input image unto this new image. (Unpacked Images can be huge and your PC may not have enough memory to hold it. You will have to use disk space in those cases. Don't worry, its fast enough in both cases).
  3. Arrange the memory occupied by this new image as bytestream. This byte stream can be in memory or also in hard disk (for cases where C# can't read images by itself and you have to unpack it in a file/memory byte stream manually).
  4. Create a buffer image which you will plot in the picture box. This image will hold the section of the input image as decided by scroll position. (Scroll position is linked with input image height). Here we run into second important step. Input image width will not be similar to picture box width. So the buffer image width should be the width of Input image. Height of buffer image should be same as picture box height. (I am not playing around with image DPI. This will complicate the understanding of this subject).
  5. Now with every scroll, we will just transfer the section of unpacked image into the memory position where the buffer image is stored. This way, we will copy the image section by just moving data bytes from one point to another. This is the fastest way to move images.
  6. Display the buffer image into your picture box.

Let's see this more detail:

Create a Windows Form.

Program starts with trying to read the image into memory as a Bitmap. Please note here that C# is limited in reading various image formats. For those that we can't read in this fashion, we need to read those images manually and unpack it into a byte stream (byte steam is explained below).

Bitmap timp;  //temporary image
try 
{timp = (Bitmap)Image.FromFile(file); }
catch 
{MessageBox.Show("Unknown Image format type."); return; }
imgwid = timp.Width; imghei = timp.Height;

if ((imgwid * 3) % 4 == 0) stride = (int)(3 * imgwid);
else stride = (int)((imgwid * 3) + 4 - (imgwid * 3) % 4);

Here, assuming its a simple image which C# can read, we read the input image into a Bitmap. Imgwid stores its width, Imghei stores its height in pixels and its Stride (bytes in memory for every row of image). (For complex images, we need to unpack them ourselves). Stride here is not the stride of the input image but the stride of the image where we will finally copy the input image.

Now we will make a byte array into memory that will have the required space for storing this data. This byte is formatted as a Bitmap image. (We are doing it because we will copy the input image into new image.) We also make a memory stream of this data block. Here again, if memory is not available, we need to make a filestream of this unpacked data. (I am not showing that here).

byte[] bigbyte = new byte[stride * imghei]; 
GCHandle handle = GCHandle.Alloc(bigbyte, GCHandleType.Pinned);
IntPtr pointer = Marshal.UnsafeAddrOfPinnedArrayElement(bigbyte, 0);
Bitmap tbmp= new Bitmap(imgwid,imghei,stride, PixelFormat.Format24bppRgb, pointer);
MemoryStream bms = new MemoryStream(bigbyte);

Basically, what we have done is created a new image with known resolution of 24 bit and of same size. We need to plot the input image unto this new image. This we, we will transform input image - which could be in ANY format - into a simple 24 bit resolution  without loss of data.  

Graphics gtw = Graphics.FromImage(tbmp);
gtw.DrawImage(timp, new Rectangle(0, 0, (int)imgwid, (int)imghei));

Now, we will create our buffer image and make it all white to start with. This image is 24 bit (RGB format):

Bitmap  bufferpic = new Bitmap(imgwid, Y , PixelFormat.Format24bppRgb);//Y is ClientSize.Height
Graphics gtw = Graphics.FromImage(bufferpic);
gtw.Clear(Color.White); 

We now initialize the scroll bar. Our scroll bar is with the name sbar. Its maximum value is not the image height but the image height minus the view area on the screen.

this.sbar.Value = 0;
this.sbar.Maximum = Math.Max(imghei - this.ClientSize.Height , 0);

The scroll bar move event is just storing its position in a variable (called pos) and calling for a redraw of the picturebox. We will do the image transfer in the Windows Form Paint event using the position variable.

private void sbar_Scroll(object sender, System.Windows.Forms.ScrollEventArgs e)
{
  pos = sbar.Value;
  Invalidate();
}

Now comes the last part of transferring the image section. This is done in the Windows Form Paint event.

private void Form_Paint(object sender, System.Windows.Forms.PaintEventArgs e)
{
    rect1 = new Rectangle(0, 0, (int)bufferpic.Width, (int)bufferpic.Height);
    bitmapdata = bufferpic.LockBits(rect1, ImageLockMode.ReadWrite, bufferpic.PixelFormat);
    long cnt = 0, xpos = 0;
    unsafe
    {
        ptr = bitmapdata.Scan0;
        byte* data = (byte*)ptr;
        long A = pos, B = stride;
        bms.Position = A * B;
        xpos = 0;
        for (int j = 0; j < (int)(imgwid * (Math.Min(Y , imghei))); j++)
        {
            bms.Read(b, 0, 3);
            data[cnt + 2] = b[2];  //B component
            data[cnt + 1] = b[1];  //G component
            data[cnt] = b[0];  //R component
            cnt += 3; xpos++;
            if (xpos == imgwid) { if ((imgwid * 3) % 4 != 0) { cnt += 
                  (4 - (imgwid * 3) % 4); bms.Read(b, 0, (int)(4 - (imgwid * 3) % 4)); } xpos = 0; }
        }
    }
    bufferpic.UnlockBits(bitmapdata);
    this.picbox.Image = bufferpic;
    this.picbox.Update();
} 

This function does many things. Let's see how it works:

  1. Read the Bitmapdata of the buffer image and gets its memory pointer.
  2. Move the read position of the memory stream to the start of the row defined by scroll position.
  3. Read the number of bytes based on the image height of buffer image.
  4. The if loop in the for loop takes care of the stride at the end of row end. Both images may have some extra bytes at the end of the row data and we need to increase our write pointer accordingly. (We can simply read a big chunk of memory and just paste it at target destination. The code above is a copy of  one of my project and is a little complex(as I am also dealing with disk streams storing image data). I am sure we can replace the complete for loop by few functions.) 
  5. Unlock the buffer image and use this as the image in the picture box. Update the picture box.

This way we have moved the input image into our buffer image for plotting.

Finally, when a Windows Form Resize Event is fired, we need to adjust the picture size and the buffer image size to the new ClientSize. This is done as below.

private void ResizeForm_Resize(object sender, System.EventArgs e)
{
    X = this.ClientSize.Width;
    Y = this.ClientSize.Height;
       
    this.sbar.Location = new System.Drawing.Point(X - 15, 0); //15 is size of scrollbar
    this.sbar.Size = new System.Drawing.Size(15, Y); //adjust scrollbar position and size
    this.picbox.Size = new System.Drawing.Size(X - 15, Y); //adjust picturebox size
 
    if (bufferpic != null) 
    {
        bufferpic.Dispose();
        GC.Collect(); // dispose the buffer image and free the memory. 
        bufferpic = new Bitmap(imgwid, Y, PixelFormat.Format24bppRgb); //get new buffer image
        Graphics gtw = Graphics.FromImage(bufferpic);
        gtw.Clear(Color.White); //paint the image with white color
        gtw.Dispose();
    }
     Invalidate();
}

Points of Interest

Even though this is a simple way of image scrolling, there are variations in unpacking the input image. Windows is very limited in reading various image types and thus we need to unpack the image into a stream of R, G, B component manually.

In a nutshell, this is the way fast scrolling is achieved. We may do some simple modifications like paletizing the unpacked data to reduce its size in memory.

History

There is no history of this code. I would like to thank those who (unknowingly) helped me understand the concepts needed for managing memory in C# from images.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here