Skip to content

Commit bc082d6

Browse files
Merge pull request #390 from SixLabors/js/fix-9
Add RoundedRectanglePolygon and PathBuilder helpers.
2 parents 7f09902 + 82d769e commit bc082d6

43 files changed

Lines changed: 619 additions & 346 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ image.Mutate(ctx => ctx.Paint(canvas =>
2323
{
2424
canvas.Fill(Brushes.Solid(Color.White));
2525
canvas.Fill(Brushes.Solid(Color.Red), new EllipsePolygon(200, 200, 100));
26-
canvas.Draw(Pens.Solid(Color.Blue, 3F), new RectangularPolygon(50, 50, 200, 100));
26+
canvas.Draw(Pens.Solid(Color.Blue, 3F), new RectanglePolygon(50, 50, 200, 100));
2727
}));
2828
```
2929

samples/DrawShapesWithImageSharp/Program.cs

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,7 @@ private static void DrawPosterComposition()
121121

122122
// The lake is a plain rectangle filled with a vertical LinearGradientBrush. Building it
123123
// with PathBuilder keeps the construction style identical to the surrounding shapes
124-
// even though a RectangularPolygon would also work for an axis-aligned rectangle.
124+
// even though a RectanglePolygon would also work for an axis-aligned rectangle.
125125
PathBuilder lakeShape = new();
126126
lakeShape.AddLines(
127127
new PointF(0, 432),
@@ -175,7 +175,7 @@ private static void DrawPosterComposition()
175175
// Save pushes a clipping state; Restore pops it. Anything drawn between the two is
176176
// confined to lakeHighlight even though the brush spans its full bounding rectangle.
177177
canvas.Save(lakeHighlightClipOptions, lakeHighlight);
178-
canvas.Fill(Brushes.ForwardDiagonal(Color.White.WithAlpha(.36F), Color.Transparent), new RectangularPolygon(lakeHighlightBounds));
178+
canvas.Fill(Brushes.ForwardDiagonal(Color.White.WithAlpha(.36F), Color.Transparent), new RectanglePolygon(lakeHighlightBounds));
179179
canvas.Restore();
180180

181181
// Title panel: pushing posterPanelOptions onto the canvas with Save activates a
@@ -249,7 +249,7 @@ private static void DrawPosterComposition()
249249

250250
canvas.Save(posterPanelOptions);
251251
canvas.SaveLayer(new GraphicsOptions { BlendPercentage = .94F }, posterPanelLayerBounds);
252-
canvas.Fill(Brushes.Solid(Color.White.WithAlpha(.86F)), new RectangularPolygon(posterPanelBounds));
252+
canvas.Fill(Brushes.Solid(Color.White.WithAlpha(.86F)), new RectanglePolygon(posterPanelBounds));
253253
canvas.DrawText(posterTextOptions, posterText, Brushes.Solid(Color.DarkSlateGray), pen: null);
254254
canvas.Restore();
255255
canvas.Restore();
@@ -296,8 +296,8 @@ private static void DrawTransitMap()
296296
pen: null);
297297

298298
// Legend panel: reuse the route pens so the key is drawn with the same stroke options as the map.
299-
canvas.Fill(Brushes.Solid(Color.White.WithAlpha(.9F)), new RectangularPolygon(680, 46, 220, 126));
300-
canvas.Draw(Pens.Solid(Color.LightSlateGray, 2), new RectangularPolygon(680, 46, 220, 126));
299+
canvas.Fill(Brushes.Solid(Color.White.WithAlpha(.9F)), new RectanglePolygon(680, 46, 220, 126));
300+
canvas.Draw(Pens.Solid(Color.LightSlateGray, 2), new RectanglePolygon(680, 46, 220, 126));
301301
canvas.DrawLine(harborLinePen, new PointF(708, 78), new PointF(768, 78));
302302
canvas.DrawLine(gardenLoopPen, new PointF(708, 112), new PointF(768, 112));
303303
canvas.DrawLine(airportLinePen, new PointF(708, 146), new PointF(768, 146));
@@ -627,7 +627,7 @@ private static void DrawTypographySheet()
627627
// options the draw call uses, otherwise WrappingLength, LineSpacing, or font fallback
628628
// can change where the lines break and the box drifts off the text.
629629
FontRectangle measuredBox = TextMeasurer.MeasureRenderableBounds(measuredText, measuredOptions);
630-
RectangularPolygon measuredBackground = new(
630+
RectanglePolygon measuredBackground = new(
631631
measuredBox.X - 10,
632632
measuredBox.Y - 8,
633633
measuredBox.Width + 20,
@@ -852,9 +852,9 @@ static void DrawPanel(
852852
Brush titleBrush,
853853
Pen rulePen)
854854
{
855-
RectangularPolygon panel = new(origin.X, origin.Y, width, height);
855+
RectanglePolygon panel = new(origin.X, origin.Y, width, height);
856856

857-
// RectangularPolygon implements IPath. The same shape can be both filled (with a
857+
// RectanglePolygon implements IPath. The same shape can be both filled (with a
858858
// semi-transparent brush) and stroked (with a pen), which keeps the panel framing
859859
// consistent across the sheet without re-allocating geometry.
860860
canvas.Fill(Brushes.Solid(Color.White.WithAlpha(.54F)), panel);
@@ -947,7 +947,7 @@ static void DrawBeforeAfterWipePanel(
947947
// involved. A simple right-half rectangle is the entire mask, so OilPaint runs only
948948
// on those pixels and the left half stays as the original photograph.
949949
float midX = imageArea.X + (imageArea.Width / 2F);
950-
RectangularPolygon afterRegion = new(
950+
RectanglePolygon afterRegion = new(
951951
midX,
952952
imageArea.Y,
953953
imageArea.Width / 2F,
@@ -1019,7 +1019,7 @@ static void DrawImageBrushPanel(
10191019
imageArea.Y + (imageArea.Height / 2F));
10201020
float outerRadius = (MathF.Min(imageArea.Width, imageArea.Height) / 2F) - 6F;
10211021
float innerRadius = outerRadius * 0.5F;
1022-
Star star = new(starCenter.X, starCenter.Y, 5, innerRadius, outerRadius);
1022+
StarPolygon star = new(starCenter.X, starCenter.Y, 5, innerRadius, outerRadius);
10231023

10241024
// ImageBrush samples the source image in world coordinates: a destination pixel at
10251025
// (x, y) reads source pixel (x - offset.X, y - offset.Y) inside SourceRegion. The
@@ -1080,7 +1080,7 @@ static void DrawPhotoInTextPanel(
10801080
ShapeOptions = new ShapeOptions { BooleanOperation = BooleanOperation.Intersection },
10811081
};
10821082

1083-
canvas.Fill(Brushes.Solid(Color.ParseHex("#E2DCC2")), new RectangularPolygon(imageArea));
1083+
canvas.Fill(Brushes.Solid(Color.ParseHex("#E2DCC2")), new RectanglePolygon(imageArea));
10841084
canvas.Save(clipToGlyphs, glyphClips);
10851085
canvas.DrawImage(source, source.Bounds, imageArea, null);
10861086
canvas.Restore();
@@ -1109,7 +1109,7 @@ static float DrawPanelChrome(
11091109
Color titleColor,
11101110
Color captionColor)
11111111
{
1112-
RectangularPolygon panelShape = new(panel);
1112+
RectanglePolygon panelShape = new(panel);
11131113
canvas.Fill(Brushes.Solid(Color.White), panelShape);
11141114
canvas.Draw(Pens.Solid(Color.ParseHex("#D7D2C0"), 1), panelShape);
11151115

samples/DrawShapesWithImageSharp/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ Image-compositing scene demonstrating four ways a photograph (`tests/Images/Inpu
3737

