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]); } } }