繁体   English   中英

将8bpp图像转换为24bpp并保留色彩

[英]Transform 8bpp image into 24bpp and preserve color

我有一个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彩色图像?

我尝试了使用非托管代码的手动方法-本地基准测试显示它比无知( 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;
}

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

该类也不是很快,因为它将位图数据复制到另一个数组并返回,并在不需要时创建Color等。如果您确实想要性能,您将自己进行处理。

您有两种选择:

  • 直接使用Bitmap GetPixelSetPixel 它会按预期工作。

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

这里的人们似乎都把事情复杂化了。 将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;
}

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM