Developing with Graphics/ko

From Free Pascal wiki
Jump to navigationJump to search

Deutsch (de) English (en) español (es) français (fr) italiano (it) 日本語 (ja) 한국어 (ko) Nederlands (nl) português (pt) русский (ru) slovenčina (sk) 中文(中国大陆) (zh_CN) 中文(臺灣) (zh_TW)

이 페이지는 비트맵과 다른 그래픽들 다루는 튜토리얼의 시작이 될 것이다. 나는 프로그래머가 아니지만, 자신의 경험을 나눌 수 있는 사람은 누구나 다 초대한다! 다음 섹션에 링크를 추가하고 페이지를 더하고 자신의 WiKi 문서를 만들기만 하면 된다.

이 페이지에선 일반적인 정보를 줄 것이다.

라이브러리

그래픽 라이브러리 - 이곳에서는 개발에 사용할 수 있는 주요한 그래픽 라이브러리 들을 볼 수 있을 것이다.

다른 그래픽 문서

2D 그리기

  • ZenGL - OpenGL을 이용한 크로스 플랫폼 게임 개발 라이브러리
  • BGRABitmap - 픽셀 등을 직접 접근하여 shape과 비트맵을 투병하게 그림
  • LazRGBGraphics - 메모리 이미지 프로세싱과 팩셀 조작(스캔 라인 처럼)을 쉽게 하기 위한 패키지.
  • fpvectorial - 벡터이미지의 읽기, 수정 기록을 지원
  • 더블 그래디언트 - 'double gradient' & 'n gradient' 비트맵을 쉽게 그림
  • 그래디언트필터 - TGradientFiller는 라자루스에서 사용지 그래디언트를 만드는 최선의 방법이다.
  • PascalMagick - ImageMagick과 인터페이스할 수 있는 API를 사용하기 쉽게 한다. 멀티 플랫폼의 자유 소프트웨어 수트로 비트맵 이미지를 생성, 편집하고 구성할 수 있다.
  • 샘플 그래픽 - 라자루스와 드로잉 툴을 이용한 그래픽 갤러리
  • 빠른 직접 픽셀 접근 - 직적 비트맵 픽셀 접근을 위한 메쏘드들의 비교
  • AggPas - AggPas 는 Anti-Grain Geometry 라이브러리의 오브젝트 파스칼 변환물로서 안티 알리아싱된 드로잉과 정확한 서브 픽셀의 정확성에있어서 빠르고 강력한 기능을 제공한다. AggPas를 메모리상에서 몇몇 벡터 데이터로부터픽셀 이미지를 만드는 렌더링 엔진으로 사용할 수가 있다.

3D 그리기

  • GLScene - 비주얼 OpenGL 그래픽 라이브러리의 시작 GLScene

챠트

  • TAChart - 라자루스의 챠팅 컴포넌트
  • PlotPanel - 애니매이트 그래프를 위한 플롯과 챠팅 컴포넌트.
  • Perlin Noise - LCL 어플리케이션에서 Perlin Noise를 사용하는 것에 대한 문서.

LCL의 그래픽 모델에 대한 소개

The LCL provides two kinds of drawing class: Native classes and non-native classes. Native graphics classes are the most traditional way of drawing graphics in the LCL and are also the most important one, while the non-native classes are complementary, but also very important. The native classes are mostly located in the unit Graphics of the LCL and are the well known classes: TBitmap, TCanvas, TFont, TBrush, TPen, TPortableNetworkGraphic, etc.

TCanvas is a class capable of executing drawings. It cannot exist alone and must either be attached to something visible (or at least which may possibly be visible), such as a visual control descending from TControl, or be attached to an off-screen buffer from a TRasterImage descendent (TBitmap is the most commonly used). TFont, TBrush and TPen describe how the drawing of various operations will be executed in the Canvas.

TRasterImage (usually used via its descendant TBitmap) is a memory area reserved for drawing graphics, but it is created for maximum compatibility with the native Canvas and therefore in LCL-Gtk2 in X11 it is located in the X11 server, which makes pixel access via the Pixels property extremely slow. In Windows it is very fast because Windows allows creating a locally allocated image which can receive drawings from a Windows Canvas.

Besides these there are also non-native drawing classes located in the units graphtype (TRawImage), intfgraphics (TLazIntfImage) and lazcanvas (TLazCanvas, this one exists in Lazarus 0.9.31+). TRawImage is the storage and description of a memory area which contains an image. TLazIntfImage is an image which attaches itself to a TRawImage and takes care of converting between TFPColor and the real pixel format of the TRawImage. TLazCanvas is a non-native Canvas which can draw to an image in a TLazIntfImage.

The main difference between the native classes and the non-native ones is that the native ones do not perform exactly the same in all platforms, because the drawing is done by the underlying platform itself. The speed and also the exact final result of the image drawing can have differences. The non-native classes are guaranteed to perform exactly the same drawing in all platforms with a pixel level precision and they all perform reasonably fast in all platforms.

In the widgetset LCL-CustomDrawn the native classes are implemented using the non-native ones.

All of these classes will be better described in the sections below.

TCanvas로 작업하기

디폴트 GUI 폰트 사용하기

다음 샘플코드로 사용이 가능하다:

  SelectObject(Canvas.Handle, GetStockObject(DEFAULT_GUI_FONT));

폭이 제한된 텍스트 그리기

DrawText 루틴을 사용해라, 처음에는 DT_CALCRECT와 함께 다음에는 없이 사용한다.

  // First calculate the text size then draw it
  TextBox := Rect(0, currentPos.Y, Width, High(Integer));
  DrawText(ACanvas.Handle, PChar(Text), Length(Text),
    TextBox, DT_WORDBREAK or DT_INTERNAL or DT_CALCRECT);

  DrawText(ACanvas.Handle, PChar(Text), Length(Text),
    TextBox, DT_WORDBREAK or DT_INTERNAL);

