新トップページへ | Tip

libpngを使いpngファイルを読み込む

LastUpdate : 06/12/05

libpngを使い、pngファイルを読み込み、表示させてみます。
WindowsAPIのファイル関数を使う方法、Cのライブラリのファイル関数を使う方法と、二つためしてみました。

(1)pngファイルを読み込んでみる(WindowsAPIのHANDLEを使って)
(2)pngファイルを読み込んでみる(libcのFILEを使って)


(1)pngファイルを読み込んでみる(WindowsAPIのHANDLEを使って)

 pngファイルの簡単な読み込み方法のサンプルです。「start」と書かれているボタンを押してください。すると、画像が表示される・・・はずw
 ソースコードに書いてあるとおり「c:\1.png」というファイルを読み込みます。てきとーに変更してください。
 以下にpngファイルを読み込むソースコードを記します。ビルドには下準備が必要です(「※環境構築方法」を参照。)。
 LoadPng関数が主役です。

※環境構築方法(このソースコードを走るようにするための方法)

  1. http://www.zlib.net/からzlibのソースコードをDLする。
  2. http://www.libpng.org/pub/png/からlibpngのソースコードをDLする。
  3. それぞれのファイルを解凍し、*.cや*.hのファイルを一つのフォルダなどにまとめる(ただし、zlibに含まれているexsample.cなどmain関数が含まれているものは不要。とりあえずファイルを一緒にしてコンパイラが文句言ってきたファイルを弾けばとりあえずOK。)。
  4. プロジェクトフォルダの中にzlibpngというフォルダを作成し、そのフォルダにDLしてきたソースコードをすべて押し込む。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "crtdbg.h"
#include "windows.h" 

#include "process.h"

#include "zlibpng/zlib.h"
#include "zlibpng/png.h"


#define START_BUTTON_ID         (0x0001)

#define MWINDOW_NAME "mainwindow"
#define MWINDOW_TITLE "めいんういんどう〜w"


HINSTANCE g_hInst;

LRESULT CALLBACK WindowProc(HWND hwnd,UINT msg,WPARAM wp,LPARAM lp);
int LoadPng(HWND hwnd,char *pass);
void PngReadFileCallback(png_structp Png,png_bytep buf,png_size_t len); //ファイル読みこみのコールバック関数。


int WINAPI WinMain(HINSTANCE hinst,HINSTANCE,LPSTR,int)
{
        _CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF);

        g_hInst = hinst;

        WNDCLASSEX wcex;
        wcex.cbSize = sizeof(WNDCLASSEX);
        wcex.style              = CS_HREDRAW | CS_VREDRAW;
        wcex.lpfnWndProc        = (WNDPROC)WindowProc;
        wcex.cbClsExtra         = 0;
        wcex.cbWndExtra         = 0;
        wcex.hInstance          = hinst;
        wcex.hIcon              = LoadIcon(NULL, MAKEINTRESOURCE(IDI_APPLICATION));
        wcex.hCursor            = LoadCursor(NULL, IDC_ARROW);
        wcex.hbrBackground      = (HBRUSH)(COLOR_WINDOW+1);
        wcex.lpszMenuName       = (LPCSTR)NULL;
        wcex.lpszClassName      = MWINDOW_NAME;
        wcex.hIconSm            = LoadIcon(NULL, MAKEINTRESOURCE(IDI_APPLICATION));
        RegisterClassEx( &wcex );

        HWND hWnd = CreateWindow(MWINDOW_NAME, MWINDOW_TITLE , WS_OVERLAPPEDWINDOW,
        CW_USEDEFAULT,CW_USEDEFAULT,800,600, NULL, NULL, hinst, NULL);
        ShowWindow( hWnd, SW_SHOW );
        UpdateWindow( hWnd );

        MSG msg;
        while( GetMessage(&msg, NULL, 0, 0) ) 
        {
                TranslateMessage( &msg );
                DispatchMessage( &msg );
        }
        return (int)msg.wParam;
}

