[Bitmap Project] Bitmap Library - Inversion / Contrast

 

지난 포스팅에서 GrayScale로 바꿔주는 gray 함수를 구현했었다.

1. 읽기, 쓰기, 탐색 환경 구현

이번에는 색상반전을 시켜주는 invert함수를 구현해보자. 결국 이번에도 색, 각 색 채널의 밝기를 조절하는 문제여서 큰 틀은 gray 함수와 동일하다. 우선 지난번에 구현한 함수들을 좀 참고해보자.

#include "bitmap.h"

void invertLess8(IMAGE* img){
    //TODO: processing
    return;
}
void invert8(IMAGE* img){
    for(RGBQUAD* p=((RGBQUAD*)(img->extra))+(img->bi.clrUsed-1);p>=(RGBQUAD*)(img->extra);--p){
        //process
    }
}
void invert16(IMAGE* img){
    for (int y=img->bi.height-1; y>=0; --y)
        for (int x=img->bi.width-1; x>=0; --x){
            RGB16* p = (RGB16*) IDX(img,x,y);
            //process
        }
}
void invert24(IMAGE* img){
    for (int y=img->bi.height-1; y>=0; --y)
        for (int x=img->bi.width-1; x>=0; --x){
            RGBTRIPLE* p = (RGBTRIPLE*) IDX(img,x,y);
            //process
        }
}
void invert32(IMAGE* img){
    for (int y=img->bi.height-1; y>=0; --y)
        for (int x=img->bi.width-1; x>=0; --x){
            RGBQUAD* p = (RGBQUAD*) IDX(img,x,y);
            // process
        }
}
void (*invfunc[])(IMAGE*) = {invertLess8,invert8,invert16,invert24,invert32};

