ページ 11

画像の高速なスケーリング・描画

Posted: 2008年4月03日(木) 17:54
by へろりくしょん
いつもお世話になっています。

今、画像ビュアを作っています。
そこで、画像をウィンドウサイズに合わせて表示するという機能を実装中なのですが、
ウィンドウサイズをマウスでぐりぐりと変更中に、リアルタイムで拡大・縮小を行いながら、
表示したいと考えています。

しかし、StretchBlt()では実用に耐えれる程の速度が出ません。

デジカメで撮った写真を整理するのに、利便を求めて作っていますのでJPEGに特化した方法でも構いません。
また、IJGが配布しているlibjpegライブラリを利用しています。

速度的にはInternet Explorer程度出ればなと思っています。

どなたか良い知恵をお持ちの方、力をお貸しください。 よろしくお願いします。

Re:画像の高速なスケーリング・描画

Posted: 2008年4月04日(金) 10:49
by GPGA
DirectXを使うのはダメですか?

Re:画像の高速なスケーリング・描画

Posted: 2008年4月04日(金) 15:31
by GPGA
DirectXを使用したサンプルプログラムを添付しました。

★ イメージビューア
●開発環境
 VS2008 & DirectX9.0c (October 2004)

●操作方法
 ImageViewer.exeを起動後、出現したウインドウに画像をドラッグ&ドロップする。
 ウインドウサイズを変更することで、ウインドウサイズに合わせた拡大縮小を行う。
 Cキーを押すことで、画像の元の大きさに戻る。

●対応フォーマット
 .bmp .dds .dib .hdr .jpg .pfm .png .ppm .tga

Re:画像の高速なスケーリング・描画

Posted: 2008年4月04日(金) 15:35
by GPGA
あれ、おかしいなアップできない。容量164kbなんですけど、この容量アウトだったかな?
仕方ないので、http://gpga.namaste.jp/ImageViewer.zipにアップしました。

Re:画像の高速なスケーリング・描画

Posted: 2008年4月05日(土) 08:27
by へろりくしょん
返事が遅れました。

DirectXも考えましたが、扱う画像が当面デジカメで撮影したものをと考えていますので、
サイズが、4000x3000pixなどと平気で行きます。

DirectXだと、サーフェスや、テクスチャのサイズには2の累乗でなければならない等と、
いろいろと制限があったと聞いた記憶があり、ちょっと敬遠してました。

改めて調べて見たのですが、今ひとつ要領を得ません。 実際はどうなんでしょう。

言い忘れていましたが、私の環境はWindows2000で、VisualStudio2003を利用しています。

提示頂いたサンプルですが、実行するとMSVCR90.DLLが無いと怒られ、プロジェクトファイルを開くと
バージョンが違うと怒られてしまいます。

ので、新しくプロジェクトを作成し、ビルドしてみました。
しかし、ファイルをドラッグしても画像が表示されません。
デバッガで追いかけ、DirectX回りの関数呼び出しの戻り値をチェックしてみましたが、
特にエラーを返しているものはありませんでした。

プロジェクトファイルをメモ帳で開いて、それを元に、VisualStudio2003の方でも同じ設定に
しているはずですが、何か注意する点などはあるでしょうか。

DirectXは扱ったことがほとんど無く、3Dに関する知識の持ち合わせも無いもので、色々と
調べてはいるのですが、書かれているコードを追おうにもちょっと、いまいち理解が追いつかない状況です。

よろしくお願いします。

Re:画像の高速なスケーリング・描画

Posted: 2008年4月05日(土) 16:59
by GPGA
>しかし、ファイルをドラッグしても画像が表示されません。
DirectX9.0cであれば、どのバージョンでも動くと思いますので
考えられる範囲では、作成しようとしたテクスチャーのサイズがビデオカードの対応サイズより
大きいのかもしれません。まず、小さい画像で表示されるかどうか試してみてください。
ビデオカードが対応しているテクスチャーサイズを確認するときは
m_pD3D->CreateDeviceでデバイスを作成した後に
D3DCAPS9 caps;
m_pDevice->GetDeviceCaps(&caps);
 
を追加し、caps.MaxTextureWidthとcaps.MaxTextureHeightの値を確認してください。

プログラムはテクスチャーの作成と描画周りのみ説明します。
ウインドウの作成とDirectXの初期化についてはリファレンスで調べてください。