LRESULT CALLBACK WindowProc(HWND hwnd,UINT msg,WPARAM wp,LPARAM lp)
{
        switch(msg)
        {
        case WM_CREATE:
                {
                        CreateWindow("BUTTON","start",BS_PUSHBUTTON | WS_CHILD | WS_VISIBLE,
                                650,0,
                                100,25,
                                hwnd,
                                (HMENU)START_BUTTON_ID,
                                g_hInst,
                                NULL);

                        break;
                }
        case WM_COMMAND:
                {
                        switch( LOWORD(wp) )
                        {
                        case START_BUTTON_ID:
                                {
                                        LoadPng(hwnd,"c:\\1.png");
                                        break;
                                }
                        }
                        break;
                }
        case WM_CLOSE:
                {
                        PostQuitMessage(0);
                        break;
                }
        }
        return DefWindowProc(hwnd,msg,wp,lp);
}


int LoadPng(HWND hwnd,char *pass)
{
        int w,h,d;
        png_structp Png;
        png_infop PngInfo;
        png_byte sig[ 4 ];
        DWORD dummy;

        //pngの読み込み。
        HANDLE hFile = CreateFile(pass,GENERIC_READ,0,NULL,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,NULL);
        if( hFile == INVALID_HANDLE_VALUE )return -1;

    if( ReadFile(hFile,sig,4,&dummy,NULL) == 0 )
        {
                CloseHandle(hFile);
                return -2;
        }
        if( !png_check_sig(sig,4) )
        {
                CloseHandle(hFile);
                return -3;
        }
        if( (Png = png_create_read_struct(PNG_LIBPNG_VER_STRING,NULL,NULL,NULL)) == NULL )
        {
                CloseHandle(hFile);
                return -4;
        }
        if( (PngInfo = png_create_info_struct(Png)) == NULL )
        {
                png_destroy_read_struct(&Png,(png_infopp)NULL,(png_infopp)NULL);
                CloseHandle(hFile);
                return -5;
        }
        if( setjmp(png_jmpbuf(Png)) )
        {
                MessageBox(NULL,"jump","",MB_OK);
                png_destroy_read_struct(&Png,&PngInfo,(png_infopp)NULL);
                CloseHandle(hFile);
                return -6;
        }
    
        png_set_read_fn(Png,hFile,PngReadFileCallback); //io_ptrにhFileを代入させる。
        png_set_sig_bytes(Png,4);                               //読み込んだ分量をlibpngに知らせる(シグネチャを読み込んだ量)
        png_read_info(Png,PngInfo);                             //ファイルのInfoを取得
        png_set_bgr(Png);                                               //DIBをRGB形式からBGR形式にトランスフォームをlibpngにお願いする。

        w = PngInfo->width;                             //横幅
        h = PngInfo->height;                    //縦幅
        d = PngInfo->bit_depth/8 * 4;   //色深度

        char str[256];
        sprintf(str,"%d x %d (%d bit/channel)\nrow %d byte\nchannnel count : %d",
                PngInfo->width,PngInfo->height,PngInfo->bit_depth,
                PngInfo->rowbytes,
                PngInfo->channels);
        MessageBox(NULL,str,"",MB_OK);          //イメージのサイズなどをダイアログボックスで表示。

        //インターレスの場合は、いくらかの手間が必要が必要で、面倒なので対応しないことにしますtt
        if( PngInfo->interlace_type != PNG_INTERLACE_NONE )
        {
                MessageBox(NULL,"interrace image!","",MB_OK);
                png_destroy_read_struct(&Png,&PngInfo,(png_infopp)NULL);
                CloseHandle(hFile);
                return -7;
        }

        //データ読み込みようのバッファ
        png_bytep buf = new png_byte[ w * h * d * PngInfo->channels ];
        if( buf == NULL )
        {
                png_destroy_read_struct(&Png,&PngInfo,(png_infopp)NULL);
                CloseHandle(hFile);
                return -8;
        }

        //1ラインづつ読み込んでいく。
        png_uint_32 i = 0;
        int offset_point = 0;
        while( i < PngInfo->height )
        {
                png_read_row(Png,buf + offset_point,NULL);
                offset_point += w * d;
                i++;
        }
        //読み込み終了処理。
        png_read_end(Png,NULL); //イメージデータの後ろにあるチャンクをスキップ。
        png_destroy_read_struct(&Png,&PngInfo,(png_infopp)NULL);        //読み込み終了処理。
        CloseHandle(hFile);
        

        //読み込んだイメージを表示する
        unsigned char *image_p;
        BITMAPINFO Info;
        memset(&Info,0,sizeof(BITMAPINFO));
        Info.bmiHeader.biBitCount=32;
        Info.bmiHeader.biHeight=h*-1;   //Y軸の反転をキャンセルする。
        Info.bmiHeader.biWidth=w;
        Info.bmiHeader.biSize=sizeof(BITMAPINFOHEADER);
        Info.bmiHeader.biPlanes=1;
        
        HDC hdcScreen = GetDC(hwnd);
        HBITMAP hBitmap = CreateDIBSection(hdcScreen,&Info,DIB_RGB_COLORS,(void**)&image_p,NULL,0);
        HDC hdcBack = CreateCompatibleDC(hdcScreen);
        HBITMAP hBitmap_old=(HBITMAP)SelectObject(hdcBack,hBitmap);
        memcpy(image_p,buf,w*h*4);
        delete []buf;                                   //コピーしたのでもう使わないので解放する。

        BitBlt(hdcScreen,0,0,w,h,hdcBack,0,0,SRCCOPY);

        //終了処理。
        SelectObject(hdcBack,hBitmap_old);
        DeleteObject(hBitmap);
        DeleteDC(hdcBack);
        ReleaseDC(hwnd,hdcScreen);

        return 0;
}


