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;
}
}
}
例如,以下代码显示全黑输出:
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();
}
}
如何纠正此代码,使其可以处理1位和4位图像?
。
。
样本输入
答案 0 :(得分:2)
对于小于8位的像素格式,将多个像素打包成一个字节。因此,对于8位,4位和1位格式,您不能拥有像这样的包罗万象的语句:
if (ColorDepth == 1 || ColorDepth == 4 || ColorDepth == 8)
{
byte c = _imageData[i];
clr = Color.FromArgb(c, c, c);
}
相反,基于像素格式,当检索像素数据时,必须计算字节中的位位置并从字节中提取适当的位-在这种情况下,该位将为“高”或“低”位4位图像或1位图像时为1位。相反,在设置像素数据时,只需更改字节中的某些位(基于像素格式)即可。
假设我们有一个4位格式的图像。图片数据可能看起来像这样:
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;
Stride
是一行中的字节数,因此只需将其乘以高度* 8(对于一个字节中的8位),然后加上宽度* ColorDepth
(对于每位的位数)像素)。
然后,我们需要弄清楚我们要检索字节的前四位还是后四位。为此,我们只需计算bitindex mod 8
。显然,如果像素以字节开头,则为0(例如8 mod 8 = 0
),否则为4。基于此,如果我们需要前四个位,则将字节移位四。 C#将前四位清零:
+-----------------+ |+---+---+---+---+|---+---+---+---+ +---+---+---+---+---+---+---+---+ || 0 | 0 | 1 | 1 || 1 | 1 | 0 | 0 | => | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 1 | |+---+---+---+---+|---+---+---+---+ +---+---+---+---+---+---+---+---+ +-----------------+ ===============>>
另一方面,如果我们需要最后四位,我们AND
的图像数据字节的前四位被清零:
+---+---+---+---+---+---+---+---+ | 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);
}
对于1位单色图像,我们希望获得单个位。为此,我们AND
的图像数据字节的所有其他位都清零了(“掩码”)。例如,如果我们想获得索引5的位,则可以这样做:
+---+---+---+---+---+---+---+---+ | 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);
一旦我们检索了像素数据,我们就可以检索实际的颜色,因为GetPixel
函数返回了一个Color
对象。对于8位,4位和1位图像,像素数据实际上表示调色板的索引。调色板看起来像这样:
============= +-----+-----+-----++-----+-----+-----++-----+-----+-----+ | 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];
c
是检索到的像素数据。
对于设置像素数据也做了类似的事情。有关C#中的位操作的大量信息,例如here,here和here。
将所有内容放在一起,并与您现有的代码保持一致:
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;
}
}
}
注意: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();
答案 1 :(得分:1)
1位和4位内容使用起来很烦人。因此,使用ConvertTo8Bit
和ConvertFrom8Bit
函数,我处理的所有索引数据都将转换为每个像素更方便的1字节(8bpp)进行处理。
它们与GetImageData
函数一起从图像中获取字节,并与BuildImage
函数一起从字节中构建新的Bitmap
。
关于图像,通常要记住的重要一件事是,以像素为单位的线条宽度不一定与位数乘以宽度相同。首先,因为对于1bpp或4bpp,您总有多余的空间可以到达下一个完整字节,其次,because the .Net framework aligns image lines to multiples of 4 bytes。因此,在将图像作为字节处理时,务必保持一个“ stride”值,该值必须包含以字节为单位的实际数据宽度。
要记住的另一项重要事情是,您需要一个用于索引图像的调色板;它们的像素不是颜色,而是对调色板的引用。没有调色板,它们将无法显示任何内容,如果您忽略调色板,它们最终可能会使用Windows为每种像素格式提供的默认颜色,而这些颜色通常根本不是图像所需的。
最后,如果您在8位数组中编辑1位或4位数据,则必须确保切勿将超出原始像素格式允许的最大值的数据放入数组。因此,在4bpp数据中,永远不要有大于0x0F的字节,在1bpp中,字节中实际上应该只有0和1。
GetImageData
和BuildImage
函数:
/// <summary>
/// Gets the raw bytes from an image.
/// </summary>
/// <param name="sourceImage">The image to get the bytes from.</param>
/// <param name="stride">Stride of the retrieved image data.</param>
/// <returns>The raw bytes of the image.</returns>
public static Byte[] GetImageData(Bitmap sourceImage, out Int32 stride)
{
if (sourceImage == null)
throw new ArgumentNullException("sourceImage", "Source image is null!");
BitmapData sourceData = sourceImage.LockBits(new Rectangle(0, 0, sourceImage.Width, sourceImage.Height), ImageLockMode.ReadOnly, sourceImage.PixelFormat);
stride = sourceData.Stride;
Byte[] data = new Byte[stride * sourceImage.Height];
Marshal.Copy(sourceData.Scan0, data, 0, data.Length);
sourceImage.UnlockBits(sourceData);
return data;
}
/// <summary>
/// Creates a bitmap based on data, width, height, stride and pixel format.
/// </summary>
/// <param name="sourceData">Byte array of raw source data</param>
/// <param name="width">Width of the image</param>
/// <param name="height">Height of the image</param>
/// <param name="stride">Scanline length inside the data. If this is negative, the image is built from the bottom up (BMP format).</param>
/// <param name="pixelFormat">Pixel format</param>
/// <param name="palette">Color palette</param>
/// <param name="defaultColor">Default color to fill in on the palette if the given colors don't fully fill it.</param>
/// <returns>The new image</returns>
public static Bitmap BuildImage(Byte[] sourceData, Int32 width, Int32 height, Int32 stride, PixelFormat pixelFormat, Color[] palette, Color? defaultColor)
{
Bitmap newImage = new Bitmap(width, height, pixelFormat);
BitmapData targetData = newImage.LockBits(new Rectangle(0, 0, width, height), ImageLockMode.WriteOnly, newImage.PixelFormat);
Int32 newDataWidth = ((Image.GetPixelFormatSize(pixelFormat) * width) + 7) / 8;
// Compensate for possible negative stride on BMP format.
Boolean isFlipped = targetData.Stride < 0;
Int32 targetStride = Math.Abs(targetData.Stride);
Int64 scan0 = targetData.Scan0.ToInt64();
for (Int32 y = 0; y < height; y++)
Marshal.Copy(sourceData, y * stride, new IntPtr(scan0 + y * targetStride), newDataWidth);
newImage.UnlockBits(targetData);
// Fix negative stride on BMP format.
if (isFlipped)
newImage.RotateFlip(RotateFlipType.Rotate180FlipX);
// For indexed images, set the palette.
if ((pixelFormat & PixelFormat.Indexed) != 0 && palette != null)
{
ColorPalette pal = newImage.Palette;
for (Int32 i = 0; i < pal.Entries.Length; i++)
{
if (i < palette.Length)
pal.Entries[i] = palette[i];
else if (defaultColor.HasValue)
pal.Entries[i] = defaultColor.Value;
else
break;
}
newImage.Palette = pal;
}
return newImage;
}
ConvertTo8Bit
和ConvertFrom8Bit
函数:
/// <summary>
/// Converts given raw image data for a paletted image to 8-bit, so we have a simple one-byte-per-pixel format to work with.
/// </summary>
/// <param name="fileData">The file data.</param>
/// <param name="width">Width of the image.</param>
/// <param name="height">Height of the image.</param>
/// <param name="start">Start offset of the image data in the fileData parameter.</param>
/// <param name="bitsLength">Amount of bits used by one pixel.</param>
/// <param name="bigEndian">True if the bits in the original image data are stored as big-endian.</param>
/// <param name="stride">Stride used in the original image data. Will be adjusted to the new stride value.</param>
/// <returns>The image data in a 1-byte-per-pixel format, with a stride exactly the same as the width.</returns>
public static Byte[] ConvertTo8Bit(Byte[] fileData, Int32 width, Int32 height, Int32 start, Int32 bitsLength, Boolean bigEndian, ref Int32 stride)
{
if (bitsLength != 1 && bitsLength != 2 && bitsLength != 4 && bitsLength != 8)
throw new ArgumentOutOfRangeException("Cannot handle image data with " + bitsLength + "bits per pixel.");
// Full array
Byte[] data8bit = new Byte[width * height];
// Amount of runs that end up on the same pixel
Int32 parts = 8 / bitsLength;
// Amount of bytes to read per width
Int32 newStride = width;
// Bit mask for reducing read and shifted data to actual bits length
Int32 bitmask = (1 << bitsLength) - 1;
Int32 size = stride * height;
// File check, and getting actual data.
if (start + size > fileData.Length)
throw new IndexOutOfRangeException("Data exceeds array bounds!");
// Actual conversion process.
for (Int32 y = 0; y < height; y++)
{
for (Int32 x = 0; x < width; x++)
{
// This will hit the same byte multiple times
Int32 indexXbit = start + y * stride + x / parts;
// This will always get a new index
Int32 index8bit = y * newStride + x;
// Amount of bits to shift the data to get to the current pixel data
Int32 shift = (x % parts) * bitsLength;
// Reversed for big-endian
if (bigEndian)
shift = 8 - shift - bitsLength;
// Get data and store it.
data8bit[index8bit] = (Byte)((fileData[indexXbit] >> shift) & bitmask);
}
}
stride = newStride;
return data8bit;
}
/// <summary>
/// Converts given raw image data for a paletted 8-bit image to lower amount of bits per pixel.
/// </summary>
/// <param name="data8bit">The eight bit per pixel image data</param>
/// <param name="width">The width of the image</param>
/// <param name="height">The height of the image</param>
/// <param name="bitsLength">The new amount of bits per pixel</param>
/// <param name="bigEndian">True if the bits in the new image data are to be stored as big-endian.</param>
/// <param name="stride">Stride used in the original image data. Will be adjusted to the new stride value.</param>
/// <returns>The image data converted to the requested amount of bits per pixel.</returns>
public static Byte[] ConvertFrom8Bit(Byte[] data8bit, Int32 width, Int32 height, Int32 bitsLength, Boolean bigEndian, ref Int32 stride)
{
Int32 parts = 8 / bitsLength;
// Amount of bytes to write per width
Int32 newStride = ((bitsLength * width) + 7) / 8;
// Bit mask for reducing original data to actual bits maximum.
// Should not be needed if data is correct, but eh.
Int32 bitmask = (1 << bitsLength) - 1;
Byte[] dataXbit = new Byte[newStride * height];
// Actual conversion process.
for (Int32 y = 0; y < height; y++)
{
for (Int32 x = 0; x < width; x++)
{
// This will hit the same byte multiple times
Int32 indexXbit = y * newStride + x / parts;
// This will always get a new index
Int32 index8bit = y * stride + x;
// Amount of bits to shift the data to get to the current pixel data
Int32 shift = (x % parts) * bitsLength;
// Reversed for big-endian
if (bigEndian)
shift = 8 - shift - bitsLength;
// Get data, reduce to bit rate, shift it and store it.
dataXbit[indexXbit] |= (Byte)((data8bit[index8bit] & bitmask) << shift);
}
}
stride = newStride;
return dataXbit;
}
请注意,bigEndian
参数是指位块的顺序。通常,4bpp中的字节12 34
只会为您提供像素1 2 3 4
,在这种情况下,将采用大端规则(该值的数学上最大的部分将作为第一个像素处理)。 1bpp也是如此;值37通常会产生像素0 0 1 1 0 1 1 1
。但是在我处理过的旧DOS游戏的某些自定义文件格式中,情况并非如此(4bpp 12 34
会给出像素2 1 4 3
),因此函数为什么要使用该参数。
类似地存在start
参数,因为我使用的数据是从自定义文件格式读取的。通常,该值始终应该始终为0。