• 音视频入门-04-BMP图像四字节对齐的问题


    * 音视频入门文章目录 *

    BMP 图像四字节对齐

    表示 BMP 位图中像素的位元是以行为单位对齐存储的,每一行的大小都向上取整为4字节(32 位 DWORD)的倍数。如果图像的高度大于 1,多个经过填充实现对齐的行就形成了像素数组。

    完整存储的一行像素所需的字节数可以通过这个公式计算:

    image-demo-bmp-dword-align

    • 每一行的末尾通过填充若干个字节的数据(并不一定为 0)使该行的长度为 4 字节的倍数。像素数组读入内存后,每一行的起始地址必须为 4 的倍数。这个限制仅针对内存中的像素数组,针对存储时,仅要求每一行的大小为 4 字节的倍数,对文件的偏移没有限制。

    • 例如:对于 24 位色的位图,如果它的宽度为 1 像素,那么除了每一行的数据(蓝、绿、红)需要占 3 字节外,还会填充 1 字节;而如果宽为 2 像素,则需要 2 字节的填充;宽为 3 像素时,需要 3 字节填充;宽为 4 像素时则不需要填充。

    • 图像相同的条件下,位图图像文件通常比使用其它压缩算法的图像文件大很多。

    四字节对齐问题-发现

    没有四字节对齐的 700x700 BMP 文件

    之前,用 RGB 拼出一张 700x700 的彩虹图片,并成功保存成 BMP 文件。

    当时并没有进行四字节对齐,保存成的 BMP 文件也没有问题。

    image-demo-rainbow-bmp


    原因:
    根据四字节对齐的要求,700 x 3 = 2100,2100 / 4 = 525,行像素数据已经四字节对齐了。

    如果没有对齐会怎么样

    将图片尺寸改成 711x711:

    #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 
    ");
            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 
    ");
            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;
    }
    

    711x711 的彩虹图片也显示出来了: ![image-demo-bmp-with-dword-align.jpg](https://img2018.cnblogs.com/blog/1320320/201909/1320320-20190914145522497-2038619653.jpg)

    代码:

    04-rgb-to-bmp-fix

    参考资料:

    BMP图像四字节对齐的问题

    维基百科-BMP

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

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

    内容有误?联系作者:

    联系作者


  • 相关阅读:
    成为一个优秀的C++程序员
    C++强制类型转换:static_cast、dynamic_cast、const_cast、reinterpret_cast
    C++经典类库(C++开发必看)
    auto_ptr, unique_ptr, shared_ptr and weak_ptr智能指针讲解
    C++ clone()函数的用法
    c++ operator操作符的两种用法:重载和隐式类型转换,string转其他基本数据类型的简洁实现string_cast
    C++模板详解(转)
    static变量的作用(转)
    2018年12月17日,新工作,可是一点提不起兴趣,在原点转啊转,园子什么时候会关了呢,我们终将老去
    给C#Control组件统一增加加属性
  • 原文地址:https://www.cnblogs.com/binglingziyu/p/audio-video-basic-04-bmp-dword-align.html
Copyright © 2020-2023  润新知