[Bitmap Project] Bitmap Library - toASCII

 

처음 bitmap project를 시작하겠다고 했을 때 원래 계획은 bitmap 파일을 읽어 각 픽셀을 적절한 ASCII 문자와 대응시켜, 멀리서 텍스트파일을 보면 원래 비트맵파일의 그림같다고 느끼게 하는 것이었다. 아래처럼 말이다.

******::**======++++++++++++++++++++++++++++++++++++**********::--++++++++++++==
********++======++++++++++++++++++++++++++++++++++++************--::++++++++==@@
********++======++++++++++++++++++++++++++++++++++++**************--**++++==@@@@
********++======++++++++++++++++++****++++++++++++++++************::::++%%@@@@@@
********++======++++++++++++++****::::::::++++++++++++**************--++@@@@@@@@
**++****++========++++++++++++++****::::--::**++====******************%%@@@@@@@@
++==****++========++++++++++++++****::::------**====****++++******++%%@@@@@@%%++
====++**++========++++++++++++******::----------++==****==%%++****==@@@@@@%%++**
====++**++========++++==++++++++****::------------++++**==@@++**++@@@@@@@@==****
====++**++========++++==++++++++**::::------------::****++==::--**@@@@@@%%******
====++**++========++++==++++++**::::::::::----------::****::----**@@@@@@++******
====**::++========**++==++++******::::::::::::::::::::::--------++@@@@==********
====**::++========****++++++********::::::::::::::::--------::::%%@@%%++********
====**::**========****++++++++****++++++**++**::::--------::**++@@@@==**********
====**::**========****++++++++++++========%%++::------::**++++%%@@%%++**********
====**::**========++::++++++++====%%%%====++::::::::++++**++%%@@@@==************
====**::**========++::++++++==%%%%%%@@%%++::::::::--++==++%%@@@@%%++************
====++::**========++**++++==%%@@%%%%%%++**::::::----**%%++@@@@@@%%**************
====++::**========++++++==%%%%@@%%%%++::::::::::----::%%==%%@@@@==**************
====++::**========++====%%%%%%@@%%==**::**++++::--::**%%==%%@@%%++**************
====++::**======++**==%%==%%@@@@%%**::==%%====**::++%%@@%%%%@@%%****************
====++::**======++==%%%%==%%@@@@++****++++******::++==@@%%==@@==**************::
==%%++::**==++++====%%%%==%%@@%%**++++**::::****--**++%%%%==%%++************::::
%%%%++::**==++++====%%@@%%==%%++++==++**::::**++--**++%%%%==%%************::----
%%%%++::**==++======%%@@==%%==++@@==++******++++::**==%%%%====**********::------
%%%%++::**============%%%%==++%%@@==++********++::**%%%%%%++++**********::------
%%%%++::**======%%========++==@@%%==++********::::++%%%%%%==************--------
%%%%++::**====%%%%====++**==%%@@%%==++++++++++**++==@@%%%%==**********::--------
@@%%++::**====%%%%++++++**%%@@@@%%%%==++********++%%@@%%%%++********++::--------
@@%%++::**====%%%%++++++==@@@@@@@@%%%%==++******==@@@@%%%%++********++::------::
@@%%++::**==%%%%%%==++++==%%@@@@@@@@%%==++****::**==@@%%%%++++++++++++::--::++%%
++%%++::**==%%%%%%====++++%%@@@@@@@@%%++++****::--::++%%%%++**++++==++::::++%%%%
++++++::**==@@@@%%%%%%==++==%%@@@@%%%%++******::::----++%%++****++++**--**======
%%++++::**==@@%%%%@@%%==++==%%@@@@%%==++********::------%%++****++**::--++======
%%++++::**%%@@%%%%@@%%==++==%%%%%%====++********::::--..++++****++**--::========
%%++++::**==@@@@%%@@%%%%====%%@@%%====++********::::----::++++**++**--**========
%%++++****==@@@@@@@@%%==%%%%@@@@====++************::------======++**::========%%
==**++****%%@@@@%%@@@@====@@@@%%====++************::::----++==%%%%::++%%%%====%%
%%****::**%%@@@@%%%%%%%%==@@@@%%==++++++************::----**======++==%%%%====%%
%%****::**%%@@%%%%%%%%%%%%%%%%==++++++++************::::--::====++++========%%%%

