jpeg库使用及源码分析

http://blog.chinaunix.net/uid-20846214-id-2413069.html

注意下载了JPEG的源码后,先看看源码里面自带的libjpeg.doc文件
先来研究一下JPEG的压缩操作过程:

The rough outline of a JPEG compression operation is:

    Allocate and initialize a JPEG compression object

    Specify the destination for the compressed data (eg, a file)

    Set parameters for compression, including image size & colorspace

    jpeg_start_compress(…);

    while (scan lines remain to be written)

        jpeg_write_scanlines(…);

    jpeg_finish_compress(…);

    Release the JPEG compression object

结合代码:

/*

 * Sample routine for JPEG compression.  We assume that the target file name

 * and a compression quality factor are passed in.

 */

GLOBAL(void)

write_JPEG_file (char * filename, int quality)

{

  /* This struct contains the JPEG compression parameters and pointers to

   * working space (which is allocated as needed by the JPEG library).

   * It is possible to have several such structures, representing multiple

   * compression/decompression processes, in existence at once.  We refer

   * to any one struct (and its associated working data) as a “JPEG object”.

   */  struct jpeg_compress_struct cinfo;

这个结构体是JPEG库需要使用的,它保存了JPEG的压缩参数和JPEG库的工作状态,这个结构可以有多个,多个就表示有多个压缩和解压缩要处理。当需要对一系列的image进行compress操作时,这个结构可以重复使用。

  /* This struct represents a JPEG error handler.  It is declared separately

   * because applications often want to supply a specialized error handler

   * (see the second half of this file for an example).  But here we just

   * take the easy way out and use the standard error handler, which will

   * print a message on stderr and call exit() if compression fails.

   * Note that this struct must live as long as the main JPEG parameter

   * struct, to avoid dangling-pointer problems.

   */

  struct jpeg_error_mgr jerr;

  /* More stuff */

  FILE * outfile;    /* target file */

  JSAMPROW row_pointer[1];  /* pointer to JSAMPLE row[s] */

  int row_stride;    /* physical row width in image buffer */

第一步:分配并初始化一个JPEG的压缩对象

  /* Step 1: allocate and initialize JPEG compression object */

/* We have to set up the error handler first, in case the initialization
* step fails.  (Unlikely, but it could happen if you are out of memory.)
* This routine fills in the contents of struct jerr, and returns jerr’s
* address which we place into the link field in cinfo.
*/
cinfo.err = jpeg_std_error(&jerr);
/* Now we can initialize the JPEG compression object. */
jpeg_create_compress(&cinfo);

第二步:指定转换后数据的目的地

  /* Step 2: specify data destination (eg, a file) */
/* Note: steps 2 and 3 can be done in either order.第二步和第三步顺序无所谓,可以颠倒 */

/* Here we use the library-supplied code to send compressed data to a
* stdio stream.  You can also write your own code to do something else.
* VERY IMPORTANT: use “b” option to fopen() if you are on a machine that
* requires it in order to write binary files.
*/
if ((outfile = fopen(filename, “wb”)) == NULL) {
fprintf(stderr, “can’t open %s\n”, filename);
exit(1);
}
jpeg_stdio_dest(&cinfo, outfile);关联JPEG的压缩对象跟输入文件

第三步:设置压缩参数

  /* Step 3: set parameters for compression */

/* First we supply a description of the input image.
* Four fields of the cinfo struct must be filled in:
*/
cinfo.image_width = image_width;     /* image width and height, in pixels */
cinfo.image_height = image_height;
cinfo.input_components = 3;        /* # of color components per pixel */
cinfo.in_color_space = JCS_RGB;     /* colorspace of input image */

  /* Now use the library’s routine to set default compression parameters.
* (You must set at least cinfo.in_color_space before calling this,
* since the defaults depend on the source color space.)
*/
jpeg_set_defaults(&cinfo);使用库函数设置默认的压缩参数
/* Now you can set any non-default parameters you wish to.
* Here we just illustrate the use of quality (quantization table) scaling:
*/
jpeg_set_quality(&cinfo, quality, TRUE /* limit to baseline-JPEG values */);

第四步:开始压缩

/* Step 4: Start compressor */

/* TRUE ensures that we will write a complete interchange-JPEG file.
* Pass TRUE unless you are very sure of what you’re doing.
*/
jpeg_start_compress(&cinfo, TRUE);

第五步:这里的循环参数是库的一个状态变量cinfo.next_scanline

  /* Step 5: while (scan lines remain to be written) */
/*           jpeg_write_scanlines(…); */

/* Here we use the library’s state variable cinfo.next_scanline as the
* loop counter, so that we don’t have to keep track ourselves.
* To keep things simple, we pass one scanline per call; you can pass
* more if you wish, though.
*/
row_stride = image_width * 3;    /* JSAMPLEs per row in image_buffer */
当前扫描的行数小于图片的总行数就继续
while (cinfo.next_scanline < cinfo.image_height) {
/* jpeg_write_scanlines expects an array of pointers to scanlines.
* Here the array is only one element long, but you could pass
* more than one scanline at a time if that’s more convenient.
*/
row_pointer[0] = & image_buffer[cinfo.next_scanline * row_stride];
(void) jpeg_write_scanlines(&cinfo, row_pointer, 1);
}

第六步:完成压缩

  /* Step 6: Finish compression */

jpeg_finish_compress(&cinfo);
/* After finish_compress, we can close the output file. */
fclose(outfile);关闭输出文件

第七步:释放JPEG压缩对象,之后这个cinfo可以用作另外一个image
/* Step 7: release JPEG compression object */

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

/* And we’re done! */

}

