将 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
彩色图像,而实际上它有颜色?
您正在使用的 LockBitmap
class 不关心调色板,它假定 8bpp 图像始终是灰度并且 return 只会是灰色。
此外,class 远非快速,因为它将位图数据复制到另一个数组并返回,在不需要时创建 Color
等。如果你真的想要性能,你会自己处理.
你有两个选择:
直接使用 Bitmap
中的 GetPixel
和 SetPixel
。它会正常工作。
先将8bpp的调色板图像复制成32/24bpp的图像,然后使用class进行处理
我尝试了一种使用非托管代码的手动方法 - 本地基准测试显示它比无知的方法 (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;
}
这里的人似乎都把事情复杂化得可笑。将 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;
}
这适用于任何图像,无论其颜色格式如何。
我有一张 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
class 不关心调色板,它假定 8bpp 图像始终是灰度并且 return 只会是灰色。
此外,class 远非快速,因为它将位图数据复制到另一个数组并返回,在不需要时创建 Color
等。如果你真的想要性能,你会自己处理.
你有两个选择:
直接使用
Bitmap
中的GetPixel
和SetPixel
。它会正常工作。先将8bpp的调色板图像复制成32/24bpp的图像,然后使用class进行处理
我尝试了一种使用非托管代码的手动方法 - 本地基准测试显示它比无知的方法 (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;
}
这里的人似乎都把事情复杂化得可笑。将 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;
}
这适用于任何图像,无论其颜色格式如何。