사전 준비
이미지를 회전시키거나 이동시키기 위해선 삼각함수 그리고 벡터와 행렬을 사용한다. 그래서 벡터 연산과 행렬 연산을 가능하게 해주는 클래스를 만들어야한다.
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에 너비와 높이에 대한 역수를 곱해 연산한다.
'과거 자료' 카테고리의 다른 글
[#3] 회전 변환 식 유도하기 (0) | 2022.10.25 |
---|---|
[#3] 선형대수학 - 기초 개념 정리 (0) | 2022.10.12 |
[#2] 삼각함수 공식 정리 (0) | 2022.09.21 |
[#1] 역삼각함수 - arcsin, arccos, arctan (0) | 2022.09.17 |
ostringstream - 문자열 조립하기 (0) | 2022.09.12 |