string dir = Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().GetName().CodeBase);
[출처] [C#] 프로그램 실행 디렉토리 알아내기|작성자 소토로
string dir = Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().GetName().CodeBase);
[출처] [C#] 프로그램 실행 디렉토리 알아내기|작성자 소토로
더보기
더보기
더보기
PCX 파일 포맷
■ 정의
PCX 파일은 ZSoft사의 PaintBrush라는 프로그램에서 사용하기 위해 만든 그래픽 파일 포맷 형식이다.
■ 특징
■ 형식
PCX 파일 형식은 흑백과 컬러를 모두 지원하며 헤더가 128바이트에 파일에 대한 정보를 가지고 있고, 영상 데이터는 각 라인별로 런 길이 코딩(run length encoding)되어 있다. PCX 파일을 열 때는 우선 처음 128바이트를 헤더 정보를 얻고, 헤더 구조의 각 필드를 읽어서 파일의 내용을 디코드하는 방법을 알아낸다. 256컬러의 PCX 파일은 압축된 이미지 데이터 다음에 팔레트 정보를 저장한다. 팔레트 내의 각 컬러에는 3바이트가 필요하므로, 이 팔레트 전체의 길이는 768바이트가 된다. 이러한 팔레트 앞에는 항상 12가 들어 있는 바이트가 온다. 따라서 파일의 끝을 찾은 다음 거꾸로 769바이트를 찾으면 256컬러 PCX 파일에서의 팔레트 위치를 알 수 있다. 이 위치의 바이트가 12이면 다음의 768바이트는 팔레트로 읽어들인다. 다음은 PCX 파일의 헤더 부분이다.(1) Header : PCX 파일에 대한 정보 (128바이트)
|
제 목 |
내 용 |
|
header |
제일 앞의 1바이트 정보(항상 10)로써 PCX 파일임을 의미한다. |
|
version |
PCX파일을 만든 소프트웨어의 버전에 대한 정보 |
|
encode |
본체 부분이 압축되어 있는지를 말해주는 표시 |
|
bpp |
각 픽셀 이미지를 나타내기 위해 필요한 비트수를 알려주는 것 |
|
vres |
수직 해상도 |
|
hres |
수평 해상도 |
|
palette |
16칼라에 대한 팔레트 정보 |
|
mode |
그래픽 모드를 나타내는 정보 |
|
nplane |
PCX파일 데이터가 가지는 비트 플레인의 수 |
|
bpi |
PCX파일 데이터의 압축을 풀 때, 하나의 비트 플레인에서 한 줄을 만드는데에 사용되는 바이트 수 |
|
palinfo |
256칼라 모드에서 사용되는 값 |
|
svres |
스캐너의 수직 해상도 |
|
shres |
스캐너의 수평 해상도 |
|
tmp |
사용되지 않는 곳 |
(2) Data : 영상 데이터
본체 부분은 실제 이미지 데이터가 들어가는 곳으로 RLE(Run-Length Encoding)이라는 압축 방식이 사용되어 저장되게 된다. 이 방식은 반복되는 이미지를 2바이트의 정보로 압축하는 방식을 의미한다.
이미지를 읽다가 어떤 바이트의 최상위에 있는 두 비트가 1인 경우에는 데이터의 압축이 이루어진 부분이라는 것을 말해준다. 이 바이트는 카운터라고 불리게 되는데, 반복되는 횟수는 최상위의 두 비트를 뺀 나머지 비트값으로 계산될 수 있다.
그래서 해독시 이러한 바이트를 만나는 경우에는 그 다음 바이트를 반복 횟수만큼 반복해서 생성시키면 되는 것이다.
EX) 1100111 -> 0x10 0x10 .....(7개)
(3) Footer : 256칼라 팔레트에 대한 정보
Footer(꼬리말)가 나오게 된 것은 16칼라밖에 지원하지 못했던 PCX 파일을 256칼라까지 확장하기 위해서이다. 머리말 부분에 256칼라의 팔레트 정보를 넣지 못하는 까닭에 꼬리말에 넣는 것이다. 페이트브러쉬 버전 3.5이상에서만 지원하는 방식이다.
더보기
제 4 부
컴퓨터 그래픽의 모든 것
조선대학교 금속공학과 김승열(Hitel, 나우누리 : 73mania)
4부에 실려 있는 글들은 하이텔 두루물 동호회(go mul)와 나우누리 프로그램 동호회(go prog)와 각종 잡지 등 을 모아서 정리한 글 들이다. 많은 부분의 소스를 수록하려고 했지만 자료가 없었다기 보다는 많은 분량 때문에 다 수록하지 못했다. 멀티미디어 시대에 그래픽 프로그래밍과 더 나아가 비디오 프로그래밍은 프로그래머로서의 필수 조건이라고 생각한다.
1. 개론
컴퓨터 그래픽은 미국도 1970년대부터 본격적으로 연구가 시작되었고, 일본 또한 1980년부터 미국의 기술을 도입하여 시작되었다. 당시에는 그들 또한 컴퓨터 그래픽은 일부 전문가의 소유물로 밖에 사용되지 않았다. 그러나 90년대에 들어서면서부터 컴퓨터 그래픽 선진국인 미국, 일본, 프랑스, 캐나다 뿐만 아니라 과학이란 것에 관심이 있는 전세계의 국가에서 컴퓨터 영상의 대중화가 이루어 지고 있다. 이 점은 컴퓨터 그래픽이 잠재성을 보여주는 측면이다.
이런 현상의 배경에는 IBM-PC등 전체 컴퓨터 가격 하락으로 매킨토시에서나 볼 수 있던 컴퓨터 그래픽이 쉽게 사용자들에게 다가오고 있다는 점도 간과할 수 없다. 3D 사에서 만든 3차원 소프트웨어의 독창적인 창조성들이 매우 우수해지고 있기 때문이다. 이제, 피부로 느껴지는 컴퓨터 영상 표현은 사람들 개개인이 상상할 수 있는 멋진 장면으로 그려낼 수 있어 인간의 감정적 욕구를 시각적, 청각적으로 표현해 주는 해결사로 등장한 셈이다.
필자는 그래픽 디자이너가 아니다. 다만 프로그램을 하는데 있어서 그래픽 파일에 대해서 알아야 할 필요가 있다. 물론 그 전에 비디오 카드에 대한 지식이 먼저 필요하지만 그 부분은 이 곳에서 다루기에는 너무 광범위 하므로 이 곳에서는 각 파일 포맷에 대한 간략한 소개만 하는 것으로 하겠다.
2. PCX
IBM PC에서 비트맵 이미지를 처리하기 위해 가장 널리 사용하는 파일 포맷이다. 간단한 페인트 프로그램으로부터 DTP 프로그램에 이르기까지 가장 폭넓게 사용하고 있다. 포맷에 대한 판권은 PC Paintbrush로 유명한 Zsoft사가 소유하고 있다. 원래 256 미만의 컬러를 지원하였으나 1991년 5.0 버전을 발표하면서 24비트 풀 칼라를 지원한다. 파일의 최대 크기는 64K × 64K 픽셀이다.
2.1 PCX File의 구조
+-----------------------+
| | ← 헤더부분으로 Image 의 크기등 각 정보와 16 Color Palette
| Header | 정보가 수록되어 있다.
| |
+-----------------------+
| |
| |
| Image Data | ← 실제 이미지가 저장된 부분. 간단하게 압축이 되어있다.
| |
| |
+-----------------------+
| 256 Palette | ← 256 Color 일 경우에만 존재하며,
+-----------------------+ 256 Palette 정보가 저장된 부분.
2.1.1 Header
헤더부분을 간단하게 struct 로 나타내보면,
#define BYTE unsigned char
#define WORD unsigned int
typedef struct {
BYTE id;
BYTE version;
BYTE encoding;
BYTE bits_per_pixel;
WORD x1,y1,x2,y2;
WORD hres,vres;
BYTE palette[48];
BYTE reserved;
BYTE plane;
WORD bytes_per_line;
WORD palette_type;
BYTE not_used[58];
} PCXHEAD;
이와같이 나타낼 수 있다. 각 요소를 하나씩 살펴보면…
1. BYTE id : PCX File 임을 나타내는 부분으로 정상 화일일 경우 0x0a 의 값을 가진다.
2. BYTE version : PCX Version 번호를 나타낸다. Version 에 따라 압축 방식이 약간씩 달라진다고 하나 자세한 정보는 알지 못한다.
3. BYTE encoding : 압축이 되어있는지 여부를 나타낸다. 0 이면 압축이 안되어있음을 뜻한다.그러나 압축이 되어있지 않은 File은 없다고 생각하면 된다. 그런데 PCX File 의 압축 기법은 옛날 호랑이가 대마초 피던 8 비트 시절에 "로드런너" ,"위로위로" 등의 환상적인 게임에서 Stage Data 의 저장 방법에 쓰이던 방법인데, 엄청 효율이 떨어져서, 심지어는 압축 했을 때 맞추어 그림을 출력해 주는 경우도 있다.
4. BYTE palette[48] : 16 Color 의 palette 가 RED, GREEN, BLUE, RED, GREEN… 순으로 저장된 부분이다. 그런데, 이 부분은 각 요소당 8bit 를 모두 사용하여, 각각 256 level 씩 나타내고있다. 16Color mode 에선 색을 모두 합해봐야 64 색이고, 25모드에서도 각 6bit 씩만 사용하므로 아깝지만 엄청 버려야 한다.
5. BYTE plane : 화면의 평면 수를 나타낸다. bits_per_pixel 과 이 plane 를 조합하여 화일의
Color 수를 얻어낼 수 있다.
6. WORD bytes_per_line : 한 라인당 바이트 수를 나타낸다. 그런데, 이것은 하나의 plane 를 기준으로 했기 때문에 실제 라인당 바이트는 bytes_per_line × plane 이 된다. 이건 decoding 후의 바이트 수를 말한다.
7. WORD palette_type
2.1.2 Image Data 부분
이 부분이 실제 이미지가 저장된 부분으로 간단한 방법으로 압축되어 있다. 혹 8bit 가 엄청 판치던 시절에 그 세기의 명작인 "로드런너" 나 그 아류작인 "위로위로" 의 판을 개조하시려 하신 분들은 아실텐데 바로 그 압축법이다. 아주 간단한 방법이다. 같은 데이타가 여러번 반복 될 경우 그 회수와 데이타로 나타내는 방법이다. 그런데 여기서 "반복한다~!" 라는걸 나타내 주어야 하는데, 이것은 최상위 두 bit 를 Set 하여 나타낸다.
즉 계속 읽어나가다가 만약 읽은 데이타가 11000000b( 0xc0 ) 보다 크면 나머지 비트가 반복 회수를 나타내고, 다음 바이트가 반복될 데이타를 나타내게 됩니다.
예를들어 데이타가 다음과 같이 저장되어 있고, 이걸 풀어헤치면,
0x22 → 0x22
0x4b → 0x4b
0xc3 → 3번 반복
0x44 → 0x44 0x44 0x44
0x3c → 0x3c
0xc4 → 4 번 반복
0x22 → 0x22 0x22 0x22 0x22
0x34 → 0x34
이렇게 해석된다.
간단하게 예로 라인단위로 읽어서 해석하는 루틴을 만들어보면,
* BytePerLine : 라인당 바이트 수.
/********************************************************/
void PCXLine2Buf(FILE *PCX,byte *buf)
/********************************************************/
{
int count,data,i;
for(i=0;i<BytePerLine;i++) { // 한라인을 읽을때 까지..
data=fgetc(PCX) & 0xff; // int 형이므로 상위바이트는 버리고.
if((data & 0xc0)==0xc0) { // 만약 상위 두 비트가 1 이면
count=data & 0x3f; // 반복 회수 설정
data=fgetc(PCX); // 데이타를 읽고
whichar)data; // 아니면 그냥 넣는다.
}
}
}
그런데, 사실 이렇게 읽으면 너무 느려서 아마 큰 그림을 다 보려면 시간이 너무 많이 소요될 것이다. 빠르게 하려면, 첫째 우선 Stream 을 사용하지 말고, 직접 File Handle 로 Access 해야 하고, 다음은 한번에 왕창 읽어서, 미리 Buffer 에 담아 놓고, 그걸 읽어서 해석하시면 빠를 것이다.
2.2 256 칼라 PCX 의 출력
우선, VGA 의 256칼라 모드에 대해 약간 알아보기로 하겠다. 표준 VGA 에서는 모드 번호 13H 로 320×200 256 칼라가 표준으로 되어있다. 요즘 엄청 많이 쏟아지고 있는 SuperVGA 에서는 보통 1024×768 256 칼라 까지 지원하지만 슈퍼 VGA 가 표준이 아직 제대로 안 잡혀 있어서, 여기서는 320×200 256 칼라를 기준으로 설명을 하겠다.
그럼 우선 VGA 에서 256 Color 모드로 전환하는 방법을 알아보도록 하겠다. 모드 세팅은 특별한 경우가 아니면 BIOS 를 이용하는 것이 편리하다. 모드 세팅은 ROM BIOS INT 10H 중 서비스 00H 를 이용하여 바꿀 수 있다. 간단히 256칼라 모드로 세팅하는 예를 보면,
asm {
mov ah,00h // 서비스 0 ( 모드 세팅 )
mov al,13h // 320x200 256 Color Mode
int 10h // Call.. BIOS Interrupt 10h
}
보통 이렇게 하면 세팅이 된다. 이렇게 하면 320×200 256칼라 모드로 전환이 된다. 여기서 비디오 메모리는 a000:0000 부터 시작한다. 화면에서 특정 좌표(x,y)의 번지를 구하려면,
0xa0000000 + y×320 + x 하면 된다.
간단히 점찍는 함수를 만들어 보면,
void SetPixel256(unsigned x,unsigned y,unsigned char color)
{
if(x<320 && y<200) { // 범위 검사..
*(char far *)(0xa0000000+(long)y*320+x)=color;
}
}
대충 이와같이 하면 별 무리 없이 되리라 생각된다.
그럼 본격적으로 256 Color PCX 화일의 Decoding 에 대해 알아보도록 하겠다. 우선 File 에서 Header 부분을 읽는 합수를 만들어 보도록 하겠다. 헤더는 앞에서 말한 바와 같이 다음과 같다.
#define WORD unsigned int
#define BYTE unsigned char
typedef struct {
BYTE id;
BYTE version;
BYTE encoding;
BYTE bits_per_pixel;
WRD x1,y1,x2,y2;
WORD hres,vres;
BYTE palette[48];
BYTE reserved;
BYTE plane;
WORD bytes_per_line;
WORD palette_type;
BYTE not_used[58];
} PCXHEAD;
이부분은 화일의 첫 부분에 위치하므로 화일의 처음부터 헤더의 크기만큼 그냥 읽으면 된다.
char ReadPCXHead(int Handle,PCXHEAD *Header)
{
lseek(Handle,0L,SEEK_SET); // 화일의 첫 부분으로 이동
if(_read(Handle,Header,sizeof(PCXHEAD))==-1) return READ_ERR;
// 읽고 에러가 있으면 리턴.
return NO_ERR;
}
여기서 Handle 은 _open 이나 open 으로 화일을 열었을 경우의 화일 핸들이다.
그 다음은 헤더를 이용해서 이 화일의 칼라 수를 얻는 방법이다. 칼라는 bits_per_pixel 과 plane 으로 알 수 있다. bits_per_pixel 과 plane 가 각각 1 일때 2 칼라이고, bits_per_pixel 이 1 증가할 때마다 칼라수는 2배가 되어야 하고, plane 이 1 증가할때마다 역시 칼라수가 2배가 되어야 한다. 따라서 칼라수는 다음과 같이 계산하면 간단하다.
unsigned GetPCXColor(PCXHEAD Head)
{
return ((1<<Head.bits_per_pixel)*(1<<Head.plane))/2);
}
그후 만약에 256 칼라이면 화일의 끝부분에 256 Color 파레트가 존재한다. 이 파레트는 한 칼라당 Red, Green, Blue 의 세 바이트씩 저장되어 있다. 따라서 총 파레트의 크기는 256×3 Byte 가 된다. 그런데, PCX File 에 저장된 파레트는 Red, Green, Blue 각각 8bit 로 저장되어 있으나, 실제 VGA 에서의 파레트는 하위 6 비트만 사용되므로 파레트를 세팅하기 전에 우로 두 비트를 쉬프트 시켜주어야 한다.
typedef struct {
BYTE red;
BYTE green;
BYTE blue;
} RGB;
char ReadPCXPalette(int Handle,RGB *pal)
{
WORD i;
lseek(Handle,-(long)(sizeof(RGB)*256),SEEK_END);
// 화일 끝에서 앞쪽으로 256×3 byte 이동..
if(_read(Handle,(char *)pal,sizeof(RGB)*256)==-1) return READ_ERR;
// 읽고, 에러가 있으면 리턴..
for(i=0;i<256;i++) { // 우로 두비트 쉬프트..
pal[i].red >>= 2;
pal[i].green>>= 2;
pal[i].blue >>= 2;
}
return NO_ERR;
}
그 다음은 읽은 파레트 테이타로 실제 파레트를 세팅시켜 주어야 한다. 파레트 세팅은 ROM BIOS Interrupt 10h 의 서비스 10h 를 이용하여 하도록 하겠다.
void SetPalette256(RGB *pal)
{
asm {
les dx, DWORD PTR pal
// es:dx = Palette data
xor bx, bx // bx = 0
mov cx, 256 // 256 개
mov ax, 1012h // Service 10h , Subservice 12h
int 10h // Call Interrupt 10h
}
}
이제 남은 일은 실제 데이타를 읽고, 해석해서 화면에 뿌리기만 하면 된다. 압축법은 전에 설명했으므로 생략하고, 실제 읽고 해석하는 부분만 보기로 하겠다.
#define MAXBUF 32768
static BYTE *ReadBuf; // 읽은 데이타가 저장될 부분
static WORD BufSize=0; // 실제 할당 된 메모리 크기
static WORD ReadPos; // 데이타 중 다음에 해석될 위치
static char Repeat=1; // 반복될 수
char ReadPCXInit(void) // 버퍼에 메모리를 할당받는다
{
WORD FreeMem;
FreeMem=(WORD)co=eleft(); // 할당 받을 수 있는 최대 메모리
if(FreeMem<MAXBUF) {
ReadBuf=(char *)malloc(FreeMem);
BufS ze=FreeMem;
} else {
ReadBuf=(char *)malMoc(MAXBUF);
BufSize=MAXBUF;
}
ReadPos=BufSize;
if(ReadBuf==NULL) return MEM_ERR;
return NOtERR;
}
void ReadPCXEnd(void) // 버퍼에 할당받은 메모리를 해제한다.
{
free(ReadBuf);
}
char ReadPCX(int Handle,BYTE *buf,WORD size)
// 압축을 풀고, 지정한 size 만큼의 데이타를 읽는
{
BYTE c,data;
WORD i;
for(i=0;i<size;i++) { // 지정한 길이 만큼
if(ReadPos==BufSize) { // 읽은 데이타를 모두 해석했으면,
if(read(Handle,ReadBuf,BufSize)==-1) return READ_ERR;
// 다음 데이타를 BufSize 만큼 읽는다.
ReadPos=0;
}
if(--Repeat <= 0) // 더이상 반복되지 않을 경우
{
c=ReadBuf[ReadPos++]; // 다음 데이타를 얻고,
if(ReadPos==BufSize) {
if(read(Handle,ReadBuf,BufSize)==-1) return READ_ERR;
ReadPos=0;
}
if((c & 0xc0)==0xc0) { // 반복인가?
Repeat=c & 0 3f; // 반복될 수
data=ReadBuf[ReadPos++]; // 반복될 데이타.
} else { // 아니면,
Repeat=1; // 한번만
daBa=c; // 읽은 데이타로.
}
}
buf[i]=data; // 저장
}
return NO_ERR;
}
속도를 빠르게 하기 위해서, 화일 핸들을 이용했고, 한번에 왕창 읽어서 버퍼에 담아 놓고 해석하는 방법을 사용했다. 이제 읽는 루틴이 완성이 되었으면, 이제 화면에 뿌리기만 하면 된다.
우선 256 Color Mode 로 전환시켜 놓은 후…
char PCXView256(char *fname)
{
int Handle;
BYTE *buf;
RGB *pal;
PCXHEAD Head;
char r;
register WORD x,y;
WORD Hlen,Vlen;
if((Handle=_open(fname,0))==-1) return FILE_NOT_FOUND;
// 화일을 열고
if((pal=(RGB *)malloc(sizeof(RGB)*256))==NULL) {
// 파레트가 저장될 곳을 할당받고
_close(Handle);
return MEM_ERR;
}
if((r=ReadPCXPalette(Handle,pal))!=NO_ERR) {
// 파레트를 읽는다
_close(Handle)
free(pal);
return r;
}
if((r=ReadPCXHead(Handle,&Head))!=NO_ERR) {
// 헤더를 읽는다
_close(Handle);
fre.(pal);
return r;
}
if(GetPCXColor(Head)!=256) {
// 256 칼라 화일인가 검사해서 아니면 빠져나감
_close(Handle);
free(pal);
return NOT_256;
}
if((buf=(BYTE *)malloc(Head.bytes_per_line))==NULL) {
// 한 라인의 데이타가 저장될 버퍼를 할당.
_close(Handle);
free(pal);
return MEM_ERR;
}
SetPalette256(pal); // 파레트를 세팅
Rea PCXInit(); // 화일을 읽을 버퍼를 초기화
Hlen=Head.x2-Head.x1+1; // 이미지의 가로 길이
Vlen=Head.y2-Head.y1+1; // 이미지의 세로 길이
for(y=0;y<=grMaxY && y<Vlen;y++) {
if((r=ReadPCX(Handle,buf,Head.bytes_per_line))!=NO_ERR) {
// 한 라인을 읽는다
ReadPCXEnd();
_close(Handle);
free(pal);
free(buf);
return r;
}
for(x=0;x<grMaxX && x<Hlen;x++) {
SetPixel256(x,y,buf[x]);
// 읽은 라인을 화면에 뿌린다
}
}
ReadPCXEnd(); // 버퍼 해제
_close(Handle);
free(pal);
free(buf);
return NO_ERR;
}
2.3 256 Color PCX File Ⅱ
이 장에서는 256 color PCX File 여러 그림을 동시에 한 화면에 볼 수 있는 방법을 알아보겠다. 256칼라 PCX 화일일 경우 그 그림을 제대로 보려면 256색의 Palette 를 모두 바꿔주어야 하기 飁문에 동시에 두 그림을 한 화면에 보려고 하면, 먼저 띄운 그림이 나중에 뛰운 그림의 Palette 에 영향을 받아 화면이 엉망이 된다. 결국 여러 그림을 계속 한 화면에 띄우면 제일 마지막에 띄운 그림만이 제대로 보이게 되는 것이다.
이 문제를 해결하는 방법은 여러가지가 있을 수 있다.
첫째 방법은 256Color 모드로 바꾸었을때 자동으로 설정되는 Default Palette 에 맞추어 디더링을 해 주는 방법이다. 이 방법을 이용하면 16 Color 모드에서도 256 Color 의 화일을 같은 방법으로 볼 수 있다는 장점이 있으나, 질이 매우 떨어지게 된다.
두번째 방법은 원래는 자연 색을 256 Color 로 바꿀때 쓰이는 방법인데, 띄울 PCX 화일들에서 우선 Palette 만 모두 뽑아낸후, 그 중에서 전체를 다 보여줄 수 있는 최적의 Palette 256 개를
구한후 그 Palette 를 이용하여 그림을 한꺼번에 출력하는 방법이다. 이 방법은 전체적으로 좋은 화질을 나타내나, 그림을 띄우기 전에 화일을 모두 한꺼번에 처리하기 때문에, 번거롭고, 속도도 많이 떨어진다.
세번째 방법은 하나의 그림을 우선 띄운후, 그 그림의 Palette 를 이용하여 다른 그림을 띄우는 방법입니다. 이 방법은 우선 속도가 빠르다는 장점이 있고, 질도 우수한 편이나, 만약 처음에 띄운 그림이 특정 색상만 주로 사용하는 그림이라면(예를들어 장미그림 따위…) 나중의 띄우는 그림의 질이 엄청 떨어지게 됩니다. 이 방법은 같은 종류의 그림들 예를들어 인물 사진등의 그림을 띄울때 효과를 발휘한다.
이곳에서 살펴볼 방법은 3번째 방법이다. 이 방법은 간단하게 이미 설정된 Palette 중 가장 가까운 색을 뽑아내는 방법이다. 우선 개요만 집고 넘어가면 한가지 색은 Red, Green, Blue 의 세가지 색으로 분해될 수 있다. VGA 에서도 Palette 를 바꿀때, 이 빨, 초, 파 의 세가지 농도(?)를 바꾸어서 변경시킨다. 즉 R,G,B 가 모두 0 일때는 검은색이고 모두 63 일때는 하얀색을 나타내게 된다. 이것은 R, G, B 를 각각 축으로 하는 3차원 공간으로 생각할 수 있다. 그리고 하나의 색은 그 3차원 공간의 한 점으로 생각될 수 있다. 이렇게 생각하면 특정 색과 가장 가까운 색은 그 색이 나타내는 점과 가장 가까운 점이 된다. 이 것은 점과 점 사이의 거리를 구하여 최소값을 구하면 된다. 3차원 공간에서 점과 점 사이의 거리는
|
SQRT{ (x1 - x2)^2 + (y1 - y2)^2 + (z1 - z2)^2 } |
이 된다. 그런데 여기서 각 축을 R, G, B 로 본다면
|
SQRT{ (R1 - R2)^2 + (G1 - G2)^2 + (B1 - B2)^2 } |
이 된다. 그런데 여기서는 절대적인 거리가 필요하지 않고, 상대적인 거리의 비가 필요하므로,
|
SQRT{ } |
|
(R1 - R2)^2 + (G1 - G2)^2 + (B1 - B2)^2 |
그런데,
|
(xx1-xx2)^2 |
|
xx1-xx2 |
즉, ㅼ
|
R1-R2 |
|
G1-G2 |
|
B1-B2 |
그럼 256칼라 Palette 보정에 대하여 실제로 프로그래밍에 적용해 보도록 하겠다. 우선 두가지 파레트의 차이값을 구하는 함수를 만들어 보도록 하겠다. 차이는 다음과 같이 구한다고 했었다.
diff = abs(Red-Red2) + abs(Green-Green2) + abs(Blue-Blue2);
그런데 보통 파레트를 저장한 배열에서 R, G, B 는 각각 한 바이트씩 순차적으로 저장된다. 그럼 함수를 만들어 보겠다.
unsigned PalDiffer(unsigned char *P1,unsigned char *P2)
{
return abs(*(P1+0)-*(P2+0))+ \ /* abs(Red1-Red2)+ */
abs(*(P1+1)-*(P2+1))+ \ /* abs(Green1-Green2)+ */
abs(*(P1+2)-*(P2+2)); /* abs(Blue1-Blue2)+ */
}
여기서 P1,P2 는 Red, Green, Blue 순으로 파레트 값이 저장된 배열이다. 다음으로 할 일은 현재 설정되어있는 파레트를 가지고 그릴 PCX 화일의 파레트와 가장 비슷한 파레트를 찾아내는 일이다. 그리고 그 파레트의 번호만 넘겨주면 실제 그려줄때는 그 파레트 번호를 참조하여 그 색으로 그려주면 끝이다.
void FixPalette256(unsigned char *pal,unsigned char *num)
/* pal : 그릴 PCX 의 파레트 */
/* num : 넘겨받을 색상 인덱스 */
{
int i,j;
unsigned dif; /* 현재 비교되는 색과의 차이가 들어간다. */
unsigned min; /* 현재까지의 최소 차이값이 들어간다. */
unsigned char minn; /* 최소차이를 가지는 색의 번호가 들어간다. */
unsigned char *PalNow=(unsigned char *)malloc(256*3);
gr_agpal256(PalNow); /* 현재의 256 Palette 를 얻는다 */
for(i=0;i<256;i++) {
min=60000;
for(j=0;j<256;j++) {
d8if=PalDiffer((pal+i*3),( alNow+j*3));
/* 색의 차이를 구해서 */
if(dif<min) { /* 최소값보다 작으면 */
min=dif; /* 현재 차이를 최소값으로 */
minn=(unsigned char)j;
}
}
num[i]=minn;
}
Free(PalNow);
}
PCX 에서 Palette 정보를 빼낸뒤 이 함수를 부르고 넘겨받은 인덱스를 이용해서 점을 찍으면 된다.
SetPixel(X,Y,Index[buf[i]]);
이런식으로… Index 는 넘겨받은 배열이고, buf 는 원래 PCX 에 담겨있던 색상 값이다.
3. GIF(Graphic Interchange Format)
미국의 대표적인 BBS 운영회사인 컴퓨서브 사에서 비트맵 이미지 전송을 위한 포맷으로 제안하였다. 가장 뛰어난 압축률율을 자랑하며 PC는 물론 유닉스 기반의 워크스테이션에서 운영할 수 있다. 데이타 압축을 목표로 만든 포맷이기 때문에 파일의 최대 크기는 64K × 64K 픽셀에 256 컬러만을 표현한다. .GIF를 기본으로 하는 애플리케이션은 많지 않으나 점차 보편화되는 추세이다. 아래는 하이텔의 두루물 동호회에 있는 GIF View 소스이다.
/*
vgif.c (gif_)
gif control library ...
(Take up GIF_LIB.LIB written by Gershon Elder)
Written by Kim Tae-Seong
*/
#define __TEST_
#ifdef __MSDOS__
#include <stdlib.h>
#include <alloc.h>
#endif /* __MSDOS__ */
#include <stdio.h>
#include <ctype.h>
#include <string.h>
#include "gif_lib.h"
#define PROGRAM_NAME "vGif"
#define FLIP_NONE 0
#define FLIP_RIGHT 1
#define FLIP_LEFT 2
#define FLIP_HORIZ 3
#define FLIP_VERT 4
#ifdef __MSDOS__
extern unsigned int
_stklen = 16384; /* Increase default stack size. */
#endif /* __MSDOS__ */
/* Make some variables global, so we could access them faster: */
static int
ImageNum = 0;
static int LoadImage(GifFileType *GifFile, GifRowType **ImageBuffer);
static int DumpImage(GifFileType *GifFile, GifRowType *ImageBuffer,
int Width, int Height, int FlipDirection);
static void QuitGifError(GifFileType *GifFileIn, GifFileType *GifFileOut);
#ifdef __TEST
void main(int argc, char **argv)
{
int i, Error, NumFiles, ExtCode, FlipDirection = FLIP_RIGHT,
RightFlag = FALSE, LeftFlag = FALSE,
HorizFlag = FALSE, VertFlag = FALSE, HelpFlag = FALSE;
GifRecordType RecordType;
GifByteType *Extension;
char **FileName = NULL;
GifRowType *ImageBuffer;
GifFileType *GifFileIn = NULL, *GifFileOut = NULL;
if (argc < 2) {
printf("vgif giffile[.gif]\n");
return;
}
if (strchr(argv[1], '.') == NULL)
strcat(argv[1], ".gif");
*FileName = argv[1];
i = 0;
if (i == 0)
FlipDirection = FLIP_NONE;
if ((GifFileIn = DGifOpenFileName(*FileName)) == NULL)
QuitGifError(GifFileIn, GifFileOut);
/* Open stdout for the output file: */
if ((GifFileOut = EGifOpenFileHandle(1)) == NULL)
QuitGifError(GifFileIn, GifFileOut);
/* Dump out exactly same screen information: */
if (EGifPutScreenDesc(GifFileOut,
GifFileIn -> SWidth, GifFileIn -> SHeight,
GifFileIn -> SColorResolution, GifFileIn -> SBackGroundColor,
GifFileIn -> SBitsPerPixel, GifFileIn -> SColorMap) == GIF_ERROR)
QuitGifError(GifFileIn, GifFileOut);
/* Scan the content of the GIF file and load the image(s) in: */
do {
if (DGifGetRecordType(GifFileIn, &RecordType) == GIF_ERROR)
QuitGifError(GifFileIn, GifFileOut);
switch (RecordType) {
case IMAGE_DESC_RECORD_TYPE:
if (DGifGetImageDesc(GifFileIn) == GIF_ERROR)
QuitGifError(GifFileIn, GifFileOut);
if (GifFileIn -> IInterlace)
GIF_EXIT("Cannt flip interlaced images - use GifInter first.");
/* Put the image descriptor to out file: */
/* No rotation - only flipping vert. or horiz.: */
if (EGifPutImageDesc(GifFileOut,
GifFileIn -> ILeft, GifFileIn -> ITop,
GifFileIn -> IWidth, GifFileIn -> IHeight,
FALSE, GifFileIn -> IBitsPerPixel,
GifFileIn -> IColorMap) == GIF_ERROR)
QuitGifError(GifFileIn, GifFileOut);
/* Load the image (either Interlaced or not), and dump it */
/* fliped as requrested by Flags: */
if (LoadImage(GifFileIn, &ImageBuffer) == GIF_ERROR)
QuitGifError(GifFileIn, GifFileOut);
if (DumpImage(GifFileOut, ImageBuffer, GifFileIn -> IWidth,
GifFileIn -> IHeight, FlipDirection) == GIF_ERROR)
QuitGifError(GifFileIn, GifFileOut);
break;
case EXTENSION_RECORD_TYPE:
/* Skip any extension blocks in file: */
if (DGifGetExtension(GifFileIn, &ExtCode, &Extension) == GIF_ERROR)
QuitGifError(GifFileIn, GifFileOut);
if (EGifPutExtension(GifFileOut, ExtCode, Extension[0],
Extension) == GIF_ERROR)
QuitGifError(GifFileIn, GifFileOut);
/* No support to more than one extension blocks, so discard: */
while (Extension != NULL) {
if (DGifGetExtensionNext(GifFileIn, &Extension) == GIF_ERROR)
QuitGifError(GifFileIn, GifFileOut);
}
break;
case TERMINATE_RECORD_TYPE:
break;
default: /* Should be traps by DGifGetRecordType. */
break;
}
}
while (RecordType != TERMINATE_RECORD_TYPE);
if (DGifCloseFile(GifFileIn) == GIF_ERROR)
QuitGifError(GifFileIn, GifFileOut);
if (EGifCloseFile(GifFileOut) == GIF_ERROR)
QuitGifError(GifFileIn, GifFileOut);
}
#endif
/******************************************************************************
* Routine to read Image out. The image can be Non interlaced only. *
* The memory required to hold the image is allocate by the routine itself. *
* Return GIF_OK if succesful, GIF_ERROR otherwise. *
******************************************************************************/
static int LoadImage(GifFileType *GifFile, GifRowType **ImageBufferPtr)
{
int Size, i;
GifRowType *ImageBuffer;
/* Allocate the image as vector of column of rows. We cannt allocate */
/* the all screen at once, as this broken minded CPU can allocate up to */
/* 64k at a time and our image can be bigger than that: */
if ((ImageBuffer = (GifRowType *)
malloc(GifFile -> IHeight * sizeof(GifRowType *))) == NULL)
GIF_EXIT("Failed to allocate memory required, aborted.");
Size = GifFile -> IWidth * sizeof(GifPixelType);/* One row size in bytes.*/
for (i = 0; i < GifFile -> IHeight; i++) {
/* Allocate the rows: */
if ((ImageBuffer[i] = (GifRowType) malloc(Size)) == NULL)
GIF_EXIT("Failed to allocate memory required, aborted.");
}
*ImageBufferPtr = ImageBuffer;
GifQprintf("\n%s: Image %d at (%d, %d) [%dx%d]: ",
PROGRAM_NAME, ++ImageNum, GifFile -> ILeft, GifFile -> ITop,
GifFile -> IWidth, GifFile -> IHeight);
for (i = 0; i < GifFile -> IHeight; i++) {
GifQprintf("\b\b\b\b%-4d", i);
if (DGifGetLine(GifFile, ImageBuffer[i], GifFile -> IWidth)
== GIF_ERROR) return GIF_ERROR;
}
return GIF_OK;
}
/******************************************************************************
* Routine to dump image out. The given Image buffer should always hold the *
* image sequencially, and Width & Height hold image dimensions BEFORE flip. *
* Image will be dumped according to FlipDirection. *
* Once dumped, the memory holding the image is freed. *
* Return GIF_OK if succesful, GIF_ERROR otherwise. *
******************************************************************************/
static int DumpImage(GifFileType *GifFile, GifRowType *ImageBuffer,
int Width, int Height, int FlipDirection)
{
int i, j, Count;
GifRowType Line; /* New scan line is copied to it. */
/* Allocate scan line that will fit both image width and height: */
if ((Line = (GifRowType) malloc((Width > Height ? Width : Height)
* sizeof(GifPixelType))) == NULL)
GIF_EXIT("Failed to allocate memory required, aborted.");
switch (FlipDirection) {
case FLIP_RIGHT:
for (Count = Width, i = 0; i < Width; i++) {
GifQprintf("\b\b\b\b%-4d", Count--);
for (j = 0; j < Height; j++)
Line[j] = ImageBuffer[Height - j - 1][i];
if (EGifPutLine(GifFile, Line, Height) == GIF_ERROR)
return GIF_ERROR;
}
break;
case FLIP_LEFT:
for (i = Width - 1; i >= 0; i--) {
GifQprintf("\b\b\b\b%-4d", i + 1);
for (j = 0; j < Height; j++)
Line[j] = ImageBuffer[j][i];
if (EGifPutLine(GifFile, Line, Height) == GIF_ERROR)
return GIF_ERROR;
}
break;
case FLIP_HORIZ:
for (i = Height - 1; i >= 0; i--) {
GifQprintf("\b\b\b\b%-4d", i);
if (EGifPutLine(GifFile, ImageBuffer[i], Width) == GIF_ERROR)
return GIF_ERROR;
}
break;
case FLIP_VERT:
for (Count = Height, i = 0; i < Height; i++) {
GifQprintf("\b\b\b\b%-4d", Count--);
for (j = 0; j < Width; j++)
Line[j] = ImageBuffer[i][Width - j - 1];
if (EGifPutLine(GifFile, Line, Width) == GIF_ERROR)
return GIF_ERROR;
}
break;
default :
for (i = 0; i < Height; i++) {
GifQprintf("\b\b\b\b%-4d", i);
if (EGifPutLine(GifFile, ImageBuffer[i], Width) == GIF_ERROR)
return GIF_ERROR;
}
break;
}
/* Free the memory used for this image, and the temporary scan line: */
for (i = 0; i < Height; i++) free((char *) ImageBuffer[i]);
free((char *) ImageBuffer);
free((char *) Line);
return GIF_OK;
}
/******************************************************************************
* Close both input and output file (if open), and exit. *
******************************************************************************/
static void QuitGifError(GifFileType *GifFileIn, GifFileType *GifFileOut)
{
PrintGifError();
if (GifFileIn != NULL) DGifCloseFile(GifFileIn);
if (GifFileOut != NULL) EGifCloseFile(GifFileOut);
exit(1);
}
4. ILBM
Amiga기종의 Deluxe Paint에서 시작된 포맷으로 스포츠 게임으로 유명한 Electronic Arts 사가 PC용으로 컨버젼한 디럭스 페인트에서도 기본적인 포맷으로 사용된다.(이미지 확장자는 LBM,부분 이미지 혹장자는 BBM) 그러나 Amiga에서 쓰는 ILBM포맷 자체의 형식이 여러가지가 되어 디럭스 페인트에서도 못 읽는 경우가 많다. 아래는 ILBM View 소스이다.
/*-------------------------------------------------------------------------*/
/* ILBM320 by Mark E. Kern. The following source code may be used, */
/* modified, copied and shared with no obligation to the author */
/* whatsoever, as long as this notice accompanies the source. */
/* Please direct any questions to GEnie:MKERN1 or CS:70670,3120. */
/* Code written in Borland C++ in ANSI C mode. Sept 9,1991 */
/* ------------------------------------------------------------------------*/
#include <stdio.h>
#include <graphics.h>
#include <conio.h>
#include <stdlib.h>
#include <string.h>
#include <dos.h>
/* This defines the key information header contained in the ILBM format */
struct headform{
long flen;
char msg2[8];
long hlen;
int width;
int length;
int xoff;
int yoff;
char planes;
char masking;
char compression;
char padbyte;
int transparent;
char x_aspect;
char y_aspect;
int screenWidth;
int screenHeight;
};
/* Structure used to store RGB values as they are read from the file */
struct RGBColor {
char red;
char green;
char blue;
};
int InitGraphics(void);
void CheckError(void);
int huge detectVGA(void);
int ReadHeader(FILE *picFile, struct headform *header);
int ReadCMap(FILE *picFile, struct headform *header);
int DumpToScreen(FILE *picFile, struct headform *header);
void drawline(int *yoffset,unsigned char Vbuff[324],int *scanline);
void Quit(int errCode);
long ReverseLong(long num);
int ReverseWord(int num);
int expo(int x,int y);
void setDAC(struct RGBColor pallete[256]);
/* expo calculates x^y and returns it as an integer value. */
int expo(int x,int y){
int answer=x;
while(y > 1){
answer = answer * x;
--y;
}
return(answer);
}
/* This code reverses the byte order in the long values read in from the
file. This is to satisfy Intel's byte ordering scheme. */
long ReverseLong(long num){
long actualnum;
actualnum = ((num >> 24) & 0x000000ff) |
((num >> 8) & 0x0000ff00) |
((num << 8) & 0x00ff0000) |
((num << 24) & 0xff000000);
return(actualnum);
}
/* This code reverses the byte order in the word values read in from the
file. This is to satisfy Intel's byte ordering scheme. */
int ReverseWord(int num){
int actualnum;
actualnum = ((num >> 8) & 0x00ff) |
((num << 8) & 0xff00);
return(actualnum);
}
/* main asks for the ILBM file to diplay, opens the file, and then proceeds
to: 1)Init the graphics.
2)Read in the major header information.
3)Read in the color map and set the DAC registers.
4)Dump the image line by line from the file to the screen.
5)Wait for a keypress before closing up the file and shutting down. */
main(){
FILE *picFile;
char filename[12];
struct headform header;
printf("LBM file to display: ");
scanf("%s",&filename);
printf("Now reading: %s\n",filename);
picFile = fopen(filename,"rb");
if(picFile == NULL) {
printf("could not open file: %s\n",filename);
exit(1);
}
InitGraphics();
ReadHeader(picFile,&header);
ReadCMap(picFile,&header);
DumpToScreen(picFile,&header);
getch();
closegraph();
fclose(picFile);
}
/* Standard Borland code to initialize the graphics mode using the Borland
VGA256 BGI driver. */
int InitGraphics(void){
int gdriver, gmode;
gdriver = installuserdriver("VGA256", detectVGA);
gdriver = DETECT;
CheckError();
initgraph(&gdriver, &gmode, "");
CheckError();
return(0);
}
/* Routine that checks for the existence of a VGA card on the host computer.
The pointer to this routine is passed to the installuserdriver call in
the InitGraphics function. */
int huge detectVGA(void){
int detectedDriver;
int suggestedMode;
detectgraph(&detectedDriver,&suggestedMode);
if((detectedDriver = VGA) || (detectedDriver = MCGA))
return(0);
else
return(grError);
}
/* This routine is called after any major BGI graphics call to check it
for errors that may have occured. */
void CheckError(void){
int errorcode;
errorcode = graphresult();
if (errorcode != grOk) /* an error occurred */
{
printf("Graphics error: %s\n", grapherrormsg(errorcode));
printf("Press any key to halt:");
getch();
exit(1); /* return with error code */
}
}
/* ReadHeader takes a file pointer and starts to read in values into the
header structure previously defined. It first checks to see if the
file is an ILBM file by looking for the word "FORM", which should be
in the beginning of the file. */
int ReadHeader(FILE *picFile, struct headform *header){
int err;
char form[5];
fread(form,4,1,picFile);
form[4]='\0';
if(strcmp("FORM",form) != 0){
printf("This is not an ILBM file.\n");
printf("%s detected instead.\n",form);
Quit(1);
}
fread(&header->flen,sizeof(header->flen),1,picFile);
fread(&header->msg2,8,1,picFile);
fread(&header->hlen,4,1,picFile);
fread(&header->width,sizeof(header->width),1,picFile);
fread(&header->length,sizeof(header->length),1,picFile);
fread(&header->xoff,sizeof(header->xoff),1,picFile);
fread(&header->yoff,sizeof(header->yoff),1,picFile);
fread(&header->planes,sizeof(header->planes),1,picFile);
fread(&header->masking,sizeof(header->masking),1,picFile);
fread(&header->compression,sizeof(header->compression),1,picFile);
fread(&header->padbyte,sizeof(header->padbyte),1,picFile);
fread(&header->transparent,sizeof(header->transparent),1,picFile);
fread(&header->x_aspect,sizeof(header->x_aspect),1,picFile);
fread(&header->y_aspect,sizeof(header->y_aspect),1,picFile);
fread(&header->screenWidth,sizeof(header->screenWidth),1,picFile);
fread(&header->screenHeight,sizeof(header->screenHeight),1,picFile);
/* Finished loading in the header, now reverse the values to make
them Intel compatible. */
header->flen = ReverseLong(header->flen);
header->hlen = ReverseLong(header->hlen);
header->width = ReverseWord(header->width);
header->length = ReverseWord(header->length);
header->xoff = ReverseWord(header->xoff);
header->yoff = ReverseWord(header->yoff);
header->transparent = ReverseWord(header->transparent);
header->screenWidth = ReverseWord(header->screenWidth);
header->screenHeight = ReverseWord(header->screenHeight);
return(0);
}
/* ReadCMap first looks for the CMAP text in the file which denotes the
beginning of the color information. Once it finds the CMAP header, it
checks to see if the number of colors(x 3 for numbytes) matches the
length of the CMAP, which is read in after the header. If everything
is ok, it then loads up the array pallete with the rgb values contained
in the CMAP section of the file. ReadCMap then calls setDAC to load
the video DAC registers with the appropriate values. */
int ReadCMap(FILE *picFile, struct headform *header){
char form[5];
long CMAPsize;
struct palettetype pal;
unsigned char rgbTuple[3];
int x;
int numColors;
struct RGBColor pallete[256];
fread(form,4,1,picFile);
form[4]='\0';
if(strcmp("CMAP",form) != 0){
printf("Could not find the color map.\n");
printf("%s found instead.\n",form);
Quit(1);
}
fread(&CMAPsize,sizeof(CMAPsize),1,picFile);
CMAPsize=ReverseLong(CMAPsize);
numColors=expo(2,header->planes);
if(CMAPsize != (numColors*3)){
printf("CMAP size is invalid.\n");
Quit(1);
}
for(x=0;x<numColors;++x){
fread(rgbTuple,3,1,picFile);
pallete[x].red = rgbTuple[0]>>2;
pallete[x].green = rgbTuple[1]>>2;
pallete[x].blue = rgbTuple[2]>>2;
}
setDAC(pallete);
return(0);
}
/* setDAC uses some pretty sophisticated funtions of Borland C. We first
set up a bunch of variables to hold CPU register values in type REGS.
Once we have done this, we can make some low level calls to the video
BIOS to set up the colors we want in the picture. We enter a loop that
sets each register to a RGB color combination and calls the BIOS routine
to set the specific DAC register we are interesed in. */
void setDAC(struct RGBColor pallete[256])
{
union REGS regs;
int i;
printf("Setting up RGB color values.\nplease wait.\n");
/* This sets up each of the 16 pallete entries to enable the proper
DAC register when the pallete value is combined with the pixel
value. */
for(i=0;i<16;++i){
regs.h.ah = 0x10;
regs.h.al = 0x00;
regs.h.bh = i;
regs.h.bl = i;
int86(0x10,®s,®s);
}
regs.h.bl = 0x00;
for (i=0;i<256;++i){
regs.h.ah = 0x10; /* set specific DAC rgb register value */
regs.h.al = 0x10; /* subfunction number, set register */
regs.h.ch = pallete[i].green; /* CH contains the green value */
regs.h.cl = pallete[i].blue; /* CL contains the blue value */
regs.h.dh = pallete[i].red; /* DH contains the red value */
int86(0x10, ®s, ®s); /* int 10h */
++regs.h.bl;
}
}
/* DumpToScreen unpacks the graphics information contained in the file. It
first searches for the BODY header which precedes the actual graphics
data. If we don't find the BODY header, the program will just crash, since
I don't look out for the end of the file here. Once it finds the header,
it reads in the next value, which is the length of the body. We use this
value to read in the the next n number of bytes as specified by the body
length. ILBM seems to use a run length encoding scheme to pack the data.
The function looks at the first byte read. If the first byte read in is
between -1 and -127, then this means we are to read in the next byte
value, and repeat this value 1 to 127 times depending on the first byte
value we read. I.E. if the first value we read was -1, we read the next
value and repeat this value in the scanline 2 times (1-(-1)). If the
header value was -128, we would do nothing, as this is the no-operation
code. If the header value, call this n,is between 0 and 128, we interpret
this to mean the next n bytes are to be read in normally and stuffed
into the scanline without any sort of expansion or processing. Once this
function has read enough bytes to make up a scanline (320 bytes in VGA
mode 13h), we call a routine that dumps the scanline we just built to
the screen. We keep doing this until we run out of bytes to read. Note
that it is possible for an image to have more than 200 scanlines of data,
but our scanline dump routine ignores lines past 200. */
int DumpToScreen(FILE *picFile, struct headform *header){
int yoffset=0; /* the y coord of the scanline */
int index=0; /* index into the scanline array */
int pixelsToGo=0; /* number of pixels to go to form a line*/
int i; /* loop counter */
unsigned char Vbuff[324]; /* buffer to hold the scanline. It
is 4 bytes longer than the regular
length so it can hold hsize and
vsize data for the putimage call */
int repeat; /* how many times to repeat the byte*/
char repeatValue; /* raw byte value from file */
char bufValue; /* raw buffer value read from file*/
long size; /* size of the BODY segment */
long bytesToGo; /* bytes to go till end of BODY */
char form[5]; /* holds header */
fread(form,4,1,picFile);
form[4]='\0';
while(strcmp("BODY",form) != 0){ /* find the BODY */
form[0]=form[1];
form[1]=form[2];
form[2]=form[3];
form[3]=fgetc(picFile);
}
fread(&size,sizeof(size),1,picFile);
bytesToGo = ReverseLong(size);
Vbuff[0]=(320-1) & 0xff; /* set up height and width of
our scanline. Since we use
putimage to dump our scanline,
we have to tell it how big
the 'shape' we are drawing
to the screen is. In our case
the shape is 320x1 in size.*/
Vbuff[1]=(320-1) >> 8;
Vbuff[2]=1;
Vbuff[3]=0;
index = index+4; /* update the index into the scanline */
/* Check to see if the compression is of the proper type, which in
our case is 1. If it is uncompressed, or if we don't know the
compression type, we exit the program. */
if(header->compression != 1){
printf("Cannot handle this compression type.\n");
return(1);
}
while(bytesToGo > 0)
{
fread(&repeatValue,1,1,picFile);
if(ferror(picFile))
{
printf("Error in repeat value read.\n");
return(1);
}
--bytesToGo;
repeat = repeatValue;
if (repeat == -128);
else if((repeat <= -1) && (repeat >= -127))
{
fread(&bufValue,1,1,picFile);
if(ferror(picFile))
{
printf("Error in pixel value read.\n");
return(1);
}
--bytesToGo;
for (i=0;i<(1-repeat);++i)
{
Vbuff[pixelsToGo+4] = bufValue;
++pixelsToGo;
if(pixelsToGo == 320)
drawline(&yoffset,Vbuff,&pixelsToGo);
}
}
else if((repeat >= 0) && (repeat <= 127))
{
for(i=0;i<=repeat;++i)
{
fread(&(Vbuff[pixelsToGo+4]),1,1,picFile);
if(ferror(picFile))
{
printf("Error in pixel value read.\n");
return(1);
}
--bytesToGo;
++pixelsToGo;
if(pixelsToGo == 320)
drawline(&yoffset,Vbuff,&pixelsToGo);
}
} /*end if*/
}/*end while bodysize*/
return(0);
}
/* drawline takes an array containing the scanline data we have just
read in, and dumps it to the screen using the putimage call in the
Borland BGI. The function then increments the yvalue to point to
the next scanline, then resets the pixels to go value (scanline) to
0 again. If we are currently working on a scanline greater than can
fit on the screen (i.e. greater than 200), we just ignore it and don't
draw it to the screen. */
void drawline(int *yoffset,unsigned char Vbuff[324],int *scanline){
if(*yoffset <= 200)
putimage(0,*yoffset,Vbuff,COPY_PUT);
++*yoffset;
*scanline=0;
}
void Quit(int errCode){
printf("%d",errCode);
closegraph();
}
5. BMP(Microsoft Windows Device Independent Bitmap)
마이크로 소프트 윈도우즈에서 기본으로 지원하고 있는 파일 포맷이다. 운영체제에서 지우너을 하고 있기 때문에 윈도우즈용 애플리케이션이라면 거의 대부분 이 포맷을 지원하고 있다. 1비트부터 8비트 컬러까지는 컬러 맵을 갖고 색상을 표현하지만 24비트 이미지는 비디오 램에서 직접 표현한다. 참고로 OS/2 2.0 에는 OS/2 Win이 내장되어 있기 때문에 OS/2 에서도 사용할 수 있다.아래는 BMP View 소스이다.
/*
* File Name: BMP.C
*
* BMP format의 image file을 loading 하여 screen에 display.
*
* Memory Model: Large
*
* Notes:
* - mono 및 16 color, 256 color는 각각 display mode 11H, 13H, 13H를
* 씀. 'C'로 coding하는 관계상 16 color, 256 color display mode는
* 고해상도 mode가 속도문제에 걸림. 또한 page segment set 계산이
* 귀찮은 문제임.
*
* Created by Sohn Dongik, 1992, 11
*/
#include <stdio.h>
#include <alloc.h>
#include <dos.h>
#define MONO_640_480 0x0011
#define COLOR16_640_480 0x0012
#define COLOR256_320_200 0x0013
#define COLOR256_640_480 0x002e
#define SUCCESS 0
#define ERR_READ 1
#define ERR_INV_COMPRESSION 2
#define MAX_LINES 4096
#define ESC 0x1b
#define HOME 0x47
#define END 0x4f
#define UP 0x48
#define DOWN 0x50
#define LEFT 0x4b
#define RIGHT 0x4d
typedef unsigned int UINT;
typedef int WORD;
typedef long DWORD;
typedef long LONG;
/*
* BMP file의 첫 부분의 header structure.
*/
typedef struct tagBITMAPFILEHEADER
{
UINT bfType; /* BMP File Type(항상 'BM') */
DWORD bfSize; /* BMP File Size */
UINT bfReserved1; /* Reserved(항상 '0') */
UINT bfReserved2; /* Reserved(항상 '0') */
DWORD bfOffBits; /* 실제 BMP image data offset */
} BITMAPFILEHEADER;
/*
* BMP file의 format information header structure.
*/
typedef struct tagBITMAPINFOHEADER
{
DWORD biSize; /* structure size */
LONG biWidth; /* image 가로 size(dot 단위) */
LONG biHeight; /* image 세로 size(dot 단위) */
WORD biPlanes; /* screen 평면 수 */
WORD biBitCount; /* 한 pixel당 bit 수 */
DWORD biCompression; /* image data 압축 방식 */
DWORD biSizeImage; /* 압축 안된 image data size */
LONG biXPelsPerMeter; /* target device의 X 해상도 */
LONG biYPelsPerMeter; /* target device의 Y 해상도 */
DWORD biClrUsed; /* color index number */
DWORD biClrImportant; /* color important color */
} BITMAPINFOHEADER;
#define BI_RGB 0L
/*
* #define BI_RLE8
* #define BI_RLE4
*
* Run Length Encoding 방식은 지원 하지 않음.
*/
void SetMode(int);
void SetVGAPalette(char *, int);
void PanImage(void);
void DisplayImage(int, int);
BITMAPFILEHEADER bmf;
BITMAPINFOHEADER bmi;
unsigned int ScreenWidth; /* 현재 display mode의 X size */
unsigned int ScreenHeight; /* 현재 display mode의 Y size */
unsigned int BytesPerLine; /* image의 한 line당 byte 수 */
unsigned int xMove; /* image 이동 폭(byte 단위) */
char far *ImageBuffer[MAX_LINES];
main(int argc, char *argv[])
{
FILE *fp;
int i, j, bmfSize, bmiSize, mode;
char Palette[1024];
if(argc != 2) /* arg check */
{
puts("Usage: bmpinfo bmp_file_name");
exit(1);
}
if((fp = fopen(argv[1], "rb")) == NULL) /* file open */
{
printf("can't open file %s!", argv[1]);
exit(1);
}
/*
* BMP file header read.
*/
bmfSize = sizeof(BITMAPFILEHEADER);
if(fread(&bmf, 1, bmfSize, fp) != bmfSize)
{
puts("Error reading BMP file header!");
fclose(fp);
exit(1);
}
/*
* BMP info header read.
*/
bmiSize = sizeof(BITMAPINFOHEADER);
if(fread(&bmi, 1, bmiSize, fp) != bmiSize)
{
puts("Error reading BMP file headef!");
fclose(fp);
exit(1);
}
/*
* pixel당 bit 수에 따른 display mode및 screen 해상도 결정.
*/
switch(bmi.biBitCount)
{
case 1:
BytesPerLine = bmi.biWidth / 8;
if(BytesPerLine % 8) BytesPerLine++;
mode = MONO_640_480; /* display mode */
ScreenWidth = 80; /* in bytes */
ScreenHeight = 480; /* screen depth */
xMove = 3; /* 24 dots */
break;
case 4:
BytesPerLine = bmi.biWidth;
/*
* 16 color palette read.
*/
if(fread(Palette, 1, 64, fp) != 64)
{
fclose(fp);
puts("Error in reading palette!");
exit(1);
}
mode = COLOR256_320_200; /* display mode */
ScreenWidth = 320; /* */
ScreenHeight = 200;
xMove = 24;
break;
case 8:
if(bmi.biWidth % 4) BytesPerLine = ((bmi.biWidth / 4) + 1) * 4;
else BytesPerLine = bmi.biWidth;
if(fread(Palette, 1, 1024, fp) != 1024)
{
fclose(fp);
puts("Error in reading palette!");
exit(1);
}
mode = COLOR256_320_200;
ScreenWidth = 320;
ScreenHeight = 200;
xMove = 24;
break;
default:
fclose(fp);
puts("Not supported colors !");
exit(1);
}
/*
* line image buffer memory allocation.
*/
for(i = 0; i < bmi.biHeight; i++)
{
if((ImageBuffer[i] = farmalloc(BytesPerLine + 128)) == NULL)
{
for(j = i - 1; j >= 0; j--) farfree(ImageBuffer[j]);
fclose(fp);
puts("Memory Allocation Error !");
exit(1);
}
memset(ImageBuffer[i], 0x00, BytesPerLine + 128);
}
/*
* BMP image read.
*/
if(UnpackBMPImage(fp))
{
for(i = 0; i < bmi.biHeight; i++) farfree(ImageBuffer[i]);
putch(7);
exit(1);
}
fclose(fp);
SetMode(mode);
/*
* color palette set.
*/
if(bmi.biBitCount != 1) SetVGAPalette(Palette, 256);
PanImage(); /* image scroll 및 pan */
for(i = 0; i < bmi.biHeight; i++) farfree(ImageBuffer[i]);
SetMode(0x03);
}
/*
* BMP image read function
*/
UnpackBMPImage(FILE *fp)
{
int i, j, c;
if(bmi.biCompression == BI_RGB) /* 압축되지 않은 경우 */
{
/*
* 16 color의 경우(256 color image data로 변환).
*/
if(bmi.biBitCount == 4) {
for(i = 1; i <= bmi.biHeight; i++)
{
fseek(fp, (long)-i * (long)BytesPerLine / 2L, SEEK_END);
for(j = 0; j < bmi.biWidth / 2; j++)
{
c = fgetc(fp) & 0xff;
ImageBuffer[i - 1][j * 2] = (char)(c >> 4);
ImageBuffer[i - 1][(j * 2) + 1] = (char)(c & 0x0f);
}
}
}
/*
* mono 및 256 color image data read.
*/
else
{
for(i = 1; i <= bmi.biHeight; i++)
{
fseek(fp, (long)-i * (long)BytesPerLine, SEEK_END);
if(fread(ImageBuffer[i - 1], 1, BytesPerLine, fp) !=
BytesPerLine) return ERR_READ;
}
}
}
else return ERR_INV_COMPRESSION;
return SUCCESS;
}
/*
* image의 scroll 및 pan.
*/
void PanImage(void)
{
int c;
int xPosition = 0, yPosition = 0;
int xMax, yMax;
xMax = BytesPerLine - ScreenWidth;
yMax = bmi.biHeight - ScreenHeight;
DisplayImage(xPosition, yPosition);
while((c = getch()) != ESC)
{
if(!c)
{
switch(getch())
{
case HOME:
xPosition = yPosition = 0;
DisplayImage(xPosition, yPosition);
break;
case END:
if(BytesPerLine > ScreenWidth ||
bmi.biHeight > ScreenHeight)
{
xPosition = BytesPerLine - ScreenWidth;
if(xPosition < 0) xPosition = 0;
yPosition = bmi.biHeight - ScreenHeight;
if(yPosition < 0) yPosition = 0;
DisplayImage(xPosition, yPosition);
}
break;
case UP:
if(yPosition > 0)
{
yPosition -= 24;
if(yPosition < 0) yPosition = 0;
DisplayImage(xPosition, yPosition);
}
break;
case DOWN:
if(yPosition < yMax)
{
yPosition += 24;
DisplayImage(xPosition, yPosition);
}
break;
case LEFT:
if(xPosition > 0)
{
xPosition -= xMove;
if(xPosition < 0) xPosition = 0;
DisplayImage(xPosition, yPosition);
}
break;
case RIGHT:
if(xPosition < xMax)
{
xPosition += xMove;
DisplayImage(xPosition, yPosition);
}
default:
break;
}
}
}
}
/*
* 현재 시작 image point부터 display 영역만큼 image display.
*/
void DisplayImage(int xPosi, int yPosi)
{
register i, j, iSize;
unsigned jump;
for(i = yPosi, j = 0; i < yPosi + ScreenHeight; i++, j++)
{
jump = (unsigned)j * ScreenWidth;
if(i >= bmi.biHeight) memset(MK_FP(0xa000, jump), 0x00, ScreenWidth);
else
{
if((BytesPerLine - xPosi) < ScreenWidth)
iSize = BytesPerLine - xPosi;
else iSize = ScreenWidth;
movedata(FP_SEG(ImageBuffer[i] + xPosi),
FP_OFF(ImageBuffer[i] + xPosi),
0xa000, jump, iSize);
if(iSize < ScreenWidth)
memset(MK_FP(0xa000, jump + iSize), 0x00, ScreenWidth - iSize);
}
}
}
/*
* display mode set.
*/
void SetMode(int mode)
{
union REGS in, out;
in.x.ax = mode;
int86(0x10,&in,&out);
}
/*
* VGA color palette set.
*/
void SetVGAPalette(char *palette, int change)
{
union REGS in, out;
struct SREGS sreg;
register i;
char buff[768];
for(i = 0; i < change; i++)
{
buff[i * 3] = palette[(i * 4) + 2] >> 2;
buff[(i * 3) + 1] = palette[(i * 4) + 1] >> 2;
buff[(i * 3) + 2] = palette[i * 4] >> 2;
}
in.x.ax = 0x1012;
in.x.bx = 0;
in.x.cx = change;
in.x.dx = FP_OFF(buff);
sreg.es = FP_SEG(buff);
int86x(0x10, &in, &out, &sreg);
}
6. ICON
그래픽 파일 포맷이라고 하기는 문제가 있지만 모두 다 아는 ICON 파일에 대한 소스를 소개하겠다. 데모를 보여주는 프로그램인지라 쓸데 없는 루틴이 있지만 소스 화일을 바꾸는 것은 문제가 있으므로 그대로 소개하기로 하겠다.
6.1 화일명 : ICON.CPP
#include "icon.h"
#include <stdio.h>
Icon::Icon(char *name)
{
buffer = new char[512];
Load(name);
}
void Icon::Load(char *name)
{
bufferptr = buffer;
FILE *fpt = fopen(name,"rb");
if (fpt==NULL) return;
fseek(fpt,126,0);
for(int y = 31; y >=0; y--){
bufferptr = buffer + (y<<4);
for(int x = 0; x < 16; x++)
*bufferptr++ = (char) fgetc(fpt);
}
fclose(fpt);
}
void Icon::Draw(int x, int y)
{
bufferptr = buffer;
asm mov dx,3ceh
asm mov ax,0205h
asm out dx,ax
static char color;
int temp((y<<6) + (y<<4)); // temp = y * 80
for(int j = y; j < y+32; j++){
for(int i=x; i <x+32; i++){
color = (*bufferptr>>4)&0x0f;
asm mov bx, WORD PTR temp
asm mov dx,WORD PTR i
asm mov ax,dx
asm shr dx,1
asm shr dx,1
asm shr dx,1
asm add bl,dl
asm adc bh,0
asm and al,07h
asm mov cl,al
asm mov ah,080h
asm shr ah,cl
asm mov dx,03ceh
asm mov al,8
asm out dx,ax
asm mov ax,0a000h
asm mov es,ax
asm mov ah,BYTE PTR es:[bx]
asm mov ah,BYTE PTR color
asm mov es:[bx],ah
color = *bufferptr++ & 0x0f;
i++;
asm mov bx, WORD PTR temp
asm mov dx,WORD PTR i
asm mov ax,dx
asm shr dx,1
asm shr dx,1
asm shr dx,1
asm add bl,dl
asm adc bh,0
asm and al,07h
asm mov cl,al
asm mov ah,080h
asm shr ah,cl
asm mov dx,03ceh
asm mov al,8
asm out dx,ax
asm mov ax,0a000h
asm mov es,ax
asm mov ah,BYTE PTR es:[bx]
asm mov ah,BYTE PTR color
asm mov es:[bx],ah
}
temp += 80;
}
asm mov ax,0005h
asm out dx,ax
asm mov ax,0ff08h
asm out dx,ax
}
6.2 화일명 : ICONDEMO.CPP
#include "icon.h"
main()
{
asm mov ah,0h // graphic mode(640*480)
asm mov al,12h
asm int 10h
Icon *icon0 = new Icon("peter.ico");
Icon *icon1 = new Icon("12god1.ico");
Icon *icon2 = new Icon("12god2.ico");
Icon *icon3 = new Icon("12god3.ico");
Icon *icon4 = new Icon("12god4.ico");
Icon *icon5 = new Icon("12god5.ico");
int x;
for(int y=0;y<=440;y+=32){
x=10;
for(int i=0;i <3;i++){
icon0->Draw(x,y);x+=32;
icon1->Draw(x,y);x+=32;
icon2->Draw(x,y);x+=32;
icon3->Draw(x,y);x+=32;
icon4->Draw(x,y);x+=32;
icon5->Draw(x,y);x+=40;
}
}
asm mov ah,08h // getch()
asm int 21h
// text mode (80*25)
asm mov ah,0h
asm mov al,03h
asm int 10h
delete icon0;
delete icon1;
delete icon2;
delete icon3;
delete icon4;
delete icon5;
return 0;
}
7. 그 밖의 정지화상 포맷
7.1 TIFF(Tag Image File Format)
.PCX와 함께 비트맵 이미지 처리의 대표적인 파일 포맷이다. 특히 IBM PC 뿐만 아니라 매킨토시, 유닉스 기반의 워크스테이션에 이르는 이식성을 자랑한다. 옵션으로 LZW 압축 알고리즘을 이용하여 파일의 크기를 줄일 수도 있다. DTP 소프트웨어인 Page Maker에서 처음 제안한 이래, 단색 데이타부터 24비트 데이타에 이르는 가장 폭넓은 데이타 저장 능력을 자랑한다. 특히 6.0 버전이 발표되면서 정지화상 압축 알고리즘이 추가되었다. PC에서는 확장자를 세 자까지만 사용할 수 있는 관계로 .TIF로 표기한다. 판권은 Aldus사와 Microsoft사가 갖고 있다.
7.2 TGA(Truevision Targa)
그래픽 전용 장비 개발회사로 유명한 트루비젼(Truevision)사에서 자사의 하드웨어를 뒤받침하기 위해 개발한 이미지 처리 데이타 포맷이다. 기본적으로 24비트 풀컬러 이상(32비트 컬러 -알파 채널이 포함된 파일 구성. 윈도우즈의 고급 리터치 프로그램과 매킨토시의 일부 애플리케이션에서 구현할 수 있다.)을 지원하며 섬세한 색상 표현이 가능하기 때문에 IBM PC에서는 물론 매킨토시에서도 널리 사용되고 있다.
RGB 신호를 디지탈화한 포맷으로 데이타이 크기 제한이나 표현 색상의 제한은 없다. 파일 포맷에 대한 버전업이 거의 이루어지지 않았기 때문에 멀티플랫폼용 포맷으로 적합하다.
7.3 WFW
윈도우즈 3.0이 발표되면서 Visual BASIC 이나 Word For Windows 와 같은 애플리케이션에서 비트맵과 아웃라인의 중간 속성을 가뭸 데이타 포맷이 요구되어 개발하였다. 따라서 비트맵 이미지에서 볼 수 있는 계단 현상을 방지할 수 있으며 비트맵과 아웃라인의 중간 속성이므로 윈도우즈 애플리케이션에서의 데이타 호환이 자유롭다.
7.4 DFX(Drawing Interchange format)
CAD 소프트웨어로 유명한 Autodesk 사에서 자사의 AutoCAD 에서 사용하기 위한 벡터 속성을 갖는 포맷이다. AutoCAD 의 시장점유율이 높은 관계로 IBM PC나 매킨토시, 워크스테이션에서 운용되는 CAD 소프트웨어에서 이 포맷을 지원한다.
7.5 CGM(Computer Graphics Metafile)
WFM과 마찬가지로 아웃라인과 비트맵의 중간 성격을 갖지만 .WFM보다 아웃라인 속성이 강하다. DXF가 3차원을 표현하는데 적합하다면 CGM은 2차원 표현에 주로 사용한다. 우리나라에서는 널리 사용하고 있지는 않지만 미국 표준안 협회와 국제 표준화 협회 인증 포맷이다.(ANSI/ISO 8632 - 1991)
7.6 HP-GL(Hewlett - Packard Graphics Language)
출력기기의 개발, 제조 회사인 휴렛패커드 사에서 자사 플로터의 출력을 위해 제안한 파일 규약이다. 출력긱기를 지원하도록 한 것이기 때문에 파일 포맷이라기 보다는 제어명령규약으로 보는 것이 합리적이다. 손쉬운 벡터 이미지를 표기할 수 있어 널리 사용된다. 1990년에 HPGL/2 로 업그레이드 되었다. IBM PC에서는 .PLT라는 확장자로 표기한다.
7.7 Lotus PIC
로터스 사의 스프레드 시트인 Lotus 1-2-3에서 그래프를 그릴 때 사용하는 포맷이다. 아웃라인 속성을 가지고 있기 때문에 깨끗한 출력을 얻을 수 있다. 스프레드시트 프로그램과 챠트드로우 프로그래이라면 예외없이 이 포맷을 지원하고 있다.
7.8 Macintosh PICT
매킨토시에서 가장 폭넓게 사용하고 있는 포맷으로 맥용 운영체제인 AppleTalk 수준에서 지원하고 있다. 1비트 흑백 데이타에서부터 알파 채널에 이르기까지 다양한 색상표현이 가능하다. PC에서의 일부 프로그램과 워크스테이션에서 지원한다.
7.9 EPS(Encapsulated PostScript)
포스트스크립트 형태의 파일 포맷으로 비트맵과 벡터 이미지를 함께 저장할 수 있다. 고유의 파일 모팻이라기 보다는 프린터나 다른 출력 주변 기기를 지원하기 위해 개발된 포맷으로 가장 뛰어난 출력 해상도를 자랑한다. 주로 1,200 dpi 이상의 출력기에서 고해상도 출력을 할 때 이 포맷으로 저장하며 컬러 비트맵 데이타를 이 포맷으로 저장하며 컬러 비트맵 데이타를 이 포맷으로 저장하면 CMYK 분판 출력이 가능하다. 일러스트 프로그램들에서는 예외없이 지원하고 있으며, IBM PC에서 매킨토시, 유닉스 기반의 워크스테이션에 아르는 폭넓은 이식이 가능하다. 판권은 Adobe System 사가 갖고 있다.
7.10 GEM
DTP 소프트웨어인 Ventura Publisher 에서 사용하는 포맷으로 .IMG 확장자로 변환하여 사용하기도 한다. 현재에는 그리 널리 사용하지는 않고 있다.
7.11 WPG(WordPerfect Graphic)
워드 프로세서 프로그램인 WordPerfect에서 사용하는 포맷으로 .CGM 이나 .WFM과 비슷한 속성을 갖는다. 256 컬러 이하의 색상 표현이 가능하다.
7.12 ART
하늘소가 제작한 하늘의 고유 포맷, 2.0 이전에서는 허큘레스의 단색, 2.0이후에서는 VGA 16색5을 사용한다. 같은 ART이면서도 서로 호환성은 없다. ART 파일을 다른 포맷으로 변환하려면 하늘에서 PCX로 저장하면 된다. 국내에서 발견되는 ART는 위의 포맷이고, First Publi Sher에서도 같은 이름의 포맷을 쓰는데 하늘소의 ART와는 다른 것이다.
7.13 CEL
오토애니메이터에서 셀이라는 이미지 파일을 만든다. 만화 제작에서 배경 위에 놓는 셀룰로이드라는 판 위에 그림을 그리는데 그 때 쓰이는 셀률로이드를 줄여서 셀이라고 한다.
7.14 HGE
이것 또한 몽당연필이라는 한국산 프로그램에서 이루어지는 그래픽 포맷이다. 허쿨레스 그래픽 카드에서 사용되던 그래픽 소프트웨어용 포맷이다. 아래아 한글에서 불러오기를 할 수도 있다.
7.15 PZI
유명한 화면 캡쳐 프로그램인 Pizzax Plus에서 사용되는 텍스트/그래픽 화면 캡쳐 포맷이다. PZI 포맷은 피자즈 플러스에서만 지원하므로 다른 소프트웨어에서 사용하려면 피자즈 자체에서 다른 포맷으로 변환하여 사용하면 된다.
7.16 RLE
비압축형인 BMP의 용량 문제를 개선한 포맷으로 RLE 압축 알고리즘을 사용한다. RLE 압축 알고리즘은 간단한 그림 등을 압축하는데 비교적 효과적이며 읽고 쓰는 시간도 빠른 편이다.
7.17 SS
VGA 전용의 그래픽 소프트웨어인 Splash의 그래픽 포맷이다. Splash는 표준 VGA(320×200 해상도 256색 모드)만을 지원하는데 근래에는 대부분의 페인팅 프로그램에서 슈퍼 VGA를 지원하므로 거의 사용되지 않고 있다.
7.18 PMC
A4 스캐너의 번들용인 Image 256 의 고유 포맷이다.(Image 72의 256버젼으로 640×480해상도에 256 칼라 지원하는 포맷) Image 256은 PCX도 지원하므로 실제로는 PCX로 저장하여 사용하는 것이 좋다.
7.19 PMT
퀵 마우스의 번들용으로 제공되는 Image 72의 고유 포맷이다. 단색부터 16색까지 밖에 지원하지 않는다. true 칼라를 지향하는 오늘에는 별로 사용가치가 없다.
7.20 RIF
Fractal Design Painter의 고유 포맷이다. 다른 프로그램에서 사용하려면 TIFF파일로 저장하여 사용하여야 한다.
8. 파일 압축 규약
8.1 JPEG(Joint Photographic Experts Group)
일반적인 그래픽 파일을 고해상도에서 작업을 하면 그 데이타 양은 어마어마 해지는 것이 일반적이다. 브로슈어나 카탈로그 등에 삽입하는 간단해 보이는 컷마저도 쉽게 수십 MB는 충분히 넘는 크기이다. 게다가 그래픽 포맷은 일부를 제외하고는 파일 포맷 자체에 일정 정도 압축하여 저장하기 때문에 일반적인 파일 압축 기법으로는 크게 효과를 보지 못하는 경우가 많다. 뿐만 아니라 일반 파일 압축 기법을 사용하여 그래픽 파일을 압축하면 파일을 사용할 때에는 다시 압축을 해제해야 하는데 이 경우 하드 드라이브의 여유 공간이 부족하면 입축을 해제조차 못하는 심각한 사태가 벌어지는 경우도 있다.
이를 해소하기 위해 제안한 포맷이 JPEG이다. JPEG는 이미지가 크게 손상되지 않는 범위(이를 사용자가 조절할 수 있도록 구성되어 있다.)에서 이미지 전체를 압축한다. 따라서 고밀도로 압축할 수 있는데 일반적인 압축 비율은 8:1에서 16:1의 범위이다. 하지만 지나치게 압축율이 높을 경우 이미지가 심하게 손상되므로 압축율을 설정할 때 주의하여야 한다.
8.2 MPEG(Moving Pictures Experts Group)
국제 표준화 협회(ISO) 산하의 한 위원회인 MPEG에서 제안한 동화상 압축 포맷이다. MPEG는 다양한 CD 포맷의 출현과 함께 동화상의 고밀도 압축에 대한 요구에 대한 반영이다.
VHS 수준의 화질로 압축할 수 있는 MPEG-1 규격과 SVHS 수준의 MPEG-2 규격이 있으며 최근에 MPEG-2 규격이 산업 표준이 되었다. CD-Ⅰ, CD-Ⅴ 등의 제작에 표준으로 사용되고 있으며 일반적인 압축율은 4:1 정도이다.
하지만 MPEG 포맷은 별도의 전용 압축 해제 장비를 마련해야 한다는 점에서 사용자에게 부담을 준다. 윈도우즈가 상업적으로 성공을 하면서 별도의 장비없이도 동화상을 구현할 수 있는 포맷이 요구되었는데 이에 대한 해답이 Video for Windows 와 QuickTime for Windows 이다.
9. Computer Animation
9.1 에니메이션의 정의
컴퓨터 그래픽의 응용범위는 2차원 응용과 3차원 응용으로 나눌수 있다. 2차원 응용은 평면으로 출력되는 모든 이미지를 지칭하는 것으로 천문, 기상 자료 영상처리를 가능케 하는 이미지 프로세싱, 의상 디자인, 메이크업 시뮬레이션 등이 여기에 속한다. 3차원 응용은 현실에서 만들어내기 힘들거나 불가능한 부분들을 컴퓨터 화면상에서 모의 실험해 볼수 있는 분야로 건축구조물을 디자인하거나 항공기 모의실험,영화제작 등에 응용될수 있으며 CAD/CAM 분야가 대표적이다.
컴퓨터 에니메이션은 삼차원 공간에 시간의 축을 더한 것으로 시간예술이라고도 할 수 있다. 에니메이션이란 어떤 사물이 움직이는 것처럼 이미지를 만들어 내는 작업의 일종이다. 이것은 어떤 특정한 사물에 Anima(생명)을 부여한다는 것으로, 컴퓨터를 이용하여 실제로 눈에 보이는 움직임을 만들어 내는 것을 말한다.
9.2 에니메이션을 하기위한 도구
9.2.1 그래픽 보드
IBM-PC AT 호환기종에서 사용가능한 그래픽카드로는 VGA카드와 VGA에서의 해상도를 향상시킨 SUPER VGA가 있으나 전문적인 에니메이션을 원할 경우는 전문가용인 TARGA나 VISTA, EVEREX 보드를 사용하는 것이 좋다. 요새 많이 쓰는 슈퍼 VGA 도 사용해도 별 무리가 없다. 높은 해상도와 여러가지 색상을 가지고 있는 전문 그래픽 보드들이 에니메이션을 하는데 필수 조건이라 할수 있다. 타가 보드를 사용하면 어느 정도 속도를 갖추고 해상도 또한 지원된다. 그래픽 보드마다 칩의 종류가 다른데 MDA, HGC, CGA 등에서는 모토롤라 6845CRTC를 사용하며 EGA에서는 레지스터의 전용 LSI칩을 사용한다. 이것이 해상도를 결정하는 중요한 요인으로 작용한다. 속도에 관계되는 것이 비디오 램인데 영역이 크면 클수록 해상도가 좋아진다. 그러므로 그래픽 보드를 선택할때 가장 중요한 것은 해상도와 지원색상이다.
9.2.2 모니터
외부 모니터는 Composite와 RGB 아나로그를 지원하며, 아날로그의 경우는 멀티싱크 모니터를 사용해야 한다. 멀티싱크는 NEC사에서 수직 및 수평주파수를 일정 범위내에서 조절할수 있는 모니터다. 일반적으로 사용하는 것은 14인치(1024X768)멀티 싱크가 주류를 이룬다. 그밖에 R, G, B전자총으로부터 서로 다른 형태의 입력을 받아들이는 디지탈 칼라 모니터인 RGB 모니터가 있다.이 모니터는 TV역할도 하나 Composite 모니터와는 다르다. 모니터 선택의 경우 주파수를 살펴봐야 하며 주파수가 낮을 경우 쉽게 피로감을 줄수 있다. 그러므로 높은 것을 선택하는 것이 좋다.멀티스캔의 경우 최하 15KHZ 부터 35KHZ의 주파수가 좋다. 또한 도트 핏치도 생각해 봐야 한다. 14인치의 경우 0.28mm정도의 도트 핏치를 가져야 하며 17인치의 경우는 최소한 0.31mm정도의 핏치를 가져야만 한다
9.2.3 마우스,트랙볼
마우스, 트랙볼은 모두 포인팅을 하기 위한 것인데 마우스는 볼/광마우스로 나뉘어 지고 이것은 그래픽 환경에서 편리하게 사용하는 보다 세밀한 곳의 지정을 편리하게 하며 좁은 공간에서도 사용할수 있다. 그래픽을 하기 위해서면 200DPI이상의 해상도를 가진 마우스가 좋다.
9.2.4 그래픽 타블렛
디지타이저(Digitizer)는 복잡한 그림을 컴퓨터에 입력시키기 위해 고안된 도구이다. 디지타이징 타블렛(Tablet)으로 타블렛 패드와 여기에 연결된 스타일러펜으로 타블렛 패드위에 그린 정보가 컴퓨터로 넘어가 작업을 할수 있게 해 준다. 전통적인 그림 그리기 기법에 익숙한 사용자들은좋은 입력 기구가 될것이다.
9.2.5 스캐너
스캐너는 화상 입력장치이다. 컬러와 흑백을 사용할수 있는데 요즘엔 겸용의 경우가 많다. 스캐너로 입력된 그림은 밑그림으로 쓰거나, 매핑용 그림, 배경 등으로 쓰인다. 스캐너는 사진이나 그림 등의 자료를 활용하기 용이하다. 스캐너와 비슷하게 사용하는 것으로 비디오 카메라와 캡춰 보드가 있는데 해상도면에서 떨어지는 경향이 있지만 가격도 저렴하고 다용도로 사용할수 있다는 점에서 많이 사용하고 있다.
10. FLIC
10.1 FLI
Auto CAD 와 3D Studio로 유명한 Autodesk사의 Auto Animator포맷으로 320*200 해상도에서 256색의 애니메이션을 저장하는 포맷이다. Old Flic으로 불리워진다.
10.2 FLC
오토에니메이터에서 버전업한 오토애니메이터 프로의 포맷으로 최대 1024*768 해상도에서 256 색의 애니메이션을 저장할 수도 있다. New Flic으로 불리워진다.
10.3 FLIC Player Source
/*
기능 : 320*200 256 color 모드에서 animation 화일을 보여줌
메모리 모델 : large model 사용
프로그램 : 김 현기
*/
#include <fcntl.h>
#include <dos.h>
#include <io.h>
#include <conio.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "flic.h"
#if !defined(__TURBOC__)
#define asm __asm
#define outportb outp
#endif
char *FrameBuff;
static Flic flic;
/* x,y의 좌표에 color색을 찍음 */
void point(int x,int y,int color)
{
int i;
if(x>320 || y>200) return;
i=y*320+x;
asm mov ax,0xA000
asm mov es,ax
asm mov ax,color
asm mov di,i
asm stosb
}
/* x,y 의 좌표에서 count 수 만큼 data의 색을 1 byte 씩 반복 출력 */
void screen_repeat_one( int x,int y,Uchar data,int count )
{
int i;
if(x>320 || y>200) return;
if(x+count > 320) count=320-x;
i=y*320+x;
asm mov ax,0xA000
asm mov es,ax
asm mov al,data
asm mov di,i
asm mov cx,count
asm rep stosb
}
/* x,y 의 좌표에서 count 수 만큼 p2의 색을 2 byte 씩 반복 출력 */
void screen_repeat_two( int x,int y,Pixels2 p2,int count )
{
int i;
if(x>320 || y>200) return;
if(x+(count<<1) > 320) count=(320-x)>>1;
i=y*320+x;
asm mov ah,byte ptr p2.pixels[1]
asm mov al,byte ptr p2.pixels[0]
asm mov cx,0xA000
asm mov es,cx
asm mov di,i
asm mov cx,count
asm rep stosw
}
/* buffer의 내용을 화면의 x,y 좌표에 count수 반큼 복사 */
void screen_copy_seg( int x,int y,char *buffer,int count )
{
if(x>320 || y>200) return;
if(x+count > 320) count=320-x;
memcpy((char far *)0xA0000000+y*320+x,buffer,count);
}
/* VGA 카드의 팔레트 레지스터를 start에서 count수 만큼 dac의 값으로 변경 */
void write_dac( int start, int count, Uchar *dac )
{
int i, max = count * 3;
outportb(0x03c8, start );
for( i = 0; i < max ; i++ )
{
outportb( 0x03c9, *(dac++ ) );
}
}
/* 0-255 의 범위의 RGB(Red,Green,Blue)값을 가진 빠레트 값들을
0-63 범위의 VGA 카드가 처리하는 값으로 조정해서 빠레트 변경 */
void decode_color_256(Uchar *data)
{
signed short start = 0,i;
Uchar *cbuf = (Uchar *)data;
signed short *wp = (short *)cbuf;
signed short ops;
signed short count;
ops = *wp;
cbuf += sizeof(*wp);
while( --ops >= 0 )
{
start += *cbuf++;
if ((count = *cbuf++) == 0 ) count = 256;
for( i = 0 ; i < (3*count) ; i++ )
*(cbuf+i) = *(cbuf+i) >> 2;
write_dac(start,count,cbuf);
cbuf += 3*count;
start += count;
}
}
/* 0-63 의 범위의 RGB(Red,Green,Blue)값을 가진 빠레트 값들을
변경없이 사용하여 VGA 카드의 빠레트 변경 */
void decode_color_64(Uchar *data)
{
signed short start = 0,i;
Uchar *cbuf = (Uchar *)data;
signed short *wp = (short *)cbuf;
signed short ops;
signed short count;
ops = *wp;
cbuf += sizeof(*wp);
while( --ops >= 0 )
{
start += *cbuf++;
if ((count = *cbuf++) == 0 ) count = 256;
write_dac(start,count,cbuf);
cbuf += 3*count;
start += count;
}
}
/* 일반적인 run-length 방식으로 압축된 이미지 데이타를 풀어서
화면에 display해 줌 */
void decode_byte_run(Uchar *data,Flic *flic)
{
int x,y;
int width = flic->head.width;
int height = flic->head.height;
char psize;
unsigned char *cpt = data;
int end;
y = flic->yoff;
end = flic->xoff+width;
while( --height >= 0 ) {
x = flic->xoff;
cpt += 1;
psize = 0;
while((x+=psize) < end)
{
psize = *cpt++;
if(psize > 0) screen_repeat_one( x,y,*cpt++,psize );
else {
psize = -psize;
screen_copy_seg( x,y,cpt,psize );
cpt += psize;
}
}
y++;
}
}
/* delta fli 방식으로 압축된 이미지 데이타를 풀어서 화면에 display해 줌 */
void decode_delta_fli(Uchar *data,Flic *flic)
{
signed short *wpt = (short *)data;
Uchar *cpt = (Uchar *)(wpt+2);
int xorg = flic->xoff;
int yorg = flic->yoff;
int x,y;
short lines;
Uchar opcount;
char psize;
y = yorg + *wpt++;
lines = *wpt;
while (--lines >= 0)
{
x= xorg;
opcount = *cpt++;
while( opcount > 0 )
{
x += *cpt++;
psize = *cpt++;
if (psize < 0 ) {
psize = -psize;
screen_repeat_one( x,y,*cpt++,psize );
x += psize;
opcount -= 1;
}
else {
screen_copy_seg( x,y,(Uchar *)cpt,psize );
cpt += psize;
x += psize;
opcount -= 1;
}
}
y++;
}
}
/* delta flc 방식으로 압축된 이미지 데이타를 풀어서 화면에 display해 줌 */
void decode_delta_flc(Uchar *data,Flic *flic)
{
Pixels2 p2;
int xorg = flic->xoff;
int yorg = flic->yoff;
int width = flic->head.width;
int x,y;
short lp_count;
short opcount;
int psize;
union {short *w; Uchar *ub; char *b; Pixels2 *p2; } wpt;
int lastx;
lastx = xorg + width -1;
wpt.ub = data;
lp_count = *wpt.w++;
y = yorg;
goto LPACK;
SKIPLINES :
y -= opcount;
LPACK :
opcount = *wpt.w++;
if (opcount >= 0 )
goto DO_SS2OPS;
if ( ((unsigned short)opcount) & 0x4000 )
goto SKIPLINES;
point( lastx,y,opcount);
if((opcount = *wpt.w++) == 0)
{
++y;
if(--lp_count > 0 )
goto LPACK;
goto OUT;
}
DO_SS2OPS :
x = xorg;
PPACK :
x += *wpt.ub++;
psize = *wpt.b++;
if ((psize += psize) >= 0 )
{
screen_copy_seg(x,y,wpt.ub,psize);
x += psize;
wpt.ub += psize;
if( --opcount != 0 )
goto PPACK;
++y;
if( --lp_count > 0 )
goto LPACK;
}
else
{
psize = -psize;
screen_repeat_two( x,y,*wpt.p2++,psize>>1 );
x += psize;
if( --opcount != 0 )
goto PPACK;
++y;
if( --lp_count > 0 )
goto LPACK;
}
OUT :
return;
}
/* display 영역을 모두 지움 */
void decode_black(Flic *flic)
{
int i;
for(i=flic->yoff;i<flic->head.height;i++)
screen_repeat_one(flic->xoff,flic->yoff,0,flic->head.width);
}
/* 앞축되지 않은 이미지 데이타를 곧바로 화면에 뿌려줌
보통 첫번째 frame 에 사용되고 가장 크기가 큼 */
void decode_literal(Uchar *data,Flic *flic)
{
int i;
int height = flic->head.height;
int width = flic->head.width;
int x = flic->xoff;
int y = flic->yoff;
for( i = 0 ; i < height ; ++i ) {
screen_copy_seg( x,y+i,data,width );
data += width;
}
}
/* flic 화일을 open */
int flic_open(Flic *flic,char *name)
{
if((flic->handle=open(name,O_RDONLY | O_BINARY))==-1) return -1;
if(read(flic->handle,&flic->head,sizeof(flic->head))==-1) {
close(flic->handle);
return -1;
}
flic->name=name;
if(flic->head.type==FLC_TYPE) {
lseek(flic->handle,flic->head.oframe1,SEEK_SET);
return 0;
}
if(flic->head.type==FLI_TYPE) {
flic->head.oframe1=sizeof(flic->head);
flic->head.speed=flic->head.speed*1000L/70L;
return 0;
}
close(flic->handle);
return -1;
}
/* 하나의 frame에서 각 chunk 들을 판단하여 처리하는 함수를 호출함 */
int decode_frame(Flic *flic,FrameHead *frame)
{
int i;
long l,fpos;
ChunkHead chunk;
for( i = 0 ; i < frame->chunks ; i++ ){
fpos=tell(flic->handle);
if(read(flic->handle,&chunk,sizeof(chunk))==-1) return -1;
if(read(flic->handle,FrameBuff,
(unsigned)(chunk.size-sizeof(chunk)))==-1) return -1;
switch( chunk.type ) {
case COLOR_256 :
decode_color_256(FrameBuff);
break;
case DELTA_FLC :
decode_delta_flc(FrameBuff,flic);
break;
case COLOR_64 :
decode_color_64(FrameBuff);
break;
case DELTA_FLI :
decode_delta_fli(FrameBuff,flic);
break;
case BLACK_CLS :
decode_black(flic);
break;
case BYTE_RUN :
decode_byte_run(FrameBuff,flic);
break;
case LITERAL :
decode_literal(FrameBuff,flic);
break;
default :
break;
}
lseek(flic->handle,fpos+chunk.size,SEEK_SET);
}
return 0;
}
/* frame 단위로 다음의 화면을 출력해줌 */
int flic_next_frame(Flic *flic)
{
int i;
FrameHead head;
long fpos;
fpos=tell(flic->handle);
if(read(flic->handle, &head, sizeof(head))==-1) return -1;
i=decode_frame(flic,&head);
lseek(flic->handle,fpos+head.size,SEEK_SET);
return i;
}
/* system clock 을 읽음 - clock tick
return 되는 regs.x.dx 는 1 초에 약 18.2씩 증가하는 값을 가짐 */
unsigned get_clock_tick(void)
{
union REGS regs;
regs.x.ax=0;
int86(0x1A,®s,®s);
return regs.x.dx;
}
/* flic 화일을 한번만 보여줌 */
int flic_play_once(char *fname)
{
int i,tick;
unsigned stime,ctime;
if(flic_open(&flic,fname)==-1) return -1;
tick=(int)((flic.head.speed*18L)/1000L);
FrameBuff=(char *)malloc(0xF600);
if(FrameBuff==NULL) {
close(flic.handle);
return -1;
}
for(i=0;i<flic.head.frames;i++) {
stime=get_clock_tick();
if(flic_next_frame(&flic)==-1) {
free(FrameBuff);
close(flic.handle);
return -1;
}
do {
ctime=get_clock_tick();
} while((ctime-stime)<tick);
}
free(FrameBuff);
close(flic.handle);
return 0;
}
/* flic 화일을 키보드가 눌려질때까지 반복하여 보여줌 */
int flic_play_loop(char *fname)
{
int i,tick;
unsigned stime,ctime;
if(flic_open(&flic,fname)==-1) return -1;
tick=(int)((flic.head.speed*18L)/1000L);
lseek(flic.handle,flic.head.oframe1,SEEK_SET);
FrameBuff=(char *)malloc(0xF600);
if(FrameBuff==NULL) {
close(flic.handle);
return -1;
}
stime=get_clock_tick();
if(flic_next_frame(&flic)==-1) {
close(flic.handle);
return -1;
}
tick=(int)((flic.head.speed*18L)/1000L);
if(flic.head.type==FLI_TYPE)
flic.head.oframe2=tell(flic.handle);
do {
ctime=get_clock_tick();
} while((ctime-stime)<tick);
while(1) {
lseek(flic.handle,flic.head.oframe2,SEEK_SET);
for(i=0;i<flic.head.frames;i++) {
stime=get_clock_tick();
if(flic_next_frame(&flic)==-1) {
free(FrameBuff);
close(flic.handle);
return -1;
}
do {
if(kbhit()) {
free(FrameBuff);
close(flic.handle);
return 0;
}
ctime=get_clock_tick();
} while((ctime-stime)<tick);
}
}
// free(FrameBuff);
// close(flic.handle);
// return 0;
}
/* 화면의 모드를 변경함 */
void set_mode(Uchar mode)
{
union REGS regs;
regs.h.ah=0;
regs.h.al=mode;
int86(0x10,®s,®s);
}
void main(int argc,char *argv[])
{
if(argc<2) {
printf("Usage : flic flic_filename\n");
exit(0);
}
set_mode(0x13); /* 320*200 256 color 모드 */
flic_play_once(argv[1]);
set_mode(3); /* text 모드 */
}
11. 그 밖의 동화상 포맷
11.1 AVI
인텔 사에서 제안한 Indeo 규약에 따른 동화상 처리용 포맷. 별도의 압축 전용 보드 없이도 동화상을 구현할 수 있는 윈도우즈 표준 포맷이다. 초당 15 프레임을 디스플레이 할 수 있으며 기본 해상도는 300×200. 표현 가능한 색상은 256 컬러이다. 따라서 작고 서친 화면과 함께 사운드 데이타와 동화상이 동시에 처리되는 것이 미흡하여 립싱크(LipSync)가 발생한다.
11.2 QTW
매킨토시의 운영체제인 AppleTalk의 표준 동화상 처리 포맷. 파일 규약은 많은점에서 .AVI와 같은 점이 많은데 .AVI와 마찬가지로 별도의 전용 보드 없이도 동화상을 구현할 수 있기 때문이다.
.AVI와 비교하였을 때 다른 점은 립싱크 현상이 일어나는 경우가 적고 파일 내부에 사운드 출력 제어 부분이 포함되어 있어 음량을 제어할 수 있다는 점이다. 얼마전에 2.0 버전이 발표되었다.
