使用相同的自定义调色板将 8bpp 索引位图转换为 24bpp 并再次转换回来

Converting 8bpp Indexed Bitmap to 24bpp and back again with the same custom color palette

我正在制作一个工具,可以对一些位图进行一些图形编辑。我有一些带有现有调色板的 8bppIndexed 位图,我必须将其转换为 24bppRgb 以允许我在其上绘制并根据用户选择的某些选项编辑像素颜色。位图图形为原始 8bpp .bmp 文件:

//Get Bitmap from file
Bitmap graphic = new Bitmap(source);

//Get palette and pixel format
ColorPalette pal = graphic.Palette;
PixelFormat pix = graphic.PixelFormat; //Format8bppIndexed

//Create datagraphic in 24bppRgb to allow for editing
PixelFormat datapix = PixelFormat.Format24bppRgb;
Bitmap datagraphic = graphic.Clone(new Rectangle(0, 0, graphic.Width, graphic.Height), datapix);

绘制和编辑位图数据图形后,我试图将其转换回 8bppIndexed,使用与位图图形相同的调色板。我可以这样做:

//Convert back to graphic pixel format
datagraphic = datagraphic.Clone(new Rectangle(0, 0, datagraphic.Width, datagraphic.Height), pix); //Format8bppIndexed

//Apply color palette
datagraphic.Palette = pal;

但是,在克隆具有与图形相同像素格式的数据图形后,它会创建一个全新的调色板。应用ColorPalette pal后数据图中的颜色都不正确。它们仅通过两个调色板之间的索引号匹配。是否有另一种方法可以保留颜色?

是的,位图需要使用自定义调色板进行 8bpp 索引。我试图避免需要通过 Photoshop 来使用正确的调色板更改所有最终结果数据图形的颜色索引。

我应该说我对 C# 还是很陌生。我希望我清楚我要做什么。我感谢这里的任何帮助。提前致谢。

将 8 位图像转换为 24bpp 很简单:您只需将其绘制在具有相同尺寸和首选像素格式的新图像上即可。

public static Bitmap PaintOn24bpp(Image image)
{
    Bitmap bp = new Bitmap(image.Width, image.Height, PixelFormat.Format24bppRgb);
    using (Graphics gr = Graphics.FromImage(bp))
        gr.DrawImage(image, new Rectangle(0, 0, bp.Width, bp.Height));
    return bp;
}

这适用于任何源图像,无论其像素格式如何,但对于包含透明度的图像,您可能需要对其进行预处理以填充任何透明区域,因为我不知道考虑透明度的绘画是否会起作用您正在绘制的图像不支持 alpha。

相反,将高色图像转换为索引图像要复杂得多,但如果您有调色板,这当然是可能的。这种情况下的主要问题是使用 Graphics 不起作用,因为索引图形没有彩色像素;他们有一个调色板和仅参考这些颜色的像素。

编辑 8 位数据通常归结为构建包含图像数据的字节数组,并将其转换为带有调色板的图像。实际转换是通过创建一个新的 8bpp 位图,使用 LockBits 函数打开其支持字节数组,然后使用 Marshal.Copy.

将新数据复制到其中来完成的

在那之后,最后缺少的 link 是一种将图像的彩色像素与其最接近的调色板匹配相匹配的方法,因此您可以获取结果匹配数组并将其烘焙成 8bpp 图像。这通常是通过计算您的颜色与调色板中每种颜色的毕达哥拉斯距离(在 3D RGB 颜色中 space),然后获取该距离最小的颜色的索引来完成的。

从开始到结束的整个过程在这个答案中有详细解释:

这处理 2 种颜色,但是处理 2 种或 256 种颜色的方法是完全相同的,因为两种情况下的最终结果都是 8bpp 图像。

它可以针对直接处理 24bpp 图像而不是将输入转换为 32bpp 进行优化,但这可能不值得付出努力,尤其是因为那里发布的方法绝对适用于任何输入。