謝辞最初に、画像回転を実装するのに非常に参考になったソースなどの 作者さんがたにお礼を申し上げます。 堀さんの「DelphiX」の「DXRender.pas」はクリッピングの参考書として 使わせていただきました。SANDMANさんの「QuadrupleD」のサンプル「hashiri」は 高速化、固定小数点の参考書として使わせていただきました。 これらのようなソースをフリーで公開していただき、どうもありがとうございました。What's 画像回転?画像回転とは、読んで字のごとく「画像を回転させる処理」ですね(^^;)![]()
やることと、その利点というわけで、見た目は割と地味なんですけど、「いざ」と思ってやってみると、 手が出なかったり、うまくできなかったりするものです。また、ゲームなどで用いられる グラフィックの回転パターンをいちいち用意せずとも、プログラムで 回転させることができるというのは、大きな魅力だと思います。Direct3D IMでも こういうことが出来るわけなんですが、これには高速な「ビデオカード」が必要になります。 今の日本のパソコンユーザーにはノートを使っている人が結構いるみたいなので、 高速なビデオカードを持ってない人もいるわけです(実際に、Direct3DIMの機能をソフトウェアでエミュレート する、「HEL」というものを使うことができるのだが、これは遅くてしゃれになりません。 さらにいうならハードウェアによって動いたり、動かなかったりで、とても大変らしい)。 そういうことも考えると、こちらの方がいいかな〜と僕は考えています。んじゃ、作る!!前置きはこのくらいにしてそろそろ作っていくことにします。座標変換を考えるために、アルゴリズムを考えておきましょう。 基本的には「この点を回転させたらどこへいくか」ということがわかればいいのです。 当たり前ですよね。ということは、変換後の座標=回転(変換前の座標) となります。回転には、高校2年生で習うはず(僕は高専生なのでちょっと違うかもしんないけど)の y' = x・sinX + y・cosX
逆関数を求める次変換関数がわかったところで、逆関数を求めてみましょう。先ほどの式を「移行」とかを使って、x,yについて解こうとすると(つまり、x=〜、y=〜の式にしようとすると)、 なんか連立方程式が出来そうで怖いですね。連立方程式なんてあまり解きたくありませんし、 解くのも難しそうです(おいおい、そんなんでいいのかよ・・・)。 というわけで、別の方法を考えてみます。唐突ですが次のような考え方は出来ないでしょうか? ![]() y = x'・sin-X + y'・cos-X
高速化 〜逆変換再び〜前回、var x,y,u,v:Integer; begin for y:= 0 to 変形後の画像の高さ-1 do begin for x:= 0 to 変形後の画像の幅-1 do begin u=fx(x); v=fy(y); 変形後の画像[x,y]:=変形前の画像[u,v]; end; end; end;で画像変形ができるよー、でも遅くてゲームでは使い物にならないよー、といいました。 今回もその一例にあたります。だいたい、逆変換に三角関数が出てきている時点でアウトです。 じゃぁ、三角関数の値をあらかじめ計算しておいたテーブルを使ったらいいのか?しかし、 それには掛け算が必要になるので、遅そうです。じゃぁどうすればいいのかっちゅーと、 なんと、足し引き、シフトのみでいけるんですよ!!
クリッピング・・・何それ?前回、「座標変換、落とし穴、高速化」の三つについてふれましたが、 画像変形においては「クリッピング」という処理が極めて重要になってきます。 これをきちんとやっておかないと、「アドレス違反」が起こり画像変形どころではありません。 下手すると「Windowsごと」落ちてしまいます。ホントです。 じゃぁ「クリッピング」とはいったい何物なのか?とりあえず外人ではなさそうです。 これは「変なところはいじくらないようにする処理」です(僕はそう思っている)。 たとえば、32×32のサイズの画像で「xが64、yが256の座標の色を教えて」といっても 「そんなんあるか、アホ!!」と怒られて(プログラムが落ちて)しまいます。 最悪のときは、横っ面をはたかれ(Windowsが落ち)ます。
完成 〜長いよ、これ〜そうこうして出来あがったのが、下のソースです。クリッピングのせいでかなりややこしく、長くなっていますが、 やってることは簡単です。 ちなみにこのソースは、QuadrupleDコンポーネントがインストールされてないと、 使えません。さらにいうなら、16Bitカラー専用です。何かのたしになれば幸いです。 さらにいうと、高速化はあまりしていません。とりあえず固定小数点(?)を 使っていますが、アセンブラなどで最適化すれば、もっと速くなると思います。 それでは、がんばってください(^^;) //回転後の矩形はうまくおさまっとるか? function RotationClip(const Dest, Src: TDDDDGenSurface; X, Y, Width, Height: Integer; cx, cy: Double; rotAngle: Integer; var StartX, StartY, EndX, EndY: Integer): Boolean; //回転後の座標を求める function RotatePoint(ax, ay: Integer): TPoint; var c, s: Double; begin ax := Trunc(ax - Width *cx); ay := Trunc(ay - Height*cy); c := CosFast(rotAngle); s := SinFast(rotAngle); Result.X := X+Trunc(ax * c - ay * s); Result.Y := Y+Trunc(ax * s + ay * c); end; var i: Integer; Points: array[0..3] of TPoint; begin //四つの頂点を回転させる Points[0] := RotatePoint(0, 0); Points[1] := RotatePoint(Width, 0); Points[2] := RotatePoint(0, Height); Points[3] := RotatePoint(Width, Height); //とりあえず左上っぽい座標wp設定しておく StartX := Points[0].X; StartY := Points[0].Y; EndX := StartX; EndY := StartY; //左上、右下座標をセットする for i:=1 to 3 do with Points[i] do begin StartX := Min(StartX, X); //一番小さいのが左 StartY := Min(StartY, Y); // 上 EndX := Max(EndX, X); //一番大きいのが右 EndY := Max(EndY, Y); // 下 end; //サーフェースの大きさにあわせる StartX := Max(StartX, 0); StartY := Max(StartY, 0); EndX := Min(EndX, Dest.Width); EndY := Min(EndY, Dest.Height); //範囲内にありますか Result := (StartX<=Integer(Dest.Width )) and (EndX>0) and (EndX-StartX>0) and (StartY<=Integer(Dest.Height)) and (EndY>0) and (EndY-StartY>0); end; procedure TRenderMachine.DrawRotate(src, dest: TDDDDGenSurface; x, y, Width, Height: Integer; rec: TRect; cx, cy: Single; Transparent: Boolean; rotAngle: Integer); var StartX, EndX, StartY, EndY: Integer; //最小内包矩形の頂点 dx,dy:Integer; //転送先へのアクセス用のカウンタ変数 sx,sy:Integer; //転送もとの座標 gx,gy,gsx,gsy:Integer; c,s:Integer; IncX,IncY:Integer; ww,hh:Integer; XScale,YScale:Single; SrcRect:TRect; SrcCol:WORD; pDestLine,pDest,pSrcTop,pSrc:pWORD; dddsd,sddsd:DDSurfaceDesc2; begin //クリッピング if (Width = 0) or (Height = 0) then exit; if Src.IsLost then exit; if Dest.IsLost then exit; if (Src = nil) or (Dest = nil) then exit; if (rec.Left < 0) or (rec.Top < 0) or (rec.Right > Integer(Src.Width )) or (rec.Bottom > Integer(Src.Height)) or (rec.Left >= rec.Right ) or (rec.Top >= rec.Bottom) then Exit; rotAngle:=-rotAngle; //回転後の点を求める if not RotationClip(Dest, Src, X, Y, Width, Height, cx, cy, rotAngle, StartX, StartY, EndX, EndY) then Exit; XScale:=Width /GetRectWidth (Rec); YScale:=Height/GetRectHeight(Rec); //Sin,Cosの値を65536倍でゲット c:=Round(CosFast(-rotAngle)*65536/XScale); s:=Round(SinFast(-rotAngle)*65536/YScale); IncX:= Round(CosFast(rotAngle)*65536/XScale); IncY:=-Round(SinFast(rotAngle)*65536/YScale); //回転後の左上座標を-rotAngle回転させて、変換前の座標を求める(逆変換) gsx:=(StartX-X)*c+(Y-StartY)*s+Round(GetRectWidth (rec)*cx*65536); gsy:=(StartX-X)*s-(Y-StartY)*c+Round(GetRectHeight(rec)*cy*65536); //ロック Dest.Lock(dddsd); Src.Lock(sddsd); //転送先のポインタを左上にセット pDestLine:=dddsd.lpSurface; Inc(pDestLine,StartX+StartY*Dest.Width); //転送元のポインタを左上にセット pSrcTop:=sddsd.lpSurface; Inc(pSrcTop,Rec.Left+Rec.Top*Src.Width); SrcRect:=Rect(0,0,GetRectWidth(Rec),GetRectHeight(Rec)); //最小内包矩形の全ピクセルにアクセス for dy:= StartY to EndY-1 do begin pDest:=pDestLine; gx:=gsx; gy:=gsy; for dx:= StartX to EndX-1 do begin //座標を進める Inc(gx,IncX); Inc(gy,IncY); //固定小数点をもとにもどす sx:=gx shr 16; sy:=gy shr 16; //アドレス的には大丈夫? if (sx >= SrcRect.Left)and(sx < SrcRect.Right)and(sy >= SrcRect.Top)and(sy < SrcRect.Bottom) then begin pSrc:=pSrcTop; Inc(pSrc,sx+sy*Src.Width); SrcCol:=pSrc^; if SrcCol <> Src.ColorKey then pDest^:=SrcCol; end; Inc(pDest); //転送先のポインタを右へ進める end; Inc(pDestLine,Dest.Width); //転送先のポインタを次のラインの左端に Dec(gsx,IncY); Inc(gsy,IncX); end; //アンロック Dest.UnLock; Src.UnLock; end;それでは、お待ちかねのサンプルです。 拡大・縮小についてはまだ実装していません。 rotate.zip 189KB |
※きっかけ なーんて、たいそうなことをいっておりますが、結局のところ僕が使いたいだけです。 今回のやつだと、 「DelphiXからQuadruple Dにいったはいいけど、画像回転関数がなーーーい!!」 ってのが理由です(^^;)画像回転はポピュラーな割に、PASCALの簡単なコードが手元にないということ、 日本語のページでは回転してるのをあまり見たこと無いこと、そしてWinGLへの ほのかな対抗意識があったので、自分で作るしかありませんでした (うまくいってなかったときは「WinGL」にレジストするか?・・・」とかも思いましたが)。
※QuadrupleD
※DelphiX
※三角関数
※テーブル
※シフト
|