libpngを使ってみよう(読み込み編)


libpngとはPNG用の公式ライブラリです。ほとんどのPNGの機能をサポートしていて、拡張可能で、七年以上にわたって検証されてきました。開発バージョン(つまり、バグがあるかもしれないし、改変されるかも知れないし、試験的な機能を含んでいるかもしれない)のホームサイトは http://libpng.sourceforge.net/ です。また、ライブラリについて質問は png-implementメーリングリストにしてください。(libpngホームページより)


libpngを使えば、驚くほど簡単にPNGファイルの読み込みや書き込みをする事が出来ます。ここでは、libpngを実際にVisual Studio .NETで使用するまでの手順を説明していきます。(VS 6.0でも動くかも知れないけど、インストールが面倒なので未確認)

libpngをビルドしよう

libpngはzlibを使用しているので、まずはzlibが必要です。zlibホームサイトからzlibのソースコードをダウンロードしましょう。zlib source codeと書かれている物をダウンロードすればOKです。zip fileの物を落としておくと展開に面倒がなくていいですね。 つぎに、libpngホームページから、libpngのソースコードをダウンロードしましょう。上の方に英語で説明が沢山書いてありますが、それはひとまずおいといて、Source Codeの横にあるリンクをクリックしましょう。これも.zipを落としておくと便利でしょう。
それではファイルを展開します。まず、zlibのアーカイブを展開して出来たファイルを「zlib」という名前のフォルダに放り込んでください。同じようにlibpngアーカイブにも「libpng」と言った名前をつけたフォルダに放り込んでおいてください。その後に、その二つのフォルダを同じディレクトリに入れておきます。
沢山のファイルが出てきましたね。READMEやLICENSEは重要です。使用方法や配布条件について書いてあります。LICENSEについてはこのページの一番最後に解説を書いていますのでそれも参考にしてくださいね。
それではビルドしてみましょう。libpngの方を展開したファイル群のprojects\msvcにlibpng.dswがあるはずなのでそれを開いてください。.NETを使用していると、「7.0形式に変換するか?」という問い合わせのダイアログがでるはずですので、「すべてはい」を押して変換しちゃいましょう。libpngはDLL形式の物も作成できますが、ここではLIB形式の物を作ってみます。[ビルド]→[構成マネージャ]で、LIBを選んでおきましょう。
それが出来たらメニューの[ビルド]→[ソリューションのビルド]を選んでください。ビルドが始まります。正常にビルドが出来ればプロジェクトのあったフォルダの「win32\libpng\lib」にlibpng.libが出来ているはずです。これでlibpngライブラリの完成です。


libpngを使える状態にする

これでlibpng.libが出来ましたが、VC++から使える状態にしなければ成りません。libpng.libを開発用のフォルダにでも「lib」といったフォルダをつくって入れておきます。また、libpngを展開して出来たpng.hとpngconf.h、zlibを展開して出来たzlib.hとzconf.hも「include」というフォルダをつくって入れておきましょう。
それでは、この二つのフォルダをVC++から認識させます。VC++のメニュー[ツール]→[オプション]の[プロジェクト\VC++ ディレクトリ]の「ディレクトリを表示するプロジェクト:ライブラリ」で、先ほどの「lib」フォルダを一番下に追加します。
同様にインクルードの方にも「include」フォルダも追加すればできあがりです。


PNGファイルを読み込む

これで、libpngを使用する準備は整いました。それでは実際にPNGファイルの読み込みをしてみましょう。

その前にプログラムでlibpngを使用する事を宣言するために、先頭に
#include "png.h"
#pragma comment(lib, "libpng.lib")
と書き込んでおいてください。#includeの説明は必要ないと思います。#pragma comment~は、コンパイラに"libpng.lib"を使う事を教えてます。

では、今からこのサンプルプログラムのソースコードを解説していきます。このプログラムでは、PNGの読み込みから表示までをしています。まずはこのサンプルプログラムをダウンロードして開いてみてくださいね。

