问题描述
我有一个 8bpp 索引位图,带有自定义的 256 色调色板,其中调色板中的特定颜色(Color.Pink
和 Color.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,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,data.Scan0,buffer.Length);
image.UnlockBits(data);
生成这个有趣的图像(缩放 x25):
现在,您要使调色板中的两种颜色透明:Color.Pink
和 Color.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
对象,我使用的是之前创建的 Palette 副本 (var palette = image.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()
方法生成新的位图,并使用接受Graphics.DrawImage()
的ImageAttributes
方法绘制带有新颜色图的索引图像论点:
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),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);
这些方法生成相同的输出。
- 可用于更改索引图像格式的调色板。
- 另一个在设备上下文中显示具有重新映射颜色的图像(例如,将位图分配给控件的图像属性)。
- [额外] 另一种选择是使用 TextureBrush,如下所示:
How to draw a transparent shape over an Image