December 23, 2020

Groupe "Développeurs Delphi et Pascal" francophone

 Le groupe "Développeurs Delphi et Pascal" vient d'être fondé sur LinkedIn.

C'est un groupe destiné à réunir les développeurs francophones qui utilisent Delphi ou le langage Pascal. Vous pouvez y poster - en Français - des questions techniques et des spécialistes vous aideront.

Bienvenue à tous !


December 19, 2020

ICS V8.65 announced

 ICS V8.65 has been released at: http://wiki.overbyte.eu/wiki/index.php/ICS_Download

ICS is a free internet component library for Delphi 7, 2006 to 2010, XE to XE8, 10 Seattle, 10.1 Berlin, 10.2 Tokyo, 10.3 Rio and 10.4 Sydney, and C++ Builder 2006 to XE3, 10.2 Tokyo, 10.3 Rio and 10.4 Sydney. ICS supports VCL and FMX, Win32, Win64 and MacOS 32-bit targets.

The distribution zip includes the latest OpenSSL 1.1.1i win32, with other versions of OpenSSL being available from the download page.

Major Changes in ICS V8.65 include:

1 - The ReadMe8.txt file has a new 'Getting Started with ICS' section listing the types of projects ICS may be used for, and suggesting the correct components to use, and their related sample applications for  testing.  This is recommended reading for anyone doing new ICS development since it discusses all the new high level components like TSslHttpRest added in the last few years which can reduce development effort considerably.  It may also be viewed at: http://wiki.overbyte.eu/wiki/index.php/ICS_Getting_Started

2 - Added new TIcsRestEmail component that provides basic support for Google and Microsoft Outlook email REST APIs including OAuth2 login and refresh to get an access token for SMTP and POP3 XOAuth2 and 0AuthBearer authentication. TIcsRestEmail has methods to send and read email, to list IDs in a mailbox, read headers and message bodies by ID, send emails and delete emails.

3 - The SMPT, POP3 and MailQueue samples all now support XOAuth2 and 0AuthBearer authentication using the TIcsRestEmail component. The low level component call an event to get the authentication access token, which is provided by IcsRestEmail, together with a refresh token which is saved  instead of a password.

4 - To access email using REST APIs or OAuth2/SMTP/POP3 an 'application account' needs to be created though the Google or Microsoft provider console.

5 - Added a new TIcsTwitter component and sample, requires a developer account from Twitter. Includes login to Twitter, send tweet, search tweets and get specific tweets, all responses are Json which the application needs to untangle.

6 - Improved TRestParams allowing them to save more Delphi types correctly without conversion to strings, and to save parameters in new formats.

7 - There are various OAuth2 improvements to make it easier to implement. Added several TOAuthUri records designed to set-up common OAuth2 account  settings for providers like Google, Twitter, Microsoft  and Sipgate, by using the LoadAuthUri method.

8 - TSimpleWebSrv continues to get less simple, it has aWebSrvIP2 property for a second address so it can listen on IPv4 and IPv6 at the same time, with and without SSL if necessary.  Setting WebSrvIP to localhost sets both 127.0.0.1 and [::1] so the browser OAuth2 redirect can choose IPv4 or IPv6.

9 - Made some improvements to SuperObject, used for Json creation and parsing.  When parsing Json there are new functions that return a sensible error message about parse errors and the location.  There is a new object type DateTime or DT which reads or writes TDateTime to avoid the application needing to do the ISO string conversion. Json can now be parsed to a depth of 64 levels.

10 - Rewrote and improved the way ICS reads SSL/TLS certificates and bundles, simplifying code that has got partly duplicated over the years as new methods were added, and improving error handling so the infamous stack error should no longer appear, instead more useful messages.  All certificate files are now written with the UTF8 character set for the added comments that may include non-ASCII characters.

11 - All the ICS root bundles are now created cleanly by an application, rather than mostly manually by copy and editing, to reduce errors. This fixed four corrupted root certificates in the older bundles, see http://wiki.overbyte.eu/wiki/index.php/FAQ_SSL/TLS_Certificate_Authority_Root_Stores

12 - Previously the Jose unit offered functions primarily for client JWS/JWT REST applications, it now includes extra functions for REST servers to check and verify the JWS/JWT sent by clients.  The Jose sample application has new tests for JWS/JWT, and to parse and display Json.

13 - Added a new Multi Host FTP Server sample using IcsHosts. Really designed to be a Windows service application. It supports multiple SSL hosts with multiple listeners, can order it's own SSL certificates and will create self signed certificates for any missing,  and will email status information and errors to an administrator.

14 - Fixed a long term external SSL session cache issue in some components and samples that meant if an SSL handshake fails due to a bad certificate or chain, it is necessary to remove the SSL session from cache so an immediate retry does not succeed by skipping the certificate checks. This is only a short term issue, because the cache is usually cleared after a few minutes. This will effect any client applications using the external SSL session cache including HTTPS.

15 - Increased the TCP send and receive buffer size to 64K in all components and samples, and generally don't allow it to be set lower.  Unfortunately the default buffer size never kept up with faster internet speeds which meant some components transferred data slowly.

16 - Made some improvements ordering SSL/TLS certificates. Made Windows Server DNS updating using WMI more robust so wild card Acme orders work reliably.

17 - Made some internal changes loading OpenSSL, to avoid the two DLLs being loaded from different directories and to give better exceptions if they are missing.

More detailed release notes are at: http://wiki.overbyte.eu/wiki/index.php/ICS_V8.65

To get help, use the forum:  https://en.delphipraxis.net/forum/37-ics-internet-component-suite/


December 13, 2020

Logitech Gaming LED SDK for Delphi

You've got a LED illuminated keyboard, mouse or headset from Logitech? Then using Logitech Gaming LED SDK, you'll be able to make illuminations on your Logitech hardware supporting that feature. For example, you may assign colors to keys on the keyboard, make it flash or pulse.

Full source code, including demos on Github: https://github.com/fpiette/Logitech-Gaming-LED-SDK-for-Delphi

Minimalist example:

uses
    System.SysUtils,
    LogitechLedLib in 'LogitechLedLib.pas';
begin
    WriteLn('Your keyboard is pulsing for 5 seconds...');
    LogiLed.LoadLedEngine();
    LogiLed.InitWithName('Logitech LED Delphi Console Demo');
    LogiLed.SetTargetDevice(LOGI_DEVICETYPE_ALL);
    LogiLed.PulseLighting(100, 90, 60, 5000, 200);
    WriteLn('Hit RETURN');
    ReadLn;
    LogiLed.Shutdown();
end.

November 18, 2020

FMX Line Angle Demo

 

A simple demo showing how to create a GUI with FMX to let the user draw two intersecting lines and then compute and show the angle between the two lines.

The use has to click on 3 points: the point where the lines intersect, the point where the first line (red) ends and the point where the second line (green) ends.

The user interface is updated in real-time following mouse movements.


 Full source code can be found there: https://github.com/fpiette/Delphi-FMX-Line-Angle-Demo 


October 23, 2020

Image Organizer

 I have created a nice application using Delphi and I provide full source code on GitHub at https://github.com/fpiette/OvbImgOrganizer

OvbImgOrganizer is an application written with Delphi that maintain an index of your images, allow searching with the tags you associate with each image. It also provides a display feature much like Win7 image preview.

I used the following features/components:

- The index is stored in a SQLite database using FireDAC

- The tree is in a VirtualStringTree.

- Image drawing/resizing/rotating/Flipping are done using Direct2D canvas.

- Drag&Drop  is based on this http://stackoverflow.com/questions/4354071

- Translation to other languages using DxGetText.

 Main screen:


 Photo viewer screen:


To start with the application, run the executable. The first time it will ask where to put the data file used for image index.

Then drag images from Windows Explorer and drop them into the main part of the application. Instead of Drag&Drop you can use Menu / Collection / Add images to collection. A dialog opens and you can select one or more files. 

Images are not moved nor copied to the index. Their paths are recorded in the index. If you delete an image, it won't display anymore. The application itself never delete any image. You can remove the index entry but not the image file.

You can create tags by right clicking on the "Available tags" tree on the right. The popup menu has entries to create a full tree of tags. A tag should start with a letter and can contains alphanumeric characters but no space. A tag must be unique in the tree.

You can select images by checking the checkbox under each image. Then you can add tags to all selected images by double clicking on the tag you want in the "Available tags".

You can search for tags by entering one or more tags in the edit box in the upper left corner. When you click on "Search" button, it will search the index for images having all the specified tags (Implicit "and").

When there are to much images to fit the screen, you can click "Load more" button to load more images according to the search criteria.

Usual keyboard shortcuts are active. For example Ctrl+A select all images loaded. Arrows and PgUp/PgDn, Home/End will work as expected.

There are right-click popup menu for almost everything. Try !

To show the files, select (with the checkbox below each image or Ctrl+A to select all) and then click the slide show button on the top-right of the window. An image viewer is shown with the first image. You can then use the arrows or the buttons to navigate thru all selected. The square button make the window full screen and black out the second screen if any to show the images full screen. There are also buttons to flip and rotate the image displayed.


August 29, 2020

Direct2D canvas for Delphi forms

In this blog post, I will show you how easy it is the have a Direct2D canvas for your Delphi form.

What is a canvas?
 

In Delphi VCL technology, a canvas is an abstraction encapsulating Windows API to render content on screen. In VCL, the standard canvas is implemented using GDI. It allows the developer to draw anything on screen. The class that encapsulate a canvas is names TCanvas. Every form has a TCanvas instance. You use it from the form’s OnPaint event handler. Similarly, all VCL component which are able to render something on screen has a TCanvas instance and a Paint method you can override to draw your own content.

TCanvas has method and properties to render many graphic primitives such as lines, rectangles, ellipses, polygons, bitmaps and text. There are properties such as Brush and Pen to select how you want something to be rendered. For example, a Pen is used to select the color, width and style of the rectangle outline while a Brush is use to select the color used to paint the rectangle’s interior.



Why would you like a Direct2D canvas?

Direct2D is a Microsoft DirectX technology especially designed for high performance 2D drawing. This is an API that provides Win32 / Win64 developers with the ability to perform 2D graphics rendering tasks with superior performance and visual quality.

Delphi is exactly a development platform for Win32 / Win64 application and has everything required to use almost any Windows API.

Delphi VCL has a TCanvas based on Direct2D API. It is not created by default but you can easily create it if you need it. But why would you need it?

I see two reasons:
    1. Speed
    2. More rendering features

The aim of this blog post is not to explain all the details of Direct2D. No, it is to show you how easy it is to start using it. Actually, my previous blog post already made use od it to display images. Now, I will show you how to benefit from powerful new Direct2D API: apply transformations to your drawings.


Direct2D transformation are geometric computation inserted between you call of a drawing primitive (For example a rectangle) and the actual rendering (The rectangle appears on screen).

 

The demo

The demo will show a simple transformation: a rotation. The demo has a for-loop which draw the same rectangle 24 times after applying a rotation transformation with an incrementing angle. The result visible on screen is 24 rectangles rotated 15°, drawing a nice picture.





How does it works?

Instead of showing the full code as is, I will explain the steps required to build the application from scratch. You’ll then be able to apply the steps to your own application.

1. Create a VCL form application

2. Add the units Vcl.Direct2D and Winapi.D2D1 to the uses clause.

3. Add the following code to the form’s declaration:

    private
       
FD2DCanvas : TDirect2DCanvas;
        function CreateD2DCanvas: Boolean;
    protected
        procedure CreateWnd; override;

 

 4. Implement CreateD2DCanvas method:

    function TMainForm.CreateD2DCanvas: Boolean;
    begin
        try
           
FD2DCanvas.Free;
           
FD2DCanvas    := TDirect2DCanvas.Create(Handle);
           
Result        := TRUE;
        except
           
Result        := FALSE;
        end;
    end;

5. Implement CreateWnd method:

    procedure TMainForm.CreateWnd;
    begin
        inherited;
        CreateD2DCanvas;
    end;

6. Add an OnResize event handler to your form:

    procedure TMainForm.FormResize(Sender: TObject);
    var
        Size: D2D1_SIZE_U;
    begin
        // When the windows is resized, we needs to resize RenderTarget as well
        Size := D2D1SizeU(ClientWidth, ClientHeight);
        ID2D1HwndRenderTarget(FD2DCanvas.RenderTarget).Resize(Size);
        Invalidate;
    end;

