Browse Source

added IRegion and droped use of vectors in brushes

af/merge-core
Scott Williams 9 years ago
parent
commit
b8de058453
  1. 4
      .travis.yml
  2. 8
      ConsoleApp1/ConsoleApp1.csproj
  3. 9
      ConsoleApp1/Program.cs
  4. 20
      src/ImageSharp.Drawing/Brushes/ImageBrush{TColor}.cs
  5. 18
      src/ImageSharp.Drawing/Brushes/PatternBrush{TColor}.cs
  6. 16
      src/ImageSharp.Drawing/Brushes/Processors/BrushApplicator.cs
  7. 31
      src/ImageSharp.Drawing/Brushes/RecolorBrush{TColor}.cs
  8. 8
      src/ImageSharp.Drawing/Brushes/SolidBrush{TColor}.cs
  9. 6
      src/ImageSharp.Drawing/Draw.cs
  10. 2
      src/ImageSharp.Drawing/DrawRectangle.cs
  11. 4
      src/ImageSharp.Drawing/Fill.cs
  12. 4
      src/ImageSharp.Drawing/FillRectangle.cs
  13. 24
      src/ImageSharp.Drawing/IDrawableRegion.cs
  14. 51
      src/ImageSharp.Drawing/IRegion.cs
  15. 8
      src/ImageSharp.Drawing/Pens/Pen{TColor}.cs
  16. 9
      src/ImageSharp.Drawing/Pens/Processors/PenApplicator.cs
  17. 7
      src/ImageSharp.Drawing/PointInfo.cs
  18. 75
      src/ImageSharp.Drawing/Processors/DrawPathProcessor.cs
  19. 7
      src/ImageSharp.Drawing/Processors/FillProcessor.cs
  20. 153
      src/ImageSharp.Drawing/Processors/FillShapeProcessor.cs
  21. 5
      src/ImageSharp.Drawing/Processors/PointInfoExtensions.cs
  22. 180
      src/ImageSharp.Drawing/ShapeRegion.cs
  23. 8
      src/ImageSharp.Drawing/project.json
  24. 3
      tests/ImageSharp.Sandbox46/ImageSharp.Sandbox46.csproj
  25. 5
      tests/ImageSharp.Tests/Drawing/DrawPathTests.cs
  26. 71
      tests/ImageSharp.Tests/Drawing/Helpers/BezierPolygon.cs
  27. 71
      tests/ImageSharp.Tests/Drawing/Helpers/LinearPolygon.cs
  28. 22
      tests/ImageSharp.Tests/Drawing/LineComplexPolygonTests.cs
  29. 3
      tests/ImageSharp.Tests/Drawing/SolidBezierTests.cs
  30. 16
      tests/ImageSharp.Tests/Drawing/SolidComplexPolygonTests.cs
  31. 9
      tests/ImageSharp.Tests/Drawing/SolidPolygonTests.cs

4
.travis.yml

@ -20,8 +20,8 @@ branches:
script: script:
- dotnet restore - dotnet restore
- dotnet build -c Release src/*/project.json - dotnet build -c Release src/*/project.csproj
- dotnet test tests/ImageSharp.Tests/project.json -c Release -f "netcoreapp1.1" - dotnet test tests/ImageSharp.Tests/project.csproj -c Release -f "netcoreapp1.1"
env: env:
global: global:

8
ConsoleApp1/ConsoleApp1.csproj

@ -0,0 +1,8 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp1.0</TargetFramework>
</PropertyGroup>
</Project>

9
ConsoleApp1/Program.cs

@ -0,0 +1,9 @@
using System;
class Program
{
static void Main(string[] args)
{
Console.WriteLine("Hello World!");
}
}

20
src/ImageSharp.Drawing/Brushes/ImageBrush{TColor}.cs

@ -82,18 +82,24 @@ namespace ImageSharp.Drawing.Brushes
/// <summary> /// <summary>
/// Gets the color for a single pixel. /// Gets the color for a single pixel.
/// </summary> /// </summary>
/// <param name="point">The point.</param> /// <param name="x">The x.</param>
/// <param name="y">The y.</param>
/// <returns> /// <returns>
/// The color /// The color
/// </returns> /// </returns>
public override TColor GetColor(Vector2 point) public override TColor this[int x, int y]
{ {
// Offset the requested pixel by the value in the rectangle (the shapes position) get
point = point - this.offset; {
int x = (int)point.X % this.xLength; var point = new Vector2(x, y);
int y = (int)point.Y % this.yLength;
return this.source[x, y]; // Offset the requested pixel by the value in the rectangle (the shapes position)
point = point - this.offset;
x = (int)point.X % this.xLength;
y = (int)point.Y % this.yLength;
return this.source[x, y];
}
} }
/// <inheritdoc /> /// <inheritdoc />

18
src/ImageSharp.Drawing/Brushes/PatternBrush{TColor}.cs

@ -134,17 +134,21 @@ namespace ImageSharp.Drawing.Brushes
/// <summary> /// <summary>
/// Gets the color for a single pixel. /// Gets the color for a single pixel.
/// </summary> /// </summary>#
/// <param name="point">The point.</param> /// <param name="x">The x.</param>
/// <param name="y">The y.</param>
/// <returns> /// <returns>
/// The color /// The Color.
/// </returns> /// </returns>
public override TColor GetColor(Vector2 point) public override TColor this[int x, int y]
{ {
int x = (int)point.X % this.xLength; get
int y = (int)point.Y % this.stride; {
x = x % this.xLength;
y = y % this.stride;
return this.pattern[x][y]; return this.pattern[x][y];
}
} }
/// <inheritdoc /> /// <inheritdoc />

16
src/ImageSharp.Drawing/Brushes/Processors/BrushApplicator.cs

@ -16,16 +16,20 @@ namespace ImageSharp.Drawing.Processors
public abstract class BrushApplicator<TColor> : IDisposable // disposable will be required if/when there is an ImageBrush public abstract class BrushApplicator<TColor> : IDisposable // disposable will be required if/when there is an ImageBrush
where TColor : struct, IPackedPixel, IEquatable<TColor> where TColor : struct, IPackedPixel, IEquatable<TColor>
{ {
/// <summary> /// <summary>
/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. /// Gets the color for a single pixel.
/// </summary> /// </summary>
public abstract void Dispose(); /// <param name="x">The x.</param>
/// <param name="y">The y.</param>
/// <returns>
/// The color
/// </returns>
public abstract TColor this[int x, int y] { get; }
/// <summary> /// <summary>
/// Gets the color for a single pixel. /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
/// </summary> /// </summary>
/// <param name="point">The point.</param> public abstract void Dispose();
/// <returns>The color</returns>
public abstract TColor GetColor(Vector2 point);
} }
} }

31
src/ImageSharp.Drawing/Brushes/RecolorBrush{TColor}.cs

