輝度、彩度、色相のコントロール
此処では、画像の輝度、彩度、色相を変更するプログラムを検討します。
一般的に色は、明度、彩度、色相で表されますが、画像として処理をする場合は、輝度、彩度、色相で処理をする必要があります。
明度の場合定義によって異なるのですが、R、G、Bの色の最大値と、最小値の平均値だったり、最大値だけだったりします。
色を視覚的に捕らえるのではなく、単に理論的に区分けする為のもので、明度は画像の色の実際の明るさではありません。
明度と、視覚的に感じる明るさとはかけ離れたものになっています。
例えば、Windowsの場合、RGB各値の最大値は、255ですが、明度は全て同じ値となります、しかし、視覚的には、G(グリーン)が明るく、R、Bの順でB(ブルー)が一番暗く感じます。
しかし、この3色を、混合すると白にみえます。
元々、太陽光線を反射した光りで、画像を見ているのですが、太陽光線のスペクトル中の一番強い部分を利用していて、R、G、Bの光りの強さに大きな差はありませんが、視覚的に緑を明るく感じる為、輝度値として大きな係数をあたえます。
等々の理由により、此処では明るさの値として、輝度を使用します。
輝度の値は、明度と異なり、RGB三色による値となります。
下図は、明度と輝度の差を表していますが、明度の場合、そのままグレーに変換すると、同じグレーの値になり、視覚にあわせた輝度変換をすると、見た目の明るさに応じたグレー画像になります。
マゼンタは赤と青の同じ明るさの混合色なので、赤、青の単色よりもよりも明るい色の筈ですが、同じ明度として数値化されているため、その値でグレーにすると、全て同じ明るさのグレーになってしまいます。
画像として色を扱う場合は、光りの強さに応じた明るさを扱う必要があるということです。
輝度は、グレーに変換する場合の係数として、赤 0.298912 緑 0.586611 青 0.114478 となっています。
これは、カラーのテレビ放送が始まった時に、同じ電波でカラーと、モノクロの画像を送る必要があり、カラーの画像を視覚的に同じ明るさで視聴する為に定められた、変換係数のようですが、実際には、赤
0.3 緑 0.6 青 0.1 でも、問題はありません。
テレビ画像は、輝度(y)と色信号(Cb,Cr)からなっている事になり、此処での計算も、輝度(y)と色信号(Cb,Cr)を利用して計算します。
左の計算式は、上側がRGBから輝度値と彩度に変換する式で、ST が彩度の値となります。
下側が、輝度値と、彩度からRGBに変換をする式です。
輝度値をグレー画像として利用しなければ、変換の係数は赤=1/3、緑=1/3、青=1/3 でも問題ありません。
色相は、カラー値のCR、CBから計算される角度で左のHuが色相となります。
色相の単位は角度の単位となります。
arctanの計算は四象限の答えが出る計算です。
赤 0.299 緑 0.587 青 0.114で 輝度値を計算して色相を求めると、通常は 赤 113度 緑 225度 青
353度となり、120度等分にはなりません。
又、一般的な色相環は、赤の位置が0度ですが、ここの計算では0度になりません。
緑の位置は、変換係数の値に関係なく 225度 なります。
120度等分にするには、次の値に設定すれば120度等分となりますが、グレー(輝度)変換時の濃度に差が出ます。
赤 kr := 255 / (255 - 255 * tan(105 / 180 * pi)); // kr = kb =
0.211325
青 kb := kr;
緑 kg := 1 - kr - kb; // kg =
0.57735
120度等分にした場合は、赤と、青の係数が同じになるので、グレーにした場合、同じ明るさだと、同じ輝度値(グレー値)になるので、グレー画像では判別できなくなります。
しかし、グレー画像に変換した場合、どの様な変換係数を設定しても、同じ輝度値になる組み合わせの条件は、必ず存在するのであまり気にする必要はないと思います。
まして、カラー画像を、グレーに変換して処理をするような事は、特殊な場合を除いて殆ど無いでしょう。
上のサンプル画像で、右から二番目の輝度が赤 0.299 緑 0.587 青
0.114の値で輝度変換したもので、右側の輝度1が、120度等分になるして輝度変換したものです。
赤の部分の明るさが、輝度1の方が暗くなっているのが分かります。
上側の画像が、色相角度120°毎になるように変換係数を設定した場合の角度を変更した場合で、下側が標準の変換係数を使用した場合です。
120°変更した時に、差が顕著に現れていますが、240°ではあまり差が分かりません。
色相環を表示すると、赤の位置と、青の位置がずれていることが分かります。
一般的な方法では、赤を0度しますが、此処で作成したプログラムは、0度に補正せず計算された値で表示しています。
0度する場合は、プログラムをダウンロードして、赤の角度分シフト、色の方向は、描画時の座標計算を逆にして下さい。
サンプルプログラム (ColorControl)
unit Main; interface uses Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics, Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.ExtDlgs, Vcl.ExtCtrls, Vcl.StdCtrls, System.Math, Vcl.Buttons; Type TDIarray = array of array of Double; type TForm1 = class(TForm) FileOpenbtn: TButton; Image1: TImage; OpenPictureDialog1: TOpenPictureDialog; RGBtoHSVtoRGBBtn: TButton; Image2: TImage; sat_imageBtn: TButton; hue_imageBtn: TButton; YmEdit: TLabeledEdit; SmEdit: TLabeledEdit; HdEdit: TLabeledEdit; CheckBox1: TCheckBox; yTograyBtn: TButton; tran_yshBtn: TBitBtn; procedure FileOpenbtnClick(Sender: TObject); procedure FormCreate(Sender: TObject); procedure FormDestroy(Sender: TObject); procedure RGBtoHSVtoRGBBtnClick(Sender: TObject); procedure sat_imageBtnClick(Sender: TObject); procedure hue_imageBtnClick(Sender: TObject); procedure tran_yshBtnClick(Sender: TObject); procedure yTograyBtnClick(Sender: TObject); private { Private 宣言 } procedure rgb_to_yc; // R,G,Bから輝度,色差信号に変換 procedure yc_to_rgb(y, c1, c2: TDIarray); // 輝度,色差信号からR,G,B信号に変換 procedure c_to_sh; // 色差信号から彩度,色相を計算 procedure sh_to_c; // 彩度,色相から色差信号を計算 function sat_image: Integer; // 彩度データを濃淡画像化 procedure hue_image; // 色相データを画像化 procedure tran_ysh(ym, sm, hd :Double); // 輝度,彩度,色相を変える procedure setarray; // 配列サイズ設定 public { Public 宣言 } end; type TPrgbarry = array[0..0] of Trgbtriple; Prgbarry = ^TPrgbarry; var Form1: TForm1; implementation {$R *.dfm} const OpenFileFilter = '画像ファイル|*.png;*.jpg;*.gif;*.bmp;*.tif;*.ico;*.wdp'+ '|*.png|*.png' + '|*.jpg|*.jpg' + '|*.gif|*.gif' + '|*.bmp|*.bmp' + '|*.tif|*.tif' + '|*.ico|*.ico' + '|*.wdp|*.wdp'; SaveFileFilter = '画像ファイル|*.png;*.jpg;*.gif;*.bmp;*.tif;*.wdp' + '|*.png|*.png' + '|*.jpg|*.jpg' + '|*.gif|*.gif' + '|*.bmp|*.bmp' + '|*.tif|*.tif' + '|*.wdp|*.wdp'; ImageHW = 384; // 表示枠サイズ // kr = 0.299; // R 輝度変換係数 // kg = 0.587; // G 輝度変換係数 // kb = 0.114; // B 輝度変換係数 var GHeight, GWidth : integer; Vrect : Trect; InBitmap : TBitmap; OutBitmap : TBitmap; Inprgbarry : array of Prgbarry; y : TDIarray; // y 輝度 c1 : TDIarray; // R - Y c2 : TDIarray; // B - y sat : TDIarray; // 彩度 hue : TDIarray; // 色相 Out_y : TDIarray; // y 輝度 Out_sat : TDIarray; // 彩度 Out_Hue : TDIarray; // 色相 Out_c1 : TDIarray; // R - Y Out_c2 : TDIarray; // B - y kr : double; // = 0.299; R 輝度変換係数 kg : double; // = 0.587; G 輝度変換係数 kb : double; // = 0.114; B 輝度変換係数 //------- tran_ysh 輝度,彩度,色相を変えます ----------------------- // y: 入力データ配列 Y // sat: 入力データ配列 SAT // hue: 入力データ配列 HUE // out_y: 出力データ配列 Y // out_sat: 出力データ配列 SAT // out_hue: 出力データ配列 HUE // ym: 輝度の乗数 // sm: 彩度の乗数 // hd: 色相の増分 //------------------------------------------------------------------- procedure TForm1.tran_ysh(ym, sm, hd :Double); var i, j : integer; begin for i := 0 to GHeight - 1 do for j := 0 to GWidth - 1 do begin out_y[i][j] := y[i][j] * ym; // 輝度の計算 out_sat[i][j] := sat[i][j] * sm; // 彩度の計算 out_hue[i][j] := hue[i][j] + hd; // 色相の計算 if out_hue[i][j] > 360 then out_hue[i][j] := out_hue[i][j] - 360; if out_hue[i][j] < 0 then out_hue[i][j] := out_hue[i][j] + 360; end; sh_to_c; // 彩度,色相から色差信号を計算 yc_to_rgb(out_Y, out_C1, out_C2); // 輝度,色差信号からR,G,B信号に変換 Image2.Canvas.StretchDraw(VRect, OutBitmap); // 出力枠に変倍出力 end; //------------- 輝度、彩度、色相を変えます ----------------- // ym: 輝度の乗数 // sm: 彩度の乗数 // hd: 色相の増分 //-------------------------------------------------------- procedure TForm1.tran_yshBtnClick(Sender: TObject); var ym : Double; sm : Double; hd : Double; ch : Integer; begin if CheckBox1.Checked then begin // チェック時 輝度変換係数を色相角度が120度毎になるように設定 R 105度 g 225度 b 345度 // Rを105度にする kr := 255 / (255 - 255 * tan(105 / 180 * pi)); // kr = kb = 0.211325 kb := kr; kg := 1 - kr - kb; // kg = 0.57735 end else begin // 通常は R 113度 g 225度 b 353 度 kr := 0.299; // R 輝度変換係数 kg := 0.587; // G 輝度変換係数 kb := 0.114; // B 輝度変換係数 end; setarray; // 配列サイズ設定 rgb_to_yc; // R,G,Bから輝度,色差信号に変換 c_to_sh; // 色差信号から彩度,色相を計算 val(ymEdit.Text, ym, ch); // 輝度の乗数 if ch <> 0 then ym := 1; ym := abs(ym); val(smEdit.Text, sm, ch); // 彩度の乗数 if ch <> 0 then sm := 1; sm := abs(sm); val(hdEdit.Text, hd, ch); // 色相の増分 if ch <> 0 then hd := 0; if hd > 360 then hd := 360; // 360度以内に設定 if hd < -360 then hd := -360; tran_ysh(ym, sm, hd); // 輝度,彩度,色相を変える end; //--- hue_image 色相データを画像化します ------------------------------------ // sat: 彩度のデータ配列 // hue: 色相のデータ配列 // stdhue: 基準となる色相値 // OutBitmap: 出力画像 //---------------------------------------------------------------------------- procedure TForm1.hue_image; var i, j : Integer; ihue : Integer; delt : Double; OutPrgbarray : Prgbarry; stdhue : double; begin val(hdedit.Text, stdhue, i); if i <> 0 then stdhue := 0; if stdhue > 360 then stdhue := 360; // 360度以内に設定 if stdhue < -360 then stdhue := -360; for i := 0 to GHeight - 1 do begin OutPrgbarray := OutBitmap.ScanLine[i]; for j := 0 to GWidth -1 do begin if sat[i][j] > 0 then begin // 彩度がゼロ以上だったら delt := abs(hue[i][j] - stdhue); // 色相角度の絶対値 if delt < 0 then delt := delt + 360; if delt > 180 then delt := 360.0 - delt; // 180度以内に設定 ihue := Trunc(255.0 - delt * 255.0 / 180); // 255から0に変換 OutPrgbarray[j].rgbtBlue := ihue; OutPrgbarray[j].rgbtGreen := ihue; OutPrgbarray[j].rgbtRed := ihue; end else begin OutPrgbarray[j].rgbtBlue := 0; OutPrgbarray[j].rgbtGreen := 0; OutPrgbarray[j].rgbtRed := 0; end; end; end; Image2.Canvas.StretchDraw(VRect, OutBitmap); // 出力枠に変倍出力 end; procedure TForm1.hue_imageBtnClick(Sender: TObject); begin hue_image; // 色相データを画像化 end; //--- sat_image 彩度データを濃淡画像化します ------------------------------ // sat: 彩度のデータ配列 // OutBitmap: 出力画像 //-------------------------------------------------------------------------- function TForm1.sat_image: Integer; var i, j : Integer; isat : Integer; min, max : double; OutPrgbarray : Prgbarry; begin min := 255; max := 0; // 彩度の最大値と最小値検索 for i := 0 to GHeight - 1 do for j := 0 to GWidth - 1 do begin if sat[i][j] > max then max := sat[i][j]; if sat[i][j] < min then min := sat[i][j]; end; // 彩度がなかったら終了 if min = max then begin result := -1; exit; end; for i := 0 to GHeight - 1 do begin OutPrgbarray := OutBitmap.ScanLine[i]; for j := 0 to GWidth - 1 do begin isat := Trunc(255 * (sat[i][j] - min) / (max - min)); // 彩度の値を0~255に変換 OutPrgbarray[j].rgbtRed := isat; OutPrgbarray[j].rgbtGreen := isat; OutPrgbarray[j].rgbtBlue := isat; end; end; Image2.Canvas.StretchDraw(VRect, OutBitmap); // 出力枠に変倍出力 result := 0; end; procedure TForm1.sat_imageBtnClick(Sender: TObject); var Ret : Integer; begin Ret := sat_image; // 彩度データを濃淡画像化 if Ret = -1 then application.MessageBox('彩度がありません。','情報',0); end; //--- sh_to_c 彩度,色相から色差信号を計算します ----------------------------- // out_c1: 出力データ配列R-Y // out_c2: 出力データ配列B-Y // out_sat: 彩度のデータ配列 // out_hue: 色相のデータ配列 //---------------------------------------------------------------------------- procedure TForm1.sh_to_c; var i, j : Integer; rad : Double; begin for i := 0 to GHeight - 1 do for j := 0 to GWidth do begin rad := PI * out_hue[i][j] / 180; out_c1[i][j] := out_sat[i][j] * sin(rad); out_c2[i][j] := out_sat[i][j] * cos(rad); end; end; //---- c_to_sh 色差信号から彩度,色相を計算します ---------------------------- // c1: 入力データ配列R-Y // c2: 入力データ配列B-Y // sat: 彩度のデータ配列 // hue: 色相のデータ配列 // 単色角度 R 113度 g 225度 b 353度 通常は均等の角度にはなっていない // 均等配分時 R 105度 g 225度 b 345度 CheckBox check時 //---------------------------------------------------------------------------- procedure TForm1.c_to_sh; const THRESHOLD = 0; // 彩度の有無を判断する閾値 NONE = 0; // 彩度がない場合に代入する値 var i, j : Integer; fhue, length : Double; begin for i := 0 to GHeight -1 do for j := 0 to GWidth - 1 do begin length := c1[i][j] * c1[i][j] + c2[i][j] * c2[i][j]; sat[i][j] := sqrt(length); // 彩度 if sat[i][j] > THRESHOLD then begin fhue := arctan2(c1[i][j] , c2[i][j]) * 180 / pi; // 色相 if fhue < 0 then fhue := fhue + 360; hue[i][j] := fhue; end else hue[i][j] := NONE; // 彩度が閾値以下の時 end; end; //--- yc_to_rgb 輝度,色差信号からR,G,B信号に変換します --------------------- // y: 入力データ配列 Y // c1: 入力データ配列R-Y // c2: 入力データ配列B-Y // OutBitmap: 出力画像 // fr = y[i][j] + c1[i][j] // fb = y[i][j] + c2[i][j] // y[i][j] = 0.299 * fr + 0.587 * fg + 0.114 * fb // 0.587 * fg = y[i][j] - 0.299 * fr - 0.114 * fb // fg = (y[i][j] - 0.299 * fr - 0.114 * fb) / 0.587 // fg = y[i][j] - 0.299 / 0.587 * c1[i][j] - 0.114 / 0.587 * c2[i][j] //------------------------------------------------------------------------------ procedure TForm1.yc_to_rgb(y, c1, c2: TDIarray); var i, j : Integer; ir, ig, ib : Integer; OutPrgbarray : Prgbarry; a, b : Double; begin a := kr / kg; // a = 0.299 / 0.587 b := kb / kg; // b = 0.114 / 0.587 for i := 0 to GHeight - 1 do begin OutPrgbarray := OutBitmap.ScanLine[i]; for j := 0 to GWidth - 1 do begin ir := Trunc(y[i][j] + c1[i][j]); // R = Y + (R - y) if ir > 255 then ir := 255; if ir < 0 then ir := 0; ig := Trunc(y[i][j] - a * c1[i][j] - b * c2[i][j]); // G = Y - a(R - Y) - b(B - Y); if ig > 255 then ig := 255; if ig < 0 then ig := 0; ib := Trunc(y[i][j] + c2[i][j]); // B = Y + (B - Y) if ib > 255 then ib := 255; if ib < 0 then ib := 0; OutPrgbarray[j].rgbtRed := ir; OutPrgbarray[j].rgbtGreen := ig; OutPrgbarray[j].rgbtBlue := ib; end; end; end; // グレイ画像(輝度)表示 procedure TForm1.yTograyBtnClick(Sender: TObject); var i, j : Integer; iglay : Integer; OutPrgbarray : Prgbarry; begin for i := 0 to GHeight - 1 do begin OutPrgbarray := OutBitmap.ScanLine[i]; for j := 0 to GWidth - 1 do begin iglay := round(y[i,j]); OutPrgbarray[j].rgbtRed := iglay; OutPrgbarray[j].rgbtGreen := iglay; OutPrgbarray[j].rgbtBlue := iglay; end; end; Image2.Canvas.StretchDraw(VRect, OutBitmap); // 出力枠に変倍出力 end; //--- rgb_to_yc R,G,Bから輝度,色差信号に変換します ------------------------ // Inprgbarry: 入力画像配列 // y: 出力データ配列 Y // c1: 出力データ配列R-Y // c2: 出力データ配列B-Y // y[i][j] := 0.299 * fr + 0.587 * fg + 0.114 * fb; // Y // c1[i][j] := 0.701 * fr - 0.587 * fg - 0.114 * fb; // R - Y (fr - y) // c2[i][j] := -0.299 * fr - 0.587 * fg + 0.886 * fb; // B - Y (fb - y) //----------------------------------------------------------------------------- procedure TForm1.rgb_to_yc; var i, j : Integer; fr, fg, fb : Double; begin for i := 0 to GHeight - 1 do for j := 0 to GWidth - 1 do begin fr := Inprgbarry[i][j].rgbtRed; fg := Inprgbarry[i][j].rgbtGreen; fb := Inprgbarry[i][j].rgbtBlue; // fr := 0; // fg := 0; // fb := 255; y[i][j] := kr * fr + kg * fg + kb * fb; // Y c1[i][j] := fr - y[i][j]; // R - Y (fr - y) c2[i][j] := fb - y[i][j]; // B - Y (fb - y) end; end; //---------- 配列の長さセット--------------------- // y: データ配列 Y // c1: データ配列R-Y // c2: データ配列B-Y // sat: 彩度のデータ配列 // hue: 色相のデータ配列 // out_c1: 出力データ配列R-Y // out_c2: 出力データ配列B-Y // out_sat: 出力彩度のデータ配列 // out_hue: 出力色相のデータ配列 // Inprgbarry: 入力画像配列 //------------------------------------------------ procedure TForm1.setarray; var i : integer; begin setlength(Inprgbarry, GHeight); setlength(y, GHeight, GWidth); setlength(C1, GHeight, GWidth); setlength(C2, GHeight, GWidth); setlength(sat, GHeight, GWidth); setlength(hue, GHeight, GWidth); setlength(out_sat, GHeight, GWidth); setlength(out_hue, GHeight, GWidth); setlength(out_y, GHeight, GWidth); setlength(out_C1, GHeight, GWidth); setlength(out_C2, GHeight, GWidth); for i := 0 to GHeight - 1 do Inprgbarry[i] := InBitmap.ScanLine[i]; OutBitmap.Width := GWidth; OutBitmap.Height := GHeight; end; //------- 各変換後元へ戻して画像表示確認-------------------- procedure TForm1.RGBtoHSVtoRGBBtnClick(Sender: TObject); begin if CheckBox1.Checked then begin // チェック時 輝度変換係数を色相角度が120度毎になるように設定 R 105度 g 225度 b 345度 // Rを105度にする kr := 255 / (255 - 255 * tan(105 / 180 * pi)); // kr = kb = 0.211325 kb := kr; kg := 1 - kr - kb; // kg = 0.57735 end else begin // 通常は R 113度 g 225度 b 353 度 kr := 0.299; // R 輝度変換係数 kg := 0.587; // G 輝度変換係数 kb := 0.114; // B 輝度変換係数 end; setarray; // 配列サイズ設定 rgb_to_yc; // R,G,Bから輝度,色差信号に変換 c_to_sh; // 色差信号から彩度,色相を計算 yc_to_rgb(Y, C1, C2); // 輝度,色差信号からR,G,B信号に変換 Image2.Canvas.StretchDraw(VRect, OutBitmap); // 出力枠に変倍出力 sat_imageBtn.Enabled := True; hue_imageBtn.Enabled := True; yTograyBtn.Enabled := True; end; //------- 画像ファイルオープン 表示--------------------------- procedure TForm1.FileOpenbtnClick(Sender: TObject); var WIC : TWICImage; InFilename : String; IHeight : Integer; IWidth : Integer; begin VRect := Rect(0, 0, Image1.Width, Image1.Height); Image1.Canvas.Brush.Style := bsSolid; Image1.Canvas.Brush.Color := clBtnface; Image1.Canvas.FillRect(VRect); // Canvas 画像消去 Image2.Canvas.Brush.Style := bsSolid; Image2.Canvas.Brush.Color := clBtnface; Image2.Canvas.FillRect(VRect); // Canvas 画像消去 OpenPictureDialog1.Filter := OpenFileFilter; // ファイルオープンフィルターの設定 if OpenPictureDialog1.Execute then // ファイルが指定されたら begin WIC := TWICImage.Create; // TWICImageの生成 try InFilename := OpenPictureDialog1.FileName; // ファイル名の取得 WIC.LoadFromFile(InFilename); // 画像の読み込み GHeight := WIC.Height; // 画像高さ取得 GWidth := WIC.Width; // 画像幅 IWidth := ImageHW; // 出力先イメージ1の幅 IHeight := ImageHW; // 出力先イメージ1の高さ if GHeight <= GWidth then // 縦横比により出力サイズ設定 IHeight := Round(IWidth * GHeight / GWidth) else IWidth := Round(IHeight * GWidth / GHeight); Image1.Width := IWidth; Image1.Height:= IHeight; Image1.Picture.Bitmap.SetSize(IWidth, IHeight); VRect := Rect(0, 0, IWidth, IHeight); // 出力枠設定 Image1.Canvas.StretchDraw(VRect, WIC); // 出力枠に変倍出力 InBitmap.Width := GWidth; InBitmap.Height := GHeight; InBitmap.Canvas.Draw(0, 0, WIC); // DrawでInBitmapに入力画像設定フォーマット24ビットに変換 finally WIC.Free; // TWICImage 解放 end; end else exit; RGBtoHSVtoRGBBtn.Enabled := True; sat_imageBtn.Enabled := False; hue_imageBtn.Enabled := False; tran_yshBtn.Enabled := True; end; //----------- 初期設定 ------------------------ procedure TForm1.FormCreate(Sender: TObject); begin Image1.Width := ImageHW; Image1.Height := ImageHW; Image2.Width := ImageHW; Image2.Height := ImageHW; InBitmap := TBitmap.Create; OutBitmap := TBitmap.Create; InBitmap.PixelFormat := pf24bit; OutBitmap.PixelFormat := pf24bit; RGBtoHSVtoRGBBtn.Enabled := False; sat_imageBtn.Enabled := False; hue_imageBtn.Enabled := False; tran_yshBtn.Enabled := False; yTograyBtn.Enabled := False; tran_yshBtn.Caption := 'Color調整' + #13#10 + 'HSV to RGB'; end; //------------ 終了処理 ------------------------ procedure TForm1.FormDestroy(Sender: TObject); begin InBitmap.Free; OutBitmap.Free; end; end.