なんだか雲行きの怪しい雑記帖

ぐだぐだ日記とメモと,あと不定期更新

HSPのPNG読み込みからgmode7で使える形式に

タイトルの通り、「HSPのPNG読み込み対応したけど、gmode7で使える形式になってない…から変換しよう」という話。
相変わらずPNG読み出しはライブラリがあるので、新規性を見てHSP内で完結する感じでやります。
 (実行自体はHSP単体でいけるであろうWinAPIやCOM等もライブラリとします。
  今回の趣旨は「ピクセル輝度値が分かったらα値とか計算するぜ!」なので)

結論から言うと式自体は簡単だったな、と、適当に。
精度についてはどうなんだろうな、最低限分からない程度ならどうでもいい派なのでそっちも適当。

この記事は今回やってみた手法? の中身とかアルゴリズムについての説明がメインとなります。


ちなみにネタ自体は既出で、HSPコンテスト2012でPNG2Gmode7というモジュールがでてます。
あちらは半透明部分が黒ずんでしまうとのことでしたが、まぁそれも考慮しつつやっていこう、という感じです。


先に結果だけドンするとこんな感じ
alphahsp_comp.jpg

元画像にちょっと嫌なデータを与えてみました。
色自体は右上から左下へ、色相を変えながらのグラデーション。
アルファ値は上から下に向かって小さくなるグラデーションになってます。

上図で確認できる通り、PNG2Gmode7だと下に行くほど色が暗くなっていってます。
で、今回やってみた方はPNG2Gmode7だと黒ずんでしまっているα値が小さい領域でも、なかなか元の色を復元できていることが確認できます。
 (サンプルの画像を処理させたところ、α値が0でなければなかなか怖い精度で復元してきます)

というか今回やってみたのは、このα値が小さいところで色が黒ずんでしまっているのを元にもどす処理をかませているというだけなんですが…
簡単に言うと「α値以外に元の色も計算してる!」ので、描画に使っても半透明な部分が黒ずまない的な。


追記@2017/10/31
下記のブログ様にて本記事の内容が高速化&モジュール化されておりました!
非常に内容纏まっていると思いましたのでリンク貼っておきます。
HSP | 透過 PNG で gmode 7 する (その2)

ということで、本記事の以降の内容はモジュール化されてない&処理内容についての簡易な言及のみとなりますので、「それでも見たい!」という奇特な方などはどうぞ~。


全体の方針

先に述べましたが、最も手っ取り早いのはPNGファイルのフォーマットを解析するコードを書くことなのですが、そうすると既にいくつかあるDLLを使った方が速度・精度・安心感の点で優ります

なので、今回は
 「HSP標準の機能でPNG画像を読み出し、gmode7で利用できる形式でバッファに保存しよう」
という方針でやっていきます

実はこうするとHSPが読み込みに対応しているα値つき画像形式なら全てに適用できるので、ちょっとおいしかったりしますしね


HSPの画像読み込み(予備知識編)

そもそもHSPは3.31以前まではα付きの画像形式には対応していませんでした。
ではHSP3.31でのα付き画像の読み込みはどうなっているんでしょう?

大体察しがついてしまいますが、α付き画像を読み込んだ後、読み込み先のバッファにアルファブレンディングしているだけです

具体的には、バッファにα付き画像を読み込む際は、各ピクセルのチャンネル毎に次のような計算をします。



Rは結果の輝度値、Srcはα付き画像の輝度値、Dstは読み込む先のバッファの輝度値、αはα値。
 (それぞれチャンネルのビット深度を使って正規化して考えてます。
  つまり、R、Src、Dst、αは全て0以上1以下。
  実際問題ビット深度は最大値が255なので、それぞれ255をかければ実際の値になるかなぁ。)

まぁ物凄いよく見る数式だし知っておられる方も多いと思うんですが、後でここから変形してくので一応式示しておきました。


コードの確認(飛ばしても可、興味がある方のみ推奨)

一応、実際にHSPの「picload」のコードを読んでみましょう。
このためだけにOpenHSPからソースコードを落としてきました、リビジョン560。

picload命令のコマンドディスパッチはすっ飛ばして、HSP3.31から対応したpicloadのPNG画像読み込み部分。


// hspwnd_win,cppのHspWnd::Picloadメソッド
#ifdef USE_STBIMAGE
stbmode = 0;
getpath(fname,fext,16+2); // 拡張子を小文字で取り出す

