Friday, December 11, 2009

Simple and advanced methods for creating thumbnail images in .net using c#


When I stumbled upon GetThumbnailImage method available in the Image class for the first time, I immediately went ahead with the thought that now I have a native and efficient way to create thumbnails. I used a code similar to what shown below.

Option 1: using GetThumbnailImage

private Image getThumbNailUsingGetThumbnailImage(string fileName)
{
Image img = Image.FromFile(fileName);
return img.GetThumbnailImage(300, 300, null, IntPtr.Zero);
}

The result was quick. But, soon it was found that this will not work as efficiently as I expected. The biggest problem was that when the image got edited by any of the image editing tools out there, the thumbnail creation went wrong. It was because of the reason that GetThumbnailImage was depending up on the image metadata as it was set when the image got created. If the image thumbnail property is not getting modified by your image editing tool, GetThumbnailImage will fail to get the right thumbnail. Below is a similar method that retrieves the thumbnail from the PropertyItem collection of an image.

Option 2: Using PropertyItem

private Image createThumbFromProperty(string file)
{
Image image = new Bitmap(file);
Image Thumb = null;
PropertyItem[] propItems = image.PropertyItems;
foreach (PropertyItem propItem in propItems)
{
if (propItem.Id == 0x501B)
{
byte[] imageBytes = propItem.Value;
MemoryStream stream = new MemoryStream(imageBytes.Length);
stream.Write(imageBytes, 0, imageBytes.Length);
Thumb = Image.FromStream(stream);
break;
}
}
return Thumb;
}

Thus started a lookout for a performance-improved simple way to achieve this which resulted in the following methods. Basically, these methods used GDI+. The logic seems pretty simple, which is to create another small in-memory based on the size of the thumbnail needed. It breaks down to this.

1. Load the original image
2. Get the proportional size of the image based on the original Image size and target thumbnail size
3. Redraw the image to the new canvas

A sample code for this is shown below

Option 3: Using GDI+ for simple thumbnail
private Image getThumbNail(string fileName)
{
FileStream fs = new FileStream(fileName, FileMode.Open);
Image im = Image.FromStream(fs);
Size szMax = new Size(300, 300);
Size sz = getProportionalSize(szMax, im.Size);
// superior image quality
Bitmap bmpResized = new Bitmap(sz.Width, sz.Height);
using (Graphics g = Graphics.FromImage(bmpResized))
{
g.InterpolationMode = InterpolationMode.HighQualityBicubic;
g.DrawImage(
im,
new Rectangle(Point.Empty, sz),
new Rectangle(Point.Empty, im.Size),
GraphicsUnit.Pixel);
}
im.Dispose(); im = null;
fs.Close(); fs.Dispose(); fs = null;
return bmpResized;
}

private Size getProportionalSize(Size szMax, Size szReal)
{
int nWidth;
int nHeight;
double sMaxRatio;
double sRealRatio;

if (szMax.Width < 1 || szMax.Height < 1 || szReal.Width < 1 || szReal.Height < 1)
return Size.Empty;

sMaxRatio = (double)szMax.Width / (double)szMax.Height;
sRealRatio = (double)szReal.Width / (double)szReal.Height;

if (sMaxRatio < sRealRatio)
{
nWidth = Math.Min(szMax.Width, szReal.Width);
nHeight = (int)Math.Round(nWidth / sRealRatio);
}
else
{
nHeight = Math.Min(szMax.Height, szReal.Height);
nWidth = (int)Math.Round(nHeight * sRealRatio);
}

return new Size(nWidth, nHeight);
}

Now that we know how to draw things on the Bitmap (canvas), we can play around and create framed thumbnails as shown below. The code below will create a frame effect around the thumbnail.

