1. ijg库解码超大型jpeg图片(>100M)的时候,如何避免内存溢出。
采用边解码边压缩的策略,每次解码一行或者若干行图片数据,然后对于这些解码的数据,进行DQT(量化处理,过滤掉高频的数据,保持低频的数据),
这样解码完,也压缩完。
2. ijg库提供给我们的压缩接口都非常单一,仅有文件流操作,也就是仅仅只有从文件(图片)中读取,然后保存到文件中,而我们在解码大图片的时候,
一般是希望它能够留在缓存中,所以我们需要对源文件进行数据导向内存中
3. 一般而言,我们在进行图片压缩的时候,往往都希望能够随意调整图片的大小(w*h )比如原始图片时800*600,我们希望能够调整到300*300,而且
保证尽可能保持原有图片清晰度的情况
好现在对于每一个问题,我们来进行逐一的解决:
第一个问题:
1 #include<Windows.h> 2 #include <stdio.h> 3 #include "jpeglib.h" 4 #include <setjmp.h> 5 6 7 typedef struct picture{ 8 9 int image_height; /* 高 */ 10 int image_width; /* 宽 */ 11 int quality; /*质量亏损*/ 12 } Picture; 13 14 typedef struct picture Picture; 15 16 Picture m_pict; //设定一个设置参数的变量 17 18 typedef unsigned char * wu_char; 19 20 wu_char outdata; //开辟一个较大的一维数组 21 22 23 struct my_error_mgr { 24 struct jpeg_error_mgr pub; /* "public" fields */ 25 26 jmp_buf setjmp_buffer; /* for return to caller */ 27 }; 28 29 typedef struct my_error_mgr * my_error_ptr; 30 31 METHODDEF(void) 32 my_error_exit (j_common_ptr cinfo) 33 { 34 my_error_ptr myerr = (my_error_ptr) cinfo->err; 35 (*cinfo->err->output_message) (cinfo); 36 longjmp(myerr->setjmp_buffer, 1); 37 } 38 39 40 GLOBAL(int) 41 read_JPEG_file (char * filename,int* imagesize) 42 { 43 struct jpeg_decompress_struct cinfo; //解压图片信息 44 struct my_error_mgr jerr; //解压过程中错误信息 45 FILE * infile; /* 资源文件 */ 46 JSAMPARRAY buffer; /* 每次读取[1~N]行缓冲数据 暂定为一行数据 */ 47 int row_stride; /*实际宽度大小*/ 48 if ((infile = fopen(filename, "rb")) == NULL) { 49 fprintf(stderr, "文件不存在 %s ", filename); 50 return 0; 51 } 52 cinfo.err = jpeg_std_error(&jerr.pub); //解压过程中数据出错地址给予图片信息 53 jerr.pub.error_exit = my_error_exit; 54 if (setjmp(jerr.setjmp_buffer)) { //出错时,跳到这儿 55 jpeg_destroy_decompress(&cinfo); 56 fclose(infile); 57 return 0; 58 } 59 60 jpeg_create_decompress(&cinfo); //创建解压信息 61 jpeg_stdio_src(&cinfo, infile); //获得资源信息 62 (void) jpeg_read_header(&cinfo, TRUE); //获取图片信息 63 (void) jpeg_start_decompress(&cinfo); //开始解压 64 row_stride = cinfo.output_width * cinfo.output_components; //依据通道格式来进行每行宽度调整RGB格式3的倍数,灰度格式1的倍数 65 buffer = (*cinfo.mem->alloc_sarray) 66 ((j_common_ptr) &cinfo, JPOOL_IMAGE, row_stride, 1); //分配每行数组的大小 67 68 //write 69 struct jpeg_compress_struct wcinfo; 70 struct jpeg_error_mgr wjerr; 71 // FILE * outfile; /* target file */ 72 JSAMPROW row_pointer[1]; /* pointer to JSAMPLE row[s] */ 73 int wrow_stride; /* physical row width in image buffer */ 74 wcinfo.err = jpeg_std_error(&wjerr); 75 jpeg_create_compress(&wcinfo); 76 /*需要在内存中完成解压和压缩,而且必须保证时间比较快, 77 *所以使用外部内存不够理想,需要对源码进行改动,实现 78 *将目的地接口改为我们申请的一个较小的内存块中,这里讲 79 *所有指向File文件的数据流修改为指向char/unsigned char 80 *型数组中,这里比较 81 */ 82 83 jpeg_stdio_dest(&wcinfo, outdata,imagesize); 84 /*保持原始的图片大小,保持质量亏损*/ 85 wcinfo.image_width =cinfo.image_width ; 86 wcinfo.image_height =cinfo.image_height; 87 wcinfo.input_components = 3; /* # of color components per pixel */ 88 wcinfo.in_color_space = JCS_RGB; /* colorspace of input image */ 89 jpeg_set_defaults(&wcinfo); 90 wcinfo.scale_num=3; /*放大多少倍*/ 91 wcinfo.scale_denom=14; 92 jpeg_set_quality(&wcinfo, m_pict.quality, TRUE ); 93 jpeg_start_compress(&wcinfo, TRUE); 94 wrow_stride = m_pict.image_width * 3; /* JSAMPLEs per row in image_buffer */ 95 // int cmod=cinfo.image_height/wcinfo.image_height; 96 while (cinfo.output_scanline < cinfo.output_height) { 97 (void) jpeg_read_scanlines(&cinfo, buffer, 1); //解压出数据 98 //if(cinfo.output_scanline%cmod==0) 99 (void) jpeg_write_scanlines(&wcinfo,buffer,1); //压缩数据 100 } 101 102 (void) jpeg_finish_decompress(&cinfo); 103 jpeg_destroy_decompress(&cinfo); 104 fclose(infile); 105 jpeg_finish_compress(&wcinfo); 106 jpeg_destroy_compress(&wcinfo); 107 return 1; 108 }
第二个问题,如何将文件从文件区导向缓冲区
我们在压缩的时候,需要声明这个接口,来实现指针的传值,
struct jpeg_compress_struct wcinfo; struct jpeg_error_mgr wjerr;
同时需要用这个函数,将开辟的地址绑定,ijg源码提供的只有File* 接口,所以我们需要模仿这个函数,另外在写一个这个函数(最好方法就是用模板类来实现),这里我们只是简单的说下思路,
重写一个这个函数
jpeg_stdio_dest (j_compress_ptr cinfo, File* outfile,int *imagesize)
另写一个这样的函数,将参数修改为unsigned char *型
jpeg_stdio_dest (j_compress_ptr cinfo, unsigned char * outdata,int *imagesize)
然后在jpeglib.h中找到
EXTERN(void) jpeg_stdio_dest JPP((j_compress_ptr cinfo, FILE * outfile));
将其修改为
EXTERN(void) jpeg_stdio_dest JPP((j_compress_ptr cinfo, unsigned char * outdata, int *imagesize/*返回压缩后图片大小*/));
所以和这个File * outfile的数据类型,修改完这些之后,还需要修改的几个地方
文件 jdatadst.c (jpeg数据目的地文件)中
找到这个结构体,修改或者增加几个自定义变量,中文解释部分为自己加的
typedef struct { struct jpeg_destination_mgr pub; /* public fields */ unsigned char * outdata; /*自定义数据缓冲地*/ FILE * outfile; /* target stream */ JOCTET * buffer; /* start of buffer */ int *imageSize; /*表示图片大小*/ int moveSize ; /*偏移量大小*/ } my_destination_mgr;
找到这个函数
METHODDEF(boolean)
empty_output_buffer (j_compress_ptr cinfo)
将里面的文件操作,修改为内存复制便可,下面是将文件操作和缓存流结合起来放在一个文件中(加了一个标志位m_flag,ox011 表示文件操作,ox010便是缓存)
METHODDEF(boolean) empty_output_buffer (j_compress_ptr cinfo) { my_dest_ptr dest = (my_dest_ptr) cinfo->dest; if( dest->pub.m_flag==0x011 ){ if (JFWRITE(dest->outfile, dest->buffer, OUTPUT_BUF_SIZE) != (size_t) OUTPUT_BUF_SIZE) ERREXIT(cinfo, JERR_FILE_WRITE); } else{ //使用的是内存缓冲数据管理 //所以需要开辟一个新的数组 /*这里存在一个疑问,数组的大小如何控制,偏移量如何管控? 需要去思考 解答: empty_mem_output_buffer 在这个函数中,因为使用了buffer不断的扩充内存,所以不需要控制 */ /*实现内存位置偏移,这里貌似存在一个问题,就是dest是一个临时的指针*/ MEMCOPY( dest->outdata+ dest->moveSize,dest->buffer,OUTPUT_BUF_SIZE ); dest->moveSize+=OUTPUT_BUF_SIZE; //偏移增量 *(dest->imageSize)=dest->moveSize; } dest->pub.next_output_byte = dest->buffer; dest->pub.free_in_buffer = OUTPUT_BUF_SIZE; return TRUE; }
同时在这个文件下面找到
METHODDEF(void) term_destination (j_compress_ptr cinfo)
将这个文件的所有文件操作修改为内存复制
1 METHODDEF(void) 2 term_destination (j_compress_ptr cinfo) 3 { 4 my_dest_ptr dest = (my_dest_ptr) cinfo->dest; 5 size_t datacount = OUTPUT_BUF_SIZE - dest->pub.free_in_buffer; 6 7 if(dest->pub.m_flag==0x011){ 8 //表示使用文件为目的地 9 /* Write any data remaining in the buffer */ 10 if (datacount > 0) { 11 if (JFWRITE(dest->outfile, dest->buffer, datacount) != datacount) 12 ERREXIT(cinfo, JERR_FILE_WRITE); 13 } 14 fflush(dest->outfile); 15 /* Make sure we wrote the output file OK */ 16 if (ferror(dest->outfile)) 17 ERREXIT(cinfo, JERR_FILE_WRITE); 18 }else{ 19 //否则为内存数据流 20 if (datacount > 0){ 21 assert(dest->outdata==NULL); //判断数组是否为空 22 MEMCOPY(dest->outdata+dest->moveSize,dest->buffer,datacount); 23 dest->moveSize+=datacount; 24 *(dest->imageSize)=dest->moveSize; 25 } 26 } 27 }
第三个问题,关于图片缩放问题
j_compress_ptr 这个结构体有这个两个变量来设置,但是只找到了等比例缩放
unsigned int scale_num, scale_denom; /* fraction by which to scale image */