//ファイル読みこみのコールバック関数。
void PngReadFileCallback(png_structp Png,png_bytep buf,png_size_t len)
{
        DWORD dummy;
        HANDLE hFile = (HANDLE)png_get_io_ptr(Png);             //この関数でpng_set_read_fnで指定したhFileが取得できる。
        ReadFile(hFile,buf,(DWORD)len,&dummy,NULL);
}
                     
 

●setjmpについて
 ライブラリの中で何かまずいことが起きるとjumpしてくるらしいです。
 ですので、setjmpしているところでは、エラー処理(主にメモリの解放処理)を記述しています。jumpさせないようにすることもできるみたいですが、その場合、内部でエラーが起きたときabord()を呼ぶそうです。

●画像データの読み込みについて
 最も簡単な方法に、png_read_image関数を使う方法がありますが、この場合、ポインタの配列に1ラインづつのデータへのアドレスが格納される形式です。
 この方式ではゲームなどへの応用に適さないと考え、png_read_row関数を使用しています。

(2)pngファイルを読み込んでみる(libcのFILEを使って)

 pngファイルの簡単な読み込み方法のサンプルです。「start」と書かれているボタンを押してください。すると、画像が表示される・・・はずw
 ソースコードに書いてあるとおり「c:\1.png」というファイルを読み込みます。てきとーに変更してください。
 以下にpngファイルを読み込むソースコードを記します。ビルドには下準備が必要です(「※環境構築方法」を参照。)。
 LoadPng関数が主役です。

※環境構築方法(このソースコードを走るようにするための方法)

  1. http://www.zlib.net/からzlibのソースコードをDLする。
  2. http://www.libpng.org/pub/png/からlibpngのソースコードをDLする。
  3. それぞれのファイルを解凍し、*.cや*.hのファイルを一つのフォルダなどにまとめる(ただし、zlibに含まれているexsample.cなどmain関数が含まれているものは不要。とりあえずファイルを一緒にしてコンパイラが文句言ってきたファイルを弾けばとりあえずOK。)。
  4. プロジェクトフォルダの中にzlibpngというフォルダを作成し、そのフォルダにDLしてきたソースコードをすべて押し込む。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "crtdbg.h"
#include "windows.h" 


#include "zlibpng/zlib.h"
#include "zlibpng/png.h"


