Friday, February 11, 2011

 

Using jpeglib on Windows (윈도우에서 jpeglib 사용하기)

오픈 소스 jpeglib를 받아서 VIsual C++로 컴파일하는 것은 크게 어렵지 않습니다.
대부분의 경우, 소스 다운로드 받고, 프로젝트를 생성한 후, 프로젝트에 파일을 추가하고
컴파일하면 잘 돌아갑니다.

하지만, 경우에 따라서 몇가지 문제가 발생할 수 있는데, 제가 경험했던 문제들과
그 해결책을 여기에 기록으로 남깁니다.

참고로 jpeglib는 ImageMagick-windows Package에 포함되어 있는 것을
사용했습니다. 해당 Package에 VC++에서 컴파일되는 jpeglib가 만들어져 있습니다.

1. undefined refernce to "_imp_jpeg_xxxx" error
일반적으로 이 문제는 Project간 Dependency가 제대로 잡혀 있지 않을 때 나옵니다.
Linking 과정에서 함수가 포함된 Lib를 못찾는 경우죠.

하지만 제가 만난 경우는 C와 C++간 호환성 문제였습니다.
jpeglib는 C 코드로 컴파일되었고, 이 Lib를 사용하는 Windows Application은 C++ 코드로
작성되었기 때문이죠. 그런데, jpeglib 내부를 살펴보면 이 문제가 고려되어 있습니다.
C++로 컴파일 할 때에는 jpeglib.h 헤더 전체를 extern "C" { ... } 구문으로 감싸게 하는거죠.

#ifdef __cplusplus
#ifndef DONT_USE_EXTERN_C
extern "C" {
#endif
#endif

...
매크로, 데이터 타입, 함수 선언 등등.
...

#ifdef __cplusplus
#ifndef DONT_USE_EXTERN_C
}
#endif
#endif

따라서 DONT_USE_EXTERC_C를 정의하지 않은 한 문제가 발생해서는 안되었습니다.
답답해서 Windows Application C++ 파일에 extern "C" jpeg_xxx(..) 형태로 함수를
선언해봤습니다. 이러면 제대로 컴파일이 되더군요. 라이브러리가 제대로 생성되지 않은 것은
아니었고, 옵션에 문제가 있는 거였죠.

더 추적을 해보니, Windows에서는 컴파일하기 위해서 몇가지 옵션이 필요했습니다.

#define _VISUALC_
#define NeedFunctionPrototypes
#define _LIB
#define _JPEGLIB_

저는 이 옵션들을 Visual C++ Project Properties의 Preprocessr Field에 정의해 사용했습니다.
그래서 혹시 이 부분이 문제인가 싶어서, Preprocessor Field에서 해당 옵션을 제거하고,
jpeglib.h 첫부분에 매크로 정의를 추가하였습니다.

#ifndef JPEGLIB_H
#define JPEGLIB_H

#define _VISUALC_
#define NeedFunctionPrototypes
#define _LIB
#define _JPEGLIB_
...

이렇게 하니 제대로 컴파일이 되더군요.
Visual Studio에서 Preprocessor Macro를 처리하는 방식의 문제인지, 아니면
소스코드 구성의 문제인지는 모르겠으나 재미있는 경헙이었습니다.

2. INT32 compile error
jpeglib를 Win32 환경에서 C++ Application과 같이 컴파일하면 INT32라는 이름이
재정의되었다고 불평합니다.

윈도우에서 제공하는 에는 typedef signed int INT32로 정의가 되어있고,
jpeglib에서 사용하는 jmorecfg.h 파일에는 typedef long INT32로 정의되어 있기 때문입니다.
같은 이름을 서로 다른 타입으로 정의했으니 에러가 발생할 수 밖에요.

이 문제는 jmorecfg.h에서 typedef long INT32를 typedef signed int INT32로 변경하면 없어집니다.
윈도우 환경에서는 이렇게 해도 큰 문제가 발생하지 않습니다.

3. 출력 포맷 변경하기.
jpeglib 사용법을 읽어보면, jpeglib 자체에서 이미지 출력 포맷을 지정할 수 있게 되어 있습니다.
정확히는
cInfo.out_color_space = (J_COLOR_SPACE)target_format;
으로 지정한 후, decoding 작업을 수행하면 됩니다.
(void) jpeg_start_decompress(&cInfo)
....

