Browse Source

Merge branch 'refs/heads/pr/37'

af/merge-core
James Jackson-South 10 years ago
parent
commit
57d12c6f7e
  1. 14
      ImageSharp.v2.ncrunchsolution
  2. 5
      global.json
  3. 297
      src/ImageSharp/Drawing/Brushes/Brushes.cs
  4. 35
      src/ImageSharp/Drawing/Brushes/IBrush.cs
  5. 108
      src/ImageSharp/Drawing/Brushes/ImageBrush.cs
  6. 177
      src/ImageSharp/Drawing/Brushes/PatternBrush.cs
  7. 28
      src/ImageSharp/Drawing/Brushes/Processors/IBrushApplicator.cs
  8. 107
      src/ImageSharp/Drawing/Brushes/SolidBrush.cs
  9. 566
      src/ImageSharp/Drawing/Draw.cs
  10. 192
      src/ImageSharp/Drawing/Fill.cs
  11. 32
      src/ImageSharp/Drawing/GraphicsOptions.cs
  12. 101
      src/ImageSharp/Drawing/Paths/BezierLineSegment.cs
  13. 25
      src/ImageSharp/Drawing/Paths/ILineSegment.cs
  14. 52
      src/ImageSharp/Drawing/Paths/IPath.cs
  15. 285
      src/ImageSharp/Drawing/Paths/InternalPath.cs
  16. 56
      src/ImageSharp/Drawing/Paths/LinearLineSegment.cs
  17. 81
      src/ImageSharp/Drawing/Paths/Path.cs
  18. 39
      src/ImageSharp/Drawing/Paths/PointInfo.cs
  19. 31
      src/ImageSharp/Drawing/Pens/IPen.cs
  20. 315
      src/ImageSharp/Drawing/Pens/Pen.cs
  21. 208
      src/ImageSharp/Drawing/Pens/Pens.cs
  22. 27
      src/ImageSharp/Drawing/Pens/Processors/ColoredPointInfo.cs
  23. 35
      src/ImageSharp/Drawing/Pens/Processors/IPenApplicator.cs
  24. 195
      src/ImageSharp/Drawing/Processors/DrawPathProcessor.cs
  25. 97
      src/ImageSharp/Drawing/Processors/FillProcessor.cs
  26. 131
      src/ImageSharp/Drawing/Processors/FillShapeProcessor.cs
  27. 68
      src/ImageSharp/Drawing/Shapes/BezierPolygon.cs
  28. 4924
      src/ImageSharp/Drawing/Shapes/Clipper.cs
  29. 196
      src/ImageSharp/Drawing/Shapes/ComplexPolygon.cs
  30. 34
      src/ImageSharp/Drawing/Shapes/IShape.cs
  31. 62
      src/ImageSharp/Drawing/Shapes/LinearPolygon.cs
  32. 122
      src/ImageSharp/Drawing/Shapes/Polygon.cs
  33. 13
      src/ImageSharp/Numerics/Rectangle.cs
  34. 334
      src/ImageSharp/Numerics/RectangleF.cs
  35. 64
      tests/ImageSharp.Benchmarks/Drawing/DrawBeziers.cs
  36. 62
      tests/ImageSharp.Benchmarks/Drawing/DrawLines.cs
  37. 62
      tests/ImageSharp.Benchmarks/Drawing/DrawPolygon.cs
  38. 59
      tests/ImageSharp.Benchmarks/Drawing/FillPolygon.cs
  39. 51
      tests/ImageSharp.Benchmarks/Drawing/FillWithPattern.cs
  40. 103
      tests/ImageSharp.Tests/Drawing/BeziersTests.cs
  41. 102
      tests/ImageSharp.Tests/Drawing/DrawPathTests.cs
  42. 235
      tests/ImageSharp.Tests/Drawing/FillPatternTests.cs
  43. 89
      tests/ImageSharp.Tests/Drawing/FillSolidBrushTests.cs
  44. 195
      tests/ImageSharp.Tests/Drawing/LineComplexPolygonTests.cs
  45. 221
      tests/ImageSharp.Tests/Drawing/LineTests.cs
  46. 121
      tests/ImageSharp.Tests/Drawing/PolygonTests.cs
  47. 101
      tests/ImageSharp.Tests/Drawing/SolidBezierTests.cs
  48. 141
      tests/ImageSharp.Tests/Drawing/SolidComplexPolygonTests.cs
  49. 174
      tests/ImageSharp.Tests/Drawing/SolidPolygonTests.cs
  50. 10
      tests/ImageSharp.Tests/FileTestBase.cs

14
ImageSharp.v2.ncrunchsolution

@ -0,0 +1,14 @@
<SolutionConfiguration>
<FileVersion>1</FileVersion>
<InferProjectReferencesUsingAssemblyNames>false</InferProjectReferencesUsingAssemblyNames>
<AllowParallelTestExecution>false</AllowParallelTestExecution>
<AllowTestsToRunInParallelWithThemselves>true</AllowTestsToRunInParallelWithThemselves>
<FrameworkUtilisationTypeForNUnit>UseDynamicAnalysis</FrameworkUtilisationTypeForNUnit>
<FrameworkUtilisationTypeForGallio>UseStaticAnalysis</FrameworkUtilisationTypeForGallio>
<FrameworkUtilisationTypeForMSpec>UseStaticAnalysis</FrameworkUtilisationTypeForMSpec>
<FrameworkUtilisationTypeForMSTest>UseStaticAnalysis</FrameworkUtilisationTypeForMSTest>
<FrameworkUtilisationTypeForXUnit2>UseDynamicAnalysis</FrameworkUtilisationTypeForXUnit2>
<NCrunchCacheStoragePath />
<MetricsExclusionList>
</MetricsExclusionList>
</SolutionConfiguration>

5
global.json

@ -1,3 +1,6 @@
{
"projects": [ "src" ]
"projects": [ "src" ],
"sdk": {
"version": "1.0.0-preview2-003121"
}
}

297
src/ImageSharp/Drawing/Brushes/Brushes.cs

@ -0,0 +1,297 @@
// <copyright file="Brushes.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp.Drawing.Brushes
{
using System;
using System.Collections.Generic;
using System.Numerics;
using System.Threading.Tasks;
/// <summary>
/// A collection of methods for creating brushes
/// </summary>
public class Brushes
{
/// <summary>
/// Create as brush that will paint a solid color
/// </summary>
/// <param name="color">The color.</param>
/// <returns>A Brush</returns>
public static SolidBrush Solid(Color color)
=> new SolidBrush(color);
/// <summary>
/// Create as brush that will paint a Percent10 Hatch Pattern with
/// in the specified foreground color and a transparent background
/// </summary>
/// <param name="foreColor">Color of the foreground.</param>
/// <returns>A Brush</returns>
public static PatternBrush Percent10(Color foreColor)
=> new PatternBrush(Brushes<Color, uint>.Percent10(foreColor, Color.Transparent));
/// <summary>
/// Create as brush that will paint a Percent10 Hatch Pattern with
/// in the specified foreground and background colors
/// </summary>
/// <param name="foreColor">Color of the foreground.</param>
/// <param name="backColor">Color of the background.</param>
/// <returns>A Brush</returns>
public static PatternBrush Percent10(Color foreColor, Color backColor)
=> new PatternBrush(Brushes<Color, uint>.Percent10(foreColor, backColor));
/// <summary>
/// Create as brush that will paint a Percent20 Hatch Pattern with
/// in the specified foreground color and a transparent background
/// </summary>
/// <param name="foreColor">Color of the foreground.</param>
/// <returns>A Brush</returns>
public static PatternBrush Percent20(Color foreColor)
=> new PatternBrush(Brushes<Color, uint>.Percent20(foreColor, Color.Transparent));
/// <summary>
/// Create as brush that will paint a Percent20 Hatch Pattern with
/// in the specified foreground and background colors
/// </summary>
/// <param name="foreColor">Color of the foreground.</param>
/// <param name="backColor">Color of the background.</param>
/// <returns>A Brush</returns>
public static PatternBrush Percent20(Color foreColor, Color backColor)
=> new PatternBrush(Brushes<Color, uint>.Percent20(foreColor, backColor));
/// <summary>
/// Create as brush that will paint a Horizontal Hatch Pattern with
/// in the specified foreground color and a transparent background
/// </summary>
/// <param name="foreColor">Color of the foreground.</param>
/// <returns>A Brush</returns>
public static PatternBrush Horizontal(Color foreColor)
=> new PatternBrush(Brushes<Color, uint>.Horizontal(foreColor, Color.Transparent));
/// <summary>
/// Create as brush that will paint a Horizontal Hatch Pattern with
/// in the specified foreground and background colors
/// </summary>
/// <param name="foreColor">Color of the foreground.</param>
/// <param name="backColor">Color of the background.</param>
/// <returns>A Brush</returns>
public static PatternBrush Horizontal(Color foreColor, Color backColor)
=> new PatternBrush(Brushes<Color, uint>.Horizontal(foreColor, backColor));
/// <summary>
/// Create as brush that will paint a Min Hatch Pattern with
/// in the specified foreground color and a transparent background
/// </summary>
/// <param name="foreColor">Color of the foreground.</param>
/// <returns>A Brush</returns>
public static PatternBrush Min(Color foreColor)
=> new PatternBrush(Brushes<Color, uint>.Min(foreColor, Color.Transparent));
/// <summary>
/// Create as brush that will paint a Min Hatch Pattern with
/// in the specified foreground and background colors
/// </summary>
/// <param name="foreColor">Color of the foreground.</param>
/// <param name="backColor">Color of the background.</param>
/// <returns>A Brush</returns>
public static PatternBrush Min(Color foreColor, Color backColor)
=> new PatternBrush(Brushes<Color, uint>.Min(foreColor, backColor));
/// <summary>
/// Create as brush that will paint a Vertical Hatch Pattern with
/// in the specified foreground color and a transparent background
/// </summary>
/// <param name="foreColor">Color of the foreground.</param>
/// <returns>A Brush</returns>
public static PatternBrush Vertical(Color foreColor)
=> new PatternBrush(Brushes<Color, uint>.Vertical(foreColor, Color.Transparent));
/// <summary>
/// Create as brush that will paint a Vertical Hatch Pattern with
/// in the specified foreground and background colors
/// </summary>
/// <param name="foreColor">Color of the foreground.</param>
/// <param name="backColor">Color of the background.</param>
/// <returns>A Brush</returns>
public static PatternBrush Vertical(Color foreColor, Color backColor)
=> new PatternBrush(Brushes<Color, uint>.Vertical(foreColor, backColor));
/// <summary>
/// Create as brush that will paint a Forward Diagonal Hatch Pattern with
/// in the specified foreground color and a transparent background
/// </summary>
/// <param name="foreColor">Color of the foreground.</param>
/// <returns>A Brush</returns>
public static PatternBrush ForwardDiagonal(Color foreColor)
=> new PatternBrush(Brushes<Color, uint>.ForwardDiagonal(foreColor, Color.Transparent));
/// <summary>
/// Create as brush that will paint a Forward Diagonal Hatch Pattern with
/// in the specified foreground and background colors
/// </summary>
/// <param name="foreColor">Color of the foreground.</param>
/// <param name="backColor">Color of the background.</param>
/// <returns>A Brush</returns>
public static PatternBrush ForwardDiagonal(Color foreColor, Color backColor)
=> new PatternBrush(Brushes<Color, uint>.ForwardDiagonal(foreColor, backColor));
/// <summary>
/// Create as brush that will paint a Backward Diagonal Hatch Pattern with
/// in the specified foreground color and a transparent background
/// </summary>
/// <param name="foreColor">Color of the foreground.</param>
/// <returns>A Brush</returns>
public static PatternBrush BackwardDiagonal(Color foreColor)
=> new PatternBrush(Brushes<Color, uint>.BackwardDiagonal(foreColor, Color.Transparent));
/// <summary>
/// Create as brush that will paint a Backward Diagonal Hatch Pattern with
/// in the specified foreground and background colors
/// </summary>
/// <param name="foreColor">Color of the foreground.</param>
/// <param name="backColor">Color of the background.</param>
/// <returns>A Brush</returns>
public static PatternBrush BackwardDiagonal(Color foreColor, Color backColor)
=> new PatternBrush(Brushes<Color, uint>.BackwardDiagonal(foreColor, backColor));
}
/// <summary>
/// A collection of methods for creating brushes.
/// </summary>
/// <typeparam name="TColor">The type of the color.</typeparam>
/// <typeparam name="TPacked">The type of the packed.</typeparam>
/// <returns>A Brush</returns>
public partial class Brushes<TColor, TPacked>
where TColor : struct, IPackedPixel<TPacked>
where TPacked : struct
{
// note 2d arrays when configured using initalizer look inverted
// ---> Y axis
// ^
// | X - axis
// |
// see PatternBrush for details about how to make new patterns work
private static readonly bool[,] Percent10Pattern = new bool[,]
{
{ true, false, false, false },
{ false, false, false, false },
{ false, false, true, false },
{ false, false, false, false }
};
private static readonly bool[,] Percent20Pattern = new bool[,]
{
{ true, false, true, false },
{ false, false, false, false },
{ false, true, false, true },
{ false, false, false, false }
};
private static readonly bool[,] HorizontalPattern = new bool[,]
{
{ false, true, false, false },
};
private static readonly bool[,] MinPattern = new bool[,]
{
{ false, false, false, true },
};
private static readonly bool[,] VerticalPattern = new bool[,]
{
{ false },
{ true },
{ false },
{ false }
};
private static readonly bool[,] ForwardDiagonalPattern = new bool[,]
{
{ true, false, false, false },
{ false, true, false, false },
{ false, false, true, false },
{ false, false, false, true }
};
private static readonly bool[,] BackwardDiagonalPattern = new bool[,]
{
{ false, false, false, true },
{ false, false, true, false },
{ false, true, false, false },
{ true, false, false, false }
};
/// <summary>
/// Create as brush that will paint a solid color
/// </summary>
/// <param name="color">The color.</param>
/// <returns>A Brush</returns>
public static SolidBrush<TColor, TPacked> Solid(TColor color)
=> new SolidBrush<TColor, TPacked>(color);
/// <summary>
/// Create as brush that will paint a Percent10 Hatch Pattern within the specified colors
/// </summary>
/// <param name="foreColor">Color of the foreground.</param>
/// <param name="backColor">Color of the background.</param>
/// <returns>A Brush</returns>
public static PatternBrush<TColor, TPacked> Percent10(TColor foreColor, TColor backColor)
=> new PatternBrush<TColor, TPacked>(foreColor, backColor, Percent10Pattern);
/// <summary>
/// Create as brush that will paint a Percent20 Hatch Pattern within the specified colors
/// </summary>
/// <param name="foreColor">Color of the foreground.</param>
/// <param name="backColor">Color of the background.</param>
/// <returns>A Brush</returns>
public static PatternBrush<TColor, TPacked> Percent20(TColor foreColor, TColor backColor)
=> new PatternBrush<TColor, TPacked>(foreColor, backColor, Percent20Pattern);
/// <summary>
/// Create as brush that will paint a Horizontal Hatch Pattern within the specified colors
/// </summary>
/// <param name="foreColor">Color of the foreground.</param>
/// <param name="backColor">Color of the background.</param>
/// <returns>A Brush</returns>
public static PatternBrush<TColor, TPacked> Horizontal(TColor foreColor, TColor backColor)
=> new PatternBrush<TColor, TPacked>(foreColor, backColor, HorizontalPattern);
/// <summary>
/// Create as brush that will paint a Min Hatch Pattern within the specified colors
/// </summary>
/// <param name="foreColor">Color of the foreground.</param>
/// <param name="backColor">Color of the background.</param>
/// <returns>A Brush</returns>
public static PatternBrush<TColor, TPacked> Min(TColor foreColor, TColor backColor)
=> new PatternBrush<TColor, TPacked>(foreColor, backColor, MinPattern);
/// <summary>
/// Create as brush that will paint a Vertical Hatch Pattern within the specified colors
/// </summary>
/// <param name="foreColor">Color of the foreground.</param>
/// <param name="backColor">Color of the background.</param>
/// <returns>A Brush</returns>
public static PatternBrush<TColor, TPacked> Vertical(TColor foreColor, TColor backColor)
=> new PatternBrush<TColor, TPacked>(foreColor, backColor, VerticalPattern);
/// <summary>
/// Create as brush that will paint a Forward Diagonal Hatch Pattern within the specified colors
/// </summary>
/// <param name="foreColor">Color of the foreground.</param>
/// <param name="backColor">Color of the background.</param>
/// <returns>A Brush</returns>
public static PatternBrush<TColor, TPacked> ForwardDiagonal(TColor foreColor, TColor backColor)
=> new PatternBrush<TColor, TPacked>(foreColor, backColor, ForwardDiagonalPattern);
/// <summary>
/// Create as brush that will paint a Backward Diagonal Hatch Pattern within the specified colors
/// </summary>
/// <param name="foreColor">Color of the foreground.</param>
/// <param name="backColor">Color of the background.</param>
/// <returns>A Brush</returns>
public static PatternBrush<TColor, TPacked> BackwardDiagonal(TColor foreColor, TColor backColor)
=> new PatternBrush<TColor, TPacked>(foreColor, backColor, BackwardDiagonalPattern);
}
}

35
src/ImageSharp/Drawing/Brushes/IBrush.cs

@ -0,0 +1,35 @@
// <copyright file="IBrush.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp.Drawing
{
using System;
using Processors;
/// <summary>
/// Brush represents a logical configuration of a brush whcih can be used to source pixel colors
/// </summary>
/// <typeparam name="TColor">The type of the color.</typeparam>
/// <typeparam name="TPacked">The type of the packed.</typeparam>
/// <remarks>
/// A brush is a simple class that will return an <see cref="IBrushApplicator{TColor, TPacked}" /> that will perform the
/// logic for converting a pixel location to a <typeparamref name="TColor"/>.
/// </remarks>
public interface IBrush<TColor, TPacked>
where TColor : struct, IPackedPixel<TPacked>
where TPacked : struct
{
/// <summary>
/// Creates the applicator for this brush.
/// </summary>
/// <param name="region">The region the brush will be applied to.</param>
/// <returns>The brush applicator 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>
IBrushApplicator<TColor, TPacked> CreateApplicator(RectangleF region);
}
}

108
src/ImageSharp/Drawing/Brushes/ImageBrush.cs

@ -0,0 +1,108 @@
// <copyright file="ImageBrush.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp.Drawing.Brushes
{
using System;
using System.Collections.Generic;
using System.Numerics;
using System.Threading.Tasks;
using Processors;
/// <summary>
/// Provides an implementaion of a solid brush for painting with repeating images.
/// </summary>
public class ImageBrush : ImageBrush<Color, uint>
{
/// <summary>
/// Initializes a new instance of the <see cref="ImageBrush" /> class.
/// </summary>
/// <param name="color">The color.</param>
public ImageBrush(IImageBase<Color, uint> image)
: base(image)
{
}
}
/// <summary>
/// Provides an implementaion of a solid brush for painting solid color areas.
/// </summary>
/// <typeparam name="TColor">The type of the color.</typeparam>
/// <typeparam name="TPacked">The type of the packed.</typeparam>
public class ImageBrush<TColor, TPacked> : IBrush<TColor, TPacked>
where TColor : struct, IPackedPixel<TPacked>
where TPacked : struct
{
private readonly IImageBase<TColor, TPacked> image;
/// <summary>
/// Initializes a new instance of the <see cref="SolidBrush{TColor, TPacked}"/> class.
/// </summary>
/// <param name="color">The color.</param>
public ImageBrush(IImageBase<TColor, TPacked> image)
{
this.image = image;
}
/// <summary>
/// Creates the applicator for this brush.
/// </summary>
/// <param name="region">The region the brush will be applied to.</param>
/// <returns>
/// The brush applicator for this brush
/// </returns>
/// <remarks>
/// The <paramref name="region" /> when being applied to things like shapes would ussually be the
/// bounding box of the shape not necessarily the bounds of the whole image
/// </remarks>
public IBrushApplicator<TColor, TPacked> CreateApplicator(RectangleF region)
{
return new ImageBrushApplicator(this.image, region);
}
private class ImageBrushApplicator : IBrushApplicator<TColor, TPacked>
{
private readonly PixelAccessor<TColor, TPacked> source;
private readonly int yLength;
private readonly int xLength;
private readonly Vector2 offset;
private readonly float YOffset;
public ImageBrushApplicator(IImageBase<TColor, TPacked> image, RectangleF region)
{
this.source = image.Lock();
this.xLength = image.Width;
this.yLength = image.Height;
this.offset = new Vector2((float)Math.Max(Math.Floor(region.Top), 0),
(float)Math.Max(Math.Floor(region.Left), 0));
}
/// <summary>
/// Gets the color for a single pixel.
/// </summary>
/// <param name="point">The point.</param>
/// <returns>
/// The color
/// </returns>
public TColor GetColor(Vector2 point)
{
//offset the requested pixel by the value in the rectangle (the shapes position)
point = point - offset;
var x = (int)point.X % this.xLength;
var y = (int)point.Y % this.yLength;
return source[x, y];
}
/// <summary>
/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
/// </summary>
public void Dispose()
{
source.Dispose();
}
}
}
}

177
src/ImageSharp/Drawing/Brushes/PatternBrush.cs

