diff --git a/.editorconfig b/.editorconfig
index 83670fa8..03036f8a 100644
--- a/.editorconfig
+++ b/.editorconfig
@@ -1,5 +1,5 @@
-# Version: 1.6.2 (Using https://semver.org/)
-# Updated: 2020-11-02
+# Version: 2.1.0 (Using https://semver.org/)
+# Updated: 2021-03-03
# See https://github.com/RehanSaeed/EditorConfig/releases for release notes.
# See https://github.com/RehanSaeed/EditorConfig for updates to this file.
# See http://EditorConfig.org for more information about .editorconfig files.
@@ -60,87 +60,84 @@ indent_size = 2
[*.{cmd,bat}]
end_of_line = crlf
+# Bash Files
+[*.sh]
+end_of_line = lf
+
# Makefiles
[Makefile]
indent_style = tab
##########################################
-# File Header (Uncomment to support file headers)
-# https://docs.microsoft.com/visualstudio/ide/reference/add-file-header
+# Default .NET Code Style Severities
+# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/configuration-options#scope
##########################################
-# [*.{cs,csx,cake,vb,vbx,tt,ttinclude}]
-file_header_template = Copyright (c) Six Labors.\nLicensed under the Apache License, Version 2.0.
-
-# SA1636: File header copyright text should match
-# Justification: .editorconfig supports file headers. If this is changed to a value other than "none", a stylecop.json file will need to added to the project.
-# dotnet_diagnostic.SA1636.severity = none
+[*.{cs,csx,cake,vb,vbx}]
+# Default Severity for all .NET Code Style rules below
+dotnet_analyzer_diagnostic.severity = warning
##########################################
-# .NET Language Conventions
-# https://docs.microsoft.com/visualstudio/ide/editorconfig-language-conventions
+# Language Rules
+# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/language-rules
##########################################
-# .NET Code Style Settings
-# https://docs.microsoft.com/visualstudio/ide/editorconfig-language-conventions#net-code-style-settings
+# .NET Style Rules
+# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/language-rules#net-style-rules
[*.{cs,csx,cake,vb,vbx}]
# "this." and "Me." qualifiers
-# https://docs.microsoft.com/visualstudio/ide/editorconfig-language-conventions#this-and-me
dotnet_style_qualification_for_field = true:warning
dotnet_style_qualification_for_property = true:warning
dotnet_style_qualification_for_method = true:warning
dotnet_style_qualification_for_event = true:warning
# Language keywords instead of framework type names for type references
-# https://docs.microsoft.com/visualstudio/ide/editorconfig-language-conventions#language-keywords
dotnet_style_predefined_type_for_locals_parameters_members = true:warning
dotnet_style_predefined_type_for_member_access = true:warning
# Modifier preferences
-# https://docs.microsoft.com/visualstudio/ide/editorconfig-language-conventions#normalize-modifiers
dotnet_style_require_accessibility_modifiers = always:warning
csharp_preferred_modifier_order = public,private,protected,internal,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,volatile,async:warning
visual_basic_preferred_modifier_order = Partial,Default,Private,Protected,Public,Friend,NotOverridable,Overridable,MustOverride,Overloads,Overrides,MustInherit,NotInheritable,Static,Shared,Shadows,ReadOnly,WriteOnly,Dim,Const,WithEvents,Widening,Narrowing,Custom,Async:warning
dotnet_style_readonly_field = true:warning
# Parentheses preferences
-# https://docs.microsoft.com/visualstudio/ide/editorconfig-language-conventions#parentheses-preferences
dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity:warning
dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity:warning
dotnet_style_parentheses_in_other_binary_operators = always_for_clarity:warning
-dotnet_style_parentheses_in_other_operators = never_if_unnecessary:suggestion
+dotnet_style_parentheses_in_other_operators = always_for_clarity:suggestion
# Expression-level preferences
-# https://docs.microsoft.com/visualstudio/ide/editorconfig-language-conventions#expression-level-preferences
dotnet_style_object_initializer = true:warning
dotnet_style_collection_initializer = true:warning
dotnet_style_explicit_tuple_names = true:warning
dotnet_style_prefer_inferred_tuple_names = true:warning
dotnet_style_prefer_inferred_anonymous_type_member_names = true:warning
dotnet_style_prefer_auto_properties = true:warning
-dotnet_style_prefer_is_null_check_over_reference_equality_method = true:warning
dotnet_style_prefer_conditional_expression_over_assignment = false:suggestion
+dotnet_diagnostic.IDE0045.severity = suggestion
dotnet_style_prefer_conditional_expression_over_return = false:suggestion
+dotnet_diagnostic.IDE0046.severity = suggestion
dotnet_style_prefer_compound_assignment = true:warning
+dotnet_style_prefer_simplified_interpolation = true:warning
+dotnet_style_prefer_simplified_boolean_expressions = true:warning
# Null-checking preferences
-# https://docs.microsoft.com/visualstudio/ide/editorconfig-language-conventions#null-checking-preferences
dotnet_style_coalesce_expression = true:warning
dotnet_style_null_propagation = true:warning
-# Parameter preferences
-# https://docs.microsoft.com/visualstudio/ide/editorconfig-language-conventions#parameter-preferences
-dotnet_code_quality_unused_parameters = all:warning
-# More style options (Undocumented)
-# https://github.com/MicrosoftDocs/visualstudio-docs/issues/3641
+dotnet_style_prefer_is_null_check_over_reference_equality_method = true:warning
+# File header preferences
+file_header_template = Copyright (c) Six Labors.\nLicensed under the Apache License, Version 2.0.
+# SA1636: File header copyright text should match
+# Justification: .editorconfig supports file headers. If this is changed to a value other than "none", a stylecop.json file will need to added to the project.
+# dotnet_diagnostic.SA1636.severity = none
+
+# Undocumented
dotnet_style_operator_placement_when_wrapping = end_of_line
-# https://github.com/dotnet/roslyn/pull/40070
-dotnet_style_prefer_simplified_interpolation = true:warning
-# C# Code Style Settings
-# https://docs.microsoft.com/visualstudio/ide/editorconfig-language-conventions#c-code-style-settings
+# C# Style Rules
+# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/language-rules#c-style-rules
[*.{cs,csx,cake}]
-# Implicit and explicit types
-# https://docs.microsoft.com/visualstudio/ide/editorconfig-language-conventions#implicit-and-explicit-types
+# 'var' preferences
csharp_style_var_for_built_in_types = never
csharp_style_var_when_type_is_apparent = true:warning
csharp_style_var_elsewhere = false:warning
# Expression-bodied members
-# https://docs.microsoft.com/visualstudio/ide/editorconfig-language-conventions#expression-bodied-members
csharp_style_expression_bodied_methods = true:warning
csharp_style_expression_bodied_constructors = true:warning
csharp_style_expression_bodied_operators = true:warning
@@ -149,47 +146,64 @@ csharp_style_expression_bodied_indexers = true:warning
csharp_style_expression_bodied_accessors = true:warning
csharp_style_expression_bodied_lambdas = true:warning
csharp_style_expression_bodied_local_functions = true:warning
-# Pattern matching
-# https://docs.microsoft.com/visualstudio/ide/editorconfig-language-conventions#pattern-matching
+# Pattern matching preferences
csharp_style_pattern_matching_over_is_with_cast_check = true:warning
csharp_style_pattern_matching_over_as_with_null_check = true:warning
-# Inlined variable declarations
-# https://docs.microsoft.com/visualstudio/ide/editorconfig-language-conventions#inlined-variable-declarations
-csharp_style_inlined_variable_declaration = true:warning
+csharp_style_prefer_switch_expression = true:warning
+csharp_style_prefer_pattern_matching = true:warning
+csharp_style_prefer_not_pattern = true:warning
# Expression-level preferences
-# https://docs.microsoft.com/visualstudio/ide/editorconfig-language-conventions#expression-level-preferences
+csharp_style_inlined_variable_declaration = true:warning
csharp_prefer_simple_default_expression = true:warning
+csharp_style_pattern_local_over_anonymous_function = true:warning
+csharp_style_deconstructed_variable_declaration = true:warning
+csharp_style_prefer_index_operator = true:warning
+csharp_style_prefer_range_operator = true:warning
+csharp_style_implicit_object_creation_when_type_is_apparent = true:warning
# "Null" checking preferences
-# https://docs.microsoft.com/visualstudio/ide/editorconfig-language-conventions#c-null-checking-preferences
csharp_style_throw_expression = true:warning
csharp_style_conditional_delegate_call = true:warning
# Code block preferences
-# https://docs.microsoft.com/visualstudio/ide/editorconfig-language-conventions#code-block-preferences
csharp_prefer_braces = true:warning
-# Unused value preferences
-# https://docs.microsoft.com/visualstudio/ide/editorconfig-language-conventions#unused-value-preferences
-csharp_style_unused_value_expression_statement_preference = discard_variable:suggestion
-csharp_style_unused_value_assignment_preference = discard_variable:suggestion
-# Index and range preferences
-# https://docs.microsoft.com/visualstudio/ide/editorconfig-language-conventions#index-and-range-preferences
-csharp_style_prefer_index_operator = true:warning
-csharp_style_prefer_range_operator = true:warning
-# Miscellaneous preferences
-# https://docs.microsoft.com/visualstudio/ide/editorconfig-language-conventions#miscellaneous-preferences
-csharp_style_deconstructed_variable_declaration = true:warning
-csharp_style_pattern_local_over_anonymous_function = true:warning
+csharp_prefer_simple_using_statement = true:suggestion
+dotnet_diagnostic.IDE0063.severity = suggestion
+# 'using' directive preferences
csharp_using_directive_placement = outside_namespace:warning
+# Modifier preferences
csharp_prefer_static_local_function = true:warning
-csharp_prefer_simple_using_statement = true:suggestion
##########################################
-# .NET Formatting Conventions
-# https://docs.microsoft.com/visualstudio/ide/editorconfig-code-style-settings-reference#formatting-conventions
+# Unnecessary Code Rules
+# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/unnecessary-code-rules
##########################################
-# Organize usings
-# https://docs.microsoft.com/visualstudio/ide/editorconfig-formatting-conventions#organize-using-directives
+# .NET Unnecessary code rules
+[*.{cs,csx,cake,vb,vbx}]
+dotnet_code_quality_unused_parameters = all:warning
+dotnet_remove_unnecessary_suppression_exclusions = none:warning
+
+# C# Unnecessary code rules
+[*.{cs,csx,cake}]
+csharp_style_unused_value_expression_statement_preference = discard_variable:suggestion
+dotnet_diagnostic.IDE0058.severity = suggestion
+csharp_style_unused_value_assignment_preference = discard_variable:suggestion
+dotnet_diagnostic.IDE0059.severity = suggestion
+
+##########################################
+# Formatting Rules
+# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/formatting-rules
+##########################################
+
+# .NET formatting rules
+# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/formatting-rules#net-formatting-rules
+[*.{cs,csx,cake,vb,vbx}]
+# Organize using directives
dotnet_sort_system_directives_first = true
+dotnet_separate_import_directive_groups = false
+
+# C# formatting rules
+# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/formatting-rules#c-formatting-rules
+[*.{cs,csx,cake}]
# Newline options
# https://docs.microsoft.com/visualstudio/ide/editorconfig-formatting-conventions#new-line-options
csharp_new_line_before_open_brace = all
@@ -231,14 +245,14 @@ csharp_space_around_declaration_statements = false
csharp_space_before_open_square_brackets = false
csharp_space_between_empty_square_brackets = false
csharp_space_between_square_brackets = false
-# Wrapping options
+# Wrap options
# https://docs.microsoft.com/visualstudio/ide/editorconfig-formatting-conventions#wrap-options
csharp_preserve_single_line_statements = false
csharp_preserve_single_line_blocks = true
##########################################
-# .NET Naming Conventions
-# https://docs.microsoft.com/visualstudio/ide/editorconfig-naming-conventions
+# .NET Naming Rules
+# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/naming-rules
##########################################
[*.{cs,csx,cake,vb,vbx}]
@@ -261,8 +275,9 @@ dotnet_naming_style.prefix_type_parameters_with_t_style.capitalization = pascal_
dotnet_naming_style.prefix_type_parameters_with_t_style.required_prefix = T
# disallowed_style - Anything that has this style applied is marked as disallowed
dotnet_naming_style.disallowed_style.capitalization = pascal_case
-dotnet_naming_style.disallowed_style.required_prefix = ____RULE_VIOLATION____
-dotnet_naming_style.disallowed_style.required_suffix = ____RULE_VIOLATION____
+# Disabled while we investigate compatibility with VS 16.10
+#dotnet_naming_style.disallowed_style.required_prefix = ____RULE_VIOLATION____
+#dotnet_naming_style.disallowed_style.required_suffix = ____RULE_VIOLATION____
# internal_error_style - This style should never occur... if it does, it indicates a bug in file or in the parser using the file
dotnet_naming_style.internal_error_style.capitalization = pascal_case
dotnet_naming_style.internal_error_style.required_prefix = ____INTERNAL_ERROR____
diff --git a/.gitattributes b/.gitattributes
index 01a3825f..70ced690 100644
--- a/.gitattributes
+++ b/.gitattributes
@@ -4,7 +4,6 @@
# normalize to Unix-style line endings
###############################################################################
* text eol=lf
-
###############################################################################
# Set explicit file behavior to:
# treat as text and
@@ -54,7 +53,6 @@
*.txt text eol=lf
*.vb text eol=lf
*.yml text eol=lf
-
###############################################################################
# Set explicit file behavior to:
# treat as text
@@ -62,7 +60,6 @@
# diff as csharp
###############################################################################
*.cs text eol=lf diff=csharp
-
###############################################################################
# Set explicit file behavior to:
# treat as text
@@ -74,7 +71,6 @@
*.fsproj text eol=lf merge=union
*.ncrunchproject text eol=lf merge=union
*.vbproj text eol=lf merge=union
-
###############################################################################
# Set explicit file behavior to:
# treat as text
@@ -82,40 +78,28 @@
# use a union merge when resoling conflicts
###############################################################################
*.sln text eol=crlf merge=union
-
###############################################################################
# Set explicit file behavior to:
# treat as binary
###############################################################################
*.basis binary
-*.bmp binary
-*.dds binary
*.dll binary
*.eot binary
*.exe binary
-*.gif binary
-*.jpg binary
-*.ktx binary
*.otf binary
*.pbm binary
*.pdf binary
-*.png binary
*.ppt binary
*.pptx binary
*.pvr binary
*.snk binary
-*.tga binary
-*.tif binary
-*.tiff binary
*.ttc binary
*.ttf binary
*.wbmp binary
-*.webp binary
*.woff binary
*.woff2 binary
*.xls binary
*.xlsx binary
-
###############################################################################
# Set explicit file behavior to:
# diff as plain text
@@ -127,3 +111,18 @@
*.pptx diff=astextplain
*.rtf diff=astextplain
*.svg diff=astextplain
+###############################################################################
+# Handle image files by git lfs
+###############################################################################
+*.jpg filter=lfs diff=lfs merge=lfs -text
+*.jpeg filter=lfs diff=lfs merge=lfs -text
+*.bmp filter=lfs diff=lfs merge=lfs -text
+*.gif filter=lfs diff=lfs merge=lfs -text
+*.png filter=lfs diff=lfs merge=lfs -text
+*.tif filter=lfs diff=lfs merge=lfs -text
+*.tiff filter=lfs diff=lfs merge=lfs -text
+*.tga filter=lfs diff=lfs merge=lfs -text
+*.webp filter=lfs diff=lfs merge=lfs -text
+*.dds filter=lfs diff=lfs merge=lfs -text
+*.ktx filter=lfs diff=lfs merge=lfs -text
+*.ktx2 filter=lfs diff=lfs merge=lfs -text
diff --git a/shared-infrastructure b/shared-infrastructure
index b7b9a275..0ea21d9e 160000
--- a/shared-infrastructure
+++ b/shared-infrastructure
@@ -1 +1 @@
-Subproject commit b7b9a2755e456a96acbf103494228226d92eddf3
+Subproject commit 0ea21d9e2a76d307dae9cfb74e33234b259352b7
diff --git a/src/ImageSharp.Drawing/Processing/BrushApplicator.cs b/src/ImageSharp.Drawing/Processing/BrushApplicator.cs
index 85dd0100..aeebd1fa 100644
--- a/src/ImageSharp.Drawing/Processing/BrushApplicator.cs
+++ b/src/ImageSharp.Drawing/Processing/BrushApplicator.cs
@@ -2,15 +2,12 @@
// Licensed under the Apache License, Version 2.0.
using System;
-using System.Buffers;
-using SixLabors.ImageSharp.Advanced;
-using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Drawing.Processing
{
///
- /// A primitive that converts a point into a color for discovering the fill color based on an implementation.
+ /// Performs the application of an implementation against individual scanlines.
///
/// The pixel format.
///
@@ -22,7 +19,7 @@ public abstract class BrushApplicator : IDisposable
///
/// The configuration instance to use when performing operations.
/// The graphics options.
- /// The target.
+ /// The target image frame.
internal BrushApplicator(Configuration configuration, GraphicsOptions options, ImageFrame target)
{
this.Configuration = configuration;
@@ -42,23 +39,15 @@ internal BrushApplicator(Configuration configuration, GraphicsOptions options, I
internal PixelBlender Blender { get; }
///
- /// Gets the target image.
+ /// Gets the target image frame.
///
protected ImageFrame Target { get; }
///
- /// Gets thegraphics options
+ /// Gets the graphics options
///
protected GraphicsOptions Options { get; }
- ///
- /// Gets the overlay pixel at the specified position.
- ///
- /// The x-coordinate.
- /// The y-coordinate.
- /// The at the specified position.
- internal abstract TPixel this[int x, int y] { get; }
-
///
public void Dispose()
{
@@ -75,43 +64,16 @@ protected virtual void Dispose(bool disposing)
}
///
- /// Applies the opacity weighting for each pixel in a scanline to the target based on the pattern contained in the brush.
+ /// Applies the opacity weighting for each pixel in a scanline to the target based on the
+ /// pattern contained in the brush.
///
- /// A collection of opacity values between 0 and 1 to be merged with the brushed color value before being applied to the target.
+ ///
+ /// A collection of opacity values between 0 and 1 to be merged with the brushed color value
+ /// before being applied to the
+ /// target.
+ ///
/// The x-position in the target pixel space that the start of the scanline data corresponds to.
/// The y-position in the target pixel space that whole scanline corresponds to.
- /// scanlineBuffer will be > scanlineWidth but provide and offset in case we want to share a larger buffer across runs.
- public virtual void Apply(Span scanline, int x, int y)
- {
- MemoryAllocator memoryAllocator = this.Configuration.MemoryAllocator;
-
- using (IMemoryOwner amountBuffer = memoryAllocator.Allocate(scanline.Length))
- using (IMemoryOwner overlay = memoryAllocator.Allocate(scanline.Length))
- {
- Span amountSpan = amountBuffer.Memory.Span;
- Span overlaySpan = overlay.Memory.Span;
- float blendPercentage = this.Options.BlendPercentage;
-
- if (blendPercentage < 1)
- {
- for (int i = 0; i < scanline.Length; i++)
- {
- amountSpan[i] = scanline[i] * blendPercentage;
- overlaySpan[i] = this[x + i, y];
- }
- }
- else
- {
- for (int i = 0; i < scanline.Length; i++)
- {
- amountSpan[i] = scanline[i];
- overlaySpan[i] = this[x + i, y];
- }
- }
-
- Span destinationRow = this.Target.GetPixelRowSpan(y).Slice(x, scanline.Length);
- this.Blender.Blend(this.Configuration, destinationRow, destinationRow, overlaySpan, amountSpan);
- }
- }
+ public abstract void Apply(Span scanline, int x, int y);
}
}
diff --git a/src/ImageSharp.Drawing/Processing/EllipticGradientBrush.cs b/src/ImageSharp.Drawing/Processing/EllipticGradientBrush.cs
index 3e424ce3..714d48f0 100644
--- a/src/ImageSharp.Drawing/Processing/EllipticGradientBrush.cs
+++ b/src/ImageSharp.Drawing/Processing/EllipticGradientBrush.cs
@@ -2,7 +2,7 @@
// Licensed under the Apache License, Version 2.0.
using System;
-
+using System.Numerics;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Drawing.Processing
@@ -126,16 +126,16 @@ public RadialGradientBrushApplicator(
}
///
- protected override float PositionOnGradient(float xt, float yt)
+ protected override float PositionOnGradient(float x, float y)
{
- float x0 = xt - this.center.X;
- float y0 = yt - this.center.Y;
+ float x0 = x - this.center.X;
+ float y0 = y - this.center.Y;
- float x = (x0 * this.cosRotation) - (y0 * this.sinRotation);
- float y = (x0 * this.sinRotation) + (y0 * this.cosRotation);
+ float xR = (x0 * this.cosRotation) - (y0 * this.sinRotation);
+ float yR = (x0 * this.sinRotation) + (y0 * this.cosRotation);
- float xSquared = x * x;
- float ySquared = y * y;
+ float xSquared = xR * xR;
+ float ySquared = yR * yR;
return (xSquared / this.referenceRadiusSquared) + (ySquared / this.secondRadiusSquared);
}
@@ -147,18 +147,7 @@ private float AngleBetween(PointF junction, PointF a, PointF b)
return MathF.Atan2(vB.Y, vB.X) - MathF.Atan2(vA.Y, vA.X);
}
- private float DistanceBetween(
- PointF p1,
- PointF p2)
- {
- // TODO: Can we not just use Vector2 distance here?
- float dX = p1.X - p2.X;
- float dXsquared = dX * dX;
-
- float dY = p1.Y - p2.Y;
- float dYsquared = dY * dY;
- return MathF.Sqrt(dXsquared + dYsquared);
- }
+ private float DistanceBetween(PointF p1, PointF p2) => Vector2.Distance(p1, p2);
}
}
}
diff --git a/src/ImageSharp.Drawing/Processing/GradientBrush.cs b/src/ImageSharp.Drawing/Processing/GradientBrush.cs
index cac9894b..cede432d 100644
--- a/src/ImageSharp.Drawing/Processing/GradientBrush.cs
+++ b/src/ImageSharp.Drawing/Processing/GradientBrush.cs
@@ -2,7 +2,10 @@
// Licensed under the Apache License, Version 2.0.
using System;
+using System.Buffers;
using System.Numerics;
+using System.Threading;
+using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Drawing.Processing
@@ -15,9 +18,7 @@ public abstract class GradientBrush : IBrush
///
/// Defines how the colors are repeated beyond the interval [0..1]
/// The gradient colors.
- protected GradientBrush(
- GradientRepetitionMode repetitionMode,
- params ColorStop[] colorStops)
+ protected GradientBrush(GradientRepetitionMode repetitionMode, params ColorStop[] colorStops)
{
this.RepetitionMode = repetitionMode;
this.ColorStops = colorStops;
@@ -44,6 +45,7 @@ public abstract BrushApplicator CreateApplicator(
///
/// Base class for gradient brush applicators
///
+ /// The pixel format.
internal abstract class GradientBrushApplicator : BrushApplicator
where TPixel : unmanaged, IPixel
{
@@ -53,6 +55,14 @@ internal abstract class GradientBrushApplicator : BrushApplicator threadContextData;
+
+ private bool isDisposed;
+
///
/// Initializes a new instance of the class.
///
@@ -69,12 +79,18 @@ protected GradientBrushApplicator(
GradientRepetitionMode repetitionMode)
: base(configuration, options, target)
{
- this.colorStops = colorStops; // TODO: requires colorStops to be sorted by position - should that be checked?
+ // TODO: requires colorStops to be sorted by position.
+ // Use Array.Sort with a custom comparer.
+ this.colorStops = colorStops;
this.repetitionMode = repetitionMode;
+ this.scalineWidth = target.Width;
+ this.allocator = configuration.MemoryAllocator;
+ this.threadContextData = new ThreadLocal(
+ () => new ThreadContextData(this.allocator, this.scalineWidth),
+ true);
}
- ///
- internal override TPixel this[int x, int y]
+ internal TPixel this[int x, int y]
{
get
{
@@ -114,16 +130,45 @@ protected GradientBrushApplicator(
{
return from.Color.ToPixel();
}
- else
+
+ float onLocalGradient = (positionOnCompleteGradient - from.Ratio) / (to.Ratio - from.Ratio);
+
+ return new Color(Vector4.Lerp((Vector4)from.Color, (Vector4)to.Color, onLocalGradient)).ToPixel();
+ }
+ }
+
+ ///
+ public override void Apply(Span scanline, int x, int y)
+ {
+ ThreadContextData contextData = this.threadContextData.Value;
+ Span amounts = contextData.AmountSpan.Slice(0, scanline.Length);
+ Span overlays = contextData.OverlaySpan.Slice(0, scanline.Length);
+ float blendPercentage = this.Options.BlendPercentage;
+
+ // TODO: Remove bounds checks.
+ if (blendPercentage < 1)
+ {
+ for (int i = 0; i < scanline.Length; i++)
+ {
+ amounts[i] = scanline[i] * blendPercentage;
+ overlays[i] = this[x + i, y];
+ }
+ }
+ else
+ {
+ for (int i = 0; i < scanline.Length; i++)
{
- float onLocalGradient = (positionOnCompleteGradient - from.Ratio) / (to.Ratio - from.Ratio);
- return new Color(Vector4.Lerp((Vector4)from.Color, (Vector4)to.Color, onLocalGradient)).ToPixel();
+ amounts[i] = scanline[i];
+ overlays[i] = this[x + i, y];
}
}
+
+ Span destinationRow = this.Target.GetPixelRowSpan(y).Slice(x, scanline.Length);
+ this.Blender.Blend(this.Configuration, destinationRow, destinationRow, overlays, amounts);
}
///
- /// calculates the position on the gradient for a given point.
+ /// Calculates the position on the gradient for a given point.
/// This method is abstract as it's content depends on the shape of the gradient.
///
/// The x-coordinate of the point.
@@ -136,8 +181,30 @@ protected GradientBrushApplicator(
///
protected abstract float PositionOnGradient(float x, float y);
- private (ColorStop from, ColorStop to) GetGradientSegment(
- float positionOnCompleteGradient)
+ ///
+ protected override void Dispose(bool disposing)
+ {
+ if (this.isDisposed)
+ {
+ return;
+ }
+
+ base.Dispose(disposing);
+
+ if (disposing)
+ {
+ foreach (ThreadContextData data in this.threadContextData.Values)
+ {
+ data.Dispose();
+ }
+
+ this.threadContextData.Dispose();
+ }
+
+ this.isDisposed = true;
+ }
+
+ private (ColorStop from, ColorStop to) GetGradientSegment(float positionOnCompleteGradient)
{
ColorStop localGradientFrom = this.colorStops[0];
ColorStop localGradientTo = default;
@@ -158,6 +225,33 @@ protected GradientBrushApplicator(
return (localGradientFrom, localGradientTo);
}
+
+ private sealed class ThreadContextData : IDisposable
+ {
+ private bool isDisposed;
+ private readonly IMemoryOwner amountBuffer;
+ private readonly IMemoryOwner overlayBuffer;
+
+ public ThreadContextData(MemoryAllocator allocator, int scanlineLength)
+ {
+ this.amountBuffer = allocator.Allocate(scanlineLength);
+ this.overlayBuffer = allocator.Allocate(scanlineLength);
+ }
+
+ public Span AmountSpan => this.amountBuffer.Memory.Span;
+
+ public Span OverlaySpan => this.overlayBuffer.Memory.Span;
+
+ public void Dispose()
+ {
+ if (!this.isDisposed)
+ {
+ this.isDisposed = true;
+ this.amountBuffer.Dispose();
+ this.overlayBuffer.Dispose();
+ }
+ }
+ }
}
}
}
diff --git a/src/ImageSharp.Drawing/Processing/ImageBrush.cs b/src/ImageSharp.Drawing/Processing/ImageBrush.cs
index 0c22e21b..c8887c02 100644
--- a/src/ImageSharp.Drawing/Processing/ImageBrush.cs
+++ b/src/ImageSharp.Drawing/Processing/ImageBrush.cs
@@ -3,8 +3,6 @@
using System;
using System.Buffers;
-
-using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
@@ -108,8 +106,7 @@ public ImageBrushApplicator(
this.offsetX = (int)MathF.Max(MathF.Floor(region.Left), 0);
}
- ///
- internal override TPixel this[int x, int y]
+ internal TPixel this[int x, int y]
{
get
{
diff --git a/src/ImageSharp.Drawing/Processing/LinearGradientBrush.cs b/src/ImageSharp.Drawing/Processing/LinearGradientBrush.cs
index ebd1f844..5ad8d7ce 100644
--- a/src/ImageSharp.Drawing/Processing/LinearGradientBrush.cs
+++ b/src/ImageSharp.Drawing/Processing/LinearGradientBrush.cs
@@ -54,6 +54,7 @@ public override BrushApplicator CreateApplicator(
///
/// The linear gradient brush applicator.
///
+ /// The pixel format.
private sealed class LinearGradientBrushApplicator : GradientBrushApplicator
where TPixel : unmanaged, IPixel
{
diff --git a/src/ImageSharp.Drawing/Processing/PathGradientBrush.cs b/src/ImageSharp.Drawing/Processing/PathGradientBrush.cs
index 81df5b13..8621b781 100644
--- a/src/ImageSharp.Drawing/Processing/PathGradientBrush.cs
+++ b/src/ImageSharp.Drawing/Processing/PathGradientBrush.cs
@@ -6,6 +6,7 @@
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
+using System.Threading;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
@@ -13,12 +14,10 @@ namespace SixLabors.ImageSharp.Drawing.Processing
{
///
/// Provides an implementation of a brush for painting gradients between multiple color positions in 2D coordinates.
- /// It works similarly with the class in System.Drawing.Drawing2D of the same name.
///
public sealed class PathGradientBrush : IBrush
{
private readonly IList edges;
-
private readonly Color centerColor;
private readonly bool hasSpecialCenterColor;
@@ -90,9 +89,13 @@ public BrushApplicator CreateApplicator(
ImageFrame source,
RectangleF region)
where TPixel : unmanaged, IPixel
- {
- return new PathGradientBrushApplicator(configuration, options, source, this.edges, this.centerColor, this.hasSpecialCenterColor);
- }
+ => new PathGradientBrushApplicator(
+ configuration,
+ options,
+ source,
+ this.edges,
+ this.centerColor,
+ this.hasSpecialCenterColor);
private static Color CalculateCenterColor(Color[] colors)
{
@@ -131,13 +134,11 @@ public Intersection(PointF point, float distance)
///
private class Edge
{
- private readonly Path path;
-
private readonly float length;
public Edge(Path path, Color startColor, Color endColor)
{
- this.path = path;
+ this.Path = path;
Vector2[] points = path.LineSegments.SelectMany(s => s.Flatten().ToArray()).Select(p => (Vector2)p).ToArray();
@@ -150,6 +151,8 @@ public Edge(Path path, Color startColor, Color endColor)
this.length = DistanceBetween(this.End, this.Start);
}
+ public Path Path { get; }
+
public PointF Start { get; }
public Vector4 StartColor { get; }
@@ -158,38 +161,39 @@ public Edge(Path path, Color startColor, Color endColor)
public Vector4 EndColor { get; }
- public Intersection? FindIntersection(PointF start, PointF end, MemoryAllocator allocator)
+ public Intersection? FindIntersection(
+ PointF start,
+ PointF end,
+ Span intersections,
+ Span orientations)
{
- // TODO: The number of max intersections is upper bound to the number of nodes of the path.
- // Normally these numbers would be small and could potentially be stackalloc rather than pooled.
- // Investigate performance beifit of checking length and choosing approach.
- using (IMemoryOwner memory = allocator.Allocate(this.path.MaxIntersections))
+ int pathIntersections = this.Path.FindIntersections(
+ start,
+ end,
+ intersections.Slice(0, this.Path.MaxIntersections),
+ orientations.Slice(0, this.Path.MaxIntersections));
+
+ if (pathIntersections == 0)
{
- Span buffer = memory.Memory.Span;
- int intersections = this.path.FindIntersections(start, end, buffer);
+ return null;
+ }
- if (intersections == 0)
- {
- return null;
- }
+ intersections = intersections.Slice(0, pathIntersections);
- buffer = buffer.Slice(0, intersections);
+ PointF minPoint = intersections[0];
+ var min = new Intersection(minPoint, ((Vector2)(minPoint - start)).LengthSquared());
+ for (int i = 1; i < intersections.Length; i++)
+ {
+ PointF point = intersections[i];
+ var current = new Intersection(point, ((Vector2)(point - start)).LengthSquared());
- PointF minPoint = buffer[0];
- var min = new Intersection(minPoint, ((Vector2)(minPoint - start)).LengthSquared());
- for (int i = 1; i < buffer.Length; i++)
+ if (min.Distance > current.Distance)
{
- PointF point = buffer[i];
- var current = new Intersection(point, ((Vector2)(point - start)).LengthSquared());
-
- if (min.Distance > current.Distance)
- {
- min = current;
- }
+ min = current;
}
-
- return min;
}
+
+ return min;
}
public Vector4 ColorAt(float distance)
@@ -205,7 +209,8 @@ public Vector4 ColorAt(float distance)
///
/// The path gradient brush applicator.
///
- private class PathGradientBrushApplicator : BrushApplicator
+ /// The pixel format.
+ private sealed class PathGradientBrushApplicator : BrushApplicator
where TPixel : unmanaged, IPixel
{
private readonly PointF center;
@@ -222,6 +227,16 @@ private class PathGradientBrushApplicator : BrushApplicator
private readonly TPixel transparentPixel;
+ private readonly MemoryAllocator allocator;
+
+ private readonly int maxIntersections;
+
+ private readonly int scalineWidth;
+
+ private readonly ThreadLocal threadContextData;
+
+ private bool isDisposed;
+
///
/// Initializes a new instance of the class.
///
@@ -247,14 +262,18 @@ public PathGradientBrushApplicator(
this.centerColor = (Vector4)centerColor;
this.hasSpecialCenterColor = hasSpecialCenterColor;
this.centerPixel = centerColor.ToPixel();
-
this.maxDistance = points.Select(p => (Vector2)(p - this.center)).Max(d => d.Length());
-
this.transparentPixel = Color.Transparent.ToPixel();
+
+ this.scalineWidth = source.Width;
+ this.maxIntersections = this.edges.Max(e => e.Path.MaxIntersections);
+ this.allocator = configuration.MemoryAllocator;
+ this.threadContextData = new ThreadLocal(
+ () => new ThreadContextData(this.allocator, this.scalineWidth, this.maxIntersections),
+ true);
}
- ///
- internal override TPixel this[int x, int y]
+ internal TPixel this[int x, int y, Span intersections, Span orientations]
{
get
{
@@ -268,18 +287,19 @@ public PathGradientBrushApplicator(
if (this.edges.Count == 3 && !this.hasSpecialCenterColor)
{
if (!FindPointOnTriangle(
- this.edges[0].Start,
- this.edges[1].Start,
- this.edges[2].Start,
- point,
- out float u,
- out float v))
+ this.edges[0].Start,
+ this.edges[1].Start,
+ this.edges[2].Start,
+ point,
+ out float u,
+ out float v))
{
return this.transparentPixel;
}
- Vector4 pointColor = ((1 - u - v) * this.edges[0].StartColor) + (u * this.edges[0].EndColor) +
- (v * this.edges[2].StartColor);
+ Vector4 pointColor = ((1 - u - v) * this.edges[0].StartColor)
+ + (u * this.edges[0].EndColor)
+ + (v * this.edges[2].StartColor);
TPixel px = default;
px.FromScaledVector4(pointColor);
@@ -289,7 +309,7 @@ public PathGradientBrushApplicator(
var direction = Vector2.Normalize(point - this.center);
PointF end = point + (PointF)(direction * this.maxDistance);
- (Edge edge, Intersection? info) = this.FindIntersection(point, end);
+ (Edge edge, Intersection? info) = this.FindIntersection(point, end, intersections, orientations);
if (!info.HasValue)
{
@@ -310,14 +330,72 @@ public PathGradientBrushApplicator(
}
}
- private (Edge edge, Intersection? info) FindIntersection(PointF start, PointF end)
+ ///
+ public override void Apply(Span scanline, int x, int y)
+ {
+ ThreadContextData contextData = this.threadContextData.Value;
+ Span amounts = contextData.AmountSpan.Slice(0, scanline.Length);
+ Span overlays = contextData.OverlaySpan.Slice(0, scanline.Length);
+ Span intersections = contextData.IntersectionsSpan;
+ Span orientations = contextData.OrientationsSpan;
+ float blendPercentage = this.Options.BlendPercentage;
+
+ // TODO: Remove bounds checks.
+ if (blendPercentage < 1)
+ {
+ for (int i = 0; i < scanline.Length; i++)
+ {
+ amounts[i] = scanline[i] * blendPercentage;
+ overlays[i] = this[x + i, y, intersections, orientations];
+ }
+ }
+ else
+ {
+ for (int i = 0; i < scanline.Length; i++)
+ {
+ amounts[i] = scanline[i];
+ overlays[i] = this[x + i, y, intersections, orientations];
+ }
+ }
+
+ Span destinationRow = this.Target.GetPixelRowSpan(y).Slice(x, scanline.Length);
+ this.Blender.Blend(this.Configuration, destinationRow, destinationRow, overlays, amounts);
+ }
+
+ ///
+ protected override void Dispose(bool disposing)
+ {
+ if (this.isDisposed)
+ {
+ return;
+ }
+
+ base.Dispose(disposing);
+
+ if (disposing)
+ {
+ foreach (ThreadContextData data in this.threadContextData.Values)
+ {
+ data.Dispose();
+ }
+
+ this.threadContextData.Dispose();
+ }
+
+ this.isDisposed = true;
+ }
+
+ private (Edge edge, Intersection? info) FindIntersection(
+ PointF start,
+ PointF end,
+ Span intersections,
+ Span orientations)
{
(Edge edge, Intersection? info) closest = default;
- MemoryAllocator allocator = this.Configuration.MemoryAllocator;
foreach (Edge edge in this.edges)
{
- Intersection? intersection = edge.FindIntersection(start, end, allocator);
+ Intersection? intersection = edge.FindIntersection(start, end, intersections, orientations);
if (!intersection.HasValue)
{
@@ -366,6 +444,43 @@ private static bool FindPointOnTriangle(PointF v1, PointF v2, PointF v3, PointF
v = ((d00 * d21) - (d01 * d20)) / denominator;
return true;
}
+
+ private sealed class ThreadContextData : IDisposable
+ {
+ private bool isDisposed;
+ private readonly IMemoryOwner amountBuffer;
+ private readonly IMemoryOwner overlayBuffer;
+ private readonly IMemoryOwner intersectionsBuffer;
+ private readonly IMemoryOwner orientationsBuffer;
+
+ public ThreadContextData(MemoryAllocator allocator, int scanlineLength, int maxIntersections)
+ {
+ this.amountBuffer = allocator.Allocate(scanlineLength);
+ this.overlayBuffer = allocator.Allocate(scanlineLength);
+ this.intersectionsBuffer = allocator.Allocate(maxIntersections);
+ this.orientationsBuffer = allocator.Allocate(maxIntersections);
+ }
+
+ public Span AmountSpan => this.amountBuffer.Memory.Span;
+
+ public Span OverlaySpan => this.overlayBuffer.Memory.Span;
+
+ public Span IntersectionsSpan => this.intersectionsBuffer.Memory.Span;
+
+ public Span OrientationsSpan => this.orientationsBuffer.Memory.Span;
+
+ public void Dispose()
+ {
+ if (!this.isDisposed)
+ {
+ this.isDisposed = true;
+ this.amountBuffer.Dispose();
+ this.overlayBuffer.Dispose();
+ this.intersectionsBuffer.Dispose();
+ this.orientationsBuffer.Dispose();
+ }
+ }
+ }
}
}
}
diff --git a/src/ImageSharp.Drawing/Processing/PatternBrush.cs b/src/ImageSharp.Drawing/Processing/PatternBrush.cs
index 1792a2d7..6a609ee5 100644
--- a/src/ImageSharp.Drawing/Processing/PatternBrush.cs
+++ b/src/ImageSharp.Drawing/Processing/PatternBrush.cs
@@ -4,6 +4,7 @@
using System;
using System.Buffers;
using System.Numerics;
+using System.Threading;
using SixLabors.ImageSharp.Drawing.Utilities;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
@@ -31,7 +32,7 @@ namespace SixLabors.ImageSharp.Drawing.Processing
/// 0
///
///
- public class PatternBrush : IBrush
+ public sealed class PatternBrush : IBrush
{
///
/// The pattern.
@@ -103,13 +104,15 @@ public BrushApplicator CreateApplicator(
///
/// The pattern brush applicator.
///
- private class PatternBrushApplicator : BrushApplicator
+ /// The pixel format.
+ private sealed class PatternBrushApplicator : BrushApplicator
where TPixel : unmanaged, IPixel
{
- ///
- /// The pattern.
- ///
private readonly DenseMatrix pattern;
+ private readonly MemoryAllocator allocator;
+ private readonly int scalineWidth;
+ private readonly ThreadLocal threadContextData;
+ private bool isDisposed;
///
/// Initializes a new instance of the class.
@@ -126,10 +129,14 @@ public PatternBrushApplicator(
: base(configuration, options, source)
{
this.pattern = pattern;
+ this.scalineWidth = source.Width;
+ this.allocator = configuration.MemoryAllocator;
+ this.threadContextData = new ThreadLocal(
+ () => new ThreadContextData(this.allocator, this.scalineWidth),
+ true);
}
- ///
- internal override TPixel this[int x, int y]
+ internal TPixel this[int x, int y]
{
get
{
@@ -145,29 +152,74 @@ public PatternBrushApplicator(
public override void Apply(Span scanline, int x, int y)
{
int patternY = y % this.pattern.Rows;
- MemoryAllocator memoryAllocator = this.Configuration.MemoryAllocator;
+ ThreadContextData contextData = this.threadContextData.Value;
+ Span amounts = contextData.AmountSpan.Slice(0, scanline.Length);
+ Span overlays = contextData.OverlaySpan.Slice(0, scanline.Length);
- using (IMemoryOwner amountBuffer = memoryAllocator.Allocate(scanline.Length))
- using (IMemoryOwner overlay = memoryAllocator.Allocate(scanline.Length))
+ for (int i = 0; i < scanline.Length; i++)
{
- Span amountSpan = amountBuffer.Memory.Span;
- Span overlaySpan = overlay.Memory.Span;
+ amounts[i] = NumericUtilities.ClampFloat(scanline[i] * this.Options.BlendPercentage, 0, 1F);
- for (int i = 0; i < scanline.Length; i++)
- {
- amountSpan[i] = NumericUtilities.ClampFloat(scanline[i] * this.Options.BlendPercentage, 0, 1F);
+ int patternX = (x + i) % this.pattern.Columns;
+ overlays[i] = this.pattern[patternY, patternX];
+ }
+
+ Span destinationRow = this.Target.GetPixelRowSpan(y).Slice(x, scanline.Length);
+ this.Blender.Blend(
+ this.Configuration,
+ destinationRow,
+ destinationRow,
+ overlays,
+ amounts);
+ }
- int patternX = (x + i) % this.pattern.Columns;
- overlaySpan[i] = this.pattern[patternY, patternX];
+ ///
+ protected override void Dispose(bool disposing)
+ {
+ if (this.isDisposed)
+ {
+ return;
+ }
+
+ base.Dispose(disposing);
+
+ if (disposing)
+ {
+ foreach (ThreadContextData data in this.threadContextData.Values)
+ {
+ data.Dispose();
}
- Span destinationRow = this.Target.GetPixelRowSpan(y).Slice(x, scanline.Length);
- this.Blender.Blend(
- this.Configuration,
- destinationRow,
- destinationRow,
- overlaySpan,
- amountSpan);
+ this.threadContextData.Dispose();
+ }
+
+ this.isDisposed = true;
+ }
+
+ private sealed class ThreadContextData : IDisposable
+ {
+ private bool isDisposed;
+ private readonly IMemoryOwner amountBuffer;
+ private readonly IMemoryOwner overlayBuffer;
+
+ public ThreadContextData(MemoryAllocator allocator, int scanlineLength)
+ {
+ this.amountBuffer = allocator.Allocate(scanlineLength);
+ this.overlayBuffer = allocator.Allocate(scanlineLength);
+ }
+
+ public Span AmountSpan => this.amountBuffer.Memory.Span;
+
+ public Span OverlaySpan => this.overlayBuffer.Memory.Span;
+
+ public void Dispose()
+ {
+ if (!this.isDisposed)
+ {
+ this.isDisposed = true;
+ this.amountBuffer.Dispose();
+ this.overlayBuffer.Dispose();
+ }
}
}
}
diff --git a/src/ImageSharp.Drawing/Processing/RadialGradientBrush.cs b/src/ImageSharp.Drawing/Processing/RadialGradientBrush.cs
index cefddda1..af725d98 100644
--- a/src/ImageSharp.Drawing/Processing/RadialGradientBrush.cs
+++ b/src/ImageSharp.Drawing/Processing/RadialGradientBrush.cs
@@ -83,8 +83,8 @@ public RadialGradientBrushApplicator(
/// As this is a circular gradient, the position on the gradient is based on
/// the distance of the point to the center.
///
- /// The X coordinate of the target pixel.
- /// The Y coordinate of the target pixel.
+ /// The x-coordinate of the target pixel.
+ /// The y-coordinate of the target pixel.
/// the position on the color gradient.
protected override float PositionOnGradient(float x, float y)
{
@@ -93,6 +93,7 @@ protected override float PositionOnGradient(float x, float y)
return distance / this.radius;
}
+ ///
public override void Apply(Span scanline, int x, int y)
{
// TODO: each row is symmetric across center, so we can calculate half of it and mirror it to improve performance.
diff --git a/src/ImageSharp.Drawing/Processing/RecolorBrush.cs b/src/ImageSharp.Drawing/Processing/RecolorBrush.cs
index c9993169..bf33642f 100644
--- a/src/ImageSharp.Drawing/Processing/RecolorBrush.cs
+++ b/src/ImageSharp.Drawing/Processing/RecolorBrush.cs
@@ -4,7 +4,7 @@
using System;
using System.Buffers;
using System.Numerics;
-using SixLabors.ImageSharp.Advanced;
+using System.Threading;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
@@ -13,7 +13,7 @@ namespace SixLabors.ImageSharp.Drawing.Processing
///
/// Provides an implementation of a brush that can recolor an image
///
- public class RecolorBrush : IBrush
+ public sealed class RecolorBrush : IBrush
{
///
/// Initializes a new instance of the class.
@@ -50,33 +50,28 @@ public BrushApplicator CreateApplicator(
ImageFrame source,
RectangleF region)
where TPixel : unmanaged, IPixel
- {
- return new RecolorBrushApplicator(
+ => new RecolorBrushApplicator(
configuration,
options,
source,
this.SourceColor.ToPixel(),
this.TargetColor.ToPixel(),
this.Threshold);
- }
///
/// The recolor brush applicator.
///
+ /// The pixel format.
private class RecolorBrushApplicator : BrushApplicator
where TPixel : unmanaged, IPixel
{
- ///
- /// The source color.
- ///
private readonly Vector4 sourceColor;
-
- ///
- /// The threshold.
- ///
private readonly float threshold;
-
private readonly TPixel targetColorPixel;
+ private readonly MemoryAllocator allocator;
+ private readonly int scalineWidth;
+ private readonly ThreadLocal threadContextData;
+ private bool isDisposed;
///
/// Initializes a new instance of the class.
@@ -105,10 +100,15 @@ public RecolorBrushApplicator(
var minColor = default(TPixel);
minColor.FromVector4(new Vector4(float.MinValue));
this.threshold = Vector4.DistanceSquared(maxColor.ToVector4(), minColor.ToVector4()) * threshold;
+
+ this.scalineWidth = source.Width;
+ this.allocator = configuration.MemoryAllocator;
+ this.threadContextData = new ThreadLocal(
+ () => new ThreadContextData(this.allocator, this.scalineWidth),
+ true);
}
- ///
- internal override TPixel this[int x, int y]
+ internal TPixel this[int x, int y]
{
get
{
@@ -132,32 +132,77 @@ public RecolorBrushApplicator(
///
public override void Apply(Span scanline, int x, int y)
{
- MemoryAllocator memoryAllocator = this.Configuration.MemoryAllocator;
+ ThreadContextData contextData = this.threadContextData.Value;
+ Span amounts = contextData.AmountSpan.Slice(0, scanline.Length);
+ Span overlays = contextData.OverlaySpan.Slice(0, scanline.Length);
- using (IMemoryOwner amountBuffer = memoryAllocator.Allocate(scanline.Length))
- using (IMemoryOwner overlay = memoryAllocator.Allocate(scanline.Length))
+ for (int i = 0; i < scanline.Length; i++)
{
- Span amountSpan = amountBuffer.Memory.Span;
- Span overlaySpan = overlay.Memory.Span;
+ amounts[i] = scanline[i] * this.Options.BlendPercentage;
- for (int i = 0; i < scanline.Length; i++)
- {
- amountSpan[i] = scanline[i] * this.Options.BlendPercentage;
+ int offsetX = x + i;
- int offsetX = x + i;
+ // No doubt this one can be optimized further but I can't imagine its
+ // actually being used and can probably be removed/internalized for now
+ overlays[i] = this[offsetX, y];
+ }
+
+ Span destinationRow = this.Target.GetPixelRowSpan(y).Slice(x, scanline.Length);
+ this.Blender.Blend(
+ this.Configuration,
+ destinationRow,
+ destinationRow,
+ overlays,
+ amounts);
+ }
+
+ ///
+ protected override void Dispose(bool disposing)
+ {
+ if (this.isDisposed)
+ {
+ return;
+ }
- // No doubt this one can be optimized further but I can't imagine its
- // actually being used and can probably be removed/internalized for now
- overlaySpan[i] = this[offsetX, y];
+ base.Dispose(disposing);
+
+ if (disposing)
+ {
+ foreach (ThreadContextData data in this.threadContextData.Values)
+ {
+ data.Dispose();
}
- Span destinationRow = this.Target.GetPixelRowSpan(y).Slice(x, scanline.Length);
- this.Blender.Blend(
- this.Configuration,
- destinationRow,
- destinationRow,
- overlaySpan,
- amountSpan);
+ this.threadContextData.Dispose();
+ }
+
+ this.isDisposed = true;
+ }
+
+ private sealed class ThreadContextData : IDisposable
+ {
+ private bool isDisposed;
+ private readonly IMemoryOwner amountBuffer;
+ private readonly IMemoryOwner overlayBuffer;
+
+ public ThreadContextData(MemoryAllocator allocator, int scanlineLength)
+ {
+ this.amountBuffer = allocator.Allocate(scanlineLength);
+ this.overlayBuffer = allocator.Allocate(scanlineLength);
+ }
+
+ public Span AmountSpan => this.amountBuffer.Memory.Span;
+
+ public Span OverlaySpan => this.overlayBuffer.Memory.Span;
+
+ public void Dispose()
+ {
+ if (!this.isDisposed)
+ {
+ this.isDisposed = true;
+ this.amountBuffer.Dispose();
+ this.overlayBuffer.Dispose();
+ }
}
}
}
diff --git a/src/ImageSharp.Drawing/Processing/SolidBrush.cs b/src/ImageSharp.Drawing/Processing/SolidBrush.cs
index f01cb6f7..3c5fba33 100644
--- a/src/ImageSharp.Drawing/Processing/SolidBrush.cs
+++ b/src/ImageSharp.Drawing/Processing/SolidBrush.cs
@@ -3,8 +3,7 @@
using System;
using System.Buffers;
-
-using SixLabors.ImageSharp.Advanced;
+using System.Threading;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
@@ -13,16 +12,13 @@ namespace SixLabors.ImageSharp.Drawing.Processing
///
/// Provides an implementation of a solid brush for painting solid color areas.
///
- public class SolidBrush : IBrush
+ public sealed class SolidBrush : IBrush
{
///
/// Initializes a new instance of the class.
///
/// The color.
- public SolidBrush(Color color)
- {
- this.Color = color;
- }
+ public SolidBrush(Color color) => this.Color = color;
///
/// Gets the color.
@@ -36,16 +32,19 @@ public BrushApplicator CreateApplicator(
ImageFrame source,
RectangleF region)
where TPixel : unmanaged, IPixel
- {
- return new SolidBrushApplicator(configuration, options, source, this.Color.ToPixel());
- }
+ => new SolidBrushApplicator(configuration, options, source, this.Color.ToPixel());
///
/// The solid brush applicator.
///
- private class SolidBrushApplicator : BrushApplicator
+ /// The pixel format.
+ private sealed class SolidBrushApplicator : BrushApplicator
where TPixel : unmanaged, IPixel
{
+ private readonly IMemoryOwner colors;
+ private readonly MemoryAllocator allocator;
+ private readonly int scalineWidth;
+ private readonly ThreadLocal threadContextData;
private bool isDisposed;
///
@@ -62,17 +61,56 @@ public SolidBrushApplicator(
TPixel color)
: base(configuration, options, source)
{
- this.Colors = configuration.MemoryAllocator.Allocate(source.Width);
- this.Colors.Memory.Span.Fill(color);
+ this.colors = configuration.MemoryAllocator.Allocate(source.Width);
+ this.colors.Memory.Span.Fill(color);
+ this.scalineWidth = source.Width;
+ this.allocator = configuration.MemoryAllocator;
+
+ // The threadlocal value is lazily invoked so there is no need to optionally create the type.
+ this.threadContextData = new ThreadLocal(
+ () => new ThreadContextData(this.allocator, this.scalineWidth),
+ true);
}
- ///
- /// Gets the colors.
- ///
- protected IMemoryOwner Colors { get; private set; }
+ ///
+ public override void Apply(Span scanline, int x, int y)
+ {
+ Span destinationRow = this.Target.GetPixelRowSpan(y).Slice(x);
- ///
- internal override TPixel this[int x, int y] => this.Colors.Memory.Span[x];
+ // Constrain the spans to each other
+ if (destinationRow.Length > scanline.Length)
+ {
+ destinationRow = destinationRow.Slice(0, scanline.Length);
+ }
+ else
+ {
+ scanline = scanline.Slice(0, destinationRow.Length);
+ }
+
+ Configuration configuration = this.Configuration;
+ if (this.Options.BlendPercentage == 1F)
+ {
+ // TODO: refactor the BlendPercentage == 1 logic to a separate, simpler BrushApplicator class.
+ this.Blender.Blend(configuration, destinationRow, destinationRow, this.colors.Memory.Span, scanline);
+ }
+ else
+ {
+ ThreadContextData contextData = this.threadContextData.Value;
+ Span amounts = contextData.AmountSpan.Slice(0, scanline.Length);
+
+ for (int i = 0; i < scanline.Length; i++)
+ {
+ amounts[i] = scanline[i] * this.Options.BlendPercentage;
+ }
+
+ this.Blender.Blend(
+ configuration,
+ destinationRow,
+ destinationRow,
+ this.colors.Memory.Span,
+ amounts);
+ }
+ }
///
protected override void Dispose(bool disposing)
@@ -84,52 +122,34 @@ protected override void Dispose(bool disposing)
if (disposing)
{
- this.Colors.Dispose();
+ this.colors.Dispose();
+ foreach (ThreadContextData data in this.threadContextData.Values)
+ {
+ data.Dispose();
+ }
+
+ this.threadContextData.Dispose();
}
- this.Colors = null;
this.isDisposed = true;
}
- ///
- public override void Apply(Span scanline, int x, int y)
+ private sealed class ThreadContextData : IDisposable
{
- Span destinationRow = this.Target.GetPixelRowSpan(y).Slice(x);
+ private bool isDisposed;
+ private readonly IMemoryOwner amountBuffer;
- // constrain the spans to each other
- if (destinationRow.Length > scanline.Length)
- {
- destinationRow = destinationRow.Slice(0, scanline.Length);
- }
- else
- {
- scanline = scanline.Slice(0, destinationRow.Length);
- }
+ public ThreadContextData(MemoryAllocator allocator, int scanlineLength)
+ => this.amountBuffer = allocator.Allocate(scanlineLength);
- Configuration configuration = this.Configuration;
- MemoryAllocator memoryAllocator = configuration.MemoryAllocator;
+ public Span AmountSpan => this.amountBuffer.Memory.Span;
- if (this.Options.BlendPercentage == 1f)
- {
- this.Blender.Blend(configuration, destinationRow, destinationRow, this.Colors.Memory.Span, scanline);
- }
- else
+ public void Dispose()
{
- using (IMemoryOwner amountBuffer = memoryAllocator.Allocate(scanline.Length))
+ if (!this.isDisposed)
{
- Span amountSpan = amountBuffer.Memory.Span;
-
- for (int i = 0; i < scanline.Length; i++)
- {
- amountSpan[i] = scanline[i] * this.Options.BlendPercentage;
- }
-
- this.Blender.Blend(
- configuration,
- destinationRow,
- destinationRow,
- this.Colors.Memory.Span,
- amountSpan);
+ this.isDisposed = true;
+ this.amountBuffer.Dispose();
}
}
}
diff --git a/src/ImageSharp.Drawing/Shapes/ClipperExtensions.cs b/src/ImageSharp.Drawing/Shapes/ClipPathExtensions.cs
similarity index 76%
rename from src/ImageSharp.Drawing/Shapes/ClipperExtensions.cs
rename to src/ImageSharp.Drawing/Shapes/ClipPathExtensions.cs
index 92cce8ca..53230639 100644
--- a/src/ImageSharp.Drawing/Shapes/ClipperExtensions.cs
+++ b/src/ImageSharp.Drawing/Shapes/ClipPathExtensions.cs
@@ -7,16 +7,16 @@
namespace SixLabors.ImageSharp.Drawing
{
///
- /// Clipping extensions for shapes
+ /// Path extensions to clip paths.
///
- public static class ClipperExtensions
+ public static class ClipPathExtensions
{
///
/// Clips the specified holes.
///
/// The shape.
/// The holes.
- /// Returns a new shape with the holes cliped out out the shape.
+ /// Returns a new shape with the holes clipped out of the shape.
public static IPath Clip(this IPath shape, IEnumerable holes)
{
var clipper = new Clipper();
@@ -34,7 +34,8 @@ public static IPath Clip(this IPath shape, IEnumerable holes)
///
/// The shape.
/// The holes.
- /// Returns a new shape with the holes cliped out out the shape.
- public static IPath Clip(this IPath shape, params IPath[] holes) => shape.Clip((IEnumerable)holes);
+ /// Returns a new shape with the holes clipped out of the shape.
+ public static IPath Clip(this IPath shape, params IPath[] holes)
+ => shape.Clip((IEnumerable)holes);
}
}
diff --git a/src/ImageSharp.Drawing/Shapes/ComplexPolygon.cs b/src/ImageSharp.Drawing/Shapes/ComplexPolygon.cs
index 5b13fdb2..4acaf615 100644
--- a/src/ImageSharp.Drawing/Shapes/ComplexPolygon.cs
+++ b/src/ImageSharp.Drawing/Shapes/ComplexPolygon.cs
@@ -6,18 +6,19 @@
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
-using SixLabors.ImageSharp.Drawing.Utilities;
namespace SixLabors.ImageSharp.Drawing
{
///
- /// Represents a complex polygon made up of one or more shapes overlayed on each other, where overlaps causes holes.
+ /// Represents a complex polygon made up of one or more shapes overlayed on each other,
+ /// where overlaps causes holes.
///
///
- public sealed class ComplexPolygon : IPath, IInternalPathOwner
+ public sealed class ComplexPolygon : IPath, IPathInternals, IInternalPathOwner
{
private readonly IPath[] paths;
- private List internalPaths = null;
+ private readonly List internalPaths;
+ private readonly float length;
///
/// Initializes a new instance of the class.
@@ -34,7 +35,10 @@ public ComplexPolygon(IEnumerable paths)
/// The paths.
public ComplexPolygon(params IPath[] paths)
{
- this.paths = paths ?? throw new ArgumentNullException(nameof(paths));
+ Guard.NotNull(paths, nameof(paths));
+
+ this.paths = paths;
+ this.internalPaths = new List(this.paths.Length);
if (paths.Length > 0)
{
@@ -43,235 +47,61 @@ public ComplexPolygon(params IPath[] paths)
float minY = float.MaxValue;
float maxY = float.MinValue;
float length = 0;
- int intersections = 0;
- foreach (IPath s in this.paths)
+ foreach (IPath p in this.paths)
{
- length += s.Length;
- if (s.Bounds.Left < minX)
+ if (p.Bounds.Left < minX)
{
- minX = s.Bounds.Left;
+ minX = p.Bounds.Left;
}
- if (s.Bounds.Right > maxX)
+ if (p.Bounds.Right > maxX)
{
- maxX = s.Bounds.Right;
+ maxX = p.Bounds.Right;
}
- if (s.Bounds.Top < minY)
+ if (p.Bounds.Top < minY)
{
- minY = s.Bounds.Top;
+ minY = p.Bounds.Top;
}
- if (s.Bounds.Bottom > maxY)
+ if (p.Bounds.Bottom > maxY)
{
- maxY = s.Bounds.Bottom;
+ maxY = p.Bounds.Bottom;
}
- intersections += s.MaxIntersections;
+ foreach (ISimplePath s in p.Flatten())
+ {
+ var ip = new InternalPath(s.Points, s.IsClosed);
+ length += ip.Length;
+ this.internalPaths.Add(ip);
+ }
}
- this.MaxIntersections = intersections;
- this.Length = length;
+ this.length = length;
this.Bounds = new RectangleF(minX, minY, maxX - minX, maxY - minY);
}
else
{
- this.MaxIntersections = 0;
- this.Length = 0;
+ this.length = 0;
this.Bounds = RectangleF.Empty;
}
this.PathType = PathTypes.Mixed;
}
- ///
- /// Gets the length of the path.
- ///
- public float Length { get; }
-
- ///
- /// Gets a value indicating whether this instance is closed, open or a composite path with a mixture of open and closed figures.
- ///
+ ///
public PathTypes PathType { get; }
///
- /// Gets the paths that make up this shape
+ /// Gets the collection of paths that make up this shape.
///
- ///
- /// The paths.
- ///
public IEnumerable Paths => this.paths;
- ///
- /// Gets the bounding box of this shape.
- ///
- ///
- /// The bounds.
- ///
+ ///
public RectangleF Bounds { get; }
- ///
- /// Gets the maximum number intersections that a shape can have when testing a line.
- ///
- ///
- /// The maximum intersections.
- ///
- public int MaxIntersections { get; }
-
- ///
- /// the distance of the point from the outline of the shape, if the value is negative it is inside the polygon bounds
- ///
- /// The point.
- ///
- /// Returns the distance from thr shape to the point
- ///
- ///
- /// Due to the clipping we did during construction we know that out shapes do not overlap at there edges
- /// therefore for a point to be in more that one we must be in a hole of another, theoretically this could
- /// then flip again to be in a outline inside a hole inside an outline :)
- ///
- public PointInfo Distance(PointF point)
- {
- float dist = float.MaxValue;
- PointInfo pointInfo = default;
- bool inside = false;
- foreach (IPath shape in this.Paths)
- {
- PointInfo d = shape.Distance(point);
-
- if (d.DistanceFromPath <= 0)
- {
- // we are inside a poly
- d.DistanceFromPath = -d.DistanceFromPath; // flip the sign
- inside ^= true; // flip the inside flag
- }
-
- if (d.DistanceFromPath < dist)
- {
- dist = d.DistanceFromPath;
- pointInfo = d;
- }
- }
-
- if (inside)
- {
- pointInfo.DistanceFromPath = -pointInfo.DistanceFromPath;
- }
-
- return pointInfo;
- }
-
- ///
- /// Based on a line described by and
- /// populate a buffer for all points on all the polygons, that make up this complex shape,
- /// that the line intersects.
- ///
- /// The start point of the line.
- /// The end point of the line.
- /// The buffer that will be populated with intersections.
- /// The offset within the buffer
- ///
- /// The number of intersections populated into the buffer.
- ///
- public int FindIntersections(PointF start, PointF end, PointF[] buffer, int offset)
- => this.FindIntersections(start, end, buffer, offset, IntersectionRule.OddEven);
-
- ///
- public int FindIntersections(PointF start, PointF end, Span buffer)
- => this.FindIntersections(start, end, buffer, IntersectionRule.OddEven);
-
- ///
- /// Based on a line described by and
- /// populate a buffer for all points on all the polygons, that make up this complex shape,
- /// that the line intersects.
- ///
- /// The start point of the line.
- /// The end point of the line.
- /// The buffer that will be populated with intersections.
- /// The offset within the buffer
- /// The intersection rule to use
- ///
- /// The number of intersections populated into the buffer.
- ///
- public int FindIntersections(PointF start, PointF end, PointF[] buffer, int offset, IntersectionRule intersectionRule)
- {
- Span subBuffer = buffer.AsSpan(offset);
- return this.FindIntersections(start, end, subBuffer, intersectionRule);
- }
-
- ///
- public int FindIntersections(PointF start, PointF end, Span buffer, IntersectionRule intersectionRule)
- {
- this.EnsureInternalPathsInitalized();
-
- int totalAdded = 0;
- InternalPath.PointOrientation[] orientations = ArrayPool.Shared.Rent(buffer.Length); // the largest number of intersections of any sub path of the set is the max size with need for this buffer.
- Span orientationsSpan = orientations;
- try
- {
- foreach (var ip in this.internalPaths)
- {
- Span subBuffer = buffer.Slice(totalAdded);
- Span subOrientationsSpan = orientationsSpan.Slice(totalAdded);
-
- var position = ip.FindIntersectionsWithOrientation(start, end, subBuffer, subOrientationsSpan);
- totalAdded += position;
- }
-
- Span distances = stackalloc float[totalAdded];
- for (int i = 0; i < totalAdded; i++)
- {
- distances[i] = Vector2.DistanceSquared(start, buffer[i]);
- }
-
- var activeBuffer = buffer.Slice(0, totalAdded);
- var activeOrientationsSpan = orientationsSpan.Slice(0, totalAdded);
- SortUtility.Sort(distances, activeBuffer, activeOrientationsSpan);
-
- if (intersectionRule == IntersectionRule.Nonzero)
- {
- totalAdded = InternalPath.ApplyNonZeroIntersectionRules(activeBuffer, activeOrientationsSpan);
- }
- }
- finally
- {
- ArrayPool.Shared.Return(orientations);
- }
-
- return totalAdded;
- }
-
- private void EnsureInternalPathsInitalized()
- {
- if (this.internalPaths == null)
- {
- lock (this.paths)
- {
- if (this.internalPaths == null)
- {
- this.internalPaths = new List(this.paths.Length);
-
- foreach (var p in this.paths)
- {
- foreach (var s in p.Flatten())
- {
- var ip = new InternalPath(s.Points, s.IsClosed);
- this.internalPaths.Add(ip);
- }
- }
- }
- }
- }
- }
-
- ///
- /// Determines whether the contains the specified point
- ///
- /// The point.
- ///
- /// true if the contains the specified point; otherwise, false.
- ///
+ ///
public bool Contains(PointF point)
{
bool inside = false;
@@ -286,18 +116,12 @@ public bool Contains(PointF point)
return inside;
}
- ///
- /// Transforms the shape using the specified matrix.
- ///
- /// The matrix.
- ///
- /// A new shape with the matrix applied to it.
- ///
+ ///
public IPath Transform(Matrix3x2 matrix)
{
if (matrix.IsIdentity)
{
- // no transform to apply skip it
+ // No transform to apply skip it
return this;
}
@@ -311,12 +135,7 @@ public IPath Transform(Matrix3x2 matrix)
return new ComplexPolygon(shapes);
}
- ///
- /// Converts the into a simple linear path..
- ///
- ///
- /// Returns the current as simple linear path.
- ///
+ ///
public IEnumerable Flatten()
{
var paths = new List();
@@ -328,60 +147,44 @@ public IEnumerable Flatten()
return paths.ToArray();
}
- ///
- /// Converts a path to a closed path.
- ///
- ///
- /// Returns the path as a closed path.
- ///
+ ///
public IPath AsClosedPath()
{
if (this.PathType == PathTypes.Closed)
{
return this;
}
- else
- {
- var paths = new IPath[this.paths.Length];
- for (int i = 0; i < this.paths.Length; i++)
- {
- paths[i] = this.paths[i].AsClosedPath();
- }
- return new ComplexPolygon(paths);
+ var paths = new IPath[this.paths.Length];
+ for (int i = 0; i < this.paths.Length; i++)
+ {
+ paths[i] = this.paths[i].AsClosedPath();
}
+
+ return new ComplexPolygon(paths);
}
- ///
- /// Calculates the point a certain distance a path.
- ///
- /// The distance along the path to find details of.
- ///
- /// Returns details about a point along a path.
- ///
- public SegmentInfo PointAlongPath(float distanceAlongPath)
+ ///
+ SegmentInfo IPathInternals.PointAlongPath(float distance)
{
- distanceAlongPath = distanceAlongPath % this.Length;
-
- foreach (IPath p in this.Paths)
+ distance %= this.length;
+ foreach (InternalPath p in this.internalPaths)
{
- if (p.Length >= distanceAlongPath)
+ if (p.Length >= distance)
{
- return p.PointAlongPath(distanceAlongPath);
+ return p.PointAlongPath(distance);
}
- // reduce it before trying the next path
- distanceAlongPath -= p.Length;
+ // Reduce it before trying the next path
+ distance -= p.Length;
}
+ // TODO: Perf. Throwhelper
throw new InvalidOperationException("Should not be possible to reach this line");
}
///
IReadOnlyList IInternalPathOwner.GetRingsAsInternalPath()
- {
- this.EnsureInternalPathsInitalized();
- return this.internalPaths;
- }
+ => this.internalPaths;
}
}
diff --git a/src/ImageSharp.Drawing/Shapes/EllipsePolygon.cs b/src/ImageSharp.Drawing/Shapes/EllipsePolygon.cs
index 58fe33b0..8747ddfa 100644
--- a/src/ImageSharp.Drawing/Shapes/EllipsePolygon.cs
+++ b/src/ImageSharp.Drawing/Shapes/EllipsePolygon.cs
@@ -8,9 +8,9 @@
namespace SixLabors.ImageSharp.Drawing
{
///
- /// A shape made up of a single path made up of one of more s
+ /// An elliptical shape made up of a single path made up of one of more s.
///
- public class EllipsePolygon : IPath, ISimplePath, IInternalPathOwner
+ public sealed class EllipsePolygon : IPath, ISimplePath, IPathInternals, IInternalPathOwner
{
private readonly InternalPath innerPath;
private readonly CubicBezierLineSegment segment;
@@ -19,7 +19,7 @@ public class EllipsePolygon : IPath, ISimplePath, IInternalPathOwner
/// Initializes a new instance of the class.
///
/// The location the center of the ellipse will be placed.
- /// The width/hight of the final ellipse.
+ /// The width/height of the final ellipse.
public EllipsePolygon(PointF location, SizeF size)
: this(CreateSegment(location, size))
{
@@ -38,8 +38,8 @@ public EllipsePolygon(PointF location, float radius)
///
/// Initializes a new instance of the class.
///
- /// The X coordinate of the center of the ellipse.
- /// The Y coordinate of the center of the ellipse.
+ /// The x-coordinate of the center of the ellipse.
+ /// The y-coordinate of the center of the ellipse.
/// The width the ellipse should have.
/// The height the ellipse should have.
public EllipsePolygon(float x, float y, float width, float height)
@@ -50,8 +50,8 @@ public EllipsePolygon(float x, float y, float width, float height)
///
/// Initializes a new instance of the class.
///
- /// The X coordinate of the center of the circle.
- /// The Y coordinate of the center of the circle.
+ /// The x-coordinate of the center of the circle.
+ /// The y-coordinate of the center of the circle.
/// The radius final circle.
public EllipsePolygon(float x, float y, float radius)
: this(new PointF(x, y), new SizeF(radius * 2, radius * 2))
@@ -64,129 +64,43 @@ private EllipsePolygon(CubicBezierLineSegment segment)
this.innerPath = new InternalPath(segment, true);
}
- ///
- /// Gets a value indicating whether this instance is a closed path.
- ///
- bool ISimplePath.IsClosed => true;
+ ///
+ public bool IsClosed => true;
- ///
- /// Gets the points that make up this simple linear path.
- ///
- ReadOnlyMemory ISimplePath.Points => this.innerPath.Points();
+ ///
+ public ReadOnlyMemory Points => this.innerPath.Points();
///
public RectangleF Bounds => this.innerPath.Bounds;
- ///
- /// Gets a value indicating whether this instance is closed, open or a composite path with a mixture of open and closed figures.
- ///
- PathTypes IPath.PathType => PathTypes.Closed;
+ ///
+ public PathTypes PathType => PathTypes.Closed;
- ///
- /// Gets the maximum number intersections that a shape can have when testing a line.
- ///
- int IPath.MaxIntersections => this.innerPath.PointCount;
+ ///
+ public IPath Transform(Matrix3x2 matrix) => matrix.IsIdentity
+ ? this
+ : new EllipsePolygon(this.segment.Transform(matrix));
- ///
- public float Length => this.innerPath.Length;
+ ///
+ public IPath AsClosedPath() => this;
///
- public PointInfo Distance(PointF point)
- {
- PointInfo dist = this.innerPath.DistanceFromPath(point);
- bool isInside = this.innerPath.PointInPolygon(point);
- if (isInside)
- {
- dist.DistanceFromPath *= -1;
- }
-
- return dist;
- }
-
- ///
- /// Transforms the rectangle using specified matrix.
- ///
- /// The matrix.
- ///
- /// A new path with the matrix applied to it.
- ///
- public EllipsePolygon Transform(Matrix3x2 matrix)
- {
- return matrix.IsIdentity
- ? this
- : new EllipsePolygon(this.segment.Transform(matrix));
- }
-
- ///
- /// Transforms the path using the specified matrix.
- ///
- /// The matrix.
- ///
- /// A new path with the matrix applied to it.
- ///
- IPath IPath.Transform(Matrix3x2 matrix) => this.Transform(matrix);
-
- ///
- /// Returns this polygon as a path
- ///
- /// This polygon as a path
- IPath IPath.AsClosedPath() => this;
-
- ///
- /// Converts the into a simple linear path..
- ///
- ///
- /// Returns the current as simple linear path.
- ///
public IEnumerable Flatten()
{
yield return this;
}
///
- int IPath.FindIntersections(PointF start, PointF end, PointF[] buffer, int offset, IntersectionRule intersectionRule)
- {
- Span subBuffer = buffer.AsSpan(offset);
- return this.innerPath.FindIntersections(start, end, subBuffer, intersectionRule);
- }
+ public bool Contains(PointF point) => this.innerPath.PointInPolygon(point);
- ///
- int IPath.FindIntersections(PointF start, PointF end, Span buffer, IntersectionRule intersectionRule)
- {
- return this.innerPath.FindIntersections(start, end, buffer, intersectionRule);
- }
+ ///
+ // TODO switch this out to a calculated algorithm
+ SegmentInfo IPathInternals.PointAlongPath(float distance)
+ => this.innerPath.PointAlongPath(distance);
///
- int IPath.FindIntersections(PointF start, PointF end, PointF[] buffer, int offset)
- {
- Span subBuffer = buffer.AsSpan(offset);
- return this.innerPath.FindIntersections(start, end, subBuffer, IntersectionRule.OddEven);
- }
-
- ///
- int IPath.FindIntersections(PointF start, PointF end, Span buffer)
- {
- return this.innerPath.FindIntersections(start, end, buffer, IntersectionRule.OddEven);
- }
-
- ///
- /// Determines whether the contains the specified point
- ///
- /// The point.
- ///
- /// true if the contains the specified point; otherwise, false.
- ///
- public bool Contains(PointF point)
- {
- return this.innerPath.PointInPolygon(point);
- }
-
- ///
- public SegmentInfo PointAlongPath(float distanceAlongPath)
- {
- // TODO switch this out to a calculated algorithum
- return this.innerPath.PointAlongPath(distanceAlongPath);
- }
+ IReadOnlyList IInternalPathOwner.GetRingsAsInternalPath()
+ => new[] { this.innerPath };
private static CubicBezierLineSegment CreateSegment(Vector2 location, SizeF size)
{
@@ -229,8 +143,5 @@ private static CubicBezierLineSegment CreateSegment(Vector2 location, SizeF size
return new CubicBezierLineSegment(points);
}
-
- ///
- IReadOnlyList IInternalPathOwner.GetRingsAsInternalPath() => new[] { this.innerPath };
}
}
diff --git a/src/ImageSharp.Drawing/Shapes/IInternalPathOwner.cs b/src/ImageSharp.Drawing/Shapes/IInternalPathOwner.cs
index 231279b4..576e033c 100644
--- a/src/ImageSharp.Drawing/Shapes/IInternalPathOwner.cs
+++ b/src/ImageSharp.Drawing/Shapes/IInternalPathOwner.cs
@@ -12,11 +12,9 @@ namespace SixLabors.ImageSharp.Drawing
internal interface IInternalPathOwner
{
///
- /// Returns the rings as a list of -s.
+ /// Returns the rings as a readonly collection of elements.
///
- /// The list
+ /// The .
IReadOnlyList GetRingsAsInternalPath();
-
- // TODO: We may want to reconfigure StyleCop rules for internals to avoid unnecessary redundant trivial code comments like in this file.
}
}
diff --git a/src/ImageSharp.Drawing/Shapes/IPath.cs b/src/ImageSharp.Drawing/Shapes/IPath.cs
index ef3e77c6..3f7df8b1 100644
--- a/src/ImageSharp.Drawing/Shapes/IPath.cs
+++ b/src/ImageSharp.Drawing/Shapes/IPath.cs
@@ -1,14 +1,13 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
-using System;
using System.Collections.Generic;
using System.Numerics;
namespace SixLabors.ImageSharp.Drawing
{
///
- /// Represents a logic path that can be drawn
+ /// Represents a logic path that can be drawn.
///
public interface IPath
{
@@ -18,38 +17,10 @@ public interface IPath
PathTypes PathType { get; }
///
- /// Gets the bounds enclosing the path
+ /// Gets the bounds enclosing the path.
///
RectangleF Bounds { get; }
- ///
- /// Gets the maximum number intersections that a shape can have when testing a line.
- ///
- int MaxIntersections { get; }
-
- ///
- /// Gets the length of the path.
- ///
- float Length { get; }
-
- ///
- /// Calculates the point a certain distance along a path.
- ///
- /// The distance along the path to find details of.
- ///
- /// Returns details about a point along a path.
- ///
- SegmentInfo PointAlongPath(float distanceAlongPath);
-
- ///
- /// Calculates the distance along and away from the path for a specified point.
- ///
- /// The point along the path.
- ///
- /// Returns details about the point and its distance away from the path.
- ///
- PointInfo Distance(PointF point);
-
///
/// Converts the into a simple linear path.
///
@@ -57,63 +28,11 @@ public interface IPath
IEnumerable Flatten();
///
- /// Based on a line described by and
- /// populate a buffer for all points on the polygon that the line intersects.
- ///
- /// The start point of the line.
- /// The end point of the line.
- /// The buffer that will be populated with intersections.
- /// The offset within the buffer to start.
- ///
- /// The number of intersections populated into the buffer.
- ///
- int FindIntersections(PointF start, PointF end, PointF[] buffer, int offset);
-
- ///
- /// Based on a line described by and
- /// populate a buffer for all points on the polygon that the line intersects.
- ///
- /// The start point of the line.
- /// The end point of the line.
- /// The buffer that will be populated with intersections.
- ///
- /// The number of intersections populated into the buffer.
- ///
- int FindIntersections(PointF start, PointF end, Span buffer);
-
- ///
- /// Based on a line described by and
- /// populate a buffer for all points on the polygon that the line intersects.
- ///
- /// The start point of the line.
- /// The end point of the line.
- /// The buffer that will be populated with intersections.
- /// The offset within the buffer to start.
- /// How intersections are handled
- ///
- /// The number of intersections populated into the buffer.
- ///
- int FindIntersections(PointF start, PointF end, PointF[] buffer, int offset, IntersectionRule intersectionRule);
-
- ///
- /// Based on a line described by and
- /// populate a buffer for all points on the polygon that the line intersects.
- ///
- /// The start point of the line.
- /// The end point of the line.
- /// The buffer that will be populated with intersections.
- /// How intersections are handled
- ///
- /// The number of intersections populated into the buffer.
- ///
- int FindIntersections(PointF start, PointF end, Span buffer, IntersectionRule intersectionRule);
-
- ///
- /// Determines whether the contains the specified point
+ /// Determines whether the contains the specified point.
///
/// The point.
///
- /// true if the contains the specified point; otherwise, false.
+ /// if the contains the specified point; otherwise, .
///
bool Contains(PointF point);
diff --git a/src/ImageSharp.Drawing/Shapes/IPathInternals.cs b/src/ImageSharp.Drawing/Shapes/IPathInternals.cs
new file mode 100644
index 00000000..fc6d3212
--- /dev/null
+++ b/src/ImageSharp.Drawing/Shapes/IPathInternals.cs
@@ -0,0 +1,20 @@
+// Copyright (c) Six Labors.
+// Licensed under the Apache License, Version 2.0.
+
+namespace SixLabors.ImageSharp.Drawing
+{
+ ///
+ /// An interface for internal operations we don't want to expose on .
+ ///
+ internal interface IPathInternals : IPath
+ {
+ ///
+ /// Returns information about a point at a given distance along a path.
+ ///
+ /// The distance along the path to return details for.
+ ///
+ /// The segment information.
+ ///
+ SegmentInfo PointAlongPath(float distance);
+ }
+}
diff --git a/src/ImageSharp.Drawing/Shapes/ISimplePath.cs b/src/ImageSharp.Drawing/Shapes/ISimplePath.cs
index d8ebdfa4..779e285b 100644
--- a/src/ImageSharp.Drawing/Shapes/ISimplePath.cs
+++ b/src/ImageSharp.Drawing/Shapes/ISimplePath.cs
@@ -1,13 +1,12 @@
-// Copyright (c) Six Labors.
+// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System;
-using System.Collections.Generic;
namespace SixLabors.ImageSharp.Drawing
{
///
- /// Represents a logic path that can be drawn
+ /// Represents a simple (non-composite) path defined by a series of points.
///
public interface ISimplePath
{
@@ -21,4 +20,4 @@ public interface ISimplePath
///
ReadOnlyMemory Points { get; }
}
-}
\ No newline at end of file
+}
diff --git a/src/ImageSharp.Drawing/Shapes/InternalPath.cs b/src/ImageSharp.Drawing/Shapes/InternalPath.cs
index 3f8cf4c3..2e1ba485 100644
--- a/src/ImageSharp.Drawing/Shapes/InternalPath.cs
+++ b/src/ImageSharp.Drawing/Shapes/InternalPath.cs
@@ -7,7 +7,6 @@
using System.Linq;
using System.Numerics;
using System.Runtime.CompilerServices;
-using SixLabors.ImageSharp.Drawing.Shapes.Rasterization;
using SixLabors.ImageSharp.Drawing.Utilities;
using SixLabors.ImageSharp.Memory;
@@ -97,27 +96,6 @@ private InternalPath(PointData[] points, bool isClosedPath)
}
}
- ///
- /// the orrientateion of an point form a line
- ///
- internal enum PointOrientation
- {
- ///
- /// Point is colienear
- ///
- Colinear = 0,
-
- ///
- /// Its clockwise
- ///
- Clockwise = 1,
-
- ///
- /// Its counter clockwise
- ///
- Counterclockwise = 2
- }
-
///
/// Gets the bounds.
///
@@ -139,109 +117,97 @@ internal enum PointOrientation
///
public int PointCount => this.points.Length;
- ///
- /// Calculates the distance from the path.
- ///
- /// The point.
- /// Returns the distance from the path
- public PointInfo DistanceFromPath(PointF point)
- {
- PointInfoInternal internalInfo = default;
- internalInfo.DistanceSquared = float.MaxValue; // Set it to max so that CalculateShorterDistance can reduce it back down
-
- int polyCorners = this.points.Length;
-
- if (!this.closedPath)
- {
- polyCorners -= 1;
- }
-
- int closestPoint = 0;
- for (int i = 0; i < polyCorners; i++)
- {
- int next = i + 1;
- if (this.closedPath && next == polyCorners)
- {
- next = 0;
- }
-
- if (this.CalculateShorterDistance(this.points[i].Point, this.points[next].Point, point, ref internalInfo))
- {
- closestPoint = i;
- }
- }
-
- return new PointInfo
- {
- DistanceAlongPath = this.points[closestPoint].TotalLength + Vector2.Distance(this.points[closestPoint].Point, internalInfo.PointOnLine),
- DistanceFromPath = MathF.Sqrt(internalInfo.DistanceSquared),
- SearchPoint = point,
- ClosestPointOnPath = internalInfo.PointOnLine
- };
- }
-
///
/// Based on a line described by and
/// populates a buffer for all points on the path that the line intersects.
///
- /// The start.
- /// The end.
- /// The buffer.
- /// number of intersections hit
- public int FindIntersections(PointF start, PointF end, Span buffer)
- => this.FindIntersections(start, end, buffer, IntersectionRule.OddEven);
+ /// The start position.
+ /// The end position.
+ /// The buffer for storing each intersection.
+ ///
+ /// The buffer for storing the orientation of each intersection.
+ /// Must be the same length as .
+ ///
+ /// The number of intersections found.
+ public int FindIntersections(PointF start, PointF end, Span intersections, Span orientations)
+ => this.FindIntersections(start, end, intersections, orientations, IntersectionRule.OddEven);
///
/// Based on a line described by and
/// populates a buffer for all points on the path that the line intersects.
///
- /// The start.
- /// The end.
- /// The buffer.
- /// Intersection rule types
- /// number of intersections hit
- public int FindIntersections(PointF start, PointF end, Span buffer, IntersectionRule intersectionRule)
+ /// The start position.
+ /// The end position.
+ /// The buffer for storing each intersection.
+ ///
+ /// The buffer for storing the orientation of each intersection.
+ /// Must be the same length as .
+ ///
+ /// How intersections should be handled.
+ /// The number of intersections found.
+ public int FindIntersections(
+ PointF start,
+ PointF end,
+ Span intersections,
+ Span orientations,
+ IntersectionRule intersectionRule)
{
- PointOrientation[] orientations = ArrayPool.Shared.Rent(buffer.Length);
- try
- {
- Span orientationsSpan = orientations.AsSpan(0, buffer.Length);
- var position = this.FindIntersectionsWithOrientation(start, end, buffer, orientationsSpan);
+ int position = this.FindIntersectionsWithOrientation(start, end, intersections, orientations);
- var activeBuffer = buffer.Slice(0, position);
- var activeOrientationsSpan = orientationsSpan.Slice(0, position);
+ Span activeBuffer = intersections.Slice(0, position);
+ Span activeOrientationsSpan = orientations.Slice(0, position);
- // intersection rules only really apply to closed paths
- if (intersectionRule == IntersectionRule.Nonzero && this.closedPath)
- {
- position = ApplyNonZeroIntersectionRules(activeBuffer, activeOrientationsSpan);
- }
-
- return position;
- }
- finally
+ // Intersection rules only really apply to closed paths
+ if (intersectionRule == IntersectionRule.Nonzero && this.closedPath)
{
- ArrayPool.Shared.Return(orientations);
+ position = ApplyNonZeroIntersectionRules(activeBuffer, activeOrientationsSpan);
}
+
+ return position;
}
///
/// Based on a line described by and
/// populates a buffer for all points on the path that the line intersects.
///
- /// The start.
- /// The end.
- /// The buffer.
- /// The buffer for storeing the orientation of each intersection.
+ /// The start position.
+ /// The end position.
+ /// The buffer for storing each intersection.
+ /// The buffer for storing the orientation of each intersection.
/// number of intersections hit
- public int FindIntersectionsWithOrientation(PointF start, PointF end, Span buffer, Span orientationsSpan)
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public int FindIntersectionsWithOrientation(
+ PointF start,
+ PointF end,
+ Span intersections,
+ Span orientations)
{
+ DebugGuard.IsTrue(intersections.Length == orientations.Length, nameof(orientations), "Intersection and orientation lengths must match.");
+
if (this.points.Length < 2)
{
return 0;
}
- int count = buffer.Length;
+ return this.FindIntersectionsWithOrientationInternal(start, end, intersections, orientations);
+ }
+
+ ///
+ /// Based on a line described by and
+ /// populates a buffer for all points on the path that the line intersects.
+ ///
+ /// The start position.
+ /// The end position.
+ /// The buffer for storing each intersection.
+ /// The buffer for storing the orientation of each intersection.
+ /// number of intersections hit
+ public int FindIntersectionsWithOrientationInternal(
+ PointF start,
+ PointF end,
+ Span intersections,
+ Span orientations)
+ {
+ int count = intersections.Length;
this.ClampPoints(ref start, ref end);
@@ -251,162 +217,168 @@ public int FindIntersectionsWithOrientation(PointF start, PointF end, Span.Shared.Rent(this.points.Length);
+ // Avoid pool overhead for short runs.
+ // This method can be called in high volume.
+ int pointsLength = this.points.Length;
+ int maxStackSize = 1024 / Unsafe.SizeOf();
+ PassPointData[] rentedFromPool = null;
+ Span buffer =
+ pointsLength > maxStackSize
+ ? (rentedFromPool = ArrayPool.Shared.Rent(pointsLength))
+ : stackalloc PassPointData[maxStackSize];
+
+ Span precalculate = buffer.Slice(0, pointsLength);
+
+ // Pre calculate relative orientations X places ahead and behind
+ Vector2 startToEnd = end - start;
+ PointOrientation prevOrientation = CalulateOrientation(startToEnd, this.points[polyCorners - 1].Point - end);
+ PointOrientation nextOrientation = CalulateOrientation(startToEnd, this.points[0].Point - end);
+ PointOrientation nextPlus1Orientation = CalulateOrientation(startToEnd, this.points[1].Point - end);
+
+ // iterate over all points and precalculate data about each, pre cacluating it relative orientation
+ for (int i = 0; i < polyCorners && count > 0; i++)
+ {
+ ref Segment edge = ref this.points[i].Segment;
+
+ // shift all orientations along but one place and fill in the last one
+ PointOrientation pointOrientation = nextOrientation;
+ nextOrientation = nextPlus1Orientation;
+ nextPlus1Orientation = CalulateOrientation(startToEnd, this.points[WrapArrayIndex(i + 2, this.points.Length)].Point - end);
+
+ // should this point cause the last matched point to be excluded
+ bool removeLastIntersection = nextOrientation == PointOrientation.Collinear &&
+ pointOrientation == PointOrientation.Collinear &&
+ nextPlus1Orientation != prevOrientation &&
+ (this.closedPath || i > 0) &&
+ (IsOnSegment(target, edge.Start) || IsOnSegment(target, edge.End));
+
+ // is there any chance the segments will intersection (do their bounding boxes touch)
+ bool doIntersect = false;
+ if (pointOrientation == PointOrientation.Collinear || pointOrientation != nextOrientation)
+ {
+ doIntersect = (edge.Min.X - Epsilon) <= target.Max.X &&
+ (edge.Max.X + Epsilon) >= target.Min.X &&
+ (edge.Min.Y - Epsilon) <= target.Max.Y &&
+ (edge.Max.Y + Epsilon) >= target.Min.Y;
+ }
- Span precaclulateSpan = precaclulate.AsSpan(0, this.points.Length);
+ precalculate[i] = new PassPointData
+ {
+ RemoveLastIntersectionAndSkip = removeLastIntersection,
+ RelativeOrientation = pointOrientation,
+ DoIntersect = doIntersect
+ };
- try
+ prevOrientation = pointOrientation;
+ }
+
+ // seed the last point for deduping at begining of closed line
+ if (this.closedPath)
{
- // pre calculate relative orientations X places ahead and behind
- Vector2 startToEnd = end - start;
- PointOrientation prevOrientation = CalulateOrientation(startToEnd, this.points[polyCorners - 1].Point - end);
- PointOrientation nextOrientation = CalulateOrientation(startToEnd, this.points[0].Point - end);
- PointOrientation nextPlus1Orientation = CalulateOrientation(startToEnd, this.points[1].Point - end);
+ int prev = polyCorners - 1;
- // iterate over all points and precalculate data about each, pre cacluating it relative orientation
- for (int i = 0; i < polyCorners && count > 0; i++)
+ if (precalculate[prev].DoIntersect)
{
- ref Segment edge = ref this.points[i].Segment;
-
- // shift all orientations along but one place and fill in the last one
- PointOrientation pointOrientation = nextOrientation;
- nextOrientation = nextPlus1Orientation;
- nextPlus1Orientation = CalulateOrientation(startToEnd, this.points[WrapArrayIndex(i + 2, this.points.Length)].Point - end);
-
- // should this point cause the last matched point to be excluded
- bool removeLastIntersection = nextOrientation == PointOrientation.Colinear &&
- pointOrientation == PointOrientation.Colinear &&
- nextPlus1Orientation != prevOrientation &&
- (this.closedPath || i > 0) &&
- (IsOnSegment(target, edge.Start) || IsOnSegment(target, edge.End));
-
- // is there any chance the segments will intersection (do their bounding boxes touch)
- bool doIntersect = false;
- if (pointOrientation == PointOrientation.Colinear || pointOrientation != nextOrientation)
- {
- doIntersect = (edge.Min.X - Epsilon) <= target.Max.X &&
- (edge.Max.X + Epsilon) >= target.Min.X &&
- (edge.Min.Y - Epsilon) <= target.Max.Y &&
- (edge.Max.Y + Epsilon) >= target.Min.Y;
- }
-
- precaclulateSpan[i] = new PassPointData
- {
- RemoveLastIntersectionAndSkip = removeLastIntersection,
- RelativeOrientation = pointOrientation,
- DoIntersect = doIntersect
- };
-
- prevOrientation = pointOrientation;
+ lastPoint = FindIntersection(this.points[prev].Segment, target);
}
+ }
- // seed the last point for deduping at begining of closed line
- if (this.closedPath)
- {
- int prev = polyCorners - 1;
+ for (int i = 0; i < polyCorners && count > 0; i++)
+ {
+ int next = WrapArrayIndex(i + 1, this.points.Length);
- if (precaclulateSpan[prev].DoIntersect)
+ if (precalculate[i].RemoveLastIntersectionAndSkip)
+ {
+ if (position > 0)
{
- lastPoint = FindIntersection(this.points[prev].Segment, target);
+ position--;
+ count++;
}
+
+ continue;
}
- for (int i = 0; i < polyCorners && count > 0; i++)
+ if (precalculate[i].DoIntersect)
{
- int next = WrapArrayIndex(i + 1, this.points.Length);
-
- if (precaclulateSpan[i].RemoveLastIntersectionAndSkip)
+ Vector2 point = FindIntersection(this.points[i].Segment, target);
+ if (point != MaxVector)
{
- if (position > 0)
+ if (lastPoint.Equivalent(point, Epsilon2))
{
- position--;
- count++;
- }
+ lastPoint = MaxVector;
- continue;
- }
+ int last = WrapArrayIndex(i - 1 + polyCorners, polyCorners);
- if (precaclulateSpan[i].DoIntersect)
- {
- Vector2 point = FindIntersection(this.points[i].Segment, target);
- if (point != MaxVector)
- {
- if (lastPoint.Equivalent(point, Epsilon2))
+ // hit the same point a second time do we need to remove the old one if just clipping
+ if (this.points[next].Point.Equivalent(point, Epsilon))
{
- lastPoint = MaxVector;
-
- int last = WrapArrayIndex(i - 1 + polyCorners, polyCorners);
-
- // hit the same point a second time do we need to remove the old one if just clipping
- if (this.points[next].Point.Equivalent(point, Epsilon))
- {
- next = i;
- }
+ next = i;
+ }
- if (this.points[last].Point.Equivalent(point, Epsilon))
- {
- last = i;
- }
+ if (this.points[last].Point.Equivalent(point, Epsilon))
+ {
+ last = i;
+ }
- PointOrientation side = precaclulateSpan[next].RelativeOrientation;
- PointOrientation side2 = precaclulateSpan[last].RelativeOrientation;
+ PointOrientation side = precalculate[next].RelativeOrientation;
+ PointOrientation side2 = precalculate[last].RelativeOrientation;
- if (side != side2)
- {
- // differnet side we skip adding as we are passing through it
- continue;
- }
+ if (side != side2)
+ {
+ // differnet side we skip adding as we are passing through it
+ continue;
}
-
- // only need to track this during odd non zero rulings
- orientationsSpan[position] = precaclulateSpan[i].RelativeOrientation;
- buffer[position] = point;
- position++;
- count--;
}
- lastPoint = point;
+ // only need to track this during odd non zero rulings
+ orientations[position] = precalculate[i].RelativeOrientation;
+ intersections[position] = point;
+ position++;
+ count--;
}
- else
- {
- lastPoint = MaxVector;
- }
- }
- Vector2 startVector = start;
- Span distances = stackalloc float[position];
- for (int i = 0; i < position; i++)
+ lastPoint = point;
+ }
+ else
{
- distances[i] = Vector2.DistanceSquared(startVector, buffer[i]);
+ lastPoint = MaxVector;
}
+ }
- var activeBuffer = buffer.Slice(0, position);
- var activeOrientationsSpan = orientationsSpan.Slice(0, position);
- SortUtility.Sort(distances, activeBuffer, activeOrientationsSpan);
-
- return position;
+ Vector2 startVector = start;
+ Span distances = stackalloc float[position];
+ for (int i = 0; i < distances.Length; i++)
+ {
+ distances[i] = Vector2.DistanceSquared(startVector, intersections[i]);
}
- finally
+
+ Span activeBuffer = intersections.Slice(0, position);
+ Span activeOrientationsSpan = orientations.Slice(0, position);
+ SortUtility.Sort(distances, activeBuffer, activeOrientationsSpan);
+
+ if (rentedFromPool != null)
{
- ArrayPool.Shared.Return(precaclulate);
+ ArrayPool.Shared.Return(rentedFromPool);
}
+
+ return position;
}
- internal static int ApplyNonZeroIntersectionRules(Span buffer, Span orientationsSpan)
+ internal static int ApplyNonZeroIntersectionRules(Span intersections, Span orientations)
{
int newpositions = 0;
int tracker = 0;
int diff = 0;
- for (int i = 0; i < buffer.Length; i++)
+ for (int i = 0; i < intersections.Length; i++)
{
bool include = tracker == 0;
- switch (orientationsSpan[i])
+ switch (orientations[i])
{
case PointOrientation.Counterclockwise:
diff = 1;
@@ -414,8 +386,7 @@ internal static int ApplyNonZeroIntersectionRules(Span buffer, Span buffer, Span.Shared.Rent(this.points.Length);
+ // If it hit any points then class it as inside
+ int max = this.points.Length;
+ PointF[] intersections = ArrayPool.Shared.Rent(max);
+ PointOrientation[] orientations = ArrayPool.Shared.Rent(max);
try
{
- int intersection = this.FindIntersections(point, new Vector2(this.Bounds.Left - 1, this.Bounds.Top - 1), buffer);
+ int intersection = this.FindIntersections(
+ point,
+ new Vector2(this.Bounds.Left - 1, this.Bounds.Top - 1),
+ intersections.AsSpan(0, max),
+ orientations.AsSpan(0, max));
+
if ((intersection & 1) == 1)
{
return true;
}
- // check if the point is on an intersection is it is then inside
+ // Check if the point is on an intersection is it is then inside
for (int i = 0; i < intersection; i++)
{
- if (buffer[i].Equivalent(point, Epsilon))
+ if (intersections[i].Equivalent(point, Epsilon))
{
return true;
}
@@ -471,7 +449,8 @@ public bool PointInPolygon(PointF point)
}
finally
{
- ArrayPool.Shared.Return(buffer);
+ ArrayPool.Shared.Return(intersections);
+ ArrayPool.Shared.Return(orientations);
}
return false;
@@ -490,9 +469,10 @@ public bool PointInPolygon(PointF point)
///
/// Returns details about a point along a path.
///
+ /// Thrown if no points found.
internal SegmentInfo PointAlongPath(float distanceAlongPath)
{
- distanceAlongPath = distanceAlongPath % this.Length;
+ distanceAlongPath %= this.Length;
int pointCount = this.PointCount;
if (this.closedPath)
{
@@ -515,13 +495,12 @@ internal SegmentInfo PointAlongPath(float distanceAlongPath)
Angle = (float)(Math.Atan2(diff.Y, diff.X) % (Math.PI * 2))
};
}
- else
- {
- distanceAlongPath -= this.points[next].Length;
- }
+
+ distanceAlongPath -= this.points[next].Length;
}
- throw new InvalidOperationException("should alwys reach a point along the path");
+ // TODO: Perf - Throwhelper.
+ throw new InvalidOperationException("Should always reach a point along the path.");
}
internal IMemoryOwner ExtractVertices(MemoryAllocator allocator)
@@ -551,12 +530,10 @@ private static bool IsOnSegment(Vector2 p, Vector2 q, Vector2 r)
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static bool IsOnSegment(in Segment seg, Vector2 q)
- {
- return (q.X - Epsilon2) <= seg.Max.X &&
- (q.X + Epsilon2) >= seg.Min.X &&
- (q.Y - Epsilon2) <= seg.Max.Y &&
- (q.Y + Epsilon2) >= seg.Min.Y;
- }
+ => (q.X - Epsilon2) <= seg.Max.X
+ && (q.X + Epsilon2) >= seg.Min.X
+ && (q.Y - Epsilon2) <= seg.Max.Y
+ && (q.Y + Epsilon2) >= seg.Min.Y;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static bool IsOnSegments(in Segment seg1, in Segment seg2, Vector2 q)
@@ -590,10 +567,7 @@ private static bool IsOnSegments(in Segment seg1, in Segment seg2, Vector2 q)
// Modulo is a very slow operation.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- private static int WrapArrayIndex(int i, int arrayLength)
- {
- return i < arrayLength ? i : i - arrayLength;
- }
+ private static int WrapArrayIndex(int i, int arrayLength) => i < arrayLength ? i : i - arrayLength;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static PointOrientation CalulateOrientation(Vector2 p, Vector2 q, Vector2 r)
@@ -606,7 +580,7 @@ private static PointOrientation CalulateOrientation(Vector2 p, Vector2 q, Vector
if (val > -Epsilon && val < Epsilon)
{
- return PointOrientation.Colinear; // colinear
+ return PointOrientation.Collinear; // colinear
}
return (val > 0) ? PointOrientation.Clockwise : PointOrientation.Counterclockwise; // clock or counterclock wise
@@ -621,7 +595,7 @@ private static PointOrientation CalulateOrientation(Vector2 qp, Vector2 rq)
if (val > -Epsilon && val < Epsilon)
{
- return PointOrientation.Colinear; // colinear
+ return PointOrientation.Collinear; // colinear
}
return (val > 0) ? PointOrientation.Clockwise : PointOrientation.Counterclockwise; // clock or counterclock wise
@@ -727,7 +701,7 @@ private static PointData[] Simplify(ReadOnlyMemory vectors, bool isClose
results.Add(new PointData
{
Point = points[0],
- Orientation = PointOrientation.Colinear,
+ Orientation = PointOrientation.Collinear,
Length = 0
});
}
@@ -745,7 +719,7 @@ private static PointData[] Simplify(ReadOnlyMemory vectors, bool isClose
new PointData
{
Point = points[0],
- Orientation = PointOrientation.Colinear,
+ Orientation = PointOrientation.Collinear,
Segment = new Segment(points[0], points[next]),
Length = 0,
TotalLength = 0
@@ -776,7 +750,7 @@ private static PointData[] Simplify(ReadOnlyMemory vectors, bool isClose
{
int next = WrapArrayIndex(i + 1, polyCorners);
PointOrientation or = CalulateOrientation(lastPoint, points[i], points[next]);
- if (or == PointOrientation.Colinear && next != 0)
+ if (or == PointOrientation.Collinear && next != 0)
{
continue;
}
@@ -797,7 +771,7 @@ private static PointData[] Simplify(ReadOnlyMemory vectors, bool isClose
if (isClosed && removeCloseAndCollinear)
{
// walk back removing collinear points
- while (results.Count > 2 && results.Last().Orientation == PointOrientation.Colinear)
+ while (results.Count > 2 && results.Last().Orientation == PointOrientation.Collinear)
{
results.RemoveAt(results.Count - 1);
}
@@ -857,69 +831,6 @@ private void ClampPoints(ref PointF start, ref PointF end)
}
}
- ///
- /// Calculate any shorter distances along the path.
- ///
- /// The start position.
- /// The end position.
- /// The current point.
- /// The info.
- ///
- /// The .
- ///
- private bool CalculateShorterDistance(Vector2 start, Vector2 end, Vector2 point, ref PointInfoInternal info)
- {
- Vector2 diffEnds = end - start;
-
- float lengthSquared = diffEnds.LengthSquared();
- Vector2 diff = point - start;
-
- Vector2 multiplied = diff * diffEnds;
- float u = (multiplied.X + multiplied.Y) / lengthSquared;
-
- if (u > 1)
- {
- u = 1;
- }
- else if (u < 0)
- {
- u = 0;
- }
-
- Vector2 multipliedByU = diffEnds * u;
-
- Vector2 pointOnLine = start + multipliedByU;
-
- Vector2 d = pointOnLine - point;
-
- float dist = d.LengthSquared();
-
- if (info.DistanceSquared > dist)
- {
- info.DistanceSquared = dist;
- info.PointOnLine = pointOnLine;
- return true;
- }
-
- return false;
- }
-
- ///
- /// Contains information about the current point.
- ///
- private struct PointInfoInternal
- {
- ///
- /// The distance squared.
- ///
- public float DistanceSquared;
-
- ///
- /// The point on the current line.
- ///
- public PointF PointOnLine;
- }
-
private struct PointData
{
public PointF Point;
diff --git a/src/ImageSharp.Drawing/Shapes/Outliner.cs b/src/ImageSharp.Drawing/Shapes/OutlinePathExtensions.cs
similarity index 90%
rename from src/ImageSharp.Drawing/Shapes/Outliner.cs
rename to src/ImageSharp.Drawing/Shapes/OutlinePathExtensions.cs
index e226849f..02887b4c 100644
--- a/src/ImageSharp.Drawing/Shapes/Outliner.cs
+++ b/src/ImageSharp.Drawing/Shapes/OutlinePathExtensions.cs
@@ -13,7 +13,7 @@ namespace SixLabors.ImageSharp.Drawing
///
/// Path extensions to generate outlines of paths.
///
- public static class Outliner
+ public static class OutlinePathExtensions
{
private const double MiterOffsetDelta = 20;
private const float ScalingFactor = 1000.0f;
@@ -36,9 +36,7 @@ public static IPath GenerateOutline(this IPath path, float width, float[] patter
/// The pattern made of multiples of the width.
/// A new path representing the outline.
public static IPath GenerateOutline(this IPath path, float width, ReadOnlySpan pattern)
- {
- return path.GenerateOutline(width, pattern, false);
- }
+ => path.GenerateOutline(width, pattern, false);
///
/// Generates a outline of the path with alternating on and off segments based on the pattern.
@@ -218,7 +216,7 @@ public static IPath GenerateOutline(this IPath path, float width, JointStyle joi
return ExecuteOutliner(width, offset);
}
- private static IPath ExecuteOutliner(float width, ClipperOffset offset)
+ private static ComplexPolygon ExecuteOutliner(float width, ClipperOffset offset)
{
var tree = new List>();
offset.Execute(ref tree, width * ScalingFactor / 2);
@@ -233,41 +231,25 @@ private static IPath ExecuteOutliner(float width, ClipperOffset offset)
}
private static IntPoint ToPoint(this PointF vector)
- {
- return new IntPoint(vector.X * ScalingFactor, vector.Y * ScalingFactor);
- }
+ => new IntPoint(vector.X * ScalingFactor, vector.Y * ScalingFactor);
private static IntPoint ToPoint(this Vector2 vector)
- {
- return new IntPoint(vector.X * ScalingFactor, vector.Y * ScalingFactor);
- }
+ => new IntPoint(vector.X * ScalingFactor, vector.Y * ScalingFactor);
private static JoinType Convert(JointStyle style)
- {
- switch (style)
+ => style switch
{
- case JointStyle.Round:
- return JoinType.jtRound;
- case JointStyle.Miter:
- return JoinType.jtMiter;
- case JointStyle.Square:
- default:
- return JoinType.jtSquare;
- }
- }
+ JointStyle.Round => JoinType.jtRound,
+ JointStyle.Miter => JoinType.jtMiter,
+ _ => JoinType.jtSquare,
+ };
private static EndType Convert(EndCapStyle style)
- {
- switch (style)
+ => style switch
{
- case EndCapStyle.Round:
- return EndType.etOpenRound;
- case EndCapStyle.Square:
- return EndType.etOpenSquare;
- case EndCapStyle.Butt:
- default:
- return EndType.etOpenButt;
- }
- }
+ EndCapStyle.Round => EndType.etOpenRound,
+ EndCapStyle.Square => EndType.etOpenSquare,
+ _ => EndType.etOpenButt,
+ };
}
}
diff --git a/src/ImageSharp.Drawing/Shapes/Path.cs b/src/ImageSharp.Drawing/Shapes/Path.cs
index 25eb858b..55244eab 100644
--- a/src/ImageSharp.Drawing/Shapes/Path.cs
+++ b/src/ImageSharp.Drawing/Shapes/Path.cs
@@ -9,10 +9,10 @@
namespace SixLabors.ImageSharp.Drawing
{
///
- /// A aggregate of s making a single logical path
+ /// A aggregate of s making a single logical path.
///
///
- public class Path : IPath, ISimplePath, IInternalPathOwner
+ public class Path : IPath, ISimplePath, IPathInternals, IInternalPathOwner
{
private readonly ILineSegment[] lineSegments;
private InternalPath innerPath;
@@ -40,48 +40,33 @@ public Path(Path path)
///
/// The segments.
public Path(params ILineSegment[] segments)
- {
- this.lineSegments = segments ?? throw new ArgumentNullException(nameof(segments));
- }
-
- ///
- /// Gets the length of the path.
- ///
- public float Length => this.InnerPath.Length;
+ => this.lineSegments = segments ?? throw new ArgumentNullException(nameof(segments));
- ///
- /// Gets a value indicating whether this instance is a closed path.
- ///
+ ///
bool ISimplePath.IsClosed => this.IsClosed;
- ///
- /// Gets the points that make up this simple linear path.
- ///
- ReadOnlyMemory ISimplePath.Points => this.InnerPath.Points();
+ ///
+ public virtual bool IsClosed => false;
+
+ ///
+ public ReadOnlyMemory Points => this.InnerPath.Points();
///
public RectangleF Bounds => this.InnerPath.Bounds;
- ///
- /// Gets a value indicating whether this instance is closed, open or a composite path with a mixture of open and closed figures.
- ///
+ ///
public PathTypes PathType => this.IsClosed ? PathTypes.Open : PathTypes.Closed;
///
/// Gets the maximum number intersections that a shape can have when testing a line.
///
- public int MaxIntersections => this.InnerPath.PointCount;
+ internal int MaxIntersections => this.InnerPath.PointCount;
///
- /// Gets the line segments
+ /// Gets readonly collection of line segments.
///
public IReadOnlyList LineSegments => this.lineSegments;
- ///
- /// Gets a value indicating whether this instance is a closed path.
- ///
- protected virtual bool IsClosed => false;
-
///
/// Gets or sets a value indicating whether close or collinear vertices should be removed. TEST ONLY!
///
@@ -91,29 +76,6 @@ public Path(params ILineSegment[] segments)
this.innerPath ??= new InternalPath(this.lineSegments, this.IsClosed, this.RemoveCloseAndCollinearPoints);
///
- public PointInfo Distance(PointF point)
- {
- PointInfo dist = this.InnerPath.DistanceFromPath(point);
-
- if (this.IsClosed)
- {
- bool isInside = this.InnerPath.PointInPolygon(point);
- if (isInside)
- {
- dist.DistanceFromPath *= -1;
- }
- }
-
- return dist;
- }
-
- ///
- /// Transforms the rectangle using specified matrix.
- ///
- /// The matrix.
- ///
- /// A new path with the matrix applied to it.
- ///
public virtual IPath Transform(Matrix3x2 matrix)
{
if (matrix.IsIdentity)
@@ -131,80 +93,46 @@ public virtual IPath Transform(Matrix3x2 matrix)
return new Path(segments);
}
- ///
- /// Returns this polygon as a path
- ///
- /// This polygon as a path
+ ///
public IPath AsClosedPath()
{
if (this.IsClosed)
{
return this;
}
- else
- {
- return new Polygon(this.LineSegments);
- }
- }
-
- ///
- /// Converts the into a simple linear path..
- ///
- ///
- /// Returns the current as simple linear path.
- ///
- public IEnumerable Flatten()
- {
- yield return this;
- }
-
- ///
- public int FindIntersections(PointF start, PointF end, PointF[] buffer, int offset)
- {
- return this.InnerPath.FindIntersections(start, end, buffer.AsSpan(offset));
- }
- ///
- public int FindIntersections(PointF start, PointF end, Span buffer)
- {
- return this.InnerPath.FindIntersections(start, end, buffer);
+ return new Polygon(this.LineSegments);
}
///
- public int FindIntersections(PointF start, PointF end, PointF[] buffer, int offset, IntersectionRule intersectionRule)
+ public IEnumerable Flatten()
{
- return this.InnerPath.FindIntersections(start, end, buffer.AsSpan(offset), intersectionRule);
+ yield return this;
}
///
- public int FindIntersections(PointF start, PointF end, Span buffer, IntersectionRule intersectionRule)
- {
- return this.InnerPath.FindIntersections(start, end, buffer, intersectionRule);
- }
+ public bool Contains(PointF point) => this.InnerPath.PointInPolygon(point);
///
- /// Determines whether the contains the specified point
+ /// Based on a line described by and
+ /// populate a buffer for all points on the polygon that the line intersects.
///
- /// The point.
+ /// The start position.
+ /// The end position.
+ /// The buffer for storing each intersection.
+ ///
+ /// The buffer for storing the orientation of each intersection.
+ /// Must be the same length as .
+ ///
///
- /// true if the contains the specified point; otherwise, false.
+ /// The number of intersections found.
///
- public bool Contains(PointF point)
- {
- return this.InnerPath.PointInPolygon(point);
- }
+ internal int FindIntersections(PointF start, PointF end, Span intersections, Span orientations)
+ => this.InnerPath.FindIntersections(start, end, intersections, orientations);
- ///
- /// Calculates the point a certain distance a path.
- ///
- /// The distance along the path to find details of.
- ///
- /// Returns details about a point along a path.
- ///
- public SegmentInfo PointAlongPath(float distanceAlongPath)
- {
- return this.InnerPath.PointAlongPath(distanceAlongPath);
- }
+ ///
+ SegmentInfo IPathInternals.PointAlongPath(float distance)
+ => this.InnerPath.PointAlongPath(distance);
///
IReadOnlyList IInternalPathOwner.GetRingsAsInternalPath() => new[] { this.InnerPath };
diff --git a/src/ImageSharp.Drawing/Shapes/PathBuilder.cs b/src/ImageSharp.Drawing/Shapes/PathBuilder.cs
index 7b308b3b..bd43dd88 100644
--- a/src/ImageSharp.Drawing/Shapes/PathBuilder.cs
+++ b/src/ImageSharp.Drawing/Shapes/PathBuilder.cs
@@ -1,4 +1,4 @@
-// Copyright (c) Six Labors.
+// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System;
@@ -289,19 +289,14 @@ private class Figure
public bool IsClosed { get; set; } = false;
- public bool IsEmpty => !this.segments.Any();
+ public bool IsEmpty => this.segments.Count == 0;
- public void AddSegment(ILineSegment segment)
- {
- this.segments.Add(segment);
- }
+ public void AddSegment(ILineSegment segment) => this.segments.Add(segment);
public IPath Build()
- {
- return this.IsClosed
- ? new Polygon(this.segments.ToArray())
- : new Path(this.segments.ToArray());
- }
+ => this.IsClosed
+ ? new Polygon(this.segments.ToArray())
+ : new Path(this.segments.ToArray());
}
}
}
diff --git a/src/ImageSharp.Drawing/Shapes/PathExtensions.Internal.cs b/src/ImageSharp.Drawing/Shapes/PathExtensions.Internal.cs
new file mode 100644
index 00000000..f0556591
--- /dev/null
+++ b/src/ImageSharp.Drawing/Shapes/PathExtensions.Internal.cs
@@ -0,0 +1,33 @@
+// Copyright (c) Six Labors.
+// Licensed under the Apache License, Version 2.0.
+
+using System;
+using System.Buffers;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace SixLabors.ImageSharp.Drawing
+{
+ ///
+ /// Convenience methods that can be applied to shapes and paths.
+ ///
+ public static partial class PathExtensions
+ {
+ ///
+ /// Create a path with the segment order reversed.
+ ///
+ /// The path to reverse.
+ /// The reversed .
+ internal static IPath Reverse(this IPath path)
+ {
+ IEnumerable segments = path.Flatten().Select(p => new LinearLineSegment(p.Points.ToArray().Reverse().ToArray()));
+ bool closed = false;
+ if (path is ISimplePath sp)
+ {
+ closed = sp.IsClosed;
+ }
+
+ return closed ? new Polygon(segments) : new Path(segments);
+ }
+ }
+}
diff --git a/src/ImageSharp.Drawing/Shapes/PathExtensions.cs b/src/ImageSharp.Drawing/Shapes/PathExtensions.cs
index 8c7b48e6..7a4478a1 100644
--- a/src/ImageSharp.Drawing/Shapes/PathExtensions.cs
+++ b/src/ImageSharp.Drawing/Shapes/PathExtensions.cs
@@ -1,18 +1,14 @@
-// Copyright (c) Six Labors.
+// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
-using System;
-using System.Buffers;
-using System.Collections.Generic;
-using System.Linq;
using System.Numerics;
namespace SixLabors.ImageSharp.Drawing
{
///
- /// Convenience methods that can be applied to shapes and paths
+ /// Convenience methods that can be applied to shapes and paths.
///
- public static class PathExtensions
+ public static partial class PathExtensions
{
///
/// Creates a path rotated by the specified radians around its center.
@@ -21,20 +17,16 @@ public static class PathExtensions
/// The radians to rotate the path.
/// A with a rotate transform applied.
public static IPathCollection Rotate(this IPathCollection path, float radians)
- {
- return path.Transform(Matrix3x2Extensions.CreateRotation(radians, RectangleF.Center(path.Bounds)));
- }
+ => path.Transform(Matrix3x2Extensions.CreateRotation(radians, RectangleF.Center(path.Bounds)));
///
/// Creates a path rotated by the specified degrees around its center.
///
/// The path to rotate.
- /// The degrees to rotate the path.
+ /// The degree to rotate the path.
/// A with a rotate transform applied.
- public static IPathCollection RotateDegree(this IPathCollection shape, float degrees)
- {
- return shape.Rotate((float)(Math.PI * degrees / 180.0));
- }
+ public static IPathCollection RotateDegree(this IPathCollection shape, float degree)
+ => shape.Rotate(GeometryUtilities.DegreeToRadian(degree));
///
/// Creates a path translated by the supplied postion
@@ -43,9 +35,7 @@ public static IPathCollection RotateDegree(this IPathCollection shape, float deg
/// The translation position.
/// A with a translate transform applied.
public static IPathCollection Translate(this IPathCollection path, PointF position)
- {
- return path.Transform(Matrix3x2.CreateTranslation(position));
- }
+ => path.Transform(Matrix3x2.CreateTranslation(position));
///
/// Creates a path translated by the supplied postion
@@ -55,9 +45,7 @@ public static IPathCollection Translate(this IPathCollection path, PointF positi
/// The amount to translate along the Y axis.
/// A with a translate transform applied.
public static IPathCollection Translate(this IPathCollection path, float x, float y)
- {
- return path.Translate(new PointF(x, y));
- }
+ => path.Translate(new PointF(x, y));
///
/// Creates a path translated by the supplied postion
@@ -67,9 +55,7 @@ public static IPathCollection Translate(this IPathCollection path, float x, floa
/// The amount to scale along the Y axis.
/// A with a translate transform applied.
public static IPathCollection Scale(this IPathCollection path, float scaleX, float scaleY)
- {
- return path.Transform(Matrix3x2.CreateScale(scaleX, scaleY, RectangleF.Center(path.Bounds)));
- }
+ => path.Transform(Matrix3x2.CreateScale(scaleX, scaleY, RectangleF.Center(path.Bounds)));
///
/// Creates a path translated by the supplied postion
@@ -78,9 +64,7 @@ public static IPathCollection Scale(this IPathCollection path, float scaleX, flo
/// The amount to scale along both the x and y axis.
/// A with a translate transform applied.
public static IPathCollection Scale(this IPathCollection path, float scale)
- {
- return path.Transform(Matrix3x2.CreateScale(scale, RectangleF.Center(path.Bounds)));
- }
+ => path.Transform(Matrix3x2.CreateScale(scale, RectangleF.Center(path.Bounds)));
///
/// Creates a path rotated by the specified radians around its center.
@@ -89,20 +73,16 @@ public static IPathCollection Scale(this IPathCollection path, float scale)
/// The radians to rotate the path.
/// A with a rotate transform applied.
public static IPath Rotate(this IPath path, float radians)
- {
- return path.Transform(Matrix3x2.CreateRotation(radians, RectangleF.Center(path.Bounds)));
- }
+ => path.Transform(Matrix3x2.CreateRotation(radians, RectangleF.Center(path.Bounds)));
///
/// Creates a path rotated by the specified degrees around its center.
///
/// The path to rotate.
- /// The degrees to rotate the path.
+ /// The degree to rotate the path.
/// A with a rotate transform applied.
- public static IPath RotateDegree(this IPath shape, float degrees)
- {
- return shape.Rotate((float)(Math.PI * degrees / 180.0));
- }
+ public static IPath RotateDegree(this IPath shape, float degree)
+ => shape.Rotate(GeometryUtilities.DegreeToRadian(degree));
///
/// Creates a path translated by the supplied postion
@@ -111,9 +91,7 @@ public static IPath RotateDegree(this IPath shape, float degrees)
/// The translation position.
/// A with a translate transform applied.
public static IPath Translate(this IPath path, PointF position)
- {
- return path.Transform(Matrix3x2.CreateTranslation(position));
- }
+ => path.Transform(Matrix3x2.CreateTranslation(position));
///
/// Creates a path translated by the supplied postion
@@ -123,9 +101,7 @@ public static IPath Translate(this IPath path, PointF position)
/// The amount to translate along the Y axis.
/// A with a translate transform applied.
public static IPath Translate(this IPath path, float x, float y)
- {
- return path.Translate(new Vector2(x, y));
- }
+ => path.Translate(new Vector2(x, y));
///
/// Creates a path translated by the supplied postion
@@ -135,9 +111,7 @@ public static IPath Translate(this IPath path, float x, float y)
/// The amount to scale along the Y axis.
/// A with a translate transform applied.
public static IPath Scale(this IPath path, float scaleX, float scaleY)
- {
- return path.Transform(Matrix3x2.CreateScale(scaleX, scaleY, RectangleF.Center(path.Bounds)));
- }
+ => path.Transform(Matrix3x2.CreateScale(scaleX, scaleY, RectangleF.Center(path.Bounds)));
///
/// Creates a path translated by the supplied postion
@@ -146,49 +120,6 @@ public static IPath Scale(this IPath path, float scaleX, float scaleY)
/// The amount to scale along both the x and y axis.
/// A with a translate transform applied.
public static IPath Scale(this IPath path, float scale)
- {
- return path.Transform(Matrix3x2.CreateScale(scale, RectangleF.Center(path.Bounds)));
- }
-
- ///
- /// Finds the intersections.
- ///
- /// The path.
- /// The start.
- /// The end.
- ///
- /// The points along the line the intersect with the boundaries of the polygon.
- ///
- public static IEnumerable FindIntersections(this IPath path, PointF start, PointF end)
- {
- PointF[] buffer = ArrayPool.Shared.Rent(path.MaxIntersections);
- try
- {
- int hits = path.FindIntersections(start, end, buffer, 0);
- PointF[] results = new PointF[hits];
- for (int i = 0; i < hits; i++)
- {
- results[i] = buffer[i];
- }
-
- return results;
- }
- finally
- {
- ArrayPool.Shared.Return(buffer);
- }
- }
-
- internal static IPath Reverse(this IPath path)
- {
- var segments = path.Flatten().Select(p => new LinearLineSegment(p.Points.ToArray().Reverse().ToArray()));
- bool closed = false;
- if (path is ISimplePath sp)
- {
- closed = sp.IsClosed;
- }
-
- return closed ? new Polygon(segments) : new Path(segments);
- }
+ => path.Transform(Matrix3x2.CreateScale(scale, RectangleF.Center(path.Bounds)));
}
}
diff --git a/src/ImageSharp.Drawing/Shapes/PointInfo.cs b/src/ImageSharp.Drawing/Shapes/PointInfo.cs
deleted file mode 100644
index 52c98edd..00000000
--- a/src/ImageSharp.Drawing/Shapes/PointInfo.cs
+++ /dev/null
@@ -1,31 +0,0 @@
-// Copyright (c) Six Labors.
-// Licensed under the Apache License, Version 2.0.
-
-namespace SixLabors.ImageSharp.Drawing
-{
- ///
- /// Returns meta data about the nearest point on a path from a vector
- ///
- public struct PointInfo
- {
- ///
- /// The search point
- ///
- public PointF SearchPoint;
-
- ///
- /// The distance along path is away from the start of the path
- ///
- public float DistanceAlongPath;
-
- ///
- /// The distance is away from .
- ///
- public float DistanceFromPath;
-
- ///
- /// The closest point to that lies on the path.
- ///
- public PointF ClosestPointOnPath;
- }
-}
diff --git a/src/ImageSharp.Drawing/Shapes/PointOrientation.cs b/src/ImageSharp.Drawing/Shapes/PointOrientation.cs
new file mode 100644
index 00000000..66b6e617
--- /dev/null
+++ b/src/ImageSharp.Drawing/Shapes/PointOrientation.cs
@@ -0,0 +1,26 @@
+// Copyright (c) Six Labors.
+// Licensed under the Apache License, Version 2.0.
+
+namespace SixLabors.ImageSharp.Drawing
+{
+ ///
+ /// Represents the orientation of a point from a line.
+ ///
+ internal enum PointOrientation
+ {
+ ///
+ /// The point is collinear.
+ ///
+ Collinear = 0,
+
+ ///
+ /// The point is clockwise.
+ ///
+ Clockwise = 1,
+
+ ///
+ /// The point is counter-clockwise.
+ ///
+ Counterclockwise = 2
+ }
+}
diff --git a/src/ImageSharp.Drawing/Shapes/Polygon.cs b/src/ImageSharp.Drawing/Shapes/Polygon.cs
index 62ca27d5..661dfe9a 100644
--- a/src/ImageSharp.Drawing/Shapes/Polygon.cs
+++ b/src/ImageSharp.Drawing/Shapes/Polygon.cs
@@ -1,4 +1,4 @@
-// Copyright (c) Six Labors.
+// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System.Collections.Generic;
@@ -7,7 +7,7 @@
namespace SixLabors.ImageSharp.Drawing
{
///
- /// A shape made up of a single path made up of one of more s
+ /// A shape made up of a single closed path made up of one of more s
///
public class Polygon : Path
{
@@ -47,18 +47,10 @@ internal Polygon(Path path)
{
}
- ///
- /// Gets a value indicating whether this instance is a closed path.
- ///
- protected override bool IsClosed => true;
+ ///
+ public override bool IsClosed => true;
- ///
- /// Transforms the rectangle using specified matrix.
- ///
- /// The matrix.
- ///
- /// A new shape with the matrix applied to it.
- ///
+ ///
public override IPath Transform(Matrix3x2 matrix)
{
if (matrix.IsIdentity)
@@ -66,7 +58,7 @@ public override IPath Transform(Matrix3x2 matrix)
return this;
}
- ILineSegment[] segments = new ILineSegment[this.LineSegments.Count];
+ var segments = new ILineSegment[this.LineSegments.Count];
int i = 0;
foreach (ILineSegment s in this.LineSegments)
{
diff --git a/src/ImageSharp.Drawing/Shapes/Rasterization/PolygonScanning.MD b/src/ImageSharp.Drawing/Shapes/Rasterization/PolygonScanning.MD
index 26966763..4206223f 100644
--- a/src/ImageSharp.Drawing/Shapes/Rasterization/PolygonScanning.MD
+++ b/src/ImageSharp.Drawing/Shapes/Rasterization/PolygonScanning.MD
@@ -2,7 +2,7 @@
Scanning is done with a variant of the ["Active Edge Table" algorithm](https://en.wikipedia.org/wiki/Scanline_rendering#Algorithm), that doesn't build a table beforehand, just maintains the list of currently active edges.
-After rasterizing polygons a collection of non-horizontal edges (ScanEdge) is extracted into ScanEdgeCollection. These are then sorted by minimum and maximum Y coordinate, which enables the maintanance of the Active Edge List as we traverse the collection from `minY` to `maxY`.
+After rasterizing polygons a collection of non-horizontal edges (ScanEdge) is extracted into ScanEdgeCollection. These are then sorted by minimum and maximum y-coordinate, which enables the maintanance of the Active Edge List as we traverse the collection from `minY` to `maxY`.
When intersecting a ScanEdge start (Y0) and end (Y1) intersections have special handling. Since these belong to vertices (connection points) sometimes we need to emit the intersection point 2 times. In other cases we do not want to emit it at all.
@@ -73,4 +73,4 @@ Edge In | Edge Out | Emit on "Edge In" | Emit on "Edge out"
⟶ | ↑ | 0 | 2
⟶ | ↓ | 0 | 1
⟶ | ⟵ | 0 | 0
-⟶ | ⟶ | 0 | 0
\ No newline at end of file
+⟶ | ⟶ | 0 | 0
diff --git a/src/ImageSharp.Drawing/Shapes/RectangularPolygon.cs b/src/ImageSharp.Drawing/Shapes/RectangularPolygon.cs
index d5834a7e..49cfdd5d 100644
--- a/src/ImageSharp.Drawing/Shapes/RectangularPolygon.cs
+++ b/src/ImageSharp.Drawing/Shapes/RectangularPolygon.cs
@@ -8,25 +8,24 @@
namespace SixLabors.ImageSharp.Drawing
{
///
- /// A way of optimizing drawing rectangles.
+ /// A polygon tha allows the optimized drawing of rectangles.
///
///
- public class RectangularPolygon : IPath, ISimplePath
+ public sealed class RectangularPolygon : IPath, ISimplePath, IPathInternals
{
private readonly Vector2 topLeft;
private readonly Vector2 bottomRight;
private readonly PointF[] points;
private readonly float halfLength;
private readonly float length;
- private readonly RectangleF bounds;
///
/// Initializes a new instance of the class.
///
- /// The x.
- /// The y.
- /// The width.
- /// The height.
+ /// The horizontal position of the rectangle.
+ /// The vertical position of the rectangle.
+ /// The width of the rectangle.
+ /// The height of the rectangle.
public RectangularPolygon(float x, float y, float width, float height)
: this(new PointF(x, y), new SizeF(width, height))
{
@@ -35,8 +34,12 @@ public RectangularPolygon(float x, float y, float width, float height)
///
/// Initializes a new instance of the class.
///
- /// The top left.
- /// The bottom right.
+ ///
+ /// The which specifies the rectangles top/left point in a two-dimensional plane.
+ ///
+ ///
+ /// The which specifies the rectangles bottom/right point in a two-dimensional plane.
+ ///
public RectangularPolygon(PointF topLeft, PointF bottomRight)
{
this.Location = topLeft;
@@ -54,16 +57,20 @@ public RectangularPolygon(PointF topLeft, PointF bottomRight)
this.halfLength = this.Size.Width + this.Size.Height;
this.length = this.halfLength * 2;
- this.bounds = new RectangleF(this.Location, this.Size);
+ this.Bounds = new RectangleF(this.Location, this.Size);
}
///
/// Initializes a new instance of the class.
///
- /// The location.
- /// The size.
- public RectangularPolygon(PointF location, SizeF size)
- : this(location, location + size)
+ ///
+ /// The which specifies the rectangles point in a two-dimensional plane.
+ ///
+ ///
+ /// The which specifies the rectangles height and width.
+ ///
+ public RectangularPolygon(PointF point, SizeF size)
+ : this(point, point + size)
{
}
@@ -79,123 +86,68 @@ public RectangularPolygon(RectangleF rectangle)
///
/// Gets the location.
///
- ///
- /// The location.
- ///
public PointF Location { get; }
///
- /// Gets the left.
+ /// Gets the x-coordinate of the left edge.
///
- ///
- /// The left.
- ///
- public float Left => this.topLeft.X;
+ public float Left => this.X;
///
- /// Gets the X.
+ /// Gets the x-coordinate.
///
- ///
- /// The X.
- ///
public float X => this.topLeft.X;
///
- /// Gets the right.
+ /// Gets the x-coordinate of the right edge.
///
- ///
- /// The right.
- ///
public float Right => this.bottomRight.X;
///
- /// Gets the top.
+ /// Gets the y-coordinate of the top edge.
///
- ///
- /// The top.
- ///
- public float Top => this.topLeft.Y;
+ public float Top => this.Y;
///
- /// Gets the Y.
+ /// Gets the y-coordinate.
///
- ///
- /// The Y.
- ///
public float Y => this.topLeft.Y;
///
- /// Gets the bottom.
+ /// Gets the y-coordinate of the bottom edge.
///
- ///
- /// The bottom.
- ///
public float Bottom => this.bottomRight.Y;
- ///
- /// Gets the bounding box of this shape.
- ///
- ///
- /// The bounds.
- ///
- RectangleF IPath.Bounds => this.bounds;
+ ///
+ public RectangleF Bounds { get; private set; }
- ///
- float IPath.Length => this.length;
+ ///
+ public bool IsClosed => true;
- ///
- /// Gets the maximum number intersections that a shape can have when testing a line.
- ///
- ///
- /// The maximum intersections.
- ///
- int IPath.MaxIntersections => 4;
-
- ///
- /// Gets a value indicating whether this instance is a closed path.
- ///
- bool ISimplePath.IsClosed => true;
-
- ///
- /// Gets the points that make this up as a simple linear path.
- ///
- ReadOnlyMemory ISimplePath.Points => this.points;
+ ///
+ public ReadOnlyMemory Points => this.points;
///
/// Gets the size.
///
- ///
- /// The size.
- ///
public SizeF Size { get; }
///
- /// Gets the size.
+ /// Gets the width.
///
- ///
- /// The size.
- ///
public float Width => this.Size.Width;
///
/// Gets the height.
///
- ///
- /// The height.
- ///
public float Height => this.Size.Height;
- ///
- /// Gets a value indicating whether this instance is closed, open or a composite path with a mixture of open and closed figures.
- ///
- PathTypes IPath.PathType => PathTypes.Closed;
+ ///
+ public PathTypes PathType => PathTypes.Closed;
///
- /// Gets the center.
+ /// Gets the center point.
///
- ///
- /// The center.
- ///
public PointF Center => (this.topLeft + this.bottomRight) / 2;
///
@@ -203,82 +155,13 @@ public RectangularPolygon(RectangleF rectangle)
///
/// The polygon to convert.
public static explicit operator RectangularPolygon(Polygon polygon)
- {
- return new RectangularPolygon(polygon.Bounds.X, polygon.Bounds.Y, polygon.Bounds.Width, polygon.Bounds.Height);
- }
+ => new RectangularPolygon(polygon.Bounds.X, polygon.Bounds.Y, polygon.Bounds.Width, polygon.Bounds.Height);
- ///
- /// Determines if the specified point is contained within the rectangular region defined by
- /// this .
- ///
- /// The point.
- ///
- /// The
- ///
+ ///
public bool Contains(PointF point)
- {
- return Vector2.Clamp(point, this.topLeft, this.bottomRight) == (Vector2)point;
- }
-
- ///
- public int FindIntersections(PointF start, PointF end, PointF[] buffer, int offset, IntersectionRule intersectionRule)
- {
- return this.FindIntersections(start, end, buffer.AsSpan(offset), intersectionRule);
- }
-
- ///
- public int FindIntersections(PointF start, PointF end, PointF[] buffer, int offset)
- {
- Span subBuffer = buffer.AsSpan(offset);
- return this.FindIntersections(start, end, subBuffer);
- }
+ => Vector2.Clamp(point, this.topLeft, this.bottomRight) == (Vector2)point;
- ///
- public int FindIntersections(PointF start, PointF end, Span buffer, IntersectionRule intersectionRule)
- {
- int offset = 0;
- int discovered = 0;
- Vector2 startPoint = Vector2.Clamp(start, this.topLeft, this.bottomRight);
- Vector2 endPoint = Vector2.Clamp(end, this.topLeft, this.bottomRight);
-
- // start doesn't change when its inside the shape thus not crossing
- if (startPoint != (Vector2)start)
- {
- if (startPoint == Vector2.Clamp(startPoint, start, end))
- {
- // if start closest is within line then its a valid point
- discovered++;
- buffer[offset++] = startPoint;
- }
- }
-
- // end didn't change it must not intercept with an edge
- if (endPoint != (Vector2)end)
- {
- if (endPoint == Vector2.Clamp(endPoint, start, end))
- {
- // if start closest is within line then its a valid point
- discovered++;
- buffer[offset] = endPoint;
- }
- }
-
- return discovered;
- }
-
- ///
- public int FindIntersections(PointF start, PointF end, Span buffer)
- {
- return this.FindIntersections(start, end, buffer, IntersectionRule.OddEven);
- }
-
- ///
- /// Transforms the rectangle using specified matrix.
- ///
- /// The matrix.
- ///
- /// A new shape with the matrix applied to it.
- ///
+ ///
public IPath Transform(Matrix3x2 matrix)
{
if (matrix.IsIdentity)
@@ -286,217 +169,62 @@ public IPath Transform(Matrix3x2 matrix)
return this;
}
- // rectangles may be rotated and skewed which means they will then nedd representing by a polygon
+ // Rectangles may be rotated and skewed which means they will then nedd representing by a polygon
return new Polygon(new LinearLineSegment(this.points).Transform(matrix));
}
///
- public SegmentInfo PointAlongPath(float distanceAlongPath)
+ SegmentInfo IPathInternals.PointAlongPath(float distance)
{
- distanceAlongPath = distanceAlongPath % this.length;
+ distance %= this.length;
- if (distanceAlongPath < this.Width)
+ if (distance < this.Width)
{
// we are on the top stretch
return new SegmentInfo
{
- Point = new Vector2(this.Left + distanceAlongPath, this.Top),
+ Point = new Vector2(this.Left + distance, this.Top),
Angle = MathF.PI
};
}
- else
- {
- distanceAlongPath -= this.Width;
- if (distanceAlongPath < this.Height)
- {
- // down on right
- return new SegmentInfo
- {
- Point = new Vector2(this.Right, this.Top + distanceAlongPath),
- Angle = -MathF.PI / 2
- };
- }
- else
- {
- distanceAlongPath -= this.Height;
- if (distanceAlongPath < this.Width)
- {
- // botom right to left
- return new SegmentInfo
- {
- Point = new Vector2(this.Right - distanceAlongPath, this.Bottom),
- Angle = 0
- };
- }
- else
- {
- distanceAlongPath -= this.Width;
- return new SegmentInfo
- {
- Point = new Vector2(this.Left, this.Bottom - distanceAlongPath),
- Angle = (float)(Math.PI / 2)
- };
- }
- }
- }
- }
-
- ///
- /// Calculates the distance along and away from the path for a specified point.
- ///
- /// The point along the path.
- ///
- /// Returns details about the point and its distance away from the path.
- ///
- public PointInfo Distance(PointF point)
- {
- Vector2 vectorPoint = point;
-
- // Point in rectangle if after its clamped by the extremes its still the same then it must be inside :)
- Vector2 clamped = Vector2.Clamp(point, this.topLeft, this.bottomRight);
- bool isInside = clamped == vectorPoint;
- float distanceFromEdge = float.MaxValue;
- float distanceAlongEdge = 0f;
-
- if (isInside)
+ distance -= this.Width;
+ if (distance < this.Height)
{
- // get the absolute distances from the extreams
- Vector2 topLeftDist = Vector2.Abs(vectorPoint - this.topLeft);
- Vector2 bottomRightDist = Vector2.Abs(vectorPoint - this.bottomRight);
-
- // get the min components
- Vector2 minDists = Vector2.Min(topLeftDist, bottomRightDist);
-
- // and then the single smallest (dont have to worry about direction)
- distanceFromEdge = Math.Min(minDists.X, minDists.Y);
-
- // we need to make clamped the closest point
- if (this.topLeft.X + distanceFromEdge == point.X)
- {
- // closer to lhf
- clamped.X = this.topLeft.X; // y is already the same
-
- // distance along edge is length minus the amout down we are from the top of the rect
- distanceAlongEdge = this.length - (clamped.Y - this.topLeft.Y);
- }
- else if (this.topLeft.Y + distanceFromEdge == point.Y)
- {
- // closer to top
- clamped.Y = this.topLeft.Y; // x is already the same
-
- distanceAlongEdge = clamped.X - this.topLeft.X;
- }
- else if (this.bottomRight.Y - distanceFromEdge == point.Y)
- {
- // closer to bottom
- clamped.Y = this.bottomRight.Y; // x is already the same
-
- distanceAlongEdge = (this.bottomRight.X - clamped.X) + this.halfLength;
- }
- else if (this.bottomRight.X - distanceFromEdge == point.X)
- {
- // closer to rhs
- clamped.X = this.bottomRight.X; // x is already the same
- distanceAlongEdge = (clamped.Y - this.topLeft.Y) + this.Size.Width;
- }
- }
- else
- {
- // clamped is the point on the path thats closest no matter what
- distanceFromEdge = (clamped - vectorPoint).Length();
-
- // we need to figure out whats the cloests edge now and thus what distance/poitn is closest
- if (this.topLeft.X == clamped.X)
- {
- // distance along edge is length minus the amout down we are from the top of the rect
- distanceAlongEdge = this.length - (clamped.Y - this.topLeft.Y);
- }
- else if (this.topLeft.Y == clamped.Y)
- {
- distanceAlongEdge = clamped.X - this.topLeft.X;
- }
- else if (this.bottomRight.Y == clamped.Y)
- {
- distanceAlongEdge = (this.bottomRight.X - clamped.X) + this.halfLength;
- }
- else if (this.bottomRight.X == clamped.X)
+ // down on right
+ return new SegmentInfo
{
- distanceAlongEdge = (clamped.Y - this.topLeft.Y) + this.Size.Width;
- }
+ Point = new Vector2(this.Right, this.Top + distance),
+ Angle = -MathF.PI / 2
+ };
}
- if (distanceAlongEdge == this.length)
+ distance -= this.Height;
+ if (distance < this.Width)
{
- distanceAlongEdge = 0;
+ // botom right to left
+ return new SegmentInfo
+ {
+ Point = new Vector2(this.Right - distance, this.Bottom),
+ Angle = 0
+ };
}
- distanceFromEdge = isInside ? -distanceFromEdge : distanceFromEdge;
-
- return new PointInfo
+ distance -= this.Width;
+ return new SegmentInfo
{
- SearchPoint = point,
- DistanceFromPath = distanceFromEdge,
- ClosestPointOnPath = clamped,
- DistanceAlongPath = distanceAlongEdge
+ Point = new Vector2(this.Left, this.Bottom - distance),
+ Angle = (float)(Math.PI / 2)
};
}
- ///
- /// Converts the into a simple linear path..
- ///
- ///
- /// Returns the current as simple linear path.
- ///
+ ///
public IEnumerable Flatten()
{
yield return this;
}
- ///
- /// Converts a path to a closed path.
- ///
- ///
- /// Returns the path as a closed path.
- ///
- IPath IPath.AsClosedPath() => this;
-
- ///
- /// Returns whether the rectangles are equal.
- ///
- /// The other recentalge.
- /// Returns a value indicating if the rectangles are equal.
- public bool Equals(RectangularPolygon other)
- {
- return other != null &&
- this.X == other.X &&
- this.Y == other.Y &&
- this.Height == other.Height &&
- this.Width == other.Width;
- }
-
- ///
- /// Equality comparer for two RectangularPolygons
- ///
- /// The polygon to compare to.
- /// Returns a value indicating if the rectangles are equal.
- public override bool Equals(object obj)
- {
- return obj is RectangularPolygon other && this.Equals(other);
- }
-
- ///
- /// Serves as the default hash function.
- ///
- /// A hash code for the current object.
- public override int GetHashCode()
- {
- int hashCode = -1073544145;
- hashCode = (hashCode * -1521134295) + this.X.GetHashCode();
- hashCode = (hashCode * -1521134295) + this.Y.GetHashCode();
- hashCode = (hashCode * -1521134295) + this.Width.GetHashCode();
- hashCode = (hashCode * -1521134295) + this.Height.GetHashCode();
- return hashCode;
- }
+ ///
+ public IPath AsClosedPath() => this;
}
}
diff --git a/src/ImageSharp.Drawing/Shapes/RegularPolygon.cs b/src/ImageSharp.Drawing/Shapes/RegularPolygon.cs
index 226160b3..40aac31f 100644
--- a/src/ImageSharp.Drawing/Shapes/RegularPolygon.cs
+++ b/src/ImageSharp.Drawing/Shapes/RegularPolygon.cs
@@ -27,8 +27,8 @@ public RegularPolygon(PointF location, int verticies, float radius, float angle)
/// Initializes a new instance of the class.
///
/// The location the center of the polygon will be placed.
- /// The radius of the circle that would touch all verticies.
/// The number of verticies the should have.
+ /// The radius of the circle that would touch all verticies.
public RegularPolygon(PointF location, int verticies, float radius)
: this(location, verticies, radius, 0)
{
@@ -37,8 +37,8 @@ public RegularPolygon(PointF location, int verticies, float radius)
///
/// Initializes a new instance of the class.
///
- /// The X coordinate of the center of the polygon.
- /// The Y coordinate of the center of the polygon.
+ /// The x-coordinate of the center of the polygon.
+ /// The y-coordinate of the center of the polygon.
/// The number of verticies the should have.
/// The radius of the circle that would touch all verticies.
/// The angle of rotation in Radians
@@ -50,10 +50,10 @@ public RegularPolygon(float x, float y, int verticies, float radius, float angle
///
/// Initializes a new instance of the class.
///
- /// The X coordinate of the center of the polygon.
- /// The Y coordinate of the center of the polygon.
- /// The radius of the circle that would touch all verticies.
+ /// The x-coordinate of the center of the polygon.
+ /// The y-coordinate of the center of the polygon.
/// The number of verticies the should have.
+ /// The radius of the circle that would touch all verticies.
public RegularPolygon(float x, float y, int verticies, float radius)
: this(new PointF(x, y), verticies, radius)
{
@@ -66,7 +66,7 @@ private static LinearLineSegment CreateSegment(PointF location, float radius, in
var distanceVector = new PointF(0, radius);
- float anglePerSegments = (float)((2 * Math.PI) / verticies);
+ float anglePerSegments = (float)(2 * Math.PI / verticies);
float current = angle;
var points = new PointF[verticies];
for (int i = 0; i < verticies; i++)
diff --git a/src/ImageSharp.Drawing/Shapes/Star.cs b/src/ImageSharp.Drawing/Shapes/Star.cs
index 2c56c7b6..74d2112d 100644
--- a/src/ImageSharp.Drawing/Shapes/Star.cs
+++ b/src/ImageSharp.Drawing/Shapes/Star.cs
@@ -7,7 +7,7 @@
namespace SixLabors.ImageSharp.Drawing
{
///
- /// A shape made up of a single path made up of one of more s
+ /// A shape made up of a single closed path made up of one of more s
///
public sealed class Star : Polygon
{
@@ -39,8 +39,8 @@ public Star(PointF location, int prongs, float innerRadii, float outerRadii)
///
/// Initializes a new instance of the class.
///
- /// The X coordinate of the center of the polygon.
- /// The Y coordinate of the center of the polygon.
+ /// The x-coordinate of the center of the polygon.
+ /// The y-coordinate of the center of the polygon.
/// The number of verticies the should have.
/// The inner radii.
/// The outer radii.
@@ -53,8 +53,8 @@ public Star(float x, float y, int prongs, float innerRadii, float outerRadii, fl
///
/// Initializes a new instance of the class.
///
- /// The X coordinate of the center of the polygon.
- /// The Y coordinate of the center of the polygon.
+ /// The x-coordinate of the center of the polygon.
+ /// The y-coordinate of the center of the polygon.
/// The number of verticies the should have.
/// The inner radii.
/// The outer radii.
@@ -73,7 +73,7 @@ private static LinearLineSegment CreateSegment(Vector2 location, float innerRadi
var distanceVectorOuter = new Vector2(0, outerRadii);
int verticies = prongs * 2;
- float anglePerSegemnts = (float)((2 * Math.PI) / verticies);
+ float anglePerSegemnts = (float)(2 * Math.PI / verticies);
float current = angle;
var points = new PointF[verticies];
Vector2 distance = distanceVectorInner;
diff --git a/src/ImageSharp.Drawing/Shapes/TessellatedMultipolygon.cs b/src/ImageSharp.Drawing/Shapes/TessellatedMultipolygon.cs
index a0057723..15a48624 100644
--- a/src/ImageSharp.Drawing/Shapes/TessellatedMultipolygon.cs
+++ b/src/ImageSharp.Drawing/Shapes/TessellatedMultipolygon.cs
@@ -18,7 +18,7 @@ namespace SixLabors.ImageSharp.Drawing.Shapes
/// - Holes are oriented "Negative" (CW in world, CCW on screen)
/// - First vertex is always repeated at the end of the span in each ring
///
- internal class TessellatedMultipolygon : IDisposable, IReadOnlyList
+ internal sealed class TessellatedMultipolygon : IDisposable, IReadOnlyList
{
private Ring[] rings;
diff --git a/src/ImageSharp.Drawing/Shapes/Text/PathGlyphBuilder.cs b/src/ImageSharp.Drawing/Shapes/Text/PathGlyphBuilder.cs
index a4d43700..77ff191c 100644
--- a/src/ImageSharp.Drawing/Shapes/Text/PathGlyphBuilder.cs
+++ b/src/ImageSharp.Drawing/Shapes/Text/PathGlyphBuilder.cs
@@ -8,29 +8,32 @@
namespace SixLabors.ImageSharp.Drawing.Text
{
///
- /// rendering surface that Fonts can use to generate Shapes by following a path
+ /// A rendering surface that Fonts can use to generate shapes by following a path.
///
- internal class PathGlyphBuilder : GlyphBuilder
+ internal sealed class PathGlyphBuilder : GlyphBuilder
{
private const float Pi = MathF.PI;
- private readonly IPath path;
+ private readonly IPathInternals path;
private float yOffset;
///
/// Initializes a new instance of the class.
///
- /// The path to render the glyps along.
+ /// The path to render the glyphs along.
public PathGlyphBuilder(IPath path)
- : base()
{
- this.path = path;
+ if (path is IPathInternals internals)
+ {
+ this.path = internals;
+ }
+ else
+ {
+ this.path = new ComplexPolygon(path);
+ }
}
///
- protected override void BeginText(FontRectangle rect)
- {
- this.yOffset = rect.Height;
- }
+ protected override void BeginText(FontRectangle rect) => this.yOffset = rect.Height;
///
protected override void BeginGlyph(FontRectangle rect)
@@ -39,7 +42,7 @@ protected override void BeginGlyph(FontRectangle rect)
Vector2 targetPoint = point.Point + new PointF(0, rect.Top - this.yOffset);
- // due to how matrix combining works you have to combine thins in the revers order of operation
+ // Due to how matrix combining works you have to combine this in the reverse order of operation
// this one rotates the glype then moves it.
Matrix3x2 matrix = Matrix3x2.CreateTranslation(targetPoint - rect.Location) * Matrix3x2.CreateRotation(point.Angle - Pi, point.Point);
this.builder.SetTransform(matrix);
diff --git a/tests/ImageSharp.Drawing.Tests/Drawing/DrawComplexPolygonTests.cs b/tests/ImageSharp.Drawing.Tests/Drawing/DrawComplexPolygonTests.cs
index ab1fa0d1..0f274679 100644
--- a/tests/ImageSharp.Drawing.Tests/Drawing/DrawComplexPolygonTests.cs
+++ b/tests/ImageSharp.Drawing.Tests/Drawing/DrawComplexPolygonTests.cs
@@ -29,6 +29,7 @@ public void DrawComplexPolygon(TestImageProvider provider, bool
new Vector2(37, 85),
overlap ? new Vector2(130, 40) : new Vector2(93, 85),
new Vector2(65, 137)));
+
IPath clipped = simplePath.Clip(hole1);
Rgba32 colorRgba = Color.White;
diff --git a/tests/ImageSharp.Drawing.Tests/Drawing/FillComplexPolygonTests.cs b/tests/ImageSharp.Drawing.Tests/Drawing/FillComplexPolygonTests.cs
index 6366effa..901e9f85 100644
--- a/tests/ImageSharp.Drawing.Tests/Drawing/FillComplexPolygonTests.cs
+++ b/tests/ImageSharp.Drawing.Tests/Drawing/FillComplexPolygonTests.cs
@@ -28,6 +28,7 @@ public void ComplexPolygon_SolidFill(TestImageProvider provider,
new Vector2(37, 85),
overlap ? new Vector2(130, 40) : new Vector2(93, 85),
new Vector2(65, 137)));
+
IPath clipped = simplePath.Clip(hole1);
Rgba32 colorRgba = Color.HotPink;
diff --git a/tests/ImageSharp.Drawing.Tests/Drawing/FillPolygonTests.cs b/tests/ImageSharp.Drawing.Tests/Drawing/FillPolygonTests.cs
index a97a355c..534d1737 100644
--- a/tests/ImageSharp.Drawing.Tests/Drawing/FillPolygonTests.cs
+++ b/tests/ImageSharp.Drawing.Tests/Drawing/FillPolygonTests.cs
@@ -2,15 +2,8 @@
// Licensed under the Apache License, Version 2.0.
using System;
-using System.IO;
-using System.Linq;
using System.Numerics;
-using GeoJSON.Net.Converters;
-using GeoJSON.Net.Feature;
-using GeoJSON.Net.Geometry;
-using Newtonsoft.Json;
using SixLabors.ImageSharp.Drawing.Processing;
-using SixLabors.ImageSharp.Drawing.Shapes;
using SixLabors.ImageSharp.Drawing.Tests.TestUtilities.ImageComparison;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing;
@@ -142,8 +135,8 @@ public void FillPolygon_Complex(TestImageProvider provider, bool
},
testOutputDetails: $"Reverse({reverse})_IntersectionRule({intersectionRule})",
comparer: ImageComparer.TolerantPercentage(0.01f),
- appendSourceFileOrDescription: false,
- appendPixelTypeToFileName: false);
+ appendPixelTypeToFileName: false,
+ appendSourceFileOrDescription: false);
}
[Theory]
@@ -172,8 +165,8 @@ public void FillPolygon_Concave(TestImageProvider provider, bool
c => c.FillPolygon(color, points),
testOutputDetails: $"Reverse({reverse})",
comparer: ImageComparer.TolerantPercentage(0.01f),
- appendSourceFileOrDescription: false,
- appendPixelTypeToFileName: false);
+ appendPixelTypeToFileName: false,
+ appendSourceFileOrDescription: false);
}
[Theory]
diff --git a/tests/ImageSharp.Drawing.Tests/Drawing/Paths/ClearRectangle.cs b/tests/ImageSharp.Drawing.Tests/Drawing/Paths/ClearRectangle.cs
index b6f70620..86aecf99 100644
--- a/tests/ImageSharp.Drawing.Tests/Drawing/Paths/ClearRectangle.cs
+++ b/tests/ImageSharp.Drawing.Tests/Drawing/Paths/ClearRectangle.cs
@@ -4,6 +4,7 @@
using SixLabors.ImageSharp.Drawing.Processing;
using SixLabors.ImageSharp.Drawing.Processing.Processors.Drawing;
using SixLabors.ImageSharp.Drawing.Tests.Processing;
+using SixLabors.ImageSharp.Drawing.Tests.TestUtilities;
using Xunit;
namespace SixLabors.ImageSharp.Drawing.Tests.Drawing.Paths
@@ -23,7 +24,7 @@ public void Brush()
FillPathProcessor processor = this.Verify();
Assert.NotEqual(this.shapeOptions, processor.Options.ShapeOptions);
- Assert.Equal(this.RectanglePolygon, processor.Shape);
+ Assert.True(RectangularPolygonValueComparer.Equals(this.RectanglePolygon, processor.Shape));
Assert.Equal(this.brush, processor.Brush);
}
@@ -35,7 +36,7 @@ public void BrushDefaultOptions()
FillPathProcessor processor = this.Verify();
Assert.Equal(this.shapeOptions, processor.Options.ShapeOptions);
- Assert.Equal(this.RectanglePolygon, processor.Shape);
+ Assert.True(RectangularPolygonValueComparer.Equals(this.RectanglePolygon, processor.Shape));
Assert.Equal(this.brush, processor.Brush);
}
@@ -47,7 +48,7 @@ public void ColorSet()
FillPathProcessor processor = this.Verify();
Assert.NotEqual(this.shapeOptions, processor.Options.ShapeOptions);
- Assert.Equal(this.RectanglePolygon, processor.Shape);
+ Assert.True(RectangularPolygonValueComparer.Equals(this.RectanglePolygon, processor.Shape));
Assert.NotEqual(this.brush, processor.Brush);
SolidBrush brush = Assert.IsType(processor.Brush);
Assert.Equal(Color.Red, brush.Color);
@@ -61,7 +62,7 @@ public void ColorAndThicknessDefaultOptions()
FillPathProcessor processor = this.Verify();
Assert.Equal(this.shapeOptions, processor.Options.ShapeOptions);
- Assert.Equal(this.RectanglePolygon, processor.Shape);
+ Assert.True(RectangularPolygonValueComparer.Equals(this.RectanglePolygon, processor.Shape));
Assert.NotEqual(this.brush, processor.Brush);
SolidBrush brush = Assert.IsType(processor.Brush);
Assert.Equal(Color.Red, brush.Color);
diff --git a/tests/ImageSharp.Drawing.Tests/Drawing/Paths/DrawRectangle.cs b/tests/ImageSharp.Drawing.Tests/Drawing/Paths/DrawRectangle.cs
index 215f2b7f..ea758a67 100644
--- a/tests/ImageSharp.Drawing.Tests/Drawing/Paths/DrawRectangle.cs
+++ b/tests/ImageSharp.Drawing.Tests/Drawing/Paths/DrawRectangle.cs
@@ -4,6 +4,7 @@
using SixLabors.ImageSharp.Drawing.Processing;
using SixLabors.ImageSharp.Drawing.Processing.Processors.Drawing;
using SixLabors.ImageSharp.Drawing.Tests.Processing;
+using SixLabors.ImageSharp.Drawing.Tests.TestUtilities;
using Xunit;
namespace SixLabors.ImageSharp.Drawing.Tests.Drawing.Paths
@@ -23,7 +24,7 @@ public void CorrectlySetsPenAndPath()
DrawPathProcessor processor = this.Verify();
Assert.NotEqual(this.shapeOptions, processor.Options.ShapeOptions);
- Assert.Equal(this.RectanglePolygon, processor.Shape);
+ Assert.True(RectangularPolygonValueComparer.Equals(this.RectanglePolygon, processor.Shape));
Assert.Equal(this.pen, processor.Pen);
}
@@ -35,7 +36,7 @@ public void CorrectlySetsPenAndPathDefaultOptions()
DrawPathProcessor processor = this.Verify();
Assert.Equal(this.shapeOptions, processor.Options.ShapeOptions);
- Assert.Equal(this.RectanglePolygon, processor.Shape);
+ Assert.True(RectangularPolygonValueComparer.Equals(this.RectanglePolygon, processor.Shape));
Assert.Equal(this.pen, processor.Pen);
}
@@ -47,7 +48,7 @@ public void BrushAndThickness()
DrawPathProcessor processor = this.Verify();
Assert.NotEqual(this.shapeOptions, processor.Options.ShapeOptions);
- Assert.Equal(this.RectanglePolygon, processor.Shape);
+ Assert.True(RectangularPolygonValueComparer.Equals(this.RectanglePolygon, processor.Shape));
Assert.NotEqual(this.pen, processor.Pen);
Assert.Equal(this.pen.StrokeFill, processor.Pen.StrokeFill);
Assert.Equal(10, processor.Pen.StrokeWidth);
@@ -61,7 +62,7 @@ public void BrushAndThicknessDefaultOptions()
DrawPathProcessor processor = this.Verify();
Assert.Equal(this.shapeOptions, processor.Options.ShapeOptions);
- Assert.Equal(this.RectanglePolygon, processor.Shape);
+ Assert.True(RectangularPolygonValueComparer.Equals(this.RectanglePolygon, processor.Shape));
Assert.NotEqual(this.pen, processor.Pen);
Assert.Equal(this.pen.StrokeFill, processor.Pen.StrokeFill);
Assert.Equal(10, processor.Pen.StrokeWidth);
@@ -75,7 +76,7 @@ public void ColorAndThickness()
DrawPathProcessor processor = this.Verify();
Assert.NotEqual(this.shapeOptions, processor.Options.ShapeOptions);
- Assert.Equal(this.RectanglePolygon, processor.Shape);
+ Assert.True(RectangularPolygonValueComparer.Equals(this.RectanglePolygon, processor.Shape));
Assert.NotEqual(this.pen, processor.Pen);
SolidBrush brush = Assert.IsType(processor.Pen.StrokeFill);
Assert.Equal(Color.Red, brush.Color);
@@ -90,7 +91,7 @@ public void ColorAndThicknessDefaultOptions()
DrawPathProcessor processor = this.Verify();
Assert.Equal(this.shapeOptions, processor.Options.ShapeOptions);
- Assert.Equal(this.RectanglePolygon, processor.Shape);
+ Assert.True(RectangularPolygonValueComparer.Equals(this.RectanglePolygon, processor.Shape));
Assert.NotEqual(this.pen, processor.Pen);
SolidBrush brush = Assert.IsType(processor.Pen.StrokeFill);
Assert.Equal(Color.Red, brush.Color);
diff --git a/tests/ImageSharp.Drawing.Tests/Drawing/Paths/FillRectangle.cs b/tests/ImageSharp.Drawing.Tests/Drawing/Paths/FillRectangle.cs
index b9d8f8ab..018331b4 100644
--- a/tests/ImageSharp.Drawing.Tests/Drawing/Paths/FillRectangle.cs
+++ b/tests/ImageSharp.Drawing.Tests/Drawing/Paths/FillRectangle.cs
@@ -25,7 +25,7 @@ public void Brush()
FillPathProcessor processor = this.Verify();
Assert.NotEqual(this.shapeOptions, processor.Options.ShapeOptions);
- Assert.Equal(this.RectanglePolygon, processor.Shape);
+ Assert.True(RectangularPolygonValueComparer.Equals(this.RectanglePolygon, processor.Shape));
Assert.Equal(this.brush, processor.Brush);
}
@@ -37,7 +37,7 @@ public void BrushDefaultOptions()
FillPathProcessor processor = this.Verify();
Assert.Equal(this.shapeOptions, processor.Options.ShapeOptions);
- Assert.Equal(this.RectanglePolygon, processor.Shape);
+ Assert.True(RectangularPolygonValueComparer.Equals(this.RectanglePolygon, processor.Shape));
Assert.Equal(this.brush, processor.Brush);
}
@@ -49,7 +49,7 @@ public void ColorSet()
FillPathProcessor processor = this.Verify();
Assert.NotEqual(this.shapeOptions, processor.Options.ShapeOptions);
- Assert.Equal(this.RectanglePolygon, processor.Shape);
+ Assert.True(RectangularPolygonValueComparer.Equals(this.RectanglePolygon, processor.Shape));
Assert.NotEqual(this.brush, processor.Brush);
SolidBrush brush = Assert.IsType(processor.Brush);
Assert.Equal(Color.Red, brush.Color);
@@ -63,7 +63,7 @@ public void ColorAndThicknessDefaultOptions()
FillPathProcessor processor = this.Verify();
Assert.Equal(this.shapeOptions, processor.Options.ShapeOptions);
- Assert.Equal(this.RectanglePolygon, processor.Shape);
+ Assert.True(RectangularPolygonValueComparer.Equals(this.RectanglePolygon, processor.Shape));
Assert.NotEqual(this.brush, processor.Brush);
SolidBrush brush = Assert.IsType(processor.Brush);
Assert.Equal(Color.Red, brush.Color);
diff --git a/tests/ImageSharp.Drawing.Tests/Drawing/Paths/ShapeRegionTests.cs b/tests/ImageSharp.Drawing.Tests/Drawing/Paths/ShapeRegionTests.cs
index 29b53119..bd5f2e3d 100644
--- a/tests/ImageSharp.Drawing.Tests/Drawing/Paths/ShapeRegionTests.cs
+++ b/tests/ImageSharp.Drawing.Tests/Drawing/Paths/ShapeRegionTests.cs
@@ -11,15 +11,13 @@ namespace SixLabors.ImageSharp.Drawing.Tests.Drawing.Paths
{
public class ShapeRegionTests
{
- public abstract class MockPath : IPath
+ public abstract class MockPath : IPath, IPathInternals
{
public abstract RectangleF Bounds { get; }
public IPath AsClosedPath() => this;
- public abstract SegmentInfo PointAlongPath(float distanceAlongPath);
-
- public abstract PointInfo Distance(PointF point);
+ public abstract SegmentInfo PointAlongPath(float distance);
public abstract IEnumerable Flatten();
@@ -29,33 +27,6 @@ public abstract class MockPath : IPath
public abstract PathTypes PathType { get; }
- public abstract int MaxIntersections { get; }
-
- public abstract float Length { get; }
-
- public int FindIntersections(PointF start, PointF end, PointF[] buffer, int offset, IntersectionRule intersectionRule)
- {
- return this.FindIntersections(start, end, buffer, 0);
- }
-
- public int FindIntersections(PointF s, PointF e, Span buffer, IntersectionRule intersectionRule)
- {
- Assert.Equal(this.TestYToScan, s.Y);
- Assert.Equal(this.TestYToScan, e.Y);
- Assert.True(s.X < this.Bounds.Left);
- Assert.True(e.X > this.Bounds.Right);
-
- this.TestFindIntersectionsInvocationCounter++;
-
- return this.TestFindIntersectionsResult;
- }
-
- public int FindIntersections(PointF start, PointF end, PointF[] buffer, int offset)
- => this.FindIntersections(start, end, buffer, offset, IntersectionRule.OddEven);
-
- public int FindIntersections(PointF s, PointF e, Span buffer)
- => this.FindIntersections(s, e, buffer, IntersectionRule.OddEven);
-
public int TestFindIntersectionsInvocationCounter { get; private set; }
public virtual int TestYToScan => 10;
diff --git a/tests/ImageSharp.Drawing.Tests/Issues/Issues_48.cs b/tests/ImageSharp.Drawing.Tests/Issues/Issues_48.cs
deleted file mode 100644
index 368393d6..00000000
--- a/tests/ImageSharp.Drawing.Tests/Issues/Issues_48.cs
+++ /dev/null
@@ -1,23 +0,0 @@
-// Copyright (c) Six Labors.
-// Licensed under the Apache License, Version 2.0.
-
-using System;
-using System.Collections.Generic;
-using System.Drawing;
-using System.Text;
-using Xunit;
-
-namespace SixLabors.ImageSharp.Drawing.Tests.Issues
-{
- // https://github.com/SixLabors/ImageSharp.Drawing/issues/48
- // index out of rang error if zero length ChildPath of a ComplexPolygon
- public class Issues_48
- {
- [Fact]
- public void DoNotThowIfPolygonHasZeroLength()
- {
- var emptyPoly = new ComplexPolygon(new Polygon());
- emptyPoly.FindIntersections(new PointF(0, 0), new PointF(100, 100));
- }
- }
-}
diff --git a/tests/ImageSharp.Drawing.Tests/Shapes/ComplexPolygonTests.cs b/tests/ImageSharp.Drawing.Tests/Shapes/ComplexPolygonTests.cs
deleted file mode 100644
index 6a23bdac..00000000
--- a/tests/ImageSharp.Drawing.Tests/Shapes/ComplexPolygonTests.cs
+++ /dev/null
@@ -1,288 +0,0 @@
-// Copyright (c) Six Labors.
-// Licensed under the Apache License, Version 2.0.
-
-using System;
-using System.Buffers;
-using System.Collections.Generic;
-using System.Globalization;
-using System.Linq;
-using System.Numerics;
-using SixLabors.ImageSharp;
-using Xunit;
-using Xunit.Abstractions;
-
-namespace SixLabors.ImageSharp.Drawing.Tests
-{
- public class ComplexPolygonTests
- {
- public static readonly TheoryData CommonOffsetData
- = new TheoryData()
- {
- { 0, 0, 0 },
- { 1500, 1500, 0 },
- { 3000, 3000, 0 },
- { 8000, 8000, 0 },
- { 20000, 20000, 0 },
- { 0, 0, 42 },
- { 1500, 1500, 42 },
- { 3000, 3000, 42 },
- { 8000, 8000, 42 },
- { 20000, 20000, 42 },
- { 0, 0, 123 },
- { 1500, 1500, 123 },
- { 3000, 3000, 123 },
- { 8000, 8000, 123 },
- { 20000, 20000, 123 },
- };
-
- public ComplexPolygonTests(ITestOutputHelper output)
- {
- this.Output = output;
- }
-
- private ITestOutputHelper Output { get; }
-
- [Fact]
- public void NonezeroFindIntersections()
- {
- var simplePath1 = new Polygon(new LinearLineSegment(
- new PointF(2, 1),
- new PointF(2, 5),
- new PointF(3, 5),
- new PointF(3, 1)));
-
- var simplePath2 = new Polygon(new LinearLineSegment(
- new PointF(1, 2),
- new PointF(1, 3),
- new PointF(4, 3),
- new PointF(4, 2)));
-
- var complex = new ComplexPolygon(simplePath1, simplePath2);
-
- var buffer = new PointF[10];
- int points = complex.FindIntersections(new PointF(0, 2.5f), new PointF(6, 2.5f), buffer, 0, IntersectionRule.Nonzero);
-
- Assert.Equal(2, points);
- Assert.Equal(1, buffer[0].X);
- Assert.Equal(4, buffer[1].X);
- }
-
- [Fact]
- public void OddEvenFindIntersections()
- {
- var simplePath1 = new Polygon(new LinearLineSegment(
- new PointF(2, 1),
- new PointF(2, 5),
- new PointF(3, 5),
- new PointF(3, 1)));
-
- var simplePath2 = new Polygon(new LinearLineSegment(
- new PointF(1, 2),
- new PointF(1, 3),
- new PointF(4, 3),
- new PointF(4, 2)));
-
- var complex = new ComplexPolygon(simplePath1, simplePath2);
-
- var buffer = new PointF[10];
- int points = complex.FindIntersections(new PointF(0, 2.5f), new PointF(6, 2.5f), buffer, 0, IntersectionRule.OddEven);
-
- Assert.Equal(4, points);
- Assert.Equal(1, buffer[0].X);
- Assert.Equal(2, buffer[1].X);
- Assert.Equal(3, buffer[2].X);
- Assert.Equal(4, buffer[3].X);
- }
-
- [Fact]
- public void MissingIntersection()
- {
- float[] data = new float[6];
- var simplePath = new Polygon(new LinearLineSegment(
- new PointF(10, 10),
- new PointF(200, 150),
- new PointF(50, 300)));
-
- var hole1 = new Polygon(new LinearLineSegment(
- new PointF(65, 137),
- new PointF(37, 85),
- new PointF(93, 85)));
-
- int intersections1 = this.ScanY(hole1, 137, data, 6, 0);
- Assert.Equal(2, intersections1);
- IPath poly = simplePath.Clip(hole1);
-
- int intersections = this.ScanY(poly, 137, data, 6, 0);
-
- // returns an even number of points
- Assert.Equal(4, intersections);
- }
-
- public int ScanY(IPath shape, float y, float[] buffer, int length, int offset)
- {
- var start = new PointF(shape.Bounds.Left - 1, y);
- var end = new PointF(shape.Bounds.Right + 1, y);
- PointF[] innerbuffer = ArrayPool.Shared.Rent(length);
- try
- {
- int count = shape.FindIntersections(start, end, innerbuffer, 0);
-
- for (int i = 0; i < count; i++)
- {
- buffer[i + offset] = innerbuffer[i].X;
- }
-
- return count;
- }
- finally
- {
- ArrayPool.Shared.Return(innerbuffer);
- }
- }
-
- [Theory]
- [MemberData(nameof(CommonOffsetData))]
- public void MissingIntersectionsOpenSans_a(int dx, int dy, int noiseSeed)
- {
- const string Path = @"36.57813x49.16406 35.41797x43.67969 35.41797x43.67969 35.13672x43.67969 35.13672x43.67969 34.41629x44.54843 33.69641x45.34412 32.97708x46.06674 32.2583x46.71631 31.54007x47.29282 30.82239x47.79626 30.10526x48.22665 29.38867x48.58398 29.38867x48.58398 28.65012x48.88474 27.86707x49.14539 27.03952x49.36594 26.16748x49.54639 25.25095x49.68674 24.28992x49.78699 23.28439x49.84714 22.23438x49.86719 22.23438x49.86719 21.52775x49.85564 20.84048x49.82104 20.17258x49.76337 19.52405x49.68262 18.28506x49.4519 17.12354x49.12891 16.03946x48.71362 15.03284x48.20605 14.10367x47.6062 13.25195x46.91406 13.25195x46.91406 12.48978x46.13678 11.82922x45.28149 11.27029x44.34821 10.81299x43.33691 10.45731x42.24762 10.20325x41.08032 10.05081x39.83502 10.0127x39.18312 10x38.51172 10x38.51172 10.01823x37.79307 10.07292x37.09613 10.16407x36.42088 10.29169x35.76733 10.6563x34.52533 11.16675x33.37012 11.82304x32.3017 12.62518x31.32007 13.57317x30.42523 14.10185x30.01036 14.66699x29.61719 15.2686x29.24571 15.90666x28.89594 16.58119x28.56786 17.29218x28.26147 18.03962x27.97679 18.82353x27.71381 19.6439x27.47252 20.50073x27.25293 22.32378x26.87885 24.29266x26.59155 26.40739x26.39105 28.66797x26.27734 28.66797x26.27734 35.20703x26.06641 35.20703x26.06641 35.20703x23.67578 35.20703x23.67578 35.17654x22.57907 35.08508x21.55652 34.93265x20.60812 34.71924x19.73389 34.44485x18.93381 34.1095x18.20789 33.71317x17.55612 33.25586x16.97852 33.25586x16.97852 32.73154x16.47177 32.13416x16.03259 31.46371x15.66098 30.72021x15.35693 29.90366x15.12045 29.01404x14.95154 28.05136x14.85019 27.01563x14.81641 27.01563x14.81641 25.79175x14.86255 24.52832x15.00098 23.88177x15.1048 23.22534x15.23169 21.88281x15.55469 20.50073x15.96997 19.0791x16.47754 17.61792x17.07739 16.11719x17.76953 16.11719x17.76953 14.32422x13.30469 14.32422x13.30469 15.04465x12.92841 15.7821x12.573 17.30811x11.9248 18.90222x11.36011 20.56445x10.87891 20.56445x10.87891 22.26184x10.49438 23.96143x10.21973 24.81204x10.1236 25.66321x10.05493 26.51492x10.01373 27.36719x10 27.36719x10 29.03409x10.04779 29.82572x10.10753 30.58948x10.19116 31.32536x10.29869 32.03336x10.43011 32.71348x10.58543 33.36572x10.76465 34.58658x11.19476 35.69592x11.72046 36.69376x12.34174 37.58008x13.05859 37.58008x13.05859 38.35873x13.88092 39.03357x14.8186 39.60458x15.87164 40.07178x17.04004 40.26644x17.6675 40.43515x18.32379 40.5779x19.00893 40.6947x19.7229 40.78555x20.46571 40.85043x21.23737 40.88937x22.03786 40.90234x22.86719 40.90234x22.86719 40.90234x49.16406
-23.39453x45.05078 24.06655x45.03911 24.72031x45.00409 25.97302x44.86401 27.15268x44.63055 28.25928x44.30371 29.29282x43.88348 30.2533x43.36987 31.14072x42.76288 31.95508x42.0625 31.95508x42.0625 32.6843x41.27808 33.31628x40.41895 33.85104x39.48511 34.28857x38.47656 34.62888x37.39331 34.87195x36.23535 35.01779x35.00269 35.06641x33.69531 35.06641x33.69531 35.06641x30.21484 35.06641x30.21484 29.23047x30.46094 29.23047x30.46094 27.55093x30.54855 25.9928x30.68835 24.55606x30.88034 23.24072x31.12451 22.04678x31.42087 20.97424x31.76941 20.0231x32.17014 19.19336x32.62305 19.19336x32.62305 18.47238x33.13528 17.84753x33.71399 17.31882x34.35916 16.88623x35.0708 16.54977x35.84891 16.30945x36.69348 16.16525x37.60452 16.11719x38.58203 16.11719x38.58203 16.14713x39.34943 16.23694x40.06958 16.38663x40.74249 16.59619x41.36816 17.19495x42.47778 18.0332x43.39844 18.0332x43.39844 19.08679x44.12134 19.68527x44.40533 20.33154x44.6377 21.0256x44.81842 21.76746x44.94751 22.5571x45.02496 23.39453x45.05078";
-
- Vector2 offset = MakeOffsetVector(dx, dy, noiseSeed);
-
- this.TestMissingFontIntersectionCore(
- offset,
- Path,
- 10,
- (intersections, _)
- => Assert.True(intersections % 2 == 0, $"even number of intersections expected but found {intersections}"));
- }
-
- [Theory]
- [MemberData(nameof(CommonOffsetData))]
- public void MissingIntersectionsOpenSans_o(int dx, int dy, int noiseSeed)
- {
- const string Path = @"45.40234x29.93359 45.3838x31.09519 45.32819x32.22452 45.23549x33.32157 45.10571x34.38635 44.93886x35.41886 44.73492x36.4191 44.49391x37.38706 44.21582x38.32275 43.90065x39.22617 43.5484x40.09732 43.15907x40.9362 42.73267x41.7428 42.26918x42.51713 41.76862x43.25919 41.23097x43.96897 40.65625x44.64648 40.65625x44.64648 40.04884x45.28719 39.41315x45.88657 38.74916x46.4446 38.05688x46.9613 37.33632x47.43667 36.58746x47.8707 35.81032x48.26339 35.00488x48.61475 34.17116x48.92477 33.30914x49.19345 32.41884x49.4208 31.50024x49.60681 30.55336x49.75149 29.57819x49.85483 28.57472x49.91683 27.54297x49.9375 27.54297x49.9375 26.2691x49.8996 25.03149x49.78589 23.83014x49.59637 22.66504x49.33105 21.53619x48.98993 20.4436x48.573 19.38727x48.08026 18.36719x47.51172 18.36719x47.51172 17.3938x46.87231 16.47754x46.16699 15.61841x45.39575 14.81641x44.55859 14.07153x43.65552 13.38379x42.68652 12.75317x41.65161 12.17969x40.55078 12.17969x40.55078 11.66882x39.39282 11.22607x38.18652 10.85144x36.93188 10.54492x35.62891 10.30652x34.27759 10.13623x32.87793 10.03406x31.42993 10x29.93359 10x29.93359 10.0184x28.77213 10.07361x27.64322 10.16562x26.54685 10.29443x25.48303 10.46005x24.45176 10.66248x23.45303 10.9017x22.48685 11.17773x21.55322 11.49057x20.65214 11.84021x19.7836 12.22665x18.94761 12.6499x18.14417 13.10995x17.37327 13.60681x16.63492 14.14047x15.92912 14.71094x15.25586 14.71094x15.25586 15.31409x14.61941 15.9458x14.02402 16.60608x13.46969 17.29492x12.95642 18.01233x12.48421 18.7583x12.05307 19.53284x11.66299 20.33594x11.31396 21.1676x11.006 22.02783x10.73911 22.91663x10.51327 23.83398x10.32849 24.77991x10.18478 25.75439x10.08212 26.75745x10.02053 27.78906x10 27.78906x10 28.78683x10.02101 29.75864x10.08405 30.70449x10.1891 31.62439x10.33618 32.51833x10.52528 33.38632x10.75641 34.22836x11.02956 35.04443x11.34473 35.83456x11.70192 36.59872x12.10114 37.33694x12.54237 38.04919x13.02563 38.7355x13.55092 39.39584x14.11823 40.03024x14.72755 40.63867x15.37891 40.63867x15.37891 41.21552x16.0661 41.75516x16.78296 42.25757x17.52948 42.72278x18.30566 43.15077x19.11151 43.54153x19.94702 43.89509x20.81219 44.21143x21.70703 44.49055x22.63153 44.73245x23.58569 44.93714x24.56952 45.10461x25.58301 45.23487x26.62616 45.32791x27.69897 45.38374x28.80145 45.40234x29.93359
-16.04688x29.93359 16.09302x31.72437 16.23145x33.40527 16.33527x34.20453 16.46216x34.97632 16.61212x35.72064 16.78516x36.4375 16.98126x37.12689 17.20044x37.78882 17.44269x38.42328 17.70801x39.03027 18.30786x40.16187 19x41.18359 19x41.18359 19.78168x42.08997 20.65015x42.87549 21.60541x43.54016 22.64746x44.08398 23.77631x44.50696 24.99194x44.80908 26.29437x44.99036 26.97813x45.03568 27.68359x45.05078 27.68359x45.05078 28.38912x45.03575 29.07309x44.99063 30.37634x44.81018 31.59335x44.50943 32.72412x44.08838 33.76865x43.54703 34.72693x42.88538 35.59897x42.10342 36.38477x41.20117 36.38477x41.20117 37.08102x40.18301 37.68445x39.05334 37.95135x38.44669 38.19504x37.81216 38.41552x37.14976 38.61279x36.45947 38.78686x35.74131 38.93771x34.99527 39.06536x34.22135 39.1698x33.41956 39.30905x31.73233 39.35547x29.93359 39.35547x29.93359 39.30905x28.15189 39.1698x26.48059 39.06536x25.68635 38.93771x24.91971 38.78686x24.18067 38.61279x23.46924 38.41552x22.78541 38.19504x22.12918 37.95135x21.50056 37.68445x20.89954 37.08102x19.7803 36.38477x18.77148 36.38477x18.77148 35.59787x17.87747 34.72253x17.10266 33.75876x16.44705 32.70654x15.91064 31.56589x15.49344 30.33679x15.19543 29.68908x15.09113 29.01926x15.01663 28.32732x14.97193 27.61328x14.95703 27.61328x14.95703 26.90796x14.97173 26.22461x15.01581 24.92383x15.19214 23.71094x15.48602 22.58594x15.89746 21.54883x16.42645 20.59961x17.073 19.73828x17.8371 18.96484x18.71875 18.96484x18.71875 18.28094x19.71686 17.68823x20.83032 17.42607x21.43031 17.18671x22.05914 16.97014x22.71681 16.77637x23.40332 16.60539x24.11867 16.45721x24.86285 16.33183x25.63588 16.22925x26.43774 16.09247x28.12799 16.04688x29.93359 ";
-
- Vector2 offset = MakeOffsetVector(dx, dy, noiseSeed);
-
- this.TestMissingFontIntersectionCore(
- offset,
- Path,
- 30,
- (intersections, data) =>
- {
- float expectedMinX = 28f + offset.X;
- Assert.True(data[1] < expectedMinX, $"second intersection should be > {expectedMinX} but was {data[1]}");
- Assert.True(intersections % 2 == 0, $"even number of intersections expected but found {intersections}");
- });
- }
-
- private void TestMissingFontIntersectionCore(Vector2 offset, string path, int scanStartY, Action validateIntersections)
- {
- string[] paths = path.Split(new[] { '\n', '\r' }, StringSplitOptions.RemoveEmptyEntries);
-
- Polygon[] polys = paths.Select(line =>
- {
- string[] pl = line.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
-
- PointF[] points = pl.Select(p => p.Split('x'))
- .Select(p =>
- {
- float x = float.Parse(p[0], CultureInfo.InvariantCulture);
- float y = float.Parse(p[1], CultureInfo.InvariantCulture);
- return (PointF)(new Vector2(x, y) + offset);
- })
- .ToArray();
-
- return new Polygon(new LinearLineSegment(points));
- }).ToArray();
-
- var complex = new ComplexPolygon(polys);
-
- float[] data = new float[complex.MaxIntersections];
- int intersections = this.ScanY(complex, scanStartY + offset.Y, data, complex.MaxIntersections, 0);
-
- validateIntersections(intersections, data);
- }
-
- // Test is based on @woutware's the idea and drawing logic in opening comment:
- // https://github.com/pull/43#issue-189141926
- [Theory]
- [InlineData(100, 100, 10, 3)]
- [InlineData(800, 600, 8, 2)]
- [InlineData(1500, 1500, 10, 3)]
- [InlineData(4000, 4000, 8, 2)]
- public void SmallRingAtLargeCoords_HorizontalScansShouldFind4IntersectionPoints(int w, int h, int r, int thickness)
- {
- int cx = w - (2 * r);
- int cy = h - (2 * 3);
-
- var ellipse = new EllipsePolygon(cx, cy, r);
- IPath path = ellipse.GenerateOutline(thickness);
-
- int yMin = cy - r + thickness + 1;
- int yMax = cy + r - thickness;
-
- var buffer = new PointF[16];
-
- var badPositions = new List();
-
- for (int y = yMin; y < yMax; y++)
- {
- var start = new PointF(-1, y);
- var end = new PointF(w + 1, y);
-
- int intersectionCount = path.FindIntersections(start, end, buffer);
- if (intersectionCount != 4)
- {
- badPositions.Add(y);
- }
- }
-
- if (badPositions.Count > 0)
- {
- // The char overload is causing mysterious build failures in Visual Studio
- string badPoz = string.Join(",", badPositions);
- this.Output.WriteLine($"BAD: {badPositions.Count} of {yMax - yMin}: {badPoz}");
-
- Assert.True(false);
- }
- }
-
- // Test is based on @woutware's the idea in another issue comment:
- // https://github.com/pull/43#issuecomment-390358702
- [Theory]
- [MemberData(nameof(CommonOffsetData))]
- public void OffsetingIntersectingSegments_ShouldPreserveIntersection(int dx, int dy, int noiseSeed)
- {
- Vector2 offset = MakeOffsetVector(dx, dy, noiseSeed);
-
- PointF a = new Vector2(21.904f, 78.48f) + offset;
- PointF b = new Vector2(22.026f, 79.8f) + offset;
-
- PointF c = new Vector2(48f, 78.5f) + offset;
- PointF d = new Vector2(20f, 78.5f) + offset;
-
- var path = new Path(new LinearLineSegment(a, b));
-
- int count = path.FindIntersections(c, d, new PointF[1]);
- Assert.Equal(1, count);
- }
-
- private static Vector2 MakeOffsetVector(int dx, int dy, int noiseSeed)
- {
- var offset = new Vector2(dx, dy);
-
- // Let's randomize the input data, while still keeping the test reproducible
- if (noiseSeed > 0)
- {
- var rnd = new Random(noiseSeed);
- offset.X += (float)rnd.NextDouble();
- offset.Y += (float)rnd.NextDouble();
- }
-
- return offset;
- }
- }
-}
diff --git a/tests/ImageSharp.Drawing.Tests/Shapes/EllipseTests.cs b/tests/ImageSharp.Drawing.Tests/Shapes/EllipseTests.cs
index 2c03cdef..fc2b0c18 100644
--- a/tests/ImageSharp.Drawing.Tests/Shapes/EllipseTests.cs
+++ b/tests/ImageSharp.Drawing.Tests/Shapes/EllipseTests.cs
@@ -2,8 +2,6 @@
// Licensed under the Apache License, Version 2.0.
using System;
-using System.Linq;
-using System.Numerics;
using Xunit;
namespace SixLabors.ImageSharp.Drawing.Tests
@@ -49,35 +47,5 @@ public void HeightMustBeGreateThan0(float height, bool throws)
Assert.NotNull(p);
}
}
-
- [Fact]
- public void ClippingCornerShouldReturn2Points()
- {
- var poly = new EllipsePolygon(50, 50, 30, 50);
- PointF[] points = poly.FindIntersections(new Vector2(0, 75), new Vector2(100, 75)).ToArray();
-
- Assert.Equal(2, points.Length);
- }
-
- [Fact]
- public void AcrossEllipsShouldReturn2()
- {
- var poly = new EllipsePolygon(50, 50, 30, 50);
- PointF[] points = poly.FindIntersections(new Vector2(0, 49), new Vector2(100, 49)).ToArray();
-
- Assert.Equal(2, points.Length);
- }
-
- [Fact]
- public void AcrossEllipseShouldReturn2()
- {
- IPath poly = new EllipsePolygon(0, 0, 10, 20).Scale(5);
- poly = poly.Translate(-poly.Bounds.Location) // touch top left
- .Translate(new Vector2(10)); // move in from top left
-
- PointF[] points = poly.FindIntersections(new Vector2(0, 10), new Vector2(100, 10)).ToArray();
-
- Assert.Equal(2, points.Length);
- }
}
}
diff --git a/tests/ImageSharp.Drawing.Tests/Shapes/GeneralClosedPolygonIntersectionTests.cs b/tests/ImageSharp.Drawing.Tests/Shapes/GeneralClosedPolygonIntersectionTests.cs
deleted file mode 100644
index 61cfde7a..00000000
--- a/tests/ImageSharp.Drawing.Tests/Shapes/GeneralClosedPolygonIntersectionTests.cs
+++ /dev/null
@@ -1,114 +0,0 @@
-// Copyright (c) Six Labors.
-// Licensed under the Apache License, Version 2.0.
-
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Numerics;
-using Xunit;
-
-namespace SixLabors.ImageSharp.Drawing.Tests
-{
- public class GeneralClosedPolygonIntersectionTests
- {
- private static readonly Dictionary ShapesData = new Dictionary
- {
- { "ellispeWithHole", new ComplexPolygon(new EllipsePolygon(new Vector2(603), 161f), new EllipsePolygon(new Vector2(603), 61f)) },
- { "largeEllipse", new EllipsePolygon(new Vector2(603), 603f - 60) },
- { "iris_0", TestShapes.IrisSegment(0) },
- { "iris_1", TestShapes.IrisSegment(1) },
- { "iris_2", TestShapes.IrisSegment(2) },
- { "iris_3", TestShapes.IrisSegment(3) },
- { "iris_4", TestShapes.IrisSegment(4) },
- { "iris_5", TestShapes.IrisSegment(5) },
- { "iris_6", TestShapes.IrisSegment(6) },
- { "scaled_300_iris_0", TestShapes.IrisSegment(300, 0) },
- { "scaled_300_iris_1", TestShapes.IrisSegment(300, 1) },
- { "scaled_300_iris_2", TestShapes.IrisSegment(300, 2) },
- { "scaled_300_iris_3", TestShapes.IrisSegment(300, 3) },
- { "scaled_300_iris_4", TestShapes.IrisSegment(300, 4) },
- { "scaled_300_iris_5", TestShapes.IrisSegment(300, 5) },
- { "scaled_300_iris_6", TestShapes.IrisSegment(300, 6) },
- { "clippedRect", new RectangularPolygon(10, 10, 40, 40).Clip(new RectangularPolygon(20, 0, 20, 20)) },
- { "hourGlass", TestShapes.HourGlass().AsClosedPath() },
- { "BigCurve", new Polygon(new CubicBezierLineSegment(new Vector2(10, 400), new Vector2(30, 10), new Vector2(240, 30), new Vector2(300, 400))) },
- {
- "ChopCorner", new Polygon(new LinearLineSegment(
- new Vector2(8, 8),
- new Vector2(64, 8),
- new Vector2(64, 64),
- new Vector2(120, 64),
- new Vector2(120, 120),
- new Vector2(8, 120)))
- }
- };
-
- public static TheoryData PolygonsTheoryData = new TheoryData
- {
- { "ellispeWithHole" },
- { "largeEllipse" },
- { "iris_0" },
- { "iris_1" },
- { "iris_2" },
- { "iris_3" },
- { "iris_4" },
- { "iris_5" },
- { "iris_6" },
- { "scaled_300_iris_0" },
- { "scaled_300_iris_1" },
- { "scaled_300_iris_2" },
- { "scaled_300_iris_3" },
- { "scaled_300_iris_4" },
- { "scaled_300_iris_5" },
- { "scaled_300_iris_6" },
- { "clippedRect" },
- { "hourGlass" },
- { "ChopCorner" },
- };
-
- [Theory]
- [MemberData(nameof(PolygonsTheoryData))]
- public void ShapeMissingEdgeHits(string name)
- {
- IPath polygon = ShapesData[name];
- int top = (int)Math.Ceiling(polygon.Bounds.Top);
- int bottom = (int)Math.Floor(polygon.Bounds.Bottom);
-
- for (int y = top; y <= bottom; y++)
- {
- IEnumerable intersections = polygon.FindIntersections(new Vector2(polygon.Bounds.Left - 1, y), new Vector2(polygon.Bounds.Right + 1, y));
- if (intersections.Count() % 2 != 0)
- {
- Assert.True(false, $"crosssection of '{name}' at '{y}' produced {intersections.Count()} number of intersections");
- }
- }
- }
-
- public static TheoryData SpecificErrors = new TheoryData
- {
- { "ellispeWithHole", 603 },
- { "ellispeWithHole", 442 },
- { "iris_5", 694 },
- { "iris_2", 512 },
- { "scaled_300_iris_3", 135 },
- { "scaled_300_iris_0", 165 },
- { "clippedRect", 20 },
- { "clippedRect", 10 },
- { "hourGlass", 25 },
- { "hourGlass", 175 },
- { "BigCurve", 115 },
- { "ChopCorner", 64 },
- };
-
- [Theory]
- [MemberData(nameof(SpecificErrors))]
- public void SpecificMisses(string name, int yScanLine)
- {
- IPath polygon = ShapesData[name];
-
- int intersections = polygon.FindIntersections(new Vector2(polygon.Bounds.Left - 1, yScanLine), new Vector2(polygon.Bounds.Right + 1, yScanLine)).Count();
-
- Assert.True(intersections % 2 == 0, $"crosssection of '{name}' at '{yScanLine}' produced {intersections} intersections");
- }
- }
-}
diff --git a/tests/ImageSharp.Drawing.Tests/Shapes/InternalPathExtensions.cs b/tests/ImageSharp.Drawing.Tests/Shapes/InternalPathExtensions.cs
index 4cc6799c..a68aa729 100644
--- a/tests/ImageSharp.Drawing.Tests/Shapes/InternalPathExtensions.cs
+++ b/tests/ImageSharp.Drawing.Tests/Shapes/InternalPathExtensions.cs
@@ -1,6 +1,7 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
+using System;
using System.Buffers;
using System.Collections.Generic;
using System.Numerics;
@@ -18,18 +19,26 @@ internal static class InternalPathExtensions
internal static IEnumerable FindIntersections(this InternalPath path, Vector2 start, Vector2 end, IntersectionRule intersectionRule = IntersectionRule.OddEven)
{
var results = new List();
- PointF[] buffer = ArrayPool.Shared.Rent(path.PointCount);
+ int max = path.PointCount;
+ PointF[] intersections = ArrayPool.Shared.Rent(max);
+ PointOrientation[] orientations = ArrayPool.Shared.Rent(max);
try
{
- int hits = path.FindIntersections(start, end, buffer, intersectionRule);
+ int hits = path.FindIntersections(
+ start,
+ end,
+ intersections.AsSpan(0, max),
+ orientations.AsSpan(0, max),
+ intersectionRule);
for (int i = 0; i < hits; i++)
{
- results.Add(buffer[i]);
+ results.Add(intersections[i]);
}
}
finally
{
- ArrayPool.Shared.Return(buffer);
+ ArrayPool.Shared.Return(intersections);
+ ArrayPool.Shared.Return(orientations);
}
return results;
diff --git a/tests/ImageSharp.Drawing.Tests/Shapes/InternalPathTests.cs b/tests/ImageSharp.Drawing.Tests/Shapes/InternalPathTests.cs
index b6b7cd2c..d5d45452 100644
--- a/tests/ImageSharp.Drawing.Tests/Shapes/InternalPathTests.cs
+++ b/tests/ImageSharp.Drawing.Tests/Shapes/InternalPathTests.cs
@@ -85,37 +85,6 @@ private static InternalPath Create(PointF location, SizeF size, bool closed = tr
},
};
- public static TheoryData PathDistanceTheoryData { get; }
- = new TheoryData
- {
- { new PointF(0, 0), 0f, 0f },
- { new PointF(1, 0), 0f, 1f },
- { new PointF(9, 0), 0f, 9f },
- { new PointF(10, 0), 0f, 10f },
- { new PointF(10, 1), 0f, 11f },
- { new PointF(10, 9), 0f, 19f },
- { new PointF(10, 10), 0f, 20f },
- { new PointF(9, 10), 0f, 21f },
- { new PointF(1, 10), 0f, 29f },
- { new PointF(0, 10), 0f, 30f },
- { new PointF(0, 9), 0f, 31f },
- { new PointF(0, 1), 0f, 39f },
- { new PointF(4, 3), 3f, 4f },
- { new PointF(3, 4), 3f, 36f },
- { new PointF(-1, 0), 1f, 0f },
- { new PointF(1, -1), 1f, 1f },
- { new PointF(9, -1), 1f, 9f },
- { new PointF(11, 0), 1f, 10f },
- { new PointF(11, 1), 1f, 11f },
- { new PointF(11, 9), 1f, 19f },
- { new PointF(11, 10), 1f, 20f },
- { new PointF(9, 11), 1f, 21f },
- { new PointF(1, 11), 1f, 29f },
- { new PointF(-1, 10), 1f, 30f },
- { new PointF(-1, 9), 1f, 31f },
- { new PointF(-1, 1), 1f, 39f },
- };
-
[Theory]
[MemberData(nameof(PointInPolygonTheoryData))]
public void PointInPolygon(TestPoint location, TestSize size, TestPoint point, bool isInside)
@@ -155,35 +124,17 @@ public void PointOnPath(float distance, float expectedX, float expectedY, float
Assert.Equal(expectedAngle, point.Angle, 4);
}
- [Theory]
- [MemberData(nameof(PathDistanceTheoryData))]
- public void DistanceFromPath_Path(TestPoint point, float expectedDistance, float alongPath)
- {
- InternalPath shape = Create(new PointF(0, 0), new Size(10, 10));
- PointInfo info = shape.DistanceFromPath(point);
- Assert.Equal(expectedDistance, info.DistanceFromPath);
- Assert.Equal(alongPath, info.DistanceAlongPath);
- }
-
- [Fact]
- public void DistanceFromPath_Path_Closed()
- {
- InternalPath shape = Create(new PointF(0, 0), new Size(10, 10), false);
- PointInfo info = shape.DistanceFromPath(new PointF(5, 5));
- Assert.Equal(5, info.DistanceFromPath);
- Assert.Equal(5, info.DistanceAlongPath);
- }
-
[Fact]
public void Intersections_buffer()
{
InternalPath shape = Create(new PointF(0, 0), new Size(10, 10));
- var buffer = new PointF[shape.PointCount];
- int hits = shape.FindIntersections(new PointF(5, -10), new PointF(5, 20), buffer);
+ var intersections = new PointF[shape.PointCount];
+ var orientations = new PointOrientation[shape.PointCount];
+ int hits = shape.FindIntersections(new PointF(5, -10), new PointF(5, 20), intersections, orientations);
Assert.Equal(2, hits);
- Assert.Equal(new PointF(5, 0), buffer[0]);
- Assert.Equal(new PointF(5, 10), buffer[1]);
+ Assert.Equal(new PointF(5, 0), intersections[0]);
+ Assert.Equal(new PointF(5, 10), intersections[1]);
}
[Fact]
diff --git a/tests/ImageSharp.Drawing.Tests/Shapes/Issues/Issue_ClippedPaths.cs b/tests/ImageSharp.Drawing.Tests/Shapes/Issues/Issue_ClippedPaths.cs
index 2a9e1c91..af347827 100644
--- a/tests/ImageSharp.Drawing.Tests/Shapes/Issues/Issue_ClippedPaths.cs
+++ b/tests/ImageSharp.Drawing.Tests/Shapes/Issues/Issue_ClippedPaths.cs
@@ -1,7 +1,6 @@
-// Copyright (c) Six Labors.
+// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
-using System.Linq;
using Xunit;
namespace SixLabors.ImageSharp.Drawing.Tests.Issues
@@ -26,30 +25,5 @@ public void ClippedTriangle()
Assert.False(outline.Contains(new PointF(74, 97)));
}
-
- [Fact]
- public void ClippedTriangleGapInIntersections()
- {
- var simplePath = new Polygon(new LinearLineSegment(
- new PointF(10, 10),
- new PointF(200, 150),
- new PointF(50, 300)));
-
- var hole1 = new Polygon(new LinearLineSegment(
- new PointF(37, 85),
- new PointF(93, 85),
- new PointF(65, 137)));
-
- IPath clippedPath = simplePath.Clip(hole1);
- IPath outline = clippedPath.GenerateOutline(5, new[] { 1f });
- var buffer = new PointF[20];
-
- var start = new PointF(outline.Bounds.Left - 1, 102);
- var end = new PointF(outline.Bounds.Right + 1, 102);
-
- int matches = outline.FindIntersections(start, end, buffer, 0);
- int maxIndex = buffer.Select((x, i) => new { x, i }).Where(x => x.x.X > 0).Select(x => x.i).Last();
- Assert.Equal(matches - 1, maxIndex);
- }
}
}
diff --git a/tests/ImageSharp.Drawing.Tests/Shapes/PathTests.cs b/tests/ImageSharp.Drawing.Tests/Shapes/PathTests.cs
index cb6c3372..3f5c14ef 100644
--- a/tests/ImageSharp.Drawing.Tests/Shapes/PathTests.cs
+++ b/tests/ImageSharp.Drawing.Tests/Shapes/PathTests.cs
@@ -1,4 +1,4 @@
-// Copyright (c) Six Labors.
+// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System.Linq;
@@ -25,43 +25,6 @@ public void Bounds()
Assert.Equal(5, path.Bounds.Bottom);
}
- public static TheoryData PathDistanceTheoryData =
- new TheoryData
- {
- { new PointF(0, 0), 0f, 0f },
- { new PointF(1, 0), 0f, 1f },
- { new PointF(9, 0), 0f, 9f },
- { new PointF(10, 0), 0f, 10f },
- { new PointF(10, 1), 0f, 11f },
- { new PointF(10, 9), 0f, 19f },
- { new PointF(10, 10), 0f, 20f },
- { new PointF(9, 10), 0f, 21f },
- { new PointF(1, 10), 0f, 29f },
- { new PointF(0, 10), 0f, 30f },
- { new PointF(0, 1), 1f, 0f },
- { new PointF(4, 3), 3f, 4f },
- { new PointF(3, 4), 4f, 3f },
- { new PointF(-1, 0), 1f, 0f },
- { new PointF(1, -1), 1f, 1f },
- { new PointF(9, -1), 1f, 9f },
- { new PointF(11, 0), 1f, 10f },
- { new PointF(11, 1), 1f, 11f },
- { new PointF(11, 9), 1f, 19f },
- { new PointF(11, 10), 1f, 20f },
- { new PointF(9, 11), 1f, 21f },
- { new PointF(1, 11), 1f, 29f }
- };
-
- [Theory]
- [MemberData(nameof(PathDistanceTheoryData))]
- public void DistanceFromPath_Path(TestPoint point, float expectedDistance, float alongPath)
- {
- var path = new Path(new LinearLineSegment(new PointF(0, 0), new PointF(10, 0), new PointF(10, 10), new PointF(0, 10)));
- PointInfo info = path.Distance(point);
- Assert.Equal(expectedDistance, info.DistanceFromPath);
- Assert.Equal(alongPath, info.DistanceAlongPath);
- }
-
[Fact]
public void SimplePath()
{
diff --git a/tests/ImageSharp.Drawing.Tests/Shapes/PolygonClipper/ClipperTests.cs b/tests/ImageSharp.Drawing.Tests/Shapes/PolygonClipper/ClipperTests.cs
index 852bed3a..62b7f515 100644
--- a/tests/ImageSharp.Drawing.Tests/Shapes/PolygonClipper/ClipperTests.cs
+++ b/tests/ImageSharp.Drawing.Tests/Shapes/PolygonClipper/ClipperTests.cs
@@ -5,6 +5,7 @@
using System.Linq;
using System.Numerics;
using SixLabors.ImageSharp.Drawing.PolygonClipper;
+using SixLabors.ImageSharp.Drawing.Tests.TestUtilities;
using Xunit;
namespace SixLabors.ImageSharp.Drawing.Tests.PolygonClipper
@@ -82,7 +83,8 @@ public void NonOverlapping()
.OfType().Select(x => (RectangularPolygon)x);
Assert.Single(shapes);
- Assert.Contains(this.topLeft, shapes);
+ Assert.Contains(
+ shapes, x => RectangularPolygonValueComparer.Equals(this.topLeft, x));
Assert.DoesNotContain(this.topRight, shapes);
}
@@ -93,19 +95,19 @@ public void OverLappingReturns1NewShape()
IEnumerable shapes = this.Clip(this.bigSquare, this.topLeft);
Assert.Single(shapes);
- Assert.DoesNotContain(this.bigSquare, shapes);
- Assert.DoesNotContain(this.topLeft, shapes);
+ Assert.DoesNotContain(shapes, x => RectangularPolygonValueComparer.Equals(this.bigSquare, x));
+ Assert.DoesNotContain(shapes, x => RectangularPolygonValueComparer.Equals(this.topLeft, x));
}
[Fact]
- public void OverlappingButNotCrossingRetuensOrigionalShapes()
+ public void OverlappingButNotCrossingReturnsOrigionalShapes()
{
IEnumerable shapes = this.Clip(this.bigSquare, this.hole)
.OfType().Select(x => (RectangularPolygon)x);
Assert.Equal(2, shapes.Count());
- Assert.Contains(this.bigSquare, shapes);
- Assert.Contains(this.hole, shapes);
+ Assert.Contains(shapes, x => RectangularPolygonValueComparer.Equals(this.bigSquare, x));
+ Assert.Contains(shapes, x => RectangularPolygonValueComparer.Equals(this.hole, x));
}
[Fact]
@@ -113,14 +115,17 @@ public void TouchingButNotOverlapping()
{
IEnumerable shapes = this.Clip(this.topMiddle, this.topLeft);
Assert.Single(shapes);
- Assert.DoesNotContain(this.topMiddle, shapes);
- Assert.DoesNotContain(this.topLeft, shapes);
+ Assert.DoesNotContain(shapes, x => RectangularPolygonValueComparer.Equals(this.topMiddle, x));
+ Assert.DoesNotContain(shapes, x => RectangularPolygonValueComparer.Equals(this.topLeft, x));
}
[Fact]
public void ClippingRectanglesCreateCorrectNumberOfPoints()
{
- IEnumerable paths = new RectangularPolygon(10, 10, 40, 40).Clip(new RectangularPolygon(20, 0, 20, 20)).Flatten();
+ IEnumerable paths = new RectangularPolygon(10, 10, 40, 40)
+ .Clip(new RectangularPolygon(20, 0, 20, 20))
+ .Flatten();
+
Assert.Single(paths);
IReadOnlyList points = paths.First().Points.ToArray();
diff --git a/tests/ImageSharp.Drawing.Tests/Shapes/PolygonTests.cs b/tests/ImageSharp.Drawing.Tests/Shapes/PolygonTests.cs
index e7ec3f65..95dfbb85 100644
--- a/tests/ImageSharp.Drawing.Tests/Shapes/PolygonTests.cs
+++ b/tests/ImageSharp.Drawing.Tests/Shapes/PolygonTests.cs
@@ -65,51 +65,6 @@ public void PointInPolygon(TestPoint[] controlPoints, TestPoint point, bool isIn
},
};
- [Theory]
- [MemberData(nameof(DistanceTheoryData))]
- public void Distance(TestPoint[] controlPoints, TestPoint point, float expected)
- {
- var shape = new Polygon(new LinearLineSegment(controlPoints.Select(x => (PointF)x).ToArray()));
- Assert.Equal(expected, shape.Distance(point).DistanceFromPath);
- }
-
- public static TheoryData PathDistanceTheoryData =
- new TheoryData
- {
- { new PointF(0, 0), 0f, 0f },
- { new PointF(1, 0), 0f, 1f },
- { new PointF(9, 0), 0f, 9f },
- { new PointF(10, 0), 0f, 10f },
- { new PointF(10, 1), 0f, 11f },
- { new PointF(10, 9), 0f, 19f },
- { new PointF(10, 10), 0f, 20f },
- { new PointF(9, 10), 0f, 21f },
- { new PointF(1, 10), 0f, 29f },
- { new PointF(0, 10), 0f, 30f },
- { new PointF(0, 1), 0f, 39f },
- { new PointF(4, 3), -3f, 4f },
- { new PointF(3, 4), -3f, 36f },
- { new PointF(-1, 0), 1f, 0f },
- { new PointF(1, -1), 1f, 1f },
- { new PointF(9, -1), 1f, 9f },
- { new PointF(11, 0), 1f, 10f },
- { new PointF(11, 1), 1f, 11f },
- { new PointF(11, 9), 1f, 19f },
- { new PointF(11, 10), 1f, 20f },
- { new PointF(9, 11), 1f, 21f },
- { new PointF(1, 11), 1f, 29f }
- };
-
- [Theory]
- [MemberData(nameof(PathDistanceTheoryData))]
- public void DistanceFromPath_Path(TestPoint point, float expectedDistance, float alongPath)
- {
- IPath path = new Polygon(new LinearLineSegment(new PointF(0, 0), new PointF(10, 0), new PointF(10, 10), new PointF(0, 10)));
- PointInfo info = path.Distance(point);
- Assert.Equal(expectedDistance, info.DistanceFromPath);
- Assert.Equal(alongPath, info.DistanceAlongPath);
- }
-
[Fact]
public void AsSimpleLinearPath()
{
@@ -121,29 +76,6 @@ public void AsSimpleLinearPath()
Assert.Equal(new PointF(5, 5), paths[2]);
}
- [Fact]
- public void FindIntersectionsBuffer()
- {
- var poly = new Polygon(new LinearLineSegment(new PointF(0, 0), new PointF(0, 10), new PointF(10, 10), new PointF(10, 0)));
- var buffer = new PointF[2];
-
- int hits = poly.FindIntersections(new PointF(5, -5), new PointF(5, 15), buffer, 0);
- Assert.Equal(2, hits);
- Assert.Contains(new PointF(5, 10), buffer);
- Assert.Contains(new PointF(5, 0), buffer);
- }
-
- [Fact]
- public void FindIntersectionsCollection()
- {
- var poly = new Polygon(new LinearLineSegment(new PointF(0, 0), new PointF(0, 10), new PointF(10, 10), new PointF(10, 0)));
-
- PointF[] buffer = poly.FindIntersections(new PointF(5, -5), new PointF(5, 15)).ToArray();
- Assert.Equal(2, buffer.Length);
- Assert.Contains(new PointF(5, 10), buffer);
- Assert.Contains(new PointF(5, 0), buffer);
- }
-
[Fact]
public void ReturnsWrapperOfSelfASOwnPath_SingleSegment()
{
@@ -181,140 +113,5 @@ public void MaxIntersections()
// with linear polygons its the number of points the segments have
Assert.Equal(2, poly.MaxIntersections);
}
-
- [Fact]
- public void FindBothIntersections()
- {
- var poly = new Polygon(new LinearLineSegment(
- new PointF(10, 10),
- new PointF(200, 150),
- new PointF(50, 300)));
- IEnumerable intersections = poly.FindIntersections(new PointF(float.MinValue, 55), new PointF(float.MaxValue, 55));
- Assert.Equal(2, intersections.Count());
- }
-
- [Fact]
- public void HandleClippingInnerCorner()
- {
- var simplePath = new Polygon(new LinearLineSegment(
- new PointF(10, 10),
- new PointF(200, 150),
- new PointF(50, 300)));
-
- var hole1 = new Polygon(new LinearLineSegment(
- new PointF(37, 85),
- new PointF(130, 40),
- new PointF(65, 137)));
-
- IPath poly = simplePath.Clip(hole1);
-
- IEnumerable intersections = poly.FindIntersections(new PointF(float.MinValue, 137), new PointF(float.MaxValue, 137));
-
- // returns an even number of points
- Assert.Equal(4, intersections.Count());
- }
-
- [Fact]
- public void CrossingCorner()
- {
- var simplePath = new Polygon(new LinearLineSegment(
- new PointF(10, 10),
- new PointF(200, 150),
- new PointF(50, 300)));
-
- IEnumerable intersections = simplePath.FindIntersections(new PointF(float.MinValue, 150), new PointF(float.MaxValue, 150));
-
- // returns an even number of points
- Assert.Equal(2, intersections.Count());
- }
-
- [Fact]
- public void ClippingEdgefromInside()
- {
- IPath simplePath = new RectangularPolygon(10, 10, 100, 100).Clip(new RectangularPolygon(20, 0, 20, 20));
-
- IEnumerable intersections = simplePath.FindIntersections(new PointF(float.MinValue, 20), new PointF(float.MaxValue, 20));
-
- // returns an even number of points
- Assert.Equal(4, intersections.Count());
- }
-
- [Fact]
- public void ClippingEdgeFromOutside()
- {
- var simplePath = new Polygon(new LinearLineSegment(
- new PointF(10, 10),
- new PointF(100, 10),
- new PointF(50, 300)));
-
- IEnumerable intersections = simplePath.FindIntersections(new PointF(float.MinValue, 10), new PointF(float.MaxValue, 10));
-
- // returns an even number of points
- Assert.Equal(0, intersections.Count() % 2);
- }
-
- [Fact]
- public void HandleClippingOutterCorner()
- {
- var simplePath = new Polygon(new LinearLineSegment(
- new PointF(10, 10),
- new PointF(200, 150),
- new PointF(50, 300)));
-
- var hole1 = new Polygon(new LinearLineSegment(
- new PointF(37, 85),
- new PointF(130, 40),
- new PointF(65, 137)));
-
- IPath poly = simplePath.Clip(hole1);
-
- IEnumerable intersections = poly.FindIntersections(new PointF(float.MinValue, 300), new PointF(float.MaxValue, 300));
-
- // returns an even number of points
- Assert.Equal(2, intersections.Count());
- }
-
- [Fact]
- public void MissingIntersection()
- {
- var simplePath = new Polygon(new LinearLineSegment(
- new PointF(10, 10),
- new PointF(200, 150),
- new PointF(50, 300)));
-
- var hole1 = new Polygon(new LinearLineSegment(
- new PointF(37, 85),
- new PointF(130, 40),
- new PointF(65, 137)));
-
- IPath poly = simplePath.Clip(hole1);
-
- IEnumerable intersections = poly.FindIntersections(new PointF(float.MinValue, 85), new PointF(float.MaxValue, 85));
-
- // returns an even number of points
- Assert.Equal(4, intersections.Count());
- }
-
- [Theory]
- [InlineData(243)]
- [InlineData(341)]
- [InlineData(199)]
- public void BezierPolygonReturning2Points(int y)
- {
- // missing bands in test from ImageSharp
- PointF[] simplePath = new[]
- {
- new PointF(10, 400),
- new PointF(30, 10),
- new PointF(240, 30),
- new PointF(300, 400)
- };
-
- var poly = new Polygon(new CubicBezierLineSegment(simplePath));
-
- var points = poly.FindIntersections(new PointF(float.MinValue, y), new PointF(float.MaxValue, y)).ToList();
-
- Assert.Equal(2, points.Count);
- }
}
}
diff --git a/tests/ImageSharp.Drawing.Tests/Shapes/RectangleTests.cs b/tests/ImageSharp.Drawing.Tests/Shapes/RectangleTests.cs
index 71271ed2..66cd0a63 100644
--- a/tests/ImageSharp.Drawing.Tests/Shapes/RectangleTests.cs
+++ b/tests/ImageSharp.Drawing.Tests/Shapes/RectangleTests.cs
@@ -69,37 +69,6 @@ public class RectangleTests
},
};
- public static TheoryData PathDistanceTheoryData =
- new TheoryData
- {
- { new PointF(0, 0), 0f, 0f },
- { new PointF(1, 0), 0f, 1f },
- { new PointF(9, 0), 0f, 9f },
- { new PointF(10, 0), 0f, 10f },
- { new PointF(10, 1), 0f, 11f },
- { new PointF(10, 9), 0f, 19f },
- { new PointF(10, 10), 0f, 20f },
- { new PointF(9, 10), 0f, 21f },
- { new PointF(1, 10), 0f, 29f },
- { new PointF(0, 10), 0f, 30f },
- { new PointF(0, 9), 0f, 31f },
- { new PointF(0, 1), 0f, 39f },
- { new PointF(4, 3), -3f, 4f },
- { new PointF(3, 4), -3f, 36f },
- { new PointF(-1, 0), 1f, 0f },
- { new PointF(1, -1), 1f, 1f },
- { new PointF(9, -1), 1f, 9f },
- { new PointF(11, 0), 1f, 10f },
- { new PointF(11, 1), 1f, 11f },
- { new PointF(11, 9), 1f, 19f },
- { new PointF(11, 10), 1f, 20f },
- { new PointF(9, 11), 1f, 21f },
- { new PointF(1, 11), 1f, 29f },
- { new PointF(-1, 10), 1f, 30f },
- { new PointF(-1, 9), 1f, 31f },
- { new PointF(-1, 1), 1f, 39f },
- };
-
[Theory]
[MemberData(nameof(PointInPolygonTheoryData))]
public void PointInPolygon(TestPoint location, TestSize size, TestPoint point, bool isInside)
@@ -108,25 +77,6 @@ public void PointInPolygon(TestPoint location, TestSize size, TestPoint point, b
Assert.Equal(isInside, shape.Contains(point));
}
- [Theory]
- [MemberData(nameof(DistanceTheoryData))]
- public void Distance(TestPoint location, TestSize size, TestPoint point, float expectecDistance)
- {
- IPath shape = new RectangularPolygon(location, size);
-
- Assert.Equal(expectecDistance, shape.Distance(point).DistanceFromPath);
- }
-
- [Theory]
- [MemberData(nameof(PathDistanceTheoryData))]
- public void DistanceFromPath_Path(TestPoint point, float expectecDistance, float alongPath)
- {
- IPath shape = new RectangularPolygon(0, 0, 10, 10);
- PointInfo info = shape.Distance(point);
- Assert.Equal(expectecDistance, info.DistanceFromPath);
- Assert.Equal(alongPath, info.DistanceAlongPath);
- }
-
[Fact]
public void Left()
{
@@ -184,36 +134,6 @@ public void LienearSegements()
Assert.Equal(new PointF(10, 24), segemnts[3]);
}
- [Fact]
- public void Intersections_2()
- {
- IPath shape = new RectangularPolygon(1, 1, 10, 10);
- IEnumerable intersections = shape.FindIntersections(new PointF(0, 5), new PointF(20, 5));
-
- Assert.Equal(2, intersections.Count());
- Assert.Equal(new PointF(1, 5), intersections.First());
- Assert.Equal(new PointF(11, 5), intersections.Last());
- }
-
- [Fact]
- public void Intersections_1()
- {
- IPath shape = new RectangularPolygon(1, 1, 10, 10);
- IEnumerable intersections = shape.FindIntersections(new PointF(0, 5), new PointF(5, 5));
-
- Assert.Single(intersections);
- Assert.Equal(new PointF(1, 5), intersections.First());
- }
-
- [Fact]
- public void Intersections_0()
- {
- IPath shape = new RectangularPolygon(1, 1, 10, 10);
- IEnumerable intersections = shape.FindIntersections(new PointF(0, 5), new PointF(-5, 5));
-
- Assert.Empty(intersections);
- }
-
[Fact]
public void Bounds_Path()
{
@@ -224,13 +144,6 @@ public void Bounds_Path()
Assert.Equal(24, shape.Bounds.Bottom);
}
- [Fact]
- public void MaxIntersections_Shape()
- {
- IPath shape = new RectangularPolygon(10, 11, 12, 13);
- Assert.Equal(4, shape.MaxIntersections);
- }
-
[Fact]
public void ShapePaths()
{
@@ -279,8 +192,8 @@ public void Center()
[InlineData(620, 150, 50, Pi)] // wrap about end of path
public void PointOnPath(float distance, float expectedX, float expectedY, float expectedAngle)
{
- IPath shape = new RectangularPolygon(50, 50, 200, 60);
- SegmentInfo point = shape.PointAlongPath(distance);
+ var shape = new RectangularPolygon(50, 50, 200, 60);
+ SegmentInfo point = ((IPathInternals)shape).PointAlongPath(distance);
Assert.Equal(expectedX, point.Point.X);
Assert.Equal(expectedY, point.Point.Y);
Assert.Equal(expectedAngle, point.Angle);
diff --git a/tests/ImageSharp.Drawing.Tests/Shapes/RegularPolygonTests.cs b/tests/ImageSharp.Drawing.Tests/Shapes/RegularPolygonTests.cs
index 9ef617d5..4330d567 100644
--- a/tests/ImageSharp.Drawing.Tests/Shapes/RegularPolygonTests.cs
+++ b/tests/ImageSharp.Drawing.Tests/Shapes/RegularPolygonTests.cs
@@ -95,23 +95,5 @@ public void AngleChangesOnePointToStartAtThatPosition()
Assert.Contains(allAngles, a => Math.Abs(a - anAngle) > 0.000001);
}
-
- [Fact]
- public void TriangleMissingIntersectionsDownCenter()
- {
- var poly = new RegularPolygon(50, 50, 3, 30);
- PointF[] points = poly.FindIntersections(new PointF(0, 50), new PointF(100, 50)).ToArray();
-
- Assert.Equal(2, points.Length);
- }
-
- [Fact]
- public void ClippingCornerShouldReturn2Points()
- {
- var poly = new RegularPolygon(50, 50, 7, 30, -(float)Math.PI);
- PointF[] points = poly.FindIntersections(new PointF(0, 20), new PointF(100, 20)).ToArray();
-
- Assert.Equal(2, points.Length);
- }
}
}
diff --git a/tests/ImageSharp.Drawing.Tests/Shapes/StarTests.cs b/tests/ImageSharp.Drawing.Tests/Shapes/StarTests.cs
index 48574d2a..7472c044 100644
--- a/tests/ImageSharp.Drawing.Tests/Shapes/StarTests.cs
+++ b/tests/ImageSharp.Drawing.Tests/Shapes/StarTests.cs
@@ -107,23 +107,5 @@ public void AngleChangesOnePointToStartAtThatPosition()
Assert.Contains(allAngles, a => Math.Abs(a - anAngle) > 0.000001);
}
-
- [Fact]
- public void TriangleMissingIntersectionsDownCenter()
- {
- var poly = new Star(50, 50, 3, 50, 30);
- PointF[] points = poly.FindIntersections(new Vector2(0, 50), new Vector2(100, 50)).ToArray();
-
- Assert.Equal(2, points.Length);
- }
-
- [Fact]
- public void ClippingCornerShouldReturn2Points()
- {
- var star = new Star(40, 40, 3, 10, 20);
- PointF[] points = star.FindIntersections(new Vector2(0, 30), new Vector2(100, 30)).ToArray();
-
- Assert.True(points.Length % 2 == 0, "Should have even number of intersection points");
- }
}
}
diff --git a/tests/ImageSharp.Drawing.Tests/TestUtilities/RectangularPolygonValueComparer.cs b/tests/ImageSharp.Drawing.Tests/TestUtilities/RectangularPolygonValueComparer.cs
new file mode 100644
index 00000000..925dcecc
--- /dev/null
+++ b/tests/ImageSharp.Drawing.Tests/TestUtilities/RectangularPolygonValueComparer.cs
@@ -0,0 +1,20 @@
+// Copyright (c) Six Labors.
+// Licensed under the Apache License, Version 2.0.
+
+namespace SixLabors.ImageSharp.Drawing.Tests.TestUtilities
+{
+ ///