C#打印像素值

3

I have a 8 bit bitmap color image. when i do a

Color pixelcolor = b.GetPixel(j,i);    
Console.Write(pixelcolor.ToString() + " " );

I get

 Color [A=255, R=255, G=255, B=255]

我只需要获取8位值,而不是R、G、B、A分开的24位值。


这张图片是8位索引还是非索引的? - Emond
@ErnodeWeerd,老实说我从未遇到过非索引的8bpp格式。这意味着每个颜色分量只有2或3个比特,太小了无法存储有意义的颜色信息。 - Nyerguds
@Nyerguds - 按照规格书,没有遇到这种情况并不意味着它不可能发生。单色或四色图像并不那么离谱。 - Emond
没有 .Net 颜色格式支持这样的事情,因此对于在 .Net 中加载的图像来说,这些可能性是不相关的。而且,技术上来说,单色 8 位仍然被 .Net 视为索引颜色;调色板只是填充了 256 个灰度值。 - Nyerguds
4个回答

5

使用Bitmap类本身无法实现此操作。不过,您可以使用LockBits方法直接访问像素。

使用不安全的代码:(请记得先在项目中启用不安全代码

public static unsafe Byte GetIndexedPixel(Bitmap b, Int32 x, Int32 y)
{
    if (b.PixelFormat != PixelFormat.Format8bppIndexed) throw new ArgumentException("Image is not in 8 bit per pixel indexed format!");
    if (x < 0 || x >= b.Width) throw new ArgumentOutOfRangeException("x", string.Format("x should be in 0-{0}", b.Width));
    if (y < 0 || y >= b.Height) throw new ArgumentOutOfRangeException("y", string.Format("y should be in 0-{0}", b.Height));
    BitmapData data = b.LockBits(new Rectangle(0, 0, b.Width, b.Height), ImageLockMode.ReadOnly, b.PixelFormat);
    try
    {
        Byte* scan0 = (Byte*)data.Scan0;
        return scan0[x + y * data.Stride];
    }
    finally
    {
        if (data != null) b.UnlockBits(data);
    }
}

使用 Marshal.Copy 的安全替代方案:

public static Byte GetIndexedPixel(Bitmap b, Int32 x, Int32 y)
{
    if (b.PixelFormat != PixelFormat.Format8bppIndexed) throw new ArgumentException("Image is not in 8 bit per pixel indexed format!");
    if (x < 0 || x >= b.Width) throw new ArgumentOutOfRangeException("x", string.Format("x should be in 0-{0}", b.Width));
    if (y < 0 || y >= b.Height) throw new ArgumentOutOfRangeException("y", string.Format("y should be in 0-{0}", b.Height));
    BitmapData data = b.LockBits(new Rectangle(0, 0, b.Width, b.Height), ImageLockMode.ReadOnly, b.PixelFormat);
    try
    {
        Byte[] pixel = new Byte[1];
        Marshal.Copy(new IntPtr(data.Scan0.ToInt64() + x + y * data.Stride), pixel, 0, 1);
        return pixel[0];
    }
    finally
    {
        if (data != null) b.UnlockBits(data);
    }
}

您可以通过使用Marshal.Copy来获取字节,从而避免不安全的情况。 - Nyerguds
1
是的,但是为了查询一个像素而不得不复制整个缓冲区很烦人。但通常大多数甚至所有像素都会被读取,所以在这种情况下,Marshal.Copy是一个很好的选择。 - vidstige
2
使用这段代码本身也有与普通的 GetPixel 常见的问题相同的问题:当循环处理所有内容时,对于每个像素都需要锁定和解锁,在具有成千上万甚至数万像素的图像上操作非常缓慢,而仅检查字节数组的内容则非常快速。 - Nyerguds
请注意...您不能只使用 Marshal.Copyx + y*data.Stride 处精确地复制一个字节吗? - Nyerguds
啊,我在C#方面有太多的经验了;我不能再“建议”编辑了;我直接进行编辑。无论如何,我已经添加了它。我也修复了像素格式;在这种情况下,“indexed”不是有效的;它是所有可能的索引格式的组合。你需要在这里考虑图像的实际像素格式。我在做这个的时候意识到另一件事(但我没有添加它),那就是如果你锁定new Rectangle(x, y, 1, 1),你实际上只锁定了一个像素,然后data.Scan0就是你需要的字节的确切地址 ;) - Nyerguds
显示剩余3条评论

1

Bitmap类中的方法不能直接让您获取调色板索引。

您可以使用Palette属性获取图像的调色板,然后在那里查找颜色,但这是一个解决方法。

要直接获取调色板索引,您需要使用LockBits方法直接访问图像数据。您可以使用封送将数据复制到数组中,或者在不安全模式下使用指针进行访问。


Color 值中的 A 属性是 Alpha 组件。它可以具有 0 到 255 的值,其中 0 是完全透明的,255 是完全不透明的。


-1

你需要的值实际上是RGB,它们是颜色对应的红色绿色蓝色组件的8位位图值。

A是颜色的Alpha组件,表示透明度值。如果你不关心它,可以在字符串输出中省略它。


在8位索引位图上,像素不包含R、G、B和A的数据;这需要 多个 8位值,这将使图像每像素24或32位。8位图像中的每个像素都是一个 单个 8位值,它是要查找的颜色板中的 _索引_。 - Nyerguds

-1

如果您不想使用LockBits,可以这样做:

警告:此方法仅在调色板没有重复值且在设置pixelRGB后未被另一个线程更改时才有效。

/// <summary>
/// Gets the pixel value in bytes. Uses Bitmap GetPixel method.
/// </summary>
/// <param name="bmp">Bitmap</param>
/// <param name="location">Pixel location</param>
/// <returns>Pixel value</returns>
public static byte Get8bppImagePixel(Bitmap bmp, Point location)
{
    Color pixelRGB = bmp.GetPixel(location.X, location.Y);
    int pixel8bpp = Array.IndexOf(bmp.Palette.Entries, pixelRGB);
    return (byte)pixel8bpp;
}

如果调色板中存在重复项,则此方法不可靠。 - Nyerguds
@Nyerguds 代码使用当前位图调色板。唯一的问题是,如果另一个线程在获取pixelRGB之后更改了调色板,则会出现问题。 - Pedro77
不,我的意思是,Palette.Entries 可以包含同一种颜色多次。如果一个像素有某个颜色,但它在调色板上出现了两次,并且该像素恰好使用第二次出现的颜色,则此代码无法获取正确的索引。解决这个问题的一种方法是在此操作之前将图像的调色板设置为灰度渐变,并在操作完成后恢复它。 - Nyerguds
1
@Nyerguds 哦,我明白了。是的,你说得对。我会把这个观察结果加入到我的回答中。 - Pedro77

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接