Browse Source

remove all drawing except draw image

af/merge-core
Scott Williams 6 years ago
parent
commit
a56287cb85
  1. 15
      ImageSharp.sln
  2. 3
      appveyor.yml
  3. 3
      build.ps1
  4. 53
      src/ImageSharp.Drawing/Common/Extensions/GraphicsOptionsExtensions.cs
  5. 24
      src/ImageSharp.Drawing/ImageSharp.Drawing.csproj
  6. 2
      src/ImageSharp.Drawing/ImageSharp.Drawing.csproj.DotSettings
  7. 36
      src/ImageSharp.Drawing/Primitives/Region.cs
  8. 24
      src/ImageSharp.Drawing/Primitives/ShapePath.cs
  9. 64
      src/ImageSharp.Drawing/Primitives/ShapeRegion.cs
  10. 117
      src/ImageSharp.Drawing/Processing/BrushApplicator.cs
  11. 222
      src/ImageSharp.Drawing/Processing/Brushes.cs
  12. 35
      src/ImageSharp.Drawing/Processing/ColorStop.cs
  13. 22
      src/ImageSharp.Drawing/Processing/DrawingHelpers.cs
  14. 165
      src/ImageSharp.Drawing/Processing/EllipticGradientBrush.cs
  15. 106
      src/ImageSharp.Drawing/Processing/Extensions/DrawBezierExtensions.cs
  16. 106
      src/ImageSharp.Drawing/Processing/Extensions/DrawLineExtensions.cs
  17. 110
      src/ImageSharp.Drawing/Processing/Extensions/DrawPathCollectionExtensions.cs
  18. 103
      src/ImageSharp.Drawing/Processing/Extensions/DrawPathExtensions.cs
  19. 106
      src/ImageSharp.Drawing/Processing/Extensions/DrawPolygonExtensions.cs
  20. 103
      src/ImageSharp.Drawing/Processing/Extensions/DrawRectangleExtensions.cs
  21. 179
      src/ImageSharp.Drawing/Processing/Extensions/DrawTextExtensions.cs
  22. 76
      src/ImageSharp.Drawing/Processing/Extensions/FillPathBuilderExtensions.cs
  23. 76
      src/ImageSharp.Drawing/Processing/Extensions/FillPathCollectionExtensions.cs
  24. 64
      src/ImageSharp.Drawing/Processing/Extensions/FillPathExtensions.cs
  25. 70
      src/ImageSharp.Drawing/Processing/Extensions/FillPolygonExtensions.cs
  26. 66
      src/ImageSharp.Drawing/Processing/Extensions/FillRectangleExtensions.cs
  27. 95
      src/ImageSharp.Drawing/Processing/Extensions/FillRegionExtensions.cs
  28. 164
      src/ImageSharp.Drawing/Processing/GradientBrush.cs
  29. 37
      src/ImageSharp.Drawing/Processing/GradientRepetitionMode.cs
  30. 40
      src/ImageSharp.Drawing/Processing/IBrush.cs
  31. 28
      src/ImageSharp.Drawing/Processing/IPen.cs
  32. 173
      src/ImageSharp.Drawing/Processing/ImageBrush.cs
  33. 160
      src/ImageSharp.Drawing/Processing/LinearGradientBrush.cs
  34. 309
      src/ImageSharp.Drawing/Processing/PathGradientBrush.cs
  35. 178
      src/ImageSharp.Drawing/Processing/PatternBrush.cs
  36. 76
      src/ImageSharp.Drawing/Processing/Pen.cs
  37. 97
      src/ImageSharp.Drawing/Processing/Pens.cs
  38. 41
      src/ImageSharp.Drawing/Processing/Processors/Drawing/FillProcessor.cs
  39. 122
      src/ImageSharp.Drawing/Processing/Processors/Drawing/FillProcessor{TPixel}.cs
  40. 49
      src/ImageSharp.Drawing/Processing/Processors/Drawing/FillRegionProcessor.cs
  41. 197
      src/ImageSharp.Drawing/Processing/Processors/Drawing/FillRegionProcessor{TPixel}.cs
  42. 79
      src/ImageSharp.Drawing/Processing/Processors/Text/DrawTextProcessor.cs
  43. 453
      src/ImageSharp.Drawing/Processing/Processors/Text/DrawTextProcessor{TPixel}.cs
  44. 104
      src/ImageSharp.Drawing/Processing/RadialGradientBrush.cs
  45. 166
      src/ImageSharp.Drawing/Processing/RecolorBrush.cs
  46. 139
      src/ImageSharp.Drawing/Processing/SolidBrush.cs
  47. 217
      src/ImageSharp.Drawing/Processing/TextGraphicsOptions.cs
  48. 29
      src/ImageSharp.Drawing/Utils/NumberUtils.cs
  49. 84
      src/ImageSharp.Drawing/Utils/QuickSort.cs
  50. 0
      src/ImageSharp/Drawing/Processing/Extensions/DrawImageExtensions.cs
  51. 0
      src/ImageSharp/Drawing/Processing/Processors/Drawing/DrawImageProcessor.cs
  52. 0
      src/ImageSharp/Drawing/Processing/Processors/Drawing/DrawImageProcessor{TPixelBg,TPixelFg}.cs
  53. 63
      tests/ImageSharp.Benchmarks/Drawing/DrawBeziers.cs
  54. 62
      tests/ImageSharp.Benchmarks/Drawing/DrawLines.cs
  55. 60
      tests/ImageSharp.Benchmarks/Drawing/DrawPolygon.cs
  56. 96
      tests/ImageSharp.Benchmarks/Drawing/DrawText.cs
  57. 102
      tests/ImageSharp.Benchmarks/Drawing/DrawTextOutline.cs
  58. 84
      tests/ImageSharp.Benchmarks/Drawing/FillPolygon.cs
  59. 59
      tests/ImageSharp.Benchmarks/Drawing/FillRectangle.cs
  60. 53
      tests/ImageSharp.Benchmarks/Drawing/FillWithPattern.cs
  61. 1
      tests/ImageSharp.Benchmarks/ImageSharp.Benchmarks.csproj
  62. 1
      tests/ImageSharp.Sandbox46/ImageSharp.Sandbox46.csproj
  63. 47
      tests/ImageSharp.Tests/Drawing/DrawBezierTests.cs
  64. 69
      tests/ImageSharp.Tests/Drawing/DrawComplexPolygonTests.cs
  65. 4
      tests/ImageSharp.Tests/Drawing/DrawImageTests.cs
  66. 99
      tests/ImageSharp.Tests/Drawing/DrawLinesTests.cs
  67. 78
      tests/ImageSharp.Tests/Drawing/DrawPathTests.cs
  68. 41
      tests/ImageSharp.Tests/Drawing/DrawPolygonTests.cs
  69. 61
      tests/ImageSharp.Tests/Drawing/FillComplexPolygonTests.cs
  70. 147
      tests/ImageSharp.Tests/Drawing/FillEllipticGradientBrushTest.cs
  71. 50
      tests/ImageSharp.Tests/Drawing/FillImageBrushTests.cs
  72. 453
      tests/ImageSharp.Tests/Drawing/FillLinearGradientBrushTests.cs
  73. 157
      tests/ImageSharp.Tests/Drawing/FillPathGradientBrushTests.cs
  74. 278
      tests/ImageSharp.Tests/Drawing/FillPatternBrushTests.cs
  75. 154
      tests/ImageSharp.Tests/Drawing/FillPolygonTests.cs
  76. 73
      tests/ImageSharp.Tests/Drawing/FillRadialGradientBrushTests.cs
  77. 156
      tests/ImageSharp.Tests/Drawing/FillRegionProcessorTests.cs
  78. 201
      tests/ImageSharp.Tests/Drawing/FillSolidBrushTests.cs
  79. 119
      tests/ImageSharp.Tests/Drawing/Paths/DrawPathCollection.cs
  80. 94
      tests/ImageSharp.Tests/Drawing/Paths/FillPath.cs
  81. 123
      tests/ImageSharp.Tests/Drawing/Paths/FillPathCollection.cs
  82. 95
      tests/ImageSharp.Tests/Drawing/Paths/FillPolygon.cs
  83. 97
      tests/ImageSharp.Tests/Drawing/Paths/FillRectangle.cs
  84. 10
      tests/ImageSharp.Tests/Drawing/Paths/ShapePathTests.cs
  85. 127
      tests/ImageSharp.Tests/Drawing/Paths/ShapeRegionTests.cs
  86. 52
      tests/ImageSharp.Tests/Drawing/RecolorImageTests.cs
  87. 64
      tests/ImageSharp.Tests/Drawing/SolidBezierTests.cs
  88. 178
      tests/ImageSharp.Tests/Drawing/SolidFillBlendedShapesTests.cs
  89. 154
      tests/ImageSharp.Tests/Drawing/Text/DrawText.cs
  90. 265
      tests/ImageSharp.Tests/Drawing/Text/DrawTextOnImageTests.cs
  91. 211
      tests/ImageSharp.Tests/Drawing/Text/TextGraphicsOptionsTests.cs
  92. 51
      tests/ImageSharp.Tests/Drawing/Utils/QuickSortTests.cs
  93. 10
      tests/ImageSharp.Tests/GraphicsOptionsTests.cs
  94. 22
      tests/ImageSharp.Tests/Image/ImageTests.WrapMemory.cs
  95. 1
      tests/ImageSharp.Tests/ImageSharp.Tests.csproj
  96. 51
      tests/ImageSharp.Tests/Issues/Issue412.cs
  97. 7
      tests/ImageSharp.Tests/TestUtilities/ImageProviders/SolidProvider.cs
  98. 46
      tests/ImageSharp.Tests/TestUtilities/Tests/TestEnvironmentTests.cs

15
ImageSharp.sln