511×511 크기의 lena를 toASCII로 변환한 txt파일도 있다. 크기가 511×511이기 때문에 세로는 511줄이며 가로는 (모양을 맞추기위해) 그 511×2칸이기때문에 여기서 임베디드로 바로 보여주기는 어렵다. 위 링크를 통해 들어가 zoom out을 최대로 하면 lena여사가 보인다.

뭐 쨌든, 이제 본격적으로 시작해보자.

1. 전체적인 구조, 텍스트 파일 쓰기

이번엔 writeImage함수를 쓸 일이 없고 대신 텍스트 파일 쓰기를 구현해야겠다. toASCII라는 함수만 부르면 텍스트를 저장하도록 toASCII함수를 구현해보자.

int toASCII(char* src, char* dst){
    FILE *fpTXT;
    IMAGE img;
    if(!readImage(&img,src)) return 0;
    
    fpTXT = fopen(dst, "w");
    if (fpTXT == NULL){ freeImage(&img); return 0; }

    
    for (int y=img.bi.height-1; y>=0; --y){
        for (int x=0; x<img.bi.width; ++x){
            char c;
            //ASCII 문자를 얻는 처리과정
            fprintf(fpTXT, "%c%c", c, c);
        }
        fprintf(fpTXT, "\n");
    }
    fclose(fpTXT);
    
    freeImage(&img);
    return 1;
}

src에 저장되어있는 비트맵 파일경로를 통해 img에 불러오고 dst에 저장되어있는 텍스트파일 경로를 통해 텍스트파일을 쓸 준비를 한다. 이후 출력순서대로 픽셀을 읽어 그 순서대로 문자를 텍스트파일에 출력할 수 있도록 한다. 처리과정을 통해 문자를 얻어내면 2번 저장한다.(보통 문자가 세로로 길쭉해서 1번만 저장하면 마치 픽셀이 세로로 길쭉해진 것과 같아 결과물이 납작해보인다.) 한 행이 끝나면 개행문자를 삽입하면서 반복하다 끝내게 된다.

뭐… 우리가 앞서 보아왔던 것과 많이 달라보이지 않는다. 딱히 문제도 없어보이고. 이제 중요한건 어떻게 문자를 골라야 텍스트파일을 그림처럼 표현할 수 있을까?

2. 밝기 표현

앞선 예시들을 한번 살펴보아라. 색이나 글자크기등이 있는 RichText가 아니기 때문에 당연 흑백이다. 앞서 gray scale을 다룰때도 얘기했었지만, 결국 각 픽셀의 밝기의 차이가 곧 그림의 표현일 것이다. 각 채널로부터 픽셀의 밝기를 구하는 것 역시 grayscale을 다룰때 했었다.

p->rgbRed=p->rgbGreen=p->rgbBlue
    = ( (p->rgbRed) + (p->rgbGreen) + (p->rgbBlue) ) / 3;

좋다. 이제 그럼 밝기는 구할 수 있고, 해당 밝기수준에 대응하는 문자를 결정후 출력만 해내면 되겠다. 밝기수준에 대응하는 문자는 어떻게 결정할까

공백만 잔뜩있다면 하얀색, 또는 밝은 색이 표현 된것이고, ‘■’처럼 뭔가 까만색 가득한 문자가 많을수록 어둡거나, 검은색에 가까운 것으로 볼 수 있겠다. 하지만 ‘■’는 ASCII 코드에 없기때문에 이런 특수문자를 이용해 출력하면 머리가 아파질거 같다. ASCII라고 이미 명시하기도 했고, ASCII안에서 해결해보자.

하지만 코드표를 찾아봐도 뭐가 적당할지 딱히 감이 오지 않았다. Google에 Bitmap to ASCII라고만 검색해도 많은 자료들이 나오는데 이것저것 해보다 코딩도장 예시가 제일 잘 맞는 것 같아 이걸 그대로 차용했다.

char ascii[] = { '#', '#', '@', '%', '=', '+', '*', ':', '-', '.', ' ' };
RGBQUAD* pixel = IDX(????);
unsigned short gray = (pixel->rgbRed + pixel->rgbGreen + pixel->rgbBlue) / 3;
char c = ascii[gray * sizeof(ascii) / 256];

뭐 이런 식으로 구현해야겠다. 픽셀의 밝기는 0~255이므로 크기 256으로 나눠주면 대략적인 1에대한 밝기의 비율이 나오겠다. 거기에 sizeof(ascii)를 곱하면 밝기비율은 0~(sizeof(ascii)-1)의 범위로 조정된다.

3. char* ascii_n(IMAGE* img,int x, int y);

