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

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

グラフィックエディタの製作(7)
基本機能の作成−色選択機能

 描画と並んで、色の選択もグラフィックエディタの大事な機能です。色の選択がスムーズだと、ツール全体の使い勝手がよくなります。今回のグラフィックエディタでは、カラーパレットとスポイトという2種類の色選択手段を用意しました。

カラーパレット

 カラーパレットはグラデーションから色を選ぶ機能です。Windowsで一般的なカラーパレットはFig. 13のようなものですね。しかしグラフィックエディタの場合、このカラーパレットはあまり使い勝手がよくありません。そこで今回は、オリジナルのカラーパレットを作ることにしました。

Fig13

Fig. 13 Windowsで一般的なカラーパレット


 作ってみたのがFig. 14のようなカラーパレットです。これはメインウィンドウとは別の小さなウィンドウになっていて、好きな位置に動かすことができます。

Fig14

Fig. 14 今回作成したカラーパレット


 一般にコンピュータでは色をRGB方式(赤/緑/青の輝度)で表しますが、このカラーパレットはHSV方式を使います。H(Hue)は色相、S(Saturation)は彩度、V(Value)は明度と呼ばれます。RGBとHSVは相互に変換できます。HSVには人間が直感的に色を選びやすいというメリットがあります。

 Fig. 14の上部にあるのが色相を選ぶバーです。色相を選ぶと、下の正方形のグラデーションが変化します。グラデーションの横方向が彩度、縦方向が明度に対応します。グラデーションを左クリックすると前景色を選ぶことができます。前景色と背景色は左下の矩形に表示されていて、クリックすると前景色と背景色が入れ替えられます。また、右下には前景色のHSV値とRGB値が表示されます。

プログラム例

 カラーパレットはメインフォームとは別のフォーム(ColorForm)にしました。コンポーネントの配置はFig. 15のようになっています。HBarが色相バー、SVBoxが彩度/明度ボックスです。どちらもTPaintBoxクラスを使います。

Fig15

Fig. 15 カラーパレットのコンポーネント配置(オブジェクトツリー)


 プログラムのポイントはRGB方式とHSV方式の間の変換です。変換方式についてここでは詳しく説明しませんが、興味がある方はCG関連の書籍などを調べてみてください。List 13はHSV方式で色を扱うためのクラスで、List 14はHSV→RGBおよびRGB→HSVの変換を行うメソッドです。このプログラムでは、R、G、B、S、Vは0〜255の値、Hは0〜360の値としました。

List 13 HSV方式で色を扱うクラス(UColorForm.h)
// HSV方式で色を扱うクラス
// 0< =H< =360, 0< =S< =255, 0< =V< =255;
class HSV {
private:
    // H,S,Vの値を保持するフィールド
    int H_, S_, V_;
    // Hを設定するメソッド
    void setH(int h) {
        h%=360;
        if (h> =0) H_=h; else H_=h+360;
    }
    // Sを設定するメソッド
    void setS(int s) {
        if (s< 0) S_=0; else if (s> 255) S_=255; else S_=s;
    }
    // Vを設定するメソッド
    void setV(int v) {
        if (v< 0) V_=0; else if (v> 255) V_=255; else V_=v;
    }
public:
    // 引数なしのコンストラクタ
    HSV() : H(0), S(0), V(0) {}
    // 引数ありのコンストラクタ
    HSV(int h, int s, int v) {
        h%=360;
        if (h> =0) H_=h; else H_=h+360;
        if (s< 0) S_=0; else if (s> 255) S_=255; else S_=s;
        if (v< 0) V_=0; else if (v> 255) V_=255; else V_=v;
    }
    // 外部からH,S,Vの値にアクセスするためのプロパティ
    __property int H={read=H_, write=setH};
    __property int S={read=S_, write=setS};
    __property int V={read=V_, write=setV};
};

