图像解码之一——使用libjpeg解码jpeg图片
libjpeg简介
libjpeg库的数据结构
libjpeg库的使用
1、设置出错处理函数
1:
2: cinfo.err = jpeg_std_error(&jerr.pub);
3: jerr.pub.error_exit = my_error_exit;
4:
5: if (setjmp(jerr.setjmp_buffer)) {
6:
9: jpeg_destroy_decompress(&cinfo);
10: fclose(infile);
11: return 0;
12: }
1:
4: METHODDEF(void)
5: my_error_exit (j_common_ptr cinfo)
6: {
7:
8: my_error_ptr myerr = (my_error_ptr) cinfo->err;
9:
10:
11:
12: (*cinfo->err->output_message) (cinfo);
13:
14:
15: longjmp(myerr->setjmp_buffer, 1);
16: }
2、初始化解码对象
1:
2: jpeg_create_decompress(&cinfo);
3、初始化源数据
1:
2: jpeg_stdio_src(&cinfo, infile);
4、读取jpeg文件的头信息
1:
2: (void) jpeg_read_header(&cinfo, TRUE);
5、设置解码参数
out_color_space:输出的颜色格式,libjpeg定义如下:
1:
2: typedef enum {
3: JCS_UNKNOWN,
4: JCS_GRAYSCALE,
5: JCS_RGB,
6: JCS_YCbCr,
7: JCS_CMYK,
8: JCS_YCCK,
9: #ifdef ANDROID_RGB
10: JCS_RGBA_8888,
11: JCS_RGB_565
12: #endif
13: } J_COLOR_SPACE;
我们可以看出谷歌在Android扩展了几种输出格式,那么如果你需要的颜色格式输出格式libjpeg不支持(比如:YUYV等颜色格式),那么请参考Android对libjpeg的扩展自行修改,不用担心复杂性,实现起来比较easy。请重点研究jdcolor.c文件中的jinit_color_deconverter函数。
scale_num,scale_denom:因为实际的显示设备千变万化,我们可能需要根据实际情况对输出数据进行一些缩放才能够显示。libjpeg支持对输出数据进行缩放(scale),这个变量就是用来设置缩放的参数。目前libjpeg支持1/2,1/4,1/8三种缩放。
mem:可以指定内存管理相关的内容,比如分配和释放内存,指定libjpeg可以使用的最大内存。默认情况下不同的平台下面都有一个libjpeg默认最大可用内存值,比如Android平台上面该值为10000000L(10M),请参考jmemxxxx.c文件中的DEFAULT_MAX_MEM,了解不同平台的默认最大内存值。通过修改mem->pub.max_memory_to_use的值,库的使用者可以自定义libjpeg可以使用的最大内存值。
6、开始解码
1:
2: (void) jpeg_start_decompress(&cinfo);
7、读取解码数据
1:
4: while (cinfo.output_scanline < cinfo.output_height) {
5:
9: (void) jpeg_read_scanlines(&cinfo, buffer, 1);
10:
11: put_scanline_someplace(buffer[0], row_stride);
12: }
8、结束解码
1:
2: (void) jpeg_finish_decompress(&cinfo);
9、释放解码对象
1:
2:
3:
4: jpeg_destroy_decompress(&cinfo);
总结
标签:image,
LibJpeg解码内存中的Jpeg数据
熟悉libjpeg的朋友都知道libjpeg是一个开源的库。Linux和Android都是用libjpeg来支持jpeg文件的,可见其功能多么强大。但是默认情况下libjpeg只能处理jpeg文件的解码,或者把图像编码到jpeg文件。在嵌入式设备中没有文件系统也是很正常的事情,难道我们就不能利用libjpeg的强大功能了吗?当然不是!本文将会介绍怎样扩展libjpeg让其能够解码内存中的jpeg数据。
struct jpeg_source_mgr { const JOCTET * next_input_byte; size_t bytes_in_buffer; JMETHOD(void, init_source, (j_decompress_ptr cinfo)); JMETHOD(boolean, fill_input_buffer, (j_decompress_ptr cinfo)); JMETHOD(void, skip_input_data, (j_decompress_ptr cinfo, long num_bytes)); JMETHOD(boolean, resync_to_restart, (j_decompress_ptr cinfo, int desired)); JMETHOD(void, term_source, (j_decompress_ptr cinfo)); };
可以看出source manager对象可以注册多个回调函数来对数据进行读写。在看jdatasrc.c中的代码:
typedef struct { struct jpeg_source_mgr pub; FILE * infile; JOCTET * buffer; boolean start_of_file; } my_source_mgr;
该文件为jpeglib的source manger初始化和管理的地方。上面的数据结构是内部使用的源数据。可以看出其源数据只支持文件输入(infile变量),并提供缓存功能(buffer变量)。
EXTERN(void) jpeg_stdio_src JPP((j_decompress_ptr cinfo, FILE * infile));
通过这个接口我们可以看出它的source manager只能接收文件作为输入。该函数的实现在jdatasrc.c文件中。
typedef struct{ UINT8* img_buffer; UINT32 buffer_size; UINT32 pos; }BUFF_JPG; typedef struct { struct jpeg_source_mgr pub; union{ BUFF_JPG jpg; VFS_FILE * infile; }; JOCTET * buffer; boolean start_of_file; } my_source_mgr;
可以看出我们通过union来支持内存数据(jpg变量)或者文件输入。因为需要负责读写必须要标识出当前内存读写的位置,所以必须要在BUFF_JPG数据结构中定义pos变量。
METHODDEF(boolean) jpg_fill_input_buffer (j_decompress_ptr cinfo) { my_src_ptr src = (my_src_ptr) cinfo->src; size_t nbytes; if(src->jpg.img_buffer == NULL || src->jpg.pos >= src->jpg.buffer_size){ nbytes = -1; } else { nbytes = (src->jpg.pos + INPUT_BUF_SIZE > src->jpg.buffer_size ? src->jpg.buffer_size - src->jpg.pos : INPUT_BUF_SIZE); MEMCPY(src->buffer, src->jpg.img_buffer + src->jpg.pos, nbytes); src->jpg.pos += nbytes; } if (nbytes <= 0) { if (src->start_of_file) ERREXIT(cinfo, JERR_INPUT_EMPTY); WARNMS(cinfo, JWRN_JPEG_EOF); src->buffer[0] = (JOCTET) 0xFF; src->buffer[1] = (JOCTET) JPEG_EOI; nbytes = 2; } src->pub.next_input_byte = src->buffer; src->pub.bytes_in_buffer = nbytes; src->start_of_file = FALSE; return TRUE; }
可以看出我们读取数据都是从内存缓存中读取,如果到达缓存末尾就返回-1。
METHODDEF(void) skip_input_data (j_decompress_ptr cinfo, long num_bytes) { my_src_ptr src = (my_src_ptr) cinfo->src; if (num_bytes > 0) { while (num_bytes > (long) src->pub.bytes_in_buffer) { num_bytes -= (long) src->pub.bytes_in_buffer; (void) fill_input_buffer(cinfo); } src->pub.next_input_byte += (size_t) num_bytes; src->pub.bytes_in_buffer -= (size_t) num_bytes; } }
请注意显示地调用了fill_input_buffer,而不是调用注册给source manager的回调函数。这样做是不严谨的,虽然只支持文件输入的情况下,这样写没有任何问题,但是如果我们增加其他的输入方式的话(比如内存数据输入),这样写将不会调用到我们注册给Source manager的fill_input_buffer回调函数。所以如上的代码修改为:
METHODDEF(void) skip_input_data (j_decompress_ptr cinfo, long num_bytes) { my_src_ptr src = (my_src_ptr) cinfo->src; if (num_bytes > 0) { while (num_bytes > (long) src->pub.bytes_in_buffer) { num_bytes -= (long) src->pub.bytes_in_buffer; //(void) fill_input_buffer(cinfo); (void) src->pub.fill_input_buffer(cinfo); } src->pub.next_input_byte += (size_t) num_bytes; src->pub.bytes_in_buffer -= (size_t) num_bytes; } }
调用我们注册的回调函数来读取数据。
GLOBAL(void) jpeg_stdio_buffer_src (j_decompress_ptr cinfo, UINT8 * buffer, UINT32 size) { my_src_ptr src; if (cinfo->src == NULL) { cinfo->src = (struct jpeg_source_mgr *) (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_PERMANENT, SIZEOF(my_source_mgr)); src = (my_src_ptr) cinfo->src; src->buffer = (JOCTET *) (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_PERMANENT, INPUT_BUF_SIZE * SIZEOF(JOCTET)); } src = (my_src_ptr) cinfo->src; src->pub.init_source = init_source; src->pub.fill_input_buffer = jpg_fill_input_buffer; src->pub.skip_input_data = skip_input_data; src->pub.resync_to_restart = jpeg_resync_to_restart; src->pub.term_source = term_source; //src->infile = infile; src->jpg.img_buffer = buffer; src->jpg.buffer_size = size; src->jpg.pos = 0; src->pub.bytes_in_buffer = 0; src->pub.next_input_byte = NULL; }
通过该函数会发现:我们用户输入的buffer初始化了my_source_mgr,并用我们实现的回调函数jpg_fill_input_buffer初始化了jpeg_source_mgr数据结构中的fill_input_buffer。这样每次libjpeg读取数据就将会调用jpg_fill_input_buffer来读取内存jpeg数据了。
EXTERN(void) jpeg_stdio_buffer_src JPP((j_decompress_ptr cinfo, UINT8 * buffer, UINT32 size));