JPG學習筆記1(附完整程式碼)

哇哩顧得發表於2021-02-12

  在學習圖象處理的過程中,JPEG是我的第一個攔路虎。一直很想手寫一下JPG的壓縮和解壓的過程,我在網上找到了一些程式碼或者文章,很多都是沒有註釋或者是解釋不夠清楚的。所以特地寫這篇文章記錄自己從無到有寫一個JPEG_Encoder的過程,也能幫助其他學習圖形或者音視訊的童鞋。對於不想看文章的同學,這邊直接上程式碼 https://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 format are stored with the least significant bytes first)

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檔案) 和BMPHeader結構.

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

 以上全部的程式碼在https://github.com/Cheemion/JPEG_COMPRESS/tree/main/Day1

 完結

 Thanks for reading, happy lunar new year.


 

參考資料

[1]https://docs.microsoft.com/en-us/windows/win32/gdi/bitmap-storage

[2]https://docs.microsoft.com/en-us/previous-versions//dd183376(v=vs.85)

[3]https://docs.microsoft.com/en-us/windows/win32/api/wingdi/ns-wingdi-bitmapcoreheader

[4]https://github.com/Cheemion/JPEG_COMPRESS/blob/main/resource/Compressed%20Image%20File%20Formats%20JPEG%2C%20PNG%2C%20GIF%2C%20XBM%2C%20BMP%20-%20John%20Miano.pdf

相關文章