[FM2] XE3.FM2에서 Control을 Bitmap으로 내보내기 및 Bitmap.Pixels 사용하기

2012.10.04 20:49

Firemonkey2로 버전업 된 후로 참 많은 것이 변했습니다.


TControl은 말할 것도 없고 TBitmap도 많은 내용이 변했네요.


Unit test에서 특정 Pixel의 색상을 얻어오고자 할때 기존 Bitmap.Pixels이 없어진 것을 확인 후 깜짝 놀랐습니다.


자... 그럼 제가 삽질로 얻어온 내용을 공유하도록 합니다.


| Control을 Bitmap(TImage)으로 내보내기

var
  Bitmap: TBitmap;
  Map: TBitmapData;
begin
  Bitmap :=  TBitmap.Create(Round(Panel1.Width), Round(Panel1.Height));
  try
    Bitmap.Canvas.BeginScene;

    // Bitmap으로 내보내기
    Bitmap.Assign(Panel1.MakeScreenshot);
 
    // Pixel의 값을 원하실 때는 이렇게
    Bitmap.Map(TMapAccess.maRead, Map);
    //AlphaColor := Map.GetPixel(Round(X), Round(Y));
    Bitmap.Unmap(Map);
    Bitmap.Canvas.EndScene;

    Image1.Bitmap.Assign(Bitmap);
  finally
    Bitmap.Free;
  end;
end;


Panel1의 화면을 Bitmap으로 내보내는 코드 입니다.

특정 좌표의 값은 Map.GetPixel로 얻어오실 수 있구요 반환은 TAlphaColor 입니다.


TBitmapData라는 구조체를 이용하는군요 구글링에도 없는 따끈한 정보입니다. 실은 많이 안찾아 봣습니다. -_-;

험프리.김현수 파이어몽키 Bitmap.Pixels, Delphi, Fm2, FMX, TBitmapData, XE3, 태그를 입력해 주세요.

[FMX] Firemonkey 구조 - 어떻게 하나의 코드로 여러 플랫폼에서 실행 될까?

2012.06.23 03:04

이번에는 이리저리 파이어몽키 소스 보며 익혔던 간단한 구조에 대해 설명하려 합니다.

(딱, 제가 아는 만큼만 소개합니다.^^)


파이어몽키는 멀티 플랫폼을 지원하는 델파이 프레임웤 입니다.

어떻게 파이어몽키는 하나의 소스로 여러개의 플랫폼을 지원할까요?

여러개의 플랫폼을 지원하는 열쇠는 FMX.Platform.pas의 Platform: TPlatform에 있습니다.
(C:\Program Files (x86)\Embarcadero\RAD Studio\9.0\source\fmx에 소스파일이 있습니다.)


FMX.Platform의 TPlatform 클래스를 보시면 대부분(거의 다)이 추상메소드(virtual; abstract;) 입니다. 구현이 안되어 있다는 것이죠.

그리고 메소드명이 상당히 플랫폼에 종속적인 냄새를 풍깁니다.

CreateWindow(창을 생성하는 건 OS에서 해주겠죠.)
CreateTimer(타이머도 OS에서 이벤트를 발생해 줍니다.)
WaitMessage(메시지 처리도 OS와 연관이 있지요.) 등등 모두 플랫폼에 종족적인 메소드들입니다.

그리고 소스 디렉토리에 보시면 FMX.Platform.Win과 FMX.Platform.Mac 두개의 플랫폼 관련 유닛파일을 보실 수 있습니다.

즉, FMX.Platform의 TPlatform을 상속받은 클래스들이 구현되어 있겠죠.

맞습니다. 각 파일을 보시면 TPlatformWin과 TPlatformCocoa 두개의 클래스가 TPlatform을 상속받습니다.

물론 추상메소드들도 모두 구현되어 있습니다.

TPlatformWin은 Windows API를 이용하여 구현이 되어 있고, TPlatformCocoa는 OS X API로 구현이 되어 있습니다.


그럼 어떻게 Firemonkey는 플랫폼에 맞는 TPlatform 객체를 사용할까요?

위의 질문에 답하기 위해 먼저 프로젝트 소스파일을 들여다 보겠습니다.

Delphi XE2에서 <Firemonkey HD Application> 프로젝트를 생성하시면 아래와 같은 소스코드를 기본적으로 생성합니다.

program Project1;

