006-04  前へ←  ホームへ  →次へ  007-02

No.007 - 01  画像操作の基本


画像操作の基本を簡単に説明します。


・ビットマップ DDB と DIB
DDB も DIB もビットマップです。
その差はデバイス依存か非デバイス依存かということだけ……なんつってもなんのことやらわかりません。(筆者はピンとこなかった)
要は
DDB は BitBlt とかの hdc を使うAPIでしか画像操作できない直接ピクセル操作ができない)。
DIB は BitBlt とか使える以外にも直接ピクセル操作ができる
ということです。

DDB( デバイス依存ビットマップ <Dvice Dependent Bitmap>)
DIB( 非デバイス依存ビットマップ <Dvice Independent Bitmap>)…独立型ビットマップと呼ぶこともある


・ピクセル操作できるCreateDibSection
ビットマップを作成するAPIは数あれど、つまるところ
きちんと画像操作をするにはCreateDibSectionしかありません。
これ以外の関数は直接ピクセルへのポインタが取得できないからです。
SetDIBits() 関数もピクセル操作できますが、遅すぎて話にならない)

ピクセル・ポインタが取得できるのはCreateDibSectionだけ!(ジャンプ風)


・色数を決めるビット深度
ビットマップに限らず多くの画像データにはビット深度という概念があります。
1ピクセルに何ビット使うかということです。
1ビットならモノクロ画像、24ビットならフルカラーです。
8ビット以下は256色以下のパレットデータを作成し、そのパレット番号が各ピクセルの値になります。
16ビット以上は各ビットを赤、緑、青のRGBにわけて使います。
パレット型
1ビット 2色 1バイト=8ピクセル
4ビット 16色 1バイト=4ピクセル
8ビット 256色 1バイト=1ピクセル

RGB型
16ビット 65536色 ハイカラー 2バイト=1ピクセル(R,G,B各5bit…1bitは余り、またはGが6bitのときもあり)
24ビット - フルカラー 3バイト=1ピクセル(R,G,B各1バイト、最も一般的なビット深度)
32ビット - トゥルーカラー 4バイト=1ピクセル(R,G,B各1バイト+予備1バイト…アルファ値の予定だったらしい)

16bit のGが6bitのときもあるというはカラーマスクで判定します。でもそういうのに出会ったことが無いので下記サンプルではやってません。
緑が多いというのは人間は緑色に敏感らしいからです。蛍光灯が少し緑がかっているという話もあります。
最近のプラズマテレビは64bitのものもあるらしい。拡張の準備はしておいたほうがよさそうです。

CreateDibSectionはビット深度を「ビットカウント bitCount」と表現しています


・パレットと、HDC, HBITMAP
DIB をWinows がビットマップ画像として扱うためのハンドルHBITMAPがあります。
HBITMAPは、ほとんどの場合HDCと関連付けるためだけに使われてるようです。

8BIT以下のDIBを扱うときパレットを扱います。パレットは256色以下のRGB配列データです。
で、8BIT以下のDIBはこの256色以下のRGB配列データの配列番号が各ピクセルの値となっているわけですが、
DIBのパレットとHDCのパレットが違うときが大変です。

各ピクセルの値はパレットの配列番号なので、パレットが違うと色も変わります。
そこでDIBのパレットとHDCのパレットをあわせるためSetDIBColorTable()というAPI関数を使っています。
DIBのパレットに変更があるたびHDCのパレットも更新してやります。


・ピクセル配列はボトムアップ方式
ピクセル配列は底辺行からスタートします。

なぜそんな仕様になっているかというと昔、IBMで仕様決める人が「数学の座標系とかは↑が正値なんだからそれにあわせよう」
とかなんとか言ったらしく現在に至るってコトらしいです。
今となっては迷惑な話ですが、筆者も同様の立場なら同じ行動をとったかも知れません。プログラムと数学の絆は深いので。

また、いろんな書籍があるなかで
「DIB情報ヘッダの高さをマイナス値にすると、トップダウンになってピクセル配列が上辺からスタートするよ」と書いてる本もありますが
DIB情報ヘッダの高さをマイナス値にしてはいけません。
環境によっては対応してません。ていうか、うちのWin98でやったらフリーズしました。


CreateDibSection の大きなワナ
CreateDibSectionにはおおきな不満があります。
リソースです。
CreateDibSectionリソースを食います。

XP 等ではさしたる問題ではないのかもしれません?が、Win98、Win95では大問題のようです。
Win98、Win95ではリソースは2MBまでという制約があるらしいのです。
現実、CreateDibSectionを使った一定以上のメモリを使うアプリケーションを作ったらWin98ではフリーズ、XPは問題なし、という現象が起こりました。実験はマシンのメモリはWin98は312Mで、XPは256Mという環境で行いました。