3838
- **Before / after wipe**`canvas.Apply(rightHalfRect, ctx => ctx.OilPaint(15, 5))` scopes an `OilPaint` processor to the right half of the photograph.
3939
- **Privacy redaction**`canvas.Apply(ellipse, ctx => ctx.Pixelate(10))` pixelates an elliptical face-shaped region and leaves the rest untouched.
40-
- **Image as a brush**`new ImageBrush<Rgba32>(source, source.Bounds, brushOffset)` wraps the photograph as a `Brush` so a `Star` path can be filled with it as a texture; the brush offset aligns the mountain in the photograph with the star's centre.
40+
- **Image as a brush**`new ImageBrush<Rgba32>(source, source.Bounds, brushOffset)` wraps the photograph as a `Brush` so a `StarPolygon` path can be filled with it as a texture; the brush offset aligns the mountain in the photograph with the star's centre.
4141
- **Photo in text**`TextBuilder.GeneratePaths("MASK", ...)` produces one `IPath` per glyph; `canvas.Save(intersectionOptions, glyphPaths)` uses them as a compound clip so `DrawImage` only renders inside the letterforms.
4242

4343
## Running

samples/WebGPUExternalSurfaceDemo/Scenes/ApplyReadbackScene.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ public override void Paint(DrawingCanvas canvas, TimeSpan deltaTime)
6060
canvas.Apply(edgeRegion, ctx => ctx.DetectEdges());
6161
canvas.Apply(blurRegion, ctx => ctx.GaussianBlur(Math.Max(3F, Math.Min(viewportSize.Width, viewportSize.Height) / 120F)));
6262