uses
  FMX.Forms,
  Unit1 in 'Unit1.pas' {Form1};

{$R *.res}

begin
  Application.Initialize;
  Application.CreateForm(TForm1, Form1);
  Application.Run;
end.

위의 프로젝트 소스를 보면 도무지 참조하는 유닛이 FMX.Forms 밖에 없습니다.


그럼 FMX.Forms 유닛을 살펴보겠습니다.

주욱 보시면 VCL에서도 익숙한 TApplication, 앞으로 친하게 지내야될 TCommonCustomForm 등등의 클래스 등이 보입니다.

그리고 제일 아래에 보면 우리가 찾던 Platform 관련된 코드가 보입니다.

initialization
  RegisterFmxClasses([TApplication], [TApplication]);
  Screen := TScreen.Create(nil);
  Platform := PlatformClass.Create(nil);
  System.Classes.RegisterFindGlobalComponentProc(FindGlobalComponent);

finalization
  System.Classes.UnregisterFindGlobalComponentProc(FindGlobalComponent);
  FreeAndNil(Screen);
  // Platform global is freed in FMX.Types

위 코드의 4번째 줄을 보시면

Platform := PlatformClass.Create(nil);

이라고 PlatformClass를 이용해 TPlatform 객체를 생성해 할당합니다.


자 그럼 다시 PaltformClass를 따라가 봅니다.

FMX.Platform.pas에 PlatformClass 함수가 구현되어 있습니다.

function PlatformClass: TPlatformClass;
begin
  Result := ActualPlatformClass;
end;

그럼 ActualPlatformClass는 어디에 구현되어 있을까요?


예상하시는 분들도 있겠지만 위에서 설명한 Platform별 유닛(FMX.Platform.Win, FMX.Platform.Mac)에 구현되어 있습니다.

MSWINDOWS의 경우 TPlatformWin을 반환하고

MACOS의 경우 TPlatformCocoa를 반환합니다.

근데 의문이 듭니다. 어떻게 델파이는 2개중에 하나를 선택할까요?

그건 바로 Conditional define($IFDEF)을 이용합니다.

FMX.Platform의 구현부(implementation) 시작을 보시면 아래와 같은 코드를 보실 수 있습니다.

{$IFDEF IOS}
uses
  FMX.Platform.iOS, FMX.Canvas.iOS, FMX.Context.GLES;
{$ENDIF}

{$IFDEF MACOS}
uses
  FMX.Platform.Mac, FMX.Canvas.Mac, FMX.Context.Mac;
{$ENDIF}

{$IFDEF MSWINDOWS}
uses
  FMX.Platform.Win, FMX.Context.DX9;
{$ENDIF}

보시면 아시겠지만 IOS, MACOS, MSWINDOWS 별로 다른 유닛을 참조하도록 되어있습니다.

위의 3가지는 모두 Firemonkey가 지원하는 플랫폼입니다.

즉 Project Manager의 Target platform에서 Platform을 선택하면 그에 맞는 값(IOS, MACOS, MSWINDOWS)이 내부적으로 정의됩니다.

그래서 결국은 Target platform에 맞는 TPlatform 객체를 이용하여 OS에 맞는 API를 사용하여 실행됩니다.


좀더 설명을 하자면 위의 프로젝트 소스를 보시면 아래와 같이 Application을 실행(RUN)하는 코드를 볼 수 있습니다.

Application.Run;

Run 메소드를 살펴보면 아래와 같이 Platform에 따른 코드를 사용하여 Application을 실행합니다.

procedure TApplication.Run;
begin
  FRunning := True;
  AddExitProc(DoneApplication);
  try
    Platform.Run; // Platform의 Run 실행
  finally
    FRunning := False;
  end;
end;

// MSWINDOWS에 맞게 Application 실행
procedure TPlatformWin.Run;
begin
  { checking for canvas }
  if GlobalUseDirect2D then
    SetD2DDefault;

  Application.RealCreateForms;
  repeat
    try
      Application.HandleMessage;
    except
      Application.HandleException(Self);
    end;
  until Application.Terminated;
end;

// MACOS 맞게 Application 실행
procedure TPlatformCocoa.Run;
begin
  Application.RealCreateForms;
  CreateApplicationMenu;
  FRunLoopObserver := CFRunLoopObserverCreate(kCFAllocatorDefault, kCFRunLoopBeforeWaiting, True, 0, RunLoopObserverCallback, nil);
  CFRunLoopAddObserver(CFRunLoopGetCurrent, FRunLoopObserver, kCFRunLoopCommonModes);
  NSApp.Run;