まず、扱うサーフェイスやテクスチャが2乗数である必要はありますが
使用する画像そのものが2乗数である必要はありません。
D3DXCreateTextureFromFileExの第5引数に1を与えると、読み込む画像を
テクスチャにするときに、幅と高さを2乗数にし、追加された部分を自動的に黒で埋めてくれます。
たとえば、800x600の画像を読み込んだ場合、テクスチャのサイズは1024x1024となり
横は800dot~1024dot、縦は600dot~1024dotの部分を自動的に黒で埋めてくれます。

> hr = m_pTexture->GetLevelDesc(0, &m_Desc);
これは作成したテクスチャーの情報を取得しています。
上記の画像の場合、
m_Desc.Width == 1024
m_Desc.Height == 1024
となります。

>hr = ::D3DXGetImageInfoFromFile(szFileName, &m_Info);
これは読み込む画像の情報を取得しています。
上記の画像の場合、
m_Info.Width == 800
m_Info.Height == 600
となります。


次に描画周りです。
>m_pDevice->Clear(0, NULL, D3DCLEAR_TARGET, D3DCOLOR_XRGB(0, 80, 200), 1.0f, 0);
これは、その名の通りバックバッファをクリアするのに使用します。

>m_pDevice->BeginScene();
>m_pDevice->EndScene();
DirectXで描画をする場合、その処理はBeginScene~EndSceneの中で行う必要があります。

>hr = m_pDevice->Present(NULL, NULL, NULL, NULL);
描画がした内容を実際の画面に反映させています。

一旦切ります。

Re:画像の高速なスケーリング・描画

Posted: 2008年4月05日(土) 17:42
by GPGA
>m_pDevice->SetFVF(D3DFVF_XYZRHW | D3DFVF_DIFFUSE | D3DFVF_TEX1);
これは、描画する頂点の情報を設定しています。
D3DFVF_XYZRHWは座標を持ち、頂点でデータを定義する構造体に
float x, y, z, rhw;
を入れる必要があります。

D3DFVF_DIFFUSEはディフューズ色成分を持ち、頂点でデータを定義する構造体に
DWORD color
を入れる必要があります。

D3DFVF_TEX1はテクスチャ座標を持ち、頂点でデータを定義する構造体に
float tu, tv
を入れる必要があります。
>const float tu = (float)m_Info.Width / m_Desc.Width;
>const float tv = (float)m_Info.Height / m_Desc.Height;
 
テクスチャの座標は0.0f~1.0fで扱います。
今回のテクスチャのサイズは1024x1024であり、表示した領域は800x600なので
上記の計算式で、表示するための座標を出しています。
struct VERTEX2D
{
    float       x, y, z, rhw;
    DWORD       color;
    float       tu, tv;
} v[4];
 
これはm_pDevice->SetFVFで設定した内容に基づいた構造体を定義しています。
その後の代入式をバラスと

// 左上の頂点
v[0].x = 0.0f; // X座標
v[0].y = 0.0f; // Y座標
v[0].z = 0.0f; // Z座標
v[0].rhw = 1.0f; // 説明が難しいので割愛
v[0].color = D3DCOLOR_XRGB(255, 255, 255); // 色
v[0].tu = 0.0f; // テクスチャのX座標
v[0].tv = 0.0f; // テクスチャのY座標

// 右上の頂点
v[1].x = (float)m_Info.Width; // X座標
v[1].y = 0.0f; // Y座標
v[1].z = 0.0f; // Z座標
v[1].rhw = 1.0f; // 説明が難しいので割愛
v[1].color = D3DCOLOR_XRGB(255, 255, 255); // 色
v[1].tu = tu; // テクスチャのX座標
v[1].tv = 0.0f; // テクスチャのY座標

// 左下の頂点
v[2].x = 0.0f; // X座標
v[2].y = (float)m_Info.Height; // Y座標
v[2].z = 0.0f; // Z座標
v[2].rhw = 1.0f; // 説明が難しいので割愛
v[2].color = D3DCOLOR_XRGB(255, 255, 255); // 色
v[2].tu = 0.0f; // テクスチャのX座標
v[2].tv = tv; // テクスチャのY座標

// 右下の頂点
v[3].x = (float)m_Info.Width; // X座標
v[3].y = (float)m_Info.Height; // Y座標
v[3].z = 0.0f; // Z座標
v[3].rhw = 1.0f; // 説明が難しいので割愛
v[3].color = D3DCOLOR_XRGB(255, 255, 255); // 色
v[3].tu = tu; // テクスチャのX座標
v[3].tv = tv; // テクスチャのY座標

