Browse Source

replace outlineing logic with path generation.

af/merge-core
Scott Williams 9 years ago
parent
commit
4ab9228508
  1. 110
      src/ImageSharp.Drawing/DrawPath.cs
  2. 51
      src/ImageSharp.Drawing/Drawable.cs
  3. 2
      src/ImageSharp.Drawing/Paths/DrawPath.cs
  4. 90
      src/ImageSharp.Drawing/Paths/ShapePath.cs
  5. 8
      src/ImageSharp.Drawing/Paths/ShapeRegion.cs
  6. 31
      src/ImageSharp.Drawing/Pens/IPen.cs
  7. 192
      src/ImageSharp.Drawing/Pens/Pen{TPixel}.cs
  8. 140
      src/ImageSharp.Drawing/Processors/DrawPathProcessor.cs
  9. 15
      src/ImageSharp.Drawing/Processors/FillRegionProcessor.cs
  10. 8
      src/ImageSharp.Drawing/Region.cs
  11. 54
      tests/ImageSharp.Tests/Drawing/FillRegionProcessorTests.cs
  12. 163
      tests/ImageSharp.Tests/Drawing/Paths/DrawBeziersTests.cs
  13. 160
      tests/ImageSharp.Tests/Drawing/Paths/DrawLinesTests.cs
  14. 149
      tests/ImageSharp.Tests/Drawing/Paths/DrawPath.cs
  15. 161
      tests/ImageSharp.Tests/Drawing/Paths/DrawPolygon.cs
  16. 177
      tests/ImageSharp.Tests/Drawing/Paths/DrawRectangle.cs
  17. 105
      tests/ImageSharp.Tests/Drawing/Paths/ShapePathTests.cs
  18. 4
      tests/ImageSharp.Tests/Drawing/Paths/ShapeRegionTests.cs
  19. 12
      tests/ImageSharp.Tests/Drawing/Text/DrawText.cs

110
src/ImageSharp.Drawing/DrawPath.cs

@ -1,110 +0,0 @@
// <copyright file="DrawPath.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.Pens;
using Drawing.Processors;
using ImageSharp.PixelFormats;
/// <summary>
/// Extension methods for the <see cref="Image{TPixel}"/> type.
/// </summary>
public static partial class ImageExtensions
{
/// <summary>
/// Draws the outline of the region with the provided pen.
/// </summary>
/// <typeparam name="TPixel">The type of the color.</typeparam>
/// <param name="source">The image this method extends.</param>
/// <param name="pen">The pen.</param>
/// <param name="path">The path.</param>
/// <param name="options">The options.</param>
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
public static Image<TPixel> Draw<TPixel>(this Image<TPixel> source, IPen<TPixel> pen, Drawable path, GraphicsOptions options)
where TPixel : struct, IPixel<TPixel>
{
return source.Apply(new DrawPathProcessor<TPixel>(pen, path, options));
}
/// <summary>
/// Draws the outline of the polygon with the provided pen.
/// </summary>
/// <typeparam name="TPixel">The type of the color.</typeparam>
/// <param name="source">The image this method extends.</param>
/// <param name="pen">The pen.</param>
/// <param name="path">The path.</param>
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
public static Image<TPixel> Draw<TPixel>(this Image<TPixel> source, IPen<TPixel> pen, Drawable path)
where TPixel : struct, IPixel<TPixel>
{
return source.Draw(pen, path, GraphicsOptions.Default);
}
/// <summary>
/// Draws the outline of the polygon with the provided brush at the provided thickness.
/// </summary>
/// <typeparam name="TPixel">The type of the color.</typeparam>
/// <param name="source">The image this method extends.</param>
/// <param name="brush">The brush.</param>
/// <param name="thickness">The thickness.</param>
/// <param name="path">The path.</param>
/// <param name="options">The options.</param>
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
public static Image<TPixel> Draw<TPixel>(this Image<TPixel> source, IBrush<TPixel> brush, float thickness, Drawable path, GraphicsOptions options)
where TPixel : struct, IPixel<TPixel>
{
return source.Draw(new Pen<TPixel>(brush, thickness), path, options);
}
/// <summary>
/// Draws the outline of the polygon with the provided brush at the provided thickness.
/// </summary>
/// <typeparam name="TPixel">The type of the color.</typeparam>
/// <param name="source">The image this method extends.</param>
/// <param name="brush">The brush.</param>
/// <param name="thickness">The thickness.</param>
/// <param name="path">The path.</param>
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
public static Image<TPixel> Draw<TPixel>(this Image<TPixel> source, IBrush<TPixel> brush, float thickness, Drawable path)
where TPixel : struct, IPixel<TPixel>
{
return source.Draw(new Pen<TPixel>(brush, thickness), path);
}
/// <summary>
/// Draws the outline of the polygon with the provided brush at the provided thickness.
/// </summary>
/// <typeparam name="TPixel">The type of the color.</typeparam>
/// <param name="source">The image this method extends.</param>
/// <param name="color">The color.</param>
/// <param name="thickness">The thickness.</param>
/// <param name="path">The path.</param>
/// <param name="options">The options.</param>
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
public static Image<TPixel> Draw<TPixel>(this Image<TPixel> source, TPixel color, float thickness, Drawable path, GraphicsOptions options)
where TPixel : struct, IPixel<TPixel>
{
return source.Draw(new SolidBrush<TPixel>(color), thickness, path, options);
}
/// <summary>
/// Draws the outline of the polygon with the provided brush at the provided thickness.
/// </summary>
/// <typeparam name="TPixel">The type of the color.</typeparam>
/// <param name="source">The image this method extends.</param>
/// <param name="color">The color.</param>
/// <param name="thickness">The thickness.</param>
/// <param name="path">The path.</param>
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
public static Image<TPixel> Draw<TPixel>(this Image<TPixel> source, TPixel color, float thickness, Drawable path)
where TPixel : struct, IPixel<TPixel>
{
return source.Draw(new SolidBrush<TPixel>(color), thickness, path);
}
}
}

51
src/ImageSharp.Drawing/Drawable.cs

@ -1,51 +0,0 @@
// <copyright file="Drawable.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp.Drawing
{
/// <summary>
/// Represents a path or set of paths that can be drawn as an outline.
/// </summary>
public abstract class Drawable
{
/// <summary>
/// Gets the maximum number of intersections to could be returned.
/// </summary>
public abstract int MaxIntersections { get; }
/// <summary>
/// Gets the bounds.
/// </summary>
public abstract Rectangle Bounds { get; }
/// <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>
public abstract PointInfo GetPointInfo(int x, int y);
/// <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 abstract 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>
public abstract int ScanY(int y, float[] buffer, int length, int offset);
}
}

2
src/ImageSharp.Drawing/Paths/DrawPath.cs

@ -28,7 +28,7 @@ namespace ImageSharp
public static Image<TPixel> Draw<TPixel>(this Image<TPixel> source, IPen<TPixel> pen, IPath path, GraphicsOptions options)
where TPixel : struct, IPixel<TPixel>
{
return source.Draw(pen, new ShapePath(path), options);
return source.Fill(pen.StokeFill, new ShapePath(path, pen), options);
}
/// <summary>

90
src/ImageSharp.Drawing/Paths/ShapePath.cs

