まどかの 日記

[2001/10/13〜2001/10/21の日記]
[2001/10/22〜2001/10/28の日記]
[2001/10/29〜2001/11/04の日記]
[2001/11/05〜2001/11/11の日記]
[2001/11/12〜2001/11/18の日記]
[2001/11/19〜2001/11/25の日記]
[2001/11/26〜2001/12/02の日記]
[2001/12/03〜2001/12/09の日記]

0065
2001/12/16 ()
 P/ECEで音楽やりました 
 えー、今日は昼くらいに起きて、のんびりP/ECEのMMLで「TO MAKE THE END OF BATTLE」を作ろうと思っていたのですが、日頃の疲れが溜まっているせいか、夕方5時まで寝てしまいました(^^; うわ、寝すぎ。

 で、ゆっくりとご飯を炊いて、レトルトのカレーで今日1回のみの食事を作りました。そして、テレビを見ながらカレーを食べて、7時ごろからMMLに取り組み始めました。

 とりあえずリファレンスとサンプルを見ながら書いていたのですが、どうも、MMLをバイナリデータに変換するコンバータの出来が悪いのか、BIOSをアップデートしていないのが悪いのか、パートのループ指定を行をまたいで指定するとおかしくなったり、タイを指定する”&”を2行に渡ってやるようにすると上手くいかなかったり、結構悩みながら、黙々と作っていました。

 ちなみに、これらのバグ(?)はAパートはAパートだけで、まとめて書くと上手くいくようです。

 A @3 o4l16[cdefgab
 A >cdef <abcd&d4 ]2

 こんな感じで。中身は適当ですが。

 で、明日ある社員さんに聞いてもらおうと、一生懸命作って、とりあえずできたのが、月曜の午前3時でした(^^; エンベローブやビブラートの設定が良く分からなかったので、音の出来はそれなりですが、いい感じに出来ました。

 それで、出来たMMLをコンバートして、MMLの音楽を鳴らすためだけのプログラムに乗っけて、ひたすら聞けるようにしました(^^;
 これで、1曲だけですが、携帯音楽プレーヤーの出来上がりです(^^;

 明日はこれを持っていってある社員さんに聞いてもらいます。
 そんでもって、明日はある社員さんと出張に行くので、行きと帰りの電車の中ではP/ECE話で盛り上がることでしょう(^^;

 というわけで、今日はこの辺で。
 おやすみなさい。

0064
2001/12/15 ()
 Oh! 初雪 
 はい、今日は名古屋に初雪が降りました。寒かったねぇ。
 
 私は彼女と名古屋に買い物に出掛けるため、彼女が乗って来るバスに一緒に乗ろうと、バス停で待っている時に、初雪を確認しました。

 しかし、彼女に「雪降ってたねぇ!!」って教えてもらうまで、初雪を見たという自覚がありませんでした(^^;
 最初に見たときは、「なんやこの白いの」っていう感じで、その白いものが雪だなんて1ミリも思いませんでした。だって、天気予報で今日は晴れっていうのを信じきっていたので(^^; いや、ほんとうに晴れと報ぜられたかどうかも記憶があいまいで疑わしいのですけどね。
 どうやら、私の中の雪という存在ってかなり薄いようです(^^; すぐに気づけよな>私

 というわけで、今日は彼女と名古屋の地下街やハンズでお買い物をしました。
 で、あとはいつも通りの休日でした。

 そして、自分の部屋に帰ってきて、現在に至ります。

 さあ、この休みには何をしよう。
 せっかくブレゼンハムの拡大やバイリニア補間拡大ができたんだから、そろそろ減色教室の方に実装して、開発を進めようか。いや、それよりも同人ノベルゲームの方をプラスディスクやお祭りディスクに進めるか。それとも、自力3Dでもやり始めてみるか…… と、色々悩んでいるうちにある考えが浮かびました。

 そういや、P/ECEいぢってないなぁ……

 そう、発売日の次の日にはりきって買いに行き、ある社員さんにP/ECEを買うように薦めたりしたのに、まだ何も作ってないではないか。
 建前上、ゲームを作るネタが無いということで他のことをやってきましたが、ある社員さんもP/ECEを買ったそうですし、言い出しっぺの私が何もしていないのでは、格好がつきません。
 というか、せっかく買ったんだからなんかつくらないとねぇ。でも、ゲームは色々考えることが多くて、結構時間がかかるものだし、P/ECEばっかりに時間を割くわけにはいかないし。
 短時間であんまり考えることが少なくて、それでいて楽しい手軽なプログラムって無いものか……

 あ…… ありました。手軽で楽しい、昔MSXでも夢中になったプログラム。
 そう、音楽プログラムです。MMLを書いて音楽を鳴らすのです。P/ECEではMMLで曲を作れるのです。

 ああ、MMLって懐かしいなぁ。MML書いて音楽鳴らすのって、昔からプログラムやってきた人なら一度は通る道なんじゃないかなぁ。普通にプログラム考えるより簡単だもんね。ほとんど写し書きに近いから。

 ちなみにMMLとは「Music Macro Langage」の略で、今で言うところの音を鳴らすためのスクリプトです。音程や音の長さを表す記号を並べて、曲にするわけです。ところで、P/ECEのドキュメントにMMLが何の略で何をするものなのかという説明が無かったところを見ると、MMLってプログラマ界ではやっぱり常識なんですね。

 で、これなら画面にタイトルを表示するだけの簡単な雛型プログラムを書いて、後は楽譜見ながらMMLを書くだけで、楽しい音楽プログラムのできあがりです(^^; さあ、久しぶりに音楽鳴らして楽しもう。

 というわけで、明日はP/ECEで音楽を鳴らすことにしました。
 そんでもって作る曲はもちろん、私の大好きなこの1曲。YsIIのオープニング「TO MAKE THE END OF BATTLE」です。これぞ知る人ぞ知る、名曲中の名曲だ。この曲を知らずにYsは語れません(^^;

 しかしながら、MML書くには楽譜を見ないとできません。しかもこの曲の楽譜は実家においてきてしまったので、これじゃ作ることができないじゃないか! と思ったのですが、幸い以前買った「YsII エターナル」の初回特典でついてきたメモリアルCDに、JPEG画像として楽譜が付いていたので、それを印刷して使うことにしました。
 
 ちなみにこのメモリアルCDにはYsII中で使われている曲のバンド演奏版とかボーカル付き版がムービーとして収録されているのですが、そのムービーの元ソースとなるものをすべて私は所有していので、別のありがたくもなにもなかったんだよねぇ。ファルコムさんには悪いけど、なんか期待して損した感じ(^^;
 また、初回特典で無料添付されていたOVAも、実は全部持っているのですが、4本のVHSが1枚のDVDになっているので、これはこれで、見るのにとても便利で良いです。

 ここまで書くと、皆様ももうお気づきでしょうが、私はファルコムファンです(^^; 特にYsとドラスレ英雄伝説が大好きです。今はゲームをする機会が減ってしまったので、あまり買いませんが、昔はオリジナルサウンドトラックから楽譜集まで大体のものは買ってました(^^;

 今って、公式の楽譜集ってあんまり無いと思いますが、昔は確かキングレコードとかから、Ysの全曲集やグラディウスの全曲集とかが結構出てたんですよ。これらは、普通の楽譜集と大きさが違って、CDサイズの分厚いものでした。しかも楽譜のコーナーではなく、ゲームミュージックCDと一緒の場所においてあるので、かなり見つけにくいです(^^; 一見見ただけでは、「なんだこの変な厚さのケースのCDは!?」って感じで。

 あと、これらの他「マイコンBASICマガジン」にも、一時期いろんなゲームの楽譜が付いていて、フルスコアの楽譜は大変重宝しました。

 今は、全部MIDIになってプログラムで音楽を鳴らすことが少なくなったので、こういった楽譜って全然取り上げられなくなったんだろうね。残念なことだ。そして、プログラマが音楽する時代でもなくなったんだねぇ。

 なんかしんみりした話になってきましたが、とにかくプログラムで音楽するのは楽しいのです。最近の人が数値を打ち込んで着メロ作るのといっしょの感覚ですね。もちろんMMLの方が使える音数も多いし、やれることも豊富です。P/ECEのMMLではポルタメントもできるので色んなものが作れそうです(^^;

 というところで、今日はおやすみなさい。

0063
2001/12/14 (金)
 もうWordはうんざり 
 えー、今日はWordで、今開発しているソフトウェアのマニュアルを1日かけて書いていました。
 画面のイメージを取るためにちょこちょことVBをいぢってましたが、今日は1日の半分以上をWordに費やしたので、疲れました(^^;

 ああ、もうWordってなんでこんなに使いにくいんだろう。そりゃあ、ちゃんと使いこなせれば、何でもできるからすごいワープロソフトなんだろうけど、標準で設定してある機能のいらないこと、いらないこと(^^;
 特に●とか■とか1.とか書くと、箇条書きと見なされて、次の行から勝手にまた●とかを挿入してくるのは止めてくれ。しかも箇条書きの先頭として追加された●は、そう簡単には編集できないってのが困りもの。

 メニューの「ツール」から「オートコレクト」ってのに行って、どこだったかのチェックボックスのチェックをはずすと、この機能を解除できるのだが、はじめから切っとけって。恐らく、Wordの要らない機能ベスト5に入る勢いの要らなさだろう(^^; あと、行頭をもっと揃えやすくしてくれよ。プロポーショナルフォントでも行の途中から頭を揃えたいんだっては! もっとExcelを見習えっての。レイアウトを揃えるだけでも一苦労だ。

 というわけで、かなり苦労しながら、マニュアルを書いてました(^^;

 みなさんもマニュアルのようなレイアウトが重要になってくるような文章を書くときは、Excelを使いましょう。
 Excelはセルで区切られているので、自由自在に頭を揃えることができます。さらに、画像や図形などもグリッドに合わせたりして、自由かつ綺麗に位置を決められるので、どんなレイアウトでも大丈夫です。
 しかも、Wordみたいに、バージョンが違うととたんにちゃんと読めなくなることはありません。
 MS製品の中でもっとも使えるアプリのひとつといえるのではないでしょうか。

 と、よくわからない宣伝はこれくらいにして(^^; 

 明日は恐らく彼女と名古屋の方に買い物に出掛ける予定です。
 で、来週の月曜には、また母校の工業高校に出張です。

 ああ、またなんか公開できるものってないかなぁ。あるといったら、ダブルステップブレゼンハムの直線描画ルーチンか、昔外国のサイトで解説されていたMMXでの自力アルファブレンディングのコメントを日本語訳したものくらいかなぁ。
 でも、アルファブレンディングには処理する画像データの幅が、4の倍数じゃないと駄目という欠点があるしなぁ。まあ、そこそこ速いんだけどね。

 それくらいかなぁ。

 あ、そういえば、VCでメモリマップドファイルを使っての高速BMP表示アプリのサンプルを作るとか言って、全然作ってないなぁ。でも、やる気はあんまりないなぁ(^^; 
 最近減色教室が、どんどん減色よりも画像ビューワとしての機能が充実されていくので、いっそのこと減色より使い易い画像ビューワというのを前面に出してやろうかしら。
 そしたら、別の名前で出さないとねぇ。減色教室って、意味不明だもんね(^^; 実際「教室」には何の意味も無いんですけど。はぁ……

 というところで、今日はおやすみなさい。

0062
2001/12/13 ()
 今日はブレゼンハムの拡大を 
 えー、今日は、特に何もなかった日なので、ネタがありません(^^;
 というわけで、急遽(?)、昨日書いた「ブレゼンハムの直線アルゴリズムを応用した拡大ルーチン」を公開することにしました。

 バイリニア補間の時と同様、私の使っているそのままの形で掲載するので、MCBitmapクラスのScanLine部分や、その他部分については、自分の環境に合わせて修正して使ってくださいね。

 皆様の役に立つかどうかわかりませんが、結構速いですよ。例によって、どこかのソースの寄せ集めのようなコードですが、どうかご了承ください。

 それでは、ずらずらーっと公開です。

//---------------------------------------------------------------------------
void MCGraphicEffect::EasyStretchCopy24(MCBitmap *SakiBMP,
                                        int SakiX,int SakiY,int SakiWidth,int SakiHeight,
                                        MCBitmap *MotoBMP,
                                        int MotoX,int MotoY,int MotoWidth,int MotoHeight)
{

        //簡易拡大縮小コピー(24Bitビットマップ版)
        //クリッピング・エラー処理は省いてあります
        //上下左右反転には対応していません

        int X,Y;                //描画先座標
        int mX,mY;              //転送元座標
        int EX,EY;              //誤差値
        
        //描画ループ
        EY = -SakiHeight;                                //Y方向誤差値初期化

        for(Y = SakiY,mY = MotoY;Y < (SakiY + SakiHeight);++Y)
        {

                EX = -SakiWidth;                         //X方向誤差値初期化

                for(X = SakiX,mX = MotoX;X < (SakiX + SakiWidth);++X)
                {

                        //1ピクセルコピー
                        *((RGBDATA*)SakiBMP->ScanLine[Y] + X) = *((RGBDATA*)MotoBMP->ScanLine[mY] + mX);

                        //転送元X座標を算出
                        EX += MotoWidth;                 //X方向誤差値更新

                        while(EX >= 0)
                        {
                                mX++;                    //転送元Xを進める
                                EX -= SakiWidth;         //X方向誤差値更新
                        }

                }

                //転送元Y座標を算出
                EY += MotoHeight;                        //Y方向誤差値更新

                while(EY >= 0)
                {
                        mY++;                            //転送元Yを進める
                        EY -= SakiHeight;                //Y方向誤差値更新
                }

        }

}
//---------------------------------------------------------------------------

 おや、全然ずらずらーってなってませんね(^^;

 この関数は余計な処理を省いて、ほんとに拡大しかしていないので、シンプルです。
 特殊なことをしているといえば、引数にMCBitmapという自作クラスのポインタがあるところくらいかな。ここを画像データが入っている位置次元配列のポインタに変えて、1ピクセルコピーの部分をちょっと修正すれば、すぐに使えると思います。

 ちなみに、MCBitmapクラスのScanLineメソッドにはinline指定子を付けているんだけど、ちゃんとコード展開されているのかなぁ。
 アセンブラコードを吐き出させて確認していないので、もしかしたら効果ないかもしれませんね。

 詳しい解説は図解しないと、納得するのが難しいと思うので、詳しいことは書籍や他のサイトに譲るとして、この拡大ルーチンを簡単に説明すると、ブレゼンハムの直線アルゴリズムの2重ループである。
 もっと簡単に言うと、X方向の拡大の場合、拡大後の幅に、拡大前の幅が何個入るかで、転送元1ピクセルに対して拡大後の領域に何個横に並べればいいかを決めている。Y方向でも同じね。
 そして、拡大率が整数倍ではない場合に対しては、誤差を蓄積していくことで帳尻を合わせているわけである。
 
 というわけで、よくわからない人は紙と鉛筆を使って、机上でトレースしてみよう。なるほどね。と思うはず(^^;

 これは拡大後の品質よりも高速性を重視した簡易版拡大ルーチンなので、精度はちょっと悪いかもしれないが、十分に使えるものだと思います。
 ちなみにこれは主に拡大用のルーチンなので、縮小にはあまり向いていません。縮小するときは面積平均法というアルゴリズムで、縮小した1ピクセルの範囲に該当する元画像の領域の平均色で点を描くとキレイに縮小されるようです。

 ああ、最近憶えたこれらのアルゴリズム利用して、また自力3Dをやってみたいなぁ。目指せ! メガデモ!! DirectXのように実行環境に左右されないクオリティの安定した3Dゲームかなんかを作りたいなぁ。

 でも、今のところ、ワイヤーフレームで立方体や四角形をぐりぐり回転させるくらいしかやったことないんだよね。
 勉強する時間も無いし。でも、昔やったのはただのブレゼンハムで直線を描いていただけだから、憶えたてのダブルステップブレゼンハムを使えばそれだけでもかなりの高速化になるなぁ。それに、もうちょっとがんばって直線にアンチエイリアスをかけてみたり、アフィン変換と今回の拡大縮小ルーチンを応用すれば、テクスチャマッピングもできるね。あ、ただのアフィン変換じゃ視点に近づいたときに大変なことになるから、ちゃんとパースペクティブコレクションも考えないとね。

 ここら辺の資料はずっと前からいつかやろうとこつこつ集めているので、資料は十分そろっているはず。あとはやる気と時間かな(^^;

 では、やりたいやりたいと思いながら、恐らくは当分やらない想い(久しぶりに想いを書いたなぁ。この日記のコンセプトなのに(^^;)を書き記したところで、今日はもう寝ることにします。
 ああ、減色の方を先に進めないと他のことできないんだよなぁ。がんばろ。

 そうそう、最後に、一昨日公開したバイリニア補間のコードのことですが、見やすくしようと思って、RGB分解のところで無駄なことをしています。この分解をせずにそのままバイリニア補間のマクロに放り込んでやれば、少し高速化されます(^^;


 じゃ、おやすみなさい。

0061
2001/12/12 ()
 ちょっと気が抜けた日でした 
 今日は、昨日バイリニア補間がちゃんとできたので、早速今作っているプログラムの方も修正し、正確かつ軽快に動くようになりました。うむ、良い感じだ。

 でも、原因究明に悩み、そして胸躍らせていた問題が解決したとたん、なんか気が抜けた感じになってしまいました。

 今日は一日バイリニアの失敗を教訓に、細かいところに目を向けて、ちまちま修正個所を探していました。
 で、前々から気になっていた部分をしっかりと見直して、修正してます。
 現在、バイリニア補間で拡大した背景に重ねて、レイヤーをかぶせているように、矩形を描くという処理をしています。この二つの画像の位置が拡大してもぴったり合うようにしないといけないため、今までバイリニア補間で左上にずれてしまう問題に頭を悩ませていたんですよ。

 で、バイリニア補間の問題が解決したので、後は上手く位置を合わせるだけだと思ったのですが、これが意外にも面倒で難しく、またてこずっています(^^; まあ、色々と厄介な問題がありまして。
 詳しいことは、書いちゃいけないかもしれないし、書いて説明するのもちょっと面倒なので、ここでは控えさせていただきますね。

 そうそう、今日は水曜日で、今日も出向先での作業だったのですが、水曜日はいつものごとく出向先がノー残業デイなので、最後の一人になるまえに、早々と引き上げてきました。今日は最後から4番目でした(^^; 最後になると、色々戸締りとか、電源落としたりとか、面倒なんですよね。あそこわ(^^;

 フレックスタイム制なので、みんな知らない間に来て、知らない間に帰っていきます。なので、仕事に集中していると、知らない間にみんないなかったりします(^^; もっと声掛けようよ。ねぇ。

 さて、次はどんなアルゴリズムのソースを公開しようかなぁ。公開するって言っても、たいていが他からのソースの寄せ集めなので、偉そうなことは言えませんが、私自身がちゃんと自作のツールなどで使っているルーチンなので、実用性は保証します(^^;

 次に公開するとしたら、ブレゼンハムの直線アルゴリズムを応用した通常の拡大ルーチンかなぁ。ピクセルがそのまま大きくなる拡大ね。
 通常拡大はAPIのStretchBltでもSetStretchBltModeでCOLORONCOLORを指定することでできますが、このAPIでの拡大は、中で色々やっているのか、ピクセルフォーマットとソース画像のサイズに、処理時間が大きく左右されるという欠点があります。
 
 その点、私の使っているブレゼンハムの拡大ルーチンは任意の領域の拡大処理しかしていないので、シンプルで非常に高速です。さらに、ブレゼンハムの直線アルゴリズムを応用して使っているので、みなさんもご存知の通り、整数演算しかしていません。しかも、乗除算を一切使わず、加減算とシフト演算のみを使用しているので、かなりの高速処理を実現していると思われます。

 現在の浮動小数点演算をバリバリこなせるCPUの前では、無駄なあがきかもしれませんが、最速を追求するって楽しいよね。色んな工夫してさ。CPUを選ばないから、ゲームとかでは必須だよね。こういうのって。

 この拡大ルーチンのほか、ブレゼンハムの直線描画をさらに発展させた「ダブルステップブレゼンハム」での直線描画も速いですよ〜。直線の両端から同時に描画して、ループ回数を半分にするのはもちろんのこと、傾きに応じて2ピクセル同時に描画するので、通常のブレゼンハムに比べて、ループ回数が最大4分の1になります。
 ブレゼンハムの速さで満足しているそこのあなた、まだまだ先がありますよ! さあ、レッツトライ!! <誰やねん

 これらを使えば、ソフトウェアによる3Dオブジェクトのリアルタイムレンダリングも夢じゃありません(^^;
 全部自力でやれば、DirectX使ってやるより、よっぽど為になると思うなぁ。時間はかかるけど、自分でゴリゴリ描いてるって感じがして面白いよねぇ。

 最近ではテクスチャマッピングや、パースペクティブコレクションなどの解説サイトも増えてきたから、比較的容易にこれらのことを理解できるんじゃないかなぁ。かくいう私はまだ理解していません(^^; 勉強中です。

 いいねぇ、いいねぇ。こういうことをやっている人は是非オープンソースの精神でソースを公開して欲しいねぇ。一生懸命解析しますから(^^; できればコメントは欲しいなぁ。それと図解も…… <欲張り

 というわけで、気が向いたら、そのうちブレゼンハムの拡大ルーチンでも公開しますね。
 それでは、今日はこの辺でおやすみなさい。

0060
2001/12/11 ()
 今日のバイリニア 
 えー、今日はなんとか、平均バイリニアなんて変なやり方をせずに、上手くバイリニア補間をすることができたっぽいので、それを記念(?)して、今日のバイリニア補間のコードの一部を公開します。

 もしかしたら間違っているかもしれないので、気づいた方は遠慮なく指摘してください。
 とりあえずはちゃんと左上にずれたりせずにちゃんとできてます。

 それでは、ずらずらーっと公開します。特にコメントもありませんが、暇な人だけ、暇つぶしに眺めてみてください(^^;
 ちなみにこのコードはいろんなところからの寄せ集めを私なりに整えたもので、どこかで見たことがあるかもしれないことをご了承ください。

補間処理用ルックアップテーブルを作る部分です
void MCGraphicEffect::CreateLookUpTable8b(void)
{
        
        //補間処理用ルックアップテーブルの作成

        WORD p,d,i;

        //ポインタチェック
        if(m_pHokanLookUpTable8b != NULL)
        {
                //配列は既に確保されているので何もしない
                return;
        }

        //テーブル用にメモリを確保
        m_pHokanLookUpTable8b = new WORD[256*256];

        //テーブル作成
        for(i = 0,p = 0;p < 256;++p)
        {
                
                for(d = 0;d < 256;++d,++i)
                {

                        /*              
                        double ValSin;
                        
                        //Sin値を算出
                        ValSin = sin(((double)d / 256.0) * (MC_PI / 2.0));
                        
                        //sinの値を2乗してカーブをきつくすると見た目が良いようです。
                        //補間後の結果は個人差があるので,補間後の結果が気に入らない
                        //方はこのテーブルを作成している計算式を変えてみてください。
                        ValSin *= ValSin;
                        
                        //8:8固定小数版
                        m_pHokanLookUpTable8b[i] = (WORD)((((double)p) * ValSin) * 256.0);
                        */
                        
                        //1ピクセルがモコモコに!!
                        //m_pHokanLookUpTable8b[i] = (WORD)(((double)p) * (180.0 
                       //                          * cos((0.3515625 * ((double)(255 - d)))
                       //                          * (MC_PI / 180.0))));

                        //線形補間で8:8の固定小数を用いる
                        m_pHokanLookUpTable8b[i] = (WORD)(p * d);

                        
                }

        }

}
//---------------------------------------------------------------------------
            
バイリニア補間のマクロです
#define BILINEAR8b(bInterpolationColor, bRateX,bRateY, Col0,Col1,Col2,Col3){\
unsigned int iTop,iBottom;\
iTop = m_pHokanLookUpTable8b[(Col0<<8)+(255-bRateX)]\
       + m_pHokanLookUpTable8b[(Col1<<8)+bRateX];\
iBottom = m_pHokanLookUpTable8b[(Col2<<8)+(255-bRateX)]\
	  + m_pHokanLookUpTable8b[(Col3<<8)+bRateX];\
bInterpolationColor = ((m_pHokanLookUpTable8b[((iTop + 0x0080) & 0x0000FF00)+(255-bRateY)]\
                       + m_pHokanLookUpTable8b[((iBottom + 0x0080) & 0x0000FF00)+bRateY]) + 0x0080) >> 8;\
}
//---------------------------------------------------------------------------
拡大ルーチンです
//24bitビットマップポインタ計算用
typedef struct tag_RGBDATA
{
        BYTE B,G,R;
} RGBDATA;

//---------------------------------------------------------------------------
void MCGraphicEffect::EasyZoomHokanCopy24(MCBitmap *SakiBMP,
                                          int SakiX,int SakiY,int SakiWidth,int SakiHeight,
                                          MCBitmap *MotoBMP,
                                          int MotoX,int MotoY,int MotoWidth,int MotoHeight)
{

        //簡易補間拡大コピー(24Bitビットマップ版)
        //クリッピング・エラー処理は省いてあります
        //上下左右反転には対応していません

        int X,Y;        //描画先座標
        int mX,mY;      //転送元座標
        double bx,by;   //拡大率の逆数

        DWORD fmX,fmY;                          //転送元座標固定小数版
        unsigned long fbx,fby;                  //拡大率の逆数の固定小数版
        BYTE R,G,B;                             //補間後のR・G・B
        BYTE r0,r1,r2,r3;                       //近傍ピクセルのRGBワーク
        BYTE g0,g1,g2,g3;
        BYTE b0,b1,b2,b3;
        int deltaX,deltaY;                      //拡大領域内での位置

        RGBDATA color0,color1,color2,color3;    //近傍ピクセルの色

        //拡大率の逆数を計算
        bx = ((double)MotoWidth / (double)SakiWidth);
        by = ((double)MotoHeight / (double)SakiHeight);
        
        //拡大率の逆数を固定小数に変換
        fbx = (unsigned long)(bx * 65536.0);
        fby = (unsigned long)(by * 65536.0);

        //補間拡大ループ
        fmY = (MotoY << 16) + fby;
        
        for(Y = SakiY;Y < (SakiY + SakiHeight);++Y)
        {
        
                fmX = (MotoX << 16) + fbx;
                
                for(X = SakiX;X < (SakiX + SakiWidth);++X)
                {
                
                        //整数部を取り出して、転送元座標とする
                        mX = fmX >> 16;
                        mY = fmY >> 16;
                        
                        //端っこか?
                        if(((mX + 1) >= MotoBMP->Width) || 
                           ((mY + 1) >= MotoBMP->Height) ||
                           (mX == 0) || (mY == 0))
                        {

                                if(mX >= MotoBMP->Width)
                                        mX = MotoBMP->Width - 1;
                                if(mY >= MotoBMP->Height)
                                        mY = MotoBMP->Height - 1;

                                //補間せずにそのまま描画
                                *((RGBDATA*)SakiBMP->ScanLine[Y] + X) 
                                   = *((RGBDATA*)MotoBMP->ScanLine[mY] + mX);
                        
                        }
                        else
                        {

                                //1ピクセルの拡大領域を以下のように分割して、
                                //それぞれの近傍4ピクセルでバイリニア補間を行う
                                //+----+----+
                                //| A | B |
                                //+----+----+
                                //| C | D |
                                //+----+----+

                                //拡大領域内での位置を取得(0〜255の範囲)
                                //(小数部上位8ビットを取得)          
                                deltaX = (fmX & 0x0000FF00) >> 8;
                                deltaY = (fmY & 0x0000FF00) >> 8;

                                //近傍の色を取得
                                if(deltaX < 128)
                                {
                                        
                                        if(deltaY < 128)
                                        {

                                                //Aの部分

                                                //位置補正
                                                deltaX = 128 + deltaX;
                                                deltaY = 128 + deltaY;

                                                //近傍の色を取得
                        //注目画素の左上方向4ピクセル
                                                color3 = *((RGBDATA*)MotoBMP->ScanLine[mY]     +  mX);
                                                color2 = *((RGBDATA*)MotoBMP->ScanLine[mY]     + (mX - 1));
                                                color1 = *((RGBDATA*)MotoBMP->ScanLine[mY - 1] +  mX);
                                                color0 = *((RGBDATA*)MotoBMP->ScanLine[mY - 1] + (mX - 1));

                                        }
                                        else
                                        {

                                                //Cの部分

                                                //位置補正
                                                deltaY = deltaY - 128;
                                                deltaX = 128 + deltaX;
                                                
                                                //近傍の色を取得
                        //注目画素の左下方向4ピクセル
                                                color1 = *((RGBDATA*)MotoBMP->ScanLine[mY]     +  mX);
                                                color0 = *((RGBDATA*)MotoBMP->ScanLine[mY]     + (mX - 1));
                                                color3 = *((RGBDATA*)MotoBMP->ScanLine[mY + 1] +  mX);
                                                color2 = *((RGBDATA*)MotoBMP->ScanLine[mY + 1] + (mX - 1));

                                        }

                                }
                                else
                                {
                                        
                                        if(deltaY < 128)
                                        {
                                
                                                //Bの部分

                                                //位置補正
                                                deltaX = deltaX - 128;
                                                deltaY = 128 + deltaY;
                                                
                                                //近傍の色を取得
                        //注目画素の右上方向4ピクセル
                                                color2 = *((RGBDATA*)MotoBMP->ScanLine[mY]     +  mX);
                                                color3 = *((RGBDATA*)MotoBMP->ScanLine[mY]     + (mX + 1));
                                                color0 = *((RGBDATA*)MotoBMP->ScanLine[mY - 1] +  mX);
                                                color1 = *((RGBDATA*)MotoBMP->ScanLine[mY - 1] + (mX + 1));

                                        }
                                        else
                                        {

                                                //Dの部分

                                                //位置補正
                                                deltaX = deltaX - 128;
                                                deltaY = deltaY - 128;
                                        
                                                //近傍の色を取得
                        //注目画素の右下方向4ピクセル
                                                color0 = *((RGBDATA*)MotoBMP->ScanLine[mY]     +  mX);
                                                color1 = *((RGBDATA*)MotoBMP->ScanLine[mY]     + (mX + 1));
                                                color2 = *((RGBDATA*)MotoBMP->ScanLine[mY + 1] +  mX);
                                                color3 = *((RGBDATA*)MotoBMP->ScanLine[mY + 1] + (mX + 1));

                                        }
                                        
                                }
                                
                                //近傍の色を分解する
                                r0 = color0.R;
                                r1 = color1.R;
                                r2 = color2.R;
                                r3 = color3.R;
                                
                                g0 = color0.G;
                                g1 = color1.G;
                                g2 = color2.G;
                                g3 = color3.G;
                                
                                b0 = color0.B;
                                b1 = color1.B;
                                b2 = color2.B;
                                b3 = color3.B;
                                                                
                                //バイリニア補間                                
                                BILINEAR8b(R,deltaX,deltaY,r0,r1,r2,r3);
                
                                BILINEAR8b(G,deltaX,deltaY,g0,g1,g2,g3);

                                BILINEAR8b(B,deltaX,deltaY,b0,b1,b2,b3);

                                //色を合成
                                RGBDATA color;

                                color.R = R;
                                color.G = G;
                                color.B = B;
                                
                                //色を合成して描画
                                *((RGBDATA*)SakiBMP->ScanLine[Y] + X) = color;

                        }
                        
                        fmX += fbx;

                }

                fmY += fby;

        }

}
//---------------------------------------------------------------------------

 ちなみに、MCBitmapとは私が作ったBMP操作用クラスで、C++BuilderのTBitmapをVCに移植したものだと考えてください。
 ところどころで出てくる"ScanLine"というメソッドは画像の任意1ラインの先頭ポインタを返すものです。
 ポインタは[VOID*]で返すようになっているので、好きな型にキャストして、X座標分ポインタを横に進めて、任意の位置の座標のピクセルデータを取り出すことができるようになっています。

 で、実際にこのルーチンを使って拡大した画像はこうです↓


赤い枠のあたりを拡大します。

通常の拡大


バイリニア補間拡大


 どうです? いい感じでしょ。
 ちなみに、補間テーブルを作成するところで、コメント化してある、1ピクセルがモコモコになるやり方だと、こうなります。

モコモコ(?)拡大


 ちょっと遊んでみました(^^;

 とりあえず、これで上手くいったのですが、掲示板での、私の尊敬する優秀なプログラマであり、良き友人であるGOTTiさんの発言にもあるように、まだどこか「基準の取り方を間違えている」気がするような、これで良いような…… いまいち自信がありません(^^;

 特に、deltaX/deltaYという拡大領域での位置を補正しないといけないあたりが怪しい感じ。
 こういう風に補正するのはキレイじゃないですよね。アルゴリズム的に。たぶんもっと良い方法があると思うんだよねぇ。
 どうやるかは、今は全然思いつきませんが(^^;

 結構いろいろなところで、バイリニア補間のやり方は紹介されていますが、それのどこにも左上にちょっとずれるといったことは書いてなかったので、やっぱり私のやり方が間違っているんだろうなぁ。うーん。

 今回公開したコードが私と同じような現象に悩んでいる人の助けになれば光栄です。

 でも、これでいいのかなぁ。やっぱり注目画素の右・下・右下の近傍の情報で工夫したらいいのかなぁ。別にこれにこだわらないで、今日公開したやり方でいいのかなぁ。もうわけがわからんです(^^;
 誰か正しい方法を教えてーーー

 でも、補間ってピクセルとピクセルの間の、実際には無い部分を計算で補っているのだから、注目画素から4方向の近傍4ピクセルから補間して、拡大領域の4領域を求めているのは正しいと思うんだけどなぁ。
 補間拡大って、拡大領域の中心に注目画素の色がくるのが正しいんだよね。だから、右・下・右下のピクセルだけ利用していたんじゃ、GOTTiさんも書いていたように、拡大領域の左上に注目画素の色がきちゃって、結果、全体が左上にずれちゃうんだよね。

 しかし、やっぱり拡大領域内での位置情報を補正する部分がなんか美しくない。恐らくアルゴリズムの注目する部分が間違っているんだろうなぁ。拡大アルゴリズムのように、元画像の位置から拡大後の位置を求めるんじゃなくて、拡大後の位置から逆算して元画像の位置を求めるように。

 っていうことは…… うーん、なんかひっかかってきたぞ。注目画素の4方向近傍4ピクセルにするんじゃなくて、補間のやり方は、やっぱり従来のやり方のままで、deltaX/deltaYが1ピクセルの拡大領域の左上からの距離じゃなくて、画像の左上から連続的に蓄積されている距離の小数部で、その位置から注目画素を逆算して求めるようにすれば、近傍4ピクセルは注目画素の右・下・右下固定で良い筈だね。

 ふむ、これか。こういうことか。なんかわかってきたぞ。

 となると、ループの始めの

 fmX = (MotoX << 16) + fbx;

 という初期化部分が怪しいな。これを、

 fmX = (MotoX << 16) - 0x8000 + fbx;

 ってすると、おお、できた。(←実際にやって確認しました)
 できました。ここだったんですね。いやぁ、恥ずかしい(^^; こんな単純なところで悩んでいたなんて。
 ちなみにこの0x8000は0.5 * 65536です。最初に0.5左上にずらして、拡大領域の中心部に注目画素の色をもってくるようにします。

 助言してくださった皆様、ありがとうございます。おかげで、上手くいきました。

 というわけで、ちゃんとできたバージョンのソースをもう一度載せます。
 今日は、ちょっと見にくくなるけど、許してくださいね。
 直した部分はで示します。
//---------------------------------------------------------------------------
void MCGraphicEffect::EasyZoomHokanCopy24(MCBitmap *SakiBMP,
                                          int SakiX,int SakiY,int SakiWidth,int SakiHeight,
                                          MCBitmap *MotoBMP,
                                          int MotoX,int MotoY,int MotoWidth,int MotoHeight)
{

        //簡易補間拡大コピー(24Bitビットマップ版)
        //クリッピング・エラー処理は省いてあります
        //上下左右反転には対応していません

        int X,Y;        //描画先座標
        int mX,mY;      //転送元座標
        double bx,by;   //拡大率の逆数

        DWORD fmX,fmY;                          //転送元座標固定小数版
        unsigned long fbx,fby;                  //拡大率の逆数の固定小数版
        BYTE R,G,B;                             //補間後のR・G・B
        BYTE r0,r1,r2,r3;                       //近傍ピクセルのRGBワーク
        BYTE g0,g1,g2,g3;
        BYTE b0,b1,b2,b3;
        int deltaX,deltaY;                      //拡大領域内での位置

        RGBDATA color0,color1,color2,color3;    //近傍ピクセルの色

        //拡大率の逆数を計算
        bx = ((double)MotoWidth / (double)SakiWidth);
        by = ((double)MotoHeight / (double)SakiHeight);
        
        //拡大率の逆数を固定小数に変換
        fbx = (unsigned long)(bx * 65536.0);
        fby = (unsigned long)(by * 65536.0);

        //補間拡大ループ      
     fmY = (MotoY << 16) - 0x8000 + fby;

        for(Y = SakiY;Y < (SakiY + SakiHeight);++Y)
        {
          fmX = (MotoX << 16) - 0x8000 + fbx;
                
                for(X = SakiX;X < (SakiX + SakiWidth);++X)
                {
                
                        //整数部を取り出して、転送元座標とする
                        mX = fmX >> 16;
                        mY = fmY >> 16;
                //端っこか?
               if(((mX + 1) >= MotoBMP->Width) ||
              ((mY + 1) >= MotoBMP->Height))

                {
                                if(mX >= MotoBMP->Width)
                                        mX = MotoBMP->Width - 1;
                                if(mY >= MotoBMP->Height)
                                        mY = MotoBMP->Height - 1;

                                //補間せずにそのまま描画
                                *((RGBDATA*)SakiBMP->ScanLine[Y] + X) 
                                   = *((RGBDATA*)MotoBMP->ScanLine[mY] + mX);
                        
                        }
                        else
                        {

                       //近傍の色を取得
color0 = *((RGBDATA*)MotoBMP->ScanLine[mY] + mX);
color1 = *((RGBDATA*)MotoBMP->ScanLine[mY] + (mX + 1));
color2 = *((RGBDATA*)MotoBMP->ScanLine[mY + 1] + mX);
color3 = *((RGBDATA*)MotoBMP->ScanLine[mY + 1] + (mX + 1));
//近傍の色を分解する r0 = color0.R; r1 = color1.R; r2 = color2.R; r3 = color3.R; g0 = color0.G; g1 = color1.G; g2 = color2.G; g3 = color3.G; b0 = color0.B; b1 = color1.B; b2 = color2.B; b3 = color3.B; //バイリニア補間 BILINEAR8b(R,deltaX,deltaY,r0,r1,r2,r3); BILINEAR8b(G,deltaX,deltaY,g0,g1,g2,g3); BILINEAR8b(B,deltaX,deltaY,b0,b1,b2,b3); //色を合成 RGBDATA color; color.R = R; color.G = G; color.B = B; //色を合成して描画 *((RGBDATA*)SakiBMP->ScanLine[Y] + X) = color; } fmX += fbx; } fmY += fby; } } //---------------------------------------------------------------------------
 
 一部レイアウトが崩れてしまう関係で黒の太字になっていますが、fmX・fmYの初期化部分を変えただけで、画像左・上端のチェックもしなくて良くなったし、近傍4ピクセルも右・下・右下固定で良くなり、分岐がなくなりました。これでちょっと高速化できたかな。
 かなりすっきりしましたね。うん。

 というわけで、やっとバイリニア補間が上手くいきました。
 ああ、こんな単純な間違いで結構な時間悩んでいたなんて、ほんと恥ずかしいなぁ(^^;
 私なりに一生懸命考えて試行錯誤していたつもりですが、アルゴリズムを理解する能力が足りないので、結局試行錯誤という無駄な時間を費やしているんですよねぇ。うーむ。悪い癖ってわかっているのに、いつも考えるより先に色々作ってしまう。くそぅ。
 まだまだ修行が足りないなぁ。
 
 でも、なにはともあれ、できて良かったなぁ。ありゃ、もう2時間もこれ書きながら悩んでたのか。
 明日も早いからもう寝ないとね。

 さて、今日は長々と、やっとプログラマの日記らしく書けた気がします。
 こういうネタをちゃんと整理して日記とは別にちゃんと公開したいなぁ。時間があればやりたいです。

 でわ、おやすみなさい。

0059
2001/12/10 ()
 もう寒すぎるってばよ!! 
 あーー!! 寒いっ!! 今日はなんて寒いんだ!!
 天気予報によれば、昨日より1・2度気温が低いらしいが、そんなもんなのか!? これで1・2度寒いだけなのか? いや、寒すぎるんじゃないかなぁ。

 今日は家に帰ったと同時に布団にもぐりこんだが、あまりの寒さに眩暈を覚えたほどだ。いやマヂで!!

 今はなんとか暖まって、眩暈もしなくなったので、温かいカレーうどんを食べながら、これを書いています(^^;

 で、今日は久しぶりにVBな1日でした。
 いつもはVCを使ってアプリケーションを作成するのですが、今回のはVBです。私はあんまりVBが得意じゃないので、少しずつVBらしい組み方を勉強して、VBもVCもビルダーも使える、なんでもこいのプログラマになりたいです。

 でも、組みやすさで言えば、VC→ビルダー→VBの順かなぁ。でもツール作るのなら、ビルダー→VB→VCだけどね。


 というわけで、今日はとにかく寒かったことを伝えたかったのです(^^;
 他は特になにもなかったなぁ。VBで黙々とプログラムしてただけだし……

 あ、鼻水でてきた。吐く息も白い。部屋の中なのに。

 それでは、カレーうどん食べて暖かくして寝ることにします。
 おやすみなさい。