#include "bwbmp.h" #include #include BWBmp::BWBmp() { } int BWBmp::LoadBiBmp(QString filename) { m_bwDdat.clear(); m_prDdat.clear(); QFile file(filename); int rslt = file.open(QFile::ReadOnly); if (rslt == 0) { qDebug() << "open file error" << filename; return -1; } m_bwDdat = file.readAll(); if (m_bwDdat.size() <= (int)sizeof(BitmapHead)) { m_bwDdat.clear(); file.close(); qDebug() << "file size error, size=" << m_bwDdat.size(); return -2; } BitmapHead * pHead = (BitmapHead *)(m_bwDdat.data()); if ( (pHead->identifier != 0x4d42) || (pHead->bitDatOffset != 0x3E) || (pHead->biSize != 0x28) || (pHead->biPlanes != 0x01) || (pHead->biBitPerPixel != 0x01) || (pHead->biCompression != 0x00) || 0 ) { m_bwDdat.clear(); file.close(); qDebug() << "not bi bmp"; return -3; } file.close(); return 0; } int BWBmp::SavePrBmp(QString filename) { QFile file(filename); int rslt = file.open(QFile::ReadWrite | QFile::Truncate); if (rslt == 0) { qDebug() << "open file error" << filename; return -1; } file.write(m_prDdat); file.close(); return 0; } /* 这段C++代码实现了一个名为BWBmp的类的成员函数Compress,该函数的作用是对黑白位图数据进行压缩处理,以便减少喷墨绘图仪的数据传输量。按照注释所描述的压缩算法,算法主要针对的是单个墨盒(假设宽度固定为300像素)的位图数据,其高度可变,最多支持4个墨盒。 整个函数通过对图像数据进行逐行扫描,统计连续相同颜色像素的数量,并利用特殊的编码规则进行压缩,从而达到减小数据体积的目的。当遇到不适合压缩的情况时,会选择直接复制原始数据,以保证不会因压缩反而增大数据量。 为了减少喷墨绘图仪的数据传输量,对黑白位图数据进行压缩。 压缩算法如下: 每个墨盒的位图数据宽度为300像素,高度不确定,墨盒数量支持1--4个 每个字节分为两段 用属性+值 的方式表示 __________________________________________________ | bit7 bit6 | bit5 bit4 bit3 bit2 bit1 bit0 | -------------------------------------------------- | 属性 | 值 | -------------------------------------------------- | 0 0 | 原始数据 | -------------------------------------------------- | 0 1 | 连续数据1的个数 | -------------------------------------------------- | 1 0 | 连续65个数据0的个数 | -------------------------------------------------- | 1 1 | 连续数据0的个数 | -------------------------------------------------- */ int BWBmp::Compress(int idx, int dir, int segWidth, int segHeight) { // qint64 startTime = QDateTime::currentDateTime().toMSecsSinceEpoch(); // 确保dir参数正确设置为1或-1,表示读取图像数据的方向(从上到下或从下到上)。 if (dir < 0) { dir = -1; } else { dir = 1; } // 清理目标压缩数据缓冲区,并检查源位图数据是否为空,若为空则返回错误码-1。 m_prDdat.clear(); if (m_bwDdat.isEmpty() == 1) { qDebug() << "bit dat empty"; return -1; } // 初始化临时压缩数据缓冲区tmppr,并从源位图数据中获取图像的基本信息,如宽度、高度、实际占用的字节数(行字节数),并检查这些信息的有效性,如有问题则返回错误码-2。 BitmapHead * pHead = (BitmapHead *)(m_bwDdat.data()); int width = pHead->biWidth; // 文件宽度 int height = pHead->biHeight; // 文件高度 int widthBytes = (int)((width + (32-1)) / 32) * 4; // 文件每行字节数 int wsegnum = (int)((width + segWidth - 1) / segWidth); // 宽分块数量 int hsegnum = (int)((height + segHeight - 1) / segHeight); // 高分块数量 int fill = (int)((segWidth + (8-1)) / 8) * 8 - segWidth; // 分块后每行补充Bit数 int msegnum = (segWidth + fill) * segHeight; // 每块字节数 if (0) { qDebug() << "Img dir"<bitDatOffset; } if (widthBytes <= 0 || height < 1 || width > 0x1000000) { qDebug() << "bit dat error"; return -2; } const unsigned char * pBitDatBeg = (unsigned char *)(m_bwDdat.data() + pHead->bitDatOffset); //--------------------------------------------------------- // 读取位图信息,对数组赋值 unsigned char sta,tmpdat,mod; sta = tmpdat = mod = 0; const unsigned char * pBitDat; int i, j, k, l, m, n; int addr; int x, y; //------------------------------------ int * segdat = new int [msegnum]; //------------------------------------ unsigned char tgtdat = 0; QVector compType(wsegnum); // 本块位图压缩类型, =0, 不压缩; =1, 按字节压缩(分段压缩); QVector compSegOffset(wsegnum); // 分段数据起始位置 if (dir < 0) // 反向 { pBitDatBeg += widthBytes * (height -1); } for (i = 0; i < wsegnum; i++) {// 分块 addr = m_prDdat.size(); // 压缩前的字节数 compType[i] = 1; // 按字节压缩(分段压缩); compSegOffset[i] = m_prDdat.size(); // 数据地址 if (1) { for (j = 0; j < hsegnum; j++) {// 分块 if (dir > 0) // 正向 { y = j * segHeight; // 起始坐标点 Y m = 0; // 分割后数组的索引 for (l = 0; l < segHeight; l++,y++) {// Y扫描 x = i * segWidth; // 起始坐标点 X for (k = 0; k < segWidth; k++,x++) {// x扫描赋值 if ((x < width) && (y < height)) {// 采集当前点数据 pBitDat = pBitDatBeg + (widthBytes * (height-y-1)) + (x / 8); tmpdat = *pBitDat; mod = 0x80 >> (x % 8); if ((tmpdat & mod) == 0) { sta = 0; } else { sta = 1; // qDebug() << "x =" << x << ",y =" << y // << ",[i =" << i << ",j =" << j // << ",l =" << l << ",k =" << k // << ",m =" << m << "]"; } } else {// X,Y超出最大范围后,使用默认值:0 sta = 0; } segdat[m++] = sta; } for (k = 0; k < fill; k++) {// x结尾处 字节对齐 segdat[m++] = sta; } } } else//if (dir > 0) {// 反向 y = (hsegnum - j) * segHeight - 1; // 起始坐标点 Y m = 0; // 分割后数组的索引 for (l = 0; l < segHeight; l++,y--) {// Y反向扫描 x = i * segWidth; // 起始坐标点 X for (k = 0; k < segWidth; k++,x++) {// x扫描赋值 if ((x < width) && (y < height)) {// 采集当前点数据 pBitDat = pBitDatBeg - (widthBytes * y) + (x / 8); tmpdat = *pBitDat; mod = 0x80 >> (x % 8); if ((tmpdat & mod) == 0) { sta = 0; } else { sta = 1; // qDebug() << "x =" << x << ",y =" << y // << ",[i =" << i << ",j =" << j // << ",l =" << l << ",k =" << k // << "]"; } } else {// X,Y超出最大范围后,使用默认值:0 sta = 0; } segdat[m++] = sta; } for (k = 0; k < fill; k++) {// x结尾处 字节对齐 segdat[m++] = sta; } } } //--------------------------------------------------------- // 对数据进行压缩 for (k = 0; k < msegnum; k = l) { n = 0; // 连续的个数 sta = segdat[k]; // 前一次的状态 tgtdat = 0; // 求连续相同状态的个数 for (l = k; l < msegnum; l++) { n++; if (n <= 6) {// 预先计算原始数据 tgtdat <<= 1; if (segdat[l] != 0) { tgtdat |= 0x01; } if (n == 6) {// 判断前六个位图是否相同 if (((sta == 0) && (tgtdat != 0)) || ((sta != 0) && (tgtdat != 0x3f)) || 0) { l++; break; } } } else// if (n > 6) { if (segdat[l] != sta) { n--; break; } else { if (sta == 0) {// 连续0 (最多 4160) if (n == 4160) { l++; break; } } else {// 连续1 (最多64) if (n == 64) { l++; break; } } } } } for ( ;n<6;n++) {// 原始位图时,如果结尾不足6位,低位补零 tgtdat <<= 1; // qDebug() << "for ( ;n<6;n++) n =" << n << ",l =" << l ; } {// 生成压缩数据 if (n == 6) { m_prDdat.append(tgtdat); // 添加原始位图数据 } else if (sta == 0) { if (n <= 64) { tgtdat = 0xC0 | (n & 0x3f); m_prDdat.append(tgtdat); // 添加连续数据0的个数数据 } else if (n == 4160) { tgtdat = 0x80; m_prDdat.append(tgtdat); // 添加连续 65 个数据0的个数数据 } else // (64 < n < 4160) { tgtdat = 0x80 | (n / 65); m_prDdat.append(tgtdat); // 添加连续 65 个数据0的个数数据 tgtdat = n % 65; if (tgtdat != 0) { tgtdat = 0xC0 | tgtdat; m_prDdat.append(tgtdat); // 添加连续数据0的个数数据 } } } else // if (sta == 1) { tgtdat = 0x40 | (n & 0x3f); m_prDdat.append(tgtdat); // 添加 连续数据1的个数 数据 } } } } // for (j = 0; j < hsegnum; j++) } //--------------------------------------------------------- // 判断压缩后的数据是否比压缩前的小 if ((m_prDdat.size() - addr) > (msegnum * hsegnum / 7)) {// 使用原始数据 m_prDdat.truncate(addr); // 删除当前段的压缩数据 compType[i] = 0; // 不压缩,使用原始数据; for (j = 0; j < hsegnum; j++) {// 分块 if (dir > 0) // 正向 { y = j * segHeight; // 起始坐标点 Y m = 0; // 分割后数组的索引 for (l = 0; l < segHeight; l++,y++) {// Y扫描 x = i * segWidth; // 起始坐标点 X for (k = 0; k < segWidth; k++,x++) {// x扫描赋值 if ((x < width) && (y < height)) {// 采集当前点数据 pBitDat = pBitDatBeg + (widthBytes * (height-y-1)) + (x / 8); tmpdat = *pBitDat; mod = 0x80 >> (x % 8); if ((tmpdat & mod) == 0) { sta = 0; } else { sta = 1; } } else {// X,Y超出最大范围后,使用默认值:0 sta = 0; } segdat[m++] = sta; } for (k = 0; k < fill; k++) {// x结尾处 字节对齐 segdat[m++] = sta; } } } else//if (dir > 0) {// 反向 y = (hsegnum - j) * segHeight - 1; // 起始坐标点 Y m = 0; // 分割后数组的索引 for (l = 0; l < segHeight; l++,y--) {// Y反向扫描 x = i * segWidth; // 起始坐标点 X for (k = 0; k < segWidth; k++,x++) {// x扫描赋值 if ((x < width) && (y < height)) {// 采集当前点数据 pBitDat = pBitDatBeg - (widthBytes * y) + (x / 8); tmpdat = *pBitDat; mod = 0x80 >> (x % 8); if ((tmpdat & mod) == 0) { sta = 0; } else { sta = 1; } } else {// X,Y超出最大范围后,使用默认值:0 sta = 0; } segdat[m++] = sta; } for (k = 0; k < fill; k++) {// x结尾处 字节对齐 segdat[m++] = sta; } } } // 生成原始数据 n = 0; for (m = 0; m < msegnum; m++) { tgtdat <<= 1; if (segdat[m] != 0) { tgtdat |= 0x01; } n++; if ((n % 8) == 0) { m_prDdat.append(tgtdat); // 添加 连续数据1的个数 数据 } } } } } // for (i = 0; i < wsegnum; i++) // qint64 time1 = QDateTime::currentDateTime().toMSecsSinceEpoch(); // qDebug() << "time1:" << time1 - startTime; delete []segdat; //--------------------------------------------------------- // 构造压缩位图头部信息CompBmpHead,包含块索引、压缩方向、压缩类型以及压缩后的数据大小等信息。 // 添加文件头 CompBmpHead prHead; memset(&prHead, 0, sizeof(CompBmpHead)); prHead.fileId = 0; // 整个位图文件标识 prHead.blkIdx = idx; // 当前位图块号(位图分块后的编号) prHead.datSize = m_prDdat.size(); // 本块位图数据区的大小(字节数) prHead.biHeight = segHeight * hsegnum; // 本块位图有效宽度,以像素为单位 prHead.biWidth = segWidth * wsegnum; // 本块位图有效高度,以像素为单位 prHead.dataChecksum = 0; // 本块位图数据累加校验和 if (dir < 0) // 本块位图压缩方向, =0, 从上到下; =1, 从下到上;(喷墨方向) { prHead.compDir = 1; } else { prHead.compDir = 0; } prHead.compSegWidth = segWidth; // 分段宽度(0,默认整个宽度,分段宽度必须能被本块位图有效宽度整除) prHead.compSegHeight = segHeight; // 分段高度(0,默认1行的高度) prHead.compFillWidth = fill; // 压缩填充位数 for (i=0;i<4;i++) { if (i < wsegnum) { prHead.compType[i] = compType[i]; // 本块位图压缩类型, =0, 不压缩; =1, 按字节压缩(分段压缩); prHead.compSegOffset[i] = compSegOffset[i]; // 分段数据起始位置 } } // {// 打印压缩数据 // j = m_prDdat.size(); // const unsigned char * pPrint = (unsigned char *)(m_prDdat.data()); // for (i=0;ibiWidth; int height = pHead->biHeight; int widthBytes = (int)((width + 31) / 32) * 4; if (widthBytes <= 0 || height < 1 || width > 0x1000000) { qDebug() << "bit dat error"; return -2; } qDebug() << "Img dir"<bitDatOffset; const unsigned char * pBitDatBeg = (unsigned char *)(m_bwDdat.data() + pHead->bitDatOffset); const unsigned char * pTmpDat; // 按照给定的dir值定位到位图数据的起始位置。 if (dir == -1) { pBitDatBeg += widthBytes * (height -1); } const unsigned char * pBitDat = pBitDatBeg; // 使用一个整数数组 countBuff 来记录每行中连续的0和1的个数,初始化各种计数器。 int * countBuff = new int [width]; if (countBuff == NULL) { qDebug() << "no enough memory"; return -3; } memset(countBuff, 0, sizeof(int)*width); unsigned char tmpdat, mod; int datsta = -1; int count0 = 0; int count1 = 0; int i, j, k, l; for (i = 0; i < height; i++) // 行计数 { pTmpDat = pBitDat; int countIdx = 0; count0 = 0; count1 = 0; // 遍历图像的每一行,统计当前行中连续0和1的个数,并将其存入countBuff数组。 for (j = 0, k = 0; (j < widthBytes) && (k < width); j++) // 行内扫描,检测出每段连续0和1的个数 { tmpdat = *pTmpDat++; //qDebug()<> (8 - (width - k )); } for (mod = 0x80; (mod != 0) && (k < width); k++, mod /= 2) { if ((tmpdat & mod) == 0) { if (datsta == 1) { count1 |= 0x80000000; countBuff[countIdx] = count1; countIdx++; count1 = 0; } datsta = 0; count0++; } else { if (datsta == 0) { countBuff[countIdx] = count0; countIdx++; count0 = 0; } datsta = 1; count1++; } } } if (count0 != 0) { countBuff[countIdx] = count0; countIdx++; count0 = 0; } if (count1 != 0) { count1 |= 0x80000000; countBuff[countIdx] = count1; countIdx++; count1 = 0; } // 对countBuff中的数据进行分析和编码,生成压缩数据并追加到tmppr中。根据算法描述,压缩数据是以每个字节的高两位作为“属性”位,低六位作为“值”位来表示连续0或1的个数,或者直接存放原始数据。 int psta = 0; int pcount = 0; unsigned char tgtdat = 0, pmod = 0x20, nums; for (l = 0; l < countIdx; l++) // 分析数据,生成压缩数据 { if ((countBuff[l] & 0x80000000) != 0) { count1 = countBuff[l] & 0xfffffff; while (count1 != 0) { if (psta == 0) // 当前状态是原始数据 { if (pcount != 0) { while (count1 != 0 && pcount != 0) { tgtdat |= pmod; pcount--; count1--; pmod *= 2; } if (pcount == 0) // 够一个位图 { tgtdat &= 0x3f; tmppr.append(tgtdat); // 添加 tgtdat = 0x00; } } } if (pcount == 0 && count1 != 0) // { while (count1 != 0) { if (count1 <= 6) { psta = 0x00; // 位图模式 tgtdat = psta; pmod = 0x01; pcount = 6; break; } else { if (count1 > 64) { nums = 64; } else { nums = count1; } psta = 0x40; tgtdat = psta; tgtdat |= (nums & 0x3f); tmppr.append(tgtdat); // 添加 //qDebug()<= 640) { nums = 64; muti = 10; psta = 0x80; } else if (count0 >= 64) { nums = (int)(count0/10); muti = 10; psta = 0x80; } else { nums = count0; muti = 1; psta = 0xC0; } tgtdat = psta; tgtdat |= (nums & 0x3f); tmppr.append(tgtdat); // 添加 //qDebug()<biHeight; prHead.biWidth = pHead->biWidth; if (dir < 0) { prHead.compDir = 1; } else { prHead.compDir = 0; } // 若压缩后数据量(tmppr.size())大于原位图数据量,则放弃压缩,直接复制原数据。 unsigned int prsize = tmppr.size(); if (prsize >= pHead->biBitmapDatSize) // 压缩率不足 { prHead.compType = 0; // 不压缩 prHead.datSize = pHead->biBitmapDatSize; tmppr.clear(); pBitDat = pBitDatBeg; for (i = 0; i < height; i++) // 行计数 { tmppr.append((const char*)pBitDat, widthBytes); pBitDat += widthBytes * dir; } } else { prHead.compType = 1; prHead.datSize = prsize; } m_prDdat.append((char*)(&prHead), sizeof(CompBmpHead)); m_prDdat.append(tmppr); return 0; } { #define VAL_OUTBMP 0xff // 位图之外的默认数据 m_rbwDdat.append((char*)(&bmpHead), sizeof(BitmapHead)); unsigned char sdat = 0; int count0,count1,flag; count0 = count1 = 0; flag = -1; for(int i = 0; i < size; i++) { count0 = count1 = 0; flag = -1; sdat = pPrDdat[i]; if((sdat & 0xC0) == 0xC0) // 连续0 { count0 = sdat & 0x3f; if (count0 == 0) { count0 = 64; } flag = 0; } else if((sdat & 0xC0) == 0x40) // 连续1 { count1 = sdat & 0x3f; if (count1 == 0) { count1 = 64; } flag = 1; } else if((sdat & 0xC0) == 0x80) // 连续10个0 { count0 = (sdat & 0x3f ) * 10; if (count0 == 0) { count0 = 640; } flag = 0; } else // 原始数据 {//每行扫描时最后一个像素为原始数据时原始数据个数 int pcount = 6; int val = arrdat.size() % biWidth; int cnt = biWidth - val; int cnt1 = abs((int)biWidth - ((int)widthBytes-1) * 8); if(cnt >= cnt1) { pcount = 6; } else { if(cnt > 6) { pcount = 6; } else { pcount = cnt; } } unsigned char sch = sdat & 0x3f; //够一个位图 unsigned char pmod = 0x01; while (pcount != 0) { if((sch & pmod) != 0) // 为1 { arrdat.append((char)0x01); } else // 为0 { arrdat.append((char)0x00); } pcount--; pmod *= 2; } continue; } int count = 0; unsigned char ch = 0; if(flag == 0) { count = count0; ch = 0; } else if(flag == 1) { count = count1; ch = 1; } for(int j = 0; j < count; j++) { arrdat.append(ch); } } int num = 0; unsigned int lst = ((biWidth/8)*8+8); for(unsigned int n = 0; n < biHeight; n++) { for(int j = 0; j < widthBytes; j++) { unsigned int cnt = j*8; unsigned char abyte = 0; int idx = n*biWidth+j*8; for(int i = 0; i < 8; i++, idx++, cnt++) { //qDebug()<<"idx"<= lst) { abyte = VAL_OUTBMP; // 默认值 break; } else { abyte |= ((VAL_OUTBMP) >> (i)); // 默认值 break; } } //if(abyte != 0) { //qDebug()<