날카로운 에지를 가진 텍스트 그리기(안티알리아싱 없음)

몇몇 위젯 다음을 통해 이를 지원한다:

Canvas.Font.Quality:=fqNonAntialiased

gtk2 같은 몇몇 위젯 셋은 이를 지원하지 않고 항상 엘리아스로 그린다. gtk2에서 날카로운 모서리를 가진 텍스트를 그리는 간단한 procedure가 있다. 모든 경우를 고려한 것이 아니고 아이디어만 제공한다:

procedure PaintAliased(Canvas: TCanvas; x,y: integer; const TheText: string);
var
  w,h: integer;
  IntfImg: TLazIntfImage;
  Img: TBitmap;
  dy: Integer;
  dx: Integer;
  col: TFPColor;
  FontColor: TColor;
  c: TColor;
begin
  w:=0;
  h:=0;
  Canvas.GetTextSize(TheText,w,h);
  if (w<=0) or (h<=0) then exit;
  Img:=TBitmap.Create;
  IntfImg:=nil;
  try
    // 비트맵에 텍스트를 그림
    Img.Masked:=true;
    Img.SetSize(w,h);
    Img.Canvas.Brush.Style:=bsSolid;
    Img.Canvas.Brush.Color:=clWhite;
    Img.Canvas.FillRect(0,0,w,h);
    Img.Canvas.Font:=Canvas.Font;
    Img.Canvas.TextOut(0,0,TheText);
    // 메모리 이미지를 얻어온다
    IntfImg:=Img.CreateIntfImage;
    // 회색 픽셀을 치환
    FontColor:=ColorToRGB(Canvas.Font.Color);
    for dy:=0 to h-1 do begin
      for dx:=0 to w-1 do begin
        col:=IntfImg.Colors[dx,dy];
        c:=FPColorToTColor(col);
        if c<>FontColor then
          IntfImg.Colors[dx,dy]:=colTransparent;
      end;
    end;
    // 비트맵 생성
    Img.LoadFromIntfImage(IntfImg);
    // 페인트
    Canvas.Draw(x,y,Img);
  finally
    IntfImg.Free;
    Img.Free;
  end;
end;

TBitmap과 다른 TGraphics 자손으로 작업하기

TBitmap 객체는 스크린에 보여주기 전에 비트맵을 저장한다. 비트맵을 생성할 때 높이와 넓이를 지정해야만 하며 지정하지 않으면 크기는 0이 되고 아무것도 그릴 수 없게 된다. 그리고 일반적으로 모든 다른 TRasterImage 의 자손들은 동일한 능력을 제공한다. 디스크에서 입출력하려는 포맷과 일치하는 것을 사용하여야 하며 그렇지 않다면 윈도우즈 비트맵(*bmp)일 경우 TBitmap에 관한 디스크 동작은 수행되지 않을 것이다.

(맨 먼저 기억해야 할 것은 라자루스는 플랫폼에 독립적이라는 것이므로, 윈도우즈 API 함수를 사용하는 어떠한 메써드도 질문의 대상이 아니라는 것이다. 그러므로 ScanLine 같은 메써드는 라자루스에서 지원하지 않는다. 라자루스는 장치 독립적인 Bitmap과 GDI32.dll에 있는 함수를 사용하고자 하기 때문입니다

당신이 TBitmap의 높이와 폭을 정의하지 않았다면 그것은 표준적인 것이고 매우 작은 것이라는 것을 명심해라)

Loading/Saving an image from/to the disk

To load an image from the disk use TGraphic.LoadFromFile and to save it to another disk file use TGraphic.SaveToFile. Use the appropriate TGraphic descendent which matches the format expected. See Developing_with_Graphics#Image_formats for a list of available image format classes.

var
  MyBitmap: TBitmap;
begin
  MyBitmap := TBitmap.Create;
  try
    // Load from disk
    MyBitmap.LoadFromFile(MyEdit.Text);

    // Here you can use MyBitmap.Canvas to read/write to/from the image

    // Write back to another disk file
    MyBitmap.SaveToFile(MyEdit2.Text);
  finally
    MyBitmap.Free;
  end;
end;

When using any other format the process is compelely identical, just use the adequate class. For example, for PNG images:

var
  MyPNG: TPortableNetworkGraphic;
begin
  MyPNG := TPortableNetworkGraphic.Create;
  try
    // Load from disk
    MyPNG.LoadFromFile(MyEdit.Text);

    // Here you can use MyPNG.Canvas to read/write to/from the image

    // Write back to another disk file
    MyPNG.SaveToFile(MyEdit2.Text);
  finally
    MyPNG.Free;
  end;
end;

If you don't know beforehand the format of the image, use TPicture which will determine the format based in the file extension. Note that TPicture does not support all formats supported by Lazarus, as of Lazarus 0.9.31 it supports BMP, PNG, JPEG, Pixmap and PNM while Lazarus also supports ICNS and other formats:

var
  MyPicture: TPicture;
begin
  MyPicture := TPicture.Create;
  try
    // Load from disk
    MyPicture.LoadFromFile(MyEdit.Text);

    // Here you can use MyPicture.Graphic.Canvas to read/write to/from the image

    // Write back to another disk file
    MyPicture.SaveToFile(MyEdit2.Text);
  finally
    MyPicture.Free;
  end;
end;

Direct pixel access

To do directly access the pixels of bitmaps one can either use external libraries, such as BGRABitmap, LazRGBGraphics and Graphics32 or use the Lazarus native TLazIntfImage. For a comparison of pixel access methods, see fast direct pixel access.

