Recent

Author Topic: [AGGPas] Usage of scale method  (Read 997 times)

Roland57

  • Hero Member
  • *****
  • Posts: 586
    • msegui.net
[AGGPas] Usage of scale method
« on: February 15, 2026, 08:29:53 pm »
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.
« Last Edit: February 15, 2026, 09:50:43 pm by Roland57 »
My projects are on Codeberg.

wp

  • Hero Member
  • *****
  • Posts: 13401
Re: [AGGPas] Usage of scale method
« Reply #1 on: February 15, 2026, 11:26:50 pm »
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][+][-]
  1.   agg^.scale(SURFACE_WIDTH, SURFACE_HEIGHT);
  2.   agg^.translate(SURFACE_WIDTH div 2, SURFACE_HEIGHT div 2);
  3.   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][+][-]
  1.   agg^.translate(0.5, 0.5);
  2.   agg^.scale(SURFACE_WIDTH, SURFACE_HEIGHT);
  3.   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][+][-]
  1. procedure TAggExample1.Draw(agg: Agg2D_ptr);
  2. begin
  3.   agg^.clearAll(0, 0, 0, 0);
  4.   agg^.lineCap(CapRound);
  5.   agg^.lineColor(0, 0, 255, 255);
  6.  
  7.   agg^.translate(0.5, 0.5);
  8.   agg^.scale(SURFACE_WIDTH, SURFACE_HEIGHT);
  9.   agg^.lineWidth(0.05);  // in world coordinates!
  10.  
  11.   agg^.ellipse(0, 0, 0.25, 0.25);
  12. 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.
« Last Edit: February 16, 2026, 12:00:26 am by wp »

Roland57

  • Hero Member
  • *****
  • Posts: 586
    • msegui.net
Re: [AGGPas] Usage of scale method
« Reply #2 on: February 15, 2026, 11:54:06 pm »
Perfect! Thank you very much for the solution and the short tutorial.
My projects are on Codeberg.

wp

  • Hero Member
  • *****
  • Posts: 13401
Re: [AGGPas] Usage of scale method
« Reply #3 on: February 16, 2026, 12:01:30 am »
Does my last code result in the 10% line width with Graeme's aggpas?

Roland57

  • Hero Member
  • *****
  • Posts: 586
    • msegui.net
Re: [AGGPas] Usage of scale method
« Reply #4 on: February 16, 2026, 01:10:26 am »
Yes, the result is the same.
My projects are on Codeberg.

Roland57

  • Hero Member
  • *****
  • Posts: 586
    • msegui.net
Re: [AGGPas] Usage of scale method
« Reply #5 on: February 16, 2026, 01:22:56 am »
Now I am trying to add a rotation, but I didn't get what I expect.

Code: Pascal  [Select][+][-]
  1. procedure TAggExample1.Draw(agg: Agg2D_ptr);
  2. begin
  3.   agg^.clearAll(0, 0, 0, 0);
  4.   agg^.lineCap(CapRound);
  5.   agg^.lineColor(0, 0, 255, 255);
  6.  
  7.   agg^.translate(0.5, 0.5);
  8.   agg^.scale(SURFACE_WIDTH, SURFACE_HEIGHT);
  9.   agg^.lineWidth(0.05); // in world coordinates!
  10.  
  11.   agg^.rotate(PI / 4);
  12.   agg^.rectangle(-0.25, -0.25, 0.25, 0.25);
  13. 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.  :)
« Last Edit: February 16, 2026, 06:20:59 am by Roland57 »
My projects are on Codeberg.

Graeme

  • Hero Member
  • *****
  • Posts: 1492
    • Graeme on the web
Re: [AGGPas] Usage of scale method
« Reply #6 on: February 16, 2026, 01:41:21 am »
In 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.

Quote
I 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
--
fpGUI Toolkit - a cross-platform GUI toolkit using Free Pascal
http://fpgui.sourceforge.net/

Graeme

  • Hero Member
  • *****
  • Posts: 1492
    • Graeme on the web
Re: [AGGPas] Usage of scale method
« Reply #7 on: February 16, 2026, 02:04:23 am »
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.
--
fpGUI Toolkit - a cross-platform GUI toolkit using Free Pascal
http://fpgui.sourceforge.net/

Roland57

  • Hero Member
  • *****
  • Posts: 586
    • msegui.net
Re: [AGGPas] Usage of scale method
« Reply #8 on: February 16, 2026, 09:03:26 am »
@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][+][-]
  1. procedure TAggExample1.Draw(agg: Agg2D_ptr);
  2. begin
  3.   agg^.clearAll(0, 0, 0, 0);
  4.   //agg^.noLine;
  5.   //agg^.fillColor(0, 0, 255);
  6.   agg^.lineColor(0, 0, 255);
  7.   agg^.lineWidth(0.05);
  8.  
  9.   agg^.scale(SURFACE_WIDTH, SURFACE_HEIGHT);
  10.   agg^.translate(SURFACE_WIDTH div 2, SURFACE_HEIGHT div 2);
  11.   //agg^.translate(0.5, 0.5);
  12.   //agg^.scale(SURFACE_WIDTH, SURFACE_HEIGHT);
  13.  
  14.   agg^.translate(-SURFACE_WIDTH div 2, -SURFACE_HEIGHT div 2);
  15.   agg^.rotate(PI / 4);
  16.   agg^.translate(SURFACE_WIDTH div 2, SURFACE_HEIGHT div 2);
  17.  
  18.   agg^.rectangle(-0.25, -0.25, 0.25, 0.25);
  19. end;

