元の色と白(FFFFFFh)を xor するだけです。
function
ColorReversal(col: TColor): TColor; begin Result:= $FFFFFF xor col; end; |
これを応用して、よくあるフォーカス部分の色を反転させる処理などが書けます。
モノトーンにするというのは、RGBのそれぞれの要素の大きさを同じにする、ということ。その大きさは、色の「輝度」というもので決めます。輝度の計算は、ほとんど定型みたいなので、覚えておきましょう。
function
ColorToMonotone(col: TColor): TColor; var rgbcol : Cardinal; cr, cg, cb: Integer; brightness: Double; bri: Byte; begin { RGBに変換 } rgbcol:= ColorToRGB(col); cr:= GetRValue(rgbcol); cg:= GetGValue(rgbcol); cb:= GetBValue(rgbcol); { 輝度計算 } brightness:= 0.299 * cr + 0.587 * cg + 0.114 * cb; bri:= Byte(Round(brightness)); { モノトーン色を返す } Result:= RGB(bri, bri, bri); end; |
ちなみに、GetRValue()、GetGValue()、GetBValue() というのは Win32 API です。
本来、コードで Windows ビットマップ(DIBというやつ)を扱うには、そのフォーマットを理解して、Win32 API を駆使しなければならないのですが、Delphi でWindows ビットマップを扱うのは非常に簡単。TBitmap という、そのまんまのオブジェクトが用意されていて、これが全ての操作を簡易化してくれるからなのです。
例えば、次のように、コードでビットマップの作成ができます。
procedure MakeBitmap; var tmpBmp: TBitmap; cx, cy: Integer; begin { ビットマップ生成 } tmpBmp:= TBitmap.Create; try { ここにビットマップを作成するコードを書く } //ここでは、ビットマップを 32*32 のサイズに設定して //全体を赤で塗りつぶしています... tmpBmp.Width:= 32; tmpBmp.Height:= 32; for cy:= 0 to 31 do begin for cx:= 0 to 31 do begin tmpBmp.Canvas.Pixels[cx, cy]:= clRed; end; end; { ファイルとして保存 } if tmpBmp <> nil then tmpBmp.SaveToFile('new.bmp');// new.bmp という名前で保存 finally tmpBmp.Free; end; end; |
いかにVCLが強力かが分かるでしょ。
プリンタの用紙サイズを取得するには TPrinter オブジェクトを利用します。アプリケーションの中では Printer 変数がグローバルに宣言されているので、いつでもこの変数を使用することが出来ます。そして、ビットマップのサイズを変えるには StretchBlt() Win32 API関数、または、 StretchDIBits() Win32 API関数を使用します。
以下は、StretchDIBits() を使用して、ビットマップのサイズを toRect に最適化して描画する手続きの例です。
procedure
BitmapOptimizeRect(bmp : TBitmap; //
元のビットマップ toDC : HDC; // 描画先デバイスコンテキスト toRect: TRect); // 描画先矩形のサイズ var bit_count : Integer; // 1ピクセル当たりのビット数 old_mode : Integer; // 保存用変数 binfo : PBitmapInfo; // 作業用ビットマップヘッダ binfo_size : DWORD; // 作業用ビットマップヘッダのサイズ img : Pointer; // 作業用ビットマップデータ(実際にはそのアドレス) img_size : DWORD; // 作業用ビットマップのサイズ dc : HDC; // 作業用デバイスコンテキスト old_pal : HPALETTE; // 保存用パレット begin { 1ピクセルあたりのビット数を取得 } bit_count:= GetDeviceCaps(bmp.Canvas.Handle, COLORRES); { 元のビットマップのサイズを取得 } GetDIBSizes(bmp.Handle, binfo_size, img_size); try { 作業用ビットマップのメモリ領域を確保 } binfo:= nil; binfo:= AllocMem(binfo_size); img:= nil; img:= AllocMem(img_size); { 作業用ビットマップのヘッダを初期化 } with binfo^.bmiHeader do begin biSize:= SizeOf(TBitmapInfoHeader); // この構造体に必要なバイト数を指定 biWidth:= bmp.Width; // ビットマップの幅をピクセル単位で指定 biHeight:= bmp.Height; // ビットマップの高さをピクセル単位で指定 biPlanes:= 1; // ターゲット デバイスのプレーン数 biBitCount:= bit_count; // ピクセル当たりのビット数 biCompression:= BI_RGB; // ビットマップの圧縮の種類を指定 end; dc := GetDC(0); // 作業用のデバイスコンテキストを取得 try { ビットマップのパレットを設定 } old_pal:= 0; if bmp.Palette <> 0 then old_pal := SelectPalette(dc, bmp.Palette, True); { 元のビットマップから作業用ビットマップへ転送 } GetDIBits(dc, bmp.Handle, 0, bmp.Height, img, binfo^, DIB_RGB_COLORS); { パレットを元に戻す (これはお約束) } if old_pal <> 0 then SelectPalette(dc, old_pal, True); { 描画先デバイスコンテキストの拡大モードをカラーにセット } old_mode:= SetStretchBltMode(toDC, COLORONCOLOR); { StretchDIBits() Win32 API関数で toDC に描画 } StretchDIBits(toDC, // 転送先のデバイスコンテキスト toRect.Left, // 転送先の長方形の左上隅のx座標 toRect.Top, // 転送先の長方形の左上隅のy座標 toRect.Right - toRect.Left, // 転送先の長方形の幅 toRect.Bottom - toRect.Top, // 転送先の長方形の高さ 0, // 転送元の長方形の左上隅のx座標 0, // 転送元の長方形の左上隅のy座標 binfo^.bmiHeader.biWidth, // 転送元の長方形の幅 binfo^.bmiHeader.biHeight, // 転送元の長方形の高さ img, // 作業用ビットマップのアドレス binfo^, // 作業用ビットマップヘッダのアドレス DIB_RGB_COLORS, // 使い方 SRCCOPY); // ラスタオペレーション { 拡大モードを元に戻す } SetStretchBltMode(toDC ,old_mode); finally { 使用したデバイスコンテキストの開放 } ReleaseDC(0, dc); end; finally { 確保したメモリを開放 } if binfo <> nil then FreeMem(binfo); if img <> nil then FreeMem(img); end; end; |
この手続きを利用して、Printer のキャンバスに描画すれば印刷できます。以下はその例です。
procedure
PrintStretchBitmap(bmp: TBitmap); var pr_rect: TRect; begin (プリンタの設定処理) { 用紙の矩形を取得 } with pr_rect do begin Top:= 0; Left:= 0; Bottom:= Top + Printer.PaperHeight; Right:= Left + Printer.PageWidth; end; with Printer do begin BeginDoc;// 印刷開始 BitmapOptimizeRect(bmp, Canvas.Handle, pr_rect); EndDoc;// 印刷終了 end; end; |
これを応用すれば、ビットマップをいろいろなサイズに変形できます。
よく、マウスでドラッグしてビットマップの一部を反転表示する処理を見ます。あの色の反転というのはどうやっているのか。要は、色の反転処理 の応用です。
以下に、指定された矩形内を反転表示する手続きの例を示します。
procedure RectFocus(dc:
HDC; fcsRect: TRect); var x, y : Integer; col : TColor; cvs : TCanvas; begin cvs:= TCanvas.Create; // 作業用キャンバス生成 try cvs.Handle:= dc; // デバイスコンテキストを代入 for y:= fcsRect.Top to fcsRect.Bottom do begin for x:= fcsRect.Left to fcsRect.Right do begin col:= cvs.Pixels[x,y]; col:= col xor $FFFFFF; cvs.Pixels[x,y]:= col; end; end; finally cvs.Free; // 作業用キャンバスの開放 end; end; |
描画先のDCに TCanvas をあてがうのがミソです。
やり方は2通りあります。ひとつは、マスク画像がある場合のやり方、もう一つはない場合のやり方です。「マスク画像」というのは、実際に描画したい画像の描画部分を黒(或いは白)、透過部分を白(或いは黒)で塗りつぶした画像のことです。
例えば、描画する画像 を背景 に重ねて描画したい場合、
透過部分を白、描画部分を黒で塗りつぶしたマスク画像 を用意します。
また、この場合、背景を黒で塗りつぶしたソース画像 も作成しておきます。
まずマスク画像 を背景 上に BitBlt() API関数で AND転送すると のように、
白い部分が透過して(転送されないで)、黒い部分のみ描画された(転送された)状態となります。
その状態の画像 に、描画する画像 をBitBlt() API関数でOR転送すると、
のように、マスク画像の黒く描画された部分へのみ画像が転送されて、背景が透過された状態となります。
ここで、ソース画像を srcImg、マスク画像を mskImg として、コード表現したものが以下の例です。
function
DrawImageTransmission(DC: HDC; // 背景となるDC srcImg: TBitmap; // ソース画像 mskImg: TBitmap; // マスク画像 dcRect: TRect // 描画する矩形 ): Integer; begin Result:= -1; if srcImg = nil then Exit; // イメージが無い場合は終了 if mskImg = nil then Exit; { マスク画像をDCにAND転送する } if not BitBlt(DC, dcRect.Left, dcRect.Top, dcRect.Right - dcRect.Left, dcRect.Bottom - dcRect.Top, mskImg.Canvas.Handle, 0, 0, SRCAND) then begin Result:= GetLastError; Exit; end; { ソース画像をキャンバスにOR転送する } if not BitBlt(DC, dcRect.Left, dcRect.Top, dcRect.Right - dcRect.Left, dcRect.Bottom - dcRect.Top, srcImg.Canvas.Handle, 0, 0, SRCPAINT) then begin Result:= GetLastError; end; end; |
もう一つのやり方は、あらかじめ透過すべき色が分かっている場合に有用です。
これは BitBlt() などの Win32 API関数は使用せず、ひたすら条件判断しながらビット転送するのみです。
function
DrawImageTransmission(DC : HDC;
// 背景となるDC srcImg : TBitmap; // ソース画像 trsColor : TColor; // 透過する色 dcRect : TRect // 描画する矩形 ): Integer; var vCanvas : TCanvas; fx, fy : Integer; tx, ty : Integer; end_x, end_y: Integer; begin Result:= -1; { イメージが無い場合は終了 } if srcImg = nil then Exit; vCanvas:= TCanvas.Create; try vCanvas.Handle:= DC; // デバイスコンテキストを参照 { ループ終点を求める } if (dcRect.Right - dcRect.Left) > srcImg.Height then end_x:= dcRect.Left + srcImg.Height else end_x:= dcRect.Right; if (dcRect.Bottom - dcRect.Top) > srcImg.Width then end_y:= dcRect.Top + srcImg.Width else end_y:= dcRect.Bottom; { 透過色以外のPixelをキャンバスDCへ転送 } fx:= 0; fy:= 0; for ty:= dcRect.Top to end_y do begin for tx:= dcRect.Left to end_x do begin if srcImg.Canvas.Pixels[fx, fy] <> trsColor then vCanvas.Pixels[tx, ty]:= srcImg.Canvas.Pixels[fx, fy]; Inc(fx); end; Inc(fy); end; vCanvas.Refresh;// 再描画 finally vCanvas.Free; end; end; |
大抵、ビットマップの透過色というのは原点の色が指定されていたりするので、
srcImg.Canvas.Pixels[0,0]
の色を trsColor としてしまっても良いかもしれません。
Windows のデバイスコンテキスト(DC)に描画する、ということと、Delphi の TCanvas に描画するということは、結局同じことです。そして、 TCanvas には、便利なメソッドやプロパティが多く用意されているので、これを利用すれば、簡単にDCに様々な図形を描画することができます。
次の例は、 を使ってDCにポリライン(折れ線)を描画する手続きの例です。
// // DC に Points を頂点に持つ折れ線を描画する // procedure DrawPolylineToDC(DC: HDC; Points: Array of TPoint); var Canvas : TCanvas; begin if High(Points) < 1 then Exit;//点が2点に満たない場合は終了 Canvas:= TCanvas.Create; try Canvas.Handle:= DC; // CanvasにDCを割り当てる Canvas.Pen.Color:= clBlack; // 線色を黒にする(任意) Canvas.Pen.Width:= 1; // 線幅を1にする(任意) Canvas.Pen.Style:= psSolid; // 線種を実線にする(任意) Canvas.Polyline(Points); // ポリライン描画 finally Canvas.Free; end; end; |
TCanvas には、Polyline 以外にも、LineTo、MoveTo、Polygon、Ellipse など、様々な図形を簡単に描画できるメソッドが用意されています。また、それらの色や線種、ハッチングなどの設定も、PenプロパティやBlushプロパティの値を設定するだけなので、Win32 APIでやるような面倒な構造体作成の作業は必要ありません。
これを応用して、DCに直接文字列やビットマップを描画することもできます。要は、 TCanvas を使えば、Win32 APIの面倒な手続きを省略できるのだ、ということ。
TStringGrid をそのまま印刷できないものか、と作ってみたのが、以下の手続きです。QuickReport とか使えば、こんなことする必要ないのかもしれませんけどね(笑)
procedure
GridToBitmap(Grid: TStringGrid; // 描画するTStringGrid Title, // タイトル文字列 Copylight: String; // 著作権表示文字列 TitleFontSize, // タイトルフォントサイズ DateFontSize, // 日付フォントサイズ CopyFontSize, // 著作権表示フォントサイズ FontSize, // 表のフォントサイズ Spacing, // 表の内部スペース Margin, // 縦横のマージン TitleLine, // タイトル行 FromLine, // 描画開始行 ToLine: Integer; // 描画終了行 var bmp: TBitmap); // 描画先ビットマップ var now_pos: TPoint; str_date: String; col_cnt: Integer; row_cnt: Integer; col_width_lst : Array of Integer;//セルの幅リスト row_height_lst : Array of Integer;//セルの高さリスト tmp_width: Integer; tmp_height: Integer; cell_strs: TStringList; cell_line: Integer; cell_cnt : Integer; cell_tmp : Integer; cell_rct : TRect; table_top: Integer; begin { 引数チェック } if (TitleLine < 0) or (TitleLine >= Grid.RowCount) then Exit; if FromLine > ToLine then Exit; if (FromLine < 0) or (ToLine >= Grid.RowCount) then Exit; now_pos.x:= 0; now_pos.y:= 0; SetLength(col_width_lst, Grid.ColCount); SetLength(row_height_lst, Grid.RowCount); with bmp.Canvas do begin Font.Size:= FontSize;//フォントを表の内容の文字の大きさに設定 cell_strs:= TStringList.Create;//行分割用のリスト生成 { タイトル行のサイズを調べる } for col_cnt:= 0 to Grid.ColCount - 1 do begin cell_strs.Clear; cell_line:= ReadDelimiterText(Grid.Cells[col_cnt,TitleLine], #13, cell_strs); if cell_line <= 0 then Continue; { セルの幅 } tmp_width:= 0; for cell_cnt:= 0 to cell_line - 1 do begin cell_tmp:= TextExtent(Trim(cell_strs[cell_cnt])).cx + Spacing * 2; if tmp_width < cell_tmp then tmp_width:= cell_tmp; end; if col_width_lst[col_cnt] < tmp_width then col_width_lst[col_cnt]:= tmp_width; { セルの高さ } tmp_height:= TextExtent(Grid.Cells[col_cnt,TitleLine]).cy * cell_line + Spacing * 2; if row_height_lst[TitleLine] < tmp_height then row_height_lst[TitleLine]:= tmp_height; end; { 表の縦横サイズを調べる } for row_cnt:= FromLine to ToLine do begin for col_cnt:= 0 to Grid.ColCount - 1 do begin cell_strs.Clear; cell_line:= ReadDelimiterText(Grid.Cells[col_cnt,row_cnt], #13, cell_strs); if cell_line <= 0 then Continue; { セルの幅 } tmp_width:= 0; for cell_cnt:= 0 to cell_line - 1 do begin cell_tmp:= TextExtent(Trim(cell_strs[cell_cnt])).cx + Spacing * 2; if tmp_width < cell_tmp then tmp_width:= cell_tmp; end; if col_width_lst[col_cnt] < tmp_width then col_width_lst[col_cnt]:= tmp_width; { セルの高さ } tmp_height:= TextExtent(Grid.Cells[col_cnt,row_cnt]).cy * cell_line + Spacing * 2; if row_height_lst[row_cnt] < tmp_height then row_height_lst[row_cnt]:= tmp_height; end; end; cell_strs.Free; end; { 表の横サイズを決定 } tmp_width:= 0; for col_cnt:= 0 to Grid.ColCount - 1 do begin tmp_width:= tmp_width + col_width_lst[col_cnt]; end; bmp.Width:= tmp_width + 20 + Margin * 2;//※ここでtmp_widthは表の幅 { 表の縦サイズを決定 } tmp_height:= 0; for row_cnt:= FromLine to ToLine do begin tmp_height:= tmp_height + row_height_lst[row_cnt]; end; tmp_height:= tmp_height + row_height_lst[TitleLine];// タイトル行分 bmp.Height:= tmp_height + TitleFontSize + DateFontSize + CopyFontSize + 65 // 予備幅 + Margin * 2;// ※ここでtmp_heightは表の高さ with bmp.Canvas do begin Brush.Color:= clWhite; //背景色(白) Pen.Color:= clBlack; //線色(黒) Font.Color:= clBlack; //文字色(黒) FillRect(Rect(0,0,bmp.Width,bmp.Height));//背景を白く塗りつぶす { 表描画開始位置セット } now_pos.x:= 10 + Margin; now_pos.y:= 10 + Margin; { タイトル描画 } Font.Size:= TitleFontSize; TextOut(now_pos.x, now_pos.y, Title); now_pos.y:= now_pos.y + TextExtent(Title).cy + 10; { 日付描画 } Font.Size:= DateFontSize; str_date:= FormatDateTime('yyyy"年"mm"月"dd"日" hh"時"mm"分"ss"秒"', Now); TextOut(now_pos.x, now_pos.y, str_date); now_pos.y:= now_pos.y + TextExtent(str_date).cy + 10;// 位置更新 { 内容描画 } Font.Size:= FontSize; { 表の枠を描画 } Rectangle(now_pos.x, now_pos.y, now_pos.x + tmp_width, now_pos.y + tmp_height); { 縦ラインを描画 } for col_cnt:= 0 to Grid.ColCount - 2 do begin now_pos.x:= now_pos.x + col_width_lst[col_cnt]; MoveTo(now_pos.x, now_pos.y); LineTo(now_pos.x, now_pos.y + tmp_height); end; now_pos.x:= now_pos.x + col_width_lst[Grid.ColCount - 1];// 位置更新 table_top:= now_pos.y;//この時点のY座標を保存 { 横ラインを描画 } //まずタイトル行の分 now_pos.y:= now_pos.y + row_height_lst[TitleLine]; MoveTo(now_pos.x - tmp_width, now_pos.y); LineTo(now_pos.x, now_pos.y); for row_cnt:= FromLine to ToLine - 1 do begin now_pos.y:= now_pos.y + row_height_lst[row_cnt]; MoveTo(now_pos.x - tmp_width, now_pos.y); LineTo(now_pos.x, now_pos.y); end; now_pos.x:= now_pos.x - tmp_width;//位置を表の左上に戻す now_pos.y:= table_top; { タイトル行を描画 } for col_cnt:= 0 to Grid.ColCount - 1 do begin cell_rct:= Rect(now_pos.x + Spacing, now_pos.y + Spacing, now_pos.x + col_width_lst[col_cnt] - Spacing, now_pos.y + row_height_lst[TitleLine] - Spacing); DrawText(Handle, PChar(Grid.Cells[col_cnt,TitleLine]), -1, cell_rct, DT_CENTER); now_pos.x:= now_pos.x + col_width_lst[col_cnt]; end; now_pos.x:= now_pos.x - tmp_width; now_pos.y:= now_pos.y + row_height_lst[TitleLine]; { 文字を描画 } for row_cnt:= FromLine to ToLine do begin for col_cnt:= 0 to Grid.ColCount - 1 do begin cell_rct:= Rect(now_pos.x + Spacing, now_pos.y + Spacing, now_pos.x + col_width_lst[col_cnt] - Spacing, now_pos.y + row_height_lst[row_cnt] - Spacing); DrawText(Handle, PChar(Grid.Cells[col_cnt,row_cnt]), -1, cell_rct, DT_CENTER); now_pos.x:= now_pos.x + col_width_lst[col_cnt]; end; now_pos.x:= now_pos.x - tmp_width; now_pos.y:= now_pos.y + row_height_lst[row_cnt]; end; { コピーライトを描画 } Font.Size:= CopyFontSize; now_pos.x:= now_pos.x + 5; now_pos.y:= now_pos.y + 5; TextOut(now_pos.x, now_pos.y, Copylight); end; end; |
この中の ReadDelimiterText() というのは、デリミタ区切りの文字列を分解する関数で、こちら で紹介しています。
[ Delphi へ戻る ]