下面研究JPEG的解压缩过程:

the rough outline of a JPEG decompression operation is:

    Allocate and initialize a JPEG decompression object

    Specify the source of the compressed data (eg, a file)

    Call jpeg_read_header() to obtain image info

    Set parameters for decompression

    jpeg_start_decompress(…);

    while (scan lines remain to be read)

        jpeg_read_scanlines(…);

    jpeg_finish_decompress(…);

    Release the JPEG decompression object

这个过程与压缩过程差不多,就是多了读数据流头这一步。这个头很有用,包含了image的大小,颜色空间,当咱们自己的应用程序选择解压缩参数时候要用到这些信息。例如,应用程序可以选择一个比例因子生成一个适合大小的图片。

结合代码:

/*

 * Sample routine for JPEG decompression.  We assume that the source file name

 * is passed in.  We want to return 1 on success, 0 on error.

 */

GLOBAL(int)

read_JPEG_file (char * filename)

{

  /* This struct contains the JPEG decompression parameters and pointers to

   * working space (which is allocated as needed by the JPEG library).

   */

  struct jpeg_decompress_struct cinfo;

  /* We use our private extension JPEG error handler.

   * Note that this struct must live as long as the main JPEG parameter

   * struct, to avoid dangling-pointer problems.

   */

  struct my_error_mgr jerr;

  /* More stuff */

  FILE * infile;     /* source file */

  JSAMPARRAY buffer;     /* Output row buffer */

  int row_stride;    /* physical row width in output buffer */

  /* In this example we want to open the input file before doing anything else,

   * so that the setjmp() error recovery below can assume the file is open.

   * VERY IMPORTANT: use “b” option to fopen() if you are on a machine that

   * requires it in order to read binary files.

   */

  if ((infile = fopen(filename, “rb”)) == NULL) {

    fprintf(stderr, “can’t open %s\n”, filename);

    return 0;

  }

  /* Step 1: allocate and initialize JPEG decompression object */

  /* 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;

  }

  /* Now we can initialize the JPEG decompression object. */

  jpeg_create_decompress(&cinfo);

  /* Step 2: specify data source (eg, a file) */

  jpeg_stdio_src(&cinfo, infile);

  /* Step 3: read file parameters with jpeg_read_header() */

  (void) jpeg_read_header(&cinfo, TRUE);

  /* We can ignore the return value from jpeg_read_header since

   *   (a) suspension is not possible with the stdio data source, and

   *   (b) we passed TRUE to reject a tables-only JPEG file as an error.

   * See libjpeg.doc for more info.

   */

  /* Step 4: set parameters for decompression */

  /* In this example, we don’t need to change any of the defaults set by

   * jpeg_read_header(), so we do nothing here.

   */

  /* Step 5: Start decompressor */

  (void) jpeg_start_decompress(&cinfo);

  /* We can ignore the return value since suspension is not possible

   * with the stdio data source.

   */

  /* We may need to do some setup of our own at this point before reading

   * the data.  After jpeg_start_decompress() we have the correct scaled

   * output image dimensions available, as well as the output colormap

   * if we asked for color quantization.

   * In this example, we need to make an output work buffer of the right size.

   */

  /* JSAMPLEs per row in output buffer */

  row_stride = cinfo.output_width * cinfo.output_components;

  /* Make a one-row-high sample array that will go away when done with image */

  buffer = (*cinfo.mem->alloc_sarray)

       ((j_common_ptr) &cinfo, JPOOL_IMAGE, row_stride, 1);

  /* Step 6: while (scan lines remain to be read) */

  /*           jpeg_read_scanlines(…); */

  /* 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);

jpeg_read_scanlines函数用来把解压缩的数据传递到内存Buffer中

    /* Assume put_scanline_someplace wants a pointer and sample count. */

    put_scanline_someplace(buffer[0], row_stride);

  }

  /* Step 7: Finish decompression */

  (void) jpeg_finish_decompress(&cinfo);

  /* We can ignore the return value since suspension is not possible

   * with the stdio data source.

   */

  /* Step 8: Release JPEG decompression object */

  /* This is an important step since it will release a good deal of memory. */

  jpeg_destroy_decompress(&cinfo);

  /* After finish_decompress, we can close the input file.

   * Here we postpone it until after no more JPEG errors are possible,

   * so as to simplify the setjmp error logic above.  (Actually, I don’t

   * think that jpeg_destroy can do an error exit, but why assume anything…)

   */

  fclose(infile);

  /* At this point you may want to check to see whether any corrupt-data

   * warnings occurred (test whether jerr.pub.num_warnings is nonzero).

   */

  /* And we’re done! */

  return 1;

}

