• 音视频入门-12-手动生成一张PNG图片


    * 音视频入门文章目录 *

    预热

    上一篇 【PNG文件格式详解】详细介绍了 PNG 文件的格式。

    PNG 图像格式文件由一个 8 字节的 PNG 文件署名域和 3 个以上的后续数据块(IHDR、IDAT、IEND)组成。

    PNG 文件包括 8 字节文件署名(89 50 4E 47 0D 0A 1A 0A,十六进制),用来识别 PNG 格式。

    数据块中有 4 个关键数据块:

    • 文件头数据块 IHDR(header chunk):包含有图像基本信息,作为第一个数据块出现并只出现一次。
    • 调色板数据块 PLTE(palette chunk):必须放在图像数据块之前。
    • 图像数据块 IDAT(image data chunk):存储实际图像数据。PNG 数据允许包含多个连续的图像数据块。
    • 图像结束数据 IEND(image trailer chunk):放在文件尾部,表示 PNG 数据流结束。

    数据块连起来,大概这个样子:

    PNG 标识符 PNG 数据块(IHDR) PNG 数据块(其他类型数据块) PNG 结尾数据块(IEND)

    目标图:

    彩虹条

    生成真彩 PNG 图片

    真彩 PNG 图片不需要 PLTE 调色板数据块,IDAT 数据块中存放的是图像的 RGB 数据。

    分析 - 真彩 PNG IDAT 数据块

    以 7X7 分辨率为例:

    true-color-idat.png

    代码 - 生成真彩 PNG IDAT 数据块

    // 彩虹的七种颜色
    uint32_t rainbowColors[] = {
            0XFF0000, // 红
            0XFFA500, // 橙
            0XFFFF00, // 黄
            0X00FF00, // 绿
            0X007FFF, // 青
            0X0000FF, // 蓝
            0X8B00FF  // 紫
    };
    // 生成真彩 PNG 图片的图像数据块 IDAT
    void genRGB24Data(uint8_t *rgbData, int width, int height) {
    
        for (int i = 0; i < height; ++i) {
            // 当前颜色
            uint32_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 分量
            uint8_t R = (currentColor & 0xFF0000) >> 16;
            // 当前颜色 G 分量
            uint8_t G = (currentColor & 0x00FF00) >> 8;
            // 当前颜色 B 分量
            uint8_t B = currentColor & 0x0000FF;
    
            // 每个扫描行前第一个字节是过滤器类型
            rgbData[3*(i*width)+i] = 0x00;
    
            for (int j = 0; j < width; ++j) {
                int currentIndex = 3*(i*width+j)+(i+1);
                rgbData[currentIndex] = R;
                rgbData[currentIndex+1] = G;
                rgbData[currentIndex+2] = B;
            }
        }
    }
    

    生成真彩 PNG 完整代码

    #include <stdio.h>
    #include <stdint.h>
    #include <stdlib.h>
    #include <stdbool.h>
    #include "zlib.h"
    
    // ***** functions in util.c *****
    bool isBigEndianOrder();
    void genRGB24Data(uint8_t *rgbData, int width, int height);
    uint32_t switchUint32(uint32_t i);
    uint32_t calcCrc32(uint32_t dataASCII, uint8_t *data, uint32_t length);
    
    typedef struct {
        uint32_t width;
        uint32_t height;
        uint8_t bitDepth;
        uint8_t colorType;
        uint8_t compressionMethod;
        uint8_t filterMethod;
        uint8_t interlaceMethod;
    } PNG_IHDR_DATA;
    
    int main() {
        // PNG 图片尺寸
        int width = 700, height = 700;
        // IDAT 中数据部分长度
        uint32_t IDAT_RGB_DATA_LENGTH = width*height*3+height;
    
        // PNG 文件包括 8 字节文件署名(89 50 4E 47 0D 0A 1A 0A,十六进制),用来识别 PNG 格式。
        uint8_t PNG_FILE_SIGNATURE[] = {0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A};
        // IHDR 每个字母对应的 ASCII
        uint32_t IHDR_ASCII = switchUint32(0x49484452);
        // IDAT 每个字母对应的ASCII
        uint32_t IDAT_ASCII = switchUint32(0x49444154);
        // PNG 文件的结尾 12 个字节看起来总应该是这样的:(00 00 00 00 49 45 4E 44 AE 42 60 82,十六进制)
        uint8_t PNG_IEND_DATA[] = {0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4E, 0x44, 0xAE, 0x42, 0x60, 0x82};
    
        FILE *file = fopen("/Users/staff/Desktop/0-true-color.png", "wb");
        // FILE *file = fopen("C:\Users\Administrator\Desktop\0-true-color.png", "wb+");
        if (!file) {
            printf("Could not write file
    ");
            return -1;
        }
    
        // 真彩 PNG 图片 存储的是 RGB 数
        uint8_t *rgb24Data = (uint8_t *)malloc(IDAT_RGB_DATA_LENGTH);
        // 填充 IDAT 的 RGB 数据
        genRGB24Data(rgb24Data, width, height);
    
        // 写 PNG 文件署名
        fwrite(PNG_FILE_SIGNATURE, 1, sizeof(PNG_FILE_SIGNATURE), file);
    
        // 准备 IHDR 数据
        PNG_IHDR_DATA pngIhdrData;
        pngIhdrData.width = switchUint32(width);
        pngIhdrData.height = switchUint32(height);
        pngIhdrData.bitDepth = 8;
        pngIhdrData.colorType = 2;// 2:真彩色图像,8或16    6:带α通道数据的真彩色图像,8或16
        pngIhdrData.compressionMethod = 0;
        pngIhdrData.filterMethod = 0;
        pngIhdrData.interlaceMethod = 0;
    
        // IHDR 数据长度
        uint32_t IHDR_DATA_LENGTH = 13;
        // IHDR 数据长度 转换成大端字节序
        uint32_t pngIhdrDataSize = switchUint32(IHDR_DATA_LENGTH);
        // 计算 IHDR CRC32
        uint32_t ihdrDataCrc32 = calcCrc32(IHDR_ASCII, (uint8_t *) &pngIhdrData, IHDR_DATA_LENGTH);
    
        // 写 IHDR 数据长度
        fwrite(&pngIhdrDataSize, 1, sizeof(pngIhdrDataSize), file);
        // 写 IHDR ASCII
        fwrite(&IHDR_ASCII, 1, sizeof(IHDR_ASCII), file);
        // 写 IHDR 数据
        fwrite(&pngIhdrData, 1, IHDR_DATA_LENGTH, file);
        // 写 IHDR CRC32
        fwrite(&ihdrDataCrc32, 1, sizeof(ihdrDataCrc32), file);
    
        // zlib 压缩数据
        uint8_t buf[IDAT_RGB_DATA_LENGTH];
        // 压缩后 buf 的数据长度 压缩完成后就是实际大小了
        uint32_t buflen = IDAT_RGB_DATA_LENGTH;
    
        // 执行 zlib 的压缩方法
        compress(buf, (uLongf *) &buflen, rgb24Data, IDAT_RGB_DATA_LENGTH);
        printf("
    压缩前数据长度:%d 
    压缩后数据长度为:%d 
    ", IDAT_RGB_DATA_LENGTH, buflen);
    
        // 计算 IDAT CRC32
        uint32_t idatDataCrc32 = calcCrc32(IDAT_ASCII, buf, buflen);
        // IDAT 数据长度 转换成大端字节序
        uint32_t tmpBuflen = switchUint32(buflen);
    
        // 写 IDAT 数据长度
        fwrite(&tmpBuflen, 1, sizeof(tmpBuflen), file);
        // 写 IDAT ASCII
        fwrite(&IDAT_ASCII, 1, sizeof(IDAT_ASCII), file);
        // 写 IDAT 数据
        fwrite(buf, 1, buflen, file);
        // 写 IDAT CRC32
        fwrite(&idatDataCrc32, 1, sizeof(idatDataCrc32), file);
    
        // 写 IEND 信息
        fwrite(PNG_IEND_DATA, 1, sizeof(PNG_IEND_DATA), file);
    
        // 查看字节序
        if(isBigEndianOrder()) {
            printf("大端字节序");
        } else {
            printf("小端字节序");
        }
    
        // 收尾工作
        fflush(file);
        free(rgb24Data);
        fclose(file);
        return 0;
    }
    

    生成索引 PNG 图片

    索引 PNG 图片必须有 PLTE 调色板数据块,IDAT 数据块中存放的是图像的 PLTE 调色板颜色索引数据。

    分析 - 索引 PNG IDAT 数据块

    以 7X7 分辨率为例:

    indexed-color-idat.png

    代码 - 生成索引 PNG PLTE 调色板

    // 彩虹的七种颜色
    uint32_t rainbowColors[] = {
            0XFF0000, // 红
            0XFFA500, // 橙
            0XFFFF00, // 黄
            0X00FF00, // 绿
            0X007FFF, // 青
            0X0000FF, // 蓝
            0X8B00FF  // 紫
    };
    
    /**
     * 生成索引 PNG 图片的调色板 PLTE
     * @param rgbPLTEData
     */
    void genRGBPLTE(uint8_t *rgbPLTEData) {
        for (int i = 0; i < 7; ++i) {
            uint32_t currentColor = rainbowColors[i];
            // 当前颜色 R 分量
            uint8_t R = (currentColor & 0xFF0000) >> 16;
            // 当前颜色 G 分量
            uint8_t G = (currentColor & 0x00FF00) >> 8;
            // 当前颜色 B 分量
            uint8_t B = currentColor & 0x0000FF;
    
            int currentIndex = 3*i;
            rgbPLTEData[currentIndex] = R;
            rgbPLTEData[currentIndex+1] = G;
            rgbPLTEData[currentIndex+2] = B;
        }
    }
    

    代码 - 生成索引 PNG IDAT 数据块

    /**
     * 生成索引 PNG 图片的图像数据块 IDAT
     * @param rgbIndexData
     * @param width
     * @param height
     */
    void genRGBIndexData(uint8_t *rgbIndexData, int width, int height) {
        for (int i = 0; i < height; ++i) {
            uint8_t currentColorIndex = 0;
            if(i < 100) {
                currentColorIndex = 0;
            } else if(i < 200) {
                currentColorIndex = 1;
            } else if(i < 300) {
                currentColorIndex = 2;
            } else if(i < 400) {
                currentColorIndex = 3;
            } else if(i < 500) {
                currentColorIndex = 4;
            } else if(i < 600) {
                currentColorIndex = 5;
            } else if(i < 700) {
                currentColorIndex = 6;
            }
            // 每个扫描行前第一个字节是过滤器类型
            rgbIndexData[(i*width)/2+i] = 0x00;
            for (int j = 0; j < width; ++j) {
                int currentIndex = (i*width+j)/2+(i+1);
                int positionInByte = j%2;
                if(positionInByte == 0) {
                    rgbIndexData[currentIndex] = currentColorIndex << 4;
                } else {
                    rgbIndexData[currentIndex] += currentColorIndex;
                }
            }
        }
    }
    

    生成索引 PNG 完整代码

    #include <stdio.h>
    #include <stdint.h>
    #include <stdlib.h>
    #include <stdbool.h>
    #include "zlib.h"
    
    // ***** functions in util.c *****
    bool isBigEndianOrder();
    void genRGBPLTE(uint8_t *rgbData);
    void genRGBIndexData(uint8_t *rgbIndexData, int width, int height);
    uint32_t switchUint32(uint32_t i);
    uint32_t calcCrc32(uint32_t dataASCII, uint8_t *data, uint32_t length);
    
    typedef struct {
        uint32_t width;
        uint32_t height;
        uint8_t bitDepth;
        uint8_t colorType;
        uint8_t compressionMethod;
        uint8_t filterMethod;
        uint8_t interlaceMethod;
    } PNG_IHDR_DATA;
    
    int main() {
        // PNG 图片尺寸
        int width = 700, height = 700;
        // IDAT 中数据部分长度
        uint32_t IDAT_INDEX_DATA_LENGTH = width*height/2+height;
    
        // PNG 文件包括 8 字节文件署名(89 50 4E 47 0D 0A 1A 0A,十六进制),用来识别 PNG 格式。
        uint8_t PNG_FILE_SIGNATURE[] = {0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A};
        // IHDR 每个字母对应的 ASCII
        uint32_t IHDR_ASCII = switchUint32(0x49484452);
        // PLTE 每个字母对应的ASCII
        uint32_t PLTE_ASCII = switchUint32(0x504C5445);
        // IDAT 每个字母对应的ASCII
        uint32_t IDAT_ASCII = switchUint32(0x49444154);
        // PNG 文件的结尾 12 个字节看起来总应该是这样的:(00 00 00 00 49 45 4E 44 AE 42 60 82,十六进制)
        uint8_t PNG_IEND_DATA[] = {0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4E, 0x44, 0xAE, 0x42, 0x60, 0x82};
    
        FILE *file = fopen("/Users/staff/Desktop/0-indexed-color.png", "wb");
        // FILE *file = fopen("C:\Users\Administrator\Desktop\0-indexed-color.png", "wb+");
        if (!file) {
            printf("Could not write file
    ");
            return -1;
        }
    
        // 红橙黄绿青蓝紫-七种颜色的调色板  7 种颜色 * 每种颜色占 3 字节
        uint8_t *rgbPLTEData = (uint8_t *)malloc(7*3);
        // 索引 PNG 图片,IDAT 存储的是 PLTE 中的图片索引
        uint8_t *rgbIndexData = (uint8_t *)malloc(IDAT_INDEX_DATA_LENGTH);
    
        // 填充 红橙黄绿青蓝紫-七种颜色的调色板
        genRGBPLTE(rgbPLTEData);
        // 填充 IDAT 的 PLTE 索引
        genRGBIndexData(rgbIndexData, width, height);
    
        // 写 PNG 文件署名
        fwrite(PNG_FILE_SIGNATURE, 1, sizeof(PNG_FILE_SIGNATURE), file);
    
        // 准备 IHDR 数据
        PNG_IHDR_DATA pngIhdrData;
        pngIhdrData.width = switchUint32(width);
        pngIhdrData.height = switchUint32(height);
        pngIhdrData.bitDepth = 4;
        pngIhdrData.colorType = 3; // 3:索引彩色图像,1,2,4或8
        pngIhdrData.compressionMethod = 0;
        pngIhdrData.filterMethod = 0;
        pngIhdrData.interlaceMethod = 0;
    
        // IHDR 数据长度
        uint32_t IHDR_DATA_LENGTH = 13;
        // IHDR 数据长度 转换成大端字节序
        uint32_t pngIhdrDataSize = switchUint32(IHDR_DATA_LENGTH);
        // 计算 IHDR CRC32
        uint32_t ihdrDataCrc32 = calcCrc32(IHDR_ASCII, (uint8_t *) &pngIhdrData, IHDR_DATA_LENGTH);
    
        // 写 IHDR 数据长度
        fwrite(&pngIhdrDataSize, 1, sizeof(pngIhdrDataSize), file);
        // 写 IHDR ASCII
        fwrite(&IHDR_ASCII, 1, sizeof(IHDR_ASCII), file);
        // 写 IHDR 数据
        fwrite(&pngIhdrData, 1, IHDR_DATA_LENGTH, file);
        // 写 IHDR CRC32
        fwrite(&ihdrDataCrc32, 1, sizeof(ihdrDataCrc32), file);
    
    
        // 准备 PLTE 调色板信息
        // PLTE 数据长度
        uint32_t PLTE_DATA_LENGTH = 21;
        // PLTE 数据长度 转换成大端字节序
        uint32_t pngPlteDataLength = switchUint32(PLTE_DATA_LENGTH);
        // 计算 PLTE CRC32
        uint32_t plteDataCrc32 = calcCrc32(PLTE_ASCII, rgbPLTEData, PLTE_DATA_LENGTH);
    
        // 写 PLTE 数据长度
        fwrite(&pngPlteDataLength, 1, sizeof(pngPlteDataLength), file);
        // 写 PLTE ASCII
        fwrite(&PLTE_ASCII, 1, sizeof(PLTE_ASCII), file);
        // 写 PLTE 数据
        fwrite(rgbPLTEData, 1, PLTE_DATA_LENGTH, file);
        // 写 PLTE CRC32
        fwrite(&plteDataCrc32, 1, sizeof(plteDataCrc32), file);
    
        // zlib 压缩数据
        // buf 用于存放压缩后的数据
        uint8_t buf[IDAT_INDEX_DATA_LENGTH];
        // 压缩后 buf 的数据长度 压缩完成后就是实际大小了
        uint32_t buflen = IDAT_INDEX_DATA_LENGTH;
    
        // 执行 zlib 的压缩方法
        compress(buf, (uLongf *) &buflen, rgbIndexData, IDAT_INDEX_DATA_LENGTH);
        printf("
    压缩前数据长度:%d 
    压缩后数据长度为:%d 
    ", IDAT_INDEX_DATA_LENGTH, buflen);
    
        // 计算 IDAT CRC32
        uint32_t idatDataCrc32 = calcCrc32(IDAT_ASCII, buf, buflen);
        // IDAT 数据长度 转换成大端字节序
        uint32_t tmpBuflen = switchUint32(buflen);
    
        // 写 IDAT 数据长度
        fwrite(&tmpBuflen, 1, sizeof(tmpBuflen), file);
        // 写 IDAT ASCII
        fwrite(&IDAT_ASCII, 1, sizeof(IDAT_ASCII), file);
        // 写 IDAT 数据
        fwrite(buf, 1, buflen, file);
        // 写 IDAT CRC32
        fwrite(&idatDataCrc32, 1, sizeof(idatDataCrc32), file);
    
        // 写 IEND 信息
        fwrite(PNG_IEND_DATA, 1, sizeof(PNG_IEND_DATA), file);
    
        // 查看字节序
        if(isBigEndianOrder()) {
            printf("大端字节序");
        } else {
            printf("小端字节序");
        }
    
        // 收尾工作
        fflush(file);
        free(rgbPLTEData);
        free(rgbIndexData);
        fclose(file);
        return 0;
    }
    

    总结 & 查看

    生成真彩 PNG、索引 PNG 图片之间的区别:

    • IHDR 文件头数据块中的颜色类型,索引 PNG 颜色类型是 3:索引彩色图像,真彩 PNG 颜色类型是 2:真彩色图像
    • PLTE 调色板数据块,索引 PNG 必须有调色板,真彩 PNG 不需要调色板。
    • IDAT 数据块存储的数据,索引 PNG 存储的是调色板颜色的索引,真彩 PNG 存储的是 RGB 数据。

    来看一看纯手工打造的 PNG 图片:

    generate-png-by-hand.jpg

    Congratulations!


    代码:
    11-rgb-to-png

    参考资料:

    Portable Network Graphics (PNG) Specification and Extensions

    gzip,deflate,zlib辨析

    Zlib库的安装与使用

    内容有误?联系作者:

    联系作者


    本文由博客一文多发平台 OpenWrite 发布!

  • 相关阅读:
    log4j 配置详解
    log4j2单独的配置与使用&log4j2+slf4j的结合的配置与使用
    jdk时区相差8小时
    javax.servlet.ServletException: java.lang.NoClassDefFoundError: javax/el/ELResolver错误解决办法
    SqlServer 统计用户量实例(按年,月,日分组)
    sqlserver with as 双向递归
    eclipse启动无响应,停留在Loading workbench状态
    JS制作闪动的图片
    查询数据库中表名和扩展属性
    sql 查询除某字段的其他字段的记录
  • 原文地址:https://www.cnblogs.com/binglingziyu/p/audio-video-basic-12-generate-png-by-hand.html
Copyright © 2020-2023  润新知