int invert(char* src, char* dst){
    IMAGE img;
    
    if(!readImage(&img,src)) return 0;

    invfunc[img.sizPxl](&img);

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

이대로 사용하면 될 것 같다! invert함수를 위해 사용할 것이기 때문에 gray와 관련된 이름들을 invert와 관련된 이름으로 바꿔준 것 빼고 그 형태는 거의 동일하다고 볼 수 있다.

지난 번에 grayscale에서 R,G,B 각 채널의 평균을 구해 밝기 값을 구한 뒤, 세 채널에 모두 일괄적으로 그 밝기를 같게 적용하면 grayscale 효과가 난다는 것을 파악했듯이, 이번에도 좀 짱구를 굴려보자.

2. Color Inversion Processing

검은색(0x000000)을 반전하면 흰색(0xFFFFFF)이고, 흰색을 반전하면 검은색이라는 사실은 알 것이라고 믿는다. 그리고 보통 우리가 색상반전된 이미지라 하면 대충 아래와 같은 괴이한 느낌을 떠올리고, 이미 알고 있다.

귀신이다!

온통 푸른색 천지다. 무섭기까지하다. 생각해보면 원래 사진에서는 푸른색은 거의 없고 대부분 붉은 색 계통이었는데 다 사라지고, 어색한 푸른색들만 잔뜩 생겨버려 거부감이 들 정도다.

대충 과한 색은 좀 줄이고, 적었던 색은 과하게 늘렸다는 생각이 든다. 색상 반전이라는 말과 맞아 떨어지는 듯 하다. 검은색과 흰색의 관계를 생각하면 0x000xFF로 만들어주어야 할 것같고, 그럼 0x010xFE로 만들어주어야겠다. 보수를 찾아주면 될 것 같다!

pixel->rgbRed = 0xFF - pixel->rgbRed;
pixel->rgbGreen = 0xFF - pixel->rgbGreen;
pixel->rgbBlue = 0xFF - pixel->rgbBlue;

이렇게 각 채널별로 이렇게 적용하면 될 듯하다. 근데 이거 어디서 많이 본 듯하다. 이거 비트반전으로 될 듯하다.

p->rgbRed=~(p->rgbRed);
p->rgbGreen=~(p->rgbGreen);
p->rgbBlue=~(p->rgbBlue);

사실상 같은 연산이다. 16bit의 경우에는 이번엔 매우 간단히 끝날 것 같다. 555이든 565이든 모든 비트를 반전하면 끝날 일이라

RGB16* p = (RGB16*) IDX(img,x,y);
*p=~*p;

이렇게 처리하면 끝이다. (555야 남은 1비트가 어떻게 되어도 상관 없으니.)

3. 최종 코드

앞서 나온 것들을 종합해보았다.

#include "bitmap.h"

void invertLess8(IMAGE* img){
    //TODO: processing
    return;
}
void invert8(IMAGE* img){
    for(RGBQUAD* p=((RGBQUAD*)(img->extra))+(img->bi.clrUsed-1);p>=(RGBQUAD*)(img->extra);--p){
        p->rgbAlpha=~(p->rgbAlpha);
        p->rgbRed=~(p->rgbRed);
        p->rgbGreen=~(p->rgbGreen);
        p->rgbBlue=~(p->rgbBlue);
    }
}
void invert16(IMAGE* img){
    for (int y=img->bi.height-1; y>=0; --y)
        for (int x=img->bi.width-1; x>=0; --x){
            RGB16* p = (RGB16*) IDX(img,x,y);
            *p=~*p;
        }
}
void invert24(IMAGE* img){
    for (int y=img->bi.height-1; y>=0; --y)
        for (int x=img->bi.width-1; x>=0; --x){
            RGBTRIPLE* p = (RGBTRIPLE*) IDX(img,x,y);
            p->rgbtRed=~(p->rgbtRed);
            p->rgbtGreen=~(p->rgbtGreen);
            p->rgbtBlue=~(p->rgbtBlue);
        }
}
void invert32(IMAGE* img){
    for (int y=img->bi.height-1; y>=0; --y)
        for (int x=img->bi.width-1; x>=0; --x){
            RGBQUAD* p = (RGBQUAD*) IDX(img,x,y);
            p->rgbAlpha=~(p->rgbAlpha);
            p->rgbRed=~(p->rgbRed);
            p->rgbGreen=~(p->rgbGreen);
            p->rgbBlue=~(p->rgbBlue);
        }
}
void (*invfunc[])(IMAGE*) = {invertLess8,invert8,invert16,invert24,invert32};

int invert(char* src, char* dst){
    IMAGE img;
    
    if(!readImage(&img,src)) return 0;

    invfunc[img.sizPxl](&img);

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

꽤 싱겁게 끝났다.

4. 번외: 색상대비 강조? 증가?

4-1. 색상 차를 증가시킨다?

샘플로 쓴 이미지들이 공통적으로 노이즈가 많고, 좀 흐리멍텅하달까나. 맘에 안들어서 대비, contrast를 좀 증가시켜볼까 한다.

즉 밝은 색은 더 밝게, 어두운 색은 더 어둡게 처리하여 색의 차이를 극명히 하는 것이다. RGBQUAD를 우선 살펴보자. 한 채널당 1바이트이므로 최대 0xFF이며 그 절반은 0x7F이다. 기준 이하이면 0.85배, 기준 이상이면 1.15배를 하고, 오버플로우로 인한 오류를 방지하기 위해 0xFF를 넘었는지 확인하고 넘었으면 최대값으로 퉁쳤다.

RGBQUAD* p = (RGBQUAD*) IDX(img,x,y);
int temp=(p->rgbRed)*(p->rgbRed>0x7F ? 1.15 : 0.85);
p->rgbRed=(temp>0xFF ? 0xFF : temp);
temp=(p->rgbGreen)*(p->rgbGreen>0x7F ? 1.15 : 0.85);
p->rgbGreen=(temp>0xFF ? 0xFF : temp);
temp=(p->rgbBlue)*(p->rgbBlue>0x7F ? 1.15 : 0.85);
p->rgbBlue=(temp>0xFF ? 0xFF : temp);

이렇게 하면 될 것같다. 이제 각 색상깊이에 맞추어 이 방법으로 처리하도록 구현해주면 되겠다. 그리고 그렇게 한 결과물이다.

피망

를 contrast함수로 처리하여

시망

를 얻었다.

음… 물러터지기 시작한 피망이 나와버렸다… 뭔가 색상의 차이가 도드라지긴 했는데, 그림자 있는 부분이 상당히 어설프다. 한참 요리조리 조작을 해보다 큰 착오가 있었음을 깨달았다. 사실 1.15배 해준 것 만으로도 서로 다른 두 수의 차이가 1.15배 커진다. 그런데 절반 이하는 0.85배로 오히려 줄였으니 경계값(0x7F)을 사이에 둔 두 수의 차이는 의도한 바보다 커져버렸다.

0x65 x 0.85 = 0x55 (정수형 버림)
0x85 x 1.15 = 0x98 (정수형 버림)

이렇게 되면서 그림자의 경계, 즉 어두워지기 시작하는 부분의 경계가 매끄럽지 못하게 된 것이다. 수정하자.

4-2. 색상 차 증가 - 중앙값으로부터

어차피 매우 어둡거나 매우 밝은 부분은 티가 잘 나지 않는다. 그래서 중앙을 기준으로 색상 간의 차이를 늘려준 뒤(1보다 큰 배율은 두 수의 차이를 크게한다.) overflow와 underflow만 확인해주면 될 것같다.

short disperse(unsigned char value, unsigned char max, float factor){
    short result = value - (max/2);
    result*=factor;
    result+=(max/2);
    if(result>max) result=max;
    else if(result<0) result=0;
    return result;
}

중앙값으로부터 차이에 배율을 곱해주고, 다시 중앙값을 더해주었다. 즉 중앙값으로부터의 거리를 배율만큼 증가시킨것이다. 이후 overflow와 underflow를 검사해 문제될 경우 최댓값, 최솟값으로 그냥 퉁쳐버렸다. 결과를 구경하자.

잘 익은 피망

잘 익은 피망

4-3. contrast.c

contrast역시 gray, invert와 같은 형태로 구현하였다. 귀찮아서 생략한 것 뿐이다. disperse 함수의 오버헤드를 줄이기위해 매크로 함수로 바꿔주었다. 모든 값들은 unsigned char 범위에 존재해야 하지만, overflow를 고려하여 short형으로, underflow를 고려하여 signed 형으로 임시변수 temp를 선언했음을 염두해야한다.

#define DISPERSE(VAL,MAX,FAC) (((MAX)>>1)+(((VAL)-((MAX)>>1))*(FAC)))
#define INRANGE(VAL,MAX) (((VAL)>(MAX))?(MAX):(((VAL)<0)?0:(VAL)))

void contrastLess8(IMAGE* img){
    //TODO: processing
    return;
}
void contrast8(IMAGE* img){
    for(RGBQUAD* p=((RGBQUAD*)(img->extra))+(img->bi.clrUsed-1);p>=(RGBQUAD*)(img->extra);--p){
        short temp=DISPERSE(p->rgbRed,0xFF,1.5);
        p->rgbRed=INRANGE(temp,0xFF);
        temp=DISPERSE(p->rgbGreen,0xFF,1.5);
        p->rgbGreen=INRANGE(temp,0xFF);
        temp=DISPERSE(p->rgbBlue,0xFF,1.5);
        p->rgbBlue=INRANGE(temp,0xFF);
    }
}
void contrast16(IMAGE* img){
    short r,g,b;
    if(img->bi.compression){
        for (int y=img->bi.height-1; y>=0; --y)
            for (int x=img->bi.width-1; x>=0; --x){
                RGB16* p = (RGB16*) IDX(img,x,y);
                r=DISPERSE(R565(*p),0x1F,1.3);
                g=DISPERSE(G565(*p),0x3F,1.4);
                b=DISPERSE(B565(*p),0x1F,1.3);
                *p=RGB565(INRANGE(r,0x1F),INRANGE(g,0x3F),INRANGE(b,0x1F));
            }
    }
    else{
        for (int y=img->bi.height-1; y>=0; --y)
            for (int x=img->bi.width-1; x>=0; --x){
                RGB16* p = (RGB16*) IDX(img,x,y);
                r=DISPERSE(R555(*p),0x1F,1.3);
                g=DISPERSE(G555(*p),0x1F,1.3);
                b=DISPERSE(B555(*p),0x1F,1.3);
                *p=RGB555(INRANGE(r,0x1F),INRANGE(g,0x1F),INRANGE(b,0x1F));
            }
    }
}
void contrast24(IMAGE* img){
    for (int y=img->bi.height-1; y>=0; --y)
        for (int x=img->bi.width-1; x>=0; --x){
            RGBTRIPLE* p = (RGBTRIPLE*) IDX(img,x,y);
            short temp=DISPERSE(p->rgbtRed,0xFF,1.5);
            p->rgbtRed=INRANGE(temp,0xFF);
            temp=DISPERSE(p->rgbtGreen,0xFF,1.5);
            p->rgbtGreen=INRANGE(temp,0xFF);
            temp=DISPERSE(p->rgbtBlue,0xFF,1.5);
            p->rgbtBlue=INRANGE(temp,0xFF);
        }
}
void contrast32(IMAGE* img){
    for (int y=img->bi.height-1; y>=0; --y)
        for (int x=img->bi.width-1; x>=0; --x){
            RGBQUAD* p = (RGBQUAD*) IDX(img,x,y);
            short temp=DISPERSE(p->rgbRed,0xFF,1.5);
            p->rgbRed=INRANGE(temp,0xFF);
            temp=DISPERSE(p->rgbGreen,0xFF,1.5);
            p->rgbGreen=INRANGE(temp,0xFF);
            temp=DISPERSE(p->rgbBlue,0xFF,1.5);
            p->rgbBlue=INRANGE(temp,0xFF);
        }
}

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)