Option 4: Using GDI+ for framed thumbnail
private Image getThumbNailWithFrame(string fileName)
{
FileStream fs = new FileStream(fileName, FileMode.Open);
Image im = Image.FromStream(fs);
Size szMax = new Size(300, 300);
Size sz = getProportionalSize(szMax, im.Size);
// superior image quality
Bitmap bmpResized = new Bitmap(sz.Width, sz.Height);
using (Graphics g = Graphics.FromImage(bmpResized))
{
g.InterpolationMode = InterpolationMode.HighQualityBicubic;
g.FillRectangle(Brushes.White, 0, 0, sz.Width, sz.Height);
int FrameWidth = 5;//decides the frame border width
g.DrawRectangle(new Pen(Color.Silver, FrameWidth - 2), 0, 0, sz.Width - 1, sz.Height - 1);
FrameWidth += 5;//decide the frame width
g.DrawImage(im, new Rectangle(FrameWidth, FrameWidth, sz.Width - FrameWidth * 2, sz.Height - FrameWidth * 2), new Rectangle(Point.Empty, im.Size), GraphicsUnit.Pixel);
}
im.Dispose(); im = null;
fs.Close(); fs.Dispose(); fs = null;
return bmpResized;
}

We can even extend this concept to create shades around the generated thumbnail. The below code demonstrates that on a white background.

Option 5: Using GDI+ for shaded thumbnail

private Image createThumbnailUsingGDI(ref Image imgPhoto, int destWidth, int destHeight)
{
int sourceX = 0;
int sourceY = 0;

int destX = 0;
int destY = 0;
int sourceWidth = imgPhoto.Width;
int sourceHeight = imgPhoto.Height;

Bitmap b = new Bitmap(destWidth, destHeight);

Graphics grPhoto = Graphics.FromImage(b);

grPhoto.FillRectangle(Brushes.DarkGray, new Rectangle(destX, destY, destWidth, destHeight));
grPhoto.DrawLine(new Pen(Brushes.LightGray), new Point(0, destHeight - 1), new Point(destWidth, destHeight - 1));
grPhoto.DrawLine(new Pen(Brushes.LightGray), new Point(destWidth - 1, 0), new Point(destWidth - 1, destHeight));
//shade right
grPhoto.FillRectangle(Brushes.White, new Rectangle(destWidth - 3, 0, 7, 2));
grPhoto.FillRectangle(Brushes.White, new Rectangle(destWidth - 2, 0, 7, 4));
grPhoto.FillRectangle(Brushes.White, new Rectangle(destWidth - 1, 0, 7, 6));

//shade botton
grPhoto.FillRectangle(Brushes.White, new Rectangle(0, destHeight - 3, 2, 7));
grPhoto.FillRectangle(Brushes.White, new Rectangle(0, destHeight - 2, 4, 7));
grPhoto.FillRectangle(Brushes.White, new Rectangle(0, destHeight - 1, 6, 7));
grPhoto.DrawImage(imgPhoto, new Rectangle(destX + 2, destY + 2, destWidth - 7, destHeight - 7), new Rectangle(sourceX, sourceY, sourceWidth, sourceHeight), GraphicsUnit.Pixel);

grPhoto.Dispose();
return b;

}

These methods do provides some flexibility and they are efficient enough for small scale applications. But, if we're talking about large number of images where memory and performance are critical factors, these may not suffice. We can fine tune the above methods by any/all of the following approaches and of course, lot others.

1. Having proper "using" keywords
2. making sure that the objects are destroyed properly
3. Having the thumbnail creating method loaded as static so that is always ready for you.
4. Get yourself equipped with knowledge on Graphics class which has numerous drawing capabilities and various brushes.

Once you have your thumbnail generation code ready, use it with extensive multi-threading concepts to give the user a pleasant feeling. If you're looking for any basic threading knowledge, look at my other article here.

I know, there may be few who might be hoping that we could've had something simple like GetThumbnailImage which didn't had any pitfalls. Well, there may be. System.Windows.Media.Imaging might be the place to look at for these capabilities.

Please note that this article is for learning purposes. If you are here for quick code to be used for production purposes, please look elsewhere.

Reference:
http://danbystrom.se

No comments:

Post a Comment