/*
- 示例.txt
- 該文件說明了如何使用IJG代碼作為子程序庫
- 讀取或寫入JPEG圖像文件。你應該看看這段代碼
- 與文檔文件 libjpeg.txt 結合使用。
- 這段代碼按原樣不會做任何有用的事情,但它可能會有所幫助
- 用于構建調用 JPEG 庫的例程的骨架。
- 我們以 JPEG 代碼中使用的相同編碼風格呈現這些例程
*(ANSI函數定義等);但你當然可以自由地編寫你的代碼 - 如果您愿意,可以采用不同風格的例程。
*/
/* 這個例子是原始 libjpeg 文檔的一部分,并且已被
- 自 1994 年以來未發生變化。正如 libjpeg.txt 中所述,“嚴重地
- 注釋了調用 JPEG 庫的骨架代碼。”這并不意味著
- 被編譯為獨立程序,因為它沒有 main() 函數并且
- 不從真實圖像緩沖區壓縮/解壓縮到真實圖像緩沖區(推論:
- put_scanline_someplace() 不是一個真正的函數。)首次使用
- 通過查看 tjexample.c 可以更好地服務 libjpeg-turbo,它使用
- 更簡單的 TurboJPEG API,或者位于 cjpeg.c 和 djpeg.c,它們是
- 可以(并且)編譯成獨立版本的 libjpeg API 使用示例
- 程式。請注意,此示例以及 cjpeg.c 和中的示例
- djpeg.c,將磁盤 I/O 與 JPEG 壓縮/解壓縮交錯,因此沒有
- 這些示例適用于基準測試目的。
*/
#include <stdio.h>
/*
- 包含 JPEG 庫用戶的文件。
- 您需要包含至少定義的系統標頭
- typedefs FILE 和 size_t 才能包含 jpeglib.h。
*(stdio.h 對于符合 ANSI 的系統就足夠了。) - 您可能還希望包含“jerror.h”。
*/
#include“jpeglib.h”
/*
- <setjmp.h> 用于可選的錯誤恢復機制,如圖所示
- 示例的第二部分。
*/
#include <setjmp.h>
/******************** JPEG 壓縮示例接口 ********************/
/* 示例的這半部分展示了如何將數據輸入 JPEG 壓縮器。
- 我們提供了一個最小版本,無需擔心諸如此類的改進
- 作為錯誤恢復(如果出現錯誤,JPEG 代碼將直接 exit())。
*/
/*
- 圖像數據格式:
- 標準輸入圖像格式是像素的矩形陣列,其中
- 每個像素具有相同數量的“分量”值(顏色通道)。
- 每個像素行都是一個 JSAMPLE 數組(通常是無符號字符)。
- 如果您正在使用顏色數據,則每個像素的顏色值
- 必須在行中相鄰;例如,R,G,B,R,G,B,R,G,B,… 對于 24 位
- RGB 顏色。
- 對于這個例子,我們假設這個數據結構與方式匹配
- 我們的應用程序已將圖像存儲在內存中,因此我們只需傳遞一個
- 指向我們的圖像緩沖區的指針。特別是,假設圖像是
- RGB 顏色并由以下方式描述:
*/
外部 JSAMPLE image_buffer; / 指向 R、G、B 順序數據的大數組 /
外部 int 圖像高度; / 圖像的行數 /
外部 int image_width; / 圖像的列數 */
/*
- JPEG 壓縮示例例程。我們假設目標文件名
- 和壓縮品質因數被傳入。
*/
全局(無效)
write_JPEG_file(char 文件名, int 質量)
{
/ 該結構體包含 JPEG 壓縮參數和指向
- 工作空間(由 JPEG 庫根據需要分配)。
- 可以有多個這樣的結構,代表多個
- 壓縮/解壓縮過程同時存在。我們提到
- 將任何一個結構(及其關聯的工作數據)作為“JPEG 對象”。
/
結構jpeg_compress_struct cinfo;
/ 該結構代表一個 JPEG 錯誤處理程序。是單獨聲明的 - 因為應用程序通常希望提供專門的錯誤處理程序
*(有關示例,請參閱此文件的后半部分)。但在這里我們只是
*采取簡單的方法并使用標準錯誤處理程序,這將 - 如果壓縮失敗,則在 stderr 上打印一條消息并調用 exit()。
- 請注意,該結構必須與主要 JPEG 參數一樣長
- 結構體,以避免懸空指針問題。
/
struct jpeg_error_mgr jerr;
/ 更多東西 /
文件輸出文件; /* 目標文件 /
JSAMPROW row_pointer[1]; / 指向 JSAMPLE 行的指針 /
int row_stride; / 圖像緩沖區中的物理行寬度 */
/* 步驟1:分配并初始化JPEG壓縮對象 */
/* 我們必須首先設置錯誤處理程序,以防初始化
- 步驟失敗。 (不太可能,但如果您內存不足,則可能會發生。)
- 該例程填充struct jerr的內容,并返回jerr的
- 我們放入 cinfo 中鏈接字段的地址。
/
cinfo.err = jpeg_std_error(&jerr);
/ 現在我們可以初始化 JPEG 壓縮對象。 */
jpeg_create_compress(&cinfo);
/* 步驟2:指定數據目的地(例如文件)/
/ 注意:步驟 2 和 3 可以按任意順序完成。 */
/* 這里我們使用庫提供的代碼將壓縮數據發送到
- 標準輸入輸出流。您還可以編寫自己的代碼來執行其他操作。
- 非常重要:如果您使用的機器是 fopen(),請使用“b”選項
- 需要它才能寫入二進制文件。
*/
if ((outfile = fopen(文件名, “wb”)) == NULL) {
fprintf(stderr, “無法打開 %s\n”, 文件名);
退出(1);
}
jpeg_stdio_dest(&cinfo, 輸出文件);
/* 步驟3:設置壓縮參數 */
/* 首先我們提供輸入圖像的描述。
- cinfo結構體的四個字段必須填寫:
/
cinfo.image_width = image_width; / 圖像寬度和高度,以像素為單位 /
cinfo.image_height = image_height;
cinfo.input_components = 3; / 每個像素的顏色分量數 /
cinfo.in_color_space = JCS_RGB; / 輸入圖像的色彩空間 /
/ 現在使用庫的例程設置默認壓縮參數。
*(在調用此函數之前,您必須至少設置 cinfo.in_color_space, - 因為默認值取決于源色彩空間。)
/
jpeg_set_defaults(&cinfo);
/ 現在您可以設置任何您想要的非默認參數。 - 這里我們只是舉例說明質量(量化表)縮放的使用:
/
jpeg_set_quality(&cinfo,quality,TRUE / 限制為基線 JPEG 值 */);
/* 第四步:啟動壓縮機 */
/* TRUE 確保我們將編寫完整的交換 JPEG 文件。
- 除非您非常確定自己在做什么,否則請傳遞 TRUE。
*/
jpeg_start_compress(&cinfo, TRUE);
/* 步驟5: while (掃描線仍待寫入) /
/ jpeg_write_scanlines(…); */
/* 這里我們使用庫的狀態變量 cinfo.next_scanline 作為
- 循環計數器,這樣我們就不必自己跟蹤。
- 為了簡單起見,我們每次調用都會傳遞一條掃描線;你可以通過
不過,如果您愿意的話,可以更多。
/
行步幅=圖像寬度3; / image_buffer 中每行的 JSAMPLE */
while (cinfo.next_scanline < cinfo.image_height) {
/* jpeg_write_scanlines 需要一個指向掃描線的指針數組。
- 這里數組只有一個元素長,但你可以通過
- 一次多于一條掃描線(如果這樣更方便的話)。
*/
row_pointer[0] = &image_buffer[cinfo.next_scanline * row_stride];
(void)jpeg_write_scanlines(&cinfo, row_pointer, 1);
}
/* 第6步:完成壓縮 */
jpeg_finish_compress(&cinfo);
/* finish_compress之后,我們可以關閉輸出文件。 */
fclose(輸出文件);
/* 第7步:釋放JPEG壓縮對象 */
/* 這是重要的一步,因為它將釋放大量內存。 */
jpeg_destroy_compress(&cinfo);
/* 我們就完成了! */
}
/*
- 一些要點:
- 在上面的循環中,我們忽略了jpeg_write_scanlines的返回值,
- 這是實際寫入的掃描線數。我們可以逃脫
- 這樣做是因為我們只依賴 cinfo.next_scanline 的值,
- 將正確遞增。如果你保持額外的循環
- 變量,那么你應該小心地正確地增加它們。
- 實際上,對于輸出到 stdio 流你不必擔心,因為
- 然后 jpeg_write_scanlines 將寫入所有傳遞的行(否則退出
- 出現致命錯誤)。僅當您使用數據時才會發生部分寫入
- 可以要求暫停壓縮機的目標模塊。
*(如果您不知道它的用途,則不需要它。) - 如果壓縮器需要全圖像緩沖區(用于熵編碼
*優化或多次掃描JPEG文件),它會創建臨時 - 文件中包含任何不符合最大內存設置的內容。
*(請注意,如果使用默認參數,則不需要臨時文件。) - 在某些系統上,您可能需要設置信號處理程序以確保
- 如果程序中斷,臨時文件將被刪除。請參閱 libjpeg.txt。
- 如果您想要 JPEG,則必須按從上到下的順序提供掃描線
- 文件與其他人的兼容。如果您不能輕松閱讀
- 按照這個順序你的數據,你需要一個中間數組來保存
- 圖像。有關處理自下而上的示例,請參閱 rdtarga.c 或 rdbmp.c
- 使用 JPEG 代碼的內部虛擬數組機制的源數據。
*/
/******************** JPEG解壓樣本接口 ********************/
/* 示例的這半部分展示了如何從 JPEG 解壓縮器讀取數據。
- 它比上面的更加精致,我們展示了:
- (a) 如何修改JPEG庫的標準錯誤報告行為;
- (b) 如何使用庫的內存管理器分配工作空間。
- 只是為了讓這個例子與第一個例子有點不同,我們將
- 假設我們不打算將整個圖像放入內存中
- 緩沖區,但將其逐行發送到其他地方。我們需要一個——
- 掃描線高 JSAMPLE 數組作為工作緩沖區,我們將讓 JPEG
- 內存管理器為我們分配它。這個方法其實還是蠻有用的
- 因為我們不需要記住單獨釋放緩沖區:它
- 當 JPEG 對象被清理后會自動消失。
*/
/*
- 錯誤處理:
- JPEG庫的標準錯誤處理程序(jerror.c)分為
- 幾個可以單獨覆蓋的“方法”。這可以讓你
- 調整行為而不重復大量代碼,您可能會這樣做
- 必須隨未來的每個版本進行更新。
- 我們這里的例子展示了如何重寫“error_exit”方法,以便
- 當發生致命錯誤時,控制權返回給庫的調用者,
- 而不是像標準 error_exit 方法那樣調用 exit() 。
- 我們使用 C 的 setjmp/longjmp 工具來返回控制。這意味著
- 調用 JPEG 庫的例程必須首先執行 setjmp() 調用
- 建立返回點。我們希望替換 error_exit 來執行
- 長跳轉()。但我們需要使 setjmp 緩沖區可供訪問
- 錯誤退出例程。為此,我們對
- 標準 JPEG 錯誤處理程序對象。 (如果我們使用 C++,我們會說我們
*正在創建常規錯誤處理程序的子類。) - 這是擴展的錯誤處理程序結構:
*/
結構 my_error_mgr {
結構jpeg_error_mgr pub; /“公共”字段/
jmp_buf 設置jmp_buffer; /* 返回給調用者 */
};
typedef struct my_error_mgr *my_error_ptr;
/*
- 這是將替換標準 error_exit 方法的例程:
*/
方法定義(無效)
my_error_exit(j_common_ptr cinfo)
{
/* cinfo->err 實際上指向 my_error_mgr 結構,因此強制指針 */
my_error_ptr myerr = (my_error_ptr)cinfo->err;
/* 始終顯示消息。 /
/ 如果我們選擇的話,我們可以將其推遲到返回后。 */
(*cinfo->err->output_message) (cinfo);
/* 將控制權返回給setjmp點 */
longjmp(myerr->setjmp_buffer, 1);
}
METHODDEF(int) do_read_JPEG_file(struct jpeg_decompress_struct cinfo,
字符文件名);
/*
- JPEG 解壓縮的示例例程。我們假設源文件名
- 被傳入。我們希望成功時返回 1,錯誤時返回 0。
*/
全局(整數)
read_JPEG_file(char 文件名)
{
/ 該結構體包含 JPEG 解壓縮參數和指向
- 工作空間(由 JPEG 庫根據需要分配)。
*/
struct jpeg_decompress_struct cinfo;
返回 do_read_JPEG_file(&cinfo, 文件名);
}
/*
- 我們從一個單獨的函數中調用 libjpeg API,因為修改
- setjmp() 下面的本地非易失性 jpeg_decompress_struct 實例
- 返回點,然后在 setjmp() 返回后訪問實例將
- 導致未定義的行為,可能會覆蓋全部或部分
- 結構。
*/
方法定義(int)
do_read_JPEG_file(struct jpeg_decompress_struct *cinfo, char 文件名)
{
/ 我們使用我們的私有擴展 JPEG 錯誤處理程序。
- 請注意,該結構必須與主要 JPEG 參數一樣長
- 結構體,以避免懸空指針問題。
/
結構 my_error_mgr jerr;
/ 更多東西 */
文件 infile; / 源文件 /
JSAMPARRAY 緩沖區; / 輸出行緩沖區 /
int row_stride; / 輸出緩沖區中的物理行寬度 */
/* 在此示例中,我們希望在執行其他操作之前打開輸入文件,
- 以便下面的 setjmp() 錯誤恢復可以假設文件已打開。
- 非常重要:如果您使用的機器是 fopen(),請使用“b”選項
- 需要它才能讀取二進制文件。
*/
if ((infile = fopen(文件名, “rb”)) == NULL) {
fprintf(stderr, “無法打開 %s\n”, 文件名);
返回0;
}
/* 步驟1:分配并初始化JPEG解壓對象 */
/* 我們設置正常的 JPEG 錯誤例程,然后覆蓋 error_exit。 /
cinfo->err = jpeg_std_error(&jerr.pub);
jerr.pub.error_exit = my_error_exit;
/ 建立 setjmp 返回上下文以供 my_error_exit 使用。 /
如果(setjmp(jerr.setjmp_buffer)){
/ 如果我們到達這里,JPEG 代碼已發出錯誤信號。
- 我們需要清理JPEG對象,關閉輸入文件,然后返回。
/
jpeg_destroy_decompress(cinfo);
fclose(infile);
返回0;
}
/ 現在我們可以初始化 JPEG 解壓縮對象了。 */
jpeg_create_decompress(cinfo);
/* 步驟2:指定數據源(例如文件) */
jpeg_stdio_src(cinfo, infile);
/* 步驟3:使用jpeg_read_header()讀取文件參數 */
(void)jpeg_read_header(cinfo, TRUE);
/* 我們可以忽略 jpeg_read_header 的返回值,因為
- (a) stdio 數據源不可能暫停,并且
- (b) 我們通過 TRUE 來拒絕僅包含表格的 JPEG 文件作為錯誤。
- 有關詳細信息,請參閱 libjpeg.txt。
*/
/* 第四步:設置解壓參數 */
/* 在這個例子中,我們不需要更改任何默認設置
- jpeg_read_header(),所以我們在這里什么都不做。
*/
/* 步驟5:啟動解壓器 */
(void)jpeg_start_decompress(cinfo);
/* 我們可以忽略返回值,因為不可能暫停
- 使用 stdio 數據源。
*/
/* 在閱讀之前我們可能需要自己做一些設置
- 數據。在 jpeg_start_decompress() 之后,我們得到了正確的縮放比例
- 可用的輸出圖像尺寸以及輸出色彩圖
- 如果我們要求顏色量化。
- 在本例中,我們需要創建一個大小合適的輸出工作緩沖區。
/
/ 輸出緩沖區中每行 JSAMPLE /
row_stride = cinfo->output_width * cinfo->output_components;
/ 創建一個單行高的樣本數組,當處理完圖像后該數組就會消失 */
buffer = (*cinfo->mem->alloc_sarray)
((j_common_ptr)cinfo, JPOOL_IMAGE, row_stride, 1);
/* 步驟6: while (掃描線仍有待讀取) /
/ jpeg_read_scanlines(…); */
/* 這里我們使用庫的狀態變量 cinfo->output_scanline 作為
- 循環計數器,這樣我們就不必自己跟蹤。
/
while (cinfo->output_scanline < cinfo->output_height) {
/ jpeg_read_scanlines 需要一個指向掃描線的指針數組。 - 這里數組只有一個元素長,但你可以要求
- 一次多于一條掃描線(如果這樣更方便的話)。
/
(void)jpeg_read_scanlines(cinfo, 緩沖區, 1);
/ 假設 put_scanline_someplace 需要一個指針和樣本計數。 */
put_scanline_someplace(緩沖區[0], row_stride);
}
/第七步:完成解壓/
(void)jpeg_finish_decompress(cinfo);
/* 我們可以忽略返回值,因為不可能暫停
- 使用 stdio 數據源。
*/
/* 第8步:釋放JPEG解壓對象 */
/* 這是重要的一步,因為它將釋放大量內存。 */
jpeg_destroy_decompress(cinfo);
/* finish_decompress后,我們可以關閉輸入文件。
- 這里我們推遲它,直到不可能再出現 JPEG 錯誤為止,
- 從而簡化上面的setjmp錯誤邏輯。 (其實我不
- 認為 jpeg_destroy 可以執行錯誤退出,但為什么要假設任何事情…)
*/
fclose(infile);
/* 此時您可能想要檢查是否有任何損壞的數據
- 發生警告(測試 jerr.pub.num_warnings 是否為非零)。
*/
/* 我們就完成了! */
返回1;
}
/*
- 一些要點:
- 上面的代碼中,我們忽略了jpeg_read_scanlines的返回值,
- 這是實際讀取的掃描線數。我們可以逃脫
- 這是因為我們一次只要求一行并且我們沒有使用
- 一個暫停的數據源。有關詳細信息,請參閱 libjpeg.txt。
- 我們在 jpeg_start_decompress() 之后調用 alloc_sarray() 有點作弊;
*我們應該提前做好,以確保空間 - 根據 JPEG max_memory 設置計算。在某些系統中,上述
- 代碼可能會出現內存不足錯誤。然而,一般情況下我們不
- 在 jpeg_start_decompress() 之前知道輸出圖像尺寸,除非我們
- 調用jpeg_calc_output_dimensions()。有關詳細信息,請參閱 libjpeg.txt。
- 掃描線按照 JPEG 文件中出現的順序返回,
- 標準是從上到下。如果您必須從下到上發出數據,
- 您可以使用JPEG內存管理器提供的虛擬數組之一
- 反轉數據。有關示例,請參見 wrbmp.c。
- 與壓縮一樣,某些操作模式可能需要臨時文件。
- 在某些系統上,您可能需要設置信號處理程序以確保
- 如果程序中斷,臨時文件將被刪除。請參閱 libjpeg.txt。
*/