Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Graphics.DrawImage unexpectedly resizing image

I've got some code that takes a png that is a greyscale image with transparency and attempts to create a new image that has a given background colour (looked up from a database) and overlays the original image on it to create an image of the required colour with highlights and shading. The code is run in an ASP.NET context but I don't think that is relevant.

The code works fine on my local computer but when it is deployed to our UAT server it gives unexpected results. On the UAT it creates an image of the right size but the shaded/highlighted area seems to have been shrunk in each dimension by 20%. So the main image that I am looking at is initially 5x29 and the output image is 5x29 but the shaded area is 4x23.2 (the 24th row is very slightly different but mainly the background colour so I assumed it is doing some interpolation with the resize).

My code that is failing is as follows:

private byte[] GetImageData(CacheKey key)
{
    byte[] imageData;
    using (Image image = Image.FromFile(key.FilePath))
    using (Bitmap newImage = new Bitmap(image.Width, image.Height))
    {
        using (Graphics graphic = Graphics.FromImage(newImage))
        {
            using (SolidBrush brush = new SolidBrush(ColorTranslator.FromHtml(key.BackgroundColour)))
            {
                graphic.FillRectangle(brush, 0, 0, image.Width, image.Height);
            }
            graphic.DrawImage(image, 0, 0);

            /*
            The following lines see if there is a transparency mask to create final
             transparency. It does this using GetPixel and SetPixel and just modifying
             the alpha of newImage with the alpha of mask. I don't think this should make a difference but code below anyway.
            */

            Bitmap mask;
            if (TryGetMask(key.FilePath, out mask))
            {
                ApplyMask(newImage, mask);
            }


            using (var memoryStream = new MemoryStream())
            {
                newImage.Save(memoryStream, ImageFormat.Png);
                imageData = memoryStream.ToArray();
            }
        }
    }
    return imageData;
}

private void ApplyMask(Bitmap Bitmap, Bitmap mask)
{
    if (mask.Width != Bitmap.Width || mask.Height != Bitmap.Height)
    {
        throw new ArgumentException("Bitmap sizes do not match");
    }
    for (int y = 0; y < Bitmap.Height; y++)
    {
        for (int x = 0; x < Bitmap.Width; x++)
        {
            Color colour = Bitmap.GetPixel(x, y);
            colour = Color.FromArgb(mask.GetPixel(x, y).A, colour);
            Bitmap.SetPixel(x, y, colour);
        }
    }
}

Here are the images I am getting (repeated four times to better demonstrate the issue). The first is the correct image as I am getting from my local computer. The second is what is turning up from my UAT server which is exhibiting the strange "reduced by 20%" issue. They are being used in a similar way to this as a repeating background so you can see why the effect is so noticeable. These were generated with a white background to make it easiest to see the issue. I've got similar images in other colours if anybody wants them. :)

enter image description hereenter image description hereenter image description hereenter image description here

enter image description hereenter image description hereenter image description hereenter image description here

As final clarification the images being used should be identical (the UAT was deployed from what I checked into our GIT repro and there has never been more than one version of these images so it can't be using the wrong version.

I am thinking that maybe the underlying GDI is doing something different on the server than on my computer but I can't think what that would be or why.

Any explanations for this behaviour or better yet fixes would be most appreciated. Otherwise I'm going to have to go and do it all manually pixel by pixel doing the transparency and overlay myself which seems a bit silly.

like image 393
Chris Avatar asked Aug 07 '13 12:08

Chris


2 Answers

   graphic.DrawImage(image, 0, 0);

It's not that strange when you use this overload of DrawImage(). It will try to display the image at the original physical size. Which is affected by the DPI (dots per inch) setting of the video adapter. Very common settings today are 96, 120 and 144 dpi, very easy to change with the Display applet in Windows. These dpi values correspond with 100%, 125% and 150% in the applet.

If you want to make sure this does not happen and you get the exact size in pixels then you'll need to use the DrawImage(image, Rectangle) overload.

Do note that physical size matters. If you ever use your program on a "retina" monitor, that day is getting closer and closer, then that 5x29 pixel image is going to look but like a fleck of dust on that nice display. Making programs DpiAware has been ignored for the past 30 years but it is getting to be very important.

like image 95
Hans Passant Avatar answered Nov 05 '22 09:11

Hans Passant


Maybe you have a problem with graphics.DpiX and graphics.DpiY, checks if both properties have the same values in your computer and in the server

like image 2
Alessandro D'Andria Avatar answered Nov 05 '22 08:11

Alessandro D'Andria