【问题标题】:Getting pixels' colors of a monochrome image using C#使用 C# 获取单色图像的像素颜色
【发布时间】:2012-08-26 18:30:06
【问题描述】:

正如主题所说,我有一个.bmp 图像,我需要编写一个能够获取图像任何像素颜色的代码。这是一个 1bpp(索引)图像,因此颜色将是黑色或白色。这是我目前拥有的代码:

    //This method locks the bits of line of pixels
    private BitmapData LockLine(Bitmap bmp, int y)
    {
        Rectangle lineRect = new Rectangle(0, y, bmp.Width, 1);
        BitmapData line = bmp.LockBits(lineRect,
                                       ImageLockMode.ReadWrite,
                                       bmp.PixelFormat);
        return line;
    }
    //This method takes the BitmapData of a line of pixels
    //and returns the color of one which has the needed x coordinate
    private Color GetPixelColor(BitmapData data, int x)
    {
        //I am not sure if this line is correct
        IntPtr pPixel = data.Scan0 + x; 
        //The following code works for the 24bpp image:
        byte[] rgbValues = new byte[3];
        System.Runtime.InteropServices.Marshal.Copy(pPixel, rgbValues, 0, 3);
        return Color.FromArgb(rgbValues[2], rgbValues[1], rgbValues[0]);
    }

但是如何使它适用于 1bpp 图像?如果我从指针中只读取一个字节,它总是具有255 值,所以我假设我做错了什么。
请不要建议使用System.Drawing.Bitmap.GetPixel 方法,因为它工作得太慢,我希望代码尽可能快地工作。 提前致谢。

编辑: 这是可以正常工作的代码,以防万一有人需要:

    private Color GetPixelColor(BitmapData data, int x)
    {
        int byteIndex = x / 8;
        int bitIndex = x % 8;
        IntPtr pFirstPixel = data.Scan0+byteIndex;
        byte[] color = new byte[1];
        System.Runtime.InteropServices.Marshal.Copy(pFirstPixel, color, 0, 1);
        BitArray bits = new BitArray(color);
        return bits.Get(bitIndex) ? Color.Black : Color.White;
    }

【问题讨论】:

  • 即使像素为黑色,值也是255吗?
  • 当您只需要一个像素操作时,锁定位是没有意义的。您应该简单地使用 bmp 的 GetPixel 方法,而不锁定任何位或任何东西。
  • 如果你真的想要多个像素,那么你应该锁定包含你想要的所有像素的整个区域,并使用 Scan0 的索引逻辑访问这些像素。
  • 但是,对于单个像素获取/设置,您应该使用 GetPixel/SetPixel,因为单次使用它更快。顺便说一句,锁定 1x1 矩形可能会有问题,因此您可以尝试锁定 2x2,即使您只使用该矩形的第一个像素(如果这样做,请记住处理 bmp 的右下边缘)。
  • 您的图像是每像素 1 位还是每像素 1 字节?因为你说的是​​24bpp,然后是1bpp,但看起来1bpp实际上意味着8bpp,或者不是?

标签: c# image image-processing monochrome


【解决方案1】:

好的,知道了!您需要从 BitmapData 读取位并将掩码应用于要提取颜色的位:

var bm = new Bitmap...

//lock all image bits
var bitmapData = bm.LockBits(new Rectangle(0, 0, bm.Width, bm.Height), ImageLockMode.ReadWrite, PixelFormat.Format1bppIndexed);

// this  will return the pixel index in the color pallete
// since is 1bpp it will return 0 or 1
int pixelColorIndex = GetIndexedPixel(50, 30, bitmapData);

// read the color from pallete
Color pixelColor = bm.Pallete.Entries[pixelColorIndex];

方法如下:

// x, y relative to the locked area
private int GetIndexedPixel(int x, int y, BitmapData bitmapData)
{
    var index = y * bitmapData.Stride + (x >> 3);
    var chunk = Marshal.ReadByte(bitmapData.Scan0, index);

    var mask = (byte)(0x80 >> (x & 0x7));
    return (chunk & mask) == mask ? 1 : 0;
}

2轮计算像素位置:

1) 找到'x'中的像素为(x / 8)的字节:每个字节包含8个像素,找到字节除以x/8向下舍入:58 >> 3 = 7,像素在当前行的第 7 个字节(步幅)

2) 查找当前字节上的位 (x % 8):执行x & 0x7 只获取最左边的 3 位 (x % 8)

例子:

x = 58 
// x / 8 - the pixel is on byte 7
byte = 58 >> 3 = 58 / 8 = 7 

// x % 8 - byte 7, bit 2
bitPosition = 58 & 0x7 = 2 

// the pixels are read from left to right, so we start with 0x80 and then shift right. 
mask = 0x80 >> bitPosition = 1000 0000b >> 2 =  0010 0000b 

【讨论】:

  • 完美运行!非常感谢。请问你是怎么找到计算掩码的方法的?
【解决方案2】:

首先,如果你需要在一次操作中读取单​​个像素,那么GetPixel在性能上将是相当的。昂贵的操作是锁定位,即。你应该坚持BitmapData 来完成你需要的所有阅读,并且只在最后关闭它 - 但记得关闭它!

您的像素格式似乎有些混乱,但我们假设它是正确的 1bpp。那么每个像素会占用一个位,一个字节就会有8个像素的数据。因此,您的索引计算不正确。字节的位置在x/8,那么你需要取位x%8

【讨论】:

  • 感谢您的回答!我已经提到图像是 1bpp indexed。这有关系吗?我只是对这个事实感到困惑,因为如果所有位都指示像素颜色,那么索引存储在哪里?还是我误解了什么?
  • 索引只是意味着颜色值是颜色表的索引。但由于 1 位仅留下 2 种可能性,因此您可以放心地假设这两种颜色是黑色和白色。
  • 实际上你可以在调色板上有any两种颜色,不一定是黑/白。它可以是蓝色/红色、绿色/黄色等... Bitmap.Pallete.Entries 保存位图的调色板颜色。
猜你喜欢
  • 2010-10-10
  • 2013-07-21
  • 1970-01-01
  • 2019-07-27
  • 1970-01-01
  • 2014-04-18
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多