当前位置: 首页 > 工具软件 > LibJPEG > 使用案例 >

libjpeg、libpng使用方法

罗昱
2023-12-01

背景,没什么好说的,有把图片从rgb和jpeg中互相搞的需求,不了解的话要想在GUI显示图片还是比较麻烦的
(以下不涉及原码,仅涉及使用方式)

首先讲一下jpeg

JPEG(Joint Photographic Experts Group)

JPEG是JPEG标准的产物,该标准由国际标准化组织(ISO)制订,是面向连续色调静止图像的一种压缩标准。 JPEG格式是最常用的图像文件格式,后缀名为.jpg或.jpeg。——百度百科

JPEG图片格式组成部分:SOI(文件头)+APP0(图像识别信息)+ DQT(定义量化表)+ SOF0(图像基本信息)+ DHT(定义Huffman表) + DRI(定义重新开始间隔)+ SOS(扫描行开始)+ EOI(文件尾)

一般来说,文件开头是FF D8 FF就可以知道是jpeg文件了

简单来说这是一种有损压缩的编码格式(直接读出来是不能看的),压缩能力还是比较强的,一张480600的RGB图,光是纯unsigned char来存,需要占用大概4806003字节/1024=843.75KB,编码为JPEG再存起来大概只需要10KB。比如两万张人脸图,就算只有240320,不管是存数据库还是存flash,把裸RGB存起来肯定是不行的

这是一种图像编码格式,那要在GUI上显示,就得解码。存起来又得编码。

开始

先读出来,没什么好说的

//jpeg信息用字符串来存,里面是有可能出现0的,所以需要一个len
int jpeg_write(unsigned char *jpeg, int len, char *URL)
{
    if (URL == NULL || jpeg == NULL)
        return -1;

    FILE *fp = fopen(URL, "w");
    if (fp == NULL)
    {
        printf("open file fail\n");
        return -1;
    }

    fwrite(jpeg, len, 1, fp);
    fclose(fp);

    return 0;
}

int jpeg_read(unsigned char *jpeg, int *len, char *URL)
{
    if (URL == NULL || jpeg == NULL || len == NULL)
        return -1;

    FILE *fp = fopen(URL, "r");
    if (fp == NULL)
    {
        printf("open file fail\n");
        return -1;
    }

    *len = 0;
    while ((jpeg[*len++] = fgetc(fp)) != EOF);

    fclose(fp);
    return len - 1;
}

再转为RGB,需要通过libjpeg

libjpeg

libjpeg是一个完全用C语言编写的库,包含了被广泛使用的JPEG解码、JPEG编码和其他的JPEG功能的实现。这个库由独立JPEG工作组维护。

以下详细介绍一下怎么去使用

//一般只需要包含这个库的头文件就行
#include <jpeglib.h>
//如果要自定义错误处理功能,则还需要这个
#include <setjmp.h>

//******************** part 1 ********************
//以下功能是错误处理相关。如果不进行自定义错误处理,在出错时则会直接exit掉main。而内存不足等问题是十分常见的
typedef struct _user_jpeg_error_mgr
{
    struct jpeg_error_mgr jpg_err;  /* old jpeg error */
    jmp_buf setjmp_buffer;          /* for return to caller */
} USER_JPEG_ERROR_MGR;

METHODDEF(void) user_jpeg_error_exit(j_common_ptr cinfo)
{
    /* output error msg and jump to setjmp_buffer */
    USER_JPEG_ERROR_MGR* usr_jpg_err = (USER_JPEG_ERROR_MGR *)cinfo->err;
    (*cinfo->err->output_message)(cinfo);
    longjmp(usr_jpg_err->setjmp_buffer, 1);
}
//****************** end part 1 ******************