#define START_BUTTON_ID         (0x0001)

#define MWINDOW_NAME "mainwindow"
#define MWINDOW_TITLE "めいんういんどう〜w"


HINSTANCE g_hInst;

LRESULT CALLBACK WindowProc(HWND hwnd,UINT msg,WPARAM wp,LPARAM lp);
int LoadPng(HWND hwnd,char *pass);


int WINAPI WinMain(HINSTANCE hinst,HINSTANCE,LPSTR,int)
{
        _CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF);

        g_hInst = hinst;

        WNDCLASSEX wcex;
        wcex.cbSize = sizeof(WNDCLASSEX);
        wcex.style              = CS_HREDRAW | CS_VREDRAW;
        wcex.lpfnWndProc        = (WNDPROC)WindowProc;
        wcex.cbClsExtra         = 0;
        wcex.cbWndExtra         = 0;
        wcex.hInstance          = hinst;
        wcex.hIcon              = LoadIcon(NULL, MAKEINTRESOURCE(IDI_APPLICATION));
        wcex.hCursor            = LoadCursor(NULL, IDC_ARROW);
        wcex.hbrBackground      = (HBRUSH)(COLOR_WINDOW+1);
        wcex.lpszMenuName       = (LPCSTR)NULL;
        wcex.lpszClassName      = MWINDOW_NAME;
        wcex.hIconSm            = LoadIcon(NULL, MAKEINTRESOURCE(IDI_APPLICATION));
        RegisterClassEx( &wcex );

        HWND hWnd = CreateWindow(MWINDOW_NAME, MWINDOW_TITLE , WS_OVERLAPPEDWINDOW,
        CW_USEDEFAULT,CW_USEDEFAULT,800,600, NULL, NULL, hinst, NULL);
        ShowWindow( hWnd, SW_SHOW );
        UpdateWindow( hWnd );

        MSG msg;
        while( GetMessage(&msg, NULL, 0, 0) ) 
        {
                TranslateMessage( &msg );
                DispatchMessage( &msg );
        }
        return (int)msg.wParam;
}

LRESULT CALLBACK WindowProc(HWND hwnd,UINT msg,WPARAM wp,LPARAM lp)
{
        switch(msg)
        {
        case WM_CREATE:
                {
                        CreateWindow("BUTTON","start",BS_PUSHBUTTON | WS_CHILD | WS_VISIBLE,
                                650,0,
                                100,25,
                                hwnd,
                                (HMENU)START_BUTTON_ID,
                                g_hInst,
                                NULL);

                        break;
                }
        case WM_COMMAND:
                {
                        switch( LOWORD(wp) )
                        {
                        case START_BUTTON_ID:
                                {
                                        LoadPng(hwnd,"c:\\1.png");
                                        break;
                                }
                        }
                        break;
                }
        case WM_CLOSE:
                {
                        PostQuitMessage(0);
                        break;
                }
        }
        return DefWindowProc(hwnd,msg,wp,lp);
}


