[펌]http://juno.springnote.com/pages/917704


저번 강좌에 이어서 이번에도 계속해서 비쥬얼(^^)한 그래픽 API 들을 살펴보겠습니다. 저번 강좌와 마찬가지로 에뮬레이터에서 직접 실행해보면서 이것저것 바꿔보시면서 익히는 것이 더욱 빨리 Clet에 익숙해지는 길이겠죠. 꼭 예제들을 실행해보고 조금씩 바꿔보고 자기 것으로 하시기 바랍니다.


살펴볼 Graphic API

  • 문자열출력 관련
    MC_grpDrawString MC_grpDrawUnicodeString MC_grpGetStringWidth MC_grpGetUnicodeStringWidth
    MC_grpGetFont MC_grpGetFontHeight MC_grpGetFontAscent MC_grpGetFontDescent
  • 이미지 관련
    MC_grpCreateImage MC_grpGetImageProperty MC_grpDrawImage MC_grpDestroyImage
    MC_grpGetImageFrameBuffer MC_grpDecodeNextImage MC_grpEncodeImage
  • 문자입력 관련
    MC_grpPostEvent MC_imGetSupportModeCount MC_imGetSupportedModes
    MC_imSetCurrentMode MC_imGetCurrentMode MC_imHandleInput

문자열 출력은 단어 의미 그대로 화면에 문자열을 표시하는 것입니다. 더불어 잘 사용되지는 않지만, 그 폰트를 제어하는 API도 제공되고 있습니다.

이미지 API 에서는 직접 bmp나 png 등의 이미지를 읽어들여서 화면에 출력하는 과정을 보게됩니다. 실제 게임 제작에서 가장 많이 활용될 API이기도 하겠죠.

문자 입력은 직접 문자 입력기를 구현하기 위한 API 들을 확인할 수 있습니다. 별도로 텍스트박스에 대한 컴포넌트도 존재하기는 합니다만, 이 API를 통해 좀 더 자신만의 입력기 구현법을 살펴볼 수 있습니다.


그럼 하나씩 살펴보도록 하죠~


문자열 : 화면에 글을 써요.

  1. void MC_grpDrawString(MC_GrpFrameBuffer dst, M_Int32 x, M_Int32 y,
  2.         const char *str, M_Int32 len, MC_GrpContext *pgc);
  3. void MC_grpDrawUnicodeString(MC_GrpFrameBuffer dst, M_Int32 x, M_Int32 y,
  4.         const M_UCode *str, M_Int32 len, MC_GrpContext *pgc);
  5. M_Int32 MC_grpGetStringWidth(M_Int32 font, const M_Uint8 *str, M_Int32 len);
  6. M_Int32 MC_grpGetUnicodeStringWidth(M_Int32 font, const M_UCode *str, M_Int32 len);

MC_grpDrawString은 x,y 위치에 str 문자열을 출력하는 함수입니다. 이때 pgc로 넘기는 그래픽 컨텍스트로 폰트타입이라던가 색상등을 지정할 수 있습니다. 파라메터 len 의 경우 -1 이면 문자열의 끝('\0' 와 같은 0 값)을 만날때까지 출력하며, 출력하고 싶은 만큼의 길이값을 넣으면 그 길이까지만 출력합니다.

문자열의 정확한 출력 위치를 확인하기 위해서 MC_grpGetStringWidth 를 사용합니다. 이 함수를 통해 출력하고자 하는 문자열이 현재 정해진 폰트로 출력될때의 실제 픽셀 길이를 확인할 수 있습니다. 어떻게 활용하게 되는지는 예제를 통해 확인해보도록 하겠습니다.


  1. void startClet(int argc, char* args[])
    {  
        MC_GrpFrameBuffer fb;
        MC_GrpContext     gc;
        M_Int32           screenWidth, screenHeight;
        M_Int32           curFont,strWidth;
        M_Char            outString[40];
       
        fb=MC_grpGetScreenFrameBuffer(0);
        MC_grpInitContext(&gc);  

        screenWidth = MC_GRP_GET_FRAME_BUFFER_WIDTH(fb);
        screenHeight = MC_GRP_GET_FRAME_BUFFER_HEIGHT(fb);
        // 화면 전체를 검은색으로 칠해서 잘 볼 수 있게 해둡니다.
        MC_grpSetContext(&gc,MC_GRP_CONTEXT_FG_PIXEL_IDX,(void*)MC_grpGetPixelFromRGB(0,0,0));
        MC_grpFillRect(fb,0,0,screenWidth,screenHeight,&gc);
       
        // 폰트 설정 (MC_grpGetFont 의 사용법은 잠시후에 보도록 하겠습니다.)
        curFont = MC_grpGetFont(MC_GRP_FT_FACE_SYSTEM,MC_GRP_FT_SIZE_MEDIUM,MC_GRP_FT_STYLE_PLAIN);
        MC_grpSetContext(&gc,MC_GRP_CONTEXT_FONT_IDX,(void*)curFont); // 출력할 폰트를 컨텍스트에 설정
       
        strcpy(outString,"문자열 출력 테스트!");   // 출력할 문자열
        MC_grpSetContext(&gc,MC_GRP_CONTEXT_FG_PIXEL_IDX,(void*)MC_grpGetPixelFromRGB(255,255,255));
        MC_grpDrawString(fb,0,20,outString,-1,&gc);
        strWidth = MC_grpGetStringWidth(curFont,outString,-1);   // 문자열의 길이를 구합니다.
        MC_grpSetContext(&gc,MC_GRP_CONTEXT_FG_PIXEL_IDX,(void*)MC_grpGetPixelFromRGB(255,0,255));
        MC_grpDrawString(fb,(screenWidth-strWidth)/2,40,outString,-1,&gc);
        MC_grpSetContext(&gc,MC_GRP_CONTEXT_FG_PIXEL_IDX,(void*)MC_grpGetPixelFromRGB(255,255,0));
        MC_grpDrawString(fb,screenWidth-strWidth,60,outString,-1,&gc);

        MC_grpFlushLcd( 0, fb, 0, 0, MC_GRP_GET_FRAME_BUFFER_WIDTH(fb), MC_GRP_GET_FRAME_BUFFER_HEIGHT(fb) );
    }

폰트 설정에 대해서는 잠시후 살펴보기로 하고 문자열을 출력하는 부분을 보면, 일단 MC_grpDrawString으로 출력하기전에 컨텍스트 설정을 통해 출력될 색을 지정했습니다.

그리고 각 출력 함수를 보시면 x 좌표의 값이 각기 다르다는 것을 확인하실 수 있을껍니다. 그럼 일단 출력 결과가 어떻게 나오는지 보도록 하죠.

clet6_1.png

(0,20) 좌표에 찍은 문자열(하얀색)은 x 좌표에 0을 입력했으므로 흔히 왼쪽 정렬 이라고 부르는 모습처럼 화면 왼쪽으로 붙어서 출력되었죠.

x 좌표를 (screenWidth - strWidth)/2 로 설정한 문자열(자주색)의 경우 화면 중앙에 출력된 것을 보실 수 있습니다. 즉 가운데 정렬 상태인거죠. 실제 화면에 문자열을 출력하려고 할때 그 위치를 정확히 판단하기 위해서는 출력될 문자열의 픽셀 길이를 알아야 하는 경우가 있습니다. 이런 경우 MC_grpGetStringWidth 함수로 해당 문자열이 출력될 길이를 먼저 구해서 원하는 위치에 출력을 할 수 있게 됩니다. 마찬가지로 오른쪽 정렬을 위해 마지막 문자열(노란색)의 경우 x좌표를 screenWidth - strWidth 한 것이죠.


그럼 파라메터중 len 길이의 변화가 어떤 영향을 주는지도 보도록 하겠습니다.(위에서는 len의 인수로 전부 -1 을 줘서 지정한 문자열 변수의 값 전부를 출력했죠.)

  1. void startClet(int argc, char* args[])
    {  
        MC_GrpFrameBuffer fb;
        MC_GrpContext     gc;
        M_Int32              screenWidth, screenHeight;
        M_Int32              curFont,strWidth;
        M_Char              outString[40];
        M_Int32              i, len;
       
        fb=MC_grpGetScreenFrameBuffer(0);
        MC_grpInitContext(&gc);  

        screenWidth = MC_GRP_GET_FRAME_BUFFER_WIDTH(fb);
        screenHeight = MC_GRP_GET_FRAME_BUFFER_HEIGHT(fb);

        MC_grpSetContext(&gc,MC_GRP_CONTEXT_FG_PIXEL_IDX,(void*)MC_grpGetPixelFromRGB(0,0,0));
        MC_grpFillRect(fb,0,0,screenWidth,screenHeight,&gc);
       
        curFont = MC_grpGetFont(MC_GRP_FT_FACE_SYSTEM,MC_GRP_FT_SIZE_MEDIUM,MC_GRP_FT_STYLE_PLAIN);
        MC_grpSetContext(&gc,MC_GRP_CONTEXT_FONT_IDX,(void*)curFont);

        // 이하 바뀐 부분입니다.
        MC_grpSetContext(&gc,MC_GRP_CONTEXT_FG_PIXEL_IDX,(void*)MC_grpGetPixelFromRGB(255,255,0));
        strcpy(outString,"String 출력 테스트!");
        len = (M_Int32)strlen(outString);
        for(i=1;i<=len;i++)
        {
            MC_grpDrawString (fb,10,5 + (i*15),outString,i,&gc);
        }       

        MC_grpFlushLcd( 0, fb, 0, 0, MC_GRP_GET_FRAME_BUFFER_WIDTH(fb), MC_GRP_GET_FRAME_BUFFER_HEIGHT(fb) );
    }

처음 예제 코드에서 문자열이 출력되는 부분을 조금 수정해서 len 값을 1부터 문자열의 길이까지 1씩 증가하면서 출력되도록 해보았습니다.

어떻게 출력되었나 확인해보죠.

clet6_2.png

잘 보면 영문자나 기호(!)나 빈칸(space)의 경우 1글자씩 증가했는데, 한글의 경우 한 글자가 2번씩 같은 길이인 것처럼 출력되는 것을 확인할 수 있습니다.

사실 한글의 경우 이미 아시는 분도 계시겠지만, 영어와 같은 문자와 다르게 2바이트형 문자이며, 따라서 만약 원칙적으로 1바이트씩(길이라는게 그런 의미로 볼 수 있죠.)증가 하여 출력했다면, 이상한 문자가 출력되는 경우도 발생할 수가 있을 껍니다.즉 2바이트의 데이터가 모여서 하나의 글자를 이루는 방식인데, 그중 1바이트값만 출력하려고 한다면 뭔가 이상한 문자가 될 가능성이 높겠죠. 아마도 현 Clet 환경의 MC_grpDrawString 함수의 경우 작성될때 이러한 한글 처리의 문제를 감안해서 이상 출력이 되지 않도록 미리 처리를 해두었다고 생각할 수 있겠네요. /* 그러나 이런 예외 처리를 모든 Clet 환경이 다 해 두었다고 믿고 len 값에 신경을 덜 쓰게되면, 간혹 예외처리를 안해둔 환경에서는 문자열이 깨져 보이는 일도 발생할 수 있겠죠. 한글의 길이를 처리할때는 주의해야 합니다.^^ */


문자열을 출력하는 함수 MC_grpDrawString 과 출력될 길이를 구하는 함수 MC_grpGetStringWidth 에 문자열 입력 파라메터만 다른 닮은 함수인 MC_grpDrawUnicodeString 과 MC_grpGetUnicodeStringWidth 는 함수 이름에서 예상할 수 있듯이 문자열의 인수로 유니코드 문자열을 넣어줘야 합니다.

유니코드에 대한 자세한 내용은 따로 검색등을 통해서 살펴보시길 바라겠습니다만, 간단히 2바이트 문자열의 구조이며, 대개 C 프로그램에서 문자열로 한글을 사용할 경우 KS완성형(확장)을 사용하게 되는데, JAVA등에서는 내부 문자열을 기본적으로 유니코드로 처리하고 있으며, C에서도 다국어 지원등을 제대로 하기 위해서는 유니코드의 사용이 점점 필요해지는 상황입니다. 아마 WIPI 는 JAVA도 지원하기 때문에, 데이터나 소스를 Jlet 과 혼용해서 사용해야하는 Clet 을 위해 유니코드 관련도 지원을 하려고 한 것으로 생각됩니다.

특별히 유니코드를 사용해야 하는 어플리케이션이 아니라면 잘 사용하지 않을 함수이겠지만, 간단히 테스트 해보기 원하신다면 아래와 같이 해보시면 되겠습니다.

  1. M_UCode outUnicodeString[] = L"String 출력 테스트!";   // 문자열 선언시 L 을 붙이면 VC++은 컴파일시에 유니코드로 인식합니다.
  2. MC_grpDrawUnicodeString(fb,10,20,outUnicodeString,-1,&gc);   // 파라메터는 똑같죠.

/* 아마 이 강좌를 보시면서 별도로 작성되어있는 그래픽 폰트 강좌를 혹시 보셨다면, 유니코드의 사용에 대해서 좀 더 생각하게 되실 수도 있다고 생각합니다. 일반적으로 사용되는 KS완성형의 경우 한글의 구성방식인 초성,중성,종성을 각각 분리할 수 없는 값이므로 대개 '완성형 to 조합형' 테이블등을 통해서 조합형 코드로 변경후에 초,중,종성을 분리하여 출력할 자음,모음을 판단하게 됩니다. 유니코드의 한글 값의 경우도 조합형과 마찬가지로 2바이트의 값을 특정한 규칙으로 읽어내면 자모 분리가 쉽게 가능하므로 자신만의 폰트등으로 출력하려고 하는 경우 사용하게될 가능성이 있다고 생각됩니다. 그외에도 다국어 지원등을 위한 리소스 구성등에서도 유니코드는 많이 활용되므로 기회가 된다면 유니코드에 대해서도 살펴보는 것도 좋을 것 같다고 생각합니다. */


그럼 이제 출력할 문자열의 모양(^^)을 정의하는 폰트에 관련된 API를 살펴보겠습니다. 이미 위 예제에서 쓰였습니다만, 좀 더 자세히 보도록 하죠.

  1. M_Int32 MC_grpGetFont(M_Int32 face, M_Int32 size, M_Int32 style);
    M_Int32 MC_grpGetFontHeight(M_Int32 font);
    M_Int32 MC_grpGetFontAscent(M_Int32 font);
    M_Int32 MC_grpGetFontDescent(M_Int32 font);

위 예제에서도 MC_grpGetFont 를 사용하고 있는데요, 이 함수를 통해서 지정한 폰트의 ID 값을 반환 받을 수 있죠. 폰트의 지정은 파라메터값들을 통해서 이루어 지는데, 만약 우리가 지정하는 파라메터에 일치하는 폰트가 없다면, 유사한 폰트 ID(혹은 기본 폰트 ID)를 돌려준다고 합니다.

face는 폰트 페이스를 의미하며, 사용할 수 있는 값은 MC_GRP_FT_FACE_SYSTEM (시스템 폰트), MC_GRP_FT_FACE_MONOSPACE (폭이 균일한 폰트), MC_GRP_FT_FACE_PROPORTIONAL (폭이 균일하지 않은 폰트) 중 하나입니다.

size는 폰트 사이즈를 의미하며, MC_GRP_FT_SIZE_LARGE , MC_GRP_FT_SIZE_MEDIUM , MC_GRP_FT_SIZE_SMALL 중 하나를 전달합니다.

style은 폰트 스타일을 의미하며, MC_GRP_FT_STYLE_PLAIN 이나 MC_GRP_FT_STYLE_ITALIC , MC_GRP_FT_STYLE_BOLD , MC_GRP_FT_STYLE_UNDERLINE 을 OR한 값을 전달합니다. /* 대개 워드프로세서나 일반적인 편집기등에서 많이 보셨을 형태입니다. */


MC_grpGetFontHeight 는 폰트의 높이값을 반환합니다. 즉 문자열을 여러줄로 표현해야하는 경우 이 값을 보고 각 줄간의 간격을 설정할 수 있겠죠.

MC_grpGetFontAscent 와 MC_grpGetFontDescent 는 폰트의 Ascent 값과 Descent 값을 반환합니다. Ascent 는 폰트 baseline을 기준으로 위쪽의 높이, Descent는 아래쪽의 높이를 의미합니다. 즉 두 값을 정하면 폰트의 height가 되는 것이죠. /* 자세한건 검색등을 통해서 찾아보세요^^ */

MC_grpDrawString의 파라메터 y의 값은 사실 폰트의 baseline 위치를 지정합니다. 위 예제 코드들에서 y의 인수값을 조금씩 바꾸면서 결과를 보시면 baseline이라는 것이 어디쯤이구나를 파악할 수 있을 수도 있겠지만, 사실 폰트마다의 baseline 위치는 다 다를 수 밖에 없습니다. 따라서 정확한 출력을 위해서는 이런 Ascent, Descent값도 필요하게 되는 것이죠.

그럼 예제를 통해서 실제 우리가 쓰는 에뮬레이터에서는 어떤 값들이 가능한지 파악해보도록 하죠.

  1. void startClet(int argc, char* args[])
    {  
        MC_GrpFrameBuffer fb;
        MC_GrpContext     gc;
        M_Int32 screenWidth, screenHeight;
        M_Int32 curFont;  
        M_Int32 i,j, height,y;
        M_Char  outString[] = "String 테스트";
        M_Int32 fontFace[3] = {MC_GRP_FT_FACE_SYSTEM, MC_GRP_FT_FACE_MONOSPACE, MC_GRP_FT_FACE_PROPORTIONAL};
        M_Int32 fontSize[3] = {MC_GRP_FT_SIZE_SMALL, MC_GRP_FT_SIZE_MEDIUM, MC_GRP_FT_SIZE_LARGE};
        M_Int32 fontStyle[7] = {MC_GRP_FT_STYLE_ITALIC, MC_GRP_FT_STYLE_BOLD, MC_GRP_FT_STYLE_UNDERLINE,
                                MC_GRP_FT_STYLE_ITALIC | MC_GRP_FT_STYLE_BOLD,

  2.                             MC_GRP_FT_STYLE_BOLD | MC_GRP_FT_STYLE_UNDERLINE,
                                MC_GRP_FT_STYLE_ITALIC | MC_GRP_FT_STYLE_UNDERLINE,
  3.                             MC_GRP_FT_STYLE_ITALIC | MC_GRP_FT_STYLE_BOLD | MC_GRP_FT_STYLE_UNDERLINE};
        fb=MC_grpGetScreenFrameBuffer(0);
        MC_grpInitContext(&gc);  

        screenWidth = MC_GRP_GET_FRAME_BUFFER_WIDTH(fb);
        screenHeight = MC_GRP_GET_FRAME_BUFFER_HEIGHT(fb);

        MC_grpSetContext(&gc,MC_GRP_CONTEXT_FG_PIXEL_IDX,(void*)MC_grpGetPixelFromRGB(0,0,0));
        MC_grpFillRect(fb,0,0,screenWidth,screenHeight,&gc);
       
        MC_grpSetContext(&gc,MC_GRP_CONTEXT_FG_PIXEL_IDX,(void*)MC_grpGetPixelFromRGB(255,255,0));  
        y = 20;
        for(j=0;j<3;j++)
        for(i=0;i<3;i++)
        {
            curFont = MC_grpGetFont(fontFace[j],fontSize[i],MC_GRP_FT_STYLE_PLAIN);
            MC_grpSetContext(&gc,MC_GRP_CONTEXT_FONT_IDX,(void*)curFont);
            height = MC_grpGetFontHeight(curFont);
            MC_grpDrawString(fb,10,y,outString,-1,&gc);                     
  4.         MC_knlPrintk("face[%d],size[%d],fontID[%d],height[%d],ascent[%d],descent[%d]\n",
  5.          fontFace[j],fontSize[i],curFont,height,MC_grpGetFontAscent(curFont),MC_grpGetFontDescent(curFont));
            y += height + 5;
        }
        MC_grpSetContext(&gc,MC_GRP_CONTEXT_FG_PIXEL_IDX,(void*)MC_grpGetPixelFromRGB(255,255,255));  
        for(i=0;i<7;i++)
        {
            curFont = MC_grpGetFont(MC_GRP_FT_FACE_SYSTEM,MC_GRP_FT_SIZE_MEDIUM,fontStyle[i]);
            MC_grpSetContext(&gc,MC_GRP_CONTEXT_FONT_IDX,(void*)curFont);
            height = MC_grpGetFontHeight(curFont);
            MC_grpDrawString(fb,10,y,outString,-1,&gc);
  6.         MC_knlPrintk("style[%d],fontID[%d],height[%d],ascent[%d],descent[%d]\n",
  7.          fontStyle[i],curFont,height,MC_grpGetFontAscent(curFont),MC_grpGetFontDescent(curFont));
            y += height + 5;
        }       
        MC_grpFlushLcd( 0, fb, 0, 0, MC_GRP_GET_FRAME_BUFFER_WIDTH(fb), MC_GRP_GET_FRAME_BUFFER_HEIGHT(fb) );
    }

코드는 좀 길어졌습니다만, 뭐 일단 파라메터는 대략의 조합이 가능하도록 해서 볼만하게 출력하도록 한 형태입니다. 그럼 에뮬레이터에는 어떻게 결과가 나올까요.

clet6_3.png

오잉? 디버그로 출력된 값을 보면 값은 계속 바뀌면서 들어간것 같은데 fontID 값이 일정하군요. 즉, 에뮬레이터는 저 폰트 한가지 뿐이 지원을 안한다고 볼 수 있겠죠. /* 뭔가 참 실습에 좋지 않은 결과네요...신나게 코드 작성한 사람의 노력을 ㅠㅠ */


그럼 혹시 폰에서는 어떻게 보일까 라는 호기심이 발동해서...사실 뭐 이 강좌는 에뮬레이터만을 기준으로 하는 강좌입니다만, 일단 이 글을 쓰는 제가 궁금해졌기에 급하게 폰용으로 빌드해서 제 폰에 올려보았습니다. 코드는 예제 그대로 입니다. 그 결과는~

clet6_4.jpg /* 제 폰은 SCH-V740 입니다. SKT 폰이죠. 현 에뮬레이터도 SKT 용이죠. 그래서 비슷? ^^ */

....... 어째서...안 변하는 걸까요 ㅠㅠ /* 집에서 급하게 폰 컴파일 환경까지 만들어서 올린건데 ㅠㅠ */

역시나 궁금한 마음에 디버그 결과도 뽑아 보았습니다. /* 폰 디버그 출력 방법은 컨텐츠 개발사에게만 알려주는 것이기 때문에, 강좌에서 다루지 않습니다. */

face[0],size[8],fontID[2],height[19],ascent[15],descent[4]
face[0],size[0],fontID[2],height[19],ascent[15],descent[4]
face[0],size[16],fontID[2],height[19],ascent[15],descent[4]
face[32],size[8],fontID[2],height[19],ascent[15],descent[4]
face[32],size[0],fontID[2],height[19],ascent[15],descent[4]
face[32],size[16],fontID[2],height[19],ascent[15],descent[4]
face[64],size[8],fontID[2],height[19],ascent[15],descent[4]
face[64],size[0],fontID[2],height[19],ascent[15],descent[4]
face[64],size[16],fontID[2],height[19],ascent[15],descent[4]
style[2],fontID[2],height[19],ascent[15],descent[4]
style[1],fontID[2],height[19],ascent[15],descent[4]
style[4],fontID[2],height[19],ascent[15],descent[4]
style[3],fontID[2],height[19],ascent[15],descent[4]
style[5],fontID[2],height[19],ascent[15],descent[4]
style[6],fontID[2],height[19],ascent[15],descent[4]
style[7],fontID[2],height[19],ascent[15],descent[4]

흠냐....사실 전 거의 폰용 게임을 처음 만들었을때 부터 폰 자체의 폰트가 싫어서 폰트는 따로 이미지로 만들어 쓴지라, 이정도일꺼라고는 상상을 못했는데 말이죠.ㅎㅎ


[ Bonus - SKT Clet 에서는 폰트 이렇게 제어해야 합니다. ]

사실 표준 API 로는 동일한 기본 폰트ID가 나와버려서 당황스럽지만, 사실 대개 플랫폼들마다 규정하는 확장 API 에서는 이런 부분을 보완하는 API 들을 제공하고 있습니다.

아래는 그러한 API를 사용해서 결과를 보기위해 만든 예제 코드입니다. 이런 부분은 각 플랫폼사의 규격마다 차이가 있을 수 있으므로 자세하게 살펴보지는 않도록 하겠습니다. SKT Clet 에서는 확장 함수인 OEMC_grpGetFontLists 로 사용할 수 있는 폰트 이름들을 얻고, OEMC_grpGetFontEx 를 이용해서 해당 폰트의 ID 를 얻어서 사용 가능합니다.

아래는 그 예제입니다.

  1. void startClet(int argc, char* args[])
    {  
        MC_GrpFrameBuffer fb;
        MC_GrpContext     gc;
        M_Int32              screenWidth, screenHeight;
        M_Int32              curFont;  
        M_Int32              i,j, height,x,y;
        M_Char              outString[256];
        M_Int32 fontSize[3] = {MC_GRP_FT_SIZE_SMALL, MC_GRP_FT_SIZE_MEDIUM, MC_GRP_FT_SIZE_LARGE};
        M_Int32 fontStyle[8] = {MC_GRP_FT_STYLE_PLAIN,
                                MC_GRP_FT_STYLE_ITALIC, MC_GRP_FT_STYLE_BOLD, MC_GRP_FT_STYLE_UNDERLINE,
                                MC_GRP_FT_STYLE_ITALIC | MC_GRP_FT_STYLE_BOLD,
                                MC_GRP_FT_STYLE_BOLD | MC_GRP_FT_STYLE_UNDERLINE,
                                MC_GRP_FT_STYLE_ITALIC | MC_GRP_FT_STYLE_UNDERLINE,
                                MC_GRP_FT_STYLE_ITALIC | MC_GRP_FT_STYLE_BOLD | MC_GRP_FT_STYLE_UNDERLINE};

        fb=MC_grpGetScreenFrameBuffer(0);
        MC_grpInitContext(&gc);  

        screenWidth = MC_GRP_GET_FRAME_BUFFER_WIDTH(fb);
        screenHeight = MC_GRP_GET_FRAME_BUFFER_HEIGHT(fb);

        MC_grpSetContext(&gc,MC_GRP_CONTEXT_FG_PIXEL_IDX,(void*)MC_grpGetPixelFromRGB(0,0,0));
        MC_grpFillRect(fb,0,0,screenWidth,screenHeight,&gc);
       
        OEMC_grpGetFontLists(outString,256);
        MC_knlPrintk("[%s]\n",outString);    // 제 에뮬에서는 [고딕,언약,가을비]라고 출력

        MC_grpSetContext(&gc,MC_GRP_CONTEXT_FG_PIXEL_IDX,(void*)MC_grpGetPixelFromRGB(255,255,255));  
       
        x = 5;
        for(j=0;j<3;j++)
        {       
            y = 20;
            for(i=0;i<8;i++)
            {
                curFont = OEMC_grpGetFontEx("고딕",fontSize[j],fontStyle[i]); // 예제에서는 '고딕'만 사용해봅니다.
                MC_grpSetContext(&gc,MC_GRP_CONTEXT_FONT_IDX,(void*)curFont);
                height = MC_grpGetFontHeight(curFont);
                MC_grpDrawString(fb,x,y,"Test 문자열",-1,&gc);
                MC_knlPrintk("size[%d],style[%d],fontID[%d],height[%d],ascent[%d],descent[%d]\n",
                fontSize[j],fontStyle[i],curFont,height,MC_grpGetFontAscent(curFont),MC_grpGetFontDescent(curFont));
                y += height + 5;
            }
            x += 75;
        }       
        MC_grpFlushLcd( 0, fb, 0, 0, MC_GRP_GET_FRAME_BUFFER_WIDTH(fb), MC_GRP_GET_FRAME_BUFFER_HEIGHT(fb) );
    }

자 이번에는 변화가 있길 바라면서 에뮬에서 실행해보도록 하겠습니다.

clet6_5.png

드디어 변화가 생겼네요.(ㅠㅠ 감격중) fontID 가 전부 다르게 나온다는 것은 제대로 된다는 의미겠죠^^ 뭐 근데 딱히 멋지지도 않군요. ㅎㅎ

실제 이런 식으로 사용하시려면 위와 같이 단순하게 사용해서는 안되겠죠. 어디까지나 이건 이렇게 폰트를 바꿀 수 있다는 예제일 뿐입니다.

아무튼 혹시 기본 API 로 폰트가 안 바뀌어서 고민한 분이 계신다면 도움이 되셨길 바라면서~ 다음 이야기로 넘어가겠습니다.


이미지 : 드디어 이미지~~

간단한 어플리케이션이라도 화면에 비쥬얼(^^)하게 보이려면 뭔가 그림들을 가져다 붙여서 이쁘게 꾸며야겠죠. 그림을 어떻게 화면에 보이게 할 것인지에 대해서 확인해볼 수 있는 API 가 이미지 API 들입니다.

API를 살펴보기 전에 간단히 생각해보면, 이미지 파일을 읽어서 그리고 사용한 이미지는 지운다~ 뭐 이런 과정으로 진행될꺼라는게 팍~ 떠오르시지 않으신지요^^

아래의 API 들은 각각 그런 역활을 하는 기본 함수들입니다.

  1. M_Int32 MC_grpCreateImage(MC_GrpImage *newImg, M_Uint32 bufID, M_Int32 off, M_Int32 len);
    void MC_grpDrawImage( MC_GrpFrameBuffer dst, M_Int32 dx, M_Int32 dy, M_Int32 w, M_Int32 h,
                            MC_GrpImage src, M_Int32 sx, M_Int32 sy, MC_GrpContext *pgc);
    void MC_grpDestroyImage(MC_GrpImage img);

먼저 이미지(MC_GrpImage)를 생성하기 위한 함수인 MC_grpCreateImage 를 살펴보죠.

WIPI 1.2 표준 스펙에서는 BMP,PNG,GIF(Ani포함)을 읽을 수 있다고 되어있습니다. 일단 각 이미지 파일을 메모리상의 공간에 복사해 놓고 그 버퍼의 ID 값(Clet의 메모리 사용법이죠.)을 bufID 로 해서 해당 버퍼에서 이미지 데이터가 시작하는 주소값을 off 에 그리고 데이터의 길이를 len 으로 넘겨서 이상없이 처리가 된 경우 newImg 를 통해서 Clet 에서 사용 가능한 MC_GrpImage 형의 이미지를 생성하게 됩니다.

MC_grpCreateImage의 처리가 제대로 완료되면 MC_GRP_IMAGE_DONE 라는 값이 반환되며, 만약 메모리가 부족해서 이미지 생성을 못했다면, M_E_NOMEMORY 값을 반환하며, 이식하지 못하는 이미지 포멧의 데이터가 입력된 경우 M_E_BADFORMAT 라는 값을 반환하며 newImg 는 NULL 로 리턴되게 됩니다.

또한 이 이미지 생성 함수의 사용시 주의점은 bufID 로 넘긴 이미지 데이터 버퍼의 경우 이 함수의 처리가 완료된 직후 자동으로 해제가 되는데, 이때 만약 이미지 데이터가 애니메이션형 데이터라면 바로 해제되지 않고 MC_grpDestroyImage 에서 해제하게 됩니다. 즉 이 함수로 넘기는 버퍼의 경우 해제는 결국 API 에서 알아서 한다고 생각하면 되겠네요.

이미지를 그리는 것은 MC_grpDrawImage 를 통해서 프레임버퍼 dst 에 그리게 됩니다. 이때 해당 프레임 버퍼에 그릴 영역을 (dx,dy)부터 폭은w, 높이는h 만큼의 공간에 src 이미지의 (sx,sy)위치부터의 데이터를 옮겨서 그리게 됩니다. 이때 pgc로 설정되는 그래픽 컨텍스트로 투명 처리(알파값)라던가 투명 색 지정등을 해줄 수 있습니다. 이미지 자체에 투명 처리등에 사용되는 mask비트가 있는 이미지라면 이 값이 자동적으로 사용됩니다. /* 투명색 지정의 경우 1.2 스펙에 없는 값을 사용하므로 아래의 내용을 참고해주세요. */

다 사용한 이미지는 꼭 MC_grpDestroyImage 를 통해서 메모리상에서 삭제해줘야 겠습니다.

그럼 실제로 이미지 파일을 출력해보도록 하죠. 일단 기본적인 API의 사용법을 실습해 보겠습니다. 아래 예제를 보세요.

이하 예제에서 사용되는 이미지 파일은 clet6_resource.zip안에 들어있습니다.(리소스로 추가하는건 이전 강좌등에 이미 다루었습니다.)

  1. M_Int32 getFromResource(char *name,M_Int32 *retSize) // 리소스에서 해당 이름으로 파일 내용을 가져와서 BufID를 리턴
    {
        M_Int32 resID,resSize,BufID;

        resID = MC_knlGetResourceID(name, &resSize);
        if (resID < 0 ) return -1;    // 리소스 없음
        BufID = MC_knlAlloc(resSize);
        MC_knlGetResource (resID, BufID, resSize);
        if(retSize != NULL) *retSize = resSize; // 데이터 사이즈도 넘겨받습니다.
        return BufID;
    }
    void startClet(int argc, char* args[])
    {  
        MC_GrpFrameBuffer fb;
        MC_GrpContext     gc;
        M_Int32              screenWidth, screenHeight;
        M_Int32              bufID,retVal,memSize;
        MC_GrpImage          img1;
        M_Int32              len1;

        fb=MC_grpGetScreenFrameBuffer(0);
        MC_grpInitContext(&gc);  
        screenWidth = MC_GRP_GET_FRAME_BUFFER_WIDTH(fb);
        screenHeight = MC_GRP_GET_FRAME_BUFFER_HEIGHT(fb);
        MC_grpSetContext(&gc,MC_GRP_CONTEXT_FG_PIXEL_IDX,(void*)MC_grpGetPixelFromRGB(0,0,0));
        MC_grpFillRect(fb,0,0,screenWidth,screenHeight,&gc);
       
        memSize = MC_knlGetFreeMemory();
        MC_knlPrintk("[Before Memory] = %d\n",memSize);

        // 파일을 읽어서 이미지를 만듭니다.
        bufID = getFromResource("test1.bmp",&len1);   // 리소스로 부터 test1.bmp 파일을 읽어옵니다.
        MC_knlPrintk("[Memory-1] = %d\n",memSize - MC_knlGetFreeMemory());
        if (bufID < 0) { MC_knlPrintk("test1.bmp read fail!\n"); return; }
        retVal = MC_grpCreateImage(&img1,bufID,0,len1);
        MC_knlPrintk("[Memory-2] = %d\n",memSize - MC_knlGetFreeMemory());
        if (retVal != MC_GRP_IMAGE_DONE) { MC_knlPrintk("image create fail!\n"); return; }

        MC_grpDrawImage(fb,10,10,100,100,img1,0,0,&gc);  

        MC_grpDestroyImage(img1);
        MC_knlPrintk("[Memory-3] = %d\n",memSize - MC_knlGetFreeMemory());
        MC_grpFlushLcd( 0, fb, 0, 0, MC_GRP_GET_FRAME_BUFFER_WIDTH(fb), MC_GRP_GET_FRAME_BUFFER_HEIGHT(fb) );
    }

위 예제에서는 리소스 파일로 부터 원하는 이미지 파일을 읽기 위해 getFromResource 라는 함수를 별도로 만들었습니다. 아래 예제들에서도 계속 사용되니 일단 그대로 사용해주시고요. 예제 코드에서는 메모리의 상태 변화도 같이 살펴보기 위해 디버그로 메모리의 변화값을 출력하고 있습니다. 그럼 결과를 볼까요.

clet6_6.png

예제용 이미지 파일로 사용한 것은 제가 가지고 있는 미니 피겨들을 찍은 사진을 제가 편집한 것입니다.(문제될 것 없겠죠^^) 일단 test1.bmp 로 지정한 이미지는 제대로 출력이 되었네요. 메모리 상태 변화를 보기 위해 디버그 창을 보기 좋게 안으로 붙여두었는데요. 일단 리소스를 읽어오고 나서 보니 메모리를 11096 바이트 사용한거네요. 이미지 파일의 크기가 11078 바이트이니 대략 읽은 만큼 사용했다고 볼 수 있겠습니다. /* 정확히 같지 않고 좀더 큰(18바이트정도 다르네요.) 것은 부수적으로 내부에서 사용된 부분이겠죠. bufID를 위한 공간등도 있어야 할테니깐요. */

그후 MC_grpCreateImage후에 사용된 메모리양을 보니 20108 바이트네요. API 스펙에서는 단일 이미지인 경우 파라메터로 입력한 bufID 에 해당하는 버퍼는 자동으로 삭제한다고 하는데, 과연 그게 된걸까요. 이 부분을 알려면 전 내용에서 우리 에뮬레이터의 프레임 버퍼의 속성을 다시 기억해내봐야 합니다. 간단히 이야기하면, 현재 사용중인 에뮬레이터의 프레임버퍼 구조는 픽셀당 2바이트를 할당하는 구조였습니다. 우리가 이미지로 사용한 bmp파일은 256색 타입의 bmp이고, bmp파일에 대해서 검색등을 통해 공부해보시면 256색의 경우 1픽셀에 1바이트를 대응해서 해당 파렛트의 인덱스 번호를 가르키는 방식으로 저장된다는 것을 알 수 있습니다. 256색 파렛트는 하나의 인덱스당 4바이트형으로 색정보를 기록하므로 256색의 파렛트 용량은 대략 256*4 = 1024 바이트가 될 것입니다. 즉 bmp 파일의 원 사이즈인 11078 에서 1024 정도를 뺀 약 10000 바이트정도가 이미지 데이터라고 볼 수 있겠죠.(정확히하려면 bmp 헤더 사이즈도 빼야합니다만, 일단 뭐 그걸 알려고 하는게 아니니깐 대략 계산하는 겁니다.^^) 그럼 이런 이미지 데이터를 우리 에뮬레이터가 사용하는 프레임버퍼 구조로 변경한다면 1픽셀에 2바이트 사용형태로, 즉 현재 bmp의 데이터표현법에서 2배 사이즈가 될꺼라고 예상할 수 있겠네요. 그럼 대략 20000 바이트정도가 프레임버퍼의 사이즈가 되겠네요. MC_grpCreateImage후 사용 메모리양이 20108 바이트니깐, 대략 비슷하죠.(프레임버퍼도 여러가지 정보 헤더를 가지고 이미지 데이터도 사실 여러 헤더가 필요할테니깐요.) 결국 이런식으로 생각해보면 bufID 가 가르키던 bmp파일 원래의 데이터 공간은 삭제(free) 되었다고 생각할 수 있을 것 같습니다. /* 좀 더 정확히 알고자 한다면 이후 보게될 MC_grpGetImageFrameBuffer 함수를 통해 프레임버퍼를 얻은후 그 프레임버퍼의 데이터를 직접 확인해봐도 되겠죠^^ */

그림을 그린후 MC_grpDestroyImage 를 호출하고 나서 보니 사용량이 없네요. 즉 이미지 데이터도 확실히 해제가 된 것이라고 볼 수 있겠습니다.


그럼, 이미지 생성함수등의 동작하는 모습등을 확인했으니 이제 어떤 포멧들이 출력되는지, 스펙에 맞춰서 전부 출력해보도록 하겠습니다.

  1. void startClet(int argc, char* args[])
    {  
        MC_GrpFrameBuffer fb;
        MC_GrpContext     gc;
        M_Int32              screenWidth, screenHeight;
        M_Int32              bufID;
        MC_GrpImage          img1,img2,img3,img4;
        M_Int32              len1,len2,len3,len4;

        fb=MC_grpGetScreenFrameBuffer(0);
        MC_grpInitContext(&gc);  
        screenWidth = MC_GRP_GET_FRAME_BUFFER_WIDTH(fb);
        screenHeight = MC_GRP_GET_FRAME_BUFFER_HEIGHT(fb);
        MC_grpSetContext(&gc,MC_GRP_CONTEXT_FG_PIXEL_IDX,(void*)MC_grpGetPixelFromRGB(0,0,0));
        MC_grpFillRect(fb,0,0,screenWidth,screenHeight,&gc);

        bufID = getFromResource("test1.bmp",&len1);  
        MC_grpCreateImage(&img1,bufID,0,len1);
        bufID = getFromResource("test2.png",&len2);  
        MC_grpCreateImage(&img2,bufID,0,len2);
        bufID = getFromResource("test3.gif",&len3);  
        MC_grpCreateImage(&img3,bufID,0,len3);
        bufID = getFromResource("test4.gif",&len4);  
        MC_grpCreateImage(&img4,bufID,0,len4);
       
        MC_grpDrawImage(fb,10,10,100,100,img1,0,0,&gc);  
        MC_grpDrawImage(fb,120,10,100,100,img2,0,0,&gc);  
        MC_grpDrawImage(fb,10,120,100,100,img3,0,0,&gc);  
        MC_grpDrawImage(fb,120,120,100,100,img4,0,0,&gc);  

        MC_grpDestroyImage(img4);
        MC_grpDestroyImage(img3);
        MC_grpDestroyImage(img2);
        MC_grpDestroyImage(img1);
       
        MC_grpFlushLcd( 0, fb, 0, 0, MC_GRP_GET_FRAME_BUFFER_WIDTH(fb), MC_GRP_GET_FRAME_BUFFER_HEIGHT(fb) );
    }

압축된 파일을 풀어보시면 아시겠지만, test1.bmp는 BMP파일, test2.png는 PNG파일, test3.gif는 일반 GIF파일, test4.gif는 애니메이션 GIF파일입니다.

그럼 에뮬레이터의 실행 결과를 확인해보죠.

clet6_7.png

test2.png 파일의 경우(오른쪽 상단) 투명색을 지정했기 때문에 특별한 처리를 하지 않아도 투명처리가 된 그림이 그려졌습니다. (GIF도 사실 투명색 처리를 할 수 있습니다만, 에뮬에서 테스트해보니 그림이 좀 깨지더군요. 아마 제가 사용하는 그림툴(^^)에서 생성되는 투명 GIF의 인식이 이상한건지..암튼 그래서 패스했습니다.)

test4.gif(오른쪽 하단)의 경우는 애니메이션 GIF파일인데 draw했더니 첫번째 프레임만 그려졌네요. 애니메이션 GIF의 다른 프레임은 어떻게 그릴 수 있을까요.

코드를 조금 수정해보도록 하겠습니다.

  1. void startClet(int argc, char* args[])
    {  
        MC_GrpFrameBuffer fb;
        MC_GrpContext     gc;
        M_Int32              screenWidth, screenHeight;
        M_Int32              bufID,retVal;
        MC_GrpImage          img4;
        M_Int32              len4;

        fb=MC_grpGetScreenFrameBuffer(0);
        MC_grpInitContext(&gc);  
        screenWidth = MC_GRP_GET_FRAME_BUFFER_WIDTH(fb);
        screenHeight = MC_GRP_GET_FRAME_BUFFER_HEIGHT(fb);
       
        MC_grpFillRect(fb,0,0,screenWidth,screenHeight,&gc);
       
        bufID = getFromResource("test4.gif",&len4);  
        MC_grpCreateImage(&img4,bufID,0,len4);
       
        MC_knlPrintk("MC_GRP_IS_ANIMATED [%d]\n",MC_grpGetImageProperty(img4,MC_GRP_IS_ANIMATED));
        MC_knlPrintk("MC_GRP_ANIMATE_DELAY [%d]\n",MC_grpGetImageProperty(img4,MC_GRP_ANIMATE_DELAY));
        MC_knlPrintk("MC_GRP_LOOP_COUNT [%d]\n",MC_grpGetImageProperty(img4,MC_GRP_LOOP_COUNT));
        MC_knlPrintk("MC_GRP_IMAGE_WIDTH [%d]\n",MC_grpGetImageProperty(img4,MC_GRP_IMAGE_WIDTH));
        MC_knlPrintk("MC_GRP_IMAGE_HEIGHT [%d]\n",MC_grpGetImageProperty(img4,MC_GRP_IMAGE_HEIGHT));
        MC_knlPrintk("MC_GRP_IMAGE_BPP [%d]\n",MC_grpGetImageProperty(img4,MC_GRP_IMAGE_BPP));
       
        MC_grpDrawImage(fb,10,10,100,100,img4,0,0,&gc);  
        MC_knlPrintk("img4[%d]\n",img4);
        retVal=MC_grpDecodeNextImage(img4);
        MC_grpDrawImage(fb,120,10,100,100,img4,0,0,&gc);
        MC_knlPrintk("img4[%d] retVal[%d]\n",img4,retVal);
        retVal=MC_grpDecodeNextImage(img4);
        MC_grpDrawImage(fb,10,120,100,100,img4,0,0,&gc);
        MC_knlPrintk("img4[%d] retVal[%d]\n",img4,retVal);
        retVal=MC_grpDecodeNextImage(img4);
        MC_grpDrawImage(fb,120,120,100,100,img4,0,0,&gc);
        MC_knlPrintk("img4[%d] retVal[%d]\n",img4,retVal);

        MC_grpDestroyImage(img4);
       
        MC_grpFlushLcd( 0, fb, 0, 0, MC_GRP_GET_FRAME_BUFFER_WIDTH(fb), MC_GRP_GET_FRAME_BUFFER_HEIGHT(fb) );
    }

위 코드에서는 test4.gif 를 이용해서 2개의 새로운 API 의 사용법을 보여주고 있습니다. 일단 결과가 어떻게 출력되었는지 확인해보도록 하죠.

clet6_8.png

  1. M_Int32 MC_grpGetImageProperty(MC_GrpImage img, int index);
    M_Int32 MC_grpDecodeNextImage(MC_GrpImage dst);

이미지 데이터(MC_GrpImage)의 속성값을 알고 싶을때는 MC_grpGetImageProperty 를 사용해서 미리 정해진 값을 이용해서 정보를 얻을 수 있습니다. 위의 예제에 나온 MC_GRP_IS_ANIMATED의 경우 해당 이미지가 애니메이션형일 경우 1을 반환합니다.(그외는 0) test4.gif는 애니메이션 GIF 이므로 1을 반환하죠. 이런 경우 MC_GRP_ANIMATE_DELAY로 해당 애니메이션 프레임간의 딜레이값도 알 수 있습니다.(제가 GIF파일을 만들때 1초 간격이라고 정해두었습니다.) 특별히 LOOP 수는 정하지 않았다는 것을 알 수 있고, 이미지의 폭과 높이 그리고 위에서 잠깐 예상으로 생각해봤던(^^) BPP값도 얻어올 수 있습니다.

이미지가 애니메이션형일 경우 다음 프레임 이미지를 그리기 위해서는 MC_grpDecodeNextImage 를 사용해서 다음 프레임으로 넘겨줘야 합니다. 이때 리턴되는 값이 0 (=MC_GRP_FRAME_DONE) 인 경우는 다음 프레임도 있다는 의미이며, 1 (=MC_GRP_IMAGE_DONE)인 경우는 더 이상 프레임이 없다는 의미입니다. 그외의 값은 에러 처리해야할 값입니다.


이제 남은 API 도 살펴보죠.

  1. MC_GrpFrameBuffer MC_grpGetImageFrameBuffer(MC_GrpImage img);
    M_Uint32 MC_grpEncodeImage(MC_GrpFrameBuffer src, M_Int32 x, M_Int32 y, M_Int32 w, M_Int32 h, M_Int32 *len);

MC_grpGetImageFrameBuffer의 경우 img의 데이터의 프레임버퍼을 반환받는 함수입니다. 이미지의 프레임버퍼는 현재 Clet 이 동작중인 시스템의 프레임버퍼와 동일한 형식일 확율이 높으므로 직접 제어를 하거나 draw API 대신에 MC_grpCopyFrameBuffer등을 이용할 경우등에 사용할 수 있겠습니다.

MC_grpEncodeImage의 경우 지금까지 이미지 API 들이 그리는데 중점을 둔 함수였다면, 이 함수는 src 프레임 버퍼의 내용을 BMP 형싯으로 데이터를 생성한후 반환해주는 함수입니다. 즉 함수의 리턴값은 BMP데이터가 저장된 메모리 ID( API가 메모리를 할당했으므로 나중에 직접 해제해줘야 합니다. )이며, 이 데이터의 길이는 len을 통해 받을 수 있습니다. 직접 코드로 보도록 하겠습니다.

  1. void startClet(int argc, char* args[])
    {  
        MC_GrpFrameBuffer fb;
        MC_GrpContext     gc;
        M_Int32              screenWidth, screenHeight;
        M_Int32              bufID,fID;
        MC_GrpImage          img;
        M_Int32              len;
        M_Byte              *buf;

        fb=MC_grpGetScreenFrameBuffer(0);
        MC_grpInitContext(&gc);  
        screenWidth = MC_GRP_GET_FRAME_BUFFER_WIDTH(fb);
        screenHeight = MC_GRP_GET_FRAME_BUFFER_HEIGHT(fb);
       
        MC_grpFillRect(fb,0,0,screenWidth,screenHeight,&gc);
       
        bufID = getFromResource("test4.gif",&len);  
        MC_grpCreateImage(&img,bufID,0,len);
           
        MC_grpDrawImage(fb,10,10,100,100,img,0,0,&gc);  
        MC_grpDecodeNextImage(img);
        MC_grpDrawImage(fb,120,10,100,100,img,0,0,&gc);
        MC_grpDecodeNextImage(img);
        MC_grpDrawImage(fb,10,120,100,100,img,0,0,&gc);
        MC_grpDecodeNextImage(img);
        MC_grpDrawImage(fb,120,120,100,100,img,0,0,&gc);  
        MC_grpDestroyImage(img);

        bufID = MC_grpEncodeImage(fb,10,10,210,210,&len);

        // 다음시간에 볼 파일 관련 API를 사용합니다.
        fID = MC_fsOpen("result.bmp",MC_FILE_OPEN_WRONLY,MC_DIR_PRIVATE_ACCESS);
        buf = (M_Byte*)MC_GETDPTR(bufID);
        MC_fsWrite(fID,buf,len);
        MC_fsClose(fID);
        MC_knlFree(bufID);
        MC_grpFlushLcd( 0, fb, 0, 0, MC_GRP_GET_FRAME_BUFFER_WIDTH(fb), MC_GRP_GET_FRAME_BUFFER_HEIGHT(fb) );
    }

화면에 그려지는 것은 바로 위 예제와 동일한 모습이고(코드도 각 변수명 빼고는 동일합니다.) 정확히 그 영역만 MC_grpEncodeImage 함수에 넣어서 얻어진 데이터를 result.bmp라는 파일로 쓰도록 했습니다. 일단 위 코드를 실행하니 실행에는 아무런 이상이 없었습니다만, 과연 파일이 생성되었을까요. 이 파일을 확인하려면 WIPI SDK를 설치한 곳으로 가셔서 아래와 같은 곳에서 확인 가능합니다.

clet6_9.png

윈도우 탐색기상의 절대 경로는 위와 같고 오른쪽의 파일 목록을 보면 result.bmp가 있다는 것을 확인할 수 있습니다. 탐색기 스샷에 bmp 내용도 같이 붙여두었습니다. 정확히 해당 영역만 bmp로 만들어 졌네요. 이 API를 이용하면 간단한 화면 캡쳐 기능등을 만들 수 있을 것 같네요. 다만 문제라면 저정도 사이즈가 130KB군요...아무래도 바로 사용하기에는 조금 아쉬운 느낌이 드는 API 라고 볼 수도 있겠지만, 잘 활용하면 쓸 수 있는 방법이 있겠죠^^.


이상으로 이미지 API 를 전부 살펴보았습니다. 사실 현재 2.0 API 도 나와있습니다만, 그쪽을 보면 좀 더 이미지를 다루는데 필요한 기능이 추가가 되어 있는 정도이지 크게 위의 API의 영역을 벗어나지는 않고 있습니다. 뭐든지 기초가 중요하죠^^ 일단 이렇게 사용할 수 있다를 알고 응용해보면서 새로운 것들을 익힐때 더 멋진 활용법을 찾을 수 있을 수 있겠죠^^.


[Bonus - 이미지의 투명색을 지정해서 출력해봅시다.]

사실 이 부분은 SKT Clet 에서만 되는 것일지도 모르므로 이렇게 별도의 내용으로 분리해둡니다. 사실 스펙의 그래픽 컨텍스트에서는 아무리 찾아봐도 투명색을 지정하는 ID값이 보이지가 않습니다. 이상한 일이죠. 실제로 투명을 설정해서 PNG를 만들어도 되지만, 실제 이미지 작업은 BMP와 같은 raw형태도 꽤 많이 사용하게 되므로 투명값을 지정할 수 있는게 아무래도 필요해집니다. /* 물론 픽셀 op 함수로 직접 만들어도 되겠지만요. 아무튼 왠지 그건 너무 가혹한(^^) 응용법 같아 보이거든요. */

위에서 그림 4가지를 출력하던 예제에 보면 제가 일부러 각 그림의 바탕색(투명이 되길 원하는)을 RGB(255,0,255)로 해두었다는 것을 보셨을 껍니다. (물론 관심있게 봤어야 하는 부분이지만, 실제 작업시에 투명색값으로 꽤 많이들 사용하는 값입니다.^^)

이렇게 특정색을 지정해서 그 색은 출력되지 않게 하여 투명하게 처리하도록 하려면 아래와 같이 그래픽 컨텍스트를 설정해주면 됩니다.

  1. MC_grpSetContext(&gc, MC_GRP_CONTEXT_TRANS_PIXEL_IDX, (void *)MC_grpGetPixelFromRGB(255,0,255));

흠...자 스펙 문서에서 검색해보세요~ MC_GRP_CONTEXT_TRANS_PIXEL_IDX 라는 값이 있는가.....도대체 이건 어디서 튀어나온 것일까요. 저도 신기할 따름입니다. (기회가 되면 다른 플랫폼의 Clet 에도 저 값이 있는지 좀 봐야겠습니다.) 뭐 저도 SDK의 헤더를 보던중 발견하고 해본거라...황당하기 그지 없습니다..ㅎㅎ

아무튼 동작은 잘됩니다. 일단 에뮬에서 과제물이나 기말 프로젝트등으로 작업하시는 경우에는 위 컨텍스트 값을 활용하셔도 되겠습니다. 폰에서는...글쎄요. 좀 더 확인이 필요한 값이 아닐까 생각되네요^^.


