彩色到单色的转换

Color to Monochrome conversion

参见:Save a 32-bit Bitmap as 1-bit .bmp file in C#

清单 #1

    public static Bitmap BitmapTo1Bpp(Bitmap source)
    {
        int Width = source.Width; 
        int Height = source.Height; 

        Bitmap dest = new Bitmap(Width, Height, PixelFormat.Format1bppIndexed);
        BitmapData destBmpData = dest.LockBits(new Rectangle(0, 0, Width, Height), ImageLockMode.ReadWrite, PixelFormat.Format1bppIndexed);

        byte[] destBytes = new byte[(Width + 7) / 8];//19 bytes

        for (int y = 0; y < Height; y++)
        {
            for (int x = 0; x < Width; x++)
            {
                Color c = source.GetPixel(x, y);

                if (x % 8 == 0)
                {
                    destBytes[x / 8] = 0;
                }
                if (c.GetBrightness() >= 0.5)
                {
                    destBytes[x / 8] |= (byte)(0x80 >> (x % 8));
                }
            }
            Marshal.Copy(destBytes, 0, (IntPtr)((long)destBmpData.Scan0 + destBmpData.Stride * y), destBytes.Length);
        }

        dest.UnlockBits(destBmpData);
        return dest;
    }

清单 #2

    public static Bitmap BitmapTo1Bpp222(Bitmap source)
    {
        int Width = source.Width; 
        int Height = source.Height; 

        Bitmap dest = new Bitmap(Width, Height, PixelFormat.Format1bppIndexed);
        BitmapData destBmpData = dest.LockBits(new Rectangle(0, 0, Width, Height), ImageLockMode.ReadWrite, PixelFormat.Format1bppIndexed);

        int destStride = destBmpData.Stride;
        int destSize = Math.Abs(destStride) * Height;

        byte[] destBytes = new byte[destSize];

        for (int y = 0; y < Height; y++)
        {
            for (int x = 0; x < Width; x++)
            {
                Color c = source.GetPixel(x, y);

                if (x % 8 == 0)
                {
                    destBytes[x*y / 8] = 0;
                }
                if (c.GetBrightness() >= 0.5)
                {
                    destBytes[x*y / 8] |= (byte)(0x80 >> (x % 8));
                }
            } 
        } 
        Marshal.Copy(destBytes, 0, destBmpData.Scan0, destBytes.Length);
        dest.UnlockBits(destBmpData);
        return dest;
    } 

查看Marshal.Copy()的位置。

为什么清单 #1 有效,而清单 #2 无效?

什么样的修改可以使清单 #2 工作?

这两个都太复杂了。 LockBits 可以 数据转换为 1bpp。只需以 1bpp 格式打开源,将其数据复制到新的 1bpp 图像中,就大功告成了。

我也对 GetPixelLockBits 的组合感到困惑。通常,使用 LockBits 意味着您意识到 GetPixel 是一种极其缓慢的时间浪费,它会在 每次调用时在内部执行 LockBits .

public static Bitmap BitmapTo1Bpp(Bitmap source)
{
    Rectangle rect = new Rectangle(0, 0, source.Width, source.Height);
    Bitmap dest = new Bitmap(rect.Width, rect.Height, PixelFormat.Format1bppIndexed);
    dest.SetResolution(source.HorizontalResolution, source.VerticalResolution);
    BitmapData sourceData = source.LockBits(rect, ImageLockMode.ReadOnly, PixelFormat.Format1bppIndexed);
    BitmapData targetData = dest.LockBits(rect, ImageLockMode.WriteOnly, PixelFormat.Format1bppIndexed);
    Int32 actualDataWidth = (rect.Width + 7) / 8;
    Int32 h = source.Height;
    Int32 origStride = sourceData.Stride;
    Int32 targetStride = targetData.Stride;
    // buffer for one line of image data.
    Byte[] imageData = new Byte[actualDataWidth];
    Int64 sourcePos = sourceData.Scan0.ToInt64();
    Int64 destPos = targetData.Scan0.ToInt64();
    // Copy line by line, skipping by stride but copying actual data width
    for (Int32 y = 0; y < h; y++)
    {
        Marshal.Copy(new IntPtr(sourcePos), imageData, 0, actualDataWidth);
        Marshal.Copy(imageData, 0, new IntPtr(destPos), actualDataWidth);
        sourcePos += origStride;
        destPos += targetStride;
    }
    dest.UnlockBits(targetData);
    source.UnlockBits(sourceData);
    return dest;
}

请注意,如果纯黑白的结果不是 1bpp,则应避免将数据转换为索引格式。索引格式是调色板,这样做不会减少接近图像颜色的优化调色板;它只会将图像上的颜色更改为与此位深度的标准调色板上最接近的颜色。对于 1bpp,这只是黑色和白色,这是完美的,但对于 4bpp 和 8bpp,它会给出非常糟糕的结果。

另请注意,出于某种原因,您无法从较高索引像素格式转换为较低索引像素格式;它会抛出异常。由于可以使用 new Bitmap(Bitmap) 构造函数将位图转换为 32 位,因此可以通过调用如下代码轻松避免此问题:

public static Bitmap ConvertTo1Bpp(Bitmap source)
{
    PixelFormat sourcePf = source.PixelFormat;
    if ((sourcePf & PixelFormat.Indexed) == 0 || Image.GetPixelFormatSize(sourcePf) == 1)
        return BitmapTo1Bpp(source);
    using (Bitmap bm32 = new Bitmap(source))
        return BitmapTo1Bpp(bm32);
}