c# – 如何有效地裁剪*索引*位图并获得*索引*结果?

如果你因为标题而来到这里,你可能应该跳过这个问题并直接找到答案.事实证明我的代码中有一个简单的错误.

我试图专门使用索引图像,因为我的项目的主要部分涉及调色板交换.我尝试了以下几行代码作为更大进程的一部分:

        Bitmap raw = ((Bitmap)i).Clone(region, PixelFormat.Format8bppIndexed);
        byte transparent = (byte)(Array.FindIndex(raw.Palette.Entries, x => x.A < 128));
        // Scan the bitmap for the first opaque pixel on each side
        BitmapData bd = raw.LockBits(region, ImageLockMode.ReadOnly, PixelFormat.Format8bppIndexed);

基本上,我想裁剪输入的指定区域,然后对图像的那一部分进行低级操作.

转换成功(我可以在此时验证它是一个索引位图,但它存储在一个控件中,因此丢失了类型信息).克隆调用似乎成功了.调试显示原始的PixelFormat确实是PixelFormat.Format8bppIndexed,(Bitmap)i的PixelFormat也是如此.透明调色板索引的计算工作正常.但是然后raw.LockBits失败,抱怨参数无效.

即使是奇怪的,如果我删除.Clone调用,raw.LockBits也会成功(但是其余的代码会做错事,因为源没有被裁剪).

根据this answer,带参数的Clone似乎可能会对原始数据产生某种视图,而不是实际复制它,因此LockBits无法锁定适当的数据 – 它不一定是连续的.但是如何明确复制该区域?通常的Graphics.DrawImage方法不能直接工作,因为所需的目标图像被索引,所以我无法获得它的图形.

我是否真的必须以RGB模式完成所有工作然后转换回来?当然有更优雅的东西?

最佳答案 根据@Peter Duniho的评论和一些进一步的测试和调试,我确定实际上我只是在代码中有一个错误.结论:.Clone()方法工作正常,这是所需的:

Bitmap cropped = original.Clone(region, PixelFormat.Format8bppIndexed);

或者更一般地说,

Bitmap cropped = original.Clone(region, original.PixelFormat);

其中region是所需的裁剪矩形.我的代码的问题是,在随后的LockBits代码中,该区域是不正确的 – 要锁定整个裁剪图像,我想要与Rectangle相同的宽度和高度,但是(0,0)用于X和Y.

经过一些更多测试后,我开发了以下有关可能遇到的错误的注释:

>如果您尝试使用不适合位图矩形的区域 – 即X或Y为负,或者X宽度超出位图的宽度,或者Y高度超过位图的高度 – 它将不会被剪切到原始位图的边界,而是发生异常.负宽度和高度值也无效.
>如果Bitmap.LockBits的区域具有零宽度,零高度或不适合,则会引发System.ArgumentException.消息只是“参数无效”.无论哪种方式,所以由你来决定出了什么问题.
>如果Bitmap.Clone的区域具有零宽度或零高度,则再次获得System.ArgumentException,这次使用实际信息性错误消息.但是,如果它不适合,您将获得System.OutOfMemoryException.这有点有趣,因为0的宽度和高度值显然得到明确检查,但负值则没有.

所以也许我们应该用一点功能来防范这种情况,例如:

// Create a cropped `region` of the `original` Bitmap as a new bitmap,
// preserving the original pixel format. If negative Width or Height
// are provided for the clip region and `flipNegative` is set, the result
// is flipped accordingly.
public Bitmap crop(Bitmap original, Rectangle region, bool flipNegative) {
    Rectangle bounds = new Rectangle(new Point(0, 0), original.Size);
    if (region.Width == 0 || region.Height == 0) { return null; }

    // Normalize width and height parameters,
    // and track whether we might need to flip.
    bool flipHorizontal = region.Width < 0;
    bool flipVertical = region.Height < 0;
    if (flipHorizontal)
    {
        region.X += region.Width;
        region.Width = -region.Width;
    }
    if (flipVertical)
    {
        region.Y += region.Height;
        region.Height = -region.Height;
    }

    // Ensure we have a valid clipping rectangle, and make the GDI call.
    if (!region.IntersectsWith(bounds)) { return null; }
    region.Intersect(bounds);
    Bitmap result = original.Clone(region, original.PixelFormat);

    // Flip the result as appropriate.
    if (flipHorizontal && flipNegative)
    {
        result.RotateFlip(RotateFlipType.RotateNoneFlipX);
    }
    if (flipVertical && flipNegative)
    {
        result.RotateFlip(RotateFlipType.RotateNoneFlipY);
    }
    return result;
}

(我提供了flipNegative参数,因为你可能想要任何语义.)

至于LockBits,这里没有必要,尽管它对于更低级别的操作仍然有用.

点赞