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