int jpeg_decode(unsigned char *in_buff, int in_buff_size, unsigned char **out_buff, int *out_buff_size, int *jpeg_width, int *jpeg_height, int *color_space, int *components)
{
    if (NULL == in_buff            //输入的jpeg图像信息
        || NULL == out_buff        //解码后的图像信息
        || NULL == out_buff_size   //输出图像的大小
        || NULL == color_space     //带出解码后的颜色空间
        || NULL == components      //带出解码后的颜色通道数
        || NULL == jpeg_width      //带出解码后图像的宽
        || NULL == jpeg_height)    //带出解码后图像的高
    {
        printf("some param is NULL\n");
        return -1;
    }

//******************** part 2 ********************
//参数准备
    //分配jpeg对象结构体空间,并初始化
    struct jpeg_decompress_struct cinfo;
    jpeg_create_decompress(&cinfo);

    //错误处理结构体对象,并初始化
    USER_JPEG_ERROR_MGR jerr;
    cinfo.err = jpeg_std_error(&jerr.jpg_err);
    jerr.jpg_err.error_exit = user_jpeg_error_exit;
    if (setjmp(jerr.setjmp_buffer))
    {
        jpeg_destroy_decompress(&cinfo);
        return -1;
    }

    //指定需要解码的数据源
    jpeg_mem_src(&cinfo, in_buff, in_buff_size);
    /* 这里是使用了内存中读好的RGB信息,也可以现场读文件,再使用jpeg_stdio_src,记得fclose
     * FILE * infile;
     * if ((infile = fopen("test.jpg", "rb")) == NULL)
     * {
     *      printf("fopen fail");
     *      return 0;
     * }
     * jpeg_stdio_src(&cinfo, infile);
     */

    //获取解码文件信息。将图像的缺省信息填充到cinfo结构中以便程序使用
    //包含宽cinfo.image_width,高cinfo.image_height,色彩空间cinfo.jpeg_color_space,颜色通道数cinfo.num_components等
    jpeg_read_header(&cinfo, TRUE);

    //为输出图像设定参数
    /* 可以通过分子分母来设定输出比例,仅支持1/1、1/2、1/4、1/8。如下可以设置原图1/2的图像
     * cinfo.scale_num=1;
     * cinfo.scale_denom=2;
     */
    //设置颜色空间。真彩色 JCS_RGB,通道数为3、灰度图 JCS_GRAYSCALE,通道数为1、带透明通道JCS_EXT_RGBA,通道数为4,等。可以主动设置为其他样式
    //这里设置成4可以减少字节对齐问题,如果读出来是RGB,是3个3个读,就需要申请能被4整除的 行内存块
    cinfo.out_color_space = JCS_EXT_RGBA;//cinfo.jpeg_color_space;
    cinfo.out_color_components = 4;//cinfo.num_components;

    //申请空间,带出数据
    *color_space = cinfo.out_color_space;
    *components = cinfo.out_color_components;
    *jpeg_width = cinfo.image_width;
    *jpeg_height = cinfo.image_height;

    int adjust = cinfo.image_width * cinfo.out_color_components % 4;
    *out_buff_size = (cinfo.image_width * cinfo.out_color_components + adjust) * cinfo.image_height;
    *out_buff = NULL;
    *out_buff = malloc(*out_buff_size);
    if (NULL == *out_buff)
    {
        printf("malloc out_buff_size %d failed\n", *out_buff_size);
        return -1;
    }
//****************** end part 2 ******************


//******************** part 3 ********************
/* 执行
 * 解开的数据是按照行取出的,数据像素按照scanline来存储,scanline是从左到右,从上到下的顺序,每个像素
 * 对应的各颜色或灰度通道数据是依次存储,比如一个24-bit RGB真彩色的图像中,一个scanline中的数据存储模
 * 式是R,G,B,R,G,B,R,G,B,...,每条scanline是一个JSAMPLE类型的数组,一般来说就是unsigned char,定义于
 * jmorecfg.h中。除了JSAMPLE,图像还定义了JSAMPROW和JSAMPARRAY,分别表示一行JSAMPLE和一个2D的JSAMPLE数组。
 */
    JSAMPROW row_pointer;
    //根据设定的参数开始解码
    jpeg_start_decompress(&cinfo);
    //每一行的大小
    int row_stride = cinfo.image_width * cinfo.out_color_components + adjust;

    //按照每一行来扫描,填充进输出空间里。output_scanline表示已经读出的行数
    while (cinfo.output_scanline < cinfo.output_height)
    {
        //把缓冲区的首地址放到这里
        row_pointer =(unsigned char *) &(*out_buff)[cinfo.output_scanline * row_stride];
        jpeg_read_scanlines(&cinfo, &row_pointer, 1);
    }

    //结束解码
    jpeg_finish_decompress(&cinfo);
    //释放资源
    jpeg_destroy_decompress(&cinfo);
//****************** end part 3 ******************

    return 0;
}

编码流程上差不多