@ -1,12 +1,12 @@
// <copyright file="ShapePath.cs" company="James Jackson-South">
// <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;
using System.Buffers;
using System.Collections.Immutable;
using System.Numerics;
using SixLabors.Shapes;
@ -14,91 +14,19 @@ namespace ImageSharp.Drawing
using Rectangle = ImageSharp.Rectangle;
/// <summary>
/// A drawable mapping between a <see cref="IPath"/> and a drawable region.
/// A mapping between a <see cref="IPath"/> and a region.
/// </summary>
internal class ShapePath : Drawable
internal class ShapePath : ShapeRegion
{
/// <summary>
/// Initializes a new instance of the <see cref="ShapePath"/> class.
/// </summary>
/// <param name="path">The path.</param>
public ShapePath(IPath path)
/// <param name="shape">The shape.</param>
/// <param name="pen">The pen to apply to the shape.</param>
// SixLabors.shape willbe moving to a Span/ReadOnlySpan based API shortly use ToArray for now.
public ShapePath(IPath shape, Pens.IPen pen)
: base(shape.GenerateOutline(pen.StrokeWidth, pen.StrokePattern.ToArray()))
{
this.Path = path;
this.Bounds = path.Bounds.Convert();
}
/// <summary>
/// Gets the fillable shape
/// </summary>
public IPath Path { get; }
/// <inheritdoc/>
public override int MaxIntersections => this.Path.MaxIntersections;
/// <inheritdoc/>
public override Rectangle Bounds { get; }
/// <inheritdoc/>
public override int ScanX(int x, float[] buffer, int length, int offset)
{
Vector2 start = new Vector2(x, this.Bounds.Top - 1);
Vector2 end = new Vector2(x, this.Bounds.Bottom + 1);
Vector2[] innerbuffer = ArrayPool<Vector2>.Shared.Rent(length);
try
{
int count = this.Path.FindIntersections(start, end, innerbuffer, length, 0);
for (int i = 0; i < count; i++)
{
buffer[i + offset] = innerbuffer[i].Y;
}
return count;
}
finally
{
ArrayPool<Vector2>.Shared.Return(innerbuffer);
}
}
/// <inheritdoc/>
public override int ScanY(int y, float[] buffer, int length, int offset)
{
Vector2 start = new Vector2(this.Bounds.Left - 1, y);
Vector2 end = new Vector2(this.Bounds.Right + 1, y);
Vector2[] innerbuffer = ArrayPool<Vector2>.Shared.Rent(length);
try
{
int count = this.Path.FindIntersections(start, end, innerbuffer, length, 0);
for (int i = 0; i < count; i++)
{
buffer[i + offset] = innerbuffer[i].X;
}
return count;
}
finally
{
ArrayPool<Vector2>.Shared.Return(innerbuffer);
}
}
/// <inheritdoc/>
public override PointInfo GetPointInfo(int x, int y)
{
Vector2 point = new Vector2(x, y);
SixLabors.Shapes.PointInfo dist = this.Path.Distance(point);
return new PointInfo
{
DistanceAlongPath = dist.DistanceAlongPath,
DistanceFromPath =
dist.DistanceFromPath < 0
? -dist.DistanceFromPath
: dist.DistanceFromPath
};
}
}
}

8
src/ImageSharp.Drawing/Paths/ShapeRegion.cs

@ -40,18 +40,18 @@ namespace ImageSharp.Drawing
public override Rectangle Bounds { get; }
/// <inheritdoc/>
public override int Scan(float y, float[] buffer, int length, int offset)
public override int Scan(float y, Span<float> buffer)
{
Vector2 start = new Vector2(this.Bounds.Left - 1, y);
Vector2 end = new Vector2(this.Bounds.Right + 1, y);
Vector2[] innerbuffer = ArrayPool<Vector2>.Shared.Rent(length);
Vector2[] innerbuffer = ArrayPool<Vector2>.Shared.Rent(buffer.Length);
try
{
int count = this.Shape.FindIntersections(start, end, innerbuffer, length, 0);
int count = this.Shape.FindIntersections(start, end, innerbuffer, buffer.Length, 0);
for (int i = 0; i < count; i++)
{
buffer[i + offset] = innerbuffer[i].X;
buffer[i] = innerbuffer[i].X;
}
return count;

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

@ -12,21 +12,28 @@ namespace ImageSharp.Drawing.Pens
/// Interface representing a Pen
/// </summary>
/// <typeparam name="TPixel">The type of the color.</typeparam>
public interface IPen<TPixel>
public interface IPen<TPixel> : IPen
where TPixel : struct, IPixel<TPixel>
{
/// <summary>
/// Creates the applicator for applying this pen to an Image
/// Gets the stoke fill.
/// </summary>
/// <param name="source">The source image.</param>
/// <param name="region">The region the pen will be applied to.</param>
/// <param name="options">The currently active graphic options.</param>
/// <returns>
/// Returns a the applicator for the pen.
/// </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 shape of the whole image.
/// </remarks>
PenApplicator<TPixel> CreateApplicator(ImageBase<TPixel> source, RectangleF region, GraphicsOptions options);
IBrush<TPixel> StokeFill { get; }
}
/// <summary>
/// Iterface represting the pattern and size of the stroke to apply with a Pen.
/// </summary>
public interface IPen
{
/// <summary>
/// Gets the width to apply to the stroke
/// </summary>
float StrokeWidth { get; }
/// <summary>
/// Gets the stoke pattern.
/// </summary>
System.ReadOnlySpan<float> StrokePattern { get; }
}
}

192
src/ImageSharp.Drawing/Pens/Pen{TPixel}.cs

@ -5,6 +5,7 @@
namespace ImageSharp.Drawing.Pens
{
using System;
using System.Numerics;
using ImageSharp.Drawing.Brushes;
@ -48,8 +49,8 @@ namespace ImageSharp.Drawing.Pens
/// <param name="pattern">The pattern.</param>
public Pen(IBrush<TPixel> brush, float width, float[] pattern)
{
this.Brush = brush;
this.Width = width;
this.StokeFill = brush;
this.StrokeWidth = width;
this.pattern = pattern;
}
@ -78,190 +79,17 @@ namespace ImageSharp.Drawing.Pens
/// </summary>
/// <param name="pen">The pen.</param>
internal Pen(Pen<TPixel> pen)
: this(pen.Brush, pen.Width, pen.pattern)
: this(pen.StokeFill, pen.StrokeWidth, pen.pattern)
{
}
/// <summary>
/// Gets the brush.
/// </summary>
/// <value>
/// The brush.
/// </value>
public IBrush<TPixel> 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="source">The source image.</param>
/// <param name="region">The region the pen will be applied to.</param>
/// <param name="options">The Graphics options</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 PenApplicator<TPixel> CreateApplicator(ImageBase<TPixel> source, RectangleF region, GraphicsOptions options)
{
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(source, this.Brush, region, this.Width, options);
}
return new PatternPenApplicator(source, this.Brush, region, this.Width, this.pattern, options);
}
private class SolidPenApplicator : PenApplicator<TPixel>
{
private readonly BrushApplicator<TPixel> brush;
private readonly float halfWidth;
public SolidPenApplicator(ImageBase<TPixel> sourcePixels, IBrush<TPixel> brush, RectangleF region, float width, GraphicsOptions options)
{
this.brush = brush.CreateApplicator(sourcePixels, region, options);
this.halfWidth = width / 2;
this.RequiredRegion = RectangleF.Inflate(region, width, width);
}
public override RectangleF RequiredRegion
{
get;
}
public override void Dispose()
{
this.brush.Dispose();
}
public override ColoredPointInfo<TPixel> GetColor(int x, int y, PointInfo info)
{
var result = default(ColoredPointInfo<TPixel>);
result.Color = this.brush[x, y];
if (info.DistanceFromPath < this.halfWidth)
{
// inside strip
result.DistanceFromElement = 0;
}
else
{
result.DistanceFromElement = info.DistanceFromPath - this.halfWidth;
}
return result;
}
}
private class PatternPenApplicator : PenApplicator<TPixel>
{
private readonly BrushApplicator<TPixel> brush;
private readonly float halfWidth;
private readonly float[] pattern;
private readonly float totalLength;
public PatternPenApplicator(ImageBase<TPixel> source, IBrush<TPixel> brush, RectangleF region, float width, float[] pattern, GraphicsOptions options)
{
this.brush = brush.CreateApplicator(source, region, options);
this.halfWidth = width / 2;
this.totalLength = 0;
this.pattern = new float[pattern.Length + 1];
this.pattern[0] = 0;
for (int i = 0; i < pattern.Length; i++)
{
this.totalLength += pattern[i] * width;
this.pattern[i + 1] = this.totalLength;
}
this.RequiredRegion = RectangleF.Inflate(region, width, width);
}
public override RectangleF RequiredRegion
{
get;
}
public override void Dispose()
{
this.brush.Dispose();
}
public override ColoredPointInfo<TPixel> GetColor(int x, int y, PointInfo info)
{
var infoResult = default(ColoredPointInfo<TPixel>);
infoResult.DistanceFromElement = float.MaxValue; // is really outside the element
float length = info.DistanceAlongPath % this.totalLength;
/// <inheritdoc/>
public IBrush<TPixel> StokeFill { get; }
// 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<TPixel> along with the color.
infoResult.Color = this.brush[x, y];
/// <inheritdoc/>
public float StrokeWidth { get; }
float distanceWAway = 0;
if (info.DistanceFromPath < this.halfWidth)
{
// inside strip
distanceWAway = 0;
}
else
{
distanceWAway = info.DistanceFromPath - this.halfWidth;
}
for (int i = 0; i < this.pattern.Length - 1; i++)
{
float start = this.pattern[i];
float 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
float distanceFromStart = length - start;
float distanceFromEnd = end - length;
float closestEdge = MathF.Min(distanceFromStart, distanceFromEnd);
float distanceAcross = closestEdge;
if (distanceWAway > 0)
{
infoResult.DistanceFromElement = new Vector2(distanceAcross, distanceWAway).Length();
}
else
{
infoResult.DistanceFromElement = closestEdge;
}
return infoResult;
}
}
}
return infoResult;
}
}
/// <inheritdoc/>
public ReadOnlySpan<float> StrokePattern => this.pattern;
}
}

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