그런데 J_COLOR_SPACE로 지정할 수 있는 기본 출력 포맷은 몇가지 안됩니다.
그 외의 포맷은 사용자가 추가해서 넣어야 하죠.
기본 포맷들은 다음과 같습니다.

typedef enum {
JCS_UNKNOWN, /* error/unspecified */
JCS_GRAYSCALE, /* monochrome */
JCS_RGB, /* red/green/blue */
JCS_YCbCr, /* Y/Cb/Cr (also known as YUV) */
JCS_CMYK, /* C/M/Y/K */
JCS_YCCK, /* Y/Cb/Cr/K */
} J_COLOR_SPACE;

일반적인 애플리케이션의 경우, JCS_RGB를 가장 많이 사용합니다.
하지만, 실제 Application에서는 RGBA 혹은 BGRA 포맷이 필요한 경우도 많습니다.
알파를 사용하는 다른 이미지(PNG 같은)와 Blending을 하는 경우에 특히 그렇죠.

출력포맷을 추가하는 작업은 간단합니다.
위 J_COLOR_SPACE enum에 원하는 포맷의 이름을 추가하고,
jinit_color_deconverter()@jdcolor.c 함수에 변환 루틴을 추가하면 됩니다.

(그냥 jpeglib에서 JCS_RGB로 디코딩한 후, 그 이미지를 다시 RGBA 혹은 BGRA로
변환해도 됩니다. 하지만 이렇게 하면 두 번 변환 작업을 하는 것이기 때문에 성능도
저하되고, 메모리도 2배 많이 사용해야 합니다. 모바일 환경에서는 이런 조건이
불가능한 경우가 많죠. 다음 설명과 같이 디코딩 과정에서 바로 변환을 하는 것이
제일 좋습니다. )


간단하게 32 bit BGRA 포맷으로 변환하는 경우를 살펴보면 다음과 같습니다.
(Windows에서는 BGRA 포맷을 사용합니다.)

Step 1. JCS_BGRA라는 이름으로 포맷을 J_COLOR_SPACE enum에 추가합니다.

typedef enum {
JCS_UNKNOWN, /* error/unspecified */
JCS_GRAYSCALE, /* monochrome */
JCS_RGB, /* red/green/blue */
JCS_YCbCr, /* Y/Cb/Cr (also known as YUV) */
JCS_CMYK, /* C/M/Y/K */
JCS_YCCK, /* Y/Cb/Cr/K */

JCS_BGRA, /* 새로 추가한 포맷입니다. */
} J_COLOR_SPACE;

Step 2. jinit_color_deconverter()@jdcolor.c 함수에 변환 구문을 추가합니다.

