[AGGPas] Usage of scale method lazarus / 39-Graphics ---------------------------------------------------------------------- Roland57 · 2026-02-15 19:29 Hello everybody. I am drawing with agg_2D unit in a PNG image. I would like to change coordinates, so that point 0, 0 be in the middle of the image, and x and y go from -0.5 to 0.5. The translate method alone works as expected, but the scale method doesn't. It seems that I don't know how to use it. The both images attached should be the same (excepted the color): a disk in the middle of the image. I compiled my examples using both Lazarus and fpGUI source. The result is the same. ---------------------------------------------------------------------- wp · 2026-02-15 22:26 In short: The order of operations is important, exchange "scale" and "translate" transformations. Long explanation: The ellipse has its center at "world" coordinates (0, 0) and has radii (0.25, 0.25). You want the ellipse to be drawn in the center of an image which is SURFACE_WIDTH pixels wide and SURFACE_HEIGHT pixels high ("image coordinates"). There are two possible ways how to achieve this: scale2: First multiply all world coordinates by (SURFACE_WIDTH, SURFACE_HEIGHT), --> "scale" transformation. This, however, would leave the ellipse in the left/bottom corner since the origin does not move upon multiplication -> you must add (SURFACE_WIDTH div2 , SURFACE_HEIGHT div 2) to all coordinates to bring the scaled ellipse it into the image center --> "translate" Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i   agg^.scale(SURFACE_WIDTH, SURFACE_HEIGHT);  agg^.translate(SURFACE_WIDTH div 2, SURFACE_HEIGHT div 2);  agg^.ellipse(0, 0, 0.25, 0.25); scale: Here the "translate" transformation has parameters in "world" space (0.5, 0.5). To get the desired effect you must, however, reverse their order: first translate by (0.5, 0.5) - this moves the ellipse out of the center of the world coordinates to the point (0.5, 0.5). Then scale by (SURFACE_WIDTH, SURFACE_HEIGHT) -- this blows up the world space ranging between (0,0) and (1,1) to image space ranging between (0,0) and (SURFACE_WIDTH, SURFACE_HEIGHT); the multiplication shifts the ellipse center at (0.5, 0.5) in world space to (SURFACE_WIDTH div2, SURFACE_HEIGHT div 2), and the ellipse radii become (SURFACEWIDTH div 4, SURFACE_HEIGHT div 4) pixels. Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i   agg^.translate(0.5, 0.5);  agg^.scale(SURFACE_WIDTH, SURFACE_HEIGHT);  agg^.ellipse(0, 0, 0.25, 0.25); One thing which was confusing me a bit, was that the setting of the line width had no effect on the image. But then I had the idea: after these transformations all coordinates that you use for drawing are in world space; therefore, you must specify the linewidth in world coordinates, too: The diameter of the ellipse is 0.5 - setting agg^.linewidth(0.05) makes the ellipse border 10% of the diameter: Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i procedure TAggExample1.Draw(agg: Agg2D_ptr);begin  agg^.clearAll(0, 0, 0, 0);  agg^.lineCap(CapRound);  agg^.lineColor(0, 0, 255, 255);    agg^.translate(0.5, 0.5);  agg^.scale(SURFACE_WIDTH, SURFACE_HEIGHT);  agg^.lineWidth(0.05);  // in world coordinates!   agg^.ellipse(0, 0, 0.25, 0.25);end; P.S. I did my tests with the LCL version of aggpas - you probably are using Graeme's because I did not get your screenshot with the scaled linewidth. So be prepared the my code posted here may lead to different linewidths, too. ---------------------------------------------------------------------- Roland57 · 2026-02-15 22:54 Perfect! Thank you very much for the solution and the short tutorial. ---------------------------------------------------------------------- wp · 2026-02-15 23:01 Does my last code result in the 10% line width with Graeme's aggpas? ---------------------------------------------------------------------- Roland57 · 2026-02-16 00:10 Yes, the result is the same. ---------------------------------------------------------------------- Roland57 · 2026-02-16 00:22 Now I am trying to add a rotation, but I didn't get what I expect. Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i procedure TAggExample1.Draw(agg: Agg2D_ptr);begin  agg^.clearAll(0, 0, 0, 0);  agg^.lineCap(CapRound);  agg^.lineColor(0, 0, 255, 255);   agg^.translate(0.5, 0.5);  agg^.scale(SURFACE_WIDTH, SURFACE_HEIGHT);  agg^.lineWidth(0.05); // in world coordinates!    agg^.rotate(PI / 4);  agg^.rectangle(-0.25, -0.25, 0.25, 0.25);end; I would expect that the rectangle rotate around the center of the image. I think I need to read again your explanation tomorrow morning. ---------------------------------------------------------------------- Graeme · 2026-02-16 00:41 Quote from: wp on February 15, 2026, 11:26:50 pmIn short: The order of operations is important, exchange "scale" and "translate" transformations. Yes, that is vital. The graphics pipeline is highly configurable and order makes a massive difference in output. QuoteI did my tests with the LCL version of aggpas - you probably are using Graeme's Be aware that the LCL version has plenty of bugs and not well maintained. The one included with fpGUI is used by fpGUI on a daily basis. I have added loads of memory, platform bit-ness etc changes and fixes to the fpGUI one. The Lazarus team is very welcome to pull some of those changes into their version. Regards,   Graeme ---------------------------------------------------------------------- Graeme · 2026-02-16 01:04 Hi Roland, Not sure if you are aware, but the fpGUI repo includes tons of AGG (original C++ code) and AggPas documentation: https://github.com/graemeg/fpGUI/tree/develop/extras/aggpas/docs (I just moved them from /docs/aggpas to /extras/aggpas/docs/ If you haven't yet, I would suggest reading: * introduction.html * agg2d.html The others are important too, but more advanced. ---------------------------------------------------------------------- Roland57 · 2026-02-16 08:03 @Graeme Thank you for the information. I am reading agg2d.html and extending my examples collection.  I solved my rotation problem. I think I begin to understand how it works. Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i procedure TAggExample1.Draw(agg: Agg2D_ptr);begin  agg^.clearAll(0, 0, 0, 0);  //agg^.noLine;  //agg^.fillColor(0, 0, 255);  agg^.lineColor(0, 0, 255);  agg^.lineWidth(0.05);   agg^.scale(SURFACE_WIDTH, SURFACE_HEIGHT);  agg^.translate(SURFACE_WIDTH div 2, SURFACE_HEIGHT div 2);  //agg^.translate(0.5, 0.5);  //agg^.scale(SURFACE_WIDTH, SURFACE_HEIGHT);    agg^.translate(-SURFACE_WIDTH div 2, -SURFACE_HEIGHT div 2);  agg^.rotate(PI / 4);  agg^.translate(SURFACE_WIDTH div 2, SURFACE_HEIGHT div 2);    agg^.rectangle(-0.25, -0.25, 0.25, 0.25);end; @wp Thanks again for your very useful explanations. ---------------------------------------------------------------------- wp · 2026-02-16 08:57 You can solve it simpler in your original code: When you want to rotate a shape around its center then the center must be in the origin of the coordinate system. The way you construct the rectangle, agg^.rectangle(-0.25, -0.25, 0.25, 0.25), this is absolutely true. Therefore you must first perform the rotation, then translate the square into the center of the "world" viewport and finally scale everything up to image coordinates: Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i procedure TAggExample1.Draw(agg: Agg2D_ptr);begin  agg^.clearAll(0, 0, 0, 0);  agg^.lineCap(CapRound);  agg^.lineColor(0, 0, 255, 255);   agg^.rotate(PI / 4);  agg^.translate(0.5, 0.5);  agg^.scale(SURFACE_WIDTH, SURFACE_HEIGHT);  agg^.lineWidth(0.05); // in world coordinates!   agg^.rectangle(-0.25, -0.25, 0.25, 0.25);end; ---------------------------------------------------------------------- wp · 2026-02-16 09:17 Here's an a bit more advanced exercise which does not rotate the square around its center: Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i procedure TAggExample1.Draw(agg: Agg2D_ptr);begin  agg^.clearAll(0, 0, 0, 0);  agg^.lineCap(CapRound);  agg^.lineColor(0, 0, 255, 255);   // Draw circle in the center of the image  // Basic transformation which converts world coordinates to image coordinates  agg^.translate(0.5, 0.5);  agg^.scale(SURFACE_WIDTH, SURFACE_HEIGHT);  agg^.lineWidth(0.05);  agg^.ellipse(0, 0, 0.25, 0.25);  // Circle is at center of "world"   // Draw square which touches the circle at 45°  agg^.resetTransformations;   // important!  agg^.translate(0.25+0.1, 0);     // Translate square horizontally so that it touches the circle on the x axis  agg^.rotate(PI/4);               // Rotate the square by 45° around origin. Remember: square is not at origin any more!  agg^.translate(0.5, 0.5);        // Now repeat the transformation to image coordinates that we already did for the circle.  agg^.scale(SURFACE_WIDTH, SURFACE_HEIGHT);  agg^.lineWidth(0.02);  agg^.rectangle(-0.1, -0.1, 0.1, 0.1);  // square is at the center of the "world" initially   // Draw square which touches the circle at 135°  agg^.resetTransformations;  agg^.translate(0.25+0.1, 0);  agg^.rotate(PI * 0.75);  agg^.translate(0.5, 0.5);  agg^.scale(SURFACE_WIDTH, SURFACE_HEIGHT);  agg^.lineWidth(0.02);  agg^.rectangle(-0.1, -0.1, 0.1, 0.1);end; ---------------------------------------------------------------------- Roland57 · 2026-02-16 10:33 Thank you very much for your latest examples. Quote from: wp on February 16, 2026, 09:57:17 amTherefore you must first perform the rotation, then translate the square into the center of the "world" viewport and finally scale everything up to image coordinates: Yes, it works. Perfect solution. But, in the program that I had in mind, I needed to translate and scale (or scale and translate) once, and to rotate repeatedly. Here is the first working version of the program. It's a program drawing a cardioid. Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i { https://mathimages.swarthmore.edu/index.php/Cardioid } procedure TAggExample1.Draw(agg: Agg2D_ptr);const  R = 0.3;var  a: double;var  x1, y1, x2, y2: double;begin  agg^.clearAll(0, 0, 0, 0);  agg^.lineCap(CapRound);  agg^.lineColor(0, 0, 255, 255);  agg^.lineWidth(0.001);   agg^.scale(SURFACE_WIDTH, SURFACE_HEIGHT);  agg^.translate(SURFACE_WIDTH div 2, SURFACE_HEIGHT div 2);  //agg^.translate(0.5, 0.5);  //agg^.scale(SURFACE_WIDTH, SURFACE_HEIGHT);    //agg^.noFill;  //agg^.ellipse(0, 0, R, R);    agg^.lineWidth(0.0015);    a := 0;    while a < 2 * PI - PI / 72 do  begin    x1 := 0;    y1 := -R;    x2 := R * Cos(PI - a);    y2 := R * Sin(PI - a);        //agg^.moveTo(x1, y1);    //agg^.lineTo(x2, y1);  //agg^.lineTo(x2, y2);    agg^.line(x1, y1, x2, y1);    agg^.line(x2, y1, x2, y2);        a := a + PI / 36;        agg^.translate(-SURFACE_WIDTH div 2, -SURFACE_HEIGHT div 2);    agg^.rotate(PI / 36);    agg^.translate(SURFACE_WIDTH div 2, SURFACE_HEIGHT div 2);  end;end; I attach the original program (using Cairo), if someone wants to compare the logic. Not yet had time to study your more advanced exercise, but it looks great.