100ASK_IMX6ULL-PRO 数码相框扩展项目:支持 PNG 显示

背景说明

本篇内容基于百问网课程 百问网嵌入式专家-韦东山嵌入式专注于嵌入式课程及硬件研发

项目中已支持 BMP 和 JPG 格式,本篇内容主要对项目中图片显示功能做简单说明,并增加支持 PNG 格式图片。

图片格式说明

BMP

格式说明

BMP(Bitmap)格式是一种常见的图像文件格式,以下是其主要格式说明:

文件头(Bitmap File Header):

  • 大小:14 字节。

  • 字段:

    • bfType(2 字节):固定值 0x4D42,表示这是一个 BMP 文件。
    • bfSize(4 字节):整个 BMP 文件的大小,以字节为单位。
    • bfReserved1bfReserved2(各 2 字节):保留字段,通常为 0。
    • bfOffBits(4 字节):从文件开头到图像数据的偏移量,以字节为单位。

信息头(Bitmap Information Header):

  • 大小:常见的有 40 字节(Windows V3)。

  • 字段:

    • biSize(4 字节):信息头的大小,对于 Windows V3 通常为 40。
    • biWidth(4 字节):图像的宽度,以像素为单位。
    • biHeight(4 字节):图像的高度,以像素为单位。注意,正数表示图像是 bottom-up 存储(从图像的底部开始),负数表示 top-down 存储(从图像的顶部开始)。
    • biPlanes(2 字节):固定值 1,表示颜色平面数。
    • biBitCount(2 字节):每个像素的位数,常见的值有 1、4、8、24 等,分别表示单色、16 色、256 色和真彩色。
    • biCompression(4 字节):压缩类型,常见的值有 0(无压缩)、1(BI_RLE8 压缩)、2(BI_RLE4 压缩)等。
    • biSizeImage(4 字节):图像数据的大小,以字节为单位。如果未压缩,可通过计算得出。
    • biXPelsPerMeterbiYPelsPerMeter(各 4 字节):水平和垂直分辨率,单位是像素/米。
    • biClrUsed(4 字节):实际使用的颜色表中的颜色数。 如果为 0,则使用所有可能的颜色。
    • biClrImportant(4 字节):重要颜色的数量。如果为 0,则认为所有颜色都重要。

颜色表(Color Table):

  • 对于 8 位及以下的位深度,会有颜色表。
  • 每个表项大小为 4 字节,分别表示蓝色、绿色、红色和保留值(通常设置为0)。

图像数据(Image Data):

  • 像素数据按行存储,每行的字节数必须是 4 的倍数,如果不足则用 0 填充。
  • 像素数据的存储顺序取决于颜色位数和压缩方式。

以上是 BMP 格式的基本说明,实际应用中可能会有一些变体和扩展。

示例:解析 BMP 文件

解析 BMP 文件的基本步骤如下:

  1. 读取文件头

    • 使用二进制读取方式读取前 14 个字节,解析出文件头中的各个字段。
  2. 读取信息头

    • 根据文件头中的 bfOffBits 字段跳转到信息头的位置,读取接下来的 40 个字节(对于标准 BMP 文件),解析出信息头中的各个字段。
  3. 读取颜色表(如果存在)

    • 如果图像不是 24 位或 32 位颜色深度,需要读取颜色表。颜色表的大小可以通过 biClrUsed 字段确定,如果该字段为 0,则颜色表的大小为 2^biBitCount
  4. 读取图像数据

    • 根据 bfOffBits 字段跳转到图像数据的位置,读取图像数据。注意图像数据的每一行可能需要填充字节以对齐到 4 字节边界
    • 注意数据存储的顺序:逐行自顶向下还是自底向上,每一行的数据格式(BGR+)
#include <stdio.h>
#include <stdlib.h>

typedef struct {
    unsigned short type;
    unsigned int size;
    unsigned short reserved1;
    unsigned short reserved2;
    unsigned int offset;
} BMPFileHeader;

typedef struct {
    unsigned int size;
    int width;
    int height;
    unsigned short planes;
    unsigned short bitCount;
    unsigned int compression;
    unsigned int sizeImage;
    int xPelsPerMeter;
    int yPelsPerMeter;
    unsigned int clrUsed;
    unsigned int clrImportant;
} BMPInfoHeader;

