encode/decode jpeg in memory

Some additional words:
      Libjpeg already support encode/decode image in memory since version 8b, however
      this paper is useful for us to understand the mechnism of encoding/decoding jpeg.

      Currently, the version I am using is libjpeg8c.

      - ba_jian


--------------------------------------------------------------------------------------


通过简单修改libjpeg源代码,实现内存内位图的压缩及解压缩 - [Linux]

Tag: linux   技巧   jpeg

版权声明:转载时请以超链接形式标明文章原始出处和作者信息及本声明
http://rtfsc.blogbus.com/logs/23148378.html

相信使用过的朋友应该会喜欢上libjpeg,它简单易用、压缩质量可以随意控制、并且稳定性很好,但是,官方网站给提供的libjpeg库,
不论是进行压缩时还是解压缩时,都需要用到FILE,使得我们如果想在内存中直接压缩或解压缩图像还要自己实现相应的结构,
总之,比较麻烦,尤其对初学者,更是不知从何处入手,幸运的是,libjpeg给我们提供了源代码,今天我就为大家介绍,怎样修改源代码,
使libjpeg可以非常容易的直接处理内存中的图像,而无需借助文件操作。

一、建立自己的libjpeg工程
       为了修改后编译方便,也为了以后在VC 环境下容易使用libjpeg库,我们按以下步骤将libjpeg转换为VC环境下的工程。
        1、在VC环境下重新建立一个空的static library工程,工程名为libjpeg,此处注意,新建工程不要包含mfc,不要预编译头文件;
         2、然后将libjpeg下的jcapimin.c jcapistd.c jccoefct.c jccolor.c jcdctmgr.c jchuff.c 
        jcinit.c jcmainct.c jcmarker.c jcmaster.c jcomapi.c jcparam.c 
        jcphuff.c jcprepct.c jcsample.c jctrans.c jdapimin.c jdapistd.c 
        jdatadst.c jdatasrc.c jdcoefct.c jdcolor.c jddctmgr.c jdhuff.c 
        jdinput.c jdmainct.c jdmarker.c jdmaster.c jdmerge.c jdphuff.c 
        jdpostct.c jdsample.c jdtrans.c jerror.c jfdctflt.c jfdctfst.c 
        jfdctint.c jidctflt.c jidctfst.c jidctint.c jidctred.c jquant1.c 
        jquant2.c jutils.c jmemmgr.c 
       jchuff.h  jconfig.h jdhuff.h jdct.h jerror.h jinclude.h jmemsys.h jmorecfg.h 
        jpegint.h jpeglib.h jversion.h 等文件拷贝到新工程的文件夹下,并将.c文件改名为.cpp;
         3、将所有的源文件及头文件添加到新建的工程中;
         4、编译新工程,此时就可以生成libjpeg.lib了。
二、分析并修改源代码
        我们知道,libjpeg是利用FILE进行存取图像数据的,接下来,我们就要分析一下libjpeg是怎样利用FILE进行存取图像数据的,
然后我们用内存拷贝的方式替换掉所有的文件操作(I/O),也就实现了内存中进行图像压缩和解压缩的目标。
        下面,先分析压缩图像时libjpeg是怎样利用FILE进行存储数据的。我们先看在进行图像压缩时,我们所调用的跟文件有关系的函数:
                jpeg_stdio_dest(j_compres_ptr cinfo, FILE *outfile);
        我们找到这个函数的源代码(jdatadst.cpp文件第130行):
     GLOBAL(void)
     jpeg_stdio_dest (j_compress_ptr cinfo, FILE * outfile)
     {
              my_dest_ptr dest;
             
11           if (cinfo->dest == NULL) {
12                    cinfo->dest = (struct jpeg_destination_mgr *)
13                    (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_PERMANENT,
14                   SIZEOF(my_destination_mgr));
15          }
16           dest = (my_dest_ptr) cinfo->dest;
17           dest->pub.init_destination = init_destination;
18           dest->pub.empty_output_buffer = empty_output_buffer;
19           dest->pub.term_destination = term_destination;
20           dest->outfile = outfile;
21       }

    大家看第20行,函数将FILE类型的指针赋值给了dest->outfile,很显然,以后对文件的操作,就转向了对dest->outfile 的操作,
我们只要找到所有引用outfile的函数,就可以知道libjpeg是怎样压缩图像到文件的,因此,我们继续搜outfile,搜索结果如下:

