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:
- dotnet restore
- dotnet build -c Release src/*/project.json
- dotnet test tests/ImageSharp.Tests/project.json -c Release -f "netcoreapp1.1"
- dotnet build -c Release src/*/project.csproj
- dotnet test tests/ImageSharp.Tests/project.csproj -c Release -f "netcoreapp1.1"
env:
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>
/// Gets the color for a single pixel.
/// </summary>
/// <param name="point">The point.</param>
/// <param name="x">The x.</param>
/// <param name="y">The y.</param>
/// <returns>
/// The color
/// </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)
point = point - this.offset;
int x = (int)point.X % this.xLength;
int y = (int)point.Y % this.yLength;
get
{
var point = new Vector2(x, y);
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 />

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

@ -134,17 +134,21 @@ namespace ImageSharp.Drawing.Brushes
/// <summary>
/// Gets the color for a single pixel.
/// </summary>
/// <param name="point">The point.</param>
/// </summary>#
/// <param name="x">The x.</param>
/// <param name="y">The y.</param>
/// <returns>
/// The color
/// The Color.
/// </returns>
public override TColor GetColor(Vector2 point)
public override TColor this[int x, int y]
{
int x = (int)point.X % this.xLength;
int y = (int)point.Y % this.stride;
get
{
x = x % this.xLength;
y = y % this.stride;
return this.pattern[x][y];
return this.pattern[x][y];
}
}
/// <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
where TColor : struct, IPackedPixel, IEquatable<TColor>
{
/// <summary>
/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
/// Gets the color for a single pixel.
/// </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>
/// Gets the color for a single pixel.
/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
/// </summary>
/// <param name="point">The point.</param>
/// <returns>The color</returns>
public abstract TColor GetColor(Vector2 point);
public abstract void Dispose();
}
}

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

