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

本文关键字:索引 结果 位图 裁剪 有效地 | 更新日期: 2023-09-27 18:34:50

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

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

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

基本上,我想裁剪输入的指定region,然后对图像的该部分进行低级操作。

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

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

根据这个答案,似乎Clone参数可能会产生对原始数据的某种视图,而不是实际复制它,因此LockBits不会锁定适当的数据 - 它不一定是连续的。但是,如何显式复制区域?通常的Graphics.DrawImage方法不会直接起作用,因为所需的目标图像已编入索引,因此我无法为其获取图形。

我真的必须在 RGB 模式下完成所有工作然后转换回来吗?肯定有更优雅的东西吗?

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

根据 Duniho @Peter的评论以及一些进一步的测试和调试,我确定实际上我只是在代码中有一个错误。结论:.Clone(( 方法工作得很好,是这里想要的

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

或者更笼统一点,

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

其中region是所需的裁剪Rectangle。我的代码的问题在于,在随后的LockBits代码中,该region不正确 - 要锁定整个裁剪的图像,我想要与该Rectangle相同的宽度和高度,但 (0, 0( 表示 X 和 Y。

经过更多的测试,我开发了以下关于此处可能遇到的错误的注释:

  • 如果您尝试使用不适合Bitmap矩形的region - 即 XY为负数,或X + Width超过BitmapWidth,或Y + Height超过Bitmap的高度 - 它不会被裁剪到原始Bitmap的边界,而是发生异常。负宽度和高度值也无效

  • 如果 Bitmap.LockBitsregion宽度为零、高度为零或不适合,则会引发System.ArgumentException。消息只是"参数无效"。无论哪种方式,所以由你来找出出了什么问题。

  • 如果Bitmap.Cloneregion宽度为零或高度为零,则再次收到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,这里没有必要,尽管它对于更低级的操作仍然有用。