[画像処理]レイヤー合成の高速化について

フォーラム(掲示板)ルール
フォーラム(掲示板)ルールはこちら  ※コードを貼り付ける場合は [code][/code] で囲って下さい。詳しくはこちら
たろ

[画像処理]レイヤー合成の高速化について

#1

投稿記事 by たろ » 15年前

お世話になっております。
C言語というか、画像処理の話なのですが、ご意見頂ければ幸いです。

ペイントやPhotoshopのような画像加工ソフトを作ろうとしています。
よくあるレイヤー機能を持たせたく、実装してみましたが、レイヤービットマップの合成
(アルファブレンド)処理が遅くて、もっと速くならないかと悩んでます。高速化のために、
効果的な方法はないでしょうか?

環境:WinXP Home (SP3)、VC++2008EE、C言語、Win32API

今の実装(※1)は、レイヤーが何枚かある状態で、ユーザが「レイヤーの透明度を変更」
「レイヤーの可視・不可視を切り替え」するたびに、
1) 全レイヤービットマップ(32bit)をなめて合成ビットマップ(24bit)を作る
2) 合成ビットマップを画面に表示
しています。

画像が大きいほど、レイヤーの枚数が多いほど、1) 合成ビットマップを作る処理 が
どんどん重くなっていきます。(CPU: Core Duo 1.6GHz)
・2000x2000ピクセルのレイヤー2枚合成するのに、約0.2秒
・4000x4000ピクセル      〃      、約0.7秒
・5000x5000ピクセル      〃      、約1.2秒
・      〃       3枚合成するのに、約1.3秒
・      〃       4枚合成するのに、約1.4秒

一方、私は「SAI」という5千円くらいのペイントソフトを使っていますが、このソフトは
同様の操作が、比べものにならないくらい高速なんです。大きな画像でも、レイヤーが
何枚あっても、「レイヤー透明度の変更」や「レイヤー可視・不可視の切り替え」は
一瞬でおわります。私の実装からすると信じられない速さです。

無い知恵を絞って、「浮動小数点演算は使わない」「乗除算を減らす」「除算はビットシ
フトで代替」などのC言語的な小手先の?高速化は施したつもりですが、まったくSAIとい
うソフトの足元にも及びません。

SSEやアセンブラによる高速化はちょっと敷居が高くて未経験なのですが、「画像が大き
いほど、レイヤー枚数が増えるほど遅くなる」という傾向を感じなくする程の効果がある
のかどうか・・?もっと、根本的なデータ構造やアルゴリズムが違うのではないか・・?

「1つのレイヤー透明度を変えただけで全部のレイヤーをなめているから遅い」のかな?
ただ、レイヤーの合成結果をキャッシュしておく?よい方法も思い浮かびません。
なにか定石的なアルゴリズムなどあればよいですが、見つかりません。

大きな画像になるほど合成に時間がかかるのは仕方ない、とも思うのですが、SAIという
ソフトにはそのような傾向は微塵も感じられません。不思議すぎます・・。

という感じなのですが、画像処理ソフトのレイヤー機能の実装方法について、高速化の
ためのよい方式などはないでしょうか?よろしくお願いします。

(※1)現在のレイヤー合成処理の概要

・レイヤー1枚には、32bit(RGBA)DIBデータ1つ、透明度値(0~255)1つがある。
・ビットマップの先頭から最後まで1ピクセルずつ、
・上層レイヤーから下層レイヤーに向かって、
・ピクセル透明度とレイヤー透明度を考慮した係数を、レイヤーピクセルRGB値に乗算し、
・合成ビットマップ(24bitDIB)ピクセルRGB値に加算していく。

・コードの雰囲気は以下のような感じです。(実際はもっとごちゃごちゃしてます)
/* レイヤー1つの型(片方向リスト用) */
  struct LayerNode {
      DIB32            Bitmap;    /* 32bitビットマップ(RGBA)データ */
      BYTE             Alpha;     /* レイヤー透明度(0~255) */
      BOOL             Visible;   /* 可視・不可視状態 */
      struct LayerNode *next;     /* 次(下)のレイヤー */
  };
  /* 合成ビットマップ作成 */
  for( n=0; n<=ビットマップ最終ピクセル位置; n++ )
  {
      R = G = B = 0;   /* 合成ビットマップピクセル値 */

      for( layer=最上層レイヤー; layer; layer=layer->next )
      {
          if( !layer->Visible || !layer->Alpha ) continue; /* 不可視・完全透明 */

          係数 = ピクセル透明度(layer->Bitmap->Pixel[n].A)と
                 レイヤー透明度(layer->Alpha)を考慮して算出

          R += 係数 * layer->Bitmap->Pixel[n].R
          G += 係数 * layer->Bitmap->Pixel[n].G
          B += 係数 * layer->Bitmap->Pixel[n].B
      }

      合成ビットマップ->Pixel[n].R = R;
      合成ビットマップ->Pixel[n].G = G;
      合成ビットマップ->Pixel[n].B = B;
  }
  /* 画面表示 */
  StretchDIBits相当の処理( 合成ビットマップ );

ISLe

Re:[画像処理]レイヤー合成の高速化について

#2

投稿記事 by ISLe » 15年前

UINT32の変数にA8R8G8B8のピクセルデータが入っているとしてレイヤー透明度0~255を掛ける場合、

UINT32 pixel_argb; // 元のピクセルデータ
UINT8 layertrans; // レイヤー透明度(0~255)
UINT32 pixel_0r0b = pixel_argb & 0x00ff00ff;
UINT32 pixel_00g0 = pixel_argb & 0x0000ff00;

pixel_0r0b = ((pixel_0r0b * layertrans) / 255) & 0x00ff00ff;
pixel_00g0 = ((pixel_00g0 * layertrans) / 255) & 0x0000ff00;

// ↓のほうが高速だけど誤差があったようななかったような
//pixel_0r0b = ((pixel_0r0b * (layertrans+1)) >> 8) & 0x00ff00ff;
//pixel_00g0 = ((pixel_00g0 * (layertrans+1)) >> 8) & 0x0000ff00;

pixel_dest = pixel_0r0b | pixel_00g0;

とするのが割と速いと思います。

ピクセル透明度(いわゆるアルファチャンネル)については、SAIにはその機能自体が無いようです。
Photoshopでは新規作成したデフォルトのレイヤーにアルファチャンネルは無くて、アルファチャンネルを追加したレイヤーだけがアルファチャンネルを持つので特定のレイヤーだけに計算を絞ることができます。
またメモリを食いますがアルファチャンネルは事前計算しておくことも可能です。

--追記
> 「浮動小数点演算は使わない」「乗除算を減らす」「除算はビットシフトで代替」
既に対策しているとありますね。すみません。 画像

たろ

Re:[画像処理]レイヤー合成の高速化について

#3

投稿記事 by たろ » 15年前

ISLeさんありがとうございます。

> UINT32 pixel_0r0b = pixel_argb & 0x00ff00ff;
> UINT32 pixel_00g0 = pixel_argb & 0x0000ff00;
> pixel_0r0b = ((pixel_0r0b * layertrans) / 255) & 0x00ff00ff;
> pixel_00g0 = ((pixel_00g0 * layertrans) / 255) & 0x0000ff00;
> pixel_dest = pixel_0r0b | pixel_00g0;

な、なんですかこの計算方法・・初めて見ました。こんなやり方があるんですか。
ええと・・例えば、0x00ff00 * 255 というのは、下位8ビットを無視すると、0x00ff * 255 と同じ、
ということ・・ですよね・・。そうなのか・・。

あと、1~255の範囲で掛けたり割ったりするぶんには、0xffff を超えないから大丈夫、という条件も
合わさってできる方法でしょうか。

うーんこんなのとても思いつかないなぁ・・すごい。
ありがとうございました。ちょっと試してみたいと思います。

あ、あとさらに、合成ビットマップも32bitじゃないとダメですね。
今は24bitだから、変えないとダメか。メモリ使用量がちょと増えるけど致し方あるまい。。