@ -0,0 +1,177 @@
// <copyright file="PatternBrush.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp.Drawing.Brushes
{
using System;
using System.Collections.Generic;
using System.Numerics;
using System.Threading.Tasks;
using Processors;
/// <summary>
/// Provides an implementaion 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 weather to draw the foregound color or the background color.
/// so to create a new bool[,] with your flags
///
/// For example if you wated to create a diagonal line that repeat every 4 pixels you would use a pattern like so
/// 1000
/// 0100
/// 0010
/// 0001
///
/// or you want a horrizontal stripe which is 3 pixels apart you would use a pattern like
/// 1
/// 0
/// 0
///
/// warning when use array initallzer across multiple lines the bools look inverted i.e.
/// new bool[,]{
/// {true, false, false},
/// {false,true, false}
/// }
/// would be
/// 10
/// 01
/// 00
/// </remarks>
public class PatternBrush : PatternBrush<Color, uint>
{
/// <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)
: base(foreColor, backColor, pattern)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="PatternBrush"/> class.
/// </summary>
/// <param name="brush">The brush.</param>
internal PatternBrush(PatternBrush<Color, uint> brush)
: base(brush)
{
}
}
/// <summary>
/// Provides an implementaion of a pattern brush for painting patterns.
/// </summary>
/// <typeparam name="TColor">The type of the color.</typeparam>
/// <typeparam name="TPacked">The type of the packed.</typeparam>
public partial class PatternBrush<TColor, TPacked> : IBrush<TColor, TPacked>
where TColor : struct, IPackedPixel<TPacked>
where TPacked : struct
{
private readonly TColor[][] pattern;
private readonly int stride;
/// <summary>
/// Initializes a new instance of the <see cref="PatternBrush{TColor, TPacked}"/> 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(TColor foreColor, TColor backColor, bool[,] pattern)
{
this.stride = pattern.GetLength(1);
// convert the multidimension array into a jagged one.
var height = pattern.GetLength(0);
this.pattern = new TColor[height][];
for (var x = 0; x < height; x++)
{
this.pattern[x] = new TColor[stride];
for (var y = 0; y < stride; y++)
{
if (pattern[x, y])
{
this.pattern[x][y] = foreColor;
}else
{
this.pattern[x][y] = backColor;
}
}
}
}
/// <summary>
/// Initializes a new instance of the <see cref="PatternBrush{TColor, TPacked}"/> class.
/// </summary>
/// <param name="brush">The brush.</param>
internal PatternBrush(PatternBrush<TColor, TPacked> brush)
{
this.pattern = brush.pattern;
this.stride = brush.stride;
}
/// <summary>
/// Creates the applicator for this bursh.
/// </summary>
/// <param name="region">The region the brush will be applied to.</param>
/// <returns>
/// The brush applicator for this brush
/// </returns>
/// <remarks>
/// The <paramref name="region" /> when being applied to things like shapes would ussually be the
/// bounding box of the shape not necessarily the bounds of the whole image
/// </remarks>
public IBrushApplicator<TColor, TPacked> CreateApplicator(RectangleF region)
{
return new PatternBrushApplicator(this.pattern, this.stride);
}
private class PatternBrushApplicator : IBrushApplicator<TColor, TPacked>
{
private readonly int xLength;
private readonly int stride;
private readonly TColor[][] pattern;
/// <summary>
/// Initializes a new instance of the <see cref="PatternBrushApplicator" /> class.
/// </summary>
/// <param name="foreColor">Color of the fore.</param>
/// <param name="backColor">Color of the back.</param>
/// <param name="pattern">The pattern.</param>
/// <param name="stride">The stride.</param>
public PatternBrushApplicator(TColor[][] pattern, int stride)
{
this.pattern = pattern;
this.xLength = pattern.Length;
this.stride = stride;
}
/// <summary>
/// Gets the color for a single pixel.
/// </summary>
/// <param name="point">The point.</param>
/// <returns>
/// The color
/// </returns>
public TColor GetColor(Vector2 point)
{
var x = (int)point.X % this.xLength;
var y = (int)point.Y % this.stride;
return this.pattern[x][y];
}
/// <summary>
/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
/// </summary>
public void Dispose()
{
// noop
}
}
}
}

28
src/ImageSharp/Drawing/Brushes/Processors/IBrushApplicator.cs

@ -0,0 +1,28 @@
// <copyright file="IBrushApplicator.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp.Drawing.Processors
{
using System;
using System.Numerics;
/// <summary>
/// primitive that converts a point in to a color for discoving the fill color based on an implmentation
/// </summary>
/// <typeparam name="TColor">The type of the color.</typeparam>
/// <typeparam name="TPacked">The type of the packed.</typeparam>
/// <seealso cref="System.IDisposable" />
public interface IBrushApplicator<TColor, TPacked> : IDisposable // disposable will be required if/when there is an ImageBrush
where TColor : struct, IPackedPixel<TPacked>
where TPacked : struct
{
/// <summary>
/// Gets the color for a single pixel.
/// </summary>
/// <param name="point">The point.</param>
/// <returns>The color</returns>
TColor GetColor(Vector2 point);
}
}

107
src/ImageSharp/Drawing/Brushes/SolidBrush.cs

@ -0,0 +1,107 @@
// <copyright file="SolidBrush.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp.Drawing.Brushes
{
using System;
using System.Collections.Generic;
using System.Numerics;
using System.Threading.Tasks;
using Processors;
/// <summary>
/// Provides an implementaion of a solid brush for painting solid color areas.
/// </summary>
public class SolidBrush : SolidBrush<Color, uint>
{
/// <summary>
/// Initializes a new instance of the <see cref="SolidBrush" /> class.
/// </summary>
/// <param name="color">The color.</param>
public SolidBrush(Color color)
: base(color)
{
}
}
/// <summary>
/// Provides an implementaion of a solid brush for painting solid color areas.
/// </summary>
/// <typeparam name="TColor">The type of the color.</typeparam>
/// <typeparam name="TPacked">The type of the packed.</typeparam>
public class SolidBrush<TColor, TPacked> : IBrush<TColor, TPacked>
where TColor : struct, IPackedPixel<TPacked>
where TPacked : struct
{
private readonly TColor color;
/// <summary>
/// Initializes a new instance of the <see cref="SolidBrush{TColor, TPacked}"/> class.
/// </summary>
/// <param name="color">The color.</param>
public SolidBrush(TColor color)
{
this.color = color;
}
/// <summary>
/// Gets the color.
/// </summary>
/// <value>
/// The color.
/// </value>
public TColor Color => this.color;
/// <summary>
/// Creates the applicator for this brush.
/// </summary>
/// <param name="region">The region the brush will be applied to.</param>
/// <returns>
/// The brush applicator for this brush
/// </returns>
/// <remarks>
/// The <paramref name="region" /> when being applied to things like shapes would ussually be the
/// bounding box of the shape not necessarily the bounds of the whole image
/// </remarks>
public IBrushApplicator<TColor, TPacked> CreateApplicator(RectangleF region)
{
return new SolidBrushApplicator(this.color);
}
private class SolidBrushApplicator : IBrushApplicator<TColor, TPacked>
{
private TColor color;
/// <summary>
/// Initializes a new instance of the <see cref="SolidBrushApplicator"/> class.
/// </summary>
/// <param name="color">The color.</param>
public SolidBrushApplicator(TColor color)
{
this.color = color;
}
/// <summary>
/// Gets the color for a single pixel.
/// </summary>
/// <param name="point">The point.</param>
/// <returns>
/// The color
/// </returns>
public TColor GetColor(Vector2 point)
{
return this.color;
}
/// <summary>
/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
/// </summary>
public void Dispose()
{
// noop
}
}
}
}

566
src/ImageSharp/Drawing/Draw.cs