@ -1,140 +0,0 @@
// <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.Threading.Tasks;
using ImageSharp.Memory;
using ImageSharp.PixelFormats;
using ImageSharp.Processing;
using Pens;
/// <summary>
/// Draws a path using the processor pipeline
/// </summary>
/// <typeparam name="TPixel">The type of the color.</typeparam>
/// <seealso cref="ImageSharp.Processing.ImageProcessor{TPixel}" />
internal class DrawPathProcessor<TPixel> : ImageProcessor<TPixel>
where TPixel : struct, IPixel<TPixel>
{
private const float AntialiasFactor = 1f;
private const int PaddingFactor = 1; // needs to been the same or greater than AntialiasFactor
/// <summary>
/// Initializes a new instance of the <see cref="DrawPathProcessor{TPixel}" /> class.
/// </summary>
/// <param name="pen">The details how to draw the outline/path.</param>
/// <param name="drawable">The details of the paths and outlines to draw.</param>
/// <param name="options">The drawing configuration options.</param>
public DrawPathProcessor(IPen<TPixel> pen, Drawable drawable, GraphicsOptions options)
{
this.Path = drawable;
this.Pen = pen;
this.Options = options;
}
/// <summary>
/// Gets the graphics options.
/// </summary>
public GraphicsOptions Options { get; }
/// <summary>
/// Gets the pen.
/// </summary>
public IPen<TPixel> Pen { get; }
/// <summary>
/// Gets the path.
/// </summary>
public Drawable Path { get; }
/// <inheritdoc/>
protected override void OnApply(ImageBase<TPixel> source, Rectangle sourceRectangle)
{
using (PenApplicator<TPixel> applicator = this.Pen.CreateApplicator(source, this.Path.Bounds, this.Options))
{
var rect = Rectangle.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;
}
int width = maxX - minX;
PixelBlender<TPixel> blender = PixelOperations<TPixel>.Instance.GetPixelBlender(this.Options.BlenderMode);
Parallel.For(
minY,
maxY,
this.ParallelOptions,
y =>
{
int offsetY = y - polyStartY;
using (var amount = new Buffer<float>(width))
using (var colors = new Buffer<TPixel>(width))
{
for (int i = 0; i < width; i++)
{
int x = i + minX;
int offsetX = x - startX;
PointInfo info = this.Path.GetPointInfo(offsetX, offsetY);
ColoredPointInfo<TPixel> color = applicator.GetColor(offsetX, offsetY, info);
amount[i] = (this.Opacity(color.DistanceFromElement) * this.Options.BlendPercentage).Clamp(0, 1);
colors[i] = color.Color;
}
Span<TPixel> destination = source.GetRowSpan(offsetY).Slice(minX - startX, width);
blender.Blend(destination, destination, colors, amount);
}
});
}
}
/// <summary>
/// Returns the correct opacity for the given distance.
/// </summary>
/// <param name="distance">Thw distance from the central point.</param>
/// <returns>The <see cref="float"/></returns>
private float Opacity(float distance)
{
if (distance <= 0)
{
return 1;
}
if (this.Options.Antialias && distance < AntialiasFactor)
{
return 1 - (distance / AntialiasFactor);
}
return 0;
}
}
}

15
src/ImageSharp.Drawing/Processors/FillRegionProcessor.cs