@wp
Thanks again for your very useful explanations.
My projects are on Codeberg.

wp

  • Hero Member
  • *****
  • Posts: 13401
Re: [AGGPas] Usage of scale method
« Reply #9 on: February 16, 2026, 09:57:17 am »
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][+][-]
  1. procedure TAggExample1.Draw(agg: Agg2D_ptr);
  2. begin
  3.   agg^.clearAll(0, 0, 0, 0);
  4.   agg^.lineCap(CapRound);
  5.   agg^.lineColor(0, 0, 255, 255);
  6.  
  7.   agg^.rotate(PI / 4);
  8.   agg^.translate(0.5, 0.5);
  9.   agg^.scale(SURFACE_WIDTH, SURFACE_HEIGHT);
  10.   agg^.lineWidth(0.05); // in world coordinates!
  11.  
  12.   agg^.rectangle(-0.25, -0.25, 0.25, 0.25);
  13. end;

wp

  • Hero Member
  • *****
  • Posts: 13401
Re: [AGGPas] Usage of scale method
« Reply #10 on: February 16, 2026, 10:17:25 am »
Here's an a bit more advanced exercise which does not rotate the square around its center:

Code: Pascal  [Select][+][-]
  1. procedure TAggExample1.Draw(agg: Agg2D_ptr);
  2. begin
  3.   agg^.clearAll(0, 0, 0, 0);
  4.   agg^.lineCap(CapRound);
  5.   agg^.lineColor(0, 0, 255, 255);
  6.  
  7.   // Draw circle in the center of the image
  8.   // Basic transformation which converts world coordinates to image coordinates
  9.   agg^.translate(0.5, 0.5);
  10.   agg^.scale(SURFACE_WIDTH, SURFACE_HEIGHT);
  11.   agg^.lineWidth(0.05);
  12.   agg^.ellipse(0, 0, 0.25, 0.25);  // Circle is at center of "world"
  13.  
  14.   // Draw square which touches the circle at 45°
  15.   agg^.resetTransformations;   // important!
  16.   agg^.translate(0.25+0.1, 0);     // Translate square horizontally so that it touches the circle on the x axis
  17.   agg^.rotate(PI/4);               // Rotate the square by 45° around origin. Remember: square is not at origin any more!
  18.   agg^.translate(0.5, 0.5);        // Now repeat the transformation to image coordinates that we already did for the circle.
  19.   agg^.scale(SURFACE_WIDTH, SURFACE_HEIGHT);
  20.   agg^.lineWidth(0.02);
  21.   agg^.rectangle(-0.1, -0.1, 0.1, 0.1);  // square is at the center of the "world" initially
  22.  
  23.   // Draw square which touches the circle at 135°
  24.   agg^.resetTransformations;
  25.   agg^.translate(0.25+0.1, 0);
  26.   agg^.rotate(PI * 0.75);
  27.   agg^.translate(0.5, 0.5);
  28.   agg^.scale(SURFACE_WIDTH, SURFACE_HEIGHT);
  29.   agg^.lineWidth(0.02);
  30.   agg^.rectangle(-0.1, -0.1, 0.1, 0.1);
  31. end;
« Last Edit: February 16, 2026, 10:19:13 am by wp »

Roland57

  • Hero Member
  • *****
  • Posts: 586
    • msegui.net
Cardioid
« Reply #11 on: February 16, 2026, 11:33:08 am »
Thank you very much for your latest examples.

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:

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][+][-]
  1. { https://mathimages.swarthmore.edu/index.php/Cardioid }
  2.  
  3. procedure TAggExample1.Draw(agg: Agg2D_ptr);
  4. const
  5.   R = 0.3;
  6. var
  7.   a: double;
  8. var
  9.   x1, y1, x2, y2: double;
  10. begin
  11.   agg^.clearAll(0, 0, 0, 0);
  12.   agg^.lineCap(CapRound);
  13.   agg^.lineColor(0, 0, 255, 255);
  14.   agg^.lineWidth(0.001);
  15.  
  16.   agg^.scale(SURFACE_WIDTH, SURFACE_HEIGHT);
  17.   agg^.translate(SURFACE_WIDTH div 2, SURFACE_HEIGHT div 2);
  18.   //agg^.translate(0.5, 0.5);
  19.   //agg^.scale(SURFACE_WIDTH, SURFACE_HEIGHT);
  20.  
  21.   //agg^.noFill;
  22.   //agg^.ellipse(0, 0, R, R);
  23.  
  24.   agg^.lineWidth(0.0015);
  25.  
  26.   a := 0;
  27.  
  28.   while a < 2 * PI - PI / 72 do
  29.   begin
  30.     x1 := 0;
  31.     y1 := -R;
  32.     x2 := R * Cos(PI - a);
  33.     y2 := R * Sin(PI - a);
  34.    
  35.     //agg^.moveTo(x1, y1);
  36.     //agg^.lineTo(x2, y1);
  37.     //agg^.lineTo(x2, y2);
  38.     agg^.line(x1, y1, x2, y1);
  39.     agg^.line(x2, y1, x2, y2);
  40.    
  41.     a := a + PI / 36;
  42.    
  43.     agg^.translate(-SURFACE_WIDTH div 2, -SURFACE_HEIGHT div 2);
  44.     agg^.rotate(PI / 36);
  45.     agg^.translate(SURFACE_WIDTH div 2, SURFACE_HEIGHT div 2);
  46.   end;
  47. 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.
« Last Edit: February 16, 2026, 07:20:57 pm by Roland57 »
My projects are on Codeberg.

 

TinyPortal © 2005-2018