Bmp讀寫二值圖

HotSky發表於2024-07-08
public class Bmp : IDisposable
{
    /*
        * 每行畫素資料必須是4的倍數
        * 黑白二值圖0表示黑,1表示白
        */
    public int Width { get => _width; }
    public int Height { get => Math.Abs(_height); }
    public int BitCount { get => _bitCount; }
    public int XResolution { get => _xresolution > 0 ? _xresolution : 96; }
    public int YResolution { get => _yresolution > 0 ? _yresolution : 96; }

    public void Dispose()
    {
        _br?.Dispose();
        _readStream?.Dispose();
        _writeStream?.Dispose();
    }

    Stream _readStream;
    BinaryReader _br;
    MemoryStream _writeStream;
    public Bmp(string path): this(File.OpenRead(path))
    {
    }

    public Bmp(int width, int height, int bitCount)
    {
        _width = width;
        _height = height;
        _bitCount = (short)bitCount;
        LoadStride();
        _imageSize = _stride * Math.Abs(_height);
        ResetWriteStream();
    }

    public Bmp(Stream stream)
    {
        _readStream = stream;
        _br = new BinaryReader(_readStream);

        char[] bm = _br.ReadChars(2);
        if (bm[0] != 'B' || bm[1] != 'M') throw new InvalidDataException();
        _fileSize = _br.ReadInt32();
        _reserved1 = _br.ReadInt16();
        _reserved2 = _br.ReadInt16();
        _offBits = _br.ReadInt32();
        _headSize = _br.ReadInt32();
        _width = _br.ReadInt32();
        _height = _br.ReadInt32();
        _planes = _br.ReadInt16();
        _bitCount = _br.ReadInt16();
        _compressionType = _br.ReadInt32();
        _imageSize = _br.ReadInt32();
        _xresolution = _br.ReadInt32();
        _yresolution = _br.ReadInt32();
        _clrUsed = _br.ReadInt32();
        _clrImportant = _br.ReadInt32();
        LoadStride();
        ReadStreamToWriteStream();
    }

    private void ReadStreamToWriteStream()
    {
        if (_writeStream != null)
            _writeStream.Dispose();
        _writeStream = new MemoryStream();
        byte[] bs = new byte[1024];
        _writeStream.Seek(0, SeekOrigin.Begin);
        _readStream.Seek(_offBits, SeekOrigin.Begin);
        CopyStream(_readStream, _writeStream);
    }

    int _fileSize = 0;
    /// <summary>
    /// 必須是0
    /// </summary>
    short _reserved1 = 0;
    /// <summary>
    /// 必須是0
    /// </summary>
    short _reserved2 = 0;
    /// <summary>
    /// 畫素資料開始的位元組偏移量
    /// </summary>
    int _offBits = 0;
    /// <summary>
    /// 圖片頭資料長度
    /// </summary>
    int _headSize = 0;
    int _width = 0;
    /// <summary>
    /// 正數時畫素位元組流流從左下角開始右上角結束,負數時左上角開始,右下角結束,通常是正數
    /// </summary>
    int _height = 0;
    /// <summary>
    /// 平面數,總是等於1
    /// </summary>
    short _planes = 1;
    /// <summary>
    /// 色寬[1,4,8,16,24,32]
    /// </summary>
    short _bitCount = 0;
    /// <summary>
    /// 壓縮,0表示無壓縮,1表示8位壓縮(只用於8色寬),2表示4位壓縮(只用於4色寬),3表示(只用於16、32色寬),4表示jpeg(只用於印表機),5表示png(只用於印表機)
    /// </summary>
    int _compressionType = 0;
    /// <summary>
    /// 圖片畫素資料區長度未壓縮時可以是0
    /// </summary>
    int _imageSize = 0;
    /// <summary>
    /// pixels per meter
    /// </summary>
    int _xresolution = 0;
    /// <summary>
    /// pixels per meter
    /// </summary>
    int _yresolution = 0;
    /// <summary>
    /// 使用的調色盤顏色索引數量,0表示所有;大於色寬等於24位則沒有顏色索引
    /// </summary>
    int _clrUsed = 0;
    /// <summary>
    /// 對影像顯示有重要影響的顏色索引數量,0表示所有
    /// </summary>
    int _clrImportant = 0;

    int _stride = 0;
    public int Stride
    {
        get
        {
            if(_stride == 0)
                LoadStride();
            return _stride;
        }
    }

    void LoadStride()
    {
        _stride = Math.Max(_width * _bitCount * 4 / 32, 4);
    }

    public int GetOffset(int x, int y, bool containsHead = false)
    {
        int offsetx = (int)Math.Floor(x / 8d);
        int offset = offsetx + Stride * (_height > 0 ? _height - 1 - y : y);
        return (containsHead ? _offBits : 0) + offset;
    }

