gxLib를 사용하면서 제일 아쉬운 점은 Bitmap파일만 출력할 수 있다는 점이었습니다. 물론 이 보다 더 아쉬운 점은 많지만, Bitmap만 사용할 수 있다는 것은 단점일 수 밖에 없습니다. 왜냐하면 일단 Bitmap 파일은 큽니다. 좀 화려하게 꾸민다 치면 Bitmap 파일 용량만으로도 장난 아니게 됩니다.

그래서 다른 이미지 형식을 알아 보게되었는데, PNG를 제일 먼저 선택하게 되었습니다. 이유는 GIF보다 파일 크기가 클 수 있지만 256칼라 이상으로 자유로운 색상을 이용할 수 있고, JPG 보다도 압축률이 낮기는 하지만 화질 왜곡이 없어 깔끔하게 이미지 출력할 수 있어 좋습니다. 또한 각 픽셀 별로 알파 값을 가지고 있어서 단순히 투명 효과뿐만 아니라 반투명 효과를 낼 수 있습니다.

그래서 방법을 찾아 보았는데, 이게 쉽지 않더만요. 라이브러리의 상위 디렉토리에 있는 샘플 소스를 가지고 끙끙거렸지만 답답하게 알지 못하다가 우연하게 다른 디렉토리에서 방법을 알게 되어, 우선 PNG 파일을 읽어 들이고 화면에 출력하는 방법을 정리하여 올립니다.

그러나 저도 다른 사람의 소스를 보는데 이해 능력이 떨어지고, PNG 특성을 자세히 알지 못하다 보니까, 화면 출력에 성공해 놓고도 모르는 부분이 많습니다. 그러므로 다른 분의 도움이 절실히 필요합니다. 아래의 분석 내용에 대해 작은 도움이라도 좋으니, 또 다른 분석이나 이해나 도움 내용이 있다면 말씀을 부탁드립니다.

PNG 이미지 출력 샘플 정리

소 제목을 "PNG 이미지 샘플 정리"라고 말씀드렸습니다. 샘플 정리라고 말씀드린 이유는 PNG 라이브러리에 포함된 샘플을 이해하기 쉽게 정리했다는 뜻으로, 원래 샘플은 몇 개의 파일로 나뉘어 있어서, 분석하려면 이쪽 저쪽 왔다갔다해야 합니다. 또, PNG 라이브러리가 유닉스나 리눅스 전용으로 만들어 진 것이 아니라 Apple 의 MAC이나 Microsoft 의 Windows에서도 사용할 수 있도록 제작되어 있다 보니까, 다른 플랫폼을 위한 코드 함께 작성되어 있어서 복잡해 보입니다. 그래서 아래의 소스는 리눅스에서 PNG를 출력하는 방법을 main()함수 하나에 모두 모았습니다.

혹, PNG 라이브러리가 설치하지 않의신 분이 계실지 모르겠습니다만, PC 뿐만 아니라 ARM보드와 MIPS보드를 위한 PNG 라이브러를 설치하는 방법을 하단에 설명해 놓았습니다. 그 글을 참고하여 설치하시면 되겠습니다.

PNG 라이브러리는 아래의 페이지에서 구했습니다.

http://www.libpng.org
http://www.libpng.org/pub/png/libpng.html

제가 구한 PNG 라이브러리는 이 페이지에서 libpng-1.2.33.tar.gz 로, 제가 분석한 파일은 contrib\gregbook에 있는 샘플 파일이며, 그 중에 rpng-x.creadpng.c 입니다. 다른 소스를 이용하면 png 이미지를 파일로 저장하는 것도 가능하리라 생각됩니다. 앞서 말씀드린바와 같이 rpng-x.creadpng.c 파일을 하나로 합치면서 필요한 내용을 모아서, 하나의 main() 함수를 만들었습니다.

다운로드gxlib_0.5.6.tar.gz         3MB
#include    <stdio.h>
#include    <stdlib.h>
#include    <time.h>
#include    <unistd.h>                                                           // sleep
#include    <gx.h>                                                               // 기본 그래픽 라이브러리
#include    <gxbmp.h>
#include    <gxbdf.h>                                                            // 문자 출력 라이브러리
#include    <png.h>

