Skip to content

Conversation

@JimBobSquarePants
Copy link
Member

Prerequisites

  • I have written a descriptive pull-request title
  • I have verified that there are no overlapping pull-requests open
  • I have verified that I am following matches the existing coding patterns and practice as demonstrated in the repository. These follow strict Stylecop rules 👮.
  • I have provided test coverage for my change (where applicable)

Description

Note: Most of the work here was done by @TechPizzaDev

This PR introduces several improvements and refactorings across the drawing and shapes components of the codebase. The most significant changes include refactoring the ComplexPolygon and EllipsePolygon classes for better performance and maintainability, updating the handling of Bezier and Arc line segments, and making various code quality and style improvements. Below are the most important changes grouped by theme:

Shapes and Geometry Refactoring

  • Refactored ComplexPolygon to lazily compute bounds and internal paths, reducing upfront computation and improving performance. Introduced helper methods for internal path initialization and bounds calculation.
  • Refactored EllipsePolygon to inherit from Polygon and removed redundant internal state. Updated transformation logic to work with the new structure, simplifying the class and aligning it with other shape types.
  • Added new PolygonStroker class designed to replace PolygonOffsetter

Line Segment Improvements

  • Updated CubicBezierLineSegment and ArcLineSegment to compute EndPoint and Flatten() lazily, improving efficiency and consistency. Added a convenience constructor and a method to retrieve control points for Bezier segments.

Code Quality and Style

  • Improved pattern and variable naming for clarity and consistency (e.g., opaque constant, use of slice syntax).
  • Removed unused usings and unnecessary project properties, and made minor code cleanups throughout the codebase.

Miscellaneous Fixes

  • Fixed a bug in RichTextGlyphRenderer by referencing the correct path list.
  • Updated the Visual Studio solution file for compatibility with Visual Studio 2022 (v18).

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

This PR includes a comprehensive set of code quality improvements, refactoring, and test infrastructure enhancements. The changes focus on modernizing C# code patterns, improving performance, and adding new drawing robustness test capabilities.

  • Modernization of C# syntax (range operators, simplified expressions, expression-bodied members)
  • Performance optimizations (lazy initialization, reduced allocations, better memory usage patterns)
  • Test infrastructure improvements (new test cases, Linux SkiaSharp support)

Reviewed Changes

Copilot reviewed 51 out of 51 changed files in this pull request and generated 12 comments.

Show a summary per file
File Description
tests/.../DrawingRobustnessTests.cs Adds new scaled GeoJSON test and enables previously skipped test
tests/.../TestImageProviderTests.cs Moves variable declaration to inner scope
tests/.../Issue_28_108.cs Replaces instance field with inline color conversion
tests/.../FillPatternBrushTests.cs Removes unnecessary this. qualifiers
src/.../BaseGlyphBuilder.cs Introduces duplicate paths and PathList fields causing confusion
src/.../ActiveEdgeList.cs Removes exception throw and adds readonly modifier
src/.../ScanEdgeCollection.Build.cs Extracts local method to private static method
src/.../InternalPath.cs Fixes spelling typo and modernizes code patterns
src/.../PathCollection.cs Implements lazy bounds calculation
src/.../ComplexPolygon.cs Implements lazy initialization for bounds and internal paths
src/.../EllipsePolygon.cs Refactors to inherit from Polygon, uses lazy flattening
src/.../CubicBezierLineSegment.cs Implements lazy computation of line points
src/.../PolygonClipper/* Adds new files and modernizes existing code
.github/workflows/build-and-test.yml Adds release branch triggers and formatting fixes
Various project files Adds Linux SkiaSharp native assets support
Comments suppressed due to low confidence (1)

src/ImageSharp.Drawing/Shapes/Text/BaseGlyphBuilder.cs:15

  • The existing paths field (line 15) is never used and creates confusion with the new PathList property (line 30). Both fields serve the same purpose. The old paths field should be removed, and all code should reference PathList instead.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines 51 to +52
PathF points = new(pathPoints.Length);
for (int i = 0; i < pathPoints.Length; i++)
{
points.Add(pathPoints[i]);
}
points.AddRange(pathPoints);
Copy link

Copilot AI Oct 30, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Creating a new PathF with initial capacity and then calling AddRange is inefficient. Since PathF now inherits from List<PointF> (based on diff in PolygonOffsetter.cs), you can construct it directly: PathF points = new(pathPoints) which will be more efficient.

Copilot uses AI. Check for mistakes.
Comment on lines +175 to +183
foreach (IPath p in this.paths)
{
RectangleF pBounds = p.Bounds;

minX = MathF.Min(minX, pBounds.Left);
maxX = MathF.Max(maxX, pBounds.Right);
minY = MathF.Min(minY, pBounds.Top);
maxY = MathF.Max(maxY, pBounds.Bottom);
}
Copy link

Copilot AI Oct 30, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This foreach loop immediately maps its iteration variable to another variable - consider mapping the sequence explicitly using '.Select(...)'.

Copilot uses AI. Check for mistakes.
Comment on lines +50 to 57
foreach (IPath path in this.paths)
{
RectangleF bounds = path.Bounds;
minX = Math.Min(bounds.Left, minX);
minY = Math.Min(bounds.Top, minY);
maxX = Math.Max(bounds.Right, maxX);
maxY = Math.Max(bounds.Bottom, maxY);
}
Copy link

Copilot AI Oct 30, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This foreach loop immediately maps its iteration variable to another variable - consider mapping the sequence explicitly using '.Select(...)'.

Copilot uses AI. Check for mistakes.
const float opaque = 1f;

if (options.BlendPercentage != Opaque)
if (options.BlendPercentage != opaque)
Copy link

Copilot AI Oct 30, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Equality checks on floating point values can yield unexpected results.

Copilot uses AI. Check for mistakes.
}

if (color.ToScaledVector4().W != Opaque)
if (color.ToScaledVector4().W != opaque)
Copy link

Copilot AI Oct 30, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Equality checks on floating point values can yield unexpected results.

Copilot uses AI. Check for mistakes.
Comment on lines +52 to +56
RectangleF bounds = path.Bounds;
minX = Math.Min(bounds.Left, minX);
minY = Math.Min(bounds.Top, minY);
maxX = Math.Max(bounds.Right, maxX);
maxY = Math.Max(bounds.Bottom, maxY);
Copy link

Copilot AI Oct 30, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Local scope variable 'bounds' shadows PathCollection.bounds.

Suggested change
RectangleF bounds = path.Bounds;
minX = Math.Min(bounds.Left, minX);
minY = Math.Min(bounds.Top, minY);
maxX = Math.Max(bounds.Right, maxX);
maxY = Math.Max(bounds.Bottom, maxY);
RectangleF pathBounds = path.Bounds;
minX = Math.Min(pathBounds.Left, minX);
minY = Math.Min(pathBounds.Top, minY);
maxX = Math.Max(pathBounds.Right, maxX);
maxY = Math.Max(pathBounds.Bottom, maxY);

Copilot uses AI. Check for mistakes.
this.srcVertices.Add(new VertexDistance(x, y, distance));
}

private void CloseVertexPath(bool closed)
Copy link

Copilot AI Oct 30, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Local scope variable 'closed' shadows PolygonStroker.closed.

Copilot uses AI. Check for mistakes.
=> this.internalPaths;
{
this.InitInternalPaths();
return this.internalPaths;
Copy link

Copilot AI Oct 30, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Variable this.internalPaths may be null at this access because it has a nullable type.

Suggested change
return this.internalPaths;
return this.internalPaths!;

Copilot uses AI. Check for mistakes.
internal struct VertexDistance
{
private const double Dd = 1.0 / Constants.Misc.VertexDistanceEpsilon;
public double X;
Copy link

Copilot AI Oct 30, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Field 'X' can be 'readonly'.

Copilot uses AI. Check for mistakes.
{
private const double Dd = 1.0 / Constants.Misc.VertexDistanceEpsilon;
public double X;
public double Y;
Copy link

Copilot AI Oct 30, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Field 'Y' can be 'readonly'.

Copilot uses AI. Check for mistakes.
@JimBobSquarePants JimBobSquarePants merged commit dd8f167 into SixLabors:main Oct 30, 2025
15 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants