2012년 7월 2일 월요일

turbo c


PCX algorithm


제목 내가 짜 보는 TURBO-C GAME.
필자가 앞으로 쓰고자 하는 바는 주로 게임에 대한 전체적인 프로그래밍과 그에 부합되는 그래
픽에 대한 것으로서 필자가 프로그램
을 하면서 겪었던 어려웠던 점들을 독자들과 함께 처음부터 차근차근 조그마한 것부터 풀어 나가
도록 하겠다. 또한 지금부터 우리가
만들게임은 그래픽 방식의 VGA전용의 간단한 게임으로서 중심 알고리즘이 퍽 쉽기 때문에 조금만
손을 본다면 새로운 게임을 만들 수
있을 것이다. 자 그럼 기본적인 사항부터 알아보자.
먼저 프로그램에 앞서서 게임의 종류부터 아는 것이 순서일 것이다.
1. 보드게임 : 가장 대표적인 것이 바둑, 장기, 체스 등이 있으며 알고리즘이 상당히 강조된다.
컴퓨터를 거의 인간의 지
능을 능가하도록 프로그램 해야 한다는 점에 있어서 상당한 기교를 필요로 한다.
2. 퍼즐게임 : 대표적인 것으로서는 테트리스와 헥사 등으로서 한때 우리들을 정신 못 차리게 할
정도로 유행했던 게임이다.
아직도 널리 애용되고 있을 정도로 그 매력은 무한하다. 그리 까다롭지도 않고 단
순하면서 굉장한 사고력을 짧은 시
간에 요하는 순발력을 요구하기에 비교적 쉽게 게임에 빠져들 수 있을 정도다. 특히 소
련의 알렉세이 퍼지티노프가 개발했
다고 하는 테트리스는 전세계적으로 널리 알려진 게임이다.
3. 액션게임 : 이 장르는 게임의 고전이라고 일컬어지는 인베이더와 갤러그가 대표적이다. 이후에
제비우스,트윈 코브라,
라이덴 등으로 계보를 이어가고 있다. 액션게임의 특징은 끊임없이 나타나는
상대방 아이템을 간단하게 총알과
폭탄 등의 아이템으로 정신 없이 제압하는 것이다.
4. 롤플레잉게임 : 대표적인 게임은 울티마 시리즈가 있다. 간단하게 뜻풀이를 하자면 ROLE(역
할), PLAYING(연출,연기)으로
될 것이다. 즉, 액션게임과 다른 차이점은 상황에 따라 게임의 주체자가 역할을 바꿀 수

있다는 것이다. 때에
따라서 비행기로 변했다가 배로 변하기도 하고...
5. 어드벤쳐게임 : 인디아나 죤스, 래리 시리즈 등이 대표작으로 꼽히며 시나리오에 상당한 비중
을 두고 있다. 게임 주체의
상상력을 발휘해 가면서 진행을 해 가기 때문에 게임 주체의 지적 능력을 요구한다.
6. 시뮬레이션게임 : 대표작으로는 심시티와 에프 19, 삼국지 등이 있다. 게임 자체가 상당히 스
케일이 크기 때문에 게임에 앞
서서 특정한 작전 등을 구상해야 제대로 게임을 즐길 수 있다는 것이다.
간단하게 게임의 종류에 대해서 알아보았다. 앞으로 우리가 관심을 갖고 프로그래밍을 하고자
하는 장르는 보드게임과 퍼즐게임이
다. 먼저 게임의 프로그래밍에 앞서 먼저 알아야 할 사항에 대해서 간단하게 살펴보자.
텍스트 모드와 그래픽 모드
'유채화'와 '수채화'. 처음부터 엉뚱하게 웬 미술 용어가 나오느냐고 어리둥절해 하는 독자들
이 많을 것이다.
그러나 이는 텍스트 모드와 그래픽 모드를 제대로 구분 못하는 그래픽을 처음 접하는 초보자를 위
해 필자가 주로 사용하는 용어다. 구
체적으로 예를 들면 '가'를 비디오 메모리에 쓸 경우, 텍스트 모드는 '가'가 쓰여질 위치에 이미
다른 문자가 있을지라도 무시되어 유
채화를 그릴 때처럼 그냥 덮어씌우지만, 그래픽 모드는 '가'가 쓰여질 위치에 이미 다른 문자가
있으면 그 해당 위치에 위치한 픽셀의
영향을 받는다는 점이 수채화를 그릴 때와 같다. 즉 그래픽 모드와 텍스트 모드는 '유채화'와 '
수채화'에 비유할 수 있다. 이제부터
우리들이 관심을 가져야 될 것은 '수채화', 즉 그래픽 모드이다. 국내에서 보편적으로 사용되는
그래픽 모드로는 VGA와 허큘리스 두
가지가 있다. 그럼 이들 두 가지 그래픽 모드에 대해서 개략적으로 알아보자.
허큘리스 그래픽 모드
허큘리스 모드에서는 하나의 픽셀을 나타내기 위해서는 1비트가 필요하다. 따라서 한 화면에
필요한 메모리는 720*348/8=31320바이
트, 즉 약 32KB이다. 64KB의 비디오 메모리를 가지고 있는 허큘리스 모드는 <그림 1>과 같이 32KB
로 구성된 두 개의 페이지, 즉 페이
지 0과 페이지 1로 되어 있다. 그리고 IBM PC는 허큘리스의 리프레시 버퍼의 시작 번지가 B0000H
(세그먼트 B000, 오프셋 0000)이고 6
4KB가 할당되어 있으므로 페이지 0의 시작 번지는 B0000H이고 페이지 1의 시작 번지는 B0000H로부
터 약 32KB 떨어진 B8000H가 된다.
--+--+--------------------------------------+--+--
| | B0000H | |
| +--------------------------------------+ |
| | B2000H | |
페이지 0 | +---------------------------------+ | 32KB
| | B4000H | |
| +--------------------------------------+ |
| | B6000H | |
--+--+--------------------------------------+--+--
| | B8000H | |
| +--------------------------------------+ |
| | BA000H | |
페이지 1 | +-----------------------------------+ | 32KB
| | BC000H | |
| +--------------------------------------+ |
| | BE000H | |
--+-- +-----------------------------
-------+--+--
<그림 1> 허큘리스 모드
VGA 그래픽 모드
VGA 모드에서는 먼저 알아 두어야 할 사항이 있다. VGA의 비디오 메모리의 저장 방식에는 비트
플레인 방식(bit plane format)과 픽
셀 패키드 디스플레이 방식(pixel packed display format)의 두 가지가 있으며, 각각 표시 방식이
상이하다. 픽셀 패키드 디스플레이
방식은 하나의 픽셀이 동시에 16가지(1팔레트)색을 표시해야 되기 때문에 1픽셀 당 4비트가 필요
하다. 그러므로 비디오 메모리의 구
조가 간단하지만 이러한 비디오 메모리를 제어하기 위해서는 비트 단위의 연산이 필요하므로 이
방식을 사용하기 위해서는 하드웨어
에 대해 잘 알아야 된다는 전제 조건이 있다. 또한 가장 큰 단점은 픽셀 패키드 방식으로
640*480(16컬러)의 페이지를 표현하기 위
해서는 (640*480*4)/8=153600, 약 150KB의 연속된 메모리 공간이 필요하다. 결국 IBM-PC가 제공
하는 비디오 메모리는 약64KB이므로
일반적인 표현이 불가능하다. 비트 플레인 방식은 <그림 2>와 같이 1픽셀이 동일한 번지, 동일한
비트인 4개의 비트 플레인을 갖는 방
식이며, 픽셀 패키드 디스플레이 방식은 <그림 3>과 같이 1픽셀이 필요로 하는 4개의 비트를 차
례차례 비디오 메모리에 위치시키는
방식이다.
+---------------------+
픽셀 ---->|□ | <----- 비트 플레인 0  
| +-------------------+--+
| |□ | | <----- 비트 플레인 1
| | +----------------+--+--+
| | |□ | | | <----- 비트 플레인 2
+--+--+--+-------------+--+--+--+
| | |□ | | | <----- 비트 플레인 3
+--+--+----------------+ | |
| | | |
+--+-------------------+ |
| |
+----------------------+
비디오 메모리
<그림 2> 비트 플레인 방식

+-----------------------------------+
| □□□□ □□□□ |
| <------> <------> |
| 픽셀 1 픽셀 2 |
| |
| |
| |
+-----------------------------------+
비디오 메모리
<그림 3> 픽셀 패키드 디스플레이 방식
그러나 이들 방식 중 픽셀 패키드 디스플레이 방식보다는 비트 플레인 방식을 더 많이 사용하기
때문에 이에 대한 설명이 필요할 것
같다. 비트 플레인 방식으로 640*480의 한 화면을 운영하기 위해 필요한 메모리는 <그림 4>와 같
다.

A0000H+-----------------+---+---
| | |
| | |
| | |
| | |

| |페 |
| 38400바이트 |이 |
| |지 |
| | |
| | 0 |
| | |
| | |
495FFH +---------------------------------+----+64KB
| | |
| | |
| 27136바이트 | |
| | |
| | |
B0000H +---------------------------------+----+---
<그림 4> 비트 플레인 상의 메모리 맵(그래픽 모드 12H)
1픽셀을 나타내기 위해서는 1비트가 필요하므로 X축은 640/8=80바이트이며, 전체적으로는
80*480=38400바이트가 필요하게 된다. 그
러므로 64KB(총 256KB, 64*4개의 메모리 맵)가 할당되어 있는 비디오 메모리에서 페이지 0 이외의
페이지가 존재할 수 없게 된다. 따
라서 이를 극복하기 위해 약간의 편법으로서 VGA와 비슷한 EGA 모드를 사용하기도 한다. 즉 IBM PC
에서 제공하는 EGA 로우 모드(640*20
0, 16컬러)와 EGA 하이 모드(640*350, 16컬러)를 사용하는 것을 말한다.
640*200의 EGA 로우 모드는 해상도가 너무 차이가 나기 때문에 그리 널리 사용되지는 않지만 4
개의 페이지를 제공한다는 매력이 있
다. 페이지가 많으면 그 만큼 빨리 화면을 전환할 수 있기 때문이다. 640*350의 EGA 하이 모드는
해상도가 VGA와 그리 차이가 나지
않으면서도 16컬러를 사용한다. EGA 하이 모드는 2개의 페이지를 가지며, 하드웨어적인 처리 방법
이 VGA와 유사하여 VGA 모드 자체보
다도 활용도가 높다. 실제로 게임 소프트웨어의 대부분이 EGA 하이 모드로 구현되고 있는 실정이
다.
화상 이미지( PCX파일 )
얼마 전부터 갑자기 PCX 파일에 대한 관심이 대단해졌다. 웬만한 프로그램은 거의 PCX 파일을
이용해서 만들고 있다. 프로그램의
데모조차 PCX 파일을 읽어 뿌려 주고 있다. 그 대표적인 사용 예를 들자면 여러분도 잘 아는 데
이터 통신에 있어서 유명한 '이야기'
를 들 수가 있다. 물론 GIF나 PIC 파일도 이용되지만 PCX는 특히 사용하기가 편리하다는 장점이
있다.그럼 간단하게 PCX파일에 대해
서 알고 넘어가자.
PCX 파일은 IBM PC의 GRAPHIC IMAGE의 표준 저장 방식으로서 ZSoft의 PC PAINT-
BRUSH에 사용되는 방식이다. PCX 파일의 파일 구조는 GIF 파일이나 TIFF 파일에 비해서 파일 구조
가 단순하므로 누구나
쉽게 처리할 수 있는 다는 것이 장점이다. 거의 모든 SOFTWARE가 PCX 파일을 지원하므로 IMAGE를
PCX파일로
저장하면 그 IMAGE를 다른 모든 사람들이 쉽게 사용할 수 있다.
PCX 파일은 거의 다음의 세 부분으로 이루어진다.
+--------------------------------------------------------+
| |
| HEADER ==> general information. |
| DATA ==> run length encoding에 의해 압축. |
| FOOTER ==> 256 color palette 정보를 나타냄. |
| |
+--------------------------------------------------------+
1. HEADER
HEADER의 구조를 C의 STRUCTURE로 나타내면 다음과 같다.
struct Palette {
unsigned char red;
unsigned char green;
unsigned char blue;
/* 각 상위 6비트만 사용 */
};
struct Pcxheader {
char header;
char version;
char encode;
char bit_per_pixel;
int x1;
int y1;
int x2;
int y2;
int hres;
int vres;
struct Palette pal[16];
char mode;
char nplanes;
int byte_per_line;
int pal_info;
int shres;
int svres;
char temp[54];
}Pcx;
본래 PCX 파일은 4 PLANE의 16 COLOR EGA의 구조에 맞도록 설계되어 있었다. EGA의 경우에는
bit_per_pixel이 1, nplnaes가 4이며 HE
RCULES는 bit_per_pixel가 1, nplanes가 1이다. VGA 16 COLOR 의 경우는 EGA와 같으며 VGA 256
COLOR는 bit_per_pixel가 8, nplanes가
1이며 PALETTE는 FOOTER( 256 color에 사용)에 저장된다.
그럼 해더에 대해 상세히 알아보자.

+---------------+--------------------------------------------------+
| 헤 더 | 내 용 |
+---------------+--------------------------------------------------+
| header | 항상 10으로 세팅되어 있다. 별 의미는 없고 |
| | 현재 읽혀지는 파일이 pcx파일임을 나타내는 |
| | 역할을 하고 있음. |
+---------------+--------------------------------------------------+
| version | pcx파일을 생성시킨 software의
버전, 즉 pcx |
| | 파일의 생성 버전을 나타낸다. 버전에 따라서 |
| | pcx파일을 읽는 구조가 틀린다. 다음의 값 중에 |
| | 하나로 나타난다. |
| | 0 - version 2.5의 페인트 브러쉬를 사용. |
| | 2 - version 2.8의 페인트 브러쉬를 사용했으며 |
| | 팔레트 정보를 포함한다. |
| | 3 - version 2.8의 페인트 브러쉬를 사용했으며 |
| | 내정치(default)의 팔레트를 사용. |
| | 5 - version 3.0이상의 페인트 브러쉬를 사용. |
| | 요즘 사용되는 pcx파일이 거의 버전 3.0의 |
| | 페인트 브러쉬를 사용. |
+---------------+--------------------------------------------------+
| encode | 항상 1의 값을 가진다. RUN-LENGTH의 엔코딩을 |
|
| 나타낸다. |
+---------------+--------------------------------------------------+
| bit_per_pixe l| 각각의 픽셀 이미지를 나타내기 위해서 필요한 |
| | 색상의 필요 비트수를 써 준다. 그러므로 지금 |
| | 이 값은 1로 되어 있다. 나중에 다루게 될 |
| | 24 BIT 파일 포멧에서는 다른 값으로 된다. |
+---------------+--------------------------------------------------+
| x1, y1 | pcx파일에 이미지의 시작점의 x, y의 좌표를 |
| | 나타낸다. |
+---------------+--------------------------------------------------+
| x2, y2 | pcx파일의 이미지의 마지막 지점의 x, y의 |
| | 좌표를 나타낸다. |
+---------------+--------------------------------------------------+
| vres
| 수직 해상도를 표시한다. 480으로 설정되어 있 |
| | 지만 보통 사용되지는 않는다. |
+---------------+--------------------------------------------------+
| hres | 수평 해상도를 표시한다. 640으로 설정되어 있 |
| | 지만 역시 사용되지 않는다. |
+---------------+--------------------------------------------------+
| Palette | pcx 파일의 원상태 이미지 팔레트를 나타낸다. |
| | 원래 pcx 파일이 EGA의 16 COLOR를 지원했으 |
| | 나 요즘은 256 COLOR를 많이 사용한다. |
+---------------+--------------------------------------------------+
| mode | 보통은 그래픽 모드를 나타내나 사용되지 않고------|
| | 0의 값으로 표시한다. |
+---------------+--------------------------------------------------+
| nplanes | pcx 파일이 가지고 있는 COL
OR PLANE의 수를 |
| | 나타낸다. COLOR PLANE 이란 R, G, B, I를 나타 |
| | 낸다. R은 red, G는 green, B는 blue, I는 |
| | intensity(밝기) 이다. 위의 RGBI의 값을 적절 |
| | 하게 조합해서 색을 표현하게 된다. |
| | 원래 pcx 파일이 16 COLOR , 4 PLANE 의 EGA |
| | 그래픽 모드를 사용하는 포멧으로 설계되었기 |
| | 때문에 nplanes의 값은 4가 된다. pcx파일을 |
| | 풀때 I/O PORT 를 제어해서 각각의 PLANE을 |
| | 셍팅한 상태에서 압축을 풀어야 한다. |
| | 나중에 소개할 24 BIT PCX파일에서는 이값이 3 |
| | 으로 설정된다. 자세한 사항은 후에 알아보자. |
+---------------+--------------------------------------------------+
| byte_per_line | pcx 파일이 압축을 풀 때 이미지가 한 plane에 |
|
| 포함되는 BYTE수를 나타낸다. VGA 모드에서 1 |
| | LINE이 640 픽셀이고 1 BYTE 당 8 픽셀을 나타낼 |
| | 수 있으므로 640 / 8 = 80 BYTE 가 필요하다. |
| | 즉, byte_per_line의 값은 80이다. |
+---------------+--------------------------------------------------+
| pal_info | 이 값은 256 COLOR 모드에서 사용한다. 그러므 |
| | 로 16 COLOR에서는 별로 효력이 없다. |
| | 1 : 모노크롬 흑백 이미지. |
| | 2 : 컬러 이미지. |
+---------------+--------------------------------------------------+
| svres | 스캐너의 수직 해상도를 나타낸다. |
+---------------+--------------------------------------------------+
| shres | 스캐너의 수평 해상도를 나타낸다. |
+---------------
+--------------------------------------------------+
| temp | 사용하지 않는다. |
| | |
+---------------+--------------------------------------------------+
Data 부분.
data는 계속되는 byte 이미지를 압축하는 run length encoding에 의해 저장된다.
어떤 byte의 상위 비트가 모두 1이면( 즉 16진수로 'C'가 된다. ) 그 byte의 나머지 6비트는
counter이고 그 다음 byte가 counter만
큼 반복된다는 표시이다.
따라서 상위 2비트가 모두 1인 data는 반복되지 않더라도 counter를 사용해야 한다. 또한 최
대 63 개의 픽셀을 압축할 수 있다.
한 line이 끝나는 점에서는 data가 더 반복되더라도 압축을 중단하고 다음 line에서 다시 시작한
다. plane이 여러 개 일 경우는 0번 p
lane부터 3번 plane까지 순서대로 압축된다.

