分类: LINUX
2008-07-15 09:37:19
一、建立自己的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文件第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; /* 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文件第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; /* 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_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);
.............
至此我们所有的工作均已完成,编译并运行一下测试程序,看看效果吧,如果愿意继续交流,请给我发邮件:liuyingjie201000@hotmail.com