My code works for some images but not others.
Image that works:
Image that doesn’t work (same image but smaller):
I first load the image and create an array of raw pixel data that for instance only includes the red channel data.
public byte[] GetPixels(Bitmap bmp)
{
BitmapData bmpData = bmp.LockBits(new Rectangle(0, 0, bmp.Width, bmp.Height), ImageLockMode.ReadOnly, bmp.PixelFormat);
int length = bmpData.Stride * bmpData.Height;
int bytesPerPixel = Bitmap.GetPixelFormatSize(bmp.PixelFormat) / 8;
byte[] pixelData = new byte[length];
byte[] newPixelData = new byte[length / bytesPerPixel];
Marshal.Copy(bmpData.Scan0, pixelData, 0, length);
for(int i = 0; i < newPixelData.Length; i++)
{
newPixelData[i] = pixelData[i*bytesPerPixel];
}
bmp.UnlockBits(bmpData);
bmp.Dispose();
return newPixelData;
}
Then I call a method that turns that byte[] into an 8bit per pixel indexed bitmap. I want everything else to be the same so width, height, resolution is all derived from the original image.
public Bitmap makeBitmap(byte[] bytes)
{
Bitmap bmp;
PixelFormat pxFormat = PixelFormat.Format8bppIndexed;
int stride = img.Width;
unsafe
{
fixed (byte* ptr = bytes)
{
bmp = new Bitmap(img.Width, img.Height, stride, pxFormat, (IntPtr)ptr);
}
}
bmp.SetResolution(img.HorizontalResolution,img.VerticalResolution);
ColorPalette palette = bmp.Palette;
for (int i = 0; i < 256; i++)
{
palette.Entries[i] = Color.FromArgb(i,i,i);
}
bmp.Palette = palette;
return bmp;
}
This program works fine with the 1st image I showed but won’t work with the second. here is the error I get:
The Stride seems to be the problem but i’m not sure how to fix it.. Can anyone advise on how to fix this? Imagine photoshop split channels. That’s sort of what I’m trying to do each channel will be a separate grayscale image). I’m doing calculations with the channel data as well so having the raw pixel data in a byte[] has really helped me cut down on memory usage with the larger images.
2
Answers
As I assumed. It’s all about
Stride
. This code works for 24-, 32-bit and higherimg
. The 16-bit format requires bitwise operations.The first problem is the stride, from the documentation:
578 is not an even multiple of four, so will violate this.
The second problem is the pointer:
So after the fixed block has ended the memory may move around, and the pointer becomes invalid.
The correct way to do this is to create the new bitmap with the correct width, height, and pixel format, and write a copy function that copies the data, taking the target and source stride into account. For example something like this:
You seem familiar with how to get the
Scan0
pointer, so just create your target bitmap, create bitmapData objects, and feed this function the source and target pointers, strides for each, as well as the image size and a channel parameter to specify what color channel you want.Note that is is fairly easy to get the channel order mixed up. PixelFormat.Format32bppArgb and WPFs PixelFormats.Bgra are the same thing, but named differently, due to endianess. Easiest way is to just check the result against a known result to make sure you got it right.