+-----------------------------------------------------------+
| |
| 예) C7h 25h --> 25h 25h 25h 25h 25h 25h 25h |
| C1h C3h --> C3h |
| |
+-----------------------------------------------------------+
+-----------------------
------------------------------------+
| |
| |
| 압축된 data를 푸는 pseudo code는 다음과 같다. |
| |
| unsigned char data; |
| |
| fgetc(data); |
| if((data and 0xc0)=0xc0) /* if counter */ |
| counter=(data & 0x3f); |
| fgetc( data ); |
| else |
| counter=1; |
| counter 만큼
data를 쓴다. |
| |
| |
+-----------------------------------------------------------+
압축하는 방법은 위의 역이지만 약간 더 신경을 써야 한다. 직접 해 보기 바란다.
3. footer
footer의 header 구조를 C의 structure로 나타내면 다음과 같다.
struct pcxftr {
char id; /* 항상 12 */
struct palette pal256[256]; /* 각 상위 6비트만 사용 */
};
앞에서도 언급했듯이 본래 pcx는 16 color만을 지원했기 때문에 256 color의 palette를 저장할
공간이 header에 없다. 그러므로 256
color의 palette는 따로 footer를 만들어 저장해야 한다.
+--+
+--+
| 이 부분은 제가 보내드린 demo.pcx를
| c:\>debug demo.pcx 하시고
| -d100 25f 하신 후에
| 하드카피( print screen 키를 누름 )를 하셔서 그 내용을
| 여기에 붙여 주시면 됩니다.
|
| +---+
| +---------------+ ++ |
| +---------------+------------------------------------------------+----+
+--------------------+
위의 그림은 demo.pcx를 디버그 해서 실제로 PCX 파일이 어떻게 저장되어 있는지를 나타낸 것이
다.
오프셍 100번지부터 보면 첫 바이트가 16진수 A로서 10진수로 10이다. 그리고 버전을 나타내는
숫자 05가 있다. 계속 헤더의 수형에
따라서 1,2 바이트씩 끊어서 읽으면 헤더의 정보가 된다. 그러나 여기서 주의 해야 할 것은 2바이
트의 반워드단위 정보는 역워드 방식
으로 저장 되어 있다는 사실이다. 다시 말해서 118,119번지의 정보가 X 축의 끝지점을 나타내는
정수형 즉, 반워드형으로서 7F 02는 0
27F로 인식해야 한다는 사실이다. 16진수에서 10진수로의 변환은 여러분이 더 잘 알 것이다. 그

럼 여러분들이 위의 설명과 한번 직
접 비교해 보길 바라며 12 BIT PCX파일에 대한 설명은 여기서 그만하기로 하며 모자라는 점은 계
속 보충하기로 하겠다.
비트 맵 이미지.
어느 정도 C 언어를 공부한 사람이라면 폰트에 대해서 관심이 생기는 단계를 거치기 마련이다.
특히 도깨비나 아래
한글처럼 한글을 어떻게 화면에 자유자재로 나타낼 수 있을까 하는 의문을 꼭 가지게 될 것이다.
필자 또한 그러한 단계를 거쳤으며
독자들은 이런 단계를 되도록 빨리 벗어나는 것이 C 언어를 빨리 정복하는 지름길이 될 것이라고
믿는다. 먼저 화면에 하나의 이미지
를 나타내기 위해서는 먼저 다른 그래픽 툴이나 스캐너에 의해 만들어진 PCX나, GIF, PIC, TIFF
등의 그림 파일을 읽는 방법과 비트
맵을 이용해서 이미지를 만드는 방법이 있다. 이미 PCX파일에 대해서 먼저 언급했으므로 여기서
는 손쉽게 다룰 수 있고 오락과 같은
응용 프로그램 등에서 빼놓을 수 없는 비트맵에 대해 서 먼저 자세히 다루고자 한다. 우선 먼저
폰트를 이루는 구성에 대해서 알아보
자.
비트맵 폰트의 구성
비트맵 폰트는 PCX파일과 마찬가지로 <그림 5>와 같이 앞 부분에 헤더와 뒤 부분에 데이터로
구성된다. 이 중에서 헤더는 이미지의
크기를 X와 Y로서 나타낸다. 즉 X의 시작 지점(X1)과 끝 지점(X2), Y의 시작 지점(Y1)과 끝 지점
(Y2)이 X2, X1, Y2, Y1의 순서로 되어
있다는 것이다. 예를 들어 헤더가 0X0F, 00, 0X0F, 00의 순서로 되어 있다면 폰트의 X 시작 지점
(X1)은 0이,끝 지점(X2)은 15(F)가 된
다. 그리고 Y의 시작 지점(Y1)은 0, 끝 지점(Y2)은 15(F)가 된다. 여기서 폰트의 크기는 16*16 픽
셀이라는 것을 알 수 있다. 헤더 뒤
에 위치한 데이터는 실제로 이미지를 나타내는 값으로서 데이터의 량이 많아 앞에서 설명한 PCX파
일보다 파일의 크기가 커진다.
+---------------------+-------------------+
| | |
| | |
| 헤더 | 데이터 |
| | |
| | |
+---------------------+-------------------+
<그림 5>
<예 1>의 폰트는 흑백 이미지로서 이를 컬러 이미지로 만들려면 앞서 설명했듯이 4개의 비트 플
레인에 값을 넣어 주어야 하기 때문
에 적어도 흑백 이미지 폰트보다는 4배 이상의 데이터가 필요하다. <예 2>는 <예 1>의 흑백 이미지
를 16컬러 이미지로 작성한 것이다.
{
0, 0, +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
0, 0, | | | | | | | | | | | | | | | | |
1,128, +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
1,128, | | | | | | | | | | | | | | | | |
1,128, +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
1,128, | | | | | | | | | | | | | | | | |
3,192, +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
3,192, | | | | | | | | | | | | | | | | |
19,200, +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
147,201, | | | | | | | | | | | | | | | | |
147,201, +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
255,255, | | | | | | | | | | | | | | | | |
255,255, +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
255,255, | | | | |
| | | | | | | | | | | |
145,137, +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
145,137, | | | | | | | | | | | | | | | | |
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
| | | | | | | | | | | | | | | | |
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
| | | | | | | | | | | | | | | | |
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
| | | | | | | | | | | | | | | | |
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
| | | | | | | | | | | | | | | | |
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
| | | | | | | | | | | | | | | | |
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+

| | | | | | | | | | | | | | | | |
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
| | | | | | | | | | | | | | | | |
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
| | | | | | | | | | | | | | | | |
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
};
<예 1> 흑백 이미지 폰트 예

