Shape component rotation - delphi

I need to rotate one Shape component (ellipse) around the other one (circle). It's seems to me that it's better to do it with polar coordinates. So the rotation formula is:
X := Round(CenterX + SIN(Angle) * Radius);
Y := Round(CenterY + COS(Angle) * Radius);
where X, Y - ellipse coordinates, Radius - rotation radius; Angle is rotation angle;
CenterX, CenterY - center of rotation.
Also I got that in Timer component I must write the following code:
Angle := Angle + 0.01;
if Angle> 2*Pi then Angle := Angle - 2*Pi;
And Shape must be redrawn.
It would look like this:
But I can't gather all in a bunch. I don't know how to organize this all. Thanx for any help.

Add a variable t: double to your form class, and do
procedure TForm1.Timer1Timer(Sender: TObject);
var
cx, cy: integer;
x, y: integer;
const
r = 200;
begin
cx := Shape1.Left + Shape1.Width div 2;
cy := Shape1.Top + Shape1.Height div 2;
x := cx + round(r*sin(t));
y := cy + round(r*cos(t));
Shape2.Left := x - Shape2.Width div 2;
Shape2.Top := y - Shape2.Height div 2;
t := t + 0.01;
end;
where Timer1.Interval = 30, say.
Personally, however, I really dislike when people do animations by moving VCL controls around. It is much better to resort to manual GDI (or even OpenGL) drawing.

Related

How to draw a projectile following specific XY coordinates on a given bitmp under Delphi

In fact i want to draw an animated line from a given Tpoint coordinates to an other Tpoint on a Bitmap canvas for that i tried to read some open sources of OpenGl but task seems to be hard for my little competences in delphi i found this C++ source on github and its exactly what i want to do but under delphi 2009 if the line is animated it could be better for this personnal game prototype.
i tried some functions like -LineDDA- but its is very limited.
i look for somthing like this:
function Make_Projectile( canvas:tcanvas;Start,Target:Tpoint):boolean;
with canvas do
draw the animated_arc_line form start to trget;
if we toutch target then result= true else false...
end;
I tried this piece of code:
procedure ArcByStartEndAngle(Canvas:TCanvas;P0, P1: TPoint; Angle: Double; NSeg: Integer);
var
i: Integer;
len, dx, dy, mx, my, px, py, t, cx, cy, p0x, p0y, an: Double;
xx, yy: Integer;
begin
mx := (P0.x + P1.x) / 2;
my := (P0.y + P1.y) / 2;
dx := (P1.x - P0.x) / 2;
dy := (P1.y - P0.y) / 2;
len := Math.Hypot(dx, dy);
px := -dy / len;
py := dx / len;
if Angle = Pi then
t := 0
else
t := len / Math.Tan(Angle / 2);
cx := mx + px * t;
cy := my + py * t;
p0x := P0.x - cx;
p0y := P0.y - cy;
for i := 0 to NSeg + 1 do
begin
an := i * Angle / (NSeg + 1);
xx := Round(cx + p0x * Cos(an) - p0y * Sin(an));
yy := Round(cy + p0x * Sin(an) + p0y * Cos(an));
Canvas.Pen.Color:=clBlue;
Canvas.Brush.Color:=clRed;
Canvas.Ellipse(xx - 3, yy - 3, xx + 4, yy + 4);
end;
end;
and for these arguments it gives : this result
ArcByStartEndAngle(Form1.Image1.Canvas, Point(574,199),Point(62,202), Pi / 2, 8);
Please left a piece of code that can help or at list a translation of c code above for drawing the desired effect.
the following screenshoot could give an idea : ScreenShoot
enter image description here
**

Drawing oval path through four points