On some Lazarus widgetsets (notably LCL-Gtk2), the bitmap data is not stored in memory location which can be accessed by the application and in general the LCL native interfaces draw only through native Canvas routines, so each SetPixel / GetPixel operation involves a slow call to the native Canvas API. In LCL-CustomDrawn this is not the case since the bitmap is locally stored for all backends and SetPixel / GetPixel is fast. For obtaining a solution which works in all widgetsets one should use TLazIntfImage. As Lazarus is meant to be platform independent and work in gtk2, the TBitmap class does not provide a property like Scanline. There is a GetDataLineStart function, equivalent to Scanline, but only available for memory images like TLazIntfImage which internally uses TRawImage.

To sum it up, with the standard TBitmap, you can only change pixels indirectly, by using TCanvas.Pixels. Calling a native API to draw / read an individual pixel is course slower than direct pixel access, notably so in LCL-gtk2 and LCL-Carbon.

컬러가 투명한 비트맵 그리기

라자루스 0.9.11에서 구현된 새로운 특징은 컬러 투명한 비트맵이다. 비트맵 파일(*.BMP)파일은 투명도를 저장 할 수 있는 어떠한 정보도 저장할 수 없지만, 투명영역을 표현하기위한 컬러를 선택한다면 그 정보를 가진 것 처럼 동작한다. 이것은 Win32 어플리케이션에서 사용된 일반적인 트릭이다.

다음 예제는 윈도우즈 리소스에서 비트맵을 로드하고, 투명하기 원하는 색상(clFuchsia)을 선택하고 그것을 캔버스에 그린다.

procedure MyForm.MyButtonOnClick(Sender: TObject);
var
  buffer: THandle;
  bmp: TBitmap;
  memstream: TMemoryStream;
begin
  bmp := TBitmap.Create;

  buffer := Windows.LoadBitmap(hInstance, MAKEINTRESOURCE(ResourceID));

  if (buffer = 0) then exit; // 비트맵을 로딩하다가 에러

  bmp.Handle := buffer;
  memstream := TMemoryStream.create;
  try
    bmp.SaveToStream(memstream);
    memstream.position := 0;
    bmp.LoadFromStream(memstream);
  finally
    memstream.free;
  end;

  bmp.Transparent := True;
  bmp.TransparentColor := clFuchsia;

  MyCanvas.Draw(0, 0, bmp);

  bmp.Free; // 할당된 비트맵을 해제한다.
end;

메모리 할당은 TMemoryStream으로 수행한다는 것을 명심하라. 이미지를 정확히 로딩하는지 확인하는 것은 필수이다.

스크린의 스크린 샷 얻기

라자루스 0.9.16 이래크로스 플랫폼상에서 스크린의 스크린샷을 얻기 위해 LCL을 사용할 수 있다. 다음 예제 코드가 그렇게 한다(gtk2와 win32상에서 동작하지만, gtk1에서는 현재 동작하지 않는다):

  uses LCLIntf, LCLType;

  ...

var
  MyBitmap: TBitmap;
  ScreenDC: HDC;
begin
  MyBitmap := TBitmap.Create;
  ScreenDC := GetDC(0);
  MyBitmap.LoadFromDevice(ScreenDC);
  ReleaseDC(ScreenDC);

  ...

서서히 사라지는 예

Fading 그림을 만들려고 한다면, 델파이에서는 다음과 같을 것이다:

type
  PRGBTripleArray = ^TRGBTripleArray;
  TRGBTripleArray = array[0..32767] of TRGBTriple;

procedure TForm1.FadeIn(aBitMap: TBitMap);
var
  Bitmap, BaseBitmap: TBitmap;
  Row, BaseRow: PRGBTripleArray;
  x, y, step: integer;
begin
  Bitmap := TBitmap.Create;
  try
    Bitmap.PixelFormat := pf32bit;  //  pf24bit
    Bitmap.Assign(aBitMap);
    BaseBitmap := TBitmap.Create;
    try
      BaseBitmap.PixelFormat := pf32bit;
      BaseBitmap.Assign(Bitmap);
      for step := 0 to 32 do begin
        for y := 0 to (Bitmap.Height - 1) do begin
          BaseRow := BaseBitmap.Scanline[y];
          Row := Bitmap.Scanline[y];
          for x := 0 to (Bitmap.Width - 1) do begin
            Row[x].rgbtRed := (step * BaseRow[x].rgbtRed) shr 5;
            Row[x].rgbtGreen := (step * BaseRow[x].rgbtGreen) shr 5; // Fading
            Row[x].rgbtBlue := (step * BaseRow[x].rgbtBlue) shr 5;
          end;
        end;
        Form1.Canvas.Draw(0, 0, Bitmap);
        InvalidateRect(Form1.Handle, nil, False);
        RedrawWindow(Form1.Handle, nil, 0, RDW_UPDATENOW);
      end;
    finally
      BaseBitmap.Free;
    end;
  finally
    Bitmap.Free;
  end;
end;

라자루스에서 이 함수는 다음과 같이 구현된다:

uses LCLType,      // HBitmap 타입
     IntfGraphics, // TLazIntfImage 타입
     fpImage;      // TFPColor 타입
