1

I'm loading an image into a TPaintBox, then try to rotate it with SetWorldTransform as suggested in this answer:

The picture loads fine via the TPaintBox.Invalidate -> TPaintBox.OnPaint.
But when I click the test button BtnRotateWorldTransform, I see the image flicker but no rotation.

procedure TFrmRotateImage.FormCreate(Sender: TObject);
begin
  FWICImage := TWICImage.Create; 
end;

procedure TFrmRotateImage.BtnLoadPaintBoxClick(Sender: TObject);
begin
   if DlgOpen.Execute then  // PNG or JPG file
   begin
      try
         FWICImage.LoadFromFile(DlgOpen.FileName);
         FBoxLoaded := true;
         PaintBox.Invalidate;
      finally
      end;
   end;
end;

procedure TFrmRotateImage.PaintBoxPaint(Sender: TObject);  // OnPaint handler
begin
   if not FBoxLoaded then Exit;
   PaintBox.Canvas.Draw(0,0,FWICImage);
end;

procedure TFrmRotateImage.BtnRotateWorldTransformClick(Sender: TObject);  // Test button
var
   lRect: TRect;
begin
   lRect.Top    := PaintBox.Top;
   lRect.Left   := PaintBox.Left;
   lRect.Width  := PaintBox.Width;
   lRect.Height := PaintBox.Height;
   StretchDrawRotated(PaintBox.Canvas,lRect,90,lRect.CenterPoint,FWICImage);
   PaintBox.Invalidate;
end;

with

procedure XForm_SetRotation(out AXForm: TXForm; AAngle: Extended; ACenter: TPoint);
var
  SinA, CosA: Extended;
begin
  SinCos(AAngle, SinA, CosA);
  AXForm.eM11 := CosA;
  AXForm.eM12 := SinA;
  AXForm.eM21 := -SinA;
  AXForm.eM22 := CosA;
  AXForm.eDx := (ACenter.X - (CosA * ACenter.X)) + ((SinA * ACenter.Y));
  AXForm.eDy := (ACenter.Y - (SinA * ACenter.X)) - ((CosA * ACenter.Y));
end;

procedure StretchDrawRotated(ACanvas: TCanvas; const ARect: TRect; AAngle: Extended; ACenter: TPoint; AGraphic: TGraphic);
var
  XForm, XFormOld: TXForm;
  GMode: Integer;
begin
  GMode := SetGraphicsMode(ACanvas.Handle, GM_ADVANCED);
  try
    if GetWorldTransform(ACanvas.Handle, XFormOld) then
      try
        XForm_SetRotation(XForm, AAngle, ACenter);
        SetWorldTransform(ACanvas.Handle, XForm);
        ACanvas.StretchDraw(ARect, AGraphic);
      finally
        SetWorldTransform(ACanvas.Handle, XFormOld);
      end;
  finally
    SetGraphicsMode(ACanvas.Handle, GMode);
  end;
end;

What am I forgetting?

Jan Doggen
  • 8,799
  • 13
  • 70
  • 144
  • 1
    I would call `StretchDrawRotated()` inside `OnPaint()`. – Olivier Sep 03 '20 at 19:24
  • Remove PaintBox.Invalidate from the button handler and you'll see the transform. Of course this is not correct solution, but demonstrates that the problem is a misunderstanding of how painting works. – Sertac Akyuz Sep 03 '20 at 19:32
  • @SertacAkyuz Sorry, does not help – Jan Doggen Sep 03 '20 at 20:17
  • @Olivier Tried that together with Sertacs suggestion of hiding Invalidate. Sorry, no. – Jan Doggen Sep 03 '20 at 20:20
  • @Jan of course I mean to remove the invalidate in BtnRotateWorldTransform click, not the one in the loader. I tested and it happens as I commented. – Sertac Akyuz Sep 03 '20 at 20:21
  • @SertacAkyuz Yes, that one – Jan Doggen Sep 03 '20 at 20:23
  • I suggest to restart the project from the information on this page only - that's what I did. You should see the transformed image on top of the original one. From there, the correct solution is quite easy. – Sertac Akyuz Sep 03 '20 at 20:26
  • You can use a Canvas2D to draw the image with any transformation (Rotation, scaling, translation, skew, mirror and other). Several transformations can be combined. I wrote two articles http://francois-piette.blogspot.com/2020/08/direct2d-canvas-for-delphi-forms.html and http://francois-piette.blogspot.com/2020/08/using-direct2d-and-gdi.html. The latest uses GDI+ to load the image and if you prefer you can replace GDI+ by TWICImage. – fpiette Sep 04 '20 at 07:06