Find all "outfile", Subfolders, Find Results 1, "Entire Solution"
  E:\VS2005\libjpeg\libjpeg\jpeglib.h(910):EXTERN(void) jpeg_stdio_dest JPP((j_compress_ptr cinfo, FILE * outfile));
  E:\VS2005\libjpeg\libjpeg\jdatadst.cpp(28):  FILE * outfile; 
  E:\VS2005\libjpeg\libjpeg\jdatadst.cpp(85):  if (JFWRITE(dest->outfile, dest->buffer, OUTPUT_BUF_SIZE) !=
  E:\VS2005\libjpeg\libjpeg\jdatadst.cpp(113):    if (JFWRITE(dest->outfile, dest->buffer, datacount) != datacount)
  E:\VS2005\libjpeg\libjpeg\jdatadst.cpp(116):  fflush(dest->outfile);
  E:\VS2005\libjpeg\libjpeg\jdatadst.cpp(118):  if (ferror(dest->outfile))
  E:\VS2005\libjpeg\libjpeg\jdatadst.cpp(130):jpeg_stdio_dest (j_compress_ptr cinfo, FILE * outfile)
  E:\VS2005\libjpeg\libjpeg\jdatadst.cpp(150):  dest->outfile = outfile;
  Matching lines: 8    Matching files: 2    Total files searched: 57

    可以看到,共有8处引用了outfile变量,第一处为函数声明,第二处为变量声明,第三、四、五、六处为文件操作,第七处和第八处我们
已经见过了,我们只需要把这八处改了就可以实现我们的目标了。如下:

EXTERN(void) jpeg_stdio_dest JPP((j_compress_ptr cinfo, char* outdata)); // 由EXTERN(void) jpeg_stdio_dest JPP((j_compress_ptr cinfo, FILE * outfile));改写
char * outdata;   // 由 FILE * outfile;  改写

jdatadst.cpp文件第87行empty_output_buffer (j_compress_ptr cinfo)函数
memcpy(dest->outdata,dest->buffer,OUTPUT_BUF_SIZE);// 由JFWRITE(dest->outfile, dest->buffer, OUTPUT_BUF_SIZE)改写

jdatadst.cpp文件第114行term_destination (j_compress_ptr cinfo)
memcpy(dest->outdata,dest->buffer,datacount);      // 由JFWRITE(dest->outfile, dest->buffer, datacount)改写

删除fflush(dest->outfile);和if (ferror(dest->outfile))及相关的其它语句。
peg_stdio_dest (j_compress_ptr cinfo, char* outdata)    // 由peg_stdio_dest (j_compress_ptr cinfo, FILE * outfile)改写
dest->outdata = outdata;                                // 由dest->outfile = outfile;改写

    我们改到这里,可以编译一下,应该不会有错误产生,但是,你会不会觉得有问题呢?对,我们发现,我们没有为内存区域提供偏移量(每次追加图像数据后,偏移量指向当前的位置),
另外,由于只有到压缩完才能知道图像压缩完后的数据量大小,我们还需要一个指示图像数据大小的变量。
   
    我们将这两个变量添加到outdata后面,跟outdata一样,作为dest的成员变量,如下:
typedef struct {
  struct jpeg_destination_mgr pub;

  char * outdata;  
  int  *pSize;   // 新加变量,该指针为调用者提供,压缩完后返回图像大小
  int nOutOffset;   // 新加变量
  JOCTET * buffer;  
} my_destination_mgr;

我们将通过jpeg_stdio_dest函数提供pSize指针,并在jpeg_stdio_dest的实现函数里对新添加的变量进行初始化,如下:
GLOBAL(void)
jpeg_stdio_dest (j_compress_ptr cinfo, char * outdata, int *pSize)
{
  my_dest_ptr dest;

 
  if (cinfo->dest == NULL) { 
    cinfo->dest = (struct jpeg_destination_mgr *)
      (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_PERMANENT,
      SIZEOF(my_destination_mgr));
  }

  dest = (my_dest_ptr) cinfo->dest;
  dest->pub.init_destination = init_destination;
  dest->pub.empty_output_buffer = empty_output_buffer;
  dest->pub.term_destination = term_destination;

  dest->outdata = outdata;
  dest->nOutOffset = 0;
  dest->pSize = pSize;
  *(dest->pSize)= 0;
}

改写声明函数
EXTERN(void) jpeg_stdio_dest JPP((j_compress_ptr cinfo, char* outdata, int *pSize));

jdatadst.cpp文件第87行empty_output_buffer (j_compress_ptr cinfo)函数
memcpy(dest->outdata+dest->nOutOffset,dest->buffer,OUTPUT_BUF_SIZE);// 由JFWRITE(dest->outfile, dest->buffer, OUTPUT_BUF_SIZE)改写
dest->nOutOffset+=OUTPUT_BUF_SIZE;
*(dest->pSize)=dest->nOutOffset;

jdatadst.cpp文件第114行term_destination (j_compress_ptr cinfo)
memcpy(dest->outdata+dest->nOutOffset,dest->buffer,datacount);      // 由JFWRITE(dest->outfile, dest->buffer, datacount)改写
dest->nOutOffset+=datacount;
*(dest->pSize)=dest->nOutOffset;

重新编译工程,这样我们就实现了压缩bmp位图到内存中,当然,调用jpeg_stdio_dest之前,我们需要先分配足够的内存,并把内存指针传递给jpeg_stdio_dest函数,
好了,我们再分析libjpeg在解压缩jpg图像时,是怎样从jpg文件读入图像数据的。

我们先看我们在解压缩图像时调用的与文件操作有关的函数,如下:
jpeg_stdio_src (j_decompress_ptr cinfo, FILE * infile)。
在该函数的实现代码中找到了my_src_ptr结构,并且,我们发现与文件操作有关的该结构的成员变量为infile,参考上面内容,我们搜索infile,搜索结果如下:
Find all "infile", Subfolders, Find Results 1, "Entire Solution"
  E:\VS2005\libjpeg\libjpeg\jpeglib.h(911):EXTERN(void) jpeg_stdio_src JPP((j_decompress_ptr cinfo, FILE * infile));
  E:\VS2005\libjpeg\libjpeg\jdatasrc.cpp(28):  FILE * infile;  
  E:\VS2005\libjpeg\libjpeg\jdatasrc.cpp(95):  nbytes = JFREAD(src->infile, src->buffer, INPUT_BUF_SIZE);
  E:\VS2005\libjpeg\libjpeg\jdatasrc.cpp(182):jpeg_stdio_src (j_decompress_ptr cinfo, FILE * infile)
  E:\VS2005\libjpeg\libjpeg\jdatasrc.cpp(209):  src->infile = infile;
  Matching lines: 5    Matching files: 2    Total files searched: 57

根据上面的经验,我们考虑,除了将FILE *类型变量改为char *类型的变量外,还要添加两个变量,图像大小的变量及图像偏移量,这跟图像压缩时差不多,所不同的是,
图像压缩时,图像大小是由libjpeg库返回,所以在调用是提供给libjpeg库的是个指针,而在解压缩时,图像数据大小是由调用者通过变量(不是指针)提供给libjpeg库。
由于我详细讲解了图像压缩时的我们所做的工作,我想读者朋友们很容易就能理解解压缩时所做的更改,下面我只列出我们所改写的代码,就不再详细讲解了。

jpeglib.h 第911行
EXTERN(void) jpeg_stdio_src JPP((j_decompress_ptr cinfo, char * indata,int nSize));

jdatasrc.cpp 第33行

typedef struct {
  struct jpeg_source_mgr pub; 

  char * indata;  
  int nInOffset;
  int nSize;
  JOCTET * buffer;  
  boolean start_of_file; 
} my_source_mgr;

jdatasrc.cpp 第183行
GLOBAL(void)
jpeg_stdio_src (j_decompress_ptr cinfo, char * indata, int nSize)
{
  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 = 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->indata = indata;   // 新添加行
  src->nSize = nSize;   // 新添加
  src->nInOffset = 0;   // 新添加
  src->pub.bytes_in_buffer = 0;
  src->pub.next_input_byte = NULL;
}

jdatasrc.cpp 第91行
METHODDEF(boolean)
fill_input_buffer (j_decompress_ptr cinfo)
{
  my_src_ptr src = (my_src_ptr) cinfo->src;
  size_t nbytes;

  //nbytes = JFREAD(src->infile, src->buffer, INPUT_BUF_SIZE);
  nbytes = src->nSize-src->nInOffset;
  if (nbytes>INPUT_BUF_SIZE) nbytes = INPUT_BUF_SIZE;


  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;
  }

  memcpy(src->buffer,src->indata+src->nInOffset,nbytes);
  src->nInOffset+=nbytes;

  src->pub.next_input_byte = src->buffer;
  src->pub.bytes_in_buffer = nbytes;
  src->start_of_file = FALSE;

  return TRUE;
}

