mirror of https://github.com/SixLabors/ImageSharp
Browse Source
Adds a 4 `IPath`s; - `Path` : a generic open path described by an arbitrary collection of `ILineSegments` which are closed. - `Polygon` : a generic closed path described by an arbitrary collection of `ILineSegments` which are closed. Adds 2 `ILineSegment`s; - `LinearLineSegment` : a segment made up of control points representing straight lines. - `BezierLineSegment` : a path made up of control points describing a bezier path. Adds 4 `IShape`s - `Polygon` : a generic closed path described by an arbitrary collection of `ILineSegments` which are closed. - `LinearPolygon` : a shape made up of control points representing straight lines. - `BezierPolygon` : a shape made up of control points describing a set of bezier curves which are closed by a straight line - `ComplexPolygon` : a polygon described by one or more `IShape`s as either outlines or holes. All `IShape`s can be represents as a collection of `IPath`. Adds 2 `IBrush`s - `SolidBrush` : described by a `TColor` - `Patternbrush` : described by a foreground and background `TColor` and a pattern. Adds 1 `IPen`: - `Pen` : described by an `IBrush` a width/thickness to draw the line and an optional pattern. Adds 3 Image Processors - `FillProcessor` : flood fills the image with an `IBrush`. - `FillShapeProcessor` : flood fills inside an `IShape`. - `DrawPathProcessor` : draws a collection of `IPath`s or and `IShape`s onto the image using an `IPen` to determine `TColor`. Adds a multitude of Image Extensions for accessing the above processors in a manor similar to `System.Drawing`pull/37/head
47 changed files with 10626 additions and 3 deletions
@ -1,3 +1,6 @@ |
|||
{ |
|||
"projects": [ "src" ] |
|||
"projects": [ "src" ], |
|||
"sdk": { |
|||
"version": "1.0.0-preview2-003121" |
|||
} |
|||
} |
|||
@ -0,0 +1,302 @@ |
|||
// <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
|
|||
// |
|
|||
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 }, |
|||
{ false, true, false, false }, |
|||
{ false, true, false, false }, |
|||
{ false, true, false, false } |
|||
}; |
|||
|
|||
private static readonly bool[,] MinPattern = new bool[,] |
|||
{ |
|||
{ false, false, false, true }, |
|||
{ false, false, false, true }, |
|||
{ false, false, false, true }, |
|||
{ false, false, false, true } |
|||
}; |
|||
|
|||
private static readonly bool[,] VerticalPattern = new bool[,] |
|||
{ |
|||
{ false, false, false, false }, |
|||
{ true, true, true, true }, |
|||
{ false, false, false, false }, |
|||
{ false, false, 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); |
|||
} |
|||
} |
|||
@ -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); |
|||
} |
|||
} |
|||
@ -0,0 +1,146 @@ |
|||
// <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>
|
|||
public partial 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 foreColor; |
|||
private readonly TColor backColor; |
|||
private readonly bool[,] pattern; |
|||
|
|||
/// <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.foreColor = foreColor; |
|||
this.backColor = backColor; |
|||
this.pattern = pattern; |
|||
} |
|||
|
|||
/// <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(brush.foreColor, brush.backColor, brush.pattern) |
|||
{ |
|||
} |
|||
|
|||
/// <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.foreColor, this.backColor, this.pattern); |
|||
} |
|||
|
|||
private class PatternBrushApplicator : IBrushApplicator<TColor, TPacked> |
|||
{ |
|||
private readonly int xLength; |
|||
private readonly int yLength; |
|||
private readonly bool[,] pattern; |
|||
private readonly TColor backColor = default(TColor); |
|||
private readonly TColor foreColor = default(TColor); |
|||
|
|||
/// <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>
|
|||
public PatternBrushApplicator(TColor foreColor, TColor backColor, bool[,] pattern) |
|||
{ |
|||
this.foreColor = foreColor; |
|||
this.backColor = backColor; |
|||
this.pattern = pattern; |
|||
|
|||
this.xLength = this.pattern.GetLength(0); |
|||
this.yLength = this.pattern.GetLength(1); |
|||
} |
|||
|
|||
/// <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.yLength; |
|||
|
|||
if (this.pattern[x, y]) |
|||
{ |
|||
return this.foreColor; |
|||
} |
|||
else |
|||
{ |
|||
return this.backColor; |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
|
|||
/// </summary>
|
|||
public void Dispose() |
|||
{ |
|||
// noop
|
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -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); |
|||
} |
|||
} |
|||
@ -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
|
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,421 @@ |
|||
// <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 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>
|
|||
/// <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.Process(new DrawPathProcessor<TColor, TPacked>(pen, 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="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>
|
|||
/// <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>
|
|||
/// <returns>The Image</returns>
|
|||
public static Image<TColor, TPacked> DrawPolygon<TColor, TPacked>(this Image<TColor, TPacked> source, IBrush<TColor, TPacked> brush, float thickness, PointF[] 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, PointF[] 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 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, PointF[] points) |
|||
where TColor : struct, IPackedPixel<TPacked> |
|||
where TPacked : struct |
|||
{ |
|||
return source.DrawPolygon(pen, 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="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, Point[] 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, Point[] 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 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, Point[] 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>
|
|||
/// <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)); |
|||
} |
|||
|
|||
/// <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>
|
|||
/// <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>
|
|||
/// <returns>The Image</returns>
|
|||
public static Image<TColor, TPacked> DrawLines<TColor, TPacked>(this Image<TColor, TPacked> source, IBrush<TColor, TPacked> brush, float thickness, PointF[] 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, PointF[] 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 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, PointF[] 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 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, Point[] 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, Point[] 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 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, Point[] 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>
|
|||
/// <returns>The Image</returns>
|
|||
public static Image<TColor, TPacked> DrawBeziers<TColor, TPacked>(this Image<TColor, TPacked> source, IBrush<TColor, TPacked> brush, float thickness, PointF[] 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, PointF[] 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 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, PointF[] points) |
|||
where TColor : struct, IPackedPixel<TPacked> |
|||
where TPacked : struct |
|||
{ |
|||
return source.DrawPath(pen, 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="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, Point[] 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, Point[] 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 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, Point[] points) |
|||
where TColor : struct, IPackedPixel<TPacked> |
|||
where TPacked : struct |
|||
{ |
|||
return source.DrawPath(pen, new Path(new BezierLineSegment(points))); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,150 @@ |
|||
// <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 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) |
|||
where TColor : struct, IPackedPixel<TPacked> |
|||
where TPacked : struct |
|||
{ |
|||
return source.Process(new FillShapeProcessor<TColor, TPacked>(brush, shape)); |
|||
} |
|||
|
|||
/// <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>
|
|||
/// <returns>The Image</returns>
|
|||
public static Image<TColor, TPacked> FillPolygon<TColor, TPacked>(this Image<TColor, TPacked> source, IBrush<TColor, TPacked> brush, PointF[] 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>
|
|||
/// <returns>The Image</returns>
|
|||
public static Image<TColor, TPacked> FillPolygon<TColor, TPacked>(this Image<TColor, TPacked> source, TColor color, PointF[] 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))); |
|||
} |
|||
|
|||
/// <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, Point[] 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>
|
|||
/// <returns>The Image</returns>
|
|||
public static Image<TColor, TPacked> FillPolygon<TColor, TPacked>(this Image<TColor, TPacked> source, TColor color, Point[] 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))); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,128 @@ |
|||
// <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 List<Vector2> linePoints; |
|||
|
|||
private int curveCount; // how many bezier curves in this path?
|
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="BezierLineSegment" /> class.
|
|||
/// </summary>
|
|||
/// <param name="points">The points.</param>
|
|||
public BezierLineSegment(IEnumerable<PointF> points) |
|||
: this(points?.Select(x => x.ToVector2()).ToArray()) |
|||
{ |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="BezierLineSegment"/> class.
|
|||
/// </summary>
|
|||
/// <param name="points">The points.</param>
|
|||
public BezierLineSegment(IEnumerable<Point> points) |
|||
: this(points?.Select(x => x.ToVector2()).ToArray()) |
|||
{ |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="BezierLineSegment"/> class.
|
|||
/// </summary>
|
|||
/// <param name="points">The points.</param>
|
|||
public BezierLineSegment(params PointF[] points) |
|||
: this(points?.Select(x => x.ToVector2()).ToArray()) |
|||
{ |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="BezierLineSegment"/> class.
|
|||
/// </summary>
|
|||
/// <param name="points">The points.</param>
|
|||
internal BezierLineSegment(Vector2[] points) |
|||
{ |
|||
Guard.NotNull(points, nameof(points)); |
|||
Guard.MustBeGreaterThanOrEqualTo(points.Length, 4, nameof(points)); |
|||
|
|||
this.curveCount = (points.Length - 1) / 3; |
|||
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 IEnumerable<Vector2> AsSimpleLinearPath() |
|||
{ |
|||
return this.linePoints; |
|||
} |
|||
|
|||
private List<Vector2> GetDrawingPoints(Vector2[] controlPoints) |
|||
{ |
|||
// TODO we need to calculate an optimal SegmentsPerCurve value
|
|||
// depending on the calcualted length of this curve
|
|||
var maxPoints = (int)Math.Ceiling(SegmentsPerCurve * (float)this.curveCount); |
|||
|
|||
List<Vector2> drawingPoints = new List<Vector2>(maxPoints); // set a default size to be efficient?
|
|||
|
|||
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.Add(this.CalculateBezierPoint(0, p0, p1, p2, p3)); |
|||
} |
|||
|
|||
for (int j = 1; j <= SegmentsPerCurve; j++) |
|||
{ |
|||
float t = j / (float)SegmentsPerCurve; |
|||
drawingPoints.Add(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; |
|||
} |
|||
} |
|||
} |
|||
@ -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>
|
|||
IEnumerable<Vector2> AsSimpleLinearPath(); |
|||
} |
|||
} |
|||
@ -0,0 +1,51 @@ |
|||
// <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="x">The x.</param>
|
|||
/// <param name="y">The y.</param>
|
|||
/// <returns>Returns details about the point and its distance away from the path.</returns>
|
|||
PointInfo Distance(int x, int y); |
|||
} |
|||
} |
|||
@ -0,0 +1,279 @@ |
|||
// <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(IEnumerable<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(IEnumerable<ILineSegment> segments) |
|||
{ |
|||
return segments.SelectMany(x => x.AsSimpleLinearPath()).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; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,83 @@ |
|||
// <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="points">The points.</param>
|
|||
public LinearLineSegment(IEnumerable<PointF> points) |
|||
: this(points?.Select(x => x.ToVector2()).ToArray()) |
|||
{ |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="LinearLineSegment"/> class.
|
|||
/// </summary>
|
|||
/// <param name="points">The points.</param>
|
|||
public LinearLineSegment(IEnumerable<Point> points) |
|||
: this(points?.Select(x => x.ToVector2()).ToArray()) |
|||
{ |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="LinearLineSegment"/> class.
|
|||
/// </summary>
|
|||
/// <param name="points">The points.</param>
|
|||
public LinearLineSegment(params PointF[] points) |
|||
: this(points?.Select(x => x.ToVector2()).ToArray()) |
|||
{ |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="LinearLineSegment"/> class.
|
|||
/// </summary>
|
|||
/// <param name="start">The start.</param>
|
|||
/// <param name="end">The end.</param>
|
|||
internal 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>
|
|||
internal LinearLineSegment(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 IEnumerable<Vector2> AsSimpleLinearPath() |
|||
{ |
|||
return this.points; |
|||
} |
|||
} |
|||
} |
|||
@ -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>
|
|||
internal 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 IEnumerable<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(int x, int y) |
|||
{ |
|||
return this.innerPath.DistanceFromPath(new Vector2(x, y)); |
|||
} |
|||
} |
|||
} |
|||
@ -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; |
|||
} |
|||
} |
|||
@ -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); |
|||
} |
|||
} |
|||
@ -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; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -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); |
|||
} |
|||
} |
|||
@ -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; |
|||
} |
|||
} |
|||
@ -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); |
|||
} |
|||
} |
|||
@ -0,0 +1,160 @@ |
|||
// <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; |
|||
|
|||
/// <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>
|
|||
public DrawPathProcessor(IPen<TColor, TPacked> pen, IShape shape) |
|||
: this(pen, shape.ToArray()) |
|||
{ |
|||
} |
|||
|
|||
/// <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>
|
|||
public DrawPathProcessor(IPen<TColor, TPacked> pen, params IPath[] paths) |
|||
{ |
|||
this.paths = paths; |
|||
this.pen = pen; |
|||
|
|||
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; |
|||
|
|||
for (int x = minX; x < maxX; x++) |
|||
{ |
|||
int offsetX = x - startX; |
|||
|
|||
var dist = this.paths.Select(p => p.Distance(offsetX, offsetY)).OrderBy(p => p.DistanceFromPath).First(); |
|||
|
|||
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 float Opacity(float distance) |
|||
{ |
|||
if (distance <= 0) |
|||
{ |
|||
return 1; |
|||
} |
|||
else if (distance < AntialiasFactor) |
|||
{ |
|||
return 1 - (distance / AntialiasFactor); |
|||
} |
|||
|
|||
return 0; |
|||
} |
|||
} |
|||
} |
|||
@ -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; |
|||
} |
|||
}); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,129 @@ |
|||
// <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; |
|||
|
|||
/// <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) |
|||
{ |
|||
this.poly = shape; |
|||
this.fillColor = brush; |
|||
} |
|||
|
|||
/// <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); |
|||
for (int x = minX; x < maxX; x++) |
|||
{ |
|||
int offsetX = x - startX; |
|||
currentPoint.X = offsetX; |
|||
currentPoint.Y = offsetY; |
|||
|
|||
var dist = this.poly.Distance(offsetX, offsetY); |
|||
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 (distance < AntialiasFactor) |
|||
{ |
|||
return 1 - (distance / AntialiasFactor); |
|||
} |
|||
|
|||
return 0; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,78 @@ |
|||
// <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 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 Point[] points) |
|||
{ |
|||
this.innerPolygon = new Polygon(new BezierLineSegment(points)); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="BezierPolygon"/> class.
|
|||
/// </summary>
|
|||
/// <param name="points">The points.</param>
|
|||
public BezierPolygon(params PointF[] 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="x">The x.</param>
|
|||
/// <param name="y">The y.</param>
|
|||
/// <returns>
|
|||
/// The distance from the shape.
|
|||
/// </returns>
|
|||
public float Distance(int x, int y) => this.innerPolygon.Distance(x, y); |
|||
|
|||
/// <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(); |
|||
} |
|||
} |
|||
} |
|||
File diff suppressed because it is too large
@ -0,0 +1,197 @@ |
|||
// <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="x">The x.</param>
|
|||
/// <param name="y">The y.</param>
|
|||
/// <returns>
|
|||
/// Returns the distance from thr shape to the point
|
|||
/// </returns>
|
|||
float IShape.Distance(int x, int y) |
|||
{ |
|||
// 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(x, y)).OrderBy(p => p).First(); |
|||
|
|||
if (dist <= 0) |
|||
{ |
|||
// inside poly
|
|||
foreach (var hole in this.holes) |
|||
{ |
|||
var distFromHole = hole.Distance(x, y); |
|||
|
|||
// 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(); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,33 @@ |
|||
// <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 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="x">The x.</param>
|
|||
/// <param name="y">The y.</param>
|
|||
/// <returns>Returns the distance from the shape to the point</returns>
|
|||
float Distance(int x, int y); |
|||
} |
|||
} |
|||
@ -0,0 +1,72 @@ |
|||
// <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 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 Point[] points) |
|||
{ |
|||
this.innerPolygon = new Polygon(new LinearLineSegment(points)); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="LinearPolygon"/> class.
|
|||
/// </summary>
|
|||
/// <param name="points">The points.</param>
|
|||
public LinearPolygon(params PointF[] 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="x">The x.</param>
|
|||
/// <param name="y">The y.</param>
|
|||
/// <returns>
|
|||
/// Returns the distance from the shape to the point
|
|||
/// </returns>
|
|||
public float Distance(int x, int y) => this.innerPolygon.Distance(x, y); |
|||
|
|||
/// <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(); |
|||
} |
|||
} |
|||
@ -0,0 +1,134 @@ |
|||
// <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((IEnumerable<ILineSegment>)segments) |
|||
{ |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="Polygon"/> class.
|
|||
/// </summary>
|
|||
/// <param name="segments">The segments.</param>
|
|||
public Polygon(IEnumerable<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="x">The x.</param>
|
|||
/// <param name="y">The y.</param>
|
|||
/// <returns>
|
|||
/// The distance of the point away from the shape
|
|||
/// </returns>
|
|||
public float Distance(int x, int y) |
|||
{ |
|||
var point = new Vector2(x, y); |
|||
|
|||
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(int x, int y) |
|||
{ |
|||
return this.innerPath.DistanceFromPath(new Vector2(x, y)); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Returns the current shape as a simple linear path.
|
|||
/// </summary>
|
|||
/// <returns>
|
|||
/// Returns the current <see cref="ILineSegment" /> as simple linear path.
|
|||
/// </returns>
|
|||
public IEnumerable<Vector2> AsSimpleLinearPath() |
|||
{ |
|||
return this.innerPath.Points; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,287 @@ |
|||
// <copyright file="PointF.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; |
|||
using System.Runtime.CompilerServices; |
|||
|
|||
/// <summary>
|
|||
/// Represents an ordered pair of floating point x- and y-coordinates that defines a point in
|
|||
/// a two-dimensional plane.
|
|||
/// </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 PointF : IEquatable<PointF> |
|||
{ |
|||
/// <summary>
|
|||
/// Represents a <see cref="Point"/> that has X and Y values set to zero.
|
|||
/// </summary>
|
|||
public static readonly PointF Empty = default(PointF); |
|||
|
|||
private Vector2 backingVector; |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="PointF"/> struct.
|
|||
/// </summary>
|
|||
/// <param name="x">The horizontal position of the point.</param>
|
|||
/// <param name="y">The vertical position of the point.</param>
|
|||
public PointF(float x, float y) |
|||
: this(new Vector2(x, y)) |
|||
{ |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="PointF"/> struct.
|
|||
/// </summary>
|
|||
/// <param name="vector">
|
|||
/// The vector representing the width and height.
|
|||
/// </param>
|
|||
public PointF(Vector2 vector) |
|||
{ |
|||
this.backingVector = vector; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the x-coordinate of this <see cref="PointF"/>.
|
|||
/// </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="PointF"/>.
|
|||
/// </summary>
|
|||
public float Y |
|||
{ |
|||
get { return this.backingVector.Y; } |
|||
set { this.backingVector.Y = value; } |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets a value indicating whether this <see cref="PointF"/> is empty.
|
|||
/// </summary>
|
|||
[EditorBrowsable(EditorBrowsableState.Never)] |
|||
public bool IsEmpty => this.Equals(Empty); |
|||
|
|||
/// <summary>
|
|||
/// Performs an implicit conversion from <see cref="Point"/> to <see cref="PointF"/>.
|
|||
/// </summary>
|
|||
/// <param name="d">The d.</param>
|
|||
/// <returns>
|
|||
/// The result of the conversion.
|
|||
/// </returns>
|
|||
public static implicit operator PointF(Point d) |
|||
{ |
|||
return new PointF(d.ToVector2()); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Computes the sum of adding two points.
|
|||
/// </summary>
|
|||
/// <param name="left">The point on the left hand of the operand.</param>
|
|||
/// <param name="right">The point on the right hand of the operand.</param>
|
|||
/// <returns>
|
|||
/// The <see cref="Point"/>
|
|||
/// </returns>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static PointF operator +(PointF left, PointF right) |
|||
{ |
|||
return new PointF(left.backingVector + right.backingVector); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Computes the difference left by subtracting one point from another.
|
|||
/// </summary>
|
|||
/// <param name="left">The point on the left hand of the operand.</param>
|
|||
/// <param name="right">The point on the right hand of the operand.</param>
|
|||
/// <returns>
|
|||
/// The <see cref="PointF"/>
|
|||
/// </returns>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static PointF operator -(PointF left, PointF right) |
|||
{ |
|||
return new PointF(left.backingVector - right.backingVector); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Compares two <see cref="PointF"/> objects for equality.
|
|||
/// </summary>
|
|||
/// <param name="left">
|
|||
/// The <see cref="PointF"/> on the left side of the operand.
|
|||
/// </param>
|
|||
/// <param name="right">
|
|||
/// The <see cref="PointF"/> 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>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static bool operator ==(PointF left, PointF right) |
|||
{ |
|||
return left.Equals(right); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Compares two <see cref="Point"/> objects for inequality.
|
|||
/// </summary>
|
|||
/// <param name="left">
|
|||
/// The <see cref="Point"/> on the left side of the operand.
|
|||
/// </param>
|
|||
/// <param name="right">
|
|||
/// The <see cref="Point"/> 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>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static bool operator !=(PointF left, PointF right) |
|||
{ |
|||
return !left.Equals(right); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Creates a rotation matrix for the given point and angle.
|
|||
/// </summary>
|
|||
/// <param name="origin">The origin point to rotate around</param>
|
|||
/// <param name="degrees">Rotation in degrees</param>
|
|||
/// <returns>The rotation <see cref="Matrix3x2"/></returns>
|
|||
public static Matrix3x2 CreateRotation(PointF origin, float degrees) |
|||
{ |
|||
float radians = ImageMaths.DegreesToRadians(degrees); |
|||
return Matrix3x2.CreateRotation(radians, new Vector2(origin.X, origin.Y)); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Rotates a point around a given a rotation matrix.
|
|||
/// </summary>
|
|||
/// <param name="point">The point to rotate</param>
|
|||
/// <param name="rotation">Rotation matrix used</param>
|
|||
/// <returns>The rotated <see cref="Point"/></returns>
|
|||
public static PointF Rotate(PointF point, Matrix3x2 rotation) |
|||
{ |
|||
return new PointF(Vector2.Transform(new Vector2(point.X, point.Y), rotation)); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Rotates a point around a given origin by the specified angle in degrees.
|
|||
/// </summary>
|
|||
/// <param name="point">The point to rotate</param>
|
|||
/// <param name="origin">The center point to rotate around.</param>
|
|||
/// <param name="degrees">The angle in degrees.</param>
|
|||
/// <returns>The rotated <see cref="Point"/></returns>
|
|||
public static PointF Rotate(PointF point, PointF origin, float degrees) |
|||
{ |
|||
return new PointF(Vector2.Transform(new Vector2(point.X, point.Y), CreateRotation(origin, degrees))); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Creates a skew matrix for the given point and angle.
|
|||
/// </summary>
|
|||
/// <param name="origin">The origin point to rotate around</param>
|
|||
/// <param name="degreesX">The x-angle in degrees.</param>
|
|||
/// <param name="degreesY">The y-angle in degrees.</param>
|
|||
/// <returns>The rotation <see cref="Matrix3x2"/></returns>
|
|||
public static Matrix3x2 CreateSkew(PointF origin, float degreesX, float degreesY) |
|||
{ |
|||
float radiansX = ImageMaths.DegreesToRadians(degreesX); |
|||
float radiansY = ImageMaths.DegreesToRadians(degreesY); |
|||
return Matrix3x2.CreateSkew(radiansX, radiansY, origin.backingVector); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Skews a point using a given a skew matrix.
|
|||
/// </summary>
|
|||
/// <param name="point">The point to rotate</param>
|
|||
/// <param name="skew">Rotation matrix used</param>
|
|||
/// <returns>The rotated <see cref="Point"/></returns>
|
|||
public static PointF Skew(PointF point, Matrix3x2 skew) |
|||
{ |
|||
return new PointF(Vector2.Transform(point.backingVector, skew)); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Skews a point around a given origin by the specified angles in degrees.
|
|||
/// </summary>
|
|||
/// <param name="point">The point to skew.</param>
|
|||
/// <param name="origin">The center point to rotate around.</param>
|
|||
/// <param name="degreesX">The x-angle in degrees.</param>
|
|||
/// <param name="degreesY">The y-angle in degrees.</param>
|
|||
/// <returns>The skewed <see cref="Point"/></returns>
|
|||
public static PointF Skew(PointF point, PointF origin, float degreesX, float degreesY) |
|||
{ |
|||
return new PointF(Vector2.Transform(point.backingVector, CreateSkew(origin, degreesX, degreesY))); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets a <see cref="Vector2"/> representation for this <see cref="Point"/>.
|
|||
/// </summary>
|
|||
/// <returns>A <see cref="Vector2"/> representation for this object.</returns>
|
|||
public Vector2 ToVector2() |
|||
{ |
|||
// should this be a return of the mutable vector2 backing vector instead of a copy?
|
|||
return new Vector2(this.X, this.Y); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Translates this <see cref="Point"/> by the specified amount.
|
|||
/// </summary>
|
|||
/// <param name="dx">The amount to offset the x-coordinate.</param>
|
|||
/// <param name="dy">The amount to offset the y-coordinate.</param>
|
|||
public void Offset(float dx, float dy) |
|||
{ |
|||
this.backingVector += new Vector2(dx, dy); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Translates this <see cref="PointF"/> by the specified amount.
|
|||
/// </summary>
|
|||
/// <param name="p">The <see cref="PointF"/> used offset this <see cref="PointF"/>.</param>
|
|||
public void Offset(PointF p) |
|||
{ |
|||
this.backingVector += p.backingVector; |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public override int GetHashCode() |
|||
{ |
|||
return this.backingVector.GetHashCode(); |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public override string ToString() |
|||
{ |
|||
if (this.IsEmpty) |
|||
{ |
|||
return "Point [ Empty ]"; |
|||
} |
|||
|
|||
return $"Point [ X={this.X}, Y={this.Y} ]"; |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public override bool Equals(object obj) |
|||
{ |
|||
if (obj is PointF) |
|||
{ |
|||
return this.Equals((PointF)obj); |
|||
} |
|||
|
|||
return false; |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public bool Equals(PointF other) |
|||
{ |
|||
return this.backingVector == other.backingVector; |
|||
} |
|||
} |
|||
} |
|||
@ -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(); |
|||
} |
|||
} |
|||
} |
|||
@ -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 CorePointF= ImageSharp.PointF; |
|||
using CoreColor= ImageSharp.Color; |
|||
using System.IO; |
|||
|
|||
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 CorePointF(10, 500), |
|||
new CorePointF(30, 10), |
|||
new CorePointF(240, 30), |
|||
new CorePointF(300, 500) |
|||
}); |
|||
|
|||
using (MemoryStream ms = new MemoryStream()) |
|||
{ |
|||
image.SaveAsBmp(ms); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -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 CorePointF= ImageSharp.PointF; |
|||
using CoreColor= ImageSharp.Color; |
|||
using System.IO; |
|||
|
|||
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 CorePointF(10, 10), |
|||
new CorePointF(550, 50), |
|||
new CorePointF(200, 400) |
|||
}); |
|||
|
|||
using (MemoryStream ms = new MemoryStream()) |
|||
{ |
|||
image.SaveAsBmp(ms); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -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 CorePointF= ImageSharp.PointF; |
|||
using CoreColor= ImageSharp.Color; |
|||
using System.IO; |
|||
|
|||
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 CorePointF(10, 10), |
|||
new CorePointF(550, 50), |
|||
new CorePointF(200, 400) |
|||
}); |
|||
|
|||
using (MemoryStream ms = new MemoryStream()) |
|||
{ |
|||
image.SaveAsBmp(ms); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -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 CorePoint = ImageSharp.Point; |
|||
using CoreColor = ImageSharp.Color; |
|||
using System.IO; |
|||
|
|||
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 CorePoint(10, 10), |
|||
new CorePoint(550, 50), |
|||
new CorePoint(200, 400) |
|||
} |
|||
); |
|||
|
|||
using (MemoryStream ms = new MemoryStream()) |
|||
{ |
|||
image.SaveAsBmp(ms); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -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 PointF(10, 400), |
|||
new PointF(30, 10), |
|||
new PointF(240, 30), |
|||
new PointF(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 PointF(10, 400), |
|||
new PointF(30, 10), |
|||
new PointF(240, 30), |
|||
new PointF(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]); |
|||
} |
|||
} |
|||
|
|||
} |
|||
} |
|||
@ -0,0 +1,105 @@ |
|||
// <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 Point(10, 10), |
|||
new Point(200, 150), |
|||
new Point(50, 300) |
|||
); |
|||
var bazierSegment = new BezierLineSegment(new Point(50, 300), |
|||
new Point(500, 500), |
|||
new Point(60, 10), |
|||
new Point(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 Point(10, 10), |
|||
new Point(200, 150), |
|||
new Point(50, 300) |
|||
); |
|||
var bazierSegment = new BezierLineSegment(new Point(50, 300), |
|||
new Point(500, 500), |
|||
new Point(60, 10), |
|||
new Point(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]); |
|||
} |
|||
} |
|||
|
|||
} |
|||
} |
|||
@ -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} |
|||
}); |
|||
} |
|||
|
|||
|
|||
} |
|||
} |
|||
@ -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]); |
|||
} |
|||
|
|||
} |
|||
|
|||
} |
|||
} |
|||
@ -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 Point(10, 10), |
|||
new Point(200, 150), |
|||
new Point(50, 300)); |
|||
|
|||
var hole1 = new LinearPolygon( |
|||
new Point(37, 85), |
|||
new Point(93, 85), |
|||
new Point(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 Point(10, 10), |
|||
new Point(200, 150), |
|||
new Point(50, 300)); |
|||
|
|||
var hole1 = new LinearPolygon( |
|||
new Point(37, 85), |
|||
new Point(130, 40), |
|||
new Point(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 Point(10, 10), |
|||
new Point(200, 150), |
|||
new Point(50, 300)); |
|||
|
|||
var hole1 = new LinearPolygon( |
|||
new Point(37, 85), |
|||
new Point(93, 85), |
|||
new Point(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 Point(10, 10), |
|||
new Point(200, 150), |
|||
new Point(50, 300)); |
|||
|
|||
var hole1 = new LinearPolygon( |
|||
new Point(37, 85), |
|||
new Point(93, 85), |
|||
new Point(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]); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,197 @@ |
|||
// <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 Point(10, 10), |
|||
new Point(200, 150), |
|||
new Point(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 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 Point(10, 10), |
|||
new Point(200, 150), |
|||
new Point(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 Point(10, 10), |
|||
new Point(200, 150), |
|||
new Point(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 Point(10, 10), |
|||
new Point(200, 150), |
|||
new Point(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 Point(10, 10), |
|||
new Point(200, 150), |
|||
new Point(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 Point(10, 10), |
|||
new Point(200, 150), |
|||
new Point(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 Point(10, 10), |
|||
new Point(200, 10), |
|||
new Point(200, 150), |
|||
new Point(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]); |
|||
} |
|||
} |
|||
|
|||
} |
|||
} |
|||
@ -0,0 +1,132 @@ |
|||
// <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 simplePath = new[] { |
|||
new Point(10, 10), |
|||
new Point(200, 150), |
|||
new Point(50, 300) |
|||
}; |
|||
|
|||
var image = new Image(500, 500); |
|||
|
|||
using (FileStream output = File.OpenWrite($"{path}/Simple.png")) |
|||
{ |
|||
image |
|||
.BackgroundColor(Color.Blue) |
|||
.DrawPolygon(Color.HotPink, 5, new[] { |
|||
new Point(10, 10), |
|||
new Point(200, 150), |
|||
new Point(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 Point(10, 10), |
|||
new Point(200, 150), |
|||
new Point(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 simplePath = new[] { |
|||
new Point(10, 10), |
|||
new Point(200, 10), |
|||
new Point(200, 150), |
|||
new Point(10, 150) |
|||
}; |
|||
|
|||
var image = new Image(500, 500); |
|||
|
|||
using (FileStream output = File.OpenWrite($"{path}/Rectangle.png")) |
|||
{ |
|||
image |
|||
.BackgroundColor(Color.Blue) |
|||
.DrawPolygon(Color.HotPink, 10, new[] { |
|||
new Point(10, 10), |
|||
new Point(200, 10), |
|||
new Point(200, 150), |
|||
new Point(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]); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -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 PointF(10, 400), |
|||
new PointF(30, 10), |
|||
new PointF(240, 30), |
|||
new PointF(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 PointF(10, 400), |
|||
new PointF(30, 10), |
|||
new PointF(240, 30), |
|||
new PointF(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]); |
|||
} |
|||
} |
|||
|
|||
} |
|||
} |
|||
@ -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 Point(10, 10), |
|||
new Point(200, 150), |
|||
new Point(50, 300)); |
|||
|
|||
var hole1 = new LinearPolygon( |
|||
new Point(37, 85), |
|||
new Point(93, 85), |
|||
new Point(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 Point(10, 10), |
|||
new Point(200, 150), |
|||
new Point(50, 300)); |
|||
|
|||
var hole1 = new LinearPolygon( |
|||
new Point(37, 85), |
|||
new Point(130, 40), |
|||
new Point(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 Point(10, 10), |
|||
new Point(200, 150), |
|||
new Point(50, 300)); |
|||
|
|||
var hole1 = new LinearPolygon( |
|||
new Point(37, 85), |
|||
new Point(93, 85), |
|||
new Point(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]); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,120 @@ |
|||
// <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 SolidPolygonTests : FileTestBase |
|||
{ |
|||
[Fact] |
|||
public void ImageShouldBeOverlayedByFilledPolygon() |
|||
{ |
|||
string path = CreateOutputDirectory("Drawing", "FilledPolygons"); |
|||
var simplePath = new[] { |
|||
new Point(10, 10), |
|||
new Point(200, 150), |
|||
new Point(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 ImageShouldBeOverlayedByFilledPolygonOpacity() |
|||
{ |
|||
string path = CreateOutputDirectory("Drawing", "FilledPolygons"); |
|||
var simplePath = new[] { |
|||
new Point(10, 10), |
|||
new Point(200, 150), |
|||
new Point(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 Point(10, 10), |
|||
new Point(200, 10), |
|||
new Point(200, 150), |
|||
new Point(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]); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
Loading…
Reference in new issue