LazarusCompleteGuide:9.3
그래픽 포맷
라자루스가 지원하는 그래픽 포맷은 계층 구조로 정렬된다.
TGraphic
TGraphic 은 모든 픽셀 이미지 포맷에 대한 기반 클래스이다. 이 클래스 바로 위에 그 다음 수준인 TRasterImage 가 빌드된다. TGraphic 은 모든 기본 이미지 프로퍼티들을 소개한다. TGraphic 의 주요 public 프로퍼티는 다음과 같다:
- Empty: 이미지가 비어 있는지 여부를 나타낸다. 비어 있다면 보통 그 높이와 너비는 0일 것이다.
- Height 와 Width: 이미지의 크기.
- Modified: False 로 설정할 수 있는 보조(auxiliary) 프로퍼티다. 프로퍼티가 설정된 후 이미지가 변경되었는지 확인 시 사용할 수 있다.
- Transparent: 이미지가 투명한 부분(transparency)을 포함하는지를 나타내는 플래그. 색상 투명성을 의미하거나 알파 채널을 의미한다.
OnChange 와 OnProgress 이벤트는 이미지가 변경되거나 저장 또는 로딩이 진행 중일 때 트리거된다. 진행 표시에 사용할 수 있겠다. Tgraphic은 모든 그래픽 포맷에 대해 메인 메소드를 정의하기도 하는데, 다음과 같다:
- Assign (다른 이미지의 내용을 복사하기 위함. 포맷 변환 시 사용 가능.)
- Clear (이미지 크기를 (0, 0)으로 설정하여 모든 데이터를 제거).
로딩과 저장하기는 상황에 따라 LoadFromFile(파일로부터 로딩하기), LoadFromStream(스트림으로부터 로딩하기), LoadFromLazarusResource(라자루스 리소스로부터 로딩하기), SaveToFile(파일로 저장하기), SaveToStream(스트림으로 저장하기) 로 이루어진다.
TRasterImage
TRasterImage 는 모든 LCL 이미지 클래스에 대한 기반 클래스이다. 이는 TGraphic 에서 파생되며, 이미지 데이터의 작업을 위해 API를 제공한다. 주요 요소는 픽셀 이미지를 저장하는 포맷으로서 Canvas, BitmapHandle (LCLIntf API 호출용), PixelFormat 프로퍼티가 있고, 색상이 투명한지 나타내는 TransparentColor, TransparentMode 가 있다 (투명한 부분에 알파 채널을 사용하는 대신 사용). 투명하게 정의된 색상은 이후 이미지를 그리는 데 사용되는 마스크(mask)로 변환된다. TRasterImage 에서 메인 루틴은 캔버스 핸들로부터 이미지를 로딩시키는 LoadFromDevice 이다. 파라미터에서 LoadFromDevice 로 Null 값이 전달될 경우, 스크린숏이 생성되어 이미지에 로딩된다. 이 방법은 (구식) Gtk 1 에선 작동하지 않지만 다른 인터페이스와는 가능하다: Win32/64 (pre-Windows 7), WinCE, Gtk 2, Qt, Carbon.
procedure TfrmScreenshot.btnScreenshotClick(Sender: TObject);
var bmp: TBitmap;
begin
bmp := TBitmap.Create;
bmp.LoadFromDevice(0);
imgScreenshot.Picture.Bitmap.Assign(bmp);
FreeAndNil(bmp);
end;
TGraphic 과 같이 TRasterImage 또한 published로 정의된 프로퍼티가 없다. 래스터 이미지는 높이와 너비가 픽셀로 정의된 2차원적(평면) 영역으로 구성된다. '픽셀'이란 디스플레이 표면에 나타낼 수 있는 가장 작은 사각영역이다.
'픽셀'의 정확한 크기는 모니터의 해상도와 (보통 72~135dpi) 프린터의 (프린터 화질에 따라 보통 150~1200dpi) 해상도에 따라 좌우된다. 현재 해상도는 전역적으로 이용 가능한 프로퍼티, Screen.PixelsPerInch 나 Printer.PixelsPerInch 를 이용해 얻을 수 있다.
래스터 이미지는 회전이나 크기조정(scaling)을 통해 (또는 둘을 동시에 이용해) 크기를 변경할 수 있다. 예를 들어, 아래 루틴에서는 TCanvas.CopyRect를 이용해 이미지를 새 캔버스로 전송한다:
var MyBitmap: TBitmap;
begin
// create a bitmap of size 100 x 100 pixels
MyBitmap := TBitmap.Create;
MyBitmap.Width := 100;
MyBitmap.Height := 100;
// draw in this newly defined bitmap
MyBitmap.Canvas.Brush.Color := clBlue;
MyBitmap.Canvas.Pen.Color .= clBlue;
MyBitmap.Canvas.Rectangle(20, 20, 80, 80);
// now resize to 200 x 100 pixels
MyBitmap.Width :=200;
MyBitmap.Canvas.CopyRect(Bounds(0, 0, 200, 100), MyBitmap.Canvas,
Bounds(0, 0, 100, 100));
MyImage.Canvas.Draw(0, 0, MyBitmap); // draw on the canvas
MyBitmap.Free; // free the bitmap instance
end;
모든 픽셀은 새 캔버스의 위치로 전송되고, 픽셀 사이에 '구멍'이 있을 경우 가장 가까운 값으로 채워진다. 이는 근처 픽셀로부터 이용 가능한 값이거나, 계산된 중간값이다. 중간값의 계산을 앤티 에일리어싱(anti-aliasing)이라 부른다. TCanvas.AntiAliasingMode 프로퍼티는 앤티 에일리어싱의 적용 여부를 결정한다. 앤티 에일리어싱을 적용시켜 이미지를 한 방향으로 확장시키면 어느 정도 둥글거나 희미하게 보일 수 있다. 앤티 에일리어싱이 꺼졌다면 가장자리는 항상 날카롭지만 들쭉날쭉할 수 있다. amDontCare 를 설정 시 플랫폼이 달라지면 다른 결과가 발생할 수 있다는 사실도 항상 유념해야 한다. 따라서 amOff 를 이용해 모든 플랫폼에 대해 앤티 에일리어싱을 끄는 편이 낫다. Carbon에서는 기본 값이 amOn으로 되어 있지만 다른 모든 인터페이스에선 기본 설정이 amOff로 되어 있다.
Windows 인터페이스는 앤티 에일리어싱을 전혀 다루지 않으므로 끄는 편이 좋다.
type
TAntialiasingMode = (amDontCare, // default
amOn, // turn anti-aliasing on
amOff); // turn anti-aliasing off
property AntialiasingMode: TAntialiasingMode read FAntialiasingMode
write SetAntialiasingMode default amDontCare;
이즈음 해서 Tcanvas의 이미지 복사 루틴을 언급하는 것이 좋겠다:
- CopyRect는 소스 캔버스로부터 Source 내 직사각형 영역을 Dest 내 직사각형 영역으로 복사한다.
Source와 Dest가 같은 이미지가 아닐 경우 이미지는 그에 따라 크기 조정될 것이다. - Draw는 변형(distort)하지 않고 이미지를 그린다. 목적지 위치의 상단 좌측 좌표는 X와 Y 좌표에 주어진다.
- StretchDraw는 Draw와 동일한 작업을 하지만 이미지의 크기를 대상 영역으로 조정한다.
procedure CopyRect(const Dest: TRect; SrcCanvas: Tcanvas; const Source: TRect);
procedure Draw(X, Y: Integer; SrcGraphic: TGraphic);
procedure StretchDraw(const DestRect: TRect; SrcGraphic: TGraphic);
기본 루틴 대신 LCLIntf unit으로부터 StretchBlt 루틴을 이용할 수도 있다. 이러한 다용도 함수의 파라미터로는 다음이 있다 (아래 순서):
- 대상 캔버스,
- 그 내부에 대상 영역,
- 소스 캔버스,
- 그 내부에 소스 영역,
- 복사 모드 "Rop"
이는 이미지를 전송하는 방법을 정의한다: 예를 들어, 소스 이미지의 색상을 반전시켜야 하는지, 다중 이미지를 결합해야 하는지를 정의한다. 복사 모드는 (표 9.6 참고) LCLType unit에서 정의되지만 안타깝게도 위젯 셋마다 그 구현의 범위가 다양하다. 항상 적용되는 한 가지는 일반 복사 모드로, STCCOPY 상수를 명시해야 하는 모드이다. 나머지는 자신의 플랫폼마다 테스트해야 한다.
function StretchBlt(DestDC: HOC; X, Y, Width, Height: Integer;
SrcDC: HOC; XSrc, YSrc, SrcWidth, SrcHeight: Integer; Rop: Cardinal): Boolean;
마지막 예제에서는 StretchBlt를 아래와 같이 호출하여, 표시되는 CopyRect 호출을 대체해야 한다:
LCLIntf.StretchBlt(MyBitmap.Canvas.Handle, 0, 0, 200, 100,
MyBitmap.Canvas.Handle, 0, 0, 100, 100, SRCCOPY);
상수 | 값 | 공식 |
SRCCOPY | 00CC0020h | Target = Source |
SRCPAINT | 00EE0086h | Target = Source OR Target |
SRCAND | 008800C6h | Target = Source AND Target |
SRCINVERT | 00660046h | Target = Source XOR Target |
SRCERASE | 00440328h | Target = Source AND (Not Target) |
NOTSRCCOPY | 00330008h | Target = (NOT Source) |
NOTSRCERASE | 001100A6h | Target = (NOT Source) AND (NOT Target) |
MERGECOPY | 00C000CAh | Target = (Source AND Target Brush) |
MERGEPAINT | 00BB0226h | Target = (NOT Source) OR Target |
PATCOPY | 00F00021h | Target = Target Brush |
PATPAINT | 00FB0A09h | Target = (Target Brush OR Not Source) OR Target |
PATINVERT | 005A0049h | Target = Target Brush XOR Target |
DSTINVERT | 00550009h | Target = (NOT Target) |
BLACKNESS | 00000042h | Target = BLACK |
WHITENESS | 00FF0062h | Target = WHITE |
표 9.6: StretchBlt에 대한 래스터 연산 (그 중 다수는 모든 플랫폼에서 구현되는 것은 아닌것을 명심!) |
비트맵
다른 라자루스 지원 플랫폼에서도 이용 가능한 Windows 비트맵 포맷은 0.9.28 버전 이후 OS/2 비트맵 포맷으로 확장되었다. 이는 가장 간단한 그래픽 포맷이다. 색심도는 픽셀당 1비트부터 32비트까지 가능하다. 32비트 포맷은 alpha channel 외에도 8비트 색상으로 구성된 채널 3개, 24비트 포맷의 Red, Green, Blue를 정의한다. 이론상으로 데이터는 압축이 가능하지만 라자루스는 이를 지원하지 않는다.
모든 TBitmap 프로퍼티와 메소드는 그 조상 클래스로부터 파생되는데, 대부분은 TRaseterImage 로부터 파생되지만, 소수는 TFPImageBitmap과 TCustomBitmap에서 파생된다. TBitmap 자체에서 구현되는 유일한 것은 파일 포맷을 읽고 쓰기 위한 메소드이다. 그 외에도 수용되는 파일명 확장자 리스트가 정의된다.
FTPImageBitmap 은 TBitmap 의 직계 조상이다. 이 클래스에 의해 이용 가능해진 새 루틴으로는 클래스가 확장자를 알고 있는지 검사하는 IsFileExtensionSupported 가 유일하다. TFPImageBitmap 은 TCustomBitmap 에서 파생된다. 이러한 중간 클래스는 두 가지 새 프로퍼티를 가진다. LCLIntf API 를 위한 (Windows API를 기반으로 함) 비트맵 핸들과 Monochrome이 있다.
후자는 비트맵의 TRasterImage.PixelFormat 이 pf1 bit인지 (단일 비트 크기를 가짐을 의미) 검사하는 부울(Boolean) 프로퍼티에 해당한다. TCustomBitmap 은 TRasterImage로부터 파생된다:
TCustomBitmap = class(TRasterImage)
private // ... omitted
public // ... shortened
property Handle: HBITMAP read GetBitmapHandle
write SetBitmapHandle
// for custombitmap handle = bitmaphandle
property HandleType: TBitmapHandleType read GetHandleType
write SetHandleType;
property Monochrome: Boolean read GetMonochrome
write SetMonochrome;
end;
TFPImageBitmap = class(TCustomBitmap)
protected // ... omitted
public // ... shortened
class function IsFileExtensionSupported(const FileExtension:
string): boolean;
end;
TBitmap = class(TFPImageBitmap)
protected // ... omitted
public
class function GetFileExtensions: string; override;
procedure LoadFromStream(AStream: TStream; ASize: Cardinal); override;
end;
그 외 그래픽 포맷에 해당하는 TPixmap, TPortableNetworkGraphic, TPortableAnyMapGraphic 은 TBitmap 과 비교 시 추가 프로퍼티나 함수가 없으며, TBitmap 에서와 정확히 동일한 방식으로 사용된다. PNG 그래픽의 경우, 압축을 선택하는 것조차 불가능하다. 세 가지 클래스는 TBitmap 과 같이 TFPImageBitmap의 자손들이다.
TJpegImage
JPEG는 표준화된 그래픽 포맷이다. JFIF라는 실제 포맷은 정적 이미지에만 사용 가능하지만 비디오 포맷에도 사용할 수 있다. 델파이나 다른 개발 환경에서와 같이 프리 파스칼과 라자루스에서 JPG 지원은 독립적 JPEG 그룹으로부터의 루틴을 기반으로 한다.
클래스 자체는 TFPImageBitmap에서 파생되므로, 클래스에 관해 논한 모든 내용도 여기에 적용된다. Bitmap 함수 외에도 당신은 파일이나 스트림을 로딩 또는 저장 시 압축을 제어할 수 있다.
TFPJPEGCompressionQuality = 1..100; // 100=best quality, 25=moderate quality
TJPEGReadPerformance = (jpBestQuality, jpBestSpeed);
TJPEGQualityRange = TFPJPEGCompressionQuality;
TJPEGPerformance = TJPEGReadPerformance;
TJPEGImage = class(TFPImageBitmap)
private
// ... omitted
protected
// ... omitted
public
constructor Create; override;
class function GetFileExtensions: String; Override;
// see TFPImageBitmap
public
property CompressionQuality: TJPEGQualityRange read FQuality
write FQuality;
property GrayScale: Boolean read FGrayScale;
property ProgressiveEncoding: boolean read FProgressiveEncoding;
property Performance: TJPEGPerformance read FPerformance
write FPerformance;
end;
CompressionQuality, ProgressiveEncoding 프로퍼티는 쓰기에만 적용되고, Performance는 읽기에만 적용된다. CompressionQuality 는 0~100 (퍼센트)까지 값을 허용한다. 값이 클수록 이미지 화질이 좋다. ProgressiveEncoding 은 압축을 여러 단계로 실행한다.
이는 저화질 개요를 먼저 표시하고, 로딩이 진행되면서 화질이 향상된다. 이러한 기능은 본래 개발 목적에 맞게 웹사이트에서 주로 사용된다. Performance는 파일의 로딩과 표시의 속도 단위이다. 최상의 화질은 jpBestQuality 로 얻을 수 있다. 이는 최상의 화질에 도달할 때까지 읽기를 계속한다. jpBestSpeed 는 화질을 손실하면서까지 데이터를 더 빠르게 압축 해제할 것이다. 다음 예제는 JPEG 이미지를 쓰는 방법을 보여준다.
먼저 JPEG 이미지를 생성하고, 검정색 배경에 파란색 삼각형을 그린 후, 선택된 파일로 데이터를 쓴다. 이미지 화질은 TTrackBar를 이용해 설정한다.
procedure TfrmDraw.btnDrawJPEGClick(Sender: TObject);
var jpeg : TJPEGImage;
Points : array[0..2] of TPoint;
SaveDialog: TSaveDialog;
begin
jpeg := TJPEGImage.Create;
SaveDialog := TSaveDialog.Create(NIL);
try
// Draws a blue triangle on a black background:
jpeg.Width := 100;
jpeg.Height := 100;
jpeg.Canvas.Brush.Color := clBlue;
Points[0] := Point(50, 25);
Points[1] := Point(25, 75);
Points[2] := Point(75, 75);
jpeg.Canvas.Polygon(Points);
// prepares the SaveDialog and the compression configuration
SaveDialog.DefaultExt := 'jpg';
jpeg.CompressionQuality := trackJPEG.Position;
// Write the file:
if SaveDialog.Execute then
jpeg.SaveToFile(SaveDialog.FileName);
finally
jpeg.Free; // Free ...
SaveDialog.Free; // ... the objects
end;
end;
아이콘
라자루스 내 대부분 클래스는 델파이의 클래스와 유사한 반면 TIcon은 그렇지 않다. 델파이의 TIcon은 하나의 이미지만 포함할 수 있지만 라자루스의 TIcon 버전은 이용 가능한 모든 이미지 변형(variant)을 아이콘 파일에 포함하고 있다. 각 플랫폼은 고유의 아이콘 포맷을 가진다. 윈도우에선 .ico 확장자로 된 파일이며, MacOS X에선 .icns 포맷으로 부른다. 이에 대해 라자루스에선 TlcnsIcon 클래스를 이용할 수 있다. Pixmap 포맷으로 된 일반적인 유닉스 아이콘들은 TPixmap 클래스에 의해 처리된다.
프로그래머는 일반적으로 이러한 기반 클래스를 작업할 필요가 없는데, 아이콘이 필요한 곳이라면 LCL이 TIcon을 이용해 이미지를 올바른 포맷으로 자동 변환하기 때문이다.
TIcon 의 구조는 TBitmap 이나 다른 LCL 이미지 클래스들과 마찬가지로 TRasterImage로부터 파생된다. 즉, 이미지에 캔버스, 픽셀 포맷, 크기가 있음을 의미한다. TIcon은 사실상 하나 이상의 이미지를 가진 상자(container)이지만 이러한 프로퍼티들은 현재 선택된 이미지를 참조한다.
로딩할 때는 가장 크거나 가장 색상이 많은 이미지가 현재 이미지로 선택되고, 나머지는 Current 프로퍼티의 값을 변경하여 접근한다. Current는 선택된 이미지에 대해 0을 기반으로 한 (zero-based) 색인이다. Count 프로퍼티는 아이콘 파일 내의 이미지 수를 제공하고, Add 와 Delete 메소드는 아이콘의 추가 또는 제거에 사용된다. 그 외 중요한 TIcon 메소드들로는 모든 이미지를 삭제하는 Clear와 선택된 이미지의 기본 프로퍼티를 빠르게 확인하는 GetDescription이 있다. 후자의 경우 이미지의 색인을 파라미터로 취하여 그 높이, 너비, 픽셀 포맷을 리턴한다.
주어진 이미지 크기에 적합한 이미지는 GetBestindexForSize로 찾을 수 있다. 원하는 크기는 TSize 타입의 파라미터에 제공된다.
property Current: Integer read FCurrent write SetCurrent;
property Count: Integer read GetCount;
procedure Add(AFormat: TPixelFormat; AHeight, AWidth: Word);
procedure Delete(Aindex: Integer);
procedure Clear; Override;
procedure GetDescription(Aindex: Integer; out AFormat: TPixelFormat; out AHeight, AWidth: Word);
function GetBestIndexForSize(ASize: TSize): Integer;
아래 리스트는 창에 아이콘의 이미지를 모두 표시하는 루틴을 보여준다:
procedure TForm1.Button1Click(Sender: TObject);
var Ico: TIcon; // a named variable
i, j, k, x, y: Integer; // various counting variables
begin
with OpenPictureDialog1 do
begin
Filter := 'Icon (*.ico)|*.ico'; // restrict file selection to icons
Options := [ofEnableSizing, ofViewDetail, ofReadOnly];
// ensure read-only option is set
end;
if OpenPictureDialog1.Execute then
begin
Caption := 'Icon Viewer: '
+ ExtractFileName(OpenPictureDialog1.FileName);
// The icon images are shown as TImage controls inside a panel
// Ensure we remove any images from a previous icon
while Panel2.ComponentCount > 0 do
Panel2.Components[0].Free;
Ico := TIcon.Create;
try
Ico.Width := 256; // use preset size for the base icon
Ico.Height := 256;
Ico.LoadFromFile(OpenPictureDialog1.FileName);
x := 10; // left coordinate of each image
y := 0; // default window height
if Ico.Count > -1 then // Check if there are images in the icon
for i := 0 to Ico.Count - 1 do // All Icons in the file
with TImage.Create(Panel2) do // the unnamed TImage is dynamic,
begin // so access using With
Parent := Panel12; // image shown on the panel
Enabled := True; // and enabled
AutoSize := False; // size to fit
Top := 25; // Leave space above for the text
Left := x; // X is used to calculate the left pos
Ico.Current := i; // current icon in the file
Picture.Bitmap.Height := Ico.Height;// image and component
Picture.Bitmap.Width := Ico.Width; // ...
Height := Ico.Height; // share the same size!
Width := Ico.Width;
if Height > y then
y := Height; // adjust window height
for j := 0 to Ico.Width do // all pixels of current
for k := 0 to Ico.Height do // image are sent
Picture.Bitmap.Canvas.Pixels[j, k]:= Ico.Canvas.Pixels[j, k];
Show; // Show the image
if CheckBox1.Checked then
// view the image size in a label over the image:
with TLabel.Create(Panel2) do
begin // unnamed dynamic label
Caption := Format('%dx%d', [Ico.Width,Ico.Height]);
Autosize := True;
Parent := Panel2;
Left := x;
Top := 5;
Show;
end;
x := x + Ico.Width + 10; // Increase the left position
end; {with TImage....}
Ico.Free; // Make sure that the icon is freed
if Width < x + 10 then
Width := x + 10; // Adjusts the width of the form
if Height < y + 50 + Panel1.Height then
Height := y + 50 + Panel1.Height; // Corrects the form height
end; { if OpenPictureDialog1..}
end;
아이콘 파일을 로딩하고 나면 이 프로그램은 이미지 데이터를 모두 포함하는 TIcon 을 동적으로 생성할 것이다. 얼마나 많은 이미지를 표시할 것인지 미리 알 수 없기 때문에 아이콘은 TImage 에 위치하지 않는다. 파일에 적어도 하나의 아이콘이 있다면 프로그램은 모든 이미지에 대해 반복(iterate)할 것이다. 포함된 이미지 각각마다 TImage가 생성된다. 아래 사항들을 항상 명심하길 바란다:
- 이미지는 동적으로 생성되며 이름을 가지지 않는다.
따라서 연산자를 이용해 루프에서만 접근이 가능하다. - 포함된 비트맵과 이미지 컨트롤의 크기를 설정할 필요가 있다.
이를 위해선 현재 아이콘의 데이터를 파일로부터 읽는다. - 이미지 비트맵 내에 아이콘 데이터는 루프에서 픽셀별로 처리된다.
두 컴포넌트의 캔버스가 사용된다.
프로그램 나머지 부분은 OpenPictureDialog가 다시 호출될 때 이전에 표시된 심볼과 텍스트를 모두 제거하는 등 주로 겉치레에 불과하다. 이는 라자루스에서 문제를 야기하기도 한다. for 루프에서 라벨의 삭제를 시도할 경우:
for i = 0 to Panel2.ComponentCount-1 do begin
if (Panel2.Components[i] is TLabel) or
(Panel2.Components[i] is TImage) then Panel2.Components[i].Free;
처음엔 모든 것이 정상적으로 보인다. 하지만 이것은 리스트에 오류를 발생시킬 수 있다. 이유인즉슨, 내부 컴포넌트 색인은 첫 번째 요소를 제거한 후 변경될 것이기 때문이다. 루프는 요소 ()의 제거부터 시작할 것이며, 그 결과 모든 요소들이 즉시(!) 한 위치를 하향 이동(shift down)할 것이다. 우리의 루프는 이 사실을 알지 못하므로 요소 1의 (본래 #2였던) 삭제를 계속해나갈 것이다. 연산이 절반 정도 끝나면 리스트 끝에 도달하여 다음 시도 시 오류를 야기할 것이다.
이런 경우 while 루프를 이용해 문제를 해결할 수 있다.
while Panel2.ComponentCount > 0 do Panel2.Components[0].Free;
하지만 이는 부모의 컨트롤 요소들 모두를 제거하길 원할 때에만 작동할 것이다. for 루프에서 요소들로의 접근은 루프에 있는 동안 요소들의 수가 변경되지 않을 때에만 가능하다!