注意:
- 小端模式,是指数据的高字节保存在内存的高地址中,而数据的低字节保存在内存的低地址中。越向后地址越高,比如00 01 02,02的地址是2,是高。
- BMP文件存储数据时,图像的扫描方式是按从左到右、从下到上的顺序。
- 像素存储位置,即位图数据部分相对于文件的起始偏移量。数据部分偏移量的存在,说明图像数据部分并不一定要紧随图像参数或调色板之后放置,BMP图片的制作者其实可以在调色板之后、数据部分之前填充任何内容,只要正确地设置偏移量即可。所以不一定从54开始就是像素数据。
(图片来自上面链接)
字节顺序 | 数据结构 | 描述 |
1,2 | word | 高8位位字母‘B,低8位为字母’M‘ |
3,4,5,6 | unit | 文件尺寸 |
7,8 | word | 保留字1 |
9,10 | word | 保留字1 |
11,12,13,14 | unit | 位图数据部分相对于文件的起始偏移量 |
字节顺序 | 数据结构 | 描述 |
15,16,17,18 | unit | 当前结构体的大小,通常是40或56 |
19,20,21,22 | int | 图像宽度(像素) |
23,24,25,26 | int | 图像高度(像素) |
27,28 | word | 恒为1 |
29,30 | word | 每个像素占用的位数即bpp |
31,32,33,34 | unit | 压缩方式 |
35,36,37,38 | unit | 图像的尺寸 |
39,40,41,42 | int | 水平分辨率 |
43,44,45,46 | int | 垂直分辨率 |
47,48,49,50 | uint | 引用色彩数 |
51,52,53,54 | uint | 关键色彩数 |
31-34字节表示图像数据的压缩方式,参数取值范围是0,1,2,3等
0 – RGB方式
1 – 8bpp的run-length-encoding方式
2 – 4bpp的run-length-encoding方式
3 – bit-fields方式
只有压缩方式选项被设置为bit-fileds时,当前结构体大小为56字节,否则,为40字节。
比如压缩方式为0,像素位数为1,那么像素数据为1bit一个点(0或1)
比如压缩方式为0,像素位数为0018,那么像素数据为24bit一个点,即3byte。由于BMP行固定为4字节倍数,不足倍数的,最后的一个字节用0填充。
示例:Zxing生成的二维码数据转换为1bit的bmp格式下发到点阵终端。
bmp头部分数据我这用不到就不填了,要保存bmp格式文件还是要填的。
public int showQRCode(String content,int wX, int wY, int wWidth, int wHeight, int wDisplaySeconds) throws FTPosException {
QRCodeWriter qr=new QRCodeWriter();
Map<EncodeHintType, Object> hints = new HashMap<>(3);
hints.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.H);
hints.put(EncodeHintType.CHARACTER_SET, "UTF-8");
hints.put(EncodeHintType.MARGIN, 0); //remove white side
//创建比特矩阵(位矩阵)的QR码编码的字符串
BitMatrix bitMatrix= null;
try {
bitMatrix = qr.encode(content, BarcodeFormat.QR_CODE,wWidth,wHeight,hints);
} catch (WriterException e) {
e.printStackTrace();
}
//row turn
final int width = bitMatrix.getWidth();
final int height = bitMatrix.getHeight();
final int[] pixels = new int[width * height];
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
pixels[y * width + x] = bitMatrix.get(x, height-1-y) ? 0:1; //反
}
}
//8 bit compose 1 byte
final byte[] pixelsBit = new byte[ pixels.length/8 ];
for (int y = 0; y < pixelsBit.length; y++) {
pixelsBit[y] = (byte) ((pixels[y*8+7]==0) ? pixelsBit[y]&(0xFF- 1) : pixelsBit[y]|1);
pixelsBit[y] = (byte) ((pixels[y*8+6]==0) ? pixelsBit[y]&(0xFF- 2) : pixelsBit[y]|2);
pixelsBit[y] = (byte) ((pixels[y*8+5]==0) ? pixelsBit[y]&(0xFF- 4) : pixelsBit[y]|4);
pixelsBit[y] = (byte) ((pixels[y*8+4]==0) ? pixelsBit[y]&(0xFF- 8) : pixelsBit[y]|8);
pixelsBit[y] = (byte) ((pixels[y*8+3]==0) ? pixelsBit[y]&(0xFF-16) : pixelsBit[y]|16);
pixelsBit[y] = (byte) ((pixels[y*8+2]==0) ? pixelsBit[y]&(0xFF-32) : pixelsBit[y]|32);
pixelsBit[y] = (byte) ((pixels[y*8+1]==0) ? pixelsBit[y]&(0xFF-64) : pixelsBit[y]|64);
pixelsBit[y] = (byte) ((pixels[y*8+0]==0) ? pixelsBit[y]&(0xFF-128): pixelsBit[y]|128);
}
final byte[] bmpFile = new byte[ 0x3E+pixels.length/8 ]; //图片存储的位置+像素数据大小
Arrays.fill(bmpFile, (byte) 0);
bmpFile[0]=0x42; //‘B’
bmpFile[1]=0x4d; //'M'
bmpFile[10]=0x3E; //图片存储的位置
System.arraycopy(pixelsBit,0,bmpFile,bmpFile[10],pixelsBit.length);
int ret = showBitmap(bmpFile,bmpFile.length,wX, wY, wWidth, wHeight, wDisplaySeconds);
return ret;
}
C#版
/// <summary>
/// 生成二维码
/// </summary>
/// <param name="text">内容</param>
/// <param name="width">宽度</param>
/// <param name="height">高度</param>
/// <returns>二维码Bitmap</returns>
public static Bitmap GenerateQRCode(string text, int width, int height)
{
BarcodeWriter writer = new BarcodeWriter();
writer.Format = BarcodeFormat.QR_CODE;
QrCodeEncodingOptions options = new QrCodeEncodingOptions()
{
//DisableECI = true,//设置内容编码
//CharacterSet = "UTF-8",
Width = width, //设置二维码的宽度和高度
Height = height,
Margin = 0//设置二维码的边距,单位不是固定像素
};
writer.Options = options;
Bitmap map = writer.Write(text);
return map;
}
/// <summary>
/// 解码二维码
/// </summary>
/// <param name="barcodeBitmap">待解码的二维码图片</param>
/// <returns>扫码结果</returns>
public static string DecodeQRCode(Bitmap barcodeBitmap)
{
BarcodeReader reader = new BarcodeReader();
//reader.Options.CharacterSet = "UTF-8";
var result = reader.Decode(barcodeBitmap);
return (result == null) ? null : result.Text;
}