@ -64,8 +64,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{815C0625-CD3
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ImageSharp", "src\ImageSharp\ImageSharp.csproj", "{2AA31A1F-142C-43F4-8687-09ABCA4B3A26}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ImageSharp.Drawing", "src\ImageSharp.Drawing\ImageSharp.Drawing.csproj", "{2E33181E-6E28-4662-A801-E2E7DC206029}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{56801022-D71A-4FBE-BC5B-CBA08E2284EC}"
ProjectSection(SolutionItems) = preProject
tests\Directory.Build.props = tests\Directory.Build.props
@ -378,18 +376,6 @@ Global
{2AA31A1F-142C-43F4-8687-09ABCA4B3A26}.Release|x64.Build.0 = Release|Any CPU
{2AA31A1F-142C-43F4-8687-09ABCA4B3A26}.Release|x86.ActiveCfg = Release|Any CPU
{2AA31A1F-142C-43F4-8687-09ABCA4B3A26}.Release|x86.Build.0 = Release|Any CPU
{2E33181E-6E28-4662-A801-E2E7DC206029}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{2E33181E-6E28-4662-A801-E2E7DC206029}.Debug|Any CPU.Build.0 = Debug|Any CPU
{2E33181E-6E28-4662-A801-E2E7DC206029}.Debug|x64.ActiveCfg = Debug|Any CPU
{2E33181E-6E28-4662-A801-E2E7DC206029}.Debug|x64.Build.0 = Debug|Any CPU
{2E33181E-6E28-4662-A801-E2E7DC206029}.Debug|x86.ActiveCfg = Debug|Any CPU
{2E33181E-6E28-4662-A801-E2E7DC206029}.Debug|x86.Build.0 = Debug|Any CPU
{2E33181E-6E28-4662-A801-E2E7DC206029}.Release|Any CPU.ActiveCfg = Release|Any CPU
{2E33181E-6E28-4662-A801-E2E7DC206029}.Release|Any CPU.Build.0 = Release|Any CPU
{2E33181E-6E28-4662-A801-E2E7DC206029}.Release|x64.ActiveCfg = Release|Any CPU
{2E33181E-6E28-4662-A801-E2E7DC206029}.Release|x64.Build.0 = Release|Any CPU
{2E33181E-6E28-4662-A801-E2E7DC206029}.Release|x86.ActiveCfg = Release|Any CPU
{2E33181E-6E28-4662-A801-E2E7DC206029}.Release|x86.Build.0 = Release|Any CPU
{EA3000E9-2A91-4EC4-8A68-E566DEBDC4F6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{EA3000E9-2A91-4EC4-8A68-E566DEBDC4F6}.Debug|Any CPU.Build.0 = Debug|Any CPU
{EA3000E9-2A91-4EC4-8A68-E566DEBDC4F6}.Debug|x64.ActiveCfg = Debug|Any CPU
@ -434,7 +420,6 @@ Global
{FBE8C1AD-5AEC-4514-9B64-091D8E145865} = {1799C43E-5C54-4A8F-8D64-B1475241DB0D}
{2B02E303-7CC6-4E15-97EE-DBE86B287553} = {E919DF0B-2607-4462-8FC0-5C98FE50F8C9}
{2AA31A1F-142C-43F4-8687-09ABCA4B3A26} = {815C0625-CD3D-440F-9F80-2D83856AB7AE}
{2E33181E-6E28-4662-A801-E2E7DC206029} = {815C0625-CD3D-440F-9F80-2D83856AB7AE}
{D4C5EC58-F8E6-4636-B9EE-C99D2578E5C6} = {56801022-D71A-4FBE-BC5B-CBA08E2284EC}
{FA55F5DE-11A6-487D-ABA4-BC93A02717DD} = {56801022-D71A-4FBE-BC5B-CBA08E2284EC}
{9DA226A1-8656-49A8-A58A-A8B5C081AD66} = {FA55F5DE-11A6-487D-ABA4-BC93A02717DD}

3
appveyor.yml

@ -55,7 +55,6 @@ test_script:
after_test:
- cmd: appveyor PushArtifact "artifacts\SixLabors.ImageSharp.%APPVEYOR_BUILD_VERSION%.nupkg"
- cmd: appveyor PushArtifact "artifacts\SixLabors.ImageSharp.Drawing.%APPVEYOR_BUILD_VERSION%.nupkg"
deploy:
# MyGet Deployment for builds & releases
@ -66,4 +65,4 @@ deploy:
secure: V/lEHP0UeMWIpWd0fiNlY2IgbCnJKQlGdRksECdJbOBdaE20Fl0RNL7WyqHe02o4
artifact: /.*\.nupkg/
on:
branch: master
branch: master

3
build.ps1

@ -117,6 +117,3 @@ if ($LASTEXITCODE ){ Exit $LASTEXITCODE }
Write-Host "Packaging projects"
dotnet pack ./src/ImageSharp/ -c Release --output ../../artifacts --no-build /p:packageversion=$version
if ($LASTEXITCODE ){ Exit $LASTEXITCODE }
dotnet pack ./src/ImageSharp.Drawing/ -c Release --output ../../artifacts --no-build /p:packageversion=$version
if ($LASTEXITCODE ){ Exit $LASTEXITCODE }

53
src/ImageSharp.Drawing/Common/Extensions/GraphicsOptionsExtensions.cs

@ -1,53 +0,0 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System.Numerics;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp
{
/// <summary>
/// Extensions methods fpor the <see cref="GraphicsOptions"/> class.
/// </summary>
internal static class GraphicsOptionsExtensions
{
/// <summary>
/// Evaluates if a given SOURCE color can completely replace a BACKDROP color given the current blending and composition settings.
/// </summary>
/// <param name="options">The graphics options.</param>
/// <param name="color">The source color.</param>
/// <returns>true if the color can be considered opaque</returns>
/// <remarks>
/// Blending and composition is an expensive operation, in some cases, like
/// filling with a solid color, the blending can be avoided by a plain color replacement.
/// This method can be useful for such processors to select the fast path.
/// </remarks>
public static bool IsOpaqueColorWithoutBlending(this GraphicsOptions options, Color color)
{
if (options.ColorBlendingMode != PixelColorBlendingMode.Normal)
{
return false;
}
if (options.AlphaCompositionMode != PixelAlphaCompositionMode.SrcOver
&& options.AlphaCompositionMode != PixelAlphaCompositionMode.Src)
{
return false;
}
const float Opaque = 1F;
if (options.BlendPercentage != Opaque)
{
return false;
}
if (((Vector4)color).W != Opaque)
{
return false;
}
return true;
}
}
}

24
src/ImageSharp.Drawing/ImageSharp.Drawing.csproj

@ -1,24 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<AssemblyName>SixLabors.ImageSharp.Drawing</AssemblyName>
<AssemblyTitle>SixLabors.ImageSharp.Drawing</AssemblyTitle>
<Description>An extension to ImageSharp that allows the drawing of images, paths, and text.</Description>
<PackageId>SixLabors.ImageSharp.Drawing</PackageId>
<PackageTags>Image Draw Shape Path Font</PackageTags>
<RootNamespace>SixLabors.ImageSharp</RootNamespace>
<TargetFrameworks>netcoreapp2.1;netstandard2.0;netstandard1.3</TargetFrameworks>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="SixLabors.Fonts" />
<PackageReference Include="SixLabors.Shapes" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\ImageSharp\ImageSharp.csproj" />
</ItemGroup>
</Project>

2
src/ImageSharp.Drawing/ImageSharp.Drawing.csproj.DotSettings

@ -1,2 +0,0 @@
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=processing_005Cextensions/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>

36
src/ImageSharp.Drawing/Primitives/Region.cs

@ -1,36 +0,0 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System;
using SixLabors.Primitives;
namespace SixLabors.ImageSharp.Primitives
{
/// <summary>
/// Represents a region of an image.
/// </summary>
public abstract class Region
{
/// <summary>
/// Gets the maximum number of intersections to could be returned.
/// </summary>
public abstract int MaxIntersections { get; }
/// <summary>
/// Gets the bounding box that entirely surrounds this region.
/// </summary>
/// <remarks>
/// This should always contains all possible points returned from <see cref="Scan"/>.
/// </remarks>
public abstract Rectangle Bounds { get; }
/// <summary>
/// Scans the X axis for intersections at the Y axis position.
/// </summary>
/// <param name="y">The position along the y axis to find intersections.</param>
/// <param name="buffer">The buffer.</param>
/// <param name="configuration">A <see cref="Configuration"/> instance in the context of the caller.</param>
/// <returns>The number of intersections found.</returns>
public abstract int Scan(float y, Span<float> buffer, Configuration configuration);
}
}

24
src/ImageSharp.Drawing/Primitives/ShapePath.cs

@ -1,24 +0,0 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using SixLabors.ImageSharp.Processing;
using SixLabors.Shapes;
namespace SixLabors.ImageSharp.Primitives
{
/// <summary>
/// A mapping between a <see cref="IPath"/> and a region.
/// </summary>
internal class ShapePath : ShapeRegion
{
/// <summary>
/// Initializes a new instance of the <see cref="ShapePath"/> class.
/// </summary>
/// <param name="shape">The shape.</param>
/// <param name="pen">The pen to apply to the shape.</param>
public ShapePath(IPath shape, IPen pen)
: base(shape.GenerateOutline(pen.StrokeWidth, pen.StrokePattern))
{
}
}
}

64
src/ImageSharp.Drawing/Primitives/ShapeRegion.cs

@ -1,64 +0,0 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Buffers;
using SixLabors.ImageSharp.Memory;
using SixLabors.Primitives;
using SixLabors.Shapes;
namespace SixLabors.ImageSharp.Primitives
{
/// <summary>
/// A mapping between a <see cref="IPath"/> and a region.
/// </summary>
internal class ShapeRegion : Region
{
/// <summary>
/// Initializes a new instance of the <see cref="ShapeRegion"/> class.
/// </summary>
/// <param name="shape">The shape.</param>
public ShapeRegion(IPath shape)
{
this.Shape = shape.AsClosedPath();
int left = (int)MathF.Floor(shape.Bounds.Left);
int top = (int)MathF.Floor(shape.Bounds.Top);
int right = (int)MathF.Ceiling(shape.Bounds.Right);
int bottom = (int)MathF.Ceiling(shape.Bounds.Bottom);
this.Bounds = Rectangle.FromLTRB(left, top, right, bottom);
}
/// <summary>
/// Gets the fillable shape
/// </summary>
public IPath Shape { get; }
/// <inheritdoc/>
public override int MaxIntersections => this.Shape.MaxIntersections;
/// <inheritdoc/>
public override Rectangle Bounds { get; }
/// <inheritdoc/>
public override int Scan(float y, Span<float> buffer, Configuration configuration)
{
var start = new PointF(this.Bounds.Left - 1, y);
var end = new PointF(this.Bounds.Right + 1, y);
using (IMemoryOwner<PointF> tempBuffer = configuration.MemoryAllocator.Allocate<PointF>(buffer.Length))
{
Span<PointF> innerBuffer = tempBuffer.Memory.Span;
int count = this.Shape.FindIntersections(start, end, innerBuffer);
for (int i = 0; i < count; i++)
{
buffer[i] = innerBuffer[i].X;
}
return count;
}
}
}
}

117
src/ImageSharp.Drawing/Processing/BrushApplicator.cs

@ -1,117 +0,0 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Buffers;
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.Memory;
namespace SixLabors.ImageSharp.Processing
{
/// <summary>
/// A primitive that converts a point into a color for discovering the fill color based on an implementation.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <seealso cref="IDisposable" />
public abstract class BrushApplicator<TPixel> : IDisposable
where TPixel : struct, IPixel<TPixel>
{
/// <summary>
/// Initializes a new instance of the <see cref="BrushApplicator{TPixel}"/> class.
/// </summary>
/// <param name="configuration">The configuration instance to use when performing operations.</param>
/// <param name="options">The graphics options.</param>
/// <param name="target">The target.</param>
internal BrushApplicator(Configuration configuration, GraphicsOptions options, ImageFrame<TPixel> target)
{
this.Configuration = configuration;
this.Target = target;
this.Options = options;
this.Blender = PixelOperations<TPixel>.Instance.GetPixelBlender(options);
}
/// <summary>
/// Gets the configuration instance to use when performing operations.
/// </summary>
protected Configuration Configuration { get; }
/// <summary>
/// Gets the pixel blender.
/// </summary>
internal PixelBlender<TPixel> Blender { get; }
/// <summary>
/// Gets the target image.
/// </summary>
protected ImageFrame<TPixel> Target { get; }
/// <summary>
/// Gets thegraphics options
/// </summary>
protected GraphicsOptions Options { get; }
/// <summary>
/// Gets the overlay pixel at the specified position.
/// </summary>
/// <param name="x">The x-coordinate.</param>
/// <param name="y">The y-coordinate.</param>
/// <returns>The <see typeparam="TPixel"/> at the specified position.</returns>
internal abstract TPixel this[int x, int y] { get; }
/// <inheritdoc/>
public void Dispose()
{
this.Dispose(true);
GC.SuppressFinalize(this);
}
/// <summary>
/// Disposes the object and frees resources for the Garbage Collector.
/// </summary>
/// <param name="disposing">Whether to dispose managed and unmanaged objects.</param>
protected virtual void Dispose(bool disposing)
{
}
/// <summary>
/// Applies the opacity weighting for each pixel in a scanline to the target based on the pattern contained in the brush.
/// </summary>
/// <param name="scanline">A collection of opacity values between 0 and 1 to be merged with the brushed color value before being applied to the target.</param>
/// <param name="x">The x-position in the target pixel space that the start of the scanline data corresponds to.</param>
/// <param name="y">The y-position in the target pixel space that whole scanline corresponds to.</param>
/// <remarks>scanlineBuffer will be > scanlineWidth but provide and offset in case we want to share a larger buffer across runs.</remarks>
internal virtual void Apply(Span<float> scanline, int x, int y)
{
MemoryAllocator memoryAllocator = this.Target.MemoryAllocator;
using (IMemoryOwner<float> amountBuffer = memoryAllocator.Allocate<float>(scanline.Length))
using (IMemoryOwner<TPixel> overlay = memoryAllocator.Allocate<TPixel>(scanline.Length))
{
Span<float> amountSpan = amountBuffer.Memory.Span;
Span<TPixel> 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<TPixel> destinationRow = this.Target.GetPixelRowSpan(y).Slice(x, scanline.Length);
this.Blender.Blend(this.Configuration, destinationRow, destinationRow, overlaySpan, amountSpan);
}
}
}
}

222
src/ImageSharp.Drawing/Processing/Brushes.cs

@ -1,222 +0,0 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
namespace SixLabors.ImageSharp.Processing
{
/// <summary>
/// A collection of methods for creating generic brushes.
/// </summary>
/// <returns>A New <see cref="PatternBrush"/></returns>
public static class Brushes
{
/// <summary>
/// Percent10 Hatch Pattern
/// </summary>
/// ---> x axis
/// ^
/// | y - axis
/// |
/// see PatternBrush for details about how to make new patterns work
private static readonly bool[,] Percent10Pattern =
{
{ true, false, false, false },
{ false, false, false, false },
{ false, false, true, false },
{ false, false, false, false }
};
/// <summary>
/// Percent20 pattern.
/// </summary>
private static readonly bool[,] Percent20Pattern =
{
{ true, false, false, false },
{ false, false, true, false },
{ true, false, false, false },
{ false, false, true, false }
};
/// <summary>
/// Horizontal Hatch Pattern
/// </summary>
private static readonly bool[,] HorizontalPattern =
{
{ false },
{ true },
{ false },
{ false }
};
/// <summary>
/// Min Pattern
/// </summary>
private static readonly bool[,] MinPattern =
{
{ false },
{ false },
{ false },
{ true }
};
/// <summary>
/// Vertical Pattern
/// </summary>
private static readonly bool[,] VerticalPattern =
{
{ false, true, false, false },
};
/// <summary>
/// Forward Diagonal Pattern
/// </summary>
private static readonly bool[,] ForwardDiagonalPattern =
{
{ false, false, false, true },
{ false, false, true, false },
{ false, true, false, false },
{ true, false, false, false }
};
/// <summary>
/// Backward Diagonal Pattern
/// </summary>
private static readonly bool[,] BackwardDiagonalPattern =
{
{ true, false, false, false },
{ false, true, false, false },
{ false, false, true, false },
{ false, false, false, true }
};
/// <summary>
/// Create as brush that will paint a solid color
/// </summary>
/// <param name="color">The color.</param>
/// <returns>A New <see cref="PatternBrush"/></returns>
public static SolidBrush Solid(Color color) => new SolidBrush(color);
/// <summary>
/// Create as brush that will paint a Percent10 Hatch Pattern with the specified colors
/// </summary>
/// <param name="foreColor">Color of the foreground.</param>
/// <returns>A New <see cref="PatternBrush"/></returns>
public static PatternBrush Percent10(Color foreColor) =>
new PatternBrush(foreColor, Color.Transparent, Percent10Pattern);
/// <summary>
/// Create as brush that will paint a Percent10 Hatch Pattern with the specified colors
/// </summary>
/// <param name="foreColor">Color of the foreground.</param>
/// <param name="backColor">Color of the background.</param>
/// <returns>A New <see cref="PatternBrush"/></returns>
public static PatternBrush Percent10(Color foreColor, Color backColor) =>
new PatternBrush(foreColor, backColor, Percent10Pattern);
/// <summary>
/// Create as brush that will paint a Percent20 Hatch Pattern with the specified foreground color and a
/// transparent background.
/// </summary>
/// <param name="foreColor">Color of the foreground.</param>
/// <returns>A New <see cref="PatternBrush"/></returns>
public static PatternBrush Percent20(Color foreColor) =>
new PatternBrush(foreColor, Color.Transparent, Percent20Pattern);
/// <summary>
/// Create as brush that will paint a Percent20 Hatch Pattern with the specified colors
/// </summary>
/// <param name="foreColor">Color of the foreground.</param>
/// <param name="backColor">Color of the background.</param>
/// <returns>A New <see cref="PatternBrush"/></returns>
public static PatternBrush Percent20(Color foreColor, Color backColor) =>
new PatternBrush(foreColor, backColor, Percent20Pattern);
/// <summary>
/// Create as brush that will paint a Horizontal Hatch Pattern with the specified foreground color and a
/// transparent background.
/// </summary>
/// <param name="foreColor">Color of the foreground.</param>
/// <returns>A New <see cref="PatternBrush"/></returns>
public static PatternBrush Horizontal(Color foreColor) =>
new PatternBrush(foreColor, Color.Transparent, HorizontalPattern);
/// <summary>
/// Create as brush that will paint a Horizontal Hatch Pattern with the specified colors
/// </summary>
/// <param name="foreColor">Color of the foreground.</param>
/// <param name="backColor">Color of the background.</param>
/// <returns>A New <see cref="PatternBrush"/></returns>
public static PatternBrush Horizontal(Color foreColor, Color backColor) =>
new PatternBrush(foreColor, backColor, HorizontalPattern);
/// <summary>
/// Create as brush that will paint a Min Hatch Pattern with the specified foreground color and a
/// transparent background.
/// </summary>
/// <param name="foreColor">Color of the foreground.</param>
/// <returns>A New <see cref="PatternBrush"/></returns>
public static PatternBrush Min(Color foreColor) => new PatternBrush(foreColor, Color.Transparent, MinPattern);
/// <summary>
/// Create as brush that will paint a Min Hatch Pattern with the specified colors
/// </summary>
/// <param name="foreColor">Color of the foreground.</param>
/// <param name="backColor">Color of the background.</param>
/// <returns>A New <see cref="PatternBrush"/></returns>
public static PatternBrush Min(Color foreColor, Color backColor) =>
new PatternBrush(foreColor, backColor, MinPattern);
/// <summary>
/// Create as brush that will paint a Vertical Hatch Pattern with the specified foreground color and a
/// transparent background.
/// </summary>
/// <param name="foreColor">Color of the foreground.</param>
/// <returns>A New <see cref="PatternBrush"/></returns>
public static PatternBrush Vertical(Color foreColor) =>
new PatternBrush(foreColor, Color.Transparent, VerticalPattern);
/// <summary>
/// Create as brush that will paint a Vertical Hatch Pattern with the specified colors
/// </summary>
/// <param name="foreColor">Color of the foreground.</param>
/// <param name="backColor">Color of the background.</param>
/// <returns>A New <see cref="PatternBrush"/></returns>
public static PatternBrush Vertical(Color foreColor, Color backColor) =>
new PatternBrush(foreColor, backColor, VerticalPattern);
/// <summary>
/// Create as brush that will paint a Forward Diagonal Hatch Pattern with the specified foreground color and a
/// transparent background.
/// </summary>
/// <param name="foreColor">Color of the foreground.</param>
/// <returns>A New <see cref="PatternBrush"/></returns>
public static PatternBrush ForwardDiagonal(Color foreColor) =>
new PatternBrush(foreColor, Color.Transparent, ForwardDiagonalPattern);
/// <summary>
/// Create as brush that will paint a Forward Diagonal Hatch Pattern with the specified colors
/// </summary>
/// <param name="foreColor">Color of the foreground.</param>
/// <param name="backColor">Color of the background.</param>
/// <returns>A New <see cref="PatternBrush"/></returns>
public static PatternBrush ForwardDiagonal(Color foreColor, Color backColor) =>
new PatternBrush(foreColor, backColor, ForwardDiagonalPattern);
/// <summary>
/// Create as brush that will paint a Backward Diagonal Hatch Pattern with the specified foreground color and a
/// transparent background.
/// </summary>
/// <param name="foreColor">Color of the foreground.</param>
/// <returns>A New <see cref="PatternBrush"/></returns>
public static PatternBrush BackwardDiagonal(Color foreColor) =>
new PatternBrush(foreColor, Color.Transparent, BackwardDiagonalPattern);
/// <summary>
/// Create as brush that will paint a Backward Diagonal Hatch Pattern with the specified colors
/// </summary>
/// <param name="foreColor">Color of the foreground.</param>
/// <param name="backColor">Color of the background.</param>
/// <returns>A New <see cref="PatternBrush"/></returns>
public static PatternBrush BackwardDiagonal(Color foreColor, Color backColor) =>
new PatternBrush(foreColor, backColor, BackwardDiagonalPattern);
}
}

35
src/ImageSharp.Drawing/Processing/ColorStop.cs

@ -1,35 +0,0 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System.Diagnostics;
namespace SixLabors.ImageSharp.Processing
{
/// <summary>
/// A struct that defines a single color stop.
/// </summary>
[DebuggerDisplay("ColorStop({Ratio} -> {Color}")]
public readonly struct ColorStop
{
/// <summary>
/// Initializes a new instance of the <see cref="ColorStop" /> struct.
/// </summary>
/// <param name="ratio">Where should it be? 0 is at the start, 1 at the end of the Gradient.</param>
/// <param name="color">What color should be used at that point?</param>
public ColorStop(float ratio, in Color color)
{
this.Ratio = ratio;
this.Color = color;
}
/// <summary>
/// Gets the point along the defined gradient axis.
/// </summary>
public float Ratio { get; }
/// <summary>
/// Gets the color to be used.
/// </summary>
public Color Color { get; }
}
}

22
src/ImageSharp.Drawing/Processing/DrawingHelpers.cs

@ -1,22 +0,0 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Primitives;
namespace SixLabors.ImageSharp.Processing
{
internal static class DrawingHelpers
{
/// <summary>
/// Convert a <see cref="DenseMatrix{Color}"/> to a <see cref="DenseMatrix{T}"/> of the given pixel type.
/// </summary>
public static DenseMatrix<TPixel> ToPixelMatrix<TPixel>(this DenseMatrix<Color> colorMatrix, Configuration configuration)
where TPixel : struct, IPixel<TPixel>
{
var result = new DenseMatrix<TPixel>(colorMatrix.Columns, colorMatrix.Rows);
Color.ToPixel(configuration, colorMatrix.Span, result.Span);
return result;
}
}
}

165
src/ImageSharp.Drawing/Processing/EllipticGradientBrush.cs

@ -1,165 +0,0 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.Primitives;
namespace SixLabors.ImageSharp.Processing
{
/// <summary>
/// Gradient Brush with elliptic shape.
/// The ellipse is defined by a center point,
/// a point on the longest extension of the ellipse and
/// the ratio between longest and shortest extension.
/// </summary>
public sealed class EllipticGradientBrush : GradientBrush
{
private readonly PointF center;
private readonly PointF referenceAxisEnd;
private readonly float axisRatio;
/// <inheritdoc cref="GradientBrush" />
/// <param name="center">The center of the elliptical gradient and 0 for the color stops.</param>
/// <param name="referenceAxisEnd">The end point of the reference axis of the ellipse.</param>
/// <param name="axisRatio">
/// The ratio of the axis widths.
/// The second axis' is perpendicular to the reference axis and
/// it's length is the reference axis' length multiplied by this factor.
/// </param>
/// <param name="repetitionMode">Defines how the colors of the gradients are repeated.</param>
/// <param name="colorStops">the color stops as defined in base class.</param>
public EllipticGradientBrush(
PointF center,
PointF referenceAxisEnd,
float axisRatio,
GradientRepetitionMode repetitionMode,
params ColorStop[] colorStops)
: base(repetitionMode, colorStops)
{
this.center = center;
this.referenceAxisEnd = referenceAxisEnd;
this.axisRatio = axisRatio;
}
/// <inheritdoc />
public override BrushApplicator<TPixel> CreateApplicator<TPixel>(
Configuration configuration,
GraphicsOptions options,
ImageFrame<TPixel> source,
RectangleF region) =>
new RadialGradientBrushApplicator<TPixel>(
configuration,
options,
source,
this.center,
this.referenceAxisEnd,
this.axisRatio,
this.ColorStops,
this.RepetitionMode);
/// <inheritdoc />
private sealed class RadialGradientBrushApplicator<TPixel> : GradientBrushApplicator<TPixel>
where TPixel : struct, IPixel<TPixel>
{
private readonly PointF center;
private readonly PointF referenceAxisEnd;
private readonly float axisRatio;
private readonly double rotation;
private readonly float referenceRadius;
private readonly float secondRadius;
private readonly float cosRotation;
private readonly float sinRotation;
private readonly float secondRadiusSquared;
private readonly float referenceRadiusSquared;
/// <summary>
/// Initializes a new instance of the <see cref="RadialGradientBrushApplicator{TPixel}" /> class.
/// </summary>
/// <param name="configuration">The configuration instance to use when performing operations.</param>
/// <param name="options">The graphics options.</param>
/// <param name="target">The target image.</param>
/// <param name="center">Center of the ellipse.</param>
/// <param name="referenceAxisEnd">Point on one angular points of the ellipse.</param>
/// <param name="axisRatio">
/// Ratio of the axis length's. Used to determine the length of the second axis,
/// the first is defined by <see cref="center"/> and <see cref="referenceAxisEnd"/>.</param>
/// <param name="colorStops">Definition of colors.</param>
/// <param name="repetitionMode">Defines how the gradient colors are repeated.</param>
public RadialGradientBrushApplicator(
Configuration configuration,
GraphicsOptions options,
ImageFrame<TPixel> target,
PointF center,
PointF referenceAxisEnd,
float axisRatio,
ColorStop[] colorStops,
GradientRepetitionMode repetitionMode)
: base(configuration, options, target, colorStops, repetitionMode)
{
this.center = center;
this.referenceAxisEnd = referenceAxisEnd;
this.axisRatio = axisRatio;
this.rotation = this.AngleBetween(
this.center,
new PointF(this.center.X + 1, this.center.Y),
this.referenceAxisEnd);
this.referenceRadius = this.DistanceBetween(this.center, this.referenceAxisEnd);
this.secondRadius = this.referenceRadius * this.axisRatio;
this.referenceRadiusSquared = this.referenceRadius * this.referenceRadius;
this.secondRadiusSquared = this.secondRadius * this.secondRadius;
this.sinRotation = (float)Math.Sin(this.rotation);
this.cosRotation = (float)Math.Cos(this.rotation);
}
/// <inheritdoc />
protected override float PositionOnGradient(float xt, float yt)
{
float x0 = xt - this.center.X;
float y0 = yt - this.center.Y;
float x = (x0 * this.cosRotation) - (y0 * this.sinRotation);
float y = (x0 * this.sinRotation) + (y0 * this.cosRotation);
float xSquared = x * x;
float ySquared = y * y;
return (xSquared / this.referenceRadiusSquared) + (ySquared / this.secondRadiusSquared);
}
private float AngleBetween(PointF junction, PointF a, PointF b)
{
PointF vA = a - junction;
PointF vB = b - junction;
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);
}
}
}
}

106
src/ImageSharp.Drawing/Processing/Extensions/DrawBezierExtensions.cs

@ -1,106 +0,0 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using SixLabors.Primitives;
using SixLabors.Shapes;
namespace SixLabors.ImageSharp.Processing
{
/// <summary>
/// Adds extensions that allow the drawing of Bezier paths to the <see cref="Image{TPixel}"/> type.
/// </summary>
public static class DrawBezierExtensions
{
/// <summary>
/// Draws the provided points as an open Bezier path at the provided thickness with the supplied brush
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="options">The options.</param>
/// <param name="brush">The brush.</param>
/// <param name="thickness">The thickness.</param>
/// <param name="points">The points.</param>
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
public static IImageProcessingContext DrawBeziers(
this IImageProcessingContext source,
GraphicsOptions options,
IBrush brush,
float thickness,
params PointF[] points) =>
source.Draw(options, new Pen(brush, thickness), new Path(new CubicBezierLineSegment(points)));
/// <summary>
/// Draws the provided points as an open Bezier path at the provided thickness with the supplied brush
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="brush">The brush.</param>
/// <param name="thickness">The thickness.</param>
/// <param name="points">The points.</param>
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
public static IImageProcessingContext DrawBeziers(
this IImageProcessingContext source,
IBrush brush,
float thickness,
params PointF[] points) =>
source.Draw(new Pen(brush, thickness), new Path(new CubicBezierLineSegment(points)));
/// <summary>
/// Draws the provided points as an open Bezier path at the provided thickness with the supplied brush
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="color">The color.</param>
/// <param name="thickness">The thickness.</param>
/// <param name="points">The points.</param>
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
public static IImageProcessingContext DrawBeziers(
this IImageProcessingContext source,
Color color,
float thickness,
params PointF[] points) =>
source.DrawBeziers(new SolidBrush(color), thickness, points);
/// <summary>
/// Draws the provided points as an open Bezier path at the provided thickness with the supplied brush
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="options">The options.</param>
/// <param name="color">The color.</param>
/// <param name="thickness">The thickness.</param>
/// <param name="points">The points.</param>
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
public static IImageProcessingContext DrawBeziers(
this IImageProcessingContext source,
GraphicsOptions options,
Color color,
float thickness,
params PointF[] points) =>
source.DrawBeziers(options, new SolidBrush(color), thickness, points);
/// <summary>
/// Draws the provided points as an open Bezier path with the supplied pen
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="options">The options.</param>
/// <param name="pen">The pen.</param>
/// <param name="points">The points.</param>
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
public static IImageProcessingContext DrawBeziers(
this IImageProcessingContext source,
GraphicsOptions options,
IPen pen,
params PointF[] points) =>
source.Draw(options, pen, new Path(new CubicBezierLineSegment(points)));
/// <summary>
/// Draws the provided points as an open Bezier path with the supplied pen
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="pen">The pen.</param>
/// <param name="points">The points.</param>
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
public static IImageProcessingContext DrawBeziers(
this IImageProcessingContext source,
IPen pen,
params PointF[] points) =>
source.Draw(pen, new Path(new CubicBezierLineSegment(points)));
}
}

106
src/ImageSharp.Drawing/Processing/Extensions/DrawLineExtensions.cs

@ -1,106 +0,0 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using SixLabors.Primitives;
using SixLabors.Shapes;
namespace SixLabors.ImageSharp.Processing
{
/// <summary>
/// Adds extensions that allow the drawing of lines to the <see cref="Image{TPixel}"/> type.
/// </summary>
public static class DrawLineExtensions
{
/// <summary>
/// Draws the provided Points as an open Linear path at the provided thickness with the supplied brush
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="options">The options.</param>
/// <param name="brush">The brush.</param>
/// <param name="thickness">The thickness.</param>
/// <param name="points">The points.</param>
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
public static IImageProcessingContext DrawLines(
this IImageProcessingContext source,
GraphicsOptions options,
IBrush brush,
float thickness,
params PointF[] points) =>
source.Draw(options, new Pen(brush, thickness), new Path(new LinearLineSegment(points)));
/// <summary>
/// Draws the provided Points as an open Linear path at the provided thickness with the supplied brush
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="brush">The brush.</param>
/// <param name="thickness">The thickness.</param>
/// <param name="points">The points.</param>
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
public static IImageProcessingContext DrawLines(
this IImageProcessingContext source,
IBrush brush,
float thickness,
params PointF[] points) =>
source.Draw(new Pen(brush, thickness), new Path(new LinearLineSegment(points)));
/// <summary>
/// Draws the provided Points as an open Linear path at the provided thickness with the supplied brush
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="color">The color.</param>
/// <param name="thickness">The thickness.</param>
/// <param name="points">The points.</param>
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
public static IImageProcessingContext DrawLines(
this IImageProcessingContext source,
Color color,
float thickness,
params PointF[] points) =>
source.DrawLines(new SolidBrush(color), thickness, points);
/// <summary>
/// Draws the provided Points as an open Linear path at the provided thickness with the supplied brush
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="options">The options.</param>
/// <param name="color">The color.</param>
/// <param name="thickness">The thickness.</param>
/// <param name="points">The points.</param>
/// <returns>The <see cref="Image{TPixel}"/>.</returns>>
public static IImageProcessingContext DrawLines(
this IImageProcessingContext source,
GraphicsOptions options,
Color color,
float thickness,
params PointF[] points) =>
source.DrawLines(options, new SolidBrush(color), thickness, points);
/// <summary>
/// Draws the provided Points as an open Linear path with the supplied pen
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="options">The options.</param>
/// <param name="pen">The pen.</param>
/// <param name="points">The points.</param>
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
public static IImageProcessingContext DrawLines(
this IImageProcessingContext source,
GraphicsOptions options,
IPen pen,
params PointF[] points) =>
source.Draw(options, pen, new Path(new LinearLineSegment(points)));
/// <summary>
/// Draws the provided Points as an open Linear path with the supplied pen
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="pen">The pen.</param>
/// <param name="points">The points.</param>
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
public static IImageProcessingContext DrawLines(
this IImageProcessingContext source,
IPen pen,
params PointF[] points) =>
source.Draw(pen, new Path(new LinearLineSegment(points)));
}
}

110
src/ImageSharp.Drawing/Processing/Extensions/DrawPathCollectionExtensions.cs

@ -1,110 +0,0 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using SixLabors.Shapes;
namespace SixLabors.ImageSharp.Processing
{
/// <summary>
/// Adds extensions that allow the drawing of collections of polygon outlines to the <see cref="Image{TPixel}"/> type.
/// </summary>
public static class DrawPathCollectionExtensions
{
/// <summary>
/// Draws the outline of the polygon with the provided pen.
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="options">The options.</param>
/// <param name="pen">The pen.</param>
/// <param name="paths">The paths.</param>
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
public static IImageProcessingContext Draw(
this IImageProcessingContext source,
GraphicsOptions options,
IPen pen,
IPathCollection paths)
{
foreach (IPath path in paths)
{
source.Draw(options, pen, path);
}
return source;
}
/// <summary>
/// Draws the outline of the polygon with the provided pen.
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="pen">The pen.</param>
/// <param name="paths">The paths.</param>
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
public static IImageProcessingContext
Draw(this IImageProcessingContext source, IPen pen, IPathCollection paths) =>
source.Draw(new GraphicsOptions(), pen, paths);
/// <summary>
/// Draws the outline of the polygon with the provided brush at the provided thickness.
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="options">The options.</param>
/// <param name="brush">The brush.</param>
/// <param name="thickness">The thickness.</param>
/// <param name="paths">The shapes.</param>
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
public static IImageProcessingContext Draw(
this IImageProcessingContext source,
GraphicsOptions options,
IBrush brush,
float thickness,
IPathCollection paths) =>
source.Draw(options, new Pen(brush, thickness), paths);
/// <summary>
/// Draws the outline of the polygon with the provided brush at the provided thickness.
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="brush">The brush.</param>
/// <param name="thickness">The thickness.</param>
/// <param name="paths">The paths.</param>
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
public static IImageProcessingContext Draw(
this IImageProcessingContext source,
IBrush brush,
float thickness,
IPathCollection paths) =>
source.Draw(new Pen(brush, thickness), paths);
/// <summary>
/// Draws the outline of the polygon with the provided brush at the provided thickness.
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="options">The options.</param>
/// <param name="color">The color.</param>
/// <param name="thickness">The thickness.</param>
/// <param name="paths">The paths.</param>
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
public static IImageProcessingContext Draw(
this IImageProcessingContext source,
GraphicsOptions options,
Color color,
float thickness,
IPathCollection paths) =>
source.Draw(options, new SolidBrush(color), thickness, paths);
/// <summary>
/// Draws the outline of the polygon with the provided brush at the provided thickness.
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="color">The color.</param>
/// <param name="thickness">The thickness.</param>
/// <param name="paths">The paths.</param>
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
public static IImageProcessingContext Draw(
this IImageProcessingContext source,
Color color,
float thickness,
IPathCollection paths) =>
source.Draw(new SolidBrush(color), thickness, paths);
}
}

103
src/ImageSharp.Drawing/Processing/Extensions/DrawPathExtensions.cs

@ -1,103 +0,0 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using SixLabors.ImageSharp.Primitives;
using SixLabors.Shapes;
namespace SixLabors.ImageSharp.Processing
{
/// <summary>
/// Adds extensions that allow the drawing of polygon outlines to the <see cref="Image{TPixel}"/> type.
/// </summary>
public static class DrawPathExtensions
{
/// <summary>
/// Draws the outline of the polygon with the provided pen.
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="options">The options.</param>
/// <param name="pen">The pen.</param>
/// <param name="path">The path.</param>
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
public static IImageProcessingContext Draw(
this IImageProcessingContext source,
GraphicsOptions options,
IPen pen,
IPath path) =>
source.Fill(options, pen.StrokeFill, new ShapePath(path, pen));
/// <summary>
/// Draws the outline of the polygon with the provided pen.
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="pen">The pen.</param>
/// <param name="path">The path.</param>
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
public static IImageProcessingContext Draw(this IImageProcessingContext source, IPen pen, IPath path) =>
source.Draw(new GraphicsOptions(), pen, path);
/// <summary>
/// Draws the outline of the polygon with the provided brush at the provided thickness.
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="options">The options.</param>
/// <param name="brush">The brush.</param>
/// <param name="thickness">The thickness.</param>
/// <param name="path">The shape.</param>
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
public static IImageProcessingContext Draw(
this IImageProcessingContext source,
GraphicsOptions options,
IBrush brush,
float thickness,
IPath path) =>
source.Draw(options, new Pen(brush, thickness), path);
/// <summary>
/// Draws the outline of the polygon with the provided brush at the provided thickness.
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="brush">The brush.</param>
/// <param name="thickness">The thickness.</param>
/// <param name="path">The path.</param>
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
public static IImageProcessingContext Draw(
this IImageProcessingContext source,
IBrush brush,
float thickness,
IPath path) =>
source.Draw(new Pen(brush, thickness), path);
/// <summary>
/// Draws the outline of the polygon with the provided brush at the provided thickness.
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="options">The options.</param>
/// <param name="color">The color.</param>
/// <param name="thickness">The thickness.</param>
/// <param name="path">The path.</param>
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
public static IImageProcessingContext Draw(
this IImageProcessingContext source,
GraphicsOptions options,
Color color,
float thickness,
IPath path) =>
source.Draw(options, new SolidBrush(color), thickness, path);
/// <summary>
/// Draws the outline of the polygon with the provided brush at the provided thickness.
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="color">The color.</param>
/// <param name="thickness">The thickness.</param>
/// <param name="path">The path.</param>
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
public static IImageProcessingContext Draw(
this IImageProcessingContext source,
Color color,
float thickness,
IPath path) =>
source.Draw(new SolidBrush(color), thickness, path);
}
}

106
src/ImageSharp.Drawing/Processing/Extensions/DrawPolygonExtensions.cs

@ -1,106 +0,0 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using SixLabors.Primitives;
using SixLabors.Shapes;
namespace SixLabors.ImageSharp.Processing
{
/// <summary>
/// Adds extensions that allow the drawing of closed linear polygons to the <see cref="Image{TPixel}"/> type.
/// </summary>
public static class DrawPolygonExtensions
{
/// <summary>
/// Draws the provided Points as a closed Linear Polygon with the provided brush at the provided thickness.
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="options">The options.</param>
/// <param name="brush">The brush.</param>
/// <param name="thickness">The thickness.</param>
/// <param name="points">The points.</param>
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
public static IImageProcessingContext DrawPolygon(
this IImageProcessingContext source,
GraphicsOptions options,
IBrush brush,
float thickness,
params PointF[] points) =>
source.Draw(options, new Pen(brush, thickness), new Polygon(new LinearLineSegment(points)));
/// <summary>
/// Draws the provided Points as a closed Linear Polygon with the provided brush at the provided thickness.
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="brush">The brush.</param>
/// <param name="thickness">The thickness.</param>
/// <param name="points">The points.</param>
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
public static IImageProcessingContext DrawPolygon(
this IImageProcessingContext source,
IBrush brush,
float thickness,
params PointF[] points) =>
source.Draw(new Pen(brush, thickness), new Polygon(new LinearLineSegment(points)));
/// <summary>
/// Draws the provided Points as a closed Linear Polygon with the provided brush at the provided thickness.
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="color">The color.</param>
/// <param name="thickness">The thickness.</param>
/// <param name="points">The points.</param>
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
public static IImageProcessingContext DrawPolygon(
this IImageProcessingContext source,
Color color,
float thickness,
params PointF[] points) =>
source.DrawPolygon(new SolidBrush(color), thickness, points);
/// <summary>
/// Draws the provided Points as a closed Linear Polygon with the provided brush at the provided thickness.
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="options">The options.</param>
/// <param name="color">The color.</param>
/// <param name="thickness">The thickness.</param>
/// <param name="points">The points.</param>
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
public static IImageProcessingContext DrawPolygon(
this IImageProcessingContext source,
GraphicsOptions options,
Color color,
float thickness,
params PointF[] points) =>
source.DrawPolygon(options, new SolidBrush(color), thickness, points);
/// <summary>
/// Draws the provided Points as a closed Linear Polygon with the provided Pen.
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="pen">The pen.</param>
/// <param name="points">The points.</param>
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
public static IImageProcessingContext DrawPolygon(
this IImageProcessingContext source,
IPen pen,
params PointF[] points) =>
source.Draw(new GraphicsOptions(), pen, new Polygon(new LinearLineSegment(points)));
/// <summary>
/// Draws the provided Points as a closed Linear Polygon with the provided Pen.
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="options">The options.</param>
/// <param name="pen">The pen.</param>
/// <param name="points">The points.</param>
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
public static IImageProcessingContext DrawPolygon(
this IImageProcessingContext source,
GraphicsOptions options,
IPen pen,
params PointF[] points) =>
source.Draw(options, pen, new Polygon(new LinearLineSegment(points)));
}
}

103
src/ImageSharp.Drawing/Processing/Extensions/DrawRectangleExtensions.cs

@ -1,103 +0,0 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using SixLabors.Primitives;
using SixLabors.Shapes;
namespace SixLabors.ImageSharp.Processing
{
/// <summary>
/// Adds extensions that allow the drawing of rectangles to the <see cref="Image{TPixel}"/> type.
/// </summary>
public static class DrawRectangleExtensions
{
/// <summary>
/// Draws the outline of the rectangle with the provided pen.
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="options">The options.</param>
/// <param name="pen">The pen.</param>
/// <param name="shape">The shape.</param>
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
public static IImageProcessingContext Draw(
this IImageProcessingContext source,
GraphicsOptions options,
IPen pen,
RectangleF shape) =>
source.Draw(options, pen, new RectangularPolygon(shape.X, shape.Y, shape.Width, shape.Height));
/// <summary>
/// Draws the outline of the rectangle with the provided pen.
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="pen">The pen.</param>
/// <param name="shape">The shape.</param>
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
public static IImageProcessingContext Draw(this IImageProcessingContext source, IPen pen, RectangleF shape) =>
source.Draw(new GraphicsOptions(), pen, shape);
/// <summary>
/// Draws the outline of the rectangle with the provided brush at the provided thickness.
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="options">The options.</param>
/// <param name="brush">The brush.</param>
/// <param name="thickness">The thickness.</param>
/// <param name="shape">The shape.</param>
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
public static IImageProcessingContext Draw(
this IImageProcessingContext source,
GraphicsOptions options,
IBrush brush,
float thickness,
RectangleF shape) =>
source.Draw(options, new Pen(brush, thickness), shape);
/// <summary>
/// Draws the outline of the rectangle with the provided brush at the provided thickness.
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="brush">The brush.</param>
/// <param name="thickness">The thickness.</param>
/// <param name="shape">The shape.</param>
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
public static IImageProcessingContext Draw(
this IImageProcessingContext source,
IBrush brush,
float thickness,
RectangleF shape) =>
source.Draw(new Pen(brush, thickness), shape);
/// <summary>
/// Draws the outline of the rectangle with the provided brush at the provided thickness.
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="options">The options.</param>
/// <param name="color">The color.</param>
/// <param name="thickness">The thickness.</param>
/// <param name="shape">The shape.</param>
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
public static IImageProcessingContext Draw(
this IImageProcessingContext source,
GraphicsOptions options,
Color color,
float thickness,
RectangleF shape) =>
source.Draw(options, new SolidBrush(color), thickness, shape);
/// <summary>
/// Draws the outline of the rectangle with the provided brush at the provided thickness.
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="color">The color.</param>
/// <param name="thickness">The thickness.</param>
/// <param name="shape">The shape.</param>
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
public static IImageProcessingContext Draw(
this IImageProcessingContext source,
Color color,
float thickness,
RectangleF shape) =>
source.Draw(new SolidBrush(color), thickness, shape);
}
}

179
src/ImageSharp.Drawing/Processing/Extensions/DrawTextExtensions.cs

@ -1,179 +0,0 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using SixLabors.Fonts;
using SixLabors.ImageSharp.Processing.Processors.Text;
using SixLabors.Primitives;
namespace SixLabors.ImageSharp.Processing
{
/// <summary>
/// Adds extensions that allow the drawing of text to the <see cref="Image{TPixel}"/> type.
/// </summary>
public static class DrawTextExtensions
{
/// <summary>
/// Draws the text onto the the image filled via the brush.
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="text">The text.</param>
/// <param name="font">The font.</param>
/// <param name="color">The color.</param>
/// <param name="location">The location.</param>
/// <returns>
/// The <see cref="Image{TPixel}" />.
/// </returns>
public static IImageProcessingContext DrawText(
this IImageProcessingContext source,
string text,
Font font,
Color color,
PointF location) =>
source.DrawText(new TextGraphicsOptions(), text, font, color, location);
/// <summary>
/// Draws the text onto the the image filled via the brush.
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="options">The options.</param>
/// <param name="text">The text.</param>
/// <param name="font">The font.</param>
/// <param name="color">The color.</param>
/// <param name="location">The location.</param>
/// <returns>
/// The <see cref="Image{TPixel}" />.
/// </returns>
public static IImageProcessingContext DrawText(
this IImageProcessingContext source,
TextGraphicsOptions options,
string text,
Font font,
Color color,
PointF location) =>
source.DrawText(options, text, font, Brushes.Solid(color), null, location);
/// <summary>
/// Draws the text onto the the image filled via the brush.
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="text">The text.</param>
/// <param name="font">The font.</param>
/// <param name="brush">The brush.</param>
/// <param name="location">The location.</param>
/// <returns>
/// The <see cref="Image{TPixel}" />.
/// </returns>
public static IImageProcessingContext DrawText(
this IImageProcessingContext source,
string text,
Font font,
IBrush brush,
PointF location) =>
source.DrawText(new TextGraphicsOptions(), text, font, brush, location);
/// <summary>
/// Draws the text onto the the image filled via the brush.
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="options">The options.</param>
/// <param name="text">The text.</param>
/// <param name="font">The font.</param>
/// <param name="brush">The brush.</param>
/// <param name="location">The location.</param>
/// <returns>
/// The <see cref="Image{TPixel}" />.
/// </returns>
public static IImageProcessingContext DrawText(
this IImageProcessingContext source,
TextGraphicsOptions options,
string text,
Font font,
IBrush brush,
PointF location) =>
source.DrawText(options, text, font, brush, null, location);
/// <summary>
/// Draws the text onto the the image outlined via the pen.
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="text">The text.</param>
/// <param name="font">The font.</param>
/// <param name="pen">The pen.</param>
/// <param name="location">The location.</param>
/// <returns>
/// The <see cref="Image{TPixel}" />.
/// </returns>
public static IImageProcessingContext DrawText(
this IImageProcessingContext source,
string text,
Font font,
IPen pen,
PointF location) =>
source.DrawText(new TextGraphicsOptions(), text, font, pen, location);
/// <summary>
/// Draws the text onto the the image outlined via the pen.
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="options">The options.</param>
/// <param name="text">The text.</param>
/// <param name="font">The font.</param>
/// <param name="pen">The pen.</param>
/// <param name="location">The location.</param>
/// <returns>
/// The <see cref="Image{TPixel}" />.
/// </returns>
public static IImageProcessingContext DrawText(
this IImageProcessingContext source,
TextGraphicsOptions options,
string text,
Font font,
IPen pen,
PointF location) =>
source.DrawText(options, text, font, null, pen, location);
/// <summary>
/// Draws the text onto the the image filled via the brush then outlined via the pen.
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="text">The text.</param>
/// <param name="font">The font.</param>
/// <param name="brush">The brush.</param>
/// <param name="pen">The pen.</param>
/// <param name="location">The location.</param>
/// <returns>
/// The <see cref="Image{TPixel}" />.
/// </returns>
public static IImageProcessingContext DrawText(
this IImageProcessingContext source,
string text,
Font font,
IBrush brush,
IPen pen,
PointF location) =>
source.DrawText(new TextGraphicsOptions(), text, font, brush, pen, location);
/// <summary>
/// Draws the text using the default resolution of <value>72dpi</value> onto the the image filled via the brush then outlined via the pen.
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="options">The options.</param>
/// <param name="text">The text.</param>
/// <param name="font">The font.</param>
/// <param name="brush">The brush.</param>
/// <param name="pen">The pen.</param>
/// <param name="location">The location.</param>
/// <returns>
/// The <see cref="Image{TPixel}" />.
/// </returns>
public static IImageProcessingContext DrawText(
this IImageProcessingContext source,
TextGraphicsOptions options,
string text,
Font font,
IBrush brush,
IPen pen,
PointF location) =>
source.ApplyProcessor(new DrawTextProcessor(options, text, font, brush, pen, location));
}
}

76
src/ImageSharp.Drawing/Processing/Extensions/FillPathBuilderExtensions.cs

@ -1,76 +0,0 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System;
using SixLabors.Shapes;
namespace SixLabors.ImageSharp.Processing
{
/// <summary>
/// Adds extensions that allow the filling of polygons with various brushes to the <see cref="Image{TPixel}"/> type.
/// </summary>
public static class FillPathBuilderExtensions
{
/// <summary>
/// Flood fills the image in the shape of the provided polygon with the specified brush.
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="options">The graphics options.</param>
/// <param name="brush">The brush.</param>
/// <param name="path">The shape.</param>
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
public static IImageProcessingContext Fill(
this IImageProcessingContext source,
GraphicsOptions options,
IBrush brush,
Action<PathBuilder> path)
{
var pb = new PathBuilder();
path(pb);
return source.Fill(options, brush, pb.Build());
}
/// <summary>
/// Flood fills the image in the shape of the provided polygon with the specified brush.
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="brush">The brush.</param>
/// <param name="path">The path.</param>
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
public static IImageProcessingContext Fill(
this IImageProcessingContext source,
IBrush brush,
Action<PathBuilder> path) =>
source.Fill(new GraphicsOptions(), brush, path);
/// <summary>
/// Flood fills the image in the shape of the provided polygon with the specified brush.
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="options">The options.</param>
/// <param name="color">The color.</param>
/// <param name="path">The path.</param>
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
public static IImageProcessingContext Fill(
this IImageProcessingContext source,
GraphicsOptions options,
Color color,
Action<PathBuilder> path) =>
source.Fill(options, new SolidBrush(color), path);
/// <summary>
/// Flood fills the image in the shape of the provided polygon with the specified brush.
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="color">The color.</param>
/// <param name="path">The path.</param>
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
public static IImageProcessingContext Fill(
this IImageProcessingContext source,
Color color,
Action<PathBuilder> path) =>
source.Fill(new SolidBrush(color), path);
}
}

76
src/ImageSharp.Drawing/Processing/Extensions/FillPathCollectionExtensions.cs

@ -1,76 +0,0 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using SixLabors.Shapes;
namespace SixLabors.ImageSharp.Processing
{
/// <summary>
/// Adds extensions that allow the filling of collections of polygon outlines to the <see cref="Image{TPixel}"/> type.
/// </summary>
public static class FillPathCollectionExtensions
{
/// <summary>
/// Flood fills the image in the shape of the provided polygon with the specified brush.
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="options">The graphics options.</param>
/// <param name="brush">The brush.</param>
/// <param name="paths">The shapes.</param>
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
public static IImageProcessingContext Fill(
this IImageProcessingContext source,
GraphicsOptions options,
IBrush brush,
IPathCollection paths)
{
foreach (IPath s in paths)
{
source.Fill(options, brush, s);
}
return source;
}
/// <summary>
/// Flood fills the image in the shape of the provided polygon with the specified brush.
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="brush">The brush.</param>
/// <param name="paths">The paths.</param>
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
public static IImageProcessingContext Fill(
this IImageProcessingContext source,
IBrush brush,
IPathCollection paths) =>
source.Fill(new GraphicsOptions(), brush, paths);
/// <summary>
/// Flood fills the image in the shape of the provided polygon with the specified brush.
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="options">The options.</param>
/// <param name="color">The color.</param>
/// <param name="paths">The paths.</param>
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
public static IImageProcessingContext Fill(
this IImageProcessingContext source,
GraphicsOptions options,
Color color,
IPathCollection paths) =>
source.Fill(options, new SolidBrush(color), paths);
/// <summary>
/// Flood fills the image in the shape of the provided polygon with the specified brush.
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="color">The color.</param>
/// <param name="paths">The paths.</param>
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
public static IImageProcessingContext Fill(
this IImageProcessingContext source,
Color color,
IPathCollection paths) =>
source.Fill(new SolidBrush(color), paths);
}
}

64
src/ImageSharp.Drawing/Processing/Extensions/FillPathExtensions.cs

@ -1,64 +0,0 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using SixLabors.ImageSharp.Primitives;
using SixLabors.Shapes;
namespace SixLabors.ImageSharp.Processing
{
/// <summary>
/// Adds extensions that allow the filling of polygon outlines to the <see cref="Image{TPixel}"/> type.
/// </summary>
public static class FillPathExtensions
{
/// <summary>
/// Flood fills the image in the shape of the provided polygon with the specified brush.
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="options">The graphics options.</param>
/// <param name="brush">The brush.</param>
/// <param name="path">The shape.</param>
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
public static IImageProcessingContext Fill(
this IImageProcessingContext source,
GraphicsOptions options,
IBrush brush,
IPath path) =>
source.Fill(options, brush, new ShapeRegion(path));
/// <summary>
/// Flood fills the image in the shape of the provided polygon with the specified brush.
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="brush">The brush.</param>
/// <param name="path">The path.</param>
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
public static IImageProcessingContext Fill(this IImageProcessingContext source, IBrush brush, IPath path) =>
source.Fill(new GraphicsOptions(), brush, new ShapeRegion(path));
/// <summary>
/// Flood fills the image in the shape of the provided polygon with the specified brush..
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="options">The options.</param>
/// <param name="color">The color.</param>
/// <param name="path">The path.</param>
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
public static IImageProcessingContext Fill(
this IImageProcessingContext source,
GraphicsOptions options,
Color color,
IPath path) =>
source.Fill(options, new SolidBrush(color), path);
/// <summary>
/// Flood fills the image in the shape of the provided polygon with the specified brush..
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="color">The color.</param>
/// <param name="path">The path.</param>
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
public static IImageProcessingContext Fill(this IImageProcessingContext source, Color color, IPath path) =>
source.Fill(new SolidBrush(color), path);
}
}

70
src/ImageSharp.Drawing/Processing/Extensions/FillPolygonExtensions.cs

@ -1,70 +0,0 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using SixLabors.Primitives;
using SixLabors.Shapes;
namespace SixLabors.ImageSharp.Processing
{
/// <summary>
/// Adds extensions that allow the filling of closed linear polygons to the <see cref="Image{TPixel}"/> type.
/// </summary>
public static class FillPolygonExtensions
{
/// <summary>
/// Flood fills the image in the shape of a Linear polygon described by the points
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="options">The options.</param>
/// <param name="brush">The brush.</param>
/// <param name="points">The points.</param>
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
public static IImageProcessingContext FillPolygon(
this IImageProcessingContext source,
GraphicsOptions options,
IBrush brush,
params PointF[] points) =>
source.Fill(options, brush, new Polygon(new LinearLineSegment(points)));
/// <summary>
/// Flood fills the image in the shape of a Linear polygon described by the points
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="brush">The brush.</param>
/// <param name="points">The points.</param>
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
public static IImageProcessingContext FillPolygon(
this IImageProcessingContext source,
IBrush brush,
params PointF[] points) =>
source.Fill(brush, new Polygon(new LinearLineSegment(points)));
/// <summary>
/// Flood fills the image in the shape of a Linear polygon described by the points
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="options">The options.</param>
/// <param name="color">The color.</param>
/// <param name="points">The points.</param>
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
public static IImageProcessingContext FillPolygon(
this IImageProcessingContext source,
GraphicsOptions options,
Color color,
params PointF[] points) =>
source.Fill(options, new SolidBrush(color), new Polygon(new LinearLineSegment(points)));
/// <summary>
/// Flood fills the image in the shape of a Linear polygon described by the points
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="color">The color.</param>
/// <param name="points">The points.</param>
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
public static IImageProcessingContext FillPolygon(
this IImageProcessingContext source,
Color color,
params PointF[] points) =>
source.Fill(new SolidBrush(color), new Polygon(new LinearLineSegment(points)));
}
}

66
src/ImageSharp.Drawing/Processing/Extensions/FillRectangleExtensions.cs

@ -1,66 +0,0 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using SixLabors.Primitives;
using SixLabors.Shapes;
namespace SixLabors.ImageSharp.Processing
{
/// <summary>
/// Adds extensions that allow the filling of rectangles to the <see cref="Image{TPixel}"/> type.
/// </summary>
public static class FillRectangleExtensions
{
/// <summary>
/// Flood fills the image in the shape of the provided rectangle with the specified brush.
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="options">The options.</param>
/// <param name="brush">The brush.</param>
/// <param name="shape">The shape.</param>
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
public static IImageProcessingContext Fill(
this IImageProcessingContext source,
GraphicsOptions options,
IBrush brush,
RectangleF shape) =>
source.Fill(options, brush, new RectangularPolygon(shape.X, shape.Y, shape.Width, shape.Height));
/// <summary>
/// Flood fills the image in the shape of the provided rectangle with the specified brush.
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="brush">The brush.</param>
/// <param name="shape">The shape.</param>
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
public static IImageProcessingContext
Fill(this IImageProcessingContext source, IBrush brush, RectangleF shape) =>
source.Fill(brush, new RectangularPolygon(shape.X, shape.Y, shape.Width, shape.Height));
/// <summary>
/// Flood fills the image in the shape of the provided rectangle with the specified brush.
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="options">The options.</param>
/// <param name="color">The color.</param>
/// <param name="shape">The shape.</param>
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
public static IImageProcessingContext Fill(
this IImageProcessingContext source,
GraphicsOptions options,
Color color,
RectangleF shape) =>
source.Fill(options, new SolidBrush(color), shape);
/// <summary>
/// Flood fills the image in the shape of the provided rectangle with the specified brush.
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="color">The color.</param>
/// <param name="shape">The shape.</param>
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
public static IImageProcessingContext
Fill(this IImageProcessingContext source, Color color, RectangleF shape) =>
source.Fill(new SolidBrush(color), shape);
}
}

95
src/ImageSharp.Drawing/Processing/Extensions/FillRegionExtensions.cs

@ -1,95 +0,0 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using SixLabors.ImageSharp.Primitives;
using SixLabors.ImageSharp.Processing.Processors.Drawing;
namespace SixLabors.ImageSharp.Processing
{
/// <summary>
/// Adds extensions that allow the filling of regions with various brushes to the <see cref="Image{TPixel}"/> type.
/// </summary>
public static class FillRegionExtensions
{
/// <summary>
/// Flood fills the image with the specified brush.
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="brush">The details how to fill the region of interest.</param>
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
public static IImageProcessingContext Fill(this IImageProcessingContext source, IBrush brush) =>
source.Fill(new GraphicsOptions(), brush);
/// <summary>
/// Flood fills the image with the specified color.
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="color">The color.</param>
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
public static IImageProcessingContext Fill(this IImageProcessingContext source, Color color) =>
source.Fill(new SolidBrush(color));
/// <summary>
/// Flood fills the image with in the region with the specified brush.
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="brush">The brush.</param>
/// <param name="region">The region.</param>
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
public static IImageProcessingContext Fill(this IImageProcessingContext source, IBrush brush, Region region) =>
source.Fill(new GraphicsOptions(), brush, region);
/// <summary>
/// Flood fills the image with in the region with the specified color.
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="options">The options.</param>
/// <param name="color">The color.</param>
/// <param name="region">The region.</param>
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
public static IImageProcessingContext Fill(
this IImageProcessingContext source,
GraphicsOptions options,
Color color,
Region region) =>
source.Fill(options, new SolidBrush(color), region);
/// <summary>
/// Flood fills the image with in the region with the specified color.
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="color">The color.</param>
/// <param name="region">The region.</param>
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
public static IImageProcessingContext Fill(this IImageProcessingContext source, Color color, Region region) =>
source.Fill(new SolidBrush(color), region);
/// <summary>
/// Flood fills the image with in the region with the specified brush.
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="options">The graphics options.</param>
/// <param name="brush">The brush.</param>
/// <param name="region">The region.</param>
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
public static IImageProcessingContext Fill(
this IImageProcessingContext source,
GraphicsOptions options,
IBrush brush,
Region region) =>
source.ApplyProcessor(new FillRegionProcessor(options, brush, region));
/// <summary>
/// Flood fills the image with the specified brush.
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="options">The graphics options.</param>
/// <param name="brush">The details how to fill the region of interest.</param>
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
public static IImageProcessingContext Fill(
this IImageProcessingContext source,
GraphicsOptions options,
IBrush brush) =>
source.ApplyProcessor(new FillProcessor(options, brush));
}
}

164
src/ImageSharp.Drawing/Processing/GradientBrush.cs

@ -1,164 +0,0 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Numerics;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.Primitives;
namespace SixLabors.ImageSharp.Processing
{
/// <summary>
/// Base class for Gradient brushes
/// </summary>
public abstract class GradientBrush : IBrush
{
/// <inheritdoc cref="IBrush"/>
/// <param name="repetitionMode">Defines how the colors are repeated beyond the interval [0..1]</param>
/// <param name="colorStops">The gradient colors.</param>
protected GradientBrush(
GradientRepetitionMode repetitionMode,
params ColorStop[] colorStops)
{
this.RepetitionMode = repetitionMode;
this.ColorStops = colorStops;
}
/// <summary>
/// Gets how the colors are repeated beyond the interval [0..1].
/// </summary>
protected GradientRepetitionMode RepetitionMode { get; }
/// <summary>
/// Gets the list of color stops for this gradient.
/// </summary>
protected ColorStop[] ColorStops { get; }
/// <inheritdoc />
public abstract BrushApplicator<TPixel> CreateApplicator<TPixel>(
Configuration configuration,
GraphicsOptions options,
ImageFrame<TPixel> source,
RectangleF region)
where TPixel : struct, IPixel<TPixel>;
/// <summary>
/// Base class for gradient brush applicators
/// </summary>
internal abstract class GradientBrushApplicator<TPixel> : BrushApplicator<TPixel>
where TPixel : struct, IPixel<TPixel>
{
private static readonly TPixel Transparent = Color.Transparent.ToPixel<TPixel>();
private readonly ColorStop[] colorStops;
private readonly GradientRepetitionMode repetitionMode;
/// <summary>
/// Initializes a new instance of the <see cref="GradientBrushApplicator{TPixel}"/> class.
/// </summary>
/// <param name="configuration">The configuration instance to use when performing operations.</param>
/// <param name="options">The graphics options.</param>
/// <param name="target">The target image.</param>
/// <param name="colorStops">An array of color stops sorted by their position.</param>
/// <param name="repetitionMode">Defines if and how the gradient should be repeated.</param>
protected GradientBrushApplicator(
Configuration configuration,
GraphicsOptions options,
ImageFrame<TPixel> target,
ColorStop[] colorStops,
GradientRepetitionMode repetitionMode)
: base(configuration, options, target)
{
this.colorStops = colorStops; // TODO: requires colorStops to be sorted by position - should that be checked?
this.repetitionMode = repetitionMode;
}
/// <inheritdoc/>
internal override TPixel this[int x, int y]
{
get
{
float positionOnCompleteGradient = this.PositionOnGradient(x + 0.5f, y + 0.5f);
switch (this.repetitionMode)
{
case GradientRepetitionMode.None:
// do nothing. The following could be done, but is not necessary:
// onLocalGradient = Math.Min(0, Math.Max(1, onLocalGradient));
break;
case GradientRepetitionMode.Repeat:
positionOnCompleteGradient %= 1;
break;
case GradientRepetitionMode.Reflect:
positionOnCompleteGradient %= 2;
if (positionOnCompleteGradient > 1)
{
positionOnCompleteGradient = 2 - positionOnCompleteGradient;
}
break;
case GradientRepetitionMode.DontFill:
if (positionOnCompleteGradient > 1 || positionOnCompleteGradient < 0)
{
return Transparent;
}
break;
default:
throw new ArgumentOutOfRangeException();
}
(ColorStop from, ColorStop to) = this.GetGradientSegment(positionOnCompleteGradient);
if (from.Color.Equals(to.Color))
{
return from.Color.ToPixel<TPixel>();
}
else
{
float onLocalGradient = (positionOnCompleteGradient - from.Ratio) / (to.Ratio - from.Ratio);
return new Color(Vector4.Lerp((Vector4)from.Color, (Vector4)to.Color, onLocalGradient)).ToPixel<TPixel>();
}
}
}
/// <summary>
/// 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.
/// </summary>
/// <param name="x">The x-coordinate of the point.</param>
/// <param name="y">The y-coordinate of the point.</param>
/// <returns>
/// The position the given point has on the gradient.
/// The position is not bound to the [0..1] interval.
/// Values outside of that interval may be treated differently,
/// e.g. for the <see cref="GradientRepetitionMode" /> enum.
/// </returns>
protected abstract float PositionOnGradient(float x, float y);
private (ColorStop from, ColorStop to) GetGradientSegment(
float positionOnCompleteGradient)
{
ColorStop localGradientFrom = this.colorStops[0];
ColorStop localGradientTo = default;
// TODO: ensure colorStops has at least 2 items (technically 1 would be okay, but that's no gradient)
foreach (ColorStop colorStop in this.colorStops)
{
localGradientTo = colorStop;
if (colorStop.Ratio > positionOnCompleteGradient)
{
// we're done here, so break it!
break;
}
localGradientFrom = localGradientTo;
}
return (localGradientFrom, localGradientTo);
}
}
}
}

37
src/ImageSharp.Drawing/Processing/GradientRepetitionMode.cs

@ -1,37 +0,0 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
namespace SixLabors.ImageSharp.Processing
{
/// <summary>
/// Modes to repeat a gradient.
/// </summary>
public enum GradientRepetitionMode
{
/// <summary>
/// don't repeat, keep the color of start and end beyond those points stable.
/// </summary>
None,
/// <summary>
/// Repeat the gradient.
/// If it's a black-white gradient, with Repeat it will be Black->{gray}->White|Black->{gray}->White|...
/// </summary>
Repeat,
/// <summary>
/// Reflect the gradient.
/// Similar to <see cref="Repeat"/>, but each other repetition uses inverse order of <see cref="ColorStop"/>s.
/// Used on a Black-White gradient, Reflect leads to Black->{gray}->White->{gray}->White...
/// </summary>
Reflect,
/// <summary>
/// With DontFill a gradient does not touch any pixel beyond it's borders.
/// For the <see cref="LinearGradientBrush" /> this is beyond the orthogonal through start and end,
/// TODO For the cref="PolygonalGradientBrush" it's outside the polygon,
/// For <see cref="RadialGradientBrush" /> and <see cref="EllipticGradientBrush" /> it's beyond 1.0.
/// </summary>
DontFill
}
}

40
src/ImageSharp.Drawing/Processing/IBrush.cs

@ -1,40 +0,0 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.Primitives;
namespace SixLabors.ImageSharp.Processing
{
/// <summary>
/// Brush represents a logical configuration of a brush which can be used to source pixel colors
/// </summary>
/// <remarks>
/// A brush is a simple class that will return an <see cref="BrushApplicator{TPixel}" /> that will perform the
/// logic for retrieving pixel values for specific locations.
/// </remarks>
public interface IBrush
{
/// <summary>
/// Creates the applicator for this brush.
/// </summary>
/// <typeparam name="TPixel">The pixel type.</typeparam>
/// <param name="configuration">The configuration instance to use when performing operations.</param>
/// <param name="options">The graphic options.</param>
/// <param name="source">The source image.</param>
/// <param name="region">The region the brush will be applied to.</param>
/// <returns>
/// The <see cref="BrushApplicator{TPixel}"/> for this brush.
/// </returns>
/// <remarks>
/// The <paramref name="region" /> when being applied to things like shapes would usually be the
/// bounding box of the shape not necessarily the bounds of the whole image.
/// </remarks>
BrushApplicator<TPixel> CreateApplicator<TPixel>(
Configuration configuration,
GraphicsOptions options,
ImageFrame<TPixel> source,
RectangleF region)
where TPixel : struct, IPixel<TPixel>;
}
}

28
src/ImageSharp.Drawing/Processing/IPen.cs

@ -1,28 +0,0 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System;
namespace SixLabors.ImageSharp.Processing
{
/// <summary>
/// Interface representing the pattern and size of the stroke to apply with a Pen.
/// </summary>
public interface IPen
{
/// <summary>
/// Gets the stroke fill.
/// </summary>
IBrush StrokeFill { get; }
/// <summary>
/// Gets the width to apply to the stroke
/// </summary>
float StrokeWidth { get; }
/// <summary>
/// Gets the stoke pattern.
/// </summary>
ReadOnlySpan<float> StrokePattern { get; }
}
}

173
src/ImageSharp.Drawing/Processing/ImageBrush.cs

@ -1,173 +0,0 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Buffers;
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.Primitives;
namespace SixLabors.ImageSharp.Processing
{
/// <summary>
/// Provides an implementation of an image brush for painting images within areas.
/// </summary>
public class ImageBrush : IBrush
{
/// <summary>
/// The image to paint.
/// </summary>
private readonly Image image;
/// <summary>
/// Initializes a new instance of the <see cref="ImageBrush"/> class.
/// </summary>
/// <param name="image">The image.</param>
public ImageBrush(Image image)
{
this.image = image;
}
/// <inheritdoc />
public BrushApplicator<TPixel> CreateApplicator<TPixel>(
Configuration configuration,
GraphicsOptions options,
ImageFrame<TPixel> source,
RectangleF region)
where TPixel : struct, IPixel<TPixel>
{
if (this.image is Image<TPixel> specificImage)
{
return new ImageBrushApplicator<TPixel>(configuration, options, source, specificImage, region, false);
}
specificImage = this.image.CloneAs<TPixel>();
return new ImageBrushApplicator<TPixel>(configuration, options, source, specificImage, region, true);
}
/// <summary>
/// The image brush applicator.
/// </summary>
private class ImageBrushApplicator<TPixel> : BrushApplicator<TPixel>
where TPixel : struct, IPixel<TPixel>
{
private ImageFrame<TPixel> sourceFrame;
private Image<TPixel> sourceImage;
private readonly bool shouldDisposeImage;
/// <summary>
/// The y-length.
/// </summary>
private readonly int yLength;
/// <summary>
/// The x-length.
/// </summary>
private readonly int xLength;
/// <summary>
/// The Y offset.
/// </summary>
private readonly int offsetY;
/// <summary>
/// The X offset.
/// </summary>
private readonly int offsetX;
private bool isDisposed;
/// <summary>
/// Initializes a new instance of the <see cref="ImageBrushApplicator{TPixel}"/> class.
/// </summary>
/// <param name="configuration">The configuration instance to use when performing operations.</param>
/// <param name="options">The graphics options.</param>
/// <param name="target">The target image.</param>
/// <param name="image">The image.</param>
/// <param name="region">The region.</param>
/// <param name="shouldDisposeImage">Whether to dispose the image on disposal of the applicator.</param>
public ImageBrushApplicator(
Configuration configuration,
GraphicsOptions options,
ImageFrame<TPixel> target,
Image<TPixel> image,
RectangleF region,
bool shouldDisposeImage)
: base(configuration, options, target)
{
this.sourceImage = image;
this.sourceFrame = image.Frames.RootFrame;
this.shouldDisposeImage = shouldDisposeImage;
this.xLength = image.Width;
this.yLength = image.Height;
this.offsetY = (int)MathF.Max(MathF.Floor(region.Top), 0);
this.offsetX = (int)MathF.Max(MathF.Floor(region.Left), 0);
}
/// <inheritdoc/>
internal override TPixel this[int x, int y]
{
get
{
int srcX = (x - this.offsetX) % this.xLength;
int srcY = (y - this.offsetY) % this.yLength;
return this.sourceFrame[srcX, srcY];
}
}
/// <inheritdoc />
protected override void Dispose(bool disposing)
{
if (this.isDisposed)
{
return;
}
if (disposing && this.shouldDisposeImage)
{
this.sourceImage?.Dispose();
}
this.sourceImage = null;
this.sourceFrame = null;
this.isDisposed = true;
}
/// <inheritdoc />
internal override void Apply(Span<float> scanline, int x, int y)
{
// Create a span for colors
using (IMemoryOwner<float> amountBuffer = this.Target.MemoryAllocator.Allocate<float>(scanline.Length))
using (IMemoryOwner<TPixel> overlay = this.Target.MemoryAllocator.Allocate<TPixel>(scanline.Length))
{
Span<float> amountSpan = amountBuffer.Memory.Span;
Span<TPixel> overlaySpan = overlay.Memory.Span;
int sourceY = (y - this.offsetY) % this.yLength;
int offsetX = x - this.offsetX;
Span<TPixel> sourceRow = this.sourceFrame.GetPixelRowSpan(sourceY);
for (int i = 0; i < scanline.Length; i++)
{
amountSpan[i] = scanline[i] * this.Options.BlendPercentage;
int sourceX = (i + offsetX) % this.xLength;
overlaySpan[i] = sourceRow[sourceX];
}
Span<TPixel> destinationRow = this.Target.GetPixelRowSpan(y).Slice(x, scanline.Length);
this.Blender.Blend(
this.Configuration,
destinationRow,
destinationRow,
overlaySpan,
amountSpan);
}
}
}
}
}

160
src/ImageSharp.Drawing/Processing/LinearGradientBrush.cs

@ -1,160 +0,0 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.Primitives;
namespace SixLabors.ImageSharp.Processing
{
/// <summary>
/// Provides an implementation of a brush for painting linear gradients within areas.
/// Supported right now:
/// - a set of colors in relative distances to each other.
/// </summary>
public sealed class LinearGradientBrush : GradientBrush
{
private readonly PointF p1;
private readonly PointF p2;
/// <summary>
/// Initializes a new instance of the <see cref="LinearGradientBrush"/> class.
/// </summary>
/// <param name="p1">Start point</param>
/// <param name="p2">End point</param>
/// <param name="repetitionMode">defines how colors are repeated.</param>
/// <param name="colorStops"><inheritdoc /></param>
public LinearGradientBrush(
PointF p1,
PointF p2,
GradientRepetitionMode repetitionMode,
params ColorStop[] colorStops)
: base(repetitionMode, colorStops)
{
this.p1 = p1;
this.p2 = p2;
}
/// <inheritdoc />
public override BrushApplicator<TPixel> CreateApplicator<TPixel>(
Configuration configuration,
GraphicsOptions options,
ImageFrame<TPixel> source,
RectangleF region) =>
new LinearGradientBrushApplicator<TPixel>(
configuration,
options,
source,
this.p1,
this.p2,
this.ColorStops,
this.RepetitionMode);
/// <summary>
/// The linear gradient brush applicator.
/// </summary>
private sealed class LinearGradientBrushApplicator<TPixel> : GradientBrushApplicator<TPixel>
where TPixel : struct, IPixel<TPixel>
{
private readonly PointF start;
private readonly PointF end;
/// <summary>
/// the vector along the gradient, x component
/// </summary>
private readonly float alongX;
/// <summary>
/// the vector along the gradient, y component
/// </summary>
private readonly float alongY;
/// <summary>
/// the vector perpendicular to the gradient, y component
/// </summary>
private readonly float acrossY;
/// <summary>
/// the vector perpendicular to the gradient, x component
/// </summary>
private readonly float acrossX;
/// <summary>
/// the result of <see cref="alongX"/>^2 + <see cref="alongY"/>^2
/// </summary>
private readonly float alongsSquared;
/// <summary>
/// the length of the defined gradient (between source and end)
/// </summary>
private readonly float length;
/// <summary>
/// Initializes a new instance of the <see cref="LinearGradientBrushApplicator{TPixel}" /> class.
/// </summary>
/// <param name="configuration">The configuration instance to use when performing operations.</param>
/// <param name="options">The graphics options.</param>
/// <param name="source">The source image.</param>
/// <param name="start">The start point of the gradient.</param>
/// <param name="end">The end point of the gradient.</param>
/// <param name="colorStops">A tuple list of colors and their respective position between 0 and 1 on the line.</param>
/// <param name="repetitionMode">Defines how the gradient colors are repeated.</param>
public LinearGradientBrushApplicator(
Configuration configuration,
GraphicsOptions options,
ImageFrame<TPixel> source,
PointF start,
PointF end,
ColorStop[] colorStops,
GradientRepetitionMode repetitionMode)
: base(configuration, options, source, colorStops, repetitionMode)
{
this.start = start;
this.end = end;
// the along vector:
this.alongX = this.end.X - this.start.X;
this.alongY = this.end.Y - this.start.Y;
// the cross vector:
this.acrossX = this.alongY;
this.acrossY = -this.alongX;
// some helpers:
this.alongsSquared = (this.alongX * this.alongX) + (this.alongY * this.alongY);
this.length = MathF.Sqrt(this.alongsSquared);
}
protected override float PositionOnGradient(float x, float y)
{
if (this.acrossX == 0)
{
return (x - this.start.X) / (this.end.X - this.start.X);
}
else if (this.acrossY == 0)
{
return (y - this.start.Y) / (this.end.Y - this.start.Y);
}
else
{
float deltaX = x - this.start.X;
float deltaY = y - this.start.Y;
float k = ((this.alongY * deltaX) - (this.alongX * deltaY)) / this.alongsSquared;
// point on the line:
float x4 = x - (k * this.alongY);
float y4 = y + (k * this.alongX);
// get distance from (x4,y4) to start
float distance = MathF.Sqrt(MathF.Pow(x4 - this.start.X, 2) + MathF.Pow(y4 - this.start.Y, 2));
// get and return ratio
return distance / this.length;
}
}
}
}
}

309
src/ImageSharp.Drawing/Processing/PathGradientBrush.cs

@ -1,309 +0,0 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Buffers;
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.Memory;
using SixLabors.Primitives;
using SixLabors.Shapes;
namespace SixLabors.ImageSharp.Processing
{
/// <summary>
/// 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.
/// </summary>
public sealed class PathGradientBrush : IBrush
{
private readonly IList<Edge> edges;
private readonly Color centerColor;
/// <summary>
/// Initializes a new instance of the <see cref="PathGradientBrush"/> class.
/// </summary>
/// <param name="points">Points that constitute a polygon that represents the gradient area.</param>
/// <param name="colors">Array of colors that correspond to each point in the polygon.</param>
/// <param name="centerColor">Color at the center of the gradient area to which the other colors converge.</param>
public PathGradientBrush(PointF[] points, Color[] colors, Color centerColor)
{
if (points == null)
{
throw new ArgumentNullException(nameof(points));
}
if (points.Length < 3)
{
throw new ArgumentOutOfRangeException(
nameof(points),
"There must be at least 3 lines to construct a path gradient brush.");
}
if (colors == null)
{
throw new ArgumentNullException(nameof(colors));
}
if (colors.Length == 0)
{
throw new ArgumentOutOfRangeException(
nameof(colors),
"One or more color is needed to construct a path gradient brush.");
}
int size = points.Length;
var lines = new ILineSegment[size];
for (int i = 0; i < size; i++)
{
lines[i] = new LinearLineSegment(points[i % size], points[(i + 1) % size]);
}
this.centerColor = centerColor;
Color ColorAt(int index) => colors[index % colors.Length];
this.edges = lines.Select(s => new Path(s))
.Select((path, i) => new Edge(path, ColorAt(i), ColorAt(i + 1))).ToList();
}
/// <summary>
/// Initializes a new instance of the <see cref="PathGradientBrush"/> class.
/// </summary>
/// <param name="points">Points that constitute a polygon that represents the gradient area.</param>
/// <param name="colors">Array of colors that correspond to each point in the polygon.</param>
public PathGradientBrush(PointF[] points, Color[] colors)
: this(points, colors, CalculateCenterColor(colors))
{
}
/// <inheritdoc />
public BrushApplicator<TPixel> CreateApplicator<TPixel>(
Configuration configuration,
GraphicsOptions options,
ImageFrame<TPixel> source,
RectangleF region)
where TPixel : struct, IPixel<TPixel>
{
return new PathGradientBrushApplicator<TPixel>(configuration, options, source, this.edges, this.centerColor);
}
private static Color CalculateCenterColor(Color[] colors)
{
if (colors == null)
{
throw new ArgumentNullException(nameof(colors));
}
if (colors.Length == 0)
{
throw new ArgumentOutOfRangeException(
nameof(colors),
"One or more color is needed to construct a path gradient brush.");
}
return new Color(colors.Select(c => (Vector4)c).Aggregate((p1, p2) => p1 + p2) / colors.Length);
}
private static float DistanceBetween(PointF p1, PointF p2) => ((Vector2)(p2 - p1)).Length();
private struct Intersection
{
public Intersection(PointF point, float distance)
{
this.Point = point;
this.Distance = distance;
}
public PointF Point { get; }
public float Distance { get; }
}
/// <summary>
/// An edge of the polygon that represents the gradient area.
/// </summary>
private class Edge
{
private readonly Path path;
private readonly float length;
public Edge(Path path, Color startColor, Color endColor)
{
this.path = path;
Vector2[] points = path.LineSegments.SelectMany(s => s.Flatten()).Select(p => (Vector2)p).ToArray();
this.Start = points[0];
this.StartColor = (Vector4)startColor;
this.End = points.Last();
this.EndColor = (Vector4)endColor;
this.length = DistanceBetween(this.End, this.Start);
}
public PointF Start { get; }
public Vector4 StartColor { get; }
public PointF End { get; }
public Vector4 EndColor { get; }
public Intersection? FindIntersection(PointF start, PointF end, MemoryAllocator allocator)
{
// 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<PointF> memory = allocator.Allocate<PointF>(this.path.MaxIntersections))
{
Span<PointF> buffer = memory.Memory.Span;
int intersections = this.path.FindIntersections(start, end, buffer);
if (intersections == 0)
{
return null;
}
buffer = buffer.Slice(0, intersections);
PointF minPoint = buffer[0];
var min = new Intersection(minPoint, ((Vector2)(minPoint - start)).LengthSquared());
for (int i = 1; i < buffer.Length; i++)
{
PointF point = buffer[i];
var current = new Intersection(point, ((Vector2)(point - start)).LengthSquared());
if (min.Distance > current.Distance)
{
min = current;
}
}
return min;
}
}
public Vector4 ColorAt(float distance)
{
float ratio = this.length > 0 ? distance / this.length : 0;
return Vector4.Lerp(this.StartColor, this.EndColor, ratio);
}
public Vector4 ColorAt(PointF point) => this.ColorAt(DistanceBetween(point, this.Start));
}
/// <summary>
/// The path gradient brush applicator.
/// </summary>
private class PathGradientBrushApplicator<TPixel> : BrushApplicator<TPixel>
where TPixel : struct, IPixel<TPixel>
{
private readonly PointF center;
private readonly Vector4 centerColor;
private readonly float maxDistance;
private readonly IList<Edge> edges;
private readonly TPixel centerPixel;
private readonly TPixel transparentPixel;
/// <summary>
/// Initializes a new instance of the <see cref="PathGradientBrushApplicator{TPixel}"/> class.
/// </summary>
/// <param name="configuration">The configuration instance to use when performing operations.</param>
/// <param name="options">The graphics options.</param>
/// <param name="source">The source image.</param>
/// <param name="edges">Edges of the polygon.</param>
/// <param name="centerColor">Color at the center of the gradient area to which the other colors converge.</param>
public PathGradientBrushApplicator(
Configuration configuration,
GraphicsOptions options,
ImageFrame<TPixel> source,
IList<Edge> edges,
Color centerColor)
: base(configuration, options, source)
{
this.edges = edges;
PointF[] points = edges.Select(s => s.Start).ToArray();
this.center = points.Aggregate((p1, p2) => p1 + p2) / edges.Count;
this.centerColor = (Vector4)centerColor;
this.centerPixel = centerColor.ToPixel<TPixel>();
this.maxDistance = points.Select(p => (Vector2)(p - this.center)).Max(d => d.Length());
this.transparentPixel = Color.Transparent.ToPixel<TPixel>();
}
/// <inheritdoc />
internal override TPixel this[int x, int y]
{
get
{
var point = new PointF(x, y);
if (point == this.center)
{
return this.centerPixel;
}
var direction = Vector2.Normalize(point - this.center);
PointF end = point + (PointF)(direction * this.maxDistance);
(Edge edge, Intersection? info) = this.FindIntersection(point, end);
if (!info.HasValue)
{
return this.transparentPixel;
}
PointF intersection = info.Value.Point;
Vector4 edgeColor = edge.ColorAt(intersection);
float length = DistanceBetween(intersection, this.center);
float ratio = length > 0 ? DistanceBetween(intersection, point) / length : 0;
var color = Vector4.Lerp(edgeColor, this.centerColor, ratio);
return new Color(color).ToPixel<TPixel>();
}
}
private (Edge edge, Intersection? info) FindIntersection(PointF start, PointF end)
{
(Edge edge, Intersection? info) closest = default;
MemoryAllocator allocator = this.Target.MemoryAllocator;
foreach (Edge edge in this.edges)
{
Intersection? intersection = edge.FindIntersection(start, end, allocator);
if (!intersection.HasValue)
{
continue;
}
if (closest.info == null || closest.info.Value.Distance > intersection.Value.Distance)
{
closest = (edge, intersection);
}
}
return closest;
}
}
}
}

178
src/ImageSharp.Drawing/Processing/PatternBrush.cs

@ -1,178 +0,0 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Buffers;
using System.Numerics;
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Primitives;
using SixLabors.Memory;
using SixLabors.Primitives;
namespace SixLabors.ImageSharp.Processing
{
/// <summary>
/// Provides an implementation of a pattern brush for painting patterns.
/// </summary>
/// <remarks>
/// The patterns that are used to create a custom pattern brush are made up of a repeating matrix of flags,
/// where each flag denotes whether to draw the foreground color or the background color.
/// so to create a new bool[,] with your flags
/// <para>
/// For example if you wanted to create a diagonal line that repeat every 4 pixels you would use a pattern like so
/// 1000
/// 0100
/// 0010
/// 0001
/// </para>
/// <para>
/// or you want a horizontal stripe which is 3 pixels apart you would use a pattern like
/// 1
/// 0
/// 0
/// </para>
/// </remarks>
public class PatternBrush : IBrush
{
/// <summary>
/// The pattern.
/// </summary>
private readonly DenseMatrix<Color> pattern;
private readonly DenseMatrix<Vector4> patternVector;
/// <summary>
/// Initializes a new instance of the <see cref="PatternBrush"/> class.
/// </summary>
/// <param name="foreColor">Color of the fore.</param>
/// <param name="backColor">Color of the back.</param>
/// <param name="pattern">The pattern.</param>
public PatternBrush(Color foreColor, Color backColor, bool[,] pattern)
: this(foreColor, backColor, new DenseMatrix<bool>(pattern))
{
}
/// <summary>
/// Initializes a new instance of the <see cref="PatternBrush"/> class.
/// </summary>
/// <param name="foreColor">Color of the fore.</param>
/// <param name="backColor">Color of the back.</param>
/// <param name="pattern">The pattern.</param>
internal PatternBrush(Color foreColor, Color backColor, in DenseMatrix<bool> pattern)
{
var foreColorVector = (Vector4)foreColor;
var backColorVector = (Vector4)backColor;
this.pattern = new DenseMatrix<Color>(pattern.Columns, pattern.Rows);
this.patternVector = new DenseMatrix<Vector4>(pattern.Columns, pattern.Rows);
for (int i = 0; i < pattern.Data.Length; i++)
{
if (pattern.Data[i])
{
this.pattern.Data[i] = foreColor;
this.patternVector.Data[i] = foreColorVector;
}
else
{
this.pattern.Data[i] = backColor;
this.patternVector.Data[i] = backColorVector;
}
}
}
/// <summary>
/// Initializes a new instance of the <see cref="PatternBrush"/> class.
/// </summary>
/// <param name="brush">The brush.</param>
internal PatternBrush(PatternBrush brush)
{
this.pattern = brush.pattern;
this.patternVector = brush.patternVector;
}
/// <inheritdoc />
public BrushApplicator<TPixel> CreateApplicator<TPixel>(
Configuration configuration,
GraphicsOptions options,
ImageFrame<TPixel> source,
RectangleF region)
where TPixel : struct, IPixel<TPixel> =>
new PatternBrushApplicator<TPixel>(
configuration,
options,
source,
this.pattern.ToPixelMatrix<TPixel>(configuration));
/// <summary>
/// The pattern brush applicator.
/// </summary>
private class PatternBrushApplicator<TPixel> : BrushApplicator<TPixel>
where TPixel : struct, IPixel<TPixel>
{
/// <summary>
/// The pattern.
/// </summary>
private readonly DenseMatrix<TPixel> pattern;
/// <summary>
/// Initializes a new instance of the <see cref="PatternBrushApplicator{TPixel}" /> class.
/// </summary>
/// <param name="configuration">The configuration instance to use when performing operations.</param>
/// <param name="options">The graphics options.</param>
/// <param name="source">The source image.</param>
/// <param name="pattern">The pattern.</param>
public PatternBrushApplicator(
Configuration configuration,
GraphicsOptions options,
ImageFrame<TPixel> source,
in DenseMatrix<TPixel> pattern)
: base(configuration, options, source)
{
this.pattern = pattern;
}
/// <inheritdoc/>
internal override TPixel this[int x, int y]
{
get
{
x %= this.pattern.Columns;
y %= this.pattern.Rows;
// 2d array index at row/column
return this.pattern[y, x];
}
}
/// <inheritdoc />
internal override void Apply(Span<float> scanline, int x, int y)
{
int patternY = y % this.pattern.Rows;
MemoryAllocator memoryAllocator = this.Target.MemoryAllocator;
using (IMemoryOwner<float> amountBuffer = memoryAllocator.Allocate<float>(scanline.Length))
using (IMemoryOwner<TPixel> overlay = memoryAllocator.Allocate<TPixel>(scanline.Length))
{
Span<float> amountSpan = amountBuffer.Memory.Span;
Span<TPixel> overlaySpan = overlay.Memory.Span;
for (int i = 0; i < scanline.Length; i++)
{
amountSpan[i] = NumberUtils.ClampFloat(scanline[i] * this.Options.BlendPercentage, 0, 1F);
int patternX = (x + i) % this.pattern.Columns;
overlaySpan[i] = this.pattern[patternY, patternX];
}
Span<TPixel> destinationRow = this.Target.GetPixelRowSpan(y).Slice(x, scanline.Length);
this.Blender.Blend(
this.Configuration,
destinationRow,
destinationRow,
overlaySpan,
amountSpan);
}
}
}
}
}

76
src/ImageSharp.Drawing/Processing/Pen.cs

@ -1,76 +0,0 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System;
namespace SixLabors.ImageSharp.Processing
{
/// <summary>
/// Provides a pen that can apply a pattern to a line with a set brush and thickness
/// </summary>
/// <remarks>
/// The pattern will be in to the form of new float[]{ 1f, 2f, 0.5f} this will be
/// converted into a pattern that is 3.5 times longer that the width with 3 sections
/// section 1 will be width long (making a square) and will be filled by the brush
/// section 2 will be width * 2 long and will be empty
/// section 3 will be width/2 long and will be filled
/// the the pattern will immediately repeat without gap.
/// </remarks>
public class Pen : IPen
{
private readonly float[] pattern;
/// <summary>
/// Initializes a new instance of the <see cref="Pen"/> class.
/// </summary>
/// <param name="color">The color.</param>
/// <param name="width">The width.</param>
/// <param name="pattern">The pattern.</param>
public Pen(Color color, float width, float[] pattern)
: this(new SolidBrush(color), width, pattern)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="Pen"/> class.
/// </summary>
/// <param name="brush">The brush.</param>
/// <param name="width">The width.</param>
/// <param name="pattern">The pattern.</param>
public Pen(IBrush brush, float width, float[] pattern)
{
this.StrokeFill = brush;
this.StrokeWidth = width;
this.pattern = pattern;
}
/// <summary>
/// Initializes a new instance of the <see cref="Pen"/> class.
/// </summary>
/// <param name="color">The color.</param>
/// <param name="width">The width.</param>
public Pen(Color color, float width)
: this(new SolidBrush(color), width)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="Pen"/> class.
/// </summary>
/// <param name="brush">The brush.</param>
/// <param name="width">The width.</param>
public Pen(IBrush brush, float width)
: this(brush, width, Pens.EmptyPattern)
{
}
/// <inheritdoc/>
public IBrush StrokeFill { get; }
/// <inheritdoc/>
public float StrokeWidth { get; }
/// <inheritdoc/>
public ReadOnlySpan<float> StrokePattern => this.pattern;
}
}

97
src/ImageSharp.Drawing/Processing/Pens.cs

@ -1,97 +0,0 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
namespace SixLabors.ImageSharp.Processing
{
/// <summary>
/// Contains a collection of common Pen styles
/// </summary>
public static class Pens
{
private static readonly float[] DashDotPattern = { 3f, 1f, 1f, 1f };
private static readonly float[] DashDotDotPattern = { 3f, 1f, 1f, 1f, 1f, 1f };
private static readonly float[] DottedPattern = { 1f, 1f };
private static readonly float[] DashedPattern = { 3f, 1f };
internal static readonly float[] EmptyPattern = new float[0];
/// <summary>
/// Create a solid pen with out any drawing patterns
/// </summary>
/// <param name="color">The color.</param>
/// <param name="width">The width.</param>
/// <returns>The Pen</returns>
public static Pen Solid(Color color, float width) => new Pen(color, width);
/// <summary>
/// Create a solid pen with out any drawing patterns
/// </summary>
/// <param name="brush">The brush.</param>
/// <param name="width">The width.</param>
/// <returns>The Pen</returns>
public static Pen Solid(IBrush brush, float width) => new Pen(brush, width);
/// <summary>
/// Create a pen with a 'Dash' drawing patterns
/// </summary>
/// <param name="color">The color.</param>
/// <param name="width">The width.</param>
/// <returns>The Pen</returns>
public static Pen Dash(Color color, float width) => new Pen(color, width, DashedPattern);
/// <summary>
/// Create a pen with a 'Dash' drawing patterns
/// </summary>
/// <param name="brush">The brush.</param>
/// <param name="width">The width.</param>
/// <returns>The Pen</returns>
public static Pen Dash(IBrush brush, float width) => new Pen(brush, width, DashedPattern);
/// <summary>
/// Create a pen with a 'Dot' drawing patterns
/// </summary>
/// <param name="color">The color.</param>
/// <param name="width">The width.</param>
/// <returns>The Pen</returns>
public static Pen Dot(Color color, float width) => new Pen(color, width, DottedPattern);
/// <summary>
/// Create a pen with a 'Dot' drawing patterns
/// </summary>
/// <param name="brush">The brush.</param>
/// <param name="width">The width.</param>
/// <returns>The Pen</returns>
public static Pen Dot(IBrush brush, float width) => new Pen(brush, width, DottedPattern);
/// <summary>
/// Create a pen with a 'Dash Dot' drawing patterns
/// </summary>
/// <param name="color">The color.</param>
/// <param name="width">The width.</param>
/// <returns>The Pen</returns>
public static Pen DashDot(Color color, float width) => new Pen(color, width, DashDotPattern);
/// <summary>
/// Create a pen with a 'Dash Dot' drawing patterns
/// </summary>
/// <param name="brush">The brush.</param>
/// <param name="width">The width.</param>
/// <returns>The Pen</returns>
public static Pen DashDot(IBrush brush, float width) => new Pen(brush, width, DashDotPattern);
/// <summary>
/// Create a pen with a 'Dash Dot Dot' drawing patterns
/// </summary>
/// <param name="color">The color.</param>
/// <param name="width">The width.</param>
/// <returns>The Pen</returns>
public static Pen DashDotDot(Color color, float width) => new Pen(color, width, DashDotDotPattern);
/// <summary>
/// Create a pen with a 'Dash Dot Dot' drawing patterns
/// </summary>
/// <param name="brush">The brush.</param>
/// <param name="width">The width.</param>
/// <returns>The Pen</returns>
public static Pen DashDotDot(IBrush brush, float width) => new Pen(brush, width, DashDotDotPattern);
}
}

41
src/ImageSharp.Drawing/Processing/Processors/Drawing/FillProcessor.cs

@ -1,41 +0,0 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.Primitives;
namespace SixLabors.ImageSharp.Processing.Processors.Drawing
{
/// <summary>
/// Defines a processor to fill an <see cref="Image"/> with the given <see cref="IBrush"/>
/// using blending defined by the given <see cref="GraphicsOptions"/>.
/// </summary>
public class FillProcessor : IImageProcessor
{
/// <summary>
/// Initializes a new instance of the <see cref="FillProcessor"/> class.
/// </summary>
/// <param name="options">The <see cref="GraphicsOptions"/> defining how to blend the brush pixels over the image pixels.</param>
/// <param name="brush">The brush to use for filling.</param>
public FillProcessor(GraphicsOptions options, IBrush brush)
{
this.Brush = brush;
this.Options = options;
}
/// <summary>
/// Gets the <see cref="IBrush"/> used for filling the destination image.
/// </summary>
public IBrush Brush { get; }
/// <summary>
/// Gets the <see cref="GraphicsOptions"/> defining how to blend the brush pixels over the image pixels.
/// </summary>
public GraphicsOptions Options { get; }
/// <inheritdoc />
public IImageProcessor<TPixel> CreatePixelSpecificProcessor<TPixel>(Configuration configuration, Image<TPixel> source, Rectangle sourceRectangle)
where TPixel : struct, IPixel<TPixel>
=> new FillProcessor<TPixel>(configuration, this, source, sourceRectangle);
}
}

122
src/ImageSharp.Drawing/Processing/Processors/Drawing/FillProcessor{TPixel}.cs

@ -1,122 +0,0 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Buffers;
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Advanced.ParallelUtils;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.Primitives;
namespace SixLabors.ImageSharp.Processing.Processors.Drawing
{
/// <summary>
/// Using the brush as a source of pixels colors blends the brush color with source.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
internal class FillProcessor<TPixel> : ImageProcessor<TPixel>
where TPixel : struct, IPixel<TPixel>
{
private readonly FillProcessor definition;
public FillProcessor(Configuration configuration, FillProcessor definition, Image<TPixel> source, Rectangle sourceRectangle)
: base(configuration, source, sourceRectangle)
{
this.definition = definition;
}
/// <inheritdoc/>
protected override void OnFrameApply(ImageFrame<TPixel> source)
{
Rectangle sourceRectangle = this.SourceRectangle;
Configuration configuration = this.Configuration;
int startX = sourceRectangle.X;
int endX = sourceRectangle.Right;
int startY = sourceRectangle.Y;
int endY = sourceRectangle.Bottom;
// Align start/end positions.
int minX = Math.Max(0, startX);
int maxX = Math.Min(source.Width, endX);
int minY = Math.Max(0, startY);
int maxY = Math.Min(source.Height, endY);
int width = maxX - minX;
var workingRect = Rectangle.FromLTRB(minX, minY, maxX, maxY);
IBrush brush = this.definition.Brush;
GraphicsOptions options = this.definition.Options;
// If there's no reason for blending, then avoid it.
if (this.IsSolidBrushWithoutBlending(out SolidBrush solidBrush))
{
ParallelExecutionSettings parallelSettings = ParallelExecutionSettings.FromConfiguration(configuration)
.MultiplyMinimumPixelsPerTask(4);
TPixel colorPixel = solidBrush.Color.ToPixel<TPixel>();
ParallelHelper.IterateRows(
workingRect,
parallelSettings,
rows =>
{
for (int y = rows.Min; y < rows.Max; y++)
{
source.GetPixelRowSpan(y).Slice(minX, width).Fill(colorPixel);
}
});
}
else
{
// Reset offset if necessary.
if (minX > 0)
{
startX = 0;
}
if (minY > 0)
{
startY = 0;
}
using (IMemoryOwner<float> amount = source.MemoryAllocator.Allocate<float>(width))
using (BrushApplicator<TPixel> applicator = brush.CreateApplicator(
configuration,
options,
source,
sourceRectangle))
{
amount.Memory.Span.Fill(1F);
ParallelHelper.IterateRows(
workingRect,
configuration,
rows =>
{
for (int y = rows.Min; y < rows.Max; y++)
{
int offsetY = y - startY;
int offsetX = minX - startX;
applicator.Apply(amount.Memory.Span, offsetX, offsetY);
}
});
}
}
}
private bool IsSolidBrushWithoutBlending(out SolidBrush solidBrush)
{
solidBrush = this.definition.Brush as SolidBrush;
if (solidBrush is null)
{
return false;
}
return this.definition.Options.IsOpaqueColorWithoutBlending(solidBrush.Color);
}
}
}

49
src/ImageSharp.Drawing/Processing/Processors/Drawing/FillRegionProcessor.cs

@ -1,49 +0,0 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Primitives;
using SixLabors.Primitives;
namespace SixLabors.ImageSharp.Processing.Processors.Drawing
{
/// <summary>
/// Defines a processor to fill <see cref="Image"/> pixels withing a given <see cref="Region"/>
/// with the given <see cref="IBrush"/> and blending defined by the given <see cref="GraphicsOptions"/>.
/// </summary>
public class FillRegionProcessor : IImageProcessor
{
/// <summary>
/// Initializes a new instance of the <see cref="FillRegionProcessor" /> class.
/// </summary>
/// <param name="options">The graphics options.</param>
/// <param name="brush">The details how to fill the region of interest.</param>
/// <param name="region">The region of interest to be filled.</param>
public FillRegionProcessor(GraphicsOptions options, IBrush brush, Region region)
{
this.Region = region;
this.Brush = brush;
this.Options = options;
}
/// <summary>
/// Gets the <see cref="IBrush"/> used for filling the destination image.
/// </summary>
public IBrush Brush { get; }
/// <summary>
/// Gets the region that this processor applies to.
/// </summary>
public Region Region { get; }
/// <summary>
/// Gets the <see cref="GraphicsOptions"/> defining how to blend the brush pixels over the image pixels.
/// </summary>
public GraphicsOptions Options { get; }
/// <inheritdoc />
public IImageProcessor<TPixel> CreatePixelSpecificProcessor<TPixel>(Configuration configuration, Image<TPixel> source, Rectangle sourceRectangle)
where TPixel : struct, IPixel<TPixel>
=> new FillRegionProcessor<TPixel>(configuration, this, source, sourceRectangle);
}
}

197
src/ImageSharp.Drawing/Processing/Processors/Drawing/FillRegionProcessor{TPixel}.cs

@ -1,197 +0,0 @@
// Copyright (c) Six Labors and contributors.
// 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;
using SixLabors.ImageSharp.Primitives;
using SixLabors.ImageSharp.Utils;
using SixLabors.Primitives;
namespace SixLabors.ImageSharp.Processing.Processors.Drawing
{
/// <summary>
/// Using a brush and a shape fills shape with contents of brush the
/// </summary>
/// <typeparam name="TPixel">The type of the color.</typeparam>
/// <seealso cref="ImageProcessor{TPixel}" />
internal class FillRegionProcessor<TPixel> : ImageProcessor<TPixel>
where TPixel : struct, IPixel<TPixel>
{
private readonly FillRegionProcessor definition;
public FillRegionProcessor(Configuration configuration, FillRegionProcessor definition, Image<TPixel> source, Rectangle sourceRectangle)
: base(configuration, source, sourceRectangle)
{
this.definition = definition;
}
/// <inheritdoc/>
protected override void OnFrameApply(ImageFrame<TPixel> source)
{
Configuration configuration = this.Configuration;
GraphicsOptions options = this.definition.Options;
IBrush brush = this.definition.Brush;
Region region = this.definition.Region;
Rectangle rect = region.Bounds;
// Align start/end positions.
int minX = Math.Max(0, rect.Left);
int maxX = Math.Min(source.Width, rect.Right);
int minY = Math.Max(0, rect.Top);
int maxY = Math.Min(source.Height, rect.Bottom);
if (minX >= maxX)
{
return; // no effect inside image;
}
if (minY >= maxY)
{
return; // no effect inside image;
}
int maxIntersections = region.MaxIntersections;
float subpixelCount = 4;
// we need to offset the pixel grid to account for when we outline a path.
// basically if the line is [1,2] => [3,2] then when outlining at 1 we end up with a region of [0.5,1.5],[1.5, 1.5],[3.5,2.5],[2.5,2.5]
// and this can cause missed fills when not using antialiasing.so we offset the pixel grid by 0.5 in the x & y direction thus causing the#
// region to align with the pixel grid.
float offset = 0.5f;
if (options.Antialias)
{
offset = 0f; // we are antialiasing skip offsetting as real antialiasing should take care of offset.
subpixelCount = options.AntialiasSubpixelDepth;
if (subpixelCount < 4)
{
subpixelCount = 4;
}
}
using (BrushApplicator<TPixel> applicator = brush.CreateApplicator(configuration, options, source, rect))
{
int scanlineWidth = maxX - minX;
using (IMemoryOwner<float> bBuffer = source.MemoryAllocator.Allocate<float>(maxIntersections))
using (IMemoryOwner<float> bScanline = source.MemoryAllocator.Allocate<float>(scanlineWidth))
{
bool scanlineDirty = true;
float subpixelFraction = 1f / subpixelCount;
float subpixelFractionPoint = subpixelFraction / subpixelCount;
Span<float> buffer = bBuffer.Memory.Span;
Span<float> scanline = bScanline.Memory.Span;
bool isSolidBrushWithoutBlending = this.IsSolidBrushWithoutBlending(out SolidBrush solidBrush);
TPixel solidBrushColor = isSolidBrushWithoutBlending ? solidBrush.Color.ToPixel<TPixel>() : default;
for (int y = minY; y < maxY; y++)
{
if (scanlineDirty)
{
scanline.Clear();
scanlineDirty = false;
}
float yPlusOne = y + 1;
for (float subPixel = y; subPixel < yPlusOne; subPixel += subpixelFraction)
{
int pointsFound = region.Scan(subPixel + offset, buffer, configuration);
if (pointsFound == 0)
{
// nothing on this line, skip
continue;
}
QuickSort.Sort(buffer.Slice(0, pointsFound));
for (int point = 0; point < pointsFound && point < buffer.Length - 1; point += 2)
{
// points will be paired up
float scanStart = buffer[point] - minX;
float scanEnd = buffer[point + 1] - minX;
int startX = (int)MathF.Floor(scanStart + offset);
int endX = (int)MathF.Floor(scanEnd + offset);
if (startX >= 0 && startX < scanline.Length)
{
for (float x = scanStart; x < startX + 1; x += subpixelFraction)
{
scanline[startX] += subpixelFractionPoint;
scanlineDirty = true;
}
}
if (endX >= 0 && endX < scanline.Length)
{
for (float x = endX; x < scanEnd; x += subpixelFraction)
{
scanline[endX] += subpixelFractionPoint;
scanlineDirty = true;
}
}
int nextX = startX + 1;
endX = Math.Min(endX, scanline.Length); // reduce to end to the right edge
nextX = Math.Max(nextX, 0);
for (int x = nextX; x < endX; x++)
{
scanline[x] += subpixelFraction;
scanlineDirty = true;
}
}
}
if (scanlineDirty)
{
if (!options.Antialias)
{
bool hasOnes = false;
bool hasZeros = false;
for (int x = 0; x < scanlineWidth; x++)
{
if (scanline[x] >= 0.5)
{
scanline[x] = 1;
hasOnes = true;
}
else
{
scanline[x] = 0;
hasZeros = true;
}
}
if (isSolidBrushWithoutBlending && hasOnes != hasZeros)
{
if (hasOnes)
{
source.GetPixelRowSpan(y).Slice(minX, scanlineWidth).Fill(solidBrushColor);
}
continue;
}
}
applicator.Apply(scanline, minX, y);
}
}
}
}
}
private bool IsSolidBrushWithoutBlending(out SolidBrush solidBrush)
{
solidBrush = this.definition.Brush as SolidBrush;
if (solidBrush == null)
{
return false;
}
return this.definition.Options.IsOpaqueColorWithoutBlending(solidBrush.Color);
}
}
}

79
src/ImageSharp.Drawing/Processing/Processors/Text/DrawTextProcessor.cs

@ -1,79 +0,0 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System;
using SixLabors.Fonts;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.Primitives;
namespace SixLabors.ImageSharp.Processing.Processors.Text
{
/// <summary>
/// Defines a processor to draw text on an <see cref="Image"/>.
/// </summary>
public class DrawTextProcessor : IImageProcessor
{
/// <summary>
/// Initializes a new instance of the <see cref="DrawTextProcessor"/> class.
/// </summary>
/// <param name="options">The options</param>
/// <param name="text">The text we want to render</param>
/// <param name="font">The font we want to render with</param>
/// <param name="brush">The brush to source pixel colors from.</param>
/// <param name="pen">The pen to outline text with.</param>
/// <param name="location">The location on the image to start drawing the text from.</param>
public DrawTextProcessor(TextGraphicsOptions options, string text, Font font, IBrush brush, IPen pen, PointF location)
{
Guard.NotNull(text, nameof(text));
Guard.NotNull(font, nameof(font));
if (brush is null && pen is null)
{
throw new ArgumentNullException($"Expected a {nameof(brush)} or {nameof(pen)}. Both were null");
}
this.Options = options;
this.Text = text;
this.Font = font;
this.Location = location;
this.Brush = brush;
this.Pen = pen;
}
/// <summary>
/// Gets the brush used to fill the glyphs.
/// </summary>
public IBrush Brush { get; }
/// <summary>
/// Gets the <see cref="TextGraphicsOptions"/> defining blending modes and text-specific drawing settings.
/// </summary>
public TextGraphicsOptions Options { get; }
/// <summary>
/// Gets the text to draw.
/// </summary>
public string Text { get; }
/// <summary>
/// Gets the pen used for outlining the text, if Null then we will not outline
/// </summary>
public IPen Pen { get; }
/// <summary>
/// Gets the font used to render the text.
/// </summary>
public Font Font { get; }
/// <summary>
/// Gets the location to draw the text at.
/// </summary>
public PointF Location { get; }
/// <inheritdoc />
public IImageProcessor<TPixel> CreatePixelSpecificProcessor<TPixel>(Configuration configuration, Image<TPixel> source, Rectangle sourceRectangle)
where TPixel : struct, IPixel<TPixel>
=> new DrawTextProcessor<TPixel>(configuration, this, source, sourceRectangle);
}
}

453
src/ImageSharp.Drawing/Processing/Processors/Text/DrawTextProcessor{TPixel}.cs

@ -1,453 +0,0 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Buffers;
using System.Collections.Generic;
using SixLabors.Fonts;
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Utils;
using SixLabors.Memory;
using SixLabors.Primitives;
using SixLabors.Shapes;
namespace SixLabors.ImageSharp.Processing.Processors.Text
{
/// <summary>
/// Using the brush as a source of pixels colors blends the brush color with source.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
internal class DrawTextProcessor<TPixel> : ImageProcessor<TPixel>
where TPixel : struct, IPixel<TPixel>
{
private CachingGlyphRenderer textRenderer;
private readonly DrawTextProcessor definition;
public DrawTextProcessor(Configuration configuration, DrawTextProcessor definition, Image<TPixel> source, Rectangle sourceRectangle)
: base(configuration, source, sourceRectangle)
{
this.definition = definition;
}
private TextGraphicsOptions Options => this.definition.Options;
private Font Font => this.definition.Font;
private PointF Location => this.definition.Location;
private string Text => this.definition.Text;
private IPen Pen => this.definition.Pen;
private IBrush Brush => this.definition.Brush;
protected override void BeforeImageApply()
{
base.BeforeImageApply();
// do everything at the image level as we are delegating the processing down to other processors
var style = new RendererOptions(this.Font, this.Options.DpiX, this.Options.DpiY, this.Location)
{
ApplyKerning = this.Options.ApplyKerning,
TabWidth = this.Options.TabWidth,
WrappingWidth = this.Options.WrapTextWidth,
HorizontalAlignment = this.Options.HorizontalAlignment,
VerticalAlignment = this.Options.VerticalAlignment
};
this.textRenderer = new CachingGlyphRenderer(this.Configuration.MemoryAllocator, this.Text.Length, this.Pen, this.Brush != null);
this.textRenderer.Options = (GraphicsOptions)this.Options;
var renderer = new TextRenderer(this.textRenderer);
renderer.RenderText(this.Text, style);
}
protected override void AfterImageApply()
{
base.AfterImageApply();
this.textRenderer?.Dispose();
this.textRenderer = null;
}
/// <inheritdoc/>
protected override void OnFrameApply(ImageFrame<TPixel> source)
{
// this is a no-op as we have processes all as an image, we should be able to pass out of before email apply a skip frames outcome
Draw(this.textRenderer.FillOperations, this.Brush);
Draw(this.textRenderer.OutlineOperations, this.Pen?.StrokeFill);
void Draw(List<DrawingOperation> operations, IBrush brush)
{
if (operations?.Count > 0)
{
using (BrushApplicator<TPixel> app = brush.CreateApplicator(this.Configuration, this.textRenderer.Options, source, this.SourceRectangle))
{
foreach (DrawingOperation operation in operations)
{
Buffer2D<float> buffer = operation.Map;
int startY = operation.Location.Y;
int startX = operation.Location.X;
int offsetSpan = 0;
if (startX < 0)
{
offsetSpan = -startX;
startX = 0;
}
if (startX >= source.Width)
{
continue;
}
int firstRow = 0;
if (startY < 0)
{
firstRow = -startY;
}
int maxHeight = source.Height - startY;
int end = Math.Min(operation.Map.Height, maxHeight);
for (int row = firstRow; row < end; row++)
{
int y = startY + row;
Span<float> span = buffer.GetRowSpan(row).Slice(offsetSpan);
app.Apply(span, startX, y);
}
}
}
}
}
}
private struct DrawingOperation
{
public Buffer2D<float> Map { get; set; }
public Point Location { get; set; }
}
private class CachingGlyphRenderer : IGlyphRenderer, IDisposable
{
// just enough accuracy to allow for 1/8 pixel differences which
// later are accumulated while rendering, but do not grow into full pixel offsets
// The value 8 is benchmarked to:
// - Provide a good accuracy (smaller than 0.2% image difference compared to the non-caching variant)
// - Cache hit ratio above 60%
private const float AccuracyMultiple = 8;
private readonly PathBuilder builder;
private Point currentRenderPosition;
private (GlyphRendererParameters glyph, PointF subPixelOffset) currentGlyphRenderParams;
private readonly int offset;
private PointF currentPoint;
private readonly Dictionary<(GlyphRendererParameters glyph, PointF subPixelOffset), GlyphRenderData>
glyphData = new Dictionary<(GlyphRendererParameters glyph, PointF subPixelOffset), GlyphRenderData>();
private readonly bool renderOutline;
private readonly bool renderFill;
private bool rasterizationRequired;
public CachingGlyphRenderer(MemoryAllocator memoryAllocator, int size, IPen pen, bool renderFill)
{
this.MemoryAllocator = memoryAllocator;
this.currentRenderPosition = default;
this.Pen = pen;
this.renderFill = renderFill;
this.renderOutline = pen != null;
this.offset = 2;
if (this.renderFill)
{
this.FillOperations = new List<DrawingOperation>(size);
}
if (this.renderOutline)
{
this.offset = (int)MathF.Ceiling((pen.StrokeWidth * 2) + 2);
this.OutlineOperations = new List<DrawingOperation>(size);
}
this.builder = new PathBuilder();
}
public List<DrawingOperation> FillOperations { get; }
public List<DrawingOperation> OutlineOperations { get; }
public MemoryAllocator MemoryAllocator { get; internal set; }
public IPen Pen { get; internal set; }
public GraphicsOptions Options { get; internal set; }
public void BeginFigure()
{
this.builder.StartFigure();
}
public bool BeginGlyph(RectangleF bounds, GlyphRendererParameters parameters)
{
this.currentRenderPosition = Point.Truncate(bounds.Location);
PointF subPixelOffset = bounds.Location - this.currentRenderPosition;
subPixelOffset.X = MathF.Round(subPixelOffset.X * AccuracyMultiple) / AccuracyMultiple;
subPixelOffset.Y = MathF.Round(subPixelOffset.Y * AccuracyMultiple) / AccuracyMultiple;
// we have offset our rendering origin a little bit down to prevent edge cropping, move the draw origin up to compensate
this.currentRenderPosition = new Point(this.currentRenderPosition.X - this.offset, this.currentRenderPosition.Y - this.offset);
this.currentGlyphRenderParams = (parameters, subPixelOffset);
if (this.glyphData.ContainsKey(this.currentGlyphRenderParams))
{
// we have already drawn the glyph vectors skip trying again
this.rasterizationRequired = false;
return false;
}
// we check to see if we have a render cache and if we do then we render else
this.builder.Clear();
// ensure all glyphs render around [zero, zero] so offset negative root positions so when we draw the glyph we can offset it back
this.builder.SetOrigin(new PointF(-(int)bounds.X + this.offset, -(int)bounds.Y + this.offset));
this.rasterizationRequired = true;
return true;
}
public void BeginText(RectangleF bounds)
{
// not concerned about this one
this.OutlineOperations?.Clear();
this.FillOperations?.Clear();
}
public void CubicBezierTo(PointF secondControlPoint, PointF thirdControlPoint, PointF point)
{
this.builder.AddBezier(this.currentPoint, secondControlPoint, thirdControlPoint, point);
this.currentPoint = point;
}
public void Dispose()
{
foreach (KeyValuePair<(GlyphRendererParameters glyph, PointF subPixelOffset), GlyphRenderData> kv in this.glyphData)
{
kv.Value.Dispose();
}
this.glyphData.Clear();
}
public void EndFigure()
{
this.builder.CloseFigure();
}
public void EndGlyph()
{
GlyphRenderData renderData = default;
// has the glyph been rendered already?
if (this.rasterizationRequired)
{
IPath path = this.builder.Build();
if (this.renderFill)
{
renderData.FillMap = this.Render(path);
}
if (this.renderOutline)
{
if (this.Pen.StrokePattern.Length == 0)
{
path = path.GenerateOutline(this.Pen.StrokeWidth);
}
else
{
path = path.GenerateOutline(this.Pen.StrokeWidth, this.Pen.StrokePattern);
}
renderData.OutlineMap = this.Render(path);
}
this.glyphData[this.currentGlyphRenderParams] = renderData;
}
else
{
renderData = this.glyphData[this.currentGlyphRenderParams];
}
if (this.renderFill)
{
this.FillOperations.Add(new DrawingOperation
{
Location = this.currentRenderPosition,
Map = renderData.FillMap
});
}
if (this.renderOutline)
{
this.OutlineOperations.Add(new DrawingOperation
{
Location = this.currentRenderPosition,
Map = renderData.OutlineMap
});
}
}
private Buffer2D<float> Render(IPath path)
{
Size size = Rectangle.Ceiling(path.Bounds).Size;
size = new Size(size.Width + (this.offset * 2), size.Height + (this.offset * 2));
float subpixelCount = 4;
float offset = 0.5f;
if (this.Options.Antialias)
{
offset = 0f; // we are antialiasing skip offsetting as real antialiasing should take care of offset.
subpixelCount = this.Options.AntialiasSubpixelDepth;
if (subpixelCount < 4)
{
subpixelCount = 4;
}
}
// take the path inside the path builder, scan thing and generate a Buffer2d representing the glyph and cache it.
Buffer2D<float> fullBuffer = this.MemoryAllocator.Allocate2D<float>(size.Width + 1, size.Height + 1, AllocationOptions.Clean);
using (IMemoryOwner<float> bufferBacking = this.MemoryAllocator.Allocate<float>(path.MaxIntersections))
using (IMemoryOwner<PointF> rowIntersectionBuffer = this.MemoryAllocator.Allocate<PointF>(size.Width))
{
float subpixelFraction = 1f / subpixelCount;
float subpixelFractionPoint = subpixelFraction / subpixelCount;
Span<PointF> intersectionSpan = rowIntersectionBuffer.Memory.Span;
Span<float> buffer = bufferBacking.Memory.Span;
for (int y = 0; y <= size.Height; y++)
{
Span<float> scanline = fullBuffer.GetRowSpan(y);
bool scanlineDirty = false;
float yPlusOne = y + 1;
for (float subPixel = y; subPixel < yPlusOne; subPixel += subpixelFraction)
{
var start = new PointF(path.Bounds.Left - 1, subPixel);
var end = new PointF(path.Bounds.Right + 1, subPixel);
int pointsFound = path.FindIntersections(start, end, intersectionSpan);
if (pointsFound == 0)
{
// nothing on this line skip
continue;
}
for (int i = 0; i < pointsFound && i < intersectionSpan.Length; i++)
{
buffer[i] = intersectionSpan[i].X;
}
QuickSort.Sort(buffer.Slice(0, pointsFound));
for (int point = 0; point < pointsFound; point += 2)
{
// points will be paired up
float scanStart = buffer[point];
float scanEnd = buffer[point + 1];
int startX = (int)MathF.Floor(scanStart + offset);
int endX = (int)MathF.Floor(scanEnd + offset);
if (startX >= 0 && startX < scanline.Length)
{
for (float x = scanStart; x < startX + 1; x += subpixelFraction)
{
scanline[startX] += subpixelFractionPoint;
scanlineDirty = true;
}
}
if (endX >= 0 && endX < scanline.Length)
{
for (float x = endX; x < scanEnd; x += subpixelFraction)
{
scanline[endX] += subpixelFractionPoint;
scanlineDirty = true;
}
}
int nextX = startX + 1;
endX = Math.Min(endX, scanline.Length); // reduce to end to the right edge
nextX = Math.Max(nextX, 0);
for (int x = nextX; x < endX; x++)
{
scanline[x] += subpixelFraction;
scanlineDirty = true;
}
}
}
if (scanlineDirty)
{
if (!this.Options.Antialias)
{
for (int x = 0; x < size.Width; x++)
{
if (scanline[x] >= 0.5)
{
scanline[x] = 1;
}
else
{
scanline[x] = 0;
}
}
}
}
}
}
return fullBuffer;
}
public void EndText()
{
}
public void LineTo(PointF point)
{
this.builder.AddLine(this.currentPoint, point);
this.currentPoint = point;
}
public void MoveTo(PointF point)
{
this.builder.StartFigure();
this.currentPoint = point;
}
public void QuadraticBezierTo(PointF secondControlPoint, PointF point)
{
this.builder.AddBezier(this.currentPoint, secondControlPoint, point);
this.currentPoint = point;
}
private struct GlyphRenderData : IDisposable
{
public Buffer2D<float> FillMap;
public Buffer2D<float> OutlineMap;
public void Dispose()
{
this.FillMap?.Dispose();
this.OutlineMap?.Dispose();
}
}
}
}
}

104
src/ImageSharp.Drawing/Processing/RadialGradientBrush.cs

@ -1,104 +0,0 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.Primitives;
namespace SixLabors.ImageSharp.Processing
{
/// <summary>
/// A radial gradient brush, defined by center point and radius.
/// </summary>
public sealed class RadialGradientBrush : GradientBrush
{
private readonly PointF center;
private readonly float radius;
/// <inheritdoc cref="GradientBrush" />
/// <param name="center">The center of the circular gradient and 0 for the color stops.</param>
/// <param name="radius">The radius of the circular gradient and 1 for the color stops.</param>
/// <param name="repetitionMode">Defines how the colors in the gradient are repeated.</param>
/// <param name="colorStops">the color stops as defined in base class.</param>
public RadialGradientBrush(
PointF center,
float radius,
GradientRepetitionMode repetitionMode,
params ColorStop[] colorStops)
: base(repetitionMode, colorStops)
{
this.center = center;
this.radius = radius;
}
/// <inheritdoc />
public override BrushApplicator<TPixel> CreateApplicator<TPixel>(
Configuration configuration,
GraphicsOptions options,
ImageFrame<TPixel> source,
RectangleF region) =>
new RadialGradientBrushApplicator<TPixel>(
configuration,
options,
source,
this.center,
this.radius,
this.ColorStops,
this.RepetitionMode);
/// <inheritdoc />
private sealed class RadialGradientBrushApplicator<TPixel> : GradientBrushApplicator<TPixel>
where TPixel : struct, IPixel<TPixel>
{
private readonly PointF center;
private readonly float radius;
/// <summary>
/// Initializes a new instance of the <see cref="RadialGradientBrushApplicator{TPixel}" /> class.
/// </summary>
/// <param name="configuration">The configuration instance to use when performing operations.</param>
/// <param name="options">The graphics options.</param>
/// <param name="target">The target image.</param>
/// <param name="center">Center point of the gradient.</param>
/// <param name="radius">Radius of the gradient.</param>
/// <param name="colorStops">Definition of colors.</param>
/// <param name="repetitionMode">How the colors are repeated beyond the first gradient.</param>
public RadialGradientBrushApplicator(
Configuration configuration,
GraphicsOptions options,
ImageFrame<TPixel> target,
PointF center,
float radius,
ColorStop[] colorStops,
GradientRepetitionMode repetitionMode)
: base(configuration, options, target, colorStops, repetitionMode)
{
this.center = center;
this.radius = radius;
}
/// <summary>
/// As this is a circular gradient, the position on the gradient is based on
/// the distance of the point to the center.
/// </summary>
/// <param name="x">The X coordinate of the target pixel.</param>
/// <param name="y">The Y coordinate of the target pixel.</param>
/// <returns>the position on the color gradient.</returns>
protected override float PositionOnGradient(float x, float y)
{
// TODO: Can this not use Vector2 distance?
float distance = MathF.Sqrt(MathF.Pow(this.center.X - x, 2) + MathF.Pow(this.center.Y - y, 2));
return distance / this.radius;
}
internal override void Apply(Span<float> 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.
base.Apply(scanline, x, y);
}
}
}
}

166
src/ImageSharp.Drawing/Processing/RecolorBrush.cs

@ -1,166 +0,0 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Buffers;
using System.Numerics;
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.Memory;
using SixLabors.Primitives;
namespace SixLabors.ImageSharp.Processing
{
/// <summary>
/// Provides an implementation of a brush that can recolor an image
/// </summary>
public class RecolorBrush : IBrush
{
/// <summary>
/// Initializes a new instance of the <see cref="RecolorBrush" /> class.
/// </summary>
/// <param name="sourceColor">Color of the source.</param>
/// <param name="targetColor">Color of the target.</param>
/// <param name="threshold">The threshold as a value between 0 and 1.</param>
public RecolorBrush(Color sourceColor, Color targetColor, float threshold)
{
this.SourceColor = sourceColor;
this.Threshold = threshold;
this.TargetColor = targetColor;
}
/// <summary>
/// Gets the threshold.
/// </summary>
public float Threshold { get; }
/// <summary>
/// Gets the source color.
/// </summary>
public Color SourceColor { get; }
/// <summary>
/// Gets the target color.
/// </summary>
public Color TargetColor { get; }
/// <inheritdoc />
public BrushApplicator<TPixel> CreateApplicator<TPixel>(
Configuration configuration,
GraphicsOptions options,
ImageFrame<TPixel> source,
RectangleF region)
where TPixel : struct, IPixel<TPixel>
{
return new RecolorBrushApplicator<TPixel>(
configuration,
options,
source,
this.SourceColor.ToPixel<TPixel>(),
this.TargetColor.ToPixel<TPixel>(),
this.Threshold);
}
/// <summary>
/// The recolor brush applicator.
/// </summary>
private class RecolorBrushApplicator<TPixel> : BrushApplicator<TPixel>
where TPixel : struct, IPixel<TPixel>
{
/// <summary>
/// The source color.
/// </summary>
private readonly Vector4 sourceColor;
/// <summary>
/// The threshold.
/// </summary>
private readonly float threshold;
private readonly TPixel targetColorPixel;
/// <summary>
/// Initializes a new instance of the <see cref="RecolorBrushApplicator{TPixel}" /> class.
/// </summary>
/// <param name="configuration">The configuration instance to use when performing operations.</param>
/// <param name="options">The options</param>
/// <param name="source">The source image.</param>
/// <param name="sourceColor">Color of the source.</param>
/// <param name="targetColor">Color of the target.</param>
/// <param name="threshold">The threshold .</param>
public RecolorBrushApplicator(
Configuration configuration,
GraphicsOptions options,
ImageFrame<TPixel> source,
TPixel sourceColor,
TPixel targetColor,
float threshold)
: base(configuration, options, source)
{
this.sourceColor = sourceColor.ToVector4();
this.targetColorPixel = targetColor;
// Lets hack a min max extremes for a color space by letting the IPackedPixel clamp our values to something in the correct spaces :)
var maxColor = default(TPixel);
maxColor.FromVector4(new Vector4(float.MaxValue));
var minColor = default(TPixel);
minColor.FromVector4(new Vector4(float.MinValue));
this.threshold = Vector4.DistanceSquared(maxColor.ToVector4(), minColor.ToVector4()) * threshold;
}
/// <inheritdoc />
internal override TPixel this[int x, int y]
{
get
{
// Offset the requested pixel by the value in the rectangle (the shapes position)
TPixel result = this.Target[x, y];
var background = result.ToVector4();
float distance = Vector4.DistanceSquared(background, this.sourceColor);
if (distance <= this.threshold)
{
float lerpAmount = (this.threshold - distance) / this.threshold;
return this.Blender.Blend(
result,
this.targetColorPixel,
lerpAmount);
}
return result;
}
}
/// <inheritdoc />
internal override void Apply(Span<float> scanline, int x, int y)
{
MemoryAllocator memoryAllocator = this.Target.MemoryAllocator;
using (IMemoryOwner<float> amountBuffer = memoryAllocator.Allocate<float>(scanline.Length))
using (IMemoryOwner<TPixel> overlay = memoryAllocator.Allocate<TPixel>(scanline.Length))
{
Span<float> amountSpan = amountBuffer.Memory.Span;
Span<TPixel> overlaySpan = overlay.Memory.Span;
for (int i = 0; i < scanline.Length; i++)
{
amountSpan[i] = scanline[i] * this.Options.BlendPercentage;
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
overlaySpan[i] = this[offsetX, y];
}
Span<TPixel> destinationRow = this.Target.GetPixelRowSpan(y).Slice(x, scanline.Length);
this.Blender.Blend(
this.Configuration,
destinationRow,
destinationRow,
overlaySpan,
amountSpan);
}
}
}
}
}

139
src/ImageSharp.Drawing/Processing/SolidBrush.cs

@ -1,139 +0,0 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Buffers;
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.Memory;
using SixLabors.Primitives;
namespace SixLabors.ImageSharp.Processing
{
/// <summary>
/// Provides an implementation of a solid brush for painting solid color areas.
/// </summary>
public class SolidBrush : IBrush
{
/// <summary>
/// Initializes a new instance of the <see cref="SolidBrush"/> class.
/// </summary>
/// <param name="color">The color.</param>
public SolidBrush(Color color)
{
this.Color = color;
}
/// <summary>
/// Gets the color.
/// </summary>
public Color Color { get; }
/// <inheritdoc />
public BrushApplicator<TPixel> CreateApplicator<TPixel>(
Configuration configuration,
GraphicsOptions options,
ImageFrame<TPixel> source,
RectangleF region)
where TPixel : struct, IPixel<TPixel>
{
return new SolidBrushApplicator<TPixel>(configuration, options, source, this.Color.ToPixel<TPixel>());
}
/// <summary>
/// The solid brush applicator.
/// </summary>
private class SolidBrushApplicator<TPixel> : BrushApplicator<TPixel>
where TPixel : struct, IPixel<TPixel>
{
private bool isDisposed;
/// <summary>
/// Initializes a new instance of the <see cref="SolidBrushApplicator{TPixel}"/> class.
/// </summary>
/// <param name="configuration">The configuration instance to use when performing operations.</param>
/// <param name="options">The graphics options.</param>
/// <param name="source">The source image.</param>
/// <param name="color">The color.</param>
public SolidBrushApplicator(
Configuration configuration,
GraphicsOptions options,
ImageFrame<TPixel> source,
TPixel color)
: base(configuration, options, source)
{
this.Colors = source.MemoryAllocator.Allocate<TPixel>(source.Width);
this.Colors.Memory.Span.Fill(color);
}
/// <summary>
/// Gets the colors.
/// </summary>
protected IMemoryOwner<TPixel> Colors { get; private set; }
/// <inheritdoc/>
internal override TPixel this[int x, int y] => this.Colors.Memory.Span[x];
/// <inheritdoc />
protected override void Dispose(bool disposing)
{
if (this.isDisposed)
{
return;
}
if (disposing)
{
this.Colors.Dispose();
}
this.Colors = null;
this.isDisposed = true;
}
/// <inheritdoc />
internal override void Apply(Span<float> scanline, int x, int y)
{
Span<TPixel> destinationRow = this.Target.GetPixelRowSpan(y).Slice(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);
}
MemoryAllocator memoryAllocator = this.Target.MemoryAllocator;
Configuration configuration = this.Configuration;
if (this.Options.BlendPercentage == 1f)
{
this.Blender.Blend(configuration, destinationRow, destinationRow, this.Colors.Memory.Span, scanline);
}
else
{
using (IMemoryOwner<float> amountBuffer = memoryAllocator.Allocate<float>(scanline.Length))
{
Span<float> 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);
}
}
}
}
}
}

