【转】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文件第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);  .............

时间: 2024-10-05 19:20:34

【转】libjpeg实现内存位图的压缩及解压缩-显示格式有问题的相关文章

Delphi GDI对象之脱屏位图(Offscreen Bitmaps),也叫内存位图

http://www.cnblogs.com/pchmonster/archive/2012/07/09/2583613.html 脱屏位图(Offscreen Bitmaps) 脱屏位图,也叫内存位图,普遍用于Windows程序设计中.它在内存中制作图像,然后利用Draw方法在屏幕上显示出来.当用户想更快的在屏幕上绘制图像时,脱屏位图有助于避免闪烁.脱屏位图也适合于复杂制图程序.用户可以将图像预存起来,需要时显示出来.脱屏位图用于动画,最流行的动画制作方法是Microsoft的DirectX

在内存中压缩及解压缩

在内存中压缩及解压缩 //引入头文件#import <zlib.h> //引入libz动态库 NSString *str = @"zlib compress and uncompress test\[email protected]\n2012-11-05\n"; NSData *data = [str dataUsingEncoding:NSUTF8StringEncoding]; Bytef *text = (Bytef*)[data bytes]; uLong tle

利用JAVA API函数实现数据的压缩与解压缩

综述 许多信息资料都或多或少的包含一些多余的数据.通常会导致在客户端与服务器之间,应用程序与计算机之间极大的数据传输量.最常见的解决数据存储和信息传送的方法是安装额外的存储设备和扩展现有的通讯能力.这样做是可以的,但无疑会增加组织的运作成本.一种有效的解决数据存储与信息传输的方法是通过更有效率的代码来存储数据.这篇文章简要的介绍了数据的压缩与解压缩,并展示了用java.util.zip包来实现数据的压缩与解压缩是多么的方便与高效. 当然用诸如WinZip,gzip,和Java压缩(或jar)之类

Jcompress: 一款基于huffman编码和最小堆的压缩、解压缩小程序

前言 最近基于huffman编码和最小堆排序算法实现了一个压缩.解压缩的小程序.其源代码已经上传到github上面: Jcompress下载地址 .在本人的github上面有一个叫Utility的repository,该分类下面有一个名为Jcompress的目录便是本文所述的压缩.解压缩小程序的源代码.后续会在Utility下面增加其他一些实用的小程序,比如基于socket的文件断点下载小程序等等.如果你读了此文觉得还不错,不防给笔者的github点个star, 哈哈.在正式介绍Jcompres

Huffman的应用之文件压缩与解压缩

文件压缩与解压缩> 最近这段时间一直在学习树的这种数据结构,也接触到了Huffman树以及了解了什仫是Huffman编码,而我们常用的zip压缩也是利用的Huffman编码的特性,那仫是不是可以自己实现一个文件压缩呢?当然可以了.在文件压缩中我实现了Huffman树和建堆Heap的代码,zip压缩的介绍> http://www.cricode.com/3481.html 下面开始介绍自己实现的文件压缩的思路和问题... 1).统计>读取一个文件统计这个文件中字符出现的次数. 2).建树&

linux下压缩、解压缩、归档详解

Linux下常用压缩工具有zip.bzip2.gzip.xz.tar 解压缩工具有unzip.bunzip2.gunzip.unxz bzip2.gzip.xz 以上三个命令不能对目录压缩,只能对目录下各文件压缩 压缩命令: zip 命令: 是一个应用广泛的跨平台的压缩工具,压缩文件的后缀为zip文件 语法: zip[参数][文件] 列举参数: -A 自动解压文件 -c 给压缩文件加注释 -d 删除文件 -F 修复损坏文件 -k 兼容 DOS -m 压缩完毕后,删除园文件 -q 运行时不显示信息

Linux系统压缩及解压缩

==============================================================================  Linux系统解压缩 ============================================================================== 概述: 本篇将介绍Linux系统中的压缩和解压缩的工具,以及归档工具(tar,cpio) compress/uncompress:对应 .Z 结尾的压缩格式文件

Linux学习笔记&lt;十三&gt;——文件压缩、解压缩和归档

压缩.解压缩命令: 1.compress/uncompress:压缩格式为Z,文件后缀为.Z compress /path/to/file uncompress /path/to/file.Z 2.gzip/gunzip/zcat:压缩格式为gz,文件后缀为.gz gzip [OPTION] /path/to/file:,压缩文件保存在被压缩文件的目录,压缩完成后会删除原文件 -v|verbose:显示指令执行过程 -d:解压缩,解压缩完成后删除原压缩文件 -#:1-9,指定压缩比,默认为6,数

《转》python的zipfile压缩、解压缩

网上搜索了很多关于python的zipfile压缩.解压缩.觉得讲述比较详细,例子也很明了.由于比较懒,就直接复制了. 以下内容大部分转于 http://blog.csdn.net/jgood/article/details/4351911 zip文件格式是通用的文档压缩标准,在ziplib模块中,使用ZipFile类来操作zip文件,下面具体介绍一下: class zipfile.ZipFile(file[, mode[, compression[, allowZip64]]]) 创建一个Zi