7. Add a OnPaint event handler to your form:

    procedure TMainForm.FormPaint(Sender: TObject);
    var
        Rect1 : D2D1_RECT_F;
       Angle : Single;
       I     : Integer;
    const
        RECT_SIZE  = 50;
        ANGLE_STEP = 15.0;
    begin
        FD2DCanvas.BeginDraw;
        try
            // Erase background
            FD2DCanvas.RenderTarget.Clear(D2D1ColorF(clDkGray));

            // Set pen color to draw rectangle outline
            FD2DCanvas.Pen.Color   := clYellow;

            // Clear all transformations

            FD2DCanvas.RenderTarget.SetTransform(TD2DMatrix3x2F.Identity);

            // Define rectangle to be drawn. Top left corner in center of window
            Rect1                  := Rect((ClientWidth  div 2),
                                           (ClientHeight div 2),
                                           (ClientWidth  div 2) + RECT_SIZE,
                                           (ClientHeight div 2) + RECT_SIZE);
            // Loop drawing the same rectangle but rotated step by step
            for I := 0 to Round(360.0 / ANGLE_STEP) do begin
                Angle := ANGLE_STEP * I;
                FD2DCanvas.RenderTarget.SetTransform(
                             TD2DMatrix3x2F.Rotation(Angle,
                                                     Rect1.Left,
                                                     Rect1.Top));
                FD2DCanvas.DrawRectangle(Rect1);
            end;
        finally
            FD2DCanvas.EndDraw;
        end;
    end;

8. Compile and run your application.

TDirect2DCanvas has almost the same methods and properties as the standard TCanvas. Porting code from standard TCanvas  to TDirect2DCanvas is very easy.

But TDirect2DCanvas is far from implementing all the features of Direct2D API. Fortunately, all the new features – such as transformations – are accessible very easily thru the property RenderTarget.
RenderTarget is an interface implemented in Direct2D DLL. When you call a method of RenderTarget, your are actually calling a Microsoft DLL!

In the demo code you see above, there are two calls to SetTransform. This is how we specify Direct2D to apply one or more transformation. Here we apply a simple rotation.

Transformations are described mathematically by a matrix of 3x2 floating point number. I will not enter the math details here. To help us, Microsoft has prebuilt several matrices for common transformations. Rotation is among them. In the call

     TD2DMatrix3x2F.Rotation(Angle, Rect1.Left, Rect1.Top);

We simply invoke a prebuilt matrix to rotate all subsequent drawings by the specified angle. The rotation take place around the point specified by the second and third arguments.

To cancel any transformation, just set a new transformation. If you want to transformation at all, you can use the matrix names “Identity” which is a kind of do-nothing. The code is:

    FD2DCanvas.RenderTarget.SetTransform(TD2DMatrix3x2F.Identity);

I invite your to see the online help for TDirect2DCanvas at http://docwiki.embarcadero.com/Libraries/Sydney/en/Vcl.Direct2D.TDirect2DCanvas

--
François Piette




August 6, 2020

Using Direct2D and GDI+

Direct2D is an API that provides Win32 developers with the ability to perform 2-D graphics rendering tasks with superior performance and visual quality.

Direct2D is a hardware-accelerated. That is it make use of the GPU whenever possible. This is what gives high performance and high-quality rendering for 2-D geometry, bitmaps, and text. But when a GPU is not available, Direct2D transparently fallback to software to replace the missing GPU functions.

The Direct2D API is designed to interoperate with existing code that uses GDI, GDI+, or Direct3D.

Applications that use Direct2D for graphics can deliver higher visual quality than what can be achieved using GDI. Direct2D uses per-primitive antialiasing to deliver smoother looking curves and lines in rendered content. There is also full support for transparency and alpha blending when rendering 2D primitives.

Windows GDI+ is the portion of the Windows operating system that provides two-dimensional vector graphics, imaging, and typography. GDI+ improves on Windows Graphics Device Interface (GDI) (the graphics device interface included with earlier versions of Windows) by adding new features and by optimizing existing features.

In this demo, we will use GDI+ for file I/O of image files and Direct2D for drawing the images on screen.