    public Color GetPixel(int x, int y)
    {
        switch (_bitCount)
        {
            case 1:
                {
                    byte[] bs = ReadBytes(x, y);
                    int p = bs[0] & (int)Math.Pow(2, 7 - (x % 8));
                    return p == 0 ? Color.Black : Color.White;
                }
            default:
                break;
        }
        return Color.Empty;
    }

    byte[] ReadBytes(int x, int y)
    {
        switch (_bitCount)
        {
            case 1:
                {
                    _writeStream.Seek(GetOffset(x, y), SeekOrigin.Begin);
                    return new byte[] { (byte)_writeStream.ReadByte() };
                }
            default:
                break;
        }
        return new byte[0];
    }

    public void SetPixel(int x, int y, Color color)
    {
        ValidWriteStream(true);
        switch (_bitCount)
        {
            case 1:
                {
                    byte[] bs = ReadBytes(x, y);
                    _writeStream.Seek(GetOffset(x, y), SeekOrigin.Begin);
                    int v = color.ToArgb() == Color.White.ToArgb() ? 1 : 0;
                    int pow = (int)Math.Pow(2, 7 - (x % 8));
                    if ((bs[0] & pow) == v) return;
                    if(v == 1)
                        _writeStream.WriteByte((byte)(bs[0] | pow));
                    else
                        _writeStream.WriteByte((byte)(bs[0] - pow));
                    return;
                }
            default:
                break;
        }
    }

    public void Save(Stream stream)
    {
        int fileHeadSize = 14;
        _headSize = 40;
        int colorSize = 0;//顏色索引位元組長度
        switch (_bitCount)
        {
            case 1:
                {
                    colorSize = 8;
                    break;
                }
            default:
                break;
        }
        _offBits = fileHeadSize + _headSize + colorSize;
        int fileSize = _offBits + _imageSize;

        //file head; length:14
        stream.Write(new byte[] { (byte)'B', (byte)'M' }, 0, 2);
        stream.Write(BitConverter.GetBytes(fileSize), 0, 4);
        stream.Write(BitConverter.GetBytes(_reserved1), 0, 2);
        stream.Write(BitConverter.GetBytes(_reserved2), 0, 2);
        stream.Write(BitConverter.GetBytes(_offBits), 0, 4);

        //image head; length:40
        stream.Write(BitConverter.GetBytes(_headSize), 0, 4);
        stream.Write(BitConverter.GetBytes(_width), 0, 4);
        stream.Write(BitConverter.GetBytes(_height), 0, 4);
        stream.Write(BitConverter.GetBytes(_planes), 0, 2);
        stream.Write(BitConverter.GetBytes(_bitCount), 0, 2);
        stream.Write(BitConverter.GetBytes(_compressionType), 0, 4);
        stream.Write(BitConverter.GetBytes(_imageSize), 0, 4);
        stream.Write(BitConverter.GetBytes(_xresolution), 0, 4);
        stream.Write(BitConverter.GetBytes(_yresolution), 0, 4);
        stream.Write(BitConverter.GetBytes(_clrUsed), 0, 4);
        stream.Write(BitConverter.GetBytes(_clrImportant), 0, 4);
            
        //color used
        switch (_bitCount)
        {
            case 1:
                {
                    stream.Write(new byte[] { 0, 0, 0, 0 }, 0, 4);
                    stream.Write(new byte[] { 255, 255, 255, 0 }, 0, 4);
                    break;
                }
            default:
                break;
        }

        //body
        _writeStream.Seek(0, SeekOrigin.Begin);
        CopyStream(_writeStream, stream);
    }

    void CopyStream(Stream source, Stream target)
    {
        byte[] bs = new byte[1024];
        int len;
        do
        {
            len = source.Read(bs, 0, bs.Length);
            if (len > 0)
                target.Write(bs, 0, len);
        } while (len > 0);
    }

    bool ValidWriteStream(bool resetIfInvalid)
    {
        bool valid = true;
        if (_writeStream == null || !_writeStream.CanWrite)
            valid = false;

        if (!valid && resetIfInvalid)
            ResetWriteStream();
        return valid;
    }

    void ResetWriteStream()
    {
        byte[] bs = new byte[_imageSize];
        if (_readStream != null)
            _readStream.Read(bs, 0, bs.Length);
        _writeStream = new MemoryStream(bs);
    }
}

使用參考:

using(Bmp bmp = new Bmp(@"C:\Users\admin\Pictures\5.bmp"))
{
    Color color = bmp.GetPixel(10, 10);
    Color color1 = bmp.GetPixel(444, 0);
    ;
}
using (Bmp bmp = new Bmp(10, 10, 1))
{
    for (int i = 0; i < bmp.Width; i++)
    {
        for (int j = 0; j < bmp.Height; j+=2)
            bmp.SetPixel(i, j, Color.White);
    }
    using(FileStream fs = File.OpenWrite("C:\\Users\\admin\\Pictures\\4.bmp"))
        bmp.Save(fs);
}

相關文章