{
0x0f,00,0x0f,00, /* 헤 더 */
0, 0, 0, 0, 1,128, /* 데이터 */
1,128, 1,128, 1,128,
3,192, 3,192, 19,200,
147,201,147,201,255,255,
255,255,255,255,145,137,
145,137
};
<예 2> 컬러 이미지 폰트 예
{
0x0f,00,0x0f,00, /* 헤 더 */
/* 비트 플레인 3 플레인 2 플레인 1 플레인 0의 데이터 */
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
1,128, 0, 0, 0, 0, 1,128,
1,128, 0, 0, 0, 0, 1,128,
1,128, 0, 0, 0, 0, 1,128,
1,128, 0, 0, 0, 0, 1,128,
3,192, 0, 0, 0, 0, 3,192,
3,192, 0, 0, 0, 0, 3,192,
19,200, 0, 0, 0, 0, 19,200,
147,2
01, 0, 0, 0, 0, 147,201,
147,201, 0, 0, 0, 0, 147,201,
255,255, 0, 0, 0, 0, 255,255,
255,255, 0, 0, 0, 0, 255,255,
255,255, 0, 0, 0, 0, 255,255,
145,137, 0, 0, 0, 0, 145,137,
145,137, 0, 0, 0, 0, 145,137
};
<예 2>의 이미지는 연한 적색(LIGHT BLUE)의 로버트얼굴(?)모양이 되는데,왜 그런지 간단하게
알아보자. 우선 폰트 데이터를 크게
<그림 6>과 같이 4개의 구역으로 나눈다. 구성된 데이터의 조합에 따라 구성되는 폰트의 색상은
<표 1>과 같다. 이것을 좀더 상세히
다루자면 '데이터 1' 즉, D1 그룹은 색상의 강약을 조절하는 역할을 한다. <표 1>을 보면 '데이터
1' 그룹이 값이 존재할 때는 비교
적 색상이 밝은 색을 띠게 된다는 사실을 볼 수 있다. 그리고 '데이터 2'는 적색을 제어하게 되며
'데이터 3'은 녹색을 '데이터 4'는
청색을 각각 제어하는 것을 알아둘 필요가 있다.
+----------+ +----------+ +----------+ +----------+
| | | | | | | |
| | | | | | | |
| 데이터 1 | | 데이터 2 | | 데이터 3 | | 데이터 4 |
| | | | | | | |
| D1 | | D2 | | D3 | | D4 |
| | | | | | | |
| | | | |
| | |
+----------+ +----------+ +----------+ +----------+
<그림 6> 폰트 데이터의 구성
+--------------------+----------------------------------+
| 색 상 | D1 D2 D3 D4 의 조합 |
+--------------------+----------------------------------+
| BLACK | X X X X |
| BLUE | X X X O |
| GREEN | X X O X |
| CYAN | X X O O |
| RED | X O X X |
| MAGENTA | X O X O |
|* BROWN | X O O X * |
| LIGHTGRAY | X O O O |
| DARKGRAY | O X X X
|
|** LIGHTBLUE | O X X O ** |
| LIGHTGREEN | O X O X |
| LIGHTCYAN | O X O O |
| LIGHTRED | O O X X |
| LIGHTMAGENTA | O O X O |
| YELLOW | O O O X |
|* WHITE | O O O O * |
+--------------------+----------------------------------+
O : 이미지의 데이터가 들어간다.
X : 이미지의 데이터가 들어가지 않고 0의 값으로 채워 짐.
실제로 0이 데이터가 된다.
<표 1> 폰트 색상 대조
+----------------------------------------------------------------+
| |
|
|
| ∧ 녹 색 |
| | -+ |
| | +---------------------->하얀색(15,15,15) |
| 파| | | |
| | | | |
| 랑| | | |
| | | | |
| 색| | | |
| | | | |
| | +- -------------+ |
| | |
| |
|
| | |
| | |
| +-----------------------------------------------> |
| 검정색(0,0,0) 붉 은 색 |
| |
+----------------------------------------------------------------+
위의 3차원 공간은 R,G,B로 구성되어진 색상 공간이다. 검정색이 (0,0,0), 하얀색이 (15,15,15)
를 나타낸다.
즉 공간( 0 X=Y=Z 15 의 범위)에서 한 점은 3가지 색상( R: 적, G: 녹, B: 청)의 조합을 나타
낸다.

보시다시피 D1, D2, D3, D4의 조합은 8비트의 0, 1, 2, 3,..., 15의 증가하는 방향이고, 또한
색상도 독자가 한 권씩은 가지고 있을
터보 C의 매뉴얼에 나온 색상의 순서와 일치한다.
+-------------------------+----------------------------+
| C G A | E G A/ V G A |
+----------------+--------+-------------------+--------+
| 매크로 상 수 |실제 값 | 매크로 상수 |실제 값 |
+----------------+--------+-------------------+--------+
| BLACK | 0 | EGA_BLACK | 0 |
| BLUE | 1 | EGA_BLUE | 1 |
| GREEN | 2 | EGA_GREEN | 2 |
| CYAN | 3 | EGA_CYAN | 3 |
| RED | 4 | EGA_RED | 4 |
| MAGENTA | 5 | EGA_MAGENTA | 5 |
| BORWN | 6 | EGA_LIGHTGRAY | 7 |
| LIGHTGRAY | 7 | EGA_BROWN | 20 |
| DARKGRAY | 8 | EGA_DARKGRAY | 56 |

| LIGHTBLUE | 9 | EGA_LIGHTBLUE | 57 |
| LIGHTGREEN | 10 | EGA_LIGHTGREEN | 58 |
| LIGHTCYAN | 11 | EGA_LIGHTCYAN | 59 |
| LIGHTRED | 12 | EGA_LIGHTRED | 60 |
| LIGHTMAGENTA | 13 | EGA_LIGHTMAGENTA | 61 |
| YELLOW | 14 | EGA_YELLOW | 62 |
| WHITE | 15 | EGA_WHITE | 63 |
+----------------+--------+-------------------+--------+
<표 2> 터보 C에서 제공되는 색상
참고로 터보 C에는 <표 2>와 같이 BLACK은 0, BLUE는 1, GREEN은 2, 그리고 마지막 WHITE는 15
의 값이 매크로로 정의되어 있다. <표
1>은 필자가 여러 경험을 통해서 얻은 것으로서 어떤 확실한 이론적인 근거는 없지만, 적어도 필
자가 사용해 오는데 있어서 틀린 점은
없었다. 이제 D1과 D4에 폰트 값이 들어가 있기 때문에 <예 2>에서 만들어 놓은 폰트가 왜 연한
파랑색이 되는지를 이해했을 것이다.
그럼 다음으로 터보 C에서 지원되는 PUTIMAGE(), GETIMAGE() 함수에서 사용되는 비트맵 이미
지에 대해서 알아보자. <리스트 1>은
<예 2>의 이미지를 화면에 표시해 주는 프로그램으로, 이를 실행시키면 좀 이상하게 느낄 독자도
있을 것이다. 하지만 이것은 이후 내
용의 초석이 되는 아주 중요한 부분이다.
폰트 이미지 제어
<리스트 1>의 예제 프로그램은 5개의 폰트 이미지를 대각선 방향으로5가지 방법(COPY_PUT,
XOR_PUT, OR_PUT, AND_PUT, NOT_PUT)으로
뿌려 주는 프로그램이다. 그러나 이를 실행하면 이상하게도 4개의 이미지만 보일 것이다. 게다가
4개의 이미지 중 2개씩 똑같이 보일
것이다. 그렇다면"왜 이처럼 쓸모 없는 함수를 귀찮게 만들었을까?"하고 의아해 하는 독자도 있
을 것이다. 이는 그 내면에서 엄청난
사건(?)이 발생한 사실을 알지 못하기 때문이다. 그럼 그 진상을 밝혀 보자. COPY_PUT은 말 그대
로 폰트 이미지를 그대로 복사해 주는
것으로 XOR_PUT과 함께 프로그램에서 가장 많이 사용되는 기능이다. COPY_PUT은 폰트의 데이터 값
이 0인 곳을 모두 바탕색(검정)으로
표시해 준다. 즉 이미지가 위치할 곳의 픽셀과는 아무 상관없이 자신의 이미지만 나타낸다. 꼭
사계절 항상 푸르른 소나무 같다고나
할까. XOR_PUT은 배타적 논리합(exclusive OR ; 두 비트 간의 연산으로서 두 비트의 값이 같으
면 0, 틀리면 1이 된다)으로, 허큘리
스 프로그래밍에서 많이 사용된다. 하지만 나타낼 수 있는 색이 두 가지이기 때문에 이미지를 표
현하는 데는 제한적일 수밖에 없다.
이런 상황에서 사용자가 나타내어야 할 이미지를 바탕이 무엇이 되었든지 간에 상관없이 책임(?)
지고 나타낸다는 장점이 있으나, 컬러