至此,libjpeg库的源代码中所有要改的东西我们都已经完成,剩下的事情就是我们编写一段测试程序测试一下。

三、编写测试代码

    对于libjpeg库的详细的调用步骤,请参照我的文章《利用jpeglib压缩图像为jpg格式》,上面详细介绍了利用libjpeg库
进行图像压缩和解压缩的步骤,在编写本例的测试代码时,我们在上次提供的测试代码的基础上进行改进,如下:

    无论压缩还是解压缩,与原来的libjpeg库调用不同的地方都只有一处,就是jpeg_stdio_dest和jpeg_stdio_src这两个函数的调用,
调用原来的libjpeg库时,需要为这两个函数提供已经打开的jpg文件句柄,而对于新的libjpeg库,不需要打开jpg文件了,压缩时,
我们需要提供足够大的内存区给libjpeg 库,解压缩时,只需要把存放有jpeg格式图像的内存区提供给libjpeg库就行了,下面详细介绍
对于改写后的jpeg_stdio_dest和jpeg_stdio_src这两个函数的调用方法。

    1、jpeg_stdio_dest
     函数的原形为:void jpeg_stdio_dest(j_compress_ptr cinfo, char * outData, int *pSize);
     这里,outData指向我们提供给libjpeg库用于存放压缩后图像数据的内存区,这块内存要在我们调用该函数前申请好,大家可以看到,
