1

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).

Community
  • 1
  • 1
DonBoitnott
  • 10,787
  • 6
  • 49
  • 68
  • 1
    Can you not use `Image.GetThumbnailImage`? – FlyingStreudel Jul 01 '13 at 18:50
  • @FlyingStreudel That was the predecessor to this method, and a _far worse_ performer. In both quality and speed. – DonBoitnott Jul 01 '13 at 18:51
  • gdi+ is slow than others . give a try to gdi – qwr Jul 01 '13 at 18:54
  • 1
    If you're willing to give up some quality for performance, I'd suggest you write your own downscaling algorithm. I've myself had to do that when dealing with massive images in machine vision applications (>80 MPx). In my case I simply took every 16th pixel in both directions to get a thumbnail, or rather something suitable to display onscreen. It is a bit trickier if you need a specific size though. – Chris Jul 01 '13 at 18:58
  • 1
    If you're unhappy with the built-in performance you should consider either implementing your own scaling algorithm inside an unsafe block or using DirectX to do calculations on the GPU instead of the CPU. – FlyingStreudel Jul 01 '13 at 19:00
  • @Chris Some quality, sure, but it still has to be pretty good. But the size issue...there are pre-defined thumb sizes I have to hit, all are square: 48, 64, 96, 128, 256. – DonBoitnott Jul 01 '13 at 19:00
  • @qwr Isn't GDI in C and C++? So that's the P/Invoke I'm talking about right? Got any links to code? I don't even know what GDI methods there are to being searching for them. – DonBoitnott Jul 01 '13 at 19:03
  • Ok, then you'd have to find the largest divider that gives you a size just at or below each of those sizes, and then add some borders to fill in the remaining size. Not sure if that would look odd or not. If this doesn't work for you I'd agree with FlyingStreudel, let the GPU handle it! – Chris Jul 01 '13 at 19:10
  • @DonBoitnott yes p/invoke .BitBlt,StretchBlt,GetDibits,SetDibits. Also note that it can be used on win32. btw there is managed directx SharpDx ,but think no need this for such task – qwr Jul 01 '13 at 19:12

0 Answers0