(VGA 16컬러 또는 256컬러)에서는 먼저 나타내어져 있는 픽셀과 배타적 논리합에 의해 색상이 선
정되므로 사용 시 주의해야 한다. <리
스트 1>의 XOR_PUT은 두 번째 비행기의 색을 갈색으로 변화시켜 표시한다. 그리고 검정색으로 표
시된 폰트의 빈곳은 바탕색인 하얀색
으로 표시된다.
이와 같이 변화된 이유를 분석해 보자. 먼저 바탕색인 하얀색(색상값 : 15)과 이미지인 비행기
의 연한 파랑색
(색상 값:9)를 XOR(배타적 논리합)하면 다음과 같은 결과가 얻어진다.

D1 D2 D3 D4
연한 파랑색의 데이터 값 : O X X O
바탕색(하얀색)의 데이터 값 : O O O O
--------------------------------------------------------
XOR(1) : X O O X
그리고 이미지의 빈 곳(검은색)과 바탕색을 XOR하면 그 결과는 다음과 같다.
D1 D2 D3 D4
이미지 빈 곳(검정색)의 데이터 값 : X X X X
바탕색(하얀색)의 데이터 값 : O O O O
--------------------------------------------------------
XOR(2) : O O O O
XOR(1)의 결과 X, O, O, X에 해당하는 색상을 <표 1>에서 찾아 보면 갈색(BROWN)이 됨을 알 수
있으며, XOR(2)의 결과 O, O, O, O
에 해당하는 색상은 하얀 색(WHITE)이 됨을 알 수 있다. 즉 폰트의 이미지인 연한 파랑색의 비
행기는 갈색으로 변하게 된 것이고,
검은색의 바탕은 하얀색으로 변하게 된 것이다.
OR_PUT은 전에 표시된 이미지와 OR(논리합)을 수행한다. OR_PUT은 나중에 설명할 AND_PUT과 함
께 혼합되어 중요하게 쓰인다. <리스
트 1>의 OR_PUT은 엉뚱하게도 흔적 없는 비행기를 투명 비행기로 만들어 버린다. 필자 또한 이것
때문에 한 때 무척이나 고생한 바 있
다. 이것 역시 간단한 계산으로 증명할 수 있다. 바탕색인 하얀색과 이미지인 비행기의 연한
파랑색을 OR하면 그 결과는 다음과 같다.
D1 D2 D3 D4
연한 파랑색의 데이터 값 : O X X O
바탕색(하얀색)의 데이터 값 : O O O O
-------------------------------------------------
OR(1) : O O O O
그리고 바탕색인 하얀색과 이미지의 빈 곳(검은색)을 OR하면 다음과 같은 결과를 얻을 수 있다.
D1 D2 D3 D4
이미지 빈 곳(검정색)의 데이터 값 : X X X X
바탕색(하얀색)의 데이터 값 : O O O O
------------------------------------------------------
OR(2) : O O O O
OR(1)과 OR(2)의 결과를 보면 D1, D2, D3, D4가 차례로 O, O, O, O임을 알 수 있다. 이 결과에
해당하는 색상을 <표 1>에서 찾아보
면 하얀색이다. 따라서 비행기 색도 하얀색, 바탕색 또한 똑같은 하얀색이 됨에 따라, 비행기가
'버뮤다의 삼각지(?)' 속으로 흔적도
없이 사라지게 된 것이다.
AND_PUT은 OR_PUT과함께 조합을 이루어 아주 훌륭한 역할을 수행한다.
OR_PUT에서 설명한 것을 유추한다면 AND_PUT에 대해 대충 감을 잡았으리라고 본다. AND_PUT은
OR_PUT과 마찬가지로 이미지가 놓이게
될 곳의 이전 이미지와 AND_PUT(논리곱)의 연산을 수행한다. 바탕색과 이미지와 논리곱은 다음과
같다.
D1 D2 D3 D4
연한 파랑색의 데이터 값 : O X X O
바탕색(하얀색)의 데이터 값 : O O O O
------------------------------------------------------
AND(1) : O X X O
그리고 이미지의 빈 곳과 바탕색의 논리곱은 다음과 같다.
D1 D2 D3 D4
이미지 빈 곳(검정색)의 데이터 값 : X X X X
바탕색(하얀색)의 데이터 값 : O O O O
------------------------------------------------------
AND(2) : X X X X
위의 결과를 살펴보면 연한 파랑색의 이미지와 바탕색(하얀색)과의 논리곱 결과인 AND(1)은
O, X, X, O으로 원래의 비행기와 같은
연한 파랑색이다. 그리고 AND(2)의 결과도 이미지의 빈 곳(검정색)과 같은 X, X , X, X로서 원래
의 검정색으로 되었다. 결론적으로는
맨 처음의 COPY_PUT과 같아졌다.
NOT_PUT은 그렇게 사용 빈도가 높지는 않다. NOT_PUT은 COPY_PUT과 마찬가지로 이미지가 놓이게
될 이전의 픽셀과 아무런 관계가 없
다. 단지 이미지의 역이 복사된다는 것이 좀 틀릴 뿐이다. 즉 COPY_PUT과는 정반대의 기능을 수행
한다.

D1 D2 D3 D4
연한 파랑색의 데이터 값 : O X X O
------------------------------------------------
NOT(1) : X O O X
D1 D2 D3 D4
이미지 빈 곳(검정색)의 데이터 값 : X X X X
-----------------------------------------------------
NOT(2) : O O O O
위의 결과는 결국 XOR_PUT과 같아졌다. 그렇지만 박스(바탕)를 다른 색으로 했다면 결과는 달라
졌을 것이다. 이것으로 5가지의 이미
지 표현 방법에 대한 설명을 마친다.
예제 프로그램
이제는 지금까지의 설명을 토대로 예제 프로그램을 작성해 보자. <리스트 2>는 간단 하지만
(?) 갤러그 흉내를 낸 오락 프로그램으
로, 이 글을 다 읽게 된 후에 독자 여러분이 완전한 게임으로 만들 수 있을 것이다. <리스트 2>
에서 이미지를 뿌리는데 주로 사용한
것은 XOR_PUT이다. <리스트 2>의 주요 루틴은 프로그램 끝 부분의 main()함수 안에 포함된 다음과
같은 do while 문이다.
do{
if(bioskey(1))
if(key())
move_img();
}while(end!=Yes);
do while 문은 먼저 키가 눌렸는지를 검사한다. 만약 키가 눌렸으면 그 키에 따라서 다음의 행
동을 취한다. bioskey() 함수는 매우
중요한 함수로서 그 활용에 대해서 정확하게 알아야 한다. 특히 오락 프로그램에 있어서 핵심이
되는 부분이기 때문이다. 모드 값에
따른 bioskey()함수의 기능은 <표 3>과 같다.
+------+---------------------------------------------------------+
| 모드 | 내용 |
+------+---------------------------------------------------------+
| 0 | 만일 하위 8비트가 0이 아닐 경우 bioskey()는 큐에서 대기 |
| | 하고 있는 다음 키스트로크나 키보드에서 눌려진 다음 키를 |
| | 위한 아스키 문자를 리턴한다. 만일 하위 8비트가 0일 경우 |
| | 상위 8비트는 확장된 스캔코드(확장 키코드)가 된다. |
| | 즉 키보드 버퍼가 비어 있으면 새로운 문자가 눌릴 때까지 |
| | 끝없이 기다린다. 키보드 버퍼에 입력된 문자가 들어 있으 |
| | 면 그 문자코드 값을 리턴한다. 그리고 중요한 것은 키보드 |
| | 버퍼에서 읽어들인 문자코드를 제거한다. |
+------+---------------------------------------------------------+
| 1 | 키스트로크를 읽을 수 있는가를 검사한다. 0의 리턴값은 어 |
| | 떠한 키도 사용할 수 없음을 의미한다. 그렇지 않으면 다음 |
| | 키스트로크의 값이 리턴된다. 키스트로크 자체는 모드 값이 |
| | 0인 bioskey()의 다음 호출에 의해
서 리턴되는 상태가 된다.|
| | 즉 bioskey(1)은 키가 눌릴 때까지 기다리지 않고 키가 눌 |
| | 려있지 않으면 바로 0을 리턴해 준다. 또한 bioskey(0)과 다 |
| | 른 점은 키보드 버퍼에서 키코드 값을 읽어 들이되 키보드 |
| | 버퍼 속에 있는 값은 제거하지 않고 그대로 보존해 둔다는 |
| | 것이다. |
+------+---------------------------------------------------------+
| 2 | 현재 키가 쓰였는지 검사한다. 결과는 다음 값들의 |
| | 조합에 의해서 얻어진다. |
| | 0X01 : 오른쪽 키가 눌렸을 때 |
| | 0X02 : 왼쪽 키가 눌렸을 때 |
| | 0X04 : 키가 눌렸을 때 |
| | 0X08 : 키가 눌렸을 때 |
| | 0X10 : Scroll Lock 온(토글) |
| | 0X20 : Num Lock 온(토글) |