63-
canvas.Draw(Pens.Solid(OutlineColor, 3), new RectangularPolygon(edgeRegion));
63+
canvas.Draw(Pens.Solid(OutlineColor, 3), new RectanglePolygon(edgeRegion));
6464
canvas.Draw(Pens.Solid(OutlineColor, 3), blurRegion);
6565
}
6666

samples/WebGPUExternalSurfaceDemo/Scenes/ManualTextFlowScene.cs

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -185,8 +185,8 @@ public override void Paint(DrawingCanvas canvas, TimeSpan deltaTime)
185185
this.cachedObstacleShape = this.obstacleShape;
186186
}
187187

188-
canvas.Fill(PageBrush, new RectangularPolygon(pageLeft, pageTop, pageRight - pageLeft, pageBottom - pageTop));
189-
canvas.Draw(PageOutlinePen, new RectangularPolygon(pageLeft, pageTop, pageRight - pageLeft, pageBottom - pageTop));
188+
canvas.Fill(PageBrush, new RectanglePolygon(pageLeft, pageTop, pageRight - pageLeft, pageBottom - pageTop));
189+
canvas.Draw(PageOutlinePen, new RectanglePolygon(pageLeft, pageTop, pageRight - pageLeft, pageBottom - pageTop));
190190
canvas.Fill(ObstacleBrush, obstaclePath);
191191
canvas.Draw(ObstacleOutlinePen, obstaclePath);
192192

@@ -238,7 +238,7 @@ public override void Paint(DrawingCanvas canvas, TimeSpan deltaTime)
238238
// The translucent slot fill is a visual aid for the sample. It
239239
// makes the row splitting visible so readers can compare the
240240
// available rectangles with the selected obstacle shape.
241-
canvas.Fill(SlotBrush, new RectangularPolygon(slot.Left, y, slotWidth, lineHeight));
241+
canvas.Fill(SlotBrush, new RectanglePolygon(slot.Left, y, slotWidth, lineHeight));
242242
canvas.DrawText(line, new PointF(slot.Left, y), TextBrush, pen: null);
243243

244244
rowHeight = MathF.Max(rowHeight, lineHeight);
@@ -274,14 +274,14 @@ private IPath CreateObstaclePath(PointF center, float size)
274274
// circle or rectangle math.
275275
return this.ObstacleShape switch
276276
{
277-
ManualTextFlowObstacleShape.Rectangle => new RectangularPolygon(
277+
ManualTextFlowObstacleShape.Rectangle => new RectanglePolygon(
278278
center.X - radius,
279279
center.Y - radius,
280280
size,
281281
size),
282282
ManualTextFlowObstacleShape.Triangle => new RegularPolygon(center, 3, radius, 180F),
283283
ManualTextFlowObstacleShape.Diamond => new RegularPolygon(center, 4, radius, 0F),
284-
ManualTextFlowObstacleShape.Star => new Star(center, 5, radius * .45F, radius, -18F),
284+
ManualTextFlowObstacleShape.Star => new StarPolygon(center, 5, radius * .45F, radius, -18F),
285285
_ => new EllipsePolygon(center, new SizeF(size, size))
286286
};
287287
}
@@ -583,7 +583,7 @@ internal enum ManualTextFlowObstacleShape
583583
Circle,
584584

