I am trying to convert some Delphi code as we are re-writing a Delphi 6.0 (VCL) application in .Net. I am not sure and could not figure out the comparison between 2 Delphi Tpoints(x,y) with that of C# Point(x,y).
I am trying to draw a line between 2 points but since I have no idea how Delphi draws it, I am not able to set the C# coordinates for it.
The Delphi code is simple:
Canvas.MoveTo(x, y - 128);
Canvas.LineTo(x, y);
I know about the C# coordinates though about 72 Points per inch and need to calculate the pixel density. But I am not sure about the Delphi PPI.
Any would be appreciated. Thanks.
Edit: If someone is wondering what TPoint I am talking when there is none in my code snippet, Canvas.MoveTo sets the PenPos property of the canvas which is of type TPoint.
I'm not sure what the exact question is that's being asked here. You have no Delphi TPoint in your code snippet; you simply have client rect logical coordinates.
The origin is at X = 0, Y = 0, which is the top left corner of the client area. Increasing X moves the position to the right, and increasing Y moves the position down. Logical units are pixels, so starting at the origin of 0, 0, a Canvas.MoveTo(10, 10) would set the new drawing position in from the left edge 10 pixels and down from the top 10 pixels, and a Canvas.LineTo(20, 20) from there would draw a line from the point at 10, 10 to 20, 20.
TCanvas.MoveTo and TCanvas.LineTo are simply wrappers around the underlying Windows GDI functions MoveToEx (with an always NULL third parameter) and LineTo.
As far as the C# equivalent, if you're referring to System.Drawing.Point, the units used are exactly the same (although I'm not sure where the origin is based by default). Given an origin of 0, 0, System.Drawing.Point(10, 10) should be the same position described above - 10 pixels from the left edge and 10 pixels down from the top edge.
A quick check confirms that the origin in a WinForms application is in fact the top left corner of the client area, using:
// Delphi code
procedure TForm3.FormPaint(Sender: TObject);
begin
Canvas.Pen.Color := clRed;
Canvas.MoveTo(0, 0);
Canvas.LineTo(100, 100);
end;
// C# code
private void Form1_Paint(object sender, PaintEventArgs e)
{
Pen newPen = new System.Drawing.Pen(Color.Red);
e.Graphics.DrawLine(newPen, new Point(0, 0), new Point(100, 100));
}
This produces the following output:
Related
I draw a path consisting of 2 lines going up and then back down to the same spot, or almost the same spot, but the first line is drawn too high. If I then draw the same lines using DrawLine I don't see the issue. Why is this happening?
Below is an example. Just drop a 400x400 TImage on a blank multiplatform form. The code draws 2 red paths, one with close to a 180 degree angle between the lines and one with less of an angle. The same lines are then drawn using DrawLine in blue. If the DrawPath function works correctly then the blue lines should completely cover the red lines, but they don't. In this example with a scale of 1.5 the path extends 7 pixels too high for the first path. The extent of the error reduces as the lines get further apart. The issue still happens with a scale of 1, but is less obvious.
procedure TForm1.FormActivate(Sender: TObject);
var
LPath1, LPath2 : TPathData;
i : Integer;
begin
// A path of 2 lines going up and then back down to almost the same spot
LPath1 := TPathData.Create;
LPath1.MoveTo(PointF(100,200));
LPath1.LineTo(PointF(100,50 ));
LPath1.LineTo(PointF(105,200));
// A path of 2 lines going up and then back down at a wider angle
LPath2 := TPathData.Create;
LPath2.MoveTo(PointF(200,200));
LPath2.LineTo(PointF(200,50 ));
LPath2.LineTo(PointF(260,200));
Image1.Bitmap.BitmapScale := 1.5; // The issue shows up more at larger scales
Image1.Bitmap.SetSize(Trunc(Image1.Width), Trunc(Image1.Height));
with Image1.Bitmap.Canvas do if BeginScene then begin
Clear(TAlphaColorRec.White);
// Draw the paths using DrawPath in red
Stroke.Color := TAlphaColorRec.Red;
Stroke.Thickness := 1;
DrawPath(LPath1, 1);
DrawPath(LPath2, 1);
// Draw the paths using DrawLine in blue over the top
// The red lines should be completely hidden under the blue
Stroke.Color := TAlphaColorRec.Blue;
for i := 1 to LPath1.Count - 1 do
DrawLine(LPath1.Points[i-1].Point, LPath1.Points[i].Point, 1);
for i := 1 to LPath2.Count - 1 do
DrawLine(LPath2.Points[i-1].Point, LPath2.Points[i].Point, 1);
EndScene;
end;
LPath1.Free;
LPath2.Free;
Image1.Bitmap.SaveToFile('test.png');
end;
Result of the code when run in Windows 10. I'm using Delphi 11, but the same issue happens with Delphi 10. I've tried switching GPU but the same issue occurs.
Enlarged view:
I've come to the conclusion that this isn't a glitch at all. It's because the default setting of TCanvas.Stroke.Join is TStrokeJoin.Miter. The artefact seen is just the sharp spike of the mitred corner. Using MoveTo before each line segment when constructing the path does solve the issue (because there's no join between the separate line segments) but so does setting the TCanvas.Stroke.Join parameter to TStrokeJoin.Round or TStrokeJoin.Bevel.
Note that at very sharp angles approaching 180 degrees, the miter join would become infinite. However, it appears to be limited somehow, perhaps in proportion to the stroke thickness. I don't think there's a way to change this miter limit in delphi.
This is because by default TPath is making smooth transitions between different path segments. I'm guessing it might be using Quadratic interpolation for making these smooth transitions.
Yes making smooth transition between two lines doesn't seem logical but it looks this is how it is implemented.
Now you can avoid this by telling the TPath that your two lines are not connected and thus should be treated as two separate lines even thou in reality they are connected. And you can do this by simply calling Path.MoveTo which is intended to shift position so you can create another unconnected line that dos not continue from your last path point.
Here is how modified code for your first sharp cornered line would look like:
NOTE that I'm specifying the exact same position for MoveTo command that was used for rendering of previous path line since you don't want the new line to start at new position.
// A path of 2 lines going up and then back down to almost the same spot
LPath1 := TPathData.Create;
LPath1.MoveTo(PointF(100,200));
LPath1.LineTo(PointF(100,50 ));
//Add move to command to prevent Path from trying to make smooth transitions between two lines
LPath1.MoveTo(PointF(100,50));
LPath1.LineTo(PointF(105,200));
I want to change Delphi FMX's coordinate system (for visual controls like TImage, TPaintBox, etc) to use Cartesian coordinate system (Bottom Left)
How can I do this?
This is very basic for GDI:
HDC hDC = this->Canvas->Handle;
SetMapMode(hDC, MM_LOENGLISH); //Change coordinate system
SetViewportOrgEx(hDC, 300, 200, NULL); //Change origin
Coordinate point change sample for Delphi FMX:
Image1.Position.Point := PointF(250, 250);
I've just noticed one can set a negative scale (at one or both axes) in FMX resulting in flipping things in the respective axes coordinates.
So to reverse the Y axis you need to do
SomeContainer.Scale.Point := TPointF.Create(0, -1);
after that you'd also need to offset though, but could do it by offseting a parent control by (-width/2, -height/2) I guess
Just create simple FireMokey HD app, put TImage with align=alclient on the form and trying to draw simple black rect:
procedure TForm8.FormCreate(Sender: TObject);
var
c: TCanvas;
begin
Image.Bitmap := TBitmap.Create(ClientWidth, ClientHeight);
c := Image.Bitmap.Canvas;
c.BeginScene;
try
c.Clear(claWhite);
c.Stroke.Color := claBlack;
c.Stroke.Kind := TBrushKind.bkSolid;
c.DrawRect(
TRectF.Create(7,7,ClientWidth-7,ClientHeight-7),
0,0,
[],
1
);
finally
c.EndScene;
end;
end;
And it doesn't work. Color of the rect is not black, it is kind of gray. There some changes of the color in corners. Did i need to set some other properties or what is wrong here ?
I tried different opacity values (1,100,255,65535), picture doesn't change at all and there is no information in the help what the hell this option means.
Zoomed left-top corner:
Also tried to use polygons as it described in example. Same problem - rounded corners and gray color instead of black (Opacity property of image is 1, all properties as by default):
procedure TForm8.Button2Click(Sender: TObject);
var
p1, p2, p3, p4, p5: TPointF;
MyPolygon: TPolygon;
begin
// sets the points that define the polygon
p1.Create(100, 100);
p2.Create(200, 100);
p3.Create(200, 200);
p4.Create(100, 200);
p5.Create(100, 100);
// creates the polygon
SetLength(MyPolygon, 5);
MyPolygon[0] := p1;
MyPolygon[1] := p2;
MyPolygon[2] := p3;
MyPolygon[3] := p4;
MyPolygon[4] := p5;
Image.Bitmap.Canvas.BeginScene;
// draws the polygon on the canvas
Image.Bitmap.Canvas.DrawPolygon(MyPolygon, 50);
Image.Bitmap.Canvas.EndScene;
// updates the bitmap
// Image.Bitmap.BitmapChanged;
end;
http://roman.yankovsky.me/?p=1018
if Canvas.BeginScene then
try
Canvas.Stroke.Thickness := 1.5;
Canvas.Stroke.Kind := TBrushKind.bkSolid;
Canvas.Fill.Color := TAlphaColorRec.Black;
Canvas.Fill.Kind := TBrushKind.bkSolid;
for I := 1 to 9 do
begin
Canvas.DrawLine(PointF(50 + I * 25 - (Canvas.Stroke.Thickness / 2), 0),
PointF(50 + I * 25 - (Canvas.Stroke.Thickness / 2), ClientHeight), 1);
end;
finally
Canvas.EndScene;
end;
This is easy to fix once you understand the better paradigm of Firemonkey. Firemonkey uses real coordinates, not integer coordinates. You unwittingly told it to draw lines that were centered the boundaries between pixels, so each of your lines were half in one set of pixels and half in another set of pixels.
Specifically, what happened is that your integer coordinates were interpreted as exact center points on a continuous number line. For example, say the point is 7. A line of width 1 centered on the point at 7.0 will extend from 6.5 to 7.5 on the number line. But because the pixels extend from 6.0 to 6.99 and from 7.0 to 7.99 on the number line, each pixel is half black and half white. Automatic antialiasing caused them to be drawn 50% black, which is where the two-pixel wide gray comes from.
When using FMX (now called FMX) you have to switch your thinking from integer coordinates to real coordinates, which is far more sophisticated and powerful.
The easiest solution is to move your integer-based math by 0.5 to the right and 0.5 down. Then a one-pixel wide line at 7.5 will extend from 7.0 to 7.999, which is what you were expecting. To do this, just add 0.5 to all your pixel coordinates, both horizontal and vertical, as you issue drawing commands.
The nice thing is, lines that are 0.8 pixels wide or 1.5 pixels wide will automatically appear thinner or thicker, respectively. Diagonal lines and other curves will appear correct without jagged edges. You can scale complex drawings and they will look perfect at any zoom level. (The math for the half-pixel shift stays the same for all zoom levels. The 0.5 is added after scaling immediately before drawing the line.)
The above is true for all devices: screens, bitmaps, and printers, etc. So the same code to draw on screen can be used to draw to everything else. When drawing text, you can use fractional point sizes for the fonts, so they scale with everything else.
I am writing 2-3 Trees application in Lazarus for my school project.
Everything's done, now playing with GUI (I get the same number of points for good GUI as I do for a good etc. Insert function, which is weird but nvm).
When I have like 10+ nodes in the tree, my 300*200 image size just isn't large enough.
I would like to have an TImage component which would be like 300*200 on the TForm, but it would be like 10000 * 10000 really and you could scroll in it.
Is it even possible?
Thanks
EDIT TO MAKE THE QUESTION CLEARER
A 2-3 Tree is a data structure. When drawn on a paper to see how it works, it looks like this http://www.cosc.canterbury.ac.nz/research/RG/alg/tree23.gif
As a real noobie in lazarus/delphi (have to do it in lazarus) a use this code to draw it (even if I doubt u need it to answer my question):
procedure TStrom.Paint(Image: TImage);
var
C: TCanvas;
procedure Paint1(V: TNode; Width, X, Y: integer);
begin
if V.L <> nil then //left child
begin
C.MoveTo(X, Y);
C.LineTo(X - Width div 3, Y + 50);
Paint1(V.L, Width div 3, X - Width div 3, Y + 50);
end;
if V.S <> nil then //middle child
begin
C.MoveTo(X, Y);
C.LineTo(X + Width div 3, Y + 50);
Paint1(V.S, Width div 3, X + Width div 3, Y + 50);
end;
if V.P <> nil then //right child
begin
C.MoveTo(X, Y);
C.LineTo(X + Width div 3 + Width div 3, Y + 50);
Paint1(V.P, Width div 3, X + Width div 3 + Sirka div 3, Y + 50);
end;
if V.isLeaf then
begin
C.Ellipse(X - 15, Y - 15, X + 15, Y + 15);
C.TextOut(X - 3, Y - 8, IntToStr(V.Info1));
end
else
begin
C.Rectangle(X - 15, Y - 15, X + 15, Y + 15);
C.TextOut(X - 7, Y - 8, IntToStr(V.Info1));
C.Rectangle(X + 15, Y - 15, X + 50, Y + 15);
if V.Info2 <> 0 then
C.TextOut(X + 27, Y - 8, IntToStr(V.Info2));
end;
The draw function works well, but some (most) of the nodes at the height of 3+ are drawn on other nodes, so it looks bad. The node is sitting on another node and is not 20 pixels next to it.
I thought I'd make the image where the tree is painted real big, but it would be in a small "panel". Like this: the TImage would really be 1000*1000, but in the form you could see only a little part of it. In this part there'd be horizontal and vertical scrollbars, so you could scroll through the image and see what's painted in the sections. (Like when you scroll through a web browser to see the bottom of the page :) )
We are not permitted to use any other code, just built in lazarus components. (nor are we permitted to create new components -> have no idea why)
While I'm still curious about how this could be done, it's no longer necessary for my application ( installed a second monitor to see if it'd help and it wouldn't, so I guess I'd dig through my paint method a bit :-) )
Your edit makes it more clear that you want to draw a schematic like the last tree in the example you link to.
From your code I understand that you are drawing all nodes, childs and leaves, onto a single canvas resulting in one large image/bitmap. Now, how to display only part of that large image with scroll bars next to it?
The obvious choice would be to place a TPaintBox on a TScrollBox. I do not know the default suite of components in Lazarus, but I expect both of them to be present. Give the paint box a size equal to the bounds of your visual tree and you're set: scroll bars will be shown automatically.
The paint box has an OnPaint event in which you do your paint work by drawing to PaintBox.Canvas. To optimize this, you could limit your drawing to PaintBox.Canvas.ClipRect (the part of the paint box that is visible within the scroll box), but I suspect that to be difficult, since you already have difficulty with computing the right distance between adjacent nodes. (About that: I expect the maximum width and height of the total tree to be a function of the total depth, but I could easily be mistaken.)
If Lazarus does not have a TPaintBox, then use a TImage which also has a Canvas property. Downside is that TImage "stores" all drawing operations in one big internal bitmap, which could raise memory or resource issues when your tree expands too much.
Could all images be combined to one large image? If so, then maybe this component answers your question.
It is a descendant of TGraphicControl, capable of animated zooming. Zoom in on a part of the graphic by dragging a selection rectangle, zoom out the whole graphic by double clicking it. Shoot if you need help to update it to be able to perform pan operations.
I have a line identify by x1,y1,x2,y2 which are double values. Then I have
several graphical objects (Let's name the class TShape) which cordinates
are Left, Top, Right, Bottom: double. Only Top and Left properties are writable
value. When dragging the TShape around the top and left values are updated.
I am using a function to discovery when TShape is near a Line. The function
definition is:
function NearLine(const Target: TPoint; X1, Y1, X2, Y2: double; Off: integer = 5): boolean;
NearLine returns true if point specified by Target is near the line specified by Point1
and Point2. The point must be at the distance specified by Off.
I use the function with Off = 0;
In my implementation Target is the center of the TShape which I keep updated
calculating it from Top and Left properties. Because Target
is TPoint I do:
1-
CPoint.X := Trunc(Center.X);
CPoint.Y := Trunc(Center.Y);
2-
and when the function NearLine above is true I force the mouse to release with:
3-
Mouse_Event(MOUSEEVENTF_ABSOLUTE or MOUSEEVENTF_LEFTUP, 0, 0, 0, 0);
1,2 an 3 are called within an event UpdateMove which is called while
dragging the shape.
This allow me to "stick" the TShape almost near the line however it's not
exactly what I want to achive... obviously there is an error due the Trunc function.
The error is:
deltaX := Frac(Center.X);
deltaY := Frac(Center.Y);
After releasing the mouse programmatically how I can force all the center
therefore all the shapes to be perfectly lined (collinear) with the
line?
Any help? :(
You're asking the question wrong, that's why you cant' see the answer your self. If 3 points aren't collinear, you're not going to "force" them collinear unless you change the laws of math and/or physics.
What you probably want is to find a point on the line defined by two points that's closest to your point of reference. That's pretty simple geometry: The closest point is as at the "foot" of a perpendicular drawn from your third point to the line defined by the first two! You can solve that using the Pythagoran theory alone, you don't even need fancy analytic geometry.