마지막으로 그래픽 API와는 조금 거리감이 있지만, 그래도 같은 영역에 포함된 것으로 간주되는(--;) 문자 입력 부분을 알아보겠습니다.


문자입력 : 출력이 있으면 입력도 있는법~

일단 더더욱 이해가 안되지만, 그래픽 API에 포함되어있는 조금은 동떨어진 API 하나를 보고 넘어가도록 하겠습니다.

  1. M_Int32 MC_grpPostEvent(M_Int32 id, M_Int32 type, M_Int32 param1, M_Int32 param2);

함수 이름도 MC_grp로 시작하고 스펙 문서에도 그래픽 파트에 있는 API 입니다만, 정의를 보면 '지정한 응용 프로그램이 이벤트를 받을 수 있도록 이벤트 큐에 이벤트를 넣는다.'라고 되어 있는데요. 파라메터 id 의 경우 응용 프로그램 식별자로 커널 파트에서 우리가 살펴보지 않은 API중에 프로그램ID를 얻을 수 있는 API가 있는데 거기서 얻어진 ID값을 사용하는 것으로 추정됩니다.(사실 일반 어플리케이션 작성때는 사용할 일이 없는 API이므로 저 역시 정확하게는 모르겠습니다.) 일단 이 함수를 통해서 원하는 프로그램의 handleCletEvent 함수로 type,param1,param2 를 전달한다는 것 같은데(흡사 윈도우 메세지를 던지듯이^^), 솔직히 왜 이게 그래픽 API 에 있는지는 모르겠습니다. 정말 의도 파악이 안되므로 이정도만 이야기하고 넘어가도록 하겠습니다. 일단 이 강좌의 범위에서는 몰라도 되는 API 같은 느낌이 드네요. /* 그저 스펙 문서보고 API를 정리하는 단계에서 포함되어 버린 것이죠...흠...왜 grp일까요^^ */


본격적으로 문자 입력 API를 알아보겠습니다. 문자 입력 API 는 MC_im 으로 시작하며, im은 대개 input method 를 의미합니다. /* 흔히 IME 라는 단어로 보셨을텐데, IME는 Input Method Editor 를 의미합니다. 입력기 자체를 구현하면 IME라고 부르는게 맞겠지만, 문자 입력 API는 방법을 제공하는거니 아마 im 이라고 이름을 정한 것 같네요. */

  1. M_Int32 MC_imHandleInput(char key, M_Int32 type, char *buf1, M_Int32 *size1,char *buf2, M_Int32 *size2);
    M_Int32 MC_imSetCurrentMode(M_Int32 mode);
    M_Int32 MC_imGetCurrentMode( void );
    M_Int32 MC_imGetSurpportModeCount( void );
    M_Char** MC_imGetSupportedModes( void );

문자 입력 API는 위의 5개 API가 전부입니다. 간단한가요^^. 일단 이름도 쉬워보이고 하니 간단해 보입니다만, 실상 주의해야 할게 산더미 같은 API 이므로 만약 Clet이 제공하는 사용자 인터페이스 컴포넌트(uic)의 텍스트 박스를 사용하지 않고 이 API로 직접 문자 입력을 구현하실꺼라면 제가 설명하는 내용 이상으로 많은 테스트로 주의깊게 만드시길 당부 드리겠습니다. /* 이렇게 시작하는 이유는 이 API의 경우 폰에서 구현해둔 부분(HAL 이라고 부르는)을 그대로 사용하는 API로 알려져 있으므로 폰마다 특성이 다를 수 있고 심지어 이녀석이 제대로 동작하니 않는 폰도 있을 수 있습니다. 더불어 폰도 이 API를 사용하는거나 마찬가지 일수도 있으므로 제대로 버퍼를 비워주지 않는다던가의 동작은 다른 입력 환경에까지 영향을 줄 수도 있는 API 이기 때문에 더욱 그렇습니다. 즉 여러가지로 테스트를 해봐야 하는 것이 필수인 API 입니다. */


일단 각 함수의 사용법을 순차적으로 나열해 보겠습니다.

  1. MC_imGetSurpportModeCount 함수로 현재 환경이 지원하는 입력모드의 갯수를 얻어옵니다.
    /* 함수명 주의하세요. 스펠링이 틀린 API입니다. 틀린 함수명으로 써야합니다. --; */
  2. MC_imGetSupportedModes 함수로 각 입력모드를 얻어옵니다. 각 입력 모드를 나타내는 문자는 ISO 639 표준을 따르며, 대소문자가 필요한 영어의 경우 /S,/L등을 덧붙여서 구분합니다. 즉 영어 대문자는 "EN/L" , 영어 소문자는 "EN/S" 같이 표기됩니다.
    [참고] ISO 639 표준 리스트 : http://www.loc.gov/standards/iso639-2/php/code_list.php
  3. 원하는 입력 모드를 MC_imSetCurrentMode 에 파라메터 mode으로 전달합니다. 이때 입력모드라 함은 위의 함수로 얻은 입력 모드 문자열의 index순서를 의미합니다.
    반대로 현재 입력 모드를 얻어오려면 MC_imGetCurrentMode 로 현재 입력모드의 인덱스 값을 반환받습니다.
  4. 이제 입력되는 키 값을 MC_imHandleInput 에 넘깁니다.
    파라메터 key는 입력되는 키값이며, type은 입력 키 이벤트 타입을 넣어줍니다.
    buf1 은 완성된 문자열버퍼이며, 그 사이즈는 size1 으로 알 수 있습니다.
    buf2 는 조합중인 문자열버퍼이며, 그 사이즈는 size2 로 알 수 있습니다.

대략 위와 같은 순서로 동작하면 됩니다.

일단 입력 모드라는게 어떤 것인가 실제 에뮬레이터는 어떻게 출력하는지 보도록 하겠습니다.

  1. void startClet(int argc, char* args[])
    {  
        M_Int32 imMax;
        M_Char **imMode;
        M_Int32 i;

        imMax = MC_imGetSurpportModeCount();
        imMode = MC_imGetSupportedModes();

        MC_knlPrintk("Support IM Count [%d]\n",imMax);
        for(i=0; i<imMax; i++)
        {
            MC_knlPrintk("ID[%d] Name[%s]\n",i,imMode[i]);
        }
    }

특별한 내용은 없는 단순 알아보기용 코드이므로 바로 디버그 출력창에 출력된 내용을 확인하겠습니다.

clet6_10.png

아마 제 경험으로는 SKT 의 몇몇 폰에서만 이 API를 과거 실험해본 적이 있는데 전부 동일하게 위와 같은 문자열들이 나왔던 것으로 기억합니다. /* 이런 부분도 현업에서 사용할꺼라면 직접들 다 경험해보셔야 할 부분이 되겠습니다. ^^ */

EN/L 은 영어 대문자, EN/S는 영어 소문자, N123은 숫자, KO는 한글을 의미하겠죠. /* ISO 639는 기본적으로 2글자 모드와 확장인 3글자 표기 모드가 있습니다만, 뭐 아직 위피는 한국에서만 사용될테니...딱 저정도 나오지 않을까 싶습니다. 뭐 어디까지나 추측입니다. ^^ */

일단 에뮬레이터는 모든 모드를 다 지원한다고 나오는군요. 과연 그런걸까요^^ 사실 제가 과거 이 API를 테스트해봤던 좀 구형 SDK의 경우 KO 모드에서는 아무런 반응을 보이지 않아서 에뮬 실험을 위해 직접 만든 오토마타 함수를 넣어서 실험해봤던 기억이 있는지라....^^ 뭐 일단 이번 강좌는 신형 SDK를 쓰는거나 다름 없으므로 다시 한번 도전(^^)을 해보도록 하겠습니다. /* 오토마타란 간단히 입력과 출력의 상태만으로 변화를 보여주는 추상적인 기계라고 해야할까요. 간단한것 같으면서도 꽤나 심오할 수 있고, 모든 컴퓨터 언어 제작에 기본이 되는 이론이기도 합니다. 전공으로 괜히 한학기 내내 배우는게 아니겠죠. 일단 여기서는 한글의 조합을 위해 오토마타를 사용하기 때문에 오토마타라는 말이 나왔습니다만, 실제 MC_imHandleInput 이 내부 구현을 해주고 있기 때문에 직접 구현하시는게 아니라면 몰라도 관계없는 부분이기도 합니다. 관심 있으시다면 검색이나 책등을 통해서 배워보셔도 괜찮을 것 같구요. 다만 이제 막 프로그램을 시작하신 분들께는 권하지 않겠습니다. 어느정도 전산쪽에서 구르고 나신 후에 보시길 바랍니다. 행여나 제가 과거에 했던 경험처럼 직접 폰의 한글 입력기를 각 폰에 맞춰서(--;) 만들어 보시겠다면 어느정도 공부를 하시던가 최소한 어떤 룰을 이야기하는건지는 알아야 하실껍니다. 다행히 웹을 좀 뒤져보면 좋은 한글 오토마타 구현 방법론이 여럿 보이니(책도 있구요) 파보시겠다면 말리지는 않겠습니다.^^ */


아래 코드는 일단 강제로 한글 모드로 설정하고 입력을 시도해보는 코드입니다. 과연 잘 될지 해보도록 하죠.