The demo is – as any good demo – a minimal application aimed at demonstrating how to read an image file (JPG, PNG, …) and show it on screen with zoom/pan/rotate and flip operation.


Demo code organization

For the purpose of this demo, the code is divided in two parts: a VCL component to expose Direct2D screen area and a form showing a user interface where an image file is shown and a few buttons to execute simple operations such as zoom, pan, rotate and flip.


TAcceleratedPaintPanel component

I created this component to have something similar to the standard TPaintBox but using a Direct2D canvas. I named this component TAcceleratedPaintPanel to avoid conflict with any existing component.

The component code is very simple since Delphi VCL already has a Direct2D canvas which hides most of the complexity. So, we simply start from a TPanel, activate his Direct2D canvas and expose an OnPaint method which can then be used on any form making use of TAcceleratedPaintPanel.


unit AcceleratedPaintPanel;


interface


uses

    Winapi.Messages, Winapi.D2D1,

    System.Classes,

    Vcl.Graphics, Vcl.ExtCtrls, Vcl.Direct2D;


type

    TCustomAcceleratedPaintPanel = class(TPanel)

    private

        FD2DCanvas             : TDirect2DCanvas;

        FPrevRenderTarget      : IntPtr;

        FOnPaint               : TNotifyEvent;

        FOnCreateRenderTarget  : TNotifyEvent;

        function  CreateD2DCanvas: Boolean;

        procedure WMEraseBkGnd(var Msg: TMessage); message WM_ERASEBKGND;

        procedure WMPaint(var Msg: TWMPaint); message WM_PAINT;

        procedure WMSize(var Msg: TWMSize); message WM_SIZE;

    protected

        procedure CreateWnd; override;

        function  GetRenderTarget : ID2D1HwndRenderTarget;

        procedure TriggerCreateRenderTarget; virtual;

    public

        destructor Destroy; override;

        procedure Paint; override;

        property D2DCanvas             : TDirect2DCanvas

                                                   read  FD2DCanvas;

        property RenderTarget          : ID2D1HwndRenderTarget

                                                   read  GetRenderTarget;

        property OnPaint               : TNotifyEvent

                                                   read  FOnPaint

                                                   write FOnPaint;

        property OnCreateRenderTarget  : TNotifyEvent

                                                   read  FOnCreateRenderTarget

                                                   write FOnCreateRenderTarget;

    end;


implementation


uses

    Windows, SysUtils, Controls;


destructor TCustomAcceleratedPaintPanel.Destroy;

begin

    FreeAndNil(FD2DCanvas);

    inherited Destroy;

end;


function TCustomAcceleratedPaintPanel.CreateD2DCanvas: Boolean;

begin

    try

        FD2DCanvas := TDirect2DCanvas.Create(Handle);

        Result     := TRUE;

    except

        Result     := FALSE;

    end;

    TriggerCreateRenderTarget;

end;


procedure TCustomAcceleratedPaintPanel.CreateWnd;

begin

    inherited;

    if (Win32MajorVersion < 6) or (Win32Platform <> VER_PLATFORM_WIN32_NT) then

        raise Exception.Create('Your Windows version do not support Direct2D');

    if not CreateD2DCanvas then

        raise Exception.Create('Unable to create Direct2D canvas');

end;


function TCustomAcceleratedPaintPanel.GetRenderTarget: ID2D1HwndRenderTarget;

begin

    if FD2DCanvas <> nil then begin

        Result := FD2DCanvas.RenderTarget as ID2D1HwndRenderTarget;

        if FPrevRenderTarget <> IntPtr(Result) then begin

            FPrevRenderTarget := IntPtr(Result);

            TriggerCreateRenderTarget;

        end;

    end

    else

        Result := nil;

end;


procedure TCustomAcceleratedPaintPanel.TriggerCreateRenderTarget;

begin

    if Assigned(FOnCreateRenderTarget) then

        FOnCreateRenderTarget(Self);

end;


procedure TCustomAcceleratedPaintPanel.Paint;

