libjpeg的使用

Author Avatar
Magicmanoooo 3月 09, 2019
  • 在其它设备中阅读本文章

libjpeg是一个完全使用C语言编写的library,包含了一些主要部件:JPEG解码、JPEG编码以及其他的JPEG功能实现。

使用libjpeg库在解码jpeg数据的时候,最重要的数据结构是struct jpeg_decompress_struct,一般变量定义为cinfo变量,该变量保存着jpeg数据的详细信息,也保存着解码之后输出数据的详细信息。一般情况下,每次调用libjpeg库的API的时候,都需要将这个变量作为第一个参数传入。此外,用户也可以通过修改该变量来修改libjpeg的行为,例如:输出数据格式、libjpeg库可用的最大内存等。

1. 设置出错处理函数

在使用libjpeg库的时候难免会产生错误,所以在使用libjpeg解码之前,首先要做好错误处理。在libjpeg库之中,实现了默认错误处理函数,当错误发生时,例如:如果内存不足,则默认错误处理函数将会调用exit函数结束整个进程。这对于用户来说,这样的特性并不是很合适,不过libjpeg提供了接口让用户可以注册自定义错误处理函数。

C语言虽然没有C++的异常处理机制,但是提供了setjmplongjmp机制来实现类似的功能。

例子:

  /* We set up the normal JPEG error routines, then override error_exit. */
  cinfo.err = jpeg_std_error(&jerr.pub);
  jerr.pub.error_exit = my_error_exit;
  /* Establish the setjmp return context for my_error_exit to use. */
  if (setjmp(jerr.setjmp_buffer)) {
    /* If we get here, the JPEG code has signaled an error.
     * We need to clean up the JPEG object, close the input file, and return.
     */
    jpeg_destroy_decompress(&cinfo);
    fclose(infile);
    return 0;
  }

上述代码的重点在于,用户向libjpeg注册了一个my_error_exit回调函数。当发生错误的时候,该回调函数将会被调用。然后调用setjmp函数,设置一个返回点。这样,在my_error_exit回调函数处理完错误信息之后,就可以调用longjmp函数返回到这里,在这个返回点进行资源的释放。

其中,my_error_exit回调函数的实现为:

/*
 * Here's the routine that will replace the standard error_exit method:
 */

METHODDEF(void)
my_error_exit (j_common_ptr cinfo)
{
  /* cinfo->err really points to a my_error_mgr struct, so coerce pointer */
  my_error_ptr myerr = (my_error_ptr) cinfo->err;

  /* Always display the message. */
  /* We could postpone this until after returning, if we chose. */
  (*cinfo->err->output_message) (cinfo);

  /* Return control to the setjmp point */
  longjmp(myerr->setjmp_buffer, 1);
}

可以通过检查cinfo->err->msg_code的值来判断错误类型,进行相应的处理。在此例中,只是简单地打印一个错误信息。最后调用longjmp跳转到setjmp调用的地方。

2. 初始化解码对象

要使用libjpeg解码jpeg数据,这一步很重要。

/* Now we can initialize the JPEG decompression object. */
  jpeg_create_decompress(&cinfo);

在这一步之后,如果解码结束或者解码出错之后,需要调用jpeg_destroy_decompress()来销毁解码对象,否则将会导致内存。

3. 初始化源数据

  /* Step 2: specify data source (eg, a file) */
  jpeg_stdio_src(&cinfo, infile);

【注】:libjpeg只支持文件类型的输入,其他的类型(例如,内存数据、网络数据等)却不支持。

4. 读取jpeg文件的头信息

/* Step 3: read file parameters with jpeg_read_header() */
  (void) jpeg_read_header(&cinfo, TRUE);

这一步和初始化解码对象一样,是必须调用的。

设置解码参数

这一步在许多情况下都十分重要,例如设置输出格式、设置scale(缩放)等功能,这些功能都需要在这一步设置。参数设置通过修改上一步的得到的cinfo的值来实现。

一些常用的字段:
out_color_space输出的颜色格式,其定义为:


scale_numscale_denom因为实际的显示设备千变万化,用户可能需要根据实际情况对输出数据进行一些缩放才能够显示。libjpeg支持对输出数据进行缩放(scale),这个变量就是用来设置缩放的参数。目前libjpeg支持1/21/41/8三种缩放。

mem可以指定内存管理相关的内容,比如分配和释放内存,指定libjpeg可以使用的最大内存。默认情况下,不同的平台下面都有一个libjpeg默认最大可用内存值(参考jmemxxxx.c文件中的DEFAULT_MAX_MEM,了解不同平台的默认最大内存值)。通过修改mem->pub.max_memory_to_use的值,库的使用者可以自定义libjpeg可以使用的最大内存值。

6. 开始解码

经过之前的参数设置,便可以开始解码。

  /* Step 5: Start decompressor */
  (void) jpeg_start_decompress(&cinfo);

7. 读取解码数据

  /* Here we use the library's state variable cinfo.output_scanline as the
   * loop counter, so that we don't have to keep track ourselves.
   */
  while (cinfo.output_scanline < cinfo.output_height) {
    /* jpeg_read_scanlines expects an array of pointers to scanlines.
     * Here the array is only one element long, but you could ask for
     * more than one scanline at a time if that's more convenient.
     */
    (void) jpeg_read_scanlines(&cinfo, buffer, 1);
    /* Assume put_scanline_someplace wants a pointer and sample count. */
    put_scanline_someplace(buffer[0], row_stride);
  }

【注】:虽然函数jpeg_read_scanlines可以指定一次读多少行,但是目前该函数还是只能支持一次只读1行。

8. 结束解码

  /* Step 7: Finish decompression */
  (void) jpeg_finish_decompress(&cinfo);

9. 释放解码对象

  /* Step 8: Release JPEG decompression object */

  /* This is an important step since it will release a good deal of memory. */
  jpeg_destroy_decompress(&cinfo);

至此,一个jpeg数据就已经解析完成了。