사전 준비

이미지를 회전시키거나 이동시키기 위해선 삼각함수 그리고 벡터와 행렬을 사용한다. 그래서 벡터 연산과 행렬 연산을 가능하게 해주는 클래스를 만들어야한다.

 

Vector 클래스 헤더 
#ifndef __RESOURCE_VECTOR2_H__
#define __RESOURCE_VECTOR2_H__

class Vector2
{
public:
	explicit Vector2 ();
	explicit Vector2 ( int x, int y );
	explicit Vector2 ( double x, double y );

	void set ( int x, int y );
	void set ( double x, double y );
	
	// 두 벡터를 합을 구한다.
	void setAdd ( const Vector2& a, const Vector2& b );
	// 두 벡터의 차를 구한다.
	void setSub ( const Vector2& a, const Vector2& b );
	// 두 벡터의 곱을 구한다.
	void setMul ( const Vector2& a, const Vector2& b );
	// 곱합 연산 multiply and add
	void madd ( const Vector2& a, double b );
	// this = a + ( b * c );
	void setMadd( const Vector2& a, double b, const Vector2& c );
	// 정점 보간 = a + u(b-a) + v(c-a)
	void setInterplation ( Vector2& a, Vector2& ab, Vector2& ac, double u, double v );

	// 반올림된 int를 반환한다. ( 입력 : x or y )
	int to_int ( const char* coord ) const;
	int to_int ( const char& coord ) const;

	bool operator== ( const Vector2& target );
	bool operator!= ( const Vector2& target );
	void operator+= ( const Vector2& target );
	void operator-= ( const Vector2& target );
	void operator*= ( double o );

	Vector2& operator= ( const Vector2& target );
	Vector2& operator+ ( const Vector2& target );
	Vector2& operator- ( const Vector2& target );
	Vector2& operator* ( int o );
	Vector2& operator* ( double o );

	friend Vector2& operator* ( int o, Vector2& v );
	friend Vector2& operator* ( double o, Vector2& v );

	double x_ = 0;
	double y_ = 0;
private:
	int round ( double n ) const;
};

#endif

 

Vector 컨테이너와 모호한 상황을 방지하기 위해 namespace로 둘러쌓아볼까 생각했지만 그냥 연습용 클래스다보니 Vector2로 선언했다. 정의는 아래와 같다.

 

Vector 정의
#include "Vector2.h"

Vector2::Vector2():
	x_(0.0),
	y_(0.0)
{
	// Empty
}

Vector2::Vector2 ( int x, int y )
{
	this->x_ = static_cast<double>( x );
	this->y_ = static_cast<double>( y );
}

Vector2::Vector2 ( double x, double y )
{
	this->x_ = x;
	this->y_ = y;
}

void Vector2::set ( int x, int y )
{
	this->x_ = static_cast<double>( x );
	this->y_ = static_cast<double>( y );
}

void Vector2::set ( double x, double y )
{
	this->x_ = x;
	this->y_ = y;
}

void Vector2::setAdd ( const Vector2& a, const Vector2& b )
{
	this->x_ = a.x_ + b.x_;
	this->y_ = a.y_ + b.y_;
}

void Vector2::setSub ( const Vector2& a, const Vector2& b )
{
	this->x_ = a.x_ - b.x_;
	this->y_ = a.y_ - b.y_;
}

void Vector2::setMul ( const Vector2& a, const Vector2& b )
{
	this->x_ = a.x_ * b.x_;
	this->y_ = a.y_ * b.y_;
}

void Vector2::madd ( const Vector2& a, double b )
{
	this->x_ += a.x_ * b;
	this->y_ += a.y_ * b;
}

void Vector2::setMadd( const Vector2& a, double b, const Vector2& c )
{
	this->x_ = a.x_ + ( b * c.x_ );
	this->y_ = a.y_ + ( b * c.y_ );
}

void Vector2::setInterplation ( 
	Vector2& a, 
	Vector2& ab, 
	Vector2& ac, 
	double u, 
	double v )
{
	this->setMadd( a, u, ab );  // a + u(b-a)
	this->madd( ac, v ); // a + u(b-a) + v(c-a)  
}

int Vector2::to_int ( const char* coord ) const
{
	int result = 0;
	switch ( *coord ) {
		case 'x': case 'X':
			result = this->round( x_ ); 
			break;
		case 'y': case 'Y':
			result = this->round( y_ );
			break;
	}
	return result;
}

int Vector2::to_int( const char& coord ) const
{
	return this->to_int( &coord );
}

Vector2& Vector2::operator= ( const Vector2& _target )
{
	this->x_ = _target.x_;
	this->y_ = _target.y_;
	return *this;
}

bool Vector2::operator== ( const Vector2& target )
{
	return ( this->x_ == target.x_ && 
		     this->y_ == target.y_ ) ? 
		     true : false;
}

bool Vector2::operator!= ( const Vector2& target )
{
	return !this->operator==( target );
}

void Vector2::operator+= ( const Vector2& target )
{
	this->x_ += target.x_;
	this->y_ += target.y_;
}

void Vector2::operator-= ( const Vector2& target )
{
	this->x_ -= target.x_;
	this->y_ -= target.y_;
}

void Vector2::operator*= ( double o )
{
	this->x_ *= o;
	this->y_ *= o;
}