@ -93,6 +93,7 @@ namespace ImageSharp.Drawing.Processors
using (BrushApplicator<TPixel> applicator = this.Brush.CreateApplicator(source, rect, this.Options))
{
float[] buffer = arrayPool.Rent(maxIntersections);
Span<float> bufferSpan = buffer.AsSpan().Slice(0, maxIntersections);
int scanlineWidth = maxX - minX;
using (var scanline = new Buffer<float>(scanlineWidth))
{
@ -116,14 +117,14 @@ namespace ImageSharp.Drawing.Processors
float subpixelFractionPoint = subpixelFraction / subpixelCount;
for (float subPixel = (float)y; subPixel < y + 1; subPixel += subpixelFraction)
{
int pointsFound = region.Scan(subPixel, buffer, maxIntersections, 0);
int pointsFound = region.Scan(subPixel, bufferSpan);
if (pointsFound == 0)
{
// nothing on this line skip
continue;
}
QuickSort(buffer, pointsFound);
QuickSort(bufferSpan.Slice(0, pointsFound));
for (int point = 0; point < pointsFound; point += 2)
{
@ -194,20 +195,20 @@ namespace ImageSharp.Drawing.Processors
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void Swap(float[] data, int left, int right)
private static void Swap(Span<float> data, int left, int right)
{
float tmp = data[left];
data[left] = data[right];
data[right] = tmp;
}
private static void QuickSort(float[] data, int size)
private static void QuickSort(Span<float> data)
{
int hi = Math.Min(data.Length - 1, size - 1);
int hi = Math.Min(data.Length - 1, data.Length - 1);
QuickSort(data, 0, hi);
}
private static void QuickSort(float[] data, int lo, int hi)
private static void QuickSort(Span<float> data, int lo, int hi)
{
if (lo < hi)
{
@ -217,7 +218,7 @@ namespace ImageSharp.Drawing.Processors
}
}
private static int Partition(float[] data, int lo, int hi)
private static int Partition(Span<float> data, int lo, int hi)
{
float pivot = data[lo];
int i = lo - 1;

8
src/ImageSharp.Drawing/Region.cs

@ -5,6 +5,8 @@
namespace ImageSharp.Drawing
{
using System;
/// <summary>
/// Represents a region of an image.
/// </summary>
@ -19,7 +21,7 @@ namespace ImageSharp.Drawing
/// Gets the bounding box that entirely surrounds this region.
/// </summary>
/// <remarks>
/// This should always contains all possible points returned from <see cref="Scan(float, float[], int, int)"/>.
/// This should always contains all possible points returned from <see cref="Scan(float, Span{float})"/>.
/// </remarks>
public abstract Rectangle Bounds { get; }
@ -28,9 +30,7 @@ namespace ImageSharp.Drawing
/// </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 abstract int Scan(float y, float[] buffer, int length, int offset);
public abstract int Scan(float y, Span<float> buffer);
}
}

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

@ -8,6 +8,9 @@ namespace ImageSharp.Tests.Drawing
using Moq;
using ImageSharp.PixelFormats;
using System;
using ImageSharp.Drawing.Pens;
using System.Numerics;
public class FillRegionProcessorTests
{
@ -26,14 +29,61 @@ namespace ImageSharp.Tests.Drawing
Mock<Region> region = new Mock<Region>();
region.Setup(x => x.Bounds).Returns(bounds);
GraphicsOptions options = new GraphicsOptions(antialias) {
GraphicsOptions options = new GraphicsOptions(antialias)
{
AntialiasSubpixelDepth = 1
};
FillRegionProcessor<Rgba32> processor = new FillRegionProcessor<Rgba32>(brush.Object, region.Object, options);
Image<Rgba32> img = new Image<Rgba32>(1, 1);
processor.Apply(img, bounds);
region.Verify(x => x.Scan(It.IsAny<float>(), It.IsAny<float[]>(), It.IsAny<int>(), It.IsAny<int>()), Times.Exactly(4));
region.Verify(x => x.Scan(It.IsAny<float>(), It.IsAny<Span<float>>()), Times.Exactly(4));
}
[Fact]
public void FillOffCanvas()
{
ImageSharp.Rectangle bounds = new ImageSharp.Rectangle(-100, -10, 10, 10);
Mock<IBrush<Rgba32>> brush = new Mock<IBrush<Rgba32>>();
Mock<Region> region = new Mock<Region>();
region.Setup(x => x.Bounds).Returns(bounds);
region.Setup(x => x.MaxIntersections).Returns(10);
region.Setup(x => x.Scan(It.IsAny<float>(), It.IsAny<Span<float>>()))
.Returns<float, Span<float>>((y, span) =>
{
if (y < 5)
{
span[0] = -10f;
span[1] = 100f;
return 2;
}
return 0;
});
GraphicsOptions options = new GraphicsOptions(true)
{
};
FillRegionProcessor<Rgba32> processor = new FillRegionProcessor<Rgba32>(brush.Object, region.Object, options);
Image<Rgba32> img = new Image<Rgba32>(10, 10);
processor.Apply(img, bounds);
}
[Fact]
public void DrawOffCanvas()
{
using (var img = new Image<Rgba32>(10, 10))
{
img.DrawLines(new Pen<Rgba32>(Rgba32.Black, 10), new Vector2[] {
new Vector2(-10, 5),
new Vector2(20, 5),
});
}
}
}
}

163
tests/ImageSharp.Tests/Drawing/Paths/DrawBeziersTests.cs

@ -1,163 +0,0 @@

namespace ImageSharp.Tests.Drawing.Paths
{
using System;
using ImageSharp.Drawing.Brushes;
using Xunit;
using ImageSharp.Drawing;
using System.Numerics;
using SixLabors.Shapes;
using ImageSharp.Drawing.Processors;
using ImageSharp.Drawing.Pens;
using ImageSharp.PixelFormats;
public class DrawBeziersTests : IDisposable
{
float thickness = 7.2f;
GraphicsOptions noneDefault = new GraphicsOptions();
Rgba32 color = Rgba32.HotPink;
SolidBrush<Rgba32> brush = Brushes.Solid(Rgba32.HotPink);
Pen<Rgba32> pen = new Pen<Rgba32>(Rgba32.Firebrick, 99.9f);
Vector2[] points = new Vector2[] {
new Vector2(10,10),
new Vector2(20,10),
new Vector2(20,10),
new Vector2(30,10),
};
private ProcessorWatchingImage img;
public DrawBeziersTests()
{
this.img = new Paths.ProcessorWatchingImage(10, 10);
}
public void Dispose()
{
img.Dispose();
}
[Fact]
public void CorrectlySetsBrushThicknessAndPoints()
{
img.DrawBeziers(brush, thickness, points);
Assert.NotEmpty(img.ProcessorApplications);
DrawPathProcessor<Rgba32> processor = Assert.IsType<DrawPathProcessor<Rgba32>>(img.ProcessorApplications[0].processor);
Assert.Equal(GraphicsOptions.Default, processor.Options);
ShapePath path = Assert.IsType<ShapePath>(processor.Path);
Assert.NotNull(path.Path);
SixLabors.Shapes.Path vector = Assert.IsType<SixLabors.Shapes.Path>(path.Path);
BezierLineSegment segment = Assert.IsType<BezierLineSegment>(vector.LineSegments[0]);
Pen<Rgba32> pen = Assert.IsType<Pen<Rgba32>>(processor.Pen);
Assert.Equal(brush, pen.Brush);
Assert.Equal(thickness, pen.Width);
}
[Fact]
public void CorrectlySetsBrushThicknessPointsAndOptions()
{
img.DrawBeziers(brush, thickness, points, noneDefault);
Assert.NotEmpty(img.ProcessorApplications);
DrawPathProcessor<Rgba32> processor = Assert.IsType<DrawPathProcessor<Rgba32>>(img.ProcessorApplications[0].processor);
Assert.Equal(noneDefault, processor.Options);
ShapePath path = Assert.IsType<ShapePath>(processor.Path);
SixLabors.Shapes.Path vector = Assert.IsType<SixLabors.Shapes.Path>(path.Path);
BezierLineSegment segment = Assert.IsType<BezierLineSegment>(vector.LineSegments[0]);
Pen<Rgba32> pen = Assert.IsType<Pen<Rgba32>>(processor.Pen);
Assert.Equal(brush, pen.Brush);
Assert.Equal(thickness, pen.Width);
}
[Fact]
public void CorrectlySetsColorThicknessAndPoints()
{
img.DrawBeziers(color, thickness, points);
Assert.NotEmpty(img.ProcessorApplications);
DrawPathProcessor<Rgba32> processor = Assert.IsType<DrawPathProcessor<Rgba32>>(img.ProcessorApplications[0].processor);
Assert.Equal(GraphicsOptions.Default, processor.Options);
ShapePath path = Assert.IsType<ShapePath>(processor.Path);
SixLabors.Shapes.Path vector = Assert.IsType<SixLabors.Shapes.Path>(path.Path);
BezierLineSegment segment = Assert.IsType<BezierLineSegment>(vector.LineSegments[0]);
Pen<Rgba32> pen = Assert.IsType<Pen<Rgba32>>(processor.Pen);
Assert.Equal(thickness, pen.Width);
SolidBrush<Rgba32> brush = Assert.IsType<SolidBrush<Rgba32>>(pen.Brush);
Assert.Equal(color, brush.Color);
}
[Fact]
public void CorrectlySetsColorThicknessPointsAndOptions()
{
img.DrawBeziers(color, thickness, points, noneDefault);
Assert.NotEmpty(img.ProcessorApplications);
DrawPathProcessor<Rgba32> processor = Assert.IsType<DrawPathProcessor<Rgba32>>(img.ProcessorApplications[0].processor);
Assert.Equal(noneDefault, processor.Options);
ShapePath path = Assert.IsType<ShapePath>(processor.Path);
SixLabors.Shapes.Path vector = Assert.IsType<SixLabors.Shapes.Path>(path.Path);
BezierLineSegment segment = Assert.IsType<BezierLineSegment>(vector.LineSegments[0]);
Pen<Rgba32> pen = Assert.IsType<Pen<Rgba32>>(processor.Pen);
Assert.Equal(thickness, pen.Width);
SolidBrush<Rgba32> brush = Assert.IsType<SolidBrush<Rgba32>>(pen.Brush);
Assert.Equal(color, brush.Color);
}
[Fact]
public void CorrectlySetsPenAndPoints()
{
img.DrawBeziers(pen, points);
Assert.NotEmpty(img.ProcessorApplications);
DrawPathProcessor<Rgba32> processor = Assert.IsType<DrawPathProcessor<Rgba32>>(img.ProcessorApplications[0].processor);
Assert.Equal(GraphicsOptions.Default, processor.Options);
ShapePath path = Assert.IsType<ShapePath>(processor.Path);
SixLabors.Shapes.Path vector = Assert.IsType<SixLabors.Shapes.Path>(path.Path);
BezierLineSegment segment = Assert.IsType<BezierLineSegment>(vector.LineSegments[0]);
Assert.Equal(pen, processor.Pen);
}
[Fact]
public void CorrectlySetsPenPointsAndOptions()
{
img.DrawBeziers(pen, points, noneDefault);
Assert.NotEmpty(img.ProcessorApplications);
DrawPathProcessor<Rgba32> processor = Assert.IsType<DrawPathProcessor<Rgba32>>(img.ProcessorApplications[0].processor);
Assert.Equal(noneDefault, processor.Options);
ShapePath path = Assert.IsType<ShapePath>(processor.Path);
SixLabors.Shapes.Path vector = Assert.IsType<SixLabors.Shapes.Path>(path.Path);
BezierLineSegment segment = Assert.IsType<BezierLineSegment>(vector.LineSegments[0]);
Assert.Equal(pen, processor.Pen);
}
}
}

160
tests/ImageSharp.Tests/Drawing/Paths/DrawLinesTests.cs

@ -1,160 +0,0 @@

namespace ImageSharp.Tests.Drawing.Paths
{
using System;
using ImageSharp.Drawing.Brushes;
using Xunit;
using ImageSharp.Drawing;
using System.Numerics;
using SixLabors.Shapes;
using ImageSharp.Drawing.Processors;
using ImageSharp.Drawing.Pens;
using ImageSharp.PixelFormats;
public class DrawLinesTests : IDisposable
{
float thickness = 7.2f;
GraphicsOptions noneDefault = new GraphicsOptions();
Rgba32 color = Rgba32.HotPink;
SolidBrush<Rgba32> brush = Brushes.Solid(Rgba32.HotPink);
Pen<Rgba32> pen = new Pen<Rgba32>(Rgba32.Gray, 99.9f);
Vector2[] points = new Vector2[] {
new Vector2(10,10),
new Vector2(20,10),
new Vector2(20,10),
new Vector2(30,10),
};
private ProcessorWatchingImage img;
public DrawLinesTests()
{
this.img = new Paths.ProcessorWatchingImage(10, 10);
}
public void Dispose()
{
img.Dispose();
}
[Fact]
public void CorrectlySetsBrushThicknessAndPoints()
{
img.DrawLines(brush, thickness, points);
Assert.NotEmpty(img.ProcessorApplications);
DrawPathProcessor<Rgba32> processor = Assert.IsType<DrawPathProcessor<Rgba32>>(img.ProcessorApplications[0].processor);
Assert.Equal(GraphicsOptions.Default, processor.Options);
ShapePath path = Assert.IsType<ShapePath>(processor.Path);
SixLabors.Shapes.Path vector = Assert.IsType<SixLabors.Shapes.Path>(path.Path);
LinearLineSegment segment = Assert.IsType<LinearLineSegment>(vector.LineSegments[0]);
Pen<Rgba32> pen = Assert.IsType<Pen<Rgba32>>(processor.Pen);
Assert.Equal(brush, pen.Brush);
Assert.Equal(thickness, pen.Width);
}
[Fact]
public void CorrectlySetsBrushThicknessPointsAndOptions()
{
img.DrawLines(brush, thickness, points, noneDefault);
Assert.NotEmpty(img.ProcessorApplications);
DrawPathProcessor<Rgba32> processor = Assert.IsType<DrawPathProcessor<Rgba32>>(img.ProcessorApplications[0].processor);
Assert.Equal(noneDefault, processor.Options);
ShapePath path = Assert.IsType<ShapePath>(processor.Path);
SixLabors.Shapes.Path vector = Assert.IsType<SixLabors.Shapes.Path>(path.Path);
LinearLineSegment segment = Assert.IsType<LinearLineSegment>(vector.LineSegments[0]);
Pen<Rgba32> pen = Assert.IsType<Pen<Rgba32>>(processor.Pen);
Assert.Equal(brush, pen.Brush);
Assert.Equal(thickness, pen.Width);
}
[Fact]
public void CorrectlySetsColorThicknessAndPoints()
{
img.DrawLines(color, thickness, points);
Assert.NotEmpty(img.ProcessorApplications);
DrawPathProcessor<Rgba32> processor = Assert.IsType<DrawPathProcessor<Rgba32>>(img.ProcessorApplications[0].processor);
Assert.Equal(GraphicsOptions.Default, processor.Options);
ShapePath path = Assert.IsType<ShapePath>(processor.Path);
SixLabors.Shapes.Path vector = Assert.IsType<SixLabors.Shapes.Path>(path.Path);
LinearLineSegment segment = Assert.IsType<LinearLineSegment>(vector.LineSegments[0]);
Pen<Rgba32> pen = Assert.IsType<Pen<Rgba32>>(processor.Pen);
Assert.Equal(thickness, pen.Width);
SolidBrush<Rgba32> brush = Assert.IsType<SolidBrush<Rgba32>>(pen.Brush);
Assert.Equal(color, brush.Color);
}
[Fact]
public void CorrectlySetsColorThicknessPointsAndOptions()
{
img.DrawLines(color, thickness, points, noneDefault);
Assert.NotEmpty(img.ProcessorApplications);
DrawPathProcessor<Rgba32> processor = Assert.IsType<DrawPathProcessor<Rgba32>>(img.ProcessorApplications[0].processor);
Assert.Equal(noneDefault, processor.Options);
ShapePath path = Assert.IsType<ShapePath>(processor.Path);
SixLabors.Shapes.Path vector = Assert.IsType<SixLabors.Shapes.Path>(path.Path);
LinearLineSegment segment = Assert.IsType<LinearLineSegment>(vector.LineSegments[0]);
Pen<Rgba32> pen = Assert.IsType<Pen<Rgba32>>(processor.Pen);
Assert.Equal(thickness, pen.Width);
SolidBrush<Rgba32> brush = Assert.IsType<SolidBrush<Rgba32>>(pen.Brush);
Assert.Equal(color, brush.Color);
}
[Fact]
public void CorrectlySetsPenAndPoints()
{
img.DrawLines(pen, points);
Assert.NotEmpty(img.ProcessorApplications);
DrawPathProcessor<Rgba32> processor = Assert.IsType<DrawPathProcessor<Rgba32>>(img.ProcessorApplications[0].processor);
Assert.Equal(GraphicsOptions.Default, processor.Options);
ShapePath path = Assert.IsType<ShapePath>(processor.Path);
SixLabors.Shapes.Path vector = Assert.IsType<SixLabors.Shapes.Path>(path.Path);
LinearLineSegment segment = Assert.IsType<LinearLineSegment>(vector.LineSegments[0]);
Assert.Equal(pen, processor.Pen);
}
[Fact]
public void CorrectlySetsPenPointsAndOptions()
{
img.DrawLines(pen, points, noneDefault);
Assert.NotEmpty(img.ProcessorApplications);
DrawPathProcessor<Rgba32> processor = Assert.IsType<DrawPathProcessor<Rgba32>>(img.ProcessorApplications[0].processor);
Assert.Equal(noneDefault, processor.Options);
ShapePath path = Assert.IsType<ShapePath>(processor.Path);
SixLabors.Shapes.Path vector = Assert.IsType<SixLabors.Shapes.Path>(path.Path);
LinearLineSegment segment = Assert.IsType<LinearLineSegment>(vector.LineSegments[0]);
Assert.Equal(pen, processor.Pen);
}
}
}

149
tests/ImageSharp.Tests/Drawing/Paths/DrawPath.cs

@ -1,149 +0,0 @@

namespace ImageSharp.Tests.Drawing.Paths
{
using System;
using ImageSharp.Drawing.Brushes;
using Xunit;
using ImageSharp.Drawing;
using System.Numerics;
using SixLabors.Shapes;
using ImageSharp.Drawing.Processors;
using ImageSharp.Drawing.Pens;
using ImageSharp.PixelFormats;
public class DrawPath : IDisposable
{
float thickness = 7.2f;
GraphicsOptions noneDefault = new GraphicsOptions();
Rgba32 color = Rgba32.HotPink;
SolidBrush<Rgba32> brush = Brushes.Solid(Rgba32.HotPink);
Pen<Rgba32> pen = new Pen<Rgba32>(Rgba32.Gray, 99.9f);
IPath path = new SixLabors.Shapes.Path(new LinearLineSegment(new Vector2[] {
new Vector2(10,10),
new Vector2(20,10),
new Vector2(20,10),
new Vector2(30,10),
}));
private ProcessorWatchingImage img;
public DrawPath()
{
this.img = new Paths.ProcessorWatchingImage(10, 10);
}
public void Dispose()
{
img.Dispose();
}
[Fact]
public void CorrectlySetsBrushThicknessAndPath()
{
img.Draw(brush, thickness, path);
Assert.NotEmpty(img.ProcessorApplications);
DrawPathProcessor<Rgba32> processor = Assert.IsType<DrawPathProcessor<Rgba32>>(img.ProcessorApplications[0].processor);
Assert.Equal(GraphicsOptions.Default, processor.Options);
ShapePath shapepath = Assert.IsType<ShapePath>(processor.Path);
Assert.Equal(path, shapepath.Path);
Pen<Rgba32> pen = Assert.IsType<Pen<Rgba32>>(processor.Pen);
Assert.Equal(brush, pen.Brush);
Assert.Equal(thickness, pen.Width);
}
[Fact]
public void CorrectlySetsBrushThicknessPathAndOptions()
{
img.Draw(brush, thickness, path, noneDefault);
Assert.NotEmpty(img.ProcessorApplications);
DrawPathProcessor<Rgba32> processor = Assert.IsType<DrawPathProcessor<Rgba32>>(img.ProcessorApplications[0].processor);
Assert.Equal(noneDefault, processor.Options);
ShapePath shapepath = Assert.IsType<ShapePath>(processor.Path);
Assert.Equal(path, shapepath.Path);
Pen<Rgba32> pen = Assert.IsType<Pen<Rgba32>>(processor.Pen);
Assert.Equal(brush, pen.Brush);
Assert.Equal(thickness, pen.Width);
}
[Fact]
public void CorrectlySetsColorThicknessAndPath()
{
img.Draw(color, thickness, path);
Assert.NotEmpty(img.ProcessorApplications);
DrawPathProcessor<Rgba32> processor = Assert.IsType<DrawPathProcessor<Rgba32>>(img.ProcessorApplications[0].processor);
Assert.Equal(GraphicsOptions.Default, processor.Options);
ShapePath shapepath = Assert.IsType<ShapePath>(processor.Path);
Assert.Equal(path, shapepath.Path);
Pen<Rgba32> pen = Assert.IsType<Pen<Rgba32>>(processor.Pen);
Assert.Equal(thickness, pen.Width);
SolidBrush<Rgba32> brush = Assert.IsType<SolidBrush<Rgba32>>(pen.Brush);
Assert.Equal(color, brush.Color);
}
[Fact]
public void CorrectlySetsColorThicknessPathAndOptions()
{
img.Draw(color, thickness, path, noneDefault);
Assert.NotEmpty(img.ProcessorApplications);
DrawPathProcessor<Rgba32> processor = Assert.IsType<DrawPathProcessor<Rgba32>>(img.ProcessorApplications[0].processor);
Assert.Equal(noneDefault, processor.Options);
ShapePath shapepath = Assert.IsType<ShapePath>(processor.Path);
Assert.Equal(path, shapepath.Path);
Pen<Rgba32> pen = Assert.IsType<Pen<Rgba32>>(processor.Pen);
Assert.Equal(thickness, pen.Width);
SolidBrush<Rgba32> brush = Assert.IsType<SolidBrush<Rgba32>>(pen.Brush);
Assert.Equal(color, brush.Color);
}
[Fact]
public void CorrectlySetsPenAndPath()
{
img.Draw(pen, path);
Assert.NotEmpty(img.ProcessorApplications);
DrawPathProcessor<Rgba32> processor = Assert.IsType<DrawPathProcessor<Rgba32>>(img.ProcessorApplications[0].processor);
Assert.Equal(GraphicsOptions.Default, processor.Options);
ShapePath shapepath = Assert.IsType<ShapePath>(processor.Path);
Assert.Equal(path, shapepath.Path);
Assert.Equal(pen, processor.Pen);
}
[Fact]
public void CorrectlySetsPenPathAndOptions()
{
img.Draw(pen, path, noneDefault);
Assert.NotEmpty(img.ProcessorApplications);
DrawPathProcessor<Rgba32> processor = Assert.IsType<DrawPathProcessor<Rgba32>>(img.ProcessorApplications[0].processor);
Assert.Equal(noneDefault, processor.Options);
ShapePath shapepath = Assert.IsType<ShapePath>(processor.Path);
Assert.Equal(path, shapepath.Path);
Assert.Equal(pen, processor.Pen);
}
}
}

161
tests/ImageSharp.Tests/Drawing/Paths/DrawPolygon.cs

@ -1,161 +0,0 @@

namespace ImageSharp.Tests.Drawing.Paths
{
using System;
using ImageSharp.Drawing.Brushes;
using Xunit;
using ImageSharp.Drawing;
using System.Numerics;
using SixLabors.Shapes;
using ImageSharp.Drawing.Processors;
using ImageSharp.Drawing.Pens;
using ImageSharp.PixelFormats;
public class DrawPolygon : IDisposable
{
float thickness = 7.2f;
GraphicsOptions noneDefault = new GraphicsOptions();
Rgba32 color = Rgba32.HotPink;
SolidBrush<Rgba32> brush = Brushes.Solid(Rgba32.HotPink);
Pen<Rgba32> pen = new Pen<Rgba32>(Rgba32.Gray, 99.9f);
Vector2[] points = new Vector2[] {
new Vector2(10,10),
new Vector2(20,10),
new Vector2(20,10),
new Vector2(30,10),
};
private ProcessorWatchingImage img;
public DrawPolygon()
{
this.img = new Paths.ProcessorWatchingImage(10, 10);
}
public void Dispose()
{
img.Dispose();
}
[Fact]
public void CorrectlySetsBrushThicknessAndPoints()
{
img.DrawPolygon(brush, thickness, points);
Assert.NotEmpty(img.ProcessorApplications);
DrawPathProcessor<Rgba32> processor = Assert.IsType<DrawPathProcessor<Rgba32>>(img.ProcessorApplications[0].processor);
Assert.Equal(GraphicsOptions.Default, processor.Options);
ShapePath path = Assert.IsType<ShapePath>(processor.Path);
Polygon vector = Assert.IsType<SixLabors.Shapes.Polygon>(path.Path);
LinearLineSegment segment = Assert.IsType<LinearLineSegment>(vector.LineSegments[0]);
Pen<Rgba32> pen = Assert.IsType<Pen<Rgba32>>(processor.Pen);
Assert.Equal(brush, pen.Brush);
Assert.Equal(thickness, pen.Width);
}
[Fact]
public void CorrectlySetsBrushThicknessPointsAndOptions()
{
img.DrawPolygon(brush, thickness, points, noneDefault);
Assert.NotEmpty(img.ProcessorApplications);
DrawPathProcessor<Rgba32> processor = Assert.IsType<DrawPathProcessor<Rgba32>>(img.ProcessorApplications[0].processor);
Assert.Equal(noneDefault, processor.Options);
ShapePath path = Assert.IsType<ShapePath>(processor.Path);
Polygon vector = Assert.IsType<SixLabors.Shapes.Polygon>(path.Path);
LinearLineSegment segment = Assert.IsType<LinearLineSegment>(vector.LineSegments[0]);
Pen<Rgba32> pen = Assert.IsType<Pen<Rgba32>>(processor.Pen);
Assert.Equal(brush, pen.Brush);
Assert.Equal(thickness, pen.Width);
}
[Fact]
public void CorrectlySetsColorThicknessAndPoints()
{
img.DrawPolygon(color, thickness, points);
Assert.NotEmpty(img.ProcessorApplications);
DrawPathProcessor<Rgba32> processor = Assert.IsType<DrawPathProcessor<Rgba32>>(img.ProcessorApplications[0].processor);
Assert.Equal(GraphicsOptions.Default, processor.Options);
ShapePath path = Assert.IsType<ShapePath>(processor.Path);
Polygon vector = Assert.IsType<SixLabors.Shapes.Polygon>(path.Path);
LinearLineSegment segment = Assert.IsType<LinearLineSegment>(vector.LineSegments[0]);
Pen<Rgba32> pen = Assert.IsType<Pen<Rgba32>>(processor.Pen);
Assert.Equal(thickness, pen.Width);
SolidBrush<Rgba32> brush = Assert.IsType<SolidBrush<Rgba32>>(pen.Brush);
Assert.Equal(color, brush.Color);
}
[Fact]
public void CorrectlySetsColorThicknessPointsAndOptions()
{
img.DrawPolygon(color, thickness, points, noneDefault);
Assert.NotEmpty(img.ProcessorApplications);
DrawPathProcessor<Rgba32> processor = Assert.IsType<DrawPathProcessor<Rgba32>>(img.ProcessorApplications[0].processor);
Assert.Equal(noneDefault, processor.Options);
ShapePath path = Assert.IsType<ShapePath>(processor.Path);
Polygon vector = Assert.IsType<SixLabors.Shapes.Polygon>(path.Path);
LinearLineSegment segment = Assert.IsType<LinearLineSegment>(vector.LineSegments[0]);
Pen<Rgba32> pen = Assert.IsType<Pen<Rgba32>>(processor.Pen);
Assert.Equal(thickness, pen.Width);
SolidBrush<Rgba32> brush = Assert.IsType<SolidBrush<Rgba32>>(pen.Brush);
Assert.Equal(color, brush.Color);
}
[Fact]
public void CorrectlySetsPenAndPoints()
{
img.DrawPolygon(pen, points);
Assert.NotEmpty(img.ProcessorApplications);
DrawPathProcessor<Rgba32> processor = Assert.IsType<DrawPathProcessor<Rgba32>>(img.ProcessorApplications[0].processor);
Assert.Equal(GraphicsOptions.Default, processor.Options);
ShapePath path = Assert.IsType<ShapePath>(processor.Path);
Polygon vector = Assert.IsType<SixLabors.Shapes.Polygon>(path.Path);
LinearLineSegment segment = Assert.IsType<LinearLineSegment>(vector.LineSegments[0]);
Assert.Equal(pen, processor.Pen);
}
[Fact]
public void CorrectlySetsPenPointsAndOptions()
{
img.DrawPolygon(pen, points, noneDefault);
Assert.NotEmpty(img.ProcessorApplications);
DrawPathProcessor<Rgba32> processor = Assert.IsType<DrawPathProcessor<Rgba32>>(img.ProcessorApplications[0].processor);
Assert.Equal(noneDefault, processor.Options);
ShapePath path = Assert.IsType<ShapePath>(processor.Path);
Polygon vector = Assert.IsType<SixLabors.Shapes.Polygon>(path.Path);
LinearLineSegment segment = Assert.IsType<LinearLineSegment>(vector.LineSegments[0]);
Assert.Equal(pen, processor.Pen);
}
}
}

177
tests/ImageSharp.Tests/Drawing/Paths/DrawRectangle.cs

@ -1,177 +0,0 @@

namespace ImageSharp.Tests.Drawing.Paths
{
using System;
using ImageSharp;
using ImageSharp.Drawing.Brushes;
using Xunit;
using ImageSharp.Drawing;
using ImageSharp.Drawing.Processors;
using ImageSharp.Drawing.Pens;
using ImageSharp.PixelFormats;
public class DrawRectangle : IDisposable
{
float thickness = 7.2f;
GraphicsOptions noneDefault = new GraphicsOptions();
Rgba32 color = Rgba32.HotPink;
SolidBrush<Rgba32> brush = Brushes.Solid(Rgba32.HotPink);
Pen<Rgba32> pen = new Pen<Rgba32>(Rgba32.Gray, 99.9f);
ImageSharp.Rectangle rectangle = new ImageSharp.Rectangle(10, 10, 98, 324);
private ProcessorWatchingImage img;
public DrawRectangle()
{
this.img = new Paths.ProcessorWatchingImage(10, 10);
}
public void Dispose()
{
img.Dispose();
}
[Fact]
public void CorrectlySetsBrushThicknessAndRectangle()
{
img.Draw(brush, thickness, rectangle);
Assert.NotEmpty(img.ProcessorApplications);
DrawPathProcessor<Rgba32> processor = Assert.IsType<DrawPathProcessor<Rgba32>>(img.ProcessorApplications[0].processor);
Assert.Equal(GraphicsOptions.Default, processor.Options);
ShapePath shapepath = Assert.IsType<ShapePath>(processor.Path);
SixLabors.Shapes.Rectangle rect = Assert.IsType<SixLabors.Shapes.Rectangle>(shapepath.Path);
Assert.Equal(rect.Location.X, rectangle.X);
Assert.Equal(rect.Location.Y, rectangle.Y);
Assert.Equal(rect.Size.Width, rectangle.Width);
Assert.Equal(rect.Size.Height, rectangle.Height);
Pen<Rgba32> pen = Assert.IsType<Pen<Rgba32>>(processor.Pen);
Assert.Equal(brush, pen.Brush);
Assert.Equal(thickness, pen.Width);
}
[Fact]
public void CorrectlySetsBrushThicknessRectangleAndOptions()
{
img.Draw(brush, thickness, rectangle, noneDefault);
Assert.NotEmpty(img.ProcessorApplications);
DrawPathProcessor<Rgba32> processor = Assert.IsType<DrawPathProcessor<Rgba32>>(img.ProcessorApplications[0].processor);
Assert.Equal(noneDefault, processor.Options);
ShapePath shapepath = Assert.IsType<ShapePath>(processor.Path);
SixLabors.Shapes.Rectangle rect = Assert.IsType<SixLabors.Shapes.Rectangle>(shapepath.Path);
Assert.Equal(rect.Location.X, rectangle.X);
Assert.Equal(rect.Location.Y, rectangle.Y);
Assert.Equal(rect.Size.Width, rectangle.Width);
Assert.Equal(rect.Size.Height, rectangle.Height);
Pen<Rgba32> pen = Assert.IsType<Pen<Rgba32>>(processor.Pen);
Assert.Equal(brush, pen.Brush);
Assert.Equal(thickness, pen.Width);
}
[Fact]
public void CorrectlySetsColorThicknessAndRectangle()
{
img.Draw(color, thickness, rectangle);
Assert.NotEmpty(img.ProcessorApplications);
DrawPathProcessor<Rgba32> processor = Assert.IsType<DrawPathProcessor<Rgba32>>(img.ProcessorApplications[0].processor);
Assert.Equal(GraphicsOptions.Default, processor.Options);
ShapePath shapepath = Assert.IsType<ShapePath>(processor.Path);
SixLabors.Shapes.Rectangle rect = Assert.IsType<SixLabors.Shapes.Rectangle>(shapepath.Path);
Assert.Equal(rect.Location.X, rectangle.X);
Assert.Equal(rect.Location.Y, rectangle.Y);
Assert.Equal(rect.Size.Width, rectangle.Width);
Assert.Equal(rect.Size.Height, rectangle.Height);
Pen<Rgba32> pen = Assert.IsType<Pen<Rgba32>>(processor.Pen);
Assert.Equal(thickness, pen.Width);
SolidBrush<Rgba32> brush = Assert.IsType<SolidBrush<Rgba32>>(pen.Brush);
Assert.Equal(color, brush.Color);
}
[Fact]
public void CorrectlySetsColorThicknessRectangleAndOptions()
{
img.Draw(color, thickness, rectangle, noneDefault);
Assert.NotEmpty(img.ProcessorApplications);
DrawPathProcessor<Rgba32> processor = Assert.IsType<DrawPathProcessor<Rgba32>>(img.ProcessorApplications[0].processor);
Assert.Equal(noneDefault, processor.Options);
ShapePath shapepath = Assert.IsType<ShapePath>(processor.Path);
SixLabors.Shapes.Rectangle rect = Assert.IsType<SixLabors.Shapes.Rectangle>(shapepath.Path);
Assert.Equal(rect.Location.X, rectangle.X);
Assert.Equal(rect.Location.Y, rectangle.Y);
Assert.Equal(rect.Size.Width, rectangle.Width);
Assert.Equal(rect.Size.Height, rectangle.Height);
Pen<Rgba32> pen = Assert.IsType<Pen<Rgba32>>(processor.Pen);
Assert.Equal(thickness, pen.Width);
SolidBrush<Rgba32> brush = Assert.IsType<SolidBrush<Rgba32>>(pen.Brush);
Assert.Equal(color, brush.Color);
}
[Fact]
public void CorrectlySetsPenAndRectangle()
{
img.Draw(pen, rectangle);
Assert.NotEmpty(img.ProcessorApplications);
DrawPathProcessor<Rgba32> processor = Assert.IsType<DrawPathProcessor<Rgba32>>(img.ProcessorApplications[0].processor);
Assert.Equal(GraphicsOptions.Default, processor.Options);
ShapePath shapepath = Assert.IsType<ShapePath>(processor.Path);
SixLabors.Shapes.Rectangle rect = Assert.IsType<SixLabors.Shapes.Rectangle>(shapepath.Path);
Assert.Equal(rect.Location.X, rectangle.X);
Assert.Equal(rect.Location.Y, rectangle.Y);
Assert.Equal(rect.Size.Width, rectangle.Width);
Assert.Equal(rect.Size.Height, rectangle.Height);
Assert.Equal(pen, processor.Pen);
}
[Fact]
public void CorrectlySetsPenRectangleAndOptions()
{
img.Draw(pen, rectangle, noneDefault);
Assert.NotEmpty(img.ProcessorApplications);
DrawPathProcessor<Rgba32> processor = Assert.IsType<DrawPathProcessor<Rgba32>>(img.ProcessorApplications[0].processor);
Assert.Equal(noneDefault, processor.Options);
ShapePath shapepath = Assert.IsType<ShapePath>(processor.Path);
SixLabors.Shapes.Rectangle rect = Assert.IsType<SixLabors.Shapes.Rectangle>(shapepath.Path);
Assert.Equal(rect.Location.X, rectangle.X);
Assert.Equal(rect.Location.Y, rectangle.Y);
Assert.Equal(rect.Size.Width, rectangle.Width);
Assert.Equal(rect.Size.Height, rectangle.Height);
Assert.Equal(pen, processor.Pen);
}
}
}

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

@ -18,109 +18,6 @@ namespace ImageSharp.Tests.Drawing.Paths
public class ShapePathTests
{
private readonly Mock<IPath> pathMock1;
private readonly Mock<IPath> pathMock2;
private readonly SixLabors.Shapes.Rectangle bounds1;
public ShapePathTests()
{
this.pathMock2 = new Mock<IPath>();
this.pathMock1 = new Mock<IPath>();
this.bounds1 = new SixLabors.Shapes.Rectangle(10.5f, 10, 10, 10);
pathMock1.Setup(x => x.Bounds).Returns(this.bounds1);
pathMock2.Setup(x => x.Bounds).Returns(this.bounds1);
// wire up the 2 mocks to reference eachother
pathMock1.Setup(x => x.AsClosedPath()).Returns(() => pathMock2.Object);
}
[Fact]
public void ShapePathFromPathConvertsBoundsDoesNotProxyToShape()
{
ShapePath region = new ShapePath(pathMock1.Object);
Assert.Equal(Math.Floor(bounds1.Left), region.Bounds.Left);
Assert.Equal(Math.Ceiling(bounds1.Right), region.Bounds.Right);
pathMock1.Verify(x => x.Bounds);
}
[Fact]
public void ShapePathFromPathMaxIntersectionsProxyToShape()
{
ShapePath region = new ShapePath(pathMock1.Object);
int i = region.MaxIntersections;
pathMock1.Verify(x => x.MaxIntersections);
}
[Fact]
public void ShapePathFromPathScanXProxyToShape()
{
int xToScan = 10;
ShapePath region = new ShapePath(pathMock1.Object);
pathMock1.Setup(x => x.FindIntersections(It.IsAny<Vector2>(), It.IsAny<Vector2>(), It.IsAny<Vector2[]>(), It.IsAny<int>(), It.IsAny<int>()))
.Callback<Vector2, Vector2, Vector2[], int, int>((s, e, b, c, o) => {
Assert.Equal(xToScan, s.X);
Assert.Equal(xToScan, e.X);
Assert.True(s.Y < bounds1.Top);
Assert.True(e.Y > bounds1.Bottom);
}).Returns(0);
int i = region.ScanX(xToScan, new float[0], 0, 0);
pathMock1.Verify(x => x.FindIntersections(It.IsAny<Vector2>(), It.IsAny<Vector2>(), It.IsAny<Vector2[]>(), It.IsAny<int>(), It.IsAny<int>()), Times.Once);
}
[Fact]
public void ShapePathFromPathScanYProxyToShape()
{
int yToScan = 10;
ShapePath region = new ShapePath(pathMock1.Object);
pathMock1.Setup(x => x.FindIntersections(It.IsAny<Vector2>(), It.IsAny<Vector2>(), It.IsAny<Vector2[]>(), It.IsAny<int>(), It.IsAny<int>()))
.Callback<Vector2, Vector2, Vector2[], int, int>((s, e, b, c, o) => {
Assert.Equal(yToScan, s.Y);
Assert.Equal(yToScan, e.Y);
Assert.True(s.X < bounds1.Left);
Assert.True(e.X > bounds1.Right);
}).Returns(0);
int i = region.ScanY(yToScan, new float[0], 0, 0);
pathMock1.Verify(x => x.FindIntersections(It.IsAny<Vector2>(), It.IsAny<Vector2>(), It.IsAny<Vector2[]>(), It.IsAny<int>(), It.IsAny<int>()), Times.Once);
}
[Fact]
public void ShapePathFromShapeScanXProxyToShape()
{
int xToScan = 10;
ShapePath region = new ShapePath(pathMock1.Object);
pathMock1.Setup(x => x.FindIntersections(It.IsAny<Vector2>(), It.IsAny<Vector2>(), It.IsAny<Vector2[]>(), It.IsAny<int>(), It.IsAny<int>()))
.Callback<Vector2, Vector2, Vector2[], int, int>((s, e, b, c, o) => {
Assert.Equal(xToScan, s.X);
Assert.Equal(xToScan, e.X);
Assert.True(s.Y < bounds1.Top);
Assert.True(e.Y > bounds1.Bottom);
}).Returns(0);
int i = region.ScanX(xToScan, new float[0], 0, 0);
pathMock1.Verify(x => x.FindIntersections(It.IsAny<Vector2>(), It.IsAny<Vector2>(), It.IsAny<Vector2[]>(), It.IsAny<int>(), It.IsAny<int>()), Times.Once);
}
[Fact]
public void GetPointInfoCallSinglePathForPath()
{
ShapePath region = new ShapePath(pathMock1.Object);
ImageSharp.Drawing.PointInfo info = region.GetPointInfo(10, 1);
pathMock1.Verify(x => x.Distance(new Vector2(10, 1)), Times.Once);
pathMock2.Verify(x => x.Distance(new Vector2(10, 1)), Times.Never);
}
// TODO readd these back in
}
}

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

@ -81,7 +81,7 @@ namespace ImageSharp.Tests.Drawing.Paths
Assert.True(e.X > bounds.Right);
}).Returns(0);
int i = region.Scan(yToScan, new float[0], 0, 0);
int i = region.Scan(yToScan, new float[0]);
pathMock.Verify(x => x.FindIntersections(It.IsAny<Vector2>(), It.IsAny<Vector2>(), It.IsAny<Vector2[]>(), It.IsAny<int>(), It.IsAny<int>()), Times.Once);
}
@ -100,7 +100,7 @@ namespace ImageSharp.Tests.Drawing.Paths
Assert.True(e.X > bounds.Right);
}).Returns(0);
int i = region.Scan(yToScan, new float[0], 0, 0);
int i = region.Scan(yToScan, new float[0]);
pathMock.Verify(x => x.FindIntersections(It.IsAny<Vector2>(), It.IsAny<Vector2>(), It.IsAny<Vector2[]>(), It.IsAny<int>(), It.IsAny<int>()), Times.Once);
}

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

