English 中文(简体)
C# Resized images have black borders
原标题:

I have a problem with image scaling in .NET. I use the standard Graphics type to resize images like in this example:

public static Image Scale(Image sourceImage, int destWidth, int destHeight)
{
        Bitmap toReturn = new Bitmap(sourceImage, destWidth, destHeight);

        toReturn.SetResolution(sourceImage.HorizontalResolution, sourceImage.VerticalResolution);

        using (Graphics graphics = Graphics.FromImage(toReturn))
        {
            graphics.CompositingQuality = System.Drawing.Drawing2D.CompositingQuality.HighQuality;
            graphics.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality;
            graphics.InterpolationMode = InterpolationMode.HighQualityBicubic;
            graphics.DrawImage(sourceImage, 0, 0, destWidth, destHeight);
        }
        return toReturn;
    }

But I have a big problem with resized images: they have gray and black borders and it s extremely important to make have images without them.

Why do they appear and what I can to do make them disappear?

Sample Output:

sample output

最佳回答

Try:

graphic.CompositingMode = CompositingMode.SourceCopy;
问题回答

The real solution is to use an overload of the DrawImage which allows you to pass a ImageAttributes object.

On the ImageAttributes instance, call the following method before passing it to DrawImage:

using (var ia = new ImageAttributes())
{
    ia.SetWrapMode(WrapMode.TileFlipXY);
    aGraphic.DrawImage(..., ia);
}

See also this answer

This can be caused by pixels around the edges being wrongly interpolated. I d call this a bug.

Here s the solution, though:

graphics.CompositingMode = CompositingMode.SourceCopy;
graphics.PixelOffsetMode = PixelOffsetMode.Half;
graphics.InterpolationMode = InterpolationMode.NearestNeighbor;

// Draw your image here.

graphics.InterpolationMode = InterpolationMode.HighQualityBicubic;

// Draw it again.

What this does is first drawing a "background" with the edges correctly-filled, and then draw it again with interpolation. If you don t need interpolation, then this is not necessary.

A correct answer can be pieced together from some of the other responses, but none of them is complete and some present some very bad ideas (like drawing the image twice).

The problem

There are three reasons for the artifacts you re seeing:

  1. The default Graphics.PixelOffsetMode setting causes the pixel values to be sampled incorrectly, resulting in a slight distortion of the image, particularly around the edges.
  2. InterpolationMode.HighQualityBicubic samples pixels from beyond the image edge, which are transparent by default. Those transparent pixels are mixed with the edge pixels by the sampler, resulting in semi-transparent edges.
  3. When you save a semi-transparent image in a format that doesn t support transparency (e.g. JPEG), the transparent values are replaced by black.

That all adds up to semi-black (i.e. grey) edges.

There are a few other issues with the code you posted as well:

The Bitmap constructor you used is initializing the new Bitmap by resizing the original image, so you re doing the resize operation twice. You should use a constructor overload with just the desired dimensions to create a blank canvas.

Remember that the Bitmap class represents an unmanaged copy of the image in memory. It needs to be disposed so that GDI+ can be told to release that memory when you re done with it. I assume you re doing that in the code that receives the return Image, but I point that out in case anyone else borrows this code.

The CompositingQuality.HighQuality setting used in your code will have no visual effect if you get the other settings right and will actually hurt performance fairly significantly in combination with the default value of CompositingMode.SourceOver. You can omit the CompositingQuality setting and set CompositingMode.SourceCopy to get the same results with better performance.

The SmoothingMode setting used in your code has no impact at all on DrawImage(), so it can be removed.

Solution

The correct way to remove those artifacts is to use PixelOffsetMode.Half and to use an ImageAttributes object to specify edge tiling so the HighQualityBicubic sampler has something other than transparent pixels to sample.

You can read more about the Graphics class settings and their impact on image quality and performance here: http://photosauce.net/blog/post/image-scaling-with-gdi-part-3-drawimage-and-the-settings-that-affect-it

The revised code should look something like this:

public static Image Scale(Image sourceImage, int destWidth, int destHeight)
{
    var toReturn = new Bitmap(destWidth, destHeight);

    using (var graphics = Graphics.FromImage(toReturn))
    using (var attributes = new ImageAttributes())
    {
        toReturn.SetResolution(sourceImage.HorizontalResolution, sourceImage.VerticalResolution);

        attributes.SetWrapMode(WrapMode.TileFlipXY);

        graphics.InterpolationMode = InterpolationMode.HighQualityBicubic;
        graphics.PixelOffsetMode = PixelOffsetMode.Half;
        graphics.CompositingMode = CompositingMode.SourceCopy;
        graphics.DrawImage(sourceImage, Rectangle.FromLTRB(0, 0, destWidth, destHeight), 0, 0, sourceImage.Width, sourceImage.Height, GraphicsUnit.Pixel, attributes);
    }

    return toReturn;
}

The problem lies in the fact that your bitmap toReturn has a black background by default. Copying a new image over it makes black or gray borders.

The solution is to remove the black default background, by calling:

toReturn.MakeTransparent();

Since after this line you ll be drawing on a new image without any background color the borders will disappear.

How does the following work for you? This is the code I ve used to do the same thing. The main difference I notice is that I don t use SetResolution (and I assume a square input and output, since that was the case for me).

/// <summary>
/// Resizes a square image
/// </summary>
/// <param name="OriginalImage">Image to resize</param>
/// <param name="Size">Width and height of new image</param>
/// <returns>A scaled version of the image</returns>
internal static Image ResizeImage( Image OriginalImage, int Size )
{
    Image finalImage = new Bitmap( Size, Size );

    Graphics graphic = Graphics.FromImage( finalImage );

    graphic.CompositingQuality = System.Drawing.Drawing2D.CompositingQuality.HighSpeed;
    graphic.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighSpeed;
    graphic.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic;

    Rectangle rectangle = new Rectangle( 0, 0, Size, Size );

    graphic.DrawImage( OriginalImage, rectangle );

    return finalImage;
}

It s because sampling was taken from the edges of the photo.

None of these worked for me.

However, changing the format from

System.Drawing.Imaging.PixelFormat.Format24bppRgb

to

System.Drawing.Imaging.PixelFormat.Format32bppArgb 

did solve the problem

using (System.Drawing.Bitmap newImage = new System.Drawing.Bitmap(newWidth, newHeight,
                // System.Drawing.Imaging.PixelFormat.Format24bppRgb // OMG bug
                    System.Drawing.Imaging.PixelFormat.Format32bppArgb 
                ))
            {

This is because of the smoothing (blending with the background) on the edges when drawing the image.

You could maybe draw it twice, once without and one with smoothing enabled. Or you could draw it a little bigger. Or if the original background color is known, you could first fill the image with the background color.





相关问题
Anyone feel like passing it forward?

I m the only developer in my company, and am getting along well as an autodidact, but I know I m missing out on the education one gets from working with and having code reviewed by more senior devs. ...

NSArray s, Primitive types and Boxing Oh My!

I m pretty new to the Objective-C world and I have a long history with .net/C# so naturally I m inclined to use my C# wits. Now here s the question: I feel really inclined to create some type of ...

C# Marshal / Pinvoke CBitmap?

I cannot figure out how to marshal a C++ CBitmap to a C# Bitmap or Image class. My import looks like this: [DllImport(@"test.dll", CharSet = CharSet.Unicode)] public static extern IntPtr ...

How to Use Ghostscript DLL to convert PDF to PDF/A

How to user GhostScript DLL to convert PDF to PDF/A. I know I kind of have to call the exported function of gsdll32.dll whose name is gsapi_init_with_args, but how do i pass the right arguments? BTW, ...

Linqy no matchy

Maybe it s something I m doing wrong. I m just learning Linq because I m bored. And so far so good. I made a little program and it basically just outputs all matches (foreach) into a label control. ...

热门标签