217
src/ImageSharp.Drawing/Processing/TextGraphicsOptions.cs

@ -1,217 +0,0 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using SixLabors.Fonts;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Processing
{
/// <summary>
/// Options for influencing the drawing functions.
/// </summary>
public class TextGraphicsOptions : IDeepCloneable<TextGraphicsOptions>
{
private int antialiasSubpixelDepth = 16;
private float blendPercentage = 1F;
private float tabWidth = 4F;
private float dpiX = 72F;
private float dpiY = 72F;
/// <summary>
/// Initializes a new instance of the <see cref="TextGraphicsOptions"/> class.
/// </summary>
public TextGraphicsOptions()
{
}
private TextGraphicsOptions(TextGraphicsOptions source)
{
this.AlphaCompositionMode = source.AlphaCompositionMode;
this.Antialias = source.Antialias;
this.AntialiasSubpixelDepth = source.AntialiasSubpixelDepth;
this.ApplyKerning = source.ApplyKerning;
this.BlendPercentage = source.BlendPercentage;
this.ColorBlendingMode = source.ColorBlendingMode;
this.DpiX = source.DpiX;
this.DpiY = source.DpiY;
this.HorizontalAlignment = source.HorizontalAlignment;
this.TabWidth = source.TabWidth;
this.WrapTextWidth = source.WrapTextWidth;
this.VerticalAlignment = source.VerticalAlignment;
}
/// <summary>
/// Gets or sets a value indicating whether antialiasing should be applied.
/// Defaults to true.
/// </summary>
public bool Antialias { get; set; } = true;
/// <summary>
/// Gets or sets a value indicating the number of subpixels to use while rendering with antialiasing enabled.
/// </summary>
public int AntialiasSubpixelDepth
{
get
{
return this.antialiasSubpixelDepth;
}
set
{
Guard.MustBeGreaterThanOrEqualTo(value, 0, nameof(this.AntialiasSubpixelDepth));
this.antialiasSubpixelDepth = value;
}
}
/// <summary>
/// Gets or sets a value indicating the blending percentage to apply to the drawing operation.
/// </summary>
public float BlendPercentage
{
get
{
return this.blendPercentage;
}
set
{
Guard.MustBeBetweenOrEqualTo(value, 0, 1F, nameof(this.BlendPercentage));
this.blendPercentage = value;
}
}
/// <summary>
/// Gets or sets a value indicating the color blending percentage to apply to the drawing operation.
/// Defaults to <see cref= "PixelColorBlendingMode.Normal" />.
/// </summary>
public PixelColorBlendingMode ColorBlendingMode { get; set; } = PixelColorBlendingMode.Normal;
/// <summary>
/// Gets or sets a value indicating the color blending percentage to apply to the drawing operation
/// Defaults to <see cref= "PixelAlphaCompositionMode.SrcOver" />.
/// </summary>
public PixelAlphaCompositionMode AlphaCompositionMode { get; set; } = PixelAlphaCompositionMode.SrcOver;
/// <summary>
/// Gets or sets a value indicating whether the text should be drawing with kerning enabled.
/// Defaults to true;
/// </summary>
public bool ApplyKerning { get; set; } = true;
/// <summary>
/// Gets or sets a value indicating the number of space widths a tab should lock to.
/// Defaults to 4.
/// </summary>
public float TabWidth
{
get
{
return this.tabWidth;
}
set
{
Guard.MustBeGreaterThanOrEqualTo(value, 0, nameof(this.TabWidth));
this.tabWidth = value;
}
}
/// <summary>
/// Gets or sets a value, if greater than 0, indicating the width at which text should wrap.
/// Defaults to 0.
/// </summary>
public float WrapTextWidth { get; set; }
/// <summary>
/// Gets or sets a value indicating the DPI (Dots Per Inch) to render text along the X axis.
/// Defaults to 72.
/// </summary>
public float DpiX
{
get
{
return this.dpiX;
}
set
{
Guard.MustBeGreaterThanOrEqualTo(value, 0, nameof(this.DpiX));
this.dpiX = value;
}
}
/// <summary>
/// Gets or sets a value indicating the DPI (Dots Per Inch) to render text along the Y axis.
/// Defaults to 72.
/// </summary>
public float DpiY
{
get
{
return this.dpiY;
}
set
{
Guard.MustBeGreaterThanOrEqualTo(value, 0, nameof(this.DpiY));
this.dpiY = value;
}
}
/// <summary>
/// Gets or sets a value indicating how to align the text relative to the rendering space.
/// If <see cref="WrapTextWidth"/> is greater than zero it will align relative to the space
/// defined by the location and width, if <see cref="WrapTextWidth"/> equals zero, and thus
/// wrapping disabled, then the alignment is relative to the drawing location.
/// Defaults to <see cref="HorizontalAlignment.Left"/>.
/// </summary>
public HorizontalAlignment HorizontalAlignment { get; set; } = HorizontalAlignment.Left;
/// <summary>
/// Gets or sets a value indicating how to align the text relative to the rendering space.
/// Defaults to <see cref="VerticalAlignment.Top"/>.
/// </summary>
public VerticalAlignment VerticalAlignment { get; set; } = VerticalAlignment.Top;
/// <summary>
/// Performs an implicit conversion from <see cref="GraphicsOptions"/> to <see cref="TextGraphicsOptions"/>.
/// </summary>
/// <param name="options">The options.</param>
/// <returns>
/// The result of the conversion.
/// </returns>
public static implicit operator TextGraphicsOptions(GraphicsOptions options)
{
return new TextGraphicsOptions()
{
Antialias = options.Antialias,
AntialiasSubpixelDepth = options.AntialiasSubpixelDepth,
blendPercentage = options.BlendPercentage,
ColorBlendingMode = options.ColorBlendingMode,
AlphaCompositionMode = options.AlphaCompositionMode
};
}
/// <summary>
/// Performs an explicit conversion from <see cref="TextGraphicsOptions"/> to <see cref="GraphicsOptions"/>.
/// </summary>
/// <param name="options">The options.</param>
/// <returns>
/// The result of the conversion.
/// </returns>
public static explicit operator GraphicsOptions(TextGraphicsOptions options)
{
return new GraphicsOptions()
{
Antialias = options.Antialias,
AntialiasSubpixelDepth = options.AntialiasSubpixelDepth,
ColorBlendingMode = options.ColorBlendingMode,
AlphaCompositionMode = options.AlphaCompositionMode,
BlendPercentage = options.BlendPercentage
};
}
/// <inheritdoc/>
public TextGraphicsOptions DeepClone() => new TextGraphicsOptions(this);
}
}

