背景,没什么好说的,有把图片从rgb和jpeg中互相搞的需求,不了解的话要想在GUI显示图片还是比较麻烦的
(以下不涉及原码,仅涉及使用方式)
首先讲一下jpeg
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是一个完全用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是一种采用无损压缩算法的位图格式,其设计目的是试图替代GIF和TIFF文件格式,同时增加一些GIF文件格式所不具备的特性。PNG使用从LZ77派生的无损数据压缩算法,一般应用于JAVA程序、网页或S60程序中,原因是它压缩比高,生成文件体积小。——百度百科
憋扯这没用的直接开搞
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搞到内存中,就可以进行各类操作了,显示,翻转等等