int LoadPng(HWND hwnd,char *pass)
{
        int w,h,d;
        png_structp Png;
        png_infop PngInfo;
        png_byte sig[ 4 ];

        //pngの読み込み。
        FILE *fp = fopen(pass,"rb");
        if( fp == NULL )return -1;

        if( fread(sig,4,1,fp) < 1 )
        {
                fclose(fp);
                return -2;
        }
        if( !png_check_sig(sig,4) )
        {
                fclose(fp);
                return -3;
        }
        if( (Png = png_create_read_struct(PNG_LIBPNG_VER_STRING,NULL,NULL,NULL)) == NULL )
        {
                fclose(fp);
                return -4;
        }
        if( (PngInfo = png_create_info_struct(Png)) == NULL )
        {
                png_destroy_read_struct(&Png,(png_infopp)NULL,(png_infopp)NULL);
                fclose(fp);
                return -5;
        }
        if( setjmp(png_jmpbuf(Png)) )
        {
                MessageBox(NULL,"jump","",MB_OK);
                png_destroy_read_struct(&Png,&PngInfo,(png_infopp)NULL);
                fclose(fp);
                return -6;
        }
        png_init_io(Png,fp);
        png_set_sig_bytes(Png,4);
        png_read_info(Png,PngInfo);
        png_set_bgr(Png);

        w = PngInfo->width;                             //横幅
        h = PngInfo->height;                    //縦幅
        d = PngInfo->bit_depth/8 * 4;   //色深度

        char str[256];
        sprintf(str,"%d x %d (%d bit/channel)\nrow %d byte\nchannnel count : %d",
                PngInfo->width,PngInfo->height,PngInfo->bit_depth,
                PngInfo->rowbytes,
                PngInfo->channels);
        MessageBox(NULL,str,"",MB_OK);          //イメージのサイズなどをダイアログボックスで表示。

        //インターレスの場合は、いくらかの手間が必要が必要で、面倒なので対応しないことにしますtt
        if( PngInfo->interlace_type != PNG_INTERLACE_NONE )
        {
                MessageBox(NULL,"interrace image!","",MB_OK);
                png_destroy_read_struct(&Png,&PngInfo,(png_infopp)NULL);
                fclose(fp);
                return -7;
        }

        //データ読み込みようのバッファ
        png_bytep buf = new png_byte[ w * h * d * PngInfo->channels ];
        if( buf == NULL )
        {
                png_destroy_read_struct(&Png,&PngInfo,(png_infopp)NULL);
                fclose(fp);
                return -8;
        }

        //1ラインづつ読み込んでいく。
        png_uint_32 i = 0;
        int offset_point = 0;
        while( i < PngInfo->height )
        {
                png_read_row(Png,buf + offset_point,NULL);
                offset_point += w * d;
                i++;
        }
        //読み込み終了処理。
        png_read_end(Png,NULL); //イメージデータの後ろにあるチャンクをスキップ。
        png_destroy_read_struct(&Png,&PngInfo,(png_infopp)NULL);        //読み込み終了処理。
        fclose(fp);     //処理が終了したのでファイルを閉じる。



        //読み込んだイメージを表示する
        unsigned char *image_p;
        BITMAPINFO Info;
        memset(&Info,0,sizeof(BITMAPINFO));
        Info.bmiHeader.biBitCount=32;
        Info.bmiHeader.biHeight=h*-1;   //Y軸の反転をキャンセルする。
        Info.bmiHeader.biWidth=w;
        Info.bmiHeader.biSize=sizeof(BITMAPINFOHEADER);
        Info.bmiHeader.biPlanes=1;
        
        HDC hdcScreen = GetDC(hwnd);
        HBITMAP hBitmap = CreateDIBSection(hdcScreen,&Info,DIB_RGB_COLORS,(void**)&image_p,NULL,0);
        HDC hdcBack = CreateCompatibleDC(hdcScreen);
        HBITMAP hBitmap_old=(HBITMAP)SelectObject(hdcBack,hBitmap);
        memcpy(image_p,buf,w*h*4);
        delete []buf;                                   //コピーしたのでもう使わないので解放する。

        BitBlt(hdcScreen,0,0,w,h,hdcBack,0,0,SRCCOPY);

        //終了処理。
        SelectObject(hdcBack,hBitmap_old);
        DeleteObject(hBitmap);
        DeleteDC(hdcBack);
        ReleaseDC(hwnd,hdcScreen);

        return 0;
}
                        

●setjmpについて
 ライブラリの中で何かまずいことが起きるとjumpしてくるらしいです。
 ですので、setjmpしているところでは、エラー処理(主にメモリの解放処理)を記述しています。jumpさせないようにすることもできるみたいですが、その場合、内部でエラーが起きたときabord()を呼ぶそうです。

●画像データの読み込みについて
 最も簡単な方法に、png_read_image関数を使う方法がありますが、この場合、ポインタの配列に1ラインづつのデータへのアドレスが格納される形式です。
 この方式ではゲームなどへの応用に適さないと考え、png_read_row関数を使用しています。