音影片入門-04-BMP影像四位元組對齊的問題

  • 2019 年 10 月 3 日
  • 筆記

* 音影片入門文章目錄 *

BMP 影像四位元組對齊

表示 BMP 點陣圖中像素的位元是以行為單位對齊存儲的,每一行的大小都向上取整為4位元組(32 位 DWORD)的倍數。如果影像的高度大於 1,多個經過填充實現對齊的行就形成了像素數組。

完整存儲的一行像素所需的位元組數可以通過這個公式計算:

image-demo-bmp-dword-align

  • 每一行的末尾通過填充若干個位元組的數據(並不一定為 0)使該行的長度為 4 位元組的倍數。像素數組讀入記憶體後,每一行的起始地址必須為 4 的倍數。這個限制僅針對記憶體中的像素數組,針對存儲時,僅要求每一行的大小為 4 位元組的倍數,對文件的偏移沒有限制。

  • 例如:對於 24 位色的點陣圖,如果它的寬度為 1 像素,那麼除了每一行的數據(藍、綠、紅)需要佔 3 位元組外,還會填充 1 位元組;而如果寬為 2 像素,則需要 2 位元組的填充;寬為 3 像素時,需要 3 位元組填充;寬為 4 像素時則不需要填充。

  • 影像相同的條件下,點陣圖影像文件通常比使用其它壓縮演算法的影像文件大很多。

四位元組對齊問題-發現

沒有四位元組對齊的 700×700 BMP 文件

之前,用 RGB 拼出一張 700×700 的彩虹圖片,並成功保存成 BMP 文件。

當時並沒有進行四位元組對齊,保存成的 BMP 文件也沒有問題。

image-demo-rainbow-bmp

原因:
根據四位元組對齊的要求,700 x 3 = 2100,2100 / 4 = 525,行像素數據已經四位元組對齊了。

如果沒有對齊會怎麼樣

將圖片尺寸改成 711×711:

#include <stdio.h>  #include <stdlib.h>    // 彩虹的七種顏色  u_int32_t rainbowColors[] = {          0XFF0000, // 紅          0XFFA500, // 橙          0XFFFF00, // 黃          0X00FF00, // 綠          0X007FFF, // 青          0X0000FF, // 藍          0X8B00FF  // 紫  };    /*bmp file header*/  typedef struct {      unsigned int   bfSize;           /* Size of file */      unsigned short bfReserved1;      /* Reserved */      unsigned short bfReserved2;      /* ... */      unsigned int   bfOffBits;        /* Offset to bitmap data */  } BitmapFileHeader;    /*bmp info header*/  typedef struct {      unsigned int   biSize; /* Size of info header */      int            biWidth; /* Width of image */      int            biHeight; /* Height of image */      unsigned short biPlanes; /* Number of color planes */      unsigned short biBitCount; /* Number of bits per pixel */      unsigned int   biCompression; /* Type of compression to use */      unsigned int   biSizeImage; /* Size of image data */      int            biXPelsPerMeter; /* X pixels per meter */      int            biYPelsPerMeter; /* Y pixels per meter */      unsigned int   biClrUsed; /* Number of colors used */      unsigned int   biClrImportant; /* Number of important colors */  } BitmapInfoHeader;    void writeRGBToBmp(char *filename, int width, int height) {      FILE *bitmapFile = fopen(filename, "wb");      if(!bitmapFile) {          printf("Could not write file n");          return;      }        uint16_t bfType = 0x4d42;        BitmapFileHeader fileHeader;      fileHeader.bfReserved1 = 0;      fileHeader.bfReserved2 = 0;      fileHeader.bfSize = 2 + sizeof(BitmapFileHeader) + sizeof(BitmapInfoHeader) + width*height*3;      fileHeader.bfOffBits = 0x36;        BitmapInfoHeader infoHeader;      infoHeader.biSize = sizeof(BitmapInfoHeader);      infoHeader.biWidth = width;      infoHeader.biHeight = -height;      infoHeader.biPlanes = 1;      infoHeader.biBitCount = 24;      infoHeader.biSizeImage = 0;      infoHeader.biCompression = 0;      infoHeader.biXPelsPerMeter = 5000;      infoHeader.biYPelsPerMeter = 5000;      infoHeader.biClrUsed = 0;      infoHeader.biClrImportant = 0;        fwrite(&bfType, sizeof(bfType), 1, bitmapFile);      fwrite(&fileHeader, sizeof(fileHeader), 1, bitmapFile);      fwrite(&infoHeader, sizeof(infoHeader), 1, bitmapFile);        for (int i = 0; i < width; ++i) {            // 當前顏色          u_int32_t currentColor = rainbowColors[0];          if(i < 100) {              currentColor = rainbowColors[0];          } else if(i < 200) {              currentColor = rainbowColors[1];          } else if(i < 300) {              currentColor = rainbowColors[2];          } else if(i < 400) {              currentColor = rainbowColors[3];          } else if(i < 500) {              currentColor = rainbowColors[4];          } else if(i < 600) {              currentColor = rainbowColors[5];          } else if(i < 700) {              currentColor = rainbowColors[6];          }          // 當前顏色 R 分量          u_int8_t R = (currentColor & 0xFF0000) >> 16;          // 當前顏色 G 分量          u_int8_t G = (currentColor & 0x00FF00) >> 8;          // 當前顏色 B 分量          u_int8_t B = currentColor & 0x0000FF;            for (int j = 0; j < height; ++j) {              // 按 BGR 順序寫入一個像素 RGB24 到文件中              fwrite(&B, 1, 1, bitmapFile);              fwrite(&G, 1, 1, bitmapFile);              fwrite(&R, 1, 1, bitmapFile);          }      }        fclose(bitmapFile);  }    int main() {      writeRGBToBmp("/Users/hubin/Desktop/rainbow-711x711.bmp", 711, 711);      return 0;  }