int main() {
    FILE *fp = fopen("image.bmp", "rb");
    if (!fp) {
        printf("Error opening file\n");
        return 1;
    }

    BMPFileHeader fileHeader;
    fread(&fileHeader, sizeof(BMPFileHeader), 1, fp);

    if (fileHeader.type!= 0x4D42) {
        printf("Not a BMP file\n");
        fclose(fp);
        return 1;
    }

    BMPInfoHeader infoHeader;
    fread(&infoHeader, sizeof(BMPInfoHeader), 1, fp);

    // 根据读取的信息进行后续处理,如读取颜色表、图像数据等

    fclose(fp);
    return 0;
}

JPG

JPEG(Joint Photographic Experts Group)是一种广泛使用的图像压缩标准,特别适用于照片类图像。JPEG 文件格式通常使用 .jpg.jpeg 扩展名。

JPEG 文件格式概述

JPEG 是一种有损压缩算法,意味着在压缩过程中会丢失一些信息,但这种损失通常对人眼来说是不可见的。JPEG 格式的主要优点是能够在保持高质量的同时显著减少文件大小,这使得它非常适合在网络上传输和存储大量图像。

文件结构

JPEG 文件通常包含以下几个部分:

  1. 文件头(SOI - Start of Image)

    • SOI 标记文件的开始,固定为两个字节 FF D8
  2. 标记段(Markers)

    • JPEG 文件由一系列标记段组成,每个标记段都有特定的功能。

    • 常见的标记段包括:

      • SOI (Start of Image) : 文件开始标记。
      • EOI (End of Image) : 文件结束标记。
      • SOFn (Start of Frame) : 帧开始标记,其中 n 表示不同的变体(例如 SOF0 表示基线 JPEG)。
      • DHT (Define Huffman Table) : 定义霍夫曼编码表。
      • DQT (Define Quantization Table) : 定义量化表。
      • DRI (Define Restart Interval) : 定义重启间隔。
      • APPn (Application-specific) : 应用程序特定的数据(例如 EXIF 信息)。
      • COM (Comment) : 注释数据。
  3. 扫描段(Scan Data)

    • 扫描段包含实际的图像数据,经过编码后的二进制流。
    • 扫描段可以包含多个扫描,每个扫描对应图像的一个或多个颜色分量。
其他特性
  • EXIF 信息

    • JPEG 文件可以包含 EXIF(Exchangeable Image File)信息,记录相机设置、拍摄日期等元数据。
  • ICC 配置文件

    • 可以嵌入 ICC 配置文件,用于色彩管理。
  • 缩略图

    • JPEG 文件可以包含缩略图,用于快速预览。

示例:使用 libjpeg 进行 JPEG 解码

#include <stdio.h>
#include <jpeglib.h>

void decode_jpeg(const char *filename) {
    struct jpeg_decompress_struct cinfo;
    struct jpeg_error_mgr jerr;

    FILE *infile;
    if ((infile = fopen(filename, "rb")) == NULL) {
        fprintf(stderr, "Can't open %s\n", filename);
        return;
    }

    cinfo.err = jpeg_std_error(&jerr);
    jpeg_create_decompress(&cinfo);
    jpeg_stdio_src(&cinfo, infile);

    if (jpeg_read_header(&cinfo, TRUE)!= JPEG_HEADER_OK) {
        fprintf(stderr, "Can't read JPEG header\n");
        jpeg_destroy_decompress(&cinfo);
        fclose(infile);
        return;
    }

    jpeg_start_decompress(&cinfo);

    int width = cinfo.output_width;
    int height = cinfo.output_height;
    int num_components = cinfo.output_components;

    printf("Image width: %d\n", width);
    printf("Image height: %d\n", height);
    printf("Number of components: %d\n", num_components);

    JSAMPARRAY buffer;
    int row_stride = width * num_components;
    buffer = (*cinfo.mem->alloc_sarray)((j_common_ptr)&cinfo, JPOOL_IMAGE, row_stride, 1);

    while (cinfo.output_scanline < height) {
        jpeg_read_scanlines(&cinfo, buffer, 1);
        // 在这里可以对每行的像素数据进行处理
    }

    jpeg_finish_decompress(&cinfo);
    jpeg_destroy_decompress(&cinfo);
    fclose(infile);
}

int main() {
    const char *filename = "your_image.jpg";
    decode_jpeg(filename);
    return 0;
}

PNG