I have a four points that I want to draw oval that path through these four points
I do not want to use [UIBezierPath bezierPathWithOvalInRect:frame];
Because it can not work with rotated rectangle.
I suppose that your 4 points are not arbitrary ones, but middles of edges of rectangle around the ellipse.
You can build path from four Bezier curves to approximate zero-centered unit circle. Example of code here
Then apply to control points affine transformation that transform circle to rotated ellipse with semi-axes A and B, center (CX, CY) and rotation angle Alpha.
For your case, if points are P0..P3, then
A = |P0P2| (distance)
B = |P1P3|
CX = (P0.X + P2.X)/2
CY = (P0.Y + P2.Y)/2
Alpha = ArcTan2(P0.Y - P2.Y, P0.X - P2.X)
I have Delphi code for this problem, hope it could help. Note that closed Bezier path contains 13 points (the last is the same as the first).
procedure CalcRotatedEllipse(CX, CY, A, B: Integer; Alpha: Double; var BezPts: array of TPoint);
const
MP = 0.55228475;
var
CA, SA, ACA, ASA, BCA, BSA: Double;
i, CX2, CY2: Integer;
function TransformPoint(X, Y: Double): TPoint;
begin
Result.X := Round(CX + X * ACA + Y * BSA);
Result.Y := Round(CY - X * ASA + Y * BCA);
end;
begin
Assert(Length(BezPts) = 13);
CA:= Cos(Alpha); SA := Sin(Alpha);
ACA := A * CA; ASA := A * SA;
BCA := B * CA; BSA := B * SA;
CX2 := 2 * CX; CY2 := 2 * CY;
BezPts[0] := TransformPoint(1, 0);
BezPts[1] := TransformPoint(1, MP);
BezPts[2] := TransformPoint(MP, 1);
BezPts[3] := TransformPoint(0, 1);
BezPts[4] := TransformPoint(- MP, 1);
BezPts[5] := TransformPoint(-1, MP);
for i := 0 to 5 do
BezPts[i + 6] := Point(CX2 - BezPts[i].X, CY2 - BezPts[i].Y);
BezPts[12] := BezPts[0];
end;
procedure TForm1.Button3Click(Sender: TObject);
var
Pts: array[0..12] of TPoint;
begin
CalcRotatedEllipse(200, 200, 200, 70, Pi/6, Pts);
Canvas.PolyBezier(Pts);
end;

Delphi draw parabola with PlotGird

I am using a PlotGrid component with Firemonkey and I want to be able to draw a parabola in it. Given a very easy program that takes a,b,c as input ( > ax^2 + bx + c = 0) I am going to solve the 2nd degree equation.
As you should know that can be represented with a parabola. I thought that I could use this procedure:
procedure DrawArc(const Center, Radius: TPointF; StartAngle, SweepAngle: Single; const AOpacity: Single); overload;
You can see this picture to better understand the meaning:
I want to be able to draw a parabola in a PlotGrid because the user in this way I can have a "container" for my line. Also, if I resized the component or I am using a mobile device, I won't have to repaint the x/y axis. Is this a good way to go?
I have studied a bit the situation and this is what I have thought:
The focus coords of the parabola should be what in the procedure are called Center.
The Radius.X should be the value of one of the solutions of the equations. I The Radius.Y should be the position of the vertex.
I cannot figure how to setup the sweep angle. Is this too complicated? Should I find another better solution to draw the parabola?
You cannot draw parabola with ellipse arc, it doesn't give needed curve form (only small parabola piece near middle point might be approximated).
If Firemonkey graphics engine supports Bezier curves, you can draw exact parabola with Bezier. To calculate it's coefficients, express X- and Y- parts of parametric cubic curve in power basis
F(x) = -b/2a + (t - 0.5) * needed_parabola_X_range
F(y) = a*F(x)^2 + b * F(x) + c
then transform them into Bernstein polynomial basis (here you need some understanding of Bezier math)
VCL example
var
a, b, c, w: Double;
b2a, baw: Double;
P: array[0..3] of TPoint;
Cx, Cy: array[0..3] of Double;
begin
//vertex at (200, 100)
a := 0.05;
b := -20;
c := 2100;
W := 160; //width, distance between parabola ends
b2a := - 0.5 * b / a;
baw := b2a - 0.5 * W;
Cx[0] := baw;
Cx[1] := W;
Cx[2] := 0;
CX[3] := 0;
Cy[0] := a * baw * baw + c + b * baw;
Cy[1] := b * W + 2 * a * W * baW;
Cy[2] := a * W * W;
Cy[3] := 0;
p[0] := Point(Round(Cx[0]), Round(Cy[0]));
p[1].X := Round(cx[0] + cx[1] / 3);
p[1].Y := Round(cy[0] + cy[1] / 3);
p[2].X := Round(cx[0] + (2 * cx[1] + cx[2]) / 3);
p[2].Y := Round(cy[0] + (2 * cy[1] + cy[2]) / 3);
p[3].X := Round(cx[0] + cx[1] + cx[2] + cx[3]);
p[3].Y := Round(cy[0] + cy[1] + cy[2] + cy[3]);
Canvas.PolyBezier(P);