if (!strcmp(fext,".png")) stbmode++; // ".png"の時
if (!strcmp(fext,".psd")) stbmode++; // ".psd"の時
if (!strcmp(fext,".tga")) stbmode++; // ".tga"の時

if ( stbmode ) { // stb_imageを使用して読み込む
int components;
unsigned char *sp_image;
sp_image = stbi_load_from_memory( (unsigned char *)pBuf, size, &psx, &psy, &components, 4 );
if ( sp_image == NULL ) return 3;

if (( mode == 0 )||( mode == 2 )) {
int palsw,type;
type = bm->type; palsw = bm->palmode;
MakeBmscr( id, type, -1, -1, psx, psy, psx, psy, palsw );
bm = GetBmscr( id );
if ( mode == 2 ) bm->Cls( 4 ); // 黒色でクリアしておく
}

x = bm->cx; y = bm->cy;
bm->RenderAlphaBitmap( psx, psy, components, sp_image );
bm->Send( x, y, psx, psy );

stbi_image_free(sp_image);
GlobalUnlock( h );
GlobalFree(h);
return 0;
}
#endif


HSP3.31で読み込み対応した画像形式の読み込みに関しては、パブリックドメインの「stblib」を使用しているっぽいです。
まぁ重要なのRenderAlphaBitmapだよね、というわけでRenderAlphaBitmap関数へ。


// hspwnd_win.cpp内Bmscr::RenderAlphaBitmapメソッド
~~前略
// 32bit copy
short ha;
for(b=0;b<psy;b++) {
for(a=0;a<psx;a++) {
a1=*p2++;a2=*p2++;a3=*p2++;a4=*p2++;a4r=255-a4;
if ( a4r == 0 ) {
*p++=a3;
*p++=a2;
*p++=a1;
} else {
ha=((((short)*p)* a4r)>>8) + (((short)a3)*a4>>8);
*p++=(BYTE)ha;
ha=((((short)*p)* a4r)>>8) + (((short)a2)*a4>>8);
*p++=(BYTE)ha;
ha=((((short)*p)* a4r)>>8) + (((short)a1)*a4>>8);
*p++=(BYTE)ha;
}
}
p-=p_ofs;
p2+=p2_ofs;
}
~~省略


はい、高速化の為にアレしてますが、見事にアルファブレンディングしてますね。


ha=((((short)*p)* a4r)>>8) + (((short)a3)*a4>>8);
*p++=(BYTE)ha;


の部分で、右に8ビットシフトして「256で割った」のと同じ効果だしてる。
思ったけどα値の最大値(チャンネル毎のビット深度最大値)って255だから、256で割ると「0<=α<1」になるような…まぁどうでもいいか。


ピクセル毎のアルファ値の計算

α値の推定という題にしようかと思ったけど、よく考えたら計算でだせることに気付く。

もう一度アルファブレンディングの計算式を見てみましょう。



今私達はHSPの画像読み込みにより、Rが分かる状態です。
また、picloadの第二引数を1にすることにより、Dstは予めこちら側で設定することができます。
欲しいのはSrcとαです。
変数二個ですね、条件式二個あれば解けるよね。

と、いうことで、Dstを変えて二回画像を読み込んで、なんか計算するとSrcとαが求まりそうな予感がします。
Srcを出す前にα出す方が簡単なので、先にα値を計算してみます。

用いるDstについては計算も楽そうだし精度的にもよさそうなので、黒(0)と白(1)を使用します。
そうすると上の計算式にDstを0、1それぞれ代入して




RblackはDstすなわち背景を黒にして読み込んだ時のピクセル輝度値、Rwhiteは白にして読み込んだ時。
引いてみよう!



もう見えてますね




ピクセル毎の原色の計算

先ほどの章でα値が計算できたので、普通にもっかい式解くだけです




でしたね。
足して式変形。



これで原色が元まりましたね。


さぁあとはプログラムを書くだけだ

100行程度のプログラムを貼り付ける私


/*
めんどいのでファイルチェックその他
諸々はすっとばしてますん
*/

// よみこみぃ
dialog "png", 16, "png file!"
if ( stat == 0 ) : end
file = refstr

// バッファ2は黒でロード
screen 2
picload file, 2
img_height = ginfo_winy : img_width = ginfo_winx
// バッファ3は白でロード
screen 3, img_width, img_height
color 255, 255, 255 : boxf
pos 0, 0 : picload file, 1
// 出力対象ばっふぁ4
screen 4, img_width*2, img_height