PNG(Portable Network Graphics)是一种无损压缩的位图图像格式,支持透明度通道,广泛用于互联网和数字图像处理。PNG 格式的设计目的是替代 GIF 和 TIFF 文件格式,同时保持无专利限制。以下是 PNG 格式的一些关键特性和结构说明:

特性

  1. 无损压缩:PNG 使用无损压缩算法(通常是 DEFLATE),这意味着图像在压缩和解压缩过程中不会丢失任何信息。
  2. 支持透明度:PNG 支持 alpha 通道,允许每个像素有一个透明度值,这使得图像可以平滑地融合到背景中。
  3. 多种颜色模式:PNG 支持多种颜色模式,包括灰度、真彩色(RGB)、带 alpha 通道的真彩色等。
  4. 渐进显示:PNG 支持渐进显示,即图像可以逐步加载和显示,而不是一次性完全加载。
  5. 元数据支持:PNG 文件可以包含丰富的元数据信息,如作者、版权、日期等。
  6. 错误检测:PNG 文件包含 CRC(循环冗余校验)字段,用于检测文件传输过程中的错误。

文件结构

PNG 文件由一个文件签名和多个数据块组成。每个数据块都有固定的结构,包括长度、类型、数据和 CRC 字段。

文件签名

PNG 文件的前 8 个字节是文件签名,用于识别文件格式:

89 50 4E 47 0D 0A 1A 0A
数据块结构

每个数据块的结构如下:

  • 长度(Length):4 字节,表示数据字段的长度,最大值为 (2^{31} - 1)。

  • 类型(Type):4 字节,表示数据块的类型,常见的类型包括:

    • IHDR:图像头,包含图像的基本信息,如宽度、高度、颜色类型等。
    • PLTE:调色板,用于索引颜色模式。
    • IDAT:图像数据,包含压缩后的图像数据。
    • IEND:结束标记,表示文件的结束。
    • tRNS:透明度信息。
    • gAMA:图像的 gamma 值。
    • cHRM:图像的颜色空间信息。
    • sBIT:每个颜色通道的显著位数。
    • tEXt:文本信息。
    • zTXt:压缩的文本信息。
    • iTXt:国际化的文本信息。
  • 数据(Data):长度字段指定的数据。

  • CRC(Cyclic Redundancy Check):4 字节,用于检测数据块的完整性。

颜色类型

PNG 文件支持多种颜色类型,每种颜色类型对应不同的图像数据结构:

  • 灰度图像(Color Type 0):每个像素只有一个亮度值。
  • 灰度图像 + alpha 通道(Color Type 4):每个像素有一个亮度值和一个透明度值。
  • 真彩色图像(Color Type 2):每个像素有三个颜色值(红、绿、蓝)。
  • 真彩色图像 + alpha 通道(Color Type 6):每个像素有三个颜色值和一个透明度值。
  • 索引颜色图像(Color Type 3):每个像素是一个索引值,对应于 PLTE 调色板中的颜色。

示例:使用 libpng 解析

#include <stdio.h>
#include <png.h>

void read_png_file(char *filename) {
    FILE *fp = fopen(filename, "rb");

    if (!fp) {
        printf("Could not open file %s\n", filename);
        return;
    }

    png_structp png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
    if (!png_ptr) {
        printf("Could not create read struct\n");
        fclose(fp);
        return;
    }

    png_infop info_ptr = png_create_info_struct(png_ptr);
    if (!info_ptr) {
        printf("Could not create info struct\n");
        png_destroy_read_struct(&png_ptr, NULL, NULL);
        fclose(fp);
        return;
    }

    if (setjmp(png_jmpbuf(png_ptr))) {
        printf("Error during PNG reading\n");
        png_destroy_read_struct(&png_ptr, &info_ptr, NULL);
        fclose(fp);
        return;
    }

    png_init_io(png_ptr, fp);
    png_read_info(png_ptr, info_ptr);

    int width = png_get_image_width(png_ptr, info_ptr);
    int height = png_get_image_height(png_ptr, info_ptr);
    png_byte color_type = png_get_color_type(png_ptr, info_ptr);
    png_byte bit_depth = png_get_bit_depth(png_ptr, info_ptr);

    printf("Width: %d\n", width);
    printf("Height: %d\n", height);
    printf("Color type: %d\n", color_type);
    printf("Bit depth: %d\n", bit_depth);

    png_bytep *row_pointers = (png_bytep *)malloc(sizeof(png_bytep) * height);
    for (int y = 0; y < height; y++) {
        row_pointers[y] = (png_byte *)malloc(png_get_rowbytes(png_ptr, info_ptr));
    }

    png_read_image(png_ptr, row_pointers);

    // 在这里处理像素数据
    for (int y = 0; y < height; y++) {
        png_bytep row = row_pointers[y];
        for (int x = 0; x < width; x++) {
            switch (color_type) {
                case PNG_COLOR_TYPE_GRAY:
                    printf("Gray: %d\n", row[x]);
                    break;
                case PNG_COLOR_TYPE_RGB:
                    printf("RGB: (%d, %d, %d)\n", row[x * 3], row[x * 3 + 1], row[x * 3 + 2]);
                    break;
                case PNG_COLOR_TYPE_RGBA:
                    printf("RGBA: (%d, %d, %d, %d)\n", row[x * 4], row[x * 4 + 1], row[x * 4 + 2], row[x * 4 + 3]);
                    break;
                // 处理其他颜色类型
            }
        }
    }

    for (int y = 0; y < height; y++) {
        free(row_pointers[y]);
    }
    free(row_pointers);

    png_destroy_read_struct(&png_ptr, &info_ptr, NULL);
    fclose(fp);
}

