[BMP]ビットマップの回転
<※ 1/17追記>
下記ソースではちょっとしたバグがあります。
例えば180度回転したとき、元画像の最上端と最右ラインの
ピクセルが表示されません。かつ左と下のラインに空白ができます。
下記だと、例えば100*200の画像で180度回転させたとき、回転後画像の
(0, 0)に対応する座標が(100, 200)となるためです。
座標として、そして画素を持つデータとして有効な範囲は(99, 199)までです。
直した記事はこちら
成長の過程ということで、下記は残します。正月だったし。
1週間悩みに悩んでやっと動くところまで。
あけましておめでとうございます。
[作成環境]
・Visual Studi 2008 Standart Edition
・MFC-ダイアログベース
[基本的な流れ]
①元画像情報取得(幅、高さ、画像データ)
②回転角から新しい幅と高さを計算
③回転後の座標に対応する座標を元の画像からもってくる
④回転後のデータを元に、ビットマップ作成
⑤描画
[画像回転について]
座標(cx, cy)を中心に(x1, y1)をR(rad)回転させたときの回転後の座標(x2, y2)
x2 = (x1 - cx) * cos(-R) - (y1 - cy) * sin(-R) + cx
y2 = (x1 - cx) * sin(-R) + (y1 - cy) * cos(-R) + cy
となる。
ただし、元の画像から回転後の座標を求めると、計算過程により
一部回転後画像に穴があいてしまう。
これを解消するために、回転後の座標から、対応する元の画像の座標を
算出し、あてはめていくという手法を取る。
x1 = (x2 - cx) * cos(R) - (y2 - cy) * sin(R) + cx
y1 = (x2 - cx) * sin(R) + (y2 - cy) * cos(R) + cy
回転角からの座標計算は数学的な分野になるため、求め方の詳細については割愛。
というかわからないし、計算してみるのも面倒くさい。
画像中心を原点に回転させる場合、角度によっては元の画像よりも高さ、幅ともに
大きくなる。
画像が収まりきるように、回転後の幅と高さも計算する必要がある。
元の画像の幅(sw)と高さ(sh)をR(rad)回転した後の幅(dw)と高さ(dh)は
dw = | sw * cos(R) | + | sh * sin(R) | ( |~~| は絶対値)
dh = | sw * sin(R) | + | sh * cos(R) |
上記を元にしたコード。
private:
CSpinButtonCtrl m_spinctrl; // スピンコントロール
CBitmap m_srcBmp; // 元画像
int m_srcWidth; // 元画像幅
int m_srcHeight; // 元画像高さ
RGBQUAD *m_psrcData; // 元画像データポインタ
int m_dstWidth; // 回転後幅
int m_dstHeight; // 回転後高さ
RGBQUAD *m_pdstData; // 回転後データ
int m_spinDeg; // 回転任意角
BOOL CRotateBMPDlg::OnInitDialog()
{
・・・中略
// TODO: 初期化をここに追加します。
// スピンコントロール初期化
m_spinctrl.SetRange(0,360);
// 元画像の取得
m_srcBmp.Attach((HBITMAP)LoadImage(0,_T("C:\\sample.bmp"),IMAGE_BITMAP,0,0,LR_LOADFROMFILE));
// BITMAP構造体の取得
BITMAP srcBmpInfo;
m_srcBmp.GetBitmap(&srcBmpInfo);
// 元画像幅、高さの取得
m_srcWidth = srcBmpInfo.bmWidth;
m_srcHeight = srcBmpInfo.bmHeight;
// 元画像データの取得
m_psrcData = new RGBQUAD [ m_srcWidth * m_srcHeight];
m_srcBmp.GetBitmapBits(m_srcWidth * m_srcHeight * sizeof(RGBQUAD), m_psrcData);
// 初期画像は回転なし
RotateBMP(0);
return TRUE; // フォーカスをコントロールに設定した場合を除き、TRUE を返します。
}
void CRotateBMPDlg::OnPaint()
{
if (IsIconic())
{
・・・中略
}
else
{
CDialog::OnPaint();
// 回転後画像がなかったら即リターン
if(m_pdstData == NULL) return;
CDC *pDC = GetDC(); // デバイスコンテキスト
CDC dcMem; // メモリデバイスコンテキスト
dcMem.CreateCompatibleDC(pDC);
CBitmap rot; // 回転後ビットマップ
CBitmap *old; // 古いビットマップオブジェクト
// 回転後ビットマップ作成
rot.CreateCompatibleBitmap(pDC, m_dstWidth, m_dstHeight);
// 作成したビットマップに回転データを登録する
rot.SetBitmapBits(m_dstWidth * m_dstHeight * sizeof(RGBQUAD), m_pdstData);
old = dcMem.SelectObject(&rot);
// 転送処理
pDC->BitBlt(0,0,m_dstWidth,m_dstHeight,&dcMem,0,0,SRCCOPY);
dcMem.SelectObject(old);
// 終了処理
rot.DeleteObject();
dcMem.DeleteDC();
ReleaseDC(pDC);
}
}
// 回転処理
void CRotateBMPDlg::RotateBMP(int theta)
{
// deg → rad変換
double rad = (theta * M_PI / 180);
// 回転後の高さと幅取得
m_dstWidth = (int)((fabs(m_srcWidth * cos(rad)) + fabs(m_srcHeight * sin(rad))) + 0.5);
m_dstHeight = (int)((fabs(m_srcWidth * sin(rad)) + fabs(m_srcHeight * cos(rad))) + 0.5);
// 元画像、回転後画像の中心座標計算
int srcCX = m_srcWidth/2;
int srcCY = m_srcHeight/2;
int dstCX = m_dstWidth/2;
int dstCY = m_dstHeight/2;
// 回転後イメージ格納バッファ
if(m_pdstData != NULL) delete [] m_pdstData;
m_pdstData = new RGBQUAD [ m_dstWidth * m_dstHeight ];
memset(m_pdstData, 0x00, m_dstWidth * m_dstHeight * sizeof(RGBQUAD));
// sin、cos値を事前に計算(注1)
int int_sin = (int)(sin(rad) * 1024);
int int_cos = (int)(cos(rad) * 1024);
// 元画像、回転後座標
int srcX, srcY, dstX, dstY;
// 回転後イメージから元イメージの位置算出
for(dstY = 0; dstY < m_dstHeight; dstY++)
{
for(dstX = 0; dstX < m_dstWidth; dstX++)
{
// 回転後の座標から対応する元画像の位置を計算(注1)
srcX = (((dstX - dstCX)*int_cos - (dstY - dstCY)*int_sin) >> 10) + srcCX;
srcY = (((dstX - dstCX)*int_sin + (dstY - dstCY)*int_cos) >> 10) + srcCY;
if(srcX>=0 && srcX<m_srcWidth && srcY>=0 && srcY<m_srcHeight)
{
m_pdstData[dstX + dstY*m_dstWidth] = m_psrcData[srcX + srcY*m_srcWidth];
}
}
}
}
※RGBQUADについて
メモリ確保の際にRGBQUAD型で(幅×高さ)分取ることによって、
1ピクセル単位の色データを丸ごと回転後のピクセルへコピーしている。
※注1
処理を高速にするための処理。
→sin、cosの計算は処理時間が結構かかるのでループの外で事前に計算。
→浮動小数点よりも整数のほうが処理が早い。
詳しくは、下記の参考サイト参照。
また、この段階では画像の穴抜けはないが、エッジが汚い。
これを解消するために、線形補間というものがあるようだが、
上記コードでは未反映。これも下記サイトで説明されています。
<参考URL>TSUGU software atelier
- ひと目でわかるMicrosoft Visual C++ 2008 アプリケーション開発入門 (マイクロソフト公式解説書)/増田 智明
- ¥2,919
- Amazon.co.jp