diff --git a/.travis.yml b/.travis.yml index 172079df2..ca8b90a06 100644 --- a/.travis.yml +++ b/.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: diff --git a/ConsoleApp1/ConsoleApp1.csproj b/ConsoleApp1/ConsoleApp1.csproj new file mode 100644 index 000000000..68b6f2423 --- /dev/null +++ b/ConsoleApp1/ConsoleApp1.csproj @@ -0,0 +1,8 @@ + + + + Exe + netcoreapp1.0 + + + \ No newline at end of file diff --git a/ConsoleApp1/Program.cs b/ConsoleApp1/Program.cs new file mode 100644 index 000000000..104ecf026 --- /dev/null +++ b/ConsoleApp1/Program.cs @@ -0,0 +1,9 @@ +using System; + +class Program +{ + static void Main(string[] args) + { + Console.WriteLine("Hello World!"); + } +} \ No newline at end of file diff --git a/src/ImageSharp.Drawing/Brushes/ImageBrush{TColor}.cs b/src/ImageSharp.Drawing/Brushes/ImageBrush{TColor}.cs index 9ce235a84..b9bcc2f9a 100644 --- a/src/ImageSharp.Drawing/Brushes/ImageBrush{TColor}.cs +++ b/src/ImageSharp.Drawing/Brushes/ImageBrush{TColor}.cs @@ -82,18 +82,24 @@ namespace ImageSharp.Drawing.Brushes /// /// Gets the color for a single pixel. /// - /// The point. + /// The x. + /// The y. /// /// The color /// - 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]; + } } /// diff --git a/src/ImageSharp.Drawing/Brushes/PatternBrush{TColor}.cs b/src/ImageSharp.Drawing/Brushes/PatternBrush{TColor}.cs index 7749f5ba8..3fea53052 100644 --- a/src/ImageSharp.Drawing/Brushes/PatternBrush{TColor}.cs +++ b/src/ImageSharp.Drawing/Brushes/PatternBrush{TColor}.cs @@ -134,17 +134,21 @@ namespace ImageSharp.Drawing.Brushes /// /// Gets the color for a single pixel. - /// - /// The point. + /// # + /// The x. + /// The y. /// - /// The color + /// The Color. /// - 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]; + } } /// diff --git a/src/ImageSharp.Drawing/Brushes/Processors/BrushApplicator.cs b/src/ImageSharp.Drawing/Brushes/Processors/BrushApplicator.cs index 885be5715..9103dfdf6 100644 --- a/src/ImageSharp.Drawing/Brushes/Processors/BrushApplicator.cs +++ b/src/ImageSharp.Drawing/Brushes/Processors/BrushApplicator.cs @@ -16,16 +16,20 @@ namespace ImageSharp.Drawing.Processors public abstract class BrushApplicator : IDisposable // disposable will be required if/when there is an ImageBrush where TColor : struct, IPackedPixel, IEquatable { + /// - /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. + /// Gets the color for a single pixel. /// - public abstract void Dispose(); + /// The x. + /// The y. + /// + /// The color + /// + public abstract TColor this[int x, int y] { get; } /// - /// Gets the color for a single pixel. + /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. /// - /// The point. - /// The color - public abstract TColor GetColor(Vector2 point); + public abstract void Dispose(); } } diff --git a/src/ImageSharp.Drawing/Brushes/RecolorBrush{TColor}.cs b/src/ImageSharp.Drawing/Brushes/RecolorBrush{TColor}.cs index 7149f22a0..33403facb 100644 --- a/src/ImageSharp.Drawing/Brushes/RecolorBrush{TColor}.cs +++ b/src/ImageSharp.Drawing/Brushes/RecolorBrush{TColor}.cs @@ -109,24 +109,31 @@ namespace ImageSharp.Drawing.Brushes /// /// Gets the color for a single pixel. /// - /// The point. + /// The x. + /// The y. /// /// The color /// - 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; } /// diff --git a/src/ImageSharp.Drawing/Brushes/SolidBrush{TColor}.cs b/src/ImageSharp.Drawing/Brushes/SolidBrush{TColor}.cs index c3e311399..018c272b7 100644 --- a/src/ImageSharp.Drawing/Brushes/SolidBrush{TColor}.cs +++ b/src/ImageSharp.Drawing/Brushes/SolidBrush{TColor}.cs @@ -67,14 +67,12 @@ namespace ImageSharp.Drawing.Brushes /// /// Gets the color for a single pixel. /// - /// The point. + /// The x. + /// The y. /// /// The color /// - public override TColor GetColor(Vector2 point) - { - return this.color; - } + public override TColor this[int x, int y] => this.color; /// public override void Dispose() diff --git a/src/ImageSharp.Drawing/Draw.cs b/src/ImageSharp.Drawing/Draw.cs index 8c3dbb1b5..55dba9257 100644 --- a/src/ImageSharp.Drawing/Draw.cs +++ b/src/ImageSharp.Drawing/Draw.cs @@ -32,7 +32,7 @@ namespace ImageSharp public static Image DrawPolygon(this Image source, IPen pen, IShape shape, GraphicsOptions options) where TColor : struct, IPackedPixel, IEquatable { - return source.Apply(new DrawPathProcessor(pen, shape, options)); + return source.Apply(new DrawPathProcessor(pen, new ShapeRegion(shape), options)); } /// @@ -226,7 +226,7 @@ namespace ImageSharp public static Image DrawPath(this Image source, IPen pen, IPath path, GraphicsOptions options) where TColor : struct, IPackedPixel, IEquatable { - return source.Apply(new DrawPathProcessor(pen, path, options)); + return source.Apply(new DrawPathProcessor(pen, new ShapeRegion(path), options)); } /// @@ -240,7 +240,7 @@ namespace ImageSharp public static Image DrawPath(this Image source, IPen pen, IPath path) where TColor : struct, IPackedPixel, IEquatable { - return source.Apply(new DrawPathProcessor(pen, path, GraphicsOptions.Default)); + return source.Apply(new DrawPathProcessor(pen, new ShapeRegion(path), GraphicsOptions.Default)); } /// diff --git a/src/ImageSharp.Drawing/DrawRectangle.cs b/src/ImageSharp.Drawing/DrawRectangle.cs index 17d6d5f36..243aa082d 100644 --- a/src/ImageSharp.Drawing/DrawRectangle.cs +++ b/src/ImageSharp.Drawing/DrawRectangle.cs @@ -33,7 +33,7 @@ namespace ImageSharp public static Image DrawPolygon(this Image source, IPen pen, RectangleF shape, GraphicsOptions options) where TColor : struct, IPackedPixel, IEquatable { - return source.Apply(new DrawPathProcessor(pen, (IPath)new SixLabors.Shapes.Rectangle(shape.X, shape.Y, shape.Width, shape.Height), options)); + return source.Apply(new DrawPathProcessor(pen, new ShapeRegion(new SixLabors.Shapes.Rectangle(shape.X, shape.Y, shape.Width, shape.Height)), options)); } /// diff --git a/src/ImageSharp.Drawing/Fill.cs b/src/ImageSharp.Drawing/Fill.cs index 637950e76..f94d9ca6f 100644 --- a/src/ImageSharp.Drawing/Fill.cs +++ b/src/ImageSharp.Drawing/Fill.cs @@ -56,7 +56,7 @@ namespace ImageSharp public static Image Fill(this Image source, IBrush brush, IShape shape, GraphicsOptions options) where TColor : struct, IPackedPixel, IEquatable { - return source.Apply(new FillShapeProcessor(brush, shape, options)); + return source.Apply(new FillShapeProcessor(brush, new ShapeRegion(shape), options)); } /// @@ -70,7 +70,7 @@ namespace ImageSharp public static Image Fill(this Image source, IBrush brush, IShape shape) where TColor : struct, IPackedPixel, IEquatable { - return source.Apply(new FillShapeProcessor(brush, shape, GraphicsOptions.Default)); + return source.Apply(new FillShapeProcessor(brush, new ShapeRegion(shape), GraphicsOptions.Default)); } /// diff --git a/src/ImageSharp.Drawing/FillRectangle.cs b/src/ImageSharp.Drawing/FillRectangle.cs index 7749e7c92..d60a5a19d 100644 --- a/src/ImageSharp.Drawing/FillRectangle.cs +++ b/src/ImageSharp.Drawing/FillRectangle.cs @@ -30,7 +30,7 @@ namespace ImageSharp public static Image Fill(this Image source, IBrush brush, RectangleF shape, GraphicsOptions options) where TColor : struct, IPackedPixel, IEquatable { - return source.Apply(new FillShapeProcessor(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); } /// @@ -44,7 +44,7 @@ namespace ImageSharp public static Image Fill(this Image source, IBrush brush, RectangleF shape) where TColor : struct, IPackedPixel, IEquatable { - return source.Apply(new FillShapeProcessor(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)); } /// diff --git a/src/ImageSharp.Drawing/IDrawableRegion.cs b/src/ImageSharp.Drawing/IDrawableRegion.cs new file mode 100644 index 000000000..82e9c39ac --- /dev/null +++ b/src/ImageSharp.Drawing/IDrawableRegion.cs @@ -0,0 +1,24 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Drawing +{ + using System.Numerics; + + /// + /// Represents a region with knowledge about its outline. + /// + /// + public interface IDrawableRegion : IRegion + { + /// + /// Gets the point information for the specified x and y location. + /// + /// The x. + /// The y. + /// Information about the point in relation to a drawable edge + PointInfo GetPointInfo(int x, int y); + } +} \ No newline at end of file diff --git a/src/ImageSharp.Drawing/IRegion.cs b/src/ImageSharp.Drawing/IRegion.cs new file mode 100644 index 000000000..2264a91be --- /dev/null +++ b/src/ImageSharp.Drawing/IRegion.cs @@ -0,0 +1,51 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Drawing +{ + using System.Numerics; + + /// + /// Represents a region of an image. + /// + public interface IRegion + { + /// + /// Gets the maximum number of intersections to could be returned. + /// + /// + /// The maximum intersections. + /// + int MaxIntersections { get; } + + /// + /// Gets the bounds. + /// + /// + /// The bounds. + /// + Rectangle Bounds { get; } + + /// + /// Scans the X axis for intersections. + /// + /// The x. + /// The buffer. + /// The length. + /// The offset. + /// The number of intersections found. + int ScanX(int x, float[] buffer, int length, int offset); + + /// + /// Scans the Y axis for intersections. + /// + /// The position along the y axis to find intersections. + /// The buffer. + /// The length. + /// The offset. + /// The number of intersections found. + int ScanY(int y, float[] buffer, int length, int offset); + } +} \ No newline at end of file diff --git a/src/ImageSharp.Drawing/Pens/Pen{TColor}.cs b/src/ImageSharp.Drawing/Pens/Pen{TColor}.cs index bbb3c2559..d0ea55c1e 100644 --- a/src/ImageSharp.Drawing/Pens/Pen{TColor}.cs +++ b/src/ImageSharp.Drawing/Pens/Pen{TColor}.cs @@ -144,10 +144,10 @@ namespace ImageSharp.Drawing.Pens this.brush.Dispose(); } - public override ColoredPointInfo GetColor(PointInfo info) + public override ColoredPointInfo GetColor(int x, int y, PointInfo info) { var result = default(ColoredPointInfo); - 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 GetColor(PointInfo info) + public override ColoredPointInfo GetColor(int x, int y, PointInfo info) { var infoResult = default(ColoredPointInfo); 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 along with the color. - infoResult.Color = this.brush.GetColor(info.SearchPoint); + infoResult.Color = this.brush[x, y]; float distanceWAway = 0; diff --git a/src/ImageSharp.Drawing/Pens/Processors/PenApplicator.cs b/src/ImageSharp.Drawing/Pens/Processors/PenApplicator.cs index 222598d85..de680c809 100644 --- a/src/ImageSharp.Drawing/Pens/Processors/PenApplicator.cs +++ b/src/ImageSharp.Drawing/Pens/Processors/PenApplicator.cs @@ -6,6 +6,7 @@ namespace ImageSharp.Drawing.Processors { using System; + using System.Numerics; /// /// primitive that converts a into a color and a distance away from the drawable part of the path. @@ -30,8 +31,12 @@ namespace ImageSharp.Drawing.Processors /// /// Gets a from a point represented by a . /// + /// The x. + /// The y. /// The information to extract color details about. - /// Returns the color details and distance from a solid bit of the line. - public abstract ColoredPointInfo GetColor(PointInfo info); + /// + /// Returns the color details and distance from a solid bit of the line. + /// + public abstract ColoredPointInfo GetColor(int x, int y, PointInfo info); } } diff --git a/src/ImageSharp.Drawing/Pens/Processors/PointInfo.cs b/src/ImageSharp.Drawing/PointInfo.cs similarity index 81% rename from src/ImageSharp.Drawing/Pens/Processors/PointInfo.cs rename to src/ImageSharp.Drawing/PointInfo.cs index 6fc78b09b..e222a8146 100644 --- a/src/ImageSharp.Drawing/Pens/Processors/PointInfo.cs +++ b/src/ImageSharp.Drawing/PointInfo.cs @@ -3,7 +3,7 @@ // Licensed under the Apache License, Version 2.0. // -namespace ImageSharp.Drawing.Processors +namespace ImageSharp.Drawing { using System; using System.Numerics; @@ -22,10 +22,5 @@ namespace ImageSharp.Drawing.Processors /// The distance from path /// public float DistanceFromPath; - - /// - /// The search point - /// - public Vector2 SearchPoint; } } diff --git a/src/ImageSharp.Drawing/Processors/DrawPathProcessor.cs b/src/ImageSharp.Drawing/Processors/DrawPathProcessor.cs index d6b2f3eb2..bbb5e1ab6 100644 --- a/src/ImageSharp.Drawing/Processors/DrawPathProcessor.cs +++ b/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 pen; - private readonly IPath[] paths; - private readonly RectangleF region; + private readonly IDrawableRegion region; private readonly GraphicsOptions options; /// /// Initializes a new instance of the class. /// /// The pen. - /// The shape. + /// The region. /// The options. - public DrawPathProcessor(IPen pen, IShape shape, GraphicsOptions options) - : this(pen, shape.Paths, options) + public DrawPathProcessor(IPen pen, IDrawableRegion region, GraphicsOptions options) { - } - - /// - /// Initializes a new instance of the class. - /// - /// The pen. - /// The path. - /// The options. - public DrawPathProcessor(IPen pen, IPath path, GraphicsOptions options) - : this(pen, new[] { path }, options) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// The pen. - /// The paths. - /// The options. - public DrawPathProcessor(IPen pen, IEnumerable 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(); - } } /// protected override void OnApply(ImageBase source, Rectangle sourceRectangle) { using (PixelAccessor sourcePixels = source.Lock()) - using (PenApplicator applicator = this.pen.CreateApplicator(sourcePixels, this.region)) + using (PenApplicator 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) diff --git a/src/ImageSharp.Drawing/Processors/FillProcessor.cs b/src/ImageSharp.Drawing/Processors/FillProcessor.cs index dc87e6da6..37bdac90f 100644 --- a/src/ImageSharp.Drawing/Processors/FillProcessor.cs +++ b/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); diff --git a/src/ImageSharp.Drawing/Processors/FillShapeProcessor.cs b/src/ImageSharp.Drawing/Processors/FillShapeProcessor.cs index 14e99cba3..bdec022d4 100644 --- a/src/ImageSharp.Drawing/Processors/FillShapeProcessor.cs +++ b/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 fillColor; - private readonly IShape poly; + private readonly IRegion region; private readonly GraphicsOptions options; /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// The brush. - /// The shape. + /// The region. /// The options. - public FillShapeProcessor(IBrush brush, IShape shape, GraphicsOptions options) + public FillShapeProcessor(IBrush 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 /// protected override void OnApply(ImageBase 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 arrayPool = ArrayPool.Shared; + ArrayPool arrayPool = ArrayPool.Shared; - int maxIntersections = this.poly.MaxIntersections; + int maxIntersections = this.region.MaxIntersections; using (PixelAccessor sourcePixels = source.Lock()) using (BrushApplicator 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) { diff --git a/src/ImageSharp.Drawing/Processors/PointInfoExtensions.cs b/src/ImageSharp.Drawing/Processors/PointInfoExtensions.cs index 939a71845..6613d15ac 100644 --- a/src/ImageSharp.Drawing/Processors/PointInfoExtensions.cs +++ b/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; /// @@ -29,8 +31,7 @@ namespace ImageSharp.Drawing.Processors return new PointInfo { DistanceAlongPath = source.DistanceAlongPath, - DistanceFromPath = source.DistanceFromPath, - SearchPoint = source.SearchPoint + DistanceFromPath = source.DistanceFromPath }; } } diff --git a/src/ImageSharp.Drawing/ShapeRegion.cs b/src/ImageSharp.Drawing/ShapeRegion.cs new file mode 100644 index 000000000..6bd070367 --- /dev/null +++ b/src/ImageSharp.Drawing/ShapeRegion.cs @@ -0,0 +1,180 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Drawing +{ + using System.Buffers; + using System.Collections.Immutable; + using System.Numerics; + + using ImageSharp.Drawing.Processors; + + using SixLabors.Shapes; + + using Rectangle = ImageSharp.Rectangle; + + /// + /// A drawable mapping between a / and a drawable/fillable region. + /// + /// + internal class ShapeRegion : IDrawableRegion + { + /// + /// The fillable shape + /// + private readonly IShape shape; + + /// + /// The drawable paths + /// + private readonly ImmutableArray paths; + + /// + /// Initializes a new instance of the class. + /// + /// The path. + public ShapeRegion(IPath path) + : this(ImmutableArray.Create(path)) + { + this.shape = path.AsShape(); + this.Bounds = RectangleF.Ceiling(path.Bounds.Convert()); + } + + /// + /// Initializes a new instance of the class. + /// + /// The shape. + public ShapeRegion(IShape shape) + : this(shape.Paths) + { + this.shape = shape; + this.Bounds = RectangleF.Ceiling(shape.Bounds.Convert()); + } + + /// + /// Initializes a new instance of the class. + /// + /// The paths. + private ShapeRegion(ImmutableArray paths) + { + this.paths = paths; + } + + /// + /// Gets the maximum number of intersections to could be returned. + /// + /// + /// The maximum intersections. + /// + public int MaxIntersections => this.shape.MaxIntersections; + + /// + /// Gets the bounds. + /// + /// + /// The bounds. + /// + public Rectangle Bounds { get; } + + /// + /// Scans the X axis for intersections. + /// + /// The x. + /// The buffer. + /// The length. + /// The offset. + /// + /// The number of intersections found. + /// + 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.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.Shared.Return(innerbuffer); + } + } + + /// + /// Scans the Y axis for intersections. + /// + /// The position along the y axis to find intersections. + /// The buffer. + /// The length. + /// The offset. + /// + /// The number of intersections found. + /// + 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.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.Shared.Return(innerbuffer); + } + } + + /// + /// Gets the point information for the specified x and y location. + /// + /// The x. + /// The y. + /// Information about the the point + 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(); + } + } +} diff --git a/src/ImageSharp.Drawing/project.json b/src/ImageSharp.Drawing/project.json index f0d4c6243..5fcee9b41 100644 --- a/src/ImageSharp.Drawing/project.json +++ b/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" diff --git a/tests/ImageSharp.Sandbox46/ImageSharp.Sandbox46.csproj b/tests/ImageSharp.Sandbox46/ImageSharp.Sandbox46.csproj index 305fac636..75212d361 100644 --- a/tests/ImageSharp.Sandbox46/ImageSharp.Sandbox46.csproj +++ b/tests/ImageSharp.Sandbox46/ImageSharp.Sandbox46.csproj @@ -179,6 +179,9 @@ ..\..\src\ImageSharp.Drawing\bin\$(Configuration)\net461\ImageSharp.Drawing.dll + + ..\..\src\ImageSharp.Drawing\bin\$(Configuration)\net461\SixLabors.Shapes.dll + ..\..\src\ImageSharp.Formats.Bmp\bin\$(Configuration)\net461\ImageSharp.Formats.Bmp.dll diff --git a/tests/ImageSharp.Tests/Drawing/DrawPathTests.cs b/tests/ImageSharp.Tests/Drawing/DrawPathTests.cs index b61948316..31aa87d4b 100644 --- a/tests/ImageSharp.Tests/Drawing/DrawPathTests.cs +++ b/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 diff --git a/tests/ImageSharp.Tests/Drawing/Helpers/BezierPolygon.cs b/tests/ImageSharp.Tests/Drawing/Helpers/BezierPolygon.cs new file mode 100644 index 000000000..070e55501 --- /dev/null +++ b/tests/ImageSharp.Tests/Drawing/Helpers/BezierPolygon.cs @@ -0,0 +1,71 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +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 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 Paths + { + get + { + return this.polygon.Paths; + } + } + + public int MaxIntersections + { + get + { + return this.polygon.MaxIntersections; + } + } + } +} diff --git a/tests/ImageSharp.Tests/Drawing/Helpers/LinearPolygon.cs b/tests/ImageSharp.Tests/Drawing/Helpers/LinearPolygon.cs new file mode 100644 index 000000000..fa9488266 --- /dev/null +++ b/tests/ImageSharp.Tests/Drawing/Helpers/LinearPolygon.cs @@ -0,0 +1,71 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +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 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 Paths + { + get + { + return this.polygon.Paths; + } + } + + public int MaxIntersections + { + get + { + return this.polygon.MaxIntersections; + } + } + } +} diff --git a/tests/ImageSharp.Tests/Drawing/LineComplexPolygonTests.cs b/tests/ImageSharp.Tests/Drawing/LineComplexPolygonTests.cs index 9ce93ee89..dd2ea5249 100644 --- a/tests/ImageSharp.Tests/Drawing/LineComplexPolygonTests.cs +++ b/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 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); } diff --git a/tests/ImageSharp.Tests/Drawing/SolidBezierTests.cs b/tests/ImageSharp.Tests/Drawing/SolidBezierTests.cs index 18275ef38..82e4ac33e 100644 --- a/tests/ImageSharp.Tests/Drawing/SolidBezierTests.cs +++ b/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 diff --git a/tests/ImageSharp.Tests/Drawing/SolidComplexPolygonTests.cs b/tests/ImageSharp.Tests/Drawing/SolidComplexPolygonTests.cs index 144a1398d..a1973a280 100644 --- a/tests/ImageSharp.Tests/Drawing/SolidComplexPolygonTests.cs +++ b/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 } } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/Drawing/SolidPolygonTests.cs b/tests/ImageSharp.Tests/Drawing/SolidPolygonTests.cs index b4cf8e090..3e20b3a09 100644 --- a/tests/ImageSharp.Tests/Drawing/SolidPolygonTests.cs +++ b/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 sourcePixels = image.Lock())