【问题标题】:Transform 8bpp image into 24bpp and preserve color将 8bpp 图像转换为 24bpp 并保留颜色
【发布时间】:2016-05-18 23:31:37
【问题描述】:

我有一个带有自定义调色板的 8bpp 图像,其中包含彩色图片。

现在,我正在尝试将其转换为PixelFormat.Format24bppRgb 格式的图片。我使用这里的代码使用直接像素访问http://www.codeproject.com/Tips/240428/Work-with-bitmap-faster-with-Csharp

而且用法是

Bitmap bmp = (Bitmap) Image.FromFile("T:\\500-blue-orig.png");
LockBitmap lbmpSrc = new LockBitmap(bmp);
Bitmap dst = new Bitmap(bmp.Width, bmp.Height, PixelFormat.Format24bppRgb);
LockBitmap lbmpDst = new LockBitmap(dst);

lbmpSrc.LockBits();
lbmpDst.LockBits();

dst.Palette = bmp.Palette;
for (int y = 0; y < lbmpSrc.Height; y++)
{
    for (int x = 0; x < lbmpSrc.Width; x++)
    {
        Color c = lbmpSrc.GetPixel(x, y);

        lbmpDst.SetPixel(x, y, c);
    }
}

lbmpDst.UnlockBits();
lbmpSrc.UnlockBits();

dst.Save("T:\\x.png", ImageFormat.Png);

但是,即使我复制了原始调色板,最终结果也是灰度图像。

我在这里做错了什么?如何从8bpp 实际有颜色的图片中获得24bpp 彩色图像?

【问题讨论】:

  • 为什么不在旧位图的基础上用新格式创建一个新位图,或者将位图绘制到新位图上呢?速度更快,代码更少。
  • 我确实使用了那个代码,仔细看,我使用的是来自 LockBitmap 而不是 Bitmap 的 GetPixel。而且我不能进行位图复制,因为这是更复杂的图像处理方法的一部分。
  • 是的,抱歉,注意到了。但是使用该类并没有好多少,因为仍然存在对颜色和方法调用以及数据复制的转换。可以做得更快。但不能立即看出代码有什么问题。
  • 哦,等等。您使用的代码假定 8 位图像始终是灰度的。它不处理调色板。这就是原因。
  • 但是原始图像怎么会有颜色呢?我将如何使用调色板来获得实际颜色?

标签: c# bitmap gdi+


【解决方案1】:

您使用的 LockBitmap 类不关心调色板,它假定 8bpp 图像始终是灰度的,并且只会返回灰色。

此外,该类还远远不够快,因为它将位图数据复制到另一个数组并返回,在不一定需要时创建 Color 等。如果您真的想要性能,您将自己处理。

你有两个选择:

  • 直接使用来自BitmapGetPixelSetPixel。它会正常工作。

  • 先将 8bpp 调色板图像复制成 32/24bpp 图像,然后使用该类进行处理

【讨论】:

  • 好的,我可以试试第二个选项。但出于好奇,我将如何实现一个关心调色板的 GetPixel?
  • 8 位图像根本不被视为灰度。事实上,一旦你处于字节级别,它们就不会被“考虑”任何东西。它们只是字节。这些字节指的是图像调色板上的索引。
  • @Nyerguds 如果您费心阅读正在使用的代码,它会特别假定 8bpp 值是灰度值,因为它返回的颜色将 R、G 和 B 设置为存储的值。正如我所说,它忽略了调色板。
  • 所以这只是将问题简化为“我使用的一些外部代码是错误的”。我明白了。
  • @Nyerguds 根本没有问题,只是功能与假设不同。就像我的回答所说的那样。而不是“一些外部代码”,“这个外部代码”。
【解决方案2】:

我尝试了使用非托管代码的手动方法 - 本地基准测试表明它比无知 (Bitmap.GetPixel > Bitmap.SetPixel) 方法快 99.7%。

基本上,我们使用LockBits 指针并根据调色板一一分配字节。