#define     ERROR    -1

typedef unsigned char   uch;
typedef unsigned short  ush;
typedef unsigned int    uln;
typedef unsigned long   ulg;

int   main( void)
{
   dc_t           *dc;

   FILE          *infile;
   uch            sig[8];
   png_structp    png_ptr = NULL;
   png_infop      info_ptr = NULL;

   png_uint_32    ihdr_width, ihdr_height;
   int            width, height;
   int            bit_depth, color_type;
   png_color_16p  pBackground;

   uch            bg_red =0, bg_green=0, bg_blue=0;
   uch           *image_data = NULL;
   int            image_channels, image_rowbytes;
   double         display_exponent  = 1.0 * 2.2;

   double         gamma;
   png_uint_32    rowbytes;
   png_bytepp     row_pointers = NULL;
   int            i;

   //////////////////////////////////////////////////////////////////////////////

   gx_init( "/dev/fb");
   dc = gx_get_screen_dc();
   gx_clear( dc, gx_color( dc, 0, 0, 0));
   dc->pen_color = gx_color( dc, 255, 255, 255);
   gx_line( dc, 0, 0, dc->width, dc->height);

   //////////////////////////////////////////////////////////////////////////////

   infile = fopen( "pngout.png", "rb");
   fread(sig, 1, 8, infile);
   if ( !png_check_sig(sig, 8))  printf( "bad signature\n");
   else
   {
      png_ptr = png_create_read_struct( PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); // 사용자 에러 정의 핸들러를 모두 NULL로 설정
      if ( !png_ptr)    printf( "out of memory\n");
      else
      {
         info_ptr = png_create_info_struct( png_ptr);
         if (!png_ptr)     printf( "out of memory\n");
         else
         {
            if ( setjmp( png_jmpbuf(png_ptr)))
            {
               png_destroy_read_struct(&png_ptr, &info_ptr, NULL);
               printf( "for bad IHDR");
            }
            else
            {
               png_init_io( png_ptr, infile);
               png_set_sig_bytes( png_ptr, 8);                                   // we already read the 8 signature bytes
               png_read_info( png_ptr, info_ptr);                                // read all PNG info up to image data

               /* alternatively, could make separate calls to png_get_image_width(),
                * etc., but want bit_depth and color_type for later [don't care about
                * compression_type and filter_type => NULLs] */

               png_get_IHDR(png_ptr, info_ptr, &ihdr_width, &ihdr_height, &bit_depth, &color_type, NULL, NULL, NULL);
               width    = ihdr_width;
               height   = ihdr_height;
               printf( "width     = %u\n", width      );
               printf( "height    = %u\n", height     );
               printf( "bit depth = %d\n", bit_depth  );
               printf( "color type= %d\n", color_type );

               //백그라운 칼라를 구한다. 또는 사용자가 직접 지정할 수 도 있지만 여기서는 제외

               if ( setjmp(png_jmpbuf( png_ptr))) {
                   png_destroy_read_struct(&png_ptr, &info_ptr, NULL);
                   printf( "libpng error\n");
               }
               else
               {
/*                  if ( !png_get_valid( png_ptr, info_ptr, PNG_INFO_bKGD))
 *                  {
 *                     printf( "fails due to no bKGD chunk\n");
 A                     png_destroy_read_struct(&png_ptr, &info_ptr, NULL);
 *                  }
 *                else
 */
                  {
/*** B ***/          png_get_bKGD(png_ptr, info_ptr, &pBackground);

                      /* however, it always returns the raw bKGD data, regardless of any
                       * bit-depth transformations, so check depth and adjust if necessary */

                      if ( bit_depth == 16)
                      {
                          bg_red   = pBackground->red   >> 8;
                          bg_green = pBackground->green >> 8;
                          bg_blue  = pBackground->blue  >> 8;
                      }
                      else if (color_type == PNG_COLOR_TYPE_GRAY && bit_depth < 8)
                      {
                          if (bit_depth == 1)
                              bg_red = bg_green = bg_blue = pBackground->gray? 255 : 0;
                          else if (bit_depth == 2)
                              bg_red = bg_green = bg_blue = (255/3) * pBackground->gray;
                          else /* bit_depth == 4 */
                              bg_red = bg_green = bg_blue = (255/15) * pBackground->gray;
                      }
                      else
                      {
                          bg_red   = (uch)pBackground->red;
                          bg_green = (uch)pBackground->green;
                          bg_blue  = (uch)pBackground->blue;
                      }

                     // 이제 이미지 데이터를 읽어 들인다.

                     /* setjmp() must be called in every function that calls a PNG-reading
                      * libpng function */

                     if (setjmp(png_jmpbuf(png_ptr)))
                     {
                         png_destroy_read_struct( &png_ptr, &info_ptr, NULL);
                         printf( "ERROR: setjmp(png_jmpbuf(png_ptr)\n");
                     }
                     else
                     {
                        /* expand palette images to RGB, low-bit-depth grayscale images to 8 bits,
                         * transparency chunks to full alpha channel; strip 16-bit-per-sample
                         * images to 8 bits per sample; and convert grayscale to RGB[A] */

                        if (color_type == PNG_COLOR_TYPE_PALETTE)
                            png_set_expand(png_ptr);
                        if (color_type == PNG_COLOR_TYPE_GRAY && bit_depth < 8)
                            png_set_expand(png_ptr);
                        if (png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS))
                            png_set_expand(png_ptr);
                        if (bit_depth == 16)
                            png_set_strip_16(png_ptr);
                        if (color_type == PNG_COLOR_TYPE_GRAY ¦¦
                            color_type == PNG_COLOR_TYPE_GRAY_ALPHA)
                            png_set_gray_to_rgb(png_ptr);


                        /* unlike the example in the libpng documentation, we have *no* idea where
                         * this file may have come from--so if it doesn't have a file gamma, don't
                         * do any correction ("do no harm") */

                        if (png_get_gAMA(png_ptr, info_ptr, &gamma))
                            png_set_gamma(png_ptr, display_exponent, gamma);


                        /* all transformations have been registered; now update info_ptr data,
                         * get rowbytes and channels, and allocate image memory */

                        png_read_update_info(png_ptr, info_ptr);

                        image_rowbytes = rowbytes = png_get_rowbytes(png_ptr, info_ptr);
                        image_channels = (int)png_get_channels(png_ptr, info_ptr);

                        if ( (image_data = (uch *)malloc(rowbytes*height)) == NULL)
                        {
                            png_destroy_read_struct(&png_ptr, &info_ptr, NULL);
                            printf( "error: image_data = malloc(rowbytes*height)\n");
                        }
                        else
                        {
                           if ( (row_pointers = (png_bytepp)malloc(height*sizeof( png_bytep))) == NULL)
                           {
                               png_destroy_read_struct(&png_ptr, &info_ptr, NULL);
                               free(image_data);
                               printf( "error: row_pointer = malloc(height*sizeof(png_bytep)\n");
                           }
                           else
                           {
                              printf( "readpng_get_image:\n");
                              printf( "  channels = %d\n" , image_channels);
                              printf( "  rowbytes = %ld\n", rowbytes);
                              printf( "  height   = %d\n" , height  );

                              /* set the individual row_pointers to point at the correct offsets */

                              for (i = 0;  i < height; i++)
                                  row_pointers[i] = image_data + i*rowbytes;

                              /* now we can go ahead and just read the whole image */

                              png_read_image( png_ptr, row_pointers);

                              /* and we're done!  (png_read_end() can be omitted if no processing of
                               * post-IDAT text/time/etc. is desired) */

                              free( row_pointers);
                              row_pointers = NULL;

                              png_read_end( png_ptr, NULL);

                              // 드디어 화면 출력!!

                              {
                                 ulg red, green, blue;
                                 ulg r, g, b, a;
                                 int      row;
                                 int      clrPixel;
                                 uch     *src;

                                 for ( row = 0;  row < height;  ++row)
                                 {
                                    src   = image_data + row*image_rowbytes;
                                    if ( image_channels == 3)
                                    {
                                       for (i = 0; i < width; i++)
                                       {
                                          red   = *src++;
                                          green = *src++;
                                          blue  = *src++;
                                          clrPixel = gx_color( dc, red, green, blue);
                                          gx_set_pixel( dc, i, row, clrPixel);
                                       }
                                    }
                                    else /* if (image_channels == 4) */
                                    {
                                       for (i = 0; i < width; i++)
                                       {
                                           r = *src++;
                                           g = *src++;
                                           b = *src++;
                                           a = *src++;
                                           if (a == 255)
                                           {
                                               red   = r;
                                               green = g;
                                               blue  = b;
                                                clrPixel = gx_color( dc, red, green, blue);
                                                gx_set_pixel( dc, i, row, clrPixel);
                                           } else if (a == 0) {                  // 투명인 부분
/*                                              red   = bg_red;
 B                                              green = bg_green;
 *                                              blue  = bg_blue;
 */
                                           } else {
/*                                               /  * this macro (from png.h) composites the foreground
 *                                                  * and background values and puts the result into the
 *                                                  * first argument *  /
 C                                               alpha_composite(red,   r, a, bg_red);
 *                                               alpha_composite(green, g, a, bg_green);
 *                                               alpha_composite(blue,  b, a, bg_blue);
 */
                                           }
                                       }
                                    }
                                 }
                              }
                              // 화면 출력 끝
                              png_destroy_read_struct(&png_ptr, &info_ptr, NULL);
                              free(image_data);
                           }
                        }
                     }
                  }
               }
            }
         }
      }
   }

   /* close the file */
   fclose(infile);

   gx_release_dc( dc);                                                           // dc 반환
   gx_close();                                                                   // 그래픽 라이브러리 사용 종료

   return   0;
}

