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:
- Create a Windows Form with a picture box and vertical scroll bar.
- 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).
- 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).
- 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).
- 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.
- 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; 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);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]; data[cnt + 1] = b[1]; data[cnt] = b[0]; 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:
- Read the Bitmapdata of the buffer image and gets its memory pointer.
- Move the read position of the memory stream to the start of the row defined by scroll position.
- Read the number of bytes based on the image height of buffer image.
- 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.)
- 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); this.sbar.Size = new System.Drawing.Size(15, Y); this.picbox.Size = new System.Drawing.Size(X - 15, Y);
if (bufferpic != null)
{
bufferpic.Dispose();
GC.Collect(); bufferpic = new Bitmap(imgwid, Y, PixelFormat.Format24bppRgb); Graphics gtw = Graphics.FromImage(bufferpic);
gtw.Clear(Color.White); 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.