Reputation: 2990
I have an 8bpp image with a custom palete that holds a colored picture.
Now, I'm trying to convert it to PixelFormat.Format24bppRgb
format picture. I'm using direct pixels access using the code from here http://www.codeproject.com/Tips/240428/Work-with-bitmap-faster-with-Csharp
and the usage is
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);
However the ending result is a grayscale image even though I do copy the original palette.
What am I doing wrong here? How do I get a 24bpp
colored image from a 8bpp
picture which actually has colors?
Upvotes: 2
Views: 1864
Reputation: 5629
People here seem to all be ridiculously overcomplicating matters. Converting an 8BPP image to 24BPP or 32BPP doesn't need any special code.
The only thing that's difficult about 8BPP images is manipulating them, and, you don't need to do that at all; your end result isn't one of these problematic 8BPP images.
You can do it in less than five lines:
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;
}
This will work with any image, regardless of its colour format.
Upvotes: 0
Reputation: 27095
I tried a manual approach using unmanaged code - local benchmark shows it to be 99.7% faster than the ignorant (Bitmap.GetPixel
> Bitmap.SetPixel
) approach.
Basically, we use the LockBits
pointer and assign bytes one by one based on the color palette.
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);
}
The code could be improved to write 4 bytes at a time from the color pallette, reducing the amount of random memory access. My local benchmark showed the performance of this improved by a further 25%. Note the difference in building the uint
color bytes - the alignment of bytes in a uint
was opposite of what I expected.
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);
}
I didn't create the bitmap in the method for benchmarking purposes, it should be called as such:
static Bitmap To24Bpp(Bitmap source)
{
var dest = new Bitmap(source.Width, source.Height, PixelFormat.Format24bppRgb);
To24BppUintAssignment(source, dest);
return dest;
}
Upvotes: 3
Reputation: 31153
The LockBitmap
class you are using doesn't care about palette, it assumes 8bpp images are always grayscale and will return only grays.
Also the class is far from fast since it copies bitmap data to another array and back, creates Color
when not necessarily needed etc. If you really want performance you will do the handling yourself.
You have two choices:
use GetPixel
and SetPixel
from Bitmap
directly. It will work as it should.
copy the 8bpp palette image into a 32/24bpp image first, then use that class for processing
Upvotes: 1