List 14 HSV←→RGBの変換(UColorForm.cpp)
// HSVからRGBに変換
TColor HSVToRGB(const HSV &hsv) {
    unsigned char c1, c2, c3;
    unsigned char r, g, b;
    if (hsv.S==0) {
        r=g=b=hsv.V;
    } else {
        int t=(hsv.H*6)%360;
        c1=(hsv.V*(255-hsv.S))/255;
        c2=(hsv.V*(255-(hsv.S*t)/360))/255;
        c3=(hsv.V*(255-(hsv.S*(360-t))/360))/255;
        switch (hsv.H/60) {
            case 0: r=hsv.V; g=c3; b=c1; break;
            case 1: r=c2; g=hsv.V; b=c1; break;
            case 2: r=c1; g=hsv.V; b=c3; break;
            case 3: r=c1; g=c2; b=hsv.V; break;
            case 4: r=c3; g=c1; b=hsv.V; break;
            case 5: r=hsv.V; g=c1; b=c2; break;
        }
    }
    return (TColor)RGBToColor(r,g,b);
}
// RGBからHSVに変換
HSV RGBToHSV(TColor rgb) {
    int max, min, r, g, b, d, rt, gt, bt;
    HSV hsv;
    r=RGB_R(rgb);
    g=RGB_G(rgb);
    b=RGB_B(rgb);
    max=(r> g) ? (r> b?r:b) : (g> b?g:b);
    min=(r< g) ? (r< b?r:b) : (g< b?g:b);
    d=max-min;
    hsv.V=max;
    if (max!=0) hsv.S=d*255/max; else hsv.S=0;
    if (hsv.S==0) {
        hsv.H=0;
    } else {
        rt=max-r*60/d;
        gt=max-g*60/d;
        bt=max-b*60/d;
        if (r==max) hsv.H=bt-gt; else
        if (g==max) hsv.H=120+rt-bt; else hsv.H=240+gt-rt;
        if (hsv.H< 0) hsv.H+=360;
    }
    return hsv;
}

 カラーパレットはHSV方式ですが、描画の際にはRGB方式を使います。そこで、カラーパレットで色を選択したときには、HSVからRGBに変換して描画色を求めます。Win32 APIを使う場合にも、このHSV←→RGB変換部分はほぼそのまま使えます。カラーパレットの描画には、Rectangle関数やTextOut関数といった各種の描画関数を用います。

カラーパレット描画の高速化

 カラーパレットの色相バーと彩度/明度ボックスを描くのは、実はけっこう時間がかかります。そのため、

 カラーパレットの再描画(){
     色相バーを描く
     彩度/明度ボックスを描く
 }

のようなプログラムにしてしまうと、再描画が遅くて使い物になりません。 実は、色相バーのグラデーションは変化しません。また彩度/明度ボックスのグラデーションが変化するのは、色相バーが変化したときだけです。そこで、グラデーションをあらかじめほかのビットマップに描いておき、カラーパレットを表示するときにはこのビットマップを描画するだけにすると高速化できます。プログラムは、

 カラーパレットの再描画(){
     色相ビットマップを表示
     彩度/明度ビットマップを表示
 }

というイメージになります。

 一般に、時間がかかる描画を高速化するには、変化しない部分や使い回せる部分をビットマップで保持しておく方法が有効です。たとえていうならば、「できあがったものがこちらにご用意してあります〜」という料理番組と同じ手法ですね!?

 この手法が有効なのはC++Builderに限りません。Win32 APIでももちろん有効ですし、Javaでも似たような手法を使うことがあります。

スポイト

 スポイトは画像をクリックして色を拾う機能です。スポイトの実現方法は簡単で、クリックした位置に対応する画像上のピクセルを取得し、そのピクセルの色を新たな描画色に設定するだけです。今回のグラフィックエディタでは、Ctrl+クリックでスポイトが使えます。 Win32 APIの場合には、GetPixel関数を使えばビットマップのピクセルの色を調べることができます。C++Builderの場合は、次で説明するようにTCanvasクラスのPixelsプロパティを使います。

プログラム例

 List 15がスポイト関連の処理です。クリックした画像から色を拾う部分(List 15-(1))は簡単で、ビットマップ(Bitmap)のCanvasプロパティのPixelsプロパティを使います。Pixelsプロパティはブラシ機能でも使いましたね。なお、マウスでクリックした座標(mx、my)を画像上の座標(x、y)に変換するには、

x=mx/ZoomRate;
y=my/ZoomRate;

のようにします。ZoomRateは表示の拡大縮小率です。

 List 15-(2)はカラーパレットの更新処理です。スポイトで色を拾ったら、カラーパレットもその色を指すように状態を更新しなければいけません。そこで拾った色のRGB値をHSV値に変換して、カラーパレットの色相バーや彩度/明度ボックスを更新します。

List 15 スポイト(UGEditorForm.cpp、UColorForm.cpp)
//============================================================= (1)
// スポイト(クリックした色を拾う)
void TGEditorForm::PickColor(int x, int y) {
    ColorForm-> FGColor=Bitmap-> Canvas-> Pixels[x][y];
}
//============================================================= (2)
// 前景色の設定
void TColorForm::SetFGColor(TColor color) {
    // フィールドに値を設定
    FGColor_=color;
    // 前景色表示を更新
    FGColorBox-> Invalidate();
    // 前景色のRGBをHSVに変換
    HSVColor=RGBToHSV(FGColor);
    // 色相バー、彩度/明度ボックスなどを再描画
    PaintSVBoxBitmap();
    HBar-> Invalidate();
    SVBox-> Invalidate();
    SetLabels();
}

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

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

Copyright © ITmedia, Inc. All Rights Reserved.