下面开始分析源码,以djpeg.c中的main.c为入口开始分析:(以★开头的是代码,其余的是分析)

★struct jpeg_decompress_struct cinfo;

这个结构体对象包含了JPEG解压缩参数和一些工作函数的指针,咱们的JPEG库的API需要它

★jpeg_create_decompress(&cinfo);

初始化这个结构体对象,这里的jpeg_create_decompress是一个宏替换:

★#define jpeg_create_decompress(cinfo) \
jpeg_CreateDecompress((cinfo), JPEG_LIB_VERSION, \
(size_t) sizeof(struct jpeg_decompress_struct))

这里的JPEG_LIB_VERSION在jpeglib.h头文件中定义,其目的是为了同步JPEG库和调用者的版本一致,如果调用者用的JPEG_LIB_VERSION和库里面的JPEG_LIB_VERSION不一样,程序会退出运行。下面看看函数jpeg_CreateDecompress的实现(在jdapimin.c中):

★GLOBAL(void)
jpeg_CreateDecompress (j_decompress_ptr cinfo, int version, size_t structsize)
{
int i;

/* Guard against version mismatches between library and caller. */
cinfo->mem = NULL;        /* so jpeg_destroy knows mem mgr not called */
if (version != JPEG_LIB_VERSION)  //这里判断使用者的版本和库版本是否一致
ERREXIT2(cinfo, JERR_BAD_LIB_VERSION, JPEG_LIB_VERSION, version);

 // 同样调用者的结构体对象jpeg_decompress_struct的大小也要同库一致

  if (structsize != SIZEOF(struct jpeg_decompress_struct))
ERREXIT2(cinfo, JERR_BAD_STRUCT_SIZE,
(int) SIZEOF(struct jpeg_decompress_struct), (int) structsize);

/* For debugging purposes, zero the whole master structure.
* But error manager pointer is already there, so save and restore it.
*/

  { // 关于错误处理这里先不看,先当咱们程序不会出错^oo^
struct jpeg_error_mgr * err = cinfo->err;

    将cinfo这个结构体对象清0

    MEMZERO(cinfo, SIZEOF(struct jpeg_decompress_struct));
cinfo->err = err;
}

  // is_decompressor是boolean 值,TRUE表示要解压缩

  cinfo->is_decompressor = TRUE;

/* Initialize a memory manager instance for this object */

  // 为这个对象初始化一个内存管理实例,下面会分析这个接口

  jinit_memory_mgr((j_common_ptr) cinfo);

/* Zero out pointers to permanent structures. */
cinfo->progress = NULL;
cinfo->src = NULL;  // cinfo->src是被压缩的数据源地址

  // NUM_QUANT_TBLS指定量化表个数,默认为4,最好不要改动

  // NUM_HUFF_TBLS指定量化表个数,默认为4,最好不要改动

for (i = 0; i < NUM_QUANT_TBLS; i++)
cinfo->quant_tbl_ptrs[i] = NULL; // 表指针先置空

for (i = 0; i < NUM_HUFF_TBLS; i++) {
cinfo->dc_huff_tbl_ptrs[i] = NULL; // 表指针先置空
cinfo->ac_huff_tbl_ptrs[i] = NULL; // 表指针先置空
}

/* Initialize marker processor so application can override methods
* for COM, APPn markers before calling jpeg_read_header.
*/
jinit_marker_reader(cinfo);

/* And initialize the overall input controller. */
jinit_input_controller(cinfo); // 初始化输入控制

/* OK, I’m ready */

  // global_state验证所有任务队列的有效性,即目前程序到了一个什么状态,这里    DSTATE_START为200,表示完成了create_decompress

  cinfo->global_state = DSTATE_START;
}

