Graphics.DrawImage unexpectedly resizing image

2019-02-17 22:19发布

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.

3条回答
对你真心纯属浪费
2楼-- · 2019-02-17 22:27
   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.

查看更多
何必那么认真
3楼-- · 2019-02-17 22:36

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

查看更多
虎瘦雄心在
4楼-- · 2019-02-17 22:52

Did you save and compared the generated images on both side? This looks a bit like a 24bit/32bit color problem.

What happens if you use this constructor:

public Bitmap( int width, int height, PixelFormat format )

using (Bitmap newImage = new Bitmap(image.Width, image.Height, PixelFormat.Format32bppArgb))

As i read the remarks on MSDN:

Remarks
This constructor creates a Bitmap with a PixelFormat enumeration value of Format32bppArgb.

It shouldn't make any difference.

查看更多
登录 后发表回答