@ -109,24 +109,31 @@ namespace ImageSharp.Drawing.Brushes
/// <summary>
/// Gets the color for a single pixel.
/// </summary>
/// <param name="point">The point.</param>
/// <param name="x">The x.</param>
/// <param name="y">The y.</param>
/// <returns>
/// The color
/// </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)
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)
get
{
var lerpAmount = (this.threshold - distance) / this.threshold;
Vector4 blended = Vector4BlendTransforms.PremultipliedLerp(background, this.targetColor, lerpAmount);
result.PackFromVector4(blended);
// Offset the requested pixel by the value in the rectangle (the shapes position)
TColor result = this.source[x, y];
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 />

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

@ -67,14 +67,12 @@ namespace ImageSharp.Drawing.Brushes
/// <summary>
/// Gets the color for a single pixel.
/// </summary>
/// <param name="point">The point.</param>
/// <param name="x">The x.</param>
/// <param name="y">The y.</param>
/// <returns>
/// The color
/// </returns>
public override TColor GetColor(Vector2 point)
{
return this.color;
}
public override TColor this[int x, int y] => this.color;
/// <inheritdoc />
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)
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>
@ -226,7 +226,7 @@ namespace ImageSharp
public static Image<TColor> DrawPath<TColor>(this Image<TColor> source, IPen<TColor> pen, IPath path, GraphicsOptions options)
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>
@ -240,7 +240,7 @@ namespace ImageSharp
public static Image<TColor> DrawPath<TColor>(this Image<TColor> source, IPen<TColor> pen, IPath path)
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>

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)
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>

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)
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>
@ -70,7 +70,7 @@ namespace ImageSharp
public static Image<TColor> Fill<TColor>(this Image<TColor> source, IBrush<TColor> brush, IShape shape)
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>

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)
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>
@ -44,7 +44,7 @@ namespace ImageSharp
public static Image<TColor> Fill<TColor>(this Image<TColor> source, IBrush<TColor> brush, RectangleF shape)
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>

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();
}
public override ColoredPointInfo<TColor> GetColor(PointInfo info)
public override ColoredPointInfo<TColor> GetColor(int x, int y, PointInfo info)
{
var result = default(ColoredPointInfo<TColor>);
result.Color = this.brush.GetColor(info.SearchPoint);
result.Color = this.brush[x, y];
if (info.DistanceFromPath < this.halfWidth)
{
@ -197,7 +197,7 @@ namespace ImageSharp.Drawing.Pens
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>);
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 need to calcualte the distance from the outside edge of the pattern
// 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;

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

@ -6,6 +6,7 @@
namespace ImageSharp.Drawing.Processors
{
using System;
using System.Numerics;
/// <summary>
/// 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>
/// Gets a <see cref="ColoredPointInfo{TColor}" /> from a point represented by a <see cref="PointInfo" />.
/// </summary>
/// <param name="x">The x.</param>
/// <param name="y">The y.</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>
public abstract ColoredPointInfo<TColor> GetColor(PointInfo info);
/// <returns>
/// 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.
// </copyright>
namespace ImageSharp.Drawing.Processors
namespace ImageSharp.Drawing
{
using System;
using System.Numerics;
@ -22,10 +22,5 @@ namespace ImageSharp.Drawing.Processors
/// The distance from path
/// </summary>
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 readonly IPen<TColor> pen;
private readonly IPath[] paths;
private readonly RectangleF region;
private readonly IDrawableRegion region;
private readonly GraphicsOptions options;
/// <summary>
/// Initializes a new instance of the <see cref="DrawPathProcessor{TColor}" /> class.
/// </summary>
/// <param name="pen">The pen.</param>
/// <param name="shape">The shape.</param>
/// <param name="region">The region.</param>
/// <param name="options">The options.</param>
public DrawPathProcessor(IPen<TColor> pen, IShape shape, GraphicsOptions options)
: this(pen, shape.Paths, options)
public DrawPathProcessor(IPen<TColor> pen, IDrawableRegion region, GraphicsOptions options)
{
}
/// <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.region = region;
this.pen = pen;
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/>
protected override void OnApply(ImageBase<TColor> source, Rectangle sourceRectangle)
{
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);
@ -122,16 +85,14 @@ namespace ImageSharp.Drawing.Processors
(int y) =>
{
int offsetY = y - polyStartY;
var currentPoint = default(Vector2);
for (int x = minX; x < maxX; x++)
{
// TODO add find intersections code to skip and scan large regions of this.
int offsetX = x - startX;
currentPoint.X = offsetX;
currentPoint.Y = offsetY;
var info = this.region.GetPointInfo(offsetX, offsetY);
var dist = this.Closest(currentPoint);
var color = applicator.GetColor(dist.Convert());
var color = applicator.GetColor(offsetX, offsetY, info);
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)
{
if (distance <= 0)

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

@ -71,17 +71,12 @@ namespace ImageSharp.Drawing.Processors
y =>
{
int offsetY = y - startY;
Vector2 currentPoint = default(Vector2);
for (int x = minX; x < maxX; x++)
{
int offsetX = x - startX;
int offsetColorX = x - minX;
currentPoint.X = offsetX;
currentPoint.Y = offsetY;
Vector4 backgroundVector = sourcePixels[offsetX, offsetY].ToVector4();
Vector4 sourceVector = applicator.GetColor(currentPoint).ToVector4();
Vector4 sourceVector = applicator[offsetX, offsetY].ToVector4();
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 int DrawPadding = 1;
private readonly IBrush<TColor> fillColor;
private readonly IShape poly;
private readonly IRegion region;
private readonly GraphicsOptions options;
/// <summary>
/// Initializes a new instance of the <see cref="FillShapeProcessor{TColor}"/> class.
/// Initializes a new instance of the <see cref="FillShapeProcessor{TColor}" /> class.
/// </summary>
/// <param name="brush">The brush.</param>
/// <param name="shape">The shape.</param>
/// <param name="region">The region.</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.options = options;
}
@ -44,12 +44,12 @@ namespace ImageSharp.Drawing.Processors
/// <inheritdoc/>
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 polyEndY = rect.Bottom + DrawPadding;
int startX = rect.X - DrawPadding;
int endX = rect.Right + DrawPadding;
int polyStartY = sourceRectangle.Y - DrawPadding;
int polyEndY = sourceRectangle.Bottom + DrawPadding;
int startX = sourceRectangle.X - DrawPadding;
int endX = sourceRectangle.Right + DrawPadding;
int minX = Math.Max(sourceRectangle.Left, startX);
int maxX = Math.Min(sourceRectangle.Right - 1, endX);
@ -62,9 +62,9 @@ namespace ImageSharp.Drawing.Processors
minY = Math.Max(0, minY);
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 (BrushApplicator<TColor> applicator = this.fillColor.CreateApplicator(sourcePixels, rect))
@ -73,27 +73,26 @@ namespace ImageSharp.Drawing.Processors
minY,
maxY,
this.ParallelOptions,
y =>
(int y) =>
{
Vector2[] buffer = arrayPool.Rent(maxIntersections);
float[] buffer = arrayPool.Rent(maxIntersections);
try
{
Vector2 left = new Vector2(startX, y);
Vector2 right = new Vector2(endX, y);
float right = endX;
// 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)
{
// nothign on this line skip
return;
}
QuickSortX(buffer, pointsFound);
QuickSort(buffer, pointsFound);
int currentIntersection = 0;
float nextPoint = buffer[0].X;
float nextPoint = buffer[0];
float lastPoint = float.MinValue;
bool isInside = false;
@ -108,7 +107,7 @@ namespace ImageSharp.Drawing.Processors
{
if (x < (nextPoint - DrawPadding) && x > (lastPoint + DrawPadding))
{
if (nextPoint == right.X)
if (nextPoint == right)
{
// we are in the ends run skip it
x = maxX;
@ -129,11 +128,11 @@ namespace ImageSharp.Drawing.Processors
lastPoint = nextPoint;
if (currentIntersection == pointsFound)
{
nextPoint = right.X;
nextPoint = right;
}
else
{
nextPoint = buffer[currentIntersection].X;
nextPoint = buffer[currentIntersection];
// double point from a corner flip the bit back and move on again
if (nextPoint == lastPoint)
@ -143,11 +142,11 @@ namespace ImageSharp.Drawing.Processors
currentIntersection++;
if (currentIntersection == pointsFound)
{
nextPoint = right.X;
nextPoint = right;
}
else
{
nextPoint = buffer[currentIntersection].X;
nextPoint = buffer[currentIntersection];
}
}
}
@ -192,7 +191,7 @@ namespace ImageSharp.Drawing.Processors
if (opacity > Constants.Epsilon)
{
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);
finalColor.W = backgroundVector.W;
@ -216,42 +215,37 @@ namespace ImageSharp.Drawing.Processors
minX,
maxX,
this.ParallelOptions,
x =>
(int x) =>
{
Vector2[] buffer = arrayPool.Rent(maxIntersections);
float[] buffer = arrayPool.Rent(maxIntersections);
try
{
Vector2 left = new Vector2(x, polyStartY);
Vector2 right = new Vector2(x, polyEndY);
float left = polyStartY;
float right = polyEndY;
// 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)
{
// nothign on this line skip
return;
}
QuickSortY(buffer, pointsFound);
QuickSort(buffer, pointsFound);
int currentIntersection = 0;
float nextPoint = buffer[0].Y;
float lastPoint = left.Y;
float nextPoint = buffer[0];
float lastPoint = left;
bool isInside = false;
// every odd point is the start of a line
Vector2 currentPoint = default(Vector2);
for (int y = minY; y < maxY; y++)
{
currentPoint.X = x;
currentPoint.Y = y;
if (!isInside)
{
if (y < (nextPoint - DrawPadding) && y > (lastPoint + DrawPadding))
{
if (nextPoint == right.Y)
if (nextPoint == right)
{
// we are in the ends run skip it
y = maxY;
@ -266,7 +260,7 @@ namespace ImageSharp.Drawing.Processors
{
if (y < nextPoint - DrawPadding)
{
if (nextPoint == right.Y)
if (nextPoint == right)
{
// we are in the ends run skip it
y = maxY;
@ -286,11 +280,11 @@ namespace ImageSharp.Drawing.Processors
lastPoint = nextPoint;
if (currentIntersection == pointsFound)
{
nextPoint = right.Y;
nextPoint = right;
}
else
{
nextPoint = buffer[currentIntersection].Y;
nextPoint = buffer[currentIntersection];
// double point from a corner flip the bit back and move on again
if (nextPoint == lastPoint)
@ -300,11 +294,11 @@ namespace ImageSharp.Drawing.Processors
currentIntersection++;
if (currentIntersection == pointsFound)
{
nextPoint = right.Y;
nextPoint = right;
}
else
{
nextPoint = buffer[currentIntersection].Y;
nextPoint = buffer[currentIntersection];
}
}
}
@ -350,8 +344,7 @@ namespace ImageSharp.Drawing.Processors
if (opacity > Constants.Epsilon && opacity < 1)
{
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);
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[right] = tmp;
}
private static void QuickSortY(Vector2[] 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)
private static void QuickSort(float[] data, int size)
{
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)
{
int p = PartitionX(data, lo, hi);
QuickSortX(data, lo, p);
QuickSortX(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);
int p = Partition(data, lo, hi);
QuickSort(data, lo, p);
QuickSort(data, p + 1, hi);
}
}
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 j = hi + 1;
while (true)
@ -448,13 +397,13 @@ namespace ImageSharp.Drawing.Processors
{
i = i + 1;
}
while (data[i].Y < pivot && i < hi);
while (data[i] < pivot && i < hi);
do
{
j = j - 1;
}
while (data[j].Y > pivot && j > lo);
while (data[j] > pivot && j > lo);
if (i >= j)
{

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

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

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": {
"ImageSharp": {
"target": "project",
"version": "1.0.0-*"
"target": "project"
},
"ImageSharp.Processing": {
"target": "project",
"version": "1.0.0-*"
"target": "project"
},
"SixLabors.Shapes": "0.1.0-ci0043",
"SixLabors.Shapes": "0.1.0-ci0047",
"StyleCop.Analyzers": {
"version": "1.0.0",
"type": "build"

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

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

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

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

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

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

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

@ -7,9 +7,11 @@ namespace ImageSharp.Tests.Drawing
{
using System.IO;
using Xunit;
using Drawing;
using ImageSharp.Drawing;
using System.Numerics;
using ImageSharp.Drawing.Shapes;
using SixLabors.Shapes;
public class SolidComplexPolygonTests : FileTestBase
{
@ -33,7 +35,7 @@ namespace ImageSharp.Tests.Drawing
{
image
.BackgroundColor(Color.Blue)
.Fill(Color.HotPink, new ComplexPolygon(simplePath, hole1))
.Fill(Color.HotPink, simplePath.Clip(hole1))
.Save(output);
}
@ -76,7 +78,7 @@ namespace ImageSharp.Tests.Drawing
{
image
.BackgroundColor(Color.Blue)
.Fill(Color.HotPink, new ComplexPolygon(simplePath, hole1))
.Fill(Color.HotPink, simplePath.Clip(hole1))
.Save(output);
}
@ -119,8 +121,8 @@ namespace ImageSharp.Tests.Drawing
{
image
.BackgroundColor(Color.Blue)
.Fill(color, new ComplexPolygon(simplePath, hole1))
.Save(output);
.Fill(color, simplePath.Clip(hole1))
.Save(output);
}
//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
{
using Drawing;
using ImageSharp.Drawing;
using System;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Numerics;
using Xunit;
@ -142,14 +144,15 @@ namespace ImageSharp.Tests.Drawing
public void ImageShouldBeOverlayedByFilledRectangle()
{
string path = this.CreateOutputDirectory("Drawing", "FilledPolygons");
using (Image image = new Image(500, 500))
{
using (FileStream output = File.OpenWrite($"{path}/Rectangle.png"))
{
image
.BackgroundColor(Color.Blue)
.Fill(Color.HotPink, new ImageSharp.Drawing.Shapes.RectangularPolygon(new Rectangle(10, 10, 190, 140)))
.Save(output);
.Fill(Color.HotPink, new SixLabors.Shapes.Rectangle(10,10, 190, 140))
.Save(output);
}
using (PixelAccessor<Color> sourcePixels = image.Lock())

Loading…
Cancel
Save