...
 procedure TForm1.FadeIn(ABitMap: TBitMap);
 var
   SrcIntfImg, TempIntfImg: TLazIntfImage;
   ImgHandle,ImgMaskHandle: HBitmap;
   FadeStep: Integer;
   px, py: Integer;
   CurColor: TFPColor;
   TempBitmap: TBitmap;
 begin
   SrcIntfImg:=TLazIntfImage.Create(0,0);
   SrcIntfImg.LoadFromBitmap(ABitmap.Handle,ABitmap.MaskHandle);
   TempIntfImg:=TLazIntfImage.Create(0,0);
   TempIntfImg.LoadFromBitmap(ABitmap.Handle,ABitmap.MaskHandle);
   TempBitmap:=TBitmap.Create;
   for FadeStep:=1 to 32 do begin
     for py:=0 to SrcIntfImg.Height-1 do begin
       for px:=0 to SrcIntfImg.Width-1 do begin
         CurColor:=SrcIntfImg.Colors[px,py];
         CurColor.Red:=(CurColor.Red*FadeStep) shr 5;
         CurColor.Green:=(CurColor.Green*FadeStep) shr 5;
         CurColor.Blue:=(CurColor.Blue*FadeStep) shr 5;
         TempIntfImg.Colors[px,py]:=CurColor;
       end;
     end;
     TempIntfImg.CreateBitmaps(ImgHandle,ImgMaskHandle,false);
     TempBitmap.Handle:=ImgHandle;
     TempBitmap.MaskHandle:=ImgMaskHandle;
     Canvas.Draw(0,0,TempBitmap);
   end;
   SrcIntfImg.Free;
   TempIntfImg.Free;
   TempBitmap.Free;
 end;

이 페이지의 라자루스 코드는 $LazarusPath/examples/lazintfimage/fadein1.lpi 프로젝트에서 가져온 것이다. 그러므로 만약 잘하고 싶다면 이 예제를 자세히 보고 그래픽 프로그래밍을 시작하라.

TLazIntfImage를 Canvas에 그리기

ScanLines 속성 일시적으로 TBitmap에서 삭제 되었기 때문에, Bitmap scanline 데이터에 접근하기 위한 유일한 방법은 TLazIntfImage를 사용하는 것이다. 다음은 TBitmap에서 TLazIntfImage 을 생성하는 방법에 대한 것과 Canvas에 TLazIntfImage에 그리는 방법에 대한 예이다.

uses
  ...GraphType, IntfGraphics, LCLType, LCLProc,  LCLIntf ...

procedure TForm1.Button4Click(Sender: TObject);
var
  b: TBitmap;
  t: TLazIntfImage;
  bmp, old: HBitmap;
  msk: HBitmap;
  tmpDC: HDC;
begin
  b := TBitmap.Create;
  try
    b.LoadFromFile('test.bmp');
    t := b.CreateIntfImage;
    t.CreateBitmaps(bmp, msk, false);

    tmpDC := CreateCompatibleDC(Canvas.Handle);
    old := SelectObject(tmpDC, bmp);
    BitBlt(Canvas.Handle, 0, 0, t.Width, t.Height, tmpDC, 0, 0, SRCCOPY);
    DeleteObject(SelectObject(tmpDC, old));
    DeleteObject(msk);
    DeleteDC(tmpDC);

  finally
    t.Free;
    b.Free;
  end;
end;

Working with TLazIntfImage, TRawImage and TLazCanvas

