Tag:linux 技巧 jpeg
著作權聲明:轉載時請以超連結形式標明文章原始出處和作者資訊及本聲明
http://rtfsc.blogbus.com/logs/23148378.html
相信使用過的朋友應該會喜歡上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);
............. 至此我們所有的工作均已完成,完整的測試程式請從我的資源裡下載,為增加相容性,本測試程式特意加入位元組調整功能,以解決部分讀者
測試映像時出現的映像傾斜的問題,好了,編譯並運行一下測試程式,看看效果吧。