컴파일 후 실행하면 PNG 파일이 투명 처리까지 되면서 그림이 출력되는 것을 보실 수 있습니다.

(A)로 주석 처리한 부분은 이상한 부분이 있어서 주석 처리했습니다. PNG 라이브러리와 함께 포함된 PNG 파일은 에러가 발생하지 않는데, 제가 직접 만든 PNG파일에서는 에러가 발생하네요.

/*                  if ( !png_get_valid( png_ptr, info_ptr, PNG_INFO_bKGD))
 *                  {
 *                     printf( "fails due to no bKGD chunk\n");
 A                     png_destroy_read_struct(&png_ptr, &info_ptr, NULL);
 *                  }
 *                else
 */

더 이상한 것은 png_get_valid()에서 에러가 발생했는데, (B)로 표시한 png_get_bKGD() 함수는 이상없이 실행됩니다. 왜 (A)에서는 에러가 발생하는지 더 알아 봐야 겠습니다.

/*** B ***/          png_get_bKGD(png_ptr, info_ptr, &pBackground);

(C) 부분은 PNG 이미지 중에 투명한 부분을 처리하는 루틴입니다. 샘플에서는 주석 처리를 해서 아무런 작업을 하지 않기 때문에 화면에 아무것도 그려지지 않습니다.

                                           } else if (a == 0) {                  // 투명인 부분
/*                                              red   = bg_red;
 C                                              green = bg_green;
 *                                              blue  = bg_blue;
 */
                                           } else {

(D) 부분은 알파값을 처리하는 것으로 화면과 PNG 이미지의 색상을 알파 값으로 계산하면 반투명한 색상을 구할 수 있고, 그 색상으로 화면을 갱신하면 알파 브렌딩(blending) 효과로 출력할 수 있을 것입니다. 이번 분석에서는 역시 주석 처리했습니다. 다른 소스에서 alpha_composite()의 내용을 찾아서 분석하면 될 것으로 생각됩니다.

                                           } else {
/*                                               /  * this macro (from png.h) composites the foreground
 *                                                  * and background values and puts the result into the
 *                                                  * first argument *  /
 D                                               alpha_composite(red,   r, a, bg_red);
 *                                               alpha_composite(green, g, a, bg_green);
 *                                               alpha_composite(blue,  b, a, bg_blue);
 */

PNG 라이브러리 설치하기

PNG 이미지는 압축해서 저장하는 이미지이기 때문에 zlib가 필요하고, 먼저 설치해야 합니다.

http://www.zlib.net

이 글을 작성하고 있는 시점에서는 zlib 1.2.3이 릴리즈되었습니다. zlib를 아래와 같이 설치합니다. 설치 예는 mips 칩 보드를 위한 방법으로, 설치 위치를 /usr/mipsel-linux 에 설치하겠습니다. 만일 i386이나 arm일 경우에는 아래의 디렉토리를 선택했습니다.

사용 대상 설치 위치
i386 /usr
arm /usr/mipsel-linux
mips /usr/mipsel-arm

이렇게 설치 위치를 먼저 말씀드리는 것은, 나중에 프로그램을 컴파일할 때, 자동으로 라이브러리가 검색되고 링크를 해서 실행파일을 만들기 위함입니다. 물론 -L 옵션을 사용해도 되지만, 크로스 컴파일러를 함께 사용해야 하기 때문에, 각 컴파일러가 기본으로 검색하는 디렉토리에 설치하는 것이 편합니다. 매번 -L 옵션을 변경할 수 없지 않겠습니까? ^^

이제 설치 위치가 결정되었으니 설치하겠습니다.

  1. ]# tar zxvf zlib-1.2.3.tar.tar
  2. ]# cd zlib-1.2.3
  3. ]# CC=mipsel-linux-gcc AR="mipsel-linux-ar rc" RANLIB=mipsel-linux-ranlib ./configure --prefix=/usr/mipsel-linux
  4. ]# make
  5. ]# make install

