Search code examples
c#asp.netsystem.drawingimageresizer

ImageResizer/System.Drawing and CMYK JPEGs


I'm doing some simple image resizing and rotation in an ASP.NET MVC 4 project, using the ImageResizer library. The problem is that when I use it to process 32-bpp CMYK JPEG files, it fails with an ArgumentException, but only on my (Windows Server 2008 R2) server - it works fine on my (Windows Vista) laptop.

The JPEG files in question aren't too large (700x500) or otherwise non-standard - all web browsers and Paint can open them just fine, even on the server in question itself. The JPEGs have been generated using ImageMagick, and everything works fine if I ask ImageMagick to use an RGB colorspace (-colorspace sRGB.)

The weird thing is, just resizing the image works fine in all cases, but it fails if I try to resize and rotate the image on the server.

It looks like some lower-level Win32 or GDI+ function call is what's failing here - here's the relevant part of the stack trace:

[ArgumentException: Parameter is not valid.]
   System.Drawing.Graphics.CheckErrorStatus(Int32 status) +1621285
   System.Drawing.Graphics.DrawImage(Image image, PointF[] destPoints, RectangleF srcRect, GraphicsUnit srcUnit, ImageAttributes imageAttr, DrawImageAbort callback, Int32 callbackData) +727
   System.Drawing.Graphics.DrawImage(Image image, PointF[] destPoints, RectangleF srcRect, GraphicsUnit srcUnit, ImageAttributes imageAttr) +73
   ImageResizer.ImageBuilder.RenderImage(ImageState s) +763
   ImageResizer.ImageBuilder.Render(ImageState s) +174
   ImageResizer.ImageBuilder.Process(ImageState s) +105
   ImageResizer.ImageBuilder.buildToBitmap(Bitmap source, ResizeSettings settings, Boolean transparencySupported) +276
   ImageResizer.ImageBuilder.buildToStream(Bitmap source, Stream dest, ResizeSettings settings) +149
   ImageResizer.ImageBuilder.BuildJob(ImageJob job) +940
   ImageResizer.ImageBuilder.Build(ImageJob job) +223

Any ideas?


Solution

  • After some more research and testing, I've found that the problem occurs only if the Bitmap.RotateFlip method has been called on the source bitmap, before it is drawn onto a target bitmap for cropping and resizing.

    I ended up having to re-write the code using plain old System.Drawing myself, because there is no way to change the behavior of the ImageResizer library. Specifically, I wrote my own rotation function, which uses a transformation matrix to do the same thing.

        private Bitmap RotateImage(Bitmap source, float angle)
        {
            if (angle == 0) return (Bitmap)source.Clone();
    
            Bitmap target;
            using (GraphicsPath path = new GraphicsPath())
            using (Matrix matrix = new Matrix())
            {
                path.AddRectangle(new RectangleF(0.0f, 0.0f, source.Width, source.Height));
                matrix.Rotate(angle);
                RectangleF rect = path.GetBounds(matrix);
    
                target = new Bitmap((int)Math.Round(rect.Width), (int)Math.Round(rect.Height));
                target.SetResolution(source.HorizontalResolution, source.VerticalResolution);
    
                using (Graphics g = Graphics.FromImage(target))
                {
                    g.TranslateTransform(-rect.X, -rect.Y);
                    g.RotateTransform(angle);
                    g.InterpolationMode = InterpolationMode.HighQualityBicubic;
                    g.DrawImageUnscaled(source, 0, 0);
                }
            }
            return target;
        }
    

    I would have wanted to set the PixelFormat of the new Bitmap to the one from the source - but doing so throws weird GDI+ "Out of memory" errors on encountering the CMYK JPEG files in question. This makes me wonder if this odd behavior is linked to this GDI+ bug/quirk that returns different ImageFlags and PixelFormat values on Windows 7/Server 2008 R2 and Windows Vista/Server 2008.