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