ビットマップの回転処理
数学の入門書には、良く図形の回転が取り上げられます。これは、ある数式で座標上の点を別の点に移して行く、というものでこれによって図形が回転して傾いたように見えるわけです。ただ、これをビットマップに対して行い画像の回転処理に適用しようとすると、穴だらけになってしまい実用になりません。
今回は、その対策を考えてみましょう。
図形の回転
座標面xyで、ある点(x, y)をΘだけ回転させて(x', y')に移すには、以下の計算を行います。
x’=x×cosΘ-y×sinΘ
y’=x×sinΘ+y×cosΘ
この式をもとにすると、1ピクセル1バイト・width×heightピクセルのビットマップ(lpBMP)をsラジアン回転させて別のビットマップ(lpBMP2)に描画するプログラムは以下のようになるはずですね。
for (i=0;i<height;i++)
for(j=0;j<width;j++) {
xx=j*cos(s)-y*sin(s);
yy=j*sin(s)+y*cos(s);
if (xx>=0 && yy>=0 && xx<width && yy<height)
lpBMP2[xx+yy*width]=lpBMP[j+i*width];
}
このプログラムだと、ビットマップの左下端(0, 0)を原点に回転させる事になります。その場合、回転させる角度が大きくなるとビットマップの外にはみ出してしまう部分が大きくなりますから、ビットマップの中心を原点にして回転させるようにしましょう。ビットマップの中心に原点を持ってくるには、ビットマップの各ピクセルの座標からビットマップの幅・高さの半分を引きます。
for (i=0;i<height;i++)
for(j=0;j<width;j++) {
/* 回転後のピクセルを計算 */
xx=(j-width/2)*cos(s)-(i-height/2)*sin(s)+width/2;
yy=(j-width/2)*sin(s)+(i-height/2)*cos(s)+height/2;
/* 回転前のピクセルを、対応する回転後のピクセルに描画 */
if (xx>=0 && yy>=0 && xx<width && yy<height)
lpBMP2[xx+yy*width]=lpBMP[j+i*width];
}
ビットマップの回転とデジタル画像処理の性質
これまで見てきた処理は連続的な図形に対しては良いのですが、デジタル画像であるビットマップに対して行うと小さな穴がたくさんあいてしまいます。これは、ビットマップはピクセル(点)の集まりなので、回転後のビットマップのすべてのピクセルに回転前のビットマップのピクセルが対応するとは限らないためです。
という事は、逆に回転後のビットマップのピクセルを回転前のビットマップのピクセルに対応させれば良いはずです。そのためには、回転前のビットマップをもとに「回転させるとどこに移るか」調べるのではなく、回転後のビットマップから「このピクセルはどこから移ってきたのか」を調べて描画して行く事になるでしょう。こうすれば、回転後のビットマップのすべてのピクセルを回転前のビットマップに対応させる事が出来るので、穴もあきません。
回転後のピクセルに回転前のピクセルを対応させるには、回転の式に回転後の座標を入れて角度を逆にします。つまり、回転後のビットマップを逆に回転させて回転後のピクセルを「元に戻した時の座標」を計算するわけです。
for (i=0;i<height;i++)
for(j=0;j<width;j++) {
/* 回転後に対応する回転前のピクセルを求める */
xx=(j-width/2)*cos(-s)-(i-height/2)*sin(-s)+width/2;
yy=(j-width/2)*sin(-s)+(i-height/2)*cos(-s)+height/2;
/* 回転後のピクセルに、対応する回転前のピクセルを描画 */
if (xx>=0 && yy>=0 && xx<width && yy<height)
lpBMP2[j+i*width]=lpBMP[xx+yy*width];
}
プログラム
今回のプログラムは、フルカラービットマップを読みこんでそれを0.3ラジアンずつ回転させるプログラムです。まず、ドラッグ&ドロップするか読み込みボタンでビットマップを読みこんでから、「ビットマップ」「図形」ボタンで回転させてください。「ビットマップ」がビットマップに対する、「図形」が図形に対する回転処理です。「図形」だと回転後白い穴が開くのがわかりますね。
・ビットマップ回転関数
void bitmap(void) { /* ビットマップに対する回転処理 */
int i,j,xx,yy;
iTime1=GetTickCount(); /* 処理開始前の時間を記録 */
FillMemory(lpWork,iLength*iHeight,0xff);
for (i=0;i<iHeight;i++) /* ビットマップを回転 */
for (j=0;j<iWidth;j++) {
/* 回転後のピクセルに対応する回転前のピクセルを計算 */
xx=(int)((j-iWidth/2)*cos(-0.3)-(i-iHeight/2)*sin(-0.3))+iWidth/2;
yy=(int)((j-iWidth/2)*sin(-0.3)+(i-iHeight/2)*cos(-0.3))+iHeight/2;
/* 回転後のピクセルに、対応する回転前のピクセルを描画 */
if (xx>=0 && yy>=0 && xx<iWidth && yy<iHeight)
CopyMemory(lpWork+j*3+i*iLength,lpBMP+xx*3+yy*iLength,3);
}
/* 回転処理の結果を描画 */
CopyMemory(lpBMP,lpWork,iLength*iHeight);
iTime=GetTickCount()-iTime1; /* 処理にかかった時間を計算 */
InvalidateRgn(hwnd,NULL,FALSE);
UpdateWindow (hwnd); // 再描画
}