| | 0X40 : Caps Lock 온(토글) |
| | 0X80 : Ins 온(토글) |
+------+--------------------------------------------------------+

/* *************************
* EXAMPLE1.C *
* 1993. 1. 4. *
************************* */
#include "stdio.h"
#include "graphics.h"
char tank[32*4+4] = { /* 폰트를 이루는 데이터의 수 */
0x0f,00,0x0f,00, /* 헤더 */
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
1,128, 0, 0, 0, 0, 1,128,
1,128, 0, 0, 0, 0, 1,128,
1,128, 0, 0, 0, 0, 1,128,
1,128, 0, 0, 0, 0, 1,128,
3,192, 0, 0, 0, 0, 3,192,
3,192, 0, 0, 0, 0, 3,192,
19,200, 0, 0, 0, 0, 19,200,
147,201, 0, 0, 0, 0, 147,201,
147,201, 0, 0, 0, 0, 147,201,
255,255, 0, 0, 0, 0, 255,255,
255,255, 0, 0, 0, 0, 255,255,
255,255, 0, 0, 0, 0, 255,255,
145,137, 0, 0, 0, 0, 145,137,
145,137, 0, 0, 0, 0, 145,137,
};
void main()
{
int gmode=VGAHI,gdriv=VGA;
int midx,midy;
initgraph(&gdriv,&gmode,""); /* 그래픽모드로 초기화 */
midx=getmaxx()/2;
midy=getmaxy()/2;
setfillstyle(SOLID_FILL,WHITE); /* WHITE로 사각형의 내부를 */
bar(midx-150,midy-100,midx+150,midy+100); /* 빈틈없이 채운다 */

putimage(midx-100,midy-60,tank,COPY_PUT);
putimage(midx-50, midy-30,tank,XOR_PUT);
putimage(midx, midy, tank,OR_PUT);
putimage(midx+50, midy+30,tank,AND_PUT);
putimage(midx+100,midy+60,tank,NOT_PUT);
getch();
closegraph(); /* 프로그램 종료 */
}
<리스트 1> 예제 프로그램 put_item.c
/* ****************************** *
* EXAMPLE2.C *
* 1993, 1, 5 *
* ****************************** */
#include "stdio.h"
#include "graphics.h"
#include "stdlib.h"
#include "mem.h"
#define Ret 13
#define Lt 75
#define Rt 77
#define Up 72
#define Dn 80
#define F1 59
#define Sp 32
#define Esc 27
#define Yes 1
#define No 0
unsigned char tank[32*4+4] = {
0x0f,00,0x0f,00, /* 0 */
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
1,128, 0, 0, 0, 0, 1,128,
1,128, 0, 0, 0, 0, 1,128,
1,128, 0, 0, 0, 0, 1,128,
1,128, 0, 0, 0, 0, 1,128,
3,192, 0, 0, 0, 0, 3,192,
3,192, 0, 0, 0, 0, 3,192,
19,200, 0, 0, 0, 0, 19,200,
147,201, 0, 0, 0, 0, 147,201,
147,201, 0, 0, 0, 0, 147,201,
255,255, 0, 0, 0, 0, 255,255,
255,255, 0, 0, 0, 0, 255,255,
255,255, 0, 0, 0, 0, 255,255,
145,137, 0, 0, 0, 0, 145,137,
145,137, 0, 0, 0, 0, 145,137,
};
union key {
int i;
char ch[2];
} c;
int old_x,old_y; /* 글로벌 변수 선언 및 초기화 */
int Pos_x,Pos_y;
int end=No;
void move_img() /* 이미지를 이동시키는 함수 */
{
putimage(old_x,Pos_y,tank,XOR_PUT);
putimage(Pos_x,Pos_y,tank,XOR_PUT);
old_x=Pos_x;
}
void fire()
{
int i,sx;
sx=Pos_x+8;
setcolor(RED);
for(i=Pos_y; i>0;i-=10)
{
setcolor(RED); /* 총알을 표시 */
line(sx,i-10,sx,i-20);
delay(10);
setcolor(BLACK); /* 위에 총알을 지운다 */
line(sx,i,sx,i-10);
}
}
int key()
{
if(bioskey(1))
{
c.i = bioskey(0);
if(c.ch[0]==0)
switch(c.ch[1])
{
case Rt : if(Pos_x < 640) {
Pos_x+=15;
return 1; }
else return 1;
case Lt : if(Pos_x > 0) {
Pos_x-=15;
return 1; }
else return 1;
}
else
switch(c.ch[0])
{
case Sp : fire();
return 0;
case Esc : end=Yes;
return 0;
}
}
return 1;
}
void main()
{
int gmode=VGAHI,gdriv=VGA;
old_x=Pos_x=320;
Pos_y=400;
initgraph(&gdriv,&gmode,"");
putimage( Pos_x, Pos_y,tank,COPY_PUT);
do{
if(bioskey(1))
if(key())
move_img();
} while(end!=Yes);
closegraph();
}
<리스트 2> 갤러그 프로그램 example2.c

비디오 메모리 제어에 대해서
비디오 메모리 제어에 대해서 쉽게 쓴 사람들도 많고, 이 책을 통해서 많은 횟수에 걸쳐 소개가
된 걸로 알고 있다. 때문에 지금 상
태로서는 중복되는 사항이 많을것 같아 간단히 소개하기로 한다.

+-------------------------------------------------+
| SYSTEM BIOS |
+-------------------------------------------------+
| |
| VIDEO MEMORY |
+-------------------------------------------------+
| ∧ |
| | |
| | |
| | |

| HEAP-FREE MEMORY |
| | |
| | |
| | |
| ∨ |
|-------------------------------------------------+
| |
| PROGRAM |
+-------------------------------------------------+
| |
| RAM 상주 프로그램 |
+-------------------------------------------------+
| |
| D O S |
|
|
+-------------------------------------------------+
| |
| VECTOR TABLE |
+-------------------------------------------------+
DOS상에서의 프로그램 실행시 HEAP MEMORY의 사용상태.
그래픽상에서의 가장 까다로운점이 바로 비디오 메모리의 사용일것이다. 위의 표에서 나타나 있
듯이 비디오 메모리와 여러분들의 프
로그램의 사이에 힝-자유 메모리가 존재 하고있다. 이 영역은 여러분들의 테크닉에 따라서 비디오
메모리로 사용할수도 있고, 여러분
들이 작성한 프로그램(스택이나 데이터 등등)에 사용될수도 있는 완충 지대와도 같다. 따라서 이
완충 지대를 얼마나 많이 사용하느냐
에 따라 그 프로그램에 대한 평가(?)가 내려지기도 한다. 보통 IBM-PC에 내장된 인텔사의 8088
계열의 프로세서 시스템들은 비디오
메모리로서 약 64KB를 제공하고 있지만 아미가와 매킨토시 등에 사용되는 모토로라사의 68000칭
시리즈등은 프로세서가 수 메가 바
이트의 어드레스를 제어하는 기능등을 제공하기도 한다. 그래서 상용 오락과 같이 많은 비디오 메
모리를 요구하는 프로그램에는 이
들과 같은 칭이 많이 사용되지만 지금 우리가 하려고 하는것이 보통의 IBM-PC상에서의 프로그램이
기 때문에 IBM-PC에 맡추고자 한다.
우리는 보통 모자라는 비디오 메모리를 보충하고자 FAR POINTER를 이용하여 버퍼를 만들어서
그때그때 사용할수 있다. 그렇게 되면
약 하나의 시그멘트에 해당하는 64KB의 메모리를 더 사용할수 있기 때문에 총 128KB의 비디오 메
모리를 사용할수 있게 된다는 계산이
나오게 된다.
그러나 이러한 계산은 어디까지나 이론적인 것을 바탕으로 산출된것이다.
즉, char *p
p=farmalloc(131072L);
이 될것이다.
위와같이 할당된 메모리에 접근할수 있도록 지원하는 FAR POINTER를 만드는데 있어서 가장 많
이 사용되는것이 터보 C 에서 제공되
는 함수중에서 MK_FP()가 될것이다. 사실 MK_FP()는 시그멘트와 오프셋으로 조합으로 되는 FAR
POINTER를 만드는 매크로이다.
이함수를 사용하여 쉽게 비디오 메모리로 접근할수 있다. 물론 접근하는 데이터의 타입에 따라
서 다르지만 바이트 단위로 접근을 한
다는 가정하에 가로 0번째 픽셀 세로 10번째 픽셀의 주소는 다음과 같을것이다. 우선 바이트 단
위이므로 400번째 픽셀은 400/8=50이
된다.

