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


はじめに

このドキュメントは「libpngを使ってみよう(読み込み編)」を読んでいる事を前提としています。まだ読んでない方は、先に読み込み編をお読みください。


PNGファイルを書き込む

読み込みが出来るようになったら、次は書き込みです。もちろん、ソースコードの頭に
#include "png.h"
#pragma comment(lib, "libpng.lib")
を忘れないでください。

では、今からこのサンプルプログラムのソースコードを解説していきます。このプログラムでは、PNGの保存をしています。
スクリーンショットをPNGとして保存するSavePngFile関数です。
int SavePngFile(HWND hWnd){

    FILE *fp = fopen("test.png", "wb");
    if(!fp)return 0;
    ktFileCloser ktfileclose(fp);
    png_structp png_ptr;
    png_infop info_ptr;

    png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
    if(!png_ptr) return 0;
    info_ptr = png_create_info_struct(png_ptr);
    if(!info_ptr){
        png_destroy_write_struct(&png_ptr, png_infopp_NULL);
        return 0;
    }
    ktPngWriteStructReleaser pngwritereleaser(png_ptr, info_ptr);
	
読み込み編と同様にktFileCloser と ktMemoryReleaserはファイルのクローズ、メモリの解放を自動的にするプログラムです。ここでは気にしないでかまいません。
読み込み編と違い、fopenの引数がwbに成っていますね。また、png_create_read_structの代わりにpng_create_write_structを使っています。
png_structp png_create_write_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値になります。
もちろん、png_destroy_read_structに対応するpng_destroy_write_structもあります。
int png_destroy_write_struct(png_structpp png_ptr_ptr, png_infopp info_ptr_ptr, png_infopp end_info_ptr_ptr)
引数: png_ptr_ptr png_create_write_structで取得できたpng_struct *へのアドレスを指定します。
info_ptr_ptr png_create_info_struct(後述)で取得したpng_info *へのアドレスを指定します。一緒に閉じられます。
また、読み込み編と同じpng_create_info_structをつかってpng_info *を作っています。この構造体には画像の幅や高さなどの重要な情報をあとで設定していきます。
読み込み編と同じように、サンプルでは解放をktPngWriteStructReleaserに任せていますが、ちゃんと忘れないように解放しましょう。
次は、保存する画像(ここでは現在表示されているスクリーン)を取得します。
    int Width = GetDeviceCaps(hDesktopDC, HORZRES);
    int Height = GetDeviceCaps(hDesktopDC, VERTRES);

    BYTE *pBits;
    BITMAPINFO bmpinfo = {sizeof(BITMAPINFO)};
    bmpinfo.bmiHeader.biWidth = Width;
    bmpinfo.bmiHeader.biHeight = Height;
    bmpinfo.bmiHeader.biPlanes = 1;
    bmpinfo.bmiHeader.biBitCount = 24;
    bmpinfo.bmiHeader.biCompression = BI_RGB;

    HBITMAP hBmp = CreateDIBSection(NULL, &bmpinfo, DIB_RGB_COLORS, (void**)&pBits, NULL, 0);
    if(!hBmp)return 0;
    ktGDIReleaser gdirelease((HGDIOBJ)hBmp);
    HDC hDC = CreateCompatibleDC(hDesktopDC);
    HBITMAP hOldBmp = (HBITMAP)SelectObject(hDC, hBmp);

    BitBlt(hDC, 0, 0, Width, Height, hDesktopDC, 0, 0, SRCCOPY);
	
本筋から外れるので詳解な説明は省略しますが、hDCというデバイスコンテキストをつくりpBitsが指すメモリがピクセルデータのhBmpを作成しています。そのあとでhDCにBitBltでスクリーンショットを転送しています。

それでは、読み込み編では省略したエラーチェックをやってみましょう。エラーチェックの方法としてpng_create_write_structの引数でエラーが起こったときに呼び出してもらう関数を指定する方法があります。この方法はエラーの検出は簡単ですが、エラーからの復帰が難しいのでパスします。
次に説明するのはsetjmpを利用した方法です。
    if (setjmp(png_jmpbuf(png_ptr))){
        // エラーの時ここに来る。
        SelectObject(hDC, hOldBmp);
        DeleteDC(hDC);
        return 0;
    }
	
このコードをおいておくと、libpng内でエラーが発生したときにこのifの中の最初の文に制御がうつる事となります。カナリ珍しい方法ですが、コレだとメモリの解放も出来るので、今回はコレを使ってみます。

それでは、libpngに読み込み元を教えます。
    png_init_io(png_ptr, fp);
    png_set_IHDR(png_ptr, info_ptr, Width, Height, 8, PNG_COLOR_TYPE_RGB,
        PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_BASE);
	
png_init_ioでfpから読み込む事を知らせています。また、png_set_IHDRで画像の幅、高さなどの情報を設定します。この関数は引数はたくさんありますが、幅と高さ以外の引数は通常いじる事がないので、説明を省略します。
    png_color_8 sig_bit;
    sig_bit.red = 8;
    sig_bit.green = 8;
    sig_bit.blue = 8;
    sig_bit.alpha = 0;
    png_set_sBIT(png_ptr, info_ptr, &sig_bit);
    
これも見たまんまです。RGBそれぞれが8ビットである事を指定しています。
    // PNGに書き込まれるコメント
    png_text text_ptr[1];
    text_ptr[0].key = "Description";
    text_ptr[0].text = "ktcDIB::Save() Data";
    text_ptr[0].compression = PNG_TEXT_COMPRESSION_NONE;
    png_set_text(png_ptr, info_ptr, text_ptr, 1);

    png_write_info(png_ptr, info_ptr);
	
ここでは、PNGにテキスト情報を付け加えています。別になくてもかまいません。
今回は「ktcDIB::Save() Data」という文字列を「Description」(説明)として追加しています。png_set_textの第四引数を変更する事でいくつものテキスト情報を追加する事が出来ます。

    png_write_info(png_ptr, info_ptr);

    png_set_bgr(png_ptr);
	
実際に画像に関する情報を書き込んで、今からlibpngに渡す画像がBGR形式(Windows上では画像イメージがこうなっています)である事を教えます。
それでは、実際の書き込み部分です。

    #define Pixel(x, y)     (pBits + Width * (Height - y - 1) * 3 + x * 3)
    png_bytep *row_pointers;
    row_pointers = (png_bytep *)malloc(sizeof(png_bytep *) * Height);
    for (int k = 0; k < Height; k++)
        row_pointers[k] = (png_bytep)Pixel(0, k);

    png_write_image(png_ptr, row_pointers);
    free(row_pointers);
    png_write_end(png_ptr, info_ptr);
	
これも少し難しいですが、読み込み編をごらんになった方なら大丈夫でしょう。Pixelマクロは、pBitsを起点としたピクセルデータの(x, y)の位置のポインタを取得しています。*3は作ったDIBSectionが24ビット(RGB)のため、Height - y - 1は、DIBSectionが天地逆さまになっているためです。
png_write_image(pPng, row_pointers)で実際に書き込みが行われます。
後処理は省略します。

よく分からなかった部分、不明瞭な部分があれば解説・補足をしますのでメールをください。


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