jinit_memory_mgr实现(在jmemmgr.c中)

★jinit_memory_mgr((j_common_ptr) cinfo);

/*
* Memory manager initialization.
* When this is called, only the error manager pointer is valid in cinfo!
*/
这个函数调用完毕后,cinfo结构里的错误管理指针就有效了

这里函数参数j_common_ptr cinfo把cinfo强制转换成j_common_ptr类型,j_common_ptr类型结构如下:

struct jpeg_common_struct {
jpeg_common_fields;        /* Fields common to both master struct types
};

而jpeg_common_fields是个宏替换:

#define jpeg_common_fields \
struct jpeg_error_mgr * err;    /* Error handler module */\
struct jpeg_memory_mgr * mem;    /* Memory manager module */\
struct jpeg_progress_mgr * progress; /* Progress monitor, or NULL if none */\
void * client_data;        /* Available for use by application */\
boolean is_decompressor;    /* So common code can tell which is which */\
int global_state        /* For checking call sequence validity */

GLOBAL(void)
jinit_memory_mgr (j_common_ptr cinfo)
{
my_mem_ptr mem;
long max_to_use;
int pool;
size_t test_mac;

cinfo->mem = NULL;        /* for safety if init fails */

/* Check for configuration errors.
* SIZEOF(ALIGN_TYPE) should be a power of 2; otherwise, it probably
* doesn’t reflect any real hardware alignment requirement.
* The test is a little tricky: for X>0, X and X-1 have no one-bits
* in common if and only if X is a power of 2, ie has only one one-bit.
* Some compilers may give an “unreachable code” warning here; ignore it.
*/
if ((SIZEOF(ALIGN_TYPE) & (SIZEOF(ALIGN_TYPE)-1)) != 0)
ERREXIT(cinfo, JERR_BAD_ALIGN_TYPE);
/* MAX_ALLOC_CHUNK must be representable as type size_t, and must be
* a multiple of SIZEOF(ALIGN_TYPE).
* Again, an “unreachable code” warning may be ignored here.
* But a “constant too large” warning means you need to fix MAX_ALLOC_CHUNK.
*/
test_mac = (size_t) MAX_ALLOC_CHUNK;
if ((long) test_mac != MAX_ALLOC_CHUNK ||
(MAX_ALLOC_CHUNK % SIZEOF(ALIGN_TYPE)) != 0)
ERREXIT(cinfo, JERR_BAD_ALLOC_CHUNK);

  我们返回的max_to_use的值是1000000
max_to_use = jpeg_mem_init(cinfo); /* system-dependent initialization */

/* Attempt to allocate memory manager’s control block */

  分配内存管理控制块的内存
mem = (my_mem_ptr) jpeg_get_small(cinfo, SIZEOF(my_memory_mgr));

  如果失败就释放并返回
if (mem == NULL) {
jpeg_mem_term(cinfo);    /* system-dependent cleanup */这里面没啥东西,可以自己写一些处理之类的
ERREXIT1(cinfo, JERR_OUT_OF_MEMORY, 0);
}

/* OK, fill in the method pointers */

  // 填充函数指针
mem->pub.alloc_small = alloc_small;
mem->pub.alloc_large = alloc_large;
mem->pub.alloc_sarray = alloc_sarray;
mem->pub.alloc_barray = alloc_barray;
mem->pub.request_virt_sarray = request_virt_sarray;
mem->pub.request_virt_barray = request_virt_barray;
mem->pub.realize_virt_arrays = realize_virt_arrays;
mem->pub.access_virt_sarray = access_virt_sarray;
mem->pub.access_virt_barray = access_virt_barray;
mem->pub.free_pool = free_pool;
mem->pub.self_destruct = self_destruct;

/* Make MAX_ALLOC_CHUNK accessible to other modules */
mem->pub.max_alloc_chunk = MAX_ALLOC_CHUNK;

/* Initialize working state */

  max_memory_to_use是为JPEG对象分配的内存极限大小
mem->pub.max_memory_to_use = max_to_use;
// JPOOL_NUMPOOLS == 2, JPOOL_PERMANENT == 0
for (pool = JPOOL_NUMPOOLS-1; pool >= JPOOL_PERMANENT; pool–) {
mem->small_list[pool] = NULL;
mem->large_list[pool] = NULL;
}
mem->virt_sarray_list = NULL;
mem->virt_barray_list = NULL;

mem->total_space_allocated = SIZEOF(my_memory_mgr);

/* Declare ourselves open for business */
cinfo->mem = & mem->pub;

/* Check for an environment variable JPEGMEM; if found, override the
* default max_memory setting from jpeg_mem_init.  Note that the
* surrounding application may again override this value.
* If your system doesn’t support getenv(), define NO_GETENV to disable
* this feature.
*/
#ifndef NO_GETENV
{ char * memenv;

if ((memenv = getenv(“JPEGMEM”)) != NULL) {
char ch = ‘x’;

if (sscanf(memenv, “%ld%c”, &max_to_use, &ch) > 0) {
if (ch == ‘m’ || ch == ‘M’)
max_to_use *= 1000L;
mem->pub.max_memory_to_use = max_to_use * 1000L;
}
}
}
#endif

}

★jinit_marker_reader(cinfo);

★jinit_input_controller(cinfo);

★jpeg_mem_init(cinfo)  返回一个值,简单不多解释

GLOBAL(long)
jpeg_mem_init (j_common_ptr cinfo)
{
return DEFAULT_MAX_MEM;    /* default for max_memory_to_use */
}

★mem = (my_mem_ptr) jpeg_get_small(cinfo, SIZEOF(my_memory_mgr));

GLOBAL(void *)
jpeg_get_small (j_common_ptr cinfo, size_t sizeofobject)
{
return (void *) malloc(sizeofobject);
}

★jpeg_mem_term(cinfo);

LibJpeg解码内存中的Jpeg数据【转】

LibJpeg解码内存中的Jpeg数据【转】
2010-08-12 16:17
熟悉 libjpeg的朋友都知道libjpeg是一个开源的库。Linux和Android都是用libjpeg来支持jpeg文件的,可见其功能多么强大。 但是默认情况下libjpeg只能处理jpeg文件的解码,或者把图像编码到jpeg文件。在嵌入式设备中没有文件系统也是很正常的事情,难道我们就不能 利用libjpeg的强大功能了吗?当然不是!本文将会介绍怎样扩展libjpeg让其能够解码内存中的jpeg数据。

在介绍主题之前,请允许我讨论一下公共代码库的数据输入的一些问题。因为一个公共代码库是开放给大家用的,这个世界的输入方式也是多种多样的,比如可以通 过文件输入,shell用户手工输入,内存缓存输入,网络socket输入等等。所以实现库的时候,千万不要假定用户只有一种输入方式。

通用的做法是实现一个输入的中间层。如果库是以支持面向对象语言实现的话,可以实现一套流机制,实现各式各样的流(文件流,缓存流,socket流等)。 公共代码库的输入为流对象。这样库就可以实现各式各样的输入了。一个例子请参考Android图形引擎Skia的实现。

假如库是用非面向对象的语言实现的话,那么怎样来实现多种输入方式呢?可以通过定义输入对象的数据结构,该数据结构中让用户注册读写数据的函数和数据。因 为只有调用者最清楚他的数据来源,数据读取方式。在公共代码库中,只需要调用用户注册的回调函数对数据进行读写就可以了。这样的话,也可以实现公共代码库 对多种输入方式的支持。

回到本文的主题,libjpeg对多种输入的支持就不好,它假设了用户只会用文件作为输入,没有考虑其他的输入方式。经过研究他的源代码发现其内部也是非 常容易扩展,进而实现对多种输入的支持的,但是libjpeg没有更这样做,不明白为什么。请看jpeglib.h中如下定义:

/* Data source object for decompression */
struct jpeg_source_mgr {
const JOCTET * next_input_byte; /* => next byte to read from buffer */
size_t bytes_in_buffer; /* # of bytes remaining 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; /* public fields */

FILE * infile; /* source stream */
JOCTET * buffer; /* start of buffer */
boolean start_of_file; /* have we gotten any data yet? */
} my_source_mgr;

该文件为jpeglib的source manger初始化和管理的地方。上面的数据结构是内部使用的源数据。可以看出其源数据只支持文件输入(infile变量),并提供缓存功能(buffer变量)。

其对source manager初始化的接口定义子jpeglib.h中,定义如下:

EXTERN(void ) jpeg_stdio_src JPP((j_decompress_ptr cinfo, FILE * infile));

通过这个接口我们可以看出它的source manager只能接收文件作为输入。该函数的实现在jdatasrc.c文件中。

为了支持内存jpeg数据输入,我的设计是在jdatasrc.c中实现一个新的接口来初始化jpeglib的source manger对象。并完成注册其读写的回调函数给source manager。

说干就干,首先我们需要让source manager对象支持内存数据。修改my_source_mgr数据结构如下:

typedef struct {
UINT8* img_buffer;
UINT32 buffer_size;
UINT32 pos;
}BUFF_JPG;
/* Expanded data source object for stdio input */

typedef struct {
struct jpeg_source_mgr pub; /* public fields */
union {
BUFF_JPG jpg; /* jpeg image buffer */
VFS_FILE * infile; /* source stream */
};
JOCTET * buffer; /* start of buffer */
boolean start_of_file; /* have we gotten any data yet? */
} my_source_mgr;

可以看出我们通过union来支持内存数据(jpg变量)或者文件输入。因为需要负责读写必须要标识出当前内存读写的位置,所以必须要在BUFF_JPG数据结构中定义pos变量。

下一步我们需要实现读写内存jpeg数据的回调函数了。经过分析对文件数据读写的回调函数,发现我们只需要实现jpeg_source_mgr数据结构中 的fill_input_buffer回调函数就可以了,其他的回调函数可以延用对文件读取的回调函数。在jdatasrc.c文件中,定义回调函数如 下:

/*
* This function will read the jpeg memery block to fill the library buffer.
*/
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) /* Treat empty input file as fatal error */
ERREXIT(cinfo, JERR_INPUT_EMPTY);
WARNMS(cinfo, JWRN_JPEG_EOF);
/* Insert a fake EOI marker */
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。

