戻る ホーム 上へ 進む

色の使い方(2) グラデーションを描く [1]

Tip&解説

さて,色の使い方(1) TColor って何?では予約色を説明しました。では予約色以外の色を画面モード256色の時に表示するにはどうすればよいでしょうか?

High Color や True Color を使えばいいじゃないかという考え方も有ります。既に世の中に出荷されている大部分のパソコンは High Color や True Color モードを持っています。いずれ 256色モードなんて廃止されるでしょう。でもまだわずかに残っている256色モードのパソコンのために256色モードでの扱いを考慮してプログラムすることを学んでおきましょう。

尚,この Tips にはいろいろな表示例が出てきますが,画面モードが256色だと変な色で表示されるかもしれません。おかしいようでしたら High Color か True Color で眺めてみてください。

まず,最初に紹介するのは前の Tipsで説明した RGB 型の色値 が256色モードでどのように機能するかです。以下のようになります。
Canvas のプロパティ色がどうなるか
Font.Colorフォントの色が指定された RGB 値に最も近い予約色になる
Pen.Color線画(LineTo, Rectangel の外枠, Pixels Property を使った点描画)の色が指定された RGB 値に最も近い予約色になる。
Brush.Colorブラシは指定された RGB 値に見えるように予約色のみを使ったディザパターンになる。

このように,予約色以外の色は使われないことが判ります。実例をお見せしましょう。赤のグラデーションを表示するプログラムを以下に示します。

List 1


// 赤を暗い順に16色(RGB形式)
const Colors: array[0..15] of TColor =
      ($00000000, $00000010, $00000020, $00000030,
       $00000040, $00000050, $00000060, $00000070,
       $00000080, $00000090, $000000A0, $000000B0,
       $000000C0, $000000D0, $000000E0, $000000F0);

procedure TForm1.Button1Click(Sender: TObject);
var x, y: Integer;
begin
  // 赤のグラデーションを表示??
  for x := 0 to 255 do
    for y := 0 to 255 do
      Canvas.Pixels[x, y] := Colors[x div 16];
end;

256色モードでこのプログラムの出力を見るとこんなかんじになります。

16色の帯が表示されるはずなのに3色しか表示されていません。どうすれば良いのでしょうか? パレットを使う基本的なやり方を説明しましょう。

