Png2gpn

From KBase

Jump to: navigation, search

Contents

[edit] Introduction

[edit] 기능

png 를 읽어서 새로운 Image 형식인 gpn 으로 만드는 프로그램.

[edit] 개발/실행 환경

  • cygwin - 기본적으로 cygwin 에서 build 하고 실행했지만, gd 가 동작하는 곳이라면 어디서나 가능하다.
  • gd - 최근 공식 사이트가 http://www.boutell.com/gd/ 에서 http://www.libgd.org/ 로 바뀌었다. Image 파일을 읽기 위해 사용한 라이브러리로, 현재 png2gpn 에서는 png 만 읽고 있다.

[edit] GPN file format

[edit] NDS image format

NDS 에서는 Image data 를 표현하기 위해 8bit palette mode 나 text tile mode 등도 지원하고 많이 쓰기도 하지만, 여기서는 Extended rotaion background mode 에서 사용하기 위한 16 bit mode 를 사용하기로 한다.

[edit] GPN format

GPN 파일의 구조는 다음과 같다

 struct StImage {
   int signature;
   int version;
   short width;
   short height;
   int size;
   unsigned int data[1];
 };
signature 
이 file format 을 나타내기 위한 것으로 "gpng" (32bit LE 로 0x676E7067) 가 들어간다
version 
0 이면 16 bit, 1 이면 32 alpha 를 포함한 32bit 임
width 
그림의 width
height 
그림의 height
size 
파일 전체의 크기
data 
실제 data

[edit] 16bit format

version 의 값에 따라 16 bit 를 사용하는 경우에는 data 에 있는 값들은 pixel 당 16 bit 씩 있는 값으로, Extended rotation background 에서 사용하는 것과 같은 RGB555 에 bit15 가 켜져 있는 값이다. 이를 표현해 보면

[1][1][1][1][1][1]
[5][4][3][2][1][0][9][8][7][6][5][4][3][2][1][0]
 1 [ blue 0 ~ 31 ][green 0 ~ 31 ][ red 0 ~ 31  ]

unsigned short color = (b>>3<<10)|(g>>3<<5)|(r>>3)|(1<<15);

와 같다.

[edit] 32bit format

alpha blending 을 하기 위해 r, g, b, a 의 값을 저장하는데 연산을 빠르게 하기 위하여 미리 shift 해 둔 값을 저장한다. 실제로는 RGB 15bit + alpha 6bit 밖에 사용하지 않지만, 빠른 연산을 위해 메모리를 희생한다. image 는 .o 파일로 컴파일되어 rom 에 포함되어 버리므로 loading 속도는 0 에 가깝지만 메모리가 약간 낭비되는 경향이 있다. 적절한 압축 방법을 사용하면 Loading 속도를 크게 늘리지 않으면서 rom 파일 크기를 줄일 수 있겠지만, 일단 그대로 넣는 방법을 사용한다.

[3][3][2][2][2][2][2][2][2][2][2][2][1][1][1][1][1][1][1][1][1][1]
[1][0][9][8][7][6][5][4][3][2][1][0][9][8][7][6][5][4][3][2][1][0][9][8][7][6][5][4][3][2][1][0]
 0  0  0  0  0  0 [green 0 ~ 31 ][  alpha 0 ~ 32  ][ blue 0 ~ 31 ] 0  0  0  0  0 [ red 0 ~ 31  ]

unsigned int color = (b>>3<<10) | (g>>3<<21) | ((alpha + 1)>>3<<15);

이와 같은 배치가 왜 빠른 drawing 을 하는 데 도움을 주는지는 아래 drawing 부분에서 설명하도록 한다.

[edit] Read PNG Write GPN