/* 코드의 동작 방식에 대한 설명은 하지 않겠습니다. 어디까지나 실험을 해보기 위한 코드니깐, 일단 돌려보시고 한번 조금씩 고쳐보세요^^ */

  1. #include "WIPIHeader.h"

    MC_GrpFrameBuffer fb;
    MC_GrpContext     gc;
    M_Char            completeBuf[6],incompleteBuf[6];
    M_Int32           completeSize,incompleteSize;
    M_Char            inputText[128]; // 입력된 문자열을 저장
    void drawScreen() // 화면 갱신용 그리기 함수
    {
        M_Char string[256];
        MC_grpSetContext(&gc,MC_GRP_CONTEXT_FG_PIXEL_IDX,(void*)MC_grpGetPixelFromRGB(0,0,0));
        MC_grpFillRect(fb,0,0,MC_GRP_GET_FRAME_BUFFER_WIDTH(fb), MC_GRP_GET_FRAME_BUFFER_HEIGHT(fb),&gc);
       
        if (completeSize != 0) // 완성 문자열이 있는 것이니 저장함
        {
            strcat(inputText,completeBuf);
            completeBuf[0]=0;
            completeSize=0;
        }

        if (incompleteSize != 0) // 조합중인 문자열이 있는 경우 붙여서 출력해야 입력중인지 알겠죠.
        {
            incompleteBuf[incompleteSize]=0;    // 종성자음이 제대로 안 지워지니 이렇게 처리
            MC_knlSprintk(string,"[%s%s]",inputText,incompleteBuf);
        }
        else
            MC_knlSprintk(string,"[%s]",inputText);

        MC_grpSetContext(&gc,MC_GRP_CONTEXT_FG_PIXEL_IDX,(void*)MC_grpGetPixelFromRGB(255,255,255));
        MC_grpDrawString(fb,10,50,string,-1,&gc);

        MC_grpFlushLcd( 0, fb, 0, 0, MC_GRP_GET_FRAME_BUFFER_WIDTH(fb), MC_GRP_GET_FRAME_BUFFER_HEIGHT(fb) );
    }
    void startClet(int argc, char* args[])
    {  
        M_Int32 imMax;
        M_Char **imMode;
        M_Int32 i,koIndex;

        imMax = MC_imGetSurpportModeCount();
        imMode = MC_imGetSupportedModes();

        MC_knlPrintk("Support IM Count [%d]\n",imMax);
        koIndex = 0;
        for(i=0; i<imMax; i++)
        {
            MC_knlPrintk("ID[%d] Name[%s]\n",i,imMode[i]);
            if (strcmp(imMode[i],"KO")==0) koIndex = i;    // 별로 추천할만한 방법은 아닙니다.
        }
        MC_imSetCurrentMode(koIndex);

        fb=MC_grpGetScreenFrameBuffer(0);
        MC_grpInitContext(&gc);
       
        MC_grpSetContext(&gc,MC_GRP_CONTEXT_FONT_IDX,
            (void*)MC_grpGetFont(MC_GRP_FT_FACE_SYSTEM,MC_GRP_FT_SIZE_MEDIUM,MC_GRP_FT_STYLE_PLAIN));

        completeSize = 0;
        incompleteSize = 0;
        memset(completeBuf,0,6);
        memset(incompleteBuf,0,6);
        memset(inputText,0,128);

        drawScreen();  
    }

    void handleCletEvent(int type, int param1, int param2)
    {
        M_Int32 ret;
       
        completeSize = 6; // MC_imHandleInput에 던지기전에 버퍼 사이즈를 넣어줘야함 (중요)
        incompleteSize = 6;
        switch (type)
        {
        case MV_KEY_PRESS_EVENT: // 누를때만 처리합니다.
            ret = MC_imHandleInput(param1,type,completeBuf,&completeSize,incompleteBuf,&incompleteSize);
            MC_knlPrintk("[KEY]mode[%d],key[%d],[%s:%d],[%s:%d] (%d)\n",MC_imGetCurrentMode(),param1,completeBuf,completeSize,incompleteBuf,incompleteSize,ret);
            drawScreen();
            break;
        case MV_KEY_RELEASE_EVENT:       
            break;
        }  
    }
    // 이하 안쓰지만 그냥 필수이니 붙여둡니다.
    void pauseClet()
    {
    }
    void destroyClet()
    {
    }
    void resumeClet()
    {
    }
    void paintClet(int x, int y, int w, int h)
    {  
    }

풀 소스이므로 그냥 붙여서 바로 실행 시키면 될 것 입니다. 일단 제가 실험한 결과 화면을 보면서 살짝 설명을 해 보겠습니다.

clet6_11.png

'위피'라는 글자를 입력한 디버그 출력입니다. 마지막의 -5 값은 OK 값입니다.(그냥 마무리로 눌렸습니다.^^) 사실 위 코드에는 여러가지 문제가 숨어 있습니다. 그걸 찾아내는 것은 숙제로 남겨두고요~ 사실 동작을 아는데는 문제가 없으므로~

입력 과정을 보면 한글의 한 단어가 완성되기까지(즉 첫 글자 '위')는 계속 조합중인 버퍼에서 입력되고 있는 걸 볼 수 있습니다. 그런데 왜 완성된 글자 버퍼의 길이등도 처음의 설정한 값인 6을 계속 넣느냐...한번 코드에서 그 부분을 빼보시기 바랍니다. 어떻게 동작하는지^^ /* 괜히 넣는게 아닙니다.^^ */

또한 조합중인 버퍼를 출력할때 incompleteSize를 가지고 incompleteBuf에 강제로 종결자('\0'=0)을 넣는데, 이는 위 디버그 출력 밑에서 2번째줄을 보시면 [피ㅍ:2]라고 되어있는데, 만약 그냥 버퍼를 출력하면 지워졌어야 할 'ㅍ'가 찍혀버리게 되겠죠. 이런 이유 때문에 강제로 문자 종결자를 입력하는 것입니다.


이 코드는 입력기를 이렇게 만들 수 있다라는 것을 보여드리기 위한 코드입니다. 실제 폰에서 제대로 동작하는 입력기를 구현하시려면 꽤 여러가지 처리를 추가로 해주셔야 하고 더불어 커서 이동등을 고려한 입력기를 구현한다면 더더욱 많은 처리를 하셔야 겠죠. 이런 부분은 직접 응용해보시면서 알아내셔야 하는 부분이 될 것 같습니다. /* 특별히 힌트로 key값에는 입력되는 key값 이외에 MH_IMA_FLUSH 를 넣어야 하는 경우가 있습니다. 이 값의 역활은 현재까지 조합중인 버퍼를 전부 완성 버퍼로 밀어내주는 역활을 하는데요, 제대로 입력기를 구현하시려면 꼭 이 키값도 잘 사용 하셔야 합니다. 어떨때 넣어줘야 하는 걸까요.^^ 각자 화이팅하시길!!! */


정리

얼마 안되는 파트 같았는데, 은근히 짧게 설명하려고 노력했음에도 꽤 분량이 커져버렸네요. 뭐 그만큼 코드로 이해해야하는 부분이 많았다는 것이겠죠. 이로서 이제 화면상에 원하는 이미지를 팍팍 출력할 수 있게 되었습니다.(물론 여러가지 응용 기법에 대해서는 각자 더 공부하셔야 할 부분이겠지만요^^)


개인적으로 게임 개발에 관심을 가지게 된 것이 이런 비쥬얼한 느낌이 좋아서였다고 회상이 됩니다.(그전에는 기계들의 0,1신호와 놀았거든요~.~) 뭔가 눈에 보인다는 것은 개발하는 사람에게 꽤나 즉각적인 즐거움을 줄 수 있는 부분이기도 하고 왠지 뭔가 뿌듯한 느낌이 좀 더 들기도 하고 뭐 그런 거 아닐까 싶은데요. 아무튼 표현하는 법들을 배웠으니 이제는 데이터를 저장하고 읽고 하는 부분을 확인해야 할 차례라고 생각되네요.


다음 시간에는 Clet API중 Database 와 File System 에 대해서 살펴보도록 하겠습니다. 둘다 데이터를 읽고 쓰고 하는데 필요한 API 들이니 한번에 보는게 좋겠죠.


마지막으로 이번 강좌가 한주 밀려서 작성되어서 그만큼 기다려주신 몇몇분들께(직접적으로 언급해주신^^) 죄송과 기다려 주셔서 감사하다는 말씀을 전하며...앞으로는 가급적 매주 1회씩 전진하도록 노력하겠습니다. /* 이번에 밀린 것도 주말내내 작업 관련으로 살펴봐야 할게 너무 많아서 시간을 낼 수가 없었던 거예요ㅠㅠ 결코 농땡이 아니라구요^^ */


긴 글 읽으시느라 수고 많으셨고, 다음 글에서 뵙겠습니다.


본 내용은 크리에이티브 커먼즈 코리아 저작자표시-비영리-동일조건변경허락 2.0 대한민국 라이센스에 따라 이용하실 수 있습니다.

분문의 오류나 오타등의 문의는 문의 게시판에 해주시기 바랍니다. 본 내용은 지속적으로 업데이트 될 수 있습니다.^^

Email : juno@evermore.pe.kr



Posted by