@ -136,7 +136,7 @@ namespace ImageSharp.Tests.Drawing.Text
Assert.NotEmpty(this.img.ProcessorApplications);
Assert.Equal(3, this.img.ProcessorApplications.Count); // 3 fills where applied
Assert.IsType<DrawPathProcessor<Rgba32>>(this.img.ProcessorApplications[0].processor);
Assert.IsType<FillRegionProcessor<Rgba32>>(this.img.ProcessorApplications[0].processor);
}
[Fact]
@ -146,7 +146,7 @@ namespace ImageSharp.Tests.Drawing.Text
Assert.NotEmpty(this.img.ProcessorApplications);
Assert.Equal(3, this.img.ProcessorApplications.Count); // 3 fills where applied
Assert.IsType<DrawPathProcessor<Rgba32>>(this.img.ProcessorApplications[0].processor);
Assert.IsType<FillRegionProcessor<Rgba32>>(this.img.ProcessorApplications[0].processor);
}
[Fact]
@ -156,7 +156,7 @@ namespace ImageSharp.Tests.Drawing.Text
Assert.NotEmpty(this.img.ProcessorApplications);
Assert.Equal(3, this.img.ProcessorApplications.Count); // 3 fills where applied
Assert.IsType<DrawPathProcessor<Rgba32>>(this.img.ProcessorApplications[0].processor);
Assert.IsType<FillRegionProcessor<Rgba32>>(this.img.ProcessorApplications[0].processor);
}
[Fact]
@ -166,7 +166,7 @@ namespace ImageSharp.Tests.Drawing.Text
Assert.NotEmpty(this.img.ProcessorApplications);
Assert.Equal(3, this.img.ProcessorApplications.Count); // 3 fills where applied
Assert.IsType<DrawPathProcessor<Rgba32>>(this.img.ProcessorApplications[0].processor);
Assert.IsType<FillRegionProcessor<Rgba32>>(this.img.ProcessorApplications[0].processor);
}
[Fact]
@ -207,7 +207,7 @@ namespace ImageSharp.Tests.Drawing.Text
Assert.NotEmpty(this.img.ProcessorApplications);
Assert.Equal(2, this.img.ProcessorApplications.Count);
Assert.IsType<FillRegionProcessor<Rgba32>>(this.img.ProcessorApplications[0].processor);
Assert.IsType<DrawPathProcessor<Rgba32>>(this.img.ProcessorApplications[1].processor);
Assert.IsType<FillRegionProcessor<Rgba32>>(this.img.ProcessorApplications[1].processor);
}
[Fact]
@ -218,7 +218,7 @@ namespace ImageSharp.Tests.Drawing.Text
Assert.NotEmpty(this.img.ProcessorApplications);
Assert.Equal(2, this.img.ProcessorApplications.Count);
Assert.IsType<FillRegionProcessor<Rgba32>>(this.img.ProcessorApplications[0].processor);
Assert.IsType<DrawPathProcessor<Rgba32>>(this.img.ProcessorApplications[1].processor);
Assert.IsType<FillRegionProcessor<Rgba32>>(this.img.ProcessorApplications[1].processor);
}
[Fact]

Loading…
Cancel
Save