@ -109,24 +109,31 @@ namespace ImageSharp.Drawing.Brushes
/// <summary> /// <summary>
/// Gets the color for a single pixel. /// Gets the color for a single pixel.
/// </summary> /// </summary>
/// <param name="point">The point.</param> /// <param name="x">The x.</param>
/// <param name="y">The y.</param>
/// <returns> /// <returns>
/// The color /// The color
/// </returns> /// </returns>
public override TColor GetColor(Vector2 point) public override TColor this[int x, int y]
{ {
// Offset the requested pixel by the value in the rectangle (the shapes position) get
TColor result = this.source[(int)point.X, (int)point.Y];
Vector4 background = result.ToVector4();
float distance = Vector4.DistanceSquared(background, this.sourceColor);
if (distance <= this.threshold)
{ {
var lerpAmount = (this.threshold - distance) / this.threshold; // Offset the requested pixel by the value in the rectangle (the shapes position)
Vector4 blended = Vector4BlendTransforms.PremultipliedLerp(background, this.targetColor, lerpAmount); TColor result = this.source[x, y];
result.PackFromVector4(blended); Vector4 background = result.ToVector4();
float distance = Vector4.DistanceSquared(background, this.sourceColor);
if (distance <= this.threshold)
{
var lerpAmount = (this.threshold - distance) / this.threshold;
Vector4 blended = Vector4BlendTransforms.PremultipliedLerp(
background,
this.targetColor,
lerpAmount);
result.PackFromVector4(blended);
}
return result;
} }
return result;
} }
/// <inheritdoc /> /// <inheritdoc />

8
src/ImageSharp.Drawing/Brushes/SolidBrush{TColor}.cs

@ -67,14 +67,12 @@ namespace ImageSharp.Drawing.Brushes
/// <summary> /// <summary>
/// Gets the color for a single pixel. /// Gets the color for a single pixel.
/// </summary> /// </summary>
/// <param name="point">The point.</param> /// <param name="x">The x.</param>
/// <param name="y">The y.</param>
/// <returns> /// <returns>
/// The color /// The color
/// </returns> /// </returns>
public override TColor GetColor(Vector2 point) public override TColor this[int x, int y] => this.color;
{
return this.color;
}
/// <inheritdoc /> /// <inheritdoc />
public override void Dispose() public override void Dispose()

6
src/ImageSharp.Drawing/Draw.cs

@ -32,7 +32,7 @@ namespace ImageSharp
public static Image<TColor> DrawPolygon<TColor>(this Image<TColor> source, IPen<TColor> pen, IShape shape, GraphicsOptions options) public static Image<TColor> DrawPolygon<TColor>(this Image<TColor> source, IPen<TColor> pen, IShape shape, GraphicsOptions options)
where TColor : struct, IPackedPixel, IEquatable<TColor> where TColor : struct, IPackedPixel, IEquatable<TColor>
{ {
return source.Apply(new DrawPathProcessor<TColor>(pen, shape, options)); return source.Apply(new DrawPathProcessor<TColor>(pen, new ShapeRegion(shape), options));
} }
/// <summary> /// <summary>
@ -226,7 +226,7 @@ namespace ImageSharp
public static Image<TColor> DrawPath<TColor>(this Image<TColor> source, IPen<TColor> pen, IPath path, GraphicsOptions options) public static Image<TColor> DrawPath<TColor>(this Image<TColor> source, IPen<TColor> pen, IPath path, GraphicsOptions options)
where TColor : struct, IPackedPixel, IEquatable<TColor> where TColor : struct, IPackedPixel, IEquatable<TColor>
{ {
return source.Apply(new DrawPathProcessor<TColor>(pen, path, options)); return source.Apply(new DrawPathProcessor<TColor>(pen, new ShapeRegion(path), options));
} }
/// <summary> /// <summary>
@ -240,7 +240,7 @@ namespace ImageSharp
public static Image<TColor> DrawPath<TColor>(this Image<TColor> source, IPen<TColor> pen, IPath path) public static Image<TColor> DrawPath<TColor>(this Image<TColor> source, IPen<TColor> pen, IPath path)
where TColor : struct, IPackedPixel, IEquatable<TColor> where TColor : struct, IPackedPixel, IEquatable<TColor>
{ {
return source.Apply(new DrawPathProcessor<TColor>(pen, path, GraphicsOptions.Default)); return source.Apply(new DrawPathProcessor<TColor>(pen, new ShapeRegion(path), GraphicsOptions.Default));
} }
/// <summary> /// <summary>

2
src/ImageSharp.Drawing/DrawRectangle.cs

@ -33,7 +33,7 @@ namespace ImageSharp
public static Image<TColor> DrawPolygon<TColor>(this Image<TColor> source, IPen<TColor> pen, RectangleF shape, GraphicsOptions options) public static Image<TColor> DrawPolygon<TColor>(this Image<TColor> source, IPen<TColor> pen, RectangleF shape, GraphicsOptions options)
where TColor : struct, IPackedPixel, IEquatable<TColor> where TColor : struct, IPackedPixel, IEquatable<TColor>
{ {
return source.Apply(new DrawPathProcessor<TColor>(pen, (IPath)new SixLabors.Shapes.Rectangle(shape.X, shape.Y, shape.Width, shape.Height), options)); return source.Apply(new DrawPathProcessor<TColor>(pen, new ShapeRegion(new SixLabors.Shapes.Rectangle(shape.X, shape.Y, shape.Width, shape.Height)), options));
} }
/// <summary> /// <summary>

4
src/ImageSharp.Drawing/Fill.cs

@ -56,7 +56,7 @@ namespace ImageSharp
public static Image<TColor> Fill<TColor>(this Image<TColor> source, IBrush<TColor> brush, IShape shape, GraphicsOptions options) public static Image<TColor> Fill<TColor>(this Image<TColor> source, IBrush<TColor> brush, IShape shape, GraphicsOptions options)
where TColor : struct, IPackedPixel, IEquatable<TColor> where TColor : struct, IPackedPixel, IEquatable<TColor>
{ {
return source.Apply(new FillShapeProcessor<TColor>(brush, shape, options)); return source.Apply(new FillShapeProcessor<TColor>(brush, new ShapeRegion(shape), options));
} }
/// <summary> /// <summary>
@ -70,7 +70,7 @@ namespace ImageSharp
public static Image<TColor> Fill<TColor>(this Image<TColor> source, IBrush<TColor> brush, IShape shape) public static Image<TColor> Fill<TColor>(this Image<TColor> source, IBrush<TColor> brush, IShape shape)
where TColor : struct, IPackedPixel, IEquatable<TColor> where TColor : struct, IPackedPixel, IEquatable<TColor>
{ {
return source.Apply(new FillShapeProcessor<TColor>(brush, shape, GraphicsOptions.Default)); return source.Apply(new FillShapeProcessor<TColor>(brush, new ShapeRegion(shape), GraphicsOptions.Default));
} }
/// <summary> /// <summary>

4
src/ImageSharp.Drawing/FillRectangle.cs