int jpeg_encode(unsigned char *in_buff, int width, int height, int in_component, unsigned char **out_buff, int *out_buff_size)
{
    if (NULL == in_buff || NULL == out_buff || NULL == out_buff_size)
    {
        printf("some param is NULL\n");
        return -1;
    }
    //定义一个通道数至颜色类型的映射
    int comp_to_type[] = {0, JCS_GRAYSCALE, 0, JCS_RGB, JCS_EXT_RGBA};

    struct jpeg_compress_struct cinfo = {0};
    jpeg_create_compress(&cinfo);

    PIO_JPEG_ERROR_MGR jerr;
    cinfo.err = jpeg_std_error(&jerr.jpg_err);
    jerr.jpg_err.error_exit = pio_jpeg_error_exit;
    if (setjmp(jerr.setjmp_buffer))
    {
        jpeg_destroy_compress(&cinfo);
        return -1;
    }

    jpeg_mem_dest(&cinfo, out_buff, out_buff_size);

    cinfo.image_width = width;
    cinfo.image_height = height;
    in_component = in_component == 0 ? 4 : in_component;//默认使用RGBA和4通道
    cinfo.in_color_space = comp_to_type[in_component];
    cinfo.input_components = in_component;
    jpeg_set_defaults(&cinfo);//该函数会自动设置除去上面4个参数外的其他参数为默认值

    JSAMPROW row_pointer;
    jpeg_start_compress(&cinfo, TRUE);
    int row_stride = cinfo.image_width * cinfo.input_components;
    while (cinfo.next_scanline < cinfo.image_height)
    {
        row_pointer = (unsigned char *) &in_buff[cinfo.next_scanline * row_stride];
        jpeg_write_scanlines(&cinfo, &row_pointer, 1);
    }

    jpeg_finish_compress(&cinfo);
    jpeg_destroy_compress(&cinfo);

    return 0;
}

至此实现了RGB图像和jpeg文件互转
上述俩函数中均会为out_buff申请内存,需要外部释放。解码时可见主动申请内存,编码时在jpeg_mem_dest中申请内存

PNG(Portable Network Graphics)

png是一种采用无损压缩算法的位图格式,其设计目的是试图替代GIF和TIFF文件格式,同时增加一些GIF文件格式所不具备的特性。PNG使用从LZ77派生的无损数据压缩算法,一般应用于JAVA程序、网页或S60程序中,原因是它压缩比高,生成文件体积小。——百度百科

憋扯这没用的直接开搞

libpng

libpng是一款C语言编写的比较底层的读写PNG文件的跨平台的库

//头文件没什么好说的
#include <png.h>