パレットとは予約色以外の色を使いたいことをシステムに知らせる要求書です。注文を出さないと Windows は色をくれません。まず要求書の作り方ですが,残念ながら Windows の API を使うしかありません。Delphi はこのための機能を提供していないのです (;_;。フォームが作成される時点でパレットを作ってみることにしましょう。

List 2

type
  TForm1 = class(TForm)
    Button1: TButton;
    procedure Button1Click(Sender: TObject);
    procedure FormCreate(Sender: TObject);   // ここでパレットを作る
    procedure FormDestroy(Sender: TObject);  // ここでパレットを廃棄する。
  private
    hPal: HPALETTE; // パレットを入れておく所
  protected
    function GetPalette: HPALETTE; override; // 後述
  end;

   :
   :

procedure TForm1.FormCreate(Sender: TObject);
var pPal: ^TLogPalette;
    i: Integer;
begin
  // パレット作成のための16色分のパラメータ用メモリを確保
  GetMem(pPal, SizeOf(TLogPalette) + SizeOf(TPaletteEntry) * (16-1));

  pPal^.palNumEntries := 16; // 色数を設定
  pPal^.palVersion := $0300; // おまじない

  // Colors 配列から色を取り出して構造体に設定。
  for i := 0 to 15 do begin
    // GetRValue/GetGvalue/GetBVlaue 関数は TColor から Red, Green Blue
    // の値を取り出す関数。Windows ユニットで定義されている。
    pPal.palPalEntry[i].peRed   := GetRValue(Colors[i]);
    pPal.palPalEntry[i].peGreen := GetGValue(Colors[i]);
    pPal.palPalEntry[i].peBlue  := GetBValue(Colors[i]);
    pPal.palPalEntry[i].peFlags := 0;
  end;

  // パレットを作る。
  hPal := CreatePalette(pPal^);

  // パラメータ用メモリを廃棄
  FreeMem(pPal);
end;

TLogPalette 構造体は以下の様になっています。

TPaletteEntry = packed record
  peRed: Byte;
  peGreen: Byte;
  peBlue: Byte;
  peFlags: Byte;
end;

TLogPalette = packed record
  palVersion: Word;
  palNumEntries: Word;
  palPalEntry: array[0..0] of TPaletteEntry;
end;

TLogPalette 構造体は可変長の構造体なのですが,Pascal は可変長の構造体を表現できませんから palPalEntry は配列の大きさが仮に1になっています。この配列は要求する色数分必要ですから GetMem でメモリを確保する時に足りない部分を補って確保しています。注意してください。後は,必要なパラメータを上のように埋めて CreatePalette を呼べば要求書(パレット)が作られます。パレットはフォームのメンバ変数 hPal に格納しておきます。

パレットは使いおわったら廃棄しなければなりません。ここではフォームが廃棄される時にパレットも廃棄するようにしましょう。

List 3

procedure TForm1.FormDestroy(Sender: TObject);
begin
  DeleteObject(hPal);
end;

さて,出来上がったパレットを使うには2つのことをしなければなりません。一つはフォームがパレットを使うことを VCL(Visual Component Library) に報せてあげること,もう一つは描画する時にパレットをCanvas に選択し実体化することです。

前者は 色の優先順位を Windows に決めてもらうためのものです。256色モードではシステム全体で 256色しかない色を多数のトップレベルウィンドウ(Delphi では フォーム)が取り合います。ですから Windows にウィンドウ毎に色の獲得の優先順位を決めてもらわなくてはなりません。このため本来はいくつかのメッセージに応答して複雑な処理をしなければいけないのですが,VCL がこの辺の処理をうまくやってくれます。ただし VCL のお手伝いを若干しなければなりません。

この例の場合,やるべきことは簡単です。フォームの の GetPalette メソッドを override して フォームがパレットを持っていることを VCL に報せてやれば良いのです。

List 4


type
  TForm1 = class(TForm)
    Button1: TButton;
    procedure Button1Click(Sender: TObject);
    procedure FormCreate(Sender: TObject);   // ここでパレットを作る
    procedure FormDestroy(Sender: TObject);  // ここでパレットを廃棄する。
  private
    hPal: HPALETTE; // パレットを入れておく所
  protected
    function GetPalette: HPALETTE; override; // <− これを加える
  end;
   :
   :
   :
function TForm1.GetPalette: HPALETTE;
begin Result := hPal; end;

最後にグラデーションを描画する部分を書き換えます。

List 5

procedure TForm1.Button1Click(Sender: TObject);
var x, y: Integer;
    OldPal: HPALETTE;
begin
  // Canvas にパレットを選択し実体化する。
  OldPal := SelectPalette(Canvas.Handle, hPal, True);
  RealizePalette(Canvas.Handle);

  for x := 0 to 255 do
    for y := 0 to 255 do
      // Palette RGB 形式で色を指定する。
      Canvas.Pixels[x, y] := Colors[x div 16] or $02000000;

  // パレットを元に戻す。
  SelectPalette(Canvas.Handle, OldPal, True);
end;

上のコードではまず SelectPalette API を使って Canvas にパレットを選択します。そして RealizePalette API で色を Windows に要求します。こうすることで初めて予約色以外の色が使えるようになります。使いおわったら上のように必ずパレットを元に戻しましょう。

Canvas の Pixels Property に色を代入する時は RGB 形式を使ってはいけません。PaletteRGB 形式か PaletteIndex 形式を使います。ここでは PaletteRGB 形式を使ってみました。こうすることで,色は予約色ではなくパレットで要求した色の中から選ばれます。RGB 形式を Palette RGB 形式に直すため 上位 8ビットを $00 から $02 に変更しています。

PaletteIndex 型を使う場合は

      Canvas.Pixels[x, y] := (x div 16) or $01000000;

とすれば全く同じ結果が得られます。PaletteIndex 型の色値では,色値の下位8ビットが TLogPalette 構造体の palPalEntry 配列 のインデックス値を指定します。つまりインデックス値で指定された配列のエントリにセットされた色が指定されることになります。

実行結果は以下のようになります。

最後に全てのソースを載せておきます。

unit Tips016_02_frm;

interface

uses
  Windows, Messages, SysUtils, Classes, Graphics,
  Controls, Forms, Dialogs, StdCtrls;

type
  TForm1 = class(TForm)
    Button1: TButton;
    procedure Button1Click(Sender: TObject);
    procedure FormCreate(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
  private
    hPal: HPALETTE;
  protected
    function GetPalette: HPALETTE; override;
  end;

var
  Form1: TForm1;

implementation

{$R *.DFM}

const Colors: array[0..15] of TColor =
      ($00000000, $00000010, $00000020, $00000030,
       $00000040, $00000050, $00000060, $00000070,
       $00000080, $00000090, $000000A0, $000000B0,
       $000000C0, $000000D0, $000000E0, $000000F0);


function TForm1.GetPalette: HPALETTE;
begin Result := hPal; end;

procedure TForm1.Button1Click(Sender: TObject);
var x, y: Integer;
    OldPal: HPALETTE;
begin
  OldPal := SelectPalette(Canvas.Handle, hPal, True);
  RealizePalette(Canvas.Handle);

  for x := 0 to 255 do
    for y := 0 to 255 do
      Canvas.Pixels[x, y] := Colors[x div 16] or $02000000;

  SelectPalette(Canvas.Handle, OldPal, True);
end;

procedure TForm1.FormCreate(Sender: TObject);
var pPal: ^TLogPalette;
    i: Integer;
begin
  // パレット作成のための16色分のパラメータ用メモリを確保
  GetMem(pPal, SizeOf(TLogPalette) + SizeOf(TPaletteEntry) * (16-1));

  pPal^.palNumEntries := 16; // 色数を設定
  pPal^.palVersion := $0300; // おまじない

  // 色を設定。
  for i := 0 to 15 do begin
    pPal.palPalEntry[i].peRed   := GetRValue(Colors[i]);
    pPal.palPalEntry[i].peGreen := GetGValue(Colors[i]);
    pPal.palPalEntry[i].peBlue  := GetBValue(Colors[i]);
    pPal.palPalEntry[i].peFlags := 0;
  end;

  // パレットを作る。
  hPal := CreatePalette(pPal^);

  // パラメータ用メモリを廃棄
  FreeMem(pPal);
end;

procedure TForm1.FormDestroy(Sender: TObject);
begin
  DeleteObject(hPal);
end;

end.

たかが予約色以外をちょっと表示したいぐらいでずいぶんコードが増えたことにあきれたことでしょう。でもこれ以上はあまり増えません。パレットの作成,パレットの選択と実体化,パレットを VCL に報せるコードさえあればいいのです。簡単ですよね?

Tips 17に続く

 

戻る ホーム 上へ 進む inserted by FC2 system