エンタープライズ:特集 2003/07/04 17:20:00 更新
C Magazine

C MAGAZINE 2002年8月号より転載
プログラムのレシピ――プログラミングの考え方・作り方 (9/13)

グラフィックエディタの製作(5)
基本機能の作成−描画機能

 次は描画機能を作りましょう。グラフィックエディタで描画機能を作るときの基本は、「画面に直接描くのではなく、メモリ上の画像に描く」ことです。Windowsでは複数のアプリケーションが同時に実行されるので、画面に直接描いてもほかのウィンドウに隠されたらすぐに消えてしまいます。そこで、まずメモリ上の画像に描いておき、次にそれを画面上に表示する、といった作り方が必要です。

半透明ブラシによる描画

 いろいろな形状のブラシを使って、フリーハンドで描くための機能を作ります。ベタ塗りではつまらないので、半透明のブラシにしましょう。半透明のブラシとはFig.8のようなものです。描画色と背景色を、微妙に混合して描くことができます。

 作成したグラフィックエディタでは、画像の上でマウスをドラッグすると、ブラシで描画することができます。左クリックなら前景色、右クリックなら背景色で描画します。

 色はFig. 9のようなカラーパレットで選びます。また、ブラシはFig. 10のようなブラシパレットで選択します。

Fig8

Fig. 8 半透明ブラシでの描画例


Fig9

Fig. 9 カラーパレット


Fig10

Fig. 10 ブラシパレット


半透明ブラシの仕組み

 Fig. 11で半透明ブラシの仕組みを説明しましょう。

 ブラシはFig. 11- (A)のようなモノクロ濃淡のビットマップです(これは拡大表示したものです)。黒いところは濃く、白いところは薄く描きます。たとえば白い絵の上に緑のブラシで描画すると、Fig. 11- (B)のようになります(モノクロでは色がわかりにくいかもしれませんが)。

 背景がある場合には、ブラシの濃淡を逆にして、背景に掛け合わせます。たとえばFig. 11- (C)のような背景の場合には、ブラシを掛け合わせるとFig. 11- (D)のようになります。ブラシが濃いところは背景が薄く、ブラシが薄いところは背景が濃くなるわけです。そして、Fig. 11- (D)に先ほどのFig.11- (B)を合成すると、Fig. 11- (E)のようにブラシの描画が完成します。

Fig. 11 半透明ブラシの原理

Fig11a

(A)モノクロ濃淡のビットマップ


Fig11b

(B)緑のブラシで描画する


Fig11c

(C)背景の例


Fig11d

(D)ブラシを掛け合わせたところ


Fig11e

(E)緑のブラシを合成する


 半透明ブラシの実現方法は、Win32 APIでもC++Builderでも基本的には同じです。まずメモリ上のビットマップに対してブラシ描画を行ったあとに、ビットマップを画面に転送します。

 Win32 APIの場合、ビットマップへの描画にはいくつかの方法があります。たとえばGetPixel関数やSetPixel関数を使えばビットマップを1ピクセル単位で操作できます。またDIB(Device Independent Bitmap)を使えば、すべてのピクセルをメモリ上で自由に操作できるので、より高速な処理が可能です。なお画面にビットマップを転送するには、BitBlt関数やStretchBlt関数を使います。

 C++Builderの場合、TCanvasクラスのPixelsプロパティを使います。詳しくは次のプログラム例で説明します。

プログラム例

 List 10は半透明ブラシを実現するプログラムです。基本的には、先ほど説明した仕組みをそのままプログラムにしただけです。

 List 10- (1)はRGB値を扱いやすくするためのマクロです。色は32ビットのint値で表され、R/G/B(赤/緑/青)それぞれの輝度が8ビットずつ格納されています。下位0〜7ビットがR、8〜15ビットがG、16〜23ビットがBです。これを簡単に取り出すのがRGB_R、RGB_G、RGB_Bというマクロです。RGBToColorは逆に、ばらばらのRGB値を32ビットの数値に戻します。

 ブラシや背景のビットマップをピクセル単位で読み書きするには、List 10- (2)、(4)、(5)のようにTCanvasクラスのPixelsプロパティを使います。Pixelsプロパティは、