end;

다른 메서드 또는 다른 유닛(FMX.Types 등)에서 Platform 전역변수를 이용하도록 구현되어 있습니다.


다시한번 정리하면, 

1, Project 소스에서 FMX.Forms 참조(uses)

2, FMS.Forms의 Initialization에서 FMS.Platform.pas 유닛의 PlatformClass를 통해 Platform 생성

3, FMS.Platform는 각 플랫폼 유닛의 ActualPlatformClass 함수 호출하여 Platform에 맞는 TPlatform 객체 형 방ㄴ환

4, 단, Platform에 맞는 유닛을 $IFDEF를 이용하여 결정($IFDEF는 Target platform 선택 시 정의)


이상으로 Firemonkey에서 Platform에 맞게 실행되는 구조를 두서없이 설명했습니다.

감사합니다. 끝~


PS - 다음에는 Firemonkey를 실질적으로 사용하기 위한 팁들을 몇개 소개하겠습니다.^^

험프리.김현수 파이어몽키 Firemonkey, FMX, FMX 구조, multi platform

[FMX,VCL 비교] #1 VCL의 Canvas와 FMX의 Canvas 차이

2012.06.20 12:52

파이어몽키로 작업하다보면 VCL과의 차이점으로 어려움을 겪는 경우가 많습니다.

그동안 작업하며 습득한 차이점을 한가지한가지 풀어놓으려 합니다.

그중 첫번째 Canvas 입니다.

일반적으로 TImage에 Drawing을 하는 예제로 구성하였습니다.
VCL과 FMX의 동일한 기능을 구현했으니 비교해 보시면 좋을 것 같습니다.

FMX 코딩시 주의점

1, TImage.Picture.Bitmap => TImage.Bitmap : VCL의 Picture 객체가 빠졌습니다.

2, Bitmap.Canvas.BeginScene ~ EndScene, Bitmap.BitmapChange : Canvas에 그리기고 화면에 표시하기 위한 절차입니다.

3, Brush => Fill, Pen => Stroke 등으로 속성들이 약간씩 변했습니다.

4, MoveTo, LineTo => DrawLine

5, FMX의 기본적인 좌표가 Single 형이기 때문에 Bitmap의 Pixel 단위인 Integer로 형변환(Round, Trunc)이 필요합니다.


 이하 소스코드 입니다. 빈프로젝트에 아래 소스 복사하면 컴파일 됩니다.(Unit명만 주의) 폼에 컨트롤이 없습니다.

| VCL Canvas Freeline Draw

unit Unit1;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
  Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.ExtCtrls;

type
  TForm1 = class(TForm)
    procedure FormCreate(Sender: TObject);
  private
    { Private declarations }
    FDownPos: TPoint;
    FImage: TImage;

    procedure MouseDown(Sender: TObject; Button: TMouseButton;
      Shift: TShiftState; X, Y: Integer);
    procedure MouseMove(Sender: TObject; Shift: TShiftState; X, Y: Integer);
  public
    { Public declarations }
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

procedure TForm1.FormCreate(Sender: TObject);
begin
  FImage := TImage.Create(Self);
  FImage.Parent := Self;
  FImage.Align := alClient;
  FImage.OnMouseDown := MouseDown;
  FImage.OnMouseMove := MouseMove;
  FImage.Picture.Bitmap.SetSize(FImage.Width, FImage.Height);
  FIMage.Picture.Bitmap.Canvas.Brush.Color := clBlack;
  FImage.Picture.Bitmap.Canvas.FillRect(FImage.ClientRect);
  FImage.Visible := True;
end;

procedure TForm1.MouseDown(Sender: TObject; Button: TMouseButton;
  Shift: TShiftState; X, Y: Integer);
begin
  if ssLeft in Shift then
    FDownPos := Point(X, Y);
end;

procedure TForm1.MouseMove(Sender: TObject; Shift: TShiftState; X, Y: Integer);
begin
  if ssLeft in Shift then
  begin
    with FImage.Picture.Bitmap.Canvas do
    begin
      Pen.Color := clRed;
      Pen.Width := 3;
      MoveTo(FDownPos.X, FDownPos.Y);
      LineTo(X, Y);
    end;

    FDownPos := Point(X, Y);
  end;
