I have a method that I use to get a thumbnail as a Byte[]
from an image. The method receives the path to the image.
The class works pretty well, until you reach a size threshold, after which performance drops to pitiful low levels. No, I haven't nailed down the threshold...mainly because after some research, it seems like I'm using a methodology that is inefficient and not very scalable.
My scenario seems to be confirmed in this SO thread.
That question lays out exactly what I am experiencing. The reason it isn't a solution is because the answer talks of using other graphics APIs and Paint
overrides, which obviously doesn't apply here. I tried the miscellaneous things, like setting graphics parameters, but that made little difference.
One example of a large image I am dealing with is 3872 x 2592
and about 3.5Mb
in size. Some a lot more, and many that size or smaller.
My searching has not yielded much. In fact, it seems that I can only find advice that includes the use of System.Drawing.Graphics.DrawImage()
. In one exception, it was suggested to include assemblies to attempt use of PresentationFramework
. This is a WinForms
app, so that seems a bit much just to grab a thumbnail image.
Another suggestion I came across had to do with extracting Exif
information from the file (if I recall) and attempting to grab just that data rather than the entire image. I'm not opposed, but I have yet to find a complete enough example of how that is carried out.
I wonder about P/Invoke options. Better performance than what GDI+ is (apparently) capable of delivering. But, by all means, if there's an optimization I am missing in this code, please point it out.
Here is my current method:
public static Byte[] GetImageThumbnailAsBytes(String FilePath)
{
if (File.Exists(FilePath))
{
Byte[] ba = File.ReadAllBytes(FilePath);
if (ba != null)
{
using (MemoryStream ms = new MemoryStream(ba, false))
{
Int32 thWidth = _MaxThumbWidth;
Int32 thHeight = _MaxThumbHeight;
Image i = Image.FromStream(ms, true, false);
ImageFormat imf = i.RawFormat;
Int32 w = i.Width;
Int32 h = i.Height;
Int32 th = thWidth;
Int32 tw = thWidth;
if (h > w)
{
Double ratio = (Double)w / (Double)h;
th = thHeight < h ? thHeight : h;
tw = thWidth < w ? (Int32)(ratio * thWidth) : w;
}
else
{
Double ratio = (Double)h / (Double)w;
th = thHeight < h ? (Int32)(ratio * thHeight) : h;
tw = thWidth < w ? thWidth : w;
}
Bitmap target = new Bitmap(tw, th);
Graphics g = Graphics.FromImage(target);
g.SmoothingMode = SmoothingMode.HighQuality;
g.CompositingQuality = CompositingQuality.HighQuality;
g.InterpolationMode = InterpolationMode.Bilinear; //NearestNeighbor
g.CompositingMode = CompositingMode.SourceCopy;
Rectangle rect = new Rectangle(0, 0, tw, th);
g.DrawImage(i, rect, 0, 0, w, h, GraphicsUnit.Pixel);
using (MemoryStream ms2 = new MemoryStream())
{
target.Save(ms2, imf);
target.Dispose();
i.Dispose();
return ms2.ToArray();
}
}
}
}
return new Byte[] { };
}
P.S. I got here in the first place by using the Visual Studio 2012 profiler, which told me that DrawImage()
is responsible for 97.7% of the CPU load while loading images (I did a pause/start to isolate the loading code).