I need to draw angled text on TDirect2DCanvas, but no success.
procedure TForm1.FormPaint(Sender: TObject);
var
LCanvas: TDirect2DCanvas;
const
myText = 'Kikimor';
begin
LCanvas := TDirect2DCanvas.Create(Canvas, ClientRect);
LCanvas.BeginDraw;
try
LCanvas.Font.Orientation := 90;
LCanvas.TextOut(100,100,myText);
finally
LCanvas.EndDraw;
LCanvas.Free;
end;
end;
No matter what angle I give for orientation, it always draws a straight text.
Is orientation not working or I need to do something else?
Setting TDirect2DCanvas.Font.Orientation does not have any effect (most likely not implemented, sorry, no time to debug). Direct2D wrapper supplied in Delphi is very basic.
To achieve your goal, apply transformation by hand:
procedure TForm1.FormPaint(Sender: TObject);
var
LCanvas: TDirect2DCanvas;
currentTransform: TD2D1Matrix3x2F;
ptf: TD2DPoint2f;
const
myText = 'Kikimor';
begin
LCanvas := TDirect2DCanvas.Create(self.Canvas, ClientRect);
LCanvas.BeginDraw;
try
// backup the current transformation
LCanvas.RenderTarget.GetTransform(currentTransform);
ptf.x:= 100.0; ptf.y:= 100.0; //rotation center point
// apply transformation to rotate text at 90 degrees:
LCanvas.RenderTarget.SetTransform(TD2D1Matrix3x2F.Rotation(90, ptf));
// draw the text (rotated)
LCanvas.TextOut(100, 100, myText);
// restore the original transform
LCanvas.RenderTarget.SetTransform(currentTransform);
finally
LCanvas.EndDraw;
LCanvas.Free;
end;
end;
For more extensive information/effects you can look at:
Drawing text using the IDWriteTextLayout.Draw()
The whole Direct2D category at the same site is also interesting (use Google Translate).
For those using C++ Builder I got this to work:
#include <Vcl.Direct2D.hpp>
// needed for the D2D1::Matrix3x2F::Rotation transform
#ifdef _WIN64
#pragma comment(lib,"D2D1.a")
#else
#pragma comment(lib,"D2D1.lib")
#endif
TD2DPoint2f point; // rotation centre
point.x = 100.0;
point.y = 100.0;
canvas_2d->RenderTarget->SetTransform(D2D1::Matrix3x2F::Rotation(90, point));
canvas_2d->TextOut(100, 100, text);
// restore 0 rotation afterwards
canvas_2d->RenderTarget->SetTransform(D2D1::Matrix3x2F::Rotation(0, point));
Note that trying to use GetTransform like in the Delphi version causes an exception, so I cleared the transform by passing it a new one with zero rotation, there may be a better way to do this.
The pragma is needed due to a link error, see this answer for details.
Related
I am dynamically creating a number of TBitBtn with custom bitmaps. Works nice, except if the screen is high res - which causes positioning and size to change. The other controls on the form are not affected.
Do not know what to try.
BitBbegersopp:= TbitBtn.Create(Form2);
with BitBbegersopp do
begin
Parent:=Form2;
Glyph.LoadFromFile('beger.bmp');
OnClick:= BitBbegersoppClick;
Left:= Start.Left + HDistStartB + 0*HSpacingBitB;
Height:= HSizeBitB;
Width:= VSizeBitB;
Top:= Start.Top + VDistStartB + 0*VSpacingBitB;
Hint:= 'Begersopp, sporer på oversiden';
ShowHint:= True;
Tag:= 1;
end;
Although you didn't provide an example, which leaves us with a lot of guessing, I can see two problems in your code.
The scaling to the current PPI is done inside the assignment to Parent.
In case Start is an existing control, its Left and Top properties are already scaled while the offsets used as well as the values for Width and Height are probably not.
To tackle both problems I suggest the following code sequence:
BitBbegersopp:= TbitBtn.Create(Form2);
with BitBbegersopp do
begin
Glyph.LoadFromFile('beger.bmp');
OnClick:= BitBbegersoppClick;
{ use unscaled values }
Left:= HDistStartB + 0*HSpacingBitB;
Height:= HSizeBitB;
Width:= VSizeBitB;
Top:= VDistStartB + 0*VSpacingBitB;
{ this will scale the control }
Parent:=Form2;
{ Now uses scaled values }
Left:= Start.Left + Left;
Top:= Start.Top + Top;
Hint:= 'Begersopp, sporer på oversiden';
ShowHint:= True;
Tag:= 1;
end;
BTW, please avoid with!
I'm writing 2d mini-games for another program I'm working on.
Problem is that I have very limited knowledge on coding that works with colliding objects.
For Example: how to test if 2 spheres collide like in Agar.io
or how the blocks from tetris detect that they touch each other,
or how Snake detects that the snake bit itself/food (without looking if the epicenters/coordinates of the middle of the objects are equal).
I'm using Delphi 7.
Collision testing is easy.
If you want to test if two circles collide check the distance between their two center points:
In a plane the distance between to points is calculated with the Pythagorean theorem sqrt((x2-x1)^2+(y2-y1)^2).
if (sqrt((x2-x1)^2+(y2-y1)^2)) > (Radius(Circle1)+Radius(Circle2)) then
//x1,y1 = center point of circle1
//x2,y2 = center point of circle2
//or more efficiently:
a:= (x2-x1)^2+(y2-y1)^2
b:= (Radius(Circle1)+Radius(Circle2))^2;
if a > b then
If you want to check if two boxes collide there's a standard RTL routine for that.
if IntersectRect(Rect1,Rect2) then ....
As far as the snake goes, the thing that you're describing:
if the epicenters/coordinates of the middle of the objects are equal
Is exactly how it can be done, but a faster alternative is to use a discrete grid with integer coordinates and call a collision when two parts of the snake are on the same cell.
type
// The grid is initially empty except for a border around the edges
TBodyPart = (bpNone, bpBorder, bpTail, bpBody, bpHead);
TSnakeGrid = array[0..20,0..20] of TBodyPart;
TSnake = class(TObject)
private
SnakeLength: integer;
Grid: TSnakeGrid;
....
function IsCollision: boolean;
function TSnake.IsCollision: boolean;
begin
Result:= Grid[Head.x, Head,y] <> bpEmpty;
end;
procedure TSnake.MoveSnake(Direction: TDirection);
begin
//Move the head
Grid[Head.x, Head.y]:= bpBody;
Inc(SnakeLength);
case Direction of
north: Dec(Head.y);
south:= Inc(Head.y);
west:= Dec(Head.x);
east: Inc(Head.x);
end; {case}
if Grid[Head.x, Head.y] <> bpEmpty then Grid[Head.x,Head,y]:= bpHead
else GameOver;
end;
Google for "collision detection delphi vcl" and you will find lots of code.
A classic Snake and Tetris games usually works on a grid, so a 2D array can hold all elements. For collisions you can then simply look-up if there are objects in the 2D array at a given grid position.
For detecting sphere overlap, you need some geometry to determine if the distance between the centers of the spheres is smaller than both radius combined, see more on the theory here
sphere1x = 100;
sphere1y = 200;
sphere1r = 5;
sphere2x = 105;
sphere2y = 200;
sphere2r = 10;
deltax = sphere1x - sphere2x;
deltay = sphere1y - sphere2y;
dist = (deltax * deltax) + (deltay * deltay);
rad2 = (sphere1r * sphere1r) + (sphere2r * sphere2r);
// for the actual distance you'd have to square root both dist and rad2,
// but we just want to compare which is bigger, so this is skipped for optimisation
if (dist < rad2) then
begin
// sphere collision
end;
I want to convert the workspace coordinates returned by GetWindowPlacement in rcNormalPosition.Left and rcNormalPosition.Top to screen coordinates that I can assign later to MainForm.Left and MainForm.Top. How can I do that ?
You can use the monitor property of your form to determine if the workspace of the monitor that the form is on has got any offset with the monitor's placement. E.g.
ScreenLeft := wplc.rcNormalPosition.Left +
Monitor.WorkareaRect.Left - Monitor.Left;
ScreenTop := wplc.rcNormalPosition.Top +
Monitor.WorkareaRect.Top - Monitor.Top;
The simplest and cleanest way is to use the API function that partners with GetWindowPlacement, namely SetWindowPlacement. That way you don't need to convert between workspace and screen coordinates because you let the system do the work for you.
var
WindowPlacement: TWindowPlacement;
....
WindowPlacement.length := SizeOf(WindowPlacement);
Win32Check(GetWindowPlacement(Handle, WindowPlacement));
....
Win32Check(SetWindowPlacement(Handle, WindowPlacement));
In the above code, Handle is assumed to be the window handle of the form.
If you have persisted the left and top then you'd restore them like this:
var
WindowPlacement: TWindowPlacement;
....
WindowPlacement.length := SizeOf(WindowPlacement);
Win32Check(GetWindowPlacement(Handle, WindowPlacement));
WindowPlacement.rcNormalPosition.Left := NewLeft;
WindowPlacement.rcNormalPosition.Top := NewTop;
Win32Check(SetWindowPlacement(Handle, WindowPlacement));
How can I make something like this in Delphi:
I know I can make it from 3 tables so it would be easier, but how can I make Table cells split & merge and how to get the text to turn 90deg.?
Is there some good content libraries that have split & merge built in?
Check out woll2woll or infopower. They will do the grid for sure. The font can be achieved by overriding the OnDrawDataCell, OnDrawGroupHeaderCell and OnDrawTitleCell events and writing the text with rotated font.
{****************************************************************
* Create angled font. Procedure writen by Keith Wood *
****************************************************************}
procedure CreateAngledFont (AFont : TFont; const AAngle : Integer);
var
FntLogRec: TLogFont { Storage area for font information } ;
begin
{ Get the current font information. We only want to modify the angle }
fillchar (FntLogRec, sizeof(FntLogRec), #0);
GetObject (AFont.Handle, SizeOf(FntLogRec), Addr(FntLogRec));
{ Modify the angle. "The angle, in tenths of a degrees, between the base
line of a character and the x-axis." (Windows API Help file.) }
FntLogRec.lfEscapement := (AAngle * 10);
FntLogRec.lfOrientation := (AAngle * 10);
FntLogRec.lfOutPrecision := OUT_TT_PRECIS; { Request TrueType precision }
{ Delphi will handle the deallocation of the old font handle }
AFont.Handle := CreateFontIndirect (FntLogRec);
end;
I'm working on graphics32. And I'm using the given component from its examples, the TRotLayer. Basing on the example (Examples/Layers/RotLayer_Ex), the created RotLayer only scales together with the ImgView. If ImgView.Bitmap is not assigned, the RotLayer doesn't scale. So I tinkered the source code, changing it's behavior. I changed the TRotLayer.AdjustTransformation procedure. Here's what I did.
procedure TRotLayer.AdjustTransformation;
var
ScaleX, ScaleY,
ShiftX, ShiftY: Single;
begin
Transformation.Clear;
Transformation.Translate(-BitmapCenter.X, -BitmapCenter.Y);
Transformation.Rotate(0, 0, Angle);
Transformation.Translate(Position.X, Position.Y);
Transformation.Scale(Scale.X, Scale.Y);
Transformation.Translate(Shift.X, Shift.Y);
// if Scaled and Assigned(LayerCollection) then
// with LayerCollection do
// begin
// GetViewportScale(ScaleX, ScaleY);
// GetViewportShift(ShiftX, ShiftY);
// Transformation.Scale(ScaleX, ScaleY);
// Transformation.Translate(ShiftX, ShiftY);
// end;
end;
I just ommitted the restriction and simply executed the .Scale procedure. I passed values for Scale.X and Scale.Y and it worked as I expected. The image was resized but now my problem is the positioning. The image moves up or down if I scale it's height then it moves right or left if I scale it's width. I just wanted it to resize and just stay in it's original position. I noticed that the function .Translate can possibly fix my problem but I don't know what to pass in the parameters. Or I don't know how to compute the values to pass.
Anyone can help me with this problem. Thanks.
Pseudo code:
Calculate the original bounds:
Transformation.SrcRect := FloatRect(0, 0, Old.Width, Old.Height);
Shift the origin to the center of that bounds:
Transformation.Translate(-0.5 * Old.Width, -0.5 * Old.Height);
Rotate around the new origin:
Transformation.Rotate(0, 0, Degrees);
Scale
Calculate the new bounds:
New.Bounds := Transformation.GetTransformedBounds;
Shift the origin back to (0, 0) of the new bounds:
Transformation.Translate(0.5 * New.Width, 0.5 * New.Height);
You might also take a look at Rotate bitmap by real angle for a Graphics32 example (without scaling).