・CreateDibSection の小さいワナ
Windowsなのでしょーもない仕様がいくつか過去の遺物的に残ってます。
・横幅は4の倍数でなければならない。
・ピクセル・ビットは画像の下辺行からスタートする配列である。
・32ビット画像の4バイト目には意味のあるデータがあるとは限らない(ていうかない)
・32ビット画像の4バイト目はWindowsが使うかもしれない(不明)

サンプルです。
簡単なDIB クラスを作成しています。マウスで点を書けます
各クラス名がGT_〜となっていますがGeneral Template の略です。

  
 ビット深度が8以下のDIBとそれより大きいものでは色の扱いが違うので
 DIBを作成したときあらためて色を選びなおしてやる必要があります。


resource_01.h

#define IDM_MAKE_DIB_0          1000
#define IDM_MAKE_DIB_1          1001
#define IDM_MAKE_DIB_4          1004
#define IDM_MAKE_DIB_8          1008
#define IDM_MAKE_DIB_16         1016
#define IDM_MAKE_DIB_24         1024
#define IDM_MAKE_DIB_32         1032

#define IDM_COL_1               3001
#define IDM_COL_2               3002
#define IDM_COL_3               3003
#define IDM_COL_4               3004
#define IDM_COL_5               3005
#define IDM_COL_6               3006


graphic_01.rc

#include "resource_01.h"

//リソーススクリプト
GT_DIB_MENU MENU {
    POPUP "ファイル" {
        POPUP "作成" {
            MENUITEM "1 bit" , IDM_MAKE_DIB_1
            MENUITEM "4 bit" , IDM_MAKE_DIB_4
            MENUITEM "8 bit" , IDM_MAKE_DIB_8
            MENUITEM "16 bit", IDM_MAKE_DIB_16
            MENUITEM "24 bit", IDM_MAKE_DIB_24
            MENUITEM "32 bit", IDM_MAKE_DIB_32
        }
    }
    POPUP "色" {
        MENUITEM "色1",     IDM_COL_1
        MENUITEM "色2",     IDM_COL_2
        MENUITEM "色3",     IDM_COL_3
        MENUITEM "色4",     IDM_COL_4
        MENUITEM "色5",     IDM_COL_5
        MENUITEM "色6",     IDM_COL_6
    }
}



graphic_01.cpp

#include <stdio.h>
#include <windows.h>
#include "resource_01.h"

// ====== GT_COL ==========================
class GT_COL {
        public :
        BYTE m_r, m_g, m_b;
        GT_COL(){ m_r=m_g=m_b=0; }
};
// ====== GT_COL_TABLE ==========================
class GT_COL_TABLE {
        public :
        GT_COL *m_cols;
        int m_cols_len;
        int m_using_cols_len;
        GT_COL_TABLE(){
                m_cols=NULL;
                m_cols_len=0;
                m_using_cols_len=0;
        }
        ~GT_COL_TABLE(){ destroy(); }
        make(int col_size){
                destroy();
                if (!col_size) return -1;
                m_cols = new GT_COL[col_size];
                m_cols_len = col_size;
                m_using_cols_len=0;
        }
        destroy(){
                if (m_cols) delete[] m_cols;
                m_cols = NULL;
                m_using_cols_len=0;
                m_cols_len = 0;
        };
        template <int bit> BYTE get_col_index(BYTE r, BYTE g, BYTE b);
        add_col(BYTE r, BYTE g, BYTE b);
        void set_col(BYTE index, BYTE r, BYTE g, BYTE b);
};
// 近似色をパレットから取得
template <int bit>
BYTE GT_COL_TABLE::get_col_index(BYTE r, BYTE g, BYTE b){
        GT_COL *pcol;
        if (!m_cols) return -1;
        int i, min_apx=255*255*3+1, apx, min_apx_index;
        min_apx_index=0;
        for(i=0;i<m_using_cols_len;i++){
                pcol=&m_cols[i];
                apx=(r-pcol->m_r)*(r-pcol->m_r)+(g-pcol->m_g)*(g-pcol->m_g)+(b-pcol->m_b)*(b-pcol->m_b);
                if (apx<min_apx){
                        min_apx_index=i;
                        min_apx=apx;
                }
        }
        i=1<<bit;
        if (bit && min_apx>(255*255*3/(i*i)) && m_using_cols_len<m_cols_len){
                min_apx_index = add_col(r, g, b);
        }
        return min_apx_index;
}
// パレットに追加
GT_COL_TABLE::add_col(BYTE r, BYTE g, BYTE b){
        GT_COL *pcol;
        int res_index;
        if (m_using_cols_len>=m_cols_len) return -1;
        pcol=&m_cols[m_using_cols_len];
        pcol->m_r=r;
        pcol->m_g=g;
        pcol->m_b=b;
        res_index=m_using_cols_len;
        m_using_cols_len++;
        return res_index;
}
// パレットに色設定
inline void GT_COL_TABLE::set_col(BYTE index, BYTE r, BYTE g, BYTE b){
        if (m_cols_len<=index) return ;
        GT_COL *pcol;
        pcol=&(m_cols[index]);
        pcol->m_r = r;
        pcol->m_g = g;
        pcol->m_b = b;
}