彩虹圖片已經無法顯示出來了:

image-demo-bmp-no-dword-align

四位元組對齊問題-解決

計算一行像素,四位元組對齊後的位元組數

// 計算每一行像素 4 位元組對齊後的位元組數  int caculateLineBytes(int width) {      //******* 四位元組對齊 *******      return (24 * width + 31)/32 *4;      //******* 四位元組對齊 *******  }

寫入一行像素的數據

// 計算一行像素四位元組對齊所需位元組數  int lineBytes = caculateLineBytes(width);    for (int i = 0; i < width; ++i) {      u_int32_t currentColor = rainbowColors[i];      u_int8_t R = (currentColor & 0xFF0000) >> 16;      u_int8_t G = (currentColor & 0x00FF00) >> 8;      u_int8_t B = currentColor & 0x0000FF;        // 存儲一行像素數據的數組      u_int8_t lineBytesArray[lineBytes];        for (int j = 0; j < height; ++j) {          int currentIndex = 3*j;          lineBytesArray[currentIndex] = B;          lineBytesArray[currentIndex+1] = G;          lineBytesArray[currentIndex+2] = R;      }        // 將四位元組對齊後的一行像素數據寫入文件      fwrite(lineBytesArray, sizeof(lineBytesArray), 1, file);  }

完整的程式碼

