如何处理1位和4位图像? [英] How can I work with 1-bit and 4-bit images?

查看:98
本文介绍了如何处理1位和4位图像?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

BitmapLocker类用于快速读取/写入Bitmap图像文件中的像素.

BitmapLocker class is intended for fast read/write of pixels in a Bitmap image file.

但是,Color GetPixel(int x, int y)void SetPixel(int x, int y, Color c)无法处理1位和4位图像.

But, Color GetPixel(int x, int y) and void SetPixel(int x, int y, Color c) cannot handle 1-bit and 4-bit images.

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;
        }
    }
}

例如,以下代码显示全黑输出:

For instance, the following code displays a fully black output:

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

.

.

样本输入

1位单色 4位彩色

1-bit monochrome and 4-bit color

推荐答案

对于小于8位的像素格式,一个字节中会包含一个以上的像素.因此,对于8位,4位和1位格式,您不能拥有像这样的包罗万象的语句:

For pixel formats smaller than 8 bits, more than one pixel is packed into a single byte. Therefore you can not have a catch-all statement like this for 8, 4 and 1-bit formats:

if (ColorDepth == 1 || ColorDepth == 4 || ColorDepth == 8)
{
    byte c = _imageData[i];
    clr = Color.FromArgb(c, c, c);
}

相反,基于像素格式,当检索像素数据时,必须计算字节中的位位置并从字节中提取适当的位-在这种情况下,该位将为高"或低"位4位图像或1位图像的情况下为1位.相反,设置像素数据时,只需更改字节中的某些位(基于像素格式)即可.

Instead, based on the pixel format, when retrieving the pixel data the bit position in the byte has to be calculated and appropriate bits extracted from the byte -- this would be either "high" or "low" bits in the case of 4-bit images or the single bit in case of 1-bit images. Conversely, when setting the pixel data only certain bits in the byte (based on the pixel format) need to be changed.

假设我们有一个4位格式的图像.图片数据可能看起来像这样:

Suppose we have a 4-bit format image. The image data may look something like this:

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

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

This format packs two pixels per byte. Therefore, when retrieving pixel data, we first compute the bit index for the pixel:

int biti = (Stride > 0 ? y : y - Height + 1) * Stride * 8 + x * ColorDepth;

Stride是单行中的字节数,因此只需将其乘以高度* 8(对于一个字节中的8位),然后加上宽度* ColorDepth(对于每个像素的位数).

Stride is the number of bytes in a single row, so just multiply that by height * 8 (for 8 bits in a byte) and add the width * ColorDepth (for number of bits per pixel).

然后,我们需要弄清楚是要检索字节的前四位还是后四位.为此,我们只需计算bitindex mod 8.显然,如果像素以字节开头,则为0(例如8 mod 8 = 0),否则为4.基于此,如果我们需要前四个位,则将字节移位四. C#将前四位清零:

Then we need to figure out whether we want to retrieve the first four bits in the byte or the last four bits. For that, we simply compute bitindex mod 8. Obviously if the pixel starts with the byte, this will be 0 (for example, 8 mod 8 = 0), otherwise it will be 4. Based on that, if we want the first four bits, we shift the byte by four. C# zeroes out the first four bits:

   +-----------------+                  
   |+---+---+---+---+|---+---+---+---+               +---+---+---+---+---+---+---+---+
   || 0 | 0 | 1 | 1 || 1 | 1 | 0 | 0 |      =>       | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 1 |
   |+---+---+---+---+|---+---+---+---+               +---+---+---+---+---+---+---+---+
   +-----------------+                     
             ===============>>

另一方面,如果我们想要最后四位,我们AND图像数据字节,其字节的前四位被清零:

On the other hand, if we want the last four bits, we AND the image data byte with a byte that has the first four bits zeroed out:

+---+---+---+---+---+---+---+---+  
| 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 |  
+---+---+---+---+---+---+---+---+

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

In code, all this looks something like:

byte c = 0;
if (biti % 8 == 0)
{
     c = (byte)(_imageData[i] >> 4);
}
else
{
     c = (byte)(_imageData[i] & 0xF);
}

对于1位单色图像,我们希望获得单个位.为此,我们AND图像数据字节中的所有其他位都清零了(掩码").例如,如果我们想获得索引5的位,则可以这样做:

For 1-bit, monochrome images, we want to get at the single bit. For that, we AND the image data byte with a byte that has all the other bits zeroed out (the "mask"). For example, if we want to get at the bit at index 5, we would do this:

+---+---+---+---+---+---+---+---+  
| 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 |  
+---+---+---+---+---+---+---+---+

如果结果为零,那么我们知道该位为零,否则该位为置位".在代码中:

If the result is zero, then we know that the bit is zero, otherwise the bit is "set". In code:

byte mask = (byte)(1 << bbi);
byte c = (byte)((_imageData[i] & mask) != 0 ? 1 : 0);

一旦我们检索了像素数据,我们就可以检索实际的颜色,因为GetPixel函数返回了Color对象.对于8位,4位和1位图像,像素数据实际上表示调色板的索引.调色板看起来像这样:

Once we have retrieved the pixel data, let's retrieve the actual color since the GetPixel function returns a Color object. For 8-, 4- and 1-bit images the pixel data actually represents an index into a color palette. A color palette looks something like this:

============= +-----+-----+-----++-----+-----+-----++-----+-----+-----+
              |  R  |  G  |  B  ||  R  |  G  |  B  ||  R  |  G  |  B  |  
    Color     +-----+-----+-----++-----+-----+-----++-----+-----+-----+
              | 000 | 016 | 005 || 020 | 120 | 053 || 117 | 002 | 209 |
============= +-----+-----+-----++-----+-----+-----++-----+-----+-----+
              |                 ||                 ||                 |
    Index     |        0        ||        1        ||        2        |
              |                 ||                 ||                 |
============= +-----------------++-----------------++-----------------+

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

We have access to the color palette, so to retrieve the color:

clr = Palette.Entries[c];

其中c是检索到的像素数据.

Where c is the retrieved pixel data.

对于设置像素数据也进行了类似的操作.有关C#中位操作的大量信息,例如此处此处.

Something similar is done for setting pixel data. There is plenty of information on bit manipulation in C#, such as here, here and here.

将所有内容放在一起,并与您现有的代码保持一致:

Putting it all together, keeping with your existing code:

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循环用于检索索引并不完全有效,因此,如果您经常使用该函数,则可能需要重组代码,以便它使用索引值而不是a索引图像的颜色.

Note: the for loops in SetPixel to retrieve the index are not exactly efficient so if you're using that function a lot you may want to restructure the code so it takes in an index value instead of a color for indexed images.

最后,为了使用此代码,我们必须在将更衣室对象用于索引图像之前复制调色板,这样看起来就像这样:

Finally, in order to use this code, we must copy the palette before using the locker object for indexed images, so it would look something like this:

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位和4位图像?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