@ -30,7 +30,7 @@ namespace ImageSharp
public static Image<TColor> Fill<TColor>(this Image<TColor> source, IBrush<TColor> brush, RectangleF shape, GraphicsOptions options) public static Image<TColor> Fill<TColor>(this Image<TColor> source, IBrush<TColor> brush, RectangleF shape, GraphicsOptions options)
where TColor : struct, IPackedPixel, IEquatable<TColor> where TColor : struct, IPackedPixel, IEquatable<TColor>
{ {
return source.Apply(new FillShapeProcessor<TColor>(brush, new SixLabors.Shapes.Rectangle(shape.X, shape.Y, shape.Width, shape.Height), options)); return source.Fill(brush, new SixLabors.Shapes.Rectangle(shape.X, shape.Y, shape.Width, shape.Height), options);
} }
/// <summary> /// <summary>
@ -44,7 +44,7 @@ namespace ImageSharp
public static Image<TColor> Fill<TColor>(this Image<TColor> source, IBrush<TColor> brush, RectangleF shape) public static Image<TColor> Fill<TColor>(this Image<TColor> source, IBrush<TColor> brush, RectangleF shape)
where TColor : struct, IPackedPixel, IEquatable<TColor> where TColor : struct, IPackedPixel, IEquatable<TColor>
{ {
return source.Apply(new FillShapeProcessor<TColor>(brush, new SixLabors.Shapes.Rectangle(shape.X, shape.Y, shape.Width, shape.Height), GraphicsOptions.Default)); return source.Fill(brush, new SixLabors.Shapes.Rectangle(shape.X, shape.Y, shape.Width, shape.Height));
} }
/// <summary> /// <summary>

24
src/ImageSharp.Drawing/IDrawableRegion.cs

@ -0,0 +1,24 @@
// <copyright file="IDrawableRegion.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.Numerics;
/// <summary>
/// Represents a region with knowledge about its outline.
/// </summary>
/// <seealso cref="ImageSharp.Drawing.IRegion" />
public interface IDrawableRegion : IRegion
{
/// <summary>
/// Gets the point information for the specified x and y location.
/// </summary>
/// <param name="x">The x.</param>
/// <param name="y">The y.</param>
/// <returns>Information about the point in relation to a drawable edge</returns>
PointInfo GetPointInfo(int x, int y);
}
}

51
src/ImageSharp.Drawing/IRegion.cs

@ -0,0 +1,51 @@
// <copyright file="IRegion.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.Numerics;
/// <summary>
/// Represents a region of an image.
/// </summary>
public interface IRegion
{
/// <summary>
/// Gets the maximum number of intersections to could be returned.
/// </summary>
/// <value>
/// The maximum intersections.
/// </value>
int MaxIntersections { get; }
/// <summary>
/// Gets the bounds.
/// </summary>
/// <value>
/// The bounds.
/// </value>
Rectangle Bounds { get; }
/// <summary>
/// Scans the X axis for intersections.
/// </summary>
/// <param name="x">The x.</param>
/// <param name="buffer">The buffer.</param>
/// <param name="length">The length.</param>
/// <param name="offset">The offset.</param>
/// <returns>The number of intersections found.</returns>
int ScanX(int x, float[] buffer, int length, int offset);
/// <summary>
/// Scans the Y axis for intersections.
/// </summary>
/// <param name="y">The position along the y axis to find intersections.</param>
/// <param name="buffer">The buffer.</param>
/// <param name="length">The length.</param>
/// <param name="offset">The offset.</param>
/// <returns>The number of intersections found.</returns>
int ScanY(int y, float[] buffer, int length, int offset);
}
}

8
src/ImageSharp.Drawing/Pens/Pen{TColor}.cs

@ -144,10 +144,10 @@ namespace ImageSharp.Drawing.Pens
this.brush.Dispose(); this.brush.Dispose();
} }
public override ColoredPointInfo<TColor> GetColor(PointInfo info) public override ColoredPointInfo<TColor> GetColor(int x, int y, PointInfo info)
{ {
var result = default(ColoredPointInfo<TColor>); var result = default(ColoredPointInfo<TColor>);
result.Color = this.brush.GetColor(info.SearchPoint); result.Color = this.brush[x, y];
if (info.DistanceFromPath < this.halfWidth) if (info.DistanceFromPath < this.halfWidth)
{ {
@ -197,7 +197,7 @@ namespace ImageSharp.Drawing.Pens
this.brush.Dispose(); this.brush.Dispose();
} }
public override ColoredPointInfo<TColor> GetColor(PointInfo info) public override ColoredPointInfo<TColor> GetColor(int x, int y, PointInfo info)
{ {
var infoResult = default(ColoredPointInfo<TColor>); var infoResult = default(ColoredPointInfo<TColor>);
infoResult.DistanceFromElement = float.MaxValue; // is really outside the element infoResult.DistanceFromElement = float.MaxValue; // is really outside the element
@ -207,7 +207,7 @@ namespace ImageSharp.Drawing.Pens
// we can treat the DistanceAlongPath and DistanceFromPath as x,y coords for the pattern // 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 // we need to calcualte the distance from the outside edge of the pattern
// and set them on the ColoredPointInfo<TColor> along with the color. // and set them on the ColoredPointInfo<TColor> along with the color.
infoResult.Color = this.brush.GetColor(info.SearchPoint); infoResult.Color = this.brush[x, y];
float distanceWAway = 0; float distanceWAway = 0;

9
src/ImageSharp.Drawing/Pens/Processors/PenApplicator.cs

@ -6,6 +6,7 @@
namespace ImageSharp.Drawing.Processors namespace ImageSharp.Drawing.Processors
{ {
using System; using System;
using System.Numerics;
/// <summary> /// <summary>
/// primitive that converts a <see cref="PointInfo"/> into a color and a distance away from the drawable part of the path. /// primitive that converts a <see cref="PointInfo"/> into a color and a distance away from the drawable part of the path.
@ -30,8 +31,12 @@ namespace ImageSharp.Drawing.Processors
/// <summary> /// <summary>
/// Gets a <see cref="ColoredPointInfo{TColor}" /> from a point represented by a <see cref="PointInfo" />. /// Gets a <see cref="ColoredPointInfo{TColor}" /> from a point represented by a <see cref="PointInfo" />.
/// </summary> /// </summary>
/// <param name="x">The x.</param>
/// <param name="y">The y.</param>
/// <param name="info">The information to extract color details about.</param> /// <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> /// <returns>
public abstract ColoredPointInfo<TColor> GetColor(PointInfo info); /// Returns the color details and distance from a solid bit of the line.
/// </returns>
public abstract ColoredPointInfo<TColor> GetColor(int x, int y, PointInfo info);
} }
} }

7
src/ImageSharp.Drawing/Pens/Processors/PointInfo.cs → src/ImageSharp.Drawing/PointInfo.cs

@ -3,7 +3,7 @@
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
// </copyright> // </copyright>
namespace ImageSharp.Drawing.Processors namespace ImageSharp.Drawing
{ {
using System; using System;
using System.Numerics; using System.Numerics;
@ -22,10 +22,5 @@ namespace ImageSharp.Drawing.Processors
/// The distance from path /// The distance from path
/// </summary> /// </summary>
public float DistanceFromPath; public float DistanceFromPath;
/// <summary>
/// The search point
/// </summary>
public Vector2 SearchPoint;
} }
} }

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

