[Bitmap Project] Bitmap Library - Mirror

 

이번엔 좌우반전을 해보고자 한다. 여기에는 귀여운 앵무새를 예시로 써볼까 한다.

parrots_24.bmp

귀여운 앵무새들이 mirror라는 함수로 처리된 후 왼쪽을 바라보게 만들 것이다.

1. 뒤집기?

좌우반전이면 결국 왼쪽에 있는게 오른쪽으로 가고 오른쪽에 있는게 왼쪽으로 가야한다.

  • 가장 왼쪽에 있는 픽셀은 같은 높이의 가장 오른쪽에 있는 픽셀로,
  • 가장 오른쪽에 있는 픽셀은 같은 높이의 가장 왼쪽에 있는 픽셀로,
  • 그다음 오른쪽에 있는 픽셀은 같은 높이의 왼쪽에서 두번째 픽셀로,

… 뭐 이렇게 이동 시켜야 하지 않을까.

중앙선 기준 대칭으로 접근해야겠다. 또, 서로 대칭인 픽셀들은 서로 교환되고 있으니 swap으로 해결하자.

우리가 앞서,

for (int y=img.bi.height-1; y>=0; --y)
    for (int x=0; x<img.bi.width; ++x)

이런 식으로 접근해야 출력순서대로 순차적으로 픽셀들에 접근할 수 있음은 이야기했다.

for (int y=img->bi.height-1; y>=0; --y)
    for (int x=0; x<img->bi.width; ++x){
    	RGBQUAD* a = (RGBQUAD *)IDX(img,x,y);
        RGBQUAD* b = (RGBQUAD *)IDX(img,(img->bi.width-x-1),y);
        RGBQUAD tmp = *a;
        *a = *b;
        *b = tmp;
    }

순차적으로 접근하면서 대칭인 두 점을 swap하도록 구현하였다. 근데 이대로 구현을 하게되면 뒤집히지 않은채 결과가 나온다. 정확히는 두번 뒤집힌 것이다. x번째 원소를 a로 접근을 하겠지만, 순차적으로 쭉 접근시키면 나중엔 b로도 접근하지 않겠는가.

반 뚝 잘라서 0부터 중앙지점까지만 탐색하면, swap하면서 뒷부분도 자연스럽게 해결되겠다.

for (int x=0; x< (img->bi.width)/2); ++x)

된건가…? 하지만 우리는 항상 반으로 나눌때, 아니 반이 아니더라도 영역을 나눌때, 케이스를 나눌때 그 경계 값이 우리의 의도와 맞아 떨어지는지 확인해야 한다. 꼭.

뭐 2로 나누니까 사실 홀짝이냐에 따라 갈리고, 그 안에서는 그 일반성이 유지될 것 같다. 대충 bi.width가 20과 21인 경우를 보자.

  1. 20의 경우, 원소는 0부터 19까지 있겠다. 그러면 0번 픽셀을 19번, 1번 픽셀을 18번으로 옮겨야 하겠다. 0~9 그리고 10~19로 정확히 10개씩 2개로 나뉜다. 즉 0부터 9까지 순회를 하면 10~19도 접근하게 된다. bi.width/2 == 10이다.

  2. 21의 경우, 원소는 0부터 20까지 있겠다. 그러면 0번 픽셀을 20번, 1번 픽셀을 19번으로 옮겨야 하겠다. 0~9 그리고 11~20로 정확히 10개가 2개로 나뉘고, 중간에 10이 있다. 즉 0부터 9까지 순회를 하면 11~20도 접근하게 된다. 10은 접근할 필요가 없다. 10은 기준선이 되기 때문에 대칭점이 곧 자기자신이니… bi.width/2 == 10이다.

두 경우 모두 어쨌든 0부터 bi.width/2 직전까지 순회하면 된다는 결론을 얻었다.

for (int x=0; x< (img->bi.width)/2); ++x)

이렇게 해도 되겠지만, 반복문 조건확인시마다 bi.width에 접근하고 나눗셈 연산을 하는건 매우 거슬리니,

