将24bpp转换为8bpp - 如何修复此方法,以便返回具有正步幅的位图

时间:2013-06-14 19:03:10

标签: c# image-processing bitmap bitmapdata

我正在寻找一种将Bitmap从24bpp转换为8bpp的快速方法。

我在此网站Programmer » 1bpp in C#找到了解决方案。它可以工作,但结果Bitmap步幅是负的!

How can I fix the stride of the image?

我已经尝试过这个:
Bitmap.Clone:大步仍然是负面的 新位图(b0):创建32bpp图像...¬¬

编辑: 保存到文件,读回来并制作像素数据的深层复制(Marshal.Copy或memcopy)以从文件中分离出Bitmap ..但是“有点”难看......

代码如下:

    /// <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;
    }

1 个答案:

答案 0 :(得分:2)

根据BITMAPINFOHEADER结构的文档:

  

biHeight   指定位图的高度(以像素为单位)。

     

如果biHeight是正数,则位图是自下而上的DIB及其原点   是左下角。

     

如果biHeight为负数,则位图是自上而下的DIB及其原点   是左上角。

     

如果biHeight为负数,表示自上而下的DIB,则必须使用biCompression   BI_RGB或BI_BITFIELDS。自上而下的DIB无法压缩。

因此,如果您更改发布到的代码中的行:

bmi.biHeight = -h;

然后它将创建一个具有正向步幅的自上而下的位图。

-

还有其他可能性。

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

对我来说,这会创建一个具有正向步幅的位图。但随后,原始位图有了积极的进步。我不知道如果我在负步幅的位图上调用它会怎么做。

所有这一切,我想知道你真正试图解决的问题。如果位图是自下而上还是自上而下,为什么重要?