How do you draw an image in a rotated rectangle in FireMonkey?

In FireMonkey, just draw a bitmap in the source rectangle:

Canvas.DrawBitmap(FBitmap, ImageSrcRect, ImageDstRect, 1); 

And I do it on TPaintBox canvas. Instead, I would like to draw a bitmap that is rotated (and scaled, since the destination size may not be the same as the source size.)

In particular:

  • I have two points.
  • The image should be located below the center point between these two points.
  • The image must rotate to follow the angle between the two points.

as in this image:

Desired rotation

One of the left is what I can do now; on the right is what I would like to do.

What is the best way to do this?

What i tried

To keep the existing code simple (for example, by drawing into the destination rectangle, thereby scaling the result), I tried to add a rotation matrix to the canvas matrix before calling the existing DrawBitmap code. For instance,

 OldMatrix := Canvas.Matrix; // Original, to restore W := PointB.X - PointA.X; H := PointA.Y - PointB.Y; RotationMatrix := TMatrix.CreateRotation(-ArcTan2(H, W)); Canvas.SetMatrix(OldMatrix * RotationMatrix); Canvas.DrawBitmap(FImage, ImageSrcRect, ImageDstRect, 1); Canvas.SetMatrix(OldMatrix); 

and several variations multiplied by the existing matrix, creating a completely new matrix with translation and rotation, etc. All this partially works: the rotation angle is correct, but I have a lot of problems getting the position in order to remain consistent - for example, rotating around the center point (and this does not even allow the upper part of the bitmap to rotate around the point, rather than rotating around the center.) I found that the rotated image is shifted perfectly in the lower right quadrant, but in the other three it is shifted / translated incorrectly, for example, too far to the left or cropped to the leftmost or highest position X or Y of two points. I do not know why this is so, and at this moment I ask for help.

More details

  • Delphi 10 Seattle
  • FireMonkey (on Windows)
  • The target is a TPaintBox canvas arbitrarily placed. The TScaledLayout can itself be on a TScaledLayout .
  • The goal is to draw a bitmap in the rotary target rectangle in the paintbox.
+5
source share
1 answer

As far as I understand, the main problem is to find the coordinates of the image angle in the new rotated coordinate system. This can be solved as follows:

 procedure DrawRotatedBitmap(const Canvas : TCanvas; const Bitmap : TBitmap; const PointA, PointB : TPointF; const Offset : TPointF; const Scale : Single); var OldMatrix, TranslationAlongLineMatrix, RotationMatrix, TranslationMatrix, ScaleMatrix, FinalMatrix: TMatrix; W, H : Single; SrcRect, DestRect: TRectF; Corner: TPointF; LineLength : Single; LineAngleDeg : Integer; begin OldMatrix := Canvas.Matrix; // Original, to restore try {$ifdef DRAW_HELPERS} Canvas.Fill.Color := TAlphaColorRec.Black; Canvas.DrawLine(PointA, PointB, 0.5); {$endif} W := PointB.X - PointA.X; H := PointA.Y - PointB.Y; LineLength := abs(PointA.Distance(PointB)); // Looking for the middle of the task line // and the coordinates of the image left upper angle // solving the proportion width/linelength=xo/0.5requireddimensions Corner := TPointF.Create((PointB.X + PointA.X) / 2, (PointA.Y + PointB.Y) / 2);// Middle {$ifdef DRAW_HELPERS} Canvas.Stroke.Color := TAlphaColorRec.Red; Canvas.DrawEllipse(TRectF.Create(Corner,2,2),1); {$endif} Corner.X := Corner.X - Bitmap.Width / 2 * W / LineLength; Corner.Y := Corner.Y + Bitmap.Width / 2 * H / LineLength; {$ifdef DRAW_HELPERS} Canvas.Stroke.Color := TAlphaColorRec.Green; Canvas.DrawEllipse(TRectF.Create(Corner,2,2),1); {$endif} // Account for scale (if the FMX control is scaled / zoomed); translation // (the control may not be located at (0, 0) in its parent form, so its canvas // is offset) and rotation ScaleMatrix := TMatrix.CreateScaling(Scale, Scale); TranslationMatrix := TMatrix.CreateTranslation(Offset.X, Offset.Y); RotationMatrix := TMatrix.CreateRotation(-ArcTan2(H, W)); TranslationAlongLineMatrix := TMatrix.CreateTranslation(Corner.X, Corner.Y); FinalMatrix := ((RotationMatrix * ScaleMatrix) * TranslationMatrix) * TranslationAlongLineMatrix; // If in the top left or top right quadrants, the image will appear // upside down. So, rotate the image 180 degrees // This is useful when the image contains text, ie is an annotation or similar, // or needs to always appear "under" the line LineAngleDeg := Round(RadToDeg(-Arctan2(H, W))); case LineAngleDeg of -180..-90, 90..180 : FinalMatrix := TMatrix.CreateRotation(DegToRad(180)) * TMatrix.CreateTranslation(Bitmap.Width, 0) * FinalMatrix; end; Canvas.SetMatrix(FinalMatrix); // And finally draw the bitmap DestRect := TRectF.Create(PointF(0, 0), Bitmap.Width, Bitmap.Height); SrcRect := TRectF.Create(0, 0, Bitmap.Width, Bitmap.Height); {$ifdef DRAW_HELPERS} Canvas.DrawBitmap(Bitmap, SrcRect, DestRect, 0.5); {$else} Canvas.DrawBitmap(Bitmap, SrcRect, DestRect, 1); {$endif} finally // Restore the original matrix Canvas.SetMatrix(OldMatrix); end; end; 

There is an ifdef — these are pictures of lines and points that can help you — they draw a line and some useful points (the linear center and upper left corner of the image) that are useful for debugging.

Edit DavidM: In addition, there are also translation and scaling matrices. The lightbox relies on the canvas of the parent form (ultimately), but may not be located on (0, 0), so you need to consider the position of the offset canvas destination. In addition, the control can be scaled, so it also needs to be embedded in the final rotated matrix.

This code is heavily edited and works regardless of the orientation / quadrant that is in . That is, it should work when the line is completely horizontal or vertical, as well as in quadrants, except for the lower right.

One interesting tweak is recognizing that the bitmap in the example contains text. When the line is in the upper left or upper right quadrants, that is, rising up, and then either to the left or to the right of its beginning, the raster image is displayed upside down for the human eye. The twist recognizes this and rotates the bitmap so that the "top" of the bitmap is always facing the line, and the "bottom" of the bitmap is usually pointing down, making the image look right. You can remove this setting if you do not need an image that represents something recognizable (for example, a symbol, text, label, etc.).

Artwork

With different angles and scaling.

enter image description here enter image description here

+4
source

Source: https://habr.com/ru/post/1239741/


All Articles