[畢設(shè)記錄] libjpeg將bmp格式轉(zhuǎn)換成jpg格式

前言

在畢業(yè)設(shè)計里面绪钥,下位機通過OV7725和屏幕截屏灿里,獲得了一張bmp格式的圖像。我希望可以將這個圖片發(fā)送到上位機進行圖像處理程腹,但是由于bmp格式的圖片比較大匣吊,在傳輸過程當中需要的時間比較長。所以,我希望可以將這個bmp圖像轉(zhuǎn)換成jpg格式色鸳,然后再發(fā)送社痛。經(jīng)過試驗,220多k得bmp圖片命雀,轉(zhuǎn)換成jpg格式后只有20多k(注意蒜哀,這不是壓縮比,要根據(jù)具體的圖片來確定咏雌,圖片越單一壓縮比越高)凡怎。

獲得源碼

通過資料搜集,發(fā)現(xiàn)一個開源庫libjpeg可以用來實現(xiàn)這個功能赊抖,這個庫的主要作者是LJG统倒,通過上面的兩個連接你都可以獲得源碼。我采用的后面的LJG的連接的源碼氛雪,下載對應(yīng)機器的版本房匆,然后編譯之。

編譯源碼

編譯源碼也是一個技術(shù)活报亩,我的環(huán)境是Mac OS X EI 10.11(后文的C集成開發(fā)環(huán)境是Xcode 6)浴鸿。

在Mac下編譯源碼的話比較簡單,將下載的壓縮包解壓弦追,打開命令行岳链,cd進入目錄,執(zhí)行命令:

./configure --prefix=“你希望編譯后文件的絕對路徑”(不要帶引號)

然后就執(zhí)行make和make install命令劲件,你就會在你規(guī)定的路徑下看到編譯出來的文件掸哑。

BMP格式

bmp格式,是window的標準位圖格式零远。詳細介紹苗分,百度百科請點擊在這里。下面是簡要介紹牵辣,典型的BMP圖像文件由四部分組成:

  1. 位圖頭文件數(shù)據(jù)結(jié)構(gòu)摔癣,它包含BMP圖像文件的類型、顯示內(nèi)容等信息纬向;
  2. 位圖信息數(shù)據(jù)結(jié)構(gòu)择浊,它包含有BMP圖像的寬、高逾条、壓縮方法琢岩,以及定義顏色等信息;
  3. 調(diào)色板膳帕,這個部分是可選的,有些位圖需要調(diào)色板,有些位圖危彩,比如真彩色圖(24位的BMP)就不需要調(diào)色板 (注意攒磨,本文僅僅處理24位的bmp,非24位的用后面的算法是行不通的汤徽!)娩缰;
  4. 位圖數(shù)據(jù),這部分的內(nèi)容根據(jù)BMP位圖使用的位數(shù)不同而不同谒府,在24位圖中直接使用RGB拼坎,而其他的小于24位的使用調(diào)色板中顏色索引值。

讀取BMP圖片算法

既然完疫,我們要講bmp圖片轉(zhuǎn)換泰鸡,那第一步首先是要將bmp圖片讀到內(nèi)存當中才可以進行處理。下面壳鹤,敘述bmp的讀取方法盛龄。

讀取BMP頭文件數(shù)據(jù)

根據(jù)上面的知識,我們先嘗試以下讀取BMP的頭文件數(shù)據(jù)芳誓。由資料可知余舶,BMP頭文件具體包括以下內(nèi)容:

  • 位圖文件類型 【1,2】字節(jié)
  • 位圖文件大星绿省(單位:字節(jié))【3匿值,6】字節(jié)
  • 保留字1 【7,8】字節(jié)
  • 保留字2 【9赂摆,10】字節(jié)
  • 位圖數(shù)據(jù)偏移量(單位:字節(jié))【11挟憔,14】字節(jié)

根據(jù)以上的內(nèi)容,這些信息都是按照順序存放的库正。不難理解出曲楚,我們的處理方法,應(yīng)該是定義一個結(jié)構(gòu)體來存放它們褥符。先看一下目錄結(jié)構(gòu)龙誊,在一個文件夾(我命名為c_test)下新建三個文件:bmp2jpg.h,bmp2jpg.c和main.c喷楣。

下面是結(jié)構(gòu)體定義的代碼:

//以下代碼放在bmp2jpg.h文件里面
//關(guān)于其中DWORD WORD這些變量的說明如下:
//請自己根據(jù)自己使用的系統(tǒng)來確定 WORD是兩個字節(jié)趟大,DWORD是4個字節(jié)
typedef struct tagBITMAPFILEHEADER {
  DWORD bfSize;
  WORD bfReserved1; 
  WORD bfReserved2; 
  DWORD bfOffBits;
} BITMAPFILEHEADER,tagBITMAPFILEHEADER;

