tags:

views:

474

answers:

4

I have this method for shrinking down an image for a website that I'm working on:

static byte[] createSmallerImage(
   BlogPhoto blogPhoto, 
   int newMaxWidth, 
   int newMaxHeight)
{
  Image img;
  using (MemoryStream originalImage = 
           new MemoryStream(blogPhoto.BlogPhotoImage))
  {
    img = Image.FromStream(originalImage);
  }

  int newWidth;
  int newHeight;
  byte[] arr;

  if (img.Width > img.Height)
  {
    if (img.Width <= newMaxWidth)
    {

      using (MemoryStream thumbStr = new MemoryStream())
      {
        img.Save(thumbStr, ImageFormat.Jpeg);
        img.Dispose();
        arr = thumbStr.ToArray();
      }
      return arr;
    }

    newWidth = newMaxWidth;
    newHeight = 
       (int)(((float)newWidth / (float)img.Width) * (float)img.Height);
  }
  else
  {
    if (img.Height <= newMaxHeight)
    {

      using (MemoryStream thumbStr = new MemoryStream())
      {
        img.Save(thumbStr, ImageFormat.Jpeg);
        img.Dispose();
        arr = thumbStr.ToArray();
      }
      return arr;
    }

    newHeight = newMaxHeight;
    newWidth = 
      (int)(((float)newHeight / (float)img.Height) * (float)img.Width);
  }

  Image thumb = new Bitmap(newWidth, newHeight);

  Graphics g = Graphics.FromImage(thumb);
  g.InterpolationMode = InterpolationMode.HighQualityBicubic;
  g.SmoothingMode = SmoothingMode.HighQuality;
  g.PixelOffsetMode = PixelOffsetMode.HighQuality;
  g.CompositingQuality = CompositingQuality.HighQuality;

  g.DrawImage(img, 0f, 0f, (float)newWidth, (float)newHeight);


  using (MemoryStream thumbStr = new MemoryStream())
  {
    thumb.Save(thumbStr, ImageFormat.Jpeg);
    arr = thumbStr.ToArray();
  }

  g.Dispose();
  img.Dispose();

  return arr;
}