Rotation has no effect on GetPath?

I'm using Delphi (XE5) to create a graphic component. One challenge is to rotate a closed path by SetWorldTransform, then read back the outline with GetPath. Rotation works ok, but the points retreived from GetPath are not rotated (however, obtating a region (PathToRegion) works as expected!).
My code:
procedure Rotate(DestBitmap : TBitmapEx; Radians : Single; FigureRect : TRect);
// DestBitmap is where to draw the figure. Size of DestBitmap is computed from
//the actual angle and figure size (not shown here). FigureRect is the plain
//figure rectangle without rotation
var
XForm: tagXFORM;
C, S : single;
Points : array of TPoint;
NumPoints : integer;
Bytes : TByteArray;
Rgn : HRGN;
X, Y : integer;
begin
//Locate FigureRect to center of bitmap:
X := (DestBitmap.Width - FigureRect.Width) div 2;
Y := (DestBitmap.Height - FigureRect.Height) div 2;
FigureRect.Location := Point(X,Y);
//Set rotate mode
C := Cos(Radians);
S := Sin(Radians);
XForm.eM11 := C;
XForm.eM12 := S;
XForm.eM21 := -S;
XForm.eM22 := C;
XForm.eDx := (DestBitmap.Width - DestBitmap.Width * C +
DestBitmap.Height * S) / 2;
XForm.eDy := (DestBitmap.Height - DestBitmap.Width * S -
DestBitmap.Height * C) / 2;
SetGraphicsMode(DestBitmap.Canvas.Handle, GM_ADVANCED);
SetWorldTransform(DestBitmap.Canvas.Handle, XForm);
//Rotate the figure
BeginPath(DestBitmap.Canvas.Handle);
DestBitmap.Canvas.Rectangle(FigureRect);
EndPath(DestBitmap.Canvas.Handle);
FlattenPath(DestBitmap.Canvas.Handle);
NumPoints := GetPath(DestBitmap.Canvas.Handle, Points[0], Bytes[0], 0);
SetLength(Points, NumPoints);
GetPath(DestBitmap.Canvas.Handle, Points[0], Bytes[0], NumPoints);
//Points now describes the plain, unrotated figure, but if instead:
//Rgn := PathToRegion(DestBitmap.Canvas.Handle);
//Rgn describes the rotated area, as expected
end;
That is expected, GetPath returns points in logical coordinates. Whereas the resulting region of PathToRegion uses device coordinates - so it is unaffected by the transform. See documentation of both functions.
Or three, SetWorldTransform transforms logical coordinates. To everything in the logical world, nothing has been changed. The transform is with respect to the device.

Image "fade" in / out (Not opacity)

