Canvas RoundRect image quality lazarus / 39-Graphics ---------------------------------------------------------------------- Cascade · 2025-10-30 12:53 When using Canvas.RoundRect to draw a rounded rectangle, I'm getting a very rough looking curve.  Is there a way to specify increased precision prior to drawing to get smoother curves? I've attached a screenshot which shows what I'm seeing: ---------------------------------------------------------------------- dsiders · 2025-10-30 14:11 Quote from: Cascade on October 30, 2025, 01:53:34 pmWhen using Canvas.RoundRect to draw a rounded rectangle, I'm getting a very rough looking curve.  Is there a way to specify increased precision prior to drawing to get smoother curves? I've attached a screenshot which shows what I'm seeing: It'd be a good idea to include info about your OS, Widgetset and Lazarus version. But in general, that's what TCanvas.AntialiasingMode is supposed to do... smoothing. ---------------------------------------------------------------------- Thaddy · 2025-10-30 14:30 That roughness is to be expected when the antialiasingmode := amOff. E.g: setCode: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i Canvas.AntialiasingMode:=amOn;For much better results use bgrabitmap. ---------------------------------------------------------------------- wp · 2025-10-30 16:48 You mean the polyline-like appearance with straight-line segments? In Windows, this does not happen because the Canvas.RoundRect is directly drawn by the OS. In qt6, however, I noticed that the Canvas.RoundRect is delegated to a general-purpose RoundRect procedure which composes the curvature of bezier arcs. In principle, this should be ok, but I saw that for some sizes of the rect the curvature looks similar to yours. It could be that this procedure has issues. You are on macOS, aren't you? Please post the parameters of the RoundRect used so that I can try to reproduce the issue. ---------------------------------------------------------------------- wp · 2025-11-01 08:21 I implemented TCanvas.RoundRect for cocoa, and now the rounded corners look smooth (in particular when Canvas.AntiAliasingMode is amOn). The changes will be included in the upcoming version 4.4 of Lazarus. If you do not want to wait for the new version you can apply the following patch: Make a backup copy of the files cocoagdiobjects.pas and cocoawinapi.inc; they are in folder lcl/interfaces/cocoa of your Lazarus installation. Just for safety, if something goes wrong...Open cocoagdiobjects.pas in the IDEAdd "GraphMath" to the uses clause.In the interface of the unit, add "procedure RoundRect(X1, Y1, X2, Y2, RX, RY: Integer);" to the public declarations of type TCocoaContextAdd the following code to the implementation section of the unit: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 TCocoaContext.RoundRect(X1, Y1, X2, Y2, RX, RY: Integer);var  cg: CGContextRef;  rx2, ry2: CGFloat;   procedure EnsureOrder(var X, Y: Integer);  var    tmp: Integer;  begin    if Y < X then    begin      tmp := X;      X := Y;      Y := tmp;    end;  end;   procedure DrawArc(X, Y, Width, Height: Integer; Angle: Integer);  const    ARC_LENGTH = 90*16;  var    B: TBezier;  begin    Arc2Bezier(X, Y, Width, Height, Angle, ARC_LENGTH, 0.0, B);    CGContextAddCurveToPoint(cg, B[1].X+0.5, B[1].Y+0.5, B[2].X+0.5, B[2].Y+0.5, B[3].X+0.5, B[3].Y+0.5);  end; begin  cg := CGContext;  if not Assigned(cg) then exit;   EnsureOrder(X1, X2);  EnsureOrder(Y1, Y2);   if (X2 - X1 <= 0) or (Y2 - Y1 <= 0) then    Exit;   if (RX > 0) and (RY > 0) then  begin    Dec(X2);    Dec(Y2);     if X2 - X1 < RX then RX := X2 - X1;    if Y2 - Y1 < RY then RY := Y2 - Y1;     rx2 := RX * 0.5;    // ellipse radii  (RX, RY are diameters!)    ry2 := RY * 0.5;     CGContextBeginPath(cg);                                 // Begin path    CGContextMoveToPoint(cg, X1 + rx2 + 0.5, Y2 + 0.5);     // Move to start    CGContextAddLineToPoint(cg, X2 - rx2 + 0.5, Y2 + 0.5);  // Bottom horizontal line    DrawArc(X2 - RX, Y2 - RY, RX, RY, 270*16);              // Bottom/right arc    CGContextAddLineToPoint(cg, X2 + 0.5, Y1 + ry2 + 0.5);  // Right vertical line    DrawArc(X2 - RX, Y1, RX, RY, 0);                      // Top/right arc    CGContextAddLineToPoint(cg, X1 + rx2 + 0.5, Y1 + 0.5);  // Top horizontal line    DrawArc(X1, Y1, RX, RY, 90*16);                       // Top/left arc    CGContextAddLineToPoint(cg, X1 + 0.5, Y2 - ry2 + 0.5);  // Left vertical line    DrawArc(X1, Y2 - RY, RX, RY, 180*16);                   // Bottom/left arc    CGContextClosePath(cg);                                 // Close path     CGContextDrawPath(cg, kCGPathFillStroke);    AttachedBitmap_SetModified();  end  else    Polygon([Point(X1,Y1), Point(X1,Y2-1), Point(X2-1,Y2-1), Point(X2-1,Y1)], 4, false);//    Rectangle(X1, Y1, X2, Y2, Assigned(Brush), nil);end;SaveOpen cocoawinapi.incIn the implementation section, replace function TCocoaWidgetset.RoundRect byCode: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i function TCocoaWidgetset.RoundRect(DC: HDC; X1, Y1, X2, Y2, RX, RY: Integer): Boolean;var  ctx: TCocoaContext;begin  ctx := CheckDC(DC);  Result := Assigned(ctx);  if Result then    ctx.RoundRect(X1, Y1, X2, Y2, RX, RY);end;SaveRebuild the IDE: "Tools" > "Configure Build Lazarus" > Check "Clean all" and "Switch to automatic mode after building" > "Build" ---------------------------------------------------------------------- AlexTP · 2025-11-01 12:02 Good work, thank you! very micro note from me: Swap() can be used instead of 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     begin    tmp := X;      X := Y;      Y := tmp;    end; ---------------------------------------------------------------------- wp · 2025-11-01 16:57 Quote from: AlexTP on November 01, 2025, 01:02:55 pmGood work, thank you! very micro note from me: Swap() can be used instead of 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     begin    tmp := X;      X := Y;      Y := tmp;    end; I don't think so: https://www.freepascal.org/docs- html/rtl/system/swap.html ---------------------------------------------------------------------- Josh · 2025-11-01 17:42 not tested, not by a fpc intall, so I might be a wayoff. 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 if (X2 - X1 <= 0) or (Y2 - Y1 <= 0) then    Exit; would it be able to draw a one pixel line? ie when x2-x1=0 the radii, not tested but looks like they would appear half what some users would expect, not a problem though as it would soon become obvious, maybe a little comment may help. ---------------------------------------------------------------------- wp · 2025-11-01 18:16 When x1=x2 the rectangle has zero width and will not be drawn. Likewise with y1=y2. As for the "radii": you are right. I had written a bug report about it yesterday (https://gitlab.com/freepascal.org/lazarus/lazarus/-/issues/41874); documentation already has been corrected. Of course it would be clearer to change the RX, RY parameters to something like DX, DY (because they are "diameters"), but this is a change which would go through all widgetsets, and I am sure that I will introduce a typo somewhere which will break compilation in this particular widgetset that I could not test... ---------------------------------------------------------------------- Josh · 2025-11-01 18:26 if memory serves a rectangle of 0,0,0,100 would draw a single pixel line, not at fpc so cant confirm my memory. a 0 the only way to draw a 1 pixel pixelline, a 1 draws a visible 2pixel line ---------------------------------------------------------------------- wp · 2025-11-01 18:58 No. Delphi does the same. It is a convention that the lower right pixel is not considered to belong to the rectangle, or, in other words: the last pixels drawn are horizontally at Right-1 and at vertically Bottom-1, so that as a consequence the width can be calculated as Width := Right-Left (pixels at 0, 1, 2, ..., Width-1) If you want to draw a 1-pixel line use the Line instruction. ---------------------------------------------------------------------- bytebites · 2025-11-01 19:38 Quote from: AlexTP on November 01, 2025, 01:02:55 pmGood work, thank you! very micro note from me: Swap() can be used instead of 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     begin    tmp := X;      X := Y;      Y := tmp;    end; Too early to use swap(a,b), it is defined in trunk compiler. ---------------------------------------------------------------------- Josh · 2025-11-02 05:14 hi when i was playing with swift, graphics were float based, not sure if fpc cocoa widgetset follows this. https://developer.apple.com/library/archive/documentation/Cocoa/Concep tual/CocoaDrawingGuide/Paths/Paths.html https://developer.apple.com/documentation/CoreFoundation/CGFloat- swift.struct ---------------------------------------------------------------------- Thaddy · 2025-11-02 07:16 Quote from: bytebites on November 01, 2025, 08:38:59 pmQuote from: AlexTP on November 01, 2025, 01:02:55 pmGood work, thank you! very micro note from me: Swap() can be used instead of 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     begin    tmp := X;      X := Y;      Y := tmp;    end; Too early to use swap(a,b), it is defined in trunk compiler. Yes, but you can add the same code. It also works in 3.2.2. 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 generic procedure Swap(var lhs,rhs: T);type  RawT = array[0 .. sizeof(T) - 1] of byte;var  tmp:RawT;begin  tmp:=RawT(lhs);  RawT(lhs):=RawT(rhs);  RawT(rhs):=tmp;end;The code differs from my original to cover more cases. (I swapped with temp T, which would also work in this case.). ---------------------------------------------------------------------- Cascade · 2025-11-03 17:22 Thank you for all the replies! @wp - your patch looks brilliant, I will give it a try and let you know how it goes ---------------------------------------------------------------------- Cascade · 2025-11-04 19:05 I just updated my Lazarus IDE Fixes install via FpcUpDeluxe, and I'm guessing the patch is in there, because curves (e.g. Canvas.RoundRect) are rendering really well now in 4.3.  Thank you @wp!