int main() {
    char *filename = "your_png_file.png";
    read_png_file(filename);
    return 0;
}

项目框架

对每一种格式,创建一个图片解析结构体支持像素数据的提取:

typedef struct PicFileParser {
	char *name;                     /* 图片文件解析模块的名字 */
	int (*isSupport)(PT_FileMap ptFileMap);  /* 是否支持某文件 */
	int (*GetPixelDatas)(PT_FileMap ptFileMap, PT_PixelDatas ptPixelDatas);  /* 从文件中解析出图像的象素数据 */
	int (*FreePixelDatas)(PT_PixelDatas ptPixelDatas);  /* 释放图像的象素数据所占内存 */
	int (*SaveFile)(unsigned char * pRgb, unsigned int dwWidth, unsigned int dwHeight, unsigned int dwBpp, char *strFilename);
    struct PicFileParser *ptNext;  /* 链表 */
}T_PicFileParser, *PT_PicFileParser;

辅助结构 T_FileMap 支持文件的 open 和 mmap 操作:

typedef struct FileMap {
	char strFileName[256];   /* 文件名 */
	// int iFd; 
	FILE * tFp;              /* 文件句柄 */
	int iFileSize;           /* 文件大小 */
	unsigned char *pucFileMapMem;  /* 使用mmap函数映射文件得到的内存 */
}T_FileMap, *PT_FileMap;

提取保存的像素结构:

typedef struct PixelDatas {
	int iWidth;   /* 宽度: 一行有多少个象素 */
	int iHeight;  /* 高度: 一列有多少个象素 */
	int iBpp;     /* 一个象素用多少位来表示 */
	int iLineBytes;  /* 一行数据有多少字节 */
	int iTotalBytes; /* 所有字节数 */ 
	unsigned char *aucPixelDatas;  /* 象素数据存储的地方 */
}T_PixelDatas, *PT_PixelDatas;

支持 PNG 格式

定义解析结构体:

static T_PicFileParser g_tPNGParser = {
    .name = "png",
    .isSupport = isPNGFormat,
    .GetPixelDatas = GetPixelDatasFrmPNG,
    .FreePixelDatas = FreePixelDatasForPNG,
};

核心是实现接口函数 GetPixelDatasFrmPNG,完成文件的解析和像素的提取。主要包括:

首先完成对 PNG 格式的解析,这一部分可参考前面 '示例:使用 libpng 解析'

