C# 调整大小的图片有黑色边框

13

我在.NET中遇到了一个有关图像缩放的问题。我使用标准的Graphics类型来调整图像大小,就像这个例子一样:

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;
    }

但我遇到了一个重要问题,即调整大小后的图像会出现灰色和黑色边框,非常重要的是使图像不带有这些边框。

为什么会出现这些边框,我该怎么做才能让它们消失?

示例输出:

示例输出


2
这些图片发送到浏览器的HTML长什么样? - DOK
2
图片的原始类型是什么? - Adriaan Stander
1
我曾经遇到同样的问题并发布了我的解决方案。 - Jeroen
2
图片已经不再存在,但是如果你在边缘看到了1像素的边框,你可以通过传递一个ImageAttributes实例到DrawImage()并设置TileFlipXY来解决这个问题。这将使插值重复使用外部像素边缘,而不是将其与背景或透明颜色进行平均处理。来源:http://imageresizing.net/ - Lilith River
9个回答

8
真正的解决方案是使用DrawImage的一个重载方法,允许你传递一个ImageAttributes对象。
ImageAttributes实例上,在将其传递给DrawImage之前调用以下方法:
using (var ia = new ImageAttributes())
{
    ia.SetWrapMode(WrapMode.TileFlipXY);
    aGraphic.DrawImage(..., ia);
}

另请参见这个答案


1
这是解决此问题的真正方法。 - user2233219
我已经尝试了大约3个小时来解决这个问题。这个答案是唯一有效的。我希望这能成为被采纳的解决方案。 - Andy
请查看saucecontrols的回答,同时注意设置PixelOffsetMode.Half - Jonas Äppelgran

8

正确的答案可以从其他回复中拼凑出来,但没有一个完整的回复,而且有些回复提出了一些非常糟糕的想法(比如两次绘制图像)。

问题

你看到的伪影有三个原因:

  1. 默认的 Graphics.PixelOffsetMode 设置会导致像素值被错误地采样,结果是图像略微变形,特别是在边缘周围。
  2. InterpolationMode.HighQualityBicubic 会从图像边缘之外采样像素,默认情况下这些像素是透明的。采样器将这些透明像素与边缘像素混合,导致半透明边缘。
  3. 当你将半透明图像保存在不支持透明度的格式(例如JPEG)中时,透明值会被替换为黑色。

所有这些加起来就是半黑色(即灰色)边缘。

代码中还有一些其他问题:

你使用的 Bitmap 构造函数通过调整原始图像的大小来初始化新的 Bitmap,因此你正在进行两次调整大小操作。你应该使用只有所需尺寸的构造函数重载来创建一个空画布。

记住,Bitmap类表示内存中图像的未管理副本。它需要被处理以便告诉GDI+在你完成后释放那个内存。我假设你在接收返回的Image的代码中这样做了,但我提醒一下以防其他人借用此代码。
你的代码中使用的CompositingQuality.HighQuality设置如果你正确地设置了其他设置将没有视觉效果,而且与CompositingMode.SourceOver的默认值相结合会显著降低性能。您可以省略CompositingQuality设置并设置CompositingMode.SourceCopy以获得相同的结果,并提高性能。
你的代码中使用的SmoothingMode设置对DrawImage()没有任何影响,因此可以删除它。
解决方案
消除这些伪像的正确方法是使用PixelOffsetMode.Half并使用ImageAttributes对象指定边缘平铺,这样HighQualityBicubic采样器就有了其他东西而不是透明像素来采样。
你可以在这里阅读有关 Graphics 类设置及其对图像质量和性能的影响的更多信息:http://photosauce.net/blog/post/image-scaling-with-gdi-part-3-drawimage-and-the-settings-that-affect-it 修改后的代码应该类似于这样:
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;
}

我已经按照上述方法尝试过了,但是灰色边框仍然出现 :( https://dev59.com/qbzpa4cB1Zd3GeqPJmLn - Salman
你提供的示例没有填充整个位图/画布,这是一个不同的问题,尽管行为与使用不正确的PixelOffsetMode相同。我在那个问题上留了一个答案。 - saucecontrol
2
这是目前这里唯一正确的答案。同时使用WrapMode.TileFlipXY和PixelOffsetMode.Half。我建议仅为jpg设置包装模式,否则对于具有透明度的png,在边缘附近会丢失半透明/抗锯齿效果。 - Jonas Äppelgran
1
真正的解决方案确实如此! - hubert17
我的代码在FW 4.8上运行良好,但在NET 5上某些图像顶部会出现黑线。通过这个解决方案,我解决了这个问题。谢谢。 - bertasoft

7

这可能是由于周围像素错误地进行了插值造成的。我认为这是一个bug。

但是,以下是解决方案:

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

// Draw your image here.

graphics.InterpolationMode = InterpolationMode.HighQualityBicubic;

// Draw it again.

这段代码的作用是首先用正确填充的边缘绘制“背景”,然后再使用插值重新绘制。如果您不需要插值,则此步骤是不必要的。

@Tillito 没问题 :) 请注意,尽管有效,但从性能角度来看,这不是最优解。除非您有大量图像或正在制作要分发、重复使用等的自定义组件,在这种情况下,您可能需要一些更复杂的方法来解决此问题,例如使用lockbits并手动绘制/插值。 - Camilo Martin

6
问题在于您的位图toReturn默认带有黑色背景。将新图像复制到其上会产生黑色或灰色边框。
解决方案是通过调用以下命令删除默认的黑色背景:
toReturn.MakeTransparent();

由于这行代码后,您将在没有任何背景颜色的新图像上绘制,所以边框会消失。


6

尝试:

graphic.CompositingMode = CompositingMode.SourceCopy;

4
很不幸,这不是一个解决方案。它没有任何效果。灰色边框仍然会生成。如果它对原始发布者有用,那只是偶然。解决方案在于按照这个答案所指出的指定图像属性:https://dev59.com/4XE95IYBdhLWcg3watM3#2324414 - user151323
4
@开发者Art,您的源文件是24位还是带有alpha通道的32位?调整大小带有alpha通道的图像会在边缘产生半透明像素;通过设置此模式,这些像素将与背景混合,否则它们将与黑色混合,从而导致边框。我不知道24位图像会发生什么。 - Mark Ransom
1
@Mark Ransom,看我的回答,你部分正确但没有给出正确的解决方案。 - Jeroen
1
请查看下面@marapet的答案。那是唯一一个对我有效的。 - Andy

1

这些方法对我都没有起作用。

但是,将格式从

System.Drawing.Imaging.PixelFormat.Format24bppRgb

to

System.Drawing.Imaging.PixelFormat.Format32bppArgb 

已解决问题

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

1
以下代码对你有用吗?这是我用来做同样事情的代码。我注意到的主要区别是我没有使用SetResolution(并且我假设输入和输出都是正方形,因为这对我来说是这种情况)。
/// <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;
}

2
他从零开始创建了一张默认黑色背景的图片。你正在加载一张可能没有背景的图片。 - Jeroen

1

这是因为采样是从照片的边缘进行的。


0
这是因为在绘制图像时,边缘进行了平滑处理(与背景混合)。
您可以尝试绘制两次,一次不启用平滑处理,一次启用平滑处理。或者您可以稍微放大图像。如果已知原始背景颜色,则可以先用背景颜色填充图像。

@Downvoter,我真的很感激您能留下一条评论,说明为什么我的答案如此不合适或糟糕... - Lucero

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接