begin

    D2DCanvas.Font.Assign(Font);

    D2DCanvas.Brush.Color := Color;

    if csDesigning in ComponentState then begin

        D2DCanvas.Pen.Style   := psDash;

        D2DCanvas.Brush.Style := bsSolid;

        D2DCanvas.Rectangle(0, 0, Width, Height);

    end;

    if Assigned(FOnPaint) then

        FOnPaint(Self);

end;


procedure TCustomAcceleratedPaintPanel.WMEraseBkGnd(var Msg: TMessage);

begin

    Msg.Result := 1;

end;


procedure TCustomAcceleratedPaintPanel.WMPaint(var Msg: TWMPaint);

var

    PaintStruct: TPaintStruct;

begin

    BeginPaint(Handle, PaintStruct);

    try

        FD2DCanvas.BeginDraw;

        try

            Paint;

        finally

            FD2DCanvas.EndDraw;

        end;

    finally

        EndPaint(Handle, PaintStruct);

    end;

end;


procedure TCustomAcceleratedPaintPanel.WMSize(var Msg: TWMSize);

var

    Size: D2D1_SIZE_U;

begin

    if FD2DCanvas <> nil then begin

        Size := D2D1SizeU(Width, Height);

        ID2D1HwndRenderTarget(FD2DCanvas.RenderTarget).Resize(Size);

    end;

    inherited;

end;


end


TAcceleratedPaintPanel is the actual component. As always with Delphi, it is the “custom” version with properties and event published. This is the one registered in the component package.

In the source code for this article, you’ll find two packages: one run time and one design time. Before opening the demo project, you must compile both packages and install the design time package. If you open the demo project before installing the design time package, Delphi will complain about mission TAcceleratedPaintPanel component, asking if you want to cancel or ignore. Be sure to select “cancel” because if you select “ignore”, Delphi will remove the missing component from the form and obviously this will break the code!


Loading an image file with GDI+

It is obvious that to display an image file (A picture stored in a JPG or PNG file), the file must be read in memory and then transferred to video memory.

A JPG or PNG file is not a simple array of pixel: they are compressed with or without loss. GDI+ offer functions to read and decompress image files of various well-known formats.

In Delphi winapi implementation there is a class named TGPBitmap which handle reading and writing image file in all supported formats. Opening an image file is as simple as creating an instance of TGPBitmap, passing the file name as argument. When done, to close the file, just destroy the class instance.

This looks like this:

    GPBitmap := TGPBitmap.Create(FileName);

    try

        // Code making use of the image data, removed for

        // simplicity here.

    finally

       FreeAndNil(GPBitmap);

    end;


Preparing a Direct2D bitmap

Once the image file is opened by GDI+, we can ask to access to the uncompressed image data. On the fly, the GDI+ can change the pixel format to suit any need. Direct2D needs pixel in ARGB format, that is 4 bytes per pixel: the usual 3 bytes for red, green and blue components and a 4th byte for the so-called alpha-channel used for transparency.

A simple call to TGPBitmap.LockBits will do:

var

    BitmapBuf        : array of Byte;  

    BmpData          : TBitmapData;

begin

    SetLength(BitmapBuf, GPBitmap.GetHeight * GPBitmap.GetWidth * 4);

    if GPBitmap.LockBits(MakeRect(0, 0,

                                  Integer(GPBitmap.GetWidth),

                                  Integer(GPBitmap.GetHeight)),

                         ImageLockModeRead,

                         PixelFormat32bppARGB,

                         BmpData) <> TStatus.Ok then

        raise Exception.Create('GPBitmap.LockBits failed');

end;


Obviously we need a buffer to hold the pixel data. We use an array of byte for the purpose and we need 4 bytes per pixel (4 x width x height).

When calling LockBits, we have to pass 4 arguments. The first is the region of interest rectangle, which is the full bitmap here. The second is how we intend to use the data in memory, here just read the pixels. The third is the pixel format that we need, not necessarily the same as the pixel format inside the file. LockBits is smart enough to do the conversion for us. And finally, the fourth argument is the buffer where data will be loaded.

Now having the data in memory, we can tell Direct2D to take it into his own bitmap format:

var

    BitmapProp : TD2D1BitmapProperties;