@ -27,64 +27,27 @@ namespace ImageSharp.Drawing.Processors
private const int PaddingFactor = 1; // needs to been the same or greater than AntialiasFactor private const int PaddingFactor = 1; // needs to been the same or greater than AntialiasFactor
private readonly IPen<TColor> pen; private readonly IPen<TColor> pen;
private readonly IPath[] paths; private readonly IDrawableRegion region;
private readonly RectangleF region;
private readonly GraphicsOptions options; private readonly GraphicsOptions options;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="DrawPathProcessor{TColor}" /> class. /// Initializes a new instance of the <see cref="DrawPathProcessor{TColor}" /> class.
/// </summary> /// </summary>
/// <param name="pen">The pen.</param> /// <param name="pen">The pen.</param>
/// <param name="shape">The shape.</param> /// <param name="region">The region.</param>
/// <param name="options">The options.</param> /// <param name="options">The options.</param>
public DrawPathProcessor(IPen<TColor> pen, IShape shape, GraphicsOptions options) public DrawPathProcessor(IPen<TColor> pen, IDrawableRegion region, GraphicsOptions options)
: this(pen, shape.Paths, options)
{ {
} this.region = region;
/// <summary>
/// Initializes a new instance of the <see cref="DrawPathProcessor{TColor}"/> class.
/// </summary>
/// <param name="pen">The pen.</param>
/// <param name="path">The path.</param>
/// <param name="options">The options.</param>
public DrawPathProcessor(IPen<TColor> pen, IPath path, GraphicsOptions options)
: this(pen, new[] { path }, options)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="DrawPathProcessor{TColor}" /> class.
/// </summary>
/// <param name="pen">The pen.</param>
/// <param name="paths">The paths.</param>
/// <param name="options">The options.</param>
public DrawPathProcessor(IPen<TColor> pen, IEnumerable<IPath> paths, GraphicsOptions options)
{
this.paths = paths.ToArray();
this.pen = pen; this.pen = pen;
this.options = options; this.options = options;
if (this.paths.Length != 1)
{
var maxX = this.paths.Max(x => x.Bounds.Right);
var minX = this.paths.Min(x => x.Bounds.Left);
var maxY = this.paths.Max(x => x.Bounds.Bottom);
var minY = this.paths.Min(x => x.Bounds.Top);
this.region = new RectangleF(minX, minY, maxX - minX, maxY - minY);
}
else
{
this.region = this.paths[0].Bounds.Convert();
}
} }
/// <inheritdoc/> /// <inheritdoc/>
protected override void OnApply(ImageBase<TColor> source, Rectangle sourceRectangle) protected override void OnApply(ImageBase<TColor> source, Rectangle sourceRectangle)
{ {
using (PixelAccessor<TColor> sourcePixels = source.Lock()) using (PixelAccessor<TColor> sourcePixels = source.Lock())
using (PenApplicator<TColor> applicator = this.pen.CreateApplicator(sourcePixels, this.region)) using (PenApplicator<TColor> applicator = this.pen.CreateApplicator(sourcePixels, this.region.Bounds))
{ {
var rect = RectangleF.Ceiling(applicator.RequiredRegion); var rect = RectangleF.Ceiling(applicator.RequiredRegion);
@ -122,16 +85,14 @@ namespace ImageSharp.Drawing.Processors
(int y) => (int y) =>
{ {
int offsetY = y - polyStartY; int offsetY = y - polyStartY;
var currentPoint = default(Vector2);
for (int x = minX; x < maxX; x++) for (int x = minX; x < maxX; x++)
{ {
// TODO add find intersections code to skip and scan large regions of this.
int offsetX = x - startX; int offsetX = x - startX;
currentPoint.X = offsetX; var info = this.region.GetPointInfo(offsetX, offsetY);
currentPoint.Y = offsetY;
var dist = this.Closest(currentPoint); var color = applicator.GetColor(offsetX, offsetY, info);
var color = applicator.GetColor(dist.Convert());
var opacity = this.Opacity(color.DistanceFromElement); var opacity = this.Opacity(color.DistanceFromElement);
@ -154,24 +115,6 @@ namespace ImageSharp.Drawing.Processors
} }
} }
private SixLabors.Shapes.PointInfo Closest(Vector2 point)
{
SixLabors.Shapes.PointInfo result = default(SixLabors.Shapes.PointInfo);
float distance = float.MaxValue;
for (int i = 0; i < this.paths.Length; i++)
{
var p = this.paths[i].Distance(point);
if (p.DistanceFromPath < distance)
{
distance = p.DistanceFromPath;
result = p;
}
}
return result;
}
private float Opacity(float distance) private float Opacity(float distance)
{ {
if (distance <= 0) if (distance <= 0)

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

@ -71,17 +71,12 @@ namespace ImageSharp.Drawing.Processors
y => y =>
{ {
int offsetY = y - startY; int offsetY = y - startY;
Vector2 currentPoint = default(Vector2);
for (int x = minX; x < maxX; x++) for (int x = minX; x < maxX; x++)
{ {
int offsetX = x - startX; int offsetX = x - startX;
int offsetColorX = x - minX;
currentPoint.X = offsetX;
currentPoint.Y = offsetY;
Vector4 backgroundVector = sourcePixels[offsetX, offsetY].ToVector4(); Vector4 backgroundVector = sourcePixels[offsetX, offsetY].ToVector4();
Vector4 sourceVector = applicator.GetColor(currentPoint).ToVector4(); Vector4 sourceVector = applicator[offsetX, offsetY].ToVector4();
Vector4 finalColor = Vector4BlendTransforms.PremultipliedLerp(backgroundVector, sourceVector, 1); Vector4 finalColor = Vector4BlendTransforms.PremultipliedLerp(backgroundVector, sourceVector, 1);

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

@ -25,18 +25,18 @@ namespace ImageSharp.Drawing.Processors
private const float AntialiasFactor = 1f; private const float AntialiasFactor = 1f;
private const int DrawPadding = 1; private const int DrawPadding = 1;
private readonly IBrush<TColor> fillColor; private readonly IBrush<TColor> fillColor;
private readonly IShape poly; private readonly IRegion region;
private readonly GraphicsOptions options; private readonly GraphicsOptions options;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="FillShapeProcessor{TColor}"/> class. /// Initializes a new instance of the <see cref="FillShapeProcessor{TColor}" /> class.
/// </summary> /// </summary>
/// <param name="brush">The brush.</param> /// <param name="brush">The brush.</param>
/// <param name="shape">The shape.</param> /// <param name="region">The region.</param>
/// <param name="options">The options.</param> /// <param name="options">The options.</param>
public FillShapeProcessor(IBrush<TColor> brush, IShape shape, GraphicsOptions options) public FillShapeProcessor(IBrush<TColor> brush, IRegion region, GraphicsOptions options)
{ {
this.poly = shape; this.region = region;
this.fillColor = brush; this.fillColor = brush;
this.options = options; this.options = options;
} }
@ -44,12 +44,12 @@ namespace ImageSharp.Drawing.Processors
/// <inheritdoc/> /// <inheritdoc/>
protected override void OnApply(ImageBase<TColor> source, Rectangle sourceRectangle) protected override void OnApply(ImageBase<TColor> source, Rectangle sourceRectangle)
{ {
Rectangle rect = RectangleF.Ceiling(this.poly.Bounds.Convert()); // rounds the points out away from the center Rectangle rect = RectangleF.Ceiling(this.region.Bounds); // rounds the points out away from the center
int polyStartY = rect.Y - DrawPadding; int polyStartY = sourceRectangle.Y - DrawPadding;
int polyEndY = rect.Bottom + DrawPadding; int polyEndY = sourceRectangle.Bottom + DrawPadding;
int startX = rect.X - DrawPadding; int startX = sourceRectangle.X - DrawPadding;
int endX = rect.Right + DrawPadding; int endX = sourceRectangle.Right + DrawPadding;
int minX = Math.Max(sourceRectangle.Left, startX); int minX = Math.Max(sourceRectangle.Left, startX);
int maxX = Math.Min(sourceRectangle.Right - 1, endX); int maxX = Math.Min(sourceRectangle.Right - 1, endX);
@ -62,9 +62,9 @@ namespace ImageSharp.Drawing.Processors
minY = Math.Max(0, minY); minY = Math.Max(0, minY);
maxY = Math.Min(source.Height, maxY); maxY = Math.Min(source.Height, maxY);
ArrayPool<Vector2> arrayPool = ArrayPool<Vector2>.Shared; ArrayPool<float> arrayPool = ArrayPool<float>.Shared;
int maxIntersections = this.poly.MaxIntersections; int maxIntersections = this.region.MaxIntersections;
using (PixelAccessor<TColor> sourcePixels = source.Lock()) using (PixelAccessor<TColor> sourcePixels = source.Lock())
using (BrushApplicator<TColor> applicator = this.fillColor.CreateApplicator(sourcePixels, rect)) using (BrushApplicator<TColor> applicator = this.fillColor.CreateApplicator(sourcePixels, rect))
@ -73,27 +73,26 @@ namespace ImageSharp.Drawing.Processors
minY, minY,
maxY, maxY,
this.ParallelOptions, this.ParallelOptions,
y => (int y) =>
{ {
Vector2[] buffer = arrayPool.Rent(maxIntersections); float[] buffer = arrayPool.Rent(maxIntersections);
try try
{ {
Vector2 left = new Vector2(startX, y); float right = endX;
Vector2 right = new Vector2(endX, y);
// foreach line we get all the points where this line crosses the polygon // foreach line we get all the points where this line crosses the polygon
int pointsFound = this.poly.FindIntersections(left, right, buffer, maxIntersections, 0); int pointsFound = this.region.ScanY(y, buffer, maxIntersections, 0);
if (pointsFound == 0) if (pointsFound == 0)
{ {
// nothign on this line skip // nothign on this line skip
return; return;
} }
QuickSortX(buffer, pointsFound); QuickSort(buffer, pointsFound);
int currentIntersection = 0; int currentIntersection = 0;
float nextPoint = buffer[0].X; float nextPoint = buffer[0];
float lastPoint = float.MinValue; float lastPoint = float.MinValue;
bool isInside = false; bool isInside = false;
@ -108,7 +107,7 @@ namespace ImageSharp.Drawing.Processors
{ {
if (x < (nextPoint - DrawPadding) && x > (lastPoint + DrawPadding)) if (x < (nextPoint - DrawPadding) && x > (lastPoint + DrawPadding))
{ {
if (nextPoint == right.X) if (nextPoint == right)
{ {
// we are in the ends run skip it // we are in the ends run skip it
x = maxX; x = maxX;
@ -129,11 +128,11 @@ namespace ImageSharp.Drawing.Processors
lastPoint = nextPoint; lastPoint = nextPoint;
if (currentIntersection == pointsFound) if (currentIntersection == pointsFound)
{ {
nextPoint = right.X; nextPoint = right;
} }
else else
{ {
nextPoint = buffer[currentIntersection].X; nextPoint = buffer[currentIntersection];
// double point from a corner flip the bit back and move on again // double point from a corner flip the bit back and move on again
if (nextPoint == lastPoint) if (nextPoint == lastPoint)
@ -143,11 +142,11 @@ namespace ImageSharp.Drawing.Processors
currentIntersection++; currentIntersection++;
if (currentIntersection == pointsFound) if (currentIntersection == pointsFound)
{ {
nextPoint = right.X; nextPoint = right;
} }
else else
{ {
nextPoint = buffer[currentIntersection].X; nextPoint = buffer[currentIntersection];
} }
} }
} }
@ -192,7 +191,7 @@ namespace ImageSharp.Drawing.Processors
if (opacity > Constants.Epsilon) if (opacity > Constants.Epsilon)
{ {
Vector4 backgroundVector = sourcePixels[x, y].ToVector4(); Vector4 backgroundVector = sourcePixels[x, y].ToVector4();
Vector4 sourceVector = applicator.GetColor(currentPoint).ToVector4(); Vector4 sourceVector = applicator[x, y].ToVector4();
Vector4 finalColor = Vector4BlendTransforms.PremultipliedLerp(backgroundVector, sourceVector, opacity); Vector4 finalColor = Vector4BlendTransforms.PremultipliedLerp(backgroundVector, sourceVector, opacity);
finalColor.W = backgroundVector.W; finalColor.W = backgroundVector.W;
@ -216,42 +215,37 @@ namespace ImageSharp.Drawing.Processors
minX, minX,
maxX, maxX,
this.ParallelOptions, this.ParallelOptions,
x => (int x) =>
{ {
Vector2[] buffer = arrayPool.Rent(maxIntersections); float[] buffer = arrayPool.Rent(maxIntersections);
try try
{ {
Vector2 left = new Vector2(x, polyStartY); float left = polyStartY;
Vector2 right = new Vector2(x, polyEndY); float right = polyEndY;
// foreach line we get all the points where this line crosses the polygon // foreach line we get all the points where this line crosses the polygon
int pointsFound = this.poly.FindIntersections(left, right, buffer, maxIntersections, 0); int pointsFound = this.region.ScanX(x, buffer, maxIntersections, 0);
if (pointsFound == 0) if (pointsFound == 0)
{ {
// nothign on this line skip // nothign on this line skip
return; return;
} }
QuickSortY(buffer, pointsFound); QuickSort(buffer, pointsFound);
int currentIntersection = 0; int currentIntersection = 0;
float nextPoint = buffer[0].Y; float nextPoint = buffer[0];
float lastPoint = left.Y; float lastPoint = left;
bool isInside = false; bool isInside = false;
// every odd point is the start of a line
Vector2 currentPoint = default(Vector2);
for (int y = minY; y < maxY; y++) for (int y = minY; y < maxY; y++)
{ {
currentPoint.X = x;
currentPoint.Y = y;
if (!isInside) if (!isInside)
{ {
if (y < (nextPoint - DrawPadding) && y > (lastPoint + DrawPadding)) if (y < (nextPoint - DrawPadding) && y > (lastPoint + DrawPadding))
{ {
if (nextPoint == right.Y) if (nextPoint == right)
{ {
// we are in the ends run skip it // we are in the ends run skip it
y = maxY; y = maxY;
@ -266,7 +260,7 @@ namespace ImageSharp.Drawing.Processors
{ {
if (y < nextPoint - DrawPadding) if (y < nextPoint - DrawPadding)
{ {
if (nextPoint == right.Y) if (nextPoint == right)
{ {
// we are in the ends run skip it // we are in the ends run skip it
y = maxY; y = maxY;
@ -286,11 +280,11 @@ namespace ImageSharp.Drawing.Processors
lastPoint = nextPoint; lastPoint = nextPoint;
if (currentIntersection == pointsFound) if (currentIntersection == pointsFound)
{ {
nextPoint = right.Y; nextPoint = right;
} }
else else
{ {
nextPoint = buffer[currentIntersection].Y; nextPoint = buffer[currentIntersection];
// double point from a corner flip the bit back and move on again // double point from a corner flip the bit back and move on again
if (nextPoint == lastPoint) if (nextPoint == lastPoint)
@ -300,11 +294,11 @@ namespace ImageSharp.Drawing.Processors
currentIntersection++; currentIntersection++;
if (currentIntersection == pointsFound) if (currentIntersection == pointsFound)
{ {
nextPoint = right.Y; nextPoint = right;
} }
else else
{ {
nextPoint = buffer[currentIntersection].Y; nextPoint = buffer[currentIntersection];
} }
} }
} }
@ -350,8 +344,7 @@ namespace ImageSharp.Drawing.Processors
if (opacity > Constants.Epsilon && opacity < 1) if (opacity > Constants.Epsilon && opacity < 1)
{ {
Vector4 backgroundVector = sourcePixels[x, y].ToVector4(); Vector4 backgroundVector = sourcePixels[x, y].ToVector4();
Vector4 sourceVector = applicator.GetColor(currentPoint).ToVector4(); Vector4 sourceVector = applicator[x, y].ToVector4();
Vector4 finalColor = Vector4BlendTransforms.PremultipliedLerp(backgroundVector, sourceVector, opacity); Vector4 finalColor = Vector4BlendTransforms.PremultipliedLerp(backgroundVector, sourceVector, opacity);
finalColor.W = backgroundVector.W; finalColor.W = backgroundVector.W;
@ -370,76 +363,32 @@ namespace ImageSharp.Drawing.Processors
} }
} }
private static void Swap(Vector2[] data, int left, int right) private static void Swap(float[] data, int left, int right)
{ {
Vector2 tmp = data[left]; float tmp = data[left];
data[left] = data[right]; data[left] = data[right];
data[right] = tmp; data[right] = tmp;
} }
private static void QuickSortY(Vector2[] data, int size) private static void QuickSort(float[] data, int size)
{
int hi = Math.Min(data.Length - 1, size - 1);
QuickSortY(data, 0, hi);
}
private static void QuickSortY(Vector2[] data, int lo, int hi)
{
if (lo < hi)
{
int p = PartitionY(data, lo, hi);
QuickSortY(data, lo, p);
QuickSortY(data, p + 1, hi);
}
}
private static void QuickSortX(Vector2[] data, int size)
{ {
int hi = Math.Min(data.Length - 1, size - 1); int hi = Math.Min(data.Length - 1, size - 1);
QuickSortX(data, 0, hi); QuickSort(data, 0, hi);
} }
private static void QuickSortX(Vector2[] data, int lo, int hi) private static void QuickSort(float[] data, int lo, int hi)
{ {
if (lo < hi) if (lo < hi)
{ {
int p = PartitionX(data, lo, hi); int p = Partition(data, lo, hi);
QuickSortX(data, lo, p); QuickSort(data, lo, p);
QuickSortX(data, p + 1, hi); QuickSort(data, p + 1, hi);
}
}
private static int PartitionX(Vector2[] data, int lo, int hi)
{
float pivot = data[lo].X;
int i = lo - 1;
int j = hi + 1;
while (true)
{
do
{
i = i + 1;
}
while (data[i].X < pivot && i < hi);
do
{
j = j - 1;
}
while (data[j].X > pivot && j > lo);
if (i >= j)
{
return j;
}
Swap(data, i, j);
} }
} }
private static int PartitionY(Vector2[] data, int lo, int hi) private static int Partition(float[] data, int lo, int hi)
{ {
float pivot = data[lo].Y; float pivot = data[lo];
int i = lo - 1; int i = lo - 1;
int j = hi + 1; int j = hi + 1;
while (true) while (true)
@ -448,13 +397,13 @@ namespace ImageSharp.Drawing.Processors
{ {
i = i + 1; i = i + 1;
} }
while (data[i].Y < pivot && i < hi); while (data[i] < pivot && i < hi);
do do
{ {
j = j - 1; j = j - 1;
} }
while (data[j].Y > pivot && j > lo); while (data[j] > pivot && j > lo);
if (i >= j) if (i >= j)
{ {

5
src/ImageSharp.Drawing/Processors/PointInfoExtensions.cs

@ -12,6 +12,8 @@ namespace ImageSharp.Drawing.Processors
using Drawing; using Drawing;
using ImageSharp.Processing; using ImageSharp.Processing;
using SixLabors.Shapes; using SixLabors.Shapes;
using PointInfo = ImageSharp.Drawing.PointInfo;
using Rectangle = ImageSharp.Rectangle; using Rectangle = ImageSharp.Rectangle;
/// <summary> /// <summary>
@ -29,8 +31,7 @@ namespace ImageSharp.Drawing.Processors
return new PointInfo return new PointInfo
{ {
DistanceAlongPath = source.DistanceAlongPath, DistanceAlongPath = source.DistanceAlongPath,
DistanceFromPath = source.DistanceFromPath, DistanceFromPath = source.DistanceFromPath
SearchPoint = source.SearchPoint
}; };
} }
} }

180
src/ImageSharp.Drawing/ShapeRegion.cs

@ -0,0 +1,180 @@
// <copyright file="ShapeRegion.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.Buffers;
using System.Collections.Immutable;
using System.Numerics;
using ImageSharp.Drawing.Processors;
using SixLabors.Shapes;
using Rectangle = ImageSharp.Rectangle;
/// <summary>
/// A drawable mapping between a <see cref="SixLabors.Shapes.IShape"/>/<see cref="SixLabors.Shapes.IPath"/> and a drawable/fillable region.
/// </summary>
/// <seealso cref="ImageSharp.Drawing.IDrawableRegion" />
internal class ShapeRegion : IDrawableRegion
{
/// <summary>
/// The fillable shape
/// </summary>
private readonly IShape shape;
/// <summary>
/// The drawable paths
/// </summary>
private readonly ImmutableArray<IPath> paths;
/// <summary>
/// Initializes a new instance of the <see cref="ShapeRegion"/> class.
/// </summary>
/// <param name="path">The path.</param>
public ShapeRegion(IPath path)
: this(ImmutableArray.Create(path))
{
this.shape = path.AsShape();
this.Bounds = RectangleF.Ceiling(path.Bounds.Convert());
}
/// <summary>
/// Initializes a new instance of the <see cref="ShapeRegion"/> class.
/// </summary>
/// <param name="shape">The shape.</param>
public ShapeRegion(IShape shape)
: this(shape.Paths)
{
this.shape = shape;
this.Bounds = RectangleF.Ceiling(shape.Bounds.Convert());
}
/// <summary>
/// Initializes a new instance of the <see cref="ShapeRegion"/> class.
/// </summary>
/// <param name="paths">The paths.</param>
private ShapeRegion(ImmutableArray<IPath> paths)
{
this.paths = paths;
}
/// <summary>
/// Gets the maximum number of intersections to could be returned.
/// </summary>
/// <value>
/// The maximum intersections.
/// </value>
public int MaxIntersections => this.shape.MaxIntersections;
/// <summary>
/// Gets the bounds.
/// </summary>
/// <value>
/// The bounds.
/// </value>
public Rectangle Bounds { get; }
/// <summary>
/// Scans the X axis for intersections.
/// </summary>
/// <param name="x">The x.</param>
/// <param name="buffer">The buffer.</param>
/// <param name="length">The length.</param>
/// <param name="offset">The offset.</param>
/// <returns>
/// The number of intersections found.
/// </returns>
public int ScanX(int x, float[] buffer, int length, int offset)
{
var start = new Vector2(x, this.Bounds.Top - 1);
var end = new Vector2(x, this.Bounds.Bottom + 1);
Vector2[] innerbuffer = ArrayPool<Vector2>.Shared.Rent(length);
try
{
int count = this.shape.FindIntersections(
start,
end,
innerbuffer,
length,
0);
for (var i = 0; i < count; i++)
{
buffer[i + offset] = innerbuffer[i].Y;
}
return count;
}
finally
{
ArrayPool<Vector2>.Shared.Return(innerbuffer);
}
}
/// <summary>
/// Scans the Y axis for intersections.
/// </summary>
/// <param name="y">The position along the y axis to find intersections.</param>
/// <param name="buffer">The buffer.</param>
/// <param name="length">The length.</param>
/// <param name="offset">The offset.</param>
/// <returns>
/// The number of intersections found.
/// </returns>
public int ScanY(int y, float[] buffer, int length, int offset)
{
var start = new Vector2(this.Bounds.Left - 1, y);
var end = new Vector2(this.Bounds.Right + 1, y);
Vector2[] innerbuffer = ArrayPool<Vector2>.Shared.Rent(length);
try
{
int count = this.shape.FindIntersections(
start,
end,
innerbuffer,
length,
0);
for (var i = 0; i < count; i++)
{
buffer[i + offset] = innerbuffer[i].X;
}
return count;
}
finally
{
ArrayPool<Vector2>.Shared.Return(innerbuffer);
}
}
/// <summary>
/// Gets the point information for the specified x and y location.
/// </summary>
/// <param name="x">The x.</param>
/// <param name="y">The y.</param>
/// <returns>Information about the the point</returns>
public PointInfo GetPointInfo(int x, int y)
{
var point = new Vector2(x, y);
SixLabors.Shapes.PointInfo result = default(SixLabors.Shapes.PointInfo);
float distance = float.MaxValue;
for (int i = 0; i < this.paths.Length; i++)
{
var p = this.paths[i].Distance(point);
if (p.DistanceFromPath < distance)
{
distance = p.DistanceFromPath;
result = p;
}
}
return result.Convert();
}
}
}

8
src/ImageSharp.Drawing/project.json

@ -39,14 +39,12 @@
}, },
"dependencies": { "dependencies": {
"ImageSharp": { "ImageSharp": {
"target": "project", "target": "project"
"version": "1.0.0-*"
}, },
"ImageSharp.Processing": { "ImageSharp.Processing": {
"target": "project", "target": "project"
"version": "1.0.0-*"
}, },
"SixLabors.Shapes": "0.1.0-ci0043", "SixLabors.Shapes": "0.1.0-ci0047",
"StyleCop.Analyzers": { "StyleCop.Analyzers": {
"version": "1.0.0", "version": "1.0.0",
"type": "build" "type": "build"

3
tests/ImageSharp.Sandbox46/ImageSharp.Sandbox46.csproj

@ -179,6 +179,9 @@
<Reference Include="ImageSharp.Drawing"> <Reference Include="ImageSharp.Drawing">
<HintPath>..\..\src\ImageSharp.Drawing\bin\$(Configuration)\net461\ImageSharp.Drawing.dll</HintPath> <HintPath>..\..\src\ImageSharp.Drawing\bin\$(Configuration)\net461\ImageSharp.Drawing.dll</HintPath>
</Reference> </Reference>
<Reference Include="SixLabors.Shapes">
<HintPath>..\..\src\ImageSharp.Drawing\bin\$(Configuration)\net461\SixLabors.Shapes.dll</HintPath>
</Reference>
<Reference Include="ImageSharp.Formats.Bmp"> <Reference Include="ImageSharp.Formats.Bmp">
<HintPath>..\..\src\ImageSharp.Formats.Bmp\bin\$(Configuration)\net461\ImageSharp.Formats.Bmp.dll</HintPath> <HintPath>..\..\src\ImageSharp.Formats.Bmp\bin\$(Configuration)\net461\ImageSharp.Formats.Bmp.dll</HintPath>
</Reference> </Reference>

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

@ -7,12 +7,13 @@ namespace ImageSharp.Tests.Drawing
{ {
using Drawing; using Drawing;
using ImageSharp.Drawing; using ImageSharp.Drawing;
using CorePath = ImageSharp.Drawing.Paths.Path; using CorePath = SixLabors.Shapes.Path;
using ImageSharp.Drawing.Paths; using SixLabors.Shapes;
using System; using System;
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
using System.IO; using System.IO;
using System.Numerics; using System.Numerics;
using Xunit; using Xunit;
public class DrawPathTests : FileTestBase public class DrawPathTests : FileTestBase

71
tests/ImageSharp.Tests/Drawing/Helpers/BezierPolygon.cs

@ -0,0 +1,71 @@
// <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 SixLabors.Shapes
{
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Numerics;
using SixLabors.Shapes;
public class BezierPolygon : IShape
{
private Polygon polygon;
public BezierPolygon(params Vector2[] points)
{
this.polygon = new Polygon(new BezierLineSegment(points));
}
public float Distance(Vector2 point)
{
return this.polygon.Distance(point);
}
public bool Contains(Vector2 point)
{
return this.polygon.Contains(point);
}
public int FindIntersections(Vector2 start, Vector2 end, Vector2[] buffer, int count, int offset)
{
return this.polygon.FindIntersections(start, end, buffer, count, offset);
}
public IEnumerable<Vector2> FindIntersections(Vector2 start, Vector2 end)
{
return this.polygon.FindIntersections(start, end);
}
public IShape Transform(Matrix3x2 matrix)
{
return ((IShape)this.polygon).Transform(matrix);
}
public Rectangle Bounds
{
get
{
return this.polygon.Bounds;
}
}
public ImmutableArray<IPath> Paths
{
get
{
return this.polygon.Paths;
}
}
public int MaxIntersections
{
get
{
return this.polygon.MaxIntersections;
}
}
}
}

71
tests/ImageSharp.Tests/Drawing/Helpers/LinearPolygon.cs

@ -0,0 +1,71 @@
// <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 SixLabors.Shapes
{
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Numerics;
using SixLabors.Shapes;
public class LinearPolygon : IShape
{
private Polygon polygon;
public LinearPolygon(params Vector2[] points)
{
this.polygon = new Polygon(new LinearLineSegment(points));
}
public float Distance(Vector2 point)
{
return this.polygon.Distance(point);
}
public bool Contains(Vector2 point)
{
return this.polygon.Contains(point);
}
public int FindIntersections(Vector2 start, Vector2 end, Vector2[] buffer, int count, int offset)
{
return this.polygon.FindIntersections(start, end, buffer, count, offset);
}
public IEnumerable<Vector2> FindIntersections(Vector2 start, Vector2 end)
{
return this.polygon.FindIntersections(start, end);
}
public IShape Transform(Matrix3x2 matrix)
{
return ((IShape)this.polygon).Transform(matrix);
}
public Rectangle Bounds
{
get
{
return this.polygon.Bounds;
}
}
public ImmutableArray<IPath> Paths
{
get
{
return this.polygon.Paths;
}
}
public int MaxIntersections
{
get
{
return this.polygon.MaxIntersections;
}
}
}
}

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

@ -7,26 +7,29 @@ namespace ImageSharp.Tests.Drawing
{ {
using System.IO; using System.IO;
using Xunit; using Xunit;
using Drawing;
using ImageSharp.Drawing;
using System.Numerics; using System.Numerics;
using ImageSharp.Drawing.Shapes;
using ImageSharp.Drawing.Pens; using ImageSharp.Drawing.Pens;
using SixLabors.Shapes;
public class LineComplexPolygonTests : FileTestBase public class LineComplexPolygonTests : FileTestBase
{ {
[Fact] [Fact]
public void ImageShouldBeOverlayedByPolygonOutline() public void ImageShouldBeOverlayedByPolygonOutline()
{ {
string path = this.CreateOutputDirectory("Drawing", "LineComplexPolygon"); string path = this.CreateOutputDirectory("Drawing", "LineComplexPolygon");
LinearPolygon simplePath = new LinearPolygon(
var simplePath = new Polygon(new LinearLineSegment(
new Vector2(10, 10), new Vector2(10, 10),
new Vector2(200, 150), new Vector2(200, 150),
new Vector2(50, 300)); new Vector2(50, 300)));
LinearPolygon hole1 = new LinearPolygon( var hole1 = new Polygon(new LinearLineSegment(
new Vector2(37, 85), new Vector2(37, 85),
new Vector2(93, 85), new Vector2(93, 85),
new Vector2(65, 137)); new Vector2(65, 137)));
using (Image image = new Image(500, 500)) using (Image image = new Image(500, 500))
{ {
@ -34,8 +37,8 @@ namespace ImageSharp.Tests.Drawing
{ {
image image
.BackgroundColor(Color.Blue) .BackgroundColor(Color.Blue)
.DrawPolygon(Color.HotPink, 5, new ComplexPolygon(simplePath, hole1)) .DrawPolygon(Color.HotPink, 5, new ComplexPolygon(simplePath, hole1))
.Save(output); .Save(output);
} }
using (PixelAccessor<Color> sourcePixels = image.Lock()) using (PixelAccessor<Color> sourcePixels = image.Lock())
@ -128,6 +131,7 @@ namespace ImageSharp.Tests.Drawing
new Vector2(37, 85), new Vector2(37, 85),
new Vector2(130, 40), new Vector2(130, 40),
new Vector2(65, 137)); new Vector2(65, 137));
var clipped = simplePath.Clip(hole1);
using (Image image = new Image(500, 500)) using (Image image = new Image(500, 500))
{ {
@ -135,7 +139,7 @@ namespace ImageSharp.Tests.Drawing
{ {
image image
.BackgroundColor(Color.Blue) .BackgroundColor(Color.Blue)
.DrawPolygon(Color.HotPink, 5, new ComplexPolygon(simplePath, hole1)) .DrawPolygon(Color.HotPink, 5, clipped)
.Save(output); .Save(output);
} }

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

@ -9,6 +9,9 @@ namespace ImageSharp.Tests.Drawing
using System.IO; using System.IO;
using System.Numerics; using System.Numerics;
using SixLabors.Shapes;
using Xunit; using Xunit;
public class SolidBezierTests : FileTestBase public class SolidBezierTests : FileTestBase

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

@ -7,9 +7,11 @@ namespace ImageSharp.Tests.Drawing
{ {
using System.IO; using System.IO;
using Xunit; using Xunit;
using Drawing;
using ImageSharp.Drawing;
using System.Numerics; using System.Numerics;
using ImageSharp.Drawing.Shapes;
using SixLabors.Shapes;
public class SolidComplexPolygonTests : FileTestBase public class SolidComplexPolygonTests : FileTestBase
{ {
@ -33,7 +35,7 @@ namespace ImageSharp.Tests.Drawing
{ {
image image
.BackgroundColor(Color.Blue) .BackgroundColor(Color.Blue)
.Fill(Color.HotPink, new ComplexPolygon(simplePath, hole1)) .Fill(Color.HotPink, simplePath.Clip(hole1))
.Save(output); .Save(output);
} }
@ -76,7 +78,7 @@ namespace ImageSharp.Tests.Drawing
{ {
image image
.BackgroundColor(Color.Blue) .BackgroundColor(Color.Blue)
.Fill(Color.HotPink, new ComplexPolygon(simplePath, hole1)) .Fill(Color.HotPink, simplePath.Clip(hole1))
.Save(output); .Save(output);
} }
@ -119,8 +121,8 @@ namespace ImageSharp.Tests.Drawing
{ {
image image
.BackgroundColor(Color.Blue) .BackgroundColor(Color.Blue)
.Fill(color, new ComplexPolygon(simplePath, hole1)) .Fill(color, simplePath.Clip(hole1))
.Save(output); .Save(output);
} }
//shift background color towards forground color by the opacity amount //shift background color towards forground color by the opacity amount
@ -144,4 +146,4 @@ namespace ImageSharp.Tests.Drawing
} }
} }
} }
} }

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

@ -5,8 +5,10 @@
namespace ImageSharp.Tests.Drawing namespace ImageSharp.Tests.Drawing
{ {
using Drawing;
using ImageSharp.Drawing; using ImageSharp.Drawing;
using System;
using System.Diagnostics.CodeAnalysis;
using System.IO; using System.IO;
using System.Numerics; using System.Numerics;
using Xunit; using Xunit;
@ -142,14 +144,15 @@ namespace ImageSharp.Tests.Drawing
public void ImageShouldBeOverlayedByFilledRectangle() public void ImageShouldBeOverlayedByFilledRectangle()
{ {
string path = this.CreateOutputDirectory("Drawing", "FilledPolygons"); string path = this.CreateOutputDirectory("Drawing", "FilledPolygons");
using (Image image = new Image(500, 500)) using (Image image = new Image(500, 500))
{ {
using (FileStream output = File.OpenWrite($"{path}/Rectangle.png")) using (FileStream output = File.OpenWrite($"{path}/Rectangle.png"))
{ {
image image
.BackgroundColor(Color.Blue) .BackgroundColor(Color.Blue)
.Fill(Color.HotPink, new ImageSharp.Drawing.Shapes.RectangularPolygon(new Rectangle(10, 10, 190, 140))) .Fill(Color.HotPink, new SixLabors.Shapes.Rectangle(10,10, 190, 140))
.Save(output); .Save(output);
} }
using (PixelAccessor<Color> sourcePixels = image.Lock()) using (PixelAccessor<Color> sourcePixels = image.Lock())

Loading…
Cancel
Save