//写png文件
int png_fwrite(unsigned char *in_buff, int width, int height, int components, const char *URL)
{
	if (NULL == in_buff || NULL == URL)
	{
		printf("some param is NULL\n");
		return -1;
	}
	//定义一个通道数至颜色类型的映射,1为PNG_COLOR_TYPE_GRAY、3为PNG_COLOR_TYPE_RGB、4为PNG_COLOR_TYPE_RGBA
	int comp_to_type[] = {0, PNG_COLOR_TYPE_GRAY, 0, PNG_COLOR_TYPE_RGB, PNG_COLOR_TYPE_RGBA};

	// 第一步,初始化libpng库
	png_structp png_ptr = NULL;			// 首先创建两个结构体指针,这两个可以写成全局变量来用
	png_infop info_ptr = NULL;			// 每这两个结构体对应一个PNG文件,同时操作多个需要定义多个

	png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
	if (!png_ptr)
	{
		printf("Failed to initialize PNG writer.\n");
		return -1;
	}

	info_ptr = png_create_info_struct(png_ptr);
	if (!info_ptr)
	{
		printf("Failed to initialize PNG writer.\n");
		png_destroy_write_struct(&png_ptr, NULL);
		return -1;
	}

	int iRetVal = 0;
	if (iRetVal = setjmp(png_jmpbuf(png_ptr)))	// 安装错误处理跳转点
	{						//当libpng内部出现错误的时候,libpng会调用longjmp直接跳转到这里运行。
		printf("Failed to write PNG image. errcode = %d\n", iRetVal);
		png_destroy_write_struct(&png_ptr, &info_ptr);	// 这个函数可以两个结构体一起释放,也可以单独。结束对某个PNG文件的操作后需要调用
		return -1;
	}

	// 第二步,操作文件
	FILE *fp = fopen(URL, "wb");
	if (!fp)
	{
		png_destroy_write_struct(&png_ptr, &info_ptr);
		return -1;
	}
	png_init_io(png_ptr, fp);			// 打开文件后,让libpng和这个文件流绑定起来

	//第三步,设置PNG文件属性、写入PNG文件头、写入PNG文件
	png_set_IHDR(png_ptr, info_ptr,
			width, height,			// 图像尺寸
			8,				// 颜色深度,每个颜色成分占用位数,PNG_COLOR_TYPE_GRAY、PNG_COLOR_TYPE_RGB、PNG_COLOR_TYPE_RGBA都是8
			comp_to_type[components],	// 颜色类型,通道为1是PNG_COLOR_TYPE_GRAY、3为PNG_COLOR_TYPE_RGB、4为PNG_COLOR_TYPE_RGBA
			PNG_INTERLACE_NONE,		// 不交错。PNG_INTERLACE_ADAM7表示这个PNG是交错格式,在网络上传输能以最大速度显示出图像的大致样子
			PNG_COMPRESSION_TYPE_BASE,	// 压缩方式,可选择PNG_COMPRESSION_TYPE_BASE、PNG_COMPRESSION_TYPE_DEFAULT
			PNG_FILTER_TYPE_BASE);		// 暂不清楚,可选择PNG_FILTER_TYPE_BASE、PNG_FILTER_TYPE_DEFAULT,和压缩方式同步
	png_set_packing(png_ptr);			// 设置打包信息
	png_write_info(png_ptr, info_ptr);		// 写入文件头
	//到这里,已经通过fp写入了PNG的文件头

	//第四步,逐行写入
	/* 写图的方法之一是调用png_write_image(png_ptr,行指针数组的指针);这个你不需要考虑交错文件的写入的遍数。
	而如果你需要手动写入每一行的数据,你需要调用png_write_row(png_ptr,一行像素的指针);来进行逐行的像素值写入。
	如果你设置了交错格式的PNG,你需要多写入几遍图形数据,你需要调用png_set_interlace_handling(png_ptr);来得知你需要写入的遍数。
	如果你没有设置交错格式的PNG,你只需要写入一遍。

	文件内的排布和jpeg一样,设定PNG_COLOR_TYPE_RGB的话,每一行是RGBRGBRGB...
	PNG_COLOR_TYPE_RGBA的话就是RGBARGBARGBA...
	使用png_write_row的话,第二个参数为每一行像素,一行行写就行了
	使用png_write_image,需要定义一个指针数组,每个元素指向每一行像素,调用一次全部写完
	*/
	int i = 0;
	png_byte *row_pointers[height];
	for (i = 0; i < height; i++)
	{
		row_pointers[i] = in_buff + i * width * components;//每个行指针指向每一行像素的首地址
	}
	png_write_image(png_ptr, row_pointers);

	//第五步,关闭、销毁
	png_write_end(png_ptr,info_ptr); //写入文件尾
	png_destroy_write_struct(&png_ptr,&info_ptr); //结束对这个PNG的访问
	if (fp) fclose(fp);
	return 0;
}

读png比较复杂,使用上未能覆盖所有情况,以下代码仅参考

typedef struct _pic_data
{
 int width, height; /* 尺寸 */
 int bit_depth;  /* 位深 */
 int flag;   /* 一个标志,表示是否有alpha通道 */

 unsigned char **rgba; /* 图片数组 */
} pic_data;
/**********************************************************************/
#define PNG_BYTES_TO_CHECK 4
#define HAVE_ALPHA 1
#define NO_ALPHA 0


