LazarusCompleteGuide:9.2

From 흡혈양파의 번역工房
Jump to: navigation, search

그래픽 컴포넌트

라자루스의 그래픽 컨트롤은 이미지를 로딩, 저장, 조작하도록 해준다. 그러한 연산을 위한 기본 시각적 컨트롤은 Additional 탭의 TImage, Common Controls의 TImageLIst 이 해당된다. 큰 이미지에서 스크롤이 필요한 경우 스크롤바에 이미지 컨트롤을 드롭할 수 있다 (다른 컨트롤에 대한 스크롤링 상자는 Additional 탭에서 찾을 수 있다).


같은 탭에 보면 TPaintBox 컴포넌트도 있는데, 이 또한 캔버스를 가진다. TImage 와 가장 큰 차이점은 TPaintBox 는 이미지의 영구적 기억장소를 제공하지 않는다는 점이다. 즉, 그림상자의 내용은 창을 새로 고칠(refresh) 때마다 재생성되어야 함을 의미한다. 일부 라자루스 인터페이스를 이용해 페인트 이벤트 밖에 그리는 것도 전적으로 가능하지만 다음 새로고침 시에 모두 삭제될 것이다. Carbon과 Qt에서는 이것이 전혀 불가능하므로, 애초부터 시도도 하지 말길 권한다.


이미지는 일반 열기 및 저장하기 대화창으로 로딩 및 저장할 수 있지만, 사전 정의된 TOpenPictureDialog 와 TSavePictureDialog 이 더 편리한 이미지 미리보기 기능을 제공하므로 이를 사용하는 편이 낫다.


소스 코드 몇 줄만으로 간단한 이미지 뷰어를 생성할 수 있다.


TImage와 TOpenPictureDialog만 사용하면 된다.


창에 위치시킬 이미지와 그 배율(magnification)은 (Align) alClient로 설정할 수 있다:

procedure TForm1.Image1DblClick(Sender: TObject);
begin
  if OpenPictureDialog1.Execute then
    Image1.Picture.LoadFromFile(OpenPictureDialog1.FileName);
end;


대화창은 모든 라자루스 지원 이미지 포맷을 표시하도록 사전 구성되어 있으므로, 이 최소 프로그램은 이미 실제 이미지 뷰어로서 사용될 수 있다. 하지만 큰 이미지의 스크롤을 제공하지 않는다. 이를 위해선 스크롤상자가 필요하다. 디자이너에 컨트롤을 위치시키는 순서는 다음과 같다: Form1→ScrollBox1→Image1. 스크롤상자 Align 에는 alClient 값이 주어지고, 스크롤상자 내 TImage 컴포넌트는 Left 0과 Top 0 좌표에서 시작하여 설계 모드에서 스크롤상자의 크기보다 작은 크기로 시작된다. Image1은 alNone의 정렬 값을 얻고 (중요!) AutoSize 프로퍼티는 True로 설정된다.


이제 현재 창보다 큰 그림을 로딩하면 스크롤상자가 응답하여 그림의 보이지 않는 부분을 스크롤해 보여주는 스크롤바가 표시된다. 폼의 리소스 데이터에 (.lfm) 아래와 같은 모습이다:

object ScrollBox1: TScrollBox
  Left = 0
  Height = 229
  Top = 0
  Width = 368
  Align = alClient
  AutoSize = True
  ClientHeight = 225
  ClientWidth = 364
  TabOrder = 0
  object Image1: TImage
    Left = 0
    Height = 128
    Top = 0
    Width = 256
    AutoSize = True
    OnDblClick = Image1DblClick
  end
end


가장 기본적인 그림 뷰어는 이것으로 완성된다. 이 코드를 기반으로 점점 확장해나갈 것이다.


볼 수 있듯이, 처음 TPictureDialog 를 열면 라자루스는 이미 엄청난 수의 그래픽 포맷을 처리할 수 있다. TImage의 메소드 LoadFromFile은 이미지를 Picture property로 (TPicture 타입) 로딩시킬 때 이러한 포맷들을 모두 지원한다. TImgae 는 TCustomImage로부터 파생된다. 이용 가능한 메소드와 프로퍼티의 전체 리스트는 아래와 같다:

TCustomImage = class(TGraphicControl)
  private    // ... omitted
  protected  // ... omitted
  public
    constructor Create(AOwner: TComponent); Override;
    destructor Destroy; Override;
    property Canvas: TCanvas read GetCanvas;
  public
    property Align;
    property AutoSize;
    property Center: Boolean read FCenter write SetCenter default False;
    property Constraints;
    property Picture: TPicture read FPictures writes SetPicture;
    property Visible;
    property OnClick;
    property OnMouseDown;
    property OnMouseMove;
    property OnMouseUp;
    property Stretch: Boolean read FStretch write SetStretch
                default False;
    property Transparent: Boolean read FTransparent
                write SetTransparent default False;
    property Proportional: Boolean read FProportional
                write SetProportional default False;
    property OnPictureChanged: TNotifyEvent read FOnPictureChanged
                write FOnPictureChanged;
  end;

  TImage = class(TCustomImage)
  published
    property Align;
    property Anchors;
    property AutoSize;
    property BorderSpacing;
    property Center;
    property Constraints;
    property DragCursor;
    property DragMode;
    property Enabled;
    property OnChangeBounds;
    property OnClick;
    property OnDblClick;
    property OnDragDrop;
    property OnDragOver;
    property OnEndDrag;
    property OnMouseDown;
    property OnMouseEnter;
    property OnMouseLeave;
    property OnMouseMove;
    property OnMouseUp;
    property OnMouseWheel;
    property OnMouseWheelDown;
    property OnMouseWheelUp;
    property OnPaint;
    property OnPictureChanged;
    property OnResize;
    property OnStartDrag;
    property ParentShowHint;
    property Picture;
    property PopupMenu;
    property Proportional;
    property ShowHint;
    property Stretch;
    property Transparent;
    property Visible;
  end;


ExtCtrIs 유닛 내 두 클래스의 프로퍼티의 서로 다른 목적은 이름으로 분명히 나타나야 한다. 그럼에도 불구하고 이미 눈치 챘을지도 모르지만 정의부에는 어떤 메소드도 포함하고 있지 않다. 이는 모두 Graphics 유닛에서 가져오는 Picture 프로퍼티 내부에 위치하며 TImage 컨트롤 자체보다 훨씬 복잡하다:

TPicture = class(TPersistent)
  private
      // ... omitted
  protected
      // ... omitted
  public
    constructor Create;
    destructor Destroy; Override;
    procedure LoadFromFile(const Filename: String);
    procedure SaveToFile(const Filename: String; const FileExt: String = '');
    procedure SaveToStreamWithFileExt(Stream: Tstream;
                                              const FileExt: String);
    procedure LoadFromStreamWithFileExt(Stream: Tstream;
                                              const FileExt: String);
    procedure LoadFromLazarusResource(const AName: String);
    procedure LoadFromClipboardFormat(FormatID: TClipboardFormat);
    procedure LoadFromClipboardFormatID(ClipboardType:
                         TClipboardType; FormatID: TClipboardFormat);
    procedure SaveToClipboardFormat(FormatID: TClipboardFormat);
    class function SupportsClipboardFormat(FormatID:
                                          TClipboardFormat): Boolean;
    procedure Assign(Source: TPersistent); Override;
    class procedure RegisterFileFormat(const AnExtension,
                    ADescription: String; AGraphicClass:TGraphicClass);
    class procedure RegisterClipboardFormat(FormatID: TClipboardFormat;
                                         AGraphicClass: TGraphicClass);
    class procedure UnregisterGraphicClass(AClass: TGraphicClass);
    procedure Clear; virtual;
    function FindGraphicClassWithFileExt(const Ext: String;
                 ExceptionOnNotFound: boolean = true): TGraphicClass;


클래스는 published 절을 포함하지 않는데, 이미 TImage 의 부분이기 때문이다. 이 클래스에서는 두 번째 public 정의에 있는 프로퍼티가 매우 중요하다:

