如何处理 1 位和 4 位图像?

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 位图像?

.

.

样本输入

1 位单色 和 4 位彩色
在此处输入图片说明 在此处输入图片说明

mni*_*tic 7

对于小于 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 位格式的图像。图像数据可能如下所示:

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
Run Code Online (Sandbox Code Playgroud)

这种格式每字节包含两个像素。因此,在检索像素数据时,我们首先计算像素的位索引:

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# 将前四位清零:

   +-----------------+                  
   |+---+---+---+---+|---+---+---+---+               +---+---+---+---+---+---+---+---+
   || 0 | 0 | 1 | 1 || 1 | 1 | 0 | 0 |      =>       | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 1 |
   |+---+---+---+---+|---+---+---+---+               +---+---+---+---+---+---+---+---+
   +-----------------+                     
             ===============>>
Run Code Online (Sandbox Code Playgroud)

另一方面,如果我们想要最后四位,我们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 |  
+---+---+---+---+---+---+---+---+
Run Code Online (Sandbox Code Playgroud)

在代码中,所有这些看起来像:

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 处的位,我们会这样做:

+---+---+---+---+---+---+---+---+  
| 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 |  
+---+---+---+---+---+---+---+---+
Run Code Online (Sandbox Code Playgroud)

如果结果为零,则我们知道该位为零,否则该位为“设置”。在代码中:

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 位图像,像素数据实际上表示调色板的索引。调色板看起来像这样:

============= +-----+-----+-----++-----+-----+-----++-----+-----+-----+
              |  R  |  G  |  B  ||  R  |  G  |  B  ||  R  |  G  |  B  |  
    Color     +-----+-----+-----++-----+-----+-----++-----+-----+-----+
              | 000 | 016 | 005 || 020 | 120 | 053 || 117 | 002 | 209 |
============= +-----+-----+-----++-----+-----+-----++-----+-----+-----+
              |                 ||                 ||                 |
    Index     |        0        ||        1        ||        2        |
              |                 ||                 ||                 |
============= +-----------------++-----------------++-----------------+
Run Code Online (Sandbox Code Playgroud)

我们可以访问调色板,因此要检索颜色:

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)