我们在libjpeg库内没有对该内存区进行越界访问检查并且要足够大,否则会出现内存越界访问的危险,当整个图像压缩工作完成后,pSize
返回jpg图像数据的大小。测试代码如下:
  char outdata[1000000]; // 用于缓存,这里设置为1000K,实际使用时可以采用动态申请的方式
  int nSize; // 用于存放压缩完后图像数据的大小

                ..........
                jpeg_stdio_dest(&jcs, outdata,&nSize);
                ..........

    2、jpeg_stdio_src
     函数的原形为:void jpeg_stdio_src(j_decompress_ptr cinfo, char * inData,int nSize);
     这里,inData指向我们将要进行解压缩的jpg数据,该数据我们可以直接从jpg文件中读取,也可以是通过libjpeg库在内存中直接压缩
生成的数据,nSize 当然是这个jpg数据的大小。测试代码如下:
 ..............
        char indata[1000000]; // 用于存放解压缩前的图像数据,该数据直接从jpg文件读取
        FILE *f = fopen(strSourceFileName,"rb");
 if (f==NULL)
 {
  printf("Open file error!\n");
  return;
 }
 int nSize = fread(outdata,1,1000000,f); // 读取jpg图像数据,nSize为实际读取的图像数据大小
 fclose(f);
 // 下面代码用于解压缩,从本行开始解压缩
 jpeg_stdio_src(&cinfo, outdata,nSize);
 .............

        至此我们所有的工作均已完成,完整的测试程序请从我的资源里下载,为增加兼容性,本测试程序特意加入字节调整功能,以解决部分读者
测试图像时出现的图像倾斜的问题,好了,编译并运行一下测试程序,看看效果吧,如果愿意继续交流,请给我发邮

收藏到: Del.icio.us
 
 

转自http://my.unix-center.net/~Simon_fu/?p=565

 

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

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

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

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

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

 
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变量)。

     其对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;
 

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变量。

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

 
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。

     经过调试分析还发现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;

 
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;
}
}

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

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

 
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数据了。

     最后把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就可以了。

    以上代码仅供参考,以上代码初步测试没有问题,但是未经过严格测试。如果你经过测试发现代码中的错误或者有改进的方法,请你和我联系。我的联系方式请看《 关于云中漫步 》。



http://my.chinaunix.net/space.php?uid=16427792&do=blog&id=240220

修改libjpeg以支持内存缓冲区作为数据源

已有 175 次阅读    2011-04-16 19:10      标签  缓冲区    数据源    工作组    嵌入式    空间 
因为项目需要解码jpeg图片,所以使用了ITG JPEG库libjpeg,由JPEG工作组维护,广泛使用。初略一看,最新稳定版本居然还是1998年3月27号发布的,强大!

将libjpeg应用到项目中时,发现两个最大的问题:

1.输入源只能是文件,FILE 流。项目中数据源是网络来的缓冲区,需要为libjpeg的输入源添加缓冲区支持
2.输出格式太少,项目中显示需要的是BGRA8888。由于是在嵌入式平台,为了减少数据的冗余处理,需要添加BGRA8888的输出模式。