begin

    BitmapProp.DpiX                  := 0;

    BitmapProp.DpiY                  := 0;

    BitmapProp.PixelFormat.Format    := DXGI_FORMAT_B8G8R8A8_UNORM;

    BitmapProp.PixelFormat.AlphaMode := D2D1_ALPHA_MODE_PREMULTIPLIED;


    RenderTarget.CreateBitmap(D2D1SizeU(GPBitmap.GetWidth,

                                        GPBitmap.GetHeight),

                              BmpData.Scan0,

                              BmpData.Stride,

                              BitmapProp,

                              Result);

end;


RenderTarget is an interface from Direct2D API. It is accessible thru Delphi VCL D2DCanvas.


Encapsulating LoadBitmap in a function

What we saw in the previous sections can be encapsulated in a single function taking the filename and producing the Direct2D bitmap we will use each time the UI needs to be redrawn.

It the code below, you’ll notice reference to “transform”. This will be explained later in this document.

function TDirect2DDemoMainForm.LoadBitmap(const FileName : String): Boolean;

var

    GPBitmap     : TGPBitmap;

begin

    try

        GPBitmap := TGPBitmap.Create(FileName);

        try

            if (GPBitmap.GetWidth = 0) or (GPBitmap.GetHeight = 0) then begin

                ShowMessage('Invalid file');

                Result := FALSE;

                Exit;

            end;


            FBitmapToPaint := CreateDirect2DBitmap(

                            AcceleratedPaintPanel1.D2DCanvas.RenderTarget,

                            GPBitmap);

            // Reset transforms to something the user expects

            FFlipHoriz    := FALSE;

            FFlipVert     := FALSE;

            FRotateFactor := 0.0;

            ZoomFull;

            PanCenter;

            ComputeTransform;


            // Calling Invalidate will force the component to redraw which

            // in turn will trigger OnPaint event and our paint handler which

            // draw the image on screen

            AcceleratedPaintPanel1.Invalidate;

            Result := TRUE;

        finally

            FreeAndNil(GPBitmap);

        end;

    except

        on E:Exception do begin

            ShowMessage(E.ClassName + ': ' + E.Message);

            Result := FALSE;

        end;

    end;

end;


Drawing the image

TAcceleratedPaintPanel has an OnPaint event which is triggered each time Windows request the repaint the window. The OnPaint event handler has to draw the image. This is done by calling a single method:

    AcceleratedPaintPanel1.RenderTarget.DrawBitmap(FBitmapToPaint);

RenderTarget is a Direct2D interface exposed by TAcceleratedPaintPanel. This interface has a lot of methods among which DrawBitmap that can draw a Direct2D bitmap on screen. When called with a single argument, the full bitmap is drawn as is on the window top left corner.


Direct2D transforms

Drawing a bitmap on the top left corner is not what we need in most application. We probably want to resize the bitmap so that it fits the window, maybe we want to move it left or right (panning), rotate it, flip it, zoom it. This is where Direct2D transforms comes in play.

A transform specifies how to map the points of an object from one coordinate space to another or from one position to another within the same coordinate space. This mapping is described by a transformation matrix, defined as a collection of three rows with three columns of floating-point values.

This may sound complex. Actually, it is not. We don’t really care about that matrix. It is enough to know how to initialize it to pan, zoom, rotate and flip the bitmap. To combine several transforms, we just have to multiply each individual transform involved. For example, to center a bitmap into the window, we use a scaling matrix (Zoom) and a translation matrix (panning).

Technically, a transform matrix is a record having nine single precision floating point and an operator overloading to multiply two matrices. The result is a matrix representing the two transforms.

There also some record helpers to easily create common transform matrices. For example, to create a transform matrix to zoom by a factor of 2, you write:

    Zoom := TD2DMatrix3x2F.Scale(D2D1SizeF(2.0, 2.0), D2D1PointF(0, 0));

The first argument is the zoom factor along width and height. The second argument is the zoom center.

There are similar helpers for the other transforms. Missing is the flip transform. Flipping around a horizontal or vertical axis is like looking a printed picture by transparency from the reverse side. The matrices are:

    const FlipH : D2D_MATRIX_3X2_F = (

      _11:-1.0; _12: 0.0;

      _21: 0.0; _22: 1.0;

      _31: 0.0; _32: 0.0;);

    const FlipV : D2D_MATRIX_3X2_F = (

      _11: 1.0; _12: 0.0;

      _21: 0.0; _22:-1.0;

      _31: 0.0; _32: 0.0;);


