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

ビットマップをGIF画像として保存する

戻る


ビットマップをGIF画像として保存したい場合は、次のような コードを使うとよい。

#include <windows.h>

#include <stdlib.h>
#include <stdio.h>

#include <kohn_gif.h>

#pragma comment(lib, "kohn_gif.lib")

typedef struct tagBITMAPINFOEX
{
    BITMAPINFOHEADER bmiHeader;
    RGBQUAD          bmiColors[256];
} BITMAPINFOEX, FAR * LPBITMAPINFOEX;

typedef struct tagENTRY
{
    DWORD clr1;
    DWORD clr2;
    DWORD count;
} ENTRY;

static int entry_compare(const void *a, const void *b)
{
    ...
}

static void rgb_to_hsv(BYTE r, BYTE g, BYTE b, double *hue, double *saturation, double *value)
{
    ...
}

HBITMAP CreateSolid32BppBitmap(INT cx, INT cy, COLORREF clr)
{
    ...
}

BOOL AlphaBlendBitmap(HBITMAP hbm1, HBITMAP hbm2)
{
    ...
}

BOOL Save32BppBitmapAsGif(LPCSTR pszFileName, HBITMAP hbm, COLORREF clrTransparent)
{
    struct gif_info_t *gif;
    BITMAP bm;
    HBITMAP hbmBlended;
    DWORD cdw, *pdw, *pdw2;
    DWORD clr;
#define TABLE_SIZE 1024
    ENTRY table[TABLE_SIZE], *p;
    INT count, i, x, y;
    VOID *pvBits;
    BYTE *pb;
    INT iNearest, widthbytes;
    double norm, nearest_norm;
    double h1, s1, v1, h2, s2, v2, dh;
    BOOL fAlpha;

    if (!GetObject(hbm, sizeof(BITMAP), &bm))
        return FALSE;

    fAlpha = (bm.bmBitsPixel == 32);

    gif = kgif_alloc_compress();
    if (gif == NULL)
        return FALSE;

    gif->fp = fopen(pszFileName, "wb");
    if (gif->fp == NULL)
    {
        kgif_info_free(gif);
        return FALSE;
    }

    gif->width = bm.bmWidth;
    gif->height = bm.bmHeight;

    if (clrTransparent == CLR_INVALID)
        clrTransparent = RGB(255, 255, 255);

    hbmBlended = CreateSolid32BppBitmap(bm.bmWidth, bm.bmHeight, clrTransparent);
    if (hbmBlended == NULL)
    {
        fclose(gif->fp);
        kgif_info_free(gif);
        DeleteFile(pszFileName);
        return FALSE;
    }

    AlphaBlendBitmap(hbmBlended, hbm);

    count = 0;
    GetObject(hbmBlended, sizeof(BITMAP), &bm);
    pdw = (DWORD *)bm.bmBits;
    cdw = bm.bmWidth * bm.bmHeight;
    while(cdw--)
    {
        clr = *pdw++ & 0xFFFFFF;  /* BGR */
        table[count].clr1 = clr;
        clr &= 0xF0F0F0;
        table[count].clr2 = clr;
        table[count].count = 0;
        p = table;
        while(clr != p->clr2)
            p++;
        p->count += 1;
        if (p == &table[count] && count < TABLE_SIZE - 1)
            count++;
    }
    qsort(table, count, sizeof(ENTRY), entry_compare);
    if (count > 255)
        count = 255;
    gif->colors = count + 1;
    gif->transparency_index = (fAlpha ? count : -1);

    for(i = 0; i < count; i++)
        gif->color_map[i] = table[i].clr1;
    gif->color_map[count] = 
        (GetRValue(clrTransparent) << 16) |
        (GetGValue(clrTransparent) << 8) |
        GetBValue(clrTransparent);

    pdw = (DWORD *)bm.bmBits;
    if (fAlpha && GetObject(hbm, sizeof(BITMAP), &bm))
        pdw2 = (DWORD *)bm.bmBits;
#define WIDTHBYTES(i) (((i) + 31) / 32 * 4)
    widthbytes = WIDTHBYTES(bm.bmWidth * 8);
    if (kgif_write_header(gif) != 0 || 
        (pvBits = malloc(widthbytes * bm.bmHeight)) == NULL)
    {
        fclose(gif->fp);
        kgif_info_free(gif);
        DeleteFile(pszFileName);
        DeleteObject(hbmBlended);
        return FALSE;
    }

    for(y = 0; y < bm.bmHeight; y++)
    {
        pb = (BYTE *)pvBits + widthbytes * y;
        for(x = 0; x < bm.bmWidth; x++)
        {
            if (fAlpha && (BYTE)(*pdw2 >> 24) == 0)
            {
                *pb = count;
            }
            else
            {
                clr = *pdw & 0xF0F0F0;
                for(i = 0; i < count; i++)
                {
                    if (table[i].clr2 == clr)
                    {
                        *pb = i;
                        break;
                    }
                }
                if (i == count)
                {
                    rgb_to_hsv((BYTE)(clr >> 16), (BYTE)(clr >> 8), (BYTE)clr, 
                        &h1, &s1, &v1);
                    iNearest = 0;
                    nearest_norm = 100000.0;
                    for(i = 0; i < count; i++)
                    {
                        rgb_to_hsv(
                            (BYTE)(table[i].clr1 >> 16), 
                            (BYTE)(table[i].clr1 >> 8), 
                            (BYTE)table[i].clr1, &h2, &s2, &v2);
                        dh = fabs(h2 - h1);
                        if (dh > 0.5)
                            dh = 1.0 - dh;
                        norm = dh * dh + (s2 - s1) * (s2 - s1) + (v2 - v1) * (v2 - v1);
                        if (norm < nearest_norm)
                        {
                            iNearest = i;
                            nearest_norm = norm;
                            if (norm == 0.0)
                                break;
                        }
                    }
                    *pb = iNearest;
                }
            }
            pdw++;
            pdw2++;
            pb++;
        }
    }
    for(y = bm.bmHeight - 1; y >= 0; y--)
    {
        pb = (BYTE *)pvBits + widthbytes * y;
        kgif_compress_block(gif, pb, gif->width);
    }
    free(pvBits);
    DeleteObject(hbmBlended);
    kgif_write_footer(gif);
    fclose(gif->fp);
    kgif_info_free(gif);
    return TRUE;
}