585585
/// <summary>
586-
/// A rectangular obstacle backed by <see cref="RectangularPolygon"/>.
586+
/// A rectangular obstacle backed by <see cref="RectanglePolygon"/>.
587587
/// </summary>
588588
Rectangle,
589589

@@ -598,7 +598,7 @@ internal enum ManualTextFlowObstacleShape
598598
Diamond,
599599

600600
/// <summary>
601-
/// A concave star obstacle backed by <see cref="Star"/>.
601+
/// A concave star obstacle backed by <see cref="StarPolygon"/>.
602602
/// </summary>
603603
Star
604604
}

samples/WebGPUExternalSurfaceDemo/Scenes/RichTextEditorScene.cs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -209,10 +209,10 @@ public override void Paint(DrawingCanvas canvas, TimeSpan deltaTime)
209209
}
210210

211211
canvas.Fill(BackgroundBrush, canvas.Bounds);
212-
canvas.Fill(EditorBrush, new RectangularPolygon(editorBounds));
213-
canvas.Draw(BorderPen, new RectangularPolygon(editorBounds));
212+
canvas.Fill(EditorBrush, new RectanglePolygon(editorBounds));
213+
canvas.Draw(BorderPen, new RectanglePolygon(editorBounds));
214214

215-
IPath editorClip = new RectangularPolygon(editorBounds);
215+
IPath editorClip = new RectanglePolygon(editorBounds);
216216

217217
// Selection is painted before glyphs, matching normal editor behavior.
218218
// The clipping scope applies only to text so the editor chrome remains crisp.
@@ -549,11 +549,11 @@ private void DrawSelection(DrawingCanvas canvas, TextMetrics metrics)
549549
// That keeps mixed-bidi ranges visually split instead of filling across reordered gaps.
550550
canvas.Fill(
551551
SelectionBrush,
552-
new RectangularPolygon(rectangle.X, rectangle.Y, width, height));
552+
new RectanglePolygon(rectangle.X, rectangle.Y, width, height));
553553

554554
canvas.Draw(
555555
SelectionBorderPen,
556-
new RectangularPolygon(rectangle.X, rectangle.Y, width, height));
556+
new RectanglePolygon(rectangle.X, rectangle.Y, width, height));
557557
}
558558
}
559559

src/ImageSharp.Drawing/PathBuilder.cs