// ======== GT_DIB ==================================
class GT_DIB {
        public :
        GT_COL_TABLE m_col_table;
        unsigned long *m_col_mask;
        char *m_alpha;
        HDC m_hdc;
        HBITMAP m_hbmp;

        int m_width, m_height, m_byte_width, m_bit_count;
        BYTE *m_pbits;

        GT_DIB();
        ~GT_DIB();

        // 作成と破棄
        virtual create(int cx, int cy, int a_bit_count, int a_color_size=0);
        virtual destroy();

        // ピクセル描画
        set_pixel(int x, int y, DWORD col);
        DWORD get_pixel(int x, int y);

        // アルファ値
        is_alpha(){ return (m_alpha!=NULL); }

        // カラー
        void set_pal_col(BYTE index, BYTE r, BYTE g, BYTE b);
        apply_palette_all();

        // DC 適用
        apply_dc(HDC hdc);
        apply_dc(HWND hwnd);
};

// コンストラクタ
GT_DIB::GT_DIB(){
        m_col_mask =NULL;
        m_alpha =NULL;
        m_width = m_height = m_byte_width = m_bit_count =0;
        m_pbits =NULL;

        m_hdc = NULL;
        m_hbmp = NULL;
}
// デストラクタ
GT_DIB::~GT_DIB(){
        destroy();
}
// GT_DIB 破棄
GT_DIB::destroy(){
        m_width=0;
        m_height=0;
        m_byte_width=0;
        m_bit_count=0;
        if (m_col_mask) delete[] m_col_mask;
        m_col_mask = NULL;
        m_col_table.destroy();

        if (m_hdc)              { DeleteDC(m_hdc);      m_hdc=NULL; }
        if (m_hbmp)             { DeleteObject(m_hbmp); m_hbmp=NULL; }
        m_pbits = NULL;
        return 0;
};
// 1点ピクセル取得
DWORD GT_DIB::get_pixel(int x, int y){
        if (!m_pbits) return 0;
        if (x<0 || x>=m_width || y<0 || y>=m_height) return 0;
        BYTE *ppix;
        ppix=m_pbits+(m_height-y-1)*m_byte_width + (x*m_bit_count>>3);

        switch(m_bit_count){
        case 1: return 0x01 & (*ppix >> (7-(x&7)));
        case 4: return 0x0f & (*ppix >> ((x&1)?0:4));
        case 8: return *ppix ;
        case 16: return *(WORD*)ppix;
        case 24: return 0x00FFffFF & *(DWORD*)ppix;
        case 32: return *(DWORD*)ppix ;
        }
        return 0;
}
// 1点ピクセル書きこみ
GT_DIB::set_pixel(int x, int y, DWORD col){
        if (!m_pbits) return 0;
        if (x<0 || x>=m_width || y<0 || y>=m_height) return 0;
        BYTE *ppix;
        ppix=m_pbits+(m_height-y-1)*m_byte_width + (x*m_bit_count>>3);

        switch(m_bit_count){
        case 1: *ppix &= ~(1             <<(7-(x&7)));
                        *ppix |= ((bool)col) <<(7-(x&7));
                        break;
        case 4: *ppix &= 0x0f << ((x&1)?4:0);
                        *ppix |= (0x0f & col)  << ((x&1)?0:4);
                        break;
        case 8: *ppix = (BYTE)col;                                                      break;
        case 16: *(WORD*)ppix = (WORD)col;                                      break;
        case 24: *(RGBTRIPLE*)ppix = *(RGBTRIPLE*)&col;         break;
        case 32: *(DWORD*)ppix = *(DWORD*)&col;                         break;
        }
        return 0;
}

// 作成
GT_DIB::create(int cx, int cy, int a_bit_count, int a_color_size){
        BITMAPINFO *bmp_info;
        int i, palette_entry, info_size;
        destroy();      // 作成済みメモリを破棄する

        if (cx<=0 || cy<=0) return -1;
        if (a_bit_count!=1 && a_bit_count!=4 && a_bit_count!=8
                && a_bit_count!=16 && a_bit_count!=24 && a_bit_count!=32) return -1;

        palette_entry=1;
        if              (a_color_size!=0) palette_entry=a_color_size;
        else if (a_bit_count<=8)  palette_entry=1<<a_bit_count; // 8bit以下でパレット色数指定無しなら

        info_size=sizeof(BITMAPINFOHEADER) + (palette_entry-1)*sizeof(RGBQUAD)+1;
        bmp_info = (BITMAPINFO*)malloc(info_size);
        if (!bmp_info) return -1;
        ZeroMemory(bmp_info, info_size);

        // 適当な基本色
        RGBTRIPLE rgb[8]={
                {0,0,0},{255, 255, 255}, {0,0,255}, {255,0,0}, {0,255,0}, {255, 255,0}, {255,0,255},{0,255,255}
        };

        m_col_table.make(palette_entry);
        for(i=0;i<min(palette_entry, 8);i++){
                bmp_info->bmiColors[i].rgbBlue  = rgb[i].rgbtBlue;
                bmp_info->bmiColors[i].rgbGreen = rgb[i].rgbtGreen;
                bmp_info->bmiColors[i].rgbRed   = rgb[i].rgbtRed;
                bmp_info->bmiColors[i].rgbReserved = 0;
                m_col_table.add_col(rgb[i].rgbtRed, rgb[i].rgbtGreen, rgb[i].rgbtBlue);
        }

        /* BITMAPINFO構造体設定 */
        bmp_info->bmiHeader.biSize=sizeof(BITMAPINFOHEADER);
        bmp_info->bmiHeader.biWidth=cx;
        bmp_info->bmiHeader.biHeight=cy;
        bmp_info->bmiHeader.biPlanes=1;
        bmp_info->bmiHeader.biBitCount=a_bit_count;
        bmp_info->bmiHeader.biCompression=BI_RGB;
        bmp_info->bmiHeader.biSizeImage=0;
        bmp_info->bmiHeader.biXPelsPerMeter=0;
        bmp_info->bmiHeader.biYPelsPerMeter=0;
        bmp_info->bmiHeader.biClrUsed=palette_entry;
        bmp_info->bmiHeader.biClrImportant=0;

        m_hbmp = CreateDIBSection(NULL, bmp_info, DIB_RGB_COLORS, (VOID**)&m_pbits, NULL, 0);
        if (!m_hbmp) {
                free(bmp_info);
                return -1;
        }
        m_width= cx;
        m_height=cy;
        m_bit_count=a_bit_count;

        /* バッファの1ラインの長さを計算 */
        m_byte_width=4*((m_width*m_bit_count+31)/32);
        ZeroMemory(m_pbits, m_byte_width*m_height);

        free(bmp_info);
        return 0;
}

// HDCパレット設定(全て)
GT_DIB::apply_palette_all(){
        if (!m_col_table.m_cols_len) return -1;
        RGBQUAD *p_rgb, *rgb_table;
        rgb_table=new RGBQUAD[m_col_table.m_cols_len];
        for (int i=0;i<m_col_table.m_cols_len;i++){
                p_rgb=&(rgb_table[i]);
                p_rgb->rgbRed   =m_col_table.m_cols[i].m_r;
                p_rgb->rgbGreen =m_col_table.m_cols[i].m_g;
                p_rgb->rgbBlue  =m_col_table.m_cols[i].m_b;
                p_rgb->rgbReserved=0;
        }
        if (m_hdc) SetDIBColorTable(m_hdc, 0, m_col_table.m_cols_len, rgb_table);
        delete[] rgb_table;
        return 0;
}
// パレットに色設定
inline void GT_DIB::set_pal_col(BYTE index, BYTE r, BYTE g, BYTE b){
        if (m_col_table.m_cols_len<=index) return ;
        m_col_table.set_col(index, r, g, b);

        RGBQUAD rgb;
        rgb.rgbRed              =r;
        rgb.rgbGreen    =g;
        rgb.rgbBlue             =b;
        rgb.rgbReserved =0;
        if (m_hdc) SetDIBColorTable(m_hdc, index, 1, &rgb);
}