29
src/ImageSharp.Drawing/Utils/NumberUtils.cs

@ -1,29 +0,0 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System.Runtime.CompilerServices;
namespace SixLabors.ImageSharp
{
/// <summary>
/// Utility methods for numeric primitives.
/// </summary>
internal static class NumberUtils
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static float ClampFloat(float value, float min, float max)
{
if (value >= max)
{
return max;
}
if (value <= min)
{
return min;
}
return value;
}
}
}

84
src/ImageSharp.Drawing/Utils/QuickSort.cs

@ -1,84 +0,0 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Runtime.CompilerServices;
namespace SixLabors.ImageSharp.Utils
{
/// <summary>
/// Optimized quick sort implementation for Span{float} input
/// </summary>
internal class QuickSort
{
/// <summary>
/// Sorts the elements of <paramref name="data"/> in ascending order
/// </summary>
/// <param name="data">The items to sort</param>
public static void Sort(Span<float> data)
{
if (data.Length < 2)
{
return;
}
if (data.Length == 2)
{
if (data[0] > data[1])
{
Swap(ref data[0], ref data[1]);
}
return;
}
Sort(ref data[0], 0, data.Length - 1);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void Swap(ref float left, ref float right)
{
float tmp = left;
left = right;
right = tmp;
}
private static void Sort(ref float data0, int lo, int hi)
{
if (lo < hi)
{
int p = Partition(ref data0, lo, hi);
Sort(ref data0, lo, p);
Sort(ref data0, p + 1, hi);
}
}
private static int Partition(ref float data0, int lo, int hi)
{
float pivot = Unsafe.Add(ref data0, lo);
int i = lo - 1;
int j = hi + 1;
while (true)
{
do
{
i = i + 1;
}
while (Unsafe.Add(ref data0, i) < pivot && i < hi);
do
{
j = j - 1;
}
while (Unsafe.Add(ref data0, j) > pivot && j > lo);
if (i >= j)
{
return j;
}
Swap(ref Unsafe.Add(ref data0, i), ref Unsafe.Add(ref data0, j));
}
}
}
}

0
src/ImageSharp.Drawing/Processing/Extensions/DrawImageExtensions.cs → src/ImageSharp/Drawing/Processing/Extensions/DrawImageExtensions.cs

0
src/ImageSharp.Drawing/Processing/Processors/Drawing/DrawImageProcessor.cs → src/ImageSharp/Drawing/Processing/Processors/Drawing/DrawImageProcessor.cs

0
src/ImageSharp.Drawing/Processing/Processors/Drawing/DrawImageProcessor{TPixelBg,TPixelFg}.cs → src/ImageSharp/Drawing/Processing/Processors/Drawing/DrawImageProcessor{TPixelBg,TPixelFg}.cs

63
tests/ImageSharp.Benchmarks/Drawing/DrawBeziers.cs

@ -1,63 +0,0 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System.Drawing;
using System.Drawing.Drawing2D;
using System.IO;
using System.Numerics;
using BenchmarkDotNet.Attributes;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing;
namespace SixLabors.ImageSharp.Benchmarks
{
public class DrawBeziers : BenchmarkBase
{
[Benchmark(Baseline = true, Description = "System.Drawing Draw Beziers")]
public void DrawPathSystemDrawing()
{
using (var destination = new Bitmap(800, 800))
using (var graphics = Graphics.FromImage(destination))
{
graphics.InterpolationMode = InterpolationMode.Default;
graphics.SmoothingMode = SmoothingMode.AntiAlias;
using (var pen = new System.Drawing.Pen(System.Drawing.Color.HotPink, 10))
{
graphics.DrawBeziers(pen, new[] {
new PointF(10, 500),
new PointF(30, 10),
new PointF(240, 30),
new PointF(300, 500)
});
}
using (var stream = new MemoryStream())
{
destination.Save(stream, System.Drawing.Imaging.ImageFormat.Bmp);
}
}
}
[Benchmark(Description = "ImageSharp Draw Beziers")]
public void DrawLinesCore()
{
using (var image = new Image<Rgba32>(800, 800))
{
image.Mutate(x => x.DrawBeziers(
Rgba32.HotPink,
10,
new Vector2(10, 500),
new Vector2(30, 10),
new Vector2(240, 30),
new Vector2(300, 500)));
using (var stream = new MemoryStream())
{
image.SaveAsBmp(stream);
}
}
}
}
}

62
tests/ImageSharp.Benchmarks/Drawing/DrawLines.cs

@ -1,62 +0,0 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System.Drawing;
using System.Drawing.Drawing2D;
using System.IO;
using System.Numerics;
using BenchmarkDotNet.Attributes;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing;
namespace SixLabors.ImageSharp.Benchmarks
{
public class DrawLines : BenchmarkBase
{
[Benchmark(Baseline = true, Description = "System.Drawing Draw Lines")]
public void DrawPathSystemDrawing()
{
using (var destination = new Bitmap(800, 800))
using (var graphics = Graphics.FromImage(destination))
{
graphics.InterpolationMode = InterpolationMode.Default;
graphics.SmoothingMode = SmoothingMode.AntiAlias;
using (var pen = new System.Drawing.Pen(System.Drawing.Color.HotPink, 10))
{
graphics.DrawLines(pen, new[] {
new PointF(10, 10),
new PointF(550, 50),
new PointF(200, 400)
});
}
using (var stream = new MemoryStream())
{
destination.Save(stream, System.Drawing.Imaging.ImageFormat.Bmp);
}
}
}
[Benchmark(Description = "ImageSharp Draw Lines")]
public void DrawLinesCore()
{
using (var image = new Image<Rgba32>(800, 800))
{
image.Mutate(x => x.DrawLines(
Rgba32.HotPink,
10,
new Vector2(10, 10),
new Vector2(550, 50),
new Vector2(200, 400)));
using (var stream = new MemoryStream())
{
image.SaveAsBmp(stream);
}
}
}
}
}

60
tests/ImageSharp.Benchmarks/Drawing/DrawPolygon.cs

@ -1,60 +0,0 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System.Drawing;
using System.Drawing.Drawing2D;
using BenchmarkDotNet.Attributes;
using System.IO;
using System.Numerics;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing;
namespace SixLabors.ImageSharp.Benchmarks
{
public class DrawPolygon : BenchmarkBase
{
[Benchmark(Baseline = true, Description = "System.Drawing Draw Polygon")]
public void DrawPolygonSystemDrawing()
{
using (var destination = new Bitmap(800, 800))
using (var graphics = Graphics.FromImage(destination))
{
graphics.InterpolationMode = InterpolationMode.Default;
graphics.SmoothingMode = SmoothingMode.AntiAlias;
using (var pen = new System.Drawing.Pen(System.Drawing.Color.HotPink, 10))
{
graphics.DrawPolygon(pen, new[] {
new PointF(10, 10),
new PointF(550, 50),
new PointF(200, 400)
});
}
using (var stream = new MemoryStream())
{
destination.Save(stream, System.Drawing.Imaging.ImageFormat.Bmp);
}
}
}
[Benchmark(Description = "ImageSharp Draw Polygon")]
public void DrawPolygonCore()
{
using (var image = new Image<Rgba32>(800, 800))
{
image.Mutate(x => x.DrawPolygon(
Rgba32.HotPink,
10,
new Vector2(10, 10),
new Vector2(550, 50),
new Vector2(200, 400)));
using (var ms = new MemoryStream())
{
image.SaveAsBmp(ms);
}
}
}
}
}

96
tests/ImageSharp.Benchmarks/Drawing/DrawText.cs

@ -1,96 +0,0 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System.Drawing;
using System.Drawing.Drawing2D;
using BenchmarkDotNet.Attributes;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing;
using System.Linq;
using SixLabors.ImageSharp.Processing.Processors.Text;
namespace SixLabors.ImageSharp.Benchmarks
{
[MemoryDiagnoser]
public class DrawText : BenchmarkBase
{
[Params(10, 100)]
public int TextIterations { get; set; }
public string TextPhrase { get; set; } = "Hello World";
public string TextToRender => string.Join(" ", Enumerable.Repeat(this.TextPhrase, this.TextIterations));
[Benchmark(Baseline = true, Description = "System.Drawing Draw Text")]
public void DrawTextSystemDrawing()
{
using (var destination = new Bitmap(800, 800))
using (var graphics = Graphics.FromImage(destination))
{
graphics.InterpolationMode = InterpolationMode.Default;
graphics.SmoothingMode = SmoothingMode.AntiAlias;
using (var font = new Font("Arial", 12, GraphicsUnit.Point))
{
graphics.DrawString(this.TextToRender, font, System.Drawing.Brushes.HotPink, new RectangleF(10, 10, 780, 780));
}
}
}
[Benchmark(Description = "ImageSharp Draw Text - Cached Glyphs")]
public void DrawTextCore()
{
using (var image = new Image<Rgba32>(800, 800))
{
var font = SixLabors.Fonts.SystemFonts.CreateFont("Arial", 12);
image.Mutate(x => x.ApplyProcessor(new DrawTextProcessor(new TextGraphicsOptions { Antialias = true, WrapTextWidth = 780 }, this.TextToRender, font, Processing.Brushes.Solid(Rgba32.HotPink), null, new SixLabors.Primitives.PointF(10, 10))));
}
}
[Benchmark(Description = "ImageSharp Draw Text - Nieve")]
public void DrawTextCoreOld()
{
using (var image = new Image<Rgba32>(800, 800))
{
var font = SixLabors.Fonts.SystemFonts.CreateFont("Arial", 12);
image.Mutate(x => DrawTextOldVersion(x, new TextGraphicsOptions { Antialias = true, WrapTextWidth = 780 }, this.TextToRender, font, Processing.Brushes.Solid(Rgba32.HotPink), null, new SixLabors.Primitives.PointF(10, 10)));
}
IImageProcessingContext DrawTextOldVersion(
IImageProcessingContext source,
TextGraphicsOptions options,
string text,
SixLabors.Fonts.Font font,
IBrush brush,
IPen pen,
SixLabors.Primitives.PointF location)
{
float dpiX = 72;
float dpiY = 72;
var style = new SixLabors.Fonts.RendererOptions(font, dpiX, dpiY, location)
{
ApplyKerning = options.ApplyKerning,
TabWidth = options.TabWidth,
WrappingWidth = options.WrapTextWidth,
HorizontalAlignment = options.HorizontalAlignment,
VerticalAlignment = options.VerticalAlignment
};
Shapes.IPathCollection glyphs = Shapes.TextBuilder.GenerateGlyphs(text, style);
var pathOptions = (GraphicsOptions)options;
if (brush != null)
{
source.Fill(pathOptions, brush, glyphs);
}
if (pen != null)
{
source.Draw(pathOptions, pen, glyphs);
}
return source;
}
}
}
}

102
tests/ImageSharp.Benchmarks/Drawing/DrawTextOutline.cs

@ -1,102 +0,0 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System.Drawing;
using System.Drawing.Drawing2D;
using BenchmarkDotNet.Attributes;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing;
using System.Linq;
using SixLabors.ImageSharp.Processing.Processors.Text;
namespace SixLabors.ImageSharp.Benchmarks
{
[MemoryDiagnoser]
public class DrawTextOutline : BenchmarkBase
{
[Params(10, 100)]
public int TextIterations { get; set; }
public string TextPhrase { get; set; } = "Hello World";
public string TextToRender => string.Join(" ", Enumerable.Repeat(this.TextPhrase, this.TextIterations));
[Benchmark(Baseline = true, Description = "System.Drawing Draw Text Outline")]
public void DrawTextSystemDrawing()
{
using (var destination = new Bitmap(800, 800))
using (var graphics = Graphics.FromImage(destination))
{
graphics.InterpolationMode = InterpolationMode.Default;
graphics.SmoothingMode = SmoothingMode.AntiAlias;
using (var pen = new System.Drawing.Pen(System.Drawing.Color.HotPink, 10))
using (var font = new Font("Arial", 12, GraphicsUnit.Point))
using (var gp = new GraphicsPath())
{
gp.AddString(this.TextToRender, font.FontFamily, (int)font.Style, font.Size, new RectangleF(10, 10, 780, 780), new StringFormat());
graphics.DrawPath(pen, gp);
}
}
}
[Benchmark(Description = "ImageSharp Draw Text Outline - Cached Glyphs")]
public void DrawTextCore()
{
using (var image = new Image<Rgba32>(800, 800))
{
var font = SixLabors.Fonts.SystemFonts.CreateFont("Arial", 12);
image.Mutate(x => x.ApplyProcessor(new DrawTextProcessor(new TextGraphicsOptions { Antialias = true, WrapTextWidth = 780 }, this.TextToRender, font, null, Processing.Pens.Solid(Rgba32.HotPink, 10), new SixLabors.Primitives.PointF(10, 10))));
}
}
[Benchmark(Description = "ImageSharp Draw Text Outline - Nieve")]
public void DrawTextCoreOld()
{
using (var image = new Image<Rgba32>(800, 800))
{
var font = SixLabors.Fonts.SystemFonts.CreateFont("Arial", 12);
image.Mutate(
x => DrawTextOldVersion(
x,
new TextGraphicsOptions { Antialias = true, WrapTextWidth = 780 },
this.TextToRender,
font,
null,
Processing.Pens.Solid(Rgba32.HotPink, 10),
new SixLabors.Primitives.PointF(10, 10)));
}
IImageProcessingContext DrawTextOldVersion(
IImageProcessingContext source,
TextGraphicsOptions options,
string text,
SixLabors.Fonts.Font font,
IBrush brush,
IPen pen,
SixLabors.Primitives.PointF location)
{
var style = new SixLabors.Fonts.RendererOptions(font, options.DpiX, options.DpiY, location)
{
ApplyKerning = options.ApplyKerning,
TabWidth = options.TabWidth,
WrappingWidth = options.WrapTextWidth,
HorizontalAlignment = options.HorizontalAlignment,
VerticalAlignment = options.VerticalAlignment
};
Shapes.IPathCollection glyphs = Shapes.TextBuilder.GenerateGlyphs(text, style);
var pathOptions = (GraphicsOptions)options;
if (brush != null)
{
source.Fill(pathOptions, brush, glyphs);
}
if (pen != null)
{
source.Draw(pathOptions, pen, glyphs);
}
return source;
}
}
}
}

84
tests/ImageSharp.Benchmarks/Drawing/FillPolygon.cs

@ -1,84 +0,0 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System.Drawing;
using System.Drawing.Drawing2D;
using System.IO;
using System.Numerics;
using SixLabors.Shapes;
using BenchmarkDotNet.Attributes;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing;
namespace SixLabors.ImageSharp.Benchmarks
{
public class FillPolygon : BenchmarkBase
{
private readonly Polygon shape;
public FillPolygon()
{
this.shape = new Polygon(new LinearLineSegment(
new Vector2(10, 10),
new Vector2(550, 50),
new Vector2(200, 400)));
}
[Benchmark(Baseline = true, Description = "System.Drawing Fill Polygon")]
public void DrawSolidPolygonSystemDrawing()
{
using (var destination = new Bitmap(800, 800))
using (var graphics = Graphics.FromImage(destination))
{
graphics.SmoothingMode = SmoothingMode.AntiAlias;
graphics.FillPolygon(System.Drawing.Brushes.HotPink,
new[] {
new Point(10, 10),
new Point(550, 50),
new Point(200, 400)
});
using (var stream = new MemoryStream())
{
destination.Save(stream, System.Drawing.Imaging.ImageFormat.Bmp);
}
}
}
[Benchmark(Description = "ImageSharp Fill Polygon")]
public void DrawSolidPolygonCore()
{
using (var image = new Image<Rgba32>(800, 800))
{
image.Mutate(x => x.FillPolygon(
Rgba32.HotPink,
new Vector2(10, 10),
new Vector2(550, 50),
new Vector2(200, 400)));
using (var stream = new MemoryStream())
{
image.SaveAsBmp(stream);
}
}
}
[Benchmark(Description = "ImageSharp Fill Polygon - cached shape")]
public void DrawSolidPolygonCoreCached()
{
using (var image = new Image<Rgba32>(800, 800))
{
image.Mutate(x => x.Fill(
Rgba32.HotPink,
this.shape));
using (var stream = new MemoryStream())
{
image.SaveAsBmp(stream);
}
}
}
}
}

59
tests/ImageSharp.Benchmarks/Drawing/FillRectangle.cs

@ -1,59 +0,0 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Numerics;
using BenchmarkDotNet.Attributes;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing;
using CoreRectangle = SixLabors.Primitives.Rectangle;
using CoreSize = SixLabors.Primitives.Size;
namespace SixLabors.ImageSharp.Benchmarks
{
public class FillRectangle : BenchmarkBase
{
[Benchmark(Baseline = true, Description = "System.Drawing Fill Rectangle")]
public Size FillRectangleSystemDrawing()
{
using (var destination = new Bitmap(800, 800))
using (var graphics = Graphics.FromImage(destination))
{
graphics.InterpolationMode = InterpolationMode.Default;
graphics.SmoothingMode = SmoothingMode.AntiAlias;
graphics.FillRectangle(System.Drawing.Brushes.HotPink, new Rectangle(10, 10, 190, 140));
return destination.Size;
}
}
[Benchmark(Description = "ImageSharp Fill Rectangle")]
public CoreSize FillRectangleCore()
{
using (var image = new Image<Rgba32>(800, 800))
{
image.Mutate(x => x.Fill(Rgba32.HotPink, new CoreRectangle(10, 10, 190, 140)));
return new CoreSize(image.Width, image.Height);
}
}
[Benchmark(Description = "ImageSharp Fill Rectangle - As Polygon")]
public CoreSize FillPolygonCore()
{
using (var image = new Image<Rgba32>(800, 800))
{
image.Mutate(x => x.FillPolygon(
Rgba32.HotPink,
new Vector2(10, 10),
new Vector2(200, 10),
new Vector2(200, 150),
new Vector2(10, 150)));
return new CoreSize(image.Width, image.Height);
}
}
}
}

53
tests/ImageSharp.Benchmarks/Drawing/FillWithPattern.cs

@ -1,53 +0,0 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System.Drawing;
using System.Drawing.Drawing2D;
using System.IO;
using BenchmarkDotNet.Attributes;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing;
using CoreBrushes = SixLabors.ImageSharp.Processing.Brushes;
namespace SixLabors.ImageSharp.Benchmarks
{
public class FillWithPattern
{
[Benchmark(Baseline = true, Description = "System.Drawing Fill with Pattern")]
public void DrawPatternPolygonSystemDrawing()
{
using (var destination = new Bitmap(800, 800))
using (var graphics = Graphics.FromImage(destination))
{
graphics.SmoothingMode = SmoothingMode.AntiAlias;
using (var brush = new HatchBrush(HatchStyle.BackwardDiagonal, System.Drawing.Color.HotPink))
{
graphics.FillRectangle(brush, new Rectangle(0, 0, 800, 800)); // can't find a way to flood fill with a brush
}
using (var stream = new MemoryStream())
{
destination.Save(stream, System.Drawing.Imaging.ImageFormat.Bmp);
}
}
}
[Benchmark(Description = "ImageSharp Fill with Pattern")]
public void DrawPatternPolygon3Core()
{
using (var image = new Image<Rgba32>(800, 800))
{
image.Mutate(x => x.Fill(CoreBrushes.BackwardDiagonal(Rgba32.HotPink)));
using (var stream = new MemoryStream())
{
image.SaveAsBmp(stream);
}
}
}
}
}

1
tests/ImageSharp.Benchmarks/ImageSharp.Benchmarks.csproj

@ -26,7 +26,6 @@
<ItemGroup>
<ProjectReference Include="..\..\src\ImageSharp\ImageSharp.csproj" />
<ProjectReference Include="..\..\src\ImageSharp.Drawing\ImageSharp.Drawing.csproj" />
</ItemGroup>
</Project>

1
tests/ImageSharp.Sandbox46/ImageSharp.Sandbox46.csproj

@ -22,7 +22,6 @@
<ItemGroup>
<ProjectReference Include="..\..\src\ImageSharp\ImageSharp.csproj" />
<ProjectReference Include="..\..\src\ImageSharp.Drawing\ImageSharp.Drawing.csproj" />
</ItemGroup>
</Project>

47
tests/ImageSharp.Tests/Drawing/DrawBezierTests.cs

@ -1,47 +0,0 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Numerics;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing;
using Xunit;
namespace SixLabors.ImageSharp.Tests.Drawing
{
[GroupOutput("Drawing")]
public class DrawBezierTests
{
public static readonly TheoryData<string, byte, float> DrawPathData = new TheoryData<string, byte, float>
{
{ "White", 255, 1.5f },
{ "Red", 255, 3 },
{ "HotPink", 255, 5 },
{ "HotPink", 150, 5 },
{ "White", 255, 15 },
};
[Theory]
[WithSolidFilledImages(nameof(DrawPathData), 300, 450, "Blue", PixelTypes.Rgba32)]
public void DrawBeziers<TPixel>(TestImageProvider<TPixel> provider, string colorName, byte alpha, float thickness)
where TPixel : struct, IPixel<TPixel>
{
var points = new SixLabors.Primitives.PointF[]
{
new Vector2(10, 400), new Vector2(30, 10), new Vector2(240, 30), new Vector2(300, 400)
};
Rgba32 rgba = TestUtils.GetColorByName(colorName);
rgba.A = alpha;
Color color = rgba;
FormattableString testDetails = $"{colorName}_A{alpha}_T{thickness}";
provider.RunValidatingProcessorTest( x => x.DrawBeziers(color, 5f, points),
testDetails,
appendSourceFileOrDescription: false,
appendPixelTypeToFileName: false);
}
}
}

69
tests/ImageSharp.Tests/Drawing/DrawComplexPolygonTests.cs

@ -1,69 +0,0 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System.Numerics;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing;
using SixLabors.Shapes;
using Xunit;
namespace SixLabors.ImageSharp.Tests.Drawing
{
[GroupOutput("Drawing")]
public class DrawComplexPolygonTests
{
[Theory]
[WithBasicTestPatternImages(250, 350, PixelTypes.Rgba32, false, false, false)]
[WithBasicTestPatternImages(250, 350, PixelTypes.Rgba32, true, false, false)]
[WithBasicTestPatternImages(250, 350, PixelTypes.Rgba32, false, true, false)]
[WithBasicTestPatternImages(250, 350, PixelTypes.Rgba32, false, false, true)]
public void DrawComplexPolygon<TPixel>(TestImageProvider<TPixel> provider, bool overlap, bool transparent, bool dashed)
where TPixel :struct, IPixel<TPixel>
{
var simplePath = new Polygon(new LinearLineSegment(
new Vector2(10, 10),
new Vector2(200, 150),
new Vector2(50, 300)));
var hole1 = new Polygon(new LinearLineSegment(
new Vector2(37, 85),
overlap ? new Vector2(130, 40) : new Vector2(93, 85),
new Vector2(65, 137)));
IPath clipped = simplePath.Clip(hole1);
Rgba32 colorRgba = Rgba32.White;
if (transparent)
{
colorRgba.A = 150;
}
Color color = colorRgba;
string testDetails = "";
if (overlap)
{
testDetails += "_Overlap";
}
if (transparent)
{
testDetails += "_Transparent";
}
if (dashed)
{
testDetails += "_Dashed";
}
Pen pen = dashed ? Pens.Dash(color, 5f) : Pens.Solid(color, 5f);
provider.RunValidatingProcessorTest(
x => x.Draw(pen, clipped),
testDetails,
appendPixelTypeToFileName: false,
appendSourceFileOrDescription: false);
}
}
}

4
tests/ImageSharp.Tests/Drawing/DrawImageTests.cs

@ -2,7 +2,7 @@
// Licensed under the Apache License, Version 2.0.
using System;
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Formats.Png;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing;
@ -127,7 +127,7 @@ namespace SixLabors.ImageSharp.Tests.Drawing
using (Image<Rgba32> background = provider.GetImage())
using (var overlay = new Image<Rgba32>(50, 50))
{
overlay.Mutate(c => c.Fill(Rgba32.Black));
overlay.GetPixelSpan().Fill(Rgba32.Black);
background.Mutate(c => c.DrawImage(overlay, new Point(x, y), PixelColorBlendingMode.Normal, 1F));

99
tests/ImageSharp.Tests/Drawing/DrawLinesTests.cs

@ -1,99 +0,0 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Numerics;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing;
using Xunit;
namespace SixLabors.ImageSharp.Tests.Drawing
{
[GroupOutput("Drawing")]
public class DrawLinesTests
{
[Theory]
[WithBasicTestPatternImages(250, 350, PixelTypes.Rgba32, "White", 1f, 2.5, true)]
[WithBasicTestPatternImages(250, 350, PixelTypes.Rgba32, "White", 0.6f, 10, true)]
[WithBasicTestPatternImages(250, 350, PixelTypes.Rgba32, "White", 1f, 5, false)]
[WithBasicTestPatternImages(250, 350, PixelTypes.Bgr24, "Yellow", 1f, 10, true)]
public void DrawLines_Simple<TPixel>(TestImageProvider<TPixel> provider, string colorName, float alpha, float thickness, bool antialias)
where TPixel : struct, IPixel<TPixel>
{
Color color = TestUtils.GetColorByName(colorName).WithAlpha(alpha);
Pen pen = new Pen(color, thickness);
DrawLinesImpl(provider, colorName, alpha, thickness, antialias, pen);
}
[Theory]
[WithBasicTestPatternImages(250, 350, PixelTypes.Rgba32, "White", 1f, 5, false)]
public void DrawLines_Dash<TPixel>(TestImageProvider<TPixel> provider, string colorName, float alpha, float thickness, bool antialias)
where TPixel : struct, IPixel<TPixel>
{
Color color = TestUtils.GetColorByName(colorName).WithAlpha(alpha);
Pen pen = Pens.Dash(color, thickness);
DrawLinesImpl(provider, colorName, alpha, thickness, antialias, pen);
}
[Theory]
[WithBasicTestPatternImages(250, 350, PixelTypes.Rgba32, "LightGreen", 1f, 5, false)]
public void DrawLines_Dot<TPixel>(TestImageProvider<TPixel> provider, string colorName, float alpha, float thickness, bool antialias)
where TPixel : struct, IPixel<TPixel>
{
Color color = TestUtils.GetColorByName(colorName).WithAlpha(alpha);
Pen pen = Pens.Dot(color, thickness);
DrawLinesImpl(provider, colorName, alpha, thickness, antialias, pen);
}
[Theory]
[WithBasicTestPatternImages(250, 350, PixelTypes.Rgba32, "Yellow", 1f, 5, false)]
public void DrawLines_DashDot<TPixel>(TestImageProvider<TPixel> provider, string colorName, float alpha, float thickness, bool antialias)
where TPixel : struct, IPixel<TPixel>
{
Color color = TestUtils.GetColorByName(colorName).WithAlpha(alpha);
Pen pen = Pens.DashDot(color, thickness);
DrawLinesImpl(provider, colorName, alpha, thickness, antialias, pen);
}
[Theory]
[WithBasicTestPatternImages(250, 350, PixelTypes.Rgba32, "Black", 1f, 5, false)]
public void DrawLines_DashDotDot<TPixel>(TestImageProvider<TPixel> provider, string colorName, float alpha, float thickness, bool antialias)
where TPixel : struct, IPixel<TPixel>
{
Color color = TestUtils.GetColorByName(colorName).WithAlpha(alpha);
Pen pen = Pens.DashDotDot(color, thickness);
DrawLinesImpl(provider, colorName, alpha, thickness, antialias, pen);
}
private static void DrawLinesImpl<TPixel>(
TestImageProvider<TPixel> provider,
string colorName,
float alpha,
float thickness,
bool antialias,
Pen pen)
where TPixel : struct, IPixel<TPixel>
{
SixLabors.Primitives.PointF[] simplePath = { new Vector2(10, 10), new Vector2(200, 150), new Vector2(50, 300) };
GraphicsOptions options = new GraphicsOptions { Antialias = antialias };
string aa = antialias ? "" : "_NoAntialias";
FormattableString outputDetails = $"{colorName}_A({alpha})_T({thickness}){aa}";
provider.RunValidatingProcessorTest(
c => c.DrawLines(options, pen, simplePath),
outputDetails,
appendSourceFileOrDescription: false);
}
}
}

78
tests/ImageSharp.Tests/Drawing/DrawPathTests.cs

@ -1,78 +0,0 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Numerics;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing;
using SixLabors.Primitives;
using SixLabors.Shapes;
using Xunit;
namespace SixLabors.ImageSharp.Tests.Drawing
{
[GroupOutput("Drawing")]
public class DrawPathTests
{
public static readonly TheoryData<string, byte, float> DrawPathData = new TheoryData<string, byte, float>
{
{ "White", 255, 1.5f },
{ "Red", 255, 3 },
{ "HotPink", 255, 5 },
{ "HotPink", 150, 5 },
{ "White", 255, 15 },
};
[Theory]
[WithSolidFilledImages(nameof(DrawPathData), 300, 450, "Blue", PixelTypes.Rgba32)]
public void DrawPath<TPixel>(TestImageProvider<TPixel> provider, string colorName, byte alpha, float thickness)
where TPixel : struct, IPixel<TPixel>
{
var linearSegment = new LinearLineSegment(
new Vector2(10, 10),
new Vector2(200, 150),
new Vector2(50, 300));
var bezierSegment = new CubicBezierLineSegment(
new Vector2(50, 300),
new Vector2(500, 500),
new Vector2(60, 10),
new Vector2(10, 400));
var path = new Path(linearSegment, bezierSegment);
Rgba32 rgba = TestUtils.GetColorByName(colorName);
rgba.A = alpha;
Color color = rgba;
FormattableString testDetails = $"{colorName}_A{alpha}_T{thickness}";
provider.RunValidatingProcessorTest(
x => x.Draw(color, thickness, path),
testDetails,
appendPixelTypeToFileName: false,
appendSourceFileOrDescription: false);
}
[Theory]
[WithSolidFilledImages(256, 256, "Black", PixelTypes.Rgba32)]
public void PathExtendingOffEdgeOfImageShouldNotBeCropped<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : struct, IPixel<TPixel>
{
var color = Color.White;
Pen pen = Pens.Solid(color, 5f);
provider.RunValidatingProcessorTest(
x =>
{
for (int i = 0; i < 300; i += 20)
{
var points = new PointF[] { new Vector2(100, 2), new Vector2(-10, i) };
x.DrawLines(pen, points);
}
},
appendPixelTypeToFileName: false,
appendSourceFileOrDescription: false);
}
}
}

41
tests/ImageSharp.Tests/Drawing/DrawPolygonTests.cs

@ -1,41 +0,0 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Numerics;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing;
using Xunit;
namespace SixLabors.ImageSharp.Tests.Drawing
{
[GroupOutput("Drawing")]
public class DrawPolygonTests
{
[Theory]
[WithBasicTestPatternImages(250, 350, PixelTypes.Rgba32, "White", 1f, 2.5, true)]
[WithBasicTestPatternImages(250, 350, PixelTypes.Rgba32, "White", 0.6f, 10, true)]
[WithBasicTestPatternImages(250, 350, PixelTypes.Rgba32, "White", 1f, 5, false)]
[WithBasicTestPatternImages(250, 350, PixelTypes.Bgr24, "Yellow", 1f, 10, true)]
public void DrawPolygon<TPixel>(TestImageProvider<TPixel> provider, string colorName, float alpha, float thickness, bool antialias)
where TPixel : struct, IPixel<TPixel>
{
SixLabors.Primitives.PointF[] simplePath =
{
new Vector2(10, 10), new Vector2(200, 150), new Vector2(50, 300)
};
Color color = TestUtils.GetColorByName(colorName).WithAlpha(alpha);
GraphicsOptions options = new GraphicsOptions { Antialias = antialias };
string aa = antialias ? "" : "_NoAntialias";
FormattableString outputDetails = $"{colorName}_A({alpha})_T({thickness}){aa}";
provider.RunValidatingProcessorTest(
c => c.DrawPolygon(options, color, thickness, simplePath),
outputDetails,
appendSourceFileOrDescription: false);
}
}
}

61
tests/ImageSharp.Tests/Drawing/FillComplexPolygonTests.cs

@ -1,61 +0,0 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System.Numerics;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing;
using SixLabors.Shapes;
using Xunit;
namespace SixLabors.ImageSharp.Tests.Drawing
{
[GroupOutput("Drawing")]
public class FillComplexPolygonTests
{
[Theory]
[WithSolidFilledImages(300, 400, "Blue", PixelTypes.Rgba32, false, false)]
[WithSolidFilledImages(300, 400, "Blue", PixelTypes.Rgba32, true, false)]
[WithSolidFilledImages(300, 400, "Blue", PixelTypes.Rgba32, false, true)]
public void ComplexPolygon_SolidFill<TPixel>(TestImageProvider<TPixel> provider, bool overlap, bool transparent)
where TPixel :struct, IPixel<TPixel>
{
var simplePath = new Polygon(new LinearLineSegment(
new Vector2(10, 10),
new Vector2(200, 150),
new Vector2(50, 300)));
var hole1 = new Polygon(new LinearLineSegment(
new Vector2(37, 85),
overlap ? new Vector2(130, 40) : new Vector2(93, 85),
new Vector2(65, 137)));
IPath clipped = simplePath.Clip(hole1);
Rgba32 colorRgba = Rgba32.HotPink;
if (transparent)
{
colorRgba.A = 150;
}
Color color = colorRgba;
string testDetails = "";
if (overlap)
{
testDetails += "_Overlap";
}
if (transparent)
{
testDetails += "_Transparent";
}
provider.RunValidatingProcessorTest(
x => x.Fill(color, clipped),
testDetails,
appendPixelTypeToFileName: false,
appendSourceFileOrDescription: false);
}
}
}

147
tests/ImageSharp.Tests/Drawing/FillEllipticGradientBrushTest.cs

@ -1,147 +0,0 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing;
using Xunit;
namespace SixLabors.ImageSharp.Tests.Drawing
{
using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison;
[GroupOutput("Drawing/GradientBrushes")]
public class FillEllipticGradientBrushTests
{
public static ImageComparer TolerantComparer = ImageComparer.TolerantPercentage(0.01f);
[Theory]
[WithBlankImages(10, 10, PixelTypes.Rgba32)]
public void WithEqualColorsReturnsUnicolorImage<TPixel>(
TestImageProvider<TPixel> provider)
where TPixel : struct, IPixel<TPixel>
{
Color red = Color.Red;
using (Image<TPixel> image = provider.GetImage())
{
var unicolorLinearGradientBrush =
new EllipticGradientBrush(
new SixLabors.Primitives.Point(0, 0),
new SixLabors.Primitives.Point(10, 0),
1.0f,
GradientRepetitionMode.None,
new ColorStop(0, red),
new ColorStop(1, red));
image.Mutate(x => x.Fill(unicolorLinearGradientBrush));
image.DebugSave(provider, appendPixelTypeToFileName: false, appendSourceFileOrDescription: false);
// no need for reference image in this test:
image.ComparePixelBufferTo(red);
}
}
[Theory]
[WithBlankImages(200, 200, PixelTypes.Rgba32, 0.1)]
[WithBlankImages(200, 200, PixelTypes.Rgba32, 0.4)]
[WithBlankImages(200, 200, PixelTypes.Rgba32, 0.8)]
[WithBlankImages(200, 200, PixelTypes.Rgba32, 1.0)]
[WithBlankImages(200, 200, PixelTypes.Rgba32, 1.2)]
[WithBlankImages(200, 200, PixelTypes.Rgba32, 1.6)]
[WithBlankImages(200, 200, PixelTypes.Rgba32, 2.0)]
public void AxisParallelEllipsesWithDifferentRatio<TPixel>(
TestImageProvider<TPixel> provider,
float ratio)
where TPixel : struct, IPixel<TPixel>
{
Color yellow = Color.Yellow;
Color red = Color.Red;
Color black = Color.Black;
provider.VerifyOperation(
TolerantComparer,
image =>
{
var unicolorLinearGradientBrush = new EllipticGradientBrush(
new SixLabors.Primitives.Point(image.Width / 2, image.Height / 2),
new SixLabors.Primitives.Point(image.Width / 2, (image.Width * 2) / 3),
ratio,
GradientRepetitionMode.None,
new ColorStop(0, yellow),
new ColorStop(1, red),
new ColorStop(1, black));
image.Mutate(x => x.Fill(unicolorLinearGradientBrush));
},
$"{ratio:F2}",
false,
false);
}
[Theory]
[WithBlankImages(200, 200, PixelTypes.Rgba32, 0.1, 0)]
[WithBlankImages(200, 200, PixelTypes.Rgba32, 0.4, 0)]
[WithBlankImages(200, 200, PixelTypes.Rgba32, 0.8, 0)]
[WithBlankImages(200, 200, PixelTypes.Rgba32, 1.0, 0)]
[WithBlankImages(200, 200, PixelTypes.Rgba32, 0.1, 45)]
[WithBlankImages(200, 200, PixelTypes.Rgba32, 0.4, 45)]
[WithBlankImages(200, 200, PixelTypes.Rgba32, 0.8, 45)]
[WithBlankImages(200, 200, PixelTypes.Rgba32, 1.0, 45)]
[WithBlankImages(200, 200, PixelTypes.Rgba32, 0.1, 90)]
[WithBlankImages(200, 200, PixelTypes.Rgba32, 0.4, 90)]
[WithBlankImages(200, 200, PixelTypes.Rgba32, 0.8, 90)]
[WithBlankImages(200, 200, PixelTypes.Rgba32, 1.0, 90)]
[WithBlankImages(200, 200, PixelTypes.Rgba32, 0.1, 30)]
[WithBlankImages(200, 200, PixelTypes.Rgba32, 0.4, 30)]
[WithBlankImages(200, 200, PixelTypes.Rgba32, 0.8, 30)]
[WithBlankImages(200, 200, PixelTypes.Rgba32, 1.0, 30)]
public void RotatedEllipsesWithDifferentRatio<TPixel>(
TestImageProvider<TPixel> provider,
float ratio,
float rotationInDegree)
where TPixel: struct, IPixel<TPixel>
{
FormattableString variant = $"{ratio:F2}_AT_{rotationInDegree:00}deg";
provider.VerifyOperation(
TolerantComparer,
image =>
{
Color yellow = Color.Yellow;
Color red = Color.Red;
Color black = Color.Black;
var center = new SixLabors.Primitives.Point(image.Width / 2, image.Height / 2);
double rotation = (Math.PI * rotationInDegree) / 180.0;
double cos = Math.Cos(rotation);
double sin = Math.Sin(rotation);
int offsetY = image.Height / 6;
int axisX = center.X + (int)-(offsetY * sin);
int axisY = center.Y + (int)(offsetY * cos);
var unicolorLinearGradientBrush = new EllipticGradientBrush(
center,
new SixLabors.Primitives.Point(axisX, axisY),
ratio,
GradientRepetitionMode.None,
new ColorStop(0, yellow),
new ColorStop(1, red),
new ColorStop(1, black));
image.Mutate(x => x.Fill(unicolorLinearGradientBrush));
},
variant,
false,
false);
}
}
}

50
tests/ImageSharp.Tests/Drawing/FillImageBrushTests.cs

@ -1,50 +0,0 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing;
using SixLabors.Primitives;
using Xunit;
// ReSharper disable InconsistentNaming
namespace SixLabors.ImageSharp.Tests.Drawing
{
[GroupOutput("Drawing")]
public class FillImageBrushTests
{
[Fact]
public void DoesNotDisposeImage()
{
using (var src = new Image<Rgba32>(5, 5))
{
var brush = new ImageBrush(src);
using (var dest = new Image<Rgba32>(10, 10))
{
dest.Mutate(c => c.Fill(brush, new Rectangle(0, 0, 10, 10)));
dest.Mutate(c => c.Fill(brush, new Rectangle(0, 0, 10, 10)));
}
}
}
[Theory]
[WithTestPatternImages(200, 200, PixelTypes.Rgba32 | PixelTypes.Bgra32)]
public void UseBrushOfDifferentPixelType<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : struct, IPixel<TPixel>
{
byte[] data = TestFile.Create(TestImages.Png.Ducky).Bytes;
using (Image<TPixel> background = provider.GetImage())
using (Image overlay = provider.PixelType == PixelTypes.Rgba32
? (Image)Image.Load<Bgra32>(data)
: Image.Load<Rgba32>(data))
{
var brush = new ImageBrush(overlay);
background.Mutate(c => c.Fill(brush));
background.DebugSave(provider, appendSourceFileOrDescription : false);
background.CompareToReferenceOutput(provider, appendSourceFileOrDescription: false);
}
}
}
}

453
tests/ImageSharp.Tests/Drawing/FillLinearGradientBrushTests.cs

@ -1,453 +0,0 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Globalization;
using System.Linq;
using System.Text;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing;
using Xunit;
// ReSharper disable InconsistentNaming
namespace SixLabors.ImageSharp.Tests.Drawing
{
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison;
using SixLabors.Primitives;
using SixLabors.Shapes;
[GroupOutput("Drawing/GradientBrushes")]
public class FillLinearGradientBrushTests
{
public static ImageComparer TolerantComparer = ImageComparer.TolerantPercentage(0.01f);
[Theory]
[WithBlankImages(10, 10, PixelTypes.Rgba32)]
public void WithEqualColorsReturnsUnicolorImage<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : struct, IPixel<TPixel>
{
using (Image<TPixel> image = provider.GetImage())
{
Color red = Color.Red;
var unicolorLinearGradientBrush = new LinearGradientBrush(
new SixLabors.Primitives.Point(0, 0),
new SixLabors.Primitives.Point(10, 0),
GradientRepetitionMode.None,
new ColorStop(0, red),
new ColorStop(1, red));
image.Mutate(x => x.Fill(unicolorLinearGradientBrush));
image.DebugSave(provider, appendPixelTypeToFileName: false, appendSourceFileOrDescription: false);
// no need for reference image in this test:
image.ComparePixelBufferTo(red);
}
}
[Theory]
[WithBlankImages(20, 10, PixelTypes.Rgba32)]
[WithBlankImages(20, 10, PixelTypes.Argb32)]
[WithBlankImages(20, 10, PixelTypes.Rgb24)]
public void DoesNotDependOnSinglePixelType<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : struct, IPixel<TPixel>
{
provider.VerifyOperation(
TolerantComparer,
image =>
{
var unicolorLinearGradientBrush = new LinearGradientBrush(
new SixLabors.Primitives.Point(0, 0),
new SixLabors.Primitives.Point(image.Width, 0),
GradientRepetitionMode.None,
new ColorStop(0, Color.Blue),
new ColorStop(1, Color.Yellow));
image.Mutate(x => x.Fill(unicolorLinearGradientBrush));
},
appendSourceFileOrDescription: false);
}
[Theory]
[WithBlankImages(500, 10, PixelTypes.Rgba32)]
public void HorizontalReturnsUnicolorColumns<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : struct, IPixel<TPixel>
{
provider.VerifyOperation(
TolerantComparer,
image =>
{
Color red = Color.Red;
Color yellow = Color.Yellow;
var unicolorLinearGradientBrush = new LinearGradientBrush(
new SixLabors.Primitives.Point(0, 0),
new SixLabors.Primitives.Point(image.Width, 0),
GradientRepetitionMode.None,
new ColorStop(0, red),
new ColorStop(1, yellow));
image.Mutate(x => x.Fill(unicolorLinearGradientBrush));
},
false,
false);
}
[Theory]
[WithBlankImages(500, 10, PixelTypes.Rgba32, GradientRepetitionMode.DontFill)]
[WithBlankImages(500, 10, PixelTypes.Rgba32, GradientRepetitionMode.None)]
[WithBlankImages(500, 10, PixelTypes.Rgba32, GradientRepetitionMode.Repeat)]
[WithBlankImages(500, 10, PixelTypes.Rgba32, GradientRepetitionMode.Reflect)]
public void HorizontalGradientWithRepMode<TPixel>(
TestImageProvider<TPixel> provider,
GradientRepetitionMode repetitionMode)
where TPixel : struct, IPixel<TPixel>
{
provider.VerifyOperation(
TolerantComparer,
image =>
{
Color red = Color.Red;
Color yellow = Color.Yellow;
var unicolorLinearGradientBrush = new LinearGradientBrush(
new SixLabors.Primitives.Point(0, 0),
new SixLabors.Primitives.Point(image.Width / 10, 0),
repetitionMode,
new ColorStop(0, red),
new ColorStop(1, yellow));
image.Mutate(x => x.Fill(unicolorLinearGradientBrush));
},
$"{repetitionMode}",
false,
false);
}
[Theory]
[WithBlankImages(200, 100, PixelTypes.Rgba32, new[] { 0.5f })]
[WithBlankImages(200, 100, PixelTypes.Rgba32, new[] { 0.2f, 0.4f, 0.6f, 0.8f })]
[WithBlankImages(200, 100, PixelTypes.Rgba32, new[] { 0.1f, 0.3f, 0.6f })]
public void WithDoubledStopsProduceDashedPatterns<TPixel>(
TestImageProvider<TPixel> provider,
float[] pattern)
where TPixel : struct, IPixel<TPixel>
{
string variant = string.Join("_", pattern.Select(i => i.ToString(CultureInfo.InvariantCulture)));
// ensure the input data is valid
Assert.True(pattern.Length > 0);
Color black = Color.Black;
Color white = Color.White;
// create the input pattern: 0, followed by each of the arguments twice, followed by 1.0 - toggling black and white.
ColorStop[] colorStops =
Enumerable.Repeat(new ColorStop(0, black), 1)
.Concat(
pattern
.SelectMany((f, index) => new[]
{
new ColorStop(f, index % 2 == 0 ? black : white),
new ColorStop(f, index % 2 == 0 ? white : black)
}))
.Concat(Enumerable.Repeat(new ColorStop(1, pattern.Length % 2 == 0 ? black : white), 1))
.ToArray();
using (Image<TPixel> image = provider.GetImage())
{
var unicolorLinearGradientBrush =
new LinearGradientBrush(
new SixLabors.Primitives.Point(0, 0),
new SixLabors.Primitives.Point(image.Width, 0),
GradientRepetitionMode.None,
colorStops);
image.Mutate(x => x.Fill(unicolorLinearGradientBrush));
image.DebugSave(
provider,
variant,
appendPixelTypeToFileName: false,
appendSourceFileOrDescription: false);
// the result must be a black and white pattern, no other color should occur:
Assert.All(
Enumerable.Range(0, image.Width).Select(i => image[i, 0]),
color => Assert.True(
color.Equals(black.ToPixel<TPixel>()) || color.Equals(white.ToPixel<TPixel>())));
image.CompareToReferenceOutput(
TolerantComparer,
provider,
variant,
appendPixelTypeToFileName: false,
appendSourceFileOrDescription: false);
}
}
[Theory]
[WithBlankImages(10, 500, PixelTypes.Rgba32)]
public void VerticalBrushReturnsUnicolorRows<TPixel>(
TestImageProvider<TPixel> provider)
where TPixel : struct, IPixel<TPixel>
{
provider.VerifyOperation(
image =>
{
Color red = Color.Red;
Color yellow = Color.Yellow;
var unicolorLinearGradientBrush = new LinearGradientBrush(
new SixLabors.Primitives.Point(0, 0),
new SixLabors.Primitives.Point(0, image.Height),
GradientRepetitionMode.None,
new ColorStop(0, red),
new ColorStop(1, yellow));
image.Mutate(x => x.Fill(unicolorLinearGradientBrush));
VerifyAllRowsAreUnicolor(image);
},
false,
false);
void VerifyAllRowsAreUnicolor(Image<TPixel> image)
{
for (int y = 0; y < image.Height; y++)
{
Span<TPixel> row = image.GetPixelRowSpan(y);
TPixel firstColorOfRow = row[0];
foreach (TPixel p in row)
{
Assert.Equal(firstColorOfRow, p);
}
}
}
}
public enum ImageCorner
{
TopLeft = 0,
TopRight = 1,
BottomLeft = 2,
BottomRight = 3
}
[Theory]
[WithBlankImages(200, 200, PixelTypes.Rgba32, ImageCorner.TopLeft)]
[WithBlankImages(200, 200, PixelTypes.Rgba32, ImageCorner.TopRight)]
[WithBlankImages(200, 200, PixelTypes.Rgba32, ImageCorner.BottomLeft)]
[WithBlankImages(200, 200, PixelTypes.Rgba32, ImageCorner.BottomRight)]
public void DiagonalReturnsCorrectImages<TPixel>(
TestImageProvider<TPixel> provider,
ImageCorner startCorner)
where TPixel : struct, IPixel<TPixel>
{
using (Image<TPixel> image = provider.GetImage())
{
Assert.True(image.Height == image.Width, "For the math check block at the end the image must be squared, but it is not.");
int startX = (int)startCorner % 2 == 0 ? 0 : image.Width - 1;
int startY = startCorner > ImageCorner.TopRight ? 0 : image.Height - 1;
int endX = image.Height - startX - 1;
int endY = image.Width - startY - 1;
Color red = Color.Red;
Color yellow = Color.Yellow;
var unicolorLinearGradientBrush =
new LinearGradientBrush(
new SixLabors.Primitives.Point(startX, startY),
new SixLabors.Primitives.Point(endX, endY),
GradientRepetitionMode.None,
new ColorStop(0, red),
new ColorStop(1, yellow));
image.Mutate(x => x.Fill(unicolorLinearGradientBrush));
image.DebugSave(
provider,
startCorner,
appendPixelTypeToFileName: false,
appendSourceFileOrDescription: false);
int verticalSign = startY == 0 ? 1 : -1;
int horizontalSign = startX == 0 ? 1 : -1;
for (int i = 0; i < image.Height; i++)
{
// it's diagonal, so for any (a, a) on the gradient line, for all (a-x, b+x) - +/- depending on the diagonal direction - must be the same color)
TPixel colorOnDiagonal = image[i, i];
// TODO: This is incorrect. from -0 to < 0 ??
int orthoCount = 0;
for (int offset = -orthoCount; offset < orthoCount; offset++)
{
Assert.Equal(colorOnDiagonal, image[i + (horizontalSign * offset), i + (verticalSign * offset)]);
}
}
image.CompareToReferenceOutput(
TolerantComparer,
provider,
startCorner,
appendPixelTypeToFileName: false,
appendSourceFileOrDescription: false);
}
}
[Theory]
[WithBlankImages(500, 500, PixelTypes.Rgba32, 0, 0, 499, 499, new[] { 0f, .2f, .5f, .9f }, new[] { 0, 0, 1, 1 })]
[WithBlankImages(500, 500, PixelTypes.Rgba32, 0, 499, 499, 0, new[] { 0f, 0.2f, 0.5f, 0.9f }, new[] { 0, 1, 2, 3 })]
[WithBlankImages(500, 500, PixelTypes.Rgba32, 499, 499, 0, 0, new[] { 0f, 0.7f, 0.8f, 0.9f }, new[] { 0, 1, 2, 0 })]
[WithBlankImages(500, 500, PixelTypes.Rgba32, 0, 0, 499, 499, new[] { 0f, .5f, 1f }, new[] { 0, 1, 3 })]
public void ArbitraryGradients<TPixel>(
TestImageProvider<TPixel> provider,
int startX, int startY,
int endX, int endY,
float[] stopPositions,
int[] stopColorCodes)
where TPixel : struct, IPixel<TPixel>
{
Color[] colors =
{
Color.Navy, Color.LightGreen, Color.Yellow,
Color.Red
};
var coloringVariant = new StringBuilder();
var colorStops = new ColorStop[stopPositions.Length];
for (int i = 0; i < stopPositions.Length; i++)
{
Color color = colors[stopColorCodes[i % colors.Length]];
float position = stopPositions[i];
colorStops[i] = new ColorStop(position, color);
Rgba32 rgba = color;
coloringVariant.AppendFormat(CultureInfo.InvariantCulture, "{0}@{1};", rgba.ToHex(), position);
}
FormattableString variant = $"({startX},{startY})_TO_({endX},{endY})__[{coloringVariant}]";
provider.VerifyOperation(
image =>
{
var unicolorLinearGradientBrush = new LinearGradientBrush(
new SixLabors.Primitives.Point(startX, startY),
new SixLabors.Primitives.Point(endX, endY),
GradientRepetitionMode.None,
colorStops);
image.Mutate(x => x.Fill(unicolorLinearGradientBrush));
},
variant,
false,
false);
}
[Theory]
[WithBlankImages(200, 200, PixelTypes.Rgba32, 0, 0, 199, 199, new[] { 0f, .25f, .5f, .75f, 1f }, new[] { 0, 1, 2, 3, 4 })]
public void MultiplePointGradients<TPixel>(
TestImageProvider<TPixel> provider,
int startX, int startY,
int endX, int endY,
float[] stopPositions,
int[] stopColorCodes)
where TPixel : struct, IPixel<TPixel>
{
Color[] colors =
{
Color.Black, Color.Blue, Color.Red,
Color.White, Color.Lime
};
var coloringVariant = new StringBuilder();
var colorStops = new ColorStop[stopPositions.Length];
for (int i = 0; i < stopPositions.Length; i++)
{
Color color = colors[stopColorCodes[i % colors.Length]];
float position = stopPositions[i];
colorStops[i] = new ColorStop(position, color);
Rgba32 rgba = color;
coloringVariant.AppendFormat(CultureInfo.InvariantCulture, "{0}@{1};", rgba.ToHex(), position);
}
FormattableString variant = $"({startX},{startY})_TO_({endX},{endY})__[{coloringVariant}]";
provider.VerifyOperation(
image =>
{
var unicolorLinearGradientBrush = new LinearGradientBrush(
new SixLabors.Primitives.Point(startX, startY),
new SixLabors.Primitives.Point(endX, endY),
GradientRepetitionMode.None,
colorStops);
image.Mutate(x => x.Fill(unicolorLinearGradientBrush));
},
variant,
false,
false);
}
[Theory]
[WithBlankImages(200, 200, PixelTypes.Rgba32)]
public void GradientsWithTransparencyOnExistingBackground<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : struct, IPixel<TPixel>
{
provider.VerifyOperation(
image =>
{
image.Mutate(i => i.Fill(Color.Red));
image.Mutate(ApplyGloss);
});
void ApplyGloss(IImageProcessingContext ctx)
{
Size size = ctx.GetCurrentSize();
IPathCollection glossPath = BuildGloss(size.Width, size.Height);
var graphicsOptions = new GraphicsOptions
{
Antialias = true,
ColorBlendingMode = PixelColorBlendingMode.Normal,
AlphaCompositionMode = PixelAlphaCompositionMode.SrcAtop
};
var linearGradientBrush = new LinearGradientBrush(new Point(0, 0), new Point(0, size.Height / 2), GradientRepetitionMode.Repeat, new ColorStop(0, Color.White.WithAlpha(0.5f)), new ColorStop(1, Color.White.WithAlpha(0.25f)));
ctx.Fill(graphicsOptions, linearGradientBrush, glossPath);
}
IPathCollection BuildGloss(int imageWidth, int imageHeight)
{
var pathBuilder = new PathBuilder();
pathBuilder.AddLine(new PointF(0, 0), new PointF(imageWidth, 0));
pathBuilder.AddLine(new PointF(imageWidth, 0), new PointF(imageWidth, imageHeight * 0.4f));
pathBuilder.AddBezier(new PointF(imageWidth, imageHeight * 0.4f), new PointF(imageWidth / 2, imageHeight * 0.6f), new PointF(0, imageHeight * 0.4f));
pathBuilder.CloseFigure();
return new PathCollection(pathBuilder.Build());
}
}
[Theory]
[WithBlankImages(200, 200, PixelTypes.Rgb24)]
public void BrushApplicatorIsThreadSafeIssue1044<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : struct, IPixel<TPixel>
{
provider.VerifyOperation(
TolerantComparer,
img =>
{
var brush = new PathGradientBrush(
new[] { new PointF(0, 0), new PointF(200, 0), new PointF(200, 200), new PointF(0, 200), new PointF(0, 0) },
new[] { Color.Red, Color.Yellow, Color.Green, Color.DarkCyan, Color.Red });
img.Mutate(m => m.Fill(brush));
}, false, false);
}
}
}

157
tests/ImageSharp.Tests/Drawing/FillPathGradientBrushTests.cs

@ -1,157 +0,0 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing;
using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison;
using SixLabors.Primitives;
using Xunit;
namespace SixLabors.ImageSharp.Tests.Drawing
{
[GroupOutput("Drawing/GradientBrushes")]
public class FillPathGradientBrushTests
{
public static ImageComparer TolerantComparer = ImageComparer.TolerantPercentage(0.01f);
[Theory]
[WithBlankImages(10, 10, PixelTypes.Rgba32)]
public void FillRectangleWithDifferentColors<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : struct, IPixel<TPixel>
{
provider.VerifyOperation(
TolerantComparer,
image =>
{
PointF[] points = { new PointF(0, 0), new PointF(10, 0), new PointF(10, 10), new PointF(0, 10) };
Color[] colors = { Color.Black, Color.Red, Color.Yellow, Color.Green };
var brush = new PathGradientBrush(points, colors);
image.Mutate(x => x.Fill(brush));
image.DebugSave(provider, appendPixelTypeToFileName: false, appendSourceFileOrDescription: false);
});
}
[Theory]
[WithBlankImages(10, 10, PixelTypes.Rgba32)]
public void FillTriangleWithDifferentColors<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : struct, IPixel<TPixel>
{
provider.VerifyOperation(
TolerantComparer,
image =>
{
PointF[] points = { new PointF(5, 0), new PointF(10, 10), new PointF(0, 10) };
Color[] colors = { Color.Red, Color.Green, Color.Blue };
var brush = new PathGradientBrush(points, colors);
image.Mutate(x => x.Fill(brush));
image.DebugSave(provider, appendPixelTypeToFileName: false, appendSourceFileOrDescription: false);
});
}
[Theory]
[WithBlankImages(10, 10, PixelTypes.Rgba32)]
public void FillRectangleWithSingleColor<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : struct, IPixel<TPixel>
{
using (Image<TPixel> image = provider.GetImage())
{
PointF[] points = { new PointF(0, 0), new PointF(10, 0), new PointF(10, 10), new PointF(0, 10) };
Color[] colors = { Color.Red };
var brush = new PathGradientBrush(points, colors);
image.Mutate(x => x.Fill(brush));
image.ComparePixelBufferTo(Color.Red);
}
}
[Theory]
[WithBlankImages(10, 10, PixelTypes.Rgba32)]
public void ShouldRotateTheColorsWhenThereAreMorePoints<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : struct, IPixel<TPixel>
{
provider.VerifyOperation(
TolerantComparer,
image =>
{
PointF[] points = { new PointF(0, 0), new PointF(10, 0), new PointF(10, 10), new PointF(0, 10) };
Color[] colors = { Color.Red, Color.Yellow };
var brush = new PathGradientBrush(points, colors);
image.Mutate(x => x.Fill(brush));
image.DebugSave(provider, appendPixelTypeToFileName: false, appendSourceFileOrDescription: false);
});
}
[Theory]
[WithBlankImages(10, 10, PixelTypes.Rgba32)]
public void FillWithCustomCenterColor<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : struct, IPixel<TPixel>
{
provider.VerifyOperation(
TolerantComparer,
image =>
{
PointF[] points = { new PointF(0, 0), new PointF(10, 0), new PointF(10, 10), new PointF(0, 10) };
Color[] colors = { Color.Black, Color.Red, Color.Yellow, Color.Green };
var brush = new PathGradientBrush(points, colors, Color.White);
image.Mutate(x => x.Fill(brush));
image.DebugSave(provider, appendPixelTypeToFileName: false, appendSourceFileOrDescription: false);
});
}
[Fact]
public void ShouldThrowArgumentNullExceptionWhenLinesAreNull()
{
Color[] colors = { Color.Black, Color.Red, Color.Yellow, Color.Green };
PathGradientBrush Create() => new PathGradientBrush(null, colors, Color.White);
Assert.Throws<ArgumentNullException>(Create);
}
[Fact]
public void ShouldThrowArgumentOutOfRangeExceptionWhenLessThan3PointsAreGiven()
{
PointF[] points = { new PointF(0, 0), new PointF(10, 0) };
Color[] colors = { Color.Black, Color.Red, Color.Yellow, Color.Green };
PathGradientBrush Create() => new PathGradientBrush(points, colors, Color.White);
Assert.Throws<ArgumentOutOfRangeException>(Create);
}
[Fact]
public void ShouldThrowArgumentNullExceptionWhenColorsAreNull()
{
PointF[] points = { new PointF(0, 0), new PointF(10, 0), new PointF(10, 10), new PointF(0, 10) };
PathGradientBrush Create() => new PathGradientBrush(points, null, Color.White);
Assert.Throws<ArgumentNullException>(Create);
}
[Fact]
public void ShouldThrowArgumentOutOfRangeExceptionWhenEmptyColorArrayIsGiven()
{
PointF[] points = { new PointF(0, 0), new PointF(10, 0), new PointF(10, 10), new PointF(0, 10) };
var colors = new Color[0];
PathGradientBrush Create() => new PathGradientBrush(points, colors, Color.White);
Assert.Throws<ArgumentOutOfRangeException>(Create);
}
}
}

278
tests/ImageSharp.Tests/Drawing/FillPatternBrushTests.cs

@ -1,278 +0,0 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Primitives;
using SixLabors.ImageSharp.Processing;
using Xunit;
namespace SixLabors.ImageSharp.Tests.Drawing
{
public class FillPatternBrushTests
{
private void Test(string name, Rgba32 background, IBrush brush, Rgba32[,] expectedPattern)
{
string path = TestEnvironment.CreateOutputDirectory("Drawing", "FillPatternBrushTests");
using (var image = new Image<Rgba32>(20, 20))
{
image.Mutate(x => x.Fill(background).Fill(brush));
image.Save($"{path}/{name}.png");
Buffer2D<Rgba32> sourcePixels = image.GetRootFramePixelBuffer();
// lets pick random spots to start checking
var r = new Random();
var expectedPatternFast = new DenseMatrix<Rgba32>(expectedPattern);
int xStride = expectedPatternFast.Columns;
int yStride = expectedPatternFast.Rows;
int offsetX = r.Next(image.Width / xStride) * xStride;
int offsetY = r.Next(image.Height / yStride) * yStride;
for (int x = 0; x < xStride; x++)
{
for (int y = 0; y < yStride; y++)
{
int actualX = x + offsetX;
int actualY = y + offsetY;
Rgba32 expected = expectedPatternFast[y, x]; // inverted pattern
Rgba32 actual = sourcePixels[actualX, actualY];
if (expected != actual)
{
Assert.True(false, $"Expected {expected} but found {actual} at ({actualX},{actualY})");
}
}
}
image.Mutate(x => x.Resize(80, 80, KnownResamplers.NearestNeighbor));
image.Save($"{path}/{name}x4.png");
}
}
[Fact]
public void ImageShouldBeFloodFilledWithPercent10()
{
this.Test(
"Percent10",
Rgba32.Blue,
Brushes.Percent10(Rgba32.HotPink, Rgba32.LimeGreen),
new[,]
{
{ Rgba32.HotPink, Rgba32.LimeGreen, Rgba32.LimeGreen, Rgba32.LimeGreen },
{ Rgba32.LimeGreen, Rgba32.LimeGreen, Rgba32.LimeGreen, Rgba32.LimeGreen },
{ Rgba32.LimeGreen, Rgba32.LimeGreen, Rgba32.HotPink, Rgba32.LimeGreen },
{ Rgba32.LimeGreen, Rgba32.LimeGreen, Rgba32.LimeGreen, Rgba32.LimeGreen }
});
}
[Fact]
public void ImageShouldBeFloodFilledWithPercent10Transparent()
{
this.Test(
"Percent10_Transparent",
Rgba32.Blue,
Brushes.Percent10(Rgba32.HotPink),
new Rgba32[,]
{
{ Rgba32.HotPink, Rgba32.Blue, Rgba32.Blue, Rgba32.Blue },
{ Rgba32.Blue, Rgba32.Blue, Rgba32.Blue, Rgba32.Blue },
{ Rgba32.Blue, Rgba32.Blue, Rgba32.HotPink, Rgba32.Blue },
{ Rgba32.Blue, Rgba32.Blue, Rgba32.Blue, Rgba32.Blue }
});
}
[Fact]
public void ImageShouldBeFloodFilledWithPercent20()
{
this.Test(
"Percent20",
Rgba32.Blue,
Brushes.Percent20(Rgba32.HotPink, Rgba32.LimeGreen),
new Rgba32[,]
{
{ Rgba32.HotPink, Rgba32.LimeGreen, Rgba32.LimeGreen, Rgba32.LimeGreen },
{ Rgba32.LimeGreen, Rgba32.LimeGreen, Rgba32.HotPink, Rgba32.LimeGreen },
{ Rgba32.HotPink, Rgba32.LimeGreen, Rgba32.LimeGreen, Rgba32.LimeGreen },
{ Rgba32.LimeGreen, Rgba32.LimeGreen, Rgba32.HotPink, Rgba32.LimeGreen }
});
}
[Fact]
public void ImageShouldBeFloodFilledWithPercent20_transparent()
{
this.Test(
"Percent20_Transparent",
Rgba32.Blue,
Brushes.Percent20(Rgba32.HotPink),
new Rgba32[,]
{
{ Rgba32.HotPink, Rgba32.Blue, Rgba32.Blue, Rgba32.Blue },
{ Rgba32.Blue, Rgba32.Blue, Rgba32.HotPink, Rgba32.Blue },
{ Rgba32.HotPink, Rgba32.Blue, Rgba32.Blue, Rgba32.Blue },
{ Rgba32.Blue, Rgba32.Blue, Rgba32.HotPink, Rgba32.Blue }
});
}
[Fact]
public void ImageShouldBeFloodFilledWithHorizontal()
{
this.Test(
"Horizontal",
Rgba32.Blue,
Brushes.Horizontal(Rgba32.HotPink, Rgba32.LimeGreen),
new Rgba32[,]
{
{ Rgba32.LimeGreen, Rgba32.LimeGreen, Rgba32.LimeGreen, Rgba32.LimeGreen },
{ Rgba32.HotPink, Rgba32.HotPink, Rgba32.HotPink, Rgba32.HotPink },
{ Rgba32.LimeGreen, Rgba32.LimeGreen, Rgba32.LimeGreen, Rgba32.LimeGreen },
{ Rgba32.LimeGreen, Rgba32.LimeGreen, Rgba32.LimeGreen, Rgba32.LimeGreen }
});
}
[Fact]
public void ImageShouldBeFloodFilledWithHorizontal_transparent()
{
this.Test(
"Horizontal_Transparent",
Rgba32.Blue,
Brushes.Horizontal(Rgba32.HotPink),
new Rgba32[,]
{
{ Rgba32.Blue, Rgba32.Blue, Rgba32.Blue, Rgba32.Blue },
{ Rgba32.HotPink, Rgba32.HotPink, Rgba32.HotPink, Rgba32.HotPink },
{ Rgba32.Blue, Rgba32.Blue, Rgba32.Blue, Rgba32.Blue },
{ Rgba32.Blue, Rgba32.Blue, Rgba32.Blue, Rgba32.Blue }
});
}
[Fact]
public void ImageShouldBeFloodFilledWithMin()
{
this.Test(
"Min",
Rgba32.Blue,
Brushes.Min(Rgba32.HotPink, Rgba32.LimeGreen),
new Rgba32[,]
{
{ Rgba32.LimeGreen, Rgba32.LimeGreen, Rgba32.LimeGreen, Rgba32.LimeGreen },
{ Rgba32.LimeGreen, Rgba32.LimeGreen, Rgba32.LimeGreen, Rgba32.LimeGreen },
{ Rgba32.LimeGreen, Rgba32.LimeGreen, Rgba32.LimeGreen, Rgba32.LimeGreen },
{ Rgba32.HotPink, Rgba32.HotPink, Rgba32.HotPink, Rgba32.HotPink }
});
}
[Fact]
public void ImageShouldBeFloodFilledWithMin_transparent()
{
this.Test(
"Min_Transparent",
Rgba32.Blue,
Brushes.Min(Rgba32.HotPink),
new Rgba32[,]
{
{ Rgba32.Blue, Rgba32.Blue, Rgba32.Blue, Rgba32.Blue },
{ Rgba32.Blue, Rgba32.Blue, Rgba32.Blue, Rgba32.Blue },
{ Rgba32.Blue, Rgba32.Blue, Rgba32.Blue, Rgba32.Blue },
{ Rgba32.HotPink, Rgba32.HotPink, Rgba32.HotPink, Rgba32.HotPink },
});
}
[Fact]
public void ImageShouldBeFloodFilledWithVertical()
{
this.Test(
"Vertical",
Rgba32.Blue,
Brushes.Vertical(Rgba32.HotPink, Rgba32.LimeGreen),
new Rgba32[,]
{
{ Rgba32.LimeGreen, Rgba32.HotPink, Rgba32.LimeGreen, Rgba32.LimeGreen },
{ Rgba32.LimeGreen, Rgba32.HotPink, Rgba32.LimeGreen, Rgba32.LimeGreen },
{ Rgba32.LimeGreen, Rgba32.HotPink, Rgba32.LimeGreen, Rgba32.LimeGreen },
{ Rgba32.LimeGreen, Rgba32.HotPink, Rgba32.LimeGreen, Rgba32.LimeGreen }
});
}
[Fact]
public void ImageShouldBeFloodFilledWithVertical_transparent()
{
this.Test(
"Vertical_Transparent",
Rgba32.Blue,
Brushes.Vertical(Rgba32.HotPink),
new Rgba32[,]
{
{ Rgba32.Blue, Rgba32.HotPink, Rgba32.Blue, Rgba32.Blue },
{ Rgba32.Blue, Rgba32.HotPink, Rgba32.Blue, Rgba32.Blue },
{ Rgba32.Blue, Rgba32.HotPink, Rgba32.Blue, Rgba32.Blue },
{ Rgba32.Blue, Rgba32.HotPink, Rgba32.Blue, Rgba32.Blue }
});
}
[Fact]
public void ImageShouldBeFloodFilledWithForwardDiagonal()
{
this.Test(
"ForwardDiagonal",
Rgba32.Blue,
Brushes.ForwardDiagonal(Rgba32.HotPink, Rgba32.LimeGreen),
new Rgba32[,]
{
{ Rgba32.LimeGreen, Rgba32.LimeGreen, Rgba32.LimeGreen, Rgba32.HotPink },
{ Rgba32.LimeGreen, Rgba32.LimeGreen, Rgba32.HotPink, Rgba32.LimeGreen },
{ Rgba32.LimeGreen, Rgba32.HotPink, Rgba32.LimeGreen, Rgba32.LimeGreen },
{ Rgba32.HotPink, Rgba32.LimeGreen, Rgba32.LimeGreen, Rgba32.LimeGreen }
});
}
[Fact]
public void ImageShouldBeFloodFilledWithForwardDiagonal_transparent()
{
this.Test(
"ForwardDiagonal_Transparent",
Rgba32.Blue,
Brushes.ForwardDiagonal(Rgba32.HotPink),
new Rgba32[,]
{
{ Rgba32.Blue, Rgba32.Blue, Rgba32.Blue, Rgba32.HotPink },
{ Rgba32.Blue, Rgba32.Blue, Rgba32.HotPink, Rgba32.Blue },
{ Rgba32.Blue, Rgba32.HotPink, Rgba32.Blue, Rgba32.Blue },
{ Rgba32.HotPink, Rgba32.Blue, Rgba32.Blue, Rgba32.Blue }
});
}
[Fact]
public void ImageShouldBeFloodFilledWithBackwardDiagonal()
{
this.Test(
"BackwardDiagonal",
Rgba32.Blue,
Brushes.BackwardDiagonal(Rgba32.HotPink, Rgba32.LimeGreen),
new Rgba32[,]
{
{ Rgba32.HotPink, Rgba32.LimeGreen, Rgba32.LimeGreen, Rgba32.LimeGreen },
{ Rgba32.LimeGreen, Rgba32.HotPink, Rgba32.LimeGreen, Rgba32.LimeGreen },
{ Rgba32.LimeGreen, Rgba32.LimeGreen, Rgba32.HotPink, Rgba32.LimeGreen },
{ Rgba32.LimeGreen, Rgba32.LimeGreen, Rgba32.LimeGreen, Rgba32.HotPink }
});
}
[Fact]
public void ImageShouldBeFloodFilledWithBackwardDiagonal_transparent()
{
this.Test(
"BackwardDiagonal_Transparent",
Rgba32.Blue,
Brushes.BackwardDiagonal(Rgba32.HotPink),
new Rgba32[,]
{
{ Rgba32.HotPink, Rgba32.Blue, Rgba32.Blue, Rgba32.Blue },
{ Rgba32.Blue, Rgba32.HotPink, Rgba32.Blue, Rgba32.Blue },
{ Rgba32.Blue, Rgba32.Blue, Rgba32.HotPink, Rgba32.Blue },
{ Rgba32.Blue, Rgba32.Blue, Rgba32.Blue, Rgba32.HotPink }
});
}
}
}

154
tests/ImageSharp.Tests/Drawing/FillPolygonTests.cs

@ -1,154 +0,0 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Numerics;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing;
using SixLabors.Shapes;
using Xunit;
namespace SixLabors.ImageSharp.Tests.Drawing
{
[GroupOutput("Drawing")]
public class FillPolygonTests
{
[Theory]
[WithBasicTestPatternImages(250, 350, PixelTypes.Rgba32, "White", 1f, true)]
[WithBasicTestPatternImages(250, 350, PixelTypes.Rgba32, "White", 0.6f, true)]
[WithBasicTestPatternImages(250, 350, PixelTypes.Rgba32, "White", 1f, false)]
[WithBasicTestPatternImages(250, 350, PixelTypes.Bgr24, "Yellow", 1f, true)]
public void FillPolygon_Solid<TPixel>(TestImageProvider<TPixel> provider, string colorName, float alpha, bool antialias)
where TPixel : struct, IPixel<TPixel>
{
SixLabors.Primitives.PointF[] simplePath =
{
new Vector2(10, 10), new Vector2(200, 150), new Vector2(50, 300)
};
Color color = TestUtils.GetColorByName(colorName).WithAlpha(alpha);
var options = new GraphicsOptions { Antialias = antialias };
string aa = antialias ? "" : "_NoAntialias";
FormattableString outputDetails = $"{colorName}_A{alpha}{aa}";
provider.RunValidatingProcessorTest(
c => c.FillPolygon(options, color, simplePath),
outputDetails,
appendSourceFileOrDescription: false);
}
[Theory]
[WithBasicTestPatternImages(200, 200, PixelTypes.Rgba32)]
public void FillPolygon_Concave<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : struct, IPixel<TPixel>
{
var points = new SixLabors.Primitives.PointF[]
{
new Vector2(8, 8),
new Vector2(64, 8),
new Vector2(64, 64),
new Vector2(120, 64),
new Vector2(120, 120),
new Vector2(8, 120)
};
var color = Color.LightGreen;
provider.RunValidatingProcessorTest(
c => c.FillPolygon(color, points),
appendSourceFileOrDescription: false,
appendPixelTypeToFileName: false);
}
[Theory]
[WithBasicTestPatternImages(250, 350, PixelTypes.Rgba32)]
public void FillPolygon_Pattern<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : struct, IPixel<TPixel>
{
SixLabors.Primitives.PointF[] simplePath =
{
new Vector2(10, 10), new Vector2(200, 150), new Vector2(50, 300)
};
var color = Color.Yellow;
var brush = Brushes.Horizontal(color);
provider.RunValidatingProcessorTest(
c => c.FillPolygon(brush, simplePath),
appendSourceFileOrDescription: false);
}
[Theory]
[WithBasicTestPatternImages(250, 350, PixelTypes.Rgba32, TestImages.Png.Ducky)]
[WithBasicTestPatternImages(250, 350, PixelTypes.Rgba32, TestImages.Bmp.Car)]
public void FillPolygon_ImageBrush<TPixel>(TestImageProvider<TPixel> provider, string brushImageName)
where TPixel : struct, IPixel<TPixel>
{
SixLabors.Primitives.PointF[] simplePath =
{
new Vector2(10, 10), new Vector2(200, 50), new Vector2(50, 200)
};
using (Image<TPixel> brushImage = Image.Load<TPixel>(TestFile.Create(brushImageName).Bytes))
{
var brush = new ImageBrush(brushImage);
provider.RunValidatingProcessorTest(
c => c.FillPolygon(brush, simplePath),
System.IO.Path.GetFileNameWithoutExtension(brushImageName),
appendSourceFileOrDescription: false);
}
}
[Theory]
[WithBasicTestPatternImages(250, 250, PixelTypes.Rgba32)]
public void Fill_RectangularPolygon<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : struct, IPixel<TPixel>
{
var polygon = new SixLabors.Shapes.RectangularPolygon(10, 10, 190, 140);
var color = Color.White;
provider.RunValidatingProcessorTest(
c => c.Fill(color, polygon),
appendSourceFileOrDescription: false);
}
[Theory]
[WithBasicTestPatternImages(200, 200, PixelTypes.Rgba32, 3, 50, 0f)]
[WithBasicTestPatternImages(200, 200, PixelTypes.Rgba32, 3, 60, 20f)]
[WithBasicTestPatternImages(200, 200, PixelTypes.Rgba32, 3, 60, -180f)]
[WithBasicTestPatternImages(200, 200, PixelTypes.Rgba32, 5, 70, 0f)]
[WithBasicTestPatternImages(200, 200, PixelTypes.Rgba32, 7, 80, -180f)]
public void Fill_RegularPolygon<TPixel>(TestImageProvider<TPixel> provider, int vertices, float radius, float angleDeg)
where TPixel : struct, IPixel<TPixel>
{
float angle = GeometryUtilities.DegreeToRadian(angleDeg);
var polygon = new RegularPolygon(100, 100, vertices, radius, angle);
var color = Color.Yellow;
FormattableString testOutput = $"V({vertices})_R({radius})_Ang({angleDeg})";
provider.RunValidatingProcessorTest(
c => c.Fill(color, polygon),
testOutput,
appendSourceFileOrDescription: false,
appendPixelTypeToFileName: false);
}
[Theory]
[WithBasicTestPatternImages(200, 200, PixelTypes.Rgba32)]
public void Fill_EllipsePolygon<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : struct, IPixel<TPixel>
{
var polygon = new EllipsePolygon(100, 100, 80, 120);
var color = Color.Azure;
provider.RunValidatingProcessorTest(
c => c.Fill(color, polygon),
appendSourceFileOrDescription: false,
appendPixelTypeToFileName: false);
}
}
}

73
tests/ImageSharp.Tests/Drawing/FillRadialGradientBrushTests.cs

@ -1,73 +0,0 @@
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing;
using Xunit;
namespace SixLabors.ImageSharp.Tests.Drawing
{
using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison;
[GroupOutput("Drawing/GradientBrushes")]
public class FillRadialGradientBrushTests
{
public static ImageComparer TolerantComparer = ImageComparer.TolerantPercentage(0.01f);
[Theory]
[WithBlankImages(200, 200, PixelTypes.Rgba32)]
public void WithEqualColorsReturnsUnicolorImage<TPixel>(
TestImageProvider<TPixel> provider)
where TPixel : struct, IPixel<TPixel>
{
using (Image<TPixel> image = provider.GetImage())
{
Color red = Color.Red;
var unicolorRadialGradientBrush =
new RadialGradientBrush(
new SixLabors.Primitives.Point(0, 0),
100,
GradientRepetitionMode.None,
new ColorStop(0, red),
new ColorStop(1, red));
image.Mutate(x => x.Fill(unicolorRadialGradientBrush));
image.DebugSave(provider, appendPixelTypeToFileName: false, appendSourceFileOrDescription: false);
// no need for reference image in this test:
image.ComparePixelBufferTo(red);
}
}
[Theory]
[WithBlankImages(200, 200, PixelTypes.Rgba32, 100, 100)]
[WithBlankImages(200, 200, PixelTypes.Rgba32, 0, 0)]
[WithBlankImages(200, 200, PixelTypes.Rgba32, 100, 0)]
[WithBlankImages(200, 200, PixelTypes.Rgba32, 0, 100)]
[WithBlankImages(200, 200, PixelTypes.Rgba32, -40, 100)]
public void WithDifferentCentersReturnsImage<TPixel>(
TestImageProvider<TPixel> provider,
int centerX,
int centerY)
where TPixel : struct, IPixel<TPixel>
{
provider.VerifyOperation(
TolerantComparer,
image =>
{
var brush = new RadialGradientBrush(
new SixLabors.Primitives.Point(centerX, centerY),
image.Width / 2f,
GradientRepetitionMode.None,
new ColorStop(0, Color.Red),
new ColorStop(1, Color.Yellow));
image.Mutate(x => x.Fill(brush));
},
$"center({centerX},{centerY})",
false,
false);
}
}
}

156
tests/ImageSharp.Tests/Drawing/FillRegionProcessorTests.cs

@ -1,156 +0,0 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System.Numerics;
using Moq;
using System;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Primitives;
using SixLabors.ImageSharp.Processing;
using SixLabors.ImageSharp.Processing.Processors;
using SixLabors.Primitives;
using Xunit;
using SixLabors.ImageSharp.Processing.Processors.Drawing;
using SixLabors.Shapes;
using SixLabors.ImageSharp.Advanced;
namespace SixLabors.ImageSharp.Tests.Drawing
{
public class FillRegionProcessorTests
{
[Theory]
[InlineData(true, 1, 4)]
[InlineData(true, 2, 4)]
[InlineData(true, 5, 5)]
[InlineData(true, 8, 8)]
[InlineData(false, 8, 4)]
[InlineData(false, 16, 4)] // we always do 4 sub=pixels when antialiasing is off.
public void MinimumAntialiasSubpixelDepth(bool antialias, int antialiasSubpixelDepth, int expectedAntialiasSubpixelDepth)
{
var bounds = new Rectangle(0, 0, 1, 1);
var brush = new Mock<IBrush>();
var region = new MockRegion2(bounds);
var options = new GraphicsOptions
{
Antialias = antialias,
AntialiasSubpixelDepth = 1
};
var processor = new FillRegionProcessor(options, brush.Object, region);
var img = new Image<Rgba32>(1, 1);
processor.Execute(img.GetConfiguration(), img, bounds);
Assert.Equal(4, region.ScanInvocationCounter);
}
[Fact]
public void FillOffCanvas()
{
var bounds = new Rectangle(-100, -10, 10, 10);
var brush = new Mock<IBrush>();
var options = new GraphicsOptions { Antialias = true };
var processor = new FillRegionProcessor(options, brush.Object, new MockRegion1());
var img = new Image<Rgba32>(10, 10);
processor.Execute(img.GetConfiguration(), img, bounds);
}
[Fact]
public void DrawOffCanvas()
{
using (var img = new Image<Rgba32>(10, 10))
{
img.Mutate(x => x.DrawLines(new Pen(Rgba32.Black, 10),
new Vector2(-10, 5),
new Vector2(20, 5)));
}
}
[Fact]
public void DoesNotThrowForIssue928()
{
var rectText = new RectangleF(0, 0, 2000, 2000);
using (var img = new Image<Rgba32>((int)rectText.Width, (int)rectText.Height))
{
img.Mutate(x => x.Fill(Rgba32.Transparent));
img.Mutate(ctx =>
{
ctx.DrawLines(
Rgba32.Red,
0.984252f,
new PointF(104.762581f, 1074.99365f),
new PointF(104.758667f, 1075.01721f),
new PointF(104.757675f, 1075.04114f),
new PointF(104.759628f, 1075.065f),
new PointF(104.764488f, 1075.08838f),
new PointF(104.772186f, 1075.111f),
new PointF(104.782608f, 1075.13245f),
new PointF(104.782608f, 1075.13245f)
);
}
);
}
}
[Fact]
public void DoesNotThrowFillingTriangle()
{
using (var image = new Image<Rgba32>(28, 28))
{
var path = new Polygon(
new LinearLineSegment(new PointF(17.11f, 13.99659f), new PointF(14.01433f, 27.06201f)),
new LinearLineSegment(new PointF(14.01433f, 27.06201f), new PointF(13.79267f, 14.00023f)),
new LinearLineSegment(new PointF(13.79267f, 14.00023f), new PointF(17.11f, 13.99659f))
);
image.Mutate(ctx =>
{
ctx.Fill(Rgba32.White, path);
});
}
}
// Mocking the region throws an error in netcore2.0
private class MockRegion1 : Region
{
public override Rectangle Bounds => new Rectangle(-100, -10, 10, 10);
public override int Scan(float y, Span<float> buffer, Configuration configuration)
{
if (y < 5)
{
buffer[0] = -10f;
buffer[1] = 100f;
return 2;
}
return 0;
}
public override int MaxIntersections => 10;
}
private class MockRegion2 : Region
{
public MockRegion2(Rectangle bounds)
{
this.Bounds = bounds;
}
public override int MaxIntersections => 100;
public override Rectangle Bounds { get; }
public int ScanInvocationCounter { get; private set; }
public override int Scan(float y, Span<float> buffer, Configuration configuration)
{
this.ScanInvocationCounter++;
return 0;
}
}
}
}

201
tests/ImageSharp.Tests/Drawing/FillSolidBrushTests.cs

@ -1,201 +0,0 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Primitives;
using SixLabors.ImageSharp.Processing;
using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison;
using SixLabors.Primitives;
using SixLabors.Shapes;
using Xunit;
// ReSharper disable InconsistentNaming
namespace SixLabors.ImageSharp.Tests.Drawing
{
[GroupOutput("Drawing")]
public class FillSolidBrushTests
{
[Theory]
[WithBlankImages(1, 1, PixelTypes.Rgba32)]
[WithBlankImages(7, 4, PixelTypes.Rgba32)]
[WithBlankImages(16, 7, PixelTypes.Rgba32)]
[WithBlankImages(33, 32, PixelTypes.Rgba32)]
[WithBlankImages(400, 500, PixelTypes.Rgba32)]
public void DoesNotDependOnSize<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : struct, IPixel<TPixel>
{
using (Image<TPixel> image = provider.GetImage())
{
var color = Color.HotPink;
image.Mutate(c => c.Fill(color));
image.DebugSave(provider, appendPixelTypeToFileName: false);
image.ComparePixelBufferTo(color);
}
}
[Theory]
[WithBlankImages(16, 16, PixelTypes.Rgba32 | PixelTypes.Argb32 | PixelTypes.RgbaVector)]
public void DoesNotDependOnSinglePixelType<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : struct, IPixel<TPixel>
{
using (Image<TPixel> image = provider.GetImage())
{
var color = Color.HotPink;
image.Mutate(c => c.Fill(color));
image.DebugSave(provider, appendSourceFileOrDescription: false);
image.ComparePixelBufferTo(color);
}
}
[Theory]
[WithSolidFilledImages(16, 16, "Red", PixelTypes.Rgba32, "Blue")]
[WithSolidFilledImages(16, 16, "Yellow", PixelTypes.Rgba32, "Khaki")]
public void WhenColorIsOpaque_OverridePreviousColor<TPixel>(
TestImageProvider<TPixel> provider,
string newColorName)
where TPixel : struct, IPixel<TPixel>
{
using (Image<TPixel> image = provider.GetImage())
{
Color color = TestUtils.GetColorByName(newColorName);
image.Mutate(c => c.Fill(color));
image.DebugSave(
provider,
newColorName,
appendPixelTypeToFileName: false,
appendSourceFileOrDescription: false);
image.ComparePixelBufferTo(color);
}
}
[Theory]
[WithSolidFilledImages(16, 16, "Red", PixelTypes.Rgba32, 5, 7, 3, 8)]
[WithSolidFilledImages(16, 16, "Red", PixelTypes.Rgba32, 8, 5, 6, 4)]
public void FillRegion<TPixel>(TestImageProvider<TPixel> provider, int x0, int y0, int w, int h)
where TPixel : struct, IPixel<TPixel>
{
FormattableString testDetails = $"(x{x0},y{y0},w{w},h{h})";
var region = new RectangleF(x0, y0, w, h);
Color color = TestUtils.GetColorByName("Blue");
provider.RunValidatingProcessorTest(c => c.Fill(color, region), testDetails, ImageComparer.Exact);
}
[Theory]
[WithSolidFilledImages(16, 16, "Red", PixelTypes.Rgba32, 5, 7, 3, 8)]
[WithSolidFilledImages(16, 16, "Red", PixelTypes.Rgba32, 8, 5, 6, 4)]
public void FillRegion_WorksOnWrappedMemoryImage<TPixel>(
TestImageProvider<TPixel> provider,
int x0,
int y0,
int w,
int h)
where TPixel : struct, IPixel<TPixel>
{
FormattableString testDetails = $"(x{x0},y{y0},w{w},h{h})";
var region = new RectangleF(x0, y0, w, h);
Color color = TestUtils.GetColorByName("Blue");
provider.RunValidatingProcessorTestOnWrappedMemoryImage(
c => c.Fill(color, region),
testDetails,
ImageComparer.Exact,
useReferenceOutputFrom: nameof(this.FillRegion));
}
public static readonly TheoryData<bool, string, float, PixelColorBlendingMode, float> BlendData =
new TheoryData<bool, string, float, PixelColorBlendingMode, float>
{
{ false, "Blue", 0.5f, PixelColorBlendingMode.Normal, 1.0f },
{ false, "Blue", 1.0f, PixelColorBlendingMode.Normal, 0.5f },
{ false, "Green", 0.5f, PixelColorBlendingMode.Normal, 0.3f },
{ false, "HotPink", 0.8f, PixelColorBlendingMode.Normal, 0.8f },
{ false, "Blue", 0.5f, PixelColorBlendingMode.Multiply, 1.0f },
{ false, "Blue", 1.0f, PixelColorBlendingMode.Multiply, 0.5f },
{ false, "Green", 0.5f, PixelColorBlendingMode.Multiply, 0.3f },
{ false, "HotPink", 0.8f, PixelColorBlendingMode.Multiply, 0.8f },
{ false, "Blue", 0.5f, PixelColorBlendingMode.Add, 1.0f },
{ false, "Blue", 1.0f, PixelColorBlendingMode.Add, 0.5f },
{ false, "Green", 0.5f, PixelColorBlendingMode.Add, 0.3f },
{ false, "HotPink", 0.8f, PixelColorBlendingMode.Add, 0.8f },
{ true, "Blue", 0.5f, PixelColorBlendingMode.Normal, 1.0f },
{ true, "Blue", 1.0f, PixelColorBlendingMode.Normal, 0.5f },
{ true, "Green", 0.5f, PixelColorBlendingMode.Normal, 0.3f },
{ true, "HotPink", 0.8f, PixelColorBlendingMode.Normal, 0.8f },
{ true, "Blue", 0.5f, PixelColorBlendingMode.Multiply, 1.0f },
{ true, "Blue", 1.0f, PixelColorBlendingMode.Multiply, 0.5f },
{ true, "Green", 0.5f, PixelColorBlendingMode.Multiply, 0.3f },
{ true, "HotPink", 0.8f, PixelColorBlendingMode.Multiply, 0.8f },
{ true, "Blue", 0.5f, PixelColorBlendingMode.Add, 1.0f },
{ true, "Blue", 1.0f, PixelColorBlendingMode.Add, 0.5f },
{ true, "Green", 0.5f, PixelColorBlendingMode.Add, 0.3f },
{ true, "HotPink", 0.8f, PixelColorBlendingMode.Add, 0.8f },
};
[Theory]
[WithSolidFilledImages(nameof(BlendData), 16, 16, "Red", PixelTypes.Rgba32)]
public void BlendFillColorOverBackground<TPixel>(
TestImageProvider<TPixel> provider,
bool triggerFillRegion,
string newColorName,
float alpha,
PixelColorBlendingMode blenderMode,
float blendPercentage)
where TPixel : struct, IPixel<TPixel>
{
Color fillColor = TestUtils.GetColorByName(newColorName).WithAlpha(alpha);
using (Image<TPixel> image = provider.GetImage())
{
TPixel bgColor = image[0, 0];
var options = new GraphicsOptions
{
Antialias = false,
ColorBlendingMode = blenderMode,
BlendPercentage = blendPercentage
};
if (triggerFillRegion)
{
var region = new ShapeRegion(new RectangularPolygon(0, 0, 16, 16));
image.Mutate(c => c.Fill(options, new SolidBrush(fillColor), region));
}
else
{
image.Mutate(c => c.Fill(options, new SolidBrush(fillColor)));
}
var testOutputDetails = new
{
triggerFillRegion = triggerFillRegion,
newColorName = newColorName,
alpha = alpha,
blenderMode = blenderMode,
blendPercentage = blendPercentage
};
image.DebugSave(
provider,
testOutputDetails,
appendPixelTypeToFileName: false,
appendSourceFileOrDescription: false);
PixelBlender<TPixel> blender = PixelOperations<TPixel>.Instance.GetPixelBlender(
blenderMode,
PixelAlphaCompositionMode.SrcOver);
TPixel expectedPixel = blender.Blend(bgColor, fillColor.ToPixel<TPixel>(), blendPercentage);
image.ComparePixelBufferTo(expectedPixel);
}
}
}
}

119
tests/ImageSharp.Tests/Drawing/Paths/DrawPathCollection.cs

@ -1,119 +0,0 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System.Numerics;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Primitives;
using SixLabors.ImageSharp.Processing;
using SixLabors.ImageSharp.Processing.Processors.Drawing;
using SixLabors.ImageSharp.Tests.Processing;
using SixLabors.ImageSharp.Tests.TestUtilities;
using SixLabors.Shapes;
using Xunit;
namespace SixLabors.ImageSharp.Tests.Drawing.Paths
{
public class DrawPathCollection : BaseImageOperationsExtensionTest
{
private static readonly GraphicsOptionsComparer graphicsOptionsComparer = new GraphicsOptionsComparer();
GraphicsOptions nonDefault = new GraphicsOptions { Antialias = false };
Color color = Color.HotPink;
Pen pen = Pens.Solid(Rgba32.HotPink, 1);
IPath path1 = new Path(new LinearLineSegment(new SixLabors.Primitives.PointF[] {
new Vector2(10,10),
new Vector2(20,10),
new Vector2(20,10),
new Vector2(30,10),
}));
IPath path2 = new Path(new LinearLineSegment(new SixLabors.Primitives.PointF[] {
new Vector2(10,10),
new Vector2(20,10),
new Vector2(20,10),
new Vector2(30,10),
}));
IPathCollection pathCollection;
public DrawPathCollection()
{
this.pathCollection = new PathCollection(this.path1, this.path2);
}
[Fact]
public void CorrectlySetsBrushAndPath()
{
this.operations.Draw(this.pen, this.pathCollection);
for (int i = 0; i < 2; i++)
{
FillRegionProcessor processor = this.Verify<FillRegionProcessor>(i);
Assert.Equal(new GraphicsOptions(), processor.Options, graphicsOptionsComparer);
ShapePath region = Assert.IsType<ShapePath>(processor.Region);
// path is converted to a polygon before filling
Assert.IsType<ComplexPolygon>(region.Shape);
Assert.Equal(this.pen.StrokeFill, processor.Brush);
}
}
[Fact]
public void CorrectlySetsBrushPathOptions()
{
this.operations.Draw(this.nonDefault, this.pen, this.pathCollection);
for (int i = 0; i < 2; i++)
{
FillRegionProcessor processor = this.Verify<FillRegionProcessor>(i);
Assert.Equal(this.nonDefault, processor.Options, graphicsOptionsComparer);
ShapePath region = Assert.IsType<ShapePath>(processor.Region);
Assert.IsType<ComplexPolygon>(region.Shape);
Assert.Equal(this.pen.StrokeFill, processor.Brush);
}
}
[Fact]
public void CorrectlySetsColorAndPath()
{
this.operations.Draw(this.color, 1, this.pathCollection);
for (int i = 0; i < 2; i++)
{
FillRegionProcessor processor = this.Verify<FillRegionProcessor>(i);
Assert.Equal(new GraphicsOptions(), processor.Options, graphicsOptionsComparer);
ShapePath region = Assert.IsType<ShapePath>(processor.Region);
Assert.IsType<ComplexPolygon>(region.Shape);
SolidBrush brush = Assert.IsType<SolidBrush>(processor.Brush);
Assert.Equal(this.color, brush.Color);
}
}
[Fact]
public void CorrectlySetsColorPathAndOptions()
{
this.operations.Draw(this.nonDefault, this.color, 1, this.pathCollection);
for (int i = 0; i < 2; i++)
{
FillRegionProcessor processor = this.Verify<FillRegionProcessor>(i);
Assert.Equal(this.nonDefault, processor.Options, graphicsOptionsComparer);
ShapePath region = Assert.IsType<ShapePath>(processor.Region);
Assert.IsType<ComplexPolygon>(region.Shape);
SolidBrush brush = Assert.IsType<SolidBrush>(processor.Brush);
Assert.Equal(this.color, brush.Color);
}
}
}
}

94
tests/ImageSharp.Tests/Drawing/Paths/FillPath.cs

@ -1,94 +0,0 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System.Numerics;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Primitives;
using SixLabors.ImageSharp.Processing;
using SixLabors.ImageSharp.Processing.Processors.Drawing;
using SixLabors.ImageSharp.Tests.Processing;
using SixLabors.ImageSharp.Tests.TestUtilities;
using SixLabors.Shapes;
using Xunit;
namespace SixLabors.ImageSharp.Tests.Drawing.Paths
{
public class FillPath : BaseImageOperationsExtensionTest
{
private static readonly GraphicsOptionsComparer graphicsOptionsComparer = new GraphicsOptionsComparer();
GraphicsOptions nonDefault = new GraphicsOptions { Antialias = false };
Color color = Color.HotPink;
SolidBrush brush = Brushes.Solid(Rgba32.HotPink);
IPath path = new Path(new LinearLineSegment(new SixLabors.Primitives.PointF[] {
new Vector2(10,10),
new Vector2(20,10),
new Vector2(20,10),
new Vector2(30,10),
}));
[Fact]
public void CorrectlySetsBrushAndPath()
{
this.operations.Fill(this.brush, this.path);
var processor = this.Verify<FillRegionProcessor>();
Assert.Equal(new GraphicsOptions(), processor.Options, graphicsOptionsComparer);
ShapeRegion region = Assert.IsType<ShapeRegion>(processor.Region);
// path is converted to a polygon before filling
Polygon polygon = Assert.IsType<Polygon>(region.Shape);
Assert.IsType<LinearLineSegment>(polygon.LineSegments[0]);
Assert.Equal(this.brush, processor.Brush);
}
[Fact]
public void CorrectlySetsBrushPathOptions()
{
this.operations.Fill(this.nonDefault, this.brush, this.path);
var processor = this.Verify<FillRegionProcessor>();
Assert.Equal(this.nonDefault, processor.Options, graphicsOptionsComparer);
ShapeRegion region = Assert.IsType<ShapeRegion>(processor.Region);
Polygon polygon = Assert.IsType<Polygon>(region.Shape);
Assert.IsType<LinearLineSegment>(polygon.LineSegments[0]);
Assert.Equal(this.brush, processor.Brush);
}
[Fact]
public void CorrectlySetsColorAndPath()
{
this.operations.Fill(this.color, this.path);
var processor = this.Verify<FillRegionProcessor>();
Assert.Equal(new GraphicsOptions(), processor.Options, graphicsOptionsComparer);
ShapeRegion region = Assert.IsType<ShapeRegion>(processor.Region);
Polygon polygon = Assert.IsType<Polygon>(region.Shape);
Assert.IsType<LinearLineSegment>(polygon.LineSegments[0]);
SolidBrush brush = Assert.IsType<SolidBrush>(processor.Brush);
Assert.Equal(this.color, brush.Color);
}
[Fact]
public void CorrectlySetsColorPathAndOptions()
{
this.operations.Fill(this.nonDefault, this.color, this.path);
var processor = this.Verify<FillRegionProcessor>();
Assert.Equal(this.nonDefault, processor.Options);
ShapeRegion region = Assert.IsType<ShapeRegion>(processor.Region);
Polygon polygon = Assert.IsType<Polygon>(region.Shape);
Assert.IsType<LinearLineSegment>(polygon.LineSegments[0]);
SolidBrush brush = Assert.IsType<SolidBrush>(processor.Brush);
Assert.Equal(this.color, brush.Color);
}
}
}

123
tests/ImageSharp.Tests/Drawing/Paths/FillPathCollection.cs

@ -1,123 +0,0 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System.Numerics;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Primitives;
using SixLabors.ImageSharp.Processing;
using SixLabors.ImageSharp.Processing.Processors.Drawing;
using SixLabors.ImageSharp.Tests.Processing;
using SixLabors.ImageSharp.Tests.TestUtilities;
using SixLabors.Shapes;
using Xunit;
namespace SixLabors.ImageSharp.Tests.Drawing.Paths
{
public class FillPathCollection : BaseImageOperationsExtensionTest
{
private static readonly GraphicsOptionsComparer graphicsOptionsComparer = new GraphicsOptionsComparer();
GraphicsOptions nonDefault = new GraphicsOptions { Antialias = false };
Color color = Color.HotPink;
SolidBrush brush = Brushes.Solid(Rgba32.HotPink);
IPath path1 = new Path(new LinearLineSegment(new SixLabors.Primitives.PointF[] {
new Vector2(10,10),
new Vector2(20,10),
new Vector2(20,10),
new Vector2(30,10),
}));
IPath path2 = new Path(new LinearLineSegment(new SixLabors.Primitives.PointF[] {
new Vector2(10,10),
new Vector2(20,10),
new Vector2(20,10),
new Vector2(30,10),
}));
IPathCollection pathCollection;
public FillPathCollection()
{
this.pathCollection = new PathCollection(this.path1, this.path2);
}
[Fact]
public void CorrectlySetsBrushAndPath()
{
this.operations.Fill(this.brush, this.pathCollection);
for (int i = 0; i < 2; i++)
{
FillRegionProcessor processor = this.Verify<FillRegionProcessor>(i);
Assert.Equal(new GraphicsOptions(), processor.Options, graphicsOptionsComparer);
ShapeRegion region = Assert.IsType<ShapeRegion>(processor.Region);
// path is converted to a polygon before filling
Polygon polygon = Assert.IsType<Polygon>(region.Shape);
Assert.IsType<LinearLineSegment>(polygon.LineSegments[0]);
Assert.Equal(this.brush, processor.Brush);
}
}
[Fact]
public void CorrectlySetsBrushPathOptions()
{
this.operations.Fill(this.nonDefault, this.brush, this.pathCollection);
for (int i = 0; i < 2; i++)
{
FillRegionProcessor processor = this.Verify<FillRegionProcessor>(i);
Assert.Equal(this.nonDefault, processor.Options, graphicsOptionsComparer);
ShapeRegion region = Assert.IsType<ShapeRegion>(processor.Region);
Polygon polygon = Assert.IsType<Polygon>(region.Shape);
Assert.IsType<LinearLineSegment>(polygon.LineSegments[0]);
Assert.Equal(this.brush, processor.Brush);
}
}
[Fact]
public void CorrectlySetsColorAndPath()
{
this.operations.Fill(this.color, this.pathCollection);
for (int i = 0; i < 2; i++)
{
FillRegionProcessor processor = this.Verify<FillRegionProcessor>(i);
Assert.Equal(new GraphicsOptions(), processor.Options, graphicsOptionsComparer);
ShapeRegion region = Assert.IsType<ShapeRegion>(processor.Region);
Polygon polygon = Assert.IsType<Polygon>(region.Shape);
Assert.IsType<LinearLineSegment>(polygon.LineSegments[0]);
SolidBrush brush = Assert.IsType<SolidBrush>(processor.Brush);
Assert.Equal(this.color, brush.Color);
}
}
[Fact]
public void CorrectlySetsColorPathAndOptions()
{
this.operations.Fill(this.nonDefault, this.color, this.pathCollection);
for (int i = 0; i < 2; i++)
{
FillRegionProcessor processor = this.Verify<FillRegionProcessor>(i);
Assert.Equal(this.nonDefault, processor.Options, graphicsOptionsComparer);
ShapeRegion region = Assert.IsType<ShapeRegion>(processor.Region);
Polygon polygon = Assert.IsType<Polygon>(region.Shape);
Assert.IsType<LinearLineSegment>(polygon.LineSegments[0]);
SolidBrush brush = Assert.IsType<SolidBrush>(processor.Brush);
Assert.Equal(this.color, brush.Color);
}
}
}
}

95
tests/ImageSharp.Tests/Drawing/Paths/FillPolygon.cs

@ -1,95 +0,0 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System.Numerics;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Primitives;
using SixLabors.ImageSharp.Processing;
using SixLabors.ImageSharp.Processing.Processors.Drawing;
using SixLabors.ImageSharp.Tests.Processing;
using SixLabors.ImageSharp.Tests.TestUtilities;
using SixLabors.Shapes;
using Xunit;
namespace SixLabors.ImageSharp.Tests.Drawing.Paths
{
public class FillPolygon : BaseImageOperationsExtensionTest
{
private static readonly GraphicsOptionsComparer graphicsOptionsComparer = new GraphicsOptionsComparer();
GraphicsOptions nonDefault = new GraphicsOptions { Antialias = false };
Color color = Color.HotPink;
SolidBrush brush = Brushes.Solid(Rgba32.HotPink);
SixLabors.Primitives.PointF[] path = {
new Vector2(10,10),
new Vector2(20,10),
new Vector2(20,10),
new Vector2(30,10),
};
[Fact]
public void CorrectlySetsBrushAndPath()
{
this.operations.FillPolygon(this.brush, this.path);
FillRegionProcessor processor = this.Verify<FillRegionProcessor>();
Assert.Equal(new GraphicsOptions(), processor.Options, graphicsOptionsComparer);
ShapeRegion region = Assert.IsType<ShapeRegion>(processor.Region);
Polygon polygon = Assert.IsType<Polygon>(region.Shape);
Assert.IsType<LinearLineSegment>(polygon.LineSegments[0]);
Assert.Equal(this.brush, processor.Brush);
}
[Fact]
public void CorrectlySetsBrushPathAndOptions()
{
this.operations.FillPolygon(this.nonDefault, this.brush, this.path);
FillRegionProcessor processor = this.Verify<FillRegionProcessor>();
Assert.Equal(this.nonDefault, processor.Options, graphicsOptionsComparer);
ShapeRegion region = Assert.IsType<ShapeRegion>(processor.Region);
Polygon polygon = Assert.IsType<Polygon>(region.Shape);
Assert.IsType<LinearLineSegment>(polygon.LineSegments[0]);
Assert.Equal(this.brush, processor.Brush);
}
[Fact]
public void CorrectlySetsColorAndPath()
{
this.operations.FillPolygon(this.color, this.path);
FillRegionProcessor processor = this.Verify<FillRegionProcessor>();
Assert.Equal(new GraphicsOptions(), processor.Options, graphicsOptionsComparer);
ShapeRegion region = Assert.IsType<ShapeRegion>(processor.Region);
Polygon polygon = Assert.IsType<Polygon>(region.Shape);
Assert.IsType<LinearLineSegment>(polygon.LineSegments[0]);
SolidBrush brush = Assert.IsType<SolidBrush>(processor.Brush);
Assert.Equal(this.color, brush.Color);
}
[Fact]
public void CorrectlySetsColorPathAndOptions()
{
this.operations.FillPolygon(this.nonDefault, this.color, this.path);
FillRegionProcessor processor = this.Verify<FillRegionProcessor>();
Assert.Equal(this.nonDefault, processor.Options, graphicsOptionsComparer);
ShapeRegion region = Assert.IsType<ShapeRegion>(processor.Region);
Polygon polygon = Assert.IsType<Polygon>(region.Shape);
Assert.IsType<LinearLineSegment>(polygon.LineSegments[0]);
SolidBrush brush = Assert.IsType<SolidBrush>(processor.Brush);
Assert.Equal(this.color, brush.Color);
}
}
}

97
tests/ImageSharp.Tests/Drawing/Paths/FillRectangle.cs

@ -1,97 +0,0 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Primitives;
using SixLabors.ImageSharp.Processing;
using SixLabors.ImageSharp.Processing.Processors.Drawing;
using SixLabors.ImageSharp.Tests.Processing;
using SixLabors.ImageSharp.Tests.TestUtilities;
using Xunit;
namespace SixLabors.ImageSharp.Tests.Drawing.Paths
{
public class FillRectangle : BaseImageOperationsExtensionTest
{
private static readonly GraphicsOptionsComparer graphicsOptionsComparer = new GraphicsOptionsComparer();
private GraphicsOptions nonDefault = new GraphicsOptions { Antialias = false };
private Color color = Color.HotPink;
private SolidBrush brush = Brushes.Solid(Rgba32.HotPink);
private SixLabors.Primitives.Rectangle rectangle = new SixLabors.Primitives.Rectangle(10, 10, 77, 76);
[Fact]
public void CorrectlySetsBrushAndRectangle()
{
this.operations.Fill(this.brush, this.rectangle);
FillRegionProcessor processor = this.Verify<FillRegionProcessor>();
Assert.Equal(new GraphicsOptions(), processor.Options, graphicsOptionsComparer);
ShapeRegion region = Assert.IsType<ShapeRegion>(processor.Region);
Shapes.RectangularPolygon rect = Assert.IsType<Shapes.RectangularPolygon>(region.Shape);
Assert.Equal(rect.Location.X, this.rectangle.X);
Assert.Equal(rect.Location.Y, this.rectangle.Y);
Assert.Equal(rect.Size.Width, this.rectangle.Width);
Assert.Equal(rect.Size.Height, this.rectangle.Height);
Assert.Equal(this.brush, processor.Brush);
}
[Fact]
public void CorrectlySetsBrushRectangleAndOptions()
{
this.operations.Fill(this.nonDefault, this.brush, this.rectangle);
FillRegionProcessor processor = this.Verify<FillRegionProcessor>();
Assert.Equal(this.nonDefault, processor.Options, graphicsOptionsComparer);
ShapeRegion region = Assert.IsType<ShapeRegion>(processor.Region);
Shapes.RectangularPolygon rect = Assert.IsType<Shapes.RectangularPolygon>(region.Shape);
Assert.Equal(rect.Location.X, this.rectangle.X);
Assert.Equal(rect.Location.Y, this.rectangle.Y);
Assert.Equal(rect.Size.Width, this.rectangle.Width);
Assert.Equal(rect.Size.Height, this.rectangle.Height);
Assert.Equal(this.brush, processor.Brush);
}
[Fact]
public void CorrectlySetsColorAndRectangle()
{
this.operations.Fill(this.color, this.rectangle);
FillRegionProcessor processor = this.Verify<FillRegionProcessor>();
Assert.Equal(new GraphicsOptions(), processor.Options, graphicsOptionsComparer);
ShapeRegion region = Assert.IsType<ShapeRegion>(processor.Region);
Shapes.RectangularPolygon rect = Assert.IsType<Shapes.RectangularPolygon>(region.Shape);
Assert.Equal(rect.Location.X, this.rectangle.X);
Assert.Equal(rect.Location.Y, this.rectangle.Y);
Assert.Equal(rect.Size.Width, this.rectangle.Width);
Assert.Equal(rect.Size.Height, this.rectangle.Height);
SolidBrush brush = Assert.IsType<SolidBrush>(processor.Brush);
Assert.Equal(this.color, brush.Color);
}
[Fact]
public void CorrectlySetsColorRectangleAndOptions()
{
this.operations.Fill(this.nonDefault, this.color, this.rectangle);
FillRegionProcessor processor = this.Verify<FillRegionProcessor>();
Assert.Equal(this.nonDefault, processor.Options, graphicsOptionsComparer);
ShapeRegion region = Assert.IsType<ShapeRegion>(processor.Region);
Shapes.RectangularPolygon rect = Assert.IsType<Shapes.RectangularPolygon>(region.Shape);
Assert.Equal(rect.Location.X, this.rectangle.X);
Assert.Equal(rect.Location.Y, this.rectangle.Y);
Assert.Equal(rect.Size.Width, this.rectangle.Width);
Assert.Equal(rect.Size.Height, this.rectangle.Height);
SolidBrush brush = Assert.IsType<SolidBrush>(processor.Brush);
Assert.Equal(this.color, brush.Color);
}
}
}

10
tests/ImageSharp.Tests/Drawing/Paths/ShapePathTests.cs

@ -1,10 +0,0 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
namespace SixLabors.ImageSharp.Tests.Drawing.Paths
{
public class ShapePathTests
{
// TODO read these back in
}
}

127
tests/ImageSharp.Tests/Drawing/Paths/ShapeRegionTests.cs

@ -1,127 +0,0 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using SixLabors.ImageSharp.Primitives;
using System;
using System.Collections.Generic;
using System.Numerics;
using Moq;
using SixLabors.Primitives;
using SixLabors.Shapes;
using Xunit;
namespace SixLabors.ImageSharp.Tests.Drawing.Paths
{
public class ShapeRegionTests
{
public abstract class MockPath : IPath
{
public abstract RectangleF Bounds { get; }
public IPath AsClosedPath() => this;
public abstract SegmentInfo PointAlongPath(float distanceAlongPath);
public abstract PointInfo Distance(PointF point);
public abstract IEnumerable<ISimplePath> Flatten();
public abstract bool Contains(PointF point);
public abstract IPath Transform(Matrix3x2 matrix);
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)
{
return this.FindIntersections(start, end, buffer, 0);
}
public int FindIntersections(PointF s, PointF e, Span<PointF> buffer)
{
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 TestFindIntersectionsInvocationCounter { get; private set; }
public virtual int TestYToScan => 10;
public virtual int TestFindIntersectionsResult => 3;
}
private readonly Mock<MockPath> pathMock;
private readonly RectangleF bounds;
public ShapeRegionTests()
{
this.pathMock = new Mock<MockPath> { CallBase = true };
this.bounds = new RectangleF(10.5f, 10, 10, 10);
this.pathMock.Setup(x => x.Bounds).Returns(this.bounds);
}
[Fact]
public void ShapeRegionWithPathRetainsShape()
{
var region = new ShapeRegion(this.pathMock.Object);
Assert.Equal(this.pathMock.Object, region.Shape);
}
[Fact]
public void ShapeRegionFromPathConvertsBoundsProxyToShape()
{
var region = new ShapeRegion(this.pathMock.Object);
Assert.Equal(Math.Floor(this.bounds.Left), region.Bounds.Left);
Assert.Equal(Math.Ceiling(this.bounds.Right), region.Bounds.Right);
this.pathMock.Verify(x => x.Bounds);
}
[Fact]
public void ShapeRegionFromPathMaxIntersectionsProxyToShape()
{
var region = new ShapeRegion(this.pathMock.Object);
int i = region.MaxIntersections;
this.pathMock.Verify(x => x.MaxIntersections);
}
[Fact]
public void ShapeRegionFromPathScanYProxyToShape()
{
MockPath path = this.pathMock.Object;
int yToScan = path.TestYToScan;
var region = new ShapeRegion(path);
int i = region.Scan(yToScan, new float[path.TestFindIntersectionsResult], Configuration.Default);
Assert.Equal(path.TestFindIntersectionsResult, i);
Assert.Equal(1, path.TestFindIntersectionsInvocationCounter);
}
[Fact]
public void ShapeRegionFromShapeConvertsBoundsProxyToShape()
{
var region = new ShapeRegion(this.pathMock.Object);
Assert.Equal(Math.Floor(this.bounds.Left), region.Bounds.Left);
Assert.Equal(Math.Ceiling(this.bounds.Right), region.Bounds.Right);
this.pathMock.Verify(x => x.Bounds);
}
[Fact]
public void ShapeRegionFromShapeMaxIntersectionsProxyToShape()
{
var region = new ShapeRegion(this.pathMock.Object);
int i = region.MaxIntersections;
this.pathMock.Verify(x => x.MaxIntersections);
}
}
}

52
tests/ImageSharp.Tests/Drawing/RecolorImageTests.cs

@ -1,52 +0,0 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing;
using SixLabors.Primitives;
using Xunit;
namespace SixLabors.ImageSharp.Tests.Drawing
{
[GroupOutput("Drawing")]
public class RecolorImageTests
{
[Theory]
[WithFile(TestImages.Png.CalliphoraPartial, PixelTypes.Rgba32, "Yellow", "Pink", 0.2f)]
[WithFile(TestImages.Png.CalliphoraPartial, PixelTypes.Bgra32, "Yellow", "Pink", 0.5f)]
[WithTestPatternImages(100, 100, PixelTypes.Rgba32, "Red", "Blue", 0.2f)]
[WithTestPatternImages(100, 100, PixelTypes.Rgba32, "Red", "Blue", 0.6f)]
public void Recolor<TPixel>(TestImageProvider<TPixel> provider, string sourceColorName, string targetColorName, float threshold)
where TPixel : struct, IPixel<TPixel>
{
Color sourceColor = TestUtils.GetColorByName(sourceColorName);
Color targetColor = TestUtils.GetColorByName(targetColorName);
var brush = new RecolorBrush(sourceColor, targetColor, threshold);
FormattableString testInfo = $"{sourceColorName}-{targetColorName}-{threshold}";
provider.RunValidatingProcessorTest(x => x.Fill(brush), testInfo);
}
[Theory]
[WithFile(TestImages.Png.CalliphoraPartial, PixelTypes.Bgra32, "Yellow", "Pink", 0.5f)]
[WithTestPatternImages(100, 100, PixelTypes.Rgba32, "Red", "Blue", 0.2f)]
public void Recolor_InBox<TPixel>(TestImageProvider<TPixel> provider, string sourceColorName, string targetColorName, float threshold)
where TPixel : struct, IPixel<TPixel>
{
Color sourceColor = TestUtils.GetColorByName(sourceColorName);
Color targetColor = TestUtils.GetColorByName(targetColorName);
var brush = new RecolorBrush(sourceColor, targetColor, threshold);
FormattableString testInfo = $"{sourceColorName}-{targetColorName}-{threshold}";
provider.RunValidatingProcessorTest(x =>
{
Size size = x.GetCurrentSize();
var rectangle = new Rectangle(0, size.Height / 2 - size.Height / 4, size.Width, size.Height / 2);
x.Fill(brush, rectangle);
}, testInfo);
}
}
}

64
tests/ImageSharp.Tests/Drawing/SolidBezierTests.cs

@ -1,64 +0,0 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System.Numerics;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing;
using SixLabors.Shapes;
using Xunit;
namespace SixLabors.ImageSharp.Tests.Drawing
{
[GroupOutput("Drawing")]
public class SolidBezierTests
{
[Theory]
[WithBlankImages(500, 500, PixelTypes.Rgba32)]
public void FilledBezier<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : struct, IPixel<TPixel>
{
SixLabors.Primitives.PointF[] simplePath = {
new Vector2(10, 400),
new Vector2(30, 10),
new Vector2(240, 30),
new Vector2(300, 400)
};
Color blue = Color.Blue;
Color hotPink = Color.HotPink;
using (Image<TPixel> image = provider.GetImage())
{
image.Mutate(x => x.BackgroundColor(blue));
image.Mutate(x => x.Fill(hotPink, new Polygon(new CubicBezierLineSegment(simplePath))));
image.DebugSave(provider);
image.CompareToReferenceOutput(provider);
}
}
[Theory]
[WithBlankImages(500, 500, PixelTypes.Rgba32)]
public void OverlayByFilledPolygonOpacity<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : struct, IPixel<TPixel>
{
SixLabors.Primitives.PointF[] simplePath = {
new Vector2(10, 400),
new Vector2(30, 10),
new Vector2(240, 30),
new Vector2(300, 400)
};
var color = new Rgba32(Rgba32.HotPink.R, Rgba32.HotPink.G, Rgba32.HotPink.B, 150);
using (var image = provider.GetImage() as Image<Rgba32>)
{
image.Mutate(x => x.BackgroundColor(Rgba32.Blue));
image.Mutate(x => x.Fill(color, new Polygon(new CubicBezierLineSegment(simplePath))));
image.DebugSave(provider);
image.CompareToReferenceOutput(provider);
}
}
}
}

178
tests/ImageSharp.Tests/Drawing/SolidFillBlendedShapesTests.cs

@ -1,178 +0,0 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Collections.Generic;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing;
using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison;
using SixLabors.Primitives;
using Xunit;
// ReSharper disable InconsistentNaming
namespace SixLabors.ImageSharp.Tests.Drawing
{
[GroupOutput("Drawing")]
public class SolidFillBlendedShapesTests
{
public static IEnumerable<object[]> modes = GetAllModeCombinations();
private static IEnumerable<object[]> GetAllModeCombinations()
{
foreach (var composition in Enum.GetValues(typeof(PixelAlphaCompositionMode)))
{
foreach (var blending in Enum.GetValues(typeof(PixelColorBlendingMode)))
{
yield return new object[] { blending, composition };
}
}
}
[Theory]
[WithBlankImages(nameof(modes), 250, 250, PixelTypes.Rgba32)]
public void _1DarkBlueRect_2BlendHotPinkRect<TPixel>(
TestImageProvider<TPixel> provider,
PixelColorBlendingMode blending,
PixelAlphaCompositionMode composition)
where TPixel : struct, IPixel<TPixel>
{
using (Image<TPixel> img = provider.GetImage())
{
int scaleX = img.Width / 100;
int scaleY = img.Height / 100;
img.Mutate(
x => x.Fill(
Color.DarkBlue,
new Rectangle(0 * scaleX, 40 * scaleY, 100 * scaleX, 20 * scaleY)
)
.Fill(
new GraphicsOptions { Antialias = true, ColorBlendingMode = blending, AlphaCompositionMode = composition },
Color.HotPink,
new Rectangle(20 * scaleX, 0 * scaleY, 30 * scaleX, 100 * scaleY))
);
VerifyImage(provider, blending, composition, img);
}
}
[Theory]
[WithBlankImages(nameof(modes), 250, 250, PixelTypes.Rgba32)]
public void _1DarkBlueRect_2BlendHotPinkRect_3BlendTransparentEllipse<TPixel>(
TestImageProvider<TPixel> provider,
PixelColorBlendingMode blending,
PixelAlphaCompositionMode composition)
where TPixel : struct, IPixel<TPixel>
{
using (Image<TPixel> img = provider.GetImage())
{
int scaleX = img.Width / 100;
int scaleY = img.Height / 100;
img.Mutate(
x => x.Fill(
Color.DarkBlue,
new Rectangle(0 * scaleX, 40 * scaleY, 100 * scaleX, 20 * scaleY)));
img.Mutate(
x => x.Fill(
new GraphicsOptions { Antialias = true, ColorBlendingMode = blending, AlphaCompositionMode = composition },
Color.HotPink,
new Rectangle(20 * scaleX, 0 * scaleY, 30 * scaleX, 100 * scaleY)));
img.Mutate(
x => x.Fill(
new GraphicsOptions { Antialias = true, ColorBlendingMode = blending, AlphaCompositionMode = composition },
Color.Transparent,
new Shapes.EllipsePolygon(40 * scaleX, 50 * scaleY, 50 * scaleX, 50 * scaleY))
);
VerifyImage(provider, blending, composition, img);
}
}
[Theory]
[WithBlankImages(nameof(modes), 250, 250, PixelTypes.Rgba32)]
public void _1DarkBlueRect_2BlendHotPinkRect_3BlendSemiTransparentRedEllipse<TPixel>(
TestImageProvider<TPixel> provider,
PixelColorBlendingMode blending,
PixelAlphaCompositionMode composition)
where TPixel : struct, IPixel<TPixel>
{
using (Image<TPixel> img = provider.GetImage())
{
int scaleX = (img.Width / 100);
int scaleY = (img.Height / 100);
img.Mutate(
x => x.Fill(
Color.DarkBlue,
new Rectangle(0 * scaleX, 40, 100 * scaleX, 20 * scaleY)));
img.Mutate(
x => x.Fill(
new GraphicsOptions { Antialias = true, ColorBlendingMode = blending, AlphaCompositionMode = composition },
Color.HotPink,
new Rectangle(20 * scaleX, 0, 30 * scaleX, 100 * scaleY)));
var transparentRed = Color.Red.WithAlpha(0.5f);
img.Mutate(
x => x.Fill(
new GraphicsOptions { Antialias = true, ColorBlendingMode = blending, AlphaCompositionMode = composition },
transparentRed,
new Shapes.EllipsePolygon(40 * scaleX, 50 * scaleY, 50 * scaleX, 50 * scaleY))
);
VerifyImage(provider, blending, composition, img); ;
}
}
[Theory]
[WithBlankImages(nameof(modes), 250, 250, PixelTypes.Rgba32)]
public void _1DarkBlueRect_2BlendBlackEllipse<TPixel>(
TestImageProvider<TPixel> provider,
PixelColorBlendingMode blending,
PixelAlphaCompositionMode composition)
where TPixel : struct, IPixel<TPixel>
{
using (Image<TPixel> dstImg = provider.GetImage(), srcImg = provider.GetImage())
{
int scaleX = (dstImg.Width / 100);
int scaleY = (dstImg.Height / 100);
dstImg.Mutate(
x => x.Fill(
Color.DarkBlue,
new Rectangle(0 * scaleX, 40 * scaleY, 100 * scaleX, 20 * scaleY)));
srcImg.Mutate(
x => x.Fill(
Color.Black,
new Shapes.EllipsePolygon(40 * scaleX, 50 * scaleY, 50 * scaleX, 50 * scaleY)));
dstImg.Mutate(
x => x.DrawImage(srcImg, new GraphicsOptions { Antialias = true, ColorBlendingMode = blending, AlphaCompositionMode = composition })
);
VerifyImage(provider, blending, composition, dstImg);
}
}
private static void VerifyImage<TPixel>(
TestImageProvider<TPixel> provider,
PixelColorBlendingMode blending,
PixelAlphaCompositionMode composition,
Image<TPixel> img)
where TPixel : struct, IPixel<TPixel>
{
img.DebugSave(
provider,
new { composition, blending },
appendPixelTypeToFileName: false,
appendSourceFileOrDescription: false);
var comparer = ImageComparer.TolerantPercentage(0.01f, 3);
img.CompareFirstFrameToReferenceOutput(comparer,
provider,
new { composition, blending },
appendPixelTypeToFileName: false,
appendSourceFileOrDescription: false);
}
}
}

154
tests/ImageSharp.Tests/Drawing/Text/DrawText.cs

@ -1,154 +0,0 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System.Numerics;
using SixLabors.Fonts;
using SixLabors.ImageSharp.Processing;
using SixLabors.ImageSharp.Processing.Processors.Text;
using SixLabors.ImageSharp.Tests.Processing;
using SixLabors.Primitives;
using Xunit;
namespace SixLabors.ImageSharp.Tests.Drawing.Text
{
public class DrawText : BaseImageOperationsExtensionTest
{
private readonly FontCollection FontCollection;
private readonly Font Font;
public DrawText()
{
this.FontCollection = new FontCollection();
this.Font = this.FontCollection.Install(TestFontUtilities.GetPath("SixLaborsSampleAB.woff")).CreateFont(12);
}
[Fact]
public void FillsForEachACharacterWhenBrushSetAndNotPen()
{
this.operations.DrawText(
new TextGraphicsOptions { Antialias = true },
"123",
this.Font,
Brushes.Solid(Color.Red),
null,
Vector2.Zero);
this.Verify<DrawTextProcessor>(0);
}
[Fact]
public void FillsForEachACharacterWhenBrushSetAndNotPenDefaultOptions()
{
this.operations.DrawText("123", this.Font, Brushes.Solid(Color.Red), null, Vector2.Zero);
this.Verify<DrawTextProcessor>(0);
}
[Fact]
public void FillsForEachACharacterWhenBrushSet()
{
this.operations.DrawText(new TextGraphicsOptions { Antialias = true }, "123", this.Font, Brushes.Solid(Color.Red), Vector2.Zero);
this.Verify<DrawTextProcessor>(0);
}
[Fact]
public void FillsForEachACharacterWhenBrushSetDefaultOptions()
{
this.operations.DrawText("123", this.Font, Brushes.Solid(Color.Red), Vector2.Zero);
this.Verify<DrawTextProcessor>(0);
}
[Fact]
public void FillsForEachACharacterWhenColorSet()
{
this.operations.DrawText(new TextGraphicsOptions { Antialias = true }, "123", this.Font, Color.Red, Vector2.Zero);
var processor = this.Verify<DrawTextProcessor>(0);
SolidBrush brush = Assert.IsType<SolidBrush>(processor.Brush);
Assert.Equal(Color.Red, brush.Color);
}
[Fact]
public void FillsForEachACharacterWhenColorSetDefaultOptions()
{
this.operations.DrawText("123", this.Font, Color.Red, Vector2.Zero);
var processor = this.Verify<DrawTextProcessor>(0);
SolidBrush brush = Assert.IsType<SolidBrush>(processor.Brush);
Assert.Equal(Color.Red, brush.Color);
}
[Fact]
public void DrawForEachACharacterWhenPenSetAndNotBrush()
{
this.operations.DrawText(
new TextGraphicsOptions { Antialias = true },
"123",
this.Font,
null,
Pens.Dash(Color.Red, 1),
Vector2.Zero);
this.Verify<DrawTextProcessor>(0);
}
[Fact]
public void DrawForEachACharacterWhenPenSetAndNotBrushDefaultOptions()
{
this.operations.DrawText("123", this.Font, null, Pens.Dash(Color.Red, 1), Vector2.Zero);
this.Verify<DrawTextProcessor>(0);
}
[Fact]
public void DrawForEachACharacterWhenPenSet()
{
this.operations.DrawText(new TextGraphicsOptions { Antialias = true }, "123", this.Font, Pens.Dash(Color.Red, 1), Vector2.Zero);
this.Verify<DrawTextProcessor>(0);
}
[Fact]
public void DrawForEachACharacterWhenPenSetDefaultOptions()
{
this.operations.DrawText("123", this.Font, Pens.Dash(Color.Red, 1), Vector2.Zero);
var processor = this.Verify<DrawTextProcessor>(0);
Assert.Equal("123", processor.Text);
Assert.Equal(this.Font, processor.Font);
var penBrush = Assert.IsType<SolidBrush>(processor.Pen.StrokeFill);
Assert.Equal(Color.Red, penBrush.Color);
Assert.Equal(1, processor.Pen.StrokeWidth);
Assert.Equal(PointF.Empty, processor.Location);
}
[Fact]
public void DrawForEachACharacterWhenPenSetAndFillFroEachWhenBrushSet()
{
this.operations.DrawText(
new TextGraphicsOptions { Antialias = true },
"123",
this.Font,
Brushes.Solid(Color.Red),
Pens.Dash(Color.Red, 1),
Vector2.Zero);
var processor = this.Verify<DrawTextProcessor>(0);
Assert.Equal("123", processor.Text);
Assert.Equal(this.Font, processor.Font);
var brush = Assert.IsType<SolidBrush>(processor.Brush);
Assert.Equal(Color.Red, brush.Color);
Assert.Equal(PointF.Empty, processor.Location);
var penBrush = Assert.IsType<SolidBrush>(processor.Pen.StrokeFill);
Assert.Equal(Color.Red, penBrush.Color);
Assert.Equal(1, processor.Pen.StrokeWidth);
}
}
}

265
tests/ImageSharp.Tests/Drawing/Text/DrawTextOnImageTests.cs

@ -1,265 +0,0 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Linq;
using System.Text;
using SixLabors.Fonts;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing;
using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison;
using SixLabors.Primitives;
using Xunit;
using Xunit.Abstractions;
// ReSharper disable InconsistentNaming
namespace SixLabors.ImageSharp.Tests.Drawing.Text
{
[GroupOutput("Drawing/Text")]
public class DrawTextOnImageTests
{
private const string AB = "AB\nAB";
private const string TestText = "Sphinx of black quartz, judge my vow\n0123456789";
public static ImageComparer TextDrawingComparer = ImageComparer.TolerantPercentage(1e-5f);
public static ImageComparer OutlinedTextDrawingComparer = ImageComparer.TolerantPercentage(5e-4f);
public DrawTextOnImageTests(ITestOutputHelper output)
{
this.Output = output;
}
private ITestOutputHelper Output { get; }
[Theory]
[WithSolidFilledImages(276, 336, "White", PixelTypes.Rgba32)]
public void DoesntThrowExceptionWhenOverlappingRightEdge_Issue688<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : struct, IPixel<TPixel>
{
Font font = CreateFont("OpenSans-Regular.ttf", 36);
var color = Color.Black;
var text = "A short piece of text";
using (var img = provider.GetImage())
{
// measure the text size
SizeF size = TextMeasurer.Measure(text, new RendererOptions(font));
//find out how much we need to scale the text to fill the space (up or down)
float scalingFactor = Math.Min(img.Width / size.Width, img.Height / size.Height);
//create a new font
var scaledFont = new Font(font, scalingFactor * font.Size);
var center = new PointF(img.Width / 2, img.Height / 2);
var textGraphicOptions = new TextGraphicsOptions
{
Antialias = true,
HorizontalAlignment = HorizontalAlignment.Center,
VerticalAlignment = VerticalAlignment.Center
};
img.Mutate(i => i.DrawText(textGraphicOptions, text, scaledFont, color, center));
}
}
[Theory]
[WithSolidFilledImages(1500, 500, "White", PixelTypes.Rgba32)]
public void DoesntThrowExceptionWhenOverlappingRightEdge_Issue688_2<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : struct, IPixel<TPixel>
{
using (Image<TPixel> img = provider.GetImage())
{
Font font = CreateFont("OpenSans-Regular.ttf", 39);
string text = new string('a', 10000); // exception
// string text = "Hello"; // no exception
Rgba32 color = Rgba32.Black;
var point = new PointF(100, 100);
img.Mutate(ctx => ctx.DrawText(text, font, color, point));
}
}
[Theory]
[WithSolidFilledImages(200, 100, "White", PixelTypes.Rgba32, 50, 0, 0, "SixLaborsSampleAB.woff", AB)]
[WithSolidFilledImages(900, 100, "White", PixelTypes.Rgba32, 50, 0, 0, "OpenSans-Regular.ttf", TestText)]
[WithSolidFilledImages(400, 40, "White", PixelTypes.Rgba32, 20, 0, 0, "OpenSans-Regular.ttf", TestText)]
[WithSolidFilledImages(1100, 200, "White", PixelTypes.Rgba32, 50, 150, 100, "OpenSans-Regular.ttf", TestText)]
public void FontShapesAreRenderedCorrectly<TPixel>(
TestImageProvider<TPixel> provider,
int fontSize,
int x,
int y,
string fontName,
string text)
where TPixel : struct, IPixel<TPixel>
{
Font font = CreateFont(fontName, fontSize);
var color = Color.Black;
provider.VerifyOperation(
TextDrawingComparer,
img =>
{
img.Mutate(c => c.DrawText(text, new Font(font, fontSize), color, new PointF(x, y)));
},
$"{fontName}-{fontSize}-{ToTestOutputDisplayText(text)}-({x},{y})",
appendPixelTypeToFileName: false,
appendSourceFileOrDescription: true);
}
/// <summary>
/// Based on:
/// https://github.com/SixLabors/ImageSharp/issues/572
/// </summary>
[Theory]
[WithSolidFilledImages(2480, 3508, "White", PixelTypes.Rgba32)]
public void FontShapesAreRenderedCorrectly_LargeText<TPixel>(
TestImageProvider<TPixel> provider)
where TPixel : struct, IPixel<TPixel>
{
Font font = CreateFont("OpenSans-Regular.ttf", 36);
var sb = new StringBuilder();
string str = Repeat(" ", 78) + "THISISTESTWORDSTHISISTESTWORDSTHISISTESTWORDSTHISISTESTWORDSTHISISTESTWORDS";
sb.Append(str);
string newLines = Repeat(Environment.NewLine, 80);
sb.Append(newLines);
for (int i = 0; i < 10; i++)
{
sb.AppendLine(str);
}
var textOptions = new TextGraphicsOptions
{
Antialias = true,
ApplyKerning = true,
VerticalAlignment = VerticalAlignment.Top,
HorizontalAlignment = HorizontalAlignment.Left,
};
var color = Color.Black;
// Based on the reported 0.0270% difference with AccuracyMultiple = 8
// We should avoid quality regressions leading to higher difference!
var comparer = ImageComparer.TolerantPercentage(0.03f);
provider.VerifyOperation(
comparer,
img =>
{
img.Mutate(c => c.DrawText(textOptions, sb.ToString(), font, color, new PointF(10, 5)));
},
false,
false);
}
[Theory]
[WithSolidFilledImages(200, 100, "White", PixelTypes.Rgba32, 50, 0, 0, "SixLaborsSampleAB.woff", AB)]
[WithSolidFilledImages(900, 100, "White", PixelTypes.Rgba32, 50, 0, 0, "OpenSans-Regular.ttf", TestText)]
[WithSolidFilledImages(1100, 200, "White", PixelTypes.Rgba32, 50, 150, 100, "OpenSans-Regular.ttf", TestText)]
public void FontShapesAreRenderedCorrectlyWithAPen<TPixel>(
TestImageProvider<TPixel> provider,
int fontSize,
int x,
int y,
string fontName,
string text)
where TPixel : struct, IPixel<TPixel>
{
Font font = CreateFont(fontName, fontSize);
var color = Color.Black;
provider.VerifyOperation(
OutlinedTextDrawingComparer,
img =>
{
img.Mutate(c => c.DrawText(text, new Font(font, fontSize), null, Pens.Solid(color, 1), new PointF(x, y)));
},
$"pen_{fontName}-{fontSize}-{ToTestOutputDisplayText(text)}-({x},{y})",
appendPixelTypeToFileName: false,
appendSourceFileOrDescription: true);
}
[Theory]
[WithSolidFilledImages(200, 100, "White", PixelTypes.Rgba32, 50, 0, 0, "SixLaborsSampleAB.woff", AB)]
[WithSolidFilledImages(900, 100, "White", PixelTypes.Rgba32, 50, 0, 0, "OpenSans-Regular.ttf", TestText)]
[WithSolidFilledImages(1100, 200, "White", PixelTypes.Rgba32, 50, 150, 100, "OpenSans-Regular.ttf", TestText)]
public void FontShapesAreRenderedCorrectlyWithAPenPatterned<TPixel>(
TestImageProvider<TPixel> provider,
int fontSize,
int x,
int y,
string fontName,
string text)
where TPixel : struct, IPixel<TPixel>
{
Font font = CreateFont(fontName, fontSize);
var color = Color.Black;
provider.VerifyOperation(
OutlinedTextDrawingComparer,
img =>
{
img.Mutate(c => c.DrawText(text, new Font(font, fontSize), null, Pens.DashDot(color, 3), new PointF(x, y)));
},
$"pen_{fontName}-{fontSize}-{ToTestOutputDisplayText(text)}-({x},{y})",
appendPixelTypeToFileName: false,
appendSourceFileOrDescription: true);
}
[Theory]
[WithSolidFilledImages(1000, 1500, "White", PixelTypes.Rgba32, "OpenSans-Regular.ttf")]
public void TextPositioningIsRobust<TPixel>(TestImageProvider<TPixel> provider, string fontName)
where TPixel : struct, IPixel<TPixel>
{
Font font = CreateFont(fontName, 30);
string text = Repeat("Beware the Jabberwock, my son! The jaws that bite, the claws that catch! Beware the Jubjub bird, and shun The frumious Bandersnatch!\n",
20);
var textOptions = new TextGraphicsOptions
{
Antialias = true,
WrapTextWidth = 1000
};
string details = fontName.Replace(" ", "");
// Based on the reported 0.1755% difference with AccuracyMultiple = 8
// We should avoid quality regressions leading to higher difference!
var comparer = ImageComparer.TolerantPercentage(0.2f);
provider.RunValidatingProcessorTest(
x => x.DrawText(textOptions, text, font, Color.Black, new PointF(10, 50)),
details,
comparer,
appendPixelTypeToFileName: false,
appendSourceFileOrDescription: false);
}
private static string Repeat(string str, int times) => string.Concat(Enumerable.Repeat(str, times));
private static string ToTestOutputDisplayText(string text)
{
string fnDisplayText = text.Replace("\n", "");
fnDisplayText = fnDisplayText.Substring(0, Math.Min(fnDisplayText.Length, 4));
return fnDisplayText;
}
private static Font CreateFont(string fontName, int size)
{
var fontCollection = new FontCollection();
string fontPath = TestFontUtilities.GetPath(fontName);
Font font = fontCollection.Install(fontPath).CreateFont(size);
return font;
}
}
}

211
tests/ImageSharp.Tests/Drawing/Text/TextGraphicsOptionsTests.cs

@ -1,211 +0,0 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using SixLabors.Fonts;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing;
using Xunit;
namespace SixLabors.ImageSharp.Tests.Drawing.Text
{
public class TextGraphicsOptionsTests
{
private readonly TextGraphicsOptions newTextGraphicsOptions = new TextGraphicsOptions();
private readonly TextGraphicsOptions cloneTextGraphicsOptions = new TextGraphicsOptions().DeepClone();
[Fact]
public void CloneTextGraphicsOptionsIsNotNull() => Assert.True(this.cloneTextGraphicsOptions != null);
[Fact]
public void DefaultTextGraphicsOptionsAntialias()
{
Assert.True(this.newTextGraphicsOptions.Antialias);
Assert.True(this.cloneTextGraphicsOptions.Antialias);
}
[Fact]
public void DefaultTextGraphicsOptionsAntialiasSuppixelDepth()
{
const int Expected = 16;
Assert.Equal(Expected, this.newTextGraphicsOptions.AntialiasSubpixelDepth);
Assert.Equal(Expected, this.cloneTextGraphicsOptions.AntialiasSubpixelDepth);
}
[Fact]
public void DefaultTextGraphicsOptionsBlendPercentage()
{
const float Expected = 1F;
Assert.Equal(Expected, this.newTextGraphicsOptions.BlendPercentage);
Assert.Equal(Expected, this.cloneTextGraphicsOptions.BlendPercentage);
}
[Fact]
public void DefaultTextGraphicsOptionsColorBlendingMode()
{
const PixelColorBlendingMode Expected = PixelColorBlendingMode.Normal;
Assert.Equal(Expected, this.newTextGraphicsOptions.ColorBlendingMode);
Assert.Equal(Expected, this.cloneTextGraphicsOptions.ColorBlendingMode);
}
[Fact]
public void DefaultTextGraphicsOptionsAlphaCompositionMode()
{
const PixelAlphaCompositionMode Expected = PixelAlphaCompositionMode.SrcOver;
Assert.Equal(Expected, this.newTextGraphicsOptions.AlphaCompositionMode);
Assert.Equal(Expected, this.cloneTextGraphicsOptions.AlphaCompositionMode);
}
[Fact]
public void DefaultTextGraphicsOptionsApplyKerning()
{
const bool Expected = true;
Assert.Equal(Expected, this.newTextGraphicsOptions.ApplyKerning);
Assert.Equal(Expected, this.cloneTextGraphicsOptions.ApplyKerning);
}
[Fact]
public void DefaultTextGraphicsOptionsHorizontalAlignment()
{
const HorizontalAlignment Expected = HorizontalAlignment.Left;
Assert.Equal(Expected, this.newTextGraphicsOptions.HorizontalAlignment);
Assert.Equal(Expected, this.cloneTextGraphicsOptions.HorizontalAlignment);
}
[Fact]
public void DefaultTextGraphicsOptionsVerticalAlignment()
{
const VerticalAlignment Expected = VerticalAlignment.Top;
Assert.Equal(Expected, this.newTextGraphicsOptions.VerticalAlignment);
Assert.Equal(Expected, this.cloneTextGraphicsOptions.VerticalAlignment);
}
[Fact]
public void DefaultTextGraphicsOptionsDpiX()
{
const float Expected = 72F;
Assert.Equal(Expected, this.newTextGraphicsOptions.DpiX);
Assert.Equal(Expected, this.cloneTextGraphicsOptions.DpiX);
}
[Fact]
public void DefaultTextGraphicsOptionsDpiY()
{
const float Expected = 72F;
Assert.Equal(Expected, this.newTextGraphicsOptions.DpiY);
Assert.Equal(Expected, this.cloneTextGraphicsOptions.DpiY);
}
[Fact]
public void DefaultTextGraphicsOptionsTabWidth()
{
const float Expected = 4F;
Assert.Equal(Expected, this.newTextGraphicsOptions.TabWidth);
Assert.Equal(Expected, this.cloneTextGraphicsOptions.TabWidth);
}
[Fact]
public void DefaultTextGraphicsOptionsWrapTextWidth()
{
const float Expected = 0F;
Assert.Equal(Expected, this.newTextGraphicsOptions.WrapTextWidth);
Assert.Equal(Expected, this.cloneTextGraphicsOptions.WrapTextWidth);
}
[Fact]
public void NonDefaultClone()
{
var expected = new TextGraphicsOptions
{
AlphaCompositionMode = PixelAlphaCompositionMode.DestAtop,
Antialias = false,
AntialiasSubpixelDepth = 23,
ApplyKerning = false,
BlendPercentage = .25F,
ColorBlendingMode = PixelColorBlendingMode.HardLight,
DpiX = 46F,
DpiY = 52F,
HorizontalAlignment = HorizontalAlignment.Center,
TabWidth = 3F,
VerticalAlignment = VerticalAlignment.Bottom,
WrapTextWidth = 42F
};
TextGraphicsOptions actual = expected.DeepClone();
Assert.Equal(expected.AlphaCompositionMode, actual.AlphaCompositionMode);
Assert.Equal(expected.Antialias, actual.Antialias);
Assert.Equal(expected.AntialiasSubpixelDepth, actual.AntialiasSubpixelDepth);
Assert.Equal(expected.ApplyKerning, actual.ApplyKerning);
Assert.Equal(expected.BlendPercentage, actual.BlendPercentage);
Assert.Equal(expected.ColorBlendingMode, actual.ColorBlendingMode);
Assert.Equal(expected.DpiX, actual.DpiX);
Assert.Equal(expected.DpiY, actual.DpiY);
Assert.Equal(expected.HorizontalAlignment, actual.HorizontalAlignment);
Assert.Equal(expected.TabWidth, actual.TabWidth);
Assert.Equal(expected.VerticalAlignment, actual.VerticalAlignment);
Assert.Equal(expected.WrapTextWidth, actual.WrapTextWidth);
}
[Fact]
public void CloneIsDeep()
{
var expected = new TextGraphicsOptions();
TextGraphicsOptions actual = expected.DeepClone();
actual.AlphaCompositionMode = PixelAlphaCompositionMode.DestAtop;
actual.Antialias = false;
actual.AntialiasSubpixelDepth = 23;
actual.ApplyKerning = false;
actual.BlendPercentage = .25F;
actual.ColorBlendingMode = PixelColorBlendingMode.HardLight;
actual.DpiX = 46F;
actual.DpiY = 52F;
actual.HorizontalAlignment = HorizontalAlignment.Center;
actual.TabWidth = 3F;
actual.VerticalAlignment = VerticalAlignment.Bottom;
actual.WrapTextWidth = 42F;
Assert.NotEqual(expected.AlphaCompositionMode, actual.AlphaCompositionMode);
Assert.NotEqual(expected.Antialias, actual.Antialias);
Assert.NotEqual(expected.AntialiasSubpixelDepth, actual.AntialiasSubpixelDepth);
Assert.NotEqual(expected.ApplyKerning, actual.ApplyKerning);
Assert.NotEqual(expected.BlendPercentage, actual.BlendPercentage);
Assert.NotEqual(expected.ColorBlendingMode, actual.ColorBlendingMode);
Assert.NotEqual(expected.DpiX, actual.DpiX);
Assert.NotEqual(expected.DpiY, actual.DpiY);
Assert.NotEqual(expected.HorizontalAlignment, actual.HorizontalAlignment);
Assert.NotEqual(expected.TabWidth, actual.TabWidth);
Assert.NotEqual(expected.VerticalAlignment, actual.VerticalAlignment);
Assert.NotEqual(expected.WrapTextWidth, actual.WrapTextWidth);
}
[Fact]
public void ExplicitCastOfGraphicsOptions()
{
TextGraphicsOptions textOptions = new GraphicsOptions
{
Antialias = false,
AntialiasSubpixelDepth = 99
};
Assert.False(textOptions.Antialias);
Assert.Equal(99, textOptions.AntialiasSubpixelDepth);
}
[Fact]
public void ImplicitCastToGraphicsOptions()
{
var textOptions = new TextGraphicsOptions
{
Antialias = false,
AntialiasSubpixelDepth = 99
};
var opt = (GraphicsOptions)textOptions;
Assert.False(opt.Antialias);
Assert.Equal(99, opt.AntialiasSubpixelDepth);
}
}
}

51
tests/ImageSharp.Tests/Drawing/Utils/QuickSortTests.cs

@ -1,51 +0,0 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
namespace SixLabors.ImageSharp.Tests.Drawing.Utils
{
using System;
using System.Linq;
using SixLabors.ImageSharp.Utils;
using Xunit;
public class QuickSortTests
{
public static readonly TheoryData<float[]> Data = new TheoryData<float[]>
{
new float[]{ 3, 2, 1 },
new float[0],
new float[] { 42},
new float[] { 1, 2},
new float[] { 2, 1},
new float[] { 5, 1, 2, 3, 0}
};
[Theory]
[MemberData(nameof(Data))]
public void Sort(float[] data)
{
float[] expected = data.ToArray();
Array.Sort(expected);
QuickSort.Sort(data);
Assert.Equal(expected, data);
}
[Fact]
public void SortSlice()
{
float[] data = { 3, 2, 1, 0, -1 };
Span<float> slice = data.AsSpan(1, 3);
QuickSort.Sort(slice);
float[] actual = slice.ToArray();
float[] expected = { 0, 1, 2 };
Assert.Equal(actual, expected);
}
}
}

10
tests/ImageSharp.Tests/GraphicsOptionsTests.cs

@ -86,15 +86,5 @@ namespace SixLabors.ImageSharp.Tests
Assert.NotEqual(expected, actual, graphicsOptionsComparer);
}
[Fact]
public void IsOpaqueColor()
{
Assert.True(new GraphicsOptions().IsOpaqueColorWithoutBlending(Rgba32.Red));
Assert.False(new GraphicsOptions { BlendPercentage = .5F }.IsOpaqueColorWithoutBlending(Rgba32.Red));
Assert.False(new GraphicsOptions().IsOpaqueColorWithoutBlending(Rgba32.Transparent));
Assert.False(new GraphicsOptions { ColorBlendingMode = PixelColorBlendingMode.Lighten, BlendPercentage = 1F }.IsOpaqueColorWithoutBlending(Rgba32.Red));
Assert.False(new GraphicsOptions { ColorBlendingMode = PixelColorBlendingMode.Normal, AlphaCompositionMode = PixelAlphaCompositionMode.DestOver, BlendPercentage = 1f }.IsOpaqueColorWithoutBlending(Rgba32.Red));
}
}
}