細心的讀者肯定會發(fā)現(xiàn),上面定義的結(jié)構(gòu)體怎么少了位圖文件類型一項铣焊。這里作出解釋:一般來說逊朽,會在程序開始先讀取并判斷前面2個字節(jié)(即位圖文件類型)是否為BM(若是,說明這兒文件是bmp格式曲伊。否則叽讳,就不再執(zhí)行后面的處理了)追他,所以結(jié)構(gòu)體里面沒有定義。另外岛蚤,編譯器默認是4字節(jié)對齊的(一次讀取4個字節(jié))邑狸,直接添加這個會導(dǎo)致后面的數(shù)據(jù)讀取錯誤

//bmp2jpg.c
#include "bmp2jpg.h"

// 頭文件結(jié)構(gòu)體
BITMAPFILEHEADER bmpFileHeader;
// 位圖類型
char fileType[2];
// bmp文件指針
FILE *bmp;

void showBmpHead(BITMAPFILEHEADER *pBmpHead) {
    printf("bmp文件大小:%u\r\n", (*pBmpHead).bfSize);
    printf("保留字1:%d\r\n", (*pBmpHead).bfReserved1);
    printf("保留字2:%d\r\n", (*pBmpHead).bfReserved2);
    printf("實際位圖數(shù)據(jù)的偏移字節(jié)數(shù):%u\r\n", (*pBmpHead).bfOffBits);
    printf("\r\n");
}

// 讀取bmp圖片涤妒,成功返回 0
int readBmpFileHeader(const char *bmp_file) {
    bmp = fopen(bmp_file, "rb");
    if (bmp == NULL) {
        printf("%s\n", "fail to open");
        return -1;
    }
    // 讀取文件前兩個字節(jié)判斷是否bmp格式
    fread(fileType, sizeof(char), 2, bmp);
    if (strcmp(fileType, "BM")==0) {
        fread(&bmpFileHeader, sizeof(tagBITMAPFILEHEADER), 1, bmp);
        showBmpHead(&bmpFileHeader);
    }else {
        printf("%s\n", "this file is not a bmp file");
        return -1;
    }
    return 0;
}

上面是讀取bmp的代碼单雾,用fopen以二進制的形式打開圖片文件,然后讀取一定的字節(jié)數(shù)([2,14]字節(jié)她紫,共12字節(jié))硅堆,放到結(jié)構(gòu)體變量當中,再通過showBmpHead將它們顯示出來贿讹。主函數(shù)那里調(diào)用一下readBmpFileHeader()并把圖片的路徑以字符串的形式傳進去即可渐逃。主函數(shù)如下:

//main.c
#include <stdio.h>
#include "bmp2jpg.h"

int main() {   
    readBmpFileHeader("/Users/ERIC_LAI/Desktop/bmp2jpg/bmp2jpg/image2.bmp");
    return 0;
}

結(jié)果如下所示:

bmp文件大小:230454
保留字1:0
保留字2:0
實際位圖數(shù)據(jù)的偏移字節(jié)數(shù):54

應(yīng)用同樣的道理,我們可以把第二部分的信息也獲取出來并保存到一個結(jié)構(gòu)體上围详,這里不一一贅述朴乖。

讀取BMP圖片數(shù)據(jù)

讀取的方法很簡單,定義一個容器助赞,接著上面的操作把剩下的數(shù)據(jù)讀取進容器里面就可以了买羞。這里敘述的是在pc上的讀取,在stm32等內(nèi)存有限的設(shè)備上不能使用這種方法(后文會敘述)雹食。

