如何在绘制8bpp位图时使调色板中的颜色透明

人气:828 发布:2022-10-16 标签: c# winforms gdi+ transparency bitmap

问题描述

我有一个具有定制256色调色板的8bpp索引位图,调色板中的特定颜色(Color.PinkColor.Green)表示透明度。 我可以在位图上使用MakeTransparent(color)方法(每种颜色两次),但它会将其转换为32bpp。因此,我改用:

using var imageAttr = new ImageAttributes();
imageAttr.SetColorKey(pink, pink, ColorAdjustType.Default);

然后

g.DrawImage(bitmap, destRect, X, Y, Width, Height, GraphicsUnit.Pixel, imageAttr);

按其需要绘制位图,但仅对透明颜色Color.Pink进行更改。如何对第二种颜色(Color.Green)也执行此操作?

推荐答案

ImageAttributes.SetColorKey()和Bitmap.MakeTransparent都不是需要重新映射位图调色板的索引颜色时的首选:前者一次只能设置一种颜色,后者转换32bpp图像中的原始图像。

您需要更改索引图像ColorPalette或使用ImageAttributes.SetRemapTable()方法绘制新的位图。此方法接受ColorMap对象的数组。ColorMap用于指定绘制位图时替换旧颜色的新颜色。

让我们制作一个示例8bpp图像并应用部分调色板,然后使用Image.LockBits解析BitmapData并将这些颜色应用于一组3x3矩形:

var image = new Bitmap(12, 12, PixelFormat.Format8bppIndexed);
var palette = image.Palette;  // Copy the Palette entries
palette.Entries[0] = Color.SteelBlue;
palette.Entries[1] = Color.Pink;
palette.Entries[2] = Color.Red;
palette.Entries[3] = Color.Orange;
palette.Entries[4] = Color.YellowGreen;
palette.Entries[5] = Color.Khaki;
palette.Entries[6] = Color.Green;
palette.Entries[7] = Color.LightCoral;
palette.Entries[8] = Color.Maroon;
image.Palette = palette;  // Sets back the modified palette

var data = image.LockBits(new Rectangle(0, 0, image.Width, image.Height), ImageLockMode.WriteOnly, image.PixelFormat);

int rectsCount = 3;                   // Generate 3 Rectangles per line...
int step = data.Stride / rectsCount;  // ...of this size (single dimension, since w=h here)
int colorIdx = 0, col = 0;            // Color and Column positions

byte[] buffer = new byte[data.Stride * image.Height];

for (int i = 1; i <= rectsCount; i++) {
    for (int y = 0; y < data.Height; y++) {
        for (int x = col; x < (step * i); x++) {
            buffer[x + y * data.Stride] = (byte)colorIdx;
        }
        colorIdx += (y + 1) % step == 0 ? 1 : 0;
    }
    col += step;
}

Marshal.Copy(buffer, 0, data.Scan0, buffer.Length);
image.UnlockBits(data);

生成这张有趣的图像(放大x25):

现在,您希望将调色板中的两种颜色设置为透明:Color.PinkColor.Green。 您可以生成一个ColorMap对象数组,指定哪些新颜色替换现有颜色:

var mapPink = new ColorMap() { OldColor = Color.Pink, NewColor = Color.Transparent };
var mapGreen = new ColorMap() { OldColor = Color.Green, NewColor = Color.Transparent };
var colorMap = new ColorMap[] { mapPink, mapGreen };

然后:

用新映射的颜色替换图像调色板中的每种颜色: (请注意,我不是直接传递[Image].Palette对象,而是使用之前创建的调色板的副本(var palette = image.Palette;):如果直接传递图像调色板,则不会注册更改)
private ColorPalette RemapImagePalette(ColorPalette palette, ColorMap[] colorMaps)
{
    for (int i = 0; i < palette.Entries.Length; i++) {
        foreach (ColorMap map in colorMaps) {
            if (palette.Entries[i] == map.OldColor) {
                palette.Entries[i] = map.NewColor;
            }
        }
    }
    return palette;
}

// [...]

var palette = image.Palette;
image.Palette = RemapImagePalette(palette, colorMap);
或使用ImageAttributes.SetRemapTable()方法生成新的位图,并使用接受ImageAttributes参数的Graphics.DrawImage()方法使用新的颜色映射绘制索引图像:
private Bitmap ImageRemapColors(Image image, Size newSize, ColorMap[] map)
{
    var bitmap = new Bitmap(newSize.Width, newSize.Height);

    using (var g = Graphics.FromImage(bitmap))
    using (var attributes = new ImageAttributes()) {
        if (map != null) attributes.SetRemapTable(map);

        g.InterpolationMode = InterpolationMode.NearestNeighbor;
        g.PixelOffsetMode = PixelOffsetMode.Half;
        g.DrawImage(image, new Rectangle(Point.Empty, newSize),
                    0, 0, image.Width, image.Height, GraphicsUnit.Pixel, attributes);
    }
    return bitmap;
}

// [...]

// Draws the 12x12 indexed 8bpp Image to a new 300x300 32bpp Bitmap
Bitmap remappedImage = ImageRemapColors(image, new Size(300, 300), colorMap);

这些方法生成相同的输出。

一个可用于更改索引图像格式的调色板。 另一个用于在设备上下文中显示具有重新映射的颜色的图像(例如,将Bitmap分配给控件的Image属性)。 [Extra]另一个选项是使用纹理笔刷,如下所示: How to draw a transparent shape over an Image

761