LazarusCompleteGuide:9.1
캔버스
canvas는 모든 그리기 연산의 기본이다. canvas는 시각적 컴포넌트의 프로퍼티이며, TBitmap과 같은 그래픽 클래스로부터 파생된다. Canvas 클래스 프로퍼티가 되면 고유의 메모리를 가지지 않는다. 따라서 캔버스에 대해 시각적 컨트롤을 인스턴스화하기 전에 캔버스를 사용하려 하면 오류가 발생하는 것이다. LCL은 폼에 시각적 컴포넌트를 그리고 시각적 컴포넌트를 화면에 표시하는 데 필요한 준비단계를 책임진다.
캔버스에 그리는 작업은 항상 X, Y 좌표를 이용해 이루어진다. 좌표 시스템의 원점은 항상 작업영역의 상단 좌측 모서리에 해당한다. 창의 제목 표시줄은 이 영역 밖에 위치하므로 영향을 받지 않는다. 그리기는 구분된 X와 Y 정수 좌표를 이용하거나, 두 좌표가 레코드로서, 즉 TPoint (X와 Y 좌표를 연결하는 레코드) 혹은 TRect 로서 그룹화된 좌표를 이용하는 방법이 있다. 일부 그래픽 메소드는 절대 좌표(absolute coordinate)로 작업하는 대신 TSize 레코드를 사용한다. 그러한 레코드는 너비와 높이만 정의하고 대상 위치는 정의하지 않는다.
TPoint = record
X: LongInt;
Y: LongInt;
end;
PPoint = ^TPoint;
TRect = record
case Integer of
0: (Left, Top, Right, Bottom: LongInt);
1: (TopLeft, BottomRight : TPoint);
end;
PRect = ^TRect;
TSize = record
cx : LongInt;
cy : Longint;
end;
PSize = ^TSize;
실제 선언은 좀 더 복잡하다. 이는 타입 선언이 Classes, Types, Windows 유닛의 내용과 동일하도록 보장한다. 하지만 위에 표시된 결과는 개발자들이 보는 결과이다. 작은 예제를 통해 라자루스 그리기 연산에 숨은 철학을 설명하고자 한다. 우리는 새 라자루스 애플리케이션을 생성하고, 단 두 줄만으로 창의 FormMouseDown 이벤트를 확장시켰다:
procedure TForm1.FormMouseDown(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: integer);
begin
Canvas.MoveTo(x, y);
Canvas.LineTo(100, 100);
end;
마우스가 폼을 누르고 나면 X와 Y는 실제 X와 Y 좌표를 포함할 것이다. 그리기 툴은 그 위치로 이동되고, 창에서 사전 프로그램화된 위치에 (100, 100) 선이 그려진다. 이러한 그리기 연산은 창 캔버스에서 실행되며, 다른 컨트롤의 캔버스가 명시되지 않는 한 항상 실행될 것이다. 이 작업은 언제든 바뀔 수 있다. 창이 업데이트되면 (예: 크기가 변경 시) 앞서 그린 행은 사라질 것이다.
캔버스는 이런 간단한 연산 외에도 많은 연산을 실행할 수 있다. 다음 정의에서 가장 중요한 메소드와 프로퍼티를 표 9.1에 설명하였다. 후에 더 많은 예제를 들어 보이겠다.
폼의 캔버스에서 작업은 유지되지 않으므로 컨트롤의 업데이트가 끝날 때마다 그래픽 데이터는 다시 그려져야 할 것이다. 따라서 그래픽을 유지시키려면 버퍼에 완성된 그리기 연산의 리스트를 스스로 보관해야 한다.
비시각적 버퍼 객체는 그리기가 시작되기 전에 생성되어야 한다. 다음의 간단한 예제는 bitmap 을 그리기를 위한 버퍼로 활용한다:
procedure TMyForm.FormPaint(Sender: TObject);
var MyBitmap: TBitmap;
begin
MyBitmap := TBitmap.Create; (* Create the working buffer *)
try
MyBitmap.Height := 100; (* set the image size *)
MyBitmap.Width := 100;
(* After the bitmap's memory is allocated, we can start drawing: *)
MyBitmap.Canvas.Rectangle(0, 0, 100, 100);
(* Draw the bitmap on the window's canvas *)
Canvas.Draw(0, 0, MyBitmap);
finally
MyBitmap.Free; (* free the memory buffer *)
end;
end;
캔버스에 그리기를 실행하기 위해서는 객체를 그릴 좌표만 공급하면 된다. 이러한 객체의 기본 프로퍼티들은 아래와 같이 캔버스 그리기 툴에 이미 구축되어 있다.
메소드/프로퍼티 | 설명 |
procedure Arc | 펜을 이용해 Arc를 그린다. |
procedure CopyRect | 다른 캔버스의 부분을 복사한다. |
procedure Draw | 캔버스에 이미지 클래스를 그린다 (TBitmap과 같은). |
procedure StretchDraw | 이미지의 크기조정된(scaled) 버전을 그린다. |
procedure Ellipse | 펜을 이용해 타원형 윤곽을 그린다. |
procedure FloodFill | 연결된 픽셀 표면을 색상으로 채운다. |
procedure Line | 펜을 이용해 선 하나를 그린다. |
procedure Polygon | 펜을 이용해 다각형 윤곽을 그린다. |
procedure Polyline | 펜을 이용해 연결된 선의 시퀀스를 그린다. |
procedure Rectangle | 펜을 이용해 직사각형 프레임을 그리고 브러시를 이용해 내용을 채운다. |
procedure TextOut | 폰트 눌을 이용해 텍스트를 그린다. |
procedure TextRect | 명시된 직사각형 내에서 텍스트를 그린다. |
function TextExtent function TextHeight function TextWidth |
현재 폰트를 이용해 그릴 때 텍스트가 가질 너비 또는 높이를 리턴한다 (TextExtent의 경우는 둘다) 각 이미지 픽셀은 해당 프로퍼티로 설정할 수 있다. |
property Pixels: Tcolor; [read/write] |
점을 그린다 (TPoint 그리기와 동일). |
표 9.1: TCanvas의 가장 중요한 그리기 메소드 |
캔버스의 가장 중요한 기본 프로퍼티는 그리기 브러시(원이나 직사각형을 그릴 때 표면을 채우는) Style과 Color 이다; 객체의 윤곽과 어떤 선이나 곡선을 그리는 데 사용되는 Pen, 그리고 데이터를 출력하는 데 사용되는 Font. TCanvas 클래스는 애플리케이션의 기본 그리기 연산을 처리한다. 그 이상의 연산이 필요할 경우 본인이 알아서 구현하면 될 일이다.
TPen, TBrush, TFont 클래스들은 TCanvas 클래스를 확장한다.
색상
TColor 타입의 색상은 캔버스에 그릴 때 사용된다. TColor 는 TPen, TBrush, TFont 클래스에서 가져온다. 색상명은 시스템 전체에 걸쳐 유효하고, 모든 플랫폼에 대해 동일하게 정의된다. 따라서 루프 내 루프 계수기(loop counter)를 TColor 로 캐스트(cast)를 시도할 필요가 없다. 패턴의 경우도 마찬가지다.
예를 들어:
var i: integer;
Pen.Color := TColor(i);
위는 구문상으론 옳지만 무용지물인 것이다! 우리는 항상 Graphics 유닛에서 정의된 상수를 사용할 필요가 있다. 가장 중요한 상수만 아래에 나열하였다:
clBlack TColor($000000); // Black
clMaroon TColor($000080); // Maroon
clGreen TColor($008000); // Green
clOlive TColor($008080); // Olive
clNavy TColor($800000); // Navy
clPurple TColor($800080); // Purple
clTeal TColor($808000); // Teal
clGray TColor($808080); // Gray
clSilver TColor($C0C0C0); // Silver
clRed TColor($0000FF); // Red
clLime TColor($00FF00); // Lime
clYellow TColor($00FFFF); // Yellow
clBlue TColor($FF0000); // Blue
clFuchsia TColor($FF00FF); // Fuchsia
clAqua TColor($FFFF00); // Aqua
clLtGray TColor($C0C0C0); // = clSilver alias Light Gray
clDkGray TColor($808080); // = clGray alias Dark Gray
clWhite TColor($FFFFFF); // White
StandardColorsCount = 16;
// Extended colors
clMoneyGreen = TColor($C0DCC0); // a brilliant keynote
clSkyBlue = TColor($F0CAA6); // sky blue
clCream = TColor($F0FBFF); // very light gray
clMedGray = TColor($A4A0A0); // another medium gray
ExtendedColorCount = 4;
위의 리스트 외에도 수많은 시스템 색상이 있다. 라자루스 또한 Borland가 Kylix용으로 정의한 색상을 CLX에 포함하고 있는데, 이는 구식이라 더 이상 사용해선 안 된다. 물론 자신만의 색상을 정의할 수도 있다. 이를 위해선 색상이 어떻게 정의되는지 알 필요가 있다. 색상은 BBGGRR 포맷으로 24-비트 수로 코딩된다. 해당 번호가 0으로 설정 시 채널이 까맣게 된다. $FF 숫자를 얻게 되면 다시 불이 완전히 켜질 것이다. 그 중간 값도 모두 가능하다. 표준 색상은 이를 어떻게 사용하는지 보여주는 작은 예제이다: “Maroon(적갈색)”은 불그스름한 색상으로, 적색 채널에 $80를 적용시키면 얻을 수 있다. 밝은 적색은 $FF로 만들 수 있다. 이 때 다른 채널들은 꺼진 채로 유지된다. 회색 색상을 만들기 위해서는 세 가지 채널 모두 동일한 값을 얻어야 한다; 값이 클수록 색상은 연해진다. 흰색은 $FFFFFF까지 올라간다. 아래 루틴이 이 예제를 보여준다:
procedure Tform1.FormMouseDown(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Integer );
begin
with Canvas do
begin // red component green component blue component
Pen.Color := Random($FF) + Random($FF) * $100 +
Random($FF) * $10000;
MoveTo(100, 100);
LineTo(X, Y);
end;
end;
주의를 기울여야 하는 몇 가지 특수 색상이 있다. 이러한 색의 32-비트 값은 아래와 같다:
// Special colours
clNone = TColor($1FFFFFFF);
clDefault = TColor($20000000);
색상을 정의하도록 도와주는 툴이 있다. 라자루스는 이용 가능한 팔레트에서 전문적으로 색상을 선택하는 컴포넌트 두 개를 포함한다. 색상을 할당하는 기본은 컴포넌트 팔레트의 Misc 탭에서 TColorButton 이 된다. 이 컴포넌트에서 버튼은 컴포넌트 팔레트 Dialogs 탭으로부터 TColorDialog 를 호출한다. TColorDialog 는 그 정의부에서 볼 수 있듯이 Execute 루틴을 가지지 않는다:
TColorDialog = class(TCommonDialog)
private
// ... omitted
protected
// ... omitted
public
constructor Create(TheOwner: TComponent); override;
destructor Destroy; override;
published
property Title;
property Color: TColor read FColor write FColor;
// Entry looks like ColorA = FFFF00 ... ColorX = C0C0C0
property CustomColors: TStrings read FCustomColors
write SetCustomColors;
end;
이용 가능한 프로퍼티들은 대화창의 색상을 작업 시엔 필요하지 않다. 이는 모두 TColorButton 색상 버튼 컨트롤이 처리한다. 이 속도 버튼 자손의 가장 중요한 메소드는 Click이고, 가장 중요한 프로퍼티는 ColorDialog 와 ButtonColor 이다. 가장 중요한 이벤트는 OnColorChange 이다 (ColorDialog를 이용해 색상 대화창을 할당할 수 있지만 준비된 표준 대화창을 사용하길 원하지 않을 때에만 필요하다. 프로퍼티를 비워두더라도 대화창은 포함될 것이다. 이는 버튼을 클릭하면 자동으로 발생한다.). 대화창은 최근 선택된 색상을 항상 (Color 가 아니라!) ButtonColor 에 저장한다. 선택된 색상은 OnClick 이벤트에서 전송되지 않을 것이다. 이는 현재 색상을 보유한다 (혹시 궁금한 이를 위해 알려준다.). 새 색상은 OnColorChanged 이벤트에서 이용할 수 있다. 이는 프로그램 내 Color 프로퍼티로 간단히 할당할 수 있는데, 예를 들면 아래와 같다 (다음 절 참고):
Canvas.Pen.Color := ColorButton1.ButtonColor;
프로그램 시작 시 오리지널 Color를 할당하려면 이 값을 메인 폼의 OnCreate 에 전달하라. 프로그램이 툴바를 포함하지 않는다면 당신은 컴포넌트 팔레트의 Misc 탭에서 TColorButton 을 이용해 Visible 프로퍼티를 False로 설정해야 할 것이다. 예를 들어, 그것의 OnClick 루틴을 메뉴 선택에서 활성화할 수 있다.
TPen
TPen 은 (스타일러스 또는 펜 클래스) 점, 직선, 곡선을 그릴 때 그들의 프로퍼티를 결정하며, Color, Width, Style, Mode, JoinStyle, EndCap, Cosmetic 과 같은 프로퍼티를 가진다:
TPen = class(TFPCustomPen)
private
// omitted
protected
// omitted
public
constructor Create; override;
destructor Destroy; override;
procedure Assign(Source: TPersistent); override;
property Handle: HPEN read GetHandle write SetHandle; Deprecated;
property Reference: TWSPenReference read GetReference;
function GetPattern: TPenPattern;
procedure SetPattern(APattern: TPenPattern); Reintroduce;
published
property Color: TColor read Fcolor
write SetColor default clBlack;
property Cosmetic: Boolean read Fcosmetic
write SetCosmetic default True;
property EndCap: TPenEndCap read FEndCap
write SetEndCap default pecRound;
property JoinStyle: TPenJoinStyle read FJoinStyle
write SetJoinStyle default pjsRound;
property Mode default pmCopy;
property Style default psSolid;
property Width default 1;
end;
모든 활동에 대해 사전 정의된 색상은 검정색이지만 Color는 원하는 어떤 TColor 값으로든 설정 가능하다. Width 는 1을 사전 정의 값으로 가지며, 그릴 직선의 너비를 결정한다. Mode 는 그려진 직선과 배경(background)이 어떻게 상호작용할 것인지를 결정한다. 그것의 사전 정의된 값이자 가장 흔하게 사용되는 값은 pmCopy 인데, 즉 직선의 색상이 Color property 에서 설정될 것을 의미한다. 다른 가능한 값을 표 9.2에 열거하였다.
상수 | 설명 |
pmCopy | Color에 설정된 색상으로 그린다. |
pmBlack | 검정색으로 그린다. |
pmWhite | 흰색으로 그린다. |
pmNop | 그리지 않는다. |
pmNot | 배경색을 반전(invert)시킨다. |
pmNotCopy | Color에 설정된 색상의 반전 색상으로 그린다. |
pmMergePenNot | Color의 색상과 반전 배경 색상을 결합한다. |
pmMaskPenNot | 배경색을 반전시키고 Color에 공통된 색상으로 칠한다. |
pmMaskNotPen | Color 내 색상의 반전과 배경색을 결합한다. |
pmMerge | Color 내 색상과 배경색을 결합한다. |
pmNotMerge | pmMerge에 대한 반전 색상으로 그린다. |
pmMask | Pen과 캔버스 배경에 공통되는 색상을 결합한다. |
pmNotMask | pmMask의 반전색으로 그린다. |
pmXor | Pen 색상과 XOR 연산의 배경색을 결합한다. |
pmNotXor | pmXor의 반전색으로 그린다. |
표 9.2: Mode, 그리고 캔버스 배경과의 상호작용에 가능한 값 |
라자루스 0.9.28 버전부터는 펜을 더 수월하게 사용하도록 해주는 새 프로퍼티들을 이용할 수 있다. 이는 델파이에서 제공되는 것 이상으로 가능성을 확장시키는데, JoinStyle, EndCap, Cosmetic 으로 명명된다. JoinStyle 은 두 개의 연결된 선을 그리는 방식을 제어하며, 표 9.3에 열거된 값을 취할 수 있다.
상수 | 설명 |
pjsRound | 둥근 연결선을 그린다. 기본 값이다. |
pjsBevel | 대각선 커트(Diagonal cut)로 연결선을 그린다. |
pjsMiter | 날카로운 직선(Sharp straight) 연결선을 그린다. |
표 9.3: 연결된 선을 그리기 위한 JoinStyle의 옵션 |
JoinStyle 은 연결된 선에서만 작용한다. 선이 Line, MoveTo 또는 LineTo 루틴으로 차례로 그려진 경우 효과가 없다. 이는 항상 연결되지 않은 것으로 간주되며 하나를 그린 직후 다른 선을 그리더라도 마찬가지다. JoinStyle 은 Polyline 과 (한 번의 동작으로 여러 연결된 선을 그리는) 다각형에 작용한다.
procedure TfrmPen.FormPaint(Sender: TObject);
var MyBitmap : TBitmap;
MyLine : array[0..2] of TPoint;
begin
// ... omitted
// join style
MyBitmap.Canvas.TextOut(200, 225, 'Join Style:');
MyBitmap.Canvas.Pen.Width := 10;
MyBitmap.Canvas.Pen.EndCap := pecRound;
MyBitmap.Canvas.Pen.TextOut(200, 250, 'pjsRound');
MyBitmap.Canvas.Pen.JoinStyle := pjsRound;
MyLine[0] := Point(200, 275);
MyLine[1] := Point(250, 275);
MyLine[2] := Point(250, 325);
MyBitmap.Canvas.Polyline(MyLine);
// ... omitted
end;
EndCap은 Line, MoveTo, LineTo 를 이용해 그린 간단한 선의 끝을 어떻게 그릴 것인지를 결정한다. 이 프로퍼티는 Polyline으로 그린 선 집합의 끝점 모양을 결정한다. 이는 pecRound (기본 값, 둥근 끝), pecSquare (날카로움, 직각 끝), pecFlat (매우 작은 점) 중 하나를 취할 수 있다. 마지막 옵션의 경우 간단한 직선의 pecSquare 와 동일한 모습을 하지만 직선이 가리키는 점(pointed edge)이 약간의 공간을 차지하므로 직선은 약간 더 짧을 것이다.
직선 끝의 행위는 아래 예제에서 볼 수 있다.
procedure TForm1.FormCreate(Sender: TObject);
begin
i := 0; // initialize the loop variable
end;
procedure TForm1.FormMouseDown(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Integer);
Begin
Inc(i);
with Canvas do begin
Pen.Width := 10; // Line thickness
Pen.EndCap := pecFlat; // Starting value, small!
if i mod 2 = 0 then Pen.EndCap := pecRound;
if i mod 3 = 0 then Pen.EndCap := pecSquare;
if i = 3 then i := 0;
Pen.Color := Random($FF) + Random($FF) * $100 +
Random($FF) * $10000;
MoveTo(100, 100);
LineTo(X, Y);
end;
end;
여기 사용된 루프 계수기는 Form1 선언에서 정수 클래스 변수로 정의된다.
Cosmetic 프로퍼티는 펜이 항상 1픽셀 너비일 것임을 나타낸다. 일부 라이브러리는 그리기 루틴을 단순화하는 데에 이 정보를 필요로 한다. 정보를 사용한다면 이러한 연산들을 빠르게 실행할 수 있다. 너비가 1보다 크면 이 프로퍼티는 무시될 것이므로 기본 값으로 비워두면 되겠다; 가능할 경우 단일 픽셀 너비 펜에 대한 빠른 그리기 메소드가 사용되도록 만드는 것이 유일한 목적이다.
TBrush
TBrush(브러시)는 그림 안의 영역을 채우는 데 사용되는 툴이다. 간단한 구조를 가지며 세 가지 주요 프로퍼티만 가진다: Color, Style, Bitmap:
TBrush = class(TFPCustomBrush)
private // ... omitted
Protected // ... omitted
public
procedure Assign(Source: TPersistent); Override;
constructor Create; Override;
destructor Destroy; Override;
property Bitmap: TCustomBitmap Read FBitmap Write SetBitmap;
property Handle: HBRUSH Read GetHandle Write SetHandle; Deprecated;
property Reference: TWSBrushReference Read GetReference;
published
property Color: TColor Read FColor Write SetColor Default clWhite;
property Style Default bsSolid;
end;
Style 은 어떤 영역을 브러시 Color로 채워야 하고 어떤 영역을 채워선 안 되는지 (예: 해당 영역은 현재 Color 로 유지) 나타낸다; 패턴 bsSolid, bsClear, bsHorizontal, bsVertical, bsFDiagonal, bsBDiagonal, bsCross, bsDiagCross 를 이용할 수 있다. 아래 예제 루틴은 이것이 어떤 모습인지 설명한다:
procedure TForm1.Button1Click(Sender: TObject);
var i: Integer;
begin
with Canvas do for i := 0 to 7 do
begin
case i of
0: begin Brush.Style := bsSolid; Brush.Color := clBlack;end;
1: begin Brush.Style := bsClear; Brush.Color := clWhite;end;
2: begin Brush.Style := bsHorizontal;Brush.Color:= clGreen;end;
3: begin Brush.Style := bsVertical; Brush.Color := clOlive;end;
4: begin Brush.Style := bsFDiagonal;Brush.Color := clNavy; end;
5: begin Brush.Style := bsBDiagonal;Brush.Color :=clPurple;end;
6: begin Brush.Style := bsCross; Brush.Color := clTeal; end;
7: begin Brush.Style := bsDiagCross;Brush.Color := clRed; end;
end;
FillRect(i * 100, 0, i * 100 + 100 + i, 100);
end;
end;
브러시 정의부는 직선을 그리는 방식엔 영향을 미치지 않는다. 아래 예제에서는 패턴 주위에 그려진 사각형이 모두 노란색이 될 것이다:
procedure TForm1.Button1Click(Sender: TObject);
var i: Integer;
begin
with Canvas do begin
Pen.Color := clYellow; // draw lines in yellow
for i = 0 to 7 do
begin
case i of
0: begin Brush.Style := bsSolid; Brush.Color := clBlack; end;
1: begin Brush.Style := bsClear; Brush.Color := clWhite; end;
2: begin Brush.Style := bsHorizontal;Brush.Color:= clGreen;end;
3: begin Brush.Style := bsVertical;Brush.Color := clOlive; end;
4: begin Brush.Style := bsFDiagonal;Brush.Color := clNavy; end;
5: begin Brush.Style := bsBDiagonal;Brush.Color:= clPurple;end;
6: begin Brush.Style := bsCross; Brush.Color := clTeal; end;
7: begin Brush.Style := bsDiagCross; Brush.Color := clRed; end;
end;
FillRect( i*100, 0, i*100+100+i, 100); // here the brush is drawing
Rectangle(i*100, 0, i*100+100+i, 100); // here the pen (not brush) is drawing
end;
end;
end;
사전 정의된 브러시에 더해 자신만의 브러시를 정의하는 수도 있다. 보통은 TBrush.Bitmap이 NIL로 설정된다. 자신만의 브러시를 설정하려면 Style을 TBitmap으로 변경한다. 정의된 영역은 비트맵 이미지의 색을 이용해 비트맵으로부터 데이터로 채워질 것이다. Color와 Style 프로퍼티는 이번에 사용되지 않는다.
아래 루틴은,
procedure TfrmBrush.FromPaint(Sender: TObject);
var MyBitmap: TBitmap;
begin // ... omitted
// image brushes
MyBitmap.Canvas.TextOut(25, 250, 'Canvas-aligned bitmapped Brush:');
MyBitmap.Canvas.Brush.Bitmap := TBitmap.Create;
MyBitmap.Canvas.Brush.Bitmap.Height := 20;
MyBitmap.Canvas.Brush.Bitmap.Width := 20;
MyBitmap.Canvas.Brush.Bitmap.Canvas.Brush.Color := clWhite;
MyBitmap.Canvas.Brush.Bitmap.Canvas.FillRect(0, 0, 20, 20);
MyBitmap.Canvas.Brush.Bitmap.Canvas.Brush.Color := clRed;
MyBitmap.Canvas.Brush.Bitmap.Canvas.Ellipse(0, 0, 20, 20);
MyBitmap.Canvas.Rectangle(Bounds( 25, 275, 50, 50));
MyBitmap.Canvas.Rectangle(Bounds(100, 275, 50, 50));
MyBitmap.Canvas.Brush.Bitmap.Free;
MyBitmap.Canvas.Brush.Bitmap := NIL;
// ... omitted
end;
그림 9.2와 같이 하단에 두 개의 브러시 패턴을 그릴 것이다.
비트맵 마스크(bitmap mask)는 하나 이상의 요인에 따라 좌우된다. 한편으론 물론 사용된 이미지의 영향을 받지만, 다른 한편으론 이미지의 blend 방식 또한 영향을 미친다. 그림에서 두 가지 결과가 발생하는 것은 동일한 비트맵에 다른 원점을 가진다는 사실 때문이다 (그림 9.3 참고).
따라서 프로시저를 "canvas aligned, bitmapped brush(캔버스 정렬됨, 비트맵 브러시)"라고 부르는데, 이는 그려진 패턴이 캔버스 상에 영역의 위치에 따라 달라지기 때문이다.
고정된 색상과 스타일로 다시 도형을 채우고 싶다면 Bitmap 프로퍼티를 다시 NIL로 설정하면 된다. 수동으로 비트맵을 다시 해제(free)하는 것이 중요하다. 이 기능은 TBrush 가 해제(free)될 때 자동으로 실행되지 않는 기능이다.
보통 브러시는 도형(shape)을 채우는 데만 사용된다. 하지만 한 가지 예외로 TCanvas.FrameRect 를 들 수 있는데, 이는 직선 그리기에도 브러시 프로퍼티를 사용한다.
폰트
대부분 컴퓨터 사용자들은 폰트의 개념에 익숙할 것이며, 특히 텍스트 에디터로 작업하는 경우라면 더 친숙할 것이다. 폰트는 문자를 출력 장치에 어떻게 표시할 것인지를 설명하면서 여러 스타일과 활자체(typeface)를 가능하게 한다. 라자루스는 유니코드(Unicode)로 작업하여 많은 수의 문자들을 허용한다. 하지만 모든 폰트가 가능한 문자를 모두 포함하는 것은 아니다. 폰트를 선택할 때는 외국어 애플리케이션도 예상대로 작동할 수 있어야 함을 항상 유념해야 한다.
라자루스의 폰트는 기본 프로퍼티 CharSet, Color, Height, Name, Pitch, Size, Style 을 포함한다 (표 9.4 참고). 폰트 정보는 Graphics 유닛에 아래와 같이 정의된다:
type
TFontPitch = (fpDefault, fpVariable, fpFixed);
TFontName = String;
TFontDataName = String[LF_FACESIZE -1];
TFontStyle = (fsBold, fsItalic, fsStrikeOut, fsUnderline);
TFontStyles = set of TFontStyle;
TFontStylesbase = set of TFontStyle;
TFontCharSet = 0..255;
TFontQuality = (fqDefault, fqDraft, fqProof, fqNonAntialiased,
fqAntialiased, fqCleartype, fqCleartypeNatural);
TFontData = record
Handle : HFont;
Height : Integer;
Pitch : TFontPitch;
Style : TFontStylesBase;
CharSet: TFontCharSet;
Quality: TFontQuality;
Name : TFontDataName;
Orientation: Integer;
end;
TFont = class(TFPCustomFont)
private
// .. omitted
protected
// .. omitted
public
constructor Create; override;
destructor Destroy; override;
procedure Assign(Source: TPersistent); override;
procedure Assign(const ALogFont: TLogFont);
procedure BeginUpdate;
procedure EndUpdate;
property FontData: TFontData read GetData write SetData;
function HandleAllocated: boolean;
property Handle: HFONT read GetHandle write SetHandle; deprecated;
function IsDefault: boolean;
function IsEqual(AFont: TFont): boolean; virtual;
property IsMonoSpace: boolean read GetIsMonoSpace;
procedure SetDefault;
property CanUTF8: boolean read GetCanUTF8;
property PixelsPerInch: Integer read FPixelsPerInch
write FPixelsPerInch;
property Reference: TWSFontReference read GetReference;
published
property CharSet: TFontCharSet read GetCharSet write SetCharSet
default DEFAULT_CHARSET;
property Color: TColor read FColor write SetColor
default clWindowText;
property Height: Integer read GetHeight write SetHeight
stored IsHeightStored;
property Name: string read GetName write SetName
stored IsNameStroed;
property Orientation: Integer read GetOrientation
write SetOrientation
default 0;
property Pitch: TFontPitch read GetPitch write SetPitch
default fpDefault;
property Quality: TFontQuality read FQuality write SetQuality
default fqDefault;
property Size: Integer read GetSize write SetSize stored false;
property Style: TFontStyles read GetStyle write SetStyle default [];
end;
기본 프로퍼티 외에도 데이터를 얻고 설정하며, 폰트를 할당하는 데 제공되는 메소드들도 있다. 텍스트출력은 캔버스의 Textout 메소드가 처리한다. 코드에서 이는 아래와 같은 모습일 것이다:
with Canvas do begin
Font.Name := 'Courier'; // the font name (a string)
Font.Color := clRed; // colour (a TColor)
Font.Size := 24; // size in points
Font.Style := [fsBold, fsItalic]; // a set combining various style values
TextOut(20, 20, 'Lazarus');
end;
TextOut 은 명시된 X와 Y 좌표에 명시된 텍스트를 작성할 것이다. 하지만,
TextOut(20, 20, 'Lazarus'^J'is'^J'Great!');
위의 코드를 이용할 경우 당신이 원하는 결과가 나오지는 않는다. 다중행으로 된 텍스트를 생성하려면 좀 더 복잡한 코드를 사용해야 한다:
TextOut(20, 20, 'Lazarus');
TextOut(20, 20+36, 'is');
TextOut(20, 20+36+36, 'Great!');
값 36은 우리가 사용하는 24포인트 폰트에 적절한 12포인트의 행 구분(line separation)이 발생할 것이다.
Height 는 폰트 크기를 픽셀로 나타내며, 포인트로 측정되는 (인치의 1/72) Size 프로퍼티에 설정할 수 있다. 양수 크기는 행 띄우기(line spacing)를 제외시키므로 행 띄우기를 포함하는 음수 크기를 이용할 수도 있겠다. 폰트 높이를 명시하는 두 개의 대안 프로퍼티는 (Height와 Size) 두 가지 공통된 텍스트 출력 목적지, 즉 화면 또는 용지에 인쇄를 바탕으로 한다. ‘픽셀’은 상황에 따라 크기가 다를 수 있음을 명심한다. 폰트의 Size 와 Height 는 아래 공식에 의해 관련된다:
Font.Size = -(Font.Height * 72) / Font.PixelsPerInch
Pitch 는 글자간 간격을 의미한다. 이는 fpDefault, fpFixed, fpVariable 중 하나를 취할 수 있고, 이 파라미터를 바탕으로 어떤 폰트를 사용해야 하는지를 라이브러리로 알린다. fpFixed의 경우 고정폭 폰트가 (Courier와 같은) 사용될 것이다; fpVariable 의 경우 비례 폰트가 사용된다. fpDefault 는 폰트 선택 시 이 파라미터를 무시해야 함을 나타낸다. 선택된 폰트에 대해 피치(pitch) 파라미터가 불가능하다면 라이브러리는 주어진 폰트 이름을 무시하고 다른 것으로 대체할 것이다. TFont.IsMonospace 는 실제 폰트를 제공하지 않지만 특정 폰트가 비례인지 아닌지 표시하는 설명을 제공한다.
라자루스 0.9.28 버전부터는 그리게 될 텍스트의 Orientation(방향)을 설정할 수도 있다. 이 프로퍼티는 캔버스의 X 축과 텍스트의 기본선(base line) 사이의 각으로 설정되어야 한다 (0.1도). 텍스트 출력에 사용되는 중요한 함수는 아래와 같다:
function TextExtent(const Text: string): TSize; virtual;
프로퍼티 | 설명 |
CharSet | 문자 집합 또는 코드페이지. 해당 프로퍼티는 델파이와의 호환성 용도로만 존재한다. 라자루스는 텍스트 문자열의 인코딩에 UTF8을 사용하므로 (코드페이지를 선택할 필요 없이 모든 기존 문자를 표시하는), 해당 프로퍼티는 무시된다. |
Color | 텍스트 색상. |
Height | 폰트 크기, 픽셀. |
Name | 폰트의 이름. |
Pitch | 문자간 간격. fpDefault, fpFixed, fpVariable 중 하나가 될 수 있다. |
Size | 폰트 크기, 인체의 1/72 (포인트로도 알려짐). 음수는 줄간 간격이 크기에 포함되어 있음을 의미하고, 양수는 행 띄우기(line spacing)를 제외한 값이다. |
Style | 다음 스타일 중 서로 결합하거나 어떤 것도 결합하지 않은 집합: fsBold, fsItalic, fsStrikeOut, fsUnderline. |
표 9.4: TFont의 기본 프로퍼티 |
가장 중요한 부분인 그래픽 루틴
이제 TColor, TPen, TBrush, TFont 에 관해 알아봤으니 앞서 언급한 캔버스의 메소드로 작업을 시작할 준비가 되었다.
픽셀
이미지의 각 픽셀을 개별적으로 설정하는 것이 가능하다. 하지만 이러한 작업은 매우 느린 프로세스로, 이미지를 이동시키는 경우 비실용적이라는 점은 인정해야 한다.
property Pixels[X, Y: Integer]: TColor read GetPixel write SetPixel;
Pixels 프로퍼티는 특정 하나의 이미지 픽셀을 읽거나 쓰기 때문에 브러시나 펜과 같은 추가 함수 및 프로퍼티가 필요하지 않다. 픽셀은 직접 색상 상수 또는 RGB 값을 픽셀로 할당함으로써 설정할 수 있다:
Canvas.Pixels[10, 20] := clBlack;
Reading is just as simple:
var MyColor: TColor;
begin
MyColor := Canvas.Pixels[10, 20];
직선
직선은 시작점을 설정한 후, MoveTo 로 호출 다음에 LineTo 를 호출함으로써 끝지점으로 그림을 그리면 직선이 완성된다. 각 LineTo는 그리기 커서를 추후 LineTo 명령을 위한 새 시작 위치로 이동시킨다.
procedure MoveTo(x, y: Integer);
procedure MoveTo(p: TPoint);
procedure LineTo(x, y: Integer);
procedure LineTo(p: TPoint);
다음 직선의 시작 위치는 PenPos 프로퍼티에 저장되어 후에 읽거나 쓰는 것이 가능하다:
property PenPos: TPoint read FPenPos write SetPenPos;
PenPos로 직접 할당 시 MoveTo를 호출할 때와 동일한 효과를 보인다. MoveTo 다음에 다수의 LineTo 호출이 따라올 경우 원하는 다각형을 그릴 것이다. 아래 루틴에서는 시작 위치를 설정하는 데에 더 이상 MoveTo가 필요하지 않다. Line을 이용하면 시작 위치와 끝 위치가 모두 파라미터로 전달된다. 이는 좌표 쌍에서 직접 이루어지거나 TRect 구조의 형태에서 이루어진다.
procedure Line(x1, y1, x2, y2: Integer);
procedure Line(const p1, p2: TPoint);
procedure Line(const Points: TRect);
다각형
폴리라인(polyline)은 다각형 그리기에서 turtle을 대체한다. 좌표는 여러 방법으로 전달할 수 있는데, TPoint의 배열로서 일반 파스칼 포맷으로 전달하거나, 배열에 사용할 포인트의 제공을 통해 전달하거나, 좀 더 C언어적 스타일로 포인터를 TPoint 배열로 전달하는 방법이 있다. 세 가지 버전은 모두 배열에 열거된 모든 포인트를 연결하는 선이 그려진다.
procedure Polyline(const Points: array of TPoint);
procedure Polyline(const Points: array of TPoint; Startindex: Integer; NumPts: Integer = -1);
procedure Polyline(Points: PPoint; NumPts: Integer);
채워진 다각형을 그리는 방법을 Polygon이라 부른다. 외부 프레임은 펜으로 그린다; 영역 채우기는 브러시로 실행한다. Polyline과 마찬가지로 Polygon도 연결할 포인트를 열거하는 데에 TPoints의 배열을 필요로 한다.
procedure Polygon(const Points: array of TPoint; Winding: Boolean;
Startindex: Integer = 0; NumPts: Integer = -1);
procedure Polygon(Points: PPoints; NumPts: Integer;
Winding: boolean = False); virtual;
procedure Polygon(const Points: array of TPoint);
직사각형
캔버스에는 직사각형을 그리기 위한 함수가 몇 가지 포함되어 있다. 이는 직사각형이 그래픽 작업에서 가장 자주 사용되는 요소이기 때문이다. 직사각형을 그리는 가장 단순한 루틴은 Rectangle 로, 캔버스 Pen 프로퍼티로부터 설정을 이용해 직사각형의 프레임을 그리고, 그 영역은 캔버스 Brush 프로퍼티로부터 설정을 이용해 채운다. Frame은 프레임만 그릴뿐, 영역을 채우진 않는다. FrameRect 는 Frame처럼 프레임을 그리기만 하지만 Pen 대신 Brush의 프로퍼티들을 사용한다. Brush 는 직선 너비를 정의하지 않으므로 고정된 너비 1이 사용된다. FillRect 는 Rectangle 처럼 작동하지만 FrameRect 와 마찬가지로 Brush 프로퍼티를 이용해 너비 1의 프레임을 그린다.
이는 아래와 같이 요약할 수 있다:
메소드 | 프레임 | 채우기 |
Rectangle | Pen | Brush |
Frame | Pen | None |
FillRect | Brush | Brush |
FrameRect | Brush | None |
표 9.5: 직사각형을 그리는 여러 메소드 |
네종류의 루틴들은 모두 동일한 직사각형 좌표로 호출하면 동일한 전체 영역을 커버한다.
procedure Rectangle(X1, Y1, X2, Y2: Integer);
procedure Rectangle(const ARect: TRect);
procedure Frame(const ARect: TRect);
procedure Frame(X1,Y1,X2,Y2: Integer);
procedure FrameRect(const ARect: TRect);
procedure FrameRect(X1, Y1, X2, Y2: Integer);
procedure FillRect(const ARect: TRect); virtual;
procedure FillRect(X1, Y1, X2, Y2: Integer);
이러한 기본 함수 외에도 시각적 컨트롤 요소들을 그리기 위한 확장 함수들이 있다. 예를 들어 DrawFocusRect 는 객체가 입력 포커스를 갖고 있음을 나타낸다. 그 결과 플랫폼 의존적이 된다. 윈도우에서는 점선에 직사각형 프레임이 그려지고, 점의 색은 본래 배경색과 그 보색이 교대로 칠해진다. MacOS X에서는 두꺼운 파란색 프레임이 그려지는 결과가 발생한다.
procedure DrawFocusRect(const ARect: TRect);
또 다른 특수 직사각형 루틴으로 RoundRect를 들 수 있는데, Pen 색상으로 둥근 모서리의 프레임을 그리고 최종 영역을 Brush로 채운다. 이 메소드는 다른 직사각형 루틴들과 동일한 영역을 커버한다-둥근 모서리는 제외.
둥근 모서리의 반경은 RX와 RY 반경을 이용해 타원에 의해 결정된다:
procedure RoundRect(X1, Y1, X2, Y2: Integer; RX, RY: Integer);
procedure RoundRect(const Rect: TRect; RX, RY: Integer);
원형과 타원형
Ellipse 메소드는 직사각형 영역 내에 원형이나 타원형을 그린다. 그 둘레는 Pen 설정으로 그려진다; 영역은 현재 Brush로 채운다. 이러한 절차는 두 가지 변형을 가진다: 하나는 TRect의 폼 내에 포함 직사각형(enclosing rectangle)의 점을, 다른 하나는 상단 좌측과 하단 우측 모서리 좌표를 바로 X와 Y 값으로 취한다. 원형은 특별한 타원형에 불과하므로 따로 루틴은 없다. 타원형을 둘러싼 직사각형을 정사각형으로 정의함으로써 원형을 그린다.
윈도우 구버전에서 작업할 경우 Windows 9x, ME API에서 발생하는 문제, 즉 x1 + x2 + y1 + y2의 합이 32768을 초과하지 않을지도 모른다는 점을 고려해야 한다. 이는 인정하건대 엄청 큰 수지만 인지하고 있어야 한다.
procedure Ellipse(const ARect: TRect);
procedure Ellipse(x1, y1, x2, y2: Integer);
타원형의 일부만 그리는 것도 가능하다. Arc 는 타원형의 호 일부만(part of the edge) 그린다. 그리기는 Angle16Deg에서 시작되며 시계방향으로 돌려 Angle16DegLength까지 끝난다. 두 파라미터 모두 (따라서 이름이 복잡해진다) 1˚도의 16분의 1 단위로 측정된다. 따라서 16의 값은 1˚에 일치한다. Arc 프로시저는 펜 설정만을 이용해 타원형의 부분 호(part-edge)를 그린다-영역을 채우진 않는다.
Chord는 Arc의 확장이다. 이 메소드는 부분 타원형(part-ellipse)을 두 끝점 사이에 직선으로 완성시킨다. 그 결과 둘러싼 영역이 생기고, 이후 Brush로 채워진다.
Pie 또한 Arc에서 파생된다. Pie로 그려진 파이 부분은 각 끝점으로부터 타원형 중심까지 직선을 그린 후 arc를 그림으로써 시작된다. 다시 말하지만 테두리선은 Pen을 이용해 그리며, 최종 둘러싼 영역은 Brush로 채워진다. 그려진 영역은 타원형의 중심에서 (StartX, StartY)와 (EndX, EndY) 지점까지 두 개의 선에 의해 테두리가 이뤄진다.
procedure Arc(ALeft, ATop, ARight, ABottom, Angle16Deg,
Angle16DegLength: Integer);
procedure Arc(ALeft, ATop, ARight, ABottom, SX, SY, EX, EY: Integer);
procedure Chord(x1,y1, x2,y2, Angle16Deg, Angle16DegLength: Integer);
procedure Chord(x1, y1, x2, y2, SX, SY, EX, EY: Integer);
procedure RadialPie(x1, y1, x2, y2, StartAngle16Deg,
Angle16DegLength: Integer);
procedure Pie(EllipseX1, EllipseY1, EllipseX2, EllipseY2,
StartX, StartY, EndX, EndY: Integer);
이러한 루틴들을 아래 예제에 표시하겠다:
procedure TfrmEllipse.FormPaint(Sender: TObject);
begin
with Canvas do
begin
Brush.Color := clBlue;
TextOut(50, 30, 'Ellipse');
Ellipse(50, 30, 100, 100);
TextOut(150, 30, 'Arc');
Arc(150, 50, 200, 100, 0, 90 * 16);
TextOut(50, 130, 'Chord');
Chord(50, 150, 100, 200, 0, 90 * 16);
TextOut(150,130, 'Pie');
Pie(150, 150, 200, 200, 200, 175, 175, 150);
end;
end;
텍스트 메소드
텍스트는 두 가지 방법으로 출력할 수 있다. TextOut은 상단 좌측 모서리부터 (X, Y) 시작해 우측으로 텍스트를 그린다. 선택된 언어의 쓰기 방향이 우측에서 좌측으로, 또는 상단에서 하단으로 설정된 경우에도 이와 같은 작업이 실행된다. TextRec 는 TextOut 과 정확히 동일하게 작업하지만 추가로 ARect에 명시된 직사각형 밖으로 텍스트가 그려지면 모두 잘라버린다:
procedure TextOut(X, Y: Integer; Const Text: String);
procedure TextRect(const ARect: TRect; X, Y: integer; const Text: String);
두 가지 메소드 모두 Font에 정의된 크기로 캔버스에 Text를 그린다. 아래 세 가지 메소드를 이용해 Text의 크기를 계산할 수 있다. TextHeight 는 문자열의 높이를 리턴하고 TextWidth는 너비를 리턴한다. TextExtent 는 높이와 너비를 모두 제공한다:
function TextExtent(const Text: String): TSize;
function TextHeight(const Text: String): Integer;
function TextWidth (const Text: String): Integer;