이미 많이 해봐서 알지만, 색상 깊이에 따라 접근 방식이 달라지게 되므로 각 색상깊이에 따라 처리하는 함수를 만들어야겠다. 예시는 24bit.

#include "bitmap.h"
char ascii[] = { '#', '#', '@', '%', '=', '+', '*', ':', '-', '.', ' ' };

char* ascii24(IMAGE* img, int x, int y){
    RGBTRIPLE* pixel = (RGBTRIPLE *)IDX(img,x,y);
    unsigned short gray = (pixel->rgbtRed + pixel->rgbtGreen + pixel->rgbtBlue) / 3;
    return ascii+(gray * sizeof(ascii) / 256);
}

함수 내부에 대해 설명할 것은 남아있지 않다. 각 함수들이 모두 ascii배열을 사용해야하므로 전역변수로 빼버렸다. 함수를 호출할때마다 매번 변수를 복사하는 일을 할 필요가 없으니 그냥 주소값만 넘겨줘서 알아서 처리하도록 만들었다. 이제 이걸

char* (*asciifunc[])(IMAGE*,int,int) = {asciiLess8,ascii8,ascii16,ascii24,ascii32};

int toASCII(char* src, char* dst){
    
    ...

    char* (*ascii)(IMAGE*,int,int) = asciifunc[img.sizPxl];
    for (int y=img.bi.height-1; y>=0; --y){
        for (int x=0; x<img.bi.width; ++x){
            char* c = ascii(&img,x,y);
            fprintf(fpTXT, "%c%c", *c, *c);
        }
        fprintf(fpTXT, "\n");
    }
    
    ...
    
}

이런 식으로 사용하면 되겠다.

4. 최종 코드

#include <stdio.h>
#include "bitmap.h"
char ascii[] = { '#', '#', '@', '%', '=', '+', '*', ':', '-', '.', ' ' };

char* asciiLess8(IMAGE* img, int x, int y){
    //TODO: processing
    return ascii;
}
char* ascii8(IMAGE* img, int x, int y){
    RGBQUAD* pixel = ((RGBQUAD*)(img->extra))+*(IDX(img,x,y));
    unsigned short gray = (pixel->rgbRed + pixel->rgbGreen + pixel->rgbBlue) / 3;
    return ascii+(gray * sizeof(ascii) / 256);
}
char* ascii16(IMAGE* img, int x, int y){
    RGB16* pixel = (RGB16*)IDX(img,x,y);
    unsigned short gray = img->bi.compression
        ? ( R565(*pixel) + G565(*pixel) + B565(*pixel) )
        : ( R555(*pixel) + G555(*pixel) + B555(*pixel) );
    return ascii+(gray * sizeof(ascii) / (img->bi.compression?  128 : 96));
}
char* ascii24(IMAGE* img, int x, int y){
    RGBTRIPLE* pixel = (RGBTRIPLE *)IDX(img,x,y);
    unsigned short gray = (pixel->rgbtRed + pixel->rgbtGreen + pixel->rgbtBlue) / 3;
    return ascii+(gray * sizeof(ascii) / 256);
}
char* ascii32(IMAGE* img, int x, int y){
    RGBQUAD* pixel = (RGBQUAD*)IDX(img,x,y);
    unsigned short gray = (pixel->rgbRed + pixel->rgbGreen + pixel->rgbBlue) / 3;
    return ascii+(gray * sizeof(ascii) / 256);
}
char* (*asciifunc[])(IMAGE*,int,int) = {asciiLess8,ascii8,ascii16,ascii24,ascii32};

int toASCII(char* src, char* dst){
    FILE *fpTXT;
    IMAGE img;
    if(!readImage(&img,src)) return 0;
    
    fpTXT = fopen(dst, "w");
    if (fpTXT == NULL){ freeImage(&img); return 0; }

    char* (*ascii)(IMAGE*,int,int) = asciifunc[img.sizPxl];
    for (int y=img.bi.height-1; y>=0; --y){
        for (int x=0; x<img.bi.width; ++x){
            char* c = ascii(&img,x,y);
            fprintf(fpTXT, "%c%c", *c, *c);
        }
        fprintf(fpTXT, "\n");
    }
    fclose(fpTXT);
    
    freeImage(&img);
    return 1;
}

mandrill의 toASCII 결과는 이렇게 생겼다.

여기에서도 확인할 수 있으니 직접 해보도록. 최대한 zoom out을 해보도록.

This work is licensed under Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.
(The excerpted works are exceptionally subject to a licence from its source.) Attribution-ShareAlike 4.0 International (CC BY-SA 4.0)