展開と表示を行う、LoadPngFile関数です。
int LoadPngFile(HWND hWnd){

    FILE *fp = fopen("test.png", "rb");
    if(!fp) return 0;
    ktFileCloser pngfilecloser(fp);
    int fsize;
    unsigned char *FileImage = (unsigned char*)malloc((fseek(fp, 0, SEEK_END), fsize = ftell(fp)));
    ktMemoryReleaser fileimagereleaer(FileImage);
    fseek(fp, 0, SEEK_SET);
    fread(FileImage, fsize, 1, fp);
	
ktFileCloser と ktMemoryReleaserはファイルのクローズ、メモリの解放を自動的にするプログラムです。ここでは気にしないでかまいません。
malloc((fseek(fp, 0, SEEK_END), fsize = ftell(fp)))の部分でファイルサイズ分のメモリを確保しています。ホントは分けて書くべきですが、色々あってこうなってます。
これで、FileImageにPNGファイルを読み込む事が出来ました。

それでは、メモリ上に読み込まれたデータからpng_structを作成しましょう。ファイルを扱うときにFILE*が必要なように、PNGファイルを扱うときにはpng_struct*が必要となってきます。
    if(!png_check_sig(FileImage, fsize))
        return 0;

    png_struct *pPng;
    png_info *pInfo;

    if(!(pPng = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL)))
        return 0;
    if(!(pInfo = png_create_info_struct(pPng)))
        return 0;
    ktPngReadStructReleaser pngreleaser(pPng, pInfo);
	
まずは、読み込もうとしているデータが本当にPNGファイルなのか、png_sig_checkでチェックをしています。
int png_sig_check(png_bytep sig, int num)
引数: sig チェックするデータ
num データのサイズ(8以上でなければなりません。)
返り値: PNGファイルなら非ゼロ
png_sig_checkが成功した場合png_create_read_structでpng_structを作ります。ファイルで言えば、fopenのようなものですね。
png_structp png_create_read_struct(png_const_charp user_png_ver, png_voidp error_ptr, png_error_ptr error_fn, png_error_ptr warn_fn)
引数: user_png_ver PNGのバージョンを指定します。PNG_LIBPNG_VER_STRINGを使っておけばいいです。
error_ptr, error_fn, warn_fn エラーが起こったときに呼び出される関数と渡されるデータです・・・が、今回は使用していません。NULLにしています。
返り値: 成功すれば正常なpng_struct *を返します。失敗した場合はNULL値になります。
fopenがあればfcloseがあるように、png_create_read_structがあればpng_destroy_read_structもあります。
int png_destroy_read_struct(png_structpp png_ptr_ptr, png_infopp info_ptr_ptr, png_infopp end_info_ptr_ptr)
引数: png_ptr_ptr png_create_read_structで取得できたpng_struct *へのアドレスを指定します。
info_ptr_ptr png_create_info_struct(後述)で取得したpng_info *へのアドレスを指定します。一緒に閉じられます。
end_info_ptr_ptr 今回は使いません。NULLを指定しておいてください。
今回、解放はktPngReadStructReleaserに任せているので明示的には行われてませんが、ちゃんと解放してくださいね。
無事にpng_struct *が作れたら、読み込みを開始しましょう。情報読み込み用のpng_info_struct *を作ります。
png_info_structp png_create_info_struct(png_structp png_ptr)
引数: png_ptr png_create_read_structで取得できたpng_struct *へのアドレスを指定します。
返り値: 成功すれば正常なpng_info_struct *を返します。失敗した場合はNULL値になります。
ここで取得したpng_info_struct *はpng_destroy_read_structでpng_struct *と一緒に解放できます。
それでは、実際にlibpngがファイルイメージを読み込めるように、読み込み用の関数を教えてやります。
    unsigned char* filepos = FileImage;
    png_set_read_fn(pPng,(png_voidp)&filepos,(png_rw_ptr)PngReadFunc);
	
png_set_read_fnで、実際に読み込みをする関数 PngReadFunc を教えてやります。PngReadFuncはファイルの頭で宣言されている関数で
  void PngReadFunc(png_struct *pPng, png_bytep buf, png_size_t size){
    unsigned char** p = (unsigned char**)png_get_io_ptr(pPng);
    memcpy(buf, *p, size);
    *p += (int)size;
 }	
	
と定義されています。png_set_read_fnでの第一引数・第二引数がそれぞれ、PngReadFuncの第一引数、png_get_io_ptr(pPng)に対応しています。とりあえず、PngReadFuncはbufにsize分のデータを埋める作業をしている、と思ってもらえば結構です。
それでは、さっきつくっておいたpng_info_struct *を使って情報を読み込みましょう。
    png_read_info(pPng, pInfo);

    png_uint_32 PngWidth;
    png_uint_32 PngHeight;
    int bpp;
    int ColorType;
    png_get_IHDR(pPng, pInfo, &PngWidth, &PngHeight, &bpp, &ColorType, NULL, NULL, NULL);
	