22
tests/ImageSharp.Tests/Image/ImageTests.WrapMemory.cs

@ -1,4 +1,4 @@
// Copyright (c) Six Labors and contributors.
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System;
@ -11,7 +11,6 @@ using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Common.Helpers;
using SixLabors.ImageSharp.Metadata;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.Shapes;
using SixLabors.ImageSharp.Processing;
using Xunit;
@ -60,13 +59,13 @@ namespace SixLabors.ImageSharp.Tests
{
this.bitmap.UnlockBits(this.bmpData);
}
this.IsDisposed = true;
}
public override unsafe Span<Bgra32> GetSpan()
{
void* ptr = (void*) this.bmpData.Scan0;
void* ptr = (void*)this.bmpData.Scan0;
return new Span<Bgra32>(ptr, this.length);
}
@ -119,7 +118,11 @@ namespace SixLabors.ImageSharp.Tests
using (var image = Image.WrapMemory(memory, bmp.Width, bmp.Height))
{
Assert.Equal(memory, image.GetPixelMemory());
image.Mutate(c => c.Fill(bg).Fill(fg, new RectangularPolygon(10, 10, 10, 10)));
image.GetPixelSpan().Fill(bg);
for (var i = 10; i < 20; i++)
{
image.GetPixelRowSpan(i).Slice(10, 10).Fill(fg);
}
}
Assert.False(memoryManager.IsDisposed);
@ -150,7 +153,12 @@ namespace SixLabors.ImageSharp.Tests
using (var image = Image.WrapMemory(memoryManager, bmp.Width, bmp.Height))
{
Assert.Equal(memoryManager.Memory, image.GetPixelMemory());
image.Mutate(c => c.Fill(bg).Fill(fg, new RectangularPolygon(10, 10, 10, 10)));
image.GetPixelSpan().Fill(bg);
for (var i = 10; i < 20; i++)
{
image.GetPixelRowSpan(i).Slice(10, 10).Fill(fg);
}
}
Assert.True(memoryManager.IsDisposed);
@ -167,4 +175,4 @@ namespace SixLabors.ImageSharp.Tests
!TestEnvironment.Is64BitProcess || TestHelpers.ImageSharpBuiltAgainst != "netcoreapp2.1";
}
}
}
}