for (int x=((img.bi.width>>1)-1); x>=0; --x)

이렇게 역순으로 해결하고, 2로 나누는 건 쉬프트 연산으로 바꿔주자. 어차피 컴파일러 최적화가 있어서 똑같겠지만, 비트연산이 더 빠를테니. 그럼,

for (int y=img->bi.height-1; y>=0; --y)
    for (int x=((img.bi.width>>1)-1); x>=0; --x){
    	RGBQUAD* a = (RGBQUAD *)IDX(img,x,y);
        RGBQUAD* b = (RGBQUAD *)IDX(img,(img->bi.width-x-1),y);
        RGBQUAD tmp = *a;
        *a = *b;
        *b = tmp;
    }

이렇게 구현하면 될 것 같다.

2. 적용

앞서 GrayScale이나 Inversion의 경우 색상테이블이 있는 8bit의 경우 색상테이블만 조작하였었다. 이번에도 그럴까? 이번에는 색상에 대한 조작은 없고, 픽셀데이터들이 통채로 교환되어야 하는 상황이다. 0번 픽셀이 색상테이블 3번을 가리키고 있으면, bi.width-1번 픽셀이 색상테이블 3번을 가리키게 해야할 것이다.

그러니 그냥 픽셀데이터들을 옮기기만해도 충분하겠다.

void swapLess8(IMAGE* img, int x, int y){
    //TODO: processing
    return;
}
void swap8(IMAGE* img, int x, int y){
    unsigned char* a = (unsigned char *)IDX(img,x,y);
    unsigned char* b = (unsigned char *)IDX(img,(img->bi.width-x-1),y);
    unsigned char tmp = *a;
    *a = *b;
    *b = tmp;
}
void swap16(IMAGE* img, int x, int y){
    RGB16* a = (RGB16 *)IDX(img,x,y);
    RGB16* b = (RGB16 *)IDX(img,(img->bi.width-x-1),y);
    RGB16 tmp = *a;
    *a = *b;
    *b = tmp;
    
}
void swap24(IMAGE* img, int x, int y){
    RGBTRIPLE* a = (RGBTRIPLE *)IDX(img,x,y);
    RGBTRIPLE* b = (RGBTRIPLE *)IDX(img,(img->bi.width-x-1),y);
    RGBTRIPLE tmp = *a;
    *a = *b;
    *b = tmp;
}
void swap32(IMAGE* img, int x, int y){
    RGBQUAD* a = (RGBQUAD *)IDX(img,x,y);
    RGBQUAD* b = (RGBQUAD *)IDX(img,(img->bi.width-x-1),y);
    RGBQUAD tmp = *a;
    *a = *b;
    *b = tmp;
}
void (*swapfunc[])(IMAGE*,int,int) = {swapLess8,swap8,swap16,swap24,swap32};

이렇게 각자의 swap함수를 먼저 구현하였다. 각자의 색상깊이에 맞는 자료형 포인터에 연결시킨후 swap하는게 전부이다. 그러고,

int mirror(char* src, char* dst){
    IMAGE img; 
    
    if(!readImage(&img,src)) return 0;
    void (*swap)(IMAGE*,int,int) = swapfunc[img.sizPxl];

    for (int y=img.bi.height-1; y>=0; --y)
        for (int x=((img.bi.width>>1)-1); x>=0; --x)
            swap(&img,x,y);

    if(!writeImage(&img,dst)) return 0;
    freeImage(&img);
    return 1;
}

이렇게 swap함수를 사용해주면 될 것 같다. 매번 img.sizPxl을 통해 함수포인터 배열원소 접근을 하지 않도록 함수포인터변수 swap을 따로 만들어 해결했다. IMAGE img로 읽어들이고, 다시 쓰는거야 뭐 별 특별할 것도 없다.

자 그래서 우리 앵무새는 몸을 돌렸을까?

24_mirror.bmp

잔짜잔!

이제 귀여운앵무새들이 왼쪽을 바라본다!

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)