int detect_png(char *filepath, pic_data *out)
/* 用于解码png图片 */
{
 FILE *pic_fp;
 pic_fp = fopen(filepath, "rb");
 if(pic_fp == NULL) /* 文件打开失败 */
  return -1;
 
 /* 初始化各种结构 */
 png_structp png_ptr;
 png_infop  info_ptr;
 char        buf[PNG_BYTES_TO_CHECK];
 int        temp;
 
 png_ptr  = png_create_read_struct(PNG_LIBPNG_VER_STRING, 0, 0, 0);
 info_ptr = png_create_info_struct(png_ptr);
 
 setjmp(png_jmpbuf(png_ptr)); // 这句很重要
 
 temp = fread(buf,1,PNG_BYTES_TO_CHECK,pic_fp);
 temp = png_sig_cmp((void*)buf, (png_size_t)0, PNG_BYTES_TO_CHECK);
 
 /*检测是否为png文件*/
 if (temp!=0) return 1;
 
 rewind(pic_fp);
 /*开始读文件*/
 png_init_io(png_ptr, pic_fp);
 // 读文件了
 png_read_png(png_ptr, info_ptr, PNG_TRANSFORM_EXPAND, 0);

 int color_type,channels;
 
 /*获取宽度,高度,位深,颜色类型*/
 channels      = png_get_channels(png_ptr, info_ptr); /*获取通道数*/
 out->bit_depth = png_get_bit_depth(png_ptr, info_ptr); /* 获取位深 */
 color_type    = png_get_color_type(png_ptr, info_ptr); /*颜色类型*/
 
 int i,j;
 int size, pos = 0;
 /* row_pointers里边就是rgba数据 */
 png_bytep* row_pointers;
 row_pointers = png_get_rows(png_ptr, info_ptr);
 out->width = png_get_image_width(png_ptr, info_ptr);
 out->height = png_get_image_height(png_ptr, info_ptr);
 
 size = out->width * out->height; /* 计算图片的总像素点数量 */

 if(channels == 4 || color_type == PNG_COLOR_TYPE_RGB_ALPHA)
 {/*如果是RGB+alpha通道,,或者RGB+其它字节*/
  size *= (4*sizeof(unsigned char)); /* 每个像素点占4个字节内存 */
  out->flag = HAVE_ALPHA;    /* 标记 */
  out->rgba = (unsigned char**) malloc(size);
  if(out->rgba == NULL)
  {/* 如果分配内存失败 */
   fclose(pic_fp);
   puts("错误(png):无法分配足够的内存供存储数据!");
   return 1;
  }

  temp = (4 * out->width);/* 每行有4 * out->width个字节 */
  for(i = 0; i < out->height; i++)
  {
   for(j = 0; j < temp; j += 4)
   {/* 一个字节一个字节的赋值 */
    out->rgba[0][pos] = row_pointers[i][j]; // red
    out->rgba[1][pos] = row_pointers[i][j+1]; // green
    out->rgba[2][pos] = row_pointers[i][j+2];  // blue
    out->rgba[3][pos] = row_pointers[i][j+3]; // alpha
    ++pos;
   }
  }
 }
 else if(channels == 3 || color_type == PNG_COLOR_TYPE_RGB)
 {/* 如果是RGB通道 */
  size *= (3*sizeof(unsigned char)); /* 每个像素点占3个字节内存 */
  out->flag = NO_ALPHA;    /* 标记 */
  out->rgba = (unsigned char**) malloc(size);
  if(out->rgba == NULL)
  {/* 如果分配内存失败 */
   fclose(pic_fp);
   puts("错误(png):无法分配足够的内存供存储数据!");
   return 1;
  }

  temp = (3 * out->width);/* 每行有3 * out->width个字节 */
  for(i = 0; i < out->height; i++)
  {
   for(j = 0; j < temp; j += 3)
   {/* 一个字节一个字节的赋值 */
    out->rgba[0][pos] = row_pointers[i][j]; // red
    out->rgba[1][pos] = row_pointers[i][j+1]; // green
    out->rgba[2][pos] = row_pointers[i][j+2];  // blue
    ++pos;
   }
  }
 }
 else return 1;
 
 /* 撤销数据占用的内存 */
 png_destroy_read_struct(&png_ptr, &info_ptr, 0);
 return 0;
}

以下读取更方便,但是有一些新版本适配的问题,有些函数没有,比如png_read_png

png_infop info_ptr;
png_bytepp row_pointers;

void read_png(char *file_name)
{
    FILE *fp = fopen(file_name, "rb");
    png_structp png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
    png_infop info_ptr = png_create_info_struct(png_ptr);
    png_init_io(png_ptr, fp);
    png_read_png(png_ptr, info_ptr, PNG_TRANSFORM_IDENTITY, NULL);
    row_pointers = png_get_rows(png_ptr, info_ptr);
    png_destroy_read_struct(&png_ptr, &info_ptr, NULL);
    fclose(fp);
}

把RGB搞到内存中,就可以进行各类操作了,显示,翻转等等

 类似资料: