5

i'm trying to edit the pixels of a 8bpp. Since this PixelFormat is indexed i'm aware that it uses a Color Table to map the pixel values.

Even though I can edit the bitmap by converting it to 24bpp, 8bpp editing is much faster (13ms vs 3ms). But, changing each value when accessing the 8bpp bitmap results in some random rgb colors even though the PixelFormat remains 8bpp.

I'm currently developing in C# and the algorithm is as follows:

  1. Load original Bitmap at 8bpp
  2. Create Empty temp Bitmap with 8bpp with the same size as the original
  3. LockBits of both bitmaps and, using P/Invoke, calling C++ method where I pass the Scan0 of each BitmapData object. (I used a C++ method as it offers better performance when iterating through the Bitmap's pixels)
  4. Create a int[256] palette according to some parameters and edit the temp bitmap bytes by passing the original's pixel values through the palette.
  5. UnlockBits.

My question is how can I edit the pixel values without having the strange rgb colors. Or, even better, how can I edit the 8bpp bitmap's Color Table?

Ian Boyd
  • 246,734
  • 253
  • 869
  • 1,219
Pedro Sá
  • 51
  • 1
  • 1
  • 2

3 Answers3

12

There is no need to move into C++ land or use P/Invoke at all; C# supports pointers and unsafe code perfectly fine; I may even hazard a guess that this is causing your problems.

The colors are likely coming from the default Palette. You'll find that changing the Palette shouldn't be slow at all. This is what I do to create a greyscale palette:

image = new Bitmap( _size.Width, _size.Height, PixelFormat.Format8bppIndexed);
ColorPalette pal = image.Palette;
for(int i=0;i<=255;i++) {
    // create greyscale color table
    pal.Entries[i] = Color.FromArgb(i, i, i);
}
image.Palette = pal; // you need to re-set this property to force the new ColorPalette
Dai
  • 141,631
  • 28
  • 261
  • 374
  • How do I apply the new palette to the old image? How about changing the size of the old image as well? – Leon Jul 18 '12 at 10:55
  • 1
    Just set the `Palette` property back. Resizing an image is different, use `Graphics.DrawImage` on a new image of the right dumensions for that. – Dai May 28 '13 at 06:59
  • Graphics.DrawImage is not supported on indexed pixel formats, and converting to non-indexed negates the speed benefit of using indexed formats. – marknuzz Feb 17 '16 at 00:44
  • 1
    If you want resizing on indexed images in c#, you're just going to have to write algorithms yourself based on handling raw byte arrays, and import the result into a new bitmap using LockBits. Or resize in hi-colour and then match the pixels back to their closest palette match. – Nyerguds Nov 24 '19 at 16:37
  • 1
    @nyerguds oh hai from C&C :3 – Dai Nov 24 '19 at 19:21
4

Imagine that your indexed 8ppp graysacle are stored in a linear array dataB (for speed)

Try this code:

//Create 8bpp bitmap and look bitmap data
bmp = new Bitmap(width, height, PixelFormat.Format8bppIndexed);
bmp.SetResolution(horizontalResolution, verticalResolution);
bmpData = bmp.LockBits(new Rectangle(0, 0, width, height), System.Drawing.Imaging.ImageLockMode.WriteOnly, PixelFormat.Format8bppIndexed);

//Create grayscale color table
ColorPalette palette = bmp.Palette;
for (int i = 0; i < 256; i++)
    palette.Entries[i] = Color.FromArgb(i, i, i);
bmp.Palette = palette;

//write data to bitmap
int dataCount = 0;
int stride = bmpData.Stride < 0 ? -bmpData.Stride : bmpData.Stride;
unsafe
{
    byte* row = (byte*)bmpData.Scan0;
    for (int f = 0; f < height; f++)
    {
        for (int w = 0; w < width; w++)
        {
            row[w] = (byte)Math.Min(255, Math.Max(0, dataB[dataCount]));
            dataCount++;
        }
        row += stride;
    }
}

//Unlock bitmap data
bmp.UnlockBits(bmpData);
0

Have you tried loading a System.Drawing.Image instead? That class gives you access to the colour palette. You can then wrap the System.Drawing.Image up as a System.Drawing.Bitmap once the palette is set.

I'm not sure how System.Drawing.BitMap.SetPixel() works with indexed coloured images. It could be it tries to map it to the closest colour in the palette.

Paul Ruane
  • 37,459
  • 12
  • 63
  • 82
  • Thanks for your answer. I know I can modify the bitmap's palette by changing the Bitmap's Palette property. But doing this is too slow. I was hoping that someone could tell me how to access the Color Table pointer so that I could modify it directly. SetPixel does not work and is also very sluggish. what i don't understand is why the pixel is assigned with a rgb color when i change it's value from 0 (black) to 100 (should be gray but is red). – Pedro Sá Apr 07 '10 at 15:20
  • Um. `Bitmap` inherits from `Image`... and thus has that same Palette. And you can copy palettes simply with `bm2.Palette = bm1.Palette`. – Nyerguds Jan 22 '18 at 21:48