Most of the time it works great but sometimes it gives me this exception:A generic error occurred in GDI+. Error Code -2147467259. Source: "System.Drawing". This occurs on the Image.Save(... I tried to make this code as defensive as possible but am still not getting whats causing this. If someone knows the answer that'd be great, critiques are welcome too.

A: 

I don't know what can be happening, but maybe with less MemoryStreams problem go away:

using (Image original = Image.FromStream(new MemoryStream(blogPhoto)))
{
    using (MemoryStream thumbData = new MemoryStream())
    {
        int newWidth;
        int newHeight;
        if ((original.Width <= newMaxWidth) || 
            (original.Height <= newMaxHeight))
        {
            original.Save(thumbData, ImageFormat.Jpeg);
            return thumbData.ToArray();
        }

        if (original.Width > original.Height)
        {
            newWidth = newMaxWidth;
            newHeight = (int)(((float)newWidth / 
                (float)original.Width) * (float)original.Height);
        }
        else
        {
            newHeight = newMaxHeight;
            newWidth = (int)(((float)newHeight / 
                (float)original.Height) * (float)original.Width);
        }

        //original.GetThumbnailImage(newWidth, newHeight, null, IntPtr.Zero)
        //    .Save(thumbData, ImageFormat.Jpeg);
        //return thumbData.ToArray();

        using (Image thumb = new Bitmap(newWidth, newHeight))
        {
            Graphics g = Graphics.FromImage(thumb);
            g.InterpolationMode = InterpolationMode.HighQualityBicubic;
            g.SmoothingMode = SmoothingMode.HighQuality;
            g.PixelOffsetMode = PixelOffsetMode.HighQuality;
            g.CompositingQuality = CompositingQuality.HighQuality;
            g.DrawImage(original, 0f, 0f, (float)newWidth, (float)newHeight);
            thumb.Save(thumbData, ImageFormat.Jpeg);
        }
    }
}
Rubens Farias
I'm confused how this would work since you are calling this: img.Save(imageSource, ImageFormat.Jpeg);and thumb.Save(imageSource, ImageFormat.Jpeg);then returning the imageSource.ToArray(); wouldn't this cause two images in the memorystream?
maxfridbe
sorry, my mistake; can you rely on GetThumbnailImage to get your job done? I just updated my answer to use this method.
Rubens Farias
I suppose I could (and if this error persists I may try) but the spec says: The GetThumbnailImage method works well when the requested thumbnail image has a size of about 120 x 120 pixels. If you request a large thumbnail image (for example, 300 x 300) from an Image that has an embedded thumbnail, there could be a noticeable loss of quality in the thumbnail image. It might be better to scale the main image (instead of scaling the embedded thumbnail) by calling the DrawImage method.Which may not work for me creating larger thumbnails.
maxfridbe
+1  A: 

One thing to look at is blogPhoto and the underlying data going away. Where does it get loaded from? Is it loaded from a stream? Is that stream closed before createSmallerImage? Images loaded from streams where the stream is closed work 95% of the time and only occaisonally throw a generic GDI+ error.

Kris Erickson
BlogPhotoImage is a defer loading byte[] that gets loaded from linq-to-sql.
maxfridbe
Are you loading it directly or from a MemoryStream? Is there any chance that it is going out of scope before calling createSmallerImage?
Kris Erickson
A: 

Hi,

I personally use this code, with no streams (I don't care about perfs, though) for resizing a picture:

public Image resizeImage(int newWidth, int newHeight, string stPhotoPath)
 {
     Image imgPhoto = Image.FromFile(stPhotoPath); 

     int sourceWidth = imgPhoto.Width;
     int sourceHeight = imgPhoto.Height;

     //Consider vertical pics
    if (sourceWidth < sourceHeight)
    {
        int buff = newWidth;

        newWidth = newHeight;
        newHeight = buff;
    }

    int sourceX = 0, sourceY = 0, destX = 0, destY = 0;
    float nPercent = 0, nPercentW = 0, nPercentH = 0;

    nPercentW = ((float)newWidth / (float)sourceWidth);
    nPercentH = ((float)newHeight / (float)sourceHeight);
    if (nPercentH < nPercentW)
    {
        nPercent = nPercentH;
        destX = System.Convert.ToInt16((newWidth -
        (sourceWidth * nPercent)) / 2);
    }
    else
    {
        nPercent = nPercentW;
        destY = System.Convert.ToInt16((newHeight -
        (sourceHeight * nPercent)) / 2);
    }

    int destWidth = (int)(sourceWidth * nPercent);
    int destHeight = (int)(sourceHeight * nPercent);


    Bitmap bmPhoto = new Bitmap(newWidth, newHeight,
        PixelFormat.Format24bppRgb);

    bmPhoto.SetResolution(imgPhoto.HorizontalResolution,
       imgPhoto.VerticalResolution);

    Graphics grPhoto = Graphics.FromImage(bmPhoto);
    grPhoto.Clear(Color.Black);
    grPhoto.InterpolationMode =
     InterpolationMode.HighQualityBicubic;

    grPhoto.DrawImage(imgPhoto,
        new Rectangle(destX, destY, destWidth, destHeight),
        new Rectangle(sourceX, sourceY, sourceWidth, sourceHeight),
        GraphicsUnit.Pixel);

    grPhoto.Dispose();
    return bmPhoto;
}

Hope this helps.

Vinzz
+2  A: 

Look at the documentation for Image.FromStream()

http://msdn.microsoft.com/en-us/library/93z9ee4x.aspx

You need to keep the stream open for the lifetime of the Image. Keep the first MemoryStream open longer, and it should work.

Bryan
It's **very** odd that the underlying GDI+ image class doesn't require the stream to be kept open. (http://msdn.microsoft.com/en-us/library/ms535371.aspx) But hunting through reflector i eventually see why: they must wrap the .NET Stream into an IStream suitable for GDI+. Interfaces are reference counted, while .NET Stream objects are not. Since they only wrap the stream object in a new interface, rather than copying the actual data, you have to keep the source object around.
Ian Boyd