ページ 11

libpngの使い方について教えてください

Posted: 2011年10月17日(月) 23:42
by stormrider
はじめまして、お世話になります。
Cについては初心者、開発環境はxcodeです。
最終的にやりたいことはメモリ上のpngデータをビットマップに変換することです。

http://ohwhsmm7.blog28.fc2.com/blog-entry-14.html
http://ysflight.in.coocan.jp/programmin ... oderj.html

上記サイトを参考にしているのですが、前者を参考にした方では
 ・FFFFFFFFになっている箇所が00000000になってしまう。フリーソフトなどで変換すると白くなるところが、黒に変換されてしまう。
 ・24bitパレットなしの画像は変換できたが、pixel深度やパレットなどがついた画像は、まったく元のデータと異なる画像になってしまう。
後者の方では
 ・透過部分に妙な色が付いてしまう。画像のメイン部分はきちんと表示されるが、背景に予測しない色が出てしまう。
 (元の画像ファイルが多い為、画像の方を修正するのは避けたい)
という問題が発生しています。

 libpngについての理解も浅く、なぜこのようなことが起きてしまっているのかわかりません。
 これらのトラブルからどういった間違いが予測できるでしょうか?
 この情報から何か思い当たるところのある方はご指摘お願い致します。

 また、もう一点、上記サイト中のコードでどうしても理解できない箇所があるのですが、

コード:

for( int i = 0; i < num_palette; i++ )
        {
            byBmp[ i * 4 + 0 ] = palette[ i ].blue;
            byBmp[ i * 4 + 1 ] = palette[ i ].green;
            byBmp[ i * 4 + 2 ] = palette[ i ].red;
        }
と、byBmp(ここにビットマップデータを格納します)にカラーデータを格納していますが、

コード:

bi.biSizeImage = pInfo->height * ( ( ( pInfo->width * pInfo->pixel_depth / 8 ) + 3 ) & ~3 );
BYTE *byBmp = (BYTE*)::malloc( bi.biSizeImage );
 以上のようなメモリ確保となっているため、縱横のサイズがごく小さく、パレット数が多い画像の場合確保した領域を超えてしまっていることがあります。これは私には問題に思えるのですが、理解不足のためそう見えるだけなのでしょうか。
 パレットをコピーしたあとの

コード:

    for( i = 0; i < number_passes; i++ )
    {
        for( j = pInfo->height - 1; j >= 0; j-- )
        {
            pRow = &byBmp[ j*row_size ];
            png_read_row( pPng, pRow, NULL );
        }
    }
の箇所も、取得した情報を上書きしてしまっているように見えます。
 これらについて、何か誤りがあるのか、または私の理解に至らないところがあるとしたらそれはどこなのかを、お教えください。

 よろしくお願いします。

Re: libpngの使い方について教えてください

Posted: 2011年10月18日(火) 02:35
by ISLe
ひとつめのリンク先でパレットを読み込んでいるコードは処理全体では意味を持ってません。
パレットの読み方を示すためで正しく動くことを求められているのではないと思われます。

PNGで透過マスク情報とアルファチャンネルは別のものです。
透過マスクを適用した結果が得られれば良いのであれば透過マスク情報をアルファチャンネルに変換できます。

コード:

png_set_gray_to_rgb(png_ptr); // グレイスケール画像をRGB形式に変換
png_set_palette_to_rgb(png_ptr); // パレット付き画像をRGB形式に変換
png_set_packing(png_ptr); // 各要素8ビットに拡張
png_set_strip_16(png_ptr); // 各要素8ビットに縮小
png_set_bgr(png_ptr); // RGB並びをBGR並びに変換
png_set_add_alpha(png_ptr, 0xff, PNG_FILLER_AFTER); // アルファチャンネルを追加
png_set_tRNS_to_alpha(png_ptr); // tRNSチャンク(透過マスク)をアルファチャンネルに変換
PNGファイルを読み込むところまでできたら、上記の関数を順に呼び出してください。
あとは1ピクセルあたりBGRA(各8ビット)の形式で読み出すことができると思います。
上記の関数呼び出しはカラータイプやビット深度を判断して適切に行う必要があるかもしれません。

Re: libpngの使い方について教えてください

Posted: 2011年10月19日(水) 19:35
by stormrider
ご回答ありがとうございます。
上記のコードに

コード:

double image_gamma;
png_color_16p image_background;
int intent;
if( png_get_sRGB( pPng, pInfo, &intent ) ){
	png_set_gamma( pPng, screen_gamma, 0.45455 );
} else if( png_get_gAMA( pPng, pInfo, &image_gamma ) ) {
	png_set_gamma( pPng, screen_gamma, image_gamma );
}

if( ( pInfo->color_type & PNG_COLOR_MASK_ALPHA ) 
	 || png_get_valid( pPng, pInfo, PNG_INFO_tRNS ) ) {
	if( png_get_bKGD( pPng, pInfo, &image_background ) ) {
		png_set_background( pPng, image_background, PNG_BACKGROUND_GAMMA_FILE, 1, 1.0 );
	} else {
		png_color_16 my_background = { 0xff, 0xff, 0xff, 0xff, };
		png_set_background( pPng, &my_background, PNG_BACKGROUND_GAMMA_SCREEN, 0, 1.0 );
	}
}
を組み合わせることでほぼ解決できました。
ただ、1点だけ、透過情報がなくなってしまうファイルがあります。
該当するファイルに含まれているチャンクはIHDRとIDATとIENDだけなのですが。
アルファ情報を適用するには何か特別な操作が必要なのでしょうか?
教えていただいたコードを部分的にはずしてみたりして色々試したのですが、どうしても透過情報を保存できませんでした。

Re: libpngの使い方について教えてください

Posted: 2011年10月20日(木) 02:11
by ISLe
stormrider さんが書きました:ただ、1点だけ、透過情報がなくなってしまうファイルがあります。
該当するファイルに含まれているチャンクはIHDRとIDATとIENDだけなのですが。
tRNSチャンクを含まないということは(完全な)アルファチャンネルで透過情報を持っているということですね。

先の回答のアルファチャンネルの追加部分(6,7行目)を以下のように変更してみてください。
既にアルファチャンネルを持っている場合は追加されないようにします。

コード:

if (png_get_valid(pPng, pInfo, PNG_INFO_tRNS)) {
    // tRNSチャンクがあれば ※PNGの仕様として完全なアルファチャンネルと同居しない
    // tRNSチャンクを完全なアルファチャンネルに変換
    png_set_tRNS_to_alpha(pPng);
} else if (!(pInfo->color_type & PNG_COLOR_MASK_ALPHA)) {
    // tRNSチャンクが無く、完全なアルファチャンネルも無ければ
    // 不透明で埋めた完全なアルファチャンネルを追加
    png_set_add_alpha(pPng, 0xff, PNG_FILLER_AFTER);
}

Re: libpngの使い方について教えてください

Posted: 2011年10月26日(水) 13:16
by stormrider
教えていただいた方法ですっかりうまくいきました。
丁寧に教えていただき、どうもありがとうございました。