// しょりぃ
// 処理用ばっふぁ、いちいちpgetが面倒なので
dim img_bgblack, img_height, img_width, 3
dim img_bgwhite, img_height, img_width, 3
ddim img_alpha, img_height, img_width
dim img_origcolor, img_height, img_width, 3

// バッファから色コピー
gsel 2
repeat img_height
y = cnt
repeat img_width
x = cnt

pget x, y
img_bgblack(y,x,0) = ginfo_r
img_bgblack(y,x,1) = ginfo_g
img_bgblack(y,x,2) = ginfo_b
loop
loop

gsel 3
repeat img_height
y = cnt
repeat img_width
x = cnt

pget x, y
img_bgwhite(y,x,0) = ginfo_r
img_bgwhite(y,x,1) = ginfo_g
img_bgwhite(y,x,2) = ginfo_b
loop
loop

// alpha値の計算だッ
repeat img_height
y = cnt
repeat img_width
x = cnt

upper = img_bgwhite(y,x,0)
lower = img_bgblack(y,x,0)
delta = abs( upper - lower )
ralpha = double( delta ) / 255.0
img_alpha(y,x) = 1.0 - ralpha// 0 - 1.0
loop
loop

// もとの色を計算する
repeat img_height
y = cnt
repeat img_width
x = cnt
repeat 3
ci = cnt

upper = img_bgwhite(y,x,ci)
lower = img_bgblack(y,x,ci)
sum = upper + lower
nsum = double( sum ) / 255.0
alpha = img_alpha(y,x)
ralpha = 1.0 - alpha

if ( alpha < 0.0001 ) {
img_origcolor(y,x,ci) = 0
} else {
value = ( nsum - ralpha ) / ( alpha * 2 )
pvalue = limit( 255.0 * value, 0, 255 )
img_origcolor(y,x,ci) = pvalue
}
loop
loop
loop

// しゅつりょく
gsel 4
// アルファ値
repeat img_height
y = cnt
repeat img_width
x = cnt

palpha = limit( 255.0 * img_alpha(y,x), 0, 255 )
color palpha, palpha, palpha
pset x+img_width, y
loop
loop
// 色
repeat img_height
y = cnt
repeat img_width
x = cnt

color img_origcolor(y,x,0), img_origcolor(y,x,1), img_origcolor(y,x,2)
pset x, y
loop
loop

// 確認用可視化
gsel 0
gmode 7

// 左上:背景黒
color 0, 0, 0 : boxf 0, 0, img_width, img_height
pos 0, 0 : gcopy 4, 0, 0, img_width, img_height

// 右上:背景白
color 255, 255, 255 : boxf img_width, 0, img_width*2, img_height
pos img_width, 0 : gcopy 4, 0, 0, img_width, img_height

// 左下:背景赤
color 255, 0, 0 : boxf 0, img_height, img_width, img_height*2
pos 0, img_height : gcopy 4, 0, 0, img_width, img_height

// 右下:背景青
color 0, 0, 255 : boxf img_width, img_height, img_width*2, img_height*2
pos img_width, img_height : gcopy 4, 0, 0, img_width, img_height



スクリプトを実行するとPNGファイルの在処を聞くダイアログが表示されます。
PNGファイルを選択すると、今回説明した方法でPNGの原色とα値を計算し、screenで表示します。
また、得られたgmode7用の画像を、実際にgmode7で描画します(背景は「黒」「白」「赤」「青」の四種)

実験用のコードなので速度等の最適化は全くしていません、実際に使おうなんて考えてる人は留意してください
また、保存等もできません、その辺はご自分で書くようお願いします。(バッファ4をbmpsaveするだけですが。)

サンプル用画像PNG↓
・サンプル1(複雑な色グラデーション、α値グラデーション)
pngcheck.png

・サンプル2(大半がα値0であるような画像)
pngcheck_red.png

・サンプル3(複雑なα値グラデーション、PSDファイル)
ダウンロード先 DropBox
pngcheck_psd.png
 (↑に表示してる画像はPNGだよ!(FC2ブログはPSDファイルアップロードできないよ!))

関連記事
スポンサーサイト



コメント

コメントの投稿


管理者にだけ表示を許可する

トラックバック

トラックバック URL
http://fe0km.blog.fc2.com/tb.php/45-d4ec0c41
この記事にトラックバックする(FC2ブログユーザー)