通过简单修改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

智能推荐

while循环&CPU占用率高问题深入分析与解决方案_main函数使用while(1)循环cpu占用99-程序员宅基地

文章浏览阅读3.8k次,点赞9次,收藏28次。直接上一个工作中碰到的问题,另外一个系统开启多线程调用我这边的接口,然后我这边会开启多线程批量查询第三方接口并且返回给调用方。使用的是两三年前别人遗留下来的方法,放到线上后发现确实是可以正常取到结果,但是一旦调用,CPU占用就直接100%(部署环境是win server服务器)。因此查看了下相关的老代码并使用JProfiler查看发现是在某个while循环的时候有问题。具体项目代码就不贴了,类似于下面这段代码。​​​​​​while(flag) {//your code;}这里的flag._main函数使用while(1)循环cpu占用99

【无标题】jetbrains idea shift f6不生效_idea shift +f6快捷键不生效-程序员宅基地

文章浏览阅读347次。idea shift f6 快捷键无效_idea shift +f6快捷键不生效

node.js学习笔记之Node中的核心模块_node模块中有很多核心模块,以下不属于核心模块,使用时需下载的是-程序员宅基地

文章浏览阅读135次。Ecmacript 中没有DOM 和 BOM核心模块Node为JavaScript提供了很多服务器级别,这些API绝大多数都被包装到了一个具名和核心模块中了,例如文件操作的 fs 核心模块 ,http服务构建的http 模块 path 路径操作模块 os 操作系统信息模块// 用来获取机器信息的var os = require('os')// 用来操作路径的var path = require('path')// 获取当前机器的 CPU 信息console.log(os.cpus._node模块中有很多核心模块,以下不属于核心模块,使用时需下载的是

数学建模【SPSS 下载-安装、方差分析与回归分析的SPSS实现(软件概述、方差分析、回归分析)】_化工数学模型数据回归软件-程序员宅基地

文章浏览阅读10w+次,点赞435次,收藏3.4k次。SPSS 22 下载安装过程7.6 方差分析与回归分析的SPSS实现7.6.1 SPSS软件概述1 SPSS版本与安装2 SPSS界面3 SPSS特点4 SPSS数据7.6.2 SPSS与方差分析1 单因素方差分析2 双因素方差分析7.6.3 SPSS与回归分析SPSS回归分析过程牙膏价格问题的回归分析_化工数学模型数据回归软件

利用hutool实现邮件发送功能_hutool发送邮件-程序员宅基地

文章浏览阅读7.5k次。如何利用hutool工具包实现邮件发送功能呢?1、首先引入hutool依赖<dependency> <groupId>cn.hutool</groupId> <artifactId>hutool-all</artifactId> <version>5.7.19</version></dependency>2、编写邮件发送工具类package com.pc.c..._hutool发送邮件

docker安装elasticsearch,elasticsearch-head,kibana,ik分词器_docker安装kibana连接elasticsearch并且elasticsearch有密码-程序员宅基地

文章浏览阅读867次,点赞2次,收藏2次。docker安装elasticsearch,elasticsearch-head,kibana,ik分词器安装方式基本有两种,一种是pull的方式,一种是Dockerfile的方式,由于pull的方式pull下来后还需配置许多东西且不便于复用,个人比较喜欢使用Dockerfile的方式所有docker支持的镜像基本都在https://hub.docker.com/docker的官网上能找到合..._docker安装kibana连接elasticsearch并且elasticsearch有密码

随便推点

Python 攻克移动开发失败!_beeware-程序员宅基地

文章浏览阅读1.3w次,点赞57次,收藏92次。整理 | 郑丽媛出品 | CSDN(ID:CSDNnews)近年来,随着机器学习的兴起,有一门编程语言逐渐变得火热——Python。得益于其针对机器学习提供了大量开源框架和第三方模块,内置..._beeware

Swift4.0_Timer 的基本使用_swift timer 暂停-程序员宅基地

文章浏览阅读7.9k次。//// ViewController.swift// Day_10_Timer//// Created by dongqiangfei on 2018/10/15.// Copyright 2018年 飞飞. All rights reserved.//import UIKitclass ViewController: UIViewController { ..._swift timer 暂停

元素三大等待-程序员宅基地

文章浏览阅读986次,点赞2次,收藏2次。1.硬性等待让当前线程暂停执行,应用场景:代码执行速度太快了,但是UI元素没有立马加载出来,造成两者不同步,这时候就可以让代码等待一下,再去执行找元素的动作线程休眠,强制等待 Thread.sleep(long mills)package com.example.demo;import org.junit.jupiter.api.Test;import org.openqa.selenium.By;import org.openqa.selenium.firefox.Firefox.._元素三大等待

Java软件工程师职位分析_java岗位分析-程序员宅基地

文章浏览阅读3k次,点赞4次,收藏14次。Java软件工程师职位分析_java岗位分析

Java:Unreachable code的解决方法_java unreachable code-程序员宅基地

文章浏览阅读2k次。Java:Unreachable code的解决方法_java unreachable code

标签data-*自定义属性值和根据data属性值查找对应标签_如何根据data-*属性获取对应的标签对象-程序员宅基地

文章浏览阅读1w次。1、html中设置标签data-*的值 标题 11111 222222、点击获取当前标签的data-url的值$('dd').on('click', function() { var urlVal = $(this).data('ur_如何根据data-*属性获取对应的标签对象

推荐文章

热门文章

相关标签