char far *ptr;
unsigned SEG=0xa000;
ptr=MK_FP(SEG, 11*80+0);
이것 외에도 해당 번지를 찾는 계산법이 있으나, 이전에 너무 많이 소개가되어 있는 상태라서 생
략 해도 별 지장이 없을것 같다. 다
음 으로는 나중에 프로그램에서 자주 대하게 되는 getimage()함수에 대해서 간단히 알아야 할것
이 있다. 왜냐하면 getimage()함수를
쓰게 될때 반드시 비디오 메모리 버퍼를 할당해주어야 되기 때문이다. 나중에 버퍼를 다 사용한
후 에는 반드시 farfree()나, free()
함수로 버퍼를 해제해야 한다는 것이다. 만약 그렇게 하지 않는다면 아마도 언젠가는 시스템이
다운 되는등 예상하지 못했던 일이
발생할 가능성이 높다. 그리고 버퍼를 할당 하기위해 보통 imagesize() 함수를 많이 사용할것이
다.이때구해지는 byte수는 만약에 imag
esize(0,0,15,15); 이라고 한다면, 우리가 폰트이미지로 만들경우 위의 <예 2> 에서 나타나 있듯
이 총 132바이트(헤더 4바이트 + 이
미지 데이터 128바이트)가 필요하다. 그러나 imagesize()함수로 구한 바이트수는 얼마나 될까? 이
것을 아시는 독자는 별로 없으리라.
정답은 130바이트가 된다. 즉 헤더와 데이터부분으로 구성되는데 터보 C에서는 여유분으로서 2
바이트를 추가 했다. 그렇다고 해서
만약에 imagesize()로 구한 값에 (-2)를 한다면 아마도 재미있는 사건을 경험하게 될것이다. 호
기심이 강한 독자분들은 한번 해보길
바란다. 해봐서 손해날것은 없으니까.
필자가 언젠가 한번 프로그램을 짠적이있었는데 아마도
ptr= farmalloc(imagesize(0,0,639,479));
if(ptr==NULL)
{
perror("farmalloc error"); /* 만약 메모리 할당할만큼 */
exit(1); /* 메모리 공간이 충분치 않은 */
} /* 경우 시스템에러 메시지를 */
: /* 출력후 프로그램 종료. */
:
:
farfree(ptr);
으로 했던기억이 난다. 필자의 욕심은 이론적으로는 최고 64KB가 비디오 버퍼로 제공되므로
한 화면 전체를 버퍼 ptr에 저장시켜
조금이나마 빠르게 화면을 원위치(?) 시키려고 시도를 했는데, 무참히도 실패한 기억이 난다. 다
행히도 만약을 위해 if문을 넣었기에
망정이지 자칫 잘못했으면 그동안 했던 수고가 무효가 될뻔 했다. 실제로 비디오 메모리를
farmalloc()로 보충하는 데는 하나의 화면
전체를 저장할만큼 충분하지가 못하다는 것을 잘알아야 한다. 이론적으로 터보 C의 라이브러리에
서는 " 모든 사용가능한 램을 할당
할 수도 있으며, 64KB보다 큰 블럭을 할당할 수도 있고, 화 포인터( 또는 블럭이 64KB이상 이면
huge 포인터)를 사용해서 할당된 블럭
을 억세스할 수 있다." 라고 되어있으나 farmalloc()이나, farcalloc()모두 실제로는 그렇지 않
다. 독자들도 컴파일 모드를 huge나 l
arge로 해서 해보길 바란다. 이로서 이미지 폰트와 getimage()함수와의 함수관계가 더욱 확실해졌
을 것이다. 앞에서 설명한대로 비디
오 메모리를 제어하기 위해서는 보통 MK_FP()를 통해서 비디오 메모리의 어드레스를 억세스하고,
farmalloc()이나, farcalloc()등을
통해서 동적 메모리를 할당받아 사용하게 되는데, 이 제한된 메모리를 어떻게하면
좀더 효율적으로 사용할수 있는지에 대해서는 앞으로 독자들이 프로그램을 작성하면서충분히 알수

있으리라고 생각된다. 다만 사족을
더하자면 "풍선"을 생각하면 될것이다. 즉 풍선은 어느정도 공기가 차면 더이상 지탱하지 못하
는 한계가 분명히 있다. 이러한 한계
를 무시하면 '펑'하고 풍선이 흔적도 없이(?) 사라지는 것을 잘알수 있을것이다. 그러므로 일정한
기간동안 사용하고 필요없는 비디오
메모리는 즉시 해방을 시켜 풍선을 안전하게 지켜야 할것이다.

<리스트 3> PCX파일 보여주기 PCXVIEW.C
/*************************************
THIS IS THE SAMPLE PROGRAM
PROGRAM-ID. PCXVIEW.C
1993. 2. 25.
**************************************/
#include
#include
#include
#include
#include
#include "dos.h"
#include "dir.h"
#include
#include
#include
#include
/****************************

메크로 상수 선언
*****************************/
#define EDIT_XMIN 16 /* X(392) */
#define EDIT_XMAX 630
#define EDIT_YMIN 16 /* 16+392 */
#define EDIT_YMAX 405
#define Ret 13
#define Esc 27
#define Lt 75
#define Rt 77
#define Up 72
#define Dn 80
#define No 0
#define Yes 1
#define offset(x,y) (x*2+y*160)

/*********************************
글로벌 변수 초기화
**********************************/
int cur_pic_pos=3,pic_pos=3;
int First=Yes;
int last=0;
int End=No;
int cur_mode;
char *fil;
char *p;
char *buffer_p;
char *dir[50], path[30];
int pic_choice=No;
int imagewidth,imageheight ;
unsigned char far *video_addres;
unsigned char far *addr[480], far *disp_addr[480];
FILE *fp;

