5. 파일 읽기 및 쓰기와 PostScript
1. StreamReader와 StreamWriter
우리가 한글과컴퓨터 등등의 프로그램을 다루면서 설정을 만지작거리고, 다시 프로그램을 껐다가 켜면 그 설정이 그대로 남아있는 것을 확인할 수 있다. 이는 그 설정 값이 휘발성 메모리가 아니라 영구적인 메모리에 할당되기 때문인데, 거의 반영구적인 메모리로는 하드 디스크, SSD 등을 들 수 있다. 우리가 설정해 준 것을 프로그램이 파일 형태로 하드 디스크 등등에 남기고, 우리가 나중에 프로그램을 다시 켤 때 프로그램이 그 파일을 불러와서 파일에 기록된 설정 값 대로 움직여 주기 때문에, 프로그램을 껐다가 다시 켜더라도 우리가 설정해 준 대로 프로그램이 잘 돌아가는 것이다. 이를 실현하기 위해서는 우선 파일을 읽고 쓰는 방법을 알아야 한다.
우리는 여기서 StreamReader와 StreamWriter를 이용하여 파일을 읽고 쓸 것이다. 이러한 것들은 네임스페이스 System.IO 안에 들어있으므로, 코드 상위에 다음과 같이 using System.IO라는 문구를 추가해야 한다.
그림 1 네임스페이스를 추가한 모습
우리가 실행하는 프로그램이 있는 폴더 안에 input.txt라는 텍스트 파일이 있다고 하자. 우리는 input.txt를 읽고 싶다. 그렇다면 StreamReader를 선언해야 하는데, 선언하는 꼴은 다음과 같다.
StreamReader sr = new StreamReader(“input.txt”);
sr은 StreamReader의 이름으로 사용자가 임의대로 설정할 수 있다. 그리고 new StreamReader 옆의 괄호에 파일 이름을 쓰면, StreamReader은 이제 그 파일을 읽을 수 있는 준비 상태가 된다. 텍스트 파일의 경우, 우리는 이 파일을 ‘한 줄 한 줄’ 읽어 나가야 하는데, 이때 한 줄을 읽고 읽은 것을 string temp에 담기 위해서는 다음과 같이 써야 한다.
string temp = sr.ReadLine();
종종 우리는 텍스트 파일로 작성된 표를 만나볼 수 있다. 표의 칸을 구분하기 위해 칸마다 데이터마다 띄어쓰기가 되어 있거나, 반점이 찍혀 있는 것을 볼 수 있는데, 이렇게 칸을 구분하여 하나의 배열에 담는 방법이 있다. 공백으로 구분되어 있는 칸을 나눠 데이터를 저장하는 배열의 이름을 tempArr로 한다면,
string[] tempArr = temp.Split(‘ ‘);
이렇게 하면 배열의 원소마다 우리가 읽은 줄, 즉 표의 행에 있는 칸의 내용이 담긴다.
이제 이러한 방식으로 우리는 파일을 파일이 끝날 때까지 읽고 싶다. 파일이 끝났는지 알려주는 지표가 StreamReader 클래스 안에 이미 있는데, 이는 EndOfStream이다. EndOfStream이 true이면 파일이 끝났다는 말이 된다. 이를 활용하면 다음과 같이 쓸 수 있다.
while(!sr.EndOfStream)
{
파일을_읽는_코드;
}
파일을 쓰는 방법도 StreamReader를 쓰는 방법과 별반 다를 게 없다. 파일을 쓰기 위해서는 StreamWriter라는 것을 써야 하는데, 만약 output.txt에 데이터를 작성하고 싶다면, 다음과 같이 선언하면 된다. (StreamWriter의 이름은 sw로 임의로 지정함)
StreamWriter sw = new StreamWriter(output.txt);
이도 똑같이 파일을 한 줄 한 줄 쓰는데, 만약 “Hello World!”라고 한 줄 쓰고 싶다면, 다음과 같이 코드를 작성하면 된다.
sw.WriteLine(“Hello World!”);
StreamReader와는 달리, StreamWriter는 쓰기 작업이 모두 끝난 뒤에 반드시 StreamWriter을 닫아야 한다. 그렇지 않으면 오류가 발생하는데, 닫는 코드는 다음과 같다.
sw.Close();
2. PostScript
위의 StreamReader와 StreamWriter을 사용하여 도형을 그려보자. PostScript는 실시간으로 도형을 그릴 수는 없지만, 난이도가 낮기 때문에 많이 사용된다. PostScript로 작성된 파일은 .ps 확장자를 가진 파일로 저장되며, 여러 가지 프로그램으로 이 파일을 열어서 확인할 수 있는데, 필자는 Rampant Logic Poscript Viewer을 이용하였다.
PostScript가 작동하는 방식은 우리가 일상생활에서 도형을 그리는 방법과 동일하다. 만약에 우리가 삼각형을 그리고자 하면, 우리는 도형을 그리기 위해 연필을 집고, 연필을 종이의 어떤 점에 찍은 뒤, 연필을 종이에 댄 상태에서 연필을 다른 두 점으로 이동시키고, 다시 원래의 점으로 연필을 이동시킨 다음에, 종이에서 연필을 뗀다. PostScript도 이와 비슷하게 움직인다.
우리가 연필을 집는 과정을 PostScript에서는 “newpath”라고 한다. newpath라고 명령하면 PostScript는 도형을 그리는 ‘연필’을 집게 된다. 그리고 우리가 연필을 종이에 찍을 때 ‘어떤 특정한 점’에 찍듯이, PostScript가 종이를 찍게 될 좌표를 우리가 명령으로 알려주어야 한다. 그렇게 하기 위해서 우리는 다음과 같이 명령을 내려야 한다.
(x좌표) (y좌표) moveto
연필을 찍었으면, 연필을 종이에 댄 상태에서 다른 점으로 옮겨야 종이에 선이 그려질 것이다. 우리가 연필을 점 P로 옮기기 위해서 우리는 다음과 같이 명령해 줘야 한다.
(P의 x좌표) (P의 y좌표) lineto
이렇게 선을 긋고 다시 연필이 맨 처음에 연필을 찍었던 점으로 되돌아온 상태라고 하자. 대개 도형을 그리면 도형은 열린 상태가 아닌, 닫힌 상태가 되는데, 도형을 완전히 닫힌 상태로 만들기 위해서 우리는 다음과 같이 명령해줘야 한다.
closepath
마지막으로, 우리는 연필을 종이에서 떼어야 한다. 이는 다음과 같이 명령해 주면 할 수 있다.
stroke
하지만 이렇게만 하면 우리는 도형의 테두리, 즉 선만을 그리게 된다. 도형을 색칠하기 위해서는, 우선 색을 결정해야 하고, 그 다음에 선 안을 채워야 한다. 이를 수행하려면 위에서 closepath와 stroke 사이에 다음의 명령어를 쓰면 되는데, 색은 검정색으로 하겠다.
1 setgray
fill
setgray가 흑백으로 색을 결정하는 명령어인데, setgray 앞의 0과 1 사이의 값이 0에 가까울수록 흰색에 가까우며, 1에 가까울수록 검정색에 가깝다. fill은 지정한 색으로 닫힌 선 안을 채우는 명령어이다.
우리는 흑백 말고 화려한 색으로 도형을 채우고 싶을 수 있다. 이때 우리는 빛의 삼원색을 이용하여 색을 지정해주는데, 그 명령어는 다음과 같다.
(R 비중) (G 비중) (B 비중) setrgbcolor
이 비중을 모두 1로 하면 흰색, 이 비중을 모두 0으로 하면 검정색이 나온다. 그리고 RGB에 해당되는 비중 중 하나만 1로 하고 나머지를 0으로 하면, 1의 비중을 넣은 그 색이 출력된다.
이제 이 명령어들을 가지고 실제로 삼각형을 그려보자.
using System;
using
System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.IO;
namespace helloworld2
{
class Program
{
static void Main(string[] args)
{
StreamWriter sw = new StreamWriter("output.ps");
sw.WriteLine("100 100
moveto");
sw.WriteLine("200 100
lineto");
sw.WriteLine("100 200
lineto");
sw.WriteLine("100 100
lineto");
sw.WriteLine("closepath");
sw.WriteLine("1.0 0.7 0
setrgbcolor");
sw.WriteLine("fill");
sw.WriteLine("stroke");
sw.Close();
}
}
}
이렇게 하면 (100,100), (200,100), (100,200)을 꼭짓점으로 가지는 주황색 삼각형이 그려진다. 이를 실제로 파일을 열어 확인해보자.
그림 2 PostScript으로 그린 결과
우리는 기하와 벡터 문제와 같은 수학 문제를 풀 때 3차원 입체 도형을 2차원에 나타내어 푼다. 그 이유는 간단하게도, 우리가 종이에 그릴 수 있는 것은 2차원 상의 도형밖에 없기 때문이다. 그래서 우리는 투영도를 그려서 3차원 도형을 2차원으로 표현하여 그 모습을 짐작하는 것이다.
이것 또한 PostScript로 구현할 수 있는데, 이를 위해서는 미리 생각해 줘야 하는 장치들이 있다. 첫 번째로, 입체 도형을 바라보는 카메라가 필요하다. 이때 편의성을 위해 우리는 카메라를 한 점으로 생각할 것이다. 두 번째로, 카메라와 입체 도형 사이에 있는 평면이 필요하다.
그림 3 장치
그리고 카메라와 입체 도형의 꼭짓점을 잇는 직선을 생각하고, 직선과 평면 사이의 교점을 모두 구한다. 교점과 꼭짓점은 일대일 대응이 이뤄지는데, 입체 도형의 모서리가 어떤 점과 어떤 점을 잇는 선분인지 고려하여 대응에 맞게 교점과 교점 사이를 선분으로 잇는다. (카메라와 A를 잇는 직선과 평면 사이의 교점이 A’이고, B를 잇는 직선과 평면 사이의 교점이 B’라면, A-B를 잇는 선분 → A’-B’를 잇는 선분에 대응이 되도록) 그러면, 거리에 따른 순서가 없는 입체도형의 투영도가 그려진다.
하지만 일상생활에서 우리가 보는 입체 도형의 경우, 뒤에 가려진 부분은 우리 눈에 보이지 않는다는 것을 발견할 수 있다. 그렇다면 가장 우선 해야 할 것은 꼭짓점과 카메라 사이의 거리를 알아내는 것이고, 두 번째로 해야 할 것은 그 거리에 따라 꼭짓점을 ‘정렬’하는 일이다. 그리고 PostScript로 평면을 그릴 때, 거리가 가장 먼 꼭짓점으로 이루어진 평면부터 차례차례 그려서 마지막엔 거리가 가장 가까운 꼭짓점으로 이루어진 평면을 그려서 뒤에 가려진 부분은 보이지 않도록 처리하면 된다. 문제는 꼭짓점을 ‘정렬’하는 일인데, 이는 부록에서 살펴보도록 한다.