Lines changed: 73 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -390,7 +390,7 @@ public PathBuilder AddArc(float x, float y, float radiusX, float radiusY, float
390390
/// <summary>
391391
/// Adds a pie sector to the current path as a closed figure.
392392
/// </summary>
393-
/// <param name="center">The center point of the pie.</param>
393+
/// <param name="center">The center point of the pie sector.</param>
394394
/// <param name="radius">The x and y radii of the pie ellipse.</param>
395395
/// <param name="rotation">The ellipse rotation in degrees.</param>
396396
/// <param name="startAngle">The pie start angle in degrees.</param>
@@ -400,7 +400,7 @@ public PathBuilder AddPie(PointF center, SizeF radius, float rotation, float sta
400400
{
401401
_ = this.StartFigure();
402402

403-
foreach (ILineSegment segment in new Pie(center, radius, rotation, startAngle, sweepAngle).LineSegments)
403+
foreach (ILineSegment segment in new PiePolygon(center, radius, rotation, startAngle, sweepAngle).LineSegments)
404404
{
405405
_ = this.AddSegment(segment);
406406
}
@@ -411,7 +411,7 @@ public PathBuilder AddPie(PointF center, SizeF radius, float rotation, float sta
411411
/// <summary>
412412
/// Adds a pie sector to the current path as a closed figure.
413413
/// </summary>
414-
/// <param name="center">The center point of the pie.</param>
414+
/// <param name="center">The center point of the pie sector.</param>
415415
/// <param name="radius">The x and y radii of the pie ellipse.</param>
416416
/// <param name="startAngle">The pie start angle in degrees.</param>
417417
/// <param name="sweepAngle">The pie sweep angle in degrees.</param>
@@ -477,6 +477,75 @@ public PathBuilder AddRectangle(float x, float y, float width, float height)
477477
new PointF(x + width, y + height),
478478
new PointF(x, y + height));
479479

480+
/// <summary>
481+
/// Adds a rounded rectangle to the current path as a closed figure.
482+
/// </summary>
483+
/// <param name="rectangle">The rectangle bounds.</param>
484+
/// <param name="radius">The x and y radius of each corner.</param>
485+
/// <returns>The <see cref="PathBuilder"/>.</returns>
486+
public PathBuilder AddRoundedRectangle(RectangleF rectangle, float radius)
487+
=> this.AddRoundedRectangle(rectangle, new SizeF(radius, radius));
488+
489+
/// <summary>
490+
/// Adds a rounded rectangle to the current path as a closed figure.
491+
/// </summary>
492+
/// <param name="rectangle">The rectangle bounds.</param>
493+
/// <param name="radius">The x and y radii of each corner.</param>
494+
/// <returns>The <see cref="PathBuilder"/>.</returns>
495+
public PathBuilder AddRoundedRectangle(RectangleF rectangle, SizeF radius)
496+
{
497+
_ = this.StartFigure();
498+
499+
foreach (ILineSegment segment in new RoundedRectanglePolygon(rectangle, radius).LineSegments)
500+
{
501+
_ = this.AddSegment(segment);
502+
}
503+
504+
return this.CloseFigure();
505+
}
506+
507+
/// <summary>
508+
/// Adds a rounded rectangle to the current path as a closed figure.
509+
/// </summary>
510+
/// <param name="rectangle">The rectangle bounds.</param>
511+
/// <param name="radius">The x and y radius of each corner.</param>
512+
/// <returns>The <see cref="PathBuilder"/>.</returns>
513+
public PathBuilder AddRoundedRectangle(Rectangle rectangle, float radius)
514+
=> this.AddRoundedRectangle((RectangleF)rectangle, radius);
515+
516+
/// <summary>
517+
/// Adds a rounded rectangle to the current path as a closed figure.
518+
/// </summary>
519+
/// <param name="rectangle">The rectangle bounds.</param>
520+
/// <param name="radius">The x and y radii of each corner.</param>
521+
/// <returns>The <see cref="PathBuilder"/>.</returns>
522+
public PathBuilder AddRoundedRectangle(Rectangle rectangle, SizeF radius)
523+
=> this.AddRoundedRectangle((RectangleF)rectangle, radius);
524+
525+
/// <summary>
526+
/// Adds a rounded rectangle to the current path as a closed figure.
527+
/// </summary>
528+
/// <param name="x">The x-coordinate of the rectangle.</param>
529+
/// <param name="y">The y-coordinate of the rectangle.</param>
530+
/// <param name="width">The rectangle width.</param>
531+
/// <param name="height">The rectangle height.</param>
532+
/// <param name="radius">The x and y radius of each corner.</param>
533+
/// <returns>The <see cref="PathBuilder"/>.</returns>
534+
public PathBuilder AddRoundedRectangle(float x, float y, float width, float height, float radius)
535+
=> this.AddRoundedRectangle(new RectangleF(x, y, width, height), radius);
536+
537+
/// <summary>
538+
/// Adds a rounded rectangle to the current path as a closed figure.
539+
/// </summary>
540+
/// <param name="x">The x-coordinate of the rectangle.</param>
541+
/// <param name="y">The y-coordinate of the rectangle.</param>
542+
/// <param name="width">The rectangle width.</param>
543+
/// <param name="height">The rectangle height.</param>
544+
/// <param name="radius">The x and y radii of each corner.</param>
545+
/// <returns>The <see cref="PathBuilder"/>.</returns>
546+
public PathBuilder AddRoundedRectangle(float x, float y, float width, float height, SizeF radius)
547+
=> this.AddRoundedRectangle(new RectangleF(x, y, width, height), radius);
548+
480549
/// <summary>
481550
/// Adds a polygon to the current path as a closed figure.
482551
/// </summary>
@@ -579,7 +648,7 @@ public PathBuilder AddStar(PointF center, int prongs, float innerRadii, float ou
579648
{
580649
_ = this.StartFigure();
581650

582-
foreach (ILineSegment segment in new Star(center, prongs, innerRadii, outerRadii, angle).LineSegments)
651+
foreach (ILineSegment segment in new StarPolygon(center, prongs, innerRadii, outerRadii, angle).LineSegments)
583652
{
584653
_ = this.AddSegment(segment);
585654
}

0 commit comments

Comments
 (0)