use*_*312 0 c# image-processing bitmapdata
BitmapLocker类用于快速读取/写入Bitmap图像文件中的像素。
但是,Color GetPixel(int x, int y)并void SetPixel(int x, int y, Color c)不能处理1位和4位图像。
public class BitmapLocker : IDisposable
{
//private properties
Bitmap _bitmap = null;
BitmapData _bitmapData = null;
private byte[] _imageData = null;
//public properties
public bool IsLocked { get; set; }
public IntPtr IntegerPointer { get; private set; }
public int Width
{
get
{
if (IsLocked == false) throw new InvalidOperationException("not locked");
return _bitmapData.Width;
}
}
public int Height
{
get
{
if (IsLocked == false) throw new InvalidOperationException("not locked");
return _bitmapData.Height;
}
}
public int Stride
{
get
{
if (IsLocked == false) throw new InvalidOperationException("not locked");
return _bitmapData.Stride;
}
}
public int ColorDepth
{
get
{
if (IsLocked == false) throw new InvalidOperationException("not locked");
return Bitmap.GetPixelFormatSize(_bitmapData.PixelFormat);
}
}
public int Channels
{
get
{
if (IsLocked == false) throw new InvalidOperationException("not locked");
return ColorDepth / 8;
}
}
public int PaddingOffset
{
get
{
if (IsLocked == false) throw new InvalidOperationException("not locked");
return _bitmapData.Stride - (_bitmapData.Width * Channels);
}
}
public PixelFormat ImagePixelFormat
{
get
{
if (IsLocked == false) throw new InvalidOperationException("not locked");
return _bitmapData.PixelFormat;
}
}
//public bool IsGrayscale
//{
// get
// {
// if (IsLocked == false) throw new InvalidOperationException("not locked");
// return Grayscale.IsGrayscale(_bitmap);
// }
//}
//Constructor
public BitmapLocker(Bitmap source)
{
IsLocked = false;
IntegerPointer = IntPtr.Zero;
this._bitmap = source;
}
/// Lock bitmap
public void Lock()
{
if (IsLocked == false)
{
try
{
// Lock bitmap (so that no movement of data by .NET framework) and return bitmap data
_bitmapData = _bitmap.LockBits(
new Rectangle(0, 0, _bitmap.Width, _bitmap.Height),
ImageLockMode.ReadWrite,
_bitmap.PixelFormat);
// Create byte array to copy pixel values
int noOfBytesNeededForStorage = Math.Abs(_bitmapData.Stride) * _bitmapData.Height;
_imageData = new byte[noOfBytesNeededForStorage];
IntegerPointer = _bitmapData.Scan0;
// Copy data from IntegerPointer to _imageData
Marshal.Copy(IntegerPointer, _imageData, 0, _imageData.Length);
IsLocked = true;
}
catch (Exception)
{
throw;
}
}
else
{
throw new Exception("Bitmap is already locked.");
}
}
/// Unlock bitmap
public void Unlock()
{
if (IsLocked == true)
{
try
{
// Copy data from _imageData to IntegerPointer
Marshal.Copy(_imageData, 0, IntegerPointer, _imageData.Length);
// Unlock bitmap data
_bitmap.UnlockBits(_bitmapData);
IsLocked = false;
}
catch (Exception)
{
throw;
}
}
else
{
throw new Exception("Bitmap is not locked.");
}
}
public Color GetPixel(int x, int y)
{
Color clr = Color.Empty;
// Get color components count
int cCount = ColorDepth / 8;
// Get start index of the specified pixel
int i = (Stride > 0 ? y : y - Height + 1) * Stride + x * cCount;
int dataLength = _imageData.Length - cCount;
if (i > dataLength)
{
throw new IndexOutOfRangeException();
}
if (ColorDepth == 32) // For 32 bpp get Red, Green, Blue and Alpha
{
byte b = _imageData[i];
byte g = _imageData[i + 1];
byte r = _imageData[i + 2];
byte a = _imageData[i + 3]; // a
clr = Color.FromArgb(a, r, g, b);
}
if (ColorDepth == 24) // For 24 bpp get Red, Green and Blue
{
byte b = _imageData[i];
byte g = _imageData[i + 1];
byte r = _imageData[i + 2];
clr = Color.FromArgb(r, g, b);
}
if (ColorDepth == 1 || ColorDepth == 4 || ColorDepth == 8)
// For 8 bpp get color value (Red, Green and Blue values are the same)
{
byte c = _imageData[i];
clr = Color.FromArgb(c, c, c);
}
return clr;
}
public void SetPixel(int x, int y, Color color)
{
if (!IsLocked) throw new Exception();
// Get color components count
int cCount = ColorDepth / 8;
// Get start index of the specified pixel
int i = (Stride > 0 ? y : y - Height + 1) * Stride + x * cCount;
try
{
if (ColorDepth == 32) // For 32 bpp set Red, Green, Blue and Alpha
{
_imageData[i] = color.B;
_imageData[i + 1] = color.G;
_imageData[i + 2] = color.R;
_imageData[i + 3] = color.A;
}
if (ColorDepth == 24) // For 24 bpp set Red, Green and Blue
{
_imageData[i] = color.B;
_imageData[i + 1] = color.G;
_imageData[i + 2] = color.R;
}
if (ColorDepth == 1 || ColorDepth == 4 || ColorDepth == 8)
// For 8 bpp set color value (Red, Green and Blue values are the same)
{
_imageData[i] = color.B;
}
}
catch (Exception ex)
{
throw new Exception("(" + x + ", " + y + "), " + _imageData.Length + ", " + ex.Message + ", i=" + i);
}
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (disposing)
{
// free managed resources
_bitmap = null;
_bitmapData = null;
_imageData = null;
IntegerPointer = IntPtr.Zero;
}
}
}
Run Code Online (Sandbox Code Playgroud)
例如,以下代码显示全黑输出:
public class MainClass
{
public static void Main(string [] args)
{
Bitmap source = (Bitmap)Bitmap.FromFile(@"1_bit__parrot__monochrome.png");
BitmapLocker locker = new BitmapLocker(source);
locker.Lock();
Bitmap dest = new Bitmap(source.Width, source.Height, locker.ImagePixelFormat);
BitmapLocker locker2 = new BitmapLocker(dest);
locker2.Lock();
for (int h = 0; h < locker.Height; h++)
{
for (int w = 0; w < locker.Width; w++)
{
locker2.SetPixel(w,h,locker.GetPixel(w,h));
}
}
locker2.Unlock();
locker.Unlock();
dest.Palette = source.Palette; // copy color palette too!
PictureDisplayForm f = new PictureDisplayForm(source, dest);
f.ShowDialog();
}
}
Run Code Online (Sandbox Code Playgroud)
如何更正此代码以使其可以处理 1 位和 4 位图像?
.
.
样本输入
对于小于 8 位的像素格式,一个以上的像素被打包成一个字节。因此,对于 8、4 和 1 位格式,您不能有这样的全能语句:
if (ColorDepth == 1 || ColorDepth == 4 || ColorDepth == 8)
{
byte c = _imageData[i];
clr = Color.FromArgb(c, c, c);
}
Run Code Online (Sandbox Code Playgroud)
相反,基于像素格式,在检索像素数据时,必须计算字节中的位位置并从字节中提取适当的位——在 4-的情况下,这将是“高”或“低”位位图像或在 1 位图像的情况下的单个位。相反,在设置像素数据时,仅需要更改字节中的某些位(基于像素格式)。
假设我们有一个 4 位格式的图像。图像数据可能如下所示:
Run Code Online (Sandbox Code Playgroud)bit index: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | 0 | 1 | 0 | 0 | 1 | 0 | 1 | 0 | 0 | 0 | 1 | 1 | 1 | 1 | 0 | 0 | 1 | 0 | 1 | 1 | 1 | 0 | 0 | 1 | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ byte index: 0 1 2 pixel index: 0 1 2 3 4 5
这种格式每字节包含两个像素。因此,在检索像素数据时,我们首先计算像素的位索引:
int biti = (Stride > 0 ? y : y - Height + 1) * Stride * 8 + x * ColorDepth;
Run Code Online (Sandbox Code Playgroud)
Stride是单行中的字节数,因此只需将其乘以高度 * 8(对于一个字节中的 8 位)并加上宽度 * ColorDepth(对于每像素的位数)。
然后我们需要弄清楚我们是要检索字节中的前四位还是后四位。为此,我们只需计算bitindex mod 8。显然,如果像素以字节开头,则为 0(例如8 mod 8 = 0),否则为 4。基于此,如果我们想要前四位,我们将字节移动 4。C# 将前四位清零:
Run Code Online (Sandbox Code Playgroud)+-----------------+ |+---+---+---+---+|---+---+---+---+ +---+---+---+---+---+---+---+---+ || 0 | 0 | 1 | 1 || 1 | 1 | 0 | 0 | => | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 1 | |+---+---+---+---+|---+---+---+---+ +---+---+---+---+---+---+---+---+ +-----------------+ ===============>>
另一方面,如果我们想要最后四位,我们AND将图像数据字节与前四位清零的字节:
Run Code Online (Sandbox Code Playgroud)+---+---+---+---+---+---+---+---+ | 0 | 0 | 1 | 1 | 1 | 1 | 0 | 0 | +---+---+---+---+---+---+---+---+ AND +---+---+---+---+---+---+---+---+ | 0 | 0 | 0 | 0 | 1 | 1 | 1 | 1 | +---+---+---+---+---+---+---+---+ = +---+---+---+---+---+---+---+---+ | 0 | 0 | 0 | 0 | 1 | 1 | 0 | 0 | +---+---+---+---+---+---+---+---+
在代码中,所有这些看起来像:
byte c = 0;
if (biti % 8 == 0)
{
c = (byte)(_imageData[i] >> 4);
}
else
{
c = (byte)(_imageData[i] & 0xF);
}
Run Code Online (Sandbox Code Playgroud)
对于 1 位单色图像,我们希望获得单个位。为此,我们AND使用一个字节将所有其他位清零(“掩码”)的图像数据字节。例如,如果我们想获取索引 5 处的位,我们会这样做:
Run Code Online (Sandbox Code Playgroud)+---+---+---+---+---+---+---+---+ | 0 | 0 | 1 | 1 | 1 | 1 | 0 | 0 | +---+---+---+---+---+---+---+---+ AND +---+---+---+---+---+---+---+---+ | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | +---+---+---+---+---+---+---+---+ = +---+---+---+---+---+---+---+---+ | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | +---+---+---+---+---+---+---+---+
如果结果为零,则我们知道该位为零,否则该位为“设置”。在代码中:
byte mask = (byte)(1 << bbi);
byte c = (byte)((_imageData[i] & mask) != 0 ? 1 : 0);
Run Code Online (Sandbox Code Playgroud)
一旦我们检索到像素数据,让我们检索实际颜色,因为该GetPixel函数返回一个Color对象。对于 8 位、4 位和 1 位图像,像素数据实际上表示调色板的索引。调色板看起来像这样:
Run Code Online (Sandbox Code Playgroud)============= +-----+-----+-----++-----+-----+-----++-----+-----+-----+ | R | G | B || R | G | B || R | G | B | Color +-----+-----+-----++-----+-----+-----++-----+-----+-----+ | 000 | 016 | 005 || 020 | 120 | 053 || 117 | 002 | 209 | ============= +-----+-----+-----++-----+-----+-----++-----+-----+-----+ | || || | Index | 0 || 1 || 2 | | || || | ============= +-----------------++-----------------++-----------------+
我们可以访问调色板,因此要检索颜色:
clr = Palette.Entries[c];
Run Code Online (Sandbox Code Playgroud)
c检索到的像素数据在哪里。
为设置像素数据做了类似的事情。在 C# 中有很多关于位操作的信息,例如这里、这里和这里。
将它们放在一起,与您现有的代码保持一致:
public class BitmapLocker : IDisposable
{
//private properties
Bitmap _bitmap = null;
BitmapData _bitmapData = null;
private byte[] _imageData = null;
//public properties
public bool IsLocked { get; set; }
public IntPtr IntegerPointer { get; private set; }
public int Width
{
get
{
if (IsLocked == false) throw new InvalidOperationException("not locked");
return _bitmapData.Width;
}
}
public int Height
{
get
{
if (IsLocked == false) throw new InvalidOperationException("not locked");
return _bitmapData.Height;
}
}
public int Stride
{
get
{
if (IsLocked == false) throw new InvalidOperationException("not locked");
return _bitmapData.Stride;
}
}
public int ColorDepth
{
get
{
if (IsLocked == false) throw new InvalidOperationException("not locked");
return Bitmap.GetPixelFormatSize(_bitmapData.PixelFormat);
}
}
public int Channels
{
get
{
if (IsLocked == false) throw new InvalidOperationException("not locked");
return ColorDepth / 8;
}
}
public int PaddingOffset
{
get
{
if (IsLocked == false) throw new InvalidOperationException("not locked");
return _bitmapData.Stride - (_bitmapData.Width * Channels);
}
}
public PixelFormat ImagePixelFormat
{
get
{
if (IsLocked == false) throw new InvalidOperationException("not locked");
return _bitmapData.PixelFormat;
}
}
public ColorPalette Palette
{
get
{
if (IsLocked == false) throw new InvalidOperationException("not locked");
return _bitmap.Palette;
}
}
//Constructor
public BitmapLocker(Bitmap source)
{
IsLocked = false;
IntegerPointer = IntPtr.Zero;
this._bitmap = source;
}
/// Lock bitmap
public void Lock()
{
if (IsLocked == false)
{
try
{
// Lock bitmap (so that no movement of data by .NET framework) and return bitmap data
_bitmapData = _bitmap.LockBits(
new Rectangle(0, 0, _bitmap.Width, _bitmap.Height),
ImageLockMode.ReadWrite,
_bitmap.PixelFormat);
// Create byte array to copy pixel values
int noOfBytesNeededForStorage = Math.Abs(_bitmapData.Stride) * _bitmapData.Height;
_imageData = new byte[noOfBytesNeededForStorage];
IntegerPointer = _bitmapData.Scan0;
// Copy data from IntegerPointer to _imageData
Marshal.Copy(IntegerPointer, _imageData, 0, _imageData.Length);
IsLocked = true;
}
catch (Exception)
{
throw;
}
}
else
{
throw new Exception("Bitmap is already locked.");
}
}
/// Unlock bitmap
public void Unlock()
{
if (IsLocked == true)
{
try
{
// Copy data from _imageData to IntegerPointer
Marshal.Copy(_imageData, 0, IntegerPointer, _imageData.Length);
// Unlock bitmap data
_bitmap.UnlockBits(_bitmapData);
IsLocked = false;
}
catch (Exception)
{
throw;
}
}
else
{
throw new Exception("Bitmap is not locked.");
}
}
public Color GetPixel(int x, int y)
{
Color clr = Color.Empty;
// Get the bit index of the specified pixel
int biti = (Stride > 0 ? y : y - Height + 1) * Stride * 8 + x * ColorDepth;
// Get the byte index
int i = biti / 8;
// Get color components count
int cCount = ColorDepth / 8;
int dataLength = _imageData.Length - cCount;
if (i > dataLength)
{
throw new IndexOutOfRangeException();
}
if (ColorDepth == 32) // For 32 bpp get Red, Green, Blue and Alpha
{
byte b = _imageData[i];
byte g = _imageData[i + 1];
byte r = _imageData[i + 2];
byte a = _imageData[i + 3]; // a
clr = Color.FromArgb(a, r, g, b);
}
if (ColorDepth == 24) // For 24 bpp get Red, Green and Blue
{
byte b = _imageData[i];
byte g = _imageData[i + 1];
byte r = _imageData[i + 2];
clr = Color.FromArgb(r, g, b);
}
if (ColorDepth == 8)
{
byte c = _imageData[i];
if(Palette.Entries.Length <= c)
throw new InvalidOperationException("no palette");
clr = Palette.Entries[c];
}
if (ColorDepth == 4)
{
byte c = 0;
if (biti % 8 == 0)
{
c = (byte)(_imageData[i] >> 4);
}
else
{
c = (byte)(_imageData[i] & 0xF);
}
if (Palette.Entries.Length <= c)
throw new InvalidOperationException("no palette");
clr = Palette.Entries[c];
}
if (ColorDepth == 1)
{
int bbi = biti % 8;
byte mask = (byte)(1 << bbi);
byte c = (byte)((_imageData[i] & mask) != 0 ? 1 : 0);
if (Palette.Entries.Length <= c)
throw new InvalidOperationException("no palette");
clr = Palette.Entries[c];
}
return clr;
}
public void SetPixel(int x, int y, Color color)
{
if (!IsLocked) throw new Exception();
// Get the bit index of the specified pixel
int biti = (Stride > 0 ? y : y - Height + 1) * Stride * 8 + x * ColorDepth;
// Get the byte index
int i = biti / 8;
// Get color components count
int cCount = ColorDepth / 8;
try
{
if (ColorDepth == 32) // For 32 bpp set Red, Green, Blue and Alpha
{
_imageData[i] = color.B;
_imageData[i + 1] = color.G;
_imageData[i + 2] = color.R;
_imageData[i + 3] = color.A;
}
if (ColorDepth == 24) // For 24 bpp set Red, Green and Blue
{
_imageData[i] = color.B;
_imageData[i + 1] = color.G;
_imageData[i + 2] = color.R;
}
if (ColorDepth == 8)
{
if (Palette.Entries.Length < 256)
throw new InvalidOperationException("no palette");
byte index = 0;
for (int j = 0; j < 256; j++)
{
if(Palette.Entries[j].R == color.R && Palette.Entries[j].G == color.G && Palette.Entries[j].B == color.B)
{
index = (byte)j;
break;
}
}
_imageData[i] = index;
}
if (ColorDepth == 4)
{
if (Palette.Entries.Length < 16)
throw new InvalidOperationException("no palette");
byte index = 0;
for (int j = 0; j < 16; j++)
{
if (Palette.Entries[j].R == color.R && Palette.Entries[j].G == color.G && Palette.Entries[j].B == color.B)
{
index = (byte)j;
break;
}
}
if (biti % 8 == 0)
{
_imageData[i] = (byte)((_imageData[i] & 0xF) | (index << 4));
}
else
{
_imageData[i] = (byte)((_imageData[i] & 0xF0) | index);
}
}
if (ColorDepth == 1)
{
if (Palette.Entries.Length < 2)
throw new InvalidOperationException("no palette");
byte index = 0;
for (int j = 0; j < 2; j++)
{
if (Palette.Entries[j].R == color.R && Palette.Entries[j].G == color.G && Palette.Entries[j].B == color.B)
{
index = (byte)j;
break;
}
}
int bbi = biti % 8;
byte mask = (byte)(1 << bbi);
if (index != 0)
{
_imageData[i] |= mask;
}
else
{
_imageData[i] &= (byte)~mask;
}
}
}
catch (Exception ex)
{
throw new Exception("(" + x + ", " + y + "), " + _imageData.Length + ", " + ex.Message + ", i=" + i);
}
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (disposing)
{
// free managed resources
_bitmap = null;
_bitmapData = null;
_imageData = null;
IntegerPointer = IntPtr.Zero;
}
}
}
Run Code Online (Sandbox Code Playgroud)
注意:用于SetPixel检索索引的 for 循环并不完全有效,因此如果您经常使用该函数,您可能需要重构代码,以便它接收索引值而不是索引图像的颜色。
最后,为了使用这段代码,我们必须在将储物柜对象用于索引图像之前复制调色板,所以它看起来像这样:
Bitmap source = (Bitmap)Bitmap.FromFile(@"testimage.png");
BitmapLocker locker = new BitmapLocker(source);
locker.Lock();
Bitmap dest = new Bitmap(source.Width, source.Height, locker.ImagePixelFormat);
if(source.Palette.Entries.Length > 0)
dest.Palette = source.Palette;
BitmapLocker locker2 = new BitmapLocker(dest);
locker2.Lock();
for (int h = 0; h < locker.Height; h++)
{
for (int w = 0; w < locker.Width; w++)
{
locker2.SetPixel(w, h, locker.GetPixel(w, h));
}
}
locker2.Unlock();
locker.Unlock();
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
3380 次 |
| 最近记录: |