// DC有効化
GT_DIB::apply_dc(HDC hdc){
        if (m_hdc) { DeleteDC(m_hdc);   m_hdc=NULL; }
        m_hdc = CreateCompatibleDC(hdc);
        SelectObject(m_hdc, m_hbmp);
        return 0;
}
// DC有効化(HWND hwnd) 
GT_DIB::apply_dc(HWND hwnd){
        if (m_hdc)              { DeleteDC(m_hdc);      m_hdc=NULL; }
        HDC hwnd_hdc = GetDC(hwnd);
        m_hdc = CreateCompatibleDC(hwnd_hdc);
        SelectObject(m_hdc, m_hbmp);
        ReleaseDC(hwnd, hwnd_hdc);
        return 0;
}



GT_DIB gt_dib;
DWORD col=0xffffff;

// メニュー
on_command(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp){
        static CHOOSECOLOR cc = {0};
        static DWORD col_costom[8] = {0};
        switch(LOWORD(wp)) {
        case IDM_MAKE_DIB_0:
        case IDM_MAKE_DIB_1:
        case IDM_MAKE_DIB_4:
        case IDM_MAKE_DIB_8:
        case IDM_MAKE_DIB_16:
        case IDM_MAKE_DIB_24:
        case IDM_MAKE_DIB_32:
                gt_dib.create(200, 150, LOWORD(wp)-IDM_MAKE_DIB_0, 0);
                gt_dib.apply_dc(hwnd);
                InvalidateRect(hwnd, NULL, 0);
                break;
        case IDM_COL_1: col= 0; break;
        case IDM_COL_2: col= (gt_dib.m_bit_count<=8)? 1 : 0xffffff;     break;
        case IDM_COL_3: col= (gt_dib.m_bit_count<=8)? 2 : 0x0000ff;     break;
        case IDM_COL_4: col= (gt_dib.m_bit_count<=8)? 3 : 0xff0000;     break;
        case IDM_COL_5: col= (gt_dib.m_bit_count<=8)? 4 : 0x00ff00;     break;
        case IDM_COL_6: col= (gt_dib.m_bit_count<=8)? 5 : 0xffff00;     break;
        }
}
LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp) {
        HDC hdc;
        PAINTSTRUCT ps;
        POINT po;
        static int mou_down=0;
        static char s[50];

        switch (msg) {
        case WM_PAINT:
                if (gt_dib.m_hdc){
                        hdc = BeginPaint(hwnd, &ps);
                        BitBlt( hdc, 0, 0, gt_dib.m_width, gt_dib.m_height, gt_dib.m_hdc, 0, 0, SRCCOPY);
                        sprintf(s, "ヨコ %d X タテ %d", gt_dib.m_width, gt_dib.m_height);
                        TextOut(hdc, 0, gt_dib.m_height, s, strlen(s));
                        EndPaint(hwnd, &ps);
                }
                break;
        case WM_COMMAND:
                on_command(hwnd, msg, wp, lp);
                break;
        case WM_DESTROY:
                PostQuitMessage(0);
                return 0;
        case WM_LBUTTONDOWN:
                mou_down=1;
                break;
        case WM_LBUTTONUP:
                mou_down=0;
                break;
        case WM_MOUSEMOVE:
                if (mou_down){
                        GetCursorPos(&po);
                        ScreenToClient(hwnd, &po);
                        gt_dib.set_pixel(po.x, po.y, col);
                        InvalidateRect(hwnd, NULL, 0);
                }
                break;
        }
        return DefWindowProc(hwnd, msg, wp, lp);
}

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,        PSTR lpCmdLine, int nCmdShow) {
        HWND hwnd;
        MSG msg;
        WNDCLASS winc;

        winc.style              = CS_HREDRAW | CS_VREDRAW;
        winc.lpfnWndProc        = WndProc;
        winc.cbClsExtra = winc.cbWndExtra       = 0;
        winc.hInstance          = hInstance;
        winc.hIcon              = LoadIcon(NULL, IDI_APPLICATION);
        winc.hCursor            = LoadCursor(NULL, IDC_ARROW);
        winc.hbrBackground      = (HBRUSH)GetStockObject(WHITE_BRUSH);
        winc.lpszMenuName       = TEXT("GT_DIB_MENU");
        winc.lpszClassName      = TEXT("GT_IMG");

        if (!RegisterClass(&winc)) return 1;

        hwnd = CreateWindow(
                        TEXT("GT_IMG"), TEXT("画像の基本"), WS_OVERLAPPEDWINDOW | WS_VISIBLE, 
                        CW_USEDEFAULT, CW_USEDEFAULT, 250, 250, 
                        NULL, NULL, hInstance, NULL
        );

        if (hwnd == NULL) return 1;

        while (GetMessage(&msg, NULL, 0, 0 )) {
                TranslateMessage(&msg);
                DispatchMessage(&msg);
        }

        return msg.wParam;
}


 006-04  前へ←  ホームへ  →次へ  007-02