public
  property Bitmap: Tbitmap   read GetBitmap write SetBitmap;
  property Icon: TIcon       read GetIcon   write SetIcon;
  property Jpeg: TJpegImage  read GetJpeg   write SetJpeg;
  property Pixmap: TPixmap   read GetPixmap write SetPixmap;
  property PNG: TPortableNetworkGraphic read GetPNG write SetPNG;
  property PNM: TPortableAnyMapGraphic  read GetPNM write SetPNM;
  property Graphic: TGraphic read FGraphic  write SetGraphic;
  property Height: Integer   read GetHeight;
  property Width: Integer    read GetWidth;
  property OnChange: TNotifyEvent read FOnChange write FOnChange;
  property OnProgress: TProgressEvent read FOnProgress write FOnProgress;
end;


요약하자면: TImage는 매우 간단한 컴포넌트로서 정적 이미지를 표시하고, TImage.Picture 프로퍼티로서 (TPicture 타입의) 접근할 수 있다. TImage를 이용해 파일로부터 로딩된 간단한 이미지, 거의 변경되지 않는 이미지, 또는 증분적으로만(incrementally) 그려지는 이미지를 표시할 수 있다.


TImage를 사용 시에는 그 크기가 처리되는 이미지의 크기와 완전히 무관하다는 사실을 고려하는 것이 중요하다; 두 크기는 따로 제어해야 한다.


각 TImage는 두 가지 부분으로 구성된다: 영속(persistent) 이미지를 포함하는 TGraphic (PNG 사진, 비트맵, JPEG 이미지 등), 그리고 OnPaint 이벤트 이후마다 다시 칠해지는 시각적 영역. 이미지 자체는 TImage.Picture.Graphic을 통해 (또는 기본적으로 동일한 TImage.Picture.Bitmap을 통해) 접근할 수 있다.


캔버스는 TImage.Picture.Graphic.Canvas를 통해 (또는 TImage.Picture.Bitmap.Canvas) 접근한다. 컨트롤의 OnPaint 이벤트 중에 TImage.Canvas 는 TImage.Picture.Graphic.Canvas 가 아니라 가변적(volatile)인 시각적 영역을 가리킨다는 사실을 명심해야 한다. 이러한 시각적 캔버스로 접근을 피하는 것이 상책이다. 그리기는 아래 예제와 같이 TImage 의 Graphic 에서 이루어져야 한다. 여기서는 전체 이미지를 채우는 직사각형을 그린다. TImage 에 그리기는 언제든 실행할 수 있으며, 버튼의 OnClick 이벤트 시에 실행할 수도 있겠다. 결과는 이미지에 유지될 것이다:

procedure TForm1.Button1Click(Sender: TObject);
begin
  Image1.Canvas.Brush.Style := bsSolid;
  Image1.Canvas.Brush.Color := clBlue;
  Image1.Canvas.Rectangle(0, 0, Image1.Width, Image1.Height);
end;


TPicture 의 public 섹션은 그래픽 포맷의 정의부를 포함한다. FCL이 처리하는 모든 포맷을 라자루스에서 처리할 수 있는 것은 아니다. FCL은 파일로부터 이미지를 읽고 다시 이미지를 파일로 쓰기 위한 저수준 클래스를 포함하며, 라자루스에서는 이미지가 다소 플랫폼 독립적 인터페이스를 통해 처리된다.


Graphics 유닛은 모두 TRasterImage 에서 파생된 라자루스의 이미지 클래스들을 포함한다. 이 조상 클래스는 이미지를 그리기 위한 TCanvas 와 Width, Height, PixelFormat, TransparentColor, TransparentMode 와 같은 기본 프로퍼티들을 최소한으로 제공한다. 이러한 프로퍼티들은 자손 클래스가 다수의 이미지, 즉 TIcon, TCursorImage, TlcnsIcon 과 같은 이미지를 허용하는 포맷을 표현할 때에도 이용할 수 있겠다. 이러한 경우 프로퍼티들은 최근 선택된 이미지의 상태를 나타낸다.