通过简单修改libjpeg源代码,实现内存内位图的压缩及解压缩_libjpeg 源代码-程序员宅基地

技术标签: output  input  file  manager  buffer  object  

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);
.............

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

 

 

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/jurrah/article/details/5575080

智能推荐

Sublime text 3搭建Python开发环境及常用插件安装_sublime python 环境搭建-程序员宅基地

文章浏览阅读4.9k次。Sublime text 3搭建Python开发环境及常用插件安装_sublime python 环境搭建

在CentOS 7上安装MySQL 8.0_centos7安装mysql8.0gpg密钥-程序员宅基地

文章浏览阅读643次。MySQL在首次安装后会执行一个安全脚本,用于设置root用户的密码以及其他安全选项。_centos7安装mysql8.0gpg密钥

echarts绘制圆角方形进度图_echarts symbolboundingdata-程序员宅基地

文章浏览阅读864次。这种场景下,可以使用两个系列,一个系列是完整的图形,当做『背景』来表达总数值,另一个系列是使用 `symbolClip` 进行剪裁过的图形,表达当前数值。_echarts symbolboundingdata

学python需要什么样的电脑,python需要什么样的电脑_python机器学习需要怎样配置的电脑-程序员宅基地

文章浏览阅读1k次,点赞18次,收藏16次。这篇文章主要介绍了学python对电脑配置要求高吗,具有一定借鉴价值,需要的朋友可以参考下。希望大家阅读完这篇文章后大有收获,下面让小编带着大家一起了解一下。_python机器学习需要怎样配置的电脑

最新OCR开源神器来了!-程序员宅基地

文章浏览阅读3.9k次。Datawhale开源开源方向:OCR开源项目01导读OCR方向的工程师,之前一定听说过PaddleOCR这个项目,其主要推荐的PP-OCR算法更是被国内外企业开发者广泛应用,短短半年..._github 2023年最新表格ocr

python 建筑建模_设计课开题 | Parameterized Complexities参数化建筑设计-程序员宅基地

文章浏览阅读317次。【竞赛+作品集,点燃你的设计理想】设计课开题啦!百川柯纳陆续推出以国际设计竞赛项目为参考的设计题目让大家参与,借此丰富履历,充实作品集。本期的设计题目为:Parameterized Complexities参数化建筑设计。喜欢参数化的小伙伴,你们兴奋吗?Parameterized Complexities 选题背景 近期不断有小伙伴在后台给我们留言,或者咨询百川柯纳顾问老师表达希望能够参加以“参数..._python 建筑平面图

随便推点

[ATF]-TEE/REE系统切换时ATF的寄存器的保存和恢复_atf-tee-程序员宅基地

文章浏览阅读1k次。ATF点滴1、设置运行时栈SP2、寄存器的保存和恢复的实现3、寄存器的保存和恢复的使用场景1、设置运行时栈SPbl31_entrypoint—>el3_entrypoint_common---->plat_set_my_stack—>platform_set_stack—>platform_get_stack动态找到该cpufunc platform_set_stackmov x9, x30 // lrbl platform_get_stackmov sp, x0r_atf-tee

PPT模板下载-程序员宅基地

文章浏览阅读134次。300多个各种类型的PPT模板下载,为您提供各种类型PPT模板、PPT图片、PPT素材、海报模板、新媒体配图等内容下载。

基于JAVA的智能小区物业管理系统【数据库设计、源码、开题报告】_智能化哪些系统需要数据库-程序员宅基地

文章浏览阅读546次。主要功能有:保安保洁管理、报修管理、房产信息管理、公告管理、管理员信息管理、业主信息管理、登录管理。_智能化哪些系统需要数据库

年度书单盘点 | 实用到爆炸,这份高性价比套系书单,越读越上头!-程序员宅基地

文章浏览阅读69次。本期年度书单,带大家盘点一下本年度图灵最受欢迎的套装图书,以前买套装书是为了凑单,如今套装书买回去不仅有一次性就能读完的酣畅感还极具收藏价值。一本好书往往要经过时间的验证,而阅读又是一种隐私,每个人的喜好大有不同,但能够集齐每个人的喜爱,这往往就是经典的诞生。今天这份书单里,有自成体系的套系书,还有一些因读者需求而产生的组成套系书。但不管哪种形式,它们都解决了读者在学习某些方面遇到的问题,也给大家...

thch30 steps/make_mfcc.sh详解-程序员宅基地

文章浏览阅读809次。这个脚本的输入参数有三个:1.data/mfcc/train 2.exp/make_mfcc/train 3.mfcc/train1.data/mfcc/train中有数据预处理后的一些文件:phone.txt spk2utt text utt2spk wav.scp word.txt2.exp/make_mfcc/train中应该是要保存程序运行的日志文件的3.mfcc/train中是提取出的特征文件1是输入目录,2,3是输出目录#!/bin/bash# Copyright 2012-2_thch30

smartclient listgrid style (加竖线、横线、背景色)_listgrid添加样式-程序员宅基地

文章浏览阅读2.5k次。如图所示:在jsp中引入: Style.css 代码:.myOtherGridCell { font-family:Verdana,Bitstream Vera Sans,sans-serif; font-size:11px; color:black; border-bottom:1px solid #a0a0a0;border-right:1px solid_listgrid添加样式