png_read_info関数でpng_struct *の情報をpng_info_struct *に書き込みます。そのデータには色々な情報が含まれていますが、重要なデータの一つであるIHDR(幅や高さなどの情報が含まれる)をpng_get_IHDRを利用して読み込んでいます。NULLにしている部分は本当なら圧縮方法などが返される部分ですが、今回のPNGファイルの読み込みには関係しない(というか、気にしなくても読み込める)のでとりあえず、放っておきます。
それでは、PNGデータを実際に表示できるようにしましょう。というのも、PNGファイルには白黒、256色、透明色付といった様々なタイプがあり、それをいちいち場合分けして読み込むのは面倒だからです。次の部分でイメージを通常のフルカラーイメージに変換しています。
    if(png_get_valid(pPng, pInfo, PNG_INFO_tRNS))
        png_set_expand(pPng);
    if(ColorType == PNG_COLOR_TYPE_PALETTE)
        png_set_expand(pPng);
    if(ColorType == PNG_COLOR_TYPE_GRAY && bpp < 8)
        png_set_expand(pPng);
    if(bpp > 8)
        png_set_strip_16(pPng);
    if(ColorType == PNG_COLOR_TYPE_GRAY)
        png_set_gray_to_rgb(pPng);
	
それが終わったら、いよいよ本当の読み込み部分です。
    unsigned char *BmpBuffer;
    unsigned char **Lines;
    BmpBuffer = (unsigned char *)malloc(PngWidth * PngHeight * 4);
    Lines = (unsigned char **)malloc(sizeof(unsigned char *) * PngHeight);
    for(int i = 0; i < PngHeight; i++)Lines[i] = BmpBuffer + PngWidth * 4 * i;
    if(!(ColorType & PNG_COLOR_MASK_ALPHA))png_set_filler(pPng, 0, 1);
    png_set_bgr(pPng);
    png_read_image(pPng, Lines);

    free(Lines);
	
ここについては、すこし解説しておきましょう。まず、イメージが幅PngWidth、高さPngHeightで32ビットイメージとして読み込むので、PngWidth * PngHeight * 4 bytesのメモリをBmpBufferに確保します。また、イメージは一行単位で読み込まれるので、画像の一行一行の頭部分のアドレスをLinesに格納します。
png_set_filler(pPng, 0, 1);の部分では、32ビットのうち、RでもGでもBでもない部分を0として埋める、という風に設定しています。最後の1はRGBの前に埋めるか、後に埋めるかのうち、後に埋める(=PNG_FILLER_AFTER)を指定しています。そして、png_set_bgr(pPng)で画像イメージをBGRの形式で読み込むように設定し、この二行で結果的にBGR0(αが含まれる場合BGRA)でイメージが読み込まれるようになります。WindowsのDIBはBGR0で格納されているのでこうすればそのまま利用できるのです。
png_read_image(pPng, Lines)で実際に読み込みが行われます。
これでBmpBufferにイメージが格納されました。後の表示方法は通常のビットマップと同じですので解説しません。

    HBITMAP hBmp = CreateBitmap(PngWidth, PngHeight, 1, 32, BmpBuffer);
    if(!hBmp)return 0;
    ktGDIReleaser hbmpreleaser(hBmp);

    HDC hDC = CreateCompatibleDC(NULL);
    if(!hDC)return 0;
    ktGDIReleaser hdcreleaser(hDC);

    HBITMAP hOldBmp = (HBITMAP)SelectObject(hDC, hBmp);
    HDC hWndDC = GetDC(hWnd);
    BitBlt(hWndDC, 0, 0, PngWidth, PngHeight, hDC, 0, 0, SRCCOPY);
    ReleaseDC(hWnd, hWndDC);
    SelectObject(hDC, hOldBmp);
	
これで、PNGを表示する事が出来ました。


よく分からなかった部分、不明瞭な部分があれば解説しますのでメールをください。次回は書き込み(これも簡単です!)についてやってみたいと思います。


このページはMozilla 1.6, Internet Explorer 6.0で確認しています。
Nanoseconds Hunter By kcrt(K.Takahashi)