先日、新しいプログラムを作りました。
作っている最中に、おそらく、誰もそういう事をする人は居ないと考えている様な方法だろうなとか思いました。
世の中には、とある事が前提で作られて居る物が多いです。
でも、いつかはその前提を崩す人が現れ、それに対する対策を講じる。
これは、いつまで経ってもいたちごっこですね?
まぁ、それはそれとして、その際に、Imageから点の情報を取得するGetPixelを大量に発行するのですが、
これがまぁ、とても遅い!
まぁ、遅いなら遅いで早くすれば良い。
って事で、コア数分のThreadに分割して、CPUをフル活用してあげれば良い。
大した事では無い。
しかし、これをした結果どうだったのか・・・・
速度は変わらず、CPU使用率も全然上がらない。
そもそも、データ量も大した事が無いのに、この程度の処理がthread分割するほどの処理なのか?と言う事もあり
それに、thread分割してからなんだけど、HDCのエラーが出ることもあり
どうやら、Imageの処理にはDCが介在し、ダイレクトに操作ができないんじゃ無いかという考えに至りました。
ようするに、Imageを宣言して、LoadしたものはすでにVRAM等に格納され、GetPixelするごとにGPUへアクセスをしたり、DCからメモリへマップしたりとか、そう言った作業が発生している為に時間が掛かるんじゃ無いかと?
であれば、BMPをメインメモリに落とせば良い。
c#的にはbyte配列になるのか? 昔のようにBinaryでイメージを読まないといけないのか?
とか、色々と考えたのですが・・・・
LockBitsとかいう便利な命令が有る事が分かりました。
で、これ、実行すると、Image(何処に格納されているか分からない)からメインメモリへマップする為、
その際に変換が入るようです。
楽に処理する為には32bit処理ですよね?
LockBitsの変換設定をFormat32bppArgbにすることで、int(Int32)でアクセスすれば、そのままピクセル情報が取得出来る。
そしたら、どうなったかというと、
これまで、データの読み込みが30秒以上掛かっていたのが
1秒未満になりました(笑
余りにも短すぎるので、CPU使用率は50%迄しか上がらないのですが、25%以上になるのでthreadも問題なく稼働しているようです。
それにしても、マルチスレッドの処理は、
スレッドデータの準備→スレッドの起動→スレッドの終了待ち→処理の完了
という手順を取る為、もうちょっと容量のあるデータじゃないと、オーバーヘッドの方が高い可能性があるんだけど、
まぁ、それはそれで良いです。
そんな感じで、プログラムは、無事完成し、目的は達せられたのです。
因みに、LockBitsには、ロックする範囲を指定する場所がありますが、指定した場所以外もロックされます。
イメージデータのごく一部だけを取り出してくれるとか言う事はありません。
Scan0のプロパティの位置が、指定したアドレスになるだけです。
また、次のピクセルとかいう方法はありませんので、ポインタを自分で+4しなければなりません。
1ピクセルを32ビットに変換していない場合は、24ビットなり、8ビットなりのデータとして取り扱わなければなりませんし、
また、次の行はStrideを参照して、ポインタを増減させる必要があります。 width×ピクセルサイズとか利用すると、違う場合がありますのでご注意ください。
とくにBitmapは下の行から、上の行に向けてデータが格納されている事も多いので、この値は必ず参照する必要があります。
ロックされた領域は
アンマネージ メモリ
なので
System.Runtime.InteropServices.Marshal
を使って読み書きする必要があります。
また、ロックする領域は、操作ミスを起こさない為にも、リードオンリーにすることをお勧めします。
(書き込み時は、書込オンリーをお勧め)
なので、読み込みは、
System.Runtime.InteropServices.Marshal.ReadInt32((Intptr)(scan0 + x * 4 + Stride * y));
という事になります。
帰ってくるのはARGBですのでFromArgbを利用する事で、Colorに変換する事が出来ます。
因みに、このプログラムだけでは、何も出来ないので、公開はしません。
対応するデータと、対応する変換マトリクスが揃っていないと意味が無く、そっちの方が公開出来るデータでは無いからです。
何はともあれ、Image形式のデータは一度ロックしてから使いましょう☆
|