@ -0,0 +1,566 @@
// <copyright file="Draw.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp
{
using System.Numerics;
using Drawing;
using Drawing.Brushes;
using Drawing.Paths;
using Drawing.Pens;
using Drawing.Processors;
using Drawing.Shapes;
using Processors;
/// <summary>
/// Extension methods for the <see cref="Image{TColor, TPacked}"/> type.
/// </summary>
public static partial class ImageExtensions
{
/// <summary>
/// Draws the outline of the polygon with the provided pen.
/// </summary>
/// <typeparam name="TColor">The type of the color.</typeparam>
/// <typeparam name="TPacked">The type of the packed.</typeparam>
/// <param name="source">The source.</param>
/// <param name="pen">The pen.</param>
/// <param name="shape">The shape.</param>
/// <param name="options">The options.</param>
/// <returns>
/// The Image
/// </returns>
public static Image<TColor, TPacked> DrawPolygon<TColor, TPacked>(this Image<TColor, TPacked> source, IPen<TColor, TPacked> pen, IShape shape, GraphicsOptions options)
where TColor : struct, IPackedPixel<TPacked>
where TPacked : struct
{
return source.Process(new DrawPathProcessor<TColor, TPacked>(pen, shape, options));
}
/// <summary>
/// Draws the outline of the polygon with the provided pen.
/// </summary>
/// <typeparam name="TColor">The type of the color.</typeparam>
/// <typeparam name="TPacked">The type of the packed.</typeparam>
/// <param name="source">The source.</param>
/// <param name="pen">The pen.</param>
/// <param name="shape">The shape.</param>
/// <returns>The Image</returns>
public static Image<TColor, TPacked> DrawPolygon<TColor, TPacked>(this Image<TColor, TPacked> source, IPen<TColor, TPacked> pen, IShape shape)
where TColor : struct, IPackedPixel<TPacked>
where TPacked : struct
{
return source.DrawPolygon(pen, shape, GraphicsOptions.Default);
}
/// <summary>
/// Draws the outline of the polygon with the provided brush at the provided thickness.
/// </summary>
/// <typeparam name="TColor">The type of the color.</typeparam>
/// <typeparam name="TPacked">The type of the packed.</typeparam>
/// <param name="source">The source.</param>
/// <param name="brush">The brush.</param>
/// <param name="thickness">The thickness.</param>
/// <param name="shape">The shape.</param>
/// <param name="options">The options.</param>
/// <returns>
/// The Image
/// </returns>
public static Image<TColor, TPacked> DrawPolygon<TColor, TPacked>(this Image<TColor, TPacked> source, IBrush<TColor, TPacked> brush, float thickness, IShape shape, GraphicsOptions options)
where TColor : struct, IPackedPixel<TPacked>
where TPacked : struct
{
return source.DrawPolygon(new Pen<TColor, TPacked>(brush, thickness), shape, options);
}
/// <summary>
/// Draws the outline of the polygon with the provided brush at the provided thickness.
/// </summary>
/// <typeparam name="TColor">The type of the color.</typeparam>
/// <typeparam name="TPacked">The type of the packed.</typeparam>
/// <param name="source">The source.</param>
/// <param name="brush">The brush.</param>
/// <param name="thickness">The thickness.</param>
/// <param name="shape">The shape.</param>
/// <returns>The Image</returns>
public static Image<TColor, TPacked> DrawPolygon<TColor, TPacked>(this Image<TColor, TPacked> source, IBrush<TColor, TPacked> brush, float thickness, IShape shape)
where TColor : struct, IPackedPixel<TPacked>
where TPacked : struct
{
return source.DrawPolygon(new Pen<TColor, TPacked>(brush, thickness), shape);
}
/// <summary>
/// Draws the outline of the polygon with the provided brush at the provided thickness.
/// </summary>
/// <typeparam name="TColor">The type of the color.</typeparam>
/// <typeparam name="TPacked">The type of the packed.</typeparam>
/// <param name="source">The source.</param>
/// <param name="color">The color.</param>
/// <param name="thickness">The thickness.</param>
/// <param name="shape">The shape.</param>
/// <param name="options">The options.</param>
/// <returns>
/// The Image
/// </returns>
public static Image<TColor, TPacked> DrawPolygon<TColor, TPacked>(this Image<TColor, TPacked> source, TColor color, float thickness, IShape shape, GraphicsOptions options)
where TColor : struct, IPackedPixel<TPacked>
where TPacked : struct
{
return source.DrawPolygon(new SolidBrush<TColor, TPacked>(color), thickness, shape, options);
}
/// <summary>
/// Draws the outline of the polygon with the provided brush at the provided thickness.
/// </summary>
/// <typeparam name="TColor">The type of the color.</typeparam>
/// <typeparam name="TPacked">The type of the packed.</typeparam>
/// <param name="source">The source.</param>
/// <param name="color">The color.</param>
/// <param name="thickness">The thickness.</param>
/// <param name="shape">The shape.</param>
/// <returns>The Image</returns>
public static Image<TColor, TPacked> DrawPolygon<TColor, TPacked>(this Image<TColor, TPacked> source, TColor color, float thickness, IShape shape)
where TColor : struct, IPackedPixel<TPacked>
where TPacked : struct
{
return source.DrawPolygon(new SolidBrush<TColor, TPacked>(color), thickness, shape);
}
/// <summary>
/// Draws the provided Points as a closed Linear Polygon with the provided brush at the provided thickness.
/// </summary>
/// <typeparam name="TColor">The type of the color.</typeparam>
/// <typeparam name="TPacked">The type of the packed.</typeparam>
/// <param name="source">The source.</param>
/// <param name="brush">The brush.</param>
/// <param name="thickness">The thickness.</param>
/// <param name="points">The points.</param>
/// <param name="options">The options.</param>
/// <returns>
/// The Image
/// </returns>
public static Image<TColor, TPacked> DrawPolygon<TColor, TPacked>(this Image<TColor, TPacked> source, IBrush<TColor, TPacked> brush, float thickness, Vector2[] points, GraphicsOptions options)
where TColor : struct, IPackedPixel<TPacked>
where TPacked : struct
{
return source.DrawPolygon(new Pen<TColor, TPacked>(brush, thickness), new Polygon(new LinearLineSegment(points)), options);
}
/// <summary>
/// Draws the provided Points as a closed Linear Polygon with the provided brush at the provided thickness.
/// </summary>
/// <typeparam name="TColor">The type of the color.</typeparam>
/// <typeparam name="TPacked">The type of the packed.</typeparam>
/// <param name="source">The source.</param>
/// <param name="brush">The brush.</param>
/// <param name="thickness">The thickness.</param>
/// <param name="points">The points.</param>
/// <returns>The Image</returns>
public static Image<TColor, TPacked> DrawPolygon<TColor, TPacked>(this Image<TColor, TPacked> source, IBrush<TColor, TPacked> brush, float thickness, Vector2[] points)
where TColor : struct, IPackedPixel<TPacked>
where TPacked : struct
{
return source.DrawPolygon(new Pen<TColor, TPacked>(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>
/// <typeparam name="TColor">The type of the color.</typeparam>
/// <typeparam name="TPacked">The type of the packed.</typeparam>
/// <param name="source">The source.</param>
/// <param name="color">The color.</param>
/// <param name="thickness">The thickness.</param>
/// <param name="points">The points.</param>
/// <returns>The Image</returns>
public static Image<TColor, TPacked> DrawPolygon<TColor, TPacked>(this Image<TColor, TPacked> source, TColor color, float thickness, Vector2[] points)
where TColor : struct, IPackedPixel<TPacked>
where TPacked : struct
{
return source.DrawPolygon(new SolidBrush<TColor, TPacked>(color), thickness, points);
}
/// <summary>
/// Draws the provided Points as a closed Linear Polygon with the provided brush at the provided thickness.
/// </summary>
/// <typeparam name="TColor">The type of the color.</typeparam>
/// <typeparam name="TPacked">The type of the packed.</typeparam>
/// <param name="source">The source.</param>
/// <param name="color">The color.</param>
/// <param name="thickness">The thickness.</param>
/// <param name="points">The points.</param>
/// <param name="options">The options.</param>
/// <returns>
/// The Image
/// </returns>
public static Image<TColor, TPacked> DrawPolygon<TColor, TPacked>(this Image<TColor, TPacked> source, TColor color, float thickness, Vector2[] points, GraphicsOptions options)
where TColor : struct, IPackedPixel<TPacked>
where TPacked : struct
{
return source.DrawPolygon(new SolidBrush<TColor, TPacked>(color), thickness, points, options);
}
/// <summary>
/// Draws the provided Points as a closed Linear Polygon with the provided Pen.
/// </summary>
/// <typeparam name="TColor">The type of the color.</typeparam>
/// <typeparam name="TPacked">The type of the packed.</typeparam>
/// <param name="source">The source.</param>
/// <param name="pen">The pen.</param>
/// <param name="points">The points.</param>
/// <param name="options">The options.</param>
/// <returns>
/// The Image
/// </returns>
public static Image<TColor, TPacked> DrawPolygon<TColor, TPacked>(this Image<TColor, TPacked> source, IPen<TColor, TPacked> pen, Vector2[] points, GraphicsOptions options)
where TColor : struct, IPackedPixel<TPacked>
where TPacked : struct
{
return source.DrawPolygon(pen, new Polygon(new LinearLineSegment(points)), options);
}
/// <summary>
/// Draws the provided Points as a closed Linear Polygon with the provided Pen.
/// </summary>
/// <typeparam name="TColor">The type of the color.</typeparam>
/// <typeparam name="TPacked">The type of the packed.</typeparam>
/// <param name="source">The source.</param>
/// <param name="pen">The pen.</param>
/// <param name="points">The points.</param>
/// <returns>The Image</returns>
public static Image<TColor, TPacked> DrawPolygon<TColor, TPacked>(this Image<TColor, TPacked> source, IPen<TColor, TPacked> pen, Vector2[] points)
where TColor : struct, IPackedPixel<TPacked>
where TPacked : struct
{
return source.DrawPolygon(pen, new Polygon(new LinearLineSegment(points)));
}
/// <summary>
/// Draws the path with the provided pen.
/// </summary>
/// <typeparam name="TColor">The type of the color.</typeparam>
/// <typeparam name="TPacked">The type of the packed.</typeparam>
/// <param name="source">The source.</param>
/// <param name="pen">The pen.</param>
/// <param name="path">The path.</param>
/// <param name="options">The options.</param>
/// <returns>
/// The Image
/// </returns>
public static Image<TColor, TPacked> DrawPath<TColor, TPacked>(this Image<TColor, TPacked> source, IPen<TColor, TPacked> pen, IPath path, GraphicsOptions options)
where TColor : struct, IPackedPixel<TPacked>
where TPacked : struct
{
return source.Process(new DrawPathProcessor<TColor, TPacked>(pen, path, options));
}
/// <summary>
/// Draws the path with the provided pen.
/// </summary>
/// <typeparam name="TColor">The type of the color.</typeparam>
/// <typeparam name="TPacked">The type of the packed.</typeparam>
/// <param name="source">The source.</param>
/// <param name="pen">The pen.</param>
/// <param name="path">The path.</param>
/// <returns>The Image</returns>
public static Image<TColor, TPacked> DrawPath<TColor, TPacked>(this Image<TColor, TPacked> source, IPen<TColor, TPacked> pen, IPath path)
where TColor : struct, IPackedPixel<TPacked>
where TPacked : struct
{
return source.Process(new DrawPathProcessor<TColor, TPacked>(pen, path, GraphicsOptions.Default));
}
/// <summary>
/// Draws the path with the bursh at the privdied thickness.
/// </summary>
/// <typeparam name="TColor">The type of the color.</typeparam>
/// <typeparam name="TPacked">The type of the packed.</typeparam>
/// <param name="source">The source.</param>
/// <param name="brush">The brush.</param>
/// <param name="thickness">The thickness.</param>
/// <param name="path">The path.</param>
/// <param name="options">The options.</param>
/// <returns>
/// The Image
/// </returns>
public static Image<TColor, TPacked> DrawPath<TColor, TPacked>(this Image<TColor, TPacked> source, IBrush<TColor, TPacked> brush, float thickness, IPath path, GraphicsOptions options)
where TColor : struct, IPackedPixel<TPacked>
where TPacked : struct
{
return source.DrawPath(new Pen<TColor, TPacked>(brush, thickness), path, options);
}
/// <summary>
/// Draws the path with the bursh at the privdied thickness.
/// </summary>
/// <typeparam name="TColor">The type of the color.</typeparam>
/// <typeparam name="TPacked">The type of the packed.</typeparam>
/// <param name="source">The source.</param>
/// <param name="brush">The brush.</param>
/// <param name="thickness">The thickness.</param>
/// <param name="path">The path.</param>
/// <returns>The Image</returns>
public static Image<TColor, TPacked> DrawPath<TColor, TPacked>(this Image<TColor, TPacked> source, IBrush<TColor, TPacked> brush, float thickness, IPath path)
where TColor : struct, IPackedPixel<TPacked>
where TPacked : struct
{
return source.DrawPath(new Pen<TColor, TPacked>(brush, thickness), path);
}
/// <summary>
/// Draws the path with the bursh at the privdied thickness.
/// </summary>
/// <typeparam name="TColor">The type of the color.</typeparam>
/// <typeparam name="TPacked">The type of the packed.</typeparam>
/// <param name="source">The source.</param>
/// <param name="color">The color.</param>
/// <param name="thickness">The thickness.</param>
/// <param name="path">The path.</param>
/// <param name="options">The options.</param>
/// <returns>
/// The Image
/// </returns>
public static Image<TColor, TPacked> DrawPath<TColor, TPacked>(this Image<TColor, TPacked> source, TColor color, float thickness, IPath path, GraphicsOptions options)
where TColor : struct, IPackedPixel<TPacked>
where TPacked : struct
{
return source.DrawPath(new SolidBrush<TColor, TPacked>(color), thickness, path, options);
}
/// <summary>
/// Draws the path with the bursh at the privdied thickness.
/// </summary>
/// <typeparam name="TColor">The type of the color.</typeparam>
/// <typeparam name="TPacked">The type of the packed.</typeparam>
/// <param name="source">The source.</param>
/// <param name="color">The color.</param>
/// <param name="thickness">The thickness.</param>
/// <param name="path">The path.</param>
/// <returns>The Image</returns>
public static Image<TColor, TPacked> DrawPath<TColor, TPacked>(this Image<TColor, TPacked> source, TColor color, float thickness, IPath path)
where TColor : struct, IPackedPixel<TPacked>
where TPacked : struct
{
return source.DrawPath(new SolidBrush<TColor, TPacked>(color), thickness, path);
}
/// <summary>
/// Draws the provided Points as an open Linear path at the provided thickness with the supplied brush
/// </summary>
/// <typeparam name="TColor">The type of the color.</typeparam>
/// <typeparam name="TPacked">The type of the packed.</typeparam>
/// <param name="source">The source.</param>
/// <param name="brush">The brush.</param>
/// <param name="thickness">The thickness.</param>
/// <param name="points">The points.</param>
/// <param name="options">The options.</param>
/// <returns>
/// The Image
/// </returns>
public static Image<TColor, TPacked> DrawLines<TColor, TPacked>(this Image<TColor, TPacked> source, IBrush<TColor, TPacked> brush, float thickness, Vector2[] points, GraphicsOptions options)
where TColor : struct, IPackedPixel<TPacked>
where TPacked : struct
{
return source.DrawPath(new Pen<TColor, TPacked>(brush, thickness), new Path(new LinearLineSegment(points)), options);
}
/// <summary>
/// Draws the provided Points as an open Linear path at the provided thickness with the supplied brush
/// </summary>
/// <typeparam name="TColor">The type of the color.</typeparam>
/// <typeparam name="TPacked">The type of the packed.</typeparam>
/// <param name="source">The source.</param>
/// <param name="brush">The brush.</param>
/// <param name="thickness">The thickness.</param>
/// <param name="points">The points.</param>
/// <returns>The Image</returns>
public static Image<TColor, TPacked> DrawLines<TColor, TPacked>(this Image<TColor, TPacked> source, IBrush<TColor, TPacked> brush, float thickness, Vector2[] points)
where TColor : struct, IPackedPixel<TPacked>
where TPacked : struct
{
return source.DrawPath(new Pen<TColor, TPacked>(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>
/// <typeparam name="TColor">The type of the color.</typeparam>
/// <typeparam name="TPacked">The type of the packed.</typeparam>
/// <param name="source">The source.</param>
/// <param name="color">The color.</param>
/// <param name="thickness">The thickness.</param>
/// <param name="points">The points.</param>
/// <returns>The Image</returns>
public static Image<TColor, TPacked> DrawLines<TColor, TPacked>(this Image<TColor, TPacked> source, TColor color, float thickness, Vector2[] points)
where TColor : struct, IPackedPixel<TPacked>
where TPacked : struct
{
return source.DrawLines(new SolidBrush<TColor, TPacked>(color), thickness, points);
}
/// <summary>
/// Draws the provided Points as an open Linear path at the provided thickness with the supplied brush
/// </summary>
/// <typeparam name="TColor">The type of the color.</typeparam>
/// <typeparam name="TPacked">The type of the packed.</typeparam>
/// <param name="source">The source.</param>
/// <param name="color">The color.</param>
/// <param name="thickness">The thickness.</param>
/// <param name="points">The points.</param>
/// <param name="options">The options.</param>
/// <returns>
/// The Image
/// </returns>
public static Image<TColor, TPacked> DrawLines<TColor, TPacked>(this Image<TColor, TPacked> source, TColor color, float thickness, Vector2[] points, GraphicsOptions options)
where TColor : struct, IPackedPixel<TPacked>
where TPacked : struct
{
return source.DrawLines(new SolidBrush<TColor, TPacked>(color), thickness, points, options);
}
/// <summary>
/// Draws the provided Points as an open Linear path with the supplied pen
/// </summary>
/// <typeparam name="TColor">The type of the color.</typeparam>
/// <typeparam name="TPacked">The type of the packed.</typeparam>
/// <param name="source">The source.</param>
/// <param name="pen">The pen.</param>
/// <param name="points">The points.</param>
/// <param name="options">The options.</param>
/// <returns>
/// The Image
/// </returns>
public static Image<TColor, TPacked> DrawLines<TColor, TPacked>(this Image<TColor, TPacked> source, IPen<TColor, TPacked> pen, Vector2[] points, GraphicsOptions options)
where TColor : struct, IPackedPixel<TPacked>
where TPacked : struct
{
return source.DrawPath(pen, new Path(new LinearLineSegment(points)), options);
}
/// <summary>
/// Draws the provided Points as an open Linear path with the supplied pen
/// </summary>
/// <typeparam name="TColor">The type of the color.</typeparam>
/// <typeparam name="TPacked">The type of the packed.</typeparam>
/// <param name="source">The source.</param>
/// <param name="pen">The pen.</param>
/// <param name="points">The points.</param>
/// <returns>The Image</returns>
public static Image<TColor, TPacked> DrawLines<TColor, TPacked>(this Image<TColor, TPacked> source, IPen<TColor, TPacked> pen, Vector2[] points)
where TColor : struct, IPackedPixel<TPacked>
where TPacked : struct
{
return source.DrawPath(pen, new Path(new LinearLineSegment(points)));
}
/// <summary>
/// Draws the provided Points as an open Bezier path at the provided thickness with the supplied brush
/// </summary>
/// <typeparam name="TColor">The type of the color.</typeparam>
/// <typeparam name="TPacked">The type of the packed.</typeparam>
/// <param name="source">The source.</param>
/// <param name="brush">The brush.</param>
/// <param name="thickness">The thickness.</param>
/// <param name="points">The points.</param>
/// <param name="options">The options.</param>
/// <returns>
/// The Image
/// </returns>
public static Image<TColor, TPacked> DrawBeziers<TColor, TPacked>(this Image<TColor, TPacked> source, IBrush<TColor, TPacked> brush, float thickness, Vector2[] points, GraphicsOptions options)
where TColor : struct, IPackedPixel<TPacked>
where TPacked : struct
{
return source.DrawPath(new Pen<TColor, TPacked>(brush, thickness), new Path(new BezierLineSegment(points)), options);
}
/// <summary>
/// Draws the provided Points as an open Bezier path at the provided thickness with the supplied brush
/// </summary>
/// <typeparam name="TColor">The type of the color.</typeparam>
/// <typeparam name="TPacked">The type of the packed.</typeparam>
/// <param name="source">The source.</param>
/// <param name="brush">The brush.</param>
/// <param name="thickness">The thickness.</param>
/// <param name="points">The points.</param>
/// <returns>The Image</returns>
public static Image<TColor, TPacked> DrawBeziers<TColor, TPacked>(this Image<TColor, TPacked> source, IBrush<TColor, TPacked> brush, float thickness, Vector2[] points)
where TColor : struct, IPackedPixel<TPacked>
where TPacked : struct
{
return source.DrawPath(new Pen<TColor, TPacked>(brush, thickness), new Path(new BezierLineSegment(points)));
}
/// <summary>
/// Draws the provided Points as an open Bezier path at the provided thickness with the supplied brush
/// </summary>
/// <typeparam name="TColor">The type of the color.</typeparam>
/// <typeparam name="TPacked">The type of the packed.</typeparam>
/// <param name="source">The source.</param>
/// <param name="color">The color.</param>
/// <param name="thickness">The thickness.</param>
/// <param name="points">The points.</param>
/// <returns>The Image</returns>
public static Image<TColor, TPacked> DrawBeziers<TColor, TPacked>(this Image<TColor, TPacked> source, TColor color, float thickness, Vector2[] points)
where TColor : struct, IPackedPixel<TPacked>
where TPacked : struct
{
return source.DrawBeziers(new SolidBrush<TColor, TPacked>(color), thickness, points);
}
/// <summary>
/// Draws the provided Points as an open Bezier path at the provided thickness with the supplied brush
/// </summary>
/// <typeparam name="TColor">The type of the color.</typeparam>
/// <typeparam name="TPacked">The type of the packed.</typeparam>
/// <param name="source">The source.</param>
/// <param name="color">The color.</param>
/// <param name="thickness">The thickness.</param>
/// <param name="points">The points.</param>
/// <param name="options">The options.</param>
/// <returns>
/// The Image
/// </returns>
public static Image<TColor, TPacked> DrawBeziers<TColor, TPacked>(this Image<TColor, TPacked> source, TColor color, float thickness, Vector2[] points, GraphicsOptions options)
where TColor : struct, IPackedPixel<TPacked>
where TPacked : struct
{
return source.DrawBeziers(new SolidBrush<TColor, TPacked>(color), thickness, points, options);
}
/// <summary>
/// Draws the provided Points as an open Bezier path with the supplied pen
/// </summary>
/// <typeparam name="TColor">The type of the color.</typeparam>
/// <typeparam name="TPacked">The type of the packed.</typeparam>
/// <param name="source">The source.</param>
/// <param name="pen">The pen.</param>
/// <param name="points">The points.</param>
/// <param name="options">The options.</param>
/// <returns>
/// The Image
/// </returns>
public static Image<TColor, TPacked> DrawBeziers<TColor, TPacked>(this Image<TColor, TPacked> source, IPen<TColor, TPacked> pen, Vector2[] points, GraphicsOptions options)
where TColor : struct, IPackedPixel<TPacked>
where TPacked : struct
{
return source.DrawPath(pen, new Path(new BezierLineSegment(points)), options);
}
/// <summary>
/// Draws the provided Points as an open Bezier path with the supplied pen
/// </summary>
/// <typeparam name="TColor">The type of the color.</typeparam>
/// <typeparam name="TPacked">The type of the packed.</typeparam>
/// <param name="source">The source.</param>
/// <param name="pen">The pen.</param>
/// <param name="points">The points.</param>
/// <returns>The Image</returns>
public static Image<TColor, TPacked> DrawBeziers<TColor, TPacked>(this Image<TColor, TPacked> source, IPen<TColor, TPacked> pen, Vector2[] points)
where TColor : struct, IPackedPixel<TPacked>
where TPacked : struct
{
return source.DrawPath(pen, new Path(new BezierLineSegment(points)));
}
}
}

192
src/ImageSharp/Drawing/Fill.cs

@ -0,0 +1,192 @@
// <copyright file="Fill.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp
{
using System.Numerics;
using Drawing;
using Drawing.Brushes;
using Drawing.Paths;
using Drawing.Processors;
using Drawing.Shapes;
using Processors;
/// <summary>
/// Extension methods for the <see cref="Image{TColor, TPacked}"/> type.
/// </summary>
public static partial class ImageExtensions
{
/// <summary>
/// Flood fills the image with the specified brush.
/// </summary>
/// <typeparam name="TColor">The type of the color.</typeparam>
/// <typeparam name="TPacked">The type of the packed.</typeparam>
/// <param name="source">The source.</param>
/// <param name="brush">The brush.</param>
/// <returns>The Image</returns>
public static Image<TColor, TPacked> Fill<TColor, TPacked>(this Image<TColor, TPacked> source, IBrush<TColor, TPacked> brush)
where TColor : struct, IPackedPixel<TPacked>
where TPacked : struct
{
return source.Process(new FillProcessor<TColor, TPacked>(brush));
}
/// <summary>
/// Flood fills the image with the specified color.
/// </summary>
/// <typeparam name="TColor">The type of the color.</typeparam>
/// <typeparam name="TPacked">The type of the packed.</typeparam>
/// <param name="source">The source.</param>
/// <param name="color">The color.</param>
/// <returns>The Image</returns>
public static Image<TColor, TPacked> Fill<TColor, TPacked>(this Image<TColor, TPacked> source, TColor color)
where TColor : struct, IPackedPixel<TPacked>
where TPacked : struct
{
return source.Fill(new SolidBrush<TColor, TPacked>(color));
}
/// <summary>
/// Flood fills the image in the shape o fhte provided polygon with the specified brush..
/// </summary>
/// <typeparam name="TColor">The type of the color.</typeparam>
/// <typeparam name="TPacked">The type of the packed.</typeparam>
/// <param name="source">The source.</param>
/// <param name="brush">The brush.</param>
/// <param name="shape">The shape.</param>
/// <returns>The Image</returns>
public static Image<TColor, TPacked> Fill<TColor, TPacked>(this Image<TColor, TPacked> source, IBrush<TColor, TPacked> brush, IShape shape, GraphicsOptions options)
where TColor : struct, IPackedPixel<TPacked>
where TPacked : struct
{
return source.Process(new FillShapeProcessor<TColor, TPacked>(brush, shape, options));
}
/// <summary>
/// Flood fills the image in the shape o fhte provided polygon with the specified brush..
/// </summary>
/// <typeparam name="TColor">The type of the color.</typeparam>
/// <typeparam name="TPacked">The type of the packed.</typeparam>
/// <param name="source">The source.</param>
/// <param name="brush">The brush.</param>
/// <param name="shape">The shape.</param>
/// <returns>The Image</returns>
public static Image<TColor, TPacked> Fill<TColor, TPacked>(this Image<TColor, TPacked> source, IBrush<TColor, TPacked> brush, IShape shape)
where TColor : struct, IPackedPixel<TPacked>
where TPacked : struct
{
return source.Process(new FillShapeProcessor<TColor, TPacked>(brush, shape, GraphicsOptions.Default));
}
/// <summary>
/// Flood fills the image in the shape o fhte provided polygon with the specified brush..
/// </summary>
/// <typeparam name="TColor">The type of the color.</typeparam>
/// <typeparam name="TPacked">The type of the packed.</typeparam>
/// <param name="source">The source.</param>
/// <param name="color">The color.</param>
/// <param name="shape">The shape.</param>
/// <param name="options">The options.</param>
/// <returns>
/// The Image
/// </returns>
public static Image<TColor, TPacked> Fill<TColor, TPacked>(this Image<TColor, TPacked> source, TColor color, IShape shape, GraphicsOptions options)
where TColor : struct, IPackedPixel<TPacked>
where TPacked : struct
{
return source.Fill(new SolidBrush<TColor, TPacked>(color), shape, options);
}
/// <summary>
/// Flood fills the image in the shape o fhte provided polygon with the specified brush..
/// </summary>
/// <typeparam name="TColor">The type of the color.</typeparam>
/// <typeparam name="TPacked">The type of the packed.</typeparam>
/// <param name="source">The source.</param>
/// <param name="color">The color.</param>
/// <param name="shape">The shape.</param>
/// <returns>The Image</returns>
public static Image<TColor, TPacked> Fill<TColor, TPacked>(this Image<TColor, TPacked> source, TColor color, IShape shape)
where TColor : struct, IPackedPixel<TPacked>
where TPacked : struct
{
return source.Fill(new SolidBrush<TColor, TPacked>(color), shape);
}
/// <summary>
/// Flood fills the image in the shape of a Linear polygon described by the points
/// </summary>
/// <typeparam name="TColor">The type of the color.</typeparam>
/// <typeparam name="TPacked">The type of the packed.</typeparam>
/// <param name="source">The source.</param>
/// <param name="brush">The brush.</param>
/// <param name="points">The points.</param>
/// <param name="options">The options.</param>
/// <returns>
/// The Image
/// </returns>
public static Image<TColor, TPacked> FillPolygon<TColor, TPacked>(this Image<TColor, TPacked> source, IBrush<TColor, TPacked> brush, Vector2[] points, GraphicsOptions options)
where TColor : struct, IPackedPixel<TPacked>
where TPacked : struct
{
// using Polygon directly instead of LinearPolygon as its will have less indirection
return source.Fill(brush, new Polygon(new LinearLineSegment(points)), options);
}
/// <summary>
/// Flood fills the image in the shape of a Linear polygon described by the points
/// </summary>
/// <typeparam name="TColor">The type of the color.</typeparam>
/// <typeparam name="TPacked">The type of the packed.</typeparam>
/// <param name="source">The source.</param>
/// <param name="brush">The brush.</param>
/// <param name="points">The points.</param>
/// <returns>The Image</returns>
public static Image<TColor, TPacked> FillPolygon<TColor, TPacked>(this Image<TColor, TPacked> source, IBrush<TColor, TPacked> brush, Vector2[] points)
where TColor : struct, IPackedPixel<TPacked>
where TPacked : struct
{
// using Polygon directly instead of LinearPolygon as its will have less indirection
return 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>
/// <typeparam name="TColor">The type of the color.</typeparam>
/// <typeparam name="TPacked">The type of the packed.</typeparam>
/// <param name="source">The source.</param>
/// <param name="color">The color.</param>
/// <param name="points">The points.</param>
/// <param name="options">The options.</param>
/// <returns>
/// The Image
/// </returns>
public static Image<TColor, TPacked> FillPolygon<TColor, TPacked>(this Image<TColor, TPacked> source, TColor color, Vector2[] points, GraphicsOptions options)
where TColor : struct, IPackedPixel<TPacked>
where TPacked : struct
{
// using Polygon directly instead of LinearPolygon as its will have less indirection
return source.Fill(new SolidBrush<TColor, TPacked>(color), new Polygon(new LinearLineSegment(points)), options);
}
/// <summary>
/// Flood fills the image in the shape of a Linear polygon described by the points
/// </summary>
/// <typeparam name="TColor">The type of the color.</typeparam>
/// <typeparam name="TPacked">The type of the packed.</typeparam>
/// <param name="source">The source.</param>
/// <param name="color">The color.</param>
/// <param name="points">The points.</param>
/// <returns>The Image</returns>
public static Image<TColor, TPacked> FillPolygon<TColor, TPacked>(this Image<TColor, TPacked> source, TColor color, Vector2[] points)
where TColor : struct, IPackedPixel<TPacked>
where TPacked : struct
{
// using Polygon directly instead of LinearPolygon as its will have less indirection
return source.Fill(new SolidBrush<TColor, TPacked>(color), new Polygon(new LinearLineSegment(points)));
}
}
}

32
src/ImageSharp/Drawing/GraphicsOptions.cs

@ -0,0 +1,32 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace ImageSharp.Drawing
{
/// <summary>
/// Options for influancing the drawing functions.
/// </summary>
public struct GraphicsOptions
{
/// <summary>
/// Represents the default <see cref="GraphicsOptions"/>.
/// </summary>
public static readonly GraphicsOptions Default = new GraphicsOptions(true);
/// <summary>
/// Initializes a new instance of the <see cref="GraphicsOptions"/> struct.
/// </summary>
/// <param name="enableAntialiasing">if set to <c>true</c> [enable antialiasing].</param>
public GraphicsOptions(bool enableAntialiasing)
{
Antialias = enableAntialiasing;
}
/// <summary>
/// Should antialias be applied.
/// </summary>
public bool Antialias;
}
}

101
src/ImageSharp/Drawing/Paths/BezierLineSegment.cs

@ -0,0 +1,101 @@
// <copyright file="BezierLineSegment.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp.Drawing.Paths
{
using System;
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
using Brushes;
/// <summary>
/// Represents a line segment that conistst of control points that will be rendered as a cubic bezier curve
/// </summary>
/// <seealso cref="ImageSharp.Drawing.Paths.ILineSegment" />
public class BezierLineSegment : ILineSegment
{
// code for this taken from http://devmag.org.za/2011/04/05/bzier-curves-a-tutorial/
private const int SegmentsPerCurve = 50;
private Vector2[] linePoints;
/// <summary>
/// Initializes a new instance of the <see cref="BezierLineSegment"/> class.
/// </summary>
/// <param name="points">The points.</param>
public BezierLineSegment(params Vector2[] points)
{
Guard.NotNull(points, nameof(points));
Guard.MustBeGreaterThanOrEqualTo(points.Length, 4, nameof(points));
this.linePoints = this.GetDrawingPoints(points);
}
/// <summary>
/// Returns the current <see cref="ILineSegment" /> a simple linear path.
/// </summary>
/// <returns>
/// Returns the current <see cref="ILineSegment" /> as simple linear path.
/// </returns>
public Vector2[] AsSimpleLinearPath()
{
return this.linePoints;
}
private Vector2[] GetDrawingPoints(Vector2[] controlPoints)
{
// TODO we need to calculate an optimal SegmentsPerCurve value
// depending on the calcualted length of this curve
var curveCount = (controlPoints.Length - 1) / 3;
var finalPointCount = (SegmentsPerCurve * curveCount) + 1; // we have SegmentsPerCurve for each curve plus the origon point;
var drawingPoints = new Vector2[finalPointCount];
int position = 0;
var targetPoint = controlPoints.Length - 3;
for (int i = 0; i < targetPoint; i += 3)
{
Vector2 p0 = controlPoints[i];
Vector2 p1 = controlPoints[i + 1];
Vector2 p2 = controlPoints[i + 2];
Vector2 p3 = controlPoints[i + 3];
// only do this for the first end point. When i != 0, this coincides with the end point of the previous segment,
if (i == 0)
{
drawingPoints[position++] = this.CalculateBezierPoint(0, p0, p1, p2, p3);
}
for (int j = 1; j <= SegmentsPerCurve; j++)
{
float t = j / (float)SegmentsPerCurve;
drawingPoints[position++] = this.CalculateBezierPoint(t, p0, p1, p2, p3);
}
}
return drawingPoints;
}
private Vector2 CalculateBezierPoint(float t, Vector2 p0, Vector2 p1, Vector2 p2, Vector2 p3)
{
float u = 1 - t;
float tt = t * t;
float uu = u * u;
float uuu = uu * u;
float ttt = tt * t;
Vector2 p = uuu * p0; // first term
p += 3 * uu * t * p1; // second term
p += 3 * u * tt * p2; // third term
p += ttt * p3; // fourth term
return p;
}
}
}

25
src/ImageSharp/Drawing/Paths/ILineSegment.cs

@ -0,0 +1,25 @@
// <copyright file="ILineSegment.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp.Drawing.Paths
{
using System;
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
using System.Threading.Tasks;
/// <summary>
/// Represents a simple path segment
/// </summary>
public interface ILineSegment
{
/// <summary>
/// Converts the <see cref="ILineSegment" /> into a simple linear path..
/// </summary>
/// <returns>Returns the current <see cref="ILineSegment" /> as simple linear path.</returns>
Vector2[] AsSimpleLinearPath(); // TODO move this over to ReadonlySpan<Vector2> once availible
}
}

52
src/ImageSharp/Drawing/Paths/IPath.cs

@ -0,0 +1,52 @@
// <copyright file="IPath.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp.Drawing.Paths
{
using System;
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
using System.Threading.Tasks;
/// <summary>
/// Represents a logic path that can be drawn
/// </summary>
public interface IPath : ILineSegment
{
/// <summary>
/// Gets the bounds enclosing the path
/// </summary>
/// <value>
/// The bounds.
/// </value>
RectangleF Bounds { get; }
/// <summary>
/// Gets a value indicating whether this instance is closed.
/// </summary>
/// <value>
/// <c>true</c> if this instance is closed; otherwise, <c>false</c>.
/// </value>
bool IsClosed { get; }
/// <summary>
/// Gets the length of the path
/// </summary>
/// <value>
/// The length.
/// </value>
float Length { get; }
/// <summary>
/// Calcualtes the distance along and away from the path for a specified point.
/// </summary>
/// <param name="point">The point.</param>
/// <returns>
/// Returns details about the point and its distance away from the path.
/// </returns>
PointInfo Distance(Vector2 point);
}
}

285
src/ImageSharp/Drawing/Paths/InternalPath.cs

@ -0,0 +1,285 @@
// <copyright file="InternalPath.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp.Drawing.Paths
{
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Numerics;
using System.Threading.Tasks;
/// <summary>
/// Internal logic for interigating linear paths.
/// </summary>
internal class InternalPath
{
private readonly Vector2[] points;
private readonly bool closedPath;
private readonly Lazy<float> totalDistance;
private float[] constant;
private float[] multiple;
private float[] distance;
private object locker = new object();
private bool calculated = false;
/// <summary>
/// Initializes a new instance of the <see cref="InternalPath"/> class.
/// </summary>
/// <param name="segments">The segments.</param>
/// <param name="isClosedPath">if set to <c>true</c> [is closed path].</param>
internal InternalPath(ILineSegment[] segments, bool isClosedPath)
{
Guard.NotNull(segments, nameof(segments));
this.points = this.Simplify(segments);
this.closedPath = isClosedPath;
var minX = this.points.Min(x => x.X);
var maxX = this.points.Max(x => x.X);
var minY = this.points.Min(x => x.Y);
var maxY = this.points.Max(x => x.Y);
this.Bounds = new RectangleF(minX, minY, maxX - minX, maxY - minY);
this.totalDistance = new Lazy<float>(this.CalculateLength);
}
/// <summary>
/// Gets the bounds.
/// </summary>
/// <value>
/// The bounds.
/// </value>
public RectangleF Bounds
{
get;
}
/// <summary>
/// Gets the length.
/// </summary>
/// <value>
/// The length.
/// </value>
public float Length => this.totalDistance.Value;
/// <summary>
/// Gets the points.
/// </summary>
/// <value>
/// The points.
/// </value>
internal Vector2[] Points => this.points;
/// <summary>
/// Calculates the distance from the path.
/// </summary>
/// <param name="point">The point.</param>
/// <returns>Returns the distance from the path</returns>
public PointInfo DistanceFromPath(Vector2 point)
{
this.CalculateConstants();
var internalInfo = default(PointInfoInternal);
internalInfo.DistanceSquared = float.MaxValue; // set it to max so that CalculateShorterDistance can reduce it back down
var polyCorners = this.points.Length;
if (!this.closedPath)
{
polyCorners -= 1;
}
int closestPoint = 0;
for (var i = 0; i < polyCorners; i++)
{
var next = i + 1;
if (this.closedPath && next == polyCorners)
{
next = 0;
}
if (this.CalculateShorterDistance(this.points[i], this.points[next], point, ref internalInfo))
{
closestPoint = i;
}
}
return new PointInfo
{
DistanceAlongPath = this.distance[closestPoint] + Vector2.Distance(this.points[closestPoint], point),
DistanceFromPath = (float)Math.Sqrt(internalInfo.DistanceSquared),
SearchPoint = point,
ClosestPointOnPath = internalInfo.PointOnLine
};
}
/// <summary>
/// Points the in polygon.
/// </summary>
/// <param name="point">The point.</param>
/// <returns>Returns true if the point is inside the closed path.</returns>
public bool PointInPolygon(Vector2 point)
{
// you can only be inside a path if its "closed"
if (!this.closedPath)
{
return false;
}
if (!this.Bounds.Contains(point.X, point.Y))
{
return false;
}
this.CalculateConstants();
var poly = this.points;
var polyCorners = poly.Length;
var j = polyCorners - 1;
bool oddNodes = false;
for (var i = 0; i < polyCorners; i++)
{
if ((poly[i].Y < point.Y && poly[j].Y >= point.Y)
|| (poly[j].Y < point.Y && poly[i].Y >= point.Y))
{
oddNodes ^= (point.Y * this.multiple[i]) + this.constant[i] < point.X;
}
j = i;
}
return oddNodes;
}
private Vector2[] Simplify(ILineSegment[] segments)
{
List<Vector2> points = new List<Vector2>();
foreach(var seg in segments)
{
points.AddRange(seg.AsSimpleLinearPath());
}
return points.ToArray();
}
private float CalculateLength()
{
float length = 0;
var polyCorners = this.points.Length;
if (!this.closedPath)
{
polyCorners -= 1;
}
for (var i = 0; i < polyCorners; i++)
{
var next = i + 1;
if (this.closedPath && next == polyCorners)
{
next = 0;
}
length += Vector2.Distance(this.points[i], this.points[next]);
}
return length;
}
private void CalculateConstants()
{
// http://alienryderflex.com/polygon/ source for point in polygon logic
if (this.calculated)
{
return;
}
lock (this.locker)
{
if (this.calculated)
{
return;
}
var poly = this.points;
var polyCorners = poly.Length;
this.constant = new float[polyCorners];
this.multiple = new float[polyCorners];
this.distance = new float[polyCorners];
int i, j = polyCorners - 1;
this.distance[0] = 0;
for (i = 0; i < polyCorners; i++)
{
this.distance[j] = this.distance[i] + Vector2.Distance(poly[i], poly[j]);
if (poly[j].Y == poly[i].Y)
{
this.constant[i] = poly[i].X;
this.multiple[i] = 0;
}
else
{
var subtracted = poly[j] - poly[i];
this.constant[i] = (poly[i].X - ((poly[i].Y * poly[j].X) / subtracted.Y)) + ((poly[i].Y * poly[i].X) / subtracted.Y);
this.multiple[i] = subtracted.X / subtracted.Y;
}
j = i;
}
this.calculated = true;
}
}
private bool CalculateShorterDistance(Vector2 start, Vector2 end, Vector2 point, ref PointInfoInternal info)
{
var diffEnds = end - start;
float lengthSquared = diffEnds.LengthSquared();
var diff = point - start;
var multiplied = diff * diffEnds;
var u = (multiplied.X + multiplied.Y) / lengthSquared;
if (u > 1)
{
u = 1;
}
else if (u < 0)
{
u = 0;
}
var multipliedByU = diffEnds * u;
var pointOnLine = start + multipliedByU;
var d = pointOnLine - point;
var dist = d.LengthSquared();
if (info.DistanceSquared > dist)
{
info.DistanceSquared = dist;
info.PointOnLine = pointOnLine;
return true;
}
return false;
}
private struct PointInfoInternal
{
public float DistanceSquared;
public Vector2 PointOnLine;
}
}
}

56
src/ImageSharp/Drawing/Paths/LinearLineSegment.cs

@ -0,0 +1,56 @@
// <copyright file="LinearLineSegment.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp.Drawing.Paths
{
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Numerics;
using System.Threading.Tasks;
/// <summary>
/// Represents a seriese of control points that will be joined by staight lines
/// </summary>
/// <seealso cref="ImageSharp.Drawing.Paths.ILineSegment" />
public class LinearLineSegment : ILineSegment
{
private Vector2[] points;
/// <summary>
/// Initializes a new instance of the <see cref="LinearLineSegment"/> class.
/// </summary>
/// <param name="start">The start.</param>
/// <param name="end">The end.</param>
public LinearLineSegment(Vector2 start, Vector2 end)
: this(new[] { start, end })
{
}
/// <summary>
/// Initializes a new instance of the <see cref="LinearLineSegment"/> class.
/// </summary>
/// <param name="points">The points.</param>
public LinearLineSegment(params Vector2[] points)
{
Guard.NotNull(points, nameof(points));
Guard.MustBeGreaterThanOrEqualTo(points.Count(), 2, nameof(points));
this.points = points;
}
/// <summary>
/// Converts the <see cref="ILineSegment" /> into a simple linear path..
/// </summary>
/// <returns>
/// Returns the current <see cref="ILineSegment" /> as simple linear path.
/// </returns>
public Vector2[] AsSimpleLinearPath()
{
return this.points;
}
}
}

81
src/ImageSharp/Drawing/Paths/Path.cs

@ -0,0 +1,81 @@
// <copyright file="Path.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp.Drawing.Paths
{
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Numerics;
using System.Threading.Tasks;
/// <summary>
/// A aggragate of <see cref="ILineSegment"/>s making a single logical path
/// </summary>
/// <seealso cref="ImageSharp.Drawing.Paths.IPath" />
public class Path : IPath
{
private readonly InternalPath innerPath;
/// <summary>
/// Initializes a new instance of the <see cref="Path"/> class.
/// </summary>
/// <param name="segment">The segment.</param>
public Path(params ILineSegment[] segment)
{
this.innerPath = new InternalPath(segment, false);
}
/// <summary>
/// Gets the bounds enclosing the path
/// </summary>
/// <value>
/// The bounds.
/// </value>
public RectangleF Bounds => this.innerPath.Bounds;
/// <summary>
/// Gets a value indicating whether this instance is closed.
/// </summary>
/// <value>
/// <c>true</c> if this instance is closed; otherwise, <c>false</c>.
/// </value>
public bool IsClosed => false;
/// <summary>
/// Gets the length of the path
/// </summary>
/// <value>
/// The length.
/// </value>
public float Length => this.innerPath.Length;
/// <summary>
/// Returns the current <see cref="ILineSegment" /> a simple linear path.
/// </summary>
/// <returns>
/// Returns the current <see cref="ILineSegment" /> as simple linear path.
/// </returns>
public Vector2[] AsSimpleLinearPath()
{
return this.innerPath.Points;
}
/// <summary>
/// Calcualtes the distance along and away from the path for a specified point.
/// </summary>
/// <param name="x">The x.</param>
/// <param name="y">The y.</param>
/// <returns>
/// Returns details about the point and its distance away from the path.
/// </returns>
public PointInfo Distance(Vector2 point)
{
return this.innerPath.DistanceFromPath(point);
}
}
}

39
src/ImageSharp/Drawing/Paths/PointInfo.cs

@ -0,0 +1,39 @@
// <copyright file="PointInfo.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp.Drawing.Paths
{
using System;
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
using System.Threading.Tasks;
/// <summary>
/// Returns some meta data about the nearest point on a path from a vector
/// </summary>
public struct PointInfo
{
/// <summary>
/// The search point
/// </summary>
public Vector2 SearchPoint;
/// <summary>
/// The distance along path <see cref="ClosestPointOnPath"/> is away from the start of the path
/// </summary>
public float DistanceAlongPath;
/// <summary>
/// The distance <see cref="SearchPoint"/> is away from <see cref="ClosestPointOnPath"/>.
/// </summary>
public float DistanceFromPath;
/// <summary>
/// The closest point to <see cref="SearchPoint"/> that lies on the path.
/// </summary>
public Vector2 ClosestPointOnPath;
}
}

31
src/ImageSharp/Drawing/Pens/IPen.cs

@ -0,0 +1,31 @@
// <copyright file="IPen.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp.Drawing.Pens
{
using System;
using Processors;
/// <summary>
/// interface preresenting a Pen
/// </summary>
/// <typeparam name="TColor">The type of the color.</typeparam>
/// <typeparam name="TPacked">The type of the packed.</typeparam>
public interface IPen<TColor, TPacked>
where TColor : struct, IPackedPixel<TPacked>
where TPacked : struct
{
/// <summary>
/// Creates the applicator for applying this pen to an Image
/// </summary>
/// <param name="region">The region the pen will be applied to.</param>
/// <returns>Returns a the applicator for the pen.</returns>
/// <remarks>
/// The <paramref name="region" /> when being applied to things like shapes would ussually be the
/// bounding box of the shape not necorserrally the shape of the whole image
/// </remarks>
IPenApplicator<TColor, TPacked> CreateApplicator(RectangleF region);
}
}

315
src/ImageSharp/Drawing/Pens/Pen.cs

@ -0,0 +1,315 @@
// <copyright file="Pen.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp.Drawing.Pens
{
using System;
using System.Numerics;
using Brushes;
using Drawing.Processors;
using Paths;
using Processors;
/// <summary>
/// Represenets a <see cref="Pen{TColor, TPacked}"/> in the <see cref="Color"/> color space.
/// </summary>
public partial class Pen : Pen<Color, uint>
{
/// <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)
: base(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<Color, uint> brush, float width)
: base(brush, 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>
/// <param name="pattern">The pattern.</param>
public Pen(IBrush<Color, uint> brush, float width, float[] pattern)
: base(brush, width, pattern)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="Pen"/> class.
/// </summary>
/// <param name="pen">The pen.</param>
internal Pen(Pen<Color, uint> pen)
: base(pen)
{
}
}
/// <summary>
/// Provides a pen that can apply a pattern to a line with a set brush and thickness
/// </summary>
/// <typeparam name="TColor">The type of the color.</typeparam>
/// <typeparam name="TPacked">The type of the packed.</typeparam>
/// <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 imidiatly repeat without gap.
/// </remarks>
public partial class Pen<TColor, TPacked> : IPen<TColor, TPacked>
where TColor : struct, IPackedPixel<TPacked>
where TPacked : struct
{
private static readonly float[] EmptyPattern = new float[0];
private readonly float[] pattern;
/// <summary>
/// Initializes a new instance of the <see cref="Pen{TColor, TPacked}"/> class.
/// </summary>
/// <param name="color">The color.</param>
/// <param name="width">The width.</param>
/// <param name="pattern">The pattern.</param>
public Pen(TColor color, float width, float[] pattern)
: this(new SolidBrush<TColor, TPacked>(color), width, pattern)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="Pen{TColor, TPacked}"/> class.
/// </summary>
/// <param name="brush">The brush.</param>
/// <param name="width">The width.</param>
/// <param name="pattern">The pattern.</param>
public Pen(IBrush<TColor, TPacked> brush, float width, float[] pattern)
{
this.Brush = brush;
this.Width = width;
this.pattern = pattern;
}
/// <summary>
/// Initializes a new instance of the <see cref="Pen{TColor, TPacked}"/> class.
/// </summary>
/// <param name="color">The color.</param>
/// <param name="width">The width.</param>
public Pen(TColor color, float width)
: this(new SolidBrush<TColor, TPacked>(color), width)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="Pen{TColor, TPacked}"/> class.
/// </summary>
/// <param name="brush">The brush.</param>
/// <param name="width">The width.</param>
public Pen(IBrush<TColor, TPacked> brush, float width)
: this(brush, width, EmptyPattern)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="Pen{TColor, TPacked}"/> class.
/// </summary>
/// <param name="pen">The pen.</param>
internal Pen(Pen<TColor, TPacked> pen)
: this(pen.Brush, pen.Width, pen.pattern)
{
}
/// <summary>
/// Gets the brush.
/// </summary>
/// <value>
/// The brush.
/// </value>
public IBrush<TColor, TPacked> Brush { get; }
/// <summary>
/// Gets the width.
/// </summary>
/// <value>
/// The width.
/// </value>
public float Width { get; }
/// <summary>
/// Creates the applicator for applying this pen to an Image
/// </summary>
/// <param name="region">The region the pen will be applied to.</param>
/// <returns>
/// Returns a the applicator for the pen.
/// </returns>
/// <remarks>
/// The <paramref name="region" /> when being applied to things like shapes would ussually be the
/// bounding box of the shape not necorserrally the shape of the whole image
/// </remarks>
public IPenApplicator<TColor, TPacked> CreateApplicator(RectangleF region)
{
if (this.pattern == null || this.pattern.Length < 2)
{
// if there is only one item in the pattern then 100% of it will
// be solid so use the quicker applicator
return new SolidPenApplicator(this.Brush, region, this.Width);
}
return new PatternPenApplicator(this.Brush, region, this.Width, this.pattern);
}
private class SolidPenApplicator : IPenApplicator<TColor, TPacked>
{
private readonly IBrushApplicator<TColor, TPacked> brush;
private readonly float halfWidth;
public SolidPenApplicator(IBrush<TColor, TPacked> brush, RectangleF region, float width)
{
this.brush = brush.CreateApplicator(region);
this.halfWidth = width / 2;
this.RequiredRegion = RectangleF.Outset(region, width);
}
public RectangleF RequiredRegion
{
get;
}
public void Dispose()
{
this.brush.Dispose();
}
public ColoredPointInfo<TColor, TPacked> GetColor(PointInfo info)
{
var result = default(ColoredPointInfo<TColor, TPacked>);
result.Color = this.brush.GetColor(info.SearchPoint);
if (info.DistanceFromPath < this.halfWidth)
{
// inside strip
result.DistanceFromElement = 0;
}
else
{
result.DistanceFromElement = info.DistanceFromPath - this.halfWidth;
}
return result;
}
}
private class PatternPenApplicator : IPenApplicator<TColor, TPacked>
{
private readonly IBrushApplicator<TColor, TPacked> brush;
private readonly float halfWidth;
private readonly float[] pattern;
private readonly float totalLength;
public PatternPenApplicator(IBrush<TColor, TPacked> brush, RectangleF region, float width, float[] pattern)
{
this.brush = brush.CreateApplicator(region);
this.halfWidth = width / 2;
this.totalLength = 0;
this.pattern = new float[pattern.Length + 1];
this.pattern[0] = 0;
for (var i = 0; i < pattern.Length; i++)
{
this.totalLength += pattern[i] * width;
this.pattern[i + 1] = this.totalLength;
}
this.RequiredRegion = RectangleF.Outset(region, width);
}
public RectangleF RequiredRegion
{
get;
}
public void Dispose()
{
this.brush.Dispose();
}
public ColoredPointInfo<TColor, TPacked> GetColor(PointInfo info)
{
var infoResult = default(ColoredPointInfo<TColor, TPacked>);
infoResult.DistanceFromElement = float.MaxValue; // is really outside the element
var length = info.DistanceAlongPath % this.totalLength;
// we can treat the DistanceAlongPath and DistanceFromPath as x,y coords for the pattern
// we need to calcualte the distance from the outside edge of the pattern
// and set them on the ColoredPointInfo<TColor, TPacked> along with the color.
infoResult.Color = this.brush.GetColor(info.SearchPoint);
float distanceWAway = 0;
if (info.DistanceFromPath < this.halfWidth)
{
// inside strip
distanceWAway = 0;
}
else
{
distanceWAway = info.DistanceFromPath - this.halfWidth;
}
for (var i = 0; i < this.pattern.Length - 1; i++)
{
var start = this.pattern[i];
var end = this.pattern[i + 1];
if (length >= start && length < end)
{
// in section
if (i % 2 == 0)
{
// solid part return the maxDistance
infoResult.DistanceFromElement = distanceWAway;
return infoResult;
}
else
{
// this is a none solid part
var distanceFromStart = length - start;
var distanceFromEnd = end - length;
var closestEdge = Math.Min(distanceFromStart, distanceFromEnd);
var distanceAcross = closestEdge;
if (distanceWAway > 0)
{
infoResult.DistanceFromElement = new Vector2(distanceAcross, distanceWAway).Length();
}
else
{
infoResult.DistanceFromElement = closestEdge;
}
return infoResult;
}
}
}
return infoResult;
}
}
}
}

208
src/ImageSharp/Drawing/Pens/Pens.cs

@ -0,0 +1,208 @@
// <copyright file="Pens.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp.Drawing.Pens
{
/// <summary>
/// Common Pen styles
/// </summary>
public partial class Pens
{
/// <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<Color, uint> 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(Pens<Color, uint>.Dash(color, width));
/// <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<Color, uint> brush, float width)
=> new Pen(Pens<Color, uint>.Dash(brush, width));
/// <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(Pens<Color, uint>.Dot(color, width));
/// <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<Color, uint> brush, float width)
=> new Pen(Pens<Color, uint>.Dot(brush, width));
/// <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(Pens<Color, uint>.DashDot(color, width));
/// <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<Color, uint> brush, float width)
=> new Pen(Pens<Color, uint>.DashDot(brush, width));
/// <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(Pens<Color, uint>.DashDotDot(color, width));
/// <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<Color, uint> brush, float width)
=> new Pen(Pens<Color, uint>.DashDotDot(brush, width));
}
/// <summary>
/// Common Pen styles
/// </summary>
/// <typeparam name="TColor">The type of the color.</typeparam>
/// <typeparam name="TPacked">The type of the packed.</typeparam>
public partial class Pens<TColor, TPacked>
where TColor : struct, IPackedPixel<TPacked>
where TPacked : struct
{
private static readonly float[] DashDotPattern = new[] { 3f, 1f, 1f, 1f };
private static readonly float[] DashDotDotPattern = new[] { 3f, 1f, 1f, 1f, 1f, 1f };
private static readonly float[] DottedPattern = new[] { 1f, 1f };
private static readonly float[] DashedPattern = new[] { 3f, 1f };
/// <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<TColor, TPacked> Solid(TColor color, float width)
=> new Pen<TColor, TPacked>(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<TColor, TPacked> Solid(IBrush<TColor, TPacked> brush, float width)
=> new Pen<TColor, TPacked>(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<TColor, TPacked> Dash(TColor color, float width)
=> new Pen<TColor, TPacked>(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<TColor, TPacked> Dash(IBrush<TColor, TPacked> brush, float width)
=> new Pen<TColor, TPacked>(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<TColor, TPacked> Dot(TColor color, float width)
=> new Pen<TColor, TPacked>(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<TColor, TPacked> Dot(IBrush<TColor, TPacked> brush, float width)
=> new Pen<TColor, TPacked>(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<TColor, TPacked> DashDot(TColor color, float width)
=> new Pen<TColor, TPacked>(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<TColor, TPacked> DashDot(IBrush<TColor, TPacked> brush, float width)
=> new Pen<TColor, TPacked>(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<TColor, TPacked> DashDotDot(TColor color, float width)
=> new Pen<TColor, TPacked>(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<TColor, TPacked> DashDotDot(IBrush<TColor, TPacked> brush, float width)
=> new Pen<TColor, TPacked>(brush, width, DashDotDotPattern);
}
}

27
src/ImageSharp/Drawing/Pens/Processors/ColoredPointInfo.cs

@ -0,0 +1,27 @@
// <copyright file="ColoredPointInfo.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp.Drawing.Pens.Processors
{
/// <summary>
/// Returns details about how far awau from the inside of a shape and the color the pixel could be.
/// </summary>
/// <typeparam name="TColor">The type of the color.</typeparam>
/// <typeparam name="TPacked">The type of the packed.</typeparam>
public struct ColoredPointInfo<TColor, TPacked>
where TColor : struct, IPackedPixel<TPacked>
where TPacked : struct
{
/// <summary>
/// The color
/// </summary>
public TColor Color;
/// <summary>
/// The distance from element
/// </summary>
public float DistanceFromElement;
}
}

35
src/ImageSharp/Drawing/Pens/Processors/IPenApplicator.cs

@ -0,0 +1,35 @@
// <copyright file="IPenApplicator.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp.Drawing.Pens.Processors
{
using System;
using Paths;
/// <summary>
/// primitive that converts a <see cref="PointInfo"/> into a color and a distance away from the drawable part of the path.
/// </summary>
/// <typeparam name="TColor">The type of the color.</typeparam>
/// <typeparam name="TPacked">The type of the packed.</typeparam>
public interface IPenApplicator<TColor, TPacked> : IDisposable
where TColor : struct, IPackedPixel<TPacked>
where TPacked : struct
{
/// <summary>
/// Gets the required region.
/// </summary>
/// <value>
/// The required region.
/// </value>
RectangleF RequiredRegion { get; }
/// <summary>
/// Gets a <see cref="ColoredPointInfo{TColor, TPacked}" /> from a point represented by a <see cref="PointInfo" />.
/// </summary>
/// <param name="info">The information to extract color details about.</param>
/// <returns>Returns the color details and distance from a solid bit of the line.</returns>
ColoredPointInfo<TColor, TPacked> GetColor(PointInfo info);
}
}

195
src/ImageSharp/Drawing/Processors/DrawPathProcessor.cs

@ -0,0 +1,195 @@
// <copyright file="DrawPathProcessor.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp.Drawing.Processors
{
using System;
using System.Linq;
using System.Numerics;
using System.Threading.Tasks;
using ImageSharp.Processors;
using Paths;
using Pens;
using Pens.Processors;
using Shapes;
/// <summary>
/// Draws a path using the processor pipeline
/// </summary>
/// <typeparam name="TColor">The type of the color.</typeparam>
/// <typeparam name="TPacked">The type of the packed.</typeparam>
/// <seealso cref="ImageSharp.Processors.ImageFilteringProcessor{TColor, TPacked}" />
public class DrawPathProcessor<TColor, TPacked> : ImageFilteringProcessor<TColor, TPacked>
where TColor : struct, IPackedPixel<TPacked>
where TPacked : struct
{
private const float AntialiasFactor = 1f;
private const int PaddingFactor = 1; // needs to been the same or greater than AntialiasFactor
private const float Epsilon = 0.001f;
private readonly IPen<TColor, TPacked> pen;
private readonly IPath[] paths;
private readonly RectangleF region;
private readonly GraphicsOptions options;
/// <summary>
/// Initializes a new instance of the <see cref="DrawPathProcessor{TColor, TPacked}" /> class.
/// </summary>
/// <param name="pen">The pen.</param>
/// <param name="shape">The shape.</param>
/// <param name="options">The options.</param>
public DrawPathProcessor(IPen<TColor, TPacked> pen, IShape shape, GraphicsOptions options)
: this(pen, shape.ToArray(), options)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="DrawPathProcessor{TColor, TPacked}"/> class.
/// </summary>
/// <param name="pen">The pen.</param>
/// <param name="path">The path.</param>
/// <param name="options">The options.</param>
public DrawPathProcessor(IPen<TColor, TPacked> pen, IPath path, GraphicsOptions options)
: this(pen, new[] { path }, options)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="DrawPathProcessor{TColor, TPacked}" /> class.
/// </summary>
/// <param name="pen">The pen.</param>
/// <param name="paths">The paths.</param>
/// <param name="options">The options.</param>
public DrawPathProcessor(IPen<TColor, TPacked> pen, IPath[] paths, GraphicsOptions options)
{
this.paths = paths;
this.pen = pen;
this.options = options;
if (paths.Length != 1)
{
var maxX = paths.Max(x => x.Bounds.Right);
var minX = paths.Min(x => x.Bounds.Left);
var maxY = paths.Max(x => x.Bounds.Bottom);
var minY = paths.Min(x => x.Bounds.Top);
this.region = new RectangleF(minX, minY, maxX - minX, maxY - minY);
}
else
{
this.region = paths[0].Bounds;
}
}
/// <inheritdoc/>
protected override void OnApply(ImageBase<TColor, TPacked> source, Rectangle sourceRectangle)
{
using (IPenApplicator<TColor, TPacked> applicator = this.pen.CreateApplicator(this.region))
{
var rect = RectangleF.Ceiling(applicator.RequiredRegion);
int polyStartY = rect.Y - PaddingFactor;
int polyEndY = rect.Bottom + PaddingFactor;
int startX = rect.X - PaddingFactor;
int endX = rect.Right + PaddingFactor;
int minX = Math.Max(sourceRectangle.Left, startX);
int maxX = Math.Min(sourceRectangle.Right, endX);
int minY = Math.Max(sourceRectangle.Top, polyStartY);
int maxY = Math.Min(sourceRectangle.Bottom, polyEndY);
// Align start/end positions.
minX = Math.Max(0, minX);
maxX = Math.Min(source.Width, maxX);
minY = Math.Max(0, minY);
maxY = Math.Min(source.Height, maxY);
// Reset offset if necessary.
if (minX > 0)
{
startX = 0;
}
if (minY > 0)
{
polyStartY = 0;
}
using (PixelAccessor<TColor, TPacked> sourcePixels = source.Lock())
{
Parallel.For(
minY,
maxY,
this.ParallelOptions,
y =>
{
int offsetY = y - polyStartY;
var currentPoint = default(Vector2);
for (int x = minX; x < maxX; x++)
{
int offsetX = x - startX;
currentPoint.X = offsetX;
currentPoint.Y = offsetY;
var dist = Closest(currentPoint);
var color = applicator.GetColor(dist);
var opacity = this.Opacity(color.DistanceFromElement);
if (opacity > Epsilon)
{
int offsetColorX = x - minX;
Vector4 backgroundVector = sourcePixels[offsetX, offsetY].ToVector4();
Vector4 sourceVector = color.Color.ToVector4();
var finalColor = Vector4BlendTransforms.PremultipliedLerp(backgroundVector, sourceVector, opacity);
finalColor.W = backgroundVector.W;
TColor packed = default(TColor);
packed.PackFromVector4(finalColor);
sourcePixels[offsetX, offsetY] = packed;
}
}
});
}
}
}
private PointInfo Closest(Vector2 point)
{
PointInfo result = default(PointInfo);
float distance = float.MaxValue;
for (int i = 0; i < this.paths.Length; i++)
{
var p = this.paths[i].Distance(point);
if (p.DistanceFromPath < distance)
{
distance = p.DistanceFromPath;
result = p;
}
}
return result;
}
private float Opacity(float distance)
{
if (distance <= 0)
{
return 1;
}
else if (this.options.Antialias && distance < AntialiasFactor)
{
return 1 - (distance / AntialiasFactor);
}
return 0;
}
}
}

97
src/ImageSharp/Drawing/Processors/FillProcessor.cs

@ -0,0 +1,97 @@
// <copyright file="FillProcessor.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp.Drawing.Processors
{
using System;
using System.Numerics;
using System.Threading.Tasks;
using Drawing;
using ImageSharp.Processors;
/// <summary>
/// Using the bursh as a source of pixels colors blends the brush color with source.
/// </summary>
/// <typeparam name="TColor">The pixel format.</typeparam>
/// <typeparam name="TPacked">The packed format. <example>uint, long, float.</example></typeparam>
public class FillProcessor<TColor, TPacked> : ImageFilteringProcessor<TColor, TPacked>
where TColor : struct, IPackedPixel<TPacked>
where TPacked : struct
{
private const float Epsilon = 0.001f;
private readonly IBrush<TColor, TPacked> brush;
/// <summary>
/// Initializes a new instance of the <see cref="FillProcessor{TColor, TPacked}"/> class.
/// </summary>
/// <param name="brush">The brush to source pixel colors from.</param>
public FillProcessor(IBrush<TColor, TPacked> brush)
{
this.brush = brush;
}
/// <inheritdoc/>
protected override void OnApply(ImageBase<TColor, TPacked> source, Rectangle sourceRectangle)
{
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);
// Reset offset if necessary.
if (minX > 0)
{
startX = 0;
}
if (minY > 0)
{
startY = 0;
}
// we could possibly do some optermising by having knowledge about the individual brushes operate
// for example If brush is SolidBrush<TColor, TPacked> then we could just get the color upfront
// and skip using the IBrushApplicator<TColor, TPacked>?.
using (PixelAccessor<TColor, TPacked> sourcePixels = source.Lock())
using (IBrushApplicator<TColor, TPacked> applicator = this.brush.CreateApplicator(sourceRectangle))
{
Parallel.For(
minY,
maxY,
this.ParallelOptions,
y =>
{
int offsetY = y - startY;
Vector2 currentPoint = default(Vector2);
for (int x = minX; x < maxX; x++)
{
int offsetX = x - startX;
int offsetColorX = x - minX;
currentPoint.X = offsetX;
currentPoint.Y = offsetY;
Vector4 backgroundVector = sourcePixels[offsetX, offsetY].ToVector4();
Vector4 sourceVector = applicator.GetColor(currentPoint).ToVector4();
var finalColor = Vector4BlendTransforms.PremultipliedLerp(backgroundVector, sourceVector, 1);
TColor packed = default(TColor);
packed.PackFromVector4(finalColor);
sourcePixels[offsetX, offsetY] = packed;
}
});
}
}
}
}

131
src/ImageSharp/Drawing/Processors/FillShapeProcessor.cs

@ -0,0 +1,131 @@
// <copyright file="FillShapeProcessor.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp.Drawing.Processors
{
using System;
using System.Numerics;
using System.Threading.Tasks;
using Drawing;
using ImageSharp.Processors;
using Shapes;
/// <summary>
/// Usinf a brsuh and a shape fills shape with contents of brush the
/// </summary>
/// <typeparam name="TColor">The type of the color.</typeparam>
/// <typeparam name="TPacked">The type of the packed.</typeparam>
/// <seealso cref="ImageSharp.Processors.ImageFilteringProcessor{TColor, TPacked}" />
public class FillShapeProcessor<TColor, TPacked> : ImageFilteringProcessor<TColor, TPacked>
where TColor : struct, IPackedPixel<TPacked>
where TPacked : struct
{
private const float Epsilon = 0.001f;
private const float AntialiasFactor = 1f;
private const int DrawPadding = 1;
private readonly IBrush<TColor, TPacked> fillColor;
private readonly IShape poly;
private readonly GraphicsOptions options;
/// <summary>
/// Initializes a new instance of the <see cref="FillShapeProcessor{TColor, TPacked}"/> class.
/// </summary>
/// <param name="brush">The brush.</param>
/// <param name="shape">The shape.</param>
public FillShapeProcessor(IBrush<TColor, TPacked> brush, IShape shape, GraphicsOptions options)
{
this.poly = shape;
this.fillColor = brush;
this.options = options;
}
/// <inheritdoc/>
protected override void OnApply(ImageBase<TColor, TPacked> source, Rectangle sourceRectangle)
{
var rect = RectangleF.Ceiling(this.poly.Bounds); // rounds the points out away from the center
int polyStartY = rect.Y - DrawPadding;
int polyEndY = rect.Bottom + DrawPadding;
int startX = rect.X - DrawPadding;
int endX = rect.Right + DrawPadding;
int minX = Math.Max(sourceRectangle.Left, startX);
int maxX = Math.Min(sourceRectangle.Right, endX);
int minY = Math.Max(sourceRectangle.Top, polyStartY);
int maxY = Math.Min(sourceRectangle.Bottom, polyEndY);
// Align start/end positions.
minX = Math.Max(0, minX);
maxX = Math.Min(source.Width, maxX);
minY = Math.Max(0, minY);
maxY = Math.Min(source.Height, maxY);
// Reset offset if necessary.
if (minX > 0)
{
startX = 0;
}
if (minY > 0)
{
polyStartY = 0;
}
using (PixelAccessor<TColor, TPacked> sourcePixels = source.Lock())
using (IBrushApplicator<TColor, TPacked> applicator = this.fillColor.CreateApplicator(rect))
{
Parallel.For(
minY,
maxY,
this.ParallelOptions,
y =>
{
int offsetY = y - polyStartY;
Vector2 currentPoint = default(Vector2);
Vector2 currentPointOffset = default(Vector2);
for (int x = minX; x < maxX; x++)
{
int offsetX = x - startX;
currentPoint.X = offsetX;
currentPoint.Y = offsetY;
var dist = this.poly.Distance(currentPoint);
var opacity = this.Opacity(dist);
if (opacity > Epsilon)
{
int offsetColorX = x - minX;
Vector4 backgroundVector = sourcePixels[offsetX, offsetY].ToVector4();
Vector4 sourceVector = applicator.GetColor(currentPoint).ToVector4();
var finalColor = Vector4BlendTransforms.PremultipliedLerp(backgroundVector, sourceVector, opacity);
finalColor.W = backgroundVector.W;
TColor packed = default(TColor);
packed.PackFromVector4(finalColor);
sourcePixels[offsetX, offsetY] = packed;
}
}
});
}
}
private float Opacity(float distance)
{
if (distance <= 0)
{
return 1;
}
else if (this.options.Antialias && distance < AntialiasFactor)
{
return 1 - (distance / AntialiasFactor);
}
return 0;
}
}
}

68
src/ImageSharp/Drawing/Shapes/BezierPolygon.cs

@ -0,0 +1,68 @@
// <copyright file="BezierPolygon.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp.Drawing.Shapes
{
using System.Collections;
using System.Collections.Generic;
using System.Numerics;
using Paths;
/// <summary>
/// Represents a polygon made up exclusivly of a single close cubic Bezier curve.
/// </summary>
public sealed class BezierPolygon : IShape
{
private Polygon innerPolygon;
/// <summary>
/// Initializes a new instance of the <see cref="BezierPolygon"/> class.
/// </summary>
/// <param name="points">The points.</param>
public BezierPolygon(params Vector2[] points)
{
this.innerPolygon = new Polygon(new BezierLineSegment(points));
}
/// <summary>
/// Gets the bounding box of this shape.
/// </summary>
/// <value>
/// The bounds.
/// </value>
public RectangleF Bounds => this.innerPolygon.Bounds;
/// <summary>
/// the distance of the point from the outline of the shape, if the value is negative it is inside the polygon bounds
/// </summary>
/// <param name="point">The point.</param>
/// <returns>
/// The distance from the shape.
/// </returns>
public float Distance(Vector2 point) => this.innerPolygon.Distance(point);
/// <summary>
/// Returns an enumerator that iterates through the collection.
/// </summary>
/// <returns>
/// An enumerator that can be used to iterate through the collection.
/// </returns>
public IEnumerator<IPath> GetEnumerator()
{
return this.innerPolygon.GetEnumerator();
}
/// <summary>
/// Returns an enumerator that iterates through a collection.
/// </summary>
/// <returns>
/// An <see cref="T:System.Collections.IEnumerator" /> object that can be used to iterate through the collection.
/// </returns>
IEnumerator IEnumerable.GetEnumerator()
{
return this.innerPolygon.GetEnumerator();
}
}
}

4924
src/ImageSharp/Drawing/Shapes/Clipper.cs

File diff suppressed because it is too large

196
src/ImageSharp/Drawing/Shapes/ComplexPolygon.cs

@ -0,0 +1,196 @@
// <copyright file="ComplexPolygon.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp.Drawing.Shapes
{
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
using Paths;
/// <summary>
/// Represents a complex polygon made up of one or more outline
/// polygons and one or more holes to punch out of them.
/// </summary>
/// <seealso cref="ImageSharp.Drawing.Shapes.IShape" />
public sealed class ComplexPolygon : IShape
{
private const float ClipperScaleFactor = 100f;
private IEnumerable<IShape> holes;
private IEnumerable<IShape> outlines;
private IEnumerable<IPath> paths;
/// <summary>
/// Initializes a new instance of the <see cref="ComplexPolygon"/> class.
/// </summary>
/// <param name="outline">The outline.</param>
/// <param name="holes">The holes.</param>
public ComplexPolygon(IShape outline, params IShape[] holes)
: this(new[] { outline }, holes)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="ComplexPolygon"/> class.
/// </summary>
/// <param name="outlines">The outlines.</param>
/// <param name="holes">The holes.</param>
public ComplexPolygon(IEnumerable<IShape> outlines, IEnumerable<IShape> holes)
{
Guard.NotNull(outlines, nameof(outlines));
Guard.MustBeGreaterThanOrEqualTo(outlines.Count(), 1, nameof(outlines));
this.FixAndSetShapes(outlines, holes);
var minX = outlines.Min(x => x.Bounds.Left);
var maxX = outlines.Max(x => x.Bounds.Right);
var minY = outlines.Min(x => x.Bounds.Top);
var maxY = outlines.Max(x => x.Bounds.Bottom);
this.Bounds = new RectangleF(minX, minY, maxX - minX, maxY - minY);
}
/// <summary>
/// Gets the bounding box of this shape.
/// </summary>
/// <value>
/// The bounds.
/// </value>
public RectangleF Bounds { get; }
/// <summary>
/// the distance of the point from the outline of the shape, if the value is negative it is inside the polygon bounds
/// </summary>
/// <param name="point">The point.</param>
/// <returns>
/// Returns the distance from thr shape to the point
/// </returns>
float IShape.Distance(Vector2 point)
{
// get the outline we are closest to the center of
// by rights we should only be inside 1 outline
// othersie we will start returning the distanct to the nearest shape
var dist = this.outlines.Select(o => o.Distance(point)).OrderBy(p => p).First();
if (dist <= 0)
{
// inside poly
foreach (var hole in this.holes)
{
var distFromHole = hole.Distance(point);
// less than zero we are inside shape
if (distFromHole <= 0)
{
// invert distance
dist = distFromHole * -1;
break;
}
}
}
return dist;
}
/// <summary>
/// Returns an enumerator that iterates through the collection.
/// </summary>
/// <returns>
/// An enumerator that can be used to iterate through the collection.
/// </returns>
public IEnumerator<IPath> GetEnumerator()
{
return this.paths.GetEnumerator();
}
/// <summary>
/// Returns an enumerator that iterates through a collection.
/// </summary>
/// <returns>
/// An <see cref="T:System.Collections.IEnumerator" /> object that can be used to iterate through the collection.
/// </returns>
IEnumerator IEnumerable.GetEnumerator()
{
return this.GetEnumerator();
}
private void AddPoints(ClipperLib.Clipper clipper, IShape shape, ClipperLib.PolyType polyType)
{
foreach (var path in shape)
{
var points = path.AsSimpleLinearPath();
var clipperPoints = new List<ClipperLib.IntPoint>();
foreach (var point in points)
{
var p = point * ClipperScaleFactor;
clipperPoints.Add(new ClipperLib.IntPoint((long)p.X, (long)p.Y));
}
clipper.AddPath(
clipperPoints,
polyType,
path.IsClosed);
}
}
private void AddPoints(ClipperLib.Clipper clipper, IEnumerable<IShape> shapes, ClipperLib.PolyType polyType)
{
foreach (var shape in shapes)
{
this.AddPoints(clipper, shape, polyType);
}
}
private void ExtractOutlines(ClipperLib.PolyNode tree, List<Polygon> outlines, List<Polygon> holes)
{
if (tree.Contour.Any())
{
// convert the Clipper Contour from scaled ints back down to the origional size (this is going to be lossy but not significantly)
var polygon = new Polygon(new LinearLineSegment(tree.Contour.Select(x => new Vector2(x.X / ClipperScaleFactor, x.Y / ClipperScaleFactor)).ToArray()));
if (tree.IsHole)
{
holes.Add(polygon);
}
else
{
outlines.Add(polygon);
}
}
foreach (var c in tree.Childs)
{
this.ExtractOutlines(c, outlines, holes);
}
}
private void FixAndSetShapes(IEnumerable<IShape> outlines, IEnumerable<IShape> holes)
{
var clipper = new ClipperLib.Clipper();
// add the outlines and the holes to clipper, scaling up from the float source to the int based system clipper uses
this.AddPoints(clipper, outlines, ClipperLib.PolyType.ptSubject);
this.AddPoints(clipper, holes, ClipperLib.PolyType.ptClip);
var tree = new ClipperLib.PolyTree();
clipper.Execute(ClipperLib.ClipType.ctDifference, tree);
List<Polygon> newOutlines = new List<Polygon>();
List<Polygon> newHoles = new List<Polygon>();
// convert the 'tree' back to paths
this.ExtractOutlines(tree, newOutlines, newHoles);
this.outlines = newOutlines;
this.holes = newHoles;
// extract the final list of paths out of the new polygons we just converted down to.
this.paths = newOutlines.Union(newHoles).ToArray();
}
}
}

34
src/ImageSharp/Drawing/Shapes/IShape.cs

@ -0,0 +1,34 @@
// <copyright file="IShape.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp.Drawing.Shapes
{
using System.Collections.Generic;
using System.Numerics;
using Paths;
/// <summary>
/// Represents a closed set of paths making up a single shape.
/// </summary>
public interface IShape : IEnumerable<IPath>
{
/// <summary>
/// Gets the bounding box of this shape.
/// </summary>
/// <value>
/// The bounds.
/// </value>
RectangleF Bounds { get; }
/// <summary>
/// the distance of the point from the outline of the shape, if the value is negative it is inside the polygon bounds
/// </summary>
/// <param name="point">The point.</param>
/// <returns>
/// Returns the distance from the shape to the point
/// </returns>
float Distance(Vector2 point);
}
}

62
src/ImageSharp/Drawing/Shapes/LinearPolygon.cs

@ -0,0 +1,62 @@
// <copyright file="LinearPolygon.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp.Drawing.Shapes
{
using System.Collections;
using System.Collections.Generic;
using System.Numerics;
using Paths;
/// <summary>
/// Represents a polygon made up exclusivly of a single Linear path.
/// </summary>
public sealed class LinearPolygon : IShape
{
private Polygon innerPolygon;
/// <summary>
/// Initializes a new instance of the <see cref="LinearPolygon"/> class.
/// </summary>
/// <param name="points">The points.</param>
public LinearPolygon(params Vector2[] points)
{
this.innerPolygon = new Polygon(new LinearLineSegment(points));
}
/// <summary>
/// Gets the bounding box of this shape.
/// </summary>
/// <value>
/// The bounds.
/// </value>
public RectangleF Bounds => this.innerPolygon.Bounds;
/// <summary>
/// the distance of the point from the outline of the shape, if the value is negative it is inside the polygon bounds
/// </summary>
/// <param name="point">The point.</param>
/// <returns>
/// Returns the distance from the shape to the point
/// </returns>
public float Distance(Vector2 point) => this.innerPolygon.Distance(point);
/// <summary>
/// Returns an enumerator that iterates through the collection.
/// </summary>
/// <returns>
/// An enumerator that can be used to iterate through the collection.
/// </returns>
public IEnumerator<IPath> GetEnumerator() => this.innerPolygon.GetEnumerator();
/// <summary>
/// Returns an enumerator that iterates through a collection.
/// </summary>
/// <returns>
/// An <see cref="T:System.Collections.IEnumerator" /> object that can be used to iterate through the collection.
/// </returns>
IEnumerator IEnumerable.GetEnumerator() => this.innerPolygon.GetEnumerator();
}
}

122
src/ImageSharp/Drawing/Shapes/Polygon.cs

@ -0,0 +1,122 @@
// <copyright file="Polygon.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp.Drawing.Shapes
{
using System.Collections;
using System.Collections.Generic;
using System.Numerics;
using Paths;
/// <summary>
/// A shape made up of a single path made up of one of more <see cref="ILineSegment"/>s
/// </summary>
public sealed class Polygon : IShape, IPath
{
private readonly InternalPath innerPath;
private readonly IEnumerable<IPath> pathCollection;
/// <summary>
/// Initializes a new instance of the <see cref="Polygon"/> class.
/// </summary>
/// <param name="segments">The segments.</param>
public Polygon(params ILineSegment[] segments)
{
this.innerPath = new InternalPath(segments, true);
this.pathCollection = new[] { this };
}
/// <summary>
/// Gets the bounding box of this shape.
/// </summary>
/// <value>
/// The bounds.
/// </value>
public RectangleF Bounds => this.innerPath.Bounds;
/// <summary>
/// Gets the length of the path
/// </summary>
/// <value>
/// The length.
/// </value>
public float Length => this.innerPath.Length;
/// <summary>
/// Gets a value indicating whether this instance is closed.
/// </summary>
/// <value>
/// <c>true</c> if this instance is closed; otherwise, <c>false</c>.
/// </value>
public bool IsClosed => true;
/// <summary>
/// the distance of the point from the outline of the shape, if the value is negative it is inside the polygon bounds
/// </summary>
/// <param name="point">The point.</param>
/// <returns>
/// The distance of the point away from the shape
/// </returns>
public float Distance(Vector2 point)
{
bool isInside = this.innerPath.PointInPolygon(point);
var distance = this.innerPath.DistanceFromPath(point).DistanceFromPath;
if (isInside)
{
return -distance;
}
return distance;
}
/// <summary>
/// Returns an enumerator that iterates through the collection.
/// </summary>
/// <returns>
/// An enumerator that can be used to iterate through the collection.
/// </returns>
public IEnumerator<IPath> GetEnumerator()
{
return this.pathCollection.GetEnumerator();
}
/// <summary>
/// Returns an enumerator that iterates through a collection.
/// </summary>
/// <returns>
/// An <see cref="T:System.Collections.IEnumerator" /> object that can be used to iterate through the collection.
/// </returns>
IEnumerator IEnumerable.GetEnumerator()
{
return this.pathCollection.GetEnumerator();
}
/// <summary>
/// Calcualtes the distance along and away from the path for a specified point.
/// </summary>
/// <param name="x">The x.</param>
/// <param name="y">The y.</param>
/// <returns>
/// distance metadata about the point.
/// </returns>
PointInfo IPath.Distance(Vector2 point)
{
return this.innerPath.DistanceFromPath(point);
}
/// <summary>
/// Returns the current shape as a simple linear path.
/// </summary>
/// <returns>
/// Returns the current <see cref="ILineSegment" /> as simple linear path.
/// </returns>
public Vector2[] AsSimpleLinearPath()
{
return this.innerPath.Points;
}
}
}

13
src/ImageSharp/Numerics/Rectangle.cs

@ -244,6 +244,19 @@ namespace ImageSharp
&& y < this.Bottom;
}
/// <summary>
/// Determines if the specfied <see cref="Rectangle"/> intersects the rectangular region defined by
/// this <see cref="Rectangle"/>.
/// </summary>
/// <param name="rect">The other Rectange </param>
/// <returns>The <see cref="bool"/></returns>
public bool Intersects(Rectangle rect)
{
return rect.Left <= this.Right && rect.Right >= this.Left
&&
rect.Top <= this.Bottom && rect.Bottom >= this.Top;
}
/// <inheritdoc/>
public override int GetHashCode()
{

334
src/ImageSharp/Numerics/RectangleF.cs

@ -0,0 +1,334 @@
// <copyright file="RectangleF.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp
{
using System;
using System.ComponentModel;
using System.Numerics;
/// <summary>
/// Stores a set of four integers that represent the location and size of a rectangle.
/// </summary>
/// <remarks>
/// This struct is fully mutable. This is done (against the guidelines) for the sake of performance,
/// as it avoids the need to create new values for modification operations.
/// </remarks>
public struct RectangleF : IEquatable<RectangleF>
{
/// <summary>
/// Represents a <see cref="Rectangle"/> that has X, Y, Width, and Height values set to zero.
/// </summary>
public static readonly RectangleF Empty = default(RectangleF);
/// <summary>
/// The backing vector for SIMD support.
/// </summary>
private Vector4 backingVector;
/// <summary>
/// Initializes a new instance of the <see cref="RectangleF"/> struct.
/// </summary>
/// <param name="x">The horizontal position of the rectangle.</param>
/// <param name="y">The vertical position of the rectangle.</param>
/// <param name="width">The width of the rectangle.</param>
/// <param name="height">The height of the rectangle.</param>
public RectangleF(float x, float y, float width, float height)
{
this.backingVector = new Vector4(x, y, width, height);
}
/// <summary>
/// Initializes a new instance of the <see cref="RectangleF"/> struct.
/// </summary>
/// <param name="vector">The vector.</param>
public RectangleF(Vector4 vector)
{
this.backingVector = vector;
}
/// <summary>
/// Gets or sets the x-coordinate of this <see cref="RectangleF"/>.
/// </summary>
public float X
{
get
{
return this.backingVector.X;
}
set
{
this.backingVector.X = value;
}
}
/// <summary>
/// Gets or sets the y-coordinate of this <see cref="RectangleF"/>.
/// </summary>
public float Y
{
get
{
return this.backingVector.Y;
}
set
{
this.backingVector.Y = value;
}
}
/// <summary>
/// Gets or sets the width of this <see cref="RectangleF"/>.
/// </summary>
public float Width
{
get
{
return this.backingVector.Z;
}
set
{
this.backingVector.Z = value;
}
}
/// <summary>
/// Gets or sets the height of this <see cref="RectangleF"/>.
/// </summary>
public float Height
{
get
{
return this.backingVector.W;
}
set
{
this.backingVector.W = value;
}
}
/// <summary>
/// Gets a value indicating whether this <see cref="RectangleF"/> is empty.
/// </summary>
[EditorBrowsable(EditorBrowsableState.Never)]
public bool IsEmpty => this.Equals(Empty);
/// <summary>
/// Gets the y-coordinate of the top edge of this <see cref="RectangleF"/>.
/// </summary>
public float Top => this.Y;
/// <summary>
/// Gets the x-coordinate of the right edge of this <see cref="RectangleF"/>.
/// </summary>
public float Right => this.X + this.Width;
/// <summary>
/// Gets the y-coordinate of the bottom edge of this <see cref="RectangleF"/>.
/// </summary>
public float Bottom => this.Y + this.Height;
/// <summary>
/// Gets the x-coordinate of the left edge of this <see cref="RectangleF"/>.
/// </summary>
public float Left => this.X;
/// <summary>
/// Performs an implicit conversion from <see cref="Rectangle"/> to <see cref="RectangleF"/>.
/// </summary>
/// <param name="d">The d.</param>
/// <returns>
/// The result of the conversion.
/// </returns>
public static implicit operator RectangleF(Rectangle d)
{
return new RectangleF(d.Left, d.Top, d.Width, d.Height);
}
/// <summary>
/// Computes the sum of adding two rectangles.
/// </summary>
/// <param name="left">The rectangle on the left hand of the operand.</param>
/// <param name="right">The rectangle on the right hand of the operand.</param>
/// <returns>
/// The <see cref="RectangleF"/>
/// </returns>
public static RectangleF operator +(RectangleF left, RectangleF right)
{
return new RectangleF(left.backingVector + right.backingVector);
}
/// <summary>
/// Computes the difference left by subtracting one rectangle from another.
/// </summary>
/// <param name="left">The rectangle on the left hand of the operand.</param>
/// <param name="right">The rectangle on the right hand of the operand.</param>
/// <returns>
/// The <see cref="RectangleF"/>
/// </returns>
public static RectangleF operator -(RectangleF left, RectangleF right)
{
return new RectangleF(left.backingVector - right.backingVector);
}
/// <summary>
/// Compares two <see cref="RectangleF"/> objects for equality.
/// </summary>
/// <param name="left">
/// The <see cref="RectangleF"/> on the left side of the operand.
/// </param>
/// <param name="right">
/// The <see cref="RectangleF"/> on the right side of the operand.
/// </param>
/// <returns>
/// True if the current left is equal to the <paramref name="right"/> parameter; otherwise, false.
/// </returns>
public static bool operator ==(RectangleF left, RectangleF right)
{
return left.Equals(right);
}
/// <summary>
/// Compares two <see cref="RectangleF"/> objects for inequality.
/// </summary>
/// <param name="left">
/// The <see cref="RectangleF"/> on the left side of the operand.
/// </param>
/// <param name="right">
/// The <see cref="RectangleF"/> on the right side of the operand.
/// </param>
/// <returns>
/// True if the current left is unequal to the <paramref name="right"/> parameter; otherwise, false.
/// </returns>
public static bool operator !=(RectangleF left, RectangleF right)
{
return !left.Equals(right);
}
/// <summary>
/// Returns the center point of the given <see cref="RectangleF"/>
/// </summary>
/// <param name="rectangle">The rectangle</param>
/// <returns><see cref="Point"/></returns>
public static Vector2 Center(RectangleF rectangle)
{
return new Vector2(rectangle.Left + (rectangle.Width / 2), rectangle.Top + (rectangle.Height / 2));
}
/// <summary>
/// Rounds the points away from the center this into a <see cref="Rectangle"/>
/// by rounding the dimensions to the nerent integer ensuring that the new rectangle is
/// never smaller than the source <see cref="RectangleF"/>
/// </summary>
/// <param name="source">The source area to round out</param>
/// <returns>
/// The smallest <see cref="Rectangle"/> that the <see cref="RectangleF"/> will fit inside.
/// </returns>
public static Rectangle Ceiling(RectangleF source)
{
int y = (int)Math.Floor(source.Y);
int width = (int)Math.Ceiling(source.Width);
int x = (int)Math.Floor(source.X);
int height = (int)Math.Ceiling(source.Height);
return new Rectangle(x, y, width, height);
}
/// <summary>
/// Outsets the specified region.
/// </summary>
/// <param name="region">The region.</param>
/// <param name="width">The width.</param>
/// <returns>
/// The <see cref="RectangleF"/> with all dimensions move away from the center by the offset.
/// </returns>
public static RectangleF Outset(RectangleF region, float width)
{
var dblWidth = width * 2;
return new RectangleF(region.X - width, region.Y - width, region.Width + dblWidth, region.Height + dblWidth);
}
/// <summary>
/// Determines if the specfied point is contained within the rectangular region defined by
/// this <see cref="RectangleF"/>.
/// </summary>
/// <param name="x">The x-coordinate of the given point.</param>
/// <param name="y">The y-coordinate of the given point.</param>
/// <returns>The <see cref="bool"/></returns>
public bool Contains(float x, float y)
{
// TODO: SIMD?
return this.X <= x
&& x < this.Right
&& this.Y <= y
&& y < this.Bottom;
}
/// <summary>
/// Determines if the specfied <see cref="Rectangle"/> intersects the rectangular region defined by
/// this <see cref="Rectangle"/>.
/// </summary>
/// <param name="rect">The other Rectange </param>
/// <returns>The <see cref="bool"/></returns>
public bool Intersects(RectangleF rect)
{
return rect.Left <= this.Right && rect.Right >= this.Left
&&
rect.Top <= this.Bottom && rect.Bottom >= this.Top;
}
/// <inheritdoc/>
public override int GetHashCode()
{
return this.GetHashCode(this);
}
/// <inheritdoc/>
public override string ToString()
{
if (this.IsEmpty)
{
return "Rectangle [ Empty ]";
}
return
$"Rectangle [ X={this.X}, Y={this.Y}, Width={this.Width}, Height={this.Height} ]";
}
/// <inheritdoc/>
public override bool Equals(object obj)
{
if (obj is RectangleF)
{
return this.Equals((RectangleF)obj);
}
return false;
}
/// <inheritdoc/>
public bool Equals(RectangleF other)
{
return this.backingVector.Equals(other.backingVector);
}
/// <summary>
/// Returns the hash code for this instance.
/// </summary>
/// <param name="rectangle">
/// The instance of <see cref="RectangleF"/> to return the hash code for.
/// </param>
/// <returns>
/// A 32-bit signed integer that is the hash code for this instance.
/// </returns>
private int GetHashCode(RectangleF rectangle)
{
return rectangle.backingVector.GetHashCode();
}
}
}

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

@ -0,0 +1,64 @@
// <copyright file="Crop.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp.Benchmarks
{
using System.Drawing;
using System.Drawing.Drawing2D;
using BenchmarkDotNet.Attributes;
using CoreImage = ImageSharp.Image;
using CorePoint = ImageSharp.Point;
using CoreColor = ImageSharp.Color;
using System.IO;
using System.Numerics;
public class DrawBeziers
{
[Benchmark(Baseline = true, Description = "System.Drawing Draw Beziers")]
public void DrawPathSystemDrawing()
{
using (Bitmap destination = new Bitmap(800, 800))
{
using (Graphics graphics = Graphics.FromImage(destination))
{
graphics.InterpolationMode = InterpolationMode.Default;
graphics.SmoothingMode = SmoothingMode.AntiAlias;
var pen = new Pen(Color.HotPink, 10);
graphics.DrawBeziers(pen, new[] {
new PointF(10, 500),
new PointF(30, 10),
new PointF(240, 30),
new PointF(300, 500)
});
}
using (MemoryStream ms = new MemoryStream())
{
destination.Save(ms, System.Drawing.Imaging.ImageFormat.Bmp);
}
}
}
[Benchmark(Description = "ImageSharp Draw Beziers")]
public void DrawLinesCore()
{
CoreImage image = new CoreImage(800, 800);
image.DrawBeziers(CoreColor.HotPink, 10, new[] {
new Vector2(10, 500),
new Vector2(30, 10),
new Vector2(240, 30),
new Vector2(300, 500)
});
using (MemoryStream ms = new MemoryStream())
{
image.SaveAsBmp(ms);
}
}
}
}

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

@ -0,0 +1,62 @@
// <copyright file="Crop.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp.Benchmarks
{
using System.Drawing;
using System.Drawing.Drawing2D;
using BenchmarkDotNet.Attributes;
using CoreImage = ImageSharp.Image;
using CorePoint = ImageSharp.Point;
using CoreColor = ImageSharp.Color;
using System.IO;
using System.Numerics;
public class DrawLines
{
[Benchmark(Baseline = true, Description = "System.Drawing Draw Lines")]
public void DrawPathSystemDrawing()
{
using (Bitmap destination = new Bitmap(800, 800))
{
using (Graphics graphics = Graphics.FromImage(destination))
{
graphics.InterpolationMode = InterpolationMode.Default;
graphics.SmoothingMode = SmoothingMode.AntiAlias;
var pen = new Pen(Color.HotPink, 10);
graphics.DrawLines(pen, new[] {
new PointF(10, 10),
new PointF(550, 50),
new PointF(200, 400)
});
}
using (MemoryStream ms = new MemoryStream())
{
destination.Save(ms, System.Drawing.Imaging.ImageFormat.Bmp);
}
}
}
[Benchmark(Description = "ImageSharp Draw Lines")]
public void DrawLinesCore()
{
CoreImage image = new CoreImage(800, 800);
image.DrawLines(CoreColor.HotPink, 10, new[] {
new Vector2(10, 10),
new Vector2(550, 50),
new Vector2(200, 400)
});
using (MemoryStream ms = new MemoryStream())
{
image.SaveAsBmp(ms);
}
}
}
}

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

@ -0,0 +1,62 @@
// <copyright file="Crop.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp.Benchmarks
{
using System.Drawing;
using System.Drawing.Drawing2D;
using BenchmarkDotNet.Attributes;
using CoreImage = ImageSharp.Image;
using CorePoint = ImageSharp.Point;
using CoreColor = ImageSharp.Color;
using System.IO;
using System.Numerics;
public class DrawPolygon
{
[Benchmark(Baseline = true, Description = "System.Drawing Draw Polygon")]
public void DrawPolygonSystemDrawing()
{
using (Bitmap destination = new Bitmap(800, 800))
{
using (Graphics graphics = Graphics.FromImage(destination))
{
graphics.InterpolationMode = InterpolationMode.Default;
graphics.SmoothingMode = SmoothingMode.AntiAlias;
var pen = new Pen(Color.HotPink, 10);
graphics.DrawPolygon(pen, new[] {
new PointF(10, 10),
new PointF(550, 50),
new PointF(200, 400)
});
}
using (MemoryStream ms = new MemoryStream())
{
destination.Save(ms, System.Drawing.Imaging.ImageFormat.Bmp);
}
}
}
[Benchmark(Description = "ImageSharp Draw Polygon")]
public void DrawPolygonCore()
{
CoreImage image = new CoreImage(800, 800);
image.DrawPolygon(CoreColor.HotPink, 10, new[] {
new Vector2(10, 10),
new Vector2(550, 50),
new Vector2(200, 400)
});
using (MemoryStream ms = new MemoryStream())
{
image.SaveAsBmp(ms);
}
}
}
}

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

@ -0,0 +1,59 @@
// <copyright file="Crop.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp.Benchmarks
{
using System.Drawing;
using System.Drawing.Drawing2D;
using BenchmarkDotNet.Attributes;
using CoreImage = ImageSharp.Image;
using CoreColor = ImageSharp.Color;
using System.IO;
using System.Numerics;
public class FillPolygon
{
[Benchmark(Baseline = true, Description = "System.Drawing Fill Polygon")]
public void DrawSolidPolygonSystemDrawing()
{
using (Bitmap destination = new Bitmap(800, 800))
{
using (Graphics graphics = Graphics.FromImage(destination))
{
graphics.SmoothingMode = SmoothingMode.AntiAlias;
graphics.FillPolygon(Brushes.HotPink, new[] {
new Point(10, 10),
new Point(550, 50),
new Point(200, 400)
});
}
using (MemoryStream ms = new MemoryStream())
{
destination.Save(ms, System.Drawing.Imaging.ImageFormat.Bmp);
}
}
}
[Benchmark(Description = "ImageSharp Fill Polygon")]
public void DrawSolidPolygonCore()
{
CoreImage image = new CoreImage(800, 800);
image.FillPolygon(CoreColor.HotPink,
new[] {
new Vector2(10, 10),
new Vector2(550, 50),
new Vector2(200, 400)
}
);
using (MemoryStream ms = new MemoryStream())
{
image.SaveAsBmp(ms);
}
}
}
}

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

@ -0,0 +1,51 @@
// <copyright file="Crop.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp.Benchmarks
{
using System.Drawing;
using System.Drawing.Drawing2D;
using BenchmarkDotNet.Attributes;
using CoreImage = ImageSharp.Image;
using CorePoint = ImageSharp.Point;
using CoreColor = ImageSharp.Color;
using CoreBrushes = ImageSharp.Drawing.Brushes.Brushes;
using CorePatternBrush = ImageSharp.Drawing.Brushes.PatternBrush;
using System.IO;
public class FillWithPattern
{
[Benchmark(Baseline = true, Description = "System.Drawing Fill with Pattern")]
public void DrawPatternPolygonSystemDrawing()
{
using (Bitmap destination = new Bitmap(800, 800))
{
using (Graphics graphics = Graphics.FromImage(destination))
{
graphics.SmoothingMode = SmoothingMode.AntiAlias;
var brush = new HatchBrush(HatchStyle.BackwardDiagonal, Color.HotPink);
graphics.FillRectangle(brush, new Rectangle(0,0, 800,800)); // can't find a way to flood fill with a brush
}
using (MemoryStream ms = new MemoryStream())
{
destination.Save(ms, System.Drawing.Imaging.ImageFormat.Bmp);
}
}
}
[Benchmark(Description = "ImageSharp Fill with Pattern")]
public void DrawPatternPolygon3Core()
{
CoreImage image = new CoreImage(800, 800);
image.Fill(CoreBrushes.BackwardDiagonal(CoreColor.HotPink));
using (MemoryStream ms = new MemoryStream())
{
image.SaveAsBmp(ms);
}
}
}
}

103
tests/ImageSharp.Tests/Drawing/BeziersTests.cs

@ -0,0 +1,103 @@
// <copyright file="ColorConversionTests.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp.Tests.Drawing
{
using Drawing;
using ImageSharp.Drawing;
using System;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Numerics;
using Xunit;
public class Beziers : FileTestBase
{
[Fact]
public void ImageShouldBeOverlayedByBezierLine()
{
string path = CreateOutputDirectory("Drawing","BezierLine");
var image = new Image(500, 500);
using (FileStream output = File.OpenWrite($"{path}/Simple.png"))
{
image
.BackgroundColor(Color.Blue)
.DrawBeziers(Color.HotPink, 5, new[] {
new Vector2(10, 400),
new Vector2(30, 10),
new Vector2(240, 30),
new Vector2(300, 400)
})
.Save(output);
}
using (var sourcePixels = image.Lock())
{
//top of curve
Assert.Equal(Color.HotPink, sourcePixels[138,115]);
//start points
Assert.Equal(Color.HotPink, sourcePixels[10, 400]);
Assert.Equal(Color.HotPink, sourcePixels[300, 400]);
//curve points should not be never be set
Assert.Equal(Color.Blue, sourcePixels[30, 10]);
Assert.Equal(Color.Blue, sourcePixels[240, 30]);
// inside shape should be empty
Assert.Equal(Color.Blue, sourcePixels[200, 250]);
}
}
[Fact]
public void ImageShouldBeOverlayedBezierLineWithOpacity()
{
string path = CreateOutputDirectory("Drawing", "BezierLine");
var color = new Color(Color.HotPink.R, Color.HotPink.G, Color.HotPink.B, 150);
var image = new Image(500, 500);
using (FileStream output = File.OpenWrite($"{path}/Opacity.png"))
{
image
.BackgroundColor(Color.Blue)
.DrawBeziers(color, 10, new[] {
new Vector2(10, 400),
new Vector2(30, 10),
new Vector2(240, 30),
new Vector2(300, 400)
})
.Save(output);
}
//shift background color towards forground color by the opacity amount
var mergedColor = new Color(Vector4.Lerp(Color.Blue.ToVector4(), Color.HotPink.ToVector4(), 150f/255f));
using (var sourcePixels = image.Lock())
{
//top of curve
Assert.Equal(mergedColor, sourcePixels[138, 115]);
//start points
Assert.Equal(mergedColor, sourcePixels[10, 400]);
Assert.Equal(mergedColor, sourcePixels[300, 400]);
//curve points should not be never be set
Assert.Equal(Color.Blue, sourcePixels[30, 10]);
Assert.Equal(Color.Blue, sourcePixels[240, 30]);
// inside shape should be empty
Assert.Equal(Color.Blue, sourcePixels[200, 250]);
}
}
}
}

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

@ -0,0 +1,102 @@
// <copyright file="ColorConversionTests.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp.Tests.Drawing
{
using Drawing;
using ImageSharp.Drawing;
using CorePath = ImageSharp.Drawing.Paths.Path;
using ImageSharp.Drawing.Paths;
using System;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Numerics;
using Xunit;
public class DrawPathTests : FileTestBase
{
[Fact]
public void ImageShouldBeOverlayedByPath()
{
string path = CreateOutputDirectory("Drawing", "Path");
var image = new Image(500, 500);
var linerSegemnt = new LinearLineSegment(
new Vector2(10, 10),
new Vector2(200, 150),
new Vector2(50, 300));
var bazierSegment = new BezierLineSegment(new Vector2(50, 300),
new Vector2(500, 500),
new Vector2(60, 10),
new Vector2(10, 400));
var p = new CorePath(linerSegemnt, bazierSegment);
using (FileStream output = File.OpenWrite($"{path}/Simple.png"))
{
image
.BackgroundColor(Color.Blue)
.DrawPath(Color.HotPink, 5, p)
.Save(output);
}
using (var sourcePixels = image.Lock())
{
Assert.Equal(Color.HotPink, sourcePixels[9, 9]);
Assert.Equal(Color.HotPink, sourcePixels[199, 149]);
Assert.Equal(Color.Blue, sourcePixels[50, 50]);
}
}
[Fact]
public void ImageShouldBeOverlayedPathWithOpacity()
{
string path = CreateOutputDirectory("Drawing", "Path");
var color = new Color(Color.HotPink.R, Color.HotPink.G, Color.HotPink.B, 150);
var linerSegemnt = new LinearLineSegment(
new Vector2(10, 10),
new Vector2(200, 150),
new Vector2(50, 300)
);
var bazierSegment = new BezierLineSegment(new Vector2(50, 300),
new Vector2(500, 500),
new Vector2(60, 10),
new Vector2(10, 400));
var p = new CorePath(linerSegemnt, bazierSegment);
var image = new Image(500, 500);
using (FileStream output = File.OpenWrite($"{path}/Opacity.png"))
{
image
.BackgroundColor(Color.Blue)
.DrawPath(color, 10, p)
.Save(output);
}
//shift background color towards forground color by the opacity amount
var mergedColor = new Color(Vector4.Lerp(Color.Blue.ToVector4(), Color.HotPink.ToVector4(), 150f / 255f));
using (var sourcePixels = image.Lock())
{
Assert.Equal(mergedColor, sourcePixels[9, 9]);
Assert.Equal(mergedColor, sourcePixels[199, 149]);
Assert.Equal(Color.Blue, sourcePixels[50, 50]);
}
}
}
}

235
tests/ImageSharp.Tests/Drawing/FillPatternTests.cs

@ -0,0 +1,235 @@
// <copyright file="ColorConversionTests.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp.Tests.Drawing
{
using Drawing;
using ImageSharp.Drawing;
using System;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Numerics;
using Xunit;
using ImageSharp.Drawing.Brushes;
public class FillPatternBrushTests: FileTestBase
{
private Image Test(string name, Color background, IBrush<Color, uint> brush, Color[,] expectedPattern)
{
string path = CreateOutputDirectory("Fill", "PatternBrush");
var image = new Image(20, 20);
image
.Fill(background)
.Fill(brush);
using (FileStream output = File.OpenWrite($"{path}/{name}.png"))
{
image.Save(output);
}
using (var sourcePixels = image.Lock())
{
// lets pick random spots to start checking
var r = new Random();
var xStride = expectedPattern.GetLength(1);
var yStride = expectedPattern.GetLength(0);
var offsetX = r.Next(image.Width / xStride) * xStride;
var offsetY = r.Next(image.Height / yStride) * yStride;
for (var x = 0; x < xStride; x++)
{
for (var y = 0; y < yStride; y++)
{
var actualX = x + offsetX;
var actualY = y + offsetY;
var expected = expectedPattern[y, x]; // inverted pattern
var actual = sourcePixels[actualX, actualY];
if (expected != actual)
{
Assert.True(false, $"Expected {expected} but found {actual} at ({actualX},{actualY})");
}
}
}
}
using (FileStream output = File.OpenWrite($"{path}/{name}x4.png"))
{
image.Resize(80,80).Save(output);
}
return image;
}
[Fact]
public void ImageShouldBeFloodFilledWithPercent10()
{
Test("Percent10", Color.Blue, Brushes.Percent10(Color.HotPink, Color.LimeGreen), new Color[,] {
{ Color.HotPink , Color.LimeGreen, Color.LimeGreen, Color.LimeGreen},
{ Color.LimeGreen, Color.LimeGreen, Color.LimeGreen, Color.LimeGreen},
{ Color.LimeGreen, Color.LimeGreen, Color.HotPink , Color.LimeGreen},
{ Color.LimeGreen, Color.LimeGreen, Color.LimeGreen, Color.LimeGreen}
});
}
[Fact]
public void ImageShouldBeFloodFilledWithPercent10Transparent()
{
Test("Percent10_Transparent", Color.Blue, Brushes.Percent10(Color.HotPink),
new Color[,] {
{ Color.HotPink , Color.Blue, Color.Blue, Color.Blue},
{ Color.Blue, Color.Blue, Color.Blue, Color.Blue},
{ Color.Blue, Color.Blue, Color.HotPink , Color.Blue},
{ Color.Blue, Color.Blue, Color.Blue, Color.Blue}
});
}
[Fact]
public void ImageShouldBeFloodFilledWithPercent20()
{
Test("Percent20", Color.Blue, Brushes.Percent20(Color.HotPink, Color.LimeGreen),
new Color[,] {
{ Color.HotPink , Color.LimeGreen, Color.LimeGreen, Color.LimeGreen},
{ Color.LimeGreen, Color.LimeGreen, Color.HotPink , Color.LimeGreen},
{ Color.HotPink , Color.LimeGreen, Color.LimeGreen, Color.LimeGreen},
{ Color.LimeGreen, Color.LimeGreen, Color.HotPink , Color.LimeGreen}
});
}
[Fact]
public void ImageShouldBeFloodFilledWithPercent20_transparent()
{
Test("Percent20_Transparent", Color.Blue, Brushes.Percent20(Color.HotPink),
new Color[,] {
{ Color.HotPink , Color.Blue, Color.Blue, Color.Blue},
{ Color.Blue, Color.Blue, Color.HotPink , Color.Blue},
{ Color.HotPink , Color.Blue, Color.Blue, Color.Blue},
{ Color.Blue, Color.Blue, Color.HotPink , Color.Blue}
});
}
[Fact]
public void ImageShouldBeFloodFilledWithHorizontal()
{
Test("Horizontal", Color.Blue, Brushes.Horizontal(Color.HotPink, Color.LimeGreen),
new Color[,] {
{ Color.LimeGreen , Color.LimeGreen, Color.LimeGreen, Color.LimeGreen},
{ Color.HotPink, Color.HotPink, Color.HotPink , Color.HotPink},
{ Color.LimeGreen , Color.LimeGreen, Color.LimeGreen, Color.LimeGreen},
{ Color.LimeGreen, Color.LimeGreen, Color.LimeGreen , Color.LimeGreen}
});
}
[Fact]
public void ImageShouldBeFloodFilledWithHorizontal_transparent()
{
Test("Horizontal_Transparent", Color.Blue, Brushes.Horizontal(Color.HotPink),
new Color[,] {
{ Color.Blue , Color.Blue, Color.Blue, Color.Blue},
{ Color.HotPink, Color.HotPink, Color.HotPink , Color.HotPink},
{ Color.Blue , Color.Blue, Color.Blue, Color.Blue},
{ Color.Blue, Color.Blue, Color.Blue , Color.Blue}
});
}
[Fact]
public void ImageShouldBeFloodFilledWithMin()
{
Test("Min", Color.Blue, Brushes.Min(Color.HotPink, Color.LimeGreen),
new Color[,] {
{ Color.LimeGreen , Color.LimeGreen, Color.LimeGreen, Color.LimeGreen},
{ Color.LimeGreen , Color.LimeGreen, Color.LimeGreen, Color.LimeGreen},
{ Color.LimeGreen, Color.LimeGreen, Color.LimeGreen , Color.LimeGreen},
{ Color.HotPink, Color.HotPink, Color.HotPink , Color.HotPink}
});
}
[Fact]
public void ImageShouldBeFloodFilledWithMin_transparent()
{
Test("Min_Transparent", Color.Blue, Brushes.Min(Color.HotPink),
new Color[,] {
{ Color.Blue , Color.Blue, Color.Blue, Color.Blue},
{ Color.Blue , Color.Blue, Color.Blue, Color.Blue},
{ Color.Blue, Color.Blue, Color.Blue , Color.Blue},
{ Color.HotPink, Color.HotPink, Color.HotPink , Color.HotPink},
});
}
[Fact]
public void ImageShouldBeFloodFilledWithVertical()
{
Test("Vertical", Color.Blue, Brushes.Vertical(Color.HotPink, Color.LimeGreen),
new Color[,] {
{ Color.LimeGreen, Color.HotPink, Color.LimeGreen, Color.LimeGreen},
{ Color.LimeGreen, Color.HotPink, Color.LimeGreen, Color.LimeGreen},
{ Color.LimeGreen, Color.HotPink, Color.LimeGreen, Color.LimeGreen},
{ Color.LimeGreen, Color.HotPink, Color.LimeGreen, Color.LimeGreen}
});
}
[Fact]
public void ImageShouldBeFloodFilledWithVertical_transparent()
{
Test("Vertical_Transparent", Color.Blue, Brushes.Vertical(Color.HotPink),
new Color[,] {
{ Color.Blue, Color.HotPink, Color.Blue, Color.Blue},
{ Color.Blue, Color.HotPink, Color.Blue, Color.Blue},
{ Color.Blue, Color.HotPink, Color.Blue, Color.Blue},
{ Color.Blue, Color.HotPink, Color.Blue, Color.Blue}
});
}
[Fact]
public void ImageShouldBeFloodFilledWithForwardDiagonal()
{
Test("ForwardDiagonal", Color.Blue, Brushes.ForwardDiagonal(Color.HotPink, Color.LimeGreen),
new Color[,] {
{ Color.HotPink, Color.LimeGreen, Color.LimeGreen, Color.LimeGreen},
{ Color.LimeGreen, Color.HotPink, Color.LimeGreen, Color.LimeGreen},
{ Color.LimeGreen, Color.LimeGreen, Color.HotPink, Color.LimeGreen},
{ Color.LimeGreen, Color.LimeGreen, Color.LimeGreen, Color.HotPink}
});
}
[Fact]
public void ImageShouldBeFloodFilledWithForwardDiagonal_transparent()
{
Test("ForwardDiagonal_Transparent", Color.Blue, Brushes.ForwardDiagonal(Color.HotPink),
new Color[,] {
{ Color.HotPink, Color.Blue, Color.Blue, Color.Blue},
{ Color.Blue, Color.HotPink, Color.Blue, Color.Blue},
{ Color.Blue, Color.Blue, Color.HotPink, Color.Blue},
{ Color.Blue, Color.Blue, Color.Blue, Color.HotPink}
});
}
[Fact]
public void ImageShouldBeFloodFilledWithBackwardDiagonal()
{
Test("BackwardDiagonal", Color.Blue, Brushes.BackwardDiagonal(Color.HotPink, Color.LimeGreen),
new Color[,] {
{ Color.LimeGreen, Color.LimeGreen, Color.LimeGreen, Color.HotPink},
{ Color.LimeGreen, Color.LimeGreen, Color.HotPink, Color.LimeGreen},
{ Color.LimeGreen, Color.HotPink, Color.LimeGreen, Color.LimeGreen},
{ Color.HotPink, Color.LimeGreen, Color.LimeGreen, Color.LimeGreen}
});
}
[Fact]
public void ImageShouldBeFloodFilledWithBackwardDiagonal_transparent()
{
Test("BackwardDiagonal_Transparent", Color.Blue, Brushes.BackwardDiagonal(Color.HotPink),
new Color[,] {
{ Color.Blue, Color.Blue, Color.Blue, Color.HotPink},
{ Color.Blue, Color.Blue, Color.HotPink, Color.Blue},
{ Color.Blue, Color.HotPink, Color.Blue, Color.Blue},
{ Color.HotPink, Color.Blue, Color.Blue, Color.Blue}
});
}
}
}

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

@ -0,0 +1,89 @@
// <copyright file="ColorConversionTests.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp.Tests.Drawing
{
using Drawing;
using ImageSharp.Drawing;
using System;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Numerics;
using Xunit;
public class FillSolidBrushTests: FileTestBase
{
[Fact]
public void ImageShouldBeFloodFilledWithColorOnDefaultBackground()
{
string path = CreateOutputDirectory("Fill", "SolidBrush");
var image = new Image(500, 500);
using (FileStream output = File.OpenWrite($"{path}/DefaultBack.png"))
{
image
.Fill(Color.HotPink)
.Save(output);
}
using (var sourcePixels = image.Lock())
{
Assert.Equal(Color.HotPink, sourcePixels[9, 9]);
Assert.Equal(Color.HotPink, sourcePixels[199, 149]);
}
}
[Fact]
public void ImageShouldBeFloodFilledWithColor()
{
string path = CreateOutputDirectory("Fill", "SolidBrush");
var image = new Image(500, 500);
using (FileStream output = File.OpenWrite($"{path}/Simple.png"))
{
image
.BackgroundColor(Color.Blue)
.Fill(Color.HotPink)
.Save(output);
}
using (var sourcePixels = image.Lock())
{
Assert.Equal(Color.HotPink, sourcePixels[9, 9]);
Assert.Equal(Color.HotPink, sourcePixels[199, 149]);
}
}
[Fact]
public void ImageShouldBeFloodFilledWithColorOpacity()
{
string path = CreateOutputDirectory("Fill", "SolidBrush");
var image = new Image(500, 500);
var color = new Color(Color.HotPink.R, Color.HotPink.G, Color.HotPink.B, 150);
using (FileStream output = File.OpenWrite($"{path}/Opacity.png"))
{
image
.BackgroundColor(Color.Blue)
.Fill(color)
.Save(output);
}
//shift background color towards forground color by the opacity amount
var mergedColor = new Color(Vector4.Lerp(Color.Blue.ToVector4(), Color.HotPink.ToVector4(), 150f / 255f));
using (var sourcePixels = image.Lock())
{
Assert.Equal(mergedColor, sourcePixels[9, 9]);
Assert.Equal(mergedColor, sourcePixels[199, 149]);
}
}
}
}

195
tests/ImageSharp.Tests/Drawing/LineComplexPolygonTests.cs

@ -0,0 +1,195 @@
// <copyright file="ColorConversionTests.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp.Tests.Drawing
{
using System;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using Xunit;
using Drawing;
using ImageSharp.Drawing;
using System.Numerics;
using ImageSharp.Drawing.Shapes;
using ImageSharp.Drawing.Pens;
public class LineComplexPolygonTests : FileTestBase
{
[Fact]
public void ImageShouldBeOverlayedByPolygonOutline()
{
string path = CreateOutputDirectory("Drawing", "LineComplexPolygon");
var simplePath = new LinearPolygon(
new Vector2(10, 10),
new Vector2(200, 150),
new Vector2(50, 300));
var hole1 = new LinearPolygon(
new Vector2(37, 85),
new Vector2(93, 85),
new Vector2(65, 137));
var image = new Image(500, 500);
using (FileStream output = File.OpenWrite($"{path}/Simple.png"))
{
image
.BackgroundColor(Color.Blue)
.DrawPolygon(Color.HotPink, 5, new ComplexPolygon(simplePath, hole1))
.Save(output);
}
using (var sourcePixels = image.Lock())
{
Assert.Equal(Color.HotPink, sourcePixels[10, 10]);
Assert.Equal(Color.HotPink, sourcePixels[200, 150]);
Assert.Equal(Color.HotPink, sourcePixels[50, 300]);
Assert.Equal(Color.HotPink, sourcePixels[37, 85]);
Assert.Equal(Color.HotPink, sourcePixels[93, 85]);
Assert.Equal(Color.HotPink, sourcePixels[65, 137]);
Assert.Equal(Color.Blue, sourcePixels[2, 2]);
//inside hole
Assert.Equal(Color.Blue, sourcePixels[57, 99]);
//inside shape
Assert.Equal(Color.Blue, sourcePixels[100, 192]);
}
}
[Fact]
public void ImageShouldBeOverlayedByPolygonOutlineOverlapping()
{
string path = CreateOutputDirectory("Drawing", "LineComplexPolygon");
var simplePath = new LinearPolygon(
new Vector2(10, 10),
new Vector2(200, 150),
new Vector2(50, 300));
var hole1 = new LinearPolygon(
new Vector2(37, 85),
new Vector2(130, 40),
new Vector2(65, 137));
var image = new Image(500, 500);
using (FileStream output = File.OpenWrite($"{path}/SimpleOverlapping.png"))
{
image
.BackgroundColor(Color.Blue)
.DrawPolygon(Color.HotPink, 5, new ComplexPolygon(simplePath, hole1))
.Save(output);
}
using (var sourcePixels = image.Lock())
{
Assert.Equal(Color.HotPink, sourcePixels[10, 10]);
Assert.Equal(Color.HotPink, sourcePixels[200, 150]);
Assert.Equal(Color.HotPink, sourcePixels[50, 300]);
Assert.Equal(Color.Blue, sourcePixels[130, 41]);
Assert.Equal(Color.Blue, sourcePixels[2, 2]);
//inside hole
Assert.Equal(Color.Blue, sourcePixels[57, 99]);
//inside shape
Assert.Equal(Color.Blue, sourcePixels[100, 192]);
}
}
[Fact]
public void ImageShouldBeOverlayedByPolygonOutlineDashed()
{
string path = CreateOutputDirectory("Drawing", "LineComplexPolygon");
var simplePath = new LinearPolygon(
new Vector2(10, 10),
new Vector2(200, 150),
new Vector2(50, 300));
var hole1 = new LinearPolygon(
new Vector2(37, 85),
new Vector2(93, 85),
new Vector2(65, 137));
var image = new Image(500, 500);
using (FileStream output = File.OpenWrite($"{path}/Dashed.png"))
{
image
.BackgroundColor(Color.Blue)
.DrawPolygon(Pens.Dash(Color.HotPink, 5), new ComplexPolygon(simplePath, hole1))
.Save(output);
}
}
[Fact]
public void ImageShouldBeOverlayedPolygonOutlineWithOpacity()
{
string path = CreateOutputDirectory("Drawing", "LineComplexPolygon");
var simplePath = new LinearPolygon(
new Vector2(10, 10),
new Vector2(200, 150),
new Vector2(50, 300));
var hole1 = new LinearPolygon(
new Vector2(37, 85),
new Vector2(93, 85),
new Vector2(65, 137));
var color = new Color(Color.HotPink.R, Color.HotPink.G, Color.HotPink.B, 150);
var image = new Image(500, 500);
using (FileStream output = File.OpenWrite($"{path}/Opacity.png"))
{
image
.BackgroundColor(Color.Blue)
.DrawPolygon(color, 5, new ComplexPolygon(simplePath, hole1))
.Save(output);
}
//shift background color towards forground color by the opacity amount
var mergedColor = new Color(Vector4.Lerp(Color.Blue.ToVector4(), Color.HotPink.ToVector4(), 150f / 255f));
using (var sourcePixels = image.Lock())
{
Assert.Equal(mergedColor, sourcePixels[10, 10]);
Assert.Equal(mergedColor, sourcePixels[200, 150]);
Assert.Equal(mergedColor, sourcePixels[50, 300]);
Assert.Equal(mergedColor, sourcePixels[37, 85]);
Assert.Equal(mergedColor, sourcePixels[93, 85]);
Assert.Equal(mergedColor, sourcePixels[65, 137]);
Assert.Equal(Color.Blue, sourcePixels[2, 2]);
//inside hole
Assert.Equal(Color.Blue, sourcePixels[57, 99]);
//inside shape
Assert.Equal(Color.Blue, sourcePixels[100, 192]);
}
}
}
}

221
tests/ImageSharp.Tests/Drawing/LineTests.cs

@ -0,0 +1,221 @@
// <copyright file="ColorConversionTests.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp.Tests.Drawing
{
using Drawing;
using ImageSharp.Drawing;
using ImageSharp.Drawing.Pens;
using System;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Numerics;
using Xunit;
public class LineTests : FileTestBase
{
[Fact]
public void ImageShouldBeOverlayedByPath()
{
string path = CreateOutputDirectory("Drawing", "Lines");
var image = new Image(500, 500);
using (FileStream output = File.OpenWrite($"{path}/Simple.png"))
{
image
.BackgroundColor(Color.Blue)
.DrawLines(Color.HotPink, 5, new[] {
new Vector2(10, 10),
new Vector2(200, 150),
new Vector2(50, 300)
})
.Save(output);
}
using (var sourcePixels = image.Lock())
{
Assert.Equal(Color.HotPink, sourcePixels[9, 9]);
Assert.Equal(Color.HotPink, sourcePixels[199, 149]);
Assert.Equal(Color.Blue, sourcePixels[50, 50]);
}
}
[Fact]
public void ImageShouldBeOverlayedByPath_NoAntialias()
{
string path = CreateOutputDirectory("Drawing", "Lines");
var image = new Image(500, 500);
using (FileStream output = File.OpenWrite($"{path}/Simple_noantialias.png"))
{
image
.BackgroundColor(Color.Blue)
.DrawLines(Color.HotPink, 5, new[] {
new Vector2(10, 10),
new Vector2(200, 150),
new Vector2(50, 300)
}, new GraphicsOptions(false))
.Save(output);
}
using (var sourcePixels = image.Lock())
{
Assert.Equal(Color.HotPink, sourcePixels[9, 9]);
Assert.Equal(Color.HotPink, sourcePixels[199, 149]);
Assert.Equal(Color.Blue, sourcePixels[50, 50]);
}
}
[Fact]
public void ImageShouldBeOverlayedByPathDashed()
{
string path = CreateOutputDirectory("Drawing", "Lines");
var image = new Image(500, 500);
using (FileStream output = File.OpenWrite($"{path}/Dashed.png"))
{
image
.BackgroundColor(Color.Blue)
.DrawLines(Pens.Dash(Color.HotPink, 5), new[] {
new Vector2(10, 10),
new Vector2(200, 150),
new Vector2(50, 300)
})
.Save(output);
}
}
[Fact]
public void ImageShouldBeOverlayedByPathDotted()
{
string path = CreateOutputDirectory("Drawing", "Lines");
var image = new Image(500, 500);
using (FileStream output = File.OpenWrite($"{path}/Dot.png"))
{
image
.BackgroundColor(Color.Blue)
.DrawLines(Pens.Dot(Color.HotPink, 5), new[] {
new Vector2(10, 10),
new Vector2(200, 150),
new Vector2(50, 300)
})
.Save(output);
}
}
[Fact]
public void ImageShouldBeOverlayedByPathDashDot()
{
string path = CreateOutputDirectory("Drawing", "Lines");
var image = new Image(500, 500);
using (FileStream output = File.OpenWrite($"{path}/DashDot.png"))
{
image
.BackgroundColor(Color.Blue)
.DrawLines(Pens.DashDot(Color.HotPink, 5), new[] {
new Vector2(10, 10),
new Vector2(200, 150),
new Vector2(50, 300)
})
.Save(output);
}
}
[Fact]
public void ImageShouldBeOverlayedByPathDashDotDot()
{
string path = CreateOutputDirectory("Drawing", "Lines");
var image = new Image(500, 500);
using (FileStream output = File.OpenWrite($"{path}/DashDotDot.png"))
{
image
.BackgroundColor(Color.Blue)
.DrawLines(Pens.DashDotDot(Color.HotPink, 5), new[] {
new Vector2(10, 10),
new Vector2(200, 150),
new Vector2(50, 300)
})
.Save(output);
}
}
[Fact]
public void ImageShouldBeOverlayedPathWithOpacity()
{
string path = CreateOutputDirectory("Drawing", "Lines");
var color = new Color(Color.HotPink.R, Color.HotPink.G, Color.HotPink.B, 150);
var image = new Image(500, 500);
using (FileStream output = File.OpenWrite($"{path}/Opacity.png"))
{
image
.BackgroundColor(Color.Blue)
.DrawLines(color, 10, new[] {
new Vector2(10, 10),
new Vector2(200, 150),
new Vector2(50, 300)
})
.Save(output);
}
//shift background color towards forground color by the opacity amount
var mergedColor = new Color(Vector4.Lerp(Color.Blue.ToVector4(), Color.HotPink.ToVector4(), 150f/255f));
using (var sourcePixels = image.Lock())
{
Assert.Equal(mergedColor, sourcePixels[9, 9]);
Assert.Equal(mergedColor, sourcePixels[199, 149]);
Assert.Equal(Color.Blue, sourcePixels[50, 50]);
}
}
[Fact]
public void ImageShouldBeOverlayedByPathOutline()
{
string path = CreateOutputDirectory("Drawing", "Lines");
var image = new Image(500, 500);
using (FileStream output = File.OpenWrite($"{path}/Rectangle.png"))
{
image
.BackgroundColor(Color.Blue)
.DrawLines(Color.HotPink, 10, new[] {
new Vector2(10, 10),
new Vector2(200, 10),
new Vector2(200, 150),
new Vector2(10, 150)
})
.Save(output);
}
using (var sourcePixels = image.Lock())
{
Assert.Equal(Color.HotPink, sourcePixels[8, 8]);
Assert.Equal(Color.HotPink, sourcePixels[198, 10]);
Assert.Equal(Color.Blue, sourcePixels[10, 50]);
Assert.Equal(Color.Blue, sourcePixels[50, 50]);
}
}
}
}

121
tests/ImageSharp.Tests/Drawing/PolygonTests.cs

@ -0,0 +1,121 @@
// <copyright file="ColorConversionTests.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp.Tests.Drawing
{
using System;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using Xunit;
using Drawing;
using ImageSharp.Drawing;
using System.Numerics;
public class PolygonTests : FileTestBase
{
[Fact]
public void ImageShouldBeOverlayedByPolygonOutline()
{
string path = CreateOutputDirectory("Drawing", "Polygons");
var image = new Image(500, 500);
using (FileStream output = File.OpenWrite($"{path}/Simple.png"))
{
image
.BackgroundColor(Color.Blue)
.DrawPolygon(Color.HotPink, 5, new[] {
new Vector2(10, 10),
new Vector2(200, 150),
new Vector2(50, 300)
})
.Save(output);
}
using (var sourcePixels = image.Lock())
{
Assert.Equal(Color.HotPink, sourcePixels[9, 9]);
Assert.Equal(Color.HotPink, sourcePixels[199, 149]);
Assert.Equal(Color.Blue, sourcePixels[50, 50]);
Assert.Equal(Color.Blue, sourcePixels[2, 2]);
}
}
[Fact]
public void ImageShouldBeOverlayedPolygonOutlineWithOpacity()
{
string path = CreateOutputDirectory("Drawing", "Polygons");
var simplePath = new[] {
new Vector2(10, 10),
new Vector2(200, 150),
new Vector2(50, 300)
};
var color = new Color(Color.HotPink.R, Color.HotPink.G, Color.HotPink.B, 150);
var image = new Image(500, 500);
using (FileStream output = File.OpenWrite($"{path}/Opacity.png"))
{
image
.BackgroundColor(Color.Blue)
.DrawPolygon(color, 10, simplePath)
.Save(output);
}
//shift background color towards forground color by the opacity amount
var mergedColor = new Color(Vector4.Lerp(Color.Blue.ToVector4(), Color.HotPink.ToVector4(), 150f / 255f));
using (var sourcePixels = image.Lock())
{
Assert.Equal(mergedColor, sourcePixels[9, 9]);
Assert.Equal(mergedColor, sourcePixels[199, 149]);
Assert.Equal(Color.Blue, sourcePixels[50, 50]);
Assert.Equal(Color.Blue, sourcePixels[2, 2]);
}
}
[Fact]
public void ImageShouldBeOverlayedByRectangleOutline()
{
string path = CreateOutputDirectory("Drawing", "Polygons");
var image = new Image(500, 500);
using (FileStream output = File.OpenWrite($"{path}/Rectangle.png"))
{
image
.BackgroundColor(Color.Blue)
.DrawPolygon(Color.HotPink, 10, new[] {
new Vector2(10, 10),
new Vector2(200, 10),
new Vector2(200, 150),
new Vector2(10, 150)
})
.Save(output);
}
using (var sourcePixels = image.Lock())
{
Assert.Equal(Color.HotPink, sourcePixels[8, 8]);
Assert.Equal(Color.HotPink, sourcePixels[198, 10]);
Assert.Equal(Color.HotPink, sourcePixels[10, 50]);
Assert.Equal(Color.Blue, sourcePixels[50, 50]);
Assert.Equal(Color.Blue, sourcePixels[2, 2]);
}
}
}
}

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

@ -0,0 +1,101 @@
// <copyright file="ColorConversionTests.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp.Tests.Drawing
{
using Drawing;
using ImageSharp.Drawing;
using ImageSharp.Drawing.Shapes;
using System;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Numerics;
using Xunit;
public class SolidBezierTests : FileTestBase
{
[Fact]
public void ImageShouldBeOverlayedByFilledPolygon()
{
string path = CreateOutputDirectory("Drawing", "FilledBezier");
var simplePath = new[] {
new Vector2(10, 400),
new Vector2(30, 10),
new Vector2(240, 30),
new Vector2(300, 400)
};
var image = new Image(500, 500);
using (FileStream output = File.OpenWrite($"{path}/Simple.png"))
{
image
.BackgroundColor(Color.Blue)
.Fill(Color.HotPink,new BezierPolygon(simplePath))
.Save(output);
}
using (var sourcePixels = image.Lock())
{
//top of curve
Assert.Equal(Color.HotPink, sourcePixels[138, 116]);
//start points
Assert.Equal(Color.HotPink, sourcePixels[10, 400]);
Assert.Equal(Color.HotPink, sourcePixels[300, 400]);
//curve points should not be never be set
Assert.Equal(Color.Blue, sourcePixels[30, 10]);
Assert.Equal(Color.Blue, sourcePixels[240, 30]);
// inside shape should not be empty
Assert.Equal(Color.HotPink, sourcePixels[200, 250]);
}
}
[Fact]
public void ImageShouldBeOverlayedByFilledPolygonOpacity()
{
string path = CreateOutputDirectory("Drawing", "FilledBezier");
var simplePath = new[] {
new Vector2(10, 400),
new Vector2(30, 10),
new Vector2(240, 30),
new Vector2(300, 400)
};
var color = new Color(Color.HotPink.R, Color.HotPink.G, Color.HotPink.B, 150);
var image = new Image(500, 500);
using (FileStream output = File.OpenWrite($"{path}/Opacity.png"))
{
image
.BackgroundColor(Color.Blue)
.Fill(color, new BezierPolygon(simplePath))
.Save(output);
}
//shift background color towards forground color by the opacity amount
var mergedColor = new Color(Vector4.Lerp(Color.Blue.ToVector4(), Color.HotPink.ToVector4(), 150f / 255f));
using (var sourcePixels = image.Lock())
{
//top of curve
Assert.Equal(mergedColor, sourcePixels[138, 116]);
//start points
Assert.Equal(mergedColor, sourcePixels[10, 400]);
Assert.Equal(mergedColor, sourcePixels[300, 400]);
//curve points should not be never be set
Assert.Equal(Color.Blue, sourcePixels[30, 10]);
Assert.Equal(Color.Blue, sourcePixels[240, 30]);
// inside shape should not be empty
Assert.Equal(mergedColor, sourcePixels[200, 250]);
}
}
}
}

141
tests/ImageSharp.Tests/Drawing/SolidComplexPolygonTests.cs

@ -0,0 +1,141 @@
// <copyright file="ColorConversionTests.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp.Tests.Drawing
{
using System;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using Xunit;
using Drawing;
using ImageSharp.Drawing;
using System.Numerics;
using ImageSharp.Drawing.Shapes;
public class SolidComplexPolygonTests : FileTestBase
{
[Fact]
public void ImageShouldBeOverlayedByPolygonOutline()
{
string path = CreateOutputDirectory("Drawing", "ComplexPolygon");
var simplePath = new LinearPolygon(
new Vector2(10, 10),
new Vector2(200, 150),
new Vector2(50, 300));
var hole1 = new LinearPolygon(
new Vector2(37, 85),
new Vector2(93, 85),
new Vector2(65, 137));
var image = new Image(500, 500);
using (FileStream output = File.OpenWrite($"{path}/Simple.png"))
{
image
.BackgroundColor(Color.Blue)
.Fill(Color.HotPink, new ComplexPolygon(simplePath, hole1))
.Save(output);
}
using (var sourcePixels = image.Lock())
{
Assert.Equal(Color.HotPink, sourcePixels[11, 11]);
Assert.Equal(Color.HotPink, sourcePixels[200, 150]);
Assert.Equal(Color.HotPink, sourcePixels[50, 50]);
Assert.Equal(Color.Blue, sourcePixels[2, 2]);
//inside hole
Assert.Equal(Color.Blue, sourcePixels[57, 99]);
}
}
[Fact]
public void ImageShouldBeOverlayedPolygonOutlineWithOverlap()
{
string path = CreateOutputDirectory("Drawing", "ComplexPolygon");
var simplePath = new LinearPolygon(
new Vector2(10, 10),
new Vector2(200, 150),
new Vector2(50, 300));
var hole1 = new LinearPolygon(
new Vector2(37, 85),
new Vector2(130, 40),
new Vector2(65, 137));
var image = new Image(500, 500);
using (FileStream output = File.OpenWrite($"{path}/SimpleOverlapping.png"))
{
image
.BackgroundColor(Color.Blue)
.Fill(Color.HotPink, new ComplexPolygon(simplePath, hole1))
.Save(output);
}
using (var sourcePixels = image.Lock())
{
Assert.Equal(Color.HotPink, sourcePixels[11, 11]);
Assert.Equal(Color.HotPink, sourcePixels[200, 150]);
Assert.Equal(Color.HotPink, sourcePixels[50, 50]);
Assert.Equal(Color.Blue, sourcePixels[2, 2]);
//inside hole
Assert.Equal(Color.Blue, sourcePixels[57, 99]);
}
}
[Fact]
public void ImageShouldBeOverlayedPolygonOutlineWithOpacity()
{
string path = CreateOutputDirectory("Drawing", "ComplexPolygon");
var simplePath = new LinearPolygon(
new Vector2(10, 10),
new Vector2(200, 150),
new Vector2(50, 300));
var hole1 = new LinearPolygon(
new Vector2(37, 85),
new Vector2(93, 85),
new Vector2(65, 137));
var color = new Color(Color.HotPink.R, Color.HotPink.G, Color.HotPink.B, 150);
var image = new Image(500, 500);
using (FileStream output = File.OpenWrite($"{path}/Opacity.png"))
{
image
.BackgroundColor(Color.Blue)
.Fill(color, new ComplexPolygon(simplePath, hole1))
.Save(output);
}
//shift background color towards forground color by the opacity amount
var mergedColor = new Color(Vector4.Lerp(Color.Blue.ToVector4(), Color.HotPink.ToVector4(), 150f / 255f));
using (var sourcePixels = image.Lock())
{
Assert.Equal(mergedColor, sourcePixels[11, 11]);
Assert.Equal(mergedColor, sourcePixels[200, 150]);
Assert.Equal(mergedColor, sourcePixels[50, 50]);
Assert.Equal(Color.Blue, sourcePixels[2, 2]);
//inside hole
Assert.Equal(Color.Blue, sourcePixels[57, 99]);
}
}
}
}

174
tests/ImageSharp.Tests/Drawing/SolidPolygonTests.cs

@ -0,0 +1,174 @@
// <copyright file="ColorConversionTests.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp.Tests.Drawing
{
using Drawing;
using ImageSharp.Drawing;
using System;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Numerics;
using Xunit;
using ImageSharp.Drawing.Brushes;
public class SolidPolygonTests : FileTestBase
{
[Fact]
public void ImageShouldBeOverlayedByFilledPolygon()
{
string path = CreateOutputDirectory("Drawing", "FilledPolygons");
var simplePath = new[] {
new Vector2(10, 10),
new Vector2(200, 150),
new Vector2(50, 300)
};
var image = new Image(500, 500);
using (FileStream output = File.OpenWrite($"{path}/Simple.png"))
{
image
.BackgroundColor(Color.Blue)
.FillPolygon(Color.HotPink, simplePath)
.Save(output);
}
using (var sourcePixels = image.Lock())
{
Assert.Equal(Color.HotPink, sourcePixels[11, 11]);
Assert.Equal(Color.HotPink, sourcePixels[200, 150]);
Assert.Equal(Color.HotPink, sourcePixels[50, 50]);
Assert.Equal(Color.Blue, sourcePixels[2, 2]);
}
}
[Fact]
public void ImageShouldBeOverlayedByFilledPolygon_NoAntialias()
{
string path = CreateOutputDirectory("Drawing", "FilledPolygons");
var simplePath = new[] {
new Vector2(10, 10),
new Vector2(200, 150),
new Vector2(50, 300)
};
var image = new Image(500, 500);
using (FileStream output = File.OpenWrite($"{path}/Simple_NoAntialias.png"))
{
image
.BackgroundColor(Color.Blue)
.FillPolygon(Color.HotPink, simplePath, new GraphicsOptions(false))
.Save(output);
}
using (var sourcePixels = image.Lock())
{
Assert.Equal(Color.HotPink, sourcePixels[11, 11]);
Assert.Equal(Color.HotPink, sourcePixels[200, 150]);
Assert.Equal(Color.HotPink, sourcePixels[50, 50]);
Assert.Equal(Color.Blue, sourcePixels[2, 2]);
}
}
[Fact]
public void ImageShouldBeOverlayedByFilledPolygon_Image()
{
string path = CreateOutputDirectory("Drawing", "FilledPolygons");
var simplePath = new[] {
new Vector2(10, 10),
new Vector2(200, 150),
new Vector2(50, 300)
};
var brush = new ImageBrush(Files[2].CreateImage());
var image = new Image(500, 500);
using (FileStream output = File.OpenWrite($"{path}/Image.png"))
{
image
.BackgroundColor(Color.Blue)
.FillPolygon(brush, simplePath)
.Save(output);
}
}
[Fact]
public void ImageShouldBeOverlayedByFilledPolygonOpacity()
{
string path = CreateOutputDirectory("Drawing", "FilledPolygons");
var simplePath = new[] {
new Vector2(10, 10),
new Vector2(200, 150),
new Vector2(50, 300)
};
var color = new Color(Color.HotPink.R, Color.HotPink.G, Color.HotPink.B, 150);
var image = new Image(500, 500);
using (FileStream output = File.OpenWrite($"{path}/Opacity.png"))
{
image
.BackgroundColor(Color.Blue)
.FillPolygon(color, simplePath)
.Save(output);
}
//shift background color towards forground color by the opacity amount
var mergedColor = new Color(Vector4.Lerp(Color.Blue.ToVector4(), Color.HotPink.ToVector4(), 150f / 255f));
using (var sourcePixels = image.Lock())
{
Assert.Equal(mergedColor, sourcePixels[11, 11]);
Assert.Equal(mergedColor, sourcePixels[200, 150]);
Assert.Equal(mergedColor, sourcePixels[50, 50]);
Assert.Equal(Color.Blue, sourcePixels[2, 2]);
}
}
[Fact]
public void ImageShouldBeOverlayedByFilledRectangle()
{
string path = CreateOutputDirectory("Drawing", "FilledPolygons");
var simplePath = new[] {
new Vector2(10, 10),
new Vector2(200, 10),
new Vector2(200, 150),
new Vector2(10, 150)
};
var image = new Image(500, 500);
using (FileStream output = File.OpenWrite($"{path}/Rectangle.png"))
{
image
.BackgroundColor(Color.Blue)
.FillPolygon(Color.HotPink, simplePath)
.Save(output);
}
using (var sourcePixels = image.Lock())
{
Assert.Equal(Color.HotPink, sourcePixels[11, 11]);
Assert.Equal(Color.HotPink, sourcePixels[198, 10]);
Assert.Equal(Color.HotPink, sourcePixels[10, 50]);
Assert.Equal(Color.HotPink, sourcePixels[50, 50]);
Assert.Equal(Color.Blue, sourcePixels[2, 2]);
}
}
}
}

10
tests/ImageSharp.Tests/FileTestBase.cs

@ -44,9 +44,15 @@ namespace ImageSharp.Tests
// new TestFile(TestImages.Gif.Giphy) // Perf: Enable for local testing only
};
protected string CreateOutputDirectory(string path)
protected string CreateOutputDirectory(string path, params string[] pathParts)
{
path = "TestOutput/" + path;
var postFix = "";
if (pathParts != null && pathParts.Length > 0)
{
postFix = "/" + string.Join("/", pathParts);
}
path = "TestOutput/" + path + postFix;
if (!Directory.Exists(path))
{

Loading…
Cancel
Save