에러 없이 설치되었다면 성공입니다. 이번에는 PNG 라이브러리입니다. 역시 같은 위치에 설치하겠습니다.

  1. ]# tar zxvf libpng-1.2.32.tar.gz
  2. ]# cd libpng-1.2.32
  3. ]# cp scripts/makefile.linux Makefile
  4. ]# vi Makefile

    // Makefile을 아래와 같이 타겟보드에 맞추어 컴파일러를 변경합니다.
    // PC에서 사용한다면 변경할 필요가 없습니다.

    -> AR_RC=mipsel-linux-ar rc
    -> CC=mipsel-linux-gcc
    -> RANLIB=mipsel-linux-ranlib
    -> prefix=/usr/mipsel-linux
    -> ZLIBLIB=/usr/mipsel-linux/lib
    -> ZLIBLIB=/usr/mipsel-linux/include
  5. ]# make
  6. ]# make install

역시 에러 없이 설치되었다면 성공입니다. 여기서 한가지 더 해주면 준비가 완료됩니다. 바로 PNG 라이브러리 libpng12.so 파일을 타겟보드에서 사용할 수 있도록 타겟보드의 라이브러리 디렉토리에 복사해야 합니다.

  1. ]# cd /usr/mipsel-linux/lib
  2. ]# $ ls -al | grep libpng12.so
    lrwxrwxrwx 1 root root 13 11월 4 19:21 libpng12.so -> libpng12.so.0
    lrwxrwxrwx 1 root root 20 11월 4 19:21 libpng12.so.0 -> libpng12.so.0.1.2.32
    -rwxr-xr-x 1 root root 288001 11월 4 19:21 libpng12.so.0.1.2.32

위에 출력된 libpng12.so.0.1.2.32 파일과 심볼 링크를 똑 같은 모습으로 타겟보드의 라이브러리 디렉토리에 복사하고 링크를 생성하시면 됩니다. 참고로 FALINUX의 EZ-AU1200 의 라이브러리 디렉토리 위치는 /lib 또는 /usr/mipsel-linux/lib 입니다. 여기로 libpng12.so.0.1.2.32 파일을 복사하고 링크를 생성하시면 됩니다.

이상으로 제가 분석한 PNG를 출력하는 방법을 말씀드렸습니다만, setjmp( png_jmpbuf(png_ptr) 은 도대체 왜 사용하는지 모르고 있습니다. 아직 자세하게 이해하지 못해서 한번 더 분석하는 기회를 갖으려 합니다만, 관심있어하시는 다른 분의 도움을 부탁드립니다. 작은 도움이라도 괜찮으니 말씀을 올려 주시면 큰 도움이 되겠습니다. 많은 관심과 도움을 부탁드립니다. ^^