Vector2& Vector2::operator+ ( const Vector2& target )
{
	this->x_ += target.x_;
	this->y_ += target.y_;
	return *this;
}

Vector2& Vector2::operator-(const Vector2& target)
{
	this->x_ -= target.x_;
	this->y_ -= target.y_;
	return *this;
}

Vector2& Vector2::operator* ( int o )
{
	this->x_ *= static_cast<double>( o );
	this->y_ *= static_cast<double>( o );
	return *this;
}

Vector2& Vector2::operator* ( double o )
{
	this->x_ *= o;
	this->y_ *= o;
	return *this;
}

int Vector2::round ( double n ) const
{
	double result = n;
	result += ( n > 0.0f ) ? 0.5f : -0.5f;
	return static_cast<int>( result );
} // private

Vector2& operator* ( int o, Vector2& v )
{
	v.x_ *= static_cast<double>(o);
	v.y_ *= static_cast<double>(o);
	return v;
} // friend

Vector2& operator* ( double o, Vector2& v )
{
	v.x_ *= o;
	v.y_ *= o;
	return v;
} // friend

 

 

대입의 표기를 확실하게 구분짓기 위해 this->를 붙여주었다 그리고 아래는 행렬 클래스의 선언와 정의다. 이 행렬은 2x2 행렬을 사용하였다.

 

 

Matrix22.h
#ifndef __RESOURCE_MATRIX22_H__
#define __RESOURCE_MATRIX22_H__

class Vector2;

class Matrix22
{
public:
	Matrix22 ( double e00, double e01, double e10, double e11);
	void Multiply ( Vector2* out, const Vector2& in ) const;

private:
	double m00_, m01_;
	double m10_, m11_;
};


#endif

 

Matrix22.cpp
// Resource
#include "Matrix22.h"
#include "Vector2.h"

Matrix22::Matrix22 ( double e00, double e01, 
	                 double e10, double e11 ):
	m00_(e00), m01_(e01),
	m10_(e10), m11_(e11)
{
	// Empty
}

void Matrix22::Multiply ( Vector2* out, const Vector2& in ) const
{
	// (l)eft (o)perand, (r)ight (o)perand
	double& rX = out->x_;
	double& rY = out->y_;
	const double& roX = in.x_;
	const double& roY = in.y_;

	rX = ( m00_ * roX ) + ( m01_ * roY );
	rY = ( m10_ * roX ) + ( m11_ * roY );
}

 

 

회전, 확대 및 축소, 대칭 이동

 

강체 변환을 하기 위해 이미지의 중점이 0이 되도록 대칭이동시켜야 한다. 세 정점 좌표를 변환시키려는 해당 이미지의 절반 크기를 각각 마이너스 해주면 좌표 평면상 이미지가 중앙에 위치하게 된다.

 

 

그리고 컴퓨터가 출력하는 픽셀의 점들은 (ex 0~14 픽셀 = 배열을 생각하면 된다), 실제 픽셀의 좌표가 아닌 0부터 시작되는 배열의 인덱스 즉, 첨자에 해당하는 의미이므로 각각의 정점을 좌표로 환산해주어야한다. 그래서 각 첨자에 0.5를 더해 픽셀의 중앙 지점으로 좌표로 바꾸어주고 회전을 통해 연산을 한 후 다시 첨자로 변환해서 화면에 출력하는 과정을 거친다.  그럼 위에 a(-63, -63)은 a(-63.5, -63.5)가 된다. 확대 및 축소는 배율을 정해서 곱하면 되고 대칭 이동은 좌표값에 더하거나 빼면 되므로 어렵지 않다. 그래서 회전 하는 방법만 정리한다.

 

회전

회전을 하려는 경우 각 회전 각에 대한 sin, cos을 구한 후 이동 경로를 A(x, y)를 구하는 공식을 이용한다.

 

 

 

 

다음을 벡터와 행렬로 아래와 같이 나타낼 수 있다.

 

 

 

 

 

그리고 각 세 점에 대한 정점을 구한다. 이는 픽셀 단위당 일일이 회전각을 구해 좌표를 찍는 것보다 세 정점을 구한 후 정점을 기준으로 보간하여 나머지 픽셀을 채우는 것이 더 빠르기 때문이다. 

 

우선 1차원적 직선에 대한 보간 공식은 다음과 같다. ( 여기서 P는 위에서 구하고자 하는 좌표 A이다 ) 

 

 

 

 

내가 구하려는건 2차원 좌표평면에 대한 보간 공식이므로 1차원 보간 공식을 2차원 보간으로 변환한다.

( 각 u와 v의 곱에 해당하는 항은 a 정점에서 세 정점으로 이동하는 칸을 의미한다. )

 

 

 

 

기존에 픽셀을 찍는 지점은 a부터 시작해서 각 정점을 방향으로 u, v 만큼 이동시키며 출력할 수 있게 됐다.

 

회전

 

확대 및 축소와 회전과 확대 축소를 같이 해주는 함수

 

출력

 

여기서 rcpWidth와 같은 역수로 u와 v를 구하고 있다는 점에 유의하자. 시작점을 제외한 각각의 정점은 너비와 높이에 해당하는 크기이므로 위에 공식에서 보간 공식 1 = 너비 혹은 높이가 된다. 그러므로 0부터 1에 가까워지기 위한 보간을 하기 위해 u와 v에 너비와 높이에 대한 역수를 곱해 연산한다.