delphivcldirect2ddirectwritetimagelist

Drawing a TImageList glyph to a TDirect2DCanvas


I'm currently about to replace the drawing code for an old component from GDI + UniScribe to Direct2D and DirectWrite (the successors).

So far the transition was straight forward as most of the time all I need to do was to replace calls to the Canvas (class TCanvas) to a custom FDirect2DCanvas instance (class TDirect2DCanvas, from the unit Direct2D).

Unfortunately it doesn't seem that simple when trying to draw a glyph from a TImageList instance onto the FDirect2DCanvas as the draw method is only meant for TCanvas and not for the rather general TCustomCanvas (which is the ancestor of both TCanvas and TDirect2DCanvas).

A solution for this dilemma would be to draw the TImageList glyph to a temporary bitmap and draw this to the TDirect2DCanvas. However, I fear this will probably slow down the drawing performance a lot.

Has anyone so far done this so far? What options do I have?


Solution

  • If you look at how drawing graphic objects to TDirect2DCanvas is implemented you will find that it routes through this routine.

    procedure TDirect2DCanvas.StretchDraw(const Rect: TRect; Graphic: TGraphic;
      Opacity: Byte);
    var
      D2DBitmap:  ID2D1Bitmap;
      D2DRect: TD2DRectF;
      Bitmap: TBitmap;
    begin
      Bitmap := TBitmap.Create;
      try
        Bitmap.Assign(Graphic);
    
        D2DBitmap := CreateBitmap(Bitmap);
    
        D2DRect.Left   := Rect.Left;
        D2DRect.Right  := Rect.Right;
        D2DRect.Top    := Rect.Top;
        D2DRect.Bottom := Rect.Bottom;
        RenderTarget.DrawBitmap(D2DBitmap, @D2DRect, Opacity/255);
      finally
        Bitmap.Free;
      end;
    end;
    

    Let's unpick the steps involved:

    1. Create a temporary bitmap.
    2. Copy the graphic into that bitmap.
    3. Create a ID2D1Bitmap and copy the temporary bitmap into it.
    4. Draw that ID2D1Bitmap onto the render target.

    This already looks pretty inefficient. Certainly it would be galling to call this function passing in a TBitmap and have a copy made for no good reason.

    This sort of thing is hard to avoid though when you try to blend two distinct graphics frameworks. Your image list is GDI based, and so bound to encounter friction when you try to send it to a Direct2D canvas. There simply is no way to pass GDI bitmaps directly to a Direct2D canvas, they have to be converted to Direct2D bitmaps first.

    If performance is what matters to you then you should not be starting from an image list. That will inevitably incur costs as you extract the bitmap from the GDI image list, and then convert it into the equivalent Direct2D object, ID2D1Bitmap.

    In order to achieve optimal performance, don't work with image lists. Extract each image from the image list and use TDirect2DCanvas.CreateBitmap to obtain a Direct2D bitmap, ID2D1Bitmap. Store these rather than the image list. Then when you need to draw, call DrawBitmap on RenderTarget, passing a ID2D1Bitmap.