ここまで書いて気づいたのですが、アップしたソースのrhwが0.0fになっていました。
1.0fが正しい数値ですので、直して置いてください。表示されない原因はこっちかもしれません。(私のほうは、なぜか表示されましたが(汗))


>hr = m_pDevice->SetTexture(0, m_pTexture);
>hr = m_pDevice->DrawPrimitiveUP(D3DPT_TRIANGLESTRIP, 2, v, sizeof(VERTEX2D));
最後にデバイスにテクスチャーをセットして、三角ストリップ形式で描画をしています。

Re:画像の高速なスケーリング・描画

Posted: 2008年4月05日(土) 18:47
by ま~く
気になったので^^;
表示したい画像は数千ピクセルx数千ピクセルなのですよね・・
であれば、重たいのは仕方ない気がします。
で、表示させたい場所、つまりディスプレイの解像度も数千ピクセルx数千ピクセルなのでしょうか?
もし普通の(1024x768,1280x1024,1600x1200くらい?)解像度でしたら数千ピクセルx数千ピクセルを
いっぺんに表示できませんよね。
つまり、表示する情報量としてはディスプレイ解像度だけあれば良い事になりませんか?

メモリDCに表示領域を最大に広げた時の画像を一時的に保持し、それを画面に表示させては如何でしょうか?
ビューアにどのような機能があるか分かりませんが、当然拡大機能を使用する場合などは、そのメモリDCではなく
元の画像情報を使う必要がありますが。

Re:画像の高速なスケーリング・描画

Posted: 2008年4月06日(日) 12:29
by へろりくしょん
GPGAさん、詳細な情報ありがとうございます。

>考えられる範囲では、作成しようとしたテクスチャーのサイズがビデオカードの対応サイズより
>大きいのかもしれません。まず、小さい画像で表示されるかどうか試してみてください。
>ビデオカードが対応しているテクスチャーサイズを確認するときは
>m_pD3D->CreateDeviceでデバイスを作成した後に

>D3DCAPS9 caps;
>m_pDevice->GetDeviceCaps(&caps);

 
>を追加し、caps.MaxTextureWidthとcaps.MaxTextureHeightの値を確認してください。


>1.0fが正しい数値ですので、直して置いてください。表示されない原因はこっちかもしれません。(私のほうは、なぜか表示されましたが(汗))


結論から言いますと、表示されなかった原因は後者でした。 1.0fと書き直したら無事表示されました。
しかし調べてみましたところ、caps.MaxTextureWidthとcaps.MaxTextureHeight共に1024でした。
これを超えるサイズになりますと、当然1枚のテクスチャで、1枚の画像を保持することが出来ません。
そこで、1枚の画像を複数のテクスチャで分割して保持し、それらを表示するという方法を取ってみましたが、
速度的にちょっと実用に耐えれるものではありませんでした。

多少の画質を落としてでも、画像のロード時に1枚のテクスチャに収まるサイズにスケーリングし、
それを描画して行った方がいいのかなと思案中です。
画像のロードにかかるオーバーヘッドの解決が鍵になりそうです。



ま~くさん。

>つまり、表示する情報量としてはディスプレイ解像度だけあれば良い事になりませんか?

おっしゃる通りです、当然そのように書いています。
どのみち、描画に関して、デバイスコンテキストの範囲外については何もしなくても勝手にクリップされますので、
全く問題はありません。

>ビューアにどのような機能があるか分かりませんが、当然拡大機能を使用する場合などは、そのメモリDCではなく
>元の画像情報を使う必要がありますが。

さし当たって、まさにここを問題としています。
今、実装しなければならない機能は、指定の範囲(ウィンドウサイズ)に収まるように、画像をスケーリングして、
その全てを描画する。 という機能です。

結局画像全体をスケーリングするには、その画像が持つ全ピクセルを走査しなければならず、これが最大の
ボトルネックとなっています。

私の説明が不十分だったようです。 申し訳ありません。

Re:画像の高速なスケーリング・描画

Posted: 2008年4月06日(日) 14:50
by ま~く
ごめんなさい。まだ良く理解できてないです^^;

>今、実装しなければならない機能は、指定の範囲(ウィンドウサイズ)に収まるように、画像をスケーリングして、
その全てを描画する。 という機能です