经过调试分析还发现jdatasrc.c文件中skip_input_data函数有一个不严谨的地方。原来代码中如下:

METHODDEF(void )
skip_input_data (j_decompress_ptr cinfo, long num_bytes)
{
my_src_ptr src = (my_src_ptr) cinfo->src;

/* Just a dumb implementation for now. Could use fseek() except
* it doesn’t work on pipes. Not clear that being smart is worth
* any trouble anyway — large skips are infrequent.
*/
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);
/* note we assume that fill_input_buffer will never return FALSE,
* so suspension need not be handled.
*/
}
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;

/* Just a dumb implementation for now. Could use fseek() except
* it doesn’t work on pipes. Not clear that being smart is worth
* any trouble anyway — large skips are infrequent.
*/
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);
/* note we assume that fill_input_buffer will never return FALSE,
* so suspension need not be handled.
*/
}
src->pub.next_input_byte += (size_t) num_bytes;
src->pub.bytes_in_buffer -= (size_t) num_bytes;
}
}

调用我们注册的回调函数来读取数据。

最好我们需要实现一个供用户用内存jpeg数据初始化source manager的接口。我的定义如下:

/*
* This function improve the library can use the jpeg memory block as source.
*/
GLOBAL(void )
jpeg_stdio_buffer_src (j_decompress_ptr cinfo, UINT8 * buffer, UINT32 size)
{
my_src_ptr src;

if (cinfo->src == NULL) { /* first time for this JPEG object? */
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; /* use default method */
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; /* forces fill_input_buffer on first read */
src->pub.next_input_byte = NULL; /* until buffer loaded */
}

通过该函数会发现:我们用户输入的buffer初始化了 my_source_mgr,并用我们实现的回调函数jpg_fill_input_buffer初始化了jpeg_source_mgr数据结构中的 fill_input_buffer。这样每次libjpeg读取数据就将会调用jpg_fill_input_buffer来读取内存jpeg数据了。

最后把jpeg_stdio_buffer_src接口暴露给最终用户。在jpeglib.h中增加如下定义:

EXTERN(void ) jpeg_stdio_buffer_src JPP((j_decompress_ptr cinfo, UINT8 * buffer, UINT32 size));

至此libjpeg已经可以支持内存jpeg数据的解码了。只需要在调用jpeg_stdio_src接口的地方改调用jpeg_stdio_buffer_src就可以了。

jpeglib库和intel的IJL库相比较起来效率没有intel的高,但是IJL库会存在解码每一帧图像会泄露72字节的内存。如有看到此博客的朋友,请给个建议。谢谢。

TIFF图像文件格式分析

一、           介绍

TIFF全名 tag image file format,是一种基于标志域的图形。

TIFF图像是靠指针连接来组织数据的,文件头和数据可以任意数据的存储。TIFF由四种类型:TIFF-B,二色;TIFF-G,黑白灰度;TIFF-P,带调色板的彩色图形;TIFF-R,适合RGB色彩的图形。

 

二、           组成

TIFF一般来说由四部分组成:文件头、文件目录、目录内容、图像数据;如下图,下面具体说明。

 

r_tiffstruct.jpg

 

三、           具体格式

1.        文件头部分

文件头部分有8个字节,格式为:

0                                      2                                      4

II/MM(表示字节序)

42(版本号)

文件目录在整个文件的偏移量,是一个指针,指向IFD的开始部分

5                                                                                                                                                                                                                                                                              8

注意这里的值不一定就是紧接头后的0A地址,可以是任意的。

 

2.        文件目录部分

0                                    2                                      14

IFD的总项数

1(12个字节)

2。。。。(12个字节)

下一个IFD的开始地址(用于保存多个文件)

每个项的12个字节,其是连续的,有前两个字节说明其项的总数。对于不同的TIFF格式文件,这里的不同。

 

3.        目录项

每个目录项由12个字节组成,格式如下:

 

TAG(2个字节)

TAG的数据类型

数据长度

数据值或值的偏移(文件范围)

TAG的值由TIFF标准定义;不同的TAG表示不同的意思,例如0X0100表示图像的宽度。

TAG的数据类型表示数据值的类型;例如使用long类型来表示宽度。

数据长度是以数据类型为单位的,真正的数据长度是:数据长度*sizeof(数据类型)

数据值或值的偏移:如果数据长度小于4个字节,一般直接以该字段来表示其值,如果其值大于4个字节,就指定一个文件范围的偏移地址,从那里开始的“数据长度*sizeof(数据类型)”个字节表示该标志的值。

 

4.        图像数据:

紧接上面数据之后就是图像数据了;图像数据的存储形式有不同的形式,有以像素位来存储的,也有以颜色面来存储的;具体的存储方式在目录项中定义。

四、           一些TAG

TAG

说明

十进制

16进制

256

100

图像宽度

257

101

图像高度

258

102

BitsPerSample,对于RGB888,通过偏移来表示

259

103

压缩方式(1/2/32773),1为不压缩

262

106

光度滴定,对于RGB图,该值是2

273

111

带的偏移,每个带是不同的

277

115

每像素的颜色成分,对于RGB这个值一般是3,如果需要其他有ExtraSamples标志

278

116

每带的行数

279

117

每带中压缩之后的数据字节常长度

284

11C

每个分量(RGB)是怎么存储的,1表示按照RGBRGB的顺序来存储

296

128

XresolutionYresolution的单位

更多的参考TIFF标准

 

五、           一个TIFF文件格式分析

r_tiffile.jpg

为了保证图片的宽度,不得不将一些文字折行。