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

FROM:http://hi.baidu.com/fengling1234567/blog/item/fac0dbf9b6982b5c242df2e3.html

 

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

 

 


相信使用过的朋友应该会喜欢上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 行):
1      GLOBAL(void)
2      jpeg_stdio_dest (j_compress_ptr cinfo, FILE * outfile)
3      {
4               my_dest_ptr dest;
5               /* The destination object is made permanent so that multiple JPEG images
6                * can be written to the same file without re-executing jpeg_stdio_dest.
7                * This makes it dangerous to use this manager and a different destination
8                * manager serially with the same JPEG object, because their private object
9               * sizes may be different. Caveat programmer.
10             */
11           if (cinfo->dest == NULL) { /* first time for this JPEG object? */
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; /* target stream */
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;   /* target stream */ //
FILE * outfile;   /* target stream */ 改写

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

jdatadst.cpp 文件第114term_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; /* public fields */

char * outdata;   /* target stream */
int *pSize;    //
新加变量,该指针为调用者提供,压缩完后返回图像大小
int nOutOffset;    //
新加变量
JOCTET * buffer;   /* start of 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;

/* The destination object is made permanent so that multiple JPEG images
   * can be written to the same file without re-executing jpeg_stdio_dest.
   * This makes it dangerous to use this manager and a different destination
   * manager serially with the same JPEG object, because their private object
   * sizes may be different. Caveat programmer.
   */
if (cinfo->dest == NULL) { /* first time for this JPEG object? */
    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 文件第87empty_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 文件第114term_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;   /* source stream */
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
/* Expanded data source object for stdio input */

typedef struct {
struct jpeg_source_mgr pub; /* public fields */

char * indata;   /* source stream */
int nInOffset;
int nSize;
JOCTET * buffer;   /* start of buffer */
boolean start_of_file; /* have we gotten any data yet? */
} my_source_mgr;

jdatasrc.cpp 183
GLOBAL(void)
jpeg_stdio_src (j_decompress_ptr cinfo, char * indata, int nSize)
{
my_src_ptr src;

/* The source object and input buffer are made permanent so that a series
   * of JPEG images can be read from the same file by calling jpeg_stdio_src
   * only before the first one. (If we discarded the buffer at the end of
   * one image, we'd likely lose the start of the next one.)
   * This makes it unsafe to use this manager and a different source
   * manager serially with the same JPEG object. Caveat programmer.
   */
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 = 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->indata = indata;    //
新添加行
src->nSize = nSize;    //
新添加
src->nInOffset = 0;    //
新添加
src->pub.bytes_in_buffer = 0; /* forces fill_input_buffer on first read */
src->pub.next_input_byte = NULL; /* until buffer loaded */
}

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

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_destjpeg_stdio_src 这两个函数 的调用,
调用原来的libjpeg 库时,需要为这两个函数提供已经打开的jpg 文件句柄,而对于新的libjpeg 库,不需要打开jpg 文件了,压缩时,
我们需要提供足够大的内存区给libjpeg 库,解压缩时,只需要把存放有jpeg 格式图像的内存区提供给libjpeg 库就行了,下面详细介绍
对于改写后的jpeg_stdio_destjpeg_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);
.............

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

 

 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值