TLazIntfImage is a non-native equivalent of TRasterImage (more commonly utilized in the form of it's descendent TBitmap). The first thing to be aware about this class is that unlike TBitmap it will not automatically allocate a memory area for the bitmap, one should first initialize a memory area and then give it to the TLazIntfImage. Right after creating a TLazIntfImage one should either connect it to a TRawImage or load it from a TBitmap.

TRawImage is of the type object and therefore does not need to be created nor freed. It can either allocate the image memory itself when one calls TRawImage.CreateData or one can pass a memory block allocated for examply by a 3rd party library such as the Windows API of the Cocoa Framework from Mac OS X and pass the information of the image in TRawImage.Description, TRawImage.Data and TRawImage.DataSize. Instead of attaching it to a RawImage one could also load it from a TBitmap which will copy the data from the TBitmap and won't be syncronized with it afterwards. The TLazCanvas cannot exist alone and must always be attached to a TLazIntfImage.

The example below shows how to choose a format for the data and ask the TRawImage to create it for us and then we attach it to a TLazIntfImage and then attach a TLazCanvas to it:

uses graphtype, intfgraphics, lazcanvas;

var
  AImage: TLazIntfImage;
  ACanvas: TLazCanvas;
  lRawImage: TRawImage;
begin
    lRawImage.Init;
    lRawImage.Description.Init_BPP32_A8R8G8B8_BIO_TTB(AWidth, AHeight);
    lRawImage.CreateData(True);
    AImage := TLazIntfImage.Create(0,0);
    AImage.SetRawImage(lRawImage);
    ACanvas := TLazCanvas.Create(AImage);

Initializing a TLazIntfImage

One cannot simply create an instance of TLazIntfImage and start using it. It needs to add a storage to it. There are 3 ways to do this:

1. Attach it to a TRawImage

2. Load it from a TBitmap. Note that it will copy the memory of the TBitmap so it won't remain connected to it.

   SrcIntfImg:=TLazIntfImage.Create(0,0);
   SrcIntfImg.LoadFromBitmap(ABitmap.Handle,ABitmap.MaskHandle);

3. Load it from a raw image description, like this:

   IntfImg := TLazIntfImage.Create(0,0);
   IntfImg.DataDescription:=GetDescriptionFromDevice(0);
   IntfImg.SetSize(10,10);

The 0 device in GetDescriptionFromDevice(0) uses the current screen format.

TLazIntfImage.LoadFromFile

Here is an example how to load an image directly into a TLazIntfImage. It initializes the TLazIntfImage to a 32bit RGBA format. Keep in mind that this is probably not the native format of your screen.

uses LazLogger, Graphics, IntfGraphics, GraphType;
procedure TForm1.FormCreate(Sender: TObject);
var
  AImage: TLazIntfImage;
  lRawImage: TRawImage;
begin
  // create a TLazIntfImage with 32 bits per pixel, alpha 8bit, red 8 bit, green 8bit, blue 8bit,
  // Bits In Order: bit 0 is pixel 0, Top To Bottom: line 0 is top
  lRawImage.Init;
  lRawImage.Description.Init_BPP32_A8R8G8B8_BIO_TTB(0,0);
  lRawImage.CreateData(false);
  AImage := TLazIntfImage.Create(0,0);
  try
    AImage.SetRawImage(lRawImage);
    // Load an image from disk.
    // It uses the file extension to select the right registered image reader.
    // The AImage will be resized to the width, height of the loaded image.
    AImage.LoadFromFile('lazarus/examples/openglcontrol/data/texture1.png');
    debugln(['TForm1.FormCreate ',AImage.Width,' ',AImage.Height]);
  finally
    AImage.Free;
  end;
end;

Loading a TLazIntfImage into a TImage

The pixel data of a TImage is the TImage.Picture property, which is of type TPicture. TPicture is a multi format container containing one of several common image formats like Bitmap, Icon, Jpeg or PNG . Usually you will use the TPicture.Bitmap to load a TLazIntfImage:

    Image1.Picture.Bitmap.LoadFromIntfImage(IntfImg);

Notes:

  • To load a transparent TLazIntfImage you have to set the Image1.Transparent to true.
  • TImage uses the screen format. If the TLazIntfImage has a different format then the pixels will be converted. Hint: You can use IntfImg.DataDescription:=GetDescriptionFromDevice(0); to initialize the TLazIntfImage with the screen format.

서서히 사라지는 예제

TLazIntfImage를 이용한 서서히 사라지는 예제

{ This code has been taken from the $LazarusPath/examples/lazintfimage/fadein1.lpi project. }
uses LCLType,      // HBitmap 타입
     IntfGraphics, // TLazIntfImage 타입
     fpImage;      // TFPColor 타입
...
 procedure TForm1.FadeIn(ABitMap: TBitMap);
 var
   SrcIntfImg, TempIntfImg: TLazIntfImage;
   ImgHandle,ImgMaskHandle: HBitmap;
   FadeStep: Integer;
   px, py: Integer;
   CurColor: TFPColor;
   TempBitmap: TBitmap;
 begin
   SrcIntfImg:=TLazIntfImage.Create(0,0);
   SrcIntfImg.LoadFromBitmap(ABitmap.Handle,ABitmap.MaskHandle);
   TempIntfImg:=TLazIntfImage.Create(0,0);
   TempIntfImg.LoadFromBitmap(ABitmap.Handle,ABitmap.MaskHandle);
   TempBitmap:=TBitmap.Create;
   for FadeStep:=1 to 32 do begin
     for py:=0 to SrcIntfImg.Height-1 do begin
       for px:=0 to SrcIntfImg.Width-1 do begin
         CurColor:=SrcIntfImg.Colors[px,py];
         CurColor.Red:=(CurColor.Red*FadeStep) shr 5;
         CurColor.Green:=(CurColor.Green*FadeStep) shr 5;
         CurColor.Blue:=(CurColor.Blue*FadeStep) shr 5;
         TempIntfImg.Colors[px,py]:=CurColor;
       end;
     end;
     TempIntfImg.CreateBitmaps(ImgHandle,ImgMaskHandle,false);
     TempBitmap.Handle:=ImgHandle;
     TempBitmap.MaskHandle:=ImgMaskHandle;
     Canvas.Draw(0,0,TempBitmap);
   end;
   SrcIntfImg.Free;
   TempIntfImg.Free;
   TempBitmap.Free;
 end;

Image format specific example

If you know that the TBitmap is using blue 8bit, green 8bit, red 8bit you can directly access the bytes, which is somewhat faster:

uses LCLType, // HBitmap type
     IntfGraphics, // TLazIntfImage type
     fpImage; // TFPColor type
...
type
  TRGBTripleArray = array[0..32767] of TRGBTriple;
  PRGBTripleArray = ^TRGBTripleArray;

procedure TForm1.FadeIn2(aBitMap: TBitMap);
 var
   IntfImg1, IntfImg2: TLazIntfImage;
   ImgHandle,ImgMaskHandle: HBitmap;
   FadeStep: Integer;
   px, py: Integer;
   CurColor: TFPColor;
   TempBitmap: TBitmap;
   Row1, Row2: PRGBTripleArray;
 begin
   IntfImg1:=TLazIntfImage.Create(0,0);
   IntfImg1.LoadFromBitmap(aBitmap.Handle,aBitmap.MaskHandle);

   IntfImg2:=TLazIntfImage.Create(0,0);
   IntfImg2.LoadFromBitmap(aBitmap.Handle,aBitmap.MaskHandle);

   TempBitmap:=TBitmap.Create;
   
   //with Scanline-like
   for FadeStep:=1 to 32 do begin
     for py:=0 to IntfImg1.Height-1 do begin
       Row1 := IntfImg1.GetDataLineStart(py); //like Delphi TBitMap.ScanLine
       Row2 := IntfImg2.GetDataLineStart(py); //like Delphi TBitMap.ScanLine
       for px:=0 to IntfImg1.Width-1 do begin
         Row2^[px].rgbtRed:= (FadeStep * Row1^[px].rgbtRed) shr 5;
         Row2^[px].rgbtGreen := (FadeStep * Row1^[px].rgbtGreen) shr 5; // Fading
         Row2^[px].rgbtBlue := (FadeStep * Row1^[px].rgbtBlue) shr 5;
       end;
     end;
     IntfImg2.CreateBitmaps(ImgHandle,ImgMaskHandle,false);
     
     TempBitmap.Handle:=ImgHandle;
     TempBitmap.MaskHandle:=ImgMaskHandle;
     Canvas.Draw(0,0,TempBitmap);
   end; 

   IntfImg1.Free;
   IntfImg2.Free;
   TempBitmap.Free;
 end;

Conversion between TLazIntfImage and TBitmap

Since Lazarus has no TBitmap.ScanLines property, the best way to access the pixels of an image in a fast way for both reading and writing is by using TLazIntfImage. The TBitmap can be converted to a TLazIntfImage by using TBitmap.CreateIntfImage() and after modifying the pixels it can be converted back to a TBitmap by using TBitmap.LoadFromIntfImage(); Here's the sample on how to create TLazIntfImage from TBitmap, modify it and then go back to the TBitmap.

uses
  ...GraphType, IntfGraphics, LCLType, LCLProc,  LCLIntf ...

procedure TForm1.Button4Click(Sender: TObject);
var
  b: TBitmap;
  t: TLazIntfImage;
begin
  b := TBitmap.Create;
  try
    b.LoadFromFile('test.bmp');
    t := b.CreateIntfImage;

    // Read and/or write to the pixels
    t.Colors[10,20] := colGreen;

    b.LoadFromIntfImage(t);
  finally
    t.Free;
    b.Free;
  end;
end;

Using the non-native StretchDraw from LazCanvas

Just like TCanvas.StretchDraw there is TLazCanvas.StretchDraw but you need to specify the interpolation which you desire to use. The interpolation which provides a Windows-like StretchDraw with a very sharp result (the opposite of anti-aliased) can be added with: TLazCanvas.Interpolation := TFPSharpInterpolation.Create;

There are other interpolations available in the unit fpcanvas.

uses intfgraphics, lazcanvas;

procedure TForm1.StretchDrawBitmapToBitmap(SourceBitmap, DestBitmap: TBitmap; DestWidth, DestHeight: integer);
var
  DestIntfImage, SourceIntfImage: TLazIntfImage;
  DestCanvas: TLazCanvas;
begin
  // Prepare the destination

  DestIntfImage := TLazIntfImage.Create(0, 0);
  DestIntfImage.LoadFromBitmap(DestBitmap.Handle, 0);

  DestCanvas := TLazCanvas.Create(DestIntfImage);

  //Prepare the source
  SourceIntfImage := TLazIntfImage.Create(0, 0);
  SourceIntfImage.LoadFromBitmap(SourceBitmap.Handle, 0);

  // Execute the stretch draw via TFPSharpInterpolation
  DestCanvas.Interpolation := TFPSharpInterpolation.Create;
  DestCanvas.StretchDraw(0, 0, DestWidth, DestHeight, SourceIntfImage);

  // Reload the image into the TBitmap
  DestBitmap.LoadFromIntfImage(DestIntfImage);

  SourceIntfImage.Free;
  DestCanvas.Interpolation.Free;  
  DestCanvas.Free;
  DestIntfImage.Free;
end;

procedure TForm1.FormPaint(Sender: TObject);
var
  Bmp, DestBitmap: TBitmap;
begin
  // Prepare the destination
  DestBitmap := TBitmap.Create;
  DestBitmap.Width := 100;
  DestBitmap.Height := 100;

  Bmp := TBitmap.Create;
  Bmp.Width := 10;
  Bmp.Height := 10;
  Bmp.Canvas.Pen.Color := clYellow;
  Bmp.Canvas.Brush.Color := clYellow;
  Bmp.Canvas.Rectangle(0, 0, 10, 10);
  StretchDrawBitmapToBitmap(Bmp, DestBitmap, 100, 100);
  Canvas.Draw(0, 0, Bmp);
  Canvas.Draw(100, 100, DestBitmap);
end;

모션 그래픽 - 플리커링을 피하는 방법

많은 프로그램이 2D 그래픽을 GUI에 출력하여 그린다. 이 그래픽을 빨리 변환하고자 한다면 곧 문제에 봉착할 것이다: 빠르게 변환하는 그래픽은 종종 스크린을 번쩍이게 한다. 이러한 상황은 가끔 전체 이미지를 보거나 가끔은 부분적으로 그려질 때 일어난다. 이는 그림을 그리는 시간이 필요하기 때문에 일어나는 것이다.

하지만 번쩍임을 어떻게 피하고 최적의 드로잉 속도를 얻을 수 있을까? 물론 OpenGL을 이용한 하드웨어 가속 기능을 사용할 수 있지만, 이러한 접근법은 작은 프로그램 또는 구형 컴퓨터에서는 꽤 어려운 문제가 되기도 한다. OpenGL의 도움을 받는다면 라자루스에 딸려오는 예제를 살펴보면 된다. 또한 A.J. Venter의 gamepack를 사용할 수 있는데, 더블 버퍼 캔버스와 스프라이트 컴포넌트를 제공한다.

Canvas에 그리기 위해서는 제공되는 옵션 등을 이제 시험해 볼 것이다:

TImage에 그리기

TImage는 두개 파트로 구성된다. TGraphic, 종종 TBitmap,은 영구적인 그림과 비주얼 영역을 가지며 OnPaint 때마다 다시 그려진다. TImage의 크기를 바꾸면 bitmap의 크기를 바꾸지 않는다. 그래픽(또는 비트맵)은 Image1.Picture.Graphic (또는 Image1.Picture.Bitmap)을 통해 접근할 수 있다. 캔버스는 Image1.Picture.Bitmap.Canvas이다. TImage의 비주얼 영역의 캔버스는 Image1.Canvas를 통해 Image1.OnPaint 동안에만 접근 할 수 있다.

중요: TImage의 graphic/bitmap에 그리기 위해 Image1 이벤트의 OnPaint를 사용하면 안된다. TImage의 graphic은 버퍼에 보관 되므로, 하고자 하는 모든 것은 어느 곳에서던지 그려지고 변화는 영구적인 것이 된다. 그러나 만약 항상 다시 그린다면 이미지는 번쩍이게 될 것이다. 이런 경우 다른 옵션을 시도해야 한다. TImage에 그리는 것은 다른 접근법보다 느리다고 여겨진다.

TImage의 비트맵의 크기 변경

Note: OnPaint동안 이것을 사용하지 마라.

 with Image1.Picture.Bitmap do begin
   Width:=100;
   Height:=120;
 end;

TImage의 비트맵을 페인팅하기

Note: OnPaint동안 이것을 사용하지 마라.

 with Image1.Picture.Bitmap.Canvas do begin
   // 모든 비트맵을 빨강으로 채우기
   Brush.Color:=clRed;
   FillRect(0,0,Width,Height);
 end;

Note: Image1.OnPaint의 내부에서 Image1.Canvas은 volatile visible 영역을 가리킨다. Image1.OnPaint의 외부에서는 Image1.Canvas는 Image1.Picture.Bitmap.Canvas를 가리킨다.

다른 예:

procedure TForm1.BitBtn1Click(Sender: TObject);
var
  x, y: Integer;
begin
  // 배경 그리기
  MyImage.Canvas.Pen.Color := clWhite;
  MyImage.Canvas.Rectangle(0, 0, Image.Width, Image.Height);
  
  // 사각형 그리기
  MyImage.Canvas.Pen.Color := clBlack;
  for x := 1 to 8 do
   for y := 1 to 8 do
    MyImage.Canvas.Rectangle(Round((x - 1) * Image.Width / 8), Round((y - 1) * Image.Height / 8),
       Round(x * Image.Width / 8), Round(y * Image.Height / 8));
end;

TImage의 volatile visual 영역에 페인팅하기

OnPaint 동안 이 영역에만 페인트 할 수 있다. 그 영역이 무효화 될 때 결국 LCL은 자동으로 OnPaint를 부른다. Image1.Invalidate로 수동적으로 그 여역을 무효화 시킬 수 있다.이는 즉시 OnPaint를 부르는 것이 아니므로 원할 때마다 Invalidate를 부를 수 있다.

 procedure TForm.Image1Paint(Sender: TObject);
 begin
   with Image1.Canvas do begin
     // 선을 페인트한다.
     Pen.Color:=clRed;
     Line(0,0,Width,Height);
   end;
 end;

OnPaint 이벤트에 그리기

이 경우 모든 드로잉은 폼의 OnPaint 이벤트에서 이루어져야 한다. TImage 처럼 버퍼에 남아있지 않는다.

자체적으로 그릴 수 있는 커스텀 컨트롤 생성하기

커스텀 컨트롤을 생성하는 것은 코드를 구조화 시키는 장점이 있으며 컨트롤을 재사용할 수 있다. 이 접근 법은 매우 빠르지만 TBitmap을 먼저 그리지 않고 캔버스에 그린다면 여전히 번쩍임이 나타날 것이다. 이러한 경우 컨트롤의 OnPaint 이벤트를 사용할 필요가 없다.

다음은 커스텀 컨트롤에 대한 예제이다:

uses
 Classes, SysUtils, Controls, Graphics, LCLType;

type
  TMyDrawingControl = class(TCustomControl)
  public
    procedure EraseBackground(DC: HDC); override;
    procedure Paint; override;
  end;

implementation

procedure TMyDrawingControl.EraseBackground(DC: HDC);
begin
  // 디폴트 배경 지우기를 가능하게 하려면 다음의 코멘트를 지워라
  //inherited EraseBackground(DC);
end; 

procedure TMyDrawingControl.Paint;
var
  x, y: Integer;
  Bitmap: TBitmap;
begin
  Bitmap := TBitmap.Create;
  try
    // Bitmap 크기 초기화
    Bitmap.Height := Height;
    Bitmap.Width := Width;
 
    // 배경 그리기
    Bitmap.Canvas.Pen.Color := clWhite;
    Bitmap.Canvas.Rectangle(0, 0, Width, Height);

    // 사각형 그리기
    Bitmap.Canvas.Pen.Color := clBlack;
    for x := 1 to 8 do
     for y := 1 to 8 do
      Bitmap.Canvas.Rectangle(Round((x - 1) * Width / 8), Round((y - 1) * Height / 8),
       Round(x * Width / 8), Round(y * Height / 8));
      
    Canvas.Draw(0, 0, Bitmap);
  finally
    Bitmap.Free;
  end;

  inherited Paint;
end;

그리고 그것을 폼상에 생성하는 방법이다:

procedure TMyForm.FormCreate(Sender: TObject);
begin
  MyDrawingControl:= TMyDrawingControl.Create(Self);
  MyDrawingControl.Height := 400;
  MyDrawingControl.Width := 500;
  MyDrawingControl.Top := 0;
  MyDrawingControl.Left := 0;
  MyDrawingControl.Parent := Self;
  MyDrawingControl.DoubleBuffered := True;
end;

해제하는 것을 잊으면 안된다:

procedure TMyForm.FormDestroy(Sender: TObject);
begin
  MyDrawingControl.Free;
end;

이것은 표준 위치에 있기 때문에, Top과 Left를 0으로 설정할 필요는 없지만, 컨트롤이 놓일 곳을 지정하기 위해서는 그렇게 해야 한다.

"MyDrawingControl.Parent := Self;" 는 메우 중요하므로 그렇게 하지 않는다면 컨트롤을 볼 수 없을 것이다.

"MyDrawingControl.DoubleBuffered := True;"은 윈도우에서 번쩍임을 피할려면 필요하다. gtk에서는 동작하지 않는다.

A.J. Venter의 게임팩 사용하기

게임팩 접근법은 한개의 더블 버퍼된 캔버스에 모든 것을 그리는 것으로, 준비된, 보여지는 캔버스만 업데이트 된다. 이것은 매우 짧은 코드로 구현되지만, 많은 수의 스프라이트가 넓은 범위에서 빠르게 변화하는 장면을 가능하게 해주는 잇점이 있다. 이러한 접근법을 사용하려면,A.J. Venter의 gamepack에 관심이 있을 것이다. 이는 라자우스에서 게임을 개발할 수 있는 게임 컴포넌트로서 스프라이트 컴포넌트뿐만이 아니라 더블-버퍼 디스플레이 영역을 제공해 주며 서로 서로 잘 통합되도록 디자인 되었다. 게임팩은 subversion:
을 통해 구할 수 있다.

svn co svn://silentcoder.co.za/lazarus/gamepack 추가적인 정보와 문서 그리고 다운로드는 홈페이지에서 구할 수 있다.

이미지 포맷

각 이미지 포맷을 사용하기 위한 적당한 클래스의 테이블이 여기 있다.

포맷 이미지 클래스 유닛
커서(cur) TCursor Graphics
비트맵(bmp) TBitmap Graphics
윈도우즈 아이콘 (ico) TIcon Graphics
Mac OS X 아이콘(icns) TicnsIcon Graphics
Pixmap (xpm) TPixmap Graphics
Portable Network Graphic (png) TPortableNetworkGraphic Graphics
JPEG (jpg, jpeg) TJpegImage Graphics
PNM (pnm) TPortableAnyMapGraphic Graphics

fcl-image 지원 포맷 리스트를 보세요.

포맷 변환

가끔 그래픽 포맷을 다른 형식으로 변환해야 할 때가 있다. 한가지 방법은 그래픽을 중간 형식으로 바꾼 후 TBitmap으로 변환하는 것이다. 대부분의 포맷은 TBitmap에서 만들어질 수 있다.

Bitmap을 PNG로 변환하고 파일에 저장하는 것:

procedure SaveToPng(const bmp: TBitmap; PngFileName: String);
var
  png : TPortableNetworkGraphic; 
begin 
  png := TPortableNetworkGraphic.Create;
  try
    png.Assign(bmp);
    png.SaveToFile(PngFileName);
  finally 
    png.Free;
  end;
end;

픽셀 포맷

TColor

LCL에서 TColor를 위한 내부 픽셀 포맷은 XXBBGGRR 포맷으로 네이티브 윈도우즈 포맷에 대응하며 AARRGGBB를 사용하는 대부분의 다른 라이브러리와는 반대이다. XX 파트는 컬러가 fixed 컬러인지 확인하는데 사용하며 이경우 XX는 00이어야 하고, 시스템 컬러의 인덱스인지 확인한다. 알파 채널을 위한 공간은 예약되어지지 않았다.

분리된 RGB 채널을 TColor로 변환하기 위해서는 다음을 사용한다:

RGBToColor(RedVal, GreenVal, BlueVal);

TColor의 각각의 채널을 얻으려면 변수는 Red, Green 및 Blue 함수을 사용한다:

  RedVal := Red(MyColor);
  GreenVal := Green(MyColor);
  BlueVal := Blue(MyColor);

TFPColor

TFPColor는 대부분의 라이브러에서 일반적인 AARRGGBB 포맷을 사용한다.

LCL 없이 그리기

LCL 없이 그릴 수 있다. 예를 들어 웹서버에서 돌아가는 프로그램이 만든 그래픽은 완전한 비주얼 라이브러리가 없어도 동작해야 한다. 이를 위해 FPImage(별명은 fcl-image)를 사용할 수 있다, 이는 파스칼로 작성된 매우 일반적인 이미지이며 드로잉 라이브러리이다. 사실 LCL은 역시 FPImage를 사용하고 위젯셋(winapi, gtk, carbon, ...)의 호출을 통해 드로임 함수를 구현한다.

여기에 200x100 이미지를 생성하고 백색으로 배경을 칠하며 몇몇 텍스트를 쓰고 .png로 저장하는 방법에 관한 예가 있다:

program fontdraw;

{$mode objfpc}{$H+}

uses
  Classes, SysUtils, FPimage, FPImgCanv, ftfont, FPWritePNG, FPCanvas;

procedure TestFPImgFont;
var
  Img: TFPMemoryImage;
  Writer: TFPWriterPNG;
  ms: TMemoryStream;
  ImgCanvas: TFPImageCanvas;
  fs: TFileStream;
  AFont: TFreeTypeFont;
begin
  Img:=nil;
  ImgCanvas:=nil;
  Writer:=nil;
  ms:=nil;
  fs:=nil;
  AFont:=nil;
  try
    // 프리타입 폰트 매니저 초기화
    ftfont.InitEngine;
    FontMgr.SearchPath:='/usr/share/fonts/truetype/ttf-dejavu/';
    AFont:=TFreeTypeFont.Create;

    // 폭 200, 높이 100의 이미지 생성
    Img:=TFPMemoryImage.Create(200,100);
    Img.UsePalette:=false;
    // 드로잉 동작을 통해 캔버스 생성
    ImgCanvas:=TFPImageCanvas.create(Img);

    // 하얀 배경 칠함
    ImgCanvas.Brush.FPColor:=colWhite;
    ImgCanvas.Brush.Style:=bsSolid;
    ImgCanvas.Rectangle(0,0,Img.Width,Img.Height);

    // 텍스트 그리기
    ImgCanvas.Font:=AFont;
    ImgCanvas.Font.Name:='DejaVuSans';
    ImgCanvas.Font.Size:=20;
    ImgCanvas.TextOut(10,30,'Test');

    // 메모리 스트림에 png로 이미지 쓰기
    Writer:=TFPWriterPNG.create;
    ms:=TMemoryStream.Create;
    writer.ImageWrite(ms,Img);
    // 메모리 스트림을 파일에 쓰기
    ms.Position:=0;
    fs:=TFileStream.Create('testfont.png',fmCreate);
    fs.CopyFrom(ms,ms.Size);
  finally
    AFont.Free;
    ms.Free;
    Writer.Free;
    ImgCanvas.Free;
    Img.Free;
    fs.Free;
  end;
end;

begin
  TestFPImgFont;
end.