> ピクセル透明度(いわゆるアルファチャンネル)については、SAIにはその機能自体が無いようです。

えっ、そうなんですか。
半透明な感じのブラシで塗るときは、ピクセル透明度を使ってると思ってました。
Photoshopのレイヤーも既定ではアルファチャネルがない・・そうだったのか・・。

でもそうすると、不思議なのですが・・、下のレイヤーが透けて見える感じの薄いブラシで塗っていくと、
下のレイヤーが「よく透けて見える部分」と「あまり透けてない濃い部分」のグラデーションのような状態
になると思いますが、こういうのを24bitのRGBだけでどうやって実現するんだろう・・。


> またメモリを食いますがアルファチャンネルは事前計算しておくことも可能です。

「乗算済みアルファ」という言葉を、どこかのサイトで見たことがあります。
アルファチャネルを掛けた値をキャッシュのような形で保持しておく感じでしょうか。
軽く想像してはみましたが、本当にメモリ使用量が増えそうです・・恐ろしい・・。


>> 「浮動小数点演算は使わない」「乗除算を減らす」「除算はビットシフトで代替」
> 既に対策しているとありますね。

あ、いえこの辺りはぜんぜん自信はないです。自分の思いつく限りというだけで・・。

シエル

Re:[画像処理]レイヤー合成の高速化について

#4

投稿記事 by シエル » 15年前

ビット演算は理解されてますか?

ookami

Re:[画像処理]レイヤー合成の高速化について

#5

投稿記事 by ookami » 15年前

思いつきですが以下。

アルファチャンネルの話を別にすれば、
Zバッファで多少早くなるかも?
http://ja.wikipedia.org/wiki/Z%E3%83%90 ... 5%E3%82%A1

あとは、画面外の計算を省略するとか...

--

SAIというツールは、使ったことはありませんが、
ピクセルあたり64bit使っているようですね。

更新履歴を見ると、○○を高速化、という文面が
たくさんあるので、ちょっとずつアルゴリズムが改善されている
ようですね。

たろ

Re:[画像処理]レイヤー合成の高速化について

#6

投稿記事 by たろ » 15年前

シエルさん

> ビット演算は理解されてますか?

人に解説できるくらい理解してるか?と言われると怪しいですね・・・「使ってます」くらいかな。

ちなみに「初めて見た」というのは、(&演算や|演算を見たことがない、という事ではなく)
RGB値の計算方法として初めて見た、1回の演算でビットの途中途中にある2つの数値(RGB)を
一気に演算するとは・・その発想はなかった(笑)という感じでした。
有名なやり方なんですかねぇ・・?

ビット演算と言われると、使っていたのは、

(1) 状態管理フラグで、複数同時にON/OFFになる時はまとめてビットフラグで
#define  STATE1  0x001
#define  STATE2  0x002
if( (flag & STATE1) && (flag & STATE2) ) …
(2) 2の倍数(2,4,8,…)で乗除算する時は(符号に気をつけて)ビットシフトで
n = n << 1; /* ×2 と同じ */
n = n >> 2; /* ÷4 (余り切り捨て)と同じ */
(3) 小数点以下の精度より速度優先の場合は、float/doubleでなく整数型を固定小数点型として
n = n << 12; /* 小数部12bit固定小数に変換 */
    :
/* n を掛けたり割ったり */
    :
n = n >> 12; /* 整数に戻す(小数部切り捨て) */
の3種類くらいかなぁ・・。固定小数はこちら↓のサイトを参考にそのまま使ってます。
http://www.sage-p.com/compone/toda/fixdec.htm

palladium

Re:[画像処理]レイヤー合成の高速化について

#7

投稿記事 by palladium » 15年前

ご参考になるかどうかわかりませんが
こんなページがありました。

http://lamoo.s53.xrea.com/develop/gdipl ... s_blt.html

ookami

Re:[画像処理]レイヤー合成の高速化について

#8

投稿記事 by ookami » 15年前

61192で、
> // ↓のほうが高速だけど誤差があったようななかったような
とあったので、ざっくりですが、検証してみました。

添付ファイルを参照下さい。

アルファ値が小さいほど偏差が大きくなりますが、
最大でも0.0039なので、RGB各0~255の範囲なら、
±1でるかどうか、といったところですね。

人間が見ても分からない範囲でしょうから、
表示時は高速な方、
ファイル保存時は正確な方、
といったところでしょうか。

※ すいません、言葉的には「偏差」じゃなくて「誤差」かも。

たろ

Re:[画像処理]レイヤー合成の高速化について

#9

投稿記事 by たろ » 15年前

ookamiさん

ありがとうございます。

> Zバッファで多少早くなるかも?

3次元コンピュータグラフィックスのテクニックのようですが、2次元のお絵描きソフトにも応用
できるんでしょうか。考えたことがなかったです。Zバッファのプログラムも作ったことないので、
まずは勉強から・・調べてみたいと思います。

> あとは、画面外の計算を省略するとか...