static unsafe void To24Bpp(Bitmap source, Bitmap dest)
{
    var sourceData = source.LockBits(new Rectangle(0, 0, source.Width, source.Height), ImageLockMode.ReadOnly,
        PixelFormat.Format8bppIndexed);
    var destData = dest.LockBits(new Rectangle(0, 0, dest.Width, dest.Height), ImageLockMode.WriteOnly, PixelFormat.Format24bppRgb);
    var paletteBytes = source.Palette.Entries.Select(ColorToUintRgbLeftAligned).ToArray();
    var current = (byte*) sourceData.Scan0.ToPointer();
    var lastPtr = (byte*) (sourceData.Scan0 + sourceData.Width*sourceData.Height).ToPointer();
    var targetPtr = (byte*) destData.Scan0;
    while (current <= lastPtr)
    {
        var value = paletteBytes[*current++];
        targetPtr[0] = (byte) (value >> 24);
        targetPtr[1] = (byte) (value >> 16);
        targetPtr[2] = (byte) (value >> 8);
        targetPtr += 3;
    }

    source.UnlockBits(sourceData);
    dest.UnlockBits(destData);
}

static uint ColorToUintRgbLeftAligned(Color color)
{
    return ((uint) color.B << 24) + ((uint) color.G << 16) + ((uint) color.R << 8);
}

可以改进代码以从调色板一次写入 4 个字节,从而减少随机内存访问量。我的本地基准测试显示,它的性能进一步提高了 25%。请注意构建 uint 颜色字节的区别 - uint 中的字节对齐方式与我的预期相反。

private static unsafe void To24BppUintAssignment(Bitmap source, Bitmap dest)
{
    var sourceData = source.LockBits(new Rectangle(0, 0, source.Width, source.Height), ImageLockMode.ReadOnly, PixelFormat.Format8bppIndexed);
    var destData = dest.LockBits(new Rectangle(0, 0, dest.Width, dest.Height), ImageLockMode.WriteOnly, PixelFormat.Format24bppRgb);
    uint[] paletteBytes = source.Palette.Entries.Select(ColorToUintRgbRightAligned).ToArray();
    var current = (byte*)sourceData.Scan0.ToPointer();
    var lastPtr = (byte*)(sourceData.Scan0 + sourceData.Width * sourceData.Height).ToPointer();
    var targetPtr = (byte*) destData.Scan0;
    while (current < lastPtr)
    {
        var targetAsUint = ((uint*) targetPtr);
        targetAsUint[0] = paletteBytes[*current++];
        targetPtr += 3;
    }
    uint finalValue = paletteBytes[*current];
    targetPtr[0] = (byte)(finalValue >> 24);
    targetPtr[1] = (byte)(finalValue >> 16);
    targetPtr[2] = (byte)(finalValue >> 8);
        source.UnlockBits(sourceData);
    dest.UnlockBits(destData);
    }
    private static uint ColorToUintRgbRightAligned(Color color)
    {
        return ((uint)color.B) + ((uint)color.G << 8) + ((uint)color.R << 16);
    }

我没有在方法中创建位图以进行基准测试,它应该这样调用:

static Bitmap To24Bpp(Bitmap source)
{
    var dest = new Bitmap(source.Width, source.Height, PixelFormat.Format24bppRgb);
    To24BppUintAssignment(source, dest);
    return dest;
}

【讨论】:

  • 出于好奇,如果我有 24bpp 的图片,如何在调色板中找到正确的颜色?
  • 调色板每个条目都有一个颜色,如果你有一个颜色,你必须进行反向查找。
【解决方案3】:

这里的人似乎都把事情复杂化得可笑了。将 8BPP 图像转换为 24BPP 或 32BPP 不需要任何特殊代码。

关于 8BPP 图像的唯一困难是处理它们,而且,您根本不需要这样做;您的最终结果不是这些有问题的 8BPP 图像之一。

不到五行就可以搞定:

public static Bitmap PaintOn32bpp(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;
}

这适用于任何图像,无论其颜色格式如何。

【讨论】:

    猜你喜欢
    • 2012-10-19
    • 2019-12-03
    • 2018-05-09
    • 2011-09-15
    • 2021-02-14
    • 2013-02-25
    • 1970-01-01
    • 2013-08-17
    • 2016-04-26
    相关资源
    最近更新 更多