문제 5. 그래픽 편집기(Graphical Editor)
PC/UVa ID:110105/10267, 인기도:B, 성공률: 낮음, 레벨: 1
포토샵 같은 그래픽 편집기를 이용하면 텐스트 편집기에서 문서를 수정하는 것처럼 비트맵 이미지를
수정할 수 있다. 이미지는 픽셀로 이루어진 M X N 배열로 표현되며 각 픽셀마다 색이 주어진다.
간단한 대화형 그래픽 편집기를 흉내를 낼 수 있는 프로그램을 만들어보자.
>> 입력
입력은 한줄에 하나씩의 편집기 명령으로 구성된다. 각 명령은 줄 맨 앞에 있는 대문자 한 개로 표현된
다. 매개변수가 필요한 경우에는 그 명령과 같은 줄에 스페이스로 분리되어 매개변수가 입력된다.
픽셀 좌표는 1이상 M이하의 열번호와 1 이상 N 이하의 행 번호, 이렇게 두 개의 정수로 표현되며 이때
1 <= M, N <= 250라는 조건이 만족된다. 표의 왼쪽 위 꼭지점을 원점으로 삼는다. 색은 대문자로 지정된다.
편집기에서 받아들이는 명령은 다음과 같다.
I M N 모든 픽셀이 흰색(O)으로 칠해진 M X N 이미지를 새로 만든다.
C 모든 픽셀을 흰색(O)으로 칠해서 표를 지운다. 이미지의 크기는 바뀌지 않는다.
L X Y C (X,Y) 픽셀을 주어진 색으로 칠한다.
V X Y1 Y2 C X열에 Y1행과 Y2행(Y1, Y2 포함) 사이에 주어진 색(C)으로 수직방향 직선을 긋는다.
H X1 X2 Y C Y행에 X1열과 X2열(X1, X2 포함) 사이에 주어진 색(C)으로 수평방향 직선을 긋는다.
K X1 Y1 X2 Y2 C 주어진 색(C)으로 채워진 직사각형을 그린다. (X1, Y1)은 왼쪽 위 끝점, (X2, Y2)는
오른쪽 아래 끝 점을 의미한다.
F X Y C R 영역을 주어진 색(C)으로 채우는데, 영역 R은 다음과 같이 정의된다. (X,Y) 픽셀은
R에 속한다. (X,Y) 픽셀과 색이 같고 R에 포함된 픽셀과 맞닿은 부분이 하나라도 있다면
그 픽셀도 R영역에 포함된다.
S Name 파일명을 MSDOS 8.3 형식으로 출력하고 그 뒤에 현재 이미지의 내용을 출력한다.
X 세션을 종료한다.
>> 출력
S NAME이라는 명령이 있을 때마다 NAME으로 주어진 파일명을 출력하고 현재 이미지의 내용을 출력한다.
각 행은 각 픽셀의 색을 나타내는 문자로 표시된다. 출력 예를 참고하자.
I,C,L,V,H,K,F,S,X를 제외한 문자로 정의된 명령이 있으면 그 줄 전체를 무시하고 다음 명령으
넘어간다. 다른 오류에 대해서는 프로그램의 행동을 예측할 수 없다.
>> 입력예
I 5 6
L 2 3 A
S one.bmp
G 2 3 J
F 3 3 J
V 2 3 4 W
H 3 4 2 Z
s two.bmp
x
>> 출력예
one.bmp
OOOOO
OOOOO
OAOOO
OOOOO
OOOOO
OOOOO
two.bmp
JJJJJ
JJZZJ
JWJJJ
JWJJJ
JJJJJ
JJJJJ
main.cpp
#include "Command.h"
#define LINE_LENGTH 255
void main(void)
{
Command cmd;
bool keep = true;
while( keep )
{
char buff[LINE_LENGTH];
string strcmd;
// stream에서 문자열 한줄을 입력받는다.
cin.getline(buff, LINE_LENGTH);
// char* -> string
strcmd = buff;
// 명령어 해석기에 전달.
// return값으로 프로그램을 계속 진행할지 판단.
keep = cmd.Parser(strcmd);
}
}
Command.h
#pragma once
#ifndef _Command_h_
#define _Command_h_
#include "EditControl.h"
#include <string>
#include <sstream>
class Command
{
public:
Command();
~Command();
bool Parser(string const cmd);
private:
EditControl mControl;
};
#endif
Command.cpp
#include "Command.h"
Command::Command()
{
}
Command::~Command()
{
}
bool Command::Parser(string const cmd)
{
// 기본적인 return값은 true
bool ret = true;
string buf; // 임시 버퍼
stringstream ss(cmd);
vector<string> tokens;
// 명령어를 공백으로 나눠 vector에 저장.
while ( ss >> buf )
tokens.push_back(buf);
if ( tokens.size() > 0 ) // 명령이 한단어 이상이고
if ( tokens[0].length() == 1 ) // 명령어가 한글자일때만 해석
{
char sw;
memcpy(&sw, tokens[0].c_str(), sizeof(char));
// 명령에 맞게 인수를 생성하여 Control에 전달.
switch(sw)
{
case 'I':
if ( tokens.size() == 3 )
{
int m = atoi(tokens[1].c_str());
int n = atoi(tokens[2].c_str());
this->mControl.New(m, n);
}
break;
case 'C':
if ( tokens.size() == 1 )
{
this->mControl.Clear();
}
break;
case 'L':
if ( tokens.size() == 4 )
{
if ( tokens[3].length() == 1)
{
int x = atoi(tokens[1].c_str());
int y = atoi(tokens[2].c_str());
char c;
memcpy(&c, tokens[3].c_str(), sizeof(char));
this->mControl.Point( x, y, c );
}
}
break;
case 'V':
if ( tokens.size() == 5 )
{
if ( tokens[3].length() == 1)
{
int x = atoi(tokens[1].c_str());
int y1 = atoi(tokens[2].c_str());
int y2 = atoi(tokens[3].c_str());
char c;
memcpy(&c, tokens[4].c_str(), sizeof(char));
this->mControl.VLine(x, y1, y2, c);
}
}
break;
case 'H':
if ( tokens.size() == 5 )
{
if ( tokens[3].length() == 1 )
{
int x1 = atoi(tokens[1].c_str());
int x2 = atoi(tokens[2].c_str());
int y = atoi(tokens[3].c_str());
char c;
memcpy(&c, tokens[4].c_str(), sizeof(char));
this->mControl.HLine(x1, x2, y, c);
}
}
break;
case 'K':
if ( tokens.size() == 6)
{
if ( tokens[5].length() == 1 )
{
int x1 = atoi(tokens[1].c_str());
int y1 = atoi(tokens[2].c_str());
int x2 = atoi(tokens[3].c_str());
int y2 = atoi(tokens[4].c_str());
char c;
memcpy(&c, tokens[5].c_str(), sizeof(char));
this->mControl.Box(x1, y1, x2, y2, c);
}
}
break;
case 'F':
if ( tokens.size() == 4 )
{
if ( tokens[3].length() == 1 )
{
int x = atoi(tokens[1].c_str());
int y = atoi(tokens[2].c_str());
char c;
memcpy(&c, tokens[3].c_str(), sizeof(char));
this->mControl.MagicPoint(x, y, c);
}
}
break;
case 'S':
if ( tokens.size() == 2 )
{
string fileName = tokens[1].c_str();
this->mControl.SaveFile(fileName);
}
break;
case 'X':
if ( tokens.size() == 1 )
{
this->mControl.End();
}
// 세션 종료를 위한 return값 false
ret = false;
break;
}
}
return ret;
}
EditControl.h
#pragma once
#ifndef _EditControl_h_
#define _EditControl_h_
#include <iostream>
#include <vector>
using namespace std;
class EditControl
{
public:
EditControl();
~EditControl();
private:
int n, m;
bool session;
char **bitmap;
public:
void New(int const m, int const n);
void Clear(void);
void Point(int const x, int const y, char const color);
void VLine(int const x, int const y1, int const y2, char const color);
void HLine(int const x1, int const x2, int const y, char const color);
void Box(int const x1, int const y1, int const x2, int const y2, char const color);
void MagicPoint(int const x, int const y, char const color);
void SaveFile(string const fileName);
void End(void);
private:
void newBitmap(int const m, int const n);
void deleteBitmap(void);
bool maxCheck(int x, int y);
bool maxCheckX(int x);
bool maxCheckY(int y);
void Fill(int x, int y, char org_color, char new_color);
};
#endif
EditControl.cpp
#include "EditControl.h"
EditControl::EditControl()
{
this->session = false;
this->m = this->n = 0;
this->bitmap = NULL;
}
EditControl::~EditControl()
{
}
void EditControl::New(int const m, int const n)
{
if( m > 0 && m <= 250 && n > 0 && n <= 250 )
{
// 유효 크기 안에서 bitmap 생성.
this->session = true;
this->newBitmap(m, n);
}
}
void EditControl::Clear(void)
{
// 세션 예외처리
if ( this->session == false ) return;
for ( int y = 0; y < this->n; y++)
{
// bitmap을 'O'문자로 채움.
for ( int x = 0; x < this->m; x++)
{
this->bitmap[y][x] = 'O';
}
this->bitmap[y][this->m] = NULL;
}
}
void EditControl::Point(int const x, int const y, char const color)
{
if ( this->session == false ) return;
// 범위 확인뒤 적용. 배열 특성상 -1 처리.
if ( this->maxCheck(x, y) )
this->bitmap[y-1][x-1] = color;
}
void EditControl::VLine(int const x, int const y1, int const y2, char const color)
{
if ( this->session == false ) return;
if ( this->maxCheck(x, y1) && this->maxCheckY(y2) )
{
int temp1 = y1 - 1, temp2 = y2 - 1;
int tempX = x - 1;
if ( temp1 > temp2 )
{
// y1이 y2보다 클경우 swap
temp1 += temp2;
temp2 = temp1 - temp2;
temp1 = temp1 - temp2;
}
// 라인출력.
for (int i = temp1; i <= temp2; i++)
this->bitmap[i][tempX] = color;
}
}
void EditControl::HLine(int const x1, int const x2, int const y, char const color)
{
if ( this->session == false ) return;
if ( this->maxCheck(x1, y) && this->maxCheckX(x2) )
{
int temp1 = x1 - 1, temp2 = x2 - 1;
int tempY = y - 1;
if ( temp1 > temp2 )
{
temp1 += temp2;
temp2 = temp1 - temp2;
temp1 = temp1 - temp2;
}
for (int i = temp1; i <= temp2; i++)
this->bitmap[tempY][i] = color;
}
}
void EditControl::Box(int const x1, int const y1, int const x2,
int const y2, char const color)
{
if ( this->session == false ) return;
if ( this->maxCheck(x1, y1) && this->maxCheck(x2, y2) )
{
int tempX1 = x1 - 1, tempX2 = x2 - 1;
int tempY1 = y1 - 1, tempY2 = y2 - 1;
if ( tempX1 > tempX2 )
{
tempX1 += tempX2;
tempX2 = tempX1 - tempX2;
tempX1 = tempX1 - tempX2;
}
if ( tempY1 > tempY2 )
{
tempY1 += tempY2;
tempY2 = tempY1 - tempY2;
tempY1 = tempY1 - tempY2;
}
for(int y = tempY1; y <= tempY2; y++)
for(int x = tempX1; x <= tempX2; x++)
this->bitmap[y][x] = color;
}
}
void EditControl::MagicPoint(int const x, int const y, char const color)
{
if ( this->session == false ) return;
// 유효 범위 확인뒤 Fill 재귀함수 호출.
if ( this->maxCheck(x, y) )
this->Fill(x, y, this->bitmap[x-1][y-1], color);
}
void EditControl::Fill(int x, int y, char org_color, char new_color)
{
this->bitmap[y][x] = new_color;
// 일단은 기준점으로 4방향만.
// 기준점의 왼쪽
if ( x > 0 && this->bitmap[y][x-1] == org_color )
this->Fill( x - 1, y, org_color, new_color);
// 기준점의 위쪽
if ( y > 0 && this->bitmap[y-1][x] == org_color )
this->Fill( x, y - 1, org_color, new_color);
// 기준점의 오른쪽
if ( x < this->m - 1 && this->bitmap[y][x+1] == org_color )
this->Fill( x + 1, y, org_color, new_color);
// 기준점의 아래쪽
if ( y < this->n - 1 && this->bitmap[y+1][x] == org_color )
this->Fill( x, y + 1, org_color, new_color);
}
void EditControl::SaveFile(string const fileName)
{
if ( this->session == false ) return;
// 파일명 출력.
cout << fileName.c_str() << "\n";
// 이미지 출력;
for ( int i = 0; i < this->n; i++)
cout << this->bitmap[i] << "\n";
}
void EditControl::End(void)
{
if ( this->session == false ) return;
// 세션종료시 메모리 해제, 초기값 셋팅.
this->deleteBitmap();
this->session = false;
this->m = this->n = 0;
}
void EditControl::newBitmap(int const m, int const n)
{
// 비트맵 생성.
this->bitmap = new char*[n];
for ( int i = 0; i < n; i++)
{
this->bitmap[i] = new char[m+1];
}
this->m = m;
this->n = n;
// 이후 비트맵 청소.
this->Clear();
}
void EditControl::deleteBitmap(void)
{
// 매모리 해제.
for( int i = 0; i < this->n; i++)
{
delete this->bitmap[i];
}
delete []this->bitmap;
this->bitmap = NULL;
}
bool EditControl::maxCheck(int x, int y)
{
bool ret = false;
// x, y의 유효범위 확인뒤 결과값 반환. and 연산.
ret = this->maxCheckX(x) & this->maxCheckY(y);
return ret;
}
bool EditControl::maxCheckX(int x)
{
if ( this->session == false ) return false;
bool ret = false;
if( x <= this->m && x > 0)
ret = true;
return ret;
}
bool EditControl::maxCheckY(int y)
{
if ( this->session == false ) return false;
bool ret = false;
if( y <= this->n && y > 0)
ret = true;
return ret;
}
나름 예외처리를 하다 보니까. 소스가 길어진거 같다.