Converting 24bpp to 8bpp - How can I fix this method so it returns a Bitmap with positive stride
Asked Answered
E

1

0

I'm looking for a fast way to convert a Bitmap from 24bpp to 8bpp.

I found a solution at this site Programmer » 1bpp in C#. It works, but the resulting Bitmap stride is negative!

How can I fix the stride of the image?

I've already tried this:
Bitmap.Clone: stride still negative
new Bitmap(b0): Creates a 32bpp image... ¬¬

EDIT: Save to a file, read it back and make a deep copy (Marshal.Copy or memcopy) of pixel data to detach the Bitmap from the file works.. But is "kind of" ugly...

The code is below:

    /// <summary>
    /// Copy a bitmap into a 1bpp/8bpp bitmap of the same dimensions, fast
    /// </summary>
    /// <param name="source">original bitmap</param>
    /// <param name="bpp">1 or 8, target bpp</param>
    /// <returns>a 1bpp copy of the bitmap</returns>
    /// <url>http://www.wischik.com/lu/programmer/1bpp.html</url>
    private static Bitmap ConvertTo1or8bppNegativeStride(Bitmap source, int bpp = 8)
    {
        if (bpp != 1 && bpp != 8) throw new System.ArgumentException("1 or 8", "bpp");

        // Plan: built into Windows GDI is the ability to convert
        // bitmaps from one format to another. Most of the time, this
        // job is actually done by the graphics hardware accelerator card
        // and so is extremely fast. The rest of the time, the job is done by
        // very fast native code.
        // We will call into this GDI functionality from C#. Our plan:
        // (1) Convert our Bitmap into a GDI hbitmap (ie. copy unmanaged->managed)
        // (2) Create a GDI monochrome hbitmap
        // (3) Use GDI "BitBlt" function to copy from hbitmap into monochrome (as above)
        // (4) Convert the monochrone hbitmap into a Bitmap (ie. copy unmanaged->managed)

        int w = source.Width, h = source.Height;
        IntPtr hbm = source.GetHbitmap(); // this is step (1)
        //
        // Step (2): create the monochrome bitmap.
        // "BITMAPINFO" is an interop-struct which we define below.
        // In GDI terms, it's a BITMAPHEADERINFO followed by an array of two RGBQUADs
        BITMAPINFO bmi = new BITMAPINFO();
        bmi.biSize = 40;  // the size of the BITMAPHEADERINFO struct
        bmi.biWidth = w;
        bmi.biHeight = h;
        bmi.biPlanes = 1; // "planes" are confusing. We always use just 1. Read MSDN for more info.
        bmi.biBitCount = (short)bpp; // ie. 1bpp or 8bpp
        bmi.biCompression = BI_RGB; // ie. the pixels in our RGBQUAD table are stored as RGBs, not palette indexes
        bmi.biSizeImage = (uint)(((w + 7) & 0xFFFFFFF8) * h / 8);
        bmi.biXPelsPerMeter = 1000000; // not really important
        bmi.biYPelsPerMeter = 1000000; // not really important
        // Now for the colour table.
        uint ncols = (uint)1 << bpp; // 2 colours for 1bpp; 256 colours for 8bpp
        bmi.biClrUsed = ncols;
        bmi.biClrImportant = ncols;
        bmi.cols = new uint[256]; // The structure always has fixed size 256, even if we end up using fewer colours
        if (bpp == 1)
        {
            bmi.cols[0] = MAKERGB(0, 0, 0);
            bmi.cols[1] = MAKERGB(255, 255, 255);
        }
        else
        {
            for (int i = 0; i < ncols; i++)
                bmi.cols[i] = MAKERGB(i, i, i);
        }
        // For 8bpp we've created an palette with just greyscale colours.
        // You can set up any palette you want here. Here are some possibilities:
        // greyscale: for (int i=0; i<256; i++) bmi.cols[i]=MAKERGB(i,i,i);
        // rainbow: bmi.biClrUsed=216; bmi.biClrImportant=216; int[] colv=new int[6]{0,51,102,153,204,255};
        //          for (int i=0; i<216; i++) bmi.cols[i]=MAKERGB(colv[i/36],colv[(i/6)%6],colv[i%6]);
        // optimal: a difficult topic: http://en.wikipedia.org/wiki/Color_quantization
        // 
        // Now create the indexed bitmap "hbm0"
        IntPtr bits0; // not used for our purposes. It returns a pointer to the raw bits that make up the bitmap.
        IntPtr hbm0 = CreateDIBSection(IntPtr.Zero, ref bmi, DIB_RGB_COLORS, out bits0, IntPtr.Zero, 0);
        //
        // Step (3): use GDI's BitBlt function to copy from original hbitmap into monocrhome bitmap
        // GDI programming is kind of confusing... nb. The GDI equivalent of "Graphics" is called a "DC".
        IntPtr sdc = GetDC(IntPtr.Zero);       // First we obtain the DC for the screen
        // Next, create a DC for the original hbitmap
        IntPtr hdc = CreateCompatibleDC(sdc); SelectObject(hdc, hbm);
        // and create a DC for the monochrome hbitmap
        IntPtr hdc0 = CreateCompatibleDC(sdc); SelectObject(hdc0, hbm0);
        // Now we can do the BitBlt:
        BitBlt(hdc0, 0, 0, w, h, hdc, 0, 0, SRCCOPY);
        // Step (4): convert this monochrome hbitmap back into a Bitmap:
        Bitmap b0 = Bitmap.FromHbitmap(hbm0);
        //
        // Finally some cleanup.
        DeleteDC(hdc);
        DeleteDC(hdc0);
        ReleaseDC(IntPtr.Zero, sdc);
        DeleteObject(hbm);
        DeleteObject(hbm0);

        //It have negative stride...
        return b0;
    }