I want to "fade" in / out an image. But not opacity-wise.
Pictures say more than words:
Original image:
Desired image:
How can I do that programatically? Not the way like "use Bitmap.Canvas" but the mathematical approach. ("For dummies" if possible ... :D)
I want the image to have a fade-in / -out area, not linear increasing but "curvy". I guess it has something to do with Bezier curves? If yes, how would I setup the points to get a curve like that?
Or what would be your approach here?
Thanks for any help! :)
Here is roughly how you would go about doing it (as you said, you're looking for the logic and not the full implementation)
Create the basic shape outline: Create a partial sine-wave, such that the semi-period (half-wavelength: P1 = L/2) equals the length (x-coordinate size) of your image.
Add Overtones: Add to it another sine-function. This time with wavelength given by P2 = P1 / 2 + rnd where rnd is a random real number in the interval (-P1/4 , +P1 / 4)
Repeat: Now P2 becomes the new P1.
That way you can generate the 'wavy-waves' by modulating the main wave and you will get the top boundary.
You can change the sign and get the lower boundary.
The word you might be looking for (for the shape, i.e.) is Overtones. You could look up more on generating overtones for optics or acoustics.
This example for adding overtones to a straight line would give a better idea. The code above adds up these sinusoidal waves of randomly shortening periods to create the wave-on-wave effect (source)
Thanks again, hnk. You got me the right ideas. :)
I did a little work and ended up with this:
procedure GenerateOverlayEdges(ABitmap: TBitmap; ARadiusX, ARadiusY: Integer);
const
MAX_ANGLE = 90;
var
ShapePosition, ShapeIndex, ShapesCount,
Angle, X, Y, RadiusX, RadiusY, CenterY: Integer;
PolyPoints: Array of TPoint;
begin
ABitmap.Canvas.Pen.Color := clBlack;
ABitmap.Canvas.Brush.Color := clBlack;
//ABitmap.Canvas.Pen.Color := clWhite;
//ABitmap.Canvas.Brush.Color := clWhite;
RadiusX := ARadiusX;
RadiusY := ARadiusY;
CenterY := Round(ABitmap.Height / 2);
ShapesCount := Ceil(CenterY / RadiusY);
for ShapePosition := 1 to 4 do // 1 = TopLeft, 2 = BottomLeft, 3 = TopRight, 4 = BottomRight
for ShapeIndex := 0 to ShapesCount - 1 do // Index of current "stair"
begin
Finalize(PolyPoints);
SetLength(PolyPoints, MAX_ANGLE + 1);
for Angle := 0 to MAX_ANGLE do
begin
case ShapePosition of
1, 3:
begin
RadiusX := Abs(RadiusX);
RadiusY := Abs(RadiusY);
end;
2, 4:
begin
RadiusX := Abs(RadiusX);
RadiusY := -Abs(RadiusY);
end;
end;
X := Trunc(RadiusX * Cos(Angle * 2 * Pi / 360));
Y := Trunc(RadiusY * Sin(Angle * 2 * Pi / 360));
case ShapePosition of
1:
begin
X := X + ShapeIndex * RadiusX;
Y := Y + CenterY - RadiusY - ShapeIndex * RadiusY;
end;
2:
begin
X := X + ShapeIndex * RadiusX;
Y := Y + CenterY - RadiusY + ShapeIndex * Abs(RadiusY);
end;
3:
begin
X := ABitmap.Width - X - ShapeIndex * Abs(RadiusX);
Y := Y + CenterY - RadiusY - ShapeIndex * RadiusY;
end;
4:
begin
X := ABitmap.Width - X - ShapeIndex * Abs(RadiusX);
Y := Y + CenterY + Abs(RadiusY) + ShapeIndex * Abs(RadiusY);
end;
end;
// Add points as part of a Polyon
PolyPoints[Angle] := Point(X, Y);
end;
// Set Y to the Y borders for the very first and last point of the polygon so we will get a "closed" shape
case ShapePosition of
1, 3:
begin
PolyPoints[0] := Point(PolyPoints[0].X, 0);
PolyPoints[Angle - 1] := Point(PolyPoints[Angle - 1].X, 0);
end;
2, 4:
begin
PolyPoints[0] := Point(PolyPoints[0].X, ABitmap.Height);
PolyPoints[Angle - 1] := Point(PolyPoints[Angle - 1].X, ABitmap.Height);
end;
end;
// Draw the poly points ... and fill the background at the same time
ABitmap.Canvas.Polygon(PolyPoints);
end;
Finalize(PolyPoints);
end;
Usage:
var
Bmp: TBitmap;
begin
Bmp := TBitmap.Create;
try
Bmp.LoadFromFile('C:\Temp\Osc 2.bmp'); // Original Oscilloscope Image
GenerateOverlayEdges(Bmp, 15, 20);
Bmp.SaveToFile('C:\Temp\Osc 3.bmp');
finally
Bmp.Free;
end;
end;
By changing the RadiusX and RadiusY parameters of the GenerateOverlayEdges function I can adjust the results:
8x8:
15x20:
20x10:

Resources