[毕设记录] libjpeg将bmp格式转换成jpg格式

前言

在毕业设计里面,下位机通过OV7725和屏幕截屏,获得了一张bmp格式的图像。我希望可以将这个图片发送到上位机进行图像处理,但是由于bmp格式的图片比较大,在传输过程当中需要的时间比较长。所以,我希望可以将这个bmp图像转换成jpg格式,然后再发送。经过试验,220多k得bmp图片,转换成jpg格式后只有20多k(注意,这不是压缩比,要根据具体的图片来确定,图片越单一压缩比越高)。

获得源码

通过资料搜集,发现一个开源库libjpeg可以用来实现这个功能,这个库的主要作者是LJG,通过上面的两个连接你都可以获得源码。我采用的后面的LJG的连接的源码,下载对应机器的版本,然后编译之。

编译源码

编译源码也是一个技术活,我的环境是Mac OS X EI 10.11(后文的C集成开发环境是Xcode 6)。

在Mac下编译源码的话比较简单,将下载的压缩包解压,打开命令行,cd进入目录,执行命令:

./configure --prefix=“你希望编译后文件的绝对路径”(不要带引号)

然后就执行make和make install命令,你就会在你规定的路径下看到编译出来的文件。

BMP格式

bmp格式,是window的标准位图格式。详细介绍,百度百科请点击在这里。下面是简要介绍,典型的BMP图像文件由四部分组成:

  1. 位图头文件数据结构,它包含BMP图像文件的类型、显示内容等信息;
  2. 位图信息数据结构,它包含有BMP图像的宽、高、压缩方法,以及定义颜色等信息;
  3. 调色板,这个部分是可选的,有些位图需要调色板,有些位图,比如真彩色图(24位的BMP)就不需要调色板 (注意,本文仅仅处理24位的bmp,非24位的用后面的算法是行不通的!);
  4. 位图数据,这部分的内容根据BMP位图使用的位数不同而不同,在24位图中直接使用RGB,而其他的小于24位的使用调色板中颜色索引值。

读取BMP图片算法

既然,我们要讲bmp图片转换,那第一步首先是要将bmp图片读到内存当中才可以进行处理。下面,叙述bmp的读取方法。

读取BMP头文件数据

根据上面的知识,我们先尝试以下读取BMP的头文件数据。由资料可知,BMP头文件具体包括以下内容:

  • 位图文件类型 【1,2】字节
  • 位图文件大小(单位:字节)【3,6】字节
  • 保留字1 【7,8】字节
  • 保留字2 【9,10】字节
  • 位图数据偏移量(单位:字节)【11,14】字节

根据以上的内容,这些信息都是按照顺序存放的。不难理解出,我们的处理方法,应该是定义一个结构体来存放它们。先看一下目录结构,在一个文件夹(我命名为c_test)下新建三个文件:bmp2jpg.h,bmp2jpg.c和main.c。

下面是结构体定义的代码:

//以下代码放在bmp2jpg.h文件里面
//关于其中DWORD WORD这些变量的说明如下:
//请自己根据自己使用的系统来确定 WORD是两个字节,DWORD是4个字节
typedef struct tagBITMAPFILEHEADER {
  DWORD bfSize;
  WORD bfReserved1; 
  WORD bfReserved2; 
  DWORD bfOffBits;
} BITMAPFILEHEADER,tagBITMAPFILEHEADER;

细心的读者肯定会发现,上面定义的结构体怎么少了位图文件类型一项。这里作出解释:一般来说,会在程序开始先读取并判断前面2个字节(即位图文件类型)是否为BM(若是,说明这儿文件是bmp格式。否则,就不再执行后面的处理了),所以结构体里面没有定义。另外,编译器默认是4字节对齐的(一次读取4个字节),直接添加这个会导致后面的数据读取错误

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

// 头文件结构体
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("实际位图数据的偏移字节数:%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;
    }
    // 读取文件前两个字节判断是否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以二进制的形式打开图片文件,然后读取一定的字节数([2,14]字节,共12字节),放到结构体变量当中,再通过showBmpHead将它们显示出来。主函数那里调用一下readBmpFileHeader()并把图片的路径以字符串的形式传进去即可。主函数如下:

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

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

结果如下所示:

bmp文件大小:230454
保留字1:0
保留字2:0
实际位图数据的偏移字节数:54

应用同样的道理,我们可以把第二部分的信息也获取出来并保存到一个结构体上,这里不一一赘述。

读取BMP图片数据

读取的方法很简单,定义一个容器,接着上面的操作把剩下的数据读取进容器里面就可以了。这里叙述的是在pc上的读取,在stm32等内存有限的设备上不能使用这种方法(后文会叙述)。

// 这里将函数写成内部方法,可以根据需要自行调整
// show?的方法是调试用的,这里注释掉了
// 读取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;
    }
    // 读取文件前两个字节判断是否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;
        }
        // 读取位图数据
        data_size = bmpInfoHeader.biWidth * bmpInfoHeader.biHeight * depth;
        //动态分配内存,记得需要释放(这里在调用这个函数的函数里面释放,下文可以看到)
        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;

由于本文只研究真彩色的图(毕业设计时间有限,以后可能会研究对其他类型图片的操作),所以对于这种bmp图片,剩下的部分就是具体的图片数据编码了。在真彩色的bmp格式的图片当中,每一个像素用三个数据来表示,分别是BGR(blue green red)。因为在第二部分,我们获取了图片的宽度和高度。通过这两个参数,我们便可以知道整个图片的排列规则,以及应该怎么显示它们了。

下面,就是正菜了!演示如何使用jpeglib库将bmp图片转换成jpg格式图片,使用之前需要注意,将编译的时候生成的lib和include文件夹添加到当前工程的目录下,否则会报错找不到某些函数,转换函数代码如下:

// 这个函数对外暴露,输入两个参数,分别是待转换的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; /* 行跨度(图像中一行需要多少个字节来表示) */
    char tmp = '0';
    int index = 0;
    // 读取bmp图像
    if (readBmpFile(bmp_file) == -1) return -1;
    // 将BGR编码方式转换成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;
    }
    // 设置jpeg参数
    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作为循环计数器,这样我们不需要自己来追踪。
     * 如果下一行小于图片的高度则继续循环
     */
    row_stride = bmpInfoHeader.biWidth * depth;
    while (cinfo.next_scanline < bmpInfoHeader.biHeight) {
        // 取当前行最后一位数据的地址
        row_pointer[0] =  & bmp_data[cinfo.next_scanline * row_stride];
        // 写入jpg格式数据
        (void) jpeg_write_scanlines(&cinfo, row_pointer, 1);
    }
    // 完成转换,释放对象空间,关闭文件
    jpeg_finish_compress(&cinfo);
    jpeg_destroy_compress(&cinfo);
    free(bmp_data);
    fclose(outfile);
    return 0;
}

总结之,目前整个工程下有3个文件,分别是main.c,bmp2jpg.c和bmp2jpg.h。bmp2jpg.c有上文的两个函数readBmp和bmp2jpg,main.c也很简单只是调用了一下bmp2jpg这个函数。比较复杂的是bmp2jpg.h,这里定义了一些结构体,暴露了一个函数。下面给出代码:

//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);
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,332评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,508评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,812评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,607评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,728评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,919评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,071评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,802评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,256评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,576评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,712评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,389评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,032评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,798评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,026评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,473评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,606评论 2 350

推荐阅读更多精彩内容