然后提取转换并保存像素数据

	// 准备输出数据
    ptPixelDatas->iWidth = iWidth;
    ptPixelDatas->iHeight = iHeight;
    // ptPixelDatas->iBpp          = 24; // 该变量调用对象设置
    ptPixelDatas->iLineBytes = iWidth * (ptPixelDatas->iBpp >> 3);
    ptPixelDatas->iTotalBytes = iHeight * ptPixelDatas->iLineBytes;
    ptPixelDatas->aucPixelDatas = (unsigned char *)malloc(ptPixelDatas->iTotalBytes);

    if (!ptPixelDatas->aucPixelDatas)
    {
        DBG_PRINTF("%s malloc error!\n", ptFileMap->strFileName);
        return -1;
    }
    unsigned char *tmpbuf = (unsigned char *)malloc(iWidth * iHeight * iRGBMode);
    if (!tmpbuf)
    {
        free(ptPixelDatas->aucPixelDatas);
        return -1;
    }
    for (iRow = 0; iRow < iHeight; iRow++)
    {
        png_read_row(ptPNG, tmpbuf, NULL); // 读取一行数据,读取的数据为24BPP,RGB
        CovertOneLine(iWidth, iRGBMode * 8, ptPixelDatas->iBpp, tmpbuf, ptPixelDatas->aucPixelDatas + iRow * ptPixelDatas->iLineBytes);
    }

辅助函数CovertOneLine主要完成bpp格式的转换,以便支持 LCD 显示:

/**********************************************************************
 * 函数名称: CovertOneLine
 * 功能描述: 根据BPP进行像素数据转换
 * 输入参数: iWidth      - 像素个数
 *           iSrcBpp     - 源数据BPP
 *           iDstBpp     - 目的数据BPP
 *           pudSrcDatas - 源像素数据,注意 png, bmp, jpg 传入像素数据格式不同,暂时无法通用
 * 输出参数: pudDstDatas - 目的像素数据
 * 返 回 值: 0 - 成功, 其他值 - 失败
 ***********************************************************************/
static int CovertOneLine(int iWidth, int iSrcBpp, int iDstBpp, unsigned char *pudSrcDatas, unsigned char *pudDstDatas)
{
    unsigned int dwRed;
	unsigned int dwGreen;
	unsigned int dwBlue;
	unsigned int dwColor;

	unsigned short *pwDstDatas16bpp = (unsigned short *)pudDstDatas;
	unsigned int   *pwDstDatas32bpp = (unsigned int *)pudDstDatas;

	int i;

    /*
        BPP格式转换说明: from 32/24 to 32/24/16
    */

	if (iSrcBpp != 24 && iSrcBpp != 32)
	{
		return -1;
	}

	if (iDstBpp == iSrcBpp)
	{
        if (iSrcBpp == 32){     // from 32 to 32: RGBA --> ARGB
	        unsigned int dwAlpha;
            for (i = 0; i < iWidth; i++){
                dwRed    = pudSrcDatas[i * iSrcBpp/8 + 0];
                dwGreen  = pudSrcDatas[i * iSrcBpp/8 + 1];
                dwBlue   = pudSrcDatas[i * iSrcBpp/8 + 2];
			    dwAlpha  = pudSrcDatas[i * iSrcBpp/8 + 3];
                dwColor = (dwAlpha << 24) | (dwRed << 16) | (dwGreen << 8) | dwBlue;
				*pwDstDatas32bpp = dwColor;
				pwDstDatas32bpp++;
            }
        }
        else // from 24 to 24: RGB --> RGB
    		memcpy(pudDstDatas, pudSrcDatas, iWidth * iDstBpp / 8);
	}
	else // iDstBpp == iSrcBpp,不考虑alpha
	{
		for (i = 0; i < iWidth; i++)
		{
			dwRed   = pudSrcDatas[i * iSrcBpp/8 + 0];
			dwGreen = pudSrcDatas[i * iSrcBpp/8 + 1];
			dwBlue  = pudSrcDatas[i * iSrcBpp/8 + 2];
			if (iDstBpp == 32)
			{
				dwColor = (dwRed << 16) | (dwGreen << 8) | dwBlue;
				*pwDstDatas32bpp = dwColor;
				pwDstDatas32bpp++;
			}
			else if (iDstBpp == 24){
				*pudDstDatas = dwRed;
				pudDstDatas++;
				*pudDstDatas = dwGreen;
				pudDstDatas++;
				*pudDstDatas = dwBlue;
				pudDstDatas++;
			}
			else if (iDstBpp == 16)
			{
				/* 565 */
				dwRed   = dwRed >> 3;
				dwGreen = dwGreen >> 2;
				dwBlue  = dwBlue >> 3;
				dwColor = (dwRed << 11) | (dwGreen << 5) | (dwBlue);
				*pwDstDatas16bpp = dwColor;
				pwDstDatas16bpp++;
			}
		}
	}
    return 0;
}
posted @ 2024-11-25 15:46  libq8  阅读(15)  评论(0编辑  收藏  举报