表示のズーム処理では、いちおう画面外の計算を省略していますので、今回のレイヤー合成でも
考えてみました。が、どうも採用には至らずで・・。画像全体を表示している状況を考えると、
結局ぜんぶ合成しないといけないし・・という辺りで私の頭では限界でした(^^;


SAIは1ピクセル64bitなんですか。知らなかった・・。
型でいうと、UINT64/ULONGLONGで扱うことになるのかな。確かにSAIはメモリ使用量は多いです。
でもそれであんなに高速に動作しているのがすごい・・。単純に64bit型に置き換えるというのは
CPU/OSが32bit環境だとかなり遅くなりそうな気がします。MMX/SSEを使わないと…となりそうな。
SSEとかちょっと敷居が高くて手を出せてませんorz

SAIの更新履歴、ほとんど不具合修正に見えますが、高速化もチラホラありますね。アルゴリズム
までわかるようなのは少ないですが・・。比較対象としてはちょっとレベル高すぎとは思います・・。

たろ

Re:[画像処理]レイヤー合成の高速化について

#10

投稿記事 by たろ » 15年前

palladiumさん

ありがとうございます。

画面表示処理(VRAM転送ていうんでしょうか)の高速化も、確かに気になる要素ではありました。
レイヤー合成処理と画面表示処理を順番じゃなくて一緒にやってしまう、という想像もしましたが、
ソースが手に負えなくなると思ってやめました・・。

GDI+ は「遅い」という噂もあって、使ってみたことはありませんでした。やっぱり遅いんですかね。
あと DDB も実は使ってません・・。今は、DIB を SetDIBitsToDevice() で画面に送っています。
DDB の方が多少速いらしいという噂も目にしつつ、ちょっと取り扱いAPI(CreateCompatibleDCとか)が
独特すぎて敬遠してしまってます。。

さらに新しい噂では、GDI関数はDirectX系のAPIをラップするだけになるとか何とか見たような??
ブラウザまでGPU支援機能を使うようになったりして、「高速化」と言いつつGDIしか使ってないのは
時代遅れなのかなぁ・・と思いつつ、DirectX まだ未経験です(^^;

たろ

Re:[画像処理]レイヤー合成の高速化について

#11

投稿記事 by たろ » 15年前

ookamiさん

すごい、貴重な情報をありがとうございます。m(_ _)m
仕事はやいですねぇ~。

なるほど、見た目には違いは判別できない程度の差ですかね。
それなら速い方を使いたいです。実際に画像で比べてみたいと思います。

ただ、自分のプログラムはちょっとまだ手付かずでした・・。

ISLe

Re:[画像処理]レイヤー合成の高速化について

#12

投稿記事 by ISLe » 15年前

最初にすみません。
ピクセル透明度はいわゆるアルファチャンネルではありませんでした。
ソフトの機能と概念をごっちゃにしてました。
SAIには透明度だけを分離して編集する機能はないですがピクセルごとの透明度は持ってます。
ピクセルごとの透明度がないと水彩っぽい塗りできないですもんね。

件のコードはゲームでフェードイン/フェードアウトの演出で使われる感じのものです。
今回は精度を落としてはいけない用途なのでトリッキーレベル1というところです。
ookamiさんが検証してくださったとおり誤差は表面化しないので高速版を使ってください。

Zバッファは不透明ピクセルに出会ったらそれより下のレイヤーを計算しないように、ピクセルと同じ数のバイト配列などを用意してマークしていく感じの仕組みになると思います。
アニメっぽい塗りの絵には有効と思いますが水彩っぽい塗りの絵には逆効果になるかもしれません。

たろ

Re:[画像処理]レイヤー合成の高速化について

#13

投稿記事 by たろ » 15年前

ISLeさん

私もピクセル透明度とアルファチャンネルは同じ意味だと思ってました・・。
ググッてもそういう説明が多いですし。でも確かにPhotoshopだとちょっと違うようで、
アルファチャンネルというバッファの編集ができるとか何とか?よくわかりませんが。
ひとまず水彩っぽい塗りにはピクセル透明度が必要、ということで安心しました。

なるほどゲームのフェード効果で使われているんですね。
ここはゲームの実装に詳しい方が多いですね。

Zバッファの考え方もありがとうございます。レイヤーがZ軸に相当するんですね。何となく想像できました。
実は、「不透明ピクセルに出会ったらそれより下のレイヤーを計算しない」は、ピクセル毎に
「 if( 透明度==255 ) { レイヤーループ抜ける; } 」という処理に、既になっていました。
Zバッファ方式で別にバッファを持つのは・・ちょっと悩みます・・。


自アプリの改造状況ですが、
とりあえずレイヤービットマップ32bit、合成ビットマップ24bitは変えずに、
高速版の計算方法だけを取り入れてみました。少し速くなりました。

10%くらいかな?1.0秒が0.9秒になった、くらいの感じです。
ookamiさんが計算してくれた 0.003 前後の誤差も、私の目にはまったく問題ありません。

ということで解決、でよいかなと思いましたが・・

やはりビットマップの32bit→24bit変換も一緒にやってるのが遅い気がするので、
両方32bitにしてみたいと思います。その報告で解決とさせて頂きたいと思います。 画像

たろ

Re:[画像処理]レイヤー合成の高速化について

#14

投稿記事 by たろ » 15年前

自アプリであれこれ試してみた結果を報告します。
25%くらい速くなりました。

(1) 0x00ff00ff, 0x0000ff00 マスクを使ったRGB計算(高速版)で 10%くらい
(2) 32bit→24bitをやめて32bit→32bitにして、ループ処理を単純化して 10%くらい
(3) ループ内で呼び出していた関数をベタ書き(関数呼び出しコスト削減)で 5%くらい

例えば、3000x3000ピクセルの半透明レイヤー5枚の合成にかかる時間が、
1.0秒台だったのが、0.7秒台くらいになりました。

皆様いろいろ教えて頂きありがとうございました。m(_ _)m
これで本件は解決とさせて頂きます。

それにしても、「このソフト軽くて速くていいなー」なんて何気に使っていたソフトを
作った人のすごさを思い知った次第です。

編集:解決マークはずしました。 画像

ISLe

Re:[画像処理]レイヤー合成の高速化について

#15

投稿記事 by ISLe » 15年前

#解決してた。

テーブルを用意してレイヤー間の合成処理を高速化する方法があります。

UINT8 layer1_r;
UINT8 layer2_r;
UINT8 dst_r;

// 「レイヤー1のデータ * 256 + レイヤー2のデータ」のインデックスに予め合成計算結果を入れておく
// RGB共通・合成方法別に用意すると良い
UINT8 table[65536];

dst_r = table[(layer1_r << 8) | layer2_[/url];
// RGBそれぞれに対して計算

インデックスの計算だけになるのと、オーバーフローやアンダーフローも考慮しなくて良いので速くなります。
オーバーフローやアンダーフローへの考慮はテーブル作成時に盛り込まれているので。(←追記)

共用体使って32ビットピクセルデータからRGB各要素を直接参照するとかは既にやってますかね。
最終的に24ビットに落とすのは影響少ない気がします。
画像

たろ

Re:[画像処理]レイヤー合成の高速化について

#16

投稿記事 by たろ » 15年前

ISLeさんありがとうございます!

解決というか、「もう他に思いつかないので終わり」という感じでした。
新しい話もらったので解決マーク外させてもらいます。

> テーブルを用意してレイヤー間の合成処理を高速化する方法があります。

なるほど、ルックアップテーブルと呼ばれたりする考え方でしょうか?
でも「レイヤー間の」というあたりがよく分かりません・・。

> UINT8 layer1_r;
> UINT8 layer2_r;
> UINT8 dst_r;
>
> // 「レイヤー1のデータ * 256 + レイヤー2のデータ」のインデックスに予め合成計算結果を入れておく
> // RGB共通・合成方法別に用意すると良い
> UINT8 table[65536];
>
> dst_r = table[(layer1_r << 8) | layer2_[/url];
> // RGBそれぞれに対して計算

「レイヤー1,2のデータ」とは、RGBの値のことですよね。(layer1_r,layer2_r)
例えば、「レイヤー1のR値」と「レイヤー2のR値」から「合成したR値」が決まる、ということ・・?
あれ、透明度は?レイヤー透明度とピクセル透明度はどこで考慮するのでしょうか・・??

> インデックスの計算だけになるのと、オーバーフローやアンダーフローも考慮しなくて良いので速くなります。
> オーバーフローやアンダーフローへの考慮はテーブル作成時に盛り込まれているので。

すみませんここもよく分からないのですが、([256][256]の2次元配列でなく)[65536]の1次元配列にして、
table[(layer1_r << 8) | layer2_[/url] のように使うこと、による効果でしょうか?
でも layer1_r,layer2_r が UINT8 範囲内なら [layer1_[/url][layer2_[/url] でも大丈夫そうな気もするな・・。

> 共用体使って32ビットピクセルデータからRGB各要素を直接参照するとかは既にやってますかね。

やってません!
恥ずかしながら共用体って、便利さや必要性を実感したことがなく使ったことがないです・・。
今は、
UINT32 color = *((UINT32*)ビットマップピクセルポインタ);
BYTE   A = (BYTE)(color>>24);
BYTE   R = (BYTE)(color>>16);
などとやっていました。共用体を使うと、ここら辺が効率よくなるんでしょうか?
こんな↓感じの使い方でしょうか・・??
union PIXEL32
{
    struct {
        UINT8  B,G,R,A;  /* Windows GDI/DIB ではメモリ上 BGRA の順で並ぶ */
    } e;                 /* (element の e) */
    UINT32     color;    /* 0xAARRGGBB (リトルエンディアンなので逆順) */
};
/* ARGB を直接参照できる */
((union PIXEL32*)ビットマップピクセルポインタ)->e.A
((union PIXEL32*)ビットマップピクセルポインタ)->e.R
あれ、でもこの参照のしかたなら共用体じゃなくても構造体でもできそうな・・?

> 最終的に24ビットに落とすのは影響少ない気がします。

これは、ループ内のカウンタ変数が、32ビット用ピクセル位置と24ビット用ピクセル位置と
2つ必要だったのが、(両方32ビットにすれば)1つだけで済む、というだけの話でした。
画像

ISLe

Re:[画像処理]レイヤー合成の高速化について

#17

投稿記事 by ISLe » 15年前

> でも「レイヤー間の」というあたりがよく分かりません・・。

No:61192のコードは各レイヤーで透明度を掛けたピクセルデータを求める処理でした。
今回はレイヤー同士を合成する部分なので「レイヤー間」と書いたのですが、それまでに合成した画像と各レイヤーの合成処理なので「レイヤー間」はおかしいですね。
たろさんが最初の質問に書かれてたコードは加算合成なんですけど、半透明合成はピクセルの最終的な透明度がT(0~255)だとしたら、透けて見える(それまでに合成済みの)ピクセルには255-Tの透明度を掛けて、各要素を足し算するので、自分方と相手方の間の計算という意味です。

> すみませんここもよく分からないのですが、([256][256]の2次元配列でなく)[65536]の1次元配列にして、
> table[(layer1_r << 8) | layer2_[/url] のように使うこと、による効果でしょうか?

例えば加算合成テーブルのtable[128][128]には128と128を足した結果が入っています(そのように準備する)。
ふつうに足したら128+128=256でオーバーフローしてしまうところを255で初期化しておいてそのまま使います。

> UINT32 color = *((UINT32*)ビットマップピクセルポインタ);
> BYTE A = (BYTE)(color>>24);
> BYTE R = (BYTE)(color>>16);
>
> などとやっていました。共用体を使うと、ここら辺が効率よくなるんでしょうか?こんな↓感じの使い方でしょうか・・??

union PIXEL32 pixel;
pixel.color = *((UINT32*)ビットマップピクセルポインタ);
BYTE A = pixel.e.A; // 同じメモリ領域を使っているのでcolorに代入した時点で反映されてる
BYTE R = pixel.e.R;
という感じですね。
逆にARGB要素を変更すればcolorに反映されます。

たろ

Re:[画像処理]レイヤー合成の高速化について

#18

投稿記事 by たろ » 15年前

ISLeさん

> たろさんが最初の質問に書かれてたコードは加算合成なんですけど、半透明合成はピクセルの最終的な
> 透明度がT(0~255)だとしたら、透けて見える(それまでに合成済みの)ピクセルには255-Tの透明度を掛けて、
> 各要素を足し算するので、自分方と相手方の間の計算という意味です。

なんとなく、わかりかけてきました。
というか今の加算方式の考え方を変えないとダメかな・・と思いました。
今は、「それまでに合成済みのピクセル」は参照しておらず、最終的な透明度が255を超えないように
計算したRGB値をひたすら積み上げていく感じになっていました。

ちょっとまず「それまでに合成済みのピクセルに255-Tの透明度を掛けて」という感じの処理にするには
どうすればいいか考えたいと思います。たぶん2枚だけ合成するならそうなるのかなーと思うのですが、
3枚以上の合成でどうやるのか・・。やばい、すぐにはできなさそうな予感・・。

ちなみに今のコードは、こちら↓の「Photoshopでの計算方法」の文章を読んで、
http://d.hatena.ne.jp/hajimehoshi/20080420/1208689925
上のレイヤーから順に透明度エネルギーを消費していく、という感じのを自分で適当に作りました。


> 例えば加算合成テーブルのtable[128][128]には128と128を足した結果が入っています(そのように準備する)。
> ふつうに足したら128+128=256でオーバーフローしてしまうところを255で初期化しておいてそのまま使います。

なるほど。配列が1次元か2次元かは関係ないですね。
テーブルの値と使い方はなんとなくわかったような気がします。
ただ、上の話もあってまだ改造イメージは具体的に思い浮かびません。。。


> union PIXEL32 pixel;
> pixel.color = *((UINT32*)ビットマップピクセルポインタ);
> BYTE A = pixel.e.A; // 同じメモリ領域を使っているのでcolorに代入した時点で反映されてる
> BYTE R = pixel.e.R;
> という感じですね。
> 逆にARGB要素を変更すればcolorに反映されます。

なるほど。UINT32で取ってきていおいて、RGBAがすぐに参照できるということですね。
逆にRGBAを個別に変更すればUINT32に直に反映される、というのはいいですね。
いろんな場面で使えそうな気がしてきました。ありがとうございました。

たろ

Re:[画像処理]レイヤー合成の高速化について

#19

投稿記事 by たろ » 15年前

ちょっと頭がモヤモヤしたまま考えが進まないこともありまして・・
もしよかったら合成処理を行っている関数のソースを見て頂けると助かります。
何か変なことしてないかとか、ここは効率悪いし意味わからんとか・・。

LayerBlendUpdate という関数です。最後に貼りつけます。

独自の構造体を使っているので、この関数を使ったプログラムのソースも添付しました。
複数枚のビットマップを半透明合成するだけのプログラムです。

・起動するとウィンドウが1つ
・ウィンドウにBMPファイルを複数ドラッグ&ドロップすると、勝手に半透明にして合成する
・24ビットWindowsビットマップファイルで同じ縦横サイズのものしか合成できない

目的は、LayerBlendUpdate 関数を高速化したいという話になります。
今の加算合成?のやり方をどう変更すればよいか思案中です・・。
// レイヤー合成処理
void LayerBlendUpdate( void )
{
    UINT    WxH, pos;

    if( !LayerBlend.BIH ) return;

    WxH = LayerBlend.BIH->biWidth * LayerBlend.BIH->biHeight;   // 全ピクセル数

    for( pos=0; pos<WxH; pos++ )                        // 先頭から1ピクセルずつ
    {
        UINT32      blend = 0;                          // 合成ピクセル値
        #define     ALPHA_ENERGY_MAX                    256
        UINT        alphaEnergy = ALPHA_ENERGY_MAX;     // 合成透明度エネルギーの残り(初期値=最大値)
        PLAYER      layer;

        for( layer=LayerTop; layer; layer=layer->next )     // レイヤー上から順に
        {
            UINT32  sColor = *((UINT32*)layer->Bitmap + pos);       // レイヤーピクセル値
            BYTE    pixelAlpha = (BYTE)(sColor>>24);                // ピクセル透明度
            UINT    blendAlpha;                                     // 合成透明度

            if( !layer->Visible ) continue;                         // レイヤー非表示スキップ
            if( !layer->Alpha || !pixelAlpha ) continue;            // 透明スキップ

            // エネルギーの残りからこのレイヤーピクセルの合成透明度を決定
            if( layer->Alpha==255 && pixelAlpha==255 )
            {
                // 完全不透明
                if( alphaEnergy >= ALPHA_ENERGY_MAX )
                {
                    // 最初(最上層レイヤー)のピクセルが不透明なので単純コピーして終了
                    blend = sColor;
                    alphaEnergy = 0;
                    break;
                }
                // 不透明なので残りエネルギーぜんぶもらう
                blendAlpha = alphaEnergy;
                alphaEnergy = 0;
            }
            else
            {
                // 半透明=残りエネルギーから透明度割合ぶんだけもらう
                // 本来は、レイヤー透明度×ピクセル透明度=255×255段階の精度だが、
                // 0x00ff00ff,0x0000ff00マスクを利用した計算を使うため255以下の値に圧縮する
                if( layer->Alpha==255 )
                {
                    blendAlpha = (pixelAlpha * alphaEnergy) >>8;
                }
                else if( pixelAlpha==255 )
                {
                    blendAlpha = (layer->Alpha * alphaEnergy) >>8;
                }
                else
                {
                    // +1 はなんとなく 
                    blendAlpha = ((layer->Alpha * pixelAlpha + 1) * alphaEnergy) >>16;
                }

                if( !blendAlpha ) continue;

                alphaEnergy -= blendAlpha;
            }

            // 合成透明度の割合で合成ピクセル値に加算
            blend += (((sColor & 0x00ff00ff) * (blendAlpha + 1) >>8) & 0x00ff00ff)|
                     (((sColor & 0x0000ff00) * (blendAlpha + 1) >>8) & 0x0000ff00);

            if( !alphaEnergy ) break;       // エネルギーなくなったら終わり
        }

        if( alphaEnergy )
        {
            // エネルギーが余ったので、レイヤー背景色(白固定)を加算
            UINT tmp = (0xff * alphaEnergy) >>8;
            blend += tmp | (tmp<<8) | (tmp<<16);
        }
        // 合成色1ピクセル決定
        *((UINT32*)LayerBlend.Bitmap + pos) = blend;
    }
}

めるぽん

Re:[画像処理]レイヤー合成の高速化について

#20

投稿記事 by めるぽん » 15年前

今あまり時間がなくて実際に試す時間が無いのでコードを読んだだけですが、

最終的に白背景に描画するなら、白背景と一つ手前のレイヤをアルファ合成すればアルファ値の残りのエネルギーとか考えなくていい(白背景のピクセルのアルファは必ず1.0だから、合成してもアルファは1.0)ので、
Layer1→Layer2→Layer3
という順番で繋がっているなら、
1.白背景とLayer3を合成
2.1.の画像とLayer2を合成
3.2.の画像とLayer1を合成
とした方が、余りのエネルギーとか考える必要が無くて速いと思います(この順番の単方向リンクリストだとLayer3から処理するとかできないのでデータ構造を少し弄る必要がありますが)。
あと1ピクセル毎に layer を回していますが、アクセスがあっちこっちに行ってしまうので、layer のループの中でピクセルのループを回した方がいいです。そうすれば layer->Visible や layer->Alpha のチェックで無駄に時間を取っている(layer全体で1回判定すればいいだけなのに1ピクセル毎に判定をしている)部分も無くなりますね。

めるぽん

Re:[画像処理]レイヤー合成の高速化について

#21

投稿記事 by めるぽん » 15年前

こんなイメージ
UINT32* dst; // ← 転送先。全部背景色(今回は 0xffffffff)になっていると思って下さい
LAYER* rLayer; // ← LayerTop が逆になったリストだと思って下さい

for (layer = rLayer; layer; layer = layer->next)
{
    if (!layer->Visible || layer->Alpha == 0) continue;

    src = ((UINT32*)layer->Bitmap + pos);
    int alpha = layer->Alpha; // レイヤ全体のアルファ
    for (int n = 0; n < WxH; n++)
    {
        UINT32 s = src[n];
        UINT32 d = dst[n]; // こいつのアルファは必ず 255 のはず
        // 各ピクセルのアルファ(ほんとは 255 で割るべき)
        int a = ((s >> 24) * alpha) >> 8;
        int ra = 255 - a;
        // 普通のアルファブレンド処理(ここはもっと速くできる気がする)
        dst[n] =
            0xff000000 |
            (((((s >> 16) & 0xff) * ra + ((d >> 16) & 0xff) * a)) & 0xff00) << 8) |
            (((((s >>  8) & 0xff) * ra + ((d >>  8) & 0xff) * a)) & 0xff00) << 0) |
            (((((s >>  0) & 0xff) * ra + ((d >>  0) & 0xff) * a)) & 0xff00) >> 8);
    }
}
多分間違いはいっぱいあると思いますけど、何となくイメージだけ掴んで貰えれば。

たろ

Re:[画像処理]レイヤー合成の高速化について

#22

投稿記事 by たろ » 15年前

ぬるぽんさんありがとうございます。

確かにlayer->Visibleなどの判定は無駄に多いとは思ってました。でも回避できなくて・・。

「残りのエネルギーとか考えなくていい」方法というのは、(たぶん理解できてないので)気になる点があるのですが、
エネルギー分配方式だと「下のレイヤーになるほど薄くなる」のですが、これも実現できてしまうのでしょうか?
例えば、あるレイヤーの透明度とビットマップは変わらなくても、上にたくさんレイヤーが重なるほど
どんどん薄くなっていく(結果的に配分される透明度が少なくなっていく)という・・。

いや、すみません、とりあえず頂いたコードイメージを実装してみようかな。
もうちょっとよく考えてみます。


ちなみにプログラム全体のコードを変更したので添付しなおします。
LayerBlendUpdate 関数は(まだ)そのまま変えてません。
操作性がイマイチ合成結果の確認がしにくかったので変えました。

・ファイルを1つずつドロップして上に重ねていけるようにした
・重なっているファイルの順序(レイヤー情報)を表示するようにした

めるぽん

Re:[画像処理]レイヤー合成の高速化について

#23

投稿記事 by めるぽん » 15年前

>エネルギー分配方式だと「下のレイヤーになるほど薄くなる」のですが、これも実現できてしまうのでしょうか?
エネルギー分配方式って、Layer1、Layer2 のアルファがそれぞれ 60%、75% だった場合、
Layer1 に 60%
Layer2 に 100-60=40% の 75%、つまり 0.4*0.75 = 30%
背景に残りの 10%
と分配する方法ですよね。

逆に、背景から通常のアルファブレンドで計算していく場合ですが、アルファブレンドの式は
dst*(1-α) + src*α
で求められるので、背景とLayer2をアルファブレンドすると
BG*(1-0.75) + L2*0.75
となります。この結果に対して更に Layer1 をアルファブレンドすると、
(BG*(1-0.75) + L2*0.75)*(1-0.6) + L1*0.6
となります。これを計算すると、
(BG*0.25 + L2*0.75)*0.4 + L1*0.6
= BG*0.1 + L2*0.3 + L1*0.6
となって、エネルギー分配方式と全く同じ結果になります。

実際、エネルギー分配方式の式を変形していくと、後者の式
(BG*0.25 + L2*0.75)*0.4 + L1*0.6
が出てきます。

なので、背景と合成していいのであれば、背景からアルファブレンドを繰り返すだけでいいです。
# 上記のコード、多分 s と d が逆ですね。実際に式を書いてから気がつきました

ただ、
>layer のループの中でピクセルのループを回した方がいいです
の部分に関しては、dst へ書き込む回数が増えてしまうので、もう少し何か考えた方がいいかもしれませんね。
# x86 に関してだけ言えば、レジスタの数がそんなに多くないからあまり影響がなさそうな気もします

ISLe

Re:[画像処理]レイヤー合成の高速化について

#24

投稿記事 by ISLe » 15年前

たろさん

まだ続きの投稿を読んでないのですが、とりあえずここだけコメント。

No:61399
> ちなみに今のコードは、こちら↓の「Photoshopでの計算方法」の文章を読んで、
> http://d.hatena.ne.jp/hajimehoshi/20080420/1208689925
> 上のレイヤーから順に透明度エネルギーを消費していく、という感じのを自分で適当に作りました。

このページは、ノベル系ゲームのキャラクター画像を自前でアンチエリアス処理する方法について書かれています。
アンチエイリアスは画像のフチがギザギザにならないように半透明処理するものです。
ゲームのキャラクター画像なので画像の殆どの部分が完全に不透明か完全に透明です。
そういう前提でないと高速化はほとんど見込めない(微妙)です。

それから半透明合成は相手方に自分方透明度の逆数を掛けるのでRGB各要素はオーバーフローしません。
Photoshopなど画像編集ソフトではレイヤー毎に合成方法を指定できます。
加算や減算などのオーバーフローやアンダーフローが発生する合成方法を指定したレイヤーがある場合、上から合成した場合と下から合成した場合では結果が異なってしまいます。

たろ

Re:[画像処理]レイヤー合成の高速化について

#25

投稿記事 by たろ » 15年前

ぬるぽんさん

わかりやすい解説ありがとうございました。理解できました。
なんというか・・言われてみると本当にその通り、気付かなかったのが悔しいです・・。

頂いたコードイメージを実装して試してみたら、期待通り動きました!
ちょっと整理して後ほど報告します。

たろ

Re:[画像処理]レイヤー合成の高速化について

#26

投稿記事 by たろ » 15年前

ISLeさん

> このページは、ノベル系ゲームのキャラクター画像を自前でアンチエリアス処理する方法について書かれています。

そうだったんですか。「アンチェリ」って意味不明でしたけど、アンチエイリアスのことだったのか・・。
でもすごいですね、ノベル系ゲームとか、どこでわかるんだろう・・。

> そういう前提でないと高速化はほとんど見込めない(微妙)です。

なるほど・・。ペイント系ソフトのレイヤーは確かに不透明の方が少なそうです。
うーむそこまで考えが及びませんでした。ああがんばったのに・・無念です・・。

> 加算や減算などのオーバーフローやアンダーフローが発生する合成方法を指定したレイヤーがある場合、
> 上から合成した場合と下から合成した場合では結果が異なってしまいます。

いろんな合成方式の実装も頭にはありましたが、まだ調べたりしていませんでした。
とりあえず一番基本のやつをやってみようという感じで・・。
やはりレイヤーは下から上に処理する方がよいという事でしょうか?
それとも、いろんな合成方式の実装をある程度調べてからの方がいいという感じでしょうか・・。

たろ

Re:[画像処理]レイヤー合成の高速化について

#27

投稿記事 by たろ » 15年前

教えて頂いた案を実装してみまして、2~3割速くなりました。
ありがとうございました。プログラムソースを添付します。

5000x5000ピクセル画像3枚を合成して確認した結果です。

(0) No:61413 の実装 → 2.88秒くらい

(1) 共用体を使ってピクセル参照の高速化 → 2.17秒くらい
  元々の実装にこれ入れただけで、2割ちょっとも速くなりました。
  共用体の効能をはじめて実感しました。すごい・・。

(2) ぬるぽんさんのコードイメージ → 2.05秒くらい
  ほぼそのまま実装してみました。5%くらい速くなりました。
  さらにコードの量がかなり減ってすっきりしました。

ソース上、#define 定義で切り替えるようになっています。

(1)と(2)の速度差がそれほどでもなかったのが少し意外でした。
コードを見ると、だいぶすっきりしているので、速そうな感じに見えたのですが・・。

ぬるぽんさんもおっしゃってますが、「dst へ書き込む回数が増えてしまう」というのが
けっこう響いているのでしょうか。dstはヒープ領域なので「ヒープを更新する回数が多い」
というのは確かに性能に影響しそうな気がします。

スタック変数に溜めておいて、まとめてヒープに書き出すとかすれば速くなるのかな・・。 画像

ISLe

Re:[画像処理]レイヤー合成の高速化について

#28

投稿記事 by ISLe » 15年前

No:61401(No:61413)のLayerBlendUpdate関数を整理してみました。
void LayerBlendUpdate( void )
{
    UINT    WxH, pos;
    union PIXEL32 {
        UINT32 pixel;
        struct {
            UINT8 B,G,R,A;
        };
    };
    PLAYER      layer;

    if( !LayerBlend.BIH ) return;

    WxH = LayerBlend.BIH->biWidth * LayerBlend.BIH->biHeight;   // 全ピクセル数

    { // 合成先のクリア
        // 合成先のA要素はブレンドに使用しないのでエネルギーのバッファとして使う
        union PIXEL32 *pPix = (union PIXEL32 *)LayerBlend.Bitmap;
        for( pos=0; pos<WxH; pos++, ++pPix ) {
            // 合成透明度エネルギー最大値で初期化(A要素)
            pPix->pixel = 0xff000000;
        }
    }

    for( layer=LayerTop; layer; layer=layer->next ) // レイヤー上から順に
    {
        union PIXEL32 *pSrcPix = (union PIXEL32 *)layer->Bitmap;
        union PIXEL32 *pDstPix = (union PIXEL32 *)LayerBlend.Bitmap;

        if( !layer->Visible ) continue;                         // レイヤー非表示スキップ
        //if( !layer->Alpha || !pSrcPix->A ) continue;            // 透明スキップ

        for( pos=0; pos<WxH; pos++, ++pSrcPix, ++pDstPix ) // 先頭から1ピクセルずつ
        {
            UINT32 alpha  = pSrcPix->A;
            UINT32 energy = pDstPix->A;
            UINT32 srcPixel = pSrcPix->pixel;

            //if (!energy) continue; // エネルギーゼロ

            //alpha = (alpha * (layer->Alpha + 1)) >> 8;
            //alpha = (alpha * (energy + 1)) >> 8; // エネルギー掛ける
            alpha = (alpha * (layer->Alpha + 1) * (energy + 1)) >> 16;
            energy -= alpha; // エネルギー減少

            pDstPix->pixel +=
                ((((srcPixel & 0x00ff00ff) * (alpha + 1)) >>8) & 0x00ff00ff)|
                ((((srcPixel & 0x0000ff00) * (alpha + 1)) >>8) & 0x0000ff00);
            pDstPix->A = energy;
        }
    }
    { // 背景処理(最下位のレイヤーとして扱えば↑のループにまとめられる)
        union PIXEL32 *pDstPix = (union PIXEL32 *)LayerBlend.Bitmap;

        for( pos=0; pos<WxH; pos++, ++pDstPix ) // 先頭から1ピクセルずつ
        {
            UINT32 alpha  = 255;
            UINT32 energy = pDstPix->A;
            UINT32 srcPixel = 0xffffffff; // 白背景

            //if (!energy) continue; // エネルギーゼロ

            alpha = (alpha * (energy + 1)) >> 8; // エネルギー掛ける

            pDstPix->pixel +=
                ((((srcPixel & 0x00ff00ff) * (alpha + 1)) >>8) & 0x00ff00ff)|
                ((((srcPixel & 0x0000ff00) * (alpha + 1)) >>8) & 0x0000ff00);
            pDstPix->A = 255; // 不透明
        }
    }
}
#完全に流れとズレてるなぁ

ISLe

Re:[画像処理]レイヤー合成の高速化について

#29

投稿記事 by ISLe » 15年前

No:61464のぬるぽんさんメソッドをさらに高速化してみました。
void LayerBlendUpdate( void )
{
    UINT    WxH;
    PLAYER  layer;
    union PIXEL32 {
        UINT32 pixel;
        struct {
            UINT8 B,G,R,A;
        };
    };

    if( !LayerBlend.BIH ) return;

    WxH = LayerBlend.BIH->biWidth * LayerBlend.BIH->biHeight;           // 全ピクセル数

    memset( LayerBlend.Bitmap, 0xff, LayerBlend.BIH->biSizeImage );     // 合成ビットマップ背景色(白)で埋める

    for( layer=LayerBottom; layer; layer=layer->up )                    // レイヤー下から上に
    {
        union PIXEL32 *pSrcPix = (union PIXEL32 *)layer->Bitmap;
        union PIXEL32 *pDstPix = (union PIXEL32 *)LayerBlend.Bitmap;
        UINT n;

        //if( !layer->Visible || layer->Alpha==0 ) continue;        // 不可視・透明スキップ
        if( !layer->Visible ) continue;     // 不可視スキップ

        for( n=0; n<WxH; n++, ++pSrcPix, ++pDstPix )                    // 先頭から1ピクセルずつ
        {
            UINT32  a, ra;
            UINT32  srcPixel;
            UINT32  dstPixel;
            union PIXEL32 tmpPix;

            dstPixel = pDstPix->pixel;
            srcPixel = pSrcPix->pixel;

            a = (pSrcPix->A * (layer->Alpha + 1)) >>8;
            ra = 255 - a;

            tmpPix.pixel =
                ((((dstPixel & 0x00ff00ff) * (ra + 1)) >>8) & 0x00ff00ff)|
                ((((dstPixel & 0x0000ff00) * (ra + 1)) >>8) & 0x0000ff00);
            tmpPix.pixel +=
                ((((srcPixel & 0x00ff00ff) * (a + 1)) >>8) & 0x00ff00ff)|
                ((((srcPixel & 0x0000ff00) * (a + 1)) >>8) & 0x0000ff00);
            tmpPix.A = 255;
            pDstPix->pixel = tmpPix.pixel; // tmpPix使わなくても変わらないみたい?
        }
    }
}
透明度の計算を修正しました。
前に「レイヤー間の」と言ってたときに想定してたのがこのやり方なんですよね。
--追記
この方法だと半透明合成以外の合成方法に対応するのも楽です。 画像

ISLe

Re:[画像処理]レイヤー合成の高速化について

#30

投稿記事 by ISLe » 15年前

No:61481のコードの
ra = 255 - a;

ra = a ^ 0xff;
にするとちょっぴり速くなりました。

ISLe

Re:[画像処理]レイヤー合成の高速化について

#31

投稿記事 by ISLe » 15年前

#連投すみません。

GIMP2で5000x5000ピクセル画像3枚をそれぞれレイヤーに読み込んでレイヤーの透明度のスライドバーを操作すると少し遅れてある程度のブロックごとに画面が更新されていく様子が観察されました。
ブラシを走らせるとその部分は遅れることなく更新されていきます。

SAIは使ったことがないので分からないのですが、全部計算し直して一瞬でフル合成は無理ですよね。

たろ

Re:[画像処理]レイヤー合成の高速化について

#32

投稿記事 by たろ » 15年前

ISLeさん実験と最適化までありがとうございます。勉強になります。

まず驚いたのですが、この struct の使い方がっ・・
union PIXEL32 {
UINT32 pixel;
struct {
UINT8 B,G,R,A;
};
};
これ、構造体名も変数名もないんですけど、オッケーなんですね。
知りませんでした。使い方はスマートになるので嬉しいです。

(1) No:61480 のコード
  これ、すごく速いです。現在最速です。すごい・・。
  エネルギー分配方式を使いつつ、レイヤーループの中でピクセルループをまわす、
  こんなやり方があったとは・・。もはや私のコードは跡形もないですね(笑)
  見た目もだいぶすっきりして・・うーむ、私のコードは境界値(0,255)のif判定が多すぎるのかな…。

(2) No:61481+No:61483 のコード
  No:61464のぬるぽんさん案(私実装)と比べて、BCC551では1割ちょっと速くなりました(^^)
  ただ、なぜかVC2008EEのReleaseビルドでは逆に、1割弱ですが遅くなってしまいました。
  今まで常にBCCよりVCの方が速かったのですが、こんなこともあるんですね・・。

(3) 下記コード
  (2) を、なんとかVCでも速くなるように改変してみました。これでなんとか・・。
  でも、まだ(1)の方がわずかに速いです。
void LayerBlendUpdate( void )
{
    PPIXEL32    pDstEnd;
    PLAYER      layer;

    if( !LayerBlend.BIH ) return;

    memset( LayerBlend.Bitmap, 0xff, LayerBlend.BIH->biSizeImage );         // 合成ビットマップ背景色(白)で埋める

    pDstEnd = (PPIXEL32)(LayerBlend.Bitmap + LayerBlend.BIH->biSizeImage);  // 合成ビットマップ最終ピクセル+1

    for( layer=LayerBottom; layer; layer=layer->up )                        // レイヤー下から上に
    {
        PPIXEL32 pSrcPix = (PPIXEL32)layer->Bitmap;
        PPIXEL32 pDstPix = (PPIXEL32)LayerBlend.Bitmap;

        if( !layer->Visible ) continue;                             // レイヤー不可視スキップ

        for( ; pDstPix < pDstEnd; ++pDstPix, ++pSrcPix )            // 先頭から1ピクセルずつ
        {
            UINT32  a, ra;

            a = (pSrcPix->A * (layer->Alpha + 1)) >>8;
            ra = a ^ 0xff;

            pDstPix->pixel =
            (
                ((((pDstPix->pixel & 0x00ff00ff) * (ra + 1)) >>8) & 0x00ff00ff)|
                ((((pDstPix->pixel & 0x0000ff00) * (ra + 1)) >>8) & 0x0000ff00)
            )
            + (
                ((((pSrcPix->pixel & 0x00ff00ff) * (a + 1)) >>8) & 0x00ff00ff)|
                ((((pSrcPix->pixel & 0x0000ff00) * (a + 1)) >>8) & 0x0000ff00)
            )
            | 0xff000000;
        }
    }
}
あの、ちなみにですが、ISLeさんに No:61325 で紹介して頂いた「テーブルを使う方式」は、
まだ登場していないですよね・・?これからちょっと考えてみようかと思ってます。

ぬるぽんさん、ISLeさん、ありがとうございました!m(_ _)m

たろ

Re:[画像処理]レイヤー合成の高速化について

#33

投稿記事 by たろ » 15年前

ISLeさん

> GIMP2で5000x5000ピクセル画像3枚をそれぞれレイヤーに読み込んでレイヤーの透明度のスライドバーを操作すると少し遅れてある程度のブロックごとに画面が更新されていく様子が観察されました。

はい、GIMP2(Windows版のGIMP2.6.6)は、私も同様の操作を試していました。
画像が上から順に処理されていく様子がわかりますね。なんかテレビの走査線が太くなったような雰囲気?

私のPCでは、スライドバーを動かし続けると、上の方のほんのちょっとだけしか処理されずに次の透明度の
処理に移ってしまい、スライドバーを止めてちょっと待たないと合成結果が見れない感じになります。
うーんこれは遅いなぁ・・などと思っていました。

> SAIは使ったことがないので分からないのですが、全部計算し直して一瞬でフル合成は無理ですよね。

すみません、「一瞬」は言い過ぎな気がしてきました・・。
SAIの挙動を再確認したところ、さすがに3000x3000くらいになるとモタつく感じがありました。

ただ、3000x3000でレイヤー3枚くらいなら、スライドバーの動きに合わせて、かろうじてほぼリアルタイムに
画像全体の処理結果が表示されて、フェードイン・フェードアウト動画を見ているような雰囲気になります。
動画の1秒あたりのコマ数でいうと、どうだろう・・秒10コマ弱くらいかな・・?
2000x2000ならモタつきはほとんど感じません。

GIMPだと、2000x2000でも、スライドバーを動かし続けたら、画像の上の方ちょっとだけしか処理されず、
次の処理になってしまいます。レイヤー枚数が増えることによる処理速度の低下も顕著な気がします・・。

と確認していたら・・よく考えると、GIMPは処理中の結果を逐一表示しているんですね・・。
これはけっこうな負荷になりそうな気もしてきた。合成処理が遅いとは言えないのかな・・。

ちなみにSAIはメモリ使用量はやけに多くて、私のPC(メモリ2GB)だと、5000x5000画像はレイヤー2枚までが
限界です・・。このメモリ使用量の多さに高速動作の秘密があるのか?などと勝手に推測してみたりしてます(^^;
画像

ISLe

Re:[画像処理]レイヤー合成の高速化について

#34

投稿記事 by ISLe » 15年前

> (1) No:61480 のコード
>   これ、すごく速いです。現在最速です。すごい・・。

アンチエイリアス用途前提なのでいろいろ端折っています。
実際にゲームに組み込む場合は運用レベルで工夫してもっと速くする余地があります。
半透明合成しか対応できないので画像編集ソフトでは使えないですね。

> (2) No:61481+No:61483 のコード
>   No:61464のぬるぽんさん案(私実装)と比べて、BCC551では1割ちょっと速くなりました(^^)
>   ただ、なぜかVC2008EEのReleaseビルドでは逆に、1割弱ですが遅くなってしまいました。
>   今まで常にBCCよりVCの方が速かったのですが、こんなこともあるんですね・・。
> (3) 下記コード
>   (2) を、なんとかVCでも速くなるように改変してみました。これでなんとか・・。
>   でも、まだ(1)の方がわずかに速いです。

最適化オプションを速度優先にすると変わりますかね?
こちらではオプションの影響はあまり無い感じですけど繰り返し動かしてみるとかなりバラツキがありました。
#昨晩は0xff000000をORするよりAメンバに255を代入する方が速かったのに。
このソースでは最適化はコンパイラ次第なのであんまり気にしても仕方ないですね。

> あの、ちなみにですが、ISLeさんに No:61325 で紹介して頂いた「テーブルを使う方式」は、
> まだ登場していないですよね・・?これからちょっと考えてみようかと思ってます。

半透明合成はオーバーフローしないのでテーブルを使ってないです。
加算合成や減算合成とか実装するときに「テーブルを使う方式」が役に立ちます。
ただ処理速度はもっと遅くなってしまいます。


3000x3000レイヤー3枚でそこそこついてくるというのはかなり速いですね。
レイヤー毎の透明度を事前計算したバッファを作っていたりするのかもしれません。
SAIのレイヤーの合成方法はPhotoshopやGIMP2のとはいろいろ違うみたいで、速度低下するものを避けてたりするのかも。

> と確認していたら・・よく考えると、GIMPは処理中の結果を逐一表示しているんですね・・。
> これはけっこうな負荷になりそうな気もしてきた。合成処理が遅いとは言えないのかな・・。

描画したところは即座に処理してそれ以外は操作してないときに進めるようにしているのですね。
この辺はどんなタイミングで合成処理を呼び出すかという話になります。
合成処理自体は同じコードが使えるので大丈夫ですよ。
ピクセルを差すポインタの進め方をちょっと工夫すれば一部の矩形だけを対象にできます。

たろ

Re:[画像処理]レイヤー合成の高速化について

#35

投稿記事 by たろ » 15年前

ISLeさん

> 実際にゲームに組み込む場合は運用レベルで工夫してもっと速くする余地があります。
> 半透明合成しか対応できないので画像編集ソフトでは使えないですね。

ほぇぇ・・ゲームソフトの高速化の追求はすごそうですね・・。
でも速いからもったいない気がしてます。しばらく残しておこう。

> 最適化オプションを速度優先にすると変わりますかね?

「実行速度を優先(/Ot)」をつけてみましたが、特に影響は感じられませんでした。
1.3秒と1.4秒の違いくらいです。アセンブラが読めない事もありコンパイラ任せにしてます・・。

> 半透明合成はオーバーフローしないのでテーブルを使ってないです。
> 加算合成や減算合成とか実装するときに「テーブルを使う方式」が役に立ちます。

あら、そうだったんですか。
やっぱりイマイチここら辺の話がわかってないようです・・。
他の合成方式を実装しようとした時に、今回の話の有り難さが実感できるのかなぁ。
覚えておきたいと思います。

> レイヤー毎の透明度を事前計算したバッファを作っていたりするのかもしれません。
> SAIのレイヤーの合成方法はPhotoshopやGIMP2のとはいろいろ違うみたいで、速度低下するものを避けてたりするのかも。

なるほど。この次は、そういったバッファやテーブルみたいな「メモリ消費量が増えるけど速い」
という工夫になるのかな・・。どっかの掲示板で、MMXやアセンブラもかなり使っているというのも
見たことがあります。それとの合わせ技かな。うーんちょっともうお腹いっぱいな気分・・( ̄∇ ̄;)

> ピクセルを差すポインタの進め方をちょっと工夫すれば一部の矩形だけを対象にできます。

はい、これは必要だなーと思ってました。ブラシで塗った部分だけ更新するためにも・・。
ただ、全体合成を分割処理して画面を更新しながら・・というのは、実装がゴチャゴチャしそうな事と
全体の合成にかかる時間が伸びそうで、できれば避けたいなぁと思ってしまいます。。

いろいろ教えて頂き本当にありがとうございました!

ISLe

Re:[画像処理]レイヤー合成の高速化について

#36

投稿記事 by ISLe » 15年前

No:61499のコードはまだ少し速くできました。
pDstPix->pixel =
            ((
                (((pDstPix->pixel & 0x00ff00ff) * (ra + 1)) & 0xff00ff00)|
                (((pDstPix->pixel & 0x0000ff00) * (ra + 1)) & 0x00ff0000)
            ) + (
                (((pSrcPix->pixel & 0x00ff00ff) * (a + 1)) & 0xff00ff00)|
                (((pSrcPix->pixel & 0x0000ff00) * (a + 1)) & 0x00ff0000)
            ) >> 8)
            | 0xff000000;
右シフトをまとめることができます。

たろ

Re:[画像処理]レイヤー合成の高速化について

#37

投稿記事 by たろ » 15年前

ISLeさん

おおっ、これを入れたら、No:61480 より速くなりました!
BCCでもVC2008でも一番速いです。ついにトップ交代です(笑)

ちなみに、VC2008で警告「C4554: '>>' : 演算子の優先順位に問題があります。かっこを使用して・・」
が出たので、カッコを1組増やしました。
pDstPix->pixel =
            (((
                (((pDstPix->pixel & 0x00ff00ff) * (ra + 1)) & 0xff00ff00)|
                (((pDstPix->pixel & 0x0000ff00) * (ra + 1)) & 0x00ff0000)
            ) + (
                (((pSrcPix->pixel & 0x00ff00ff) * (a + 1)) & 0xff00ff00)|
                (((pSrcPix->pixel & 0x0000ff00) * (a + 1)) & 0x00ff0000)
            )) >> 8)
            | 0xff000000;
改めて、5000x5000の3枚で、最初のNo:61401からどれくらい速くなったか確認してみました。

VC2008EEのReleaseビルド(/Ot追加)で、1.51秒台 → 1.03秒台
BCC5.5.1最適化オプション付き(※1)で、2.08秒前後 → 1.18秒台

VCで約3割、BCCで倍近く、の向上かな。コードはだいぶ短くすっきり。
ここまでのプログラム全体を添付しておきます。

ふう・・なんだかやりきった感があります(笑)
ありがとうございました!

※1
bcc32 -W -5 -O -O2 -Oc -Oi -OS -Ov LayerBlend.c

ISLe

Re:[画像処理]レイヤー合成の高速化について

#38

投稿記事 by ISLe » 15年前

No:61480も最後の右シフトをまとめたら速くなりますよ?

ウインドウズのノベル系ゲームでこういったチューニングが盛んだったのはひと昔くらい前ですかね。
あらためて調べてみると最近でもけっこう需要があるみたいで不思議な感じがします。
わたしにとってもまとめができて良い機会でした。

インラインアセンブラでMMXをゴリゴリ叩いてもアプリを64ビット対応するときには使えなくなります。
これからおぼえるならマルチコアやGPGPUを活用する方向が良いかもしれません。

開発頑張ってください。

たろ

Re:[画像処理]レイヤー合成の高速化について

#39

投稿記事 by たろ » 15年前

> No:61480も最後の右シフトをまとめたら速くなりますよ?

ひえ、そうですね・・見直してませんでした・・。やってみたら速くなりました。
ただ私のPCだと、まだ僅差で No:61499+No:61547 の方が速いような・・?まあでも微妙な差です。

> ウインドウズのノベル系ゲームでこういったチューニングが盛んだったのはひと昔くらい前ですかね。
> あらためて調べてみると最近でもけっこう需要があるみたいで不思議な感じがします。
> わたしにとってもまとめができて良い機会でした。

薄々そうかもなーとは思ってましたがやはり・・。10年前とかでしょうか・・。
検索で見つかるWebサイトも、昔のものが多くて、最近のものは少ないです。

需要は私にはめっちゃあります!(笑)今回のコメント履歴と出来上がったコードはかなりの貴重品です。
本当にいい勉強になりました。ありがとうございました。m(_ _)m

> インラインアセンブラでMMXをゴリゴリ叩いてもアプリを64ビット対応するときには使えなくなります。

あら・・そうなんですね・・。

> これからおぼえるならマルチコアやGPGPUを活用する方向が良いかもしれません。

GPGPU系は確かに、なんだか使わないとヤバいのか的な気配を感じてはいました。興味ありつつ、
でもよく考えたら自分のPCのグラフィックはチップセット内蔵だし、DirectX使っても特に効果なし?
と思って手が出ず・・でもそのうちやってみたいです。いいグラフィックがついた新しいPCがほしい(笑)

マルチコア活用って単純にはマルチスレッド化ですよね・・?一応、遅い関数をスレッドで並列化して、
2コアだと倍近く速くなるっぽいな~とかやってみたりしてました。マルチスレッドなら、今回の高速化
コードとは共存できて、相乗効果になって良さそうな感触です。

> 開発頑張ってください。

ありがとうございます!!!
完成まで辿り着くかどうかわかりませんが、いけるとこまでいきます。

たろ

Re:[画像処理]レイヤー合成の高速化について

#40

投稿記事 by たろ » 15年前

一段落した気がするので、解決といたします。
コメント頂いた皆様、ありがとうございました!m(_ _)m

閉鎖

“C言語何でも質問掲示板” へ戻る