union inkey {
int i;
char ch[2]; } c;
typedef struct {
unsigned char red;
unsigned char green;
unsigned char blue; }palette_info;
struct pcx {
char header;
char version;
char encode;
char bitperpixel;
int x1, y1;
int x2, y2;
int hres, vres;
palette_info palette[16];
char mode;
char nplanes;
int byteperline;
int palette_info;
int scaner_hres, scaner_vres;
char unused[54]; } pcxheader;
/********************
DRAW BOARD
********************/
void setcursor(int type)
{
_setcursortype(type);
}
void init_graph()
{
int gdrv=VGA,gmod=VGAHI,errorcode;
video_addres=(unsigned char far *)0xb8000000;
setcursor(0);
clrscr();
initgraph(&gdrv,&gmod," ");
errorcode= graphresult();
if(errorcode != grOk) {
perror(" YOURE WRONG!!!");
perror(" TRY IT AGAIN!");
printf(" Graphics error : %s\n",grapherrormsg(errorcode));
printf(" Press any key to Dos!\n");
getch();
exit(1);
}
}
void end_graph()
{
fcloseall();
cleardevice();
closegraph();
setcursor(2);
}
void kbox(int sx,int sy,int ex,int ey,int dep,int col)
{
int i;
setcolor(col);
for(i=0;i rectangle(sx+i,sy+i,ex-i,ey-i);
}

void kbar(int x1,int y1,int x2,int y2,int col)
{
int k;
setfillstyle(SOLID_FILL,col);
bar(x1,y1,x2,y2);
}
void kfilbar(int x,int y,char *s,int mod)
{
setcolor(2);
putimage(x,y,s,mod);
}
void outgreg(char index,char data)
{
outportb(0x3ce,index);
outportb(0x3cf,data);
}
void kline(int old_x,int old_y,int x,int y,int col,char put_method)
{
setcolor(col);
outgreg(3,put_method);
line(old_x,old_y,x,y);
}
void kfilbox(int x1, int y1, int x2, int y2,int col)
{
int y=y1;
for(; y<=y2; y++)
kline(x1,y,x2,y,col,COPY_PUT);
}
void box3d(int w_stx,int w_sty,int w_ex,int w_ey,int col,int b_col,int depl)
{
int depth;
kline(w_stx,w_sty,w_ex,w_sty,WHITE,COPY_PUT);
kline(w_stx,w_sty,w_stx,w_ey,WHITE,COPY_PUT);
kline(w_stx+1,w_sty+1,w_ex-1,w_sty+1,WHITE,COPY_PUT);
kline(w_stx+1,w_sty+1,w_stx+1,w_ey-1,WHITE,COPY_PUT);
kline(w_ex,w_sty,w_ex,w_ey,DARKGRAY,COPY_PUT);
kline(w_stx,w_ey,w_ex,w_ey,DARKGRAY,COPY_PUT);
kline(w_ex-1,w_sty+1,w_ex-1,w_ey-1,DARKGRAY,COPY_PUT);
kline(w_stx+1,w_ey-1,w_ex-1,w_ey-1,DARKGRAY,COPY_PUT);
setcolor(b_col);
for(depth=2; depth rectangle(w_stx+depth,w_sty+depth,w_ex-depth,w_ey-depth);
kline(w_stx+depth+1,w_sty+depth+1,w_ex-depth-1,w_sty+depth+1,DARKGRAY,COPY_PUT);
kline(w_stx+depth+1,w_sty+depth+1,w_stx+depth+1,w_ey-depth-1,DARKGRAY,COPY_PUT);
kline(w_stx+depth+1,w_ey-depth-1,w_ex-depth-1,w_ey-depth-1,WHITE,COPY_PUT);
kline(w_ex-depth-1,w_sty+depth+1,w_ex-de

pth-1,w_ey-depth-1,WHITE,COPY_PUT);
kfilbox(w_stx+depth+2,w_sty+depth+2,w_ex-depth-2,w_ey-depth-2,col);
}
void write_string(int x,int y,char *p,int attribute)
{
char far *v;
register int i;
v=video_addres+offset(x,y); /* 비디오 램을 직접 제어 */
for(i=x;*p;i++) {
*v++ =*p++;
*v++ =attribute;
}
}
/********************************
PICTURE LOAD
그림 파일을 읽어 온다.
*********************************/
void init_pic()
{
int i;
for(i=0; i<480; i++)
{ /* 80*4 */
addr[i] = (unsigned char far *) farcalloc(320, sizeof(char));
if(addr[i] == NULL) {
for(; i>0 ; i--) farfree(addr[i]);
clrscr();
perror("Memory allocation not enough(init_pic)!");
perror("Compile mode is NOT LARGE !!.");
exit(1);
}
}
for(i=0; i<480; i++)
disp_addr[i]=(unsigned char far *)(0xA0000000L+i*80);
for(i=0; i<480; i++)
memset(addr[i], 0xFF, 320);
}
/********************************
pcx 파일의 헤더를 읽어옴.
*********************************/
int get_info(char *filee)
{
unsigned index;
if((fp=fopen(filee, "rb")) == NULL) {
perror("Picture file is not found");
return -1;
}
if(fread(&pcxheader, sizeof(struct pcx), 1, fp) != 1) {
perror("Cannot read .pcx file...!");
return -1;
}
if(pcxheader.header != 10) {
perror("Not .pcx file...!");
return -1;
}
if(pcxheader.x2!=639 || pcxheader.y2 != 479) {
perror("BGI Error Not VGAHI file...!");
return -1;
}
return 0;
}
void PCX_load_to_buf()
{
unsigned i, j, encode_count=0;
unsigned char indata;
if(pcxheader.x2 == 319) {
imagewidth = 0;
imageheight = 0;
return;
}
else {
imagewidth = 640;
switch(pcxheader.y2) {
case 479 :
imageheight = 480;
break;
default :
imagewidth = 0;
imageheight = 0;
return;
}
}
if(pcxheader.encode == 1)
for(i = 0; i < imageheight; i++)
for(j = 0; j < pcxheader.byteperline*pcxheader.nplanes;) {
indata=getc(fp);
if((indata & 0xc0) == 0xc0) {
encode_count = indata & ~0xc0;
indata = getc(fp);
if((signed int)indata == EOF)
return;
while(encode_count--) {
*(addr[i]+j) = indata;
j++;
}
}
else {
*(addr[i]+j) = indata;
j++;
}
}
if(pcxheader.encode == 0)
for(i = 0; i < imageheight; i++)
for(j = 0; j < pcxheader.byteperline*pcxheader.nplanes; j++)
*(addr[i]+j) = getc(fp);
}
void PCX_buf_to_disp()
{
unsigned i, j;
for(i= EDIT_YMIN; i<= EDIT_YMAX; i++)
for(j = 0; j < pcxheader.nplanes; j++) {
set_plane(j);
memcpy(disp_addr[i]+2, addr[i+2]+j*80+2, 76);
}
}
int set_plane(int i)
{
int table[4] = {1,2,4,8};
outportb(0x3c4, 2);
outportb(0x3c5, table[i]);
return 1;
}
void PCX_disp_to_buf()
{
unsigned i, j;
for(i= EDIT_YMIN; i<= EDIT_YMAX; i++)
for(j = 0; j < pcxheader.nplanes; j++) {
set_plane_read(j);
memcpy(addr[i]+j*80+2, disp_addr[i]+2, 76);
}
}
int set_plane_read(int i)
{
outportb(0x3ce, 4);
outportb(0x3cf, i);
return 1;
}
void move_pic_cur()
{
write_string(28,1,"SELECT PCX PICTURE",1);
write_string(7,2,path,4);
write_string(2,cur_pic_pos,dir[cur_pic_pos-3],9);
write_string(2,pic_pos,dir[pic_pos-3],11);
cur_pic_pos= pic_pos;
}
int pic_key()
{
if(bioskey(1))
{
c.i= bioskey(0);
if(c.ch[0]==0)
switch(c.ch[1]) {
case Rt :
case Dn :
if(pic_pos < last+4) {
pic_pos+= 1;
return 1;
}
else {
pic_pos= last+4;
return 0;
}
case Up :
case Lt :
if(pic_pos > 3) {
pic_pos-= 1;
return 1;
}
else {
pic_pos= 3;
return 0;
}
}
else
switch(c.ch[0]) {
case Ret :

pic_choice=Yes;
return 0;
case Esc :
End=Yes;
return 0;
default :
return 0;
}
}
return 0;
}

/********************************
current directory 내의
pcx 파일들을 파악.
**********************************/
void get_picture()
{
struct ffblk ffblk;
int done, k, i=0 ;
cur_mode=getgraphmode();
restorecrtmode();
p=(unsigned char *)malloc(2*12);
if(!p) {
printf("\n\t Memory is not enough error.\n");
exit(1);
}
getcwd(path, 50);
chdir(path);
write_string(28,1,"SELECT PCX PICTURE",1);
write_string(7,2,path,4);
done = findfirst("*.pcx",&ffblk,0);
while(!done) {
dir[i] = calloc(strlen(ffblk.ff_name)+1, sizeof(char));
strcpy(dir[i], ffblk.ff_name);
done = findnext(&ffblk);
i++;
}
last = i;
for(k=0; last; k++,last--) {
if(k > last) break;
write_string(2,k+3,dir[k],9);
}
write_string(2,3,dir[0],11);
do {
if(bioskey(1))
if(pic_key())
move_pic_cur();
} while(pic_choice!=Yes && End!=Yes);
pic_choice=No;
fil=dir[pic_pos];
pic_pos=cur_pic_pos=3;
setgraphmode(cur_mode);
}
void load_pic()
{
int error;
if(First==Yes)
init_pic();
First=No;
get_picture();
error = get_info(fil);
if(error != -1) {
box3d(0,0,640,424,BLACK,LIGHTGRAY,9);
box3d(0,426,640,480,BLACK,LIGHTGRAY,9);
setcolor(WHITE);
settextstyle(TRIPLEX_FONT,HORIZ_DIR,4);
outtextxy(200,200,"PLEASE WAIT ....");
setcolor(LIGHTBLUE);
settextstyle(TRIPLEX_FONT,HORIZ_DIR,2);
outtextxy(18,440,"THIS IS PCXVIWER, V10.0 CONGRATULATIONS ` C Magazine '.");
PCX_load_to_buf();
PCX_buf_to_disp();
fclose(fp);
}
c.i=bioskey(0);
if(c.ch[0]==Esc)
End=Yes;
}
/*****************************
main() 루틴.
간단할수록 좋다.
******************************/
void main()
{
init_graph();
do{
load_pic(); /* ESC 키를 누르면 종료 */
} while( End !=Yes );
end_graph();
}

댓글 없음:

댓글 쓰기

국정원의 댓글 공작을 지탄합니다.

UPBIT is a South Korean company, and people died of suicide cause of coin investment.

 UPBIT is a South Korean company, and people died of suicide cause of coin. The company helps the people who control the market price manipu...