end;

end.


| FMX Canvas Freeline Draw

unit Unit2;

interface

uses
  System.SysUtils, System.Types, System.UITypes, System.Classes, System.Variants,
  FMX.Types, FMX.Controls, FMX.Forms, FMX.Dialogs, FMX.Objects;

type
  TForm2 = class(TForm)
    procedure FormCreate(Sender: TObject);
  private
    { Private declarations }
    FDownPos: TPointF;
    FImage: TImage;

    procedure MouseDown(Sender: TObject; Button: TMouseButton;
      Shift: TShiftState; X, Y: Single);
    procedure MouseMove(Sender: TObject; Shift: TShiftState; X, Y: Single);
  public
    { Public declarations }
  end;

var
  Form2: TForm2;

implementation

{$R *.fmx}

procedure TForm2.FormCreate(Sender: TObject);
begin
  FImage := TImage.Create(Self);
  FImage.Parent := Self;
  FImage.Align := TAlignLayout.alClient;
  FImage.OnMouseDown := MouseDown;
  FImage.OnMouseMove := MouseMove;
  FImage.Bitmap.Create(Round(FImage.Width), Round(FImage.Height));
  FImage.Bitmap.Canvas.BeginScene;
  FImage.Bitmap.Canvas.Fill.Color := claBlack;
  FImage.Bitmap.Canvas.FillRect(FImage.ClipRect, 0, 0, AllCorners, 1);
  FImage.Bitmap.Canvas.EndScene;
  FImage.Visible := True;

end;

procedure TForm2.MouseDown(Sender: TObject; Button: TMouseButton;
  Shift: TShiftState; X, Y: Single);
begin
  if ssLeft in Shift then
    FDownPos := PointF(X, Y);
end;

procedure TForm2.MouseMove(Sender: TObject; Shift: TShiftState; X, Y: Single);
begin
  if ssLeft in Shift then
  begin
    with FImage.Bitmap.Canvas do
    begin
      BeginScene;
      Stroke.Color := claRed;
      StrokeThickness := 3;
      DrawLine(FDownPos, PointF(X, Y), 1);
      EndScene;
    end;
    FImage.Bitmap.BitmapChanged;

    FDownPos := PointF(X, Y);
  end;
end;

end.



험프리.김현수 파이어몽키 Canvas, Firemonkey, FMX, FMX VCL 차이, 파이어몽키

[FMX] 컨트롤(TPanel)등에 Form 넣기.

2012.03.23 12:45
Delphi XE2의 FireMonkey에서는 

TForm이 TControl을 상속 받지 않아 TPanel등에 Embed 할 수 없습니다.

위 방식으로 구현된 많은 샘플코드들을 Firemonkey에서 사용하지 못해 당황하다.
아래와 같은 방법으로 해결(?: 꼼수)하여 공유합니다..
// AForm을 AParent에 Embed
procedure EmbedForm(AParent: TControl; AForm: TCustomForm);
begin
  while AForm.ChildrenCount > 0 do
    AForm.Children[0].Parent := AParent;
end;

정상적으로 말하면 Embed는 아니고 Form에 있는 컨트롤들을 Panel로
부모를 옮겨 버리는 방식입니다.
 
사용방법은
  EmbedForm(Panel1, TForm1.Create(Self));


험프리.김현수 파이어몽키

  1. Blog Icon
    이회원

    panel1에 생성을 한 폼을 어떻게 close 시켜야 하나요? 그냥 free해도 남아 있던데.

[FMX] Firemonkey에서 ODS 사용하기

2012.02.17 16:18
Firemonkey는 아무래도 Cross platform이다 보니 윈도우 API를 사용하는

OutputDebugString 등을 사용할 수 없습니다. 
근데, 너무 불편해요 ODS가 없으면 그래서 찾아보니

Window API를 사용할 수 있네요...
그것도 간단하게

uses에 Winapi.Windows를 추가하면 됩니다.
그리고 아래와 같이 예외(IFDEF)하시고 사용하시면

혹시 다른 플랫폼에서 문제가 생길 염려는 없겠죠^^

{$IFDEF MSWINDOWS}
  OutputDebugString(PChar(Format('우왕 ODS가 된다.', [])));
{$ENDIF}

끝~

험프리.김현수 파이어몽키