지난 포스팅에서 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
)이고, 흰색을 반전하면 검은색이라는 사실은 알 것이라고 믿는다. 그리고 보통 우리가 색상반전된 이미지라 하면 대충 아래와 같은 괴이한 느낌을 떠올리고, 이미 알고 있다.
온통 푸른색 천지다. 무섭기까지하다. 생각해보면 원래 사진에서는 푸른색은 거의 없고 대부분 붉은 색 계통이었는데 다 사라지고, 어색한 푸른색들만 잔뜩 생겨버려 거부감이 들 정도다.
대충 과한 색은 좀 줄이고, 적었던 색은 과하게 늘렸다는 생각이 든다. 색상 반전이라는 말과 맞아 떨어지는 듯 하다. 검은색과 흰색의 관계를 생각하면 0x00
은 0xFF
로 만들어주어야 할 것같고, 그럼 0x01
은 0xFE
로 만들어주어야겠다. 보수를 찾아주면 될 것 같다!
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);
}
}