对于第一个问题,jpeg对于解码数据源其实已经封装的很好了。在jdatasrc.c中,jpeg库使用struct jpeg_source_mgr来管理解码数据源,jpeg_source_mgr的定义如下:

  1. struct jpeg_source_mgr {
  2.   const JOCTET * next_input_byte; /* => next byte to read from buffer */
  3.   size_t bytes_in_buffer;    /* # of bytes remaining in buffer */

  4.   JMETHOD(void, init_source, (j_decompress_ptr cinfo));                //初始化
  5.   JMETHOD(boolean, fill_input_buffer, (j_decompress_ptr cinfo));        //读数据(read)
  6.   JMETHOD(void, skip_input_data, (j_decompress_ptr cinfo, long num_bytes));    //跳过一段数据(seek)
  7.   JMETHOD(boolean, resync_to_restart, (j_decompress_ptr cinfo, int desired));    //seek to start of stream
  8.   JMETHOD(void, term_source, (j_decompress_ptr cinfo));                //terminate sourece 
  9. };

解码上下文struct jpeg_decompress_struct中放置了这个结构的一个指针:

  1. struct jpeg_decompress_struct {
  2.   jpeg_common_fields;        /* Fields shared with jpeg_compress_struct */

  3.   /* Source of compressed data */
  4.   struct jpeg_source_mgr * src;
  5.   ...
  6. }

解码过程中对数据源的操作均通过jpeg_source_mgr进行。
注意到jpeg_source_mgr其实是一个通用结构,并未定义数据的实际来源。比如说,未包含一个“文件描述符”之类的东东。还不能直接使用jpeg_source_mgr,libjpeg库的做法是定义一个新的结构my_source_mgr,将jpeg_source_mgr作为他的第一个数据成员,然后将&my_source_mgr强制转化为jpeg_source_mgr *赋给jpeg_decompress_struct.src。my_source_mgr定义如下

  1. typedef struct {
  2. struct jpeg_source_mgr pub;
  3. FILE * infile;
  4. JOCTET * buffer;
  5. boolean start_of_file;
  6. } my_source_mgr;

站在C++的观点上看,jpeg_source_mgr是提供给jpeg_decompress_struct的接口类,init_source等函数指针是纯虚函数,jpeg_source_mgr派生出my_source_mgr子类。my_source_mgr实现了一种数据源,即文件流。

回到主题,若需要提供从缓冲区中获取数据,实现另外一个jpeg_source_mgr,编写响应的init_source、fill_input_buffer等回调函数即可。例如,定义:

  1. typedef struct {
  2.   struct jpeg_source_mgr pub;    /* public fields */

  3.   unsigne char *img_buffer;        /* source stream */
  4.   unsigned long buffer_size;
  5.   unsigned long pos;

  6.   JOCTET * buffer;        /* start of buffer */
  7.   boolean start_of_file;    /* have we gotten any data yet? */
  8. } my_source_mgr_buffer;

回调函数中主要需要实现的是fill_input_buffer,即从缓冲区中读取数据。可参考
http://my.unix-center.net/~Simon_fu/?p=565中的jpg_fill_input_buffer。

最后需要从libjpeg库中导出一个接口给使用者。做法是在jdatasrc.c新增一个类似于jpeg_stdio_src的函数jpeg_buffer_src,然后在jpeglib.h中导出:

  1. EXTERN(void) jpeg_buffer_src JPP((j_decompress_ptr cinfo, UINT8 * buffer, UINT32 size));

对于第二个问题,也比较好着手。jpeg使jpeg_color_deconverter管理图片原本色彩空间与输出色彩空间之间的转换,主要部分在jdcolor.c中。jpeg_color_deconverter定义如下

  1. struct jpeg_color_deconverter {
  2.   JMETHOD(void, start_pass, (j_decompress_ptr cinfo));
  3.   JMETHOD(void, color_convert, (j_decompress_ptr cinfo,
  4.                 JSAMPIMAGE input_buf, JDIMENSION input_row,
  5.                 JSAMPARRAY output_buf, int num_rows));
  6. };

图像数据从libjpeg库中传出时会先调用出jpeg_color_deconverter.color_convert(),转换到输出色彩空间
jpeg_decompress_struct中相应的字段为:

  1. struct jpeg_decompress_struct {
  2.   ...
  3.   J_COLOR_SPACE out_color_space;    /* colorspace for output */
  4.   int output_components;        /* # of color components returned */
  5.   ...
  6. }

