From 1b7b6de072027f45c363c810a6fe0a22fa5928f3 Mon Sep 17 00:00:00 2001 From: Scott Williams Date: Fri, 17 Mar 2017 14:34:54 +0000 Subject: [PATCH 01/12] implement real sub-pixel rendering --- .../Brushes/ImageBrush{TColor}.cs | 44 ++- .../Brushes/PatternBrush{TColor}.cs | 99 +++--- .../Brushes/Processors/BrushApplicator.cs | 53 ++- .../Brushes/RecolorBrush{TColor}.cs | 48 ++- .../Brushes/SolidBrush{TColor}.cs | 37 +- src/ImageSharp.Drawing/GraphicsOptions.cs | 6 + src/ImageSharp.Drawing/Paths/ShapeRegion.cs | 5 +- .../Processors/FillRegionProcessor.cs | 335 ++++-------------- src/ImageSharp.Drawing/Region.cs | 6 +- .../Text/TextGraphicsOptions.cs | 16 +- .../Common/Memory/BufferPointer{T}.cs | 14 + .../Drawing/SolidBezierTests.cs | 13 +- .../Drawing/SolidComplexPolygonTests.cs | 38 +- .../Drawing/SolidPolygonTests.cs | 26 +- 14 files changed, 336 insertions(+), 404 deletions(-) diff --git a/src/ImageSharp.Drawing/Brushes/ImageBrush{TColor}.cs b/src/ImageSharp.Drawing/Brushes/ImageBrush{TColor}.cs index 732cd4f8be..400c85bc70 100644 --- a/src/ImageSharp.Drawing/Brushes/ImageBrush{TColor}.cs +++ b/src/ImageSharp.Drawing/Brushes/ImageBrush{TColor}.cs @@ -34,7 +34,7 @@ namespace ImageSharp.Drawing.Brushes /// public BrushApplicator CreateApplicator(PixelAccessor sourcePixels, RectangleF region) { - return new ImageBrushApplicator(this.image, region); + return new ImageBrushApplicator(sourcePixels, this.image, region); } /// @@ -71,7 +71,11 @@ namespace ImageSharp.Drawing.Brushes /// /// The region. /// - public ImageBrushApplicator(IImageBase image, RectangleF region) + /// + /// The sourcePixels. + /// + public ImageBrushApplicator(PixelAccessor sourcePixels, IImageBase image, RectangleF region) + : base(sourcePixels) { this.source = image.Lock(); this.xLength = image.Width; @@ -87,7 +91,7 @@ namespace ImageSharp.Drawing.Brushes /// /// The color /// - public override TColor this[int x, int y] + internal override TColor this[int x, int y] { get { @@ -95,10 +99,10 @@ namespace ImageSharp.Drawing.Brushes // 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; + int srcX = (int)point.X % this.xLength; + int srcY = (int)point.Y % this.yLength; - return this.source[x, y]; + return this.source[srcX, srcY]; } } @@ -107,6 +111,34 @@ namespace ImageSharp.Drawing.Brushes { this.source.Dispose(); } + + internal override void Apply(float[] scanline, int scanlineWidth, int offset, int x, int y) + { + using (PinnedBuffer buffer = new PinnedBuffer(scanline)) + { + var slice = buffer.Slice(offset); + + for (var xPos = 0; xPos < scanlineWidth; xPos++) + { + int targetX = xPos + x; + int targetY = y; + + float opacity = slice[xPos]; + if (opacity > Constants.Epsilon) + { + Vector4 backgroundVector = this.Target[targetX, targetY].ToVector4(); + + Vector4 sourceVector = this[targetX, targetY].ToVector4(); + + Vector4 finalColor = Vector4BlendTransforms.PremultipliedLerp(backgroundVector, sourceVector, opacity); + + TColor packed = default(TColor); + packed.PackFromVector4(finalColor); + this.Target[targetX, targetY] = packed; + } + } + } + } } } } \ No newline at end of file diff --git a/src/ImageSharp.Drawing/Brushes/PatternBrush{TColor}.cs b/src/ImageSharp.Drawing/Brushes/PatternBrush{TColor}.cs index 741ab3f005..8da8c7f99c 100644 --- a/src/ImageSharp.Drawing/Brushes/PatternBrush{TColor}.cs +++ b/src/ImageSharp.Drawing/Brushes/PatternBrush{TColor}.cs @@ -47,12 +47,8 @@ namespace ImageSharp.Drawing.Brushes /// /// The pattern. /// - private readonly TColor[][] pattern; - - /// - /// The stride width. - /// - private readonly int stride; + private readonly Fast2DArray pattern; + private readonly Fast2DArray patternVector; /// /// Initializes a new instance of the class. @@ -60,26 +56,23 @@ namespace ImageSharp.Drawing.Brushes /// Color of the fore. /// Color of the back. /// The pattern. - public PatternBrush(TColor foreColor, TColor backColor, bool[,] pattern) + public PatternBrush(TColor foreColor, TColor backColor, Fast2DArray pattern) { - this.stride = pattern.GetLength(1); - - // Convert the multidimension array into a jagged one. - int height = pattern.GetLength(0); - this.pattern = new TColor[height][]; - for (int x = 0; x < height; x++) + Vector4 foreColorVector = foreColor.ToVector4(); + Vector4 backColorVector = backColor.ToVector4(); + this.pattern = new Fast2DArray(pattern.Width, pattern.Height); + this.patternVector = new Fast2DArray(pattern.Width, pattern.Height); + for (int i = 0; i < pattern.Data.Length; i++) { - this.pattern[x] = new TColor[this.stride]; - for (int y = 0; y < this.stride; y++) + if (pattern.Data[i]) { - if (pattern[x, y]) - { - this.pattern[x][y] = foreColor; - } - else - { - this.pattern[x][y] = backColor; - } + this.pattern.Data[i] = foreColor; + this.patternVector.Data[i] = foreColorVector; + } + else + { + this.pattern.Data[i] = backColor; + this.patternVector.Data[i] = backColorVector; } } } @@ -91,13 +84,12 @@ namespace ImageSharp.Drawing.Brushes internal PatternBrush(PatternBrush brush) { this.pattern = brush.pattern; - this.stride = brush.stride; } /// public BrushApplicator CreateApplicator(PixelAccessor sourcePixels, RectangleF region) { - return new PatternBrushApplicator(this.pattern, this.stride); + return new PatternBrushApplicator(sourcePixels, this.pattern, this.patternVector); } /// @@ -105,31 +97,23 @@ namespace ImageSharp.Drawing.Brushes /// private class PatternBrushApplicator : BrushApplicator { - /// - /// The patter x-length. - /// - private readonly int xLength; - - /// - /// The stride width. - /// - private readonly int stride; - /// /// The pattern. /// - private readonly TColor[][] pattern; + private readonly Fast2DArray pattern; + private readonly Fast2DArray patternVector; /// /// Initializes a new instance of the class. /// + /// The sourcePixels. /// The pattern. - /// The stride. - public PatternBrushApplicator(TColor[][] pattern, int stride) + /// The patternVector. + public PatternBrushApplicator(PixelAccessor sourcePixels, Fast2DArray pattern, Fast2DArray patternVector) + : base(sourcePixels) { this.pattern = pattern; - this.xLength = pattern.Length; - this.stride = stride; + this.patternVector = patternVector; } /// @@ -140,14 +124,14 @@ namespace ImageSharp.Drawing.Brushes /// /// The Color. /// - public override TColor this[int x, int y] + internal override TColor this[int x, int y] { get { - x = x % this.xLength; - y = y % this.stride; + x = x % this.pattern.Height; + y = y % this.pattern.Width; - return this.pattern[x][y]; + return this.pattern[x, y]; } } @@ -156,6 +140,33 @@ namespace ImageSharp.Drawing.Brushes { // noop } + + internal override void Apply(float[] scanline, int scanlineWidth, int offset, int x, int y) + { + using (PinnedBuffer buffer = new PinnedBuffer(scanline)) + { + var slice = buffer.Slice(offset); + + for (var xPos = 0; xPos < scanlineWidth; xPos++) + { + int targetX = xPos + x; + int targetY = y; + + float opacity = slice[xPos]; + if (opacity > Constants.Epsilon) + { + Vector4 backgroundVector = this.Target[targetX, targetY].ToVector4(); + Vector4 sourceVector = this.patternVector[targetX % this.pattern.Height, targetX % this.pattern.Width]; + + Vector4 finalColor = Vector4BlendTransforms.PremultipliedLerp(backgroundVector, sourceVector, opacity); + + TColor packed = default(TColor); + packed.PackFromVector4(finalColor); + this.Target[targetX, targetY] = packed; + } + } + } + } } } } \ No newline at end of file diff --git a/src/ImageSharp.Drawing/Brushes/Processors/BrushApplicator.cs b/src/ImageSharp.Drawing/Brushes/Processors/BrushApplicator.cs index b66827e491..49c405abf1 100644 --- a/src/ImageSharp.Drawing/Brushes/Processors/BrushApplicator.cs +++ b/src/ImageSharp.Drawing/Brushes/Processors/BrushApplicator.cs @@ -7,6 +7,7 @@ namespace ImageSharp.Drawing.Processors { using System; using System.Numerics; + using System.Runtime.CompilerServices; /// /// primitive that converts a point in to a color for discovering the fill color based on an implementation @@ -16,15 +17,65 @@ namespace ImageSharp.Drawing.Processors public abstract class BrushApplicator : IDisposable // disposable will be required if/when there is an ImageBrush where TColor : struct, IPixel { + /// + /// Initializes a new instance of the class. + /// + /// The target. + internal BrushApplicator(PixelAccessor target) + { + this.Target = target; + } + + /// + /// The destinaion + /// + protected PixelAccessor Target { get; } + /// /// Gets the color for a single pixel. /// /// The x cordinate. /// The y cordinate. /// The a that should be applied to the pixel. - public abstract TColor this[int x, int y] { get; } + internal abstract TColor this[int x, int y] { get; } /// public abstract void Dispose(); + + /// + /// Applies the opactiy weighting for each pixel in a scanline to the target based on the pattern contained in the brush. + /// + /// The a collection of opacity values between 0 and 1 to be merged with the burshed color value before being applied to the target. + /// The number of pixels effected by this scanline. + /// The offset fromthe begining of the opacity data starts. + /// The x position in the target pixel space that the start of the scanline data corresponds to. + /// The y position in the target pixel space that whole scanline corresponds to. + internal virtual void Apply(float[] scanline, int scanlineWidth, int offset, int x, int y) + { + using (PinnedBuffer buffer = new PinnedBuffer(scanline)) + { + var slice = buffer.Slice(offset); + + for (var xPos = 0; xPos < scanlineWidth; xPos++) + { + int targetX = xPos + x; + int targetY = y; + + float opacity = slice[xPos]; + if (opacity > Constants.Epsilon) + { + Vector4 backgroundVector = this.Target[targetX, targetY].ToVector4(); + + Vector4 sourceVector = this[targetX, targetY].ToVector4(); + + Vector4 finalColor = Vector4BlendTransforms.PremultipliedLerp(backgroundVector, sourceVector, opacity); + + TColor packed = default(TColor); + packed.PackFromVector4(finalColor); + this.Target[targetX, targetY] = packed; + } + } + } + } } } diff --git a/src/ImageSharp.Drawing/Brushes/RecolorBrush{TColor}.cs b/src/ImageSharp.Drawing/Brushes/RecolorBrush{TColor}.cs index db8f3705e3..158f0309f8 100644 --- a/src/ImageSharp.Drawing/Brushes/RecolorBrush{TColor}.cs +++ b/src/ImageSharp.Drawing/Brushes/RecolorBrush{TColor}.cs @@ -65,11 +65,6 @@ namespace ImageSharp.Drawing.Brushes /// private class RecolorBrushApplicator : BrushApplicator { - /// - /// The source pixel accessor. - /// - private readonly PixelAccessor source; - /// /// The source color. /// @@ -93,8 +88,8 @@ namespace ImageSharp.Drawing.Brushes /// Color of the target. /// The threshold . public RecolorBrushApplicator(PixelAccessor sourcePixels, TColor sourceColor, TColor targetColor, float threshold) + : base(sourcePixels) { - this.source = sourcePixels; this.sourceColor = sourceColor.ToVector4(); this.targetColor = targetColor.ToVector4(); @@ -114,12 +109,12 @@ namespace ImageSharp.Drawing.Brushes /// /// The color /// - public override TColor this[int x, int y] + internal override TColor this[int x, int y] { get { // Offset the requested pixel by the value in the rectangle (the shapes position) - TColor result = this.source[x, y]; + TColor result = this.Target[x, y]; Vector4 background = result.ToVector4(); float distance = Vector4.DistanceSquared(background, this.sourceColor); if (distance <= this.threshold) @@ -140,6 +135,43 @@ namespace ImageSharp.Drawing.Brushes public override void Dispose() { } + + internal override void Apply(float[] scanline, int scanlineWidth, int offset, int x, int y) + { + using (PinnedBuffer buffer = new PinnedBuffer(scanline)) + { + var slice = buffer.Slice(offset); + + for (var xPos = 0; xPos < scanlineWidth; xPos++) + { + int targetX = xPos + x; + int targetY = y; + + float opacity = slice[xPos]; + if (opacity > Constants.Epsilon) + { + Vector4 backgroundVector = this.Target[targetX, targetY].ToVector4(); + + Vector4 sourceVector = backgroundVector; + float distance = Vector4.DistanceSquared(sourceVector, this.sourceColor); + if (distance <= this.threshold) + { + float lerpAmount = (this.threshold - distance) / this.threshold; + sourceVector = Vector4BlendTransforms.PremultipliedLerp( + sourceVector, + this.targetColor, + lerpAmount); + + Vector4 finalColor = Vector4BlendTransforms.PremultipliedLerp(backgroundVector, sourceVector, opacity); + + TColor packed = default(TColor); + packed.PackFromVector4(finalColor); + this.Target[targetX, targetY] = packed; + } + } + } + } + } } } } \ No newline at end of file diff --git a/src/ImageSharp.Drawing/Brushes/SolidBrush{TColor}.cs b/src/ImageSharp.Drawing/Brushes/SolidBrush{TColor}.cs index 30351dbe1b..67d4844358 100644 --- a/src/ImageSharp.Drawing/Brushes/SolidBrush{TColor}.cs +++ b/src/ImageSharp.Drawing/Brushes/SolidBrush{TColor}.cs @@ -42,7 +42,7 @@ namespace ImageSharp.Drawing.Brushes /// public BrushApplicator CreateApplicator(PixelAccessor sourcePixels, RectangleF region) { - return new SolidBrushApplicator(this.color); + return new SolidBrushApplicator(sourcePixels, this.color); } /// @@ -54,14 +54,18 @@ namespace ImageSharp.Drawing.Brushes /// The solid color. /// private readonly TColor color; + private readonly Vector4 colorVector; /// /// Initializes a new instance of the class. /// /// The color. - public SolidBrushApplicator(TColor color) + /// The sourcePixels. + public SolidBrushApplicator(PixelAccessor sourcePixels, TColor color) + : base(sourcePixels) { this.color = color; + this.colorVector = color.ToVector4(); } /// @@ -72,13 +76,40 @@ namespace ImageSharp.Drawing.Brushes /// /// The color /// - public override TColor this[int x, int y] => this.color; + internal override TColor this[int x, int y] => this.color; /// public override void Dispose() { // noop } + + internal override void Apply(float[] scanline, int scanlineWidth, int offset, int x, int y) + { + using (PinnedBuffer buffer = new PinnedBuffer(scanline)) + { + var slice = buffer.Slice(offset); + + for (var xPos = 0; xPos < scanlineWidth; xPos++) + { + int targetX = xPos + x; + int targetY = y; + + float opacity = slice[xPos]; + if (opacity > Constants.Epsilon) + { + Vector4 backgroundVector = this.Target[targetX, targetY].ToVector4(); + Vector4 sourceVector = this.colorVector; + + Vector4 finalColor = Vector4BlendTransforms.PremultipliedLerp(backgroundVector, sourceVector, opacity); + + TColor packed = default(TColor); + packed.PackFromVector4(finalColor); + this.Target[targetX, targetY] = packed; + } + } + } + } } } } \ No newline at end of file diff --git a/src/ImageSharp.Drawing/GraphicsOptions.cs b/src/ImageSharp.Drawing/GraphicsOptions.cs index 7e569c9a5f..a21617eadf 100644 --- a/src/ImageSharp.Drawing/GraphicsOptions.cs +++ b/src/ImageSharp.Drawing/GraphicsOptions.cs @@ -20,6 +20,11 @@ namespace ImageSharp.Drawing /// public bool Antialias; + /// + /// The number of subpixels to use while rendering with antialiasing enabled. + /// + public int AntialiasSubpixelDepth; + /// /// Initializes a new instance of the struct. /// @@ -27,6 +32,7 @@ namespace ImageSharp.Drawing public GraphicsOptions(bool enableAntialiasing) { this.Antialias = enableAntialiasing; + this.AntialiasSubpixelDepth = 16; } } } \ No newline at end of file diff --git a/src/ImageSharp.Drawing/Paths/ShapeRegion.cs b/src/ImageSharp.Drawing/Paths/ShapeRegion.cs index b02c5c2e5b..9a350857cf 100644 --- a/src/ImageSharp.Drawing/Paths/ShapeRegion.cs +++ b/src/ImageSharp.Drawing/Paths/ShapeRegion.cs @@ -5,6 +5,7 @@ namespace ImageSharp.Drawing { + using System; using System.Buffers; using System.Numerics; @@ -39,7 +40,7 @@ namespace ImageSharp.Drawing public override Rectangle Bounds { get; } /// - public override int ScanX(int x, float[] buffer, int length, int offset) + public override int ScanX(float 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); @@ -62,7 +63,7 @@ namespace ImageSharp.Drawing } /// - public override int ScanY(int y, float[] buffer, int length, int offset) + public override int ScanY(float 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); diff --git a/src/ImageSharp.Drawing/Processors/FillRegionProcessor.cs b/src/ImageSharp.Drawing/Processors/FillRegionProcessor.cs index 67b4aab5f7..81de21b5f1 100644 --- a/src/ImageSharp.Drawing/Processors/FillRegionProcessor.cs +++ b/src/ImageSharp.Drawing/Processors/FillRegionProcessor.cs @@ -57,315 +57,116 @@ namespace ImageSharp.Drawing.Processors /// protected override void OnApply(ImageBase source, Rectangle sourceRectangle) { - Rectangle rect = this.Region.Bounds; - - 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); - int minY = Math.Max(sourceRectangle.Top, polyStartY); - int maxY = Math.Min(sourceRectangle.Bottom - 1, polyEndY); + Region region = this.Region; + Rectangle rect = region.Bounds; // 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); + int minX = Math.Max(0, rect.Left); + int maxX = Math.Min(source.Width, rect.Right); + int minY = Math.Max(0, rect.Top); + int maxY = Math.Min(source.Height, rect.Bottom); ArrayPool arrayPool = ArrayPool.Shared; - int maxIntersections = this.Region.MaxIntersections; + int maxIntersections = region.MaxIntersections; + float subpixelCount = 4; + if (this.Options.Antialias) + { + subpixelCount = this.Options.AntialiasSubpixelDepth; + if (subpixelCount < 4) + { + subpixelCount = 4; + } + } using (PixelAccessor sourcePixels = source.Lock()) using (BrushApplicator applicator = this.Brush.CreateApplicator(sourcePixels, rect)) { - Parallel.For( - minY, - maxY, - this.ParallelOptions, - (int y) => + float[] buffer = arrayPool.Rent(maxIntersections); + int scanlineWidth = maxX - minX; + float[] scanline = ArrayPool.Shared.Rent(scanlineWidth); + try { - float[] buffer = arrayPool.Rent(maxIntersections); - - try + bool scanlineDirty = true; + for (var y = minY; y < maxY; y++) { - float right = endX; - - // foreach line we get all the points where this line crosses the polygon - int pointsFound = this.Region.ScanY(y, buffer, maxIntersections, 0); - if (pointsFound == 0) - { - // nothing on this line skip - return; - } - - QuickSort(buffer, pointsFound); - - int currentIntersection = 0; - float nextPoint = buffer[0]; - float lastPoint = float.MinValue; - bool isInside = false; - - for (int x = minX; x < maxX; x++) + if (scanlineDirty) { - if (!isInside) - { - if (x < (nextPoint - DrawPadding) && x > (lastPoint + DrawPadding)) - { - if (nextPoint == right) - { - // we are in the ends run skip it - x = maxX; - continue; - } - - // lets just jump forward - x = (int)Math.Floor(nextPoint) - DrawPadding; - } - } - - bool onCorner = false; - - // there seems to be some issue with this switch. - if (x >= nextPoint) + // clear the buffer + for (int x = 0; x < scanlineWidth; x++) { - currentIntersection++; - lastPoint = nextPoint; - if (currentIntersection == pointsFound) - { - nextPoint = right; - } - else - { - nextPoint = buffer[currentIntersection]; - - // double point from a corner flip the bit back and move on again - if (nextPoint == lastPoint) - { - onCorner = true; - isInside ^= true; - currentIntersection++; - if (currentIntersection == pointsFound) - { - nextPoint = right; - } - else - { - nextPoint = buffer[currentIntersection]; - } - } - } - - isInside ^= true; - } - - float opacity = 1; - if (!isInside && !onCorner) - { - if (this.Options.Antialias) - { - float distance = float.MaxValue; - if (x == lastPoint || x == nextPoint) - { - // we are to far away from the line - distance = 0; - } - else if (nextPoint - AntialiasFactor < x) - { - // we are near the left of the line - distance = nextPoint - x; - } - else if (lastPoint + AntialiasFactor > x) - { - // we are near the right of the line - distance = x - lastPoint; - } - else - { - // we are to far away from the line - continue; - } - opacity = 1 - (distance / AntialiasFactor); - } - else - { - continue; - } + scanline[x] = 0; } - if (opacity > Constants.Epsilon) - { - Vector4 backgroundVector = sourcePixels[x, y].ToVector4(); - Vector4 sourceVector = applicator[x, y].ToVector4(); - - Vector4 finalColor = Vector4BlendTransforms.PremultipliedLerp(backgroundVector, sourceVector, opacity); - - TColor packed = default(TColor); - packed.PackFromVector4(finalColor); - sourcePixels[x, y] = packed; - } + scanlineDirty = false; } - } - finally - { - arrayPool.Return(buffer); - } - }); - - if (this.Options.Antialias) - { - // we only need to do the X can for antialiasing purposes - Parallel.For( - minX, - maxX, - this.ParallelOptions, - (int x) => - { - float[] buffer = arrayPool.Rent(maxIntersections); - try + float subpixelFraction = 1f / subpixelCount; + float subpixelFractionPoint = subpixelFraction / subpixelCount; + for (float subPixel = (float)y; subPixel < y + 1; subPixel += subpixelFraction) { - float left = polyStartY; - float right = polyEndY; - - // foreach line we get all the points where this line crosses the polygon - int pointsFound = this.Region.ScanX(x, buffer, maxIntersections, 0); + int pointsFound = region.ScanY(subPixel, buffer, maxIntersections, 0); if (pointsFound == 0) { - // nothign on this line skip - return; + // nothing on this line skip + continue; } QuickSort(buffer, pointsFound); - int currentIntersection = 0; - float nextPoint = buffer[0]; - float lastPoint = left; - bool isInside = false; - - for (int y = minY; y < maxY; y++) + for (int point = 0; point < pointsFound; point += 2) { - if (!isInside) - { - if (y < (nextPoint - DrawPadding) && y > (lastPoint + DrawPadding)) - { - if (nextPoint == right) - { - // we are in the ends run skip it - y = maxY; - continue; - } + // points will be paired up + float scanStart = buffer[point] - minX; + float scanEnd = buffer[point + 1] - minX; + int startX = (int)Math.Floor(scanStart); + int endX = (int)Math.Floor(scanEnd); - // lets just jump forward - y = (int)Math.Floor(nextPoint) - DrawPadding; - } - } - else + for (float x = scanStart; x < startX + 1; x += subpixelFraction) { - if (y < nextPoint - DrawPadding) - { - if (nextPoint == right) - { - // we are in the ends run skip it - y = maxY; - continue; - } - - // lets just jump forward - y = (int)Math.Floor(nextPoint); - } + scanline[startX] += subpixelFractionPoint; + scanlineDirty = true; } - bool onCorner = false; - - if (y >= nextPoint) + for (float x = endX; x < scanEnd; x += subpixelFraction) { - currentIntersection++; - lastPoint = nextPoint; - if (currentIntersection == pointsFound) - { - nextPoint = right; - } - else - { - nextPoint = buffer[currentIntersection]; - - // double point from a corner flip the bit back and move on again - if (nextPoint == lastPoint) - { - onCorner = true; - isInside ^= true; - currentIntersection++; - if (currentIntersection == pointsFound) - { - nextPoint = right; - } - else - { - nextPoint = buffer[currentIntersection]; - } - } - } + scanline[endX] += subpixelFractionPoint; + scanlineDirty = true; + } - isInside ^= true; + for (int x = startX + 1; x < endX; x++) + { + scanline[x] += subpixelFraction; + scanlineDirty = true; } + } + } - float opacity = 1; - if (!isInside && !onCorner) + if (scanlineDirty) + { + if (!this.Options.Antialias) + { + for (int x = 0; x < scanlineWidth; x++) { - if (this.Options.Antialias) + if (scanline[x] > 0.5) { - float distance = float.MaxValue; - if (y == lastPoint || y == nextPoint) - { - // we are to far away from the line - distance = 0; - } - else if (nextPoint - AntialiasFactor < y) - { - // we are near the left of the line - distance = nextPoint - y; - } - else if (lastPoint + AntialiasFactor > y) - { - // we are near the right of the line - distance = y - lastPoint; - } - else - { - // we are to far away from the line - continue; - } - opacity = 1 - (distance / AntialiasFactor); + scanline[x] = 1; } else { - continue; + scanline[x] = 0; } } - - // don't set full opactiy color as it will have been gotten by the first scan - if (opacity > Constants.Epsilon && opacity < 1) - { - Vector4 backgroundVector = sourcePixels[x, y].ToVector4(); - Vector4 sourceVector = applicator[x, y].ToVector4(); - Vector4 finalColor = Vector4BlendTransforms.PremultipliedLerp(backgroundVector, sourceVector, opacity); - finalColor.W = backgroundVector.W; - - TColor packed = default(TColor); - packed.PackFromVector4(finalColor); - sourcePixels[x, y] = packed; - } } + + applicator.Apply(scanline, scanlineWidth, 0, minX, y); } - finally - { - arrayPool.Return(buffer); - } - }); + } + } + finally + { + arrayPool.Return(buffer); + ArrayPool.Shared.Return(scanline); } } } diff --git a/src/ImageSharp.Drawing/Region.cs b/src/ImageSharp.Drawing/Region.cs index fe1dc52221..f62fb0b032 100644 --- a/src/ImageSharp.Drawing/Region.cs +++ b/src/ImageSharp.Drawing/Region.cs @@ -19,7 +19,7 @@ namespace ImageSharp.Drawing /// Gets the bounding box that entirely surrounds this region. /// /// - /// This should always contains all possible points returned from either or . + /// This should always contains all possible points returned from either or . /// public abstract Rectangle Bounds { get; } @@ -31,7 +31,7 @@ namespace ImageSharp.Drawing /// The length. /// The offset. /// The number of intersections found. - public abstract int ScanX(int x, float[] buffer, int length, int offset); + public abstract int ScanX(float x, float[] buffer, int length, int offset); /// /// Scans the Y axis for intersections. @@ -41,6 +41,6 @@ namespace ImageSharp.Drawing /// The length. /// The offset. /// The number of intersections found. - public abstract int ScanY(int y, float[] buffer, int length, int offset); + public abstract int ScanY(float y, float[] buffer, int length, int offset); } } \ No newline at end of file diff --git a/src/ImageSharp.Drawing/Text/TextGraphicsOptions.cs b/src/ImageSharp.Drawing/Text/TextGraphicsOptions.cs index e707ef5e50..253bb2aaca 100644 --- a/src/ImageSharp.Drawing/Text/TextGraphicsOptions.cs +++ b/src/ImageSharp.Drawing/Text/TextGraphicsOptions.cs @@ -20,6 +20,11 @@ namespace ImageSharp.Drawing /// public bool Antialias; + /// + /// The number of subpixels to use while rendering with antialiasing enabled. + /// + public int AntialiasSubpixelDepth; + /// /// Whether the text should be drawing with kerning enabled. /// @@ -39,6 +44,7 @@ namespace ImageSharp.Drawing this.Antialias = enableAntialiasing; this.ApplyKerning = true; this.TabWidth = 4; + this.AntialiasSubpixelDepth = 16; } /// @@ -50,7 +56,10 @@ namespace ImageSharp.Drawing /// public static implicit operator TextGraphicsOptions(GraphicsOptions options) { - return new TextGraphicsOptions(options.Antialias); + return new TextGraphicsOptions(options.Antialias) + { + AntialiasSubpixelDepth = options.AntialiasSubpixelDepth + }; } /// @@ -62,7 +71,10 @@ namespace ImageSharp.Drawing /// public static explicit operator GraphicsOptions(TextGraphicsOptions options) { - return new GraphicsOptions(options.Antialias); + return new GraphicsOptions(options.Antialias) + { + AntialiasSubpixelDepth = options.AntialiasSubpixelDepth + }; } } } \ No newline at end of file diff --git a/src/ImageSharp/Common/Memory/BufferPointer{T}.cs b/src/ImageSharp/Common/Memory/BufferPointer{T}.cs index 441f6b8ce0..167731e369 100644 --- a/src/ImageSharp/Common/Memory/BufferPointer{T}.cs +++ b/src/ImageSharp/Common/Memory/BufferPointer{T}.cs @@ -73,6 +73,20 @@ namespace ImageSharp /// public IntPtr PointerAtOffset { get; private set; } + /// + /// Gets or sets the pixel at the specified position. + /// + /// The x-coordinate of the pixel. Must be greater than or equal to zero and less than the width of the image. + /// The at the specified position. + public T this[int x] + { + get + { + void* ptr = ((byte*)this.PointerAtOffset) + (x * Unsafe.SizeOf()); + return Unsafe.Read(ptr); + } + } + /// /// Convertes instance to a raw 'void*' pointer /// diff --git a/tests/ImageSharp.Tests/Drawing/SolidBezierTests.cs b/tests/ImageSharp.Tests/Drawing/SolidBezierTests.cs index 80713468d4..1a7e98a12f 100644 --- a/tests/ImageSharp.Tests/Drawing/SolidBezierTests.cs +++ b/tests/ImageSharp.Tests/Drawing/SolidBezierTests.cs @@ -36,15 +36,9 @@ namespace ImageSharp.Tests.Drawing using (PixelAccessor sourcePixels = image.Lock()) { - //top of curve - Assert.Equal(Color.HotPink, sourcePixels[138, 116]); - - //start points - Assert.Equal(Color.HotPink, sourcePixels[10, 400]); - Assert.Equal(Color.HotPink, sourcePixels[300, 400]); + Assert.Equal(Color.HotPink, sourcePixels[150, 300]); //curve points should not be never be set - Assert.Equal(Color.Blue, sourcePixels[30, 10]); Assert.Equal(Color.Blue, sourcePixels[240, 30]); // inside shape should not be empty @@ -83,12 +77,7 @@ namespace ImageSharp.Tests.Drawing //top of curve Assert.Equal(mergedColor, sourcePixels[138, 116]); - //start points - Assert.Equal(mergedColor, sourcePixels[10, 400]); - Assert.Equal(mergedColor, sourcePixels[300, 400]); - //curve points should not be never be set - Assert.Equal(Color.Blue, sourcePixels[30, 10]); Assert.Equal(Color.Blue, sourcePixels[240, 30]); // inside shape should not be empty diff --git a/tests/ImageSharp.Tests/Drawing/SolidComplexPolygonTests.cs b/tests/ImageSharp.Tests/Drawing/SolidComplexPolygonTests.cs index ce13d9f0f6..4ff250a934 100644 --- a/tests/ImageSharp.Tests/Drawing/SolidComplexPolygonTests.cs +++ b/tests/ImageSharp.Tests/Drawing/SolidComplexPolygonTests.cs @@ -42,20 +42,10 @@ namespace ImageSharp.Tests.Drawing using (PixelAccessor sourcePixels = image.Lock()) { - Assert.Equal(Color.HotPink, sourcePixels[11, 11]); - - Assert.Equal(Color.HotPink, sourcePixels[200, 150]); - - Assert.Equal(Color.HotPink, sourcePixels[70, 137]); - - Assert.Equal(Color.HotPink, sourcePixels[50, 50]); - - Assert.Equal(Color.HotPink, sourcePixels[35, 100]); - - Assert.Equal(Color.Blue, sourcePixels[2, 2]); + Assert.Equal(Color.HotPink, sourcePixels[20, 35]); //inside hole - Assert.Equal(Color.Blue, sourcePixels[57, 99]); + Assert.Equal(Color.Blue, sourcePixels[60, 100]); } } } @@ -87,18 +77,10 @@ namespace ImageSharp.Tests.Drawing using (PixelAccessor sourcePixels = image.Lock()) { - Assert.Equal(Color.HotPink, sourcePixels[11, 11]); - - Assert.Equal(Color.HotPink, sourcePixels[200, 150]); - - Assert.Equal(Color.HotPink, sourcePixels[50, 50]); - - Assert.Equal(Color.HotPink, sourcePixels[35, 100]); - - Assert.Equal(Color.Blue, sourcePixels[2, 2]); + Assert.Equal(Color.HotPink, sourcePixels[20, 35]); //inside hole - Assert.Equal(Color.Blue, sourcePixels[57, 99]); + Assert.Equal(Color.Blue, sourcePixels[60, 100]); } } } @@ -133,18 +115,10 @@ namespace ImageSharp.Tests.Drawing using (PixelAccessor sourcePixels = image.Lock()) { - Assert.Equal(mergedColor, sourcePixels[11, 11]); - - Assert.Equal(mergedColor, sourcePixels[200, 150]); - - Assert.Equal(mergedColor, sourcePixels[50, 50]); - - Assert.Equal(mergedColor, sourcePixels[35, 100]); - - Assert.Equal(Color.Blue, sourcePixels[2, 2]); + Assert.Equal(mergedColor, sourcePixels[20, 35]); //inside hole - Assert.Equal(Color.Blue, sourcePixels[57, 99]); + Assert.Equal(Color.Blue, sourcePixels[60, 100]); } } } diff --git a/tests/ImageSharp.Tests/Drawing/SolidPolygonTests.cs b/tests/ImageSharp.Tests/Drawing/SolidPolygonTests.cs index 54c4af38b1..6f9b31e367 100644 --- a/tests/ImageSharp.Tests/Drawing/SolidPolygonTests.cs +++ b/tests/ImageSharp.Tests/Drawing/SolidPolygonTests.cs @@ -38,13 +38,7 @@ namespace ImageSharp.Tests.Drawing using (PixelAccessor sourcePixels = image.Lock()) { - Assert.Equal(Color.HotPink, sourcePixels[11, 11]); - - Assert.Equal(Color.HotPink, sourcePixels[200, 150]); - - Assert.Equal(Color.HotPink, sourcePixels[50, 50]); - - Assert.NotEqual(Color.HotPink, sourcePixels[2, 2]); + Assert.Equal(Color.HotPink, sourcePixels[81, 145]); } } } @@ -129,12 +123,6 @@ namespace ImageSharp.Tests.Drawing using (PixelAccessor sourcePixels = image.Lock()) { - Assert.Equal(mergedColor, sourcePixels[11, 11]); - - Assert.Equal(mergedColor, sourcePixels[200, 150]); - - Assert.Equal(mergedColor, sourcePixels[50, 50]); - Assert.Equal(Color.Blue, sourcePixels[2, 2]); } } @@ -187,19 +175,9 @@ namespace ImageSharp.Tests.Drawing using (PixelAccessor sourcePixels = image.Lock()) { - Assert.Equal(Color.HotPink, sourcePixels[25, 35]); - - Assert.Equal(Color.HotPink, sourcePixels[50, 79]); - - Assert.Equal(Color.HotPink, sourcePixels[75, 35]); + Assert.Equal(Color.Blue, sourcePixels[30, 65]); Assert.Equal(Color.HotPink, sourcePixels[50, 50]); - - Assert.Equal(Color.Blue, sourcePixels[2, 2]); - - Assert.Equal(Color.Blue, sourcePixels[28, 60]); - - Assert.Equal(Color.Blue, sourcePixels[67, 67]); } } } From c9d40d7611ea36e65b4dfdd8faba402cd2f945df Mon Sep 17 00:00:00 2001 From: Scott Williams Date: Fri, 17 Mar 2017 14:54:38 +0000 Subject: [PATCH 02/12] get x=>width y=>height the correct way around. --- src/ImageSharp.Drawing/Brushes/PatternBrush{TColor}.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ImageSharp.Drawing/Brushes/PatternBrush{TColor}.cs b/src/ImageSharp.Drawing/Brushes/PatternBrush{TColor}.cs index 8da8c7f99c..a143d6f311 100644 --- a/src/ImageSharp.Drawing/Brushes/PatternBrush{TColor}.cs +++ b/src/ImageSharp.Drawing/Brushes/PatternBrush{TColor}.cs @@ -128,8 +128,8 @@ namespace ImageSharp.Drawing.Brushes { get { - x = x % this.pattern.Height; - y = y % this.pattern.Width; + x = x % this.pattern.Width; + y = y % this.pattern.Height; return this.pattern[x, y]; } From 39691519d9652a27242d58db01ea5a43289cb01a Mon Sep 17 00:00:00 2001 From: Scott Williams Date: Fri, 17 Mar 2017 15:28:19 +0000 Subject: [PATCH 03/12] fix pattern brushes --- .../Brushes/Brushes{TColor}.cs | 40 ++++++++++--------- .../Brushes/PatternBrush{TColor}.cs | 29 ++++++++------ .../Drawing/FillPatternTests.cs | 37 +++++++++-------- 3 files changed, 56 insertions(+), 50 deletions(-) diff --git a/src/ImageSharp.Drawing/Brushes/Brushes{TColor}.cs b/src/ImageSharp.Drawing/Brushes/Brushes{TColor}.cs index d77a6d5941..6e092bf185 100644 --- a/src/ImageSharp.Drawing/Brushes/Brushes{TColor}.cs +++ b/src/ImageSharp.Drawing/Brushes/Brushes{TColor}.cs @@ -18,10 +18,9 @@ namespace ImageSharp.Drawing.Brushes /// /// Percent10 Hatch Pattern /// - /// note 2d arrays when configured using initalizer look inverted - /// ---> Y axis + /// ---> x axis /// ^ - /// | X - axis + /// | y - axis /// | /// see PatternBrush for details about how to make new patterns work private static readonly bool[,] Percent10Pattern = @@ -37,10 +36,10 @@ namespace ImageSharp.Drawing.Brushes /// private static readonly bool[,] Percent20Pattern = { - { true, false, true, false }, - { false, false, false, false }, - { false, true, false, true }, - { false, false, false, false } + { true, false, false, false }, + { false, false, true, false }, + { true, false, false, false }, + { false, false, true, false } }; /// @@ -48,7 +47,10 @@ namespace ImageSharp.Drawing.Brushes /// private static readonly bool[,] HorizontalPattern = { - { false, true, false, false }, + { false }, + { true }, + { false }, + { false } }; /// @@ -56,7 +58,10 @@ namespace ImageSharp.Drawing.Brushes /// private static readonly bool[,] MinPattern = { - { false, false, false, true }, + { false }, + { false }, + { false }, + { true } }; /// @@ -64,10 +69,7 @@ namespace ImageSharp.Drawing.Brushes /// private static readonly bool[,] VerticalPattern = { - { false }, - { true }, - { false }, - { false } + { false, true, false, false }, }; /// @@ -75,10 +77,10 @@ namespace ImageSharp.Drawing.Brushes /// private static readonly bool[,] ForwardDiagonalPattern = { - { true, false, false, false }, - { false, true, false, false }, + { false, false, false, true }, { false, false, true, false }, - { false, false, false, true } + { false, true, false, false }, + { true, false, false, false } }; /// @@ -86,10 +88,10 @@ namespace ImageSharp.Drawing.Brushes /// private static readonly bool[,] BackwardDiagonalPattern = { - { false, false, false, true }, - { false, false, true, false }, + { true, false, false, false }, { false, true, false, false }, - { true, false, false, false } + { false, false, true, false }, + { false, false, false, true } }; /// diff --git a/src/ImageSharp.Drawing/Brushes/PatternBrush{TColor}.cs b/src/ImageSharp.Drawing/Brushes/PatternBrush{TColor}.cs index a143d6f311..9ebaf811a2 100644 --- a/src/ImageSharp.Drawing/Brushes/PatternBrush{TColor}.cs +++ b/src/ImageSharp.Drawing/Brushes/PatternBrush{TColor}.cs @@ -30,15 +30,6 @@ namespace ImageSharp.Drawing.Brushes /// 0 /// 0 /// - /// Warning when use array initializer across multiple lines the bools look inverted i.e. - /// new bool[,]{ - /// {true, false, false}, - /// {false,true, false} - /// } - /// would be - /// 10 - /// 01 - /// 00 /// /// The pixel format. public class PatternBrush : IBrush @@ -56,7 +47,18 @@ namespace ImageSharp.Drawing.Brushes /// Color of the fore. /// Color of the back. /// The pattern. - public PatternBrush(TColor foreColor, TColor backColor, Fast2DArray pattern) + public PatternBrush(TColor foreColor, TColor backColor, bool[,] pattern) + : this(foreColor, backColor, new Fast2DArray(pattern)) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// Color of the fore. + /// Color of the back. + /// The pattern. + internal PatternBrush(TColor foreColor, TColor backColor, Fast2DArray pattern) { Vector4 foreColorVector = foreColor.ToVector4(); Vector4 backColorVector = backColor.ToVector4(); @@ -131,7 +133,8 @@ namespace ImageSharp.Drawing.Brushes x = x % this.pattern.Width; y = y % this.pattern.Height; - return this.pattern[x, y]; + // 2d array index at row/column + return this.pattern[y, x]; } } @@ -156,7 +159,9 @@ namespace ImageSharp.Drawing.Brushes if (opacity > Constants.Epsilon) { Vector4 backgroundVector = this.Target[targetX, targetY].ToVector4(); - Vector4 sourceVector = this.patternVector[targetX % this.pattern.Height, targetX % this.pattern.Width]; + + // 2d array index at row/column + Vector4 sourceVector = this.patternVector[targetY % this.pattern.Height, targetX % this.pattern.Width]; Vector4 finalColor = Vector4BlendTransforms.PremultipliedLerp(backgroundVector, sourceVector, opacity); diff --git a/tests/ImageSharp.Tests/Drawing/FillPatternTests.cs b/tests/ImageSharp.Tests/Drawing/FillPatternTests.cs index d3c1877abd..56066a7aa5 100644 --- a/tests/ImageSharp.Tests/Drawing/FillPatternTests.cs +++ b/tests/ImageSharp.Tests/Drawing/FillPatternTests.cs @@ -33,8 +33,9 @@ namespace ImageSharp.Tests.Drawing { // lets pick random spots to start checking Random r = new Random(); - int xStride = expectedPattern.GetLength(1); - int yStride = expectedPattern.GetLength(0); + var expectedPatternFast = new Fast2DArray(expectedPattern); + int xStride = expectedPatternFast.Width; + int yStride = expectedPatternFast.Height; int offsetX = r.Next(image.Width / xStride) * xStride; int offsetY = r.Next(image.Height / yStride) * yStride; for (int x = 0; x < xStride; x++) @@ -43,7 +44,7 @@ namespace ImageSharp.Tests.Drawing { int actualX = x + offsetX; int actualY = y + offsetY; - Color expected = expectedPattern[y, x]; // inverted pattern + Color expected = expectedPatternFast[y, x]; // inverted pattern Color actual = sourcePixels[actualX, actualY]; if (expected != actual) { @@ -187,10 +188,10 @@ namespace ImageSharp.Tests.Drawing { Test("ForwardDiagonal", Color.Blue, Brushes.ForwardDiagonal(Color.HotPink, Color.LimeGreen), new Color[,] { - { Color.HotPink, Color.LimeGreen, Color.LimeGreen, Color.LimeGreen}, - { Color.LimeGreen, Color.HotPink, Color.LimeGreen, Color.LimeGreen}, + { Color.LimeGreen, Color.LimeGreen, Color.LimeGreen, Color.HotPink}, { Color.LimeGreen, Color.LimeGreen, Color.HotPink, Color.LimeGreen}, - { Color.LimeGreen, Color.LimeGreen, Color.LimeGreen, Color.HotPink} + { Color.LimeGreen, Color.HotPink, Color.LimeGreen, Color.LimeGreen}, + { Color.HotPink, Color.LimeGreen, Color.LimeGreen, Color.LimeGreen} }); } @@ -199,10 +200,10 @@ namespace ImageSharp.Tests.Drawing { Test("ForwardDiagonal_Transparent", Color.Blue, Brushes.ForwardDiagonal(Color.HotPink), new Color[,] { - { Color.HotPink, Color.Blue, Color.Blue, Color.Blue}, - { Color.Blue, Color.HotPink, Color.Blue, Color.Blue}, + { Color.Blue, Color.Blue, Color.Blue, Color.HotPink}, { Color.Blue, Color.Blue, Color.HotPink, Color.Blue}, - { Color.Blue, Color.Blue, Color.Blue, Color.HotPink} + { Color.Blue, Color.HotPink, Color.Blue, Color.Blue}, + { Color.HotPink, Color.Blue, Color.Blue, Color.Blue} }); } @@ -211,10 +212,10 @@ namespace ImageSharp.Tests.Drawing { Test("BackwardDiagonal", Color.Blue, Brushes.BackwardDiagonal(Color.HotPink, Color.LimeGreen), new Color[,] { - { Color.LimeGreen, Color.LimeGreen, Color.LimeGreen, Color.HotPink}, - { Color.LimeGreen, Color.LimeGreen, Color.HotPink, Color.LimeGreen}, - { Color.LimeGreen, Color.HotPink, Color.LimeGreen, Color.LimeGreen}, - { Color.HotPink, Color.LimeGreen, Color.LimeGreen, Color.LimeGreen} + { Color.HotPink, Color.LimeGreen, Color.LimeGreen, Color.LimeGreen}, + { Color.LimeGreen, Color.HotPink, Color.LimeGreen, Color.LimeGreen}, + { Color.LimeGreen, Color.LimeGreen, Color.HotPink, Color.LimeGreen}, + { Color.LimeGreen, Color.LimeGreen, Color.LimeGreen, Color.HotPink} }); } @@ -223,13 +224,11 @@ namespace ImageSharp.Tests.Drawing { Test("BackwardDiagonal_Transparent", Color.Blue, Brushes.BackwardDiagonal(Color.HotPink), new Color[,] { - { Color.Blue, Color.Blue, Color.Blue, Color.HotPink}, - { Color.Blue, Color.Blue, Color.HotPink, Color.Blue}, - { Color.Blue, Color.HotPink, Color.Blue, Color.Blue}, - { Color.HotPink, Color.Blue, Color.Blue, Color.Blue} + { Color.HotPink, Color.Blue, Color.Blue, Color.Blue}, + { Color.Blue, Color.HotPink, Color.Blue, Color.Blue}, + { Color.Blue, Color.Blue, Color.HotPink, Color.Blue}, + { Color.Blue, Color.Blue, Color.Blue, Color.HotPink} }); } - - } } From b0b9b02d1eaed2d60b0c7540fde903be7ae5c1de Mon Sep 17 00:00:00 2001 From: Scott Williams Date: Fri, 17 Mar 2017 17:19:12 +0000 Subject: [PATCH 04/12] fix comments --- src/ImageSharp/Common/Memory/BufferPointer{T}.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/ImageSharp/Common/Memory/BufferPointer{T}.cs b/src/ImageSharp/Common/Memory/BufferPointer{T}.cs index 167731e369..0673efae06 100644 --- a/src/ImageSharp/Common/Memory/BufferPointer{T}.cs +++ b/src/ImageSharp/Common/Memory/BufferPointer{T}.cs @@ -73,16 +73,16 @@ namespace ImageSharp /// public IntPtr PointerAtOffset { get; private set; } - /// - /// Gets or sets the pixel at the specified position. + /// + /// Gets the element at the specified position. /// - /// The x-coordinate of the pixel. Must be greater than or equal to zero and less than the width of the image. - /// The at the specified position. - public T this[int x] + /// The index from the start of this Pointer to the required element. + /// The at the specified position. + public T this[int index] { get { - void* ptr = ((byte*)this.PointerAtOffset) + (x * Unsafe.SizeOf()); + byte* ptr = ((byte*)this.PointerAtOffset) + BufferPointer.SizeOf(index); return Unsafe.Read(ptr); } } From 928ac5960a5d225fe06f74bb57a54d9c21914b7b Mon Sep 17 00:00:00 2001 From: Scott Williams Date: Fri, 17 Mar 2017 18:58:27 +0000 Subject: [PATCH 05/12] fixed comment --- src/ImageSharp.Drawing/Brushes/Processors/BrushApplicator.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ImageSharp.Drawing/Brushes/Processors/BrushApplicator.cs b/src/ImageSharp.Drawing/Brushes/Processors/BrushApplicator.cs index 49c405abf1..87cf949e8f 100644 --- a/src/ImageSharp.Drawing/Brushes/Processors/BrushApplicator.cs +++ b/src/ImageSharp.Drawing/Brushes/Processors/BrushApplicator.cs @@ -27,7 +27,7 @@ namespace ImageSharp.Drawing.Processors } /// - /// The destinaion + /// Gets the destinaion /// protected PixelAccessor Target { get; } From 026474dd7e85733b60988b0f7e8f1b1e39676e91 Mon Sep 17 00:00:00 2001 From: Scott Williams Date: Fri, 17 Mar 2017 19:37:13 +0000 Subject: [PATCH 06/12] code cleanup --- .../Brushes/ImageBrush{TColor}.cs | 4 +- .../Brushes/PatternBrush{TColor}.cs | 4 +- .../Brushes/Processors/BrushApplicator.cs | 4 +- .../Brushes/RecolorBrush{TColor}.cs | 4 +- .../Brushes/SolidBrush{TColor}.cs | 4 +- src/ImageSharp.Drawing/Paths/ShapeRegion.cs | 25 +---------- .../Processors/FillRegionProcessor.cs | 4 +- src/ImageSharp.Drawing/Region.cs | 16 ++----- .../Drawing/FillPatternTests.cs | 2 +- .../Drawing/Paths/ShapeRegionTests.cs | 43 +------------------ 10 files changed, 19 insertions(+), 91 deletions(-) diff --git a/src/ImageSharp.Drawing/Brushes/ImageBrush{TColor}.cs b/src/ImageSharp.Drawing/Brushes/ImageBrush{TColor}.cs index 400c85bc70..f3ea81cf61 100644 --- a/src/ImageSharp.Drawing/Brushes/ImageBrush{TColor}.cs +++ b/src/ImageSharp.Drawing/Brushes/ImageBrush{TColor}.cs @@ -116,9 +116,9 @@ namespace ImageSharp.Drawing.Brushes { using (PinnedBuffer buffer = new PinnedBuffer(scanline)) { - var slice = buffer.Slice(offset); + BufferPointer slice = buffer.Slice(offset); - for (var xPos = 0; xPos < scanlineWidth; xPos++) + for (int xPos = 0; xPos < scanlineWidth; xPos++) { int targetX = xPos + x; int targetY = y; diff --git a/src/ImageSharp.Drawing/Brushes/PatternBrush{TColor}.cs b/src/ImageSharp.Drawing/Brushes/PatternBrush{TColor}.cs index 9ebaf811a2..55152d234f 100644 --- a/src/ImageSharp.Drawing/Brushes/PatternBrush{TColor}.cs +++ b/src/ImageSharp.Drawing/Brushes/PatternBrush{TColor}.cs @@ -148,9 +148,9 @@ namespace ImageSharp.Drawing.Brushes { using (PinnedBuffer buffer = new PinnedBuffer(scanline)) { - var slice = buffer.Slice(offset); + BufferPointer slice = buffer.Slice(offset); - for (var xPos = 0; xPos < scanlineWidth; xPos++) + for (int xPos = 0; xPos < scanlineWidth; xPos++) { int targetX = xPos + x; int targetY = y; diff --git a/src/ImageSharp.Drawing/Brushes/Processors/BrushApplicator.cs b/src/ImageSharp.Drawing/Brushes/Processors/BrushApplicator.cs index 87cf949e8f..679d871700 100644 --- a/src/ImageSharp.Drawing/Brushes/Processors/BrushApplicator.cs +++ b/src/ImageSharp.Drawing/Brushes/Processors/BrushApplicator.cs @@ -54,9 +54,9 @@ namespace ImageSharp.Drawing.Processors { using (PinnedBuffer buffer = new PinnedBuffer(scanline)) { - var slice = buffer.Slice(offset); + BufferPointer slice = buffer.Slice(offset); - for (var xPos = 0; xPos < scanlineWidth; xPos++) + for (int xPos = 0; xPos < scanlineWidth; xPos++) { int targetX = xPos + x; int targetY = y; diff --git a/src/ImageSharp.Drawing/Brushes/RecolorBrush{TColor}.cs b/src/ImageSharp.Drawing/Brushes/RecolorBrush{TColor}.cs index 158f0309f8..92e5191610 100644 --- a/src/ImageSharp.Drawing/Brushes/RecolorBrush{TColor}.cs +++ b/src/ImageSharp.Drawing/Brushes/RecolorBrush{TColor}.cs @@ -140,9 +140,9 @@ namespace ImageSharp.Drawing.Brushes { using (PinnedBuffer buffer = new PinnedBuffer(scanline)) { - var slice = buffer.Slice(offset); + BufferPointer slice = buffer.Slice(offset); - for (var xPos = 0; xPos < scanlineWidth; xPos++) + for (int xPos = 0; xPos < scanlineWidth; xPos++) { int targetX = xPos + x; int targetY = y; diff --git a/src/ImageSharp.Drawing/Brushes/SolidBrush{TColor}.cs b/src/ImageSharp.Drawing/Brushes/SolidBrush{TColor}.cs index 67d4844358..bdac7fdc71 100644 --- a/src/ImageSharp.Drawing/Brushes/SolidBrush{TColor}.cs +++ b/src/ImageSharp.Drawing/Brushes/SolidBrush{TColor}.cs @@ -88,9 +88,9 @@ namespace ImageSharp.Drawing.Brushes { using (PinnedBuffer buffer = new PinnedBuffer(scanline)) { - var slice = buffer.Slice(offset); + BufferPointer slice = buffer.Slice(offset); - for (var xPos = 0; xPos < scanlineWidth; xPos++) + for (int xPos = 0; xPos < scanlineWidth; xPos++) { int targetX = xPos + x; int targetY = y; diff --git a/src/ImageSharp.Drawing/Paths/ShapeRegion.cs b/src/ImageSharp.Drawing/Paths/ShapeRegion.cs index 9a350857cf..0868945318 100644 --- a/src/ImageSharp.Drawing/Paths/ShapeRegion.cs +++ b/src/ImageSharp.Drawing/Paths/ShapeRegion.cs @@ -40,30 +40,7 @@ namespace ImageSharp.Drawing public override Rectangle Bounds { get; } /// - public override int ScanX(float 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.Shared.Rent(length); - try - { - int count = this.Shape.FindIntersections(start, end, innerbuffer, length, 0); - - for (int i = 0; i < count; i++) - { - buffer[i + offset] = innerbuffer[i].Y; - } - - return count; - } - finally - { - ArrayPool.Shared.Return(innerbuffer); - } - } - - /// - public override int ScanY(float y, float[] buffer, int length, int offset) + public override int Scan(float 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); diff --git a/src/ImageSharp.Drawing/Processors/FillRegionProcessor.cs b/src/ImageSharp.Drawing/Processors/FillRegionProcessor.cs index 81de21b5f1..66e1f4380c 100644 --- a/src/ImageSharp.Drawing/Processors/FillRegionProcessor.cs +++ b/src/ImageSharp.Drawing/Processors/FillRegionProcessor.cs @@ -88,7 +88,7 @@ namespace ImageSharp.Drawing.Processors try { bool scanlineDirty = true; - for (var y = minY; y < maxY; y++) + for (int y = minY; y < maxY; y++) { if (scanlineDirty) { @@ -105,7 +105,7 @@ namespace ImageSharp.Drawing.Processors float subpixelFractionPoint = subpixelFraction / subpixelCount; for (float subPixel = (float)y; subPixel < y + 1; subPixel += subpixelFraction) { - int pointsFound = region.ScanY(subPixel, buffer, maxIntersections, 0); + int pointsFound = region.Scan(subPixel, buffer, maxIntersections, 0); if (pointsFound == 0) { // nothing on this line skip diff --git a/src/ImageSharp.Drawing/Region.cs b/src/ImageSharp.Drawing/Region.cs index f62fb0b032..8cab885021 100644 --- a/src/ImageSharp.Drawing/Region.cs +++ b/src/ImageSharp.Drawing/Region.cs @@ -19,28 +19,18 @@ namespace ImageSharp.Drawing /// Gets the bounding box that entirely surrounds this region. /// /// - /// This should always contains all possible points returned from either or . + /// This should always contains all possible points returned from . /// public abstract Rectangle Bounds { get; } /// - /// Scans the X axis for intersections. - /// - /// The position along the X axis to find intersections. - /// The buffer. - /// The length. - /// The offset. - /// The number of intersections found. - public abstract int ScanX(float x, float[] buffer, int length, int offset); - - /// - /// Scans the Y axis for intersections. + /// Scans the X axis for intersections at the Y axis position. /// /// The position along the y axis to find intersections. /// The buffer. /// The length. /// The offset. /// The number of intersections found. - public abstract int ScanY(float y, float[] buffer, int length, int offset); + public abstract int Scan(float y, float[] buffer, int length, int offset); } } \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Drawing/FillPatternTests.cs b/tests/ImageSharp.Tests/Drawing/FillPatternTests.cs index 56066a7aa5..8162bc5319 100644 --- a/tests/ImageSharp.Tests/Drawing/FillPatternTests.cs +++ b/tests/ImageSharp.Tests/Drawing/FillPatternTests.cs @@ -33,7 +33,7 @@ namespace ImageSharp.Tests.Drawing { // lets pick random spots to start checking Random r = new Random(); - var expectedPatternFast = new Fast2DArray(expectedPattern); + Fast2DArray expectedPatternFast = new Fast2DArray(expectedPattern); int xStride = expectedPatternFast.Width; int yStride = expectedPatternFast.Height; int offsetX = r.Next(image.Width / xStride) * xStride; diff --git a/tests/ImageSharp.Tests/Drawing/Paths/ShapeRegionTests.cs b/tests/ImageSharp.Tests/Drawing/Paths/ShapeRegionTests.cs index aa7c0575c7..78c3492332 100644 --- a/tests/ImageSharp.Tests/Drawing/Paths/ShapeRegionTests.cs +++ b/tests/ImageSharp.Tests/Drawing/Paths/ShapeRegionTests.cs @@ -67,25 +67,6 @@ namespace ImageSharp.Tests.Drawing.Paths pathMock.Verify(x => x.MaxIntersections); } - [Fact] - public void ShapeRegionFromPathScanXProxyToShape() - { - int xToScan = 10; - ShapeRegion region = new ShapeRegion(pathMock.Object); - - pathMock.Setup(x => x.FindIntersections(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) - .Callback((s, e, b, c, o) => { - Assert.Equal(xToScan, s.X); - Assert.Equal(xToScan, e.X); - Assert.True(s.Y < bounds.Top); - Assert.True(e.Y > bounds.Bottom); - }).Returns(0); - - int i = region.ScanX(xToScan, new float[0], 0, 0); - - pathMock.Verify(x => x.FindIntersections(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); - } - [Fact] public void ShapeRegionFromPathScanYProxyToShape() { @@ -100,27 +81,7 @@ namespace ImageSharp.Tests.Drawing.Paths Assert.True(e.X > bounds.Right); }).Returns(0); - int i = region.ScanY(yToScan, new float[0], 0, 0); - - pathMock.Verify(x => x.FindIntersections(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); - } - - - [Fact] - public void ShapeRegionFromShapeScanXProxyToShape() - { - int xToScan = 10; - ShapeRegion region = new ShapeRegion(pathMock.Object); - - pathMock.Setup(x => x.FindIntersections(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) - .Callback((s, e, b, c, o) => { - Assert.Equal(xToScan, s.X); - Assert.Equal(xToScan, e.X); - Assert.True(s.Y < bounds.Top); - Assert.True(e.Y > bounds.Bottom); - }).Returns(0); - - int i = region.ScanX(xToScan, new float[0], 0, 0); + int i = region.Scan(yToScan, new float[0], 0, 0); pathMock.Verify(x => x.FindIntersections(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); } @@ -139,7 +100,7 @@ namespace ImageSharp.Tests.Drawing.Paths Assert.True(e.X > bounds.Right); }).Returns(0); - int i = region.ScanY(yToScan, new float[0], 0, 0); + int i = region.Scan(yToScan, new float[0], 0, 0); pathMock.Verify(x => x.FindIntersections(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); } From 66bece5e765cd958cd68301d3140660f2ac09d9f Mon Sep 17 00:00:00 2001 From: Scott Williams Date: Fri, 17 Mar 2017 20:37:15 +0000 Subject: [PATCH 07/12] Add test coving subpixel counter --- .../Drawing/FillRegionProcessorTests.cs | 46 +++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 tests/ImageSharp.Tests/Drawing/FillRegionProcessorTests.cs diff --git a/tests/ImageSharp.Tests/Drawing/FillRegionProcessorTests.cs b/tests/ImageSharp.Tests/Drawing/FillRegionProcessorTests.cs new file mode 100644 index 0000000000..db9686f3d2 --- /dev/null +++ b/tests/ImageSharp.Tests/Drawing/FillRegionProcessorTests.cs @@ -0,0 +1,46 @@ + +namespace ImageSharp.Tests.Drawing +{ + using System; + using System.IO; + using ImageSharp; + using ImageSharp.Drawing.Brushes; + using Processing; + using System.Collections.Generic; + using Xunit; + using ImageSharp.Drawing; + using System.Numerics; + using SixLabors.Shapes; + using ImageSharp.Drawing.Processors; + using ImageSharp.Drawing.Pens; + using Moq; + using System.Collections.Immutable; + + public class FillRegionProcessorTests + { + [Theory] + [InlineData(true, 1, 4)] + [InlineData(true, 2, 4)] + [InlineData(true, 5, 5)] + [InlineData(true, 8, 8)] + [InlineData(false, 8, 4)] + [InlineData(false, 16, 4)] // we always do 4 sub=pixels when antialising is off. + public void MinimumAntialiasSubpixelDepth(bool antialias, int antialiasSubpixelDepth, int expectedAntialiasSubpixelDepth) + { + var bounds = new ImageSharp.Rectangle(0, 0, 1, 1); + + Mock> brush = new Mock>(); + Mock region = new Mock(); + region.Setup(x => x.Bounds).Returns(bounds); + + GraphicsOptions options = new GraphicsOptions(antialias) { + AntialiasSubpixelDepth = 1 + }; + FillRegionProcessor processor = new FillRegionProcessor(brush.Object, region.Object, options); + Image img = new Image(1, 1); + processor.Apply(img, bounds); + + region.Verify(x => x.Scan(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Exactly(4)); + } + } +} From 6e73df417ef8c20bdf5d1d8c922354567621e893 Mon Sep 17 00:00:00 2001 From: Scott Williams Date: Fri, 17 Mar 2017 21:30:29 +0000 Subject: [PATCH 08/12] TextGraphicsOptions Tests --- .../Drawing/Text/TextGraphicsOptionsTests.cs | 43 +++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 tests/ImageSharp.Tests/Drawing/Text/TextGraphicsOptionsTests.cs diff --git a/tests/ImageSharp.Tests/Drawing/Text/TextGraphicsOptionsTests.cs b/tests/ImageSharp.Tests/Drawing/Text/TextGraphicsOptionsTests.cs new file mode 100644 index 0000000000..a291a7d18b --- /dev/null +++ b/tests/ImageSharp.Tests/Drawing/Text/TextGraphicsOptionsTests.cs @@ -0,0 +1,43 @@ + +namespace ImageSharp.Tests.Drawing.Text +{ + using ImageSharp.Drawing; + using SixLabors.Fonts; + using System; + using System.Collections.Generic; + using System.Linq; + using System.Numerics; + using System.Threading.Tasks; + using Xunit; + + public class TextGraphicsOptionsTests + { + [Fact] + public void ExplicitCastOfGraphicsOptions() + { + GraphicsOptions opt = new GraphicsOptions(false) + { + AntialiasSubpixelDepth = 99 + }; + + TextGraphicsOptions textOptions = opt; + + Assert.Equal(false, textOptions.Antialias); + Assert.Equal(99, textOptions.AntialiasSubpixelDepth); + } + + [Fact] + public void ImplicitCastToGraphicsOptions() + { + TextGraphicsOptions textOptions = new TextGraphicsOptions(false) + { + AntialiasSubpixelDepth = 99 + }; + + GraphicsOptions opt = (GraphicsOptions)textOptions; + + Assert.Equal(false, opt.Antialias); + Assert.Equal(99, opt.AntialiasSubpixelDepth); + } + } +} \ No newline at end of file From 7eea486a69e2f139dfa635bffae38b000634a8f6 Mon Sep 17 00:00:00 2001 From: Scott Williams Date: Sat, 18 Mar 2017 09:46:03 +0000 Subject: [PATCH 09/12] add guards to verify scanlineBuffer size --- src/ImageSharp.Drawing/Brushes/ImageBrush{TColor}.cs | 7 +++++-- src/ImageSharp.Drawing/Brushes/PatternBrush{TColor}.cs | 7 +++++-- .../Brushes/Processors/BrushApplicator.cs | 9 ++++++--- src/ImageSharp.Drawing/Brushes/RecolorBrush{TColor}.cs | 7 +++++-- src/ImageSharp.Drawing/Brushes/SolidBrush{TColor}.cs | 7 +++++-- 5 files changed, 26 insertions(+), 11 deletions(-) diff --git a/src/ImageSharp.Drawing/Brushes/ImageBrush{TColor}.cs b/src/ImageSharp.Drawing/Brushes/ImageBrush{TColor}.cs index f3ea81cf61..636f3a5a4c 100644 --- a/src/ImageSharp.Drawing/Brushes/ImageBrush{TColor}.cs +++ b/src/ImageSharp.Drawing/Brushes/ImageBrush{TColor}.cs @@ -112,9 +112,12 @@ namespace ImageSharp.Drawing.Brushes this.source.Dispose(); } - internal override void Apply(float[] scanline, int scanlineWidth, int offset, int x, int y) + /// + internal override void Apply(float[] scanlineBuffer, int scanlineWidth, int offset, int x, int y) { - using (PinnedBuffer buffer = new PinnedBuffer(scanline)) + DebugGuard.MustBeGreaterThanOrEqualTo(scanlineBuffer.Length, offset + scanlineWidth, nameof(scanlineWidth)); + + using (PinnedBuffer buffer = new PinnedBuffer(scanlineBuffer)) { BufferPointer slice = buffer.Slice(offset); diff --git a/src/ImageSharp.Drawing/Brushes/PatternBrush{TColor}.cs b/src/ImageSharp.Drawing/Brushes/PatternBrush{TColor}.cs index 55152d234f..c718ce1f4a 100644 --- a/src/ImageSharp.Drawing/Brushes/PatternBrush{TColor}.cs +++ b/src/ImageSharp.Drawing/Brushes/PatternBrush{TColor}.cs @@ -144,9 +144,12 @@ namespace ImageSharp.Drawing.Brushes // noop } - internal override void Apply(float[] scanline, int scanlineWidth, int offset, int x, int y) + /// + internal override void Apply(float[] scanlineBuffer, int scanlineWidth, int offset, int x, int y) { - using (PinnedBuffer buffer = new PinnedBuffer(scanline)) + DebugGuard.MustBeGreaterThanOrEqualTo(scanlineBuffer.Length, offset + scanlineWidth, nameof(scanlineWidth)); + + using (PinnedBuffer buffer = new PinnedBuffer(scanlineBuffer)) { BufferPointer slice = buffer.Slice(offset); diff --git a/src/ImageSharp.Drawing/Brushes/Processors/BrushApplicator.cs b/src/ImageSharp.Drawing/Brushes/Processors/BrushApplicator.cs index 679d871700..a4f1eeaed2 100644 --- a/src/ImageSharp.Drawing/Brushes/Processors/BrushApplicator.cs +++ b/src/ImageSharp.Drawing/Brushes/Processors/BrushApplicator.cs @@ -45,14 +45,17 @@ namespace ImageSharp.Drawing.Processors /// /// Applies the opactiy weighting for each pixel in a scanline to the target based on the pattern contained in the brush. /// - /// The a collection of opacity values between 0 and 1 to be merged with the burshed color value before being applied to the target. + /// The a collection of opacity values between 0 and 1 to be merged with the brushed color value before being applied to the target. /// The number of pixels effected by this scanline. /// The offset fromthe begining of the opacity data starts. /// The x position in the target pixel space that the start of the scanline data corresponds to. /// The y position in the target pixel space that whole scanline corresponds to. - internal virtual void Apply(float[] scanline, int scanlineWidth, int offset, int x, int y) + /// scanlineBuffer will be > scanlineWidth but provide and offset in case we want to share a larger buffer across runs. + internal virtual void Apply(float[] scanlineBuffer, int scanlineWidth, int offset, int x, int y) { - using (PinnedBuffer buffer = new PinnedBuffer(scanline)) + DebugGuard.MustBeGreaterThanOrEqualTo(scanlineBuffer.Length, offset + scanlineWidth, nameof(scanlineWidth)); + + using (PinnedBuffer buffer = new PinnedBuffer(scanlineBuffer)) { BufferPointer slice = buffer.Slice(offset); diff --git a/src/ImageSharp.Drawing/Brushes/RecolorBrush{TColor}.cs b/src/ImageSharp.Drawing/Brushes/RecolorBrush{TColor}.cs index 92e5191610..cdfc23b913 100644 --- a/src/ImageSharp.Drawing/Brushes/RecolorBrush{TColor}.cs +++ b/src/ImageSharp.Drawing/Brushes/RecolorBrush{TColor}.cs @@ -136,9 +136,12 @@ namespace ImageSharp.Drawing.Brushes { } - internal override void Apply(float[] scanline, int scanlineWidth, int offset, int x, int y) + /// + internal override void Apply(float[] scanlineBuffer, int scanlineWidth, int offset, int x, int y) { - using (PinnedBuffer buffer = new PinnedBuffer(scanline)) + DebugGuard.MustBeGreaterThanOrEqualTo(scanlineBuffer.Length, offset + scanlineWidth, nameof(scanlineWidth)); + + using (PinnedBuffer buffer = new PinnedBuffer(scanlineBuffer)) { BufferPointer slice = buffer.Slice(offset); diff --git a/src/ImageSharp.Drawing/Brushes/SolidBrush{TColor}.cs b/src/ImageSharp.Drawing/Brushes/SolidBrush{TColor}.cs index bdac7fdc71..e3413328e0 100644 --- a/src/ImageSharp.Drawing/Brushes/SolidBrush{TColor}.cs +++ b/src/ImageSharp.Drawing/Brushes/SolidBrush{TColor}.cs @@ -84,9 +84,12 @@ namespace ImageSharp.Drawing.Brushes // noop } - internal override void Apply(float[] scanline, int scanlineWidth, int offset, int x, int y) + /// + internal override void Apply(float[] scanlineBuffer, int scanlineWidth, int offset, int x, int y) { - using (PinnedBuffer buffer = new PinnedBuffer(scanline)) + DebugGuard.MustBeGreaterThanOrEqualTo(scanlineBuffer.Length, offset + scanlineWidth, nameof(scanlineWidth)); + + using (PinnedBuffer buffer = new PinnedBuffer(scanlineBuffer)) { BufferPointer slice = buffer.Slice(offset); From e72f6fb1babec221aa69bc9126e27c7cee739289 Mon Sep 17 00:00:00 2001 From: Scott Williams Date: Sat, 18 Mar 2017 09:51:35 +0000 Subject: [PATCH 10/12] fix broken comment --- src/ImageSharp.Drawing/Brushes/Processors/BrushApplicator.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ImageSharp.Drawing/Brushes/Processors/BrushApplicator.cs b/src/ImageSharp.Drawing/Brushes/Processors/BrushApplicator.cs index a4f1eeaed2..2c460e88e0 100644 --- a/src/ImageSharp.Drawing/Brushes/Processors/BrushApplicator.cs +++ b/src/ImageSharp.Drawing/Brushes/Processors/BrushApplicator.cs @@ -47,7 +47,7 @@ namespace ImageSharp.Drawing.Processors /// /// The a collection of opacity values between 0 and 1 to be merged with the brushed color value before being applied to the target. /// The number of pixels effected by this scanline. - /// The offset fromthe begining of the opacity data starts. + /// The offset fromthe begining of the opacity data starts. /// The x position in the target pixel space that the start of the scanline data corresponds to. /// The y position in the target pixel space that whole scanline corresponds to. /// scanlineBuffer will be > scanlineWidth but provide and offset in case we want to share a larger buffer across runs. From d0c0dee58229aac1211e6afe237e041319e5bbff Mon Sep 17 00:00:00 2001 From: Scott Williams Date: Sat, 18 Mar 2017 11:46:36 +0000 Subject: [PATCH 11/12] Improve test coverage - fixed bug in pattern brush and added test coverage - change DebugGuard -> Guard - Update SixLabors.Shapes to version not doing array allocation on hot path. --- .../Brushes/ImageBrush{TColor}.cs | 2 +- .../Brushes/PatternBrush{TColor}.cs | 7 ++--- .../Brushes/RecolorBrush{TColor}.cs | 2 +- .../Brushes/SolidBrush{TColor}.cs | 2 +- .../ImageSharp.Drawing.csproj | 2 +- .../Drawing/FillPolygon.cs | 26 +++++++++++++++++++ .../Drawing/SolidPolygonTests.cs | 26 +++++++++++++++++++ 7 files changed, 60 insertions(+), 7 deletions(-) diff --git a/src/ImageSharp.Drawing/Brushes/ImageBrush{TColor}.cs b/src/ImageSharp.Drawing/Brushes/ImageBrush{TColor}.cs index 636f3a5a4c..080111f610 100644 --- a/src/ImageSharp.Drawing/Brushes/ImageBrush{TColor}.cs +++ b/src/ImageSharp.Drawing/Brushes/ImageBrush{TColor}.cs @@ -115,7 +115,7 @@ namespace ImageSharp.Drawing.Brushes /// internal override void Apply(float[] scanlineBuffer, int scanlineWidth, int offset, int x, int y) { - DebugGuard.MustBeGreaterThanOrEqualTo(scanlineBuffer.Length, offset + scanlineWidth, nameof(scanlineWidth)); + Guard.MustBeGreaterThanOrEqualTo(scanlineBuffer.Length, offset + scanlineWidth, nameof(scanlineWidth)); using (PinnedBuffer buffer = new PinnedBuffer(scanlineBuffer)) { diff --git a/src/ImageSharp.Drawing/Brushes/PatternBrush{TColor}.cs b/src/ImageSharp.Drawing/Brushes/PatternBrush{TColor}.cs index c718ce1f4a..2b4d3ec738 100644 --- a/src/ImageSharp.Drawing/Brushes/PatternBrush{TColor}.cs +++ b/src/ImageSharp.Drawing/Brushes/PatternBrush{TColor}.cs @@ -86,6 +86,7 @@ namespace ImageSharp.Drawing.Brushes internal PatternBrush(PatternBrush brush) { this.pattern = brush.pattern; + this.patternVector = brush.patternVector; } /// @@ -112,7 +113,7 @@ namespace ImageSharp.Drawing.Brushes /// The pattern. /// The patternVector. public PatternBrushApplicator(PixelAccessor sourcePixels, Fast2DArray pattern, Fast2DArray patternVector) - : base(sourcePixels) + : base(sourcePixels) { this.pattern = pattern; this.patternVector = patternVector; @@ -147,7 +148,7 @@ namespace ImageSharp.Drawing.Brushes /// internal override void Apply(float[] scanlineBuffer, int scanlineWidth, int offset, int x, int y) { - DebugGuard.MustBeGreaterThanOrEqualTo(scanlineBuffer.Length, offset + scanlineWidth, nameof(scanlineWidth)); + Guard.MustBeGreaterThanOrEqualTo(scanlineBuffer.Length, offset + scanlineWidth, nameof(scanlineWidth)); using (PinnedBuffer buffer = new PinnedBuffer(scanlineBuffer)) { @@ -164,7 +165,7 @@ namespace ImageSharp.Drawing.Brushes Vector4 backgroundVector = this.Target[targetX, targetY].ToVector4(); // 2d array index at row/column - Vector4 sourceVector = this.patternVector[targetY % this.pattern.Height, targetX % this.pattern.Width]; + Vector4 sourceVector = this.patternVector[targetY % this.patternVector.Height, targetX % this.patternVector.Width]; Vector4 finalColor = Vector4BlendTransforms.PremultipliedLerp(backgroundVector, sourceVector, opacity); diff --git a/src/ImageSharp.Drawing/Brushes/RecolorBrush{TColor}.cs b/src/ImageSharp.Drawing/Brushes/RecolorBrush{TColor}.cs index cdfc23b913..0c6e86643f 100644 --- a/src/ImageSharp.Drawing/Brushes/RecolorBrush{TColor}.cs +++ b/src/ImageSharp.Drawing/Brushes/RecolorBrush{TColor}.cs @@ -139,7 +139,7 @@ namespace ImageSharp.Drawing.Brushes /// internal override void Apply(float[] scanlineBuffer, int scanlineWidth, int offset, int x, int y) { - DebugGuard.MustBeGreaterThanOrEqualTo(scanlineBuffer.Length, offset + scanlineWidth, nameof(scanlineWidth)); + Guard.MustBeGreaterThanOrEqualTo(scanlineBuffer.Length, offset + scanlineWidth, nameof(scanlineWidth)); using (PinnedBuffer buffer = new PinnedBuffer(scanlineBuffer)) { diff --git a/src/ImageSharp.Drawing/Brushes/SolidBrush{TColor}.cs b/src/ImageSharp.Drawing/Brushes/SolidBrush{TColor}.cs index e3413328e0..e001829b01 100644 --- a/src/ImageSharp.Drawing/Brushes/SolidBrush{TColor}.cs +++ b/src/ImageSharp.Drawing/Brushes/SolidBrush{TColor}.cs @@ -87,7 +87,7 @@ namespace ImageSharp.Drawing.Brushes /// internal override void Apply(float[] scanlineBuffer, int scanlineWidth, int offset, int x, int y) { - DebugGuard.MustBeGreaterThanOrEqualTo(scanlineBuffer.Length, offset + scanlineWidth, nameof(scanlineWidth)); + Guard.MustBeGreaterThanOrEqualTo(scanlineBuffer.Length, offset + scanlineWidth, nameof(scanlineWidth)); using (PinnedBuffer buffer = new PinnedBuffer(scanlineBuffer)) { diff --git a/src/ImageSharp.Drawing/ImageSharp.Drawing.csproj b/src/ImageSharp.Drawing/ImageSharp.Drawing.csproj index f690dac8d5..e15bfe74ba 100644 --- a/src/ImageSharp.Drawing/ImageSharp.Drawing.csproj +++ b/src/ImageSharp.Drawing/ImageSharp.Drawing.csproj @@ -40,7 +40,7 @@ All - + ..\..\ImageSharp.ruleset diff --git a/tests/ImageSharp.Benchmarks/Drawing/FillPolygon.cs b/tests/ImageSharp.Benchmarks/Drawing/FillPolygon.cs index 1eafbe077f..782306deb7 100644 --- a/tests/ImageSharp.Benchmarks/Drawing/FillPolygon.cs +++ b/tests/ImageSharp.Benchmarks/Drawing/FillPolygon.cs @@ -9,6 +9,7 @@ namespace ImageSharp.Benchmarks using System.Drawing.Drawing2D; using System.IO; using System.Numerics; + using SixLabors.Shapes; using BenchmarkDotNet.Attributes; @@ -17,6 +18,15 @@ namespace ImageSharp.Benchmarks public class FillPolygon : BenchmarkBase { + private readonly Polygon shape; + + public FillPolygon() + { + this.shape = new SixLabors.Shapes.Polygon(new LinearLineSegment(new Vector2(10, 10), + new Vector2(550, 50), + new Vector2(200, 400))); + } + [Benchmark(Baseline = true, Description = "System.Drawing Fill Polygon")] public void DrawSolidPolygonSystemDrawing() { @@ -60,5 +70,21 @@ namespace ImageSharp.Benchmarks } } } + + [Benchmark(Description = "ImageSharp Fill Polygon - cached shape")] + public void DrawSolidPolygonCoreCahced() + { + using (CoreImage image = new CoreImage(800, 800)) + { + image.Fill( + CoreColor.HotPink, + this.shape); + + using (MemoryStream ms = new MemoryStream()) + { + image.SaveAsBmp(ms); + } + } + } } } \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Drawing/SolidPolygonTests.cs b/tests/ImageSharp.Tests/Drawing/SolidPolygonTests.cs index 6f9b31e367..79363480fc 100644 --- a/tests/ImageSharp.Tests/Drawing/SolidPolygonTests.cs +++ b/tests/ImageSharp.Tests/Drawing/SolidPolygonTests.cs @@ -43,6 +43,32 @@ namespace ImageSharp.Tests.Drawing } } + [Fact] + public void ImageShouldBeOverlayedByFilledPolygonWithPattern() + { + string path = this.CreateOutputDirectory("Drawing", "FilledPolygons"); + Vector2[] simplePath = new[] { + new Vector2(10, 10), + new Vector2(200, 150), + new Vector2(50, 300) + }; + + using (Image image = new Image(500, 500)) + { + using (FileStream output = File.OpenWrite($"{path}/Pattern.png")) + { + image + .FillPolygon(Brushes.Horizontal(Color.HotPink), simplePath, new GraphicsOptions(true)) + .Save(output); + } + + using (PixelAccessor sourcePixels = image.Lock()) + { + Assert.Equal(Color.HotPink, sourcePixels[81, 145]); + } + } + } + [Fact] public void ImageShouldBeOverlayedByFilledPolygonNoAntialias() { From c0c10aeb2d78850cae4cf1336190558edab870b7 Mon Sep 17 00:00:00 2001 From: Scott Williams Date: Sat, 18 Mar 2017 12:39:03 +0000 Subject: [PATCH 12/12] skip building branches with a PR open prevent building both branch and pr/merge when an active PR is open --- appveyor.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/appveyor.yml b/appveyor.yml index c456a8d722..89cb576539 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,6 +1,9 @@ version: 1.0.0.{build} image: Visual Studio 2017 +# prevent the double build when a branch has an active PR +skip_branch_with_pr: true + init: - ps: iex ((new-object net.webclient).DownloadString('https://gist.githubusercontent.com/PureKrome/0f79e25693d574807939/raw/8cf3160c9516ef1f4effc825c0a44acc918a0b5a/appveyor-build-info.ps'))