Erythrocytometer answered 14/6, 2013 at 19:3 Comment(3)
A negative stride just indicates that the bitmap is bottom-up rather than top-down. Perhaps https://mcmap.net/q/1398581/-how-can-i-copy-the-pixel-data-from-a-bitmap-with-negative-stride/56778 has some info you can use.Tardy
Yeah, I already know that, and the author of the question that you posted is me. Nobody gave me a solution in that topic.Erythrocytometer
@HansPassant, it does not solve the problem. As I've said at my question description: "new Bitmap(b0): Creates a 32bpp image" and the point here is to get a 8bpp.Erythrocytometer
T
2

According to documentation for the BITMAPINFOHEADER structure:

biHeight Specifies the height of the bitmap, in pixels.

If biHeight is positive, the bitmap is a bottom-up DIB and its origin is the lower left corner.

If biHeight is negative, the bitmap is a top-down DIB and its origin is the upper left corner.

If biHeight is negative, indicating a top-down DIB, biCompression must be either BI_RGB or BI_BITFIELDS. Top-down DIBs cannot be compressed.

So if you change the line in the code you posted to:

bmi.biHeight = -h;

Then it'll create a top-down bitmap with a positive stride.

--

There are other possibilities.

var newBmp = srcBmp.Clone(new Rectangle(0, 0, srcBmp.Width, srcBmp.Height), PixelFormat.Format8bppIndexed);

For me, that creates a bitmap that has a positive stride. But then, the original bitmap has positive stride. I don't know what it'll do if I call it on a bitmap with negative stride.

All that said, I'm wondering what problem you're really trying to solve. Why does it matter if the bitmap is bottom up or top down?

Tardy answered 14/6, 2013 at 19:28 Comment(2)
Ow, I was hopping that would solve it, but sadly it doesn't, stride stills negative.Erythrocytometer
Graphics do not work with 8bpp images. To able to do copy the pixel data, using: CopyMemory(bmpDataDest.Scan0, bmpDataSrc.Scan0, (uint)lenght) or Marshal.Copy(buffer, 0, bmpData.Scan0, lenght); oy Buffer.Copy. Why copy and not just Clone()? To make a deep clone.Erythrocytometer

© 2022 - 2024 — McMap. All rights reserved.