ソフトウェア開発 Win32プログラミング

埋め込みPNG画像を表示する

戻る

※ アルファブレンドをさらに高速化しました。


PNG画像はフルカラーが扱え、かつ、半透明を表現できる大変自由度の高い画像形式にも かかわらず、ファイルサイズが比較的小さい特徴を持っている。 従来のビットマップ画像を使うのをやめてPNG画像にすれば、ウィンドウのデザインの自由度を高めたまま、 ウィンドウの見た目を劇的に向上させることができる。

今回は、PNG画像をリソースとして実行可能ファイルに埋め込み、それをウィンドウに表示する方法について解説する。

まず最初にヘッダーファイルpngres.hにPNG画像を埋め込むためのマクロを定義する。

#define PNG 256
#define RT_PNG MAKEINTRESOURCE(PNG)

次にリソースファイルにPNG画像を埋め込むコードを記述する。

#include <windows.h>
#include "pngres.h"

LANGUAGE LANG_JAPANESE, SUBLANG_DEFAULT
 
101 PNG "bar.png"
102 PNG "star.png"
103 PNG "titletext.png"
104 PNG "menu1.png"
105 PNG "menu2.png"
106 PNG "menu3.png"
107 PNG "menubutton.png"

そして、埋め込まれたPNG画像を読み込むコードをファイルpngres.cppに記述する。

#include <windows.h>
#include <png.h>
 
#include "pngres.h"
#include "ablend.h"
 
#define WIDTHBYTES(i) (((i) + 31) / 32 * 4)
 
#pragma comment(lib, "zlib.lib")
#pragma comment(lib, "libpng.lib")
#pragma comment(lib, "msimg32.lib")

struct PngMemory
{
    LPBYTE m_pb;
    INT m_i;
    INT m_cb;
    PngMemory(LPVOID pv, INT i, INT cb)
    : m_pb((LPBYTE)pv), m_i(i), m_cb(cb)
    {
    }
};
 
static void PngReadProc(png_structp png, png_bytep data, png_size_t length)
{
    PngMemory *pngmem = (PngMemory *)png_get_io_ptr(png);
    CopyMemory(data, pngmem->m_pb + pngmem->m_i, length);
    pngmem->m_i += length;
}
 
static HBITMAP LoadPngAsBitmapFromMemory(LPVOID pv, INT cb)
{
    HBITMAP         hbm;
    png_structp     png;
    png_infop       info;
    png_uint_32     y, width, height, rowbytes;
    int             color_type, depth, widthbytes;
    double          gamma;
    BITMAPINFO      bi;
    LPBYTE          pbBits;
    PngMemory       pngmem(pv, 0, cb);
    png_bytepp      row_pointers;
 
    png = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
    if (png == NULL)
    {
        return NULL;
    }
 
    info = png_create_info_struct(png);
    if (info == NULL || setjmp(png_jmpbuf(png)))
    {
        png_destroy_read_struct(&png, NULL, NULL);
        return NULL;
    }
 
    if (setjmp(png_jmpbuf(png)))
    {
        png_destroy_read_struct(&png, &info, NULL);
        return NULL;
    }
 
    png_set_read_fn(png, &pngmem, PngReadProc);
    png_read_info(png, info);
 
    png_get_IHDR(png, info, &width, &height, &depth, &color_type,
                 NULL, NULL, NULL);
    png_set_expand(png);
    if (png_get_valid(png, info, PNG_INFO_tRNS))
        png_set_tRNS_to_alpha(png);
    png_set_strip_16(png);
    png_set_gray_to_rgb(png);
    png_set_palette_to_rgb(png);
    png_set_bgr(png);
    png_set_packing(png);
    if (png_get_gAMA(png, info, &gamma))
        png_set_gamma(png, 2.2, gamma);
    else
        png_set_gamma(png, 2.2, 0.45455);
 
    png_read_update_info(png, info);
    png_get_IHDR(png, info, &width, &height, &depth, &color_type,
                 NULL, NULL, NULL);
 
    rowbytes = png_get_rowbytes(png, info);
    row_pointers = (png_bytepp)malloc(height * png_sizeof(png_bytep));
    for (y = 0; y < height; y++)
    {
        row_pointers[y] = (png_bytep)png_malloc(png, rowbytes);
    }
 
    png_read_image(png, row_pointers);
    png_read_end(png, NULL);
 
    ZeroMemory(&bi.bmiHeader, sizeof(BITMAPINFOHEADER));
    bi.bmiHeader.biSize        = sizeof(BITMAPINFOHEADER);
    bi.bmiHeader.biWidth       = width;
    bi.bmiHeader.biHeight      = height;
    bi.bmiHeader.biPlanes      = 1;
    bi.bmiHeader.biBitCount    = (WORD)(depth * png_get_channels(png, info));
 
    hbm = CreateDIBSection(NULL, &bi, DIB_RGB_COLORS, (VOID **)&pbBits,
                           NULL, 0);
    if (hbm == NULL)
    {
        png_destroy_read_struct(&png, &info, NULL);
        return NULL;
    }
 
    widthbytes = WIDTHBYTES(width * bi.bmiHeader.biBitCount);
    for(y = 0; y < height; y++)
    {
        CopyMemory(pbBits + y * widthbytes,
                   row_pointers[height - 1 - y], rowbytes);
    }
 
    png_destroy_read_struct(&png, &info, NULL);
    free(row_pointers);
    return hbm;
}
 
HBITMAP LoadPngAsBitmapFromResource(HINSTANCE hInstance, LPCTSTR pszName)
{
    HRSRC hRsrc;
    HGLOBAL hGlobal;
    DWORD Size;
    HBITMAP hbm;
    
    LPVOID lpData;
 
    hRsrc = FindResource(hInstance, pszName, RT_PNG);
    if (hRsrc == NULL)
        return NULL;
 
    Size = SizeofResource(hInstance, hRsrc);
    hGlobal = LoadResource(hInstance, hRsrc);
    if (hGlobal == NULL)
        return NULL;
 
    lpData = LockResource(hGlobal);
    hbm = LoadPngAsBitmapFromMemory(lpData, Size);
    UnlockResource(hGlobal);
    FreeResource(hGlobal);
 
    return hbm;
}

読み込んだ画像を表示するコードは以下の通り。

VOID PngRes::Draw(HDC hdc, INT x, INT y)
{
    BITMAP bm;
    BLENDFUNCTION bf;
 
    if (!GetObject(m_hbm, sizeof(BITMAP), &bm))
        return;
 
    HDC hdcMem = CreateCompatibleDC(NULL);
    if (hdcMem != NULL)
    {
        HGDIOBJ hbmOld = SelectObject(hdcMem, m_hbm);
        if (bm.bmBitsPixel == 32)
        {
            bf.BlendOp = AC_SRC_OVER;
            bf.BlendFlags = 0;
            bf.SourceConstantAlpha = 255;
            bf.AlphaFormat = AC_SRC_ALPHA;
            MyAlphaBlend(hdc, x, y, bm.bmWidth, bm.bmHeight, hdcMem, 0, 0);
        }
        else
        {
            BitBlt(hdc, 0, 0, bm.bmWidth, bm.bmHeight,
                   hdcMem, 0, 0, SRCCOPY);
        }
        SelectObject(hdcMem, hbmOld);
        DeleteDC(hdcMem);
    }
}

MyAlphaBlendは、以下のようなアルファブレンド(透過処理)を行う関数である。 ここでなぜAlphaBlend APIを使わないのかと思う人もいるかもしれないが、 PNGの透過画像にAlphaBlend APIを使うのは間違っている。詳しくは ここを参照せよ。

※2014年7月11日訂正:積算済み形式にすれば、AlphaBlendで描画することができるが、ここでは説明しない。

#if !defined(DEBUG) && !defined(NDEBUG)
    #define NDEBUG
#endif
 
#include <windows.h>
#include <assert.h>

static void APIENTRY MyAlphaBlendBlt(
    LPBITMAP pbm, INT x, INT y, INT cx, INT cy,
    LPBITMAP pbmSrc, INT xSrc, INT ySrc)
{
    register LPBYTE dest, src;
    register INT ccx, ccy;
 
    assert(pbm->bmBitsPixel == 24 || pbm->bmBitsPixel == 32);
    assert(pbmSrc->bmBitsPixel == 32);
 
    /* はみ出た領域があれば幅と高さと位置を修正する */
    if (x < 0)
    {
        cx += x;
        xSrc += -x;
        x = 0;
    }
    cx = min(cx, (INT)pbm->bmWidth - x);
    if (xSrc < 0)
    {
        cx += xSrc;
        x += -xSrc;
        xSrc = 0;
    }
    cx = min(cx, (INT)pbmSrc->bmWidth - xSrc);
    if (y < 0)
    {
        cy += y;
        ySrc += -y;
        y = 0;
    }
    cy = min(cy, (INT)pbm->bmHeight - y);
    if (ySrc < 0)
    {
        cy += ySrc;
        y += -ySrc;
        ySrc = 0;
    }
    cy = min(cy, (INT)pbmSrc->bmHeight - ySrc);
 
    /* 転送先が無効な領域か調べる */
    if (cx <= 0 || x >= pbm->bmWidth || xSrc >= pbmSrc->bmWidth ||
        cy <= 0 || y >= pbm->bmHeight || ySrc >= pbmSrc->bmHeight)
        return;
 
    src = (LPBYTE)pbmSrc->bmBits + (xSrc << 2) +
          (pbmSrc->bmHeight - ySrc - 1) * pbmSrc->bmWidthBytes;
    ccy = cy;
    if (pbm->bmBitsPixel == 32)
    {
        dest = (LPBYTE)pbm->bmBits + (x << 2) +
               (pbm->bmHeight - y - 1) * pbm->bmWidthBytes;
        while(ccy--)
        {
            ccx = cx;
            while(ccx--)
            {
                CONST BYTE srcalpha = src[3];
                *dest++ += (*src++ - *dest) * srcalpha >> 8;
                *dest++ += (*src++ - *dest) * srcalpha >> 8;
                *dest++ += (*src++ - *dest) * srcalpha >> 8;
                *dest++ = srcalpha + (255 - srcalpha) * *dest >> 8;
                src++;
            }
            dest -= pbm->bmWidthBytes + (cx << 2);
            src -= pbmSrc->bmWidthBytes + (cx << 2);
        }
    }
    else
    {
        dest = (LPBYTE)pbm->bmBits + x * 3 +
               (pbm->bmHeight - y - 1) * pbm->bmWidthBytes;
        while(ccy--)
        {
            ccx = cx;
            while(ccx--)
            {
                CONST BYTE srcalpha = src[3];
                *dest++ += (*src++ - *dest) * srcalpha >> 8;
                *dest++ += (*src++ - *dest) * srcalpha >> 8;
                *dest++ += (*src++ - *dest) * srcalpha >> 8;
                src++;
            }
            dest -= pbm->bmWidthBytes + cx * 3;
            src -= pbmSrc->bmWidthBytes + (cx << 2);
        }
    }
}
 
// 注意:
// hdcは、24bppか32bppのDIBを選択しておく必要がある。
// hdcSrcは、32bppのDIBを選択しておく必要がある。
// 2つのビットマップはボトムアップ(biHeight > 0)でなければならない。
VOID APIENTRY MyAlphaBlend(
    HDC hdc, INT x, INT y, INT cx, INT cy,
    HDC hdcSrc, INT xSrc, INT ySrc)
{
    HBITMAP hbm, hbmSrc, hbmDummy;
    BITMAP bm, bmSrc;
 
    hbmSrc = (HBITMAP)GetCurrentObject(hdcSrc, OBJ_BITMAP);
    assert(hbmSrc != NULL);
    GetObject(hbmSrc, sizeof(bmSrc), &bmSrc);
    assert(bmSrc.bmBitsPixel == 32);
 
    hbmDummy = CreateBitmap(1, 1, 1, 1, NULL);
    if (hbmDummy != NULL)
    {
        hbm = (HBITMAP)SelectObject(hdc, hbmDummy);
        assert(hbm != NULL);
        GetObject(hbm, sizeof(bm), &bm);
        assert(bm.bmBitsPixel == 24 || bm.bmBitsPixel == 32);
 
        MyAlphaBlendBlt(&bm, x, y, cx, cy, &bmSrc, xSrc, ySrc);
        DeleteObject(SelectObject(hdc, hbm));
    }
}

なお、このコードをコンパイルするには、libpngとzlibライブラリが必要だ。

ソース: pngres.zip


戻る

©片山博文MZ
katayama.hirofumi.mz@gmail.com

inserted by FC2 system