JPG学习笔记1(附完整代码)
在学习图象处理的过程中,JPEG是我的第一个拦路虎。一直很想手写一下JPG的压缩和解压的过程,我在网上找到了一些代码或者文章,很多都是没有注释或者是解释不够清楚的。所以特地写这篇文章记录自己从无到有写一个JPEG_Encoder的过程,也能帮助其他学习图形或者音视频的童鞋。对于不想看文章的同学,这边直接上代码 //github.com/Cheemion/JPEG_COMPRESS。 以下是JPEG的压缩流程。采样->>离散傅里叶变化->>量化->>哈夫曼压缩->>写入jpg文件. 在进行这些流程之前,必须从BMP文件中读取待压缩的图片文件。
图片引用自”Compressed Image File Formats JPEG, PNG, GIF, XBM, BMP – John Miano”[4]
1.BMP文件Format
[1]BMP文件
一个文件头BITMAPFILEHEADER,里面包含了文件的各种信息。
一个图片头BITMAPINFOHEADER,里面包含了图片信息。
一个RGBQUAD array,里面包含了像素的对应关系,比如1 代表 RGB(1,1,1)。因为我们用的都是24位图片,所以我们不会不考虑这一项。
一个Color-index,就是我们的图片的像素了。我们只考虑24位图像
BMP文件的字节是大端存储的
BMP图片的每一行像素所占的字节数必须是4字节的的整数倍
BMP数据的第一行像素存储的是图片的最后一行的数据(相当于图片在BMP中是倒置的)
2.BITMAPFILEHEADER(头文件格式)
[2]头文件包含如下的字段
1 typedef struct tagBITMAPFILEHEADER { 2 WORD bfType; //文件类型 规定为'BM' 3 DWORD bfSize; //文件大小 4 WORD bfReserved1; //保留 5 WORD bfReserved2; //保留 6 DWORD bfOffBits; //数据起始地址距离首地址的位置 7 } BITMAPFILEHEADER, *LPBITMAPFILEHEADER, *PBITMAPFILEHEADER;
3.BITMAPINFOHEADER文件格式
[3]bitMapInfoHeader根据版本的不同包含了不同的结构,我们主要用到了BitMapInfoHeader和BitMapCoreHeader
以下是BitMapInfoHeader
1 typedef struct tagBITMAPINFOHEADER { 2 DWORD biSize; // infoHeader这个头文件的大小 3 LONG biWidth; // 图片宽多少像素 4 LONG biHeight; //高多少 5 WORD biPlanes; //默认1 6 WORD biBitCount; //一个像素点有多少位 7 DWORD biCompression; //是否压缩过 8 DWORD biSizeImage; //图片大小 9 LONG biXPelsPerMeter; //never used 10 LONG biYPelsPerMeter; //never used 11 DWORD biClrUsed; //never used 12 DWORD biClrImportant; //never used 13 } BITMAPINFOHEADER, *PBITMAPINFOHEADER;
以下是BitMapCoreHeader, coreHeader就简单多了,就是少了一些字段,其他一毛一样。
typedef struct tagBITMAPCOREHEADER { DWORD bcSize; WORD bcWidth; WORD bcHeight; WORD bcPlanes; WORD bcBitCount; } BITMAPCOREHEADER, *LPBITMAPCOREHEADER, *PBITMAPCOREHEADER;
4.读取图片数据
定义常用结构
using byte = unsigned char; using uint = unsigned int; struct RGB { byte blue; byte green; byte red; };
定义文件结构,和一个BMPReader(用来读取bmp文件)
1 #pragma once 4 class BMPReader { 5 public: 6 BMPReader() = default; 7 ~BMPReader() { 8 if (data) { 9 delete[] data;
data = nullptr; 10 } 11 } 12 bool open(std::string& path); 13 public: 14 uint height = 0; 15 uint width = 0; 16 uint paddingBytes = 0; 17 RGB* data = nullptr; //实际的数据 18 }; 19 20 //2个字节对齐, 21 #pragma pack(2) 22 typedef struct { 23 unsigned short bfType = 0x424d; 24 unsigned int bfSize = 0; 25 unsigned short bfReserved1 = 0; 26 unsigned short bfReserved2 = 0; 27 unsigned int bfOffBits = 0; 28 } BitMapFileHeader; 29 30 typedef struct { 31 unsigned int biSize; 32 int biWidth; 33 int biHeight; 34 unsigned short biPlanes; 35 unsigned short biBitCount; 36 unsigned int biCompression; 37 unsigned int biSizeImage; 38 int biXPelsPerMeter; 39 int biYPelsPerMeter; 40 unsigned int biClrUsed; 41 unsigned int biClrImportant; 42 } BitMapInfoHeader; 43 44 typedef struct { 45 unsigned int bcSize; 46 unsigned short bcWidth; 47 unsigned short bcHeight; 48 unsigned short bcPlanes = 1; 49 unsigned short bcBitCount = 24; 50 } BitMapCoreHeader; 51 #pragma pack()
读取图片数据到BMPReader的data字段
bool BMPReader::open(std::string& path) { FILE* file = nullptr; //判断是否打开图片成功 if ((file = fopen(path.c_str(), "rb")) == nullptr) { printf("error occured when opening file:%s", path.c_str()); return false; } BitMapFileHeader fileHeader; //读取文件头 if (fread(&fileHeader, sizeof(fileHeader), 1, file) != 1) { printf("Error - error occured when reading BITMAPFILEHEADER"); return false; } //判断是不是BMP文件,通过判断'BM' if(fileHeader.bfType != 0x4D42) { printf("Error - this is not a BMP file that you're reading"); return false; }
//读取infoHeader的大小,通过size来判断是哪个版本的BitMapInfoHeader uint infoHeaderSize; fread(&infoHeaderSize, sizeof(infoHeaderSize), 1, file); fseek(file, -sizeof(infoHeaderSize), SEEK_CUR); //2中infoHeader, 通过size来判断
//如果是BitMapCoreHeader的话 if (infoHeaderSize == sizeof(BitMapCoreHeader)) { BitMapCoreHeader bitMapCoreHeader; if (fread(&bitMapCoreHeader, sizeof(bitMapCoreHeader), 1, file) != 1) { printf("Error - mal-structure BITMAPCOREHEADER"); return false; } this->width = bitMapCoreHeader.bcWidth; this->height = bitMapCoreHeader.bcHeight; if (bitMapCoreHeader.bcBitCount != 24) { printf("Error - the picture format is not consistent with our programm"); return false; } } else { BitMapInfoHeader bitMapInfoHeader; if (fread(&bitMapInfoHeader, sizeof(bitMapInfoHeader), 1, file) != 1) { printf("Error - mal-structure BITMAPINFOHEADER"); return false; } this->width = bitMapInfoHeader.biWidth; this->height = bitMapInfoHeader.biHeight; if (bitMapInfoHeader.biBitCount != 24) { printf("Error - the picture format is not consistent with our programm"); return false; } } // 必须是4byte的整数倍,算出需要padding多少字节,也就是需要填补多少字节// if width = 1, 1 * 3个像素 * 8位 = 24位, 差一个字节, paddingSize = 1 // if width = 2, 2 * 3个 * 8位 = 48位 paddingSize = 2 // if width = 3, paddingSize = 3 // if width = 4, paddingSize = 0 this->paddingBytes = width % 4;
//为像素数据 创建内存空间 data = new (std::nothrow) RGB[this->width * this->height]; if (!data) { printf("Error - error when allocating memroy for RGB"); return false; } //跳到data数据的位置 fseek(file, fileHeader.bfOffBits, SEEK_SET); for (uint i = 0; i < height; i++) { //read data一行,一行的读取放入我们的data if (width != fread(data + (height - 1 - i) * width , sizeof(RGB), width, file)) { printf("Error - something wrong when reading data from BMP file"); delete data; return false; }
//因为可能有paddingSize,所以这边跳过PaddingSize fseek(file, paddingBytes, SEEK_CUR); } fclose(file); return true;
}
以上全部的代码在//github.com/Cheemion/JPEG_COMPRESS/tree/main/Day1
完结
Thanks for reading, happy lunar new year.
参考资料
[1]//docs.microsoft.com/en-us/windows/win32/gdi/bitmap-storage
[2]//docs.microsoft.com/en-us/previous-versions//dd183376(v=vs.85)
[3]//docs.microsoft.com/en-us/windows/win32/api/wingdi/ns-wingdi-bitmapcoreheader