Получение цветов пикселей монохромного изображения с помощью C#
Как говорит субъект, у меня есть .bmp
image и мне нужно написать код, который сможет получить цвет любого пикселя изображения. Это изображение с индексом 1 бит на пиксель, поэтому цвет будет черный или белый. Вот код, который у меня сейчас есть:
//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;
}
2 ответа
Хорошо понял! Вам нужно прочитать биты из 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): Do 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
Прежде всего, если вам нужно прочитать один пиксель за одну операцию, то GetPixel
будет эквивалентен по производительности. Дорогая операция - блокировка битов, т.е. Вы должны держаться за BitmapData
для чтения всего, что вам нужно, и только закройте его в конце - но не забудьте закрыть его!
Кажется, есть некоторая путаница с форматом вашего пикселя, но давайте предположим, что это правильный 1bpp. Тогда каждый пиксель будет занимать один бит, и в байте будут данные для 8 пикселей. Следовательно, ваш расчет индексации неверен. Расположение байта будет в x/8
тогда нужно взять немного x%8
,