J_COLOR_SPACE中定义的颜色模式为:

  1. typedef enum {
  2.     JCS_UNKNOWN,        /* error/unspecified */
  3.     JCS_GRAYSCALE,        /* monochrome */
  4.     JCS_RGB,        /* red/green/blue */
  5.     JCS_YCbCr,        /* Y/Cb/Cr (also known as YUV) */
  6.     JCS_CMYK,        /* C/M/Y/*/
  7.     JCS_YCCK        /* Y/Cb/Cr/*/
  8. } J_COLOR_SPACE;

加入一项:
  1. JCS_BGRA,        /* blue/green/red/alpha */

在jmorecfg.h中加入:

  1. #define BGRA_BLUE 0
  2. #define BGRA_GREEN 1
  3. #define BGRA_RED 2
  4. #define BGRA_ALPHA 3
  5. #define BGRA_PIXELSIZE 4

修改jcolor.c中的jinit_color_deconverter添加JCS_BGRA的处理部分如下:
  1. GLOBAL(void)
  2. jinit_color_deconverter (j_decompress_ptr cinfo)
  3. {
  4.   ...
  5.   switch (cinfo->out_color_space) {
  6.   ...
  7.   case JCS_BGRA:
  8.     cinfo->out_color_components = BGRA_PIXELSIZE;
  9.     switch (cinfo->jpeg_color_space) {
  10.     case JCS_YCbCr:
  11.       cconvert->pub.color_convert = ycc_bgra_convert;
  12.       build_ycc_rgb_table(cinfo);
  13.       break;
  14.     case JCS_GRAYSCALE:
  15.       cconvert->pub.color_convert = gray_bgra_convert;
  16.       break;
  17.     case JCS_RGB :
  18.       cconvert->pub.color_convert = rgb_bgra_convert;
  19.       break;
  20.     }
  21.     break;
  22.   ...
  23. }
仿照ycc_rgb_convert、gray_rgb_convert实现ycc_bgra_convert、gray_bgra_convert和rgb_bgra_convert。
然后就是在什么地方通知libjpeg库来使用JCS_BGRA作为cinfo->out_color_space。可惜的是,没在libjpeg中找到设置out_color_space导出函数。没办法,加一个jpeg_decompress_set_outcolor:

  1. GLOBAL(int)
  2. jpeg_decompress_set_outcolor (j_decompress_ptr cinfo,J_COLOR_SPACE color)
  3. {
  4.   cinfo->out_color_space = color;
  5. }
呵呵,就一行。
然后在jpeglib.h中导出
EXTERN(int) jpeg_decompress_set_outcolor JPP((j_decompress_ptr cinfo,J_COLOR_SPACE color));

客户端使用时需要在jpeg_read_header(&cinfo,1)和jpeg_start_decompress(&cinfo)之间调用jpeg_decompress_set_outcolor(&cinfo,JCS_BGRA)。因为jpeg_read_header()中会根据图片原来的色彩空间设定默认的输出色彩空间,而在jpeg_start_decompress()中会调用上面的jinit_color_deconverter(),并根据cinfo->out_color_components分配输出缓冲区。

这样,两个问题都弄好了。示例代码如下:

  1. struct jpeg_decompress_struct cinfo;
  2. struct jpeg_error_mgr jerr;
  3. fd= open(filename,O_RDONLY);
  4. filesize = lseek(fd,0L,SEEK_END);
  5. readsize = read(fd,filebuffer,filesize);
  6. cinfo.err = jpeg_std_error(&jerr);
  7. jpeg_create_decompress(&cinfo);
  8. jpeg_buffer_src(&cinfo,filebuffer,filesize);
  9. jpeg_read_header(&cinfo,1);
  10. jpeg_decompress_set_outcolor(&cinfo,JCS_BGRA);
  11. jpeg_start_decompress(&cinfo);
  12. while(cinfo.output_scanline < cinfo.output_height)
  13. {
  14. jpeg_read_scanlines(&cinfo,&lineBuf,1);
  15. memcpy(buffer,lineBuf,cinfo.output_width<<2);
  16. buffer+=cinfo.output_width<<2;
  17. }

回过头来想想,虽然libjpeg提供的输入源支持只有文件流,输出色彩空间也较少。但是它内部封装的很好,非常容易根据自己的需要来添加拓展。或许这也是它应用广泛,并且稳定版本能持续十多年的原因之一吧!毕竟需求是多样的!

2011.4.16
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值