Saturday, May 9, 2009

Using Jpeglib to Convert BMP to Jpeg

繼上一篇Using Jpeglib to Convert JPEG to BMP之後,我們想利用Jpeglib來將BMP轉成Jpeg。轉換格式其實最最重要的就是要讀對標頭檔(Header)也要填對它,所以之前所提的BMP Header是很重要的一環,在之前的文章也提到過了

利用jpeglib做jpeg壓縮的流程大致上與解壓縮是一樣的,重要的是要填對jpeg compression parameters:

int BmptoJpeg(char *input, char *output)
{
struct jpeg_compress_struct cinfo;
struct jpeg_error_mgr jerr;
FILE *out;
unsigned char *line, *buffer;
int row_width;
int i, j;

if ( (out = fopen(output,"wb")) == NULL ) {
printf("BmptoJpeg: ...can't open %s\n", output);
return FALSE;
}

cinfo.err = jpeg_std_error(&jerr);
jpeg_create_compress(&cinfo);

jpeg_set_defaults(&cinfo);
jpeg_stdio_dest(&cinfo, out);
jpeg_set_quality(&cinfo, 75, TRUE);
jpeg_start_compress(&cinfo, TRUE);

/* set row width and calulate padding data*/
row_width = cinfo.image_width*cinfo.input_components;
/* !!!assume the input source store in buffer!!! */
line = buffer;

/* output to jpeg file */
for (i = 0; i < cinfo.image_height; i++, line+=row_width)
jpeg_write_scanlines(&cinfo, &line, 1);

jpeg_finish_compress(&cinfo);
jpeg_destroy_compress(&cinfo);

fclose(in);
fclose(out);

return TRUE;
}


上列的code大致上就是整個jpeglib產生jpeg的流程,要特別注意的是buffer,在上面這個例子是假定buffer是存著圖的RGB資料(RAW Data)。了解整個產生jpeg流程之後,我們要來著手在bmp header的處理部份,之前提過的bmp header架構這裡也就不再提了。這邊要做的就是把bmp header裡的寬、高等資訊讀出,並且設定到jpeg compression的parameters:


int getBmpHeader(bmp_header *header, struct jpeg_compress_struct *info, FILE *in)
{
fread(header, 1, sizeof(bmp_header), in);

if (header->bmpType != 0x4d42 ) {
printf("getBmpHeader: ...it's not a BMP file\n");
return FALSE;
}

if (header->bmpCompress != 0 || header->bmpBpp != 24) {
printf("getBmpHeader: ...doesn't support compressed BMP format\n");
return FALSE;
}

info->image_width = header->bmpWidth;
info->image_height = (header->bmpHeight < 0)?
0-header->bmpHeight:header->bmpHeight;
info->input_components = header->bmpBpp/8;
info->in_color_space = JCS_RGB;

return TRUE;
}


設定之後其實也還有一項很重要的方不要忘了,就是bmp裡圖檔資料的格式是BGRBGR,而我們要輸入給jpeglib的資料是RGBRGB,所以color format轉換又要出現了:


int convertColor(unsigned char *color, int width)
{
int i, ptr;
char tmp;

for(i = 0, ptr=0; i < width; i++, ptr=i*3) {
tmp = color[ptr];
color[ptr] = color[ptr+2];
color[ptr+2] = tmp;
}
}


這些都做完之後,我們就可以將jpeglib產生jpeg的流程做結合,做結合要注意的地方就是jpeglib產生jpeg時是採用一個line一個line做讀取,我們使用的做法是先將bmp的圖檔資料讀到memory中,再從memory一行一行的取出並做color format轉換再寫入到jpeg檔。另外還有一點要注意就是bmp是要對齊4bytes/row,所以我們也要試先算出多餘的資料,在每個line做累加的時候把這些多餘的資料捨去:


int BmptoJpeg(char *input, char *output)
{
struct jpeg_compress_struct cinfo;
struct jpeg_error_mgr jerr;
bmp_header binfo;
FILE *in, *out;
unsigned char *line, *buffer;
int row_width;
int i, j;

if ( (in = fopen(input,"rb")) == NULL ) {
printf("BmptoJpeg: ...can't open %s\n", input);
return FALSE;
}

if ( (out = fopen(output,"wb")) == NULL ) {
printf("BmptoJpeg: ...can't open %s\n", output);
return FALSE;
}

cinfo.err = jpeg_std_error(&jerr);
jpeg_create_compress(&cinfo);

/* get bmp header info and set jpeg
compression parameters */
if (getBmpHeader(&binfo, &cinfo, in) == FALSE)
return FALSE;

jpeg_set_defaults(&cinfo);
jpeg_stdio_dest(&cinfo, out);
jpeg_set_quality(&cinfo, 75, TRUE);
jpeg_start_compress(&cinfo, TRUE);

/* set row width and calulate padding data*/
row_width = cinfo.image_width*cinfo.input_components
+ cinfo.image_width%4;
/* memory allocate for input buffer */
buffer = (unsigned char *)malloc(cinfo.image_width*cinfo.image_height*cinfo.input_components);
/* input from bmp file */
fread(buffer, 1, cinfo.image_width*
cinfo.image_height*cinfo.input_components, in);
line = buffer;

/* output to jpeg file */
for (i = 0; i < cinfo.image_height; i++, line+=row_width) {
convertColor(line, cinfo.image_width);
jpeg_write_scanlines(&cinfo, &line, 1);
}

jpeg_finish_compress(&cinfo);
jpeg_destroy_compress(&cinfo);

fclose(in);
fclose(out);

free(buffer);

return TRUE;
}


上列的code其實還不算完整,這是對於沒有做壓縮(且沒有platte)的BMP是有效的,加上這個code並未做倒置的動作(BMP的圖檔資料是從左下到右上的順序做讀取),所以這個code還不算完整

Reference:
Wiki: BMP file format
IJG jpeglib

No comments:

Post a Comment