// 這里將函數(shù)寫成內(nèi)部方法畜普,可以根據(jù)需要自行調(diào)整
// show?的方法是調(diào)試用的群叶,這里注釋掉了
// 讀取bmp圖片吃挑,成功返回 0
static int readBmpFile(const char *bmp_file) {
    FILE *bmp;
    static char fileType[2];
    bmp = fopen(bmp_file, "rb");
    if (bmp == NULL) {
        printf("%s\n", "fail to open");
        return -1;
    }
    // 讀取文件前兩個字節(jié)判斷是否bmp格式
    fread(fileType, sizeof(char), 2, bmp);
    // 讀取頭文件
    if (strcmp(fileType, "BM")==0) {
        readFlag = fread(&bmpFileHeader, sizeof(tagBITMAPFILEHEADER), 1, bmp);
//        showBmpHead(&bmpFileHeader);
        // 讀取位圖信息
        if (readFlag == 1) {
            readFlag = fread(&bmpInfoHeader, sizeof(tagBITMAPINFOHEADER), 1, bmp);
//            showBmpInfo(&bmpInfoHeader);
        } else {
            printf("%s\n", "fail to read the bmp information");
            return -1;
        }
        // 讀取位圖數(shù)據(jù)
        data_size = bmpInfoHeader.biWidth * bmpInfoHeader.biHeight * depth;
        //動態(tài)分配內(nèi)存,記得需要釋放(這里在調(diào)用這個函數(shù)的函數(shù)里面釋放街立,下文可以看到)
        bmp_data = malloc(data_size);
        fread(bmp_data, sizeof(BYTE), data_size, bmp);
    }else {
        printf("%s\n", "this file is not a bmp file");
        return -1;
    }
    fclose(bmp);
    return 0;

由于本文只研究真彩色的圖(畢業(yè)設(shè)計時間有限舶衬,以后可能會研究對其他類型圖片的操作),所以對于這種bmp圖片赎离,剩下的部分就是具體的圖片數(shù)據(jù)編碼了逛犹。在真彩色的bmp格式的圖片當中,每一個像素用三個數(shù)據(jù)來表示梁剔,分別是BGR(blue green red)虽画。因為在第二部分,我們獲取了圖片的寬度和高度荣病。通過這兩個參數(shù)码撰,我們便可以知道整個圖片的排列規(guī)則,以及應(yīng)該怎么顯示它們了个盆。

下面脖岛,就是正菜了朵栖!演示如何使用jpeglib庫將bmp圖片轉(zhuǎn)換成jpg格式圖片,使用之前需要注意柴梆,將編譯的時候生成的lib和include文件夾添加到當前工程的目錄下混槐,否則會報錯找不到某些函數(shù),轉(zhuǎn)換函數(shù)代碼如下:

// 這個函數(shù)對外暴露轩性,輸入兩個參數(shù),分別是待轉(zhuǎn)換的bmp圖片路徑和希望生成jpg圖片的路徑
int mp2jpg(const char *bmp_file, const char *jeg_file) {
    FILE *outfile;
    struct jpeg_compress_struct cinfo;
    struct jpeg_error_mgr jerr;
    JSAMPROW row_pointer[1]; /* 行指針 */
    int row_stride; /* 行跨度(圖像中一行需要多少個字節(jié)來表示) */
    char tmp = '0';
    int index = 0;
    // 讀取bmp圖像
    if (readBmpFile(bmp_file) == -1) return -1;
    // 將BGR編碼方式轉(zhuǎn)換成RGB編碼方式
    for (index = 0; index < data_size; index = index+3) {
        tmp = bmp_data[index];
        bmp_data[index] = bmp_data[index+2];
        bmp_data[index+2] = tmp;
    }
    cinfo.err = jpeg_std_error(&jerr);
    jpeg_create_compress(&cinfo);
    if ((outfile = fopen(jeg_file, "wb")) == NULL) {
        fprintf(stderr, "can't open %s\n", jeg_file);
        return -1;
    }
    // 設(shè)置jpeg參數(shù)
    jpeg_stdio_dest(&cinfo, outfile);
    cinfo.image_width = bmpInfoHeader.biWidth;
    cinfo.image_height = bmpInfoHeader.biHeight;
    cinfo.input_components = 3;
    cinfo.in_color_space = JCS_RGB;
    jpeg_set_defaults(&cinfo);
    jpeg_set_quality(&cinfo, JPEG_QUALITY, TRUE);
    jpeg_start_compress(&cinfo, TRUE);
    
    /* 這里使用庫提供的cinfo.next_scanline作為循環(huán)計數(shù)器狠鸳,這樣我們不需要自己來追蹤揣苏。
     * 如果下一行小于圖片的高度則繼續(xù)循環(huán)
     */
    row_stride = bmpInfoHeader.biWidth * depth;
    while (cinfo.next_scanline < bmpInfoHeader.biHeight) {
        // 取當前行最后一位數(shù)據(jù)的地址
        row_pointer[0] =  & bmp_data[cinfo.next_scanline * row_stride];
        // 寫入jpg格式數(shù)據(jù)
        (void) jpeg_write_scanlines(&cinfo, row_pointer, 1);
    }
    // 完成轉(zhuǎn)換,釋放對象空間件舵,關(guān)閉文件
    jpeg_finish_compress(&cinfo);
    jpeg_destroy_compress(&cinfo);
    free(bmp_data);
    fclose(outfile);
    return 0;
}

總結(jié)之卸察,目前整個工程下有3個文件,分別是main.c铅祸,bmp2jpg.c和bmp2jpg.h坑质。bmp2jpg.c有上文的兩個函數(shù)readBmp和bmp2jpg,main.c也很簡單只是調(diào)用了一下bmp2jpg這個函數(shù)临梗。比較復(fù)雜的是bmp2jpg.h涡扼,這里定義了一些結(jié)構(gòu)體,暴露了一個函數(shù)盟庞。下面給出代碼:

//main.c
#include <stdio.h>
#include "bmp2jpg.h"
int main() {
    bmp2jpg("/Users/ERIC_LAI/Desktop/bmp2jpg/bmp2jpg/image2.bmp", 
            "/Users/ERIC_LAI/Desktop/bmp2jpg/bmp2jpg/image.jpg");
    return 0;
}
//bmp2jpg.h
#include <string.h>
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include "jpeglib.h"
#include <setjmp.h>

// #define WIDTHunsigned charS(bits) (((bits)+31)/32*4)
typedef unsigned char BYTE;             //1
typedef unsigned short WORD;            //2
typedef unsigned int DWORD;             //4
typedef long LONG;                      //8

typedef struct tagBITMAPFILEHEADER {
  DWORD bfSize;                         //4
  WORD bfReserved1;                     //2
  WORD bfReserved2;                     //2
  DWORD bfOffBits;                   //4
} BITMAPFILEHEADER,tagBITMAPFILEHEADER;

typedef struct tagBITMAPINFOHEADER {
     DWORD biSize;                      //4
     DWORD biWidth;                     //4
     DWORD biHeight;                   //4
     WORD biPlanes;                     //2
     WORD biBitCount;                   //2
     DWORD biCompression;               //4
     DWORD biSizeImage;                 //4
     DWORD biXPelsPerMeter;             //4
     DWORD biYPelsPerMeter;             //4
     DWORD biClrUsed;                   //4
     DWORD biClrImportant;              //4
 } BITMAPINFOHEADER,tagBITMAPINFOHEADER;

typedef struct tagBGRA {
    BYTE rgbBlue;
    BYTE rgbGreen;
    BYTE rgbRed;
    BYTE rgbReserved;
} BGRA,tagBGRA;

int bmp2jpg(const char *bmp_file, const char *jeg_file);
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末吃沪,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子什猖,更是在濱河造成了極大的恐慌票彪,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,496評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件不狮,死亡現(xiàn)場離奇詭異降铸,居然都是意外死亡,警方通過查閱死者的電腦和手機摇零,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,407評論 3 392
  • 文/潘曉璐 我一進店門推掸,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人遂黍,你說我怎么就攤上這事终佛。” “怎么了雾家?”我有些...
    開封第一講書人閱讀 162,632評論 0 353
  • 文/不壞的土叔 我叫張陵铃彰,是天一觀的道長。 經(jīng)常有香客問我芯咧,道長牙捉,這世上最難降的妖魔是什么竹揍? 我笑而不...
    開封第一講書人閱讀 58,180評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮邪铲,結(jié)果婚禮上芬位,老公的妹妹穿的比我還像新娘。我一直安慰自己带到,他們只是感情好昧碉,可當我...
    茶點故事閱讀 67,198評論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著揽惹,像睡著了一般被饿。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上搪搏,一...
    開封第一講書人閱讀 51,165評論 1 299
  • 那天狭握,我揣著相機與錄音,去河邊找鬼疯溺。 笑死论颅,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的囱嫩。 我是一名探鬼主播恃疯,決...
    沈念sama閱讀 40,052評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼墨闲!你這毒婦竟也來了澡谭?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,910評論 0 274
  • 序言:老撾萬榮一對情侶失蹤损俭,失蹤者是張志新(化名)和其女友劉穎蛙奖,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體杆兵,經(jīng)...
    沈念sama閱讀 45,324評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡雁仲,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,542評論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了琐脏。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片攒砖。...
    茶點故事閱讀 39,711評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖日裙,靈堂內(nèi)的尸體忽然破棺而出吹艇,到底是詐尸還是另有隱情,我是刑警寧澤昂拂,帶...
    沈念sama閱讀 35,424評論 5 343
  • 正文 年R本政府宣布受神,位于F島的核電站,受9級特大地震影響格侯,放射性物質(zhì)發(fā)生泄漏鼻听。R本人自食惡果不足惜财著,卻給世界環(huán)境...
    茶點故事閱讀 41,017評論 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望撑碴。 院中可真熱鬧撑教,春花似錦、人聲如沸醉拓。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,668評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽亿卤。三九已至玫镐,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間怠噪,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,823評論 1 269
  • 我被黑心中介騙來泰國打工杜跷, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留傍念,地道東北人。 一個月前我還...
    沈念sama閱讀 47,722評論 2 368
  • 正文 我出身青樓葛闷,卻偏偏與公主長得像憋槐,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子淑趾,可洞房花燭夜當晚...
    茶點故事閱讀 44,611評論 2 353

推薦閱讀更多精彩內(nèi)容