J Benjamin J Benjamin - 3 months ago 20
C# Question

Parameter is not valid on Image.Save

Can anyone see anything wrong here? I get a "Parameter is not valid" on the 'imageData.Save' line but I suspect the imageData var is wonky earlier than that as I am seeing some argument exceptions inside the imageData object after it gets set. Thanks for any suggestions.

using (Image image = Image.FromFile(Server.MapPath("~/User_Data/asset.png")))
{
Image imageData = ResizeImage(image, 120, 120, image.Width, image.Height);
string base64String = this.ImageToBase64String(imageData, ImageFormat.Png);
<snip>
}

public string ImageToBase64String(Image imageData, ImageFormat format)
{
using (var stream = new MemoryStream())
{
imageData.Save(stream, format);
return Convert.ToBase64String(stream.ToArray());
}
}

public Image ResizeImage(Image image, int canvasWidth, int canvasHeight, int originalWidth, int originalHeight)
{
using (Image image2 = new Bitmap(canvasWidth, canvasHeight))
using (Graphics graphics = Graphics.FromImage(image2))
{
graphics.InterpolationMode = InterpolationMode.HighQualityBicubic;
graphics.SmoothingMode = SmoothingMode.HighQuality;
graphics.PixelOffsetMode = PixelOffsetMode.HighQuality;
graphics.CompositingQuality = CompositingQuality.HighQuality;
double num = ((double)canvasWidth) / ((double)originalWidth);
double num2 = ((double)canvasHeight) / ((double)originalHeight);
double num3 = (num < num2) ? num : num2;
int height = Convert.ToInt32((double)(originalHeight * num3));
int width = Convert.ToInt32((double)(originalWidth * num3));
int x = Convert.ToInt32((double)((canvasWidth - (originalWidth * num3)) / 2.0));
int y = Convert.ToInt32((double)((canvasHeight - (originalHeight * num3)) / 2.0));
graphics.Clear(Color.White);
graphics.DrawImage(image, x, y, width, height);
ImageCodecInfo.GetImageEncoders();
using (var parameters = new EncoderParameters(1))
{
parameters.Param[0] = new EncoderParameter(Encoder.Quality, 100L);
}
return image2;
}
}


Here is a screenshot of my debugger...this is stopped right after imageData gets established and right before the ImageToBase64String is called. Those exceptions within imageData do not crash the app but surely that doesn't look right?
enter image description here

Answer

The short answer is that you are disposing of image2 via the using block, then trying to use it outside the method. If you examine ImageData you will see all sorts of bad data and exceptions:

enter image description here

Dont dispose of it if you are returning it; the calling code will be responsible for disposing if it when it is done with it:

//using (Image image2 = new Bitmap(canvasWidth, canvasHeight))
Image image2 = new Bitmap(canvasWidth, canvasHeight);
using (Graphics graphics = Graphics.FromImage(image2))
{
    graphics.InterpolationMode = InterpolationMode.HighQualityBicubic;
    ...
}
return image2;

There is a simpler way to scale an image and that codec/quality param code is doing nothing.

public Image ResizeImage2(Image image, int canvasWidth, int canvasHeight)
{
    // org size is already available
    Size orginalSize = image.Size;
    Size newSize = Size.Empty;
    float scale;

    if (canvasWidth != 0)
    {
        scale = (float)orginalSize.Height / orginalSize.Width;
        newSize = new Size(canvasWidth, Convert.ToInt32(scale * canvasWidth));
    }
    else if (canvasHeight != 0)
    {
        scale = (float)orginalSize.Width / orginalSize.Height;
        newSize = new Size(Convert.ToInt32(scale * canvasHeight), canvasHeight);
    }     

    //using (Image image2 = new Bitmap(canvasWidth, canvasHeight))
    Image image2 = new Bitmap(newSize.Width, newSize.Height);
    using (Graphics graphics = Graphics.FromImage(image2))
    {
        graphics.InterpolationMode = InterpolationMode.HighQualityBicubic;
        graphics.SmoothingMode = SmoothingMode.HighQuality;
        graphics.PixelOffsetMode = PixelOffsetMode.HighQuality;
        graphics.CompositingQuality = CompositingQuality.HighQuality;

        graphics.Clear(Color.White);
        graphics.DrawImage(image, 0, 0, newSize.Width, newSize.Height);

        return image2;
    }
}

This scales the image passed based on a target Width or Height with a few less variables. The codec stuff was removed because you would use that with the Save method:

using (Image img = Image.FromFile(@"C:\Temp\BigHead.jpg"))
{
    // resize img to the height of a PicBox
    Image imageData = ResizeImage2(img, 0, pb2.Height);
    // winforms show img
    pb2.Image = imageData;

    // get codec
    ImageCodecInfo codec = ImageCodecInfo.GetImageEncoders()
        .FirstOrDefault(z => z.MimeType == "image/jpeg");
    using (var qparam = new EncoderParameter(System.Drawing.Imaging.Encoder.Quality, 100L))
    {
        EncoderParameters encParams = new EncoderParameters(1);
        encParams.Param[0] = qparam;
        imageData.Save(@"C:\Temp\BigHead_thumb.jpg", codec, encParams);

        // test: no encoder
        imageData.Save(@"C:\Temp\BigHead_thumb2.jpg");
    }
    // ToDo: dispose of returned image
}

The calling code can look at the image and decide whether to scale to Height or to Width. Then the Image.Save method uses the Encoder if you want to specify a non default quality level.

The resulting file sizes reflect the impact of the encoder: 72k for the default method, 194k using the Quality Param.