#include <stdio.h>  #include <stdlib.h>    // 彩虹的七種顏色  u_int32_t rainbowColors[] = {          0XFF0000, // 紅          0XFFA500, // 橙          0XFFFF00, // 黃          0X00FF00, // 綠          0X007FFF, // 青          0X0000FF, // 藍          0X8B00FF  // 紫  };    /*bmp file header*/  typedef struct {      unsigned int   bfSize;           /* Size of file */      unsigned short bfReserved1;      /* Reserved */      unsigned short bfReserved2;      /* ... */      unsigned int   bfOffBits;        /* Offset to bitmap data */  } BitmapFileHeader;    /*bmp info header*/  typedef struct {      unsigned int   biSize; /* Size of info header */      int            biWidth; /* Width of image */      int            biHeight; /* Height of image */      unsigned short biPlanes; /* Number of color planes */      unsigned short biBitCount; /* Number of bits per pixel */      unsigned int   biCompression; /* Type of compression to use */      unsigned int   biSizeImage; /* Size of image data */      int            biXPelsPerMeter; /* X pixels per meter */      int            biYPelsPerMeter; /* Y pixels per meter */      unsigned int   biClrUsed; /* Number of colors used */      unsigned int   biClrImportant; /* Number of important colors */  } BitmapInfoHeader;    // 計算每一行像素 4 位元組對齊後的位元組數  int caculateLineBytes(int width) {      //******* 四位元組對齊 *******      return (24 * width + 31)/32 *4;      //******* 四位元組對齊 *******  }    void writeRGBToBmp(char *filename, int width, int height) {      FILE *bitmapFile = fopen(filename, "wb");      if(!bitmapFile) {          printf("Could not write file n");          return;      }        uint16_t bfType = 0x4d42;        int lineBytes = caculateLineBytes(width);        BitmapFileHeader fileHeader;      fileHeader.bfReserved1 = 0;      fileHeader.bfReserved2 = 0;      fileHeader.bfSize = 2 + sizeof(BitmapFileHeader) + sizeof(BitmapInfoHeader) + lineBytes*height;      fileHeader.bfOffBits = 0x36;        BitmapInfoHeader infoHeader;      infoHeader.biSize = sizeof(BitmapInfoHeader);      infoHeader.biWidth = width;      infoHeader.biHeight = -height;      infoHeader.biPlanes = 1;      infoHeader.biBitCount = 24;      infoHeader.biSizeImage = 0;      infoHeader.biCompression = 0;      infoHeader.biXPelsPerMeter = 5000;      infoHeader.biYPelsPerMeter = 5000;      infoHeader.biClrUsed = 0;      infoHeader.biClrImportant = 0;        fwrite(&bfType, sizeof(bfType), 1, bitmapFile);      fwrite(&fileHeader, sizeof(fileHeader), 1, bitmapFile);      fwrite(&infoHeader, sizeof(infoHeader), 1, bitmapFile);        for (int i = 0; i < width; ++i) {            // 當前顏色          u_int32_t currentColor = rainbowColors[0];          if(i < 100) {              currentColor = rainbowColors[0];          } else if(i < 200) {              currentColor = rainbowColors[1];          } else if(i < 300) {              currentColor = rainbowColors[2];          } else if(i < 400) {              currentColor = rainbowColors[3];          } else if(i < 500) {              currentColor = rainbowColors[4];          } else if(i < 600) {              currentColor = rainbowColors[5];          } else if(i < 700) {              currentColor = rainbowColors[6];          }          // 當前顏色 R 分量          u_int8_t R = (currentColor & 0xFF0000) >> 16;          // 當前顏色 G 分量          u_int8_t G = (currentColor & 0x00FF00) >> 8;          // 當前顏色 B 分量          u_int8_t B = currentColor & 0x0000FF;            u_int8_t lineBytesArray[lineBytes];            for (int j = 0; j < height; ++j) {              int currentIndex = 3*j;              lineBytesArray[currentIndex] = B;              lineBytesArray[currentIndex+1] = G;              lineBytesArray[currentIndex+2] = R;          }            fwrite(lineBytesArray, sizeof(lineBytesArray), 1, bitmapFile);      }      fclose(bitmapFile);  }    int main() {      writeRGBToBmp("/Users/staff/Desktop/rainbow-711x711-fix.bmp", 711, 711);      return 0;  }

711×711 的彩虹圖片也顯示出來了:
image-demo-bmp-with-dword-align.jpg


程式碼:

04-rgb-to-bmp-fix

參考資料:

BMP影像四位元組對齊的問題

維基百科-BMP

non-dword-aligned-pixel-to-dword-aligned-bitmap

generate-bmp-file-from-array-of-rgb-values

內容有誤?聯繫作者:

聯繫作者