Pixels[x][y]

のように、2次元の配列変数と同じ方法でアクセスします。xは横方向の座標、yは縦方向の座標です。

 List 10- (3)とList 10- (4)はブラシが完全に透明なときと、完全に不透明なときの特別な処理です。透明なときには描画を省略し、不透明なときにはベタ塗りします。

List 10 半透明ブラシ(UGEditorForm.cpp)
//============================================================= (1)
// RGB値を扱うためのマクロ
#define RGBToColor(r,g,b) (((b)< < 8 |(g))< < 8 |(r))
#define RGB_R(c) ((c)&255)
#define RGB_G(c) ((c)> > 8&255)
#define RGB_B(c) ((c)> > 16&255)
// ブラシで描画
void TGEditorForm::DrawWithBrush(int x, int y, TColor color) {
    // ブラシのビットマップ
    Graphics::TBitmap *brushBmp=BrushForm-> GetBrush();
    // ブラシと画像を操作するTCanvasオブジェクト
    TCanvas *brush=brushBmp-> Canvas;
    TCanvas *bmp=Bitmap-> Canvas;
    // ブラシの座標と大きさ
    int brushX, brushY;
    int brushW=brushBmp-> Width, brushH=brushBmp-> Height;
    // 画像上の座標
    int bmpLeft=x-brushW/2, bmpTop=y-brushH/2;
    int bmpX, bmpY;
    TColor bmpCol, brushCol;
    // アルファ値(透明度)
    int alphaR, alphaG, alphaB;
    // ブラシの左上から右下に向かって
    // 1ピクセルずつ処理する
    for (
        brushY=0, bmpY=bmpTop; 
        brushY< brushH; 
        brushY++, bmpY++
    ) {
        for (
            brushX=0, bmpX=bmpLeft; 
            brushX< brushW; 
            brushX++, bmpX++
        ) {
            //================================================= (2)
            // ブラシの濃度を取得
            brushCol=brush-> Pixels[brushX][brushY]&0xffffff;
            //================================================= (3)
            // ブラシが透明ならば次のピクセルへ
            if (brushCol==0xffffff) {
                continue;
            } else
            //================================================= (4)
            // ブラシが完全に不透明ならば、
            // 描画色をベタ塗りする
            if (brushCol==0) {
                bmp-> Pixels[bmpX][bmpY]=color;
                continue;
            } else
            // ブラシが半透明ならば、描画色と背景とを合成する
            {
                // 画像(背景)の色を取得
                bmpCol=bmp-> Pixels[bmpX][bmpY];
                // ブラシの濃度をRGBに分解
                alphaR=RGB_R(brushCol); 
                alphaG=RGB_G(brushCol); 
                alphaB=RGB_B(brushCol);
                //============================================= (5)
                // ブラシの濃度を掛け合わせて、
                // 描画色と背景とを合成する
                bmp-> Pixels[bmpX][bmpY]=RGBToColor(
                    (RGB_R(bmpCol)*alphaR + 
                        RGB_R(color)*(255-alphaR))/255 ,
                    (RGB_G(bmpCol)*alphaG + 
                        RGB_G(color)*(255-alphaG))/255 ,
                    (RGB_B(bmpCol)*alphaB + 
                        RGB_B(color)*(255-alphaB))/255
                );
            }
        }
    }
    //========================================================= (6)
    // 再描画処理
    RepaintBitmap(bmpLeft, bmpTop, brushW, brushH);
}

前のページ | 1 2 3 4 5 6 7 8 9 10 11 12 13 | 次のページ

[松浦健一郎(ひぐぺん工房),C MAGAZINE]

Copyright © ITmedia, Inc. All Rights Reserved.