GLOBAL(void)
jinit_color_deconverter( j_decompress_ptr cinfo)
{
...

switch (cinfo->out_color_space)
{
...

// 아래와 같이 JCS_BGRA인 경우의 변환 규칙을 추가합니다.
// 원래 코드에는 아래 case 문이 없습니다.
//
case JCS_BGRA:
// JCS_BGRA 출력은 4바이트로 이루어져 있으므로, BGRA_PIXELSIZE = 4
// 입니다. 이 매크로는 jmorecfg.h에 정의하면 됩니다.
//
cinfo->out_color_components = BGRA_PIXELSIZE;
if (cinfo->jpeg_color_space == JCS_YCbCr)
{
// 이미지 포맷이 JCS_YCbCr이면 ycc_bgra_convert() 함수를 이용하여
// BGRA포맷으로 변환할 것을 설정합니다.
//
cconvert->pub.color_convert = ycc_bgra_convert;
build_ycc_rgb_table(cinfo);
}
else if (cinfo->jpeg_color_space == JCS_GRAYSCALE)
{
// 이미지 포맷이 JCS_GRAYSCALE이면 gray_bgra_convert() 함수를
// 이용하여
BGRA포맷으로 변환할 것을 설정합니다.
//
cconvert->pub.color_convert = gray_bgra_convert;
}
else if (cinfo->jpeg_color_space == JCS_RGB && RGB_PIXELSIZE == 3)
{
// 이미지 포맷이 24 비트 JCS_RGB이면 rgb2bgra_convert() 함수를
// 이용하여
BGRA포맷으로 변환할 것을 설정합니다.
//
cconvert->pub.color_convert = rgb2bgra_convert;
}
else
ERREXIT(cinfo, JERR_CONVERSION_NOTIMPL);
break;
....
}

물론 ycc_bgra_convert(), gray_bgra_convert(), rgb2bgra_convert() 함수는
jpeglib에 포함되어 있지 않습니다.
기존 함수들을 참조하여 사용자가 직접 제공해야 합니다.

제가 사용한 함수들의 구현은 다음과 같습니다.
읽어보시면 직관적으로 이해가 될 겁니다.

// YCbCr to BGRA 32 bit format, cyberyanne@gmail.com, 2010/01/28.
//
METHODDEF(void)
ycc_bgra_convert (j_decompress_ptr cinfo,
JSAMPIMAGE input_buf, JDIMENSION input_row,
JSAMPARRAY output_buf, int num_rows)
{
my_cconvert_ptr cconvert = (my_cconvert_ptr) cinfo->cconvert;
register int y, cb, cr;
register JSAMPROW outptr;
register JSAMPROW inptr0, inptr1, inptr2;
register JDIMENSION col;
JDIMENSION num_cols = cinfo->output_width;
/* copy these pointers into registers if possible */
register JSAMPLE * range_limit = cinfo->sample_range_limit;
register int * Crrtab = cconvert->Cr_r_tab;
register int * Cbbtab = cconvert->Cb_b_tab;
register INT32 * Crgtab = cconvert->Cr_g_tab;
register INT32 * Cbgtab = cconvert->Cb_g_tab;
SHIFT_TEMPS

while (--num_rows >= 0) {
inptr0 = input_buf[0][input_row];
inptr1 = input_buf[1][input_row];
inptr2 = input_buf[2][input_row];
input_row++;
outptr = *output_buf++;
for (col = 0; col < num_cols; col++) {
y = GETJSAMPLE(inptr0[col]);
cb = GETJSAMPLE(inptr1[col]);
cr = GETJSAMPLE(inptr2[col]);
/* Range-limiting is essential due to noise introduced by DCT losses. */
outptr[2] = range_limit[y + Crrtab[cr]];
outptr[1] = range_limit[y +
((int) RIGHT_SHIFT(Cbgtab[cb] + Crgtab[cr],
SCALEBITS))];
outptr[0] = range_limit[y + Cbbtab[cb]];
outptr[3] = 0xff; // 출력 이미지의 ALPHA는 255(완전불투명)으로 지정한다.
outptr += BGRA_PIXELSIZE;
}
}
}

// grayscale to RGBA format. by cyberyanne@gmail.com, 2010/01/28.
//
METHODDEF(void)
gray_bgra_convert (j_decompress_ptr cinfo,
JSAMPIMAGE input_buf, JDIMENSION input_row,
JSAMPARRAY output_buf, int num_rows)
{
register JSAMPROW inptr, outptr;
register JDIMENSION col;
JDIMENSION num_cols = cinfo->output_width;

while (--num_rows >= 0) {
inptr = input_buf[0][input_row++];
outptr = *output_buf++;
for (col = 0; col < num_cols; col++)
{
/* We can dispense with GETJSAMPLE() here */
outptr[RGB_RED] = outptr[RGB_GREEN] = outptr[RGB_BLUE] = inptr[col];
outptr[3] = 0xff; // 출력 이미지의 알파값은 완전불투명(255)로 지정한다.
outptr += RGBA_PIXELSIZE;
}
}
}

// RGB to BGRA format. by cyberyanne@gmail.com, 2010/01/28.
//
METHODDEF(void)
rgb2bgra_convert (j_decompress_ptr cinfo,
JSAMPIMAGE input_buf, JDIMENSION input_row,
JSAMPARRAY output_buf, int num_rows)
{
register JSAMPROW inptr, outptr;
register JDIMENSION col;
JDIMENSION num_cols = cinfo->output_width;

while (--num_rows >= 0)
{
inptr = input_buf[0][input_row++];
outptr = *output_buf++;
for (col = 0; col < num_cols; col++)
{
/* We can dispense with GETJSAMPLE() here */
outptr[2] = inptr[RGB_RED];
outptr[1] = inptr[RGB_GREEN];
outptr[0] = inptr[RGB_BLUE];
outptr[3] = 0xff; // 출력 이미지의 알파값은 완전불투명(255)로 지정한다.

inptr += RGB_PIXELSIZE;
outptr += BGRA_PIXELSIZE;
}
}
}

jpeglib를 사용하는데 조금이나마 도움이 되었으면 좋겠습니다.
끝.

Comments: Post a Comment



<< Home

This page is powered by Blogger. Isn't yours?