1
tests/ImageSharp.Tests/ImageSharp.Tests.csproj

@ -38,7 +38,6 @@
<ItemGroup>
<ProjectReference Include="..\..\src\ImageSharp\ImageSharp.csproj" />
<ProjectReference Include="..\..\src\ImageSharp.Drawing\ImageSharp.Drawing.csproj" />
</ItemGroup>
</Project>

51
tests/ImageSharp.Tests/Issues/Issue412.cs

@ -1,51 +0,0 @@
using SixLabors.Primitives;
using Xunit;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing;
namespace SixLabors.ImageSharp.Tests.Issues
{
public class Issue412
{
[Theory]
[WithBlankImages(40, 30, PixelTypes.Rgba32)]
public void AllPixelsExpectedToBeRedWhenAntialiasedDisabled<TPixel>(TestImageProvider<TPixel> provider) where TPixel : struct, IPixel<TPixel>
{
using (Image<TPixel> image = provider.GetImage())
{
image.Mutate(
context =>
{
for (var i = 0; i < 40; ++i)
{
context.DrawLines(
new GraphicsOptions { Antialias = false },
Color.Black,
1,
new PointF(i, 0.1066f),
new PointF(i, 10.1066f));
context.DrawLines(
new GraphicsOptions { Antialias = false },
Color.Red,
1,
new PointF(i, 15.1066f),
new PointF(i, 25.1066f));
}
});
image.DebugSave(provider);
for (var y = 15; y < 25; y++)
{
for (var x = 0; x < 40; x++)
{
TPixel red = Color.Red.ToPixel<TPixel>();
Assert.True(red.Equals(image[x, y]), $"expected {Color.Red} but found {image[x, y]} at [{x}, {y}]");
}
}
}
}
}
}