#ifdef UNITTEST
HBITMAP CopyAs32BppBitmap(HBITMAP hbm)
{
    ...
}

int main(void)
{
#ifndef LR_LOADREALSIZE
#define LR_LOADREALSIZE 128
#endif
    HBITMAP hbm = LoadImage(NULL, "a.bmp", IMAGE_BITMAP, 0, 0, 
                            LR_LOADFROMFILE | LR_LOADREALSIZE | 
                            LR_CREATEDIBSECTION);
    HBITMAP hbm32Bpp = CopyAs32BppBitmap(hbm);
    Save32BppBitmapAsGif("a.gif", hbm32Bpp, RGB(255, 0, 0));
    DeleteObject(hbm);
    DeleteObject(hbm32Bpp);
    return 0;
}
#endif  /* def UNITTEST */

GIF形式は、256色(8bpp (bits per pixel)) のビットしか受け付けないので、 一般には256色への減色処理が必要である。このコードでは、よく使われている色を使用頻度の 統計によって選び分ける。0xF0F0F0でマスクを行っているのは、だいたいの色を 求めているのに他ならない。つまりは、色の各RGB成分が0x0Fぐらい異なっても 同一視しているのである。

途中でAlphaBlendを行っているのは、引数に32bppのビットマップ(アルファチャンネル付きの ビットマップ)が与えられたときの半透明のピクセルの処理のためである。 clrTransparentの値は、半透明のピクセルに混ぜる色を指定する。 clrTransparentには、GIF読み込みのときに得られる色の値を使うといい。

rgb_to_hsv関数は、RGB色空間からHSV色空間への変換を行う。HSVは色がどれくらい 違うかを求めるのに便利である。

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

ソース: savegif.zip


戻る

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

inserted by FC2 system