との事ですが、表示する情報量に一旦抑えたものを利用しているのですよね?それで遅いのですか?


処理のイメージとして以下のようになると思ったのですが。

1 画像を読み込む(巨大)

2 表示縮尺 == 1.0倍 -> BitBlt

3 表示縮尺 < 1.0 (縮小 今回のお話)

3.1 GetDeviceCapsなどで取得したディスプレイ解像度で作成したメモリDCに一度だけStretchBlt
3.2 メモリDCから画面にStretchBlt
3.3 画面の再描画 -> 3.2 から処理する

4 表示縮尺 > 1.0 (拡大)
4.1 ...
4.2 ...

Re:画像の高速なスケーリング・描画

Posted: 2008年4月06日(日) 15:19
by へろりくしょん
>との事ですが、表示する情報量に一旦抑えたものを利用しているのですよね?それで遅いのですか?

その通りです。

>処理のイメージとして以下のようになると思ったのですが。

>1 画像を読み込む(巨大)

>2 表示縮尺 == 1.0倍 -> BitBlt

>3 表示縮尺 < 1.0 (縮小 今回のお話)

>3.1 GetDeviceCapsなどで取得したディスプレイ解像度で作成したメモリDCに一度だけStretchBlt
>3.2 メモリDCから画面にStretchBlt
>3.3 画面の再描画 -> 3.2 から処理する

現在の実装は概ね、この通りで間違いありませんが、正確には、メモリDCから画面へはBitBlt()で行います。
すでに、3.1でスケーリング処理は行われているので、3.2でまた行う必要も無いという判断です。

ここで、ボトルネックになる部分はStretchBlt()です。

上記の手順で、3の処理を行うのに私の環境では体感で約0.3秒ほどかかります。
これでは使い物にならないのです。

ウィンドウサイズが固定ならばこれでもいいのですが、ウィンドウサイズの変更中(ウィンドウのフレームをマウスでぐりぐりしている)では、致命的な重さとなります。

そして、その処理にかかる時間の内そのほとんどが、3.1の処理に割かれています。
ですから、このStretchBlt()に替わる高速なスケーリング処理を模索中というわけでした。

Re:画像の高速なスケーリング・描画

Posted: 2008年4月06日(日) 15:27
by へろりくしょん
ちょっと訂正します。

スケーリングアルゴリズムそのものを探しているのではなく、スケーリング処理を含めた総合的な処理方法を、ということで。

例えば、画像をロード時に、平均的なウィンドウサイズにフィットする画像を別スレッドで生成し、
拡大縮小時には、これをベースとして行う。 等でも構いません。

力をお貸しください。

Re:画像の高速なスケーリング・描画

Posted: 2008年4月06日(日) 18:22
by ま~く
>現在の実装は概ね、この通りで間違いありませんが、正確には、メモリDCから画面へはBitBlt()で行います。
すでに、3.1でスケーリング処理は行われているので、3.2でまた行う必要も無いという判断です。

ウィンドウのリサイズに対応するためには3.2でStretchBltが必要だと思うのですが認識間違ってますか?

>そして、その処理にかかる時間の内そのほとんどが、3.1の処理に割かれています。

3.1は一度だけ処理するものなので、初回表示時だけ重くなる感じで考えていました。

Re:画像の高速なスケーリング・描画

Posted: 2008年4月07日(月) 09:07
by へろりくしょん
>ウィンドウのリサイズに対応するためには3.2でStretchBltが必要だと思うのですが認識間違ってますか?

>>そして、その処理にかかる時間の内そのほとんどが、3.1の処理に割かれています。

>3.1は一度だけ処理するものなので、初回表示時だけ重くなる感じで考えていました。

なるほど、ようやくおっしゃりたい事が分かりました。
3.2での処理速度を稼ぐために、便宜的に表示するに考え得る最大のサイズをあらかじめ3.1で作っておく
という事ですね。

Re:画像の高速なスケーリング・描画

Posted: 2008年4月07日(月) 10:44
by ま~く
>なるほど、ようやくおっしゃりたい事が分かりました。
3.2での処理速度を稼ぐために、便宜的に表示するに考え得る最大のサイズをあらかじめ3.1で作っておく
という事ですね。


説明が足りなかったようですみません^^;

あとは、読み込みや3.1の処理を別スレッドで処理するなどして、アプリ自体の重みを感じさせないようにすれば良いかと思います。