[edit] main() loop

    for (i = 1; i < argc; i++) {
        if (argv[i][0] == '-') {
            switch (argv[i][1]) {
            case 'a':
                g_option.use_alpha = true;
                continue;
            case 'n':
                g_option.use_alpha = false;
                continue;
            }

Command line option 으로 -a 를 주면 alpha channel 이 포함된 32bit gpn 을 만들도록 하고 -n 을 주거나 옵션이 없으면 16bit gpn 을 만들도록 한다.

[edit] processFile()

  • GD Library 를 사용하여 png 파일을 읽어들인다. png2gpn 프로그램을 간단히 만들기 위해 24bit TrueColor 만을 사용하는 것을 가정하도록 한다.
  • Image 의 크기를 알아낸 뒤 각 pixel 별로 loop 를 돈다
    for (int y = 0; y < gpn->height; y++) {
        for (int x = 0; x < gpn->width; x++) {
            int color = gdImageGetPixel(img, x, y);
            int r = gdTrueColorGetRed(color);
            int g = gdTrueColorGetGreen(color);
            int b = gdTrueColorGetBlue(color);
            int a = 255 - (gdTrueColorGetAlpha(color) << 1);

            if (g_option.use_alpha) {
                pixel = (b>>3<<10) | (g>>3<<21) | ((a + 1)>>3<<15);
                *(unsigned int *)p = pixel;
                p += 2;
            } else {
                pixel = (b>>3<<10)|(g>>3<<5)|(r>>3)|(1<<15);
                *p = (unsigned short)pixel;
                p++;
            }
  • GD 는 Alpha 값에 7bit 만 사용하고 0 이 opaque, 127 이 transparent 이므로 255 - alpha << 1 을 해야 원하는 0~255 의 값을 얻을 수 있다.

[edit] 전체 파일

[edit] Makefile

TARGET = png2gpn.exe
all: $(TARGET)

$(TARGET): png2gpn.cpp
    g++ -o $@ $< -lgd

clean:
    rm $(TARGET)

[edit] Loading and drawing gpn on DS

[edit] Drawing 16 bit gpn

16 bit gpn 을 그리는 것은 매우 간단하다. 현재 버전의 timestable 에서는 16 bit gpn 은 full screen 을 칠하기 위한 용도로밖에 사용하지 않기 때문에 다음과 같은 구현만 있다.

void Graphics::drawBackgroundImage(Image *img)
{
    u32 *src = (u32 *)img->getData();
    DisplayBuffer &db = m_pimpl->displays[m_pimpl->activeLcd];
    memcpy(db.srcBuffer, src, 2 * SCREEN_WIDTH * SCREEN_HEIGHT);
    db.invalidRect = g_fullScreen;
}

Full screen 이 아닌 이미지를 그리는 것은 간단하므로 설명은 생략하도록 한다.

[edit] Drawing 32 bit gpn

RGB565 format 에서 빠른 alpha blending 을 하는 원리에 대해서는 http://www.virtualdub.org/blog/pivot/entry.php?id=117 에 잘 설명되어 있다. 하지만 영어니까 ^^ 핵심만 꺼내 보면,

  • 원래는 src 와 dst 의 r,g,b 를 각각 꺼내어 alpha 를 곱해야 한다. 하지만 이렇게 분리하고 곱하고 다시 합치는 것은 오래 걸린다
  • RGB565 는 32 bit 의 절반밖에 사용하지 않는다. 따라서 bit 를 적절히 배치하면 세 개의 곱셈을 동시에 할 수 있다. 예를 들어 우리가 10 진수 계산을 할 때에도 984712 * 8 같은 걸 하려면 금방 답이 안 나온다. (암산왕은 무시하자) 그런데 80702 * 8 은 쉽게 할 수 있다. 8*8=64, 7*8=56, 2*8=16 이므로 645616 이 답이 된다. 이렇게 사이사이에 0 이 들어 있으면 각 자리수의 곱셈은 다른 자리에 영향을 주지 않는다. RGB888 를 rrggbb 로, RGB565 를 0r0g0b 로 표현한다면 0r0g0b x a = (ar)(ag)(ab) 가 되는 것이다.
  • rgb 를 << 16 한 값과 OR 한 뒤 적당한 mask out 을 하면 다음과 같다
    • rgb | rgb << 16 = rgbrgb
    • rgbrgb & mask = 0g0r0b
  • 저장할 때는 g, r, b 사이의 적당한 공간에 33 단계 (0~32) 의 alpha 값을 끼워 넣도록 한다.
  • 이상은 RGB565 를 기반으로 설명한 것이지만, NDS 에서는 RGB555 를 사용한다. 이를 위한 조정을 해야 한다.

이것을 code 로 표현한 것은 다음과 같다

    u32 src = *src_curr;
    u32 dst = *curr;
    u32 alpha = (src >> 15) & 0x3F;
    src &= 0x3E07C1F;
    dst = (dst | (dst << 16)) & 0x3E07C1F;
    u32 result = ((src - dst) * alpha / 32 + dst) & 0x3E07C1F;
    *curr = result | (result >> 16) | BIT(15);
Personal tools
Wiki Help (mediawiki.org)