1 Answers1

2

I wrote a complete example. In this example, I show how to rotate, translate, scale an image and combine those transformations.

For the purpose, I wrote 3 functions to prepare the rotation, scaling and translation easily.

In the PaintBox1Paint event handler, I take fixed values for the rotation, scaling and translation. Of course, in a normal application this comes from elsewhere (UI for example). Transformation computation should be outside of the PaintBox1Paint procedure and done each time the parameters varies. Then Invalidate should be called so that it is repainted with the new computed transformation.

procedure TForm4.FormCreate(Sender: TObject);
begin
    FWICImage := TWICImage.Create;
end;

procedure TForm4.FormDestroy(Sender: TObject);
begin
    FreeAndNil(FWICImage);
end;

procedure TForm4.LoadImageButtonClick(Sender: TObject);
begin
    FWICImage.LoadFromFile('C:\Users\fpiette\Pictures\Delphi25 ICS.jpg');
    FBoxLoaded := TRUE;
    PaintBox1.Invalidate;
end;

function XForm_SetRotation(
    AAngle     : Single;
    ACenter    : TPoint) : TXForm;
var
    SinA, CosA: Extended;
begin
    SinCos(AAngle, SinA, CosA);
    Result.eM11 := CosA;
    Result.eM12 := SinA;
    Result.eM21 := -SinA;
    Result.eM22 := CosA;
    Result.eDx := (ACenter.X - (CosA * ACenter.X)) + ((SinA * ACenter.Y));
    Result.eDy := (ACenter.Y - (SinA * ACenter.X)) - ((CosA * ACenter.Y));
end;

function XForm_SetScale(
    const AZoomX : Single;
    const AZoomY : Single;
    const center: TPoint) : TXForm;
begin
    Result.eM11 := AZoomX;
    Result.eM12 := 0.0;
    Result.eM21 := 0.0;
    Result.eM22 := AZoomY;
    Result.eDx  := center.x - AZoomX * center.x;
    Result.eDy  := center.y - AZoomY * center.y;
end;

function XForm_SetTranslate(
    const ADistX : Integer;
    const ADistY : Integer) : TXForm;
begin
    Result.eM11 := 1.0;
    Result.eM12 := 0.0;
    Result.eM21 := 0.0;
    Result.eM22 := 1.0;
    Result.eDx  := ADistX;
    Result.eDy  := ADistY;
end;

procedure TForm4.PaintBox1Paint(Sender: TObject);
var
    XFormScale  : TXForm;
    XFormRot    : TXForm;
    XFormXLat   : TXForm;
    XForm       : TXForm;
    XFormOld    : TXForm;
    GMode       : Integer;
    AZoomFactor : Single;
    AAngle      : Single;
    ADistX      : Integer;
    ADistY      : Integer;
    ACanvas     : TCanvas;
begin
    if not FBoxLoaded then
        Exit;

    AZoomFactor := 0.25;
    AAngle      := 30.0;
    ADistX      := 100;
    ADistY      := 200;
    ACanvas     := PaintBox1.Canvas;

    GMode := SetGraphicsMode(ACanvas.Handle, GM_ADVANCED);
    try
        if GetWorldTransform(ACanvas.Handle, XFormOld) then begin
            try
                XFormRot   := XForm_SetRotation(
                                  AAngle,
                                  Point(FWICImage.Width div 2,
                                        FWICImage.Height div 2));
                XFormScale := XForm_SetScale(
                                  AZoomFactor, AZoomFactor, Point(0, 0));
                XFormXLat  := XForm_SetTranslate(ADistX, ADistY);
                CombineTransform(XForm, XFormRot, XFormScale);
                CombineTransform(XForm, XForm,    XFormXLat);
                SetWorldTransform(ACanvas.Handle, XForm);
                ACanvas.Draw(0, 0, FWICImage);
            finally
                SetWorldTransform(ACanvas.Handle, XFormOld);
            end;
        end;
    finally
        SetGraphicsMode(ACanvas.Handle, GMode);
    end;
end;
fpiette
  • 11,983
  • 1
  • 24
  • 46
  • Thanks very much, Francois. I consider myself a beginner in graphics programming, with this working code I can start fiddling to see what it all does. – Jan Doggen Sep 07 '20 at 17:45