2010年09月21日

PNGファイルの読み込み



PNGファイルは以下の手順で読込みます。

  1. PNGの初期化ヘッダー情報を読込む。
  2. PNGイメージデータを読込む。
  3. PNGイメージデータからDIBを作成する。




libpngを使用するためのインクルード定義です。

#include "png.h"


「libpngのインストール」でダウンロードした二つのフォルダーから、以下のファイルを今回のプログラムを格納したファイルのあるフォルダーにコピーしておきます。

「lpng143」フォルダーからコピーするファイル

png.h
pngconf.h

「zlib」フォルダーからコピーするファイル

zlib.h
zconf.h

ビットマップバイト幅の算出マクロです。

//ビットマップバイト幅の算出マクロ
#ifndef WIDTHBYTES
#define WIDTHBYTES(bits) (((bits)+31)/32*4)
#endif//WIDTHBYTES


PNGファイルを開いて_PNGStreamIn関数を呼び出します。

//PNGファイルの読み込み
HBITMAP __stdcall _LoadPNG(LPCTSTR lpszFileName)
{
if (!lpszFileName) return FALSE;
CFile infile;
if (!infile.Open(lpszFileName,CFile::modeRead|CFile::shareDenyNone)) return FALSE;
HBITMAP hBitmap=_PNGStreamIn(&infile);
infile.Close();
return hBitmap;
}


PNGファイル読み込みのためのユーザー定義関数です。

//PNGファイル読み込み関数
void _PngReadFunc(png_structp png_ptr, png_bytep buf, png_size_t size)
{
CFile* infile=(CFile*)png_get_io_ptr(png_ptr); //CFileクラスのポインタを取得する
infile->Read(buf,(UINT)size); //CFileクラスを使ってファイルを読む
}


PNGファイルストリームを読み込みます。

//PNGファイルストリームの読み込み
HBITMAP __stdcall _PNGStreamIn(CFile* infile)
{
ASSERT(infile->IsKindOf(RUNTIME_CLASS(CFile)));

//PNGの初期化とヘッダー情報の読込み-------------------------------------------

//ファイルのPNGサインをチェックする。
ULONGLONG startPos=infile->GetPosition(); //ファイルの先頭位置を保存
BYTE sig[8];
infile->Read(sig,8);
//PNGサインが正しくなければ、FALSEを返して終了する。
if (!png_check_sig(sig, 8)) return FALSE;
infile->Seek(startPos,CFile::begin); //ファイル位置を元に戻す

//PNG構造体の取得
png_structp png_ptr=NULL;
png_ptr=png_create_read_struct(PNG_LIBPNG_VER_STRING,NULL,NULL,NULL);
if (!png_ptr) return FALSE;

//PNG情報構造体の取得
png_infop info_ptr=NULL;
info_ptr=png_create_info_struct(png_ptr);
if (!info_ptr){
//PNG情報構造体の取得に失敗したら、PNGのヘッダー情報を削除し
//FALSEを返して終了する。

png_destroy_read_struct(&png_ptr,NULL,NULL);
return FALSE;
}

//これ以降の内部エラーはここに戻り、FALSEを返して終了する。
png_bytep image_data=0; //PNGからピクセルデータを受け取るバッファ
png_bytepp row_pointers=0; //PNGから受け取った画像行の先頭ポインタ

if (setjmp(png_ptr->jmpbuf)) {
if (image_data) delete[]image_data;
if (row_pointers) delete[]row_pointers;
//PNGのヘッダー情報の削除
png_destroy_read_struct(&png_ptr,&info_ptr,NULL);
return FALSE;
}

//読み込み関数の設定
png_set_read_fn(png_ptr,(png_voidp)infile,(png_rw_ptr)_PngReadFunc);

//情報ヘッダの読み込み
png_read_info(png_ptr,info_ptr);

//必要な情報の取得
png_uint_32 width; //画像幅
png_uint_32 height; //画像高さ
int bit_depth; //ビット深度
int color_type; //PNG画像の形式
png_get_IHDR(png_ptr,info_ptr,&width,&height,&bit_depth,&color_type,NULL,NULL,NULL);

//パレットがあれば、パレットを読み込む(PNGパレットは、libpng側で開放する)
int num_palette=0; //色数
png_color *palette; //PNGパレット
if (color_type==PNG_COLOR_TYPE_PALETTE)
//PNGパレットを読み込む
png_get_PLTE(png_ptr,info_ptr,&palette,&num_palette);

//PNGイメージデータを読む---------------------------------------------------

//16/chanelなら8ビットにして取り出す
if (bit_depth==16) png_set_strip_16(png_ptr);

//ガンマ属性
double file_gamma;
if (png_get_gAMA(png_ptr,info_ptr,&file_gamma)){
double scrn_gamma=2.2;//windowsのシステムガンマは2.2
png_set_gamma(png_ptr,scrn_gamma,file_gamma);
}

//すべての設定を登録
png_read_update_info(png_ptr,info_ptr);

//イメージを格納するメモリーを割り当てる為の情報を取得
png_size_t rowbytes=png_get_rowbytes(png_ptr,info_ptr);//行データのバイト数
int channels=(int)png_get_channels(png_ptr, info_ptr); //チャンネル数

//イメージを格納する為のメモリーを確保
image_data=new BYTE[rowbytes*height];

//行ポインタの配列領域を確保
row_pointers=new png_bytep[height];

// 行配列にフラット領域のそれぞれの位置を設定します
for (UINT i=0;i<height;++i)
row_pointers[i]=image_data+i*rowbytes;

//イメージを読み込む
png_read_image(png_ptr,row_pointers);

//後始末
delete[]row_pointers;
png_read_end(png_ptr,NULL);

//イメージデータからDIBを作成--------------------------------------------------

//BITMAPINFO構造体の作成
DWORD dwSizeOfBitmapInfo=sizeof(BITMAPINFOHEADER)+num_palette*sizeof(RGBQUAD);

BITMAPINFO* pBmi=(BITMAPINFO*)(new BYTE[dwSizeOfBitmapInfo]);
::ZeroMemory(pBmi,dwSizeOfBitmapInfo);

pBmi->bmiHeader.biSize =sizeof(BITMAPINFOHEADER);
pBmi->bmiHeader.biWidth =width;
pBmi->bmiHeader.biHeight=height;
pBmi->bmiHeader.biPlanes=1;
pBmi->bmiHeader.biCompression=BI_RGB;

//パレットがあるときは、BITMAPINFO構造体のカラーテーブルにコピーする。
if (color_type==PNG_COLOR_TYPE_PALETTE){
pBmi->bmiHeader.biBitCount=bit_depth;
pBmi->bmiHeader.biClrUsed =num_palette;
for(int i=0;i<num_palette;i++){
pBmi->bmiColors[i].rgbBlue =palette[i].blue;
pBmi->bmiColors[i].rgbGreen=palette[i].green;
pBmi->bmiColors[i].rgbRed =palette[i].red;
}
}
else if (channels==3) pBmi->bmiHeader.biBitCount=24;
else if (channels==4) pBmi->bmiHeader.biBitCount=32;

//ビットマップの行バイト数とイメージバイト数を算出する。
DWORD widthBytes=WIDTHBYTES(pBmi->bmiHeader.biBitCount*width);
pBmi->bmiHeader.biSizeImage=widthBytes*height;

//PNGのヘッダー情報の削除
png_destroy_read_struct(&png_ptr,&info_ptr,NULL);

//DIBセクションの作成
LPVOID pvBits;
HBITMAP hBitmap=::CreateDIBSection(NULL,pBmi,DIB_RGB_COLORS,&pvBits,0,0);
delete[]pBmi;
if (!hBitmap){
delete[]image_data;
return FALSE;
}

//イメージデータのコピー
switch(channels){
case 1://パレットDIB
for (UINT i=0;i<height;i++)
::CopyMemory((PBYTE)pvBits+i*widthBytes,image_data+(height-1-i)*rowbytes,
(width*bit_depth+7)/8);
break;
case 3://24ビットRGBDIB
for (UINT i=0;i<height;i++){
PBYTE src=image_data+(height-1-i)*rowbytes;
PBYTE dst=(PBYTE)pvBits+i*widthBytes;
for(UINT j=0;j<width;j++){
*dst++=src[2]; //青
*dst++=src[1]; //緑
*dst++=src[0]; //赤
src+=3;
}
}
break;
case 4://32ビットアルファチャネル付き
BYTE r,g,b,a;
for (UINT i=0;i<height;i++){
PBYTE src=image_data+(height-1-i)*rowbytes;
PBYTE dst=(PBYTE)pvBits+i*widthBytes;
for(UINT j=0;j<width;j++){
r=*src++; //赤
g=*src++; //緑
b=*src++; //青
a=*src++; //アルファ値
*dst++=b;
*dst++=g;
*dst++=r;
*dst++=a;
}
}
break;
}
delete[]image_data;
return hBitmap;
}


hBitmapの内容が、読み込んだPNGファイルから作成したDIBのハンドルになります。

サンプルプログラム(VisualC++net2003ソリューション)PNGFileTest01A.zip

上記のzipファイルをダウンロードして、前回の「libpngのインストール」で作成した「lpng143」フォルダーと「zlib」フォルダーがある同じフォルダー内にWinRAR等を使って解凍すると、「PNGFileTest01A」と言う名のフォルダーが作成されます。



「PNGFileTest01A」フォルダー内にある「PNGFileTest.sln」を開いて「F5」キーを押すと、ビルド確認のダイヤログが表示されるので「Yes」を選択してソリューションをビルドします。

ビルドが終わると、直ちにプログラムが自動起動してウィンドウが表示されます。ウィンドウ内にあるトラッカーの範囲内で、右クリックしてポップアップメニューで「開く」を選択して、「ファイルを開く」ダイヤログを表示させます。



「res」フォルダー内の「1729_01.png」を背景画像として選択し、「開く」ボタンを押して読み込みます。画像が表示されたところで、画像をドラッグして少し下に移動させ、画像のない所を左クリックしてトラッカーのフォーカスを移動させます。



新しいトラッカーの範囲内で再び右クリックしてポップアップメニューで「開く」を選択し、「ファイルを開く」ダイヤログを表示させます。同じ要領で「res」フォルダー内にあるすべてのPNGファイルを表示させてみて下さい。



尚背景が透過されない画像は、右にある「画像のプロパティ」ダイヤログバーの「透過色を使ってマスク透過する」にチェックを入れてみて下さい。透過色が表示されたものなら、画像のマスク部分が透過されて背景が見えるようになります。



posted by ひろし at 15:38| Comment(0) | PNGファイルフォーマット | このブログの読者になる | 更新情報をチェックする
この記事へのコメント
コメントを書く
お名前:

メールアドレス:

ホームページアドレス:

コメント:

認証コード: [必須入力]


※画像の中の文字を半角で入力してください。
×

この広告は180日以上新しい記事の投稿がないブログに表示されております。