【问题标题】:Why doesn't this code always create an identical copy of a Bitmap?为什么这段代码不总是创建一个相同的位图副本?
【发布时间】:2015-06-17 16:28:46
【问题描述】:
public static class BitmapHelper {
    public static Bitmap Clone(Bitmap bmp) {
        var rect = new Rectangle(0, 0, bmp.Width, bmp.Height);
        Bitmap newbmp = new Bitmap(rect.Width, rect.Height);
        using (Graphics gfx = Graphics.FromImage(newbmp)) {
            gfx.InterpolationMode = InterpolationMode.High;
            gfx.PixelOffsetMode = PixelOffsetMode.None;
            gfx.DrawImage(bmp, rect, rect, GraphicsUnit.Pixel);
        }
        return newbmp;
    }
}

大多数情况下,这确实会创建一个相同的副本,但如果传递给 Clone 的原始位图是通过从更大的位图中绘制一个部分来创建的,该部分延伸到更大位图的最右边,那么我取回的副本将在其最右侧边缘有轻微改变的像素。

我做错了什么?

创建原始代码的代码如下所示:

var SmallerImage = new Bitmap(_sqSize, _sqSize);
using (var gfx = Graphics.FromImage(SmallerImage)) {
    gfx.InterpolationMode = InterpolationMode.High;
    gfx.PixelOffsetMode = PixelOffsetMode.None;
    gfx.DrawImage(LargerImage, 
                  new Rectangle(0, 0, _sqSize, _sqSize), 
                  new Rectangle(p.X, p.Y, _sqSize, _sqSize), 
                  GraphicsUnit.Pixel);
}

要知道的重要一点是,点 p 和 _sqSize 使得源矩形(DrawImage 调用中的第二个矩形)直接到达 LargerImage 的最右边。

当点 p 和 _sqSize 使得源矩形不与 LargerImage 的右边缘对接时,相同的代码将创建一个相同的副本。

谁能告诉我为什么会这样?

或者,有人可以告诉我如何可靠地创建位图的精确逐像素副本吗?看起来应该很容易,但我似乎做不到。

new Bitmap(bmp) 和我的克隆方法有同样的问题。 bmp.Clone() 似乎确实有效,但它在幕后存在问题(关于它不是深层副本——与原始副本共享数据)并且使用它会在很多方面破坏我的代码。

【问题讨论】:

  • 我从未见过Bitmap(Image) 构造函数生成的不是输入图像的副本。您确定要复制的图像没有与克隆对象相同的工件吗?根据我的经验,您必须创建较小的位图版本的代码更有可能导致伪影,特别是当您尝试从与原始边缘对齐的子集进行缩放时,就像您在这里一样。看到a good, minimal, complete code example 能可靠地证明问题会很有帮助。
  • 你试过设置 PixelOffsetMode.Half 吗?这确实很重要,因为您不进行任何缩放,但尝试一下也不会受到伤害。 MSDN:考虑地址为 (0, 0) 的图像左上角的像素。使用 PixelOffsetModeNone,像素在 x 和 y 方向上覆盖 –0.5 和 0.5 之间的区域;即像素中心在(0, 0)。使用 PixelOffsetModeHalf,像素在 x 和 y 方向都覆盖 0 和 1 之间的区域;即像素中心在(0.5, 0.5)。
  • 有关使用克隆的问题及其解决方案,请参阅here(阅读两个答案!!)
  • Graphics.DrawImage() 确实不承诺生成原始图像的精确副本,其中每个像素完全与原始图像相同。如果人眼无法观察到差异,则 GDI+ 已进行优化,以尽可能偷工减料。如果一个简单的直接 bitblt 是合乎逻辑的选择,这甚至会发生。如果需要精确的副本,那么您必须使用 LockBits() 并自己复制。

标签: c# .net image bitmap


【解决方案1】:

首先,在克隆之前检查图像是否没有这些工件。

使用Clone()确实只是浅拷贝,但我相信Clone(Rectangle, PixelFormat)是深克隆:

public static Bitmap Clone(Bitmap bmp) {
    var newbmp = bmp.Clone(new Rectangle(0, 0, bmp.Width, bmp.Height), bmp.PixelFormat);
    return newbmp;
}

如果这不起作用,请尝试复制原始数据:

[DllImport("kernel32.dll", EntryPoint = "CopyMemory", SetLastError = false)]
public static extern void CopyMemory(IntPtr dest, IntPtr src, uint count);

public static Bitmap Clone(Bitmap bmp) {
    var oldData = bmp.LockBits(new Rectangle(0, 0, bmp.Width, bmp.Height), ImageLockMode.ReadOnly, bmp.PixelFormat);

    var newBmp = new Bitmap(bmp.Width, bmp.Height, oldData.PixelFormat);
    var newData = newBmp.LockBits(new Rectangle(0, 0, bmp.Width, bmp.Height), ImageLockMode.WriteOnly, bmp.PixelFormat);
    CopyMemory(newData.Scan0, oldData.Scan0, (uint)(Math.Abs(oldData.Stride) * oldData.Height));
    newBmp.UnlockBits(newData);

    bmp.UnlockBits(oldData);
    return newBmp;
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多