An important note: transform can be combined using the multiply operator but the order is important! Scaling * translating is not the same as translating * scaling.

In the demo application, I use a number of fields to specify the transform to be applied to the bitmap when displayed:

        FRotateFactor : Double;

        FZoomFactor   : Double;

        FPanLeft      : Integer;

        FPanTop       : Integer;

        FFlipHoriz    : Boolean;

        FFlipVert     : Boolean;


And one more field is used to store the result of all combined transforms:

       FTransform   : TD2DMatrix3X2F;

Each time one of the fields value is changed, a new combined transform is computed and then the paint panel is invalidated, forcing a redraw.

procedure TDirect2DDemoMainForm.ComputeTransform;

var

    Scaling      : TD2DMatrix3X2F;

    Translation  : TD2DMatrix3X2F;

    Rotation     : TD2DMatrix3X2F;

    FlippingH    : TD2DMatrix3X2F;

    FlippingHT   : TD2DMatrix3X2F;

    FlippingV    : TD2DMatrix3X2F;

    FlippingVT   : TD2DMatrix3X2F;

    Size         : TD2D1SizeU;

begin

    if not Assigned(FBitmapToPaint) then begin

        FTransform := TD2DMatrix3x2F.Identity;

        Exit;

    end;


    FBitmapToPaint.GetPixelSize(Size);


    if Abs(FZoomFactor - 1.0) <= 1E-5 then

        Scaling := TD2DMatrix3x2F.Identity

    else

        Scaling := TD2DMatrix3x2F.Scale(D2D1SizeF(FZoomFactor, FZoomFactor),

                                        D2D1PointF(0, 0));

    if Abs(FRotateFactor) <= 1E-5 then

        Rotation := TD2DMatrix3x2F.Identity

    else

        Rotation := TD2DMatrix3x2F.Rotation(FRotateFactor,

                                            Size.Width div 2,

                                            Size.Height div 2);


    Translation := TD2DMatrix3x2F.Translation(FPanLeft, FPanTop);


    if not FFlipHoriz then begin

        FlippingH     := TD2DMatrix3x2F.Identity;

        FlippingHT    := TD2DMatrix3x2F.Identity;

    end

    else begin

        FlippingH._11 := -1.0;   FlippingH._12 := 0.0;

        FlippingH._21 :=  0.0;   FlippingH._22 := 1.0;

        FlippingH._31 :=  0.0;   FlippingH._32 := 0.0;

        FlippingHT    := TD2DMatrix3x2F.Translation(-Size.width, 0);

    end;


    if not FFlipVert then begin

        FlippingV     := TD2DMatrix3x2F.Identity;

        FlippingVT    := TD2DMatrix3x2F.Identity;

    end

    else begin

        FlippingV._11 :=  1.0;   FlippingV._12 :=  0.0;

        FlippingV._21 :=  0.0;   FlippingV._22 := -1.0;

        FlippingV._31 :=  0.0;   FlippingV._32 :=  0.0;

        FlippingVT    := TD2DMatrix3x2F.Translation(0, -Size.Height);

    end;


    FTransform := FlippingVT * FlippingV *

                  FlippingHT * FlippingH *

                  Rotation   * Scaling   * Translation;

end;


Flipping are combined with a translation so that the flip axis remains in the middle of the bitmap.

Now that we have transforms, we can see the complete OnPaint event handler:

procedure TDirect2DDemoMainForm.AcceleratedPaintBox1Paint(Sender : TObject);

begin

    // Paint background

    AcceleratedPaintPanel1.RenderTarget.Clear(D2D1ColorF(clSilver));

    // Paint bitmap, if any

    if FBitmapToPaint <> nil then begin

        AcceleratedPaintPanel1.RenderTarget.SetTransform(FTransform);

        AcceleratedPaintPanel1.RenderTarget.DrawBitmap(FBitmapToPaint);

    end;

end;

 


Source code

Full source code is available on Github. It is subject to Mozilla Public License V2.0.

https://github.com/fpiette/Direct2DDemo

François Piette

Embarcadero MVP