7
tests/ImageSharp.Tests/TestUtilities/ImageProviders/SolidProvider.cs

@ -1,7 +1,8 @@
// Copyright (c) Six Labors and contributors.
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing;
using Xunit.Abstractions;
@ -55,7 +56,7 @@ namespace SixLabors.ImageSharp.Tests
Image<TPixel> image = base.GetImage();
Color color = new Rgba32(this.r, this.g, this.b, this.a);
image.Mutate(x => x.Fill(color));
image.GetPixelSpan().Fill(color.ToPixel<TPixel>());
return image;
}
@ -78,4 +79,4 @@ namespace SixLabors.ImageSharp.Tests
}
}
}
}
}

46
tests/ImageSharp.Tests/TestUtilities/Tests/TestEnvironmentTests.cs

@ -1,4 +1,4 @@
// Copyright (c) Six Labors and contributors.
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System;
@ -33,28 +33,28 @@ namespace SixLabors.ImageSharp.Tests
Assert.True(Directory.Exists(path));
}
/// <summary>
/// We need this test to make sure that the netcoreapp2.1 test execution actually covers the netcoreapp2.1 build configuration of ImageSharp.
/// </summary>
[Fact]
public void ImageSharpAssemblyUnderTest_MatchesExpectedTargetFramework()
{
this.Output.WriteLine("NetCoreVersion: " + TestEnvironment.NetCoreVersion);
this.Output.WriteLine("ImageSharpBuiltAgainst: " + TestHelpers.ImageSharpBuiltAgainst);
if (string.IsNullOrEmpty(TestEnvironment.NetCoreVersion))
{
this.Output.WriteLine("Not running under .NET Core!");
}
else if (TestEnvironment.NetCoreVersion.StartsWith("2.1"))
{
Assert.Equal("netcoreapp2.1", TestHelpers.ImageSharpBuiltAgainst);
}
else
{
Assert.Equal("netstandard2.0", TestHelpers.ImageSharpBuiltAgainst);
}
}
///// <summary>
///// We need this test to make sure that the netcoreapp2.1 test execution actually covers the netcoreapp2.1 build configuration of ImageSharp.
///// </summary>
//[Fact]
//public void ImageSharpAssemblyUnderTest_MatchesExpectedTargetFramework()
//{
// this.Output.WriteLine("NetCoreVersion: " + TestEnvironment.NetCoreVersion);
// this.Output.WriteLine("ImageSharpBuiltAgainst: " + TestHelpers.ImageSharpBuiltAgainst);
// if (string.IsNullOrEmpty(TestEnvironment.NetCoreVersion))
// {
// this.Output.WriteLine("Not running under .NET Core!");
// }
// else if (TestEnvironment.NetCoreVersion.StartsWith("2.1"))
// {
// Assert.Equal("netcoreapp2.1", TestHelpers.ImageSharpBuiltAgainst);
// }
// else
// {
// Assert.Equal("netstandard2.0", TestHelpers.ImageSharpBuiltAgainst);
// }
//}
[Fact]
public void SolutionDirectoryFullPath()

Loading…
Cancel
Save