From e768b091988dfb5bd5212fea679e0949312dd201 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Wed, 24 May 2017 10:23:10 +1000 Subject: [PATCH 01/25] Internalize PixelAccessor --- src/ImageSharp.Drawing/Brushes/IBrush.cs | 4 +- .../Brushes/ImageBrush{TPixel}.cs | 37 +++--- .../Brushes/PatternBrush{TPixel}.cs | 18 +-- .../Brushes/Processors/BrushApplicator.cs | 9 +- .../Brushes/RecolorBrush{TPixel}.cs | 22 ++-- .../Brushes/SolidBrush{TPixel}.cs | 14 +-- src/ImageSharp.Drawing/Pens/IPen.cs | 4 +- src/ImageSharp.Drawing/Pens/Pen{TPixel}.cs | 18 +-- .../Processors/DrawPathProcessor.cs | 9 +- .../Processors/FillProcessor.cs | 7 +- .../Processors/FillRegionProcessor.cs | 7 +- .../Dithering/ErrorDiffusion/ErrorDiffuser.cs | 14 +-- .../ErrorDiffusion/IErrorDiffuser.cs | 8 +- .../Dithering/Ordered/IOrderedDither.cs | 4 +- .../Dithering/Ordered/OrderedDither4x4.cs | 6 +- src/ImageSharp/Image/IImageBase{TPixel}.cs | 9 -- src/ImageSharp/Image/ImageBase{TPixel}.cs | 118 +++++++++++++++++- src/ImageSharp/Image/PixelAccessor{TPixel}.cs | 4 +- src/ImageSharp/Memory/Buffer2DExtensions.cs | 2 +- .../ErrorDiffusionDitherProcessor.cs | 17 ++- .../Binarization/OrderedDitherProcessor.cs | 21 ++-- .../Quantizers/OctreeQuantizer{TPixel}.cs | 4 +- .../Quantizers/PaletteQuantizer{TPixel}.cs | 4 +- .../Quantizers/Quantizer{TPixel}.cs | 45 +++---- .../Quantizers/WuQuantizer{TPixel}.cs | 6 +- 25 files changed, 248 insertions(+), 163 deletions(-) diff --git a/src/ImageSharp.Drawing/Brushes/IBrush.cs b/src/ImageSharp.Drawing/Brushes/IBrush.cs index 9534c7a882..8cb7317823 100644 --- a/src/ImageSharp.Drawing/Brushes/IBrush.cs +++ b/src/ImageSharp.Drawing/Brushes/IBrush.cs @@ -22,7 +22,7 @@ namespace ImageSharp.Drawing /// /// Creates the applicator for this brush. /// - /// The pixel source. + /// The source image. /// The region the brush will be applied to. /// The graphic options /// @@ -32,6 +32,6 @@ namespace ImageSharp.Drawing /// The when being applied to things like shapes would usually be the /// bounding box of the shape not necessarily the bounds of the whole image /// - BrushApplicator CreateApplicator(PixelAccessor pixelSource, RectangleF region, GraphicsOptions options); + BrushApplicator CreateApplicator(ImageBase source, RectangleF region, GraphicsOptions options); } } \ No newline at end of file diff --git a/src/ImageSharp.Drawing/Brushes/ImageBrush{TPixel}.cs b/src/ImageSharp.Drawing/Brushes/ImageBrush{TPixel}.cs index 6f851e5c3b..59dbd39266 100644 --- a/src/ImageSharp.Drawing/Brushes/ImageBrush{TPixel}.cs +++ b/src/ImageSharp.Drawing/Brushes/ImageBrush{TPixel}.cs @@ -6,7 +6,6 @@ namespace ImageSharp.Drawing.Brushes { using System; - using System.Numerics; using ImageSharp.Memory; using ImageSharp.PixelFormats; @@ -22,21 +21,21 @@ namespace ImageSharp.Drawing.Brushes /// /// The image to paint. /// - private readonly IImageBase image; + private readonly ImageBase image; /// /// Initializes a new instance of the class. /// /// The image. - public ImageBrush(IImageBase image) + public ImageBrush(ImageBase image) { this.image = image; } /// - public BrushApplicator CreateApplicator(PixelAccessor sourcePixels, RectangleF region, GraphicsOptions options) + public BrushApplicator CreateApplicator(ImageBase source, RectangleF region, GraphicsOptions options) { - return new ImageBrushApplicator(sourcePixels, this.image, region, options); + return new ImageBrushApplicator(source, this.image, region, options); } /// @@ -45,9 +44,9 @@ namespace ImageSharp.Drawing.Brushes private class ImageBrushApplicator : BrushApplicator { /// - /// The source pixel accessor. + /// The source image. /// - private readonly PixelAccessor source; + private readonly ImageBase source; /// /// The y-length. @@ -72,20 +71,14 @@ namespace ImageSharp.Drawing.Brushes /// /// Initializes a new instance of the class. /// - /// - /// The image. - /// - /// - /// The region. - /// + /// The target image. + /// The image. + /// The region. /// The options - /// - /// The sourcePixels. - /// - public ImageBrushApplicator(PixelAccessor sourcePixels, IImageBase image, RectangleF region, GraphicsOptions options) - : base(sourcePixels, options) + public ImageBrushApplicator(ImageBase target, ImageBase image, RectangleF region, GraphicsOptions options) + : base(target, options) { - this.source = image.Lock(); + this.source = image; this.xLength = image.Width; this.yLength = image.Height; this.offsetY = (int)MathF.Max(MathF.Floor(region.Top), 0); @@ -119,9 +112,9 @@ namespace ImageSharp.Drawing.Brushes /// internal override void Apply(Span scanline, int x, int y) { - // create a span for colors - using (Buffer amountBuffer = new Buffer(scanline.Length)) - using (Buffer overlay = new Buffer(scanline.Length)) + // Create a span for colors + using (var amountBuffer = new Buffer(scanline.Length)) + using (var overlay = new Buffer(scanline.Length)) { int sourceY = (y - this.offsetY) % this.yLength; int offsetX = x - this.offsetX; diff --git a/src/ImageSharp.Drawing/Brushes/PatternBrush{TPixel}.cs b/src/ImageSharp.Drawing/Brushes/PatternBrush{TPixel}.cs index 90990e54a9..5dd57bda66 100644 --- a/src/ImageSharp.Drawing/Brushes/PatternBrush{TPixel}.cs +++ b/src/ImageSharp.Drawing/Brushes/PatternBrush{TPixel}.cs @@ -62,8 +62,8 @@ namespace ImageSharp.Drawing.Brushes /// The pattern. internal PatternBrush(TPixel foreColor, TPixel backColor, Fast2DArray pattern) { - Vector4 foreColorVector = foreColor.ToVector4(); - Vector4 backColorVector = backColor.ToVector4(); + var foreColorVector = foreColor.ToVector4(); + var 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++) @@ -92,9 +92,9 @@ namespace ImageSharp.Drawing.Brushes } /// - public BrushApplicator CreateApplicator(PixelAccessor sourcePixels, RectangleF region, GraphicsOptions options) + public BrushApplicator CreateApplicator(ImageBase source, RectangleF region, GraphicsOptions options) { - return new PatternBrushApplicator(sourcePixels, this.pattern, this.patternVector, options); + return new PatternBrushApplicator(source, this.pattern, this.patternVector, options); } /// @@ -111,12 +111,12 @@ namespace ImageSharp.Drawing.Brushes /// /// Initializes a new instance of the class. /// - /// The sourcePixels. + /// The source image. /// The pattern. /// The patternVector. /// The options - public PatternBrushApplicator(PixelAccessor sourcePixels, Fast2DArray pattern, Fast2DArray patternVector, GraphicsOptions options) - : base(sourcePixels, options) + public PatternBrushApplicator(ImageBase source, Fast2DArray pattern, Fast2DArray patternVector, GraphicsOptions options) + : base(source, options) { this.pattern = pattern; this.patternVector = patternVector; @@ -152,8 +152,8 @@ namespace ImageSharp.Drawing.Brushes internal override void Apply(Span scanline, int x, int y) { int patternY = y % this.pattern.Height; - using (Buffer amountBuffer = new Buffer(scanline.Length)) - using (Buffer overlay = new Buffer(scanline.Length)) + using (var amountBuffer = new Buffer(scanline.Length)) + using (var overlay = new Buffer(scanline.Length)) { for (int i = 0; i < scanline.Length; i++) { diff --git a/src/ImageSharp.Drawing/Brushes/Processors/BrushApplicator.cs b/src/ImageSharp.Drawing/Brushes/Processors/BrushApplicator.cs index 29629324ab..29c625d7f8 100644 --- a/src/ImageSharp.Drawing/Brushes/Processors/BrushApplicator.cs +++ b/src/ImageSharp.Drawing/Brushes/Processors/BrushApplicator.cs @@ -6,7 +6,6 @@ namespace ImageSharp.Drawing.Processors { using System; - using System.Numerics; using ImageSharp.Memory; using ImageSharp.PixelFormats; @@ -24,7 +23,7 @@ namespace ImageSharp.Drawing.Processors /// /// The target. /// The options. - internal BrushApplicator(PixelAccessor target, GraphicsOptions options) + internal BrushApplicator(ImageBase target, GraphicsOptions options) { this.Target = target; @@ -41,7 +40,7 @@ namespace ImageSharp.Drawing.Processors /// /// Gets the destinaion /// - protected PixelAccessor Target { get; } + protected ImageBase Target { get; } /// /// Gets the blend percentage @@ -68,8 +67,8 @@ namespace ImageSharp.Drawing.Processors /// scanlineBuffer will be > scanlineWidth but provide and offset in case we want to share a larger buffer across runs. internal virtual void Apply(Span scanline, int x, int y) { - using (Buffer amountBuffer = new Buffer(scanline.Length)) - using (Buffer overlay = new Buffer(scanline.Length)) + using (var amountBuffer = new Buffer(scanline.Length)) + using (var overlay = new Buffer(scanline.Length)) { for (int i = 0; i < scanline.Length; i++) { diff --git a/src/ImageSharp.Drawing/Brushes/RecolorBrush{TPixel}.cs b/src/ImageSharp.Drawing/Brushes/RecolorBrush{TPixel}.cs index 64b91e3844..96d824b2fc 100644 --- a/src/ImageSharp.Drawing/Brushes/RecolorBrush{TPixel}.cs +++ b/src/ImageSharp.Drawing/Brushes/RecolorBrush{TPixel}.cs @@ -57,9 +57,9 @@ namespace ImageSharp.Drawing.Brushes public TPixel TargeTPixel { get; } /// - public BrushApplicator CreateApplicator(PixelAccessor sourcePixels, RectangleF region, GraphicsOptions options) + public BrushApplicator CreateApplicator(ImageBase source, RectangleF region, GraphicsOptions options) { - return new RecolorBrushApplicator(sourcePixels, this.SourceColor, this.TargeTPixel, this.Threshold, options); + return new RecolorBrushApplicator(source, this.SourceColor, this.TargeTPixel, this.Threshold, options); } /// @@ -87,22 +87,22 @@ namespace ImageSharp.Drawing.Brushes /// /// Initializes a new instance of the class. /// - /// The source pixels. + /// The source image. /// Color of the source. /// Color of the target. /// The threshold . /// The options - public RecolorBrushApplicator(PixelAccessor sourcePixels, TPixel sourceColor, TPixel targetColor, float threshold, GraphicsOptions options) - : base(sourcePixels, options) + public RecolorBrushApplicator(ImageBase source, TPixel sourceColor, TPixel targetColor, float threshold, GraphicsOptions options) + : base(source, options) { this.sourceColor = sourceColor.ToVector4(); this.targetColor = targetColor.ToVector4(); this.targetColorPixel = targetColor; - // Lets hack a min max extreams for a color space by letteing the IPackedPixel clamp our values to something in the correct spaces :) - TPixel maxColor = default(TPixel); + // Lets hack a min max extreams for a color space by letting the IPackedPixel clamp our values to something in the correct spaces :) + var maxColor = default(TPixel); maxColor.PackFromVector4(new Vector4(float.MaxValue)); - TPixel minColor = default(TPixel); + var minColor = default(TPixel); minColor.PackFromVector4(new Vector4(float.MinValue)); this.threshold = Vector4.DistanceSquared(maxColor.ToVector4(), minColor.ToVector4()) * threshold; } @@ -121,7 +121,7 @@ namespace ImageSharp.Drawing.Brushes { // Offset the requested pixel by the value in the rectangle (the shapes position) TPixel result = this.Target[x, y]; - Vector4 background = result.ToVector4(); + var background = result.ToVector4(); float distance = Vector4.DistanceSquared(background, this.sourceColor); if (distance <= this.threshold) { @@ -144,8 +144,8 @@ namespace ImageSharp.Drawing.Brushes /// internal override void Apply(Span scanline, int x, int y) { - using (Buffer amountBuffer = new Buffer(scanline.Length)) - using (Buffer overlay = new Buffer(scanline.Length)) + using (var amountBuffer = new Buffer(scanline.Length)) + using (var overlay = new Buffer(scanline.Length)) { for (int i = 0; i < scanline.Length; i++) { diff --git a/src/ImageSharp.Drawing/Brushes/SolidBrush{TPixel}.cs b/src/ImageSharp.Drawing/Brushes/SolidBrush{TPixel}.cs index 28f7b0e454..453b4d29ef 100644 --- a/src/ImageSharp.Drawing/Brushes/SolidBrush{TPixel}.cs +++ b/src/ImageSharp.Drawing/Brushes/SolidBrush{TPixel}.cs @@ -42,9 +42,9 @@ namespace ImageSharp.Drawing.Brushes public TPixel Color => this.color; /// - public BrushApplicator CreateApplicator(PixelAccessor sourcePixels, RectangleF region, GraphicsOptions options) + public BrushApplicator CreateApplicator(ImageBase source, RectangleF region, GraphicsOptions options) { - return new SolidBrushApplicator(sourcePixels, this.color, options); + return new SolidBrushApplicator(source, this.color, options); } /// @@ -55,13 +55,13 @@ namespace ImageSharp.Drawing.Brushes /// /// Initializes a new instance of the class. /// + /// The source image. /// The color. /// The options - /// The sourcePixels. - public SolidBrushApplicator(PixelAccessor sourcePixels, TPixel color, GraphicsOptions options) - : base(sourcePixels, options) + public SolidBrushApplicator(ImageBase source, TPixel color, GraphicsOptions options) + : base(source, options) { - this.Colors = new Buffer(sourcePixels.Width); + this.Colors = new Buffer(source.Width); for (int i = 0; i < this.Colors.Length; i++) { this.Colors[i] = color; @@ -94,7 +94,7 @@ namespace ImageSharp.Drawing.Brushes { Span destinationRow = this.Target.GetRowSpan(x, y).Slice(0, scanline.Length); - using (Buffer amountBuffer = new Buffer(scanline.Length)) + using (var amountBuffer = new Buffer(scanline.Length)) { for (int i = 0; i < scanline.Length; i++) { diff --git a/src/ImageSharp.Drawing/Pens/IPen.cs b/src/ImageSharp.Drawing/Pens/IPen.cs index d488dbfb0a..81d273091e 100644 --- a/src/ImageSharp.Drawing/Pens/IPen.cs +++ b/src/ImageSharp.Drawing/Pens/IPen.cs @@ -18,7 +18,7 @@ namespace ImageSharp.Drawing.Pens /// /// Creates the applicator for applying this pen to an Image /// - /// The pixel source. + /// The source image. /// The region the pen will be applied to. /// The currently active graphic options. /// @@ -27,6 +27,6 @@ namespace ImageSharp.Drawing.Pens /// /// The when being applied to things like shapes would usually be the bounding box of the shape not necessarily the shape of the whole image. /// - PenApplicator CreateApplicator(PixelAccessor pixelSource, RectangleF region, GraphicsOptions options); + PenApplicator CreateApplicator(ImageBase source, RectangleF region, GraphicsOptions options); } } diff --git a/src/ImageSharp.Drawing/Pens/Pen{TPixel}.cs b/src/ImageSharp.Drawing/Pens/Pen{TPixel}.cs index 1da50e0d6c..53a3c8c995 100644 --- a/src/ImageSharp.Drawing/Pens/Pen{TPixel}.cs +++ b/src/ImageSharp.Drawing/Pens/Pen{TPixel}.cs @@ -101,7 +101,7 @@ namespace ImageSharp.Drawing.Pens /// /// Creates the applicator for applying this pen to an Image /// - /// The source pixels. + /// The source image. /// The region the pen will be applied to. /// The Graphics options /// @@ -111,16 +111,16 @@ namespace ImageSharp.Drawing.Pens /// The when being applied to things like shapes would ussually be the /// bounding box of the shape not necorserrally the shape of the whole image /// - public PenApplicator CreateApplicator(PixelAccessor sourcePixels, RectangleF region, GraphicsOptions options) + public PenApplicator CreateApplicator(ImageBase source, RectangleF region, GraphicsOptions options) { if (this.pattern == null || this.pattern.Length < 2) { // if there is only one item in the pattern then 100% of it will // be solid so use the quicker applicator - return new SolidPenApplicator(sourcePixels, this.Brush, region, this.Width, options); + return new SolidPenApplicator(source, this.Brush, region, this.Width, options); } - return new PatternPenApplicator(sourcePixels, this.Brush, region, this.Width, this.pattern, options); + return new PatternPenApplicator(source, this.Brush, region, this.Width, this.pattern, options); } private class SolidPenApplicator : PenApplicator @@ -128,7 +128,7 @@ namespace ImageSharp.Drawing.Pens private readonly BrushApplicator brush; private readonly float halfWidth; - public SolidPenApplicator(PixelAccessor sourcePixels, IBrush brush, RectangleF region, float width, GraphicsOptions options) + public SolidPenApplicator(ImageBase sourcePixels, IBrush brush, RectangleF region, float width, GraphicsOptions options) { this.brush = brush.CreateApplicator(sourcePixels, region, options); this.halfWidth = width / 2; @@ -147,7 +147,7 @@ namespace ImageSharp.Drawing.Pens public override ColoredPointInfo GetColor(int x, int y, PointInfo info) { - ColoredPointInfo result = default(ColoredPointInfo); + var result = default(ColoredPointInfo); result.Color = this.brush[x, y]; if (info.DistanceFromPath < this.halfWidth) @@ -171,9 +171,9 @@ namespace ImageSharp.Drawing.Pens private readonly float[] pattern; private readonly float totalLength; - public PatternPenApplicator(PixelAccessor sourcePixels, IBrush brush, RectangleF region, float width, float[] pattern, GraphicsOptions options) + public PatternPenApplicator(ImageBase source, IBrush brush, RectangleF region, float width, float[] pattern, GraphicsOptions options) { - this.brush = brush.CreateApplicator(sourcePixels, region, options); + this.brush = brush.CreateApplicator(source, region, options); this.halfWidth = width / 2; this.totalLength = 0; @@ -200,7 +200,7 @@ namespace ImageSharp.Drawing.Pens public override ColoredPointInfo GetColor(int x, int y, PointInfo info) { - ColoredPointInfo infoResult = default(ColoredPointInfo); + var infoResult = default(ColoredPointInfo); infoResult.DistanceFromElement = float.MaxValue; // is really outside the element float length = info.DistanceAlongPath % this.totalLength; diff --git a/src/ImageSharp.Drawing/Processors/DrawPathProcessor.cs b/src/ImageSharp.Drawing/Processors/DrawPathProcessor.cs index d1332c4355..860c4c4f06 100644 --- a/src/ImageSharp.Drawing/Processors/DrawPathProcessor.cs +++ b/src/ImageSharp.Drawing/Processors/DrawPathProcessor.cs @@ -56,8 +56,7 @@ namespace ImageSharp.Drawing.Processors /// protected override void OnApply(ImageBase source, Rectangle sourceRectangle) { - using (PixelAccessor sourcePixels = source.Lock()) - using (PenApplicator applicator = this.Pen.CreateApplicator(sourcePixels, this.Path.Bounds, this.Options)) + using (PenApplicator applicator = this.Pen.CreateApplicator(source, this.Path.Bounds, this.Options)) { Rectangle rect = RectangleF.Ceiling(applicator.RequiredRegion); @@ -99,8 +98,8 @@ namespace ImageSharp.Drawing.Processors { int offsetY = y - polyStartY; - using (Buffer amount = new Buffer(width)) - using (Buffer colors = new Buffer(width)) + using (var amount = new Buffer(width)) + using (var colors = new Buffer(width)) { for (int i = 0; i < width; i++) { @@ -112,7 +111,7 @@ namespace ImageSharp.Drawing.Processors colors[i] = color.Color; } - Span destination = sourcePixels.GetRowSpan(offsetY).Slice(minX - startX, width); + Span destination = source.GetRowSpan(offsetY).Slice(minX - startX, width); blender.Blend(destination, destination, colors, amount); } }); diff --git a/src/ImageSharp.Drawing/Processors/FillProcessor.cs b/src/ImageSharp.Drawing/Processors/FillProcessor.cs index fa6f48156c..8c7cd4e8c5 100644 --- a/src/ImageSharp.Drawing/Processors/FillProcessor.cs +++ b/src/ImageSharp.Drawing/Processors/FillProcessor.cs @@ -66,12 +66,11 @@ namespace ImageSharp.Drawing.Processors int width = maxX - minX; - // we could possibly do some optermising by having knowledge about the individual brushes operate + // We could possibly do some optimization by having knowledge about the individual brushes operate // for example If brush is SolidBrush then we could just get the color upfront // and skip using the IBrushApplicator?. - using (PixelAccessor sourcePixels = source.Lock()) - using (Buffer amount = new Buffer(width)) - using (BrushApplicator applicator = this.brush.CreateApplicator(sourcePixels, sourceRectangle, this.options)) + using (var amount = new Buffer(width)) + using (BrushApplicator applicator = this.brush.CreateApplicator(source, sourceRectangle, this.options)) { for (int i = 0; i < width; i++) { diff --git a/src/ImageSharp.Drawing/Processors/FillRegionProcessor.cs b/src/ImageSharp.Drawing/Processors/FillRegionProcessor.cs index a57be3a5a1..ae828e112c 100644 --- a/src/ImageSharp.Drawing/Processors/FillRegionProcessor.cs +++ b/src/ImageSharp.Drawing/Processors/FillRegionProcessor.cs @@ -7,6 +7,7 @@ namespace ImageSharp.Drawing.Processors { using System; using System.Buffers; + using System.Runtime.CompilerServices; using Drawing; using ImageSharp.Memory; @@ -89,12 +90,11 @@ namespace ImageSharp.Drawing.Processors } } - using (PixelAccessor sourcePixels = source.Lock()) - using (BrushApplicator applicator = this.Brush.CreateApplicator(sourcePixels, rect, this.Options)) + using (BrushApplicator applicator = this.Brush.CreateApplicator(source, rect, this.Options)) { float[] buffer = arrayPool.Rent(maxIntersections); int scanlineWidth = maxX - minX; - using (Buffer scanline = new Buffer(scanlineWidth)) + using (var scanline = new Buffer(scanlineWidth)) { try { @@ -193,6 +193,7 @@ namespace ImageSharp.Drawing.Processors } } + [MethodImpl(MethodImplOptions.AggressiveInlining)] private static void Swap(float[] data, int left, int right) { float tmp = data[left]; diff --git a/src/ImageSharp/Dithering/ErrorDiffusion/ErrorDiffuser.cs b/src/ImageSharp/Dithering/ErrorDiffusion/ErrorDiffuser.cs index 7a5fabdb3a..0c4192a872 100644 --- a/src/ImageSharp/Dithering/ErrorDiffusion/ErrorDiffuser.cs +++ b/src/ImageSharp/Dithering/ErrorDiffusion/ErrorDiffuser.cs @@ -71,7 +71,7 @@ namespace ImageSharp.Dithering /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Dither(PixelAccessor pixels, TPixel source, TPixel transformed, int x, int y, int width, int height) + public void Dither(ImageBase pixels, TPixel source, TPixel transformed, int x, int y, int width, int height) where TPixel : struct, IPixel { this.Dither(pixels, source, transformed, x, y, width, height, true); @@ -79,13 +79,13 @@ namespace ImageSharp.Dithering /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Dither(PixelAccessor pixels, TPixel source, TPixel transformed, int x, int y, int width, int height, bool replacePixel) + public void Dither(ImageBase image, TPixel source, TPixel transformed, int x, int y, int width, int height, bool replacePixel) where TPixel : struct, IPixel { if (replacePixel) { // Assign the transformed pixel to the array. - pixels[x, y] = transformed; + image[x, y] = transformed; } // Calculate the error @@ -111,14 +111,14 @@ namespace ImageSharp.Dithering continue; } - Vector4 coefficientVector = new Vector4(coefficient); - Vector4 offsetColor = pixels[matrixX, matrixY].ToVector4(); + var coefficientVector = new Vector4(coefficient); + var offsetColor = image[matrixX, matrixY].ToVector4(); Vector4 result = ((error * coefficientVector) / this.divisorVector) + offsetColor; result.W = offsetColor.W; - TPixel packed = default(TPixel); + var packed = default(TPixel); packed.PackFromVector4(result); - pixels[matrixX, matrixY] = packed; + image[matrixX, matrixY] = packed; } } } diff --git a/src/ImageSharp/Dithering/ErrorDiffusion/IErrorDiffuser.cs b/src/ImageSharp/Dithering/ErrorDiffusion/IErrorDiffuser.cs index f49e7e62d2..bc785e8971 100644 --- a/src/ImageSharp/Dithering/ErrorDiffusion/IErrorDiffuser.cs +++ b/src/ImageSharp/Dithering/ErrorDiffusion/IErrorDiffuser.cs @@ -15,7 +15,7 @@ namespace ImageSharp.Dithering /// /// Transforms the image applying the dither matrix. This method alters the input pixels array /// - /// The pixel accessor + /// The image /// The source pixel /// The transformed pixel /// The column index. @@ -23,13 +23,13 @@ namespace ImageSharp.Dithering /// The image width. /// The image height. /// The pixel format. - void Dither(PixelAccessor pixels, TPixel source, TPixel transformed, int x, int y, int width, int height) + void Dither(ImageBase image, TPixel source, TPixel transformed, int x, int y, int width, int height) where TPixel : struct, IPixel; /// /// Transforms the image applying the dither matrix. This method alters the input pixels array /// - /// The pixel accessor + /// The image /// The source pixel /// The transformed pixel /// The column index. @@ -41,7 +41,7 @@ namespace ImageSharp.Dithering /// Generally this would be true for standard two-color dithering but when used in conjunction with color quantization this should be false. /// /// The pixel format. - void Dither(PixelAccessor pixels, TPixel source, TPixel transformed, int x, int y, int width, int height, bool replacePixel) + void Dither(ImageBase image, TPixel source, TPixel transformed, int x, int y, int width, int height, bool replacePixel) where TPixel : struct, IPixel; } } diff --git a/src/ImageSharp/Dithering/Ordered/IOrderedDither.cs b/src/ImageSharp/Dithering/Ordered/IOrderedDither.cs index 3f7cf49885..c69cddefed 100644 --- a/src/ImageSharp/Dithering/Ordered/IOrderedDither.cs +++ b/src/ImageSharp/Dithering/Ordered/IOrderedDither.cs @@ -15,7 +15,7 @@ namespace ImageSharp.Dithering /// /// Transforms the image applying the dither matrix. This method alters the input pixels array /// - /// The pixel accessor + /// The image /// The source pixel /// The color to apply to the pixels above the threshold. /// The color to apply to the pixels below the threshold. @@ -26,7 +26,7 @@ namespace ImageSharp.Dithering /// The image width. /// The image height. /// The pixel format. - void Dither(PixelAccessor pixels, TPixel source, TPixel upper, TPixel lower, byte[] bytes, int index, int x, int y, int width, int height) + void Dither(ImageBase image, TPixel source, TPixel upper, TPixel lower, byte[] bytes, int index, int x, int y, int width, int height) where TPixel : struct, IPixel; } } \ No newline at end of file diff --git a/src/ImageSharp/Dithering/Ordered/OrderedDither4x4.cs b/src/ImageSharp/Dithering/Ordered/OrderedDither4x4.cs index 48d6c3f6a4..a180888f70 100644 --- a/src/ImageSharp/Dithering/Ordered/OrderedDither4x4.cs +++ b/src/ImageSharp/Dithering/Ordered/OrderedDither4x4.cs @@ -28,14 +28,14 @@ namespace ImageSharp.Dithering.Ordered } /// - public void Dither(PixelAccessor pixels, TPixel source, TPixel upper, TPixel lower, byte[] bytes, int index, int x, int y, int width, int height) + public void Dither(ImageBase image, TPixel source, TPixel upper, TPixel lower, byte[] bytes, int index, int x, int y, int width, int height) where TPixel : struct, IPixel { // TODO: This doesn't really cut it for me. - // I'd rather be using float but we need to add some sort of movalization vector methods to all IPixel implementations + // I'd rather be using float but we need to add some sort of normalization vector methods to all IPixel implementations // before we can do that as the vectors all cover different ranges. source.ToXyzwBytes(bytes, 0); - pixels[x, y] = this.matrix[y % 3, x % 3] >= bytes[index] ? lower : upper; + image[x, y] = this.matrix[y % 3, x % 3] >= bytes[index] ? lower : upper; } } } \ No newline at end of file diff --git a/src/ImageSharp/Image/IImageBase{TPixel}.cs b/src/ImageSharp/Image/IImageBase{TPixel}.cs index 08d25709b3..f0ab5b0334 100644 --- a/src/ImageSharp/Image/IImageBase{TPixel}.cs +++ b/src/ImageSharp/Image/IImageBase{TPixel}.cs @@ -21,14 +21,5 @@ namespace ImageSharp /// of the array for calculations. Use Width * Height. /// TPixel[] Pixels { get; } - - /// - /// Locks the image providing access to the pixels. - /// - /// It is imperative that the accessor is correctly disposed off after use. - /// - /// - /// The - PixelAccessor Lock(); } } \ No newline at end of file diff --git a/src/ImageSharp/Image/ImageBase{TPixel}.cs b/src/ImageSharp/Image/ImageBase{TPixel}.cs index 4fd9d26cbf..f7b294cf01 100644 --- a/src/ImageSharp/Image/ImageBase{TPixel}.cs +++ b/src/ImageSharp/Image/ImageBase{TPixel}.cs @@ -7,6 +7,7 @@ namespace ImageSharp { using System; using System.Diagnostics; + using System.Runtime.CompilerServices; using ImageSharp.Memory; using ImageSharp.PixelFormats; @@ -36,6 +37,11 @@ namespace ImageSharp /// private TPixel[] pixelBuffer; + /// + /// The span representing the pixel buffer + /// + private Span span; + /// /// A value indicating whether this instance of the given entity has been disposed. /// @@ -129,6 +135,67 @@ namespace ImageSharp /// public Configuration Configuration { 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 y-coordinate of the pixel. Must be greater than or equal to zero and less than the height of the image. + /// The at the specified position. + public TPixel this[int x, int y] + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get + { + this.CheckCoordinates(x, y); + return this.pixelBuffer[(y * this.Width) + x]; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + set + { + this.CheckCoordinates(x, y); + this.pixelBuffer[(y * this.Width) + x] = value; + } + } + + /// + /// Gets a reference to 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 y-coordinate of the pixel. Must be greater than or equal to zero and less than the height of the image. + /// The at the specified position. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ref TPixel GetPixelReference(int x, int y) + { + this.CheckCoordinates(x, y); + return ref this.pixelBuffer[(y * this.Width) + x]; + } + + /// + /// Gets a representing the row 'y' beginning from the the first pixel on that row. + /// + /// The y-coordinate of the pixel row. Must be greater than or equal to zero and less than the height of the image. + /// The + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Span GetRowSpan(int y) + { + this.CheckCoordinates(y); + return this.span.Slice(y * this.Width, this.Width); + } + + /// + /// Gets a to the row 'y' beginning from the pixel at 'x'. + /// + /// The x coordinate (position in the row) + /// The y (row) coordinate + /// The + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Span GetRowSpan(int x, int y) + { + this.CheckCoordinates(x, y); + return this.span.Slice((y * this.Width) + x, this.Width - x); + } + /// /// Applies the processor. /// @@ -152,8 +219,14 @@ namespace ImageSharp GC.SuppressFinalize(this); } - /// - public PixelAccessor Lock() + /// + /// Locks the image providing access to the pixels. + /// + /// It is imperative that the accessor is correctly disposed off after use. + /// + /// + /// The + internal PixelAccessor Lock() { return new PixelAccessor(this); } @@ -174,6 +247,7 @@ namespace ImageSharp this.Width = newWidth; this.Height = newHeight; this.pixelBuffer = newPixels; + this.span = new Span(this.pixelBuffer); } /// @@ -225,6 +299,7 @@ namespace ImageSharp private void RentPixels() { this.pixelBuffer = PixelDataPool.Rent(this.Width * this.Height); + this.span = new Span(this.pixelBuffer); } /// @@ -234,6 +309,7 @@ namespace ImageSharp { PixelDataPool.Return(this.pixelBuffer); this.pixelBuffer = null; + this.span = null; } /// @@ -243,5 +319,43 @@ namespace ImageSharp { Array.Clear(this.pixelBuffer, 0, this.Width * this.Height); } + + /// + /// Checks the coordinates to ensure they are within bounds. + /// + /// The y-coordinate of the pixel. Must be greater than zero and less than the height of the image. + /// + /// Thrown if the coordinates are not within the bounds of the image. + /// + [Conditional("DEBUG")] + private void CheckCoordinates(int y) + { + if (y < 0 || y >= this.Height) + { + throw new ArgumentOutOfRangeException(nameof(y), y, $"{y} is outwith the image bounds."); + } + } + + /// + /// Checks the coordinates to ensure they are within bounds. + /// + /// The x-coordinate of the pixel. Must be greater than zero and less than the width of the image. + /// The y-coordinate of the pixel. Must be greater than zero and less than the height of the image. + /// + /// Thrown if the coordinates are not within the bounds of the image. + /// + [Conditional("DEBUG")] + private void CheckCoordinates(int x, int y) + { + if (x < 0 || x >= this.Width) + { + throw new ArgumentOutOfRangeException(nameof(x), x, $"{x} is outwith the image bounds."); + } + + if (y < 0 || y >= this.Height) + { + throw new ArgumentOutOfRangeException(nameof(y), y, $"{y} is outwith the image bounds."); + } + } } } \ No newline at end of file diff --git a/src/ImageSharp/Image/PixelAccessor{TPixel}.cs b/src/ImageSharp/Image/PixelAccessor{TPixel}.cs index a54c03b635..240e65c828 100644 --- a/src/ImageSharp/Image/PixelAccessor{TPixel}.cs +++ b/src/ImageSharp/Image/PixelAccessor{TPixel}.cs @@ -17,7 +17,7 @@ namespace ImageSharp /// Provides per-pixel access to generic pixels. /// /// The pixel format. - public sealed class PixelAccessor : IDisposable, IBuffer2D + internal sealed class PixelAccessor : IDisposable, IBuffer2D where TPixel : struct, IPixel { /// @@ -128,12 +128,14 @@ namespace ImageSharp /// The at the specified position. public TPixel this[int x, int y] { + [MethodImpl(MethodImplOptions.AggressiveInlining)] get { this.CheckCoordinates(x, y); return this.PixelArray[(y * this.Width) + x]; } + [MethodImpl(MethodImplOptions.AggressiveInlining)] set { this.CheckCoordinates(x, y); diff --git a/src/ImageSharp/Memory/Buffer2DExtensions.cs b/src/ImageSharp/Memory/Buffer2DExtensions.cs index 51e5582815..046bfd81fc 100644 --- a/src/ImageSharp/Memory/Buffer2DExtensions.cs +++ b/src/ImageSharp/Memory/Buffer2DExtensions.cs @@ -29,7 +29,7 @@ namespace ImageSharp.Memory } /// - /// Gets a to the row 'y' beginning from the pixel at 'x'. + /// Gets a to the row 'y' beginning from the pixel at the first pixel on that row. /// /// The buffer /// The y (row) coordinate diff --git a/src/ImageSharp/Processing/Processors/Binarization/ErrorDiffusionDitherProcessor.cs b/src/ImageSharp/Processing/Processors/Binarization/ErrorDiffusionDitherProcessor.cs index af2d9f760a..cb6f2fce4b 100644 --- a/src/ImageSharp/Processing/Processors/Binarization/ErrorDiffusionDitherProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Binarization/ErrorDiffusionDitherProcessor.cs @@ -85,18 +85,15 @@ namespace ImageSharp.Processing.Processors startY = 0; } - using (PixelAccessor sourcePixels = source.Lock()) + for (int y = minY; y < maxY; y++) { - for (int y = minY; y < maxY; y++) + int offsetY = y - startY; + for (int x = minX; x < maxX; x++) { - int offsetY = y - startY; - for (int x = minX; x < maxX; x++) - { - int offsetX = x - startX; - TPixel sourceColor = sourcePixels[offsetX, offsetY]; - TPixel transformedColor = sourceColor.ToVector4().X >= this.Threshold ? this.UpperColor : this.LowerColor; - this.Diffuser.Dither(sourcePixels, sourceColor, transformedColor, offsetX, offsetY, maxX, maxY); - } + int offsetX = x - startX; + TPixel sourceColor = source[offsetX, offsetY]; + TPixel transformedColor = sourceColor.ToVector4().X >= this.Threshold ? this.UpperColor : this.LowerColor; + this.Diffuser.Dither(source, sourceColor, transformedColor, offsetX, offsetY, maxX, maxY); } } } diff --git a/src/ImageSharp/Processing/Processors/Binarization/OrderedDitherProcessor.cs b/src/ImageSharp/Processing/Processors/Binarization/OrderedDitherProcessor.cs index c4d71d9afe..1bdb4d53b1 100644 --- a/src/ImageSharp/Processing/Processors/Binarization/OrderedDitherProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Binarization/OrderedDitherProcessor.cs @@ -93,21 +93,16 @@ namespace ImageSharp.Processing.Processors startY = 0; } - using (PixelAccessor sourcePixels = source.Lock()) + byte[] bytes = new byte[4]; + for (int y = minY; y < maxY; y++) { - for (int y = minY; y < maxY; y++) - { - int offsetY = y - startY; - byte[] bytes = ArrayPool.Shared.Rent(4); - - for (int x = minX; x < maxX; x++) - { - int offsetX = x - startX; - TPixel sourceColor = sourcePixels[offsetX, offsetY]; - this.Dither.Dither(sourcePixels, sourceColor, this.UpperColor, this.LowerColor, bytes, this.Index, offsetX, offsetY, maxX, maxY); - } + int offsetY = y - startY; - ArrayPool.Shared.Return(bytes); + for (int x = minX; x < maxX; x++) + { + int offsetX = x - startX; + TPixel sourceColor = source[offsetX, offsetY]; + this.Dither.Dither(source, sourceColor, this.UpperColor, this.LowerColor, bytes, this.Index, offsetX, offsetY, maxX, maxY); } } } diff --git a/src/ImageSharp/Quantizers/OctreeQuantizer{TPixel}.cs b/src/ImageSharp/Quantizers/OctreeQuantizer{TPixel}.cs index 40bce74c3f..f41272900d 100644 --- a/src/ImageSharp/Quantizers/OctreeQuantizer{TPixel}.cs +++ b/src/ImageSharp/Quantizers/OctreeQuantizer{TPixel}.cs @@ -66,7 +66,7 @@ namespace ImageSharp.Quantizers } /// - protected override void SecondPass(PixelAccessor source, byte[] output, int width, int height) + protected override void SecondPass(ImageBase source, byte[] output, int width, int height) { // Load up the values for the first pixel. We can use these to speed up the second // pass of the algorithm by avoiding transforming rows of identical color. @@ -490,7 +490,7 @@ namespace ImageSharp.Quantizers byte b = (this.blue / this.pixelCount).ToByte(); // And set the color of the palette entry - TPixel pixel = default(TPixel); + var pixel = default(TPixel); pixel.PackFromBytes(r, g, b, 255); palette[index] = pixel; diff --git a/src/ImageSharp/Quantizers/PaletteQuantizer{TPixel}.cs b/src/ImageSharp/Quantizers/PaletteQuantizer{TPixel}.cs index 7e07da6c3d..52d3f320dd 100644 --- a/src/ImageSharp/Quantizers/PaletteQuantizer{TPixel}.cs +++ b/src/ImageSharp/Quantizers/PaletteQuantizer{TPixel}.cs @@ -51,7 +51,7 @@ namespace ImageSharp.Quantizers for (int i = 0; i < constants.Length; i++) { constants[i].ToXyzwBytes(this.pixelBuffer, 0); - TPixel packed = default(TPixel); + var packed = default(TPixel); packed.PackFromBytes(this.pixelBuffer[0], this.pixelBuffer[1], this.pixelBuffer[2], this.pixelBuffer[3]); safe[i] = packed; } @@ -72,7 +72,7 @@ namespace ImageSharp.Quantizers } /// - protected override void SecondPass(PixelAccessor source, byte[] output, int width, int height) + protected override void SecondPass(ImageBase source, byte[] output, int width, int height) { // Load up the values for the first pixel. We can use these to speed up the second // pass of the algorithm by avoiding transforming rows of identical color. diff --git a/src/ImageSharp/Quantizers/Quantizer{TPixel}.cs b/src/ImageSharp/Quantizers/Quantizer{TPixel}.cs index 48f33f98b9..e994029966 100644 --- a/src/ImageSharp/Quantizers/Quantizer{TPixel}.cs +++ b/src/ImageSharp/Quantizers/Quantizer{TPixel}.cs @@ -54,35 +54,30 @@ namespace ImageSharp.Quantizers int height = image.Height; int width = image.Width; byte[] quantizedPixels = new byte[width * height]; - TPixel[] colorPalette; - using (PixelAccessor pixels = image.Lock()) + // Call the FirstPass function if not a single pass algorithm. + // For something like an Octree quantizer, this will run through + // all image pixels, build a data structure, and create a palette. + if (!this.singlePass) { - // Call the FirstPass function if not a single pass algorithm. - // For something like an Octree quantizer, this will run through - // all image pixels, build a data structure, and create a palette. - if (!this.singlePass) - { - this.FirstPass(pixels, width, height); - } + this.FirstPass(image, width, height); + } - // Collect the palette. Required before the second pass runs. - colorPalette = this.GetPalette(); + // Collect the palette. Required before the second pass runs. + TPixel[] colorPalette = this.GetPalette(); - if (this.Dither) - { - // We clone the image as we don't want to alter the original. - using (Image clone = new Image(image)) - using (PixelAccessor clonedPixels = clone.Lock()) - { - this.SecondPass(clonedPixels, quantizedPixels, width, height); - } - } - else + if (this.Dither) + { + // We clone the image as we don't want to alter the original. + using (var clone = new Image(image)) { - this.SecondPass(pixels, quantizedPixels, width, height); + this.SecondPass(clone, quantizedPixels, width, height); } } + else + { + this.SecondPass(image, quantizedPixels, width, height); + } return new QuantizedImage(width, height, colorPalette, quantizedPixels); } @@ -93,7 +88,7 @@ namespace ImageSharp.Quantizers /// The source data /// The width in pixels of the image. /// The height in pixels of the image. - protected virtual void FirstPass(PixelAccessor source, int width, int height) + protected virtual void FirstPass(ImageBase source, int width, int height) { // Loop through each row for (int y = 0; y < height; y++) @@ -114,7 +109,7 @@ namespace ImageSharp.Quantizers /// The output pixel array /// The width in pixels of the image /// The height in pixels of the image - protected abstract void SecondPass(PixelAccessor source, byte[] output, int width, int height); + protected abstract void SecondPass(ImageBase source, byte[] output, int width, int height); /// /// Override this to process the pixel in the first pass of the algorithm @@ -155,7 +150,7 @@ namespace ImageSharp.Quantizers // Not found - loop through the palette and find the nearest match. byte colorIndex = 0; float leastDistance = int.MaxValue; - Vector4 vector = pixel.ToVector4(); + var vector = pixel.ToVector4(); for (int index = 0; index < colorPalette.Length; index++) { diff --git a/src/ImageSharp/Quantizers/WuQuantizer{TPixel}.cs b/src/ImageSharp/Quantizers/WuQuantizer{TPixel}.cs index fb63c9dcd9..8d98a29d8b 100644 --- a/src/ImageSharp/Quantizers/WuQuantizer{TPixel}.cs +++ b/src/ImageSharp/Quantizers/WuQuantizer{TPixel}.cs @@ -183,7 +183,7 @@ namespace ImageSharp.Quantizers float b = Volume(this.colorCube[k], this.vmb) / weight; float a = Volume(this.colorCube[k], this.vma) / weight; - TPixel color = default(TPixel); + var color = default(TPixel); color.PackFromVector4(new Vector4(r, g, b, a) / 255F); this.palette[k] = color; } @@ -221,7 +221,7 @@ namespace ImageSharp.Quantizers } /// - protected override void FirstPass(PixelAccessor source, int width, int height) + protected override void FirstPass(ImageBase source, int width, int height) { // Build up the 3-D color histogram // Loop through each row @@ -240,7 +240,7 @@ namespace ImageSharp.Quantizers } /// - protected override void SecondPass(PixelAccessor source, byte[] output, int width, int height) + protected override void SecondPass(ImageBase source, byte[] output, int width, int height) { // Load up the values for the first pixel. We can use these to speed up the second // pass of the algorithm by avoiding transforming rows of identical color. From 0ea63d606fbdc19828dd6a4c1c290e6132e8fa5a Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Wed, 24 May 2017 10:46:30 +1000 Subject: [PATCH 02/25] Fix image disposal --- src/ImageSharp/Image/ImageBase{TPixel}.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/ImageSharp/Image/ImageBase{TPixel}.cs b/src/ImageSharp/Image/ImageBase{TPixel}.cs index f7b294cf01..eb464c74e0 100644 --- a/src/ImageSharp/Image/ImageBase{TPixel}.cs +++ b/src/ImageSharp/Image/ImageBase{TPixel}.cs @@ -309,7 +309,6 @@ namespace ImageSharp { PixelDataPool.Return(this.pixelBuffer); this.pixelBuffer = null; - this.span = null; } /// From a4ed3fe1887ad0455653e87f8f7b89dce12c1000 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Wed, 24 May 2017 10:46:54 +1000 Subject: [PATCH 03/25] Updat exunit so so testrunner works --- tests/ImageSharp.Tests/ImageSharp.Tests.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/ImageSharp.Tests/ImageSharp.Tests.csproj b/tests/ImageSharp.Tests/ImageSharp.Tests.csproj index 55b3c80e35..fbfacee32b 100644 --- a/tests/ImageSharp.Tests/ImageSharp.Tests.csproj +++ b/tests/ImageSharp.Tests/ImageSharp.Tests.csproj @@ -13,7 +13,7 @@ - + From 1aa94295efe1c8a5b842c4e9c8ea2436e4663da9 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Wed, 24 May 2017 11:51:35 +1000 Subject: [PATCH 04/25] Optimize some low hanging fruit --- .../Dithering/ErrorDiffusion/ErrorDiffuser.cs | 40 ++++++++++--------- .../ErrorDiffusionDitherProcessor.cs | 4 +- .../Quantizers/OctreeQuantizer{TPixel}.cs | 4 +- .../Quantizers/PaletteQuantizer{TPixel}.cs | 4 +- .../Quantizers/Quantizer{TPixel}.cs | 5 ++- .../Quantizers/WuQuantizer{TPixel}.cs | 4 +- 6 files changed, 38 insertions(+), 23 deletions(-) diff --git a/src/ImageSharp/Dithering/ErrorDiffusion/ErrorDiffuser.cs b/src/ImageSharp/Dithering/ErrorDiffusion/ErrorDiffuser.cs index 0c4192a872..408e6c383e 100644 --- a/src/ImageSharp/Dithering/ErrorDiffusion/ErrorDiffuser.cs +++ b/src/ImageSharp/Dithering/ErrorDiffusion/ErrorDiffuser.cs @@ -5,6 +5,7 @@ namespace ImageSharp.Dithering { + using System; using System.Numerics; using System.Runtime.CompilerServices; @@ -95,30 +96,33 @@ namespace ImageSharp.Dithering for (int row = 0; row < this.matrixHeight; row++) { int matrixY = y + row; - - for (int col = 0; col < this.matrixWidth; col++) + if (matrixY > 0 && matrixY < height) { - int matrixX = x + (col - this.startingOffset); + Span rowSpan = image.GetRowSpan(matrixY); - if (matrixX > 0 && matrixX < width && matrixY > 0 && matrixY < height) + for (int col = 0; col < this.matrixWidth; col++) { - float coefficient = this.matrix[row, col]; + int matrixX = x + (col - this.startingOffset); - // Good to disable here as we are not comparing mathematical output. - // ReSharper disable once CompareOfFloatsByEqualityOperator - if (coefficient == 0) + if (matrixX > 0 && matrixX < width) { - continue; + float coefficient = this.matrix[row, col]; + + // Good to disable here as we are not comparing mathematical output. + // ReSharper disable once CompareOfFloatsByEqualityOperator + if (coefficient == 0) + { + continue; + } + + ref TPixel pixel = ref rowSpan[matrixX]; + var offsetColor = pixel.ToVector4(); + var coefficientVector = new Vector4(coefficient); + + Vector4 result = ((error * coefficientVector) / this.divisorVector) + offsetColor; + result.W = offsetColor.W; + pixel.PackFromVector4(result); } - - var coefficientVector = new Vector4(coefficient); - var offsetColor = image[matrixX, matrixY].ToVector4(); - Vector4 result = ((error * coefficientVector) / this.divisorVector) + offsetColor; - result.W = offsetColor.W; - - var packed = default(TPixel); - packed.PackFromVector4(result); - image[matrixX, matrixY] = packed; } } } diff --git a/src/ImageSharp/Processing/Processors/Binarization/ErrorDiffusionDitherProcessor.cs b/src/ImageSharp/Processing/Processors/Binarization/ErrorDiffusionDitherProcessor.cs index cb6f2fce4b..47811f0ec2 100644 --- a/src/ImageSharp/Processing/Processors/Binarization/ErrorDiffusionDitherProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Binarization/ErrorDiffusionDitherProcessor.cs @@ -88,10 +88,12 @@ namespace ImageSharp.Processing.Processors for (int y = minY; y < maxY; y++) { int offsetY = y - startY; + Span row = source.GetRowSpan(offsetY); + for (int x = minX; x < maxX; x++) { int offsetX = x - startX; - TPixel sourceColor = source[offsetX, offsetY]; + TPixel sourceColor = row[offsetX]; TPixel transformedColor = sourceColor.ToVector4().X >= this.Threshold ? this.UpperColor : this.LowerColor; this.Diffuser.Dither(source, sourceColor, transformedColor, offsetX, offsetY, maxX, maxY); } diff --git a/src/ImageSharp/Quantizers/OctreeQuantizer{TPixel}.cs b/src/ImageSharp/Quantizers/OctreeQuantizer{TPixel}.cs index f41272900d..bd963c4527 100644 --- a/src/ImageSharp/Quantizers/OctreeQuantizer{TPixel}.cs +++ b/src/ImageSharp/Quantizers/OctreeQuantizer{TPixel}.cs @@ -78,11 +78,13 @@ namespace ImageSharp.Quantizers for (int y = 0; y < height; y++) { + Span row = source.GetRowSpan(y); + // And loop through each column for (int x = 0; x < width; x++) { // Get the pixel. - sourcePixel = source[x, y]; + sourcePixel = row[x]; // Check if this is the same as the last pixel. If so use that value // rather than calculating it again. This is an inexpensive optimization. diff --git a/src/ImageSharp/Quantizers/PaletteQuantizer{TPixel}.cs b/src/ImageSharp/Quantizers/PaletteQuantizer{TPixel}.cs index 52d3f320dd..cf3ff94eec 100644 --- a/src/ImageSharp/Quantizers/PaletteQuantizer{TPixel}.cs +++ b/src/ImageSharp/Quantizers/PaletteQuantizer{TPixel}.cs @@ -84,11 +84,13 @@ namespace ImageSharp.Quantizers for (int y = 0; y < height; y++) { + Span row = source.GetRowSpan(y); + // And loop through each column for (int x = 0; x < width; x++) { // Get the pixel. - sourcePixel = source[x, y]; + sourcePixel = row[x]; // Check if this is the same as the last pixel. If so use that value // rather than calculating it again. This is an inexpensive optimization. diff --git a/src/ImageSharp/Quantizers/Quantizer{TPixel}.cs b/src/ImageSharp/Quantizers/Quantizer{TPixel}.cs index e994029966..2e3ea7a543 100644 --- a/src/ImageSharp/Quantizers/Quantizer{TPixel}.cs +++ b/src/ImageSharp/Quantizers/Quantizer{TPixel}.cs @@ -5,6 +5,7 @@ namespace ImageSharp.Quantizers { + using System; using System.Collections.Generic; using System.Numerics; using System.Runtime.CompilerServices; @@ -93,11 +94,13 @@ namespace ImageSharp.Quantizers // Loop through each row for (int y = 0; y < height; y++) { + Span row = source.GetRowSpan(y); + // And loop through each column for (int x = 0; x < width; x++) { // Now I have the pixel, call the FirstPassQuantize function... - this.InitialQuantizePixel(source[x, y]); + this.InitialQuantizePixel(row[x]); } } } diff --git a/src/ImageSharp/Quantizers/WuQuantizer{TPixel}.cs b/src/ImageSharp/Quantizers/WuQuantizer{TPixel}.cs index 8d98a29d8b..aecca771c1 100644 --- a/src/ImageSharp/Quantizers/WuQuantizer{TPixel}.cs +++ b/src/ImageSharp/Quantizers/WuQuantizer{TPixel}.cs @@ -252,11 +252,13 @@ namespace ImageSharp.Quantizers for (int y = 0; y < height; y++) { + Span row = source.GetRowSpan(y); + // And loop through each column for (int x = 0; x < width; x++) { // Get the pixel. - sourcePixel = source[x, y]; + sourcePixel = row[x]; // Check if this is the same as the last pixel. If so use that value // rather than calculating it again. This is an inexpensive optimization. From e508d7e419c8c334a86d92fa1fc92c015bfd5f7f Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Wed, 24 May 2017 12:37:44 +1000 Subject: [PATCH 05/25] Add benchmark, update readme --- README.md | 13 ++- .../ImageSharp.Benchmarks/Image/CopyPixels.cs | 87 +++++++++++++++++-- 2 files changed, 91 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index ca24275469..0f902fc17d 100644 --- a/README.md +++ b/README.md @@ -106,17 +106,22 @@ using (Image image = Image.Load(stream)) } ``` -Setting individual pixel values is perfomed as follows: +Setting individual pixel values can perfomed as follows: ```csharp +// Individual pixels using (Image image = new Image(400, 400) -using (PixelAccessor pixels = image.Lock()) { - pixels[200, 200] = Rgba32.White; + image[200, 200] = Rgba32.White; } ``` -For advanced usage there are multiple [PixelFormat implementations](https://github.com/JimBobSquarePants/ImageSharp/tree/master/src/ImageSharp/PixelFormats) available allowing developers to implement their own color models in the same manner as Microsoft XNA Game Studio and MonoGame. +For optimized access within a loop it is recommended that the following methods are used. + +1. `image.GetRowSpan(y)` +2. `image.GetRowSPan(x, y)` + +For advanced pixel format usage there are multiple [PixelFormat implementations](https://github.com/JimBobSquarePants/ImageSharp/tree/master/src/ImageSharp/PixelFormats) available allowing developers to implement their own color models in the same manner as Microsoft XNA Game Studio and MonoGame. All in all this should allow image processing to be much more accessible to developers which has always been my goal from the start. diff --git a/tests/ImageSharp.Benchmarks/Image/CopyPixels.cs b/tests/ImageSharp.Benchmarks/Image/CopyPixels.cs index 5b5d14750a..1d4ed11936 100644 --- a/tests/ImageSharp.Benchmarks/Image/CopyPixels.cs +++ b/tests/ImageSharp.Benchmarks/Image/CopyPixels.cs @@ -5,19 +5,20 @@ namespace ImageSharp.Benchmarks.Image { + using System; using System.Threading.Tasks; using BenchmarkDotNet.Attributes; - using ImageSharp.PixelFormats; + using ImageSharp.Memory; public class CopyPixels : BenchmarkBase { - [Benchmark(Description = "Copy by Pixel")] - public Rgba32 CopyByPixel() + [Benchmark(Baseline = true, Description = "PixelAccessor Copy by indexer")] + public Rgba32 CopyByPixelAccesor() { - using (Image source = new Image(1024, 768)) - using (Image target = new Image(1024, 768)) + using (var source = new Image(1024, 768)) + using (var target = new Image(1024, 768)) { using (PixelAccessor sourcePixels = source.Lock()) using (PixelAccessor targetPixels = target.Lock()) @@ -38,5 +39,81 @@ namespace ImageSharp.Benchmarks.Image } } } + + [Benchmark(Description = "PixelAccessor Copy by Span")] + public Rgba32 CopyByPixelAccesorSpan() + { + using (var source = new Image(1024, 768)) + using (var target = new Image(1024, 768)) + { + using (PixelAccessor sourcePixels = source.Lock()) + using (PixelAccessor targetPixels = target.Lock()) + { + Parallel.For( + 0, + source.Height, + Configuration.Default.ParallelOptions, + y => + { + Span sourceRow = sourcePixels.GetRowSpan(y); + Span targetRow = targetPixels.GetRowSpan(y); + + for (int x = 0; x < source.Width; x++) + { + targetRow[x] = sourceRow[x]; + } + }); + + return targetPixels[0, 0]; + } + } + } + + [Benchmark(Description = "Copy by indexer")] + public Rgba32 Copy() + { + using (var source = new Image(1024, 768)) + using (var target = new Image(1024, 768)) + { + Parallel.For( + 0, + source.Height, + Configuration.Default.ParallelOptions, + y => + { + for (int x = 0; x < source.Width; x++) + { + target[x, y] = source[x, y]; + } + }); + + return target[0, 0]; + } + } + + [Benchmark(Description = "Copy by Span")] + public Rgba32 CopySpan() + { + using (var source = new Image(1024, 768)) + using (var target = new Image(1024, 768)) + { + Parallel.For( + 0, + source.Height, + Configuration.Default.ParallelOptions, + y => + { + Span sourceRow = source.GetRowSpan(y); + Span targetRow = target.GetRowSpan(y); + + for (int x = 0; x < source.Width; x++) + { + targetRow[x] = sourceRow[x]; + } + }); + + return target[0, 0]; + } + } } } From 49a95e4ba042a8cfebb300f3bbd860a7c02c4669 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Wed, 24 May 2017 12:58:40 +1000 Subject: [PATCH 06/25] Optimize dither --- README.md | 2 +- .../Binarization/BinaryThresholdProcessor.cs | 33 +++++++++---------- .../Binarization/OrderedDitherProcessor.cs | 3 +- 3 files changed, 18 insertions(+), 20 deletions(-) diff --git a/README.md b/README.md index 0f902fc17d..c672d7ca32 100644 --- a/README.md +++ b/README.md @@ -119,7 +119,7 @@ using (Image image = new Image(400, 400) For optimized access within a loop it is recommended that the following methods are used. 1. `image.GetRowSpan(y)` -2. `image.GetRowSPan(x, y)` +2. `image.GetRowSpan(x, y)` For advanced pixel format usage there are multiple [PixelFormat implementations](https://github.com/JimBobSquarePants/ImageSharp/tree/master/src/ImageSharp/PixelFormats) available allowing developers to implement their own color models in the same manner as Microsoft XNA Game Studio and MonoGame. diff --git a/src/ImageSharp/Processing/Processors/Binarization/BinaryThresholdProcessor.cs b/src/ImageSharp/Processing/Processors/Binarization/BinaryThresholdProcessor.cs index 5cd67f053e..a2fa1ddf39 100644 --- a/src/ImageSharp/Processing/Processors/Binarization/BinaryThresholdProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Binarization/BinaryThresholdProcessor.cs @@ -83,25 +83,22 @@ namespace ImageSharp.Processing.Processors startY = 0; } - using (PixelAccessor sourcePixels = source.Lock()) - { - Parallel.For( - minY, - maxY, - this.ParallelOptions, - y => + Parallel.For( + minY, + maxY, + this.ParallelOptions, + y => + { + Span row = source.GetRowSpan(y - startY); + + for (int x = minX; x < maxX; x++) { - int offsetY = y - startY; - for (int x = minX; x < maxX; x++) - { - int offsetX = x - startX; - TPixel color = sourcePixels[offsetX, offsetY]; - - // Any channel will do since it's Grayscale. - sourcePixels[offsetX, offsetY] = color.ToVector4().X >= threshold ? upper : lower; - } - }); - } + ref TPixel color = ref row[x - startX]; + + // Any channel will do since it's Grayscale. + color = color.ToVector4().X >= threshold ? upper : lower; + } + }); } } } \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Binarization/OrderedDitherProcessor.cs b/src/ImageSharp/Processing/Processors/Binarization/OrderedDitherProcessor.cs index 1bdb4d53b1..8983897778 100644 --- a/src/ImageSharp/Processing/Processors/Binarization/OrderedDitherProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Binarization/OrderedDitherProcessor.cs @@ -97,11 +97,12 @@ namespace ImageSharp.Processing.Processors for (int y = minY; y < maxY; y++) { int offsetY = y - startY; + Span row = source.GetRowSpan(offsetY); for (int x = minX; x < maxX; x++) { int offsetX = x - startX; - TPixel sourceColor = source[offsetX, offsetY]; + TPixel sourceColor = row[offsetX]; this.Dither.Dither(source, sourceColor, this.UpperColor, this.LowerColor, bytes, this.Index, offsetX, offsetY, maxX, maxY); } } From 5a884da4c0a24e854a90d52fe0fc25c2c40f6cec Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Wed, 24 May 2017 13:07:54 +1000 Subject: [PATCH 07/25] Optimize ColorMatrixProcessors --- .../ColorMatrix/ColorMatrixProcessor.cs | 41 ++++++------------- 1 file changed, 12 insertions(+), 29 deletions(-) diff --git a/src/ImageSharp/Processing/Processors/ColorMatrix/ColorMatrixProcessor.cs b/src/ImageSharp/Processing/Processors/ColorMatrix/ColorMatrixProcessor.cs index cfe50150fd..49af2667d4 100644 --- a/src/ImageSharp/Processing/Processors/ColorMatrix/ColorMatrixProcessor.cs +++ b/src/ImageSharp/Processing/Processors/ColorMatrix/ColorMatrixProcessor.cs @@ -7,7 +7,6 @@ namespace ImageSharp.Processing.Processors { using System; using System.Numerics; - using System.Runtime.CompilerServices; using System.Threading.Tasks; using ImageSharp.PixelFormats; @@ -61,39 +60,23 @@ namespace ImageSharp.Processing.Processors this.ParallelOptions, y => { - int offsetY = y - startY; + Span row = source.GetRowSpan(y - startY); + for (int x = minX; x < maxX; x++) { - int offsetX = x - startX; - sourcePixels[offsetX, offsetY] = this.ApplyMatrix(sourcePixels[offsetX, offsetY], matrix, compand); - } - }); - } - } + ref TPixel pixel = ref row[x - startX]; + var vector = pixel.ToVector4(); - /// - /// Applies the color matrix against the given color. - /// - /// The source color. - /// The matrix. - /// Whether to compand the color during processing. - /// - /// The . - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private TPixel ApplyMatrix(TPixel color, Matrix4x4 matrix, bool compand) - { - Vector4 vector = color.ToVector4(); + if (compand) + { + vector = vector.Expand(); + } - if (compand) - { - vector = vector.Expand(); + vector = Vector4.Transform(vector, matrix); + pixel.PackFromVector4(compand ? vector.Compress() : vector); + } + }); } - - vector = Vector4.Transform(vector, matrix); - TPixel packed = default(TPixel); - packed.PackFromVector4(compand ? vector.Compress() : vector); - return packed; } } } \ No newline at end of file From 2ed67f0562c5ecae231e22fdaeda2776473001d2 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Wed, 24 May 2017 13:43:28 +1000 Subject: [PATCH 08/25] Optimize Convolution processors --- src/ImageSharp/Image/ImageBase{TPixel}.cs | 9 +++++++ src/ImageSharp/Image/PixelAccessor{TPixel}.cs | 26 ++++++++++--------- src/ImageSharp/Memory/SpanHelper.cs | 2 +- .../Convolution/Convolution2DProcessor.cs | 18 +++++++------ .../Convolution/Convolution2PassProcessor.cs | 14 +++++----- .../Convolution/ConvolutionProcessor.cs | 16 +++++++----- 6 files changed, 51 insertions(+), 34 deletions(-) diff --git a/src/ImageSharp/Image/ImageBase{TPixel}.cs b/src/ImageSharp/Image/ImageBase{TPixel}.cs index eb464c74e0..60fcdda488 100644 --- a/src/ImageSharp/Image/ImageBase{TPixel}.cs +++ b/src/ImageSharp/Image/ImageBase{TPixel}.cs @@ -231,6 +231,15 @@ namespace ImageSharp return new PixelAccessor(this); } + /// + /// Copies the pixels to another of the same size. + /// + /// The target pixel buffer accessor. + internal void CopyTo(PixelAccessor target) + { + SpanHelper.Copy(this.span, target.PixelBuffer.Span); + } + /// /// Switches the buffers used by the image and the PixelAccessor meaning that the Image will "own" the buffer from the PixelAccessor and the PixelAccessor will now own the Images buffer. /// diff --git a/src/ImageSharp/Image/PixelAccessor{TPixel}.cs b/src/ImageSharp/Image/PixelAccessor{TPixel}.cs index 240e65c828..1d776c258a 100644 --- a/src/ImageSharp/Image/PixelAccessor{TPixel}.cs +++ b/src/ImageSharp/Image/PixelAccessor{TPixel}.cs @@ -20,6 +20,13 @@ namespace ImageSharp internal sealed class PixelAccessor : IDisposable, IBuffer2D where TPixel : struct, IPixel { +#pragma warning disable SA1401 // Fields must be private + /// + /// The containing the pixel data. + /// + internal Buffer2D PixelBuffer; +#pragma warning restore SA1401 // Fields must be private + /// /// A value indicating whether this instance of the given entity has been disposed. /// @@ -31,11 +38,6 @@ namespace ImageSharp /// private bool isDisposed; - /// - /// The containing the pixel data. - /// - private Buffer2D pixelBuffer; - /// /// Initializes a new instance of the class. /// @@ -88,7 +90,7 @@ namespace ImageSharp /// /// Gets the pixel buffer array. /// - public TPixel[] PixelArray => this.pixelBuffer.Array; + public TPixel[] PixelArray => this.PixelBuffer.Array; /// /// Gets the size of a single pixel in the number of bytes. @@ -116,7 +118,7 @@ namespace ImageSharp public ParallelOptions ParallelOptions { get; } /// - Span IBuffer2D.Span => this.pixelBuffer; + Span IBuffer2D.Span => this.PixelBuffer; private static PixelOperations Operations => PixelOperations.Instance; @@ -156,7 +158,7 @@ namespace ImageSharp // Note disposing is done. this.isDisposed = true; - this.pixelBuffer.Dispose(); + this.PixelBuffer.Dispose(); // This object will be cleaned up by the Dispose method. // Therefore, you should call GC.SuppressFinalize to @@ -171,7 +173,7 @@ namespace ImageSharp /// public void Reset() { - this.pixelBuffer.Clear(); + this.PixelBuffer.Clear(); } /// @@ -243,7 +245,7 @@ namespace ImageSharp /// If is true then caller is responsible for ensuring is called. internal TPixel[] ReturnCurrentColorsAndReplaceThemInternally(int width, int height, TPixel[] pixels) { - TPixel[] oldPixels = this.pixelBuffer.TakeArrayOwnership(); + TPixel[] oldPixels = this.PixelBuffer.TakeArrayOwnership(); this.SetPixelBufferUnsafe(width, height, pixels); return oldPixels; } @@ -254,7 +256,7 @@ namespace ImageSharp /// The target pixel buffer accessor. internal void CopyTo(PixelAccessor target) { - SpanHelper.Copy(this.pixelBuffer.Span, target.pixelBuffer.Span); + SpanHelper.Copy(this.PixelBuffer.Span, target.PixelBuffer.Span); } /// @@ -425,7 +427,7 @@ namespace ImageSharp /// The pixel buffer private void SetPixelBufferUnsafe(int width, int height, Buffer2D pixels) { - this.pixelBuffer = pixels; + this.PixelBuffer = pixels; this.Width = width; this.Height = height; diff --git a/src/ImageSharp/Memory/SpanHelper.cs b/src/ImageSharp/Memory/SpanHelper.cs index 57b7715911..0e794e1b57 100644 --- a/src/ImageSharp/Memory/SpanHelper.cs +++ b/src/ImageSharp/Memory/SpanHelper.cs @@ -70,7 +70,7 @@ namespace ImageSharp.Memory public static void Copy(Span source, Span destination) where T : struct { - Copy(source, destination, source.Length); + Copy(source, destination, Math.Min(source.Length, destination.Length)); } /// diff --git a/src/ImageSharp/Processing/Processors/Convolution/Convolution2DProcessor.cs b/src/ImageSharp/Processing/Processors/Convolution/Convolution2DProcessor.cs index 29086b53fd..b6b56adb3a 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/Convolution2DProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/Convolution2DProcessor.cs @@ -5,6 +5,7 @@ namespace ImageSharp.Processing.Processors { + using System; using System.Numerics; using System.Threading.Tasks; @@ -56,10 +57,9 @@ namespace ImageSharp.Processing.Processors int maxY = endY - 1; int maxX = endX - 1; - using (PixelAccessor targetPixels = new PixelAccessor(source.Width, source.Height)) - using (PixelAccessor sourcePixels = source.Lock()) + using (var targetPixels = new PixelAccessor(source.Width, source.Height)) { - sourcePixels.CopyTo(targetPixels); + source.CopyTo(targetPixels); Parallel.For( startY, @@ -67,6 +67,9 @@ namespace ImageSharp.Processing.Processors this.ParallelOptions, y => { + Span sourceRow = source.GetRowSpan(y); + Span targetRow = targetPixels.GetRowSpan(y); + for (int x = startX; x < endX; x++) { float rX = 0; @@ -83,6 +86,7 @@ namespace ImageSharp.Processing.Processors int offsetY = y + fyr; offsetY = offsetY.Clamp(0, maxY); + Span sourceOffsetRow = source.GetRowSpan(offsetY); for (int fx = 0; fx < kernelXWidth; fx++) { @@ -90,8 +94,7 @@ namespace ImageSharp.Processing.Processors int offsetX = x + fxr; offsetX = offsetX.Clamp(0, maxX); - - Vector4 currentColor = sourcePixels[offsetX, offsetY].ToVector4(); + var currentColor = sourceOffsetRow[offsetX].ToVector4(); if (fy < kernelXHeight) { @@ -115,9 +118,8 @@ namespace ImageSharp.Processing.Processors float green = MathF.Sqrt((gX * gX) + (gY * gY)); float blue = MathF.Sqrt((bX * bX) + (bY * bY)); - TPixel packed = default(TPixel); - packed.PackFromVector4(new Vector4(red, green, blue, sourcePixels[x, y].ToVector4().W)); - targetPixels[x, y] = packed; + ref TPixel pixel = ref targetRow[x]; + pixel.PackFromVector4(new Vector4(red, green, blue, sourceRow[x].ToVector4().W)); } }); diff --git a/src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor.cs b/src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor.cs index e391047931..efc00b08fd 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor.cs @@ -46,7 +46,7 @@ namespace ImageSharp.Processing.Processors int width = source.Width; int height = source.Height; - using (PixelAccessor firstPassPixels = new PixelAccessor(width, height)) + using (var firstPassPixels = new PixelAccessor(width, height)) using (PixelAccessor sourcePixels = source.Lock()) { this.ApplyConvolution(firstPassPixels, sourcePixels, source.Bounds, this.KernelX); @@ -84,9 +84,11 @@ namespace ImageSharp.Processing.Processors this.ParallelOptions, y => { + Span targetRow = targetPixels.GetRowSpan(y); + for (int x = startX; x < endX; x++) { - Vector4 destination = default(Vector4); + var destination = default(Vector4); // Apply each matrix multiplier to the color components for each pixel. for (int fy = 0; fy < kernelHeight; fy++) @@ -95,6 +97,7 @@ namespace ImageSharp.Processing.Processors int offsetY = y + fyr; offsetY = offsetY.Clamp(0, maxY); + Span row = sourcePixels.GetRowSpan(offsetY); for (int fx = 0; fx < kernelWidth; fx++) { @@ -103,14 +106,13 @@ namespace ImageSharp.Processing.Processors offsetX = offsetX.Clamp(0, maxX); - Vector4 currentColor = sourcePixels[offsetX, offsetY].ToVector4(); + var currentColor = row[offsetX].ToVector4(); destination += kernel[fy, fx] * currentColor; } } - TPixel packed = default(TPixel); - packed.PackFromVector4(destination); - targetPixels[x, y] = packed; + ref TPixel pixel = ref targetRow[x]; + pixel.PackFromVector4(destination); } }); } diff --git a/src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessor.cs b/src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessor.cs index 17d5e32432..06607c87a2 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessor.cs @@ -46,10 +46,9 @@ namespace ImageSharp.Processing.Processors int maxY = endY - 1; int maxX = endX - 1; - using (PixelAccessor targetPixels = new PixelAccessor(source.Width, source.Height)) - using (PixelAccessor sourcePixels = source.Lock()) + using (var targetPixels = new PixelAccessor(source.Width, source.Height)) { - sourcePixels.CopyTo(targetPixels); + source.CopyTo(targetPixels); Parallel.For( startY, @@ -57,6 +56,9 @@ namespace ImageSharp.Processing.Processors this.ParallelOptions, y => { + Span sourceRow = source.GetRowSpan(y); + Span targetRow = targetPixels.GetRowSpan(y); + for (int x = startX; x < endX; x++) { float red = 0; @@ -70,6 +72,7 @@ namespace ImageSharp.Processing.Processors int offsetY = y + fyr; offsetY = offsetY.Clamp(0, maxY); + Span sourceOffsetRow = source.GetRowSpan(offsetY); for (int fx = 0; fx < kernelLength; fx++) { @@ -78,7 +81,7 @@ namespace ImageSharp.Processing.Processors offsetX = offsetX.Clamp(0, maxX); - Vector4 currentColor = sourcePixels[offsetX, offsetY].ToVector4(); + var currentColor = sourceOffsetRow[offsetX].ToVector4(); currentColor *= this.KernelXY[fy, fx]; red += currentColor.X; @@ -87,9 +90,8 @@ namespace ImageSharp.Processing.Processors } } - TPixel packed = default(TPixel); - packed.PackFromVector4(new Vector4(red, green, blue, sourcePixels[x, y].ToVector4().W)); - targetPixels[x, y] = packed; + ref TPixel pixel = ref targetRow[x]; + pixel.PackFromVector4(new Vector4(red, green, blue, sourceRow[x].ToVector4().W)); } }); From 2423c680bbc6b05c2b2ddb7fc05232986d46040f Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Wed, 24 May 2017 13:58:10 +1000 Subject: [PATCH 09/25] Optimize Alpha --- .../Processors/Effects/AlphaProcessor.cs | 32 ++++++++----------- .../Processors/Filters/AlphaTest.cs | 4 +-- 2 files changed, 16 insertions(+), 20 deletions(-) diff --git a/src/ImageSharp/Processing/Processors/Effects/AlphaProcessor.cs b/src/ImageSharp/Processing/Processors/Effects/AlphaProcessor.cs index 5e7310e32b..a3894f8d34 100644 --- a/src/ImageSharp/Processing/Processors/Effects/AlphaProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Effects/AlphaProcessor.cs @@ -61,26 +61,22 @@ namespace ImageSharp.Processing.Processors startY = 0; } - Vector4 alphaVector = new Vector4(1, 1, 1, this.Value); + var alphaVector = new Vector4(1, 1, 1, this.Value); - using (PixelAccessor sourcePixels = source.Lock()) - { - Parallel.For( - minY, - maxY, - this.ParallelOptions, - y => + Parallel.For( + minY, + maxY, + this.ParallelOptions, + y => + { + Span row = source.GetRowSpan(y - startY); + + for (int x = minX; x < maxX; x++) { - int offsetY = y - startY; - for (int x = minX; x < maxX; x++) - { - int offsetX = x - startX; - TPixel packed = default(TPixel); - packed.PackFromVector4(sourcePixels[offsetX, offsetY].ToVector4() * alphaVector); - sourcePixels[offsetX, offsetY] = packed; - } - }); - } + ref TPixel pixel = ref row[x - startX]; + pixel.PackFromVector4(pixel.ToVector4() * alphaVector); + } + }); } } } diff --git a/tests/ImageSharp.Tests/Processors/Filters/AlphaTest.cs b/tests/ImageSharp.Tests/Processors/Filters/AlphaTest.cs index e40e3a205d..401ac916bd 100644 --- a/tests/ImageSharp.Tests/Processors/Filters/AlphaTest.cs +++ b/tests/ImageSharp.Tests/Processors/Filters/AlphaTest.cs @@ -22,7 +22,7 @@ namespace ImageSharp.Tests [Theory] [MemberData(nameof(AlphaValues))] - public void ImageShouldApplyAlphaFilter(int value) + public void ImageShouldApplyAlphaFilter(float value) { string path = this.CreateOutputDirectory("Alpha"); @@ -39,7 +39,7 @@ namespace ImageSharp.Tests [Theory] [MemberData(nameof(AlphaValues))] - public void ImageShouldApplyAlphaFilterInBox(int value) + public void ImageShouldApplyAlphaFilterInBox(float value) { string path = this.CreateOutputDirectory("Alpha"); From 7c529d7ff8192b081db65d29258cfd863f9e76c5 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Wed, 24 May 2017 14:04:47 +1000 Subject: [PATCH 10/25] Optimize BackgroundColor --- .../Processors/Effects/BackgroundColorProcessor.cs | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/src/ImageSharp/Processing/Processors/Effects/BackgroundColorProcessor.cs b/src/ImageSharp/Processing/Processors/Effects/BackgroundColorProcessor.cs index cc95f15fc6..153719191e 100644 --- a/src/ImageSharp/Processing/Processors/Effects/BackgroundColorProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Effects/BackgroundColorProcessor.cs @@ -6,7 +6,6 @@ namespace ImageSharp.Processing.Processors { using System; - using System.Numerics; using System.Threading.Tasks; using ImageSharp.Memory; @@ -64,9 +63,8 @@ namespace ImageSharp.Processing.Processors int width = maxX - minX; - using (Buffer colors = new Buffer(width)) - using (Buffer amount = new Buffer(width)) - using (PixelAccessor sourcePixels = source.Lock()) + using (var colors = new Buffer(width)) + using (var amount = new Buffer(width)) { for (int i = 0; i < width; i++) { @@ -81,11 +79,9 @@ namespace ImageSharp.Processing.Processors this.ParallelOptions, y => { - int offsetY = y - startY; + Span destination = source.GetRowSpan(y - startY).Slice(minX - startX, width); - Span destination = sourcePixels.GetRowSpan(offsetY).Slice(minX - startX, width); - - // this switched color & destination in the 2nd and 3rd places because we are applying the target colour under the current one + // This switched color & destination in the 2nd and 3rd places because we are applying the target colour under the current one blender.Blend(destination, colors, destination, amount); }); } From 334ba994e71756cb4f7bed839e7a21b5d4d94fcf Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Wed, 24 May 2017 14:10:25 +1000 Subject: [PATCH 11/25] Optimize Brightness --- .../Processors/Effects/BrightnessProcessor.cs | 39 ++++++++----------- 1 file changed, 17 insertions(+), 22 deletions(-) diff --git a/src/ImageSharp/Processing/Processors/Effects/BrightnessProcessor.cs b/src/ImageSharp/Processing/Processors/Effects/BrightnessProcessor.cs index f9f1585ea9..121d25d1e0 100644 --- a/src/ImageSharp/Processing/Processors/Effects/BrightnessProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Effects/BrightnessProcessor.cs @@ -63,31 +63,26 @@ namespace ImageSharp.Processing.Processors startY = 0; } - using (PixelAccessor sourcePixels = source.Lock()) - { - Parallel.For( - minY, - maxY, - this.ParallelOptions, - y => - { - int offsetY = y - startY; - for (int x = minX; x < maxX; x++) - { - int offsetX = x - startX; + Parallel.For( + minY, + maxY, + this.ParallelOptions, + y => + { + Span row = source.GetRowSpan(y - startY); - // TODO: Check this with other formats. - Vector4 vector = sourcePixels[offsetX, offsetY].ToVector4().Expand(); - Vector3 transformed = new Vector3(vector.X, vector.Y, vector.Z) + new Vector3(brightness); - vector = new Vector4(transformed, vector.W); + for (int x = minX; x < maxX; x++) + { + ref TPixel pixel = ref row[x - startX]; - TPixel packed = default(TPixel); - packed.PackFromVector4(vector.Compress()); + // TODO: Check this with other formats. + Vector4 vector = pixel.ToVector4().Expand(); + Vector3 transformed = new Vector3(vector.X, vector.Y, vector.Z) + new Vector3(brightness); + vector = new Vector4(transformed, vector.W); - sourcePixels[offsetX, offsetY] = packed; - } - }); - } + pixel.PackFromVector4(vector.Compress()); + } + }); } } } \ No newline at end of file From c5b332f965c3cc83e9e7819bb4cfe6d2de877a4b Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Wed, 24 May 2017 14:15:57 +1000 Subject: [PATCH 12/25] Optimize Contrast --- .../Processors/Effects/ContrastProcessor.cs | 45 +++++++++---------- 1 file changed, 22 insertions(+), 23 deletions(-) diff --git a/src/ImageSharp/Processing/Processors/Effects/ContrastProcessor.cs b/src/ImageSharp/Processing/Processors/Effects/ContrastProcessor.cs index 8308c57e2b..f5dc68545e 100644 --- a/src/ImageSharp/Processing/Processors/Effects/ContrastProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Effects/ContrastProcessor.cs @@ -45,8 +45,8 @@ namespace ImageSharp.Processing.Processors int endY = sourceRectangle.Bottom; int startX = sourceRectangle.X; int endX = sourceRectangle.Right; - Vector4 contrastVector = new Vector4(contrast, contrast, contrast, 1); - Vector4 shiftVector = new Vector4(.5F, .5F, .5F, 1); + var contrastVector = new Vector4(contrast, contrast, contrast, 1); + var shiftVector = new Vector4(.5F, .5F, .5F, 1); // Align start/end positions. int minX = Math.Max(0, startX); @@ -65,29 +65,28 @@ namespace ImageSharp.Processing.Processors startY = 0; } - using (PixelAccessor sourcePixels = source.Lock()) - { - Parallel.For( - minY, - maxY, - this.ParallelOptions, - y => + Parallel.For( + minY, + maxY, + this.ParallelOptions, + y => + { + int offsetY = y - startY; + Span row = source.GetRowSpan(y - startY); + + for (int x = minX; x < maxX; x++) { - int offsetY = y - startY; - for (int x = minX; x < maxX; x++) - { - int offsetX = x - startX; + int offsetX = x - startX; + ref TPixel pixel = ref row[x - startX]; - Vector4 vector = sourcePixels[offsetX, offsetY].ToVector4().Expand(); - vector -= shiftVector; - vector *= contrastVector; - vector += shiftVector; - TPixel packed = default(TPixel); - packed.PackFromVector4(vector.Compress()); - sourcePixels[offsetX, offsetY] = packed; - } - }); - } + Vector4 vector = pixel.ToVector4().Expand(); + vector -= shiftVector; + vector *= contrastVector; + vector += shiftVector; + + pixel.PackFromVector4(vector.Compress()); + } + }); } } } \ No newline at end of file From a56a97a2e289cac0912110e2206c75689d36aad8 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Wed, 24 May 2017 14:21:11 +1000 Subject: [PATCH 13/25] Optimize Invert --- .../Processors/Effects/ContrastProcessor.cs | 2 -- .../Processors/Effects/InvertProcessor.cs | 35 +++++++++---------- 2 files changed, 16 insertions(+), 21 deletions(-) diff --git a/src/ImageSharp/Processing/Processors/Effects/ContrastProcessor.cs b/src/ImageSharp/Processing/Processors/Effects/ContrastProcessor.cs index f5dc68545e..1daead6e50 100644 --- a/src/ImageSharp/Processing/Processors/Effects/ContrastProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Effects/ContrastProcessor.cs @@ -71,12 +71,10 @@ namespace ImageSharp.Processing.Processors this.ParallelOptions, y => { - int offsetY = y - startY; Span row = source.GetRowSpan(y - startY); for (int x = minX; x < maxX; x++) { - int offsetX = x - startX; ref TPixel pixel = ref row[x - startX]; Vector4 vector = pixel.ToVector4().Expand(); diff --git a/src/ImageSharp/Processing/Processors/Effects/InvertProcessor.cs b/src/ImageSharp/Processing/Processors/Effects/InvertProcessor.cs index a0348970e7..dc0b134421 100644 --- a/src/ImageSharp/Processing/Processors/Effects/InvertProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Effects/InvertProcessor.cs @@ -44,27 +44,24 @@ namespace ImageSharp.Processing.Processors startY = 0; } - using (PixelAccessor sourcePixels = source.Lock()) - { - Parallel.For( - minY, - maxY, - this.ParallelOptions, - y => + Parallel.For( + minY, + maxY, + this.ParallelOptions, + y => + { + Span row = source.GetRowSpan(y - startY); + + for (int x = minX; x < maxX; x++) { - int offsetY = y - startY; - for (int x = minX; x < maxX; x++) - { - int offsetX = x - startX; - Vector4 color = sourcePixels[offsetX, offsetY].ToVector4(); - Vector3 vector = inverseVector - new Vector3(color.X, color.Y, color.Z); + ref TPixel pixel = ref row[x - startX]; - TPixel packed = default(TPixel); - packed.PackFromVector4(new Vector4(vector, color.W)); - sourcePixels[offsetX, offsetY] = packed; - } - }); - } + var vector = pixel.ToVector4(); + Vector3 vector3 = inverseVector - new Vector3(vector.X, vector.Y, vector.Z); + + pixel.PackFromVector4(new Vector4(vector3, vector.W)); + } + }); } } } \ No newline at end of file From 38f5340165260e6ce2f9d9c1f85518edaea9d243 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Wed, 24 May 2017 14:41:34 +1000 Subject: [PATCH 14/25] Optimize OilPaint --- .../Effects/OilPaintingProcessor.cs | 27 ++++++++++--------- 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/src/ImageSharp/Processing/Processors/Effects/OilPaintingProcessor.cs b/src/ImageSharp/Processing/Processors/Effects/OilPaintingProcessor.cs index 1f06924af0..a43f77a1c6 100644 --- a/src/ImageSharp/Processing/Processors/Effects/OilPaintingProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Effects/OilPaintingProcessor.cs @@ -8,7 +8,7 @@ namespace ImageSharp.Processing.Processors using System; using System.Numerics; using System.Threading.Tasks; - + using ImageSharp.Memory; using ImageSharp.PixelFormats; /// @@ -69,10 +69,9 @@ namespace ImageSharp.Processing.Processors startX = 0; } - using (PixelAccessor targetPixels = new PixelAccessor(source.Width, source.Height)) - using (PixelAccessor sourcePixels = source.Lock()) + using (var targetPixels = new PixelAccessor(source.Width, source.Height)) { - sourcePixels.CopyTo(targetPixels); + source.CopyTo(targetPixels); Parallel.For( minY, @@ -80,6 +79,9 @@ namespace ImageSharp.Processing.Processors this.ParallelOptions, y => { + Span sourceRow = source.GetRowSpan(y); + Span targetRow = targetPixels.GetRowSpan(y); + for (int x = startX; x < endX; x++) { int maxIntensity = 0; @@ -107,6 +109,8 @@ namespace ImageSharp.Processing.Processors break; } + Span sourceOffsetRow = source.GetRowSpan(offsetY); + for (int fx = 0; fx <= radius; fx++) { int fxr = fx - radius; @@ -121,13 +125,13 @@ namespace ImageSharp.Processing.Processors if (offsetX < maxX) { // ReSharper disable once AccessToDisposedClosure - Vector4 color = sourcePixels[offsetX, offsetY].ToVector4(); + var vector = sourceOffsetRow[offsetX].ToVector4(); - float sourceRed = color.X; - float sourceBlue = color.Z; - float sourceGreen = color.Y; + float sourceRed = vector.X; + float sourceBlue = vector.Z; + float sourceGreen = vector.Y; - int currentIntensity = (int)Math.Round((sourceBlue + sourceGreen + sourceRed) / 3.0 * (levels - 1)); + int currentIntensity = (int)MathF.Round((sourceBlue + sourceGreen + sourceRed) / 3F * (levels - 1)); intensityBin[currentIntensity] += 1; blueBin[currentIntensity] += sourceBlue; @@ -146,9 +150,8 @@ namespace ImageSharp.Processing.Processors float green = MathF.Abs(greenBin[maxIndex] / maxIntensity); float blue = MathF.Abs(blueBin[maxIndex] / maxIntensity); - TPixel packed = default(TPixel); - packed.PackFromVector4(new Vector4(red, green, blue, sourcePixels[x, y].ToVector4().W)); - targetPixels[x, y] = packed; + ref TPixel pixel = ref targetRow[x]; + pixel.PackFromVector4(new Vector4(red, green, blue, sourceRow[x].ToVector4().W)); } } }); From 13738226accdea1475f310bc7587b747d14dd622 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Wed, 24 May 2017 14:52:50 +1000 Subject: [PATCH 15/25] Optimize Pixellate --- .../Processors/Effects/PixelateProcessor.cs | 62 +++++++++---------- 1 file changed, 30 insertions(+), 32 deletions(-) diff --git a/src/ImageSharp/Processing/Processors/Effects/PixelateProcessor.cs b/src/ImageSharp/Processing/Processors/Effects/PixelateProcessor.cs index 0287eaab8e..ba6bb5c9d8 100644 --- a/src/ImageSharp/Processing/Processors/Effects/PixelateProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Effects/PixelateProcessor.cs @@ -66,47 +66,45 @@ namespace ImageSharp.Processing.Processors // Get the range on the y-plane to choose from. IEnumerable range = EnumerableExtensions.SteppedRange(minY, i => i < maxY, size); - using (PixelAccessor sourcePixels = source.Lock()) - { - Parallel.ForEach( - range, - this.ParallelOptions, - y => + Parallel.ForEach( + range, + this.ParallelOptions, + y => + { + int offsetY = y - startY; + int offsetPy = offset; + + // Make sure that the offset is within the boundary of the image. + while (offsetY + offsetPy >= maxY) { - int offsetY = y - startY; - int offsetPy = offset; + offsetPy--; + } - for (int x = minX; x < maxX; x += size) - { - int offsetX = x - startX; - int offsetPx = offset; + Span row = source.GetRowSpan(offsetY + offsetPy); - // Make sure that the offset is within the boundary of the image. - while (offsetY + offsetPy >= maxY) - { - offsetPy--; - } + for (int x = minX; x < maxX; x += size) + { + int offsetX = x - startX; + int offsetPx = offset; - while (x + offsetPx >= maxX) - { - offsetPx--; - } + while (x + offsetPx >= maxX) + { + offsetPx--; + } - // Get the pixel color in the centre of the soon to be pixelated area. - // ReSharper disable AccessToDisposedClosure - TPixel pixel = sourcePixels[offsetX + offsetPx, offsetY + offsetPy]; + // Get the pixel color in the centre of the soon to be pixelated area. + TPixel pixel = row[offsetX + offsetPx]; - // For each pixel in the pixelate size, set it to the centre color. - for (int l = offsetY; l < offsetY + size && l < maxY; l++) + // For each pixel in the pixelate size, set it to the centre color. + for (int l = offsetY; l < offsetY + size && l < maxY; l++) + { + for (int k = offsetX; k < offsetX + size && k < maxX; k++) { - for (int k = offsetX; k < offsetX + size && k < maxX; k++) - { - sourcePixels[k, l] = pixel; - } + source[k, l] = pixel; } } - }); - } + } + }); } } } \ No newline at end of file From 5ad15fe31bafdd7a1a426a28d0fd64cc75b81703 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Wed, 24 May 2017 15:00:04 +1000 Subject: [PATCH 16/25] Optimize Glow/Vignette --- .../Processors/Overlays/GlowProcessor.cs | 41 +++++++++---------- .../Processors/Overlays/VignetteProcessor.cs | 9 ++-- 2 files changed, 24 insertions(+), 26 deletions(-) diff --git a/src/ImageSharp/Processing/Processors/Overlays/GlowProcessor.cs b/src/ImageSharp/Processing/Processors/Overlays/GlowProcessor.cs index 9de91c47b4..23fea94e93 100644 --- a/src/ImageSharp/Processing/Processors/Overlays/GlowProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Overlays/GlowProcessor.cs @@ -52,7 +52,7 @@ namespace ImageSharp.Processing.Processors int startX = sourceRectangle.X; int endX = sourceRectangle.Right; TPixel glowColor = this.GlowColor; - Vector2 centre = Rectangle.Center(sourceRectangle).ToVector2(); + var centre = Rectangle.Center(sourceRectangle).ToVector2(); float maxDistance = this.Radius > 0 ? MathF.Min(this.Radius, sourceRectangle.Width * .5F) : sourceRectangle.Width * .5F; // Align start/end positions. @@ -73,8 +73,7 @@ namespace ImageSharp.Processing.Processors } int width = maxX - minX; - using (Buffer rowColors = new Buffer(width)) - using (PixelAccessor sourcePixels = source.Lock()) + using (var rowColors = new Buffer(width)) { for (int i = 0; i < width; i++) { @@ -82,26 +81,26 @@ namespace ImageSharp.Processing.Processors } Parallel.For( - minY, - maxY, - this.ParallelOptions, - y => - { - using (Buffer amounts = new Buffer(width)) - { - int offsetY = y - startY; - int offsetX = minX - startX; - for (int i = 0; i < width; i++) - { - float distance = Vector2.Distance(centre, new Vector2(i + offsetX, offsetY)); - amounts[i] = (this.options.BlendPercentage * (1 - (.95F * (distance / maxDistance)))).Clamp(0, 1); - } + minY, + maxY, + this.ParallelOptions, + y => + { + using (var amounts = new Buffer(width)) + { + int offsetY = y - startY; + int offsetX = minX - startX; + for (int i = 0; i < width; i++) + { + float distance = Vector2.Distance(centre, new Vector2(i + offsetX, offsetY)); + amounts[i] = (this.options.BlendPercentage * (1 - (.95F * (distance / maxDistance)))).Clamp(0, 1); + } - Span destination = sourcePixels.GetRowSpan(offsetY).Slice(offsetX, width); + Span destination = source.GetRowSpan(offsetY).Slice(offsetX, width); - this.blender.Blend(destination, destination, rowColors, amounts); - } - }); + this.blender.Blend(destination, destination, rowColors, amounts); + } + }); } } } diff --git a/src/ImageSharp/Processing/Processors/Overlays/VignetteProcessor.cs b/src/ImageSharp/Processing/Processors/Overlays/VignetteProcessor.cs index be431b07d2..4dfa41989b 100644 --- a/src/ImageSharp/Processing/Processors/Overlays/VignetteProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Overlays/VignetteProcessor.cs @@ -58,7 +58,7 @@ namespace ImageSharp.Processing.Processors int startX = sourceRectangle.X; int endX = sourceRectangle.Right; TPixel vignetteColor = this.VignetteColor; - Vector2 centre = Rectangle.Center(sourceRectangle).ToVector2(); + var centre = Rectangle.Center(sourceRectangle).ToVector2(); float rX = this.RadiusX > 0 ? MathF.Min(this.RadiusX, sourceRectangle.Width * .5F) : sourceRectangle.Width * .5F; float rY = this.RadiusY > 0 ? MathF.Min(this.RadiusY, sourceRectangle.Height * .5F) : sourceRectangle.Height * .5F; float maxDistance = MathF.Sqrt((rX * rX) + (rY * rY)); @@ -81,8 +81,7 @@ namespace ImageSharp.Processing.Processors } int width = maxX - minX; - using (Buffer rowColors = new Buffer(width)) - using (PixelAccessor sourcePixels = source.Lock()) + using (var rowColors = new Buffer(width)) { for (int i = 0; i < width; i++) { @@ -95,7 +94,7 @@ namespace ImageSharp.Processing.Processors this.ParallelOptions, y => { - using (Buffer amounts = new Buffer(width)) + using (var amounts = new Buffer(width)) { int offsetY = y - startY; int offsetX = minX - startX; @@ -105,7 +104,7 @@ namespace ImageSharp.Processing.Processors amounts[i] = (this.options.BlendPercentage * (.9F * (distance / maxDistance))).Clamp(0, 1); } - Span destination = sourcePixels.GetRowSpan(offsetY).Slice(offsetX, width); + Span destination = source.GetRowSpan(offsetY).Slice(offsetX, width); this.blender.Blend(destination, destination, rowColors, amounts); } From 9dc1009d1b7e599349048076a8a70c6429197375 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Wed, 24 May 2017 15:15:49 +1000 Subject: [PATCH 17/25] Optimize Crop --- .../Processors/Transforms/CropProcessor.cs | 28 ++++++++----------- 1 file changed, 12 insertions(+), 16 deletions(-) diff --git a/src/ImageSharp/Processing/Processors/Transforms/CropProcessor.cs b/src/ImageSharp/Processing/Processors/Transforms/CropProcessor.cs index b67ef5bf1e..ade5fa8308 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/CropProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/CropProcessor.cs @@ -7,7 +7,7 @@ namespace ImageSharp.Processing.Processors { using System; using System.Threading.Tasks; - + using ImageSharp.Memory; using ImageSharp.PixelFormats; /// @@ -44,22 +44,18 @@ namespace ImageSharp.Processing.Processors int minX = Math.Max(this.CropRectangle.X, sourceRectangle.X); int maxX = Math.Min(this.CropRectangle.Right, sourceRectangle.Right); - using (PixelAccessor targetPixels = new PixelAccessor(this.CropRectangle.Width, this.CropRectangle.Height)) + using (var targetPixels = new PixelAccessor(this.CropRectangle.Width, this.CropRectangle.Height)) { - using (PixelAccessor sourcePixels = source.Lock()) - { - Parallel.For( - minY, - maxY, - this.ParallelOptions, - y => - { - for (int x = minX; x < maxX; x++) - { - targetPixels[x - minX, y - minY] = sourcePixels[x, y]; - } - }); - } + Parallel.For( + minY, + maxY, + this.ParallelOptions, + y => + { + Span sourceRow = source.GetRowSpan(minX, y); + Span targetRow = targetPixels.GetRowSpan(y - minY); + SpanHelper.Copy(sourceRow, targetRow, maxX - minX); + }); source.SwapPixelsBuffers(targetPixels); } From 10e84c94d2081454194d9b88ed748d00976c5b63 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Wed, 24 May 2017 15:19:52 +1000 Subject: [PATCH 18/25] Fix readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index c672d7ca32..5617681994 100644 --- a/README.md +++ b/README.md @@ -106,7 +106,7 @@ using (Image image = Image.Load(stream)) } ``` -Setting individual pixel values can perfomed as follows: +Setting individual pixel values can be perfomed as follows: ```csharp // Individual pixels From 3b48b116234f9d90b9ab878ac4536c101ae65853 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Wed, 24 May 2017 15:34:24 +1000 Subject: [PATCH 19/25] Optimize EntroyCrop --- src/ImageSharp/Common/Helpers/ImageMaths.cs | 25 +++++++++------------ 1 file changed, 11 insertions(+), 14 deletions(-) diff --git a/src/ImageSharp/Common/Helpers/ImageMaths.cs b/src/ImageSharp/Common/Helpers/ImageMaths.cs index 6c8c62039f..9c4dee5033 100644 --- a/src/ImageSharp/Common/Helpers/ImageMaths.cs +++ b/src/ImageSharp/Common/Helpers/ImageMaths.cs @@ -157,10 +157,10 @@ namespace ImageSharp { int width = bitmap.Width; int height = bitmap.Height; - Point topLeft = default(Point); - Point bottomRight = default(Point); + var topLeft = default(Point); + var bottomRight = default(Point); - Func, int, int, float, bool> delegateFunc; + Func, int, int, float, bool> delegateFunc; // Determine which channel to check against switch (channel) @@ -182,7 +182,7 @@ namespace ImageSharp break; } - int GetMinY(PixelAccessor pixels) + int GetMinY(ImageBase pixels) { for (int y = 0; y < height; y++) { @@ -198,7 +198,7 @@ namespace ImageSharp return 0; } - int GetMaxY(PixelAccessor pixels) + int GetMaxY(ImageBase pixels) { for (int y = height - 1; y > -1; y--) { @@ -214,7 +214,7 @@ namespace ImageSharp return height; } - int GetMinX(PixelAccessor pixels) + int GetMinX(ImageBase pixels) { for (int x = 0; x < width; x++) { @@ -230,7 +230,7 @@ namespace ImageSharp return 0; } - int GetMaxX(PixelAccessor pixels) + int GetMaxX(ImageBase pixels) { for (int x = width - 1; x > -1; x--) { @@ -246,13 +246,10 @@ namespace ImageSharp return height; } - using (PixelAccessor bitmapPixels = bitmap.Lock()) - { - topLeft.Y = GetMinY(bitmapPixels); - topLeft.X = GetMinX(bitmapPixels); - bottomRight.Y = (GetMaxY(bitmapPixels) + 1).Clamp(0, height); - bottomRight.X = (GetMaxX(bitmapPixels) + 1).Clamp(0, width); - } + topLeft.Y = GetMinY(bitmap); + topLeft.X = GetMinX(bitmap); + bottomRight.Y = (GetMaxY(bitmap) + 1).Clamp(0, height); + bottomRight.X = (GetMaxX(bitmap) + 1).Clamp(0, width); return GetBoundingRectangle(topLeft, bottomRight); } From 8b84e6653b00e4bfe42787bb32a099289948d8fe Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Wed, 24 May 2017 16:59:21 +1000 Subject: [PATCH 20/25] Optimize Flip --- .../Processors/Transforms/FlipProcessor.cs | 66 +++++++++---------- 1 file changed, 33 insertions(+), 33 deletions(-) diff --git a/src/ImageSharp/Processing/Processors/Transforms/FlipProcessor.cs b/src/ImageSharp/Processing/Processors/Transforms/FlipProcessor.cs index 2faf779053..d4303c455e 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/FlipProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/FlipProcessor.cs @@ -8,6 +8,7 @@ namespace ImageSharp.Processing.Processors using System; using System.Threading.Tasks; + using ImageSharp.Memory; using ImageSharp.PixelFormats; /// @@ -57,24 +58,23 @@ namespace ImageSharp.Processing.Processors int height = source.Height; int halfHeight = (int)Math.Ceiling(source.Height * .5F); - using (PixelAccessor targetPixels = new PixelAccessor(width, height)) + using (var targetPixels = new PixelAccessor(width, height)) { - using (PixelAccessor sourcePixels = source.Lock()) - { - Parallel.For( - 0, - halfHeight, - this.ParallelOptions, - y => - { - for (int x = 0; x < width; x++) - { - int newY = height - y - 1; - targetPixels[x, y] = sourcePixels[x, newY]; - targetPixels[x, newY] = sourcePixels[x, y]; - } - }); - } + Parallel.For( + 0, + halfHeight, + this.ParallelOptions, + y => + { + int newY = height - y - 1; + Span sourceRow = source.GetRowSpan(y); + Span altSourceRow = source.GetRowSpan(newY); + Span targetRow = targetPixels.GetRowSpan(y); + Span altTargetRow = targetPixels.GetRowSpan(newY); + + sourceRow.CopyTo(altTargetRow); + altSourceRow.CopyTo(targetRow); + }); source.SwapPixelsBuffers(targetPixels); } @@ -91,24 +91,24 @@ namespace ImageSharp.Processing.Processors int height = source.Height; int halfWidth = (int)Math.Ceiling(width * .5F); - using (PixelAccessor targetPixels = new PixelAccessor(width, height)) + using (var targetPixels = new PixelAccessor(width, height)) { - using (PixelAccessor sourcePixels = source.Lock()) - { - Parallel.For( - 0, - height, - this.ParallelOptions, - y => + Parallel.For( + 0, + height, + this.ParallelOptions, + y => + { + Span sourceRow = source.GetRowSpan(y); + Span targetRow = targetPixels.GetRowSpan(y); + + for (int x = 0; x < halfWidth; x++) { - for (int x = 0; x < halfWidth; x++) - { - int newX = width - x - 1; - targetPixels[x, y] = sourcePixels[newX, y]; - targetPixels[newX, y] = sourcePixels[x, y]; - } - }); - } + int newX = width - x - 1; + targetRow[x] = sourceRow[newX]; + targetRow[newX] = sourceRow[x]; + } + }); source.SwapPixelsBuffers(targetPixels); } From 2eb849957c2b57e68508b33ddeeb02dcfdb4575a Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Wed, 24 May 2017 17:30:14 +1000 Subject: [PATCH 21/25] Optimize Resize --- .../ResamplingWeightedProcessor.Weights.cs | 5 ++ .../Transforms/ResamplingWeightedProcessor.cs | 4 +- .../Processors/Transforms/ResizeProcessor.cs | 66 +++++++++---------- 3 files changed, 36 insertions(+), 39 deletions(-) diff --git a/src/ImageSharp/Processing/Processors/Transforms/ResamplingWeightedProcessor.Weights.cs b/src/ImageSharp/Processing/Processors/Transforms/ResamplingWeightedProcessor.Weights.cs index d49f37803f..b5266c9bd8 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/ResamplingWeightedProcessor.Weights.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/ResamplingWeightedProcessor.Weights.cs @@ -1,3 +1,8 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + namespace ImageSharp.Processing.Processors { using System; diff --git a/src/ImageSharp/Processing/Processors/Transforms/ResamplingWeightedProcessor.cs b/src/ImageSharp/Processing/Processors/Transforms/ResamplingWeightedProcessor.cs index e2f77d812e..757b0889a5 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/ResamplingWeightedProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/ResamplingWeightedProcessor.cs @@ -6,9 +6,7 @@ namespace ImageSharp.Processing.Processors { using System; - using System.Buffers; using System.Runtime.CompilerServices; - using System.Runtime.InteropServices; using ImageSharp.PixelFormats; @@ -90,7 +88,7 @@ namespace ImageSharp.Processing.Processors IResampler sampler = this.Sampler; float radius = MathF.Ceiling(scale * sampler.Radius); - WeightsBuffer result = new WeightsBuffer(sourceSize, destinationSize); + var result = new WeightsBuffer(sourceSize, destinationSize); for (int i = 0; i < destinationSize; i++) { diff --git a/src/ImageSharp/Processing/Processors/Transforms/ResizeProcessor.cs b/src/ImageSharp/Processing/Processors/Transforms/ResizeProcessor.cs index 61a64f60ff..68ff1397df 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/ResizeProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/ResizeProcessor.cs @@ -73,26 +73,24 @@ namespace ImageSharp.Processing.Processors float widthFactor = sourceRectangle.Width / (float)this.ResizeRectangle.Width; float heightFactor = sourceRectangle.Height / (float)this.ResizeRectangle.Height; - using (PixelAccessor targetPixels = new PixelAccessor(width, height)) + using (var targetPixels = new PixelAccessor(width, height)) { - using (PixelAccessor sourcePixels = source.Lock()) - { - Parallel.For( - minY, - maxY, - this.ParallelOptions, - y => - { - // Y coordinates of source points - int originY = (int)(((y - startY) * heightFactor) + sourceY); + Parallel.For( + minY, + maxY, + this.ParallelOptions, + y => + { + // Y coordinates of source points + Span sourceRow = source.GetRowSpan((int)(((y - startY) * heightFactor) + sourceY)); + Span targetRow = targetPixels.GetRowSpan(y); - for (int x = minX; x < maxX; x++) - { - // X coordinates of source points - targetPixels[x, y] = sourcePixels[(int)(((x - startX) * widthFactor) + sourceX), originY]; - } - }); - } + for (int x = minX; x < maxX; x++) + { + // X coordinates of source points + targetRow[x] = sourceRow[(int)(((x - startX) * widthFactor) + sourceX)]; + } + }); // Break out now. source.SwapPixelsBuffers(targetPixels); @@ -106,10 +104,9 @@ namespace ImageSharp.Processing.Processors // are the upper and lower bounds of the source rectangle. // TODO: Using a transposed variant of 'firstPassPixels' could eliminate the need for the WeightsWindow.ComputeWeightedColumnSum() method, and improve speed! - using (PixelAccessor targetPixels = new PixelAccessor(width, height)) + using (var targetPixels = new PixelAccessor(width, height)) { - using (PixelAccessor sourcePixels = source.Lock()) - using (Buffer2D firstPassPixels = new Buffer2D(width, source.Height)) + using (var firstPassPixels = new Buffer2D(width, source.Height)) { firstPassPixels.Clear(); @@ -120,21 +117,18 @@ namespace ImageSharp.Processing.Processors y => { // TODO: Without Parallel.For() this buffer object could be reused: - using (Buffer tempRowBuffer = new Buffer(sourcePixels.Width)) + using (var tempRowBuffer = new Buffer(source.Width)) { - Span sourceRow = sourcePixels.GetRowSpan(y); - - PixelOperations.Instance.ToVector4( - sourceRow, - tempRowBuffer, - sourceRow.Length); + Span firstPassRow = firstPassPixels.GetRowSpan(y); + Span sourceRow = source.GetRowSpan(y); + PixelOperations.Instance.ToVector4(sourceRow, tempRowBuffer, sourceRow.Length); if (this.Compand) { for (int x = minX; x < maxX; x++) { WeightsWindow window = this.HorizontalWeights.Weights[x - startX]; - firstPassPixels[x, y] = window.ComputeExpandedWeightedRowSum(tempRowBuffer, sourceX); + firstPassRow[x] = window.ComputeExpandedWeightedRowSum(tempRowBuffer, sourceX); } } else @@ -142,7 +136,7 @@ namespace ImageSharp.Processing.Processors for (int x = minX; x < maxX; x++) { WeightsWindow window = this.HorizontalWeights.Weights[x - startX]; - firstPassPixels[x, y] = window.ComputeWeightedRowSum(tempRowBuffer, sourceX); + firstPassRow[x] = window.ComputeWeightedRowSum(tempRowBuffer, sourceX); } } } @@ -157,6 +151,7 @@ namespace ImageSharp.Processing.Processors { // Ensure offsets are normalised for cropping and padding. WeightsWindow window = this.VerticalWeights.Weights[y - startY]; + Span targetRow = targetPixels.GetRowSpan(y); if (this.Compand) { @@ -165,9 +160,9 @@ namespace ImageSharp.Processing.Processors // Destination color components Vector4 destination = window.ComputeWeightedColumnSum(firstPassPixels, x, sourceY); destination = destination.Compress(); - TPixel d = default(TPixel); - d.PackFromVector4(destination); - targetPixels[x, y] = d; + + ref TPixel pixel = ref targetRow[x]; + pixel.PackFromVector4(destination); } } else @@ -177,9 +172,8 @@ namespace ImageSharp.Processing.Processors // Destination color components Vector4 destination = window.ComputeWeightedColumnSum(firstPassPixels, x, sourceY); - TPixel d = default(TPixel); - d.PackFromVector4(destination); - targetPixels[x, y] = d; + ref TPixel pixel = ref targetRow[x]; + pixel.PackFromVector4(destination); } } }); From b87512333d5971305015b40cea1e8ff8e607da1e Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Wed, 24 May 2017 18:46:27 +1000 Subject: [PATCH 22/25] Optimize Rotate and Skew --- .../Processors/Transforms/RotateProcessor.cs | 94 +++++++++---------- .../Processors/Transforms/SkewProcessor.cs | 34 +++---- 2 files changed, 62 insertions(+), 66 deletions(-) diff --git a/src/ImageSharp/Processing/Processors/Transforms/RotateProcessor.cs b/src/ImageSharp/Processing/Processors/Transforms/RotateProcessor.cs index fc5d29b06a..43dbb53eac 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/RotateProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/RotateProcessor.cs @@ -8,7 +8,7 @@ namespace ImageSharp.Processing.Processors using System; using System.Numerics; using System.Threading.Tasks; - + using ImageSharp.Memory; using ImageSharp.PixelFormats; /// @@ -45,26 +45,26 @@ namespace ImageSharp.Processing.Processors int width = this.CanvasRectangle.Width; Matrix3x2 matrix = this.GetCenteredMatrix(source, this.processMatrix); - using (PixelAccessor targetPixels = new PixelAccessor(width, height)) + using (var targetPixels = new PixelAccessor(width, height)) { - using (PixelAccessor sourcePixels = source.Lock()) - { - Parallel.For( - 0, - height, - this.ParallelOptions, - y => + Parallel.For( + 0, + height, + this.ParallelOptions, + y => + { + Span targetRow = targetPixels.GetRowSpan(y); + + for (int x = 0; x < width; x++) { - for (int x = 0; x < width; x++) + var transformedPoint = Point.Rotate(new Point(x, y), matrix); + + if (source.Bounds.Contains(transformedPoint.X, transformedPoint.Y)) { - Point transformedPoint = Point.Rotate(new Point(x, y), matrix); - if (source.Bounds.Contains(transformedPoint.X, transformedPoint.Y)) - { - targetPixels[x, y] = sourcePixels[transformedPoint.X, transformedPoint.Y]; - } + targetRow[x] = source[transformedPoint.X, transformedPoint.Y]; } - }); - } + } + }); source.SwapPixelsBuffers(targetPixels); } @@ -128,7 +128,7 @@ namespace ImageSharp.Processing.Processors int width = source.Width; int height = source.Height; - using (PixelAccessor targetPixels = new PixelAccessor(height, width)) + using (var targetPixels = new PixelAccessor(height, width)) { using (PixelAccessor sourcePixels = source.Lock()) { @@ -161,24 +161,22 @@ namespace ImageSharp.Processing.Processors int width = source.Width; int height = source.Height; - using (PixelAccessor targetPixels = new PixelAccessor(width, height)) + using (var targetPixels = new PixelAccessor(width, height)) { - using (PixelAccessor sourcePixels = source.Lock()) - { - Parallel.For( - 0, - height, - this.ParallelOptions, - y => + Parallel.For( + 0, + height, + this.ParallelOptions, + y => + { + Span sourceRow = source.GetRowSpan(y); + Span targetRow = targetPixels.GetRowSpan(height - y - 1); + + for (int x = 0; x < width; x++) { - for (int x = 0; x < width; x++) - { - int newX = width - x - 1; - int newY = height - y - 1; - targetPixels[newX, newY] = sourcePixels[x, y]; - } - }); - } + targetRow[width - x - 1] = sourceRow[x]; + } + }); source.SwapPixelsBuffers(targetPixels); } @@ -193,23 +191,21 @@ namespace ImageSharp.Processing.Processors int width = source.Width; int height = source.Height; - using (PixelAccessor targetPixels = new PixelAccessor(height, width)) + using (var targetPixels = new PixelAccessor(height, width)) { - using (PixelAccessor sourcePixels = source.Lock()) - { - Parallel.For( - 0, - height, - this.ParallelOptions, - y => + Parallel.For( + 0, + height, + this.ParallelOptions, + y => + { + Span sourceRow = source.GetRowSpan(y); + int newX = height - y - 1; + for (int x = 0; x < width; x++) { - for (int x = 0; x < width; x++) - { - int newX = height - y - 1; - targetPixels[newX, x] = sourcePixels[x, y]; - } - }); - } + targetPixels[newX, x] = sourceRow[x]; + } + }); source.SwapPixelsBuffers(targetPixels); } diff --git a/src/ImageSharp/Processing/Processors/Transforms/SkewProcessor.cs b/src/ImageSharp/Processing/Processors/Transforms/SkewProcessor.cs index 40ea6a94e5..7807d0dfcc 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/SkewProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/SkewProcessor.cs @@ -8,7 +8,7 @@ namespace ImageSharp.Processing.Processors using System; using System.Numerics; using System.Threading.Tasks; - + using ImageSharp.Memory; using ImageSharp.PixelFormats; /// @@ -45,26 +45,26 @@ namespace ImageSharp.Processing.Processors int width = this.CanvasRectangle.Width; Matrix3x2 matrix = this.GetCenteredMatrix(source, this.processMatrix); - using (PixelAccessor targetPixels = new PixelAccessor(width, height)) + using (var targetPixels = new PixelAccessor(width, height)) { - using (PixelAccessor sourcePixels = source.Lock()) - { - Parallel.For( - 0, - height, - this.ParallelOptions, - y => + Parallel.For( + 0, + height, + this.ParallelOptions, + y => + { + Span targetRow = targetPixels.GetRowSpan(y); + + for (int x = 0; x < width; x++) { - for (int x = 0; x < width; x++) + var transformedPoint = Point.Skew(new Point(x, y), matrix); + + if (source.Bounds.Contains(transformedPoint.X, transformedPoint.Y)) { - Point transformedPoint = Point.Skew(new Point(x, y), matrix); - if (source.Bounds.Contains(transformedPoint.X, transformedPoint.Y)) - { - targetPixels[x, y] = sourcePixels[transformedPoint.X, transformedPoint.Y]; - } + targetRow[x] = source[transformedPoint.X, transformedPoint.Y]; } - }); - } + } + }); source.SwapPixelsBuffers(targetPixels); } From 0a24d3195f8ab61b61540d0aedb728d23f19c1eb Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Wed, 24 May 2017 18:46:41 +1000 Subject: [PATCH 23/25] Cleanup --- .../Processing/Processors/Transforms/Matrix3x2Processor.cs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/ImageSharp/Processing/Processors/Transforms/Matrix3x2Processor.cs b/src/ImageSharp/Processing/Processors/Transforms/Matrix3x2Processor.cs index 3135551f8a..4cc03d864b 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/Matrix3x2Processor.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/Matrix3x2Processor.cs @@ -5,7 +5,6 @@ namespace ImageSharp.Processing.Processors { - using System; using System.Numerics; using ImageSharp.PixelFormats; @@ -45,8 +44,8 @@ namespace ImageSharp.Processing.Processors /// protected Matrix3x2 GetCenteredMatrix(ImageBase source, Matrix3x2 matrix) { - Matrix3x2 translationToTargetCenter = Matrix3x2.CreateTranslation(-this.CanvasRectangle.Width * .5F, -this.CanvasRectangle.Height * .5F); - Matrix3x2 translateToSourceCenter = Matrix3x2.CreateTranslation(source.Width * .5F, source.Height * .5F); + var translationToTargetCenter = Matrix3x2.CreateTranslation(-this.CanvasRectangle.Width * .5F, -this.CanvasRectangle.Height * .5F); + var translateToSourceCenter = Matrix3x2.CreateTranslation(source.Width * .5F, source.Height * .5F); return (translationToTargetCenter * matrix) * translateToSourceCenter; } } From 6208f27b8a08c86d4c80688faa595d19a2afe5a4 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Thu, 25 May 2017 00:19:37 +1000 Subject: [PATCH 24/25] Read spec, fix gifs --- src/ImageSharp/Formats/Gif/GifDecoderCore.cs | 92 +++++++++---------- src/ImageSharp/Formats/Gif/GifEncoderCore.cs | 43 ++++----- .../Sections/GifGraphicsControlExtension.cs | 2 +- .../Quantizers/OctreeQuantizer{TPixel}.cs | 12 ++- 4 files changed, 71 insertions(+), 78 deletions(-) diff --git a/src/ImageSharp/Formats/Gif/GifDecoderCore.cs b/src/ImageSharp/Formats/Gif/GifDecoderCore.cs index 272e4d0750..618d268f70 100644 --- a/src/ImageSharp/Formats/Gif/GifDecoderCore.cs +++ b/src/ImageSharp/Formats/Gif/GifDecoderCore.cs @@ -191,7 +191,7 @@ namespace ImageSharp.Formats byte packed = this.buffer[8]; - GifImageDescriptor imageDescriptor = new GifImageDescriptor + var imageDescriptor = new GifImageDescriptor { Left = BitConverter.ToInt16(this.buffer, 0), Top = BitConverter.ToInt16(this.buffer, 2), @@ -337,7 +337,7 @@ namespace ImageSharp.Formats private void ReadFrameIndices(GifImageDescriptor imageDescriptor, byte[] indices) { int dataSize = this.currentStream.ReadByte(); - using (LzwDecoder lzwDecoder = new LzwDecoder(this.currentStream)) + using (var lzwDecoder = new LzwDecoder(this.currentStream)) { lzwDecoder.DecodePixels(imageDescriptor.Width, imageDescriptor.Height, dataSize, indices); } @@ -396,62 +396,60 @@ namespace ImageSharp.Formats int interlaceIncrement = 8; // The interlacing line increment int interlaceY = 0; // The current interlaced line - using (PixelAccessor pixelAccessor = image.Lock()) + for (int y = descriptor.Top; y < descriptor.Top + descriptor.Height; y++) { - for (int y = descriptor.Top; y < descriptor.Top + descriptor.Height; y++) + // Check if this image is interlaced. + int writeY; // the target y offset to write to + if (descriptor.InterlaceFlag) { - // Check if this image is interlaced. - int writeY; // the target y offset to write to - if (descriptor.InterlaceFlag) + // If so then we read lines at predetermined offsets. + // When an entire image height worth of offset lines has been read we consider this a pass. + // With each pass the number of offset lines changes and the starting line changes. + if (interlaceY >= descriptor.Height) { - // If so then we read lines at predetermined offsets. - // When an entire image height worth of offset lines has been read we consider this a pass. - // With each pass the number of offset lines changes and the starting line changes. - if (interlaceY >= descriptor.Height) + interlacePass++; + switch (interlacePass) { - interlacePass++; - switch (interlacePass) - { - case 1: - interlaceY = 4; - break; - case 2: - interlaceY = 2; - interlaceIncrement = 4; - break; - case 3: - interlaceY = 1; - interlaceIncrement = 2; - break; - } + case 1: + interlaceY = 4; + break; + case 2: + interlaceY = 2; + interlaceIncrement = 4; + break; + case 3: + interlaceY = 1; + interlaceIncrement = 2; + break; } + } - writeY = interlaceY + descriptor.Top; + writeY = interlaceY + descriptor.Top; - interlaceY += interlaceIncrement; - } - else - { - writeY = y; - } + interlaceY += interlaceIncrement; + } + else + { + writeY = y; + } - for (int x = descriptor.Left; x < descriptor.Left + descriptor.Width; x++) - { - int index = indices[i]; + Span rowSpan = image.GetRowSpan(writeY); - if (this.graphicsControlExtension == null || - this.graphicsControlExtension.TransparencyFlag == false || - this.graphicsControlExtension.TransparencyIndex != index) - { - int indexOffset = index * 3; + for (int x = descriptor.Left; x < descriptor.Left + descriptor.Width; x++) + { + int index = indices[i]; - TPixel pixel = default(TPixel); - pixel.PackFromBytes(colorTable[indexOffset], colorTable[indexOffset + 1], colorTable[indexOffset + 2], 255); - pixelAccessor[x, writeY] = pixel; - } + if (this.graphicsControlExtension == null || + this.graphicsControlExtension.TransparencyFlag == false || + this.graphicsControlExtension.TransparencyIndex != index) + { + int indexOffset = index * 3; - i++; + ref TPixel pixel = ref rowSpan[x]; + pixel.PackFromBytes(colorTable[indexOffset], colorTable[indexOffset + 1], colorTable[indexOffset + 2], 255); } + + i++; } } @@ -492,7 +490,7 @@ namespace ImageSharp.Formats } else { - using (PixelArea emptyRow = new PixelArea(this.restoreArea.Value.Width, ComponentOrder.Xyzw)) + using (var emptyRow = new PixelArea(this.restoreArea.Value.Width, ComponentOrder.Xyzw)) { using (PixelAccessor pixelAccessor = frame.Lock()) { diff --git a/src/ImageSharp/Formats/Gif/GifEncoderCore.cs b/src/ImageSharp/Formats/Gif/GifEncoderCore.cs index ad0ecc88fb..5ef7ca1658 100644 --- a/src/ImageSharp/Formats/Gif/GifEncoderCore.cs +++ b/src/ImageSharp/Formats/Gif/GifEncoderCore.cs @@ -137,31 +137,20 @@ namespace ImageSharp.Formats private int GetTransparentIndex(QuantizedImage quantized) where TPixel : struct, IPixel { - // Find the lowest alpha value and make it the transparent index. - int index = 255; - byte alpha = 255; - bool hasEmpty = false; - - // Some images may have more than one quantized pixel returned with an alpha value of zero - // so we should always ignore if we have empty pixels present. - for (int i = 0; i < quantized.Palette.Length; i++) + // Transparent pixels are much more likely to be found at the end of a palette + int index = -1; + for (int i = quantized.Palette.Length - 1; i >= 0; i--) { quantized.Palette[i].ToXyzwBytes(this.buffer, 0); - if (!hasEmpty) + if (this.buffer[3] > 0) { - if (this.buffer[0] == 0 && this.buffer[1] == 0 && this.buffer[2] == 0 && this.buffer[3] == 0) - { - alpha = this.buffer[3]; - index = i; - hasEmpty = true; - } - - if (this.buffer[3] < alpha) - { - alpha = this.buffer[3]; - index = i; - } + continue; + } + else + { + index = i; + break; } } @@ -183,8 +172,8 @@ namespace ImageSharp.Formats /// The pixel format. /// The image to encode. /// The writer to write to the stream with. - /// The transparency index to set the default background index to. - private void WriteLogicalScreenDescriptor(Image image, EndianBinaryWriter writer, int tranparencyIndex) + /// The transparency index to set the default background index to. + private void WriteLogicalScreenDescriptor(Image image, EndianBinaryWriter writer, int transparencyIndex) where TPixel : struct, IPixel { var descriptor = new GifLogicalScreenDescriptor @@ -193,7 +182,7 @@ namespace ImageSharp.Formats Height = (short)image.Height, GlobalColorTableFlag = false, // TODO: Always false for now. GlobalColorTableSize = this.bitDepth - 1, - BackgroundColorIndex = (byte)tranparencyIndex + BackgroundColorIndex = unchecked((byte)transparencyIndex) }; writer.Write((ushort)descriptor.Width); @@ -286,8 +275,8 @@ namespace ImageSharp.Formats var extension = new GifGraphicsControlExtension { DisposalMethod = metaData.DisposalMethod, - TransparencyFlag = true, // TODO: The spec here is unclear. Can we get away with this? - TransparencyIndex = transparencyIndex, + TransparencyFlag = transparencyIndex > -1, + TransparencyIndex = unchecked((byte)transparencyIndex), DelayTime = metaData.FrameDelay }; @@ -306,7 +295,7 @@ namespace ImageSharp.Formats writer.Write(field.Byte); writer.Write((ushort)extension.DelayTime); - writer.Write((byte)extension.TransparencyIndex); + writer.Write(extension.TransparencyIndex); writer.Write(GifConstants.Terminator); } diff --git a/src/ImageSharp/Formats/Gif/Sections/GifGraphicsControlExtension.cs b/src/ImageSharp/Formats/Gif/Sections/GifGraphicsControlExtension.cs index 79d98f5fbf..503bd4fdf7 100644 --- a/src/ImageSharp/Formats/Gif/Sections/GifGraphicsControlExtension.cs +++ b/src/ImageSharp/Formats/Gif/Sections/GifGraphicsControlExtension.cs @@ -29,7 +29,7 @@ namespace ImageSharp.Formats /// The Transparency Index is such that when encountered, the corresponding pixel /// of the display device is not modified and processing goes on to the next pixel. /// - public int TransparencyIndex { get; set; } + public byte TransparencyIndex { get; set; } /// /// Gets or sets the delay time. diff --git a/src/ImageSharp/Quantizers/OctreeQuantizer{TPixel}.cs b/src/ImageSharp/Quantizers/OctreeQuantizer{TPixel}.cs index bd963c4527..e19df4cfaa 100644 --- a/src/ImageSharp/Quantizers/OctreeQuantizer{TPixel}.cs +++ b/src/ImageSharp/Quantizers/OctreeQuantizer{TPixel}.cs @@ -36,7 +36,7 @@ namespace ImageSharp.Quantizers /// /// Maximum allowed color depth /// - private int colors; + private byte colors; /// /// The reduced image palette @@ -58,7 +58,7 @@ namespace ImageSharp.Quantizers /// public override QuantizedImage Quantize(ImageBase image, int maxColors) { - this.colors = maxColors.Clamp(1, 255); + this.colors = (byte)maxColors.Clamp(1, 255); this.octree = new Octree(this.GetBitsNeededForColorDepth(this.colors)); this.palette = null; @@ -123,7 +123,7 @@ namespace ImageSharp.Quantizers /// protected override TPixel[] GetPalette() { - return this.palette ?? (this.palette = this.octree.Palletize(Math.Max(this.colors, 1))); + return this.palette ?? (this.palette = this.octree.Palletize(Math.Max(this.colors, (byte)1))); } /// @@ -143,6 +143,12 @@ namespace ImageSharp.Quantizers return this.GetClosestPixel(pixel, this.palette, this.colorMap); } + pixel.ToXyzwBytes(this.pixelBuffer, 0); + if (this.pixelBuffer[3] == 0) + { + return this.colors; + } + return (byte)this.octree.GetPaletteIndex(pixel, this.pixelBuffer); } From fb4a24869daf2723a37aa21f756f4a378b2e4cc0 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Thu, 25 May 2017 10:11:16 +1000 Subject: [PATCH 25/25] Remove Span member from ImageBase --- src/ImageSharp/Image/IImageBase{TPixel}.cs | 6 +-- src/ImageSharp/Image/Image.LoadPixelData.cs | 7 ++-- src/ImageSharp/Image/ImageBase{TPixel}.cs | 41 ++++++++----------- src/ImageSharp/Image/PixelAccessor{TPixel}.cs | 2 +- src/ImageSharp/Memory/Buffer2D.cs | 2 +- .../Processors/Filters/GrayscaleTest.cs | 4 +- 6 files changed, 27 insertions(+), 35 deletions(-) diff --git a/src/ImageSharp/Image/IImageBase{TPixel}.cs b/src/ImageSharp/Image/IImageBase{TPixel}.cs index f0ab5b0334..8b4977b7dc 100644 --- a/src/ImageSharp/Image/IImageBase{TPixel}.cs +++ b/src/ImageSharp/Image/IImageBase{TPixel}.cs @@ -16,10 +16,8 @@ namespace ImageSharp where TPixel : struct, IPixel { /// - /// Gets the pixels as an array of the given packed pixel format. - /// Important. Due to the nature in the way this is constructed do not rely on the length - /// of the array for calculations. Use Width * Height. + /// Gets the representation of the pixels as an area of contiguous memory in the given pixel format. /// - TPixel[] Pixels { get; } + Span Pixels { get; } } } \ No newline at end of file diff --git a/src/ImageSharp/Image/Image.LoadPixelData.cs b/src/ImageSharp/Image/Image.LoadPixelData.cs index 75aa318bed..7b6a4d6684 100644 --- a/src/ImageSharp/Image/Image.LoadPixelData.cs +++ b/src/ImageSharp/Image/Image.LoadPixelData.cs @@ -68,11 +68,10 @@ namespace ImageSharp where TPixel : struct, IPixel { int count = width * height; - Guard.MustBeGreaterThanOrEqualTo(data.Length, width * height, nameof(data)); - var image = new Image(config, width, height); - var dest = new Span(image.Pixels, 0, count); + Guard.MustBeGreaterThanOrEqualTo(data.Length, count, nameof(data)); - SpanHelper.Copy(data, dest, count); + var image = new Image(config, width, height); + SpanHelper.Copy(data, image.Pixels, count); return image; } diff --git a/src/ImageSharp/Image/ImageBase{TPixel}.cs b/src/ImageSharp/Image/ImageBase{TPixel}.cs index 60fcdda488..508c73eb2b 100644 --- a/src/ImageSharp/Image/ImageBase{TPixel}.cs +++ b/src/ImageSharp/Image/ImageBase{TPixel}.cs @@ -32,15 +32,12 @@ namespace ImageSharp /// public const int MaxHeight = int.MaxValue; +#pragma warning disable SA1401 // Fields must be private /// - /// The image pixels + /// The image pixels. Not private as Buffer2D requires an array in its constructor. /// - private TPixel[] pixelBuffer; - - /// - /// The span representing the pixel buffer - /// - private Span span; + internal TPixel[] PixelBuffer; +#pragma warning restore SA1401 // Fields must be private /// /// A value indicating whether this instance of the given entity has been disposed. @@ -116,7 +113,7 @@ namespace ImageSharp } /// - public TPixel[] Pixels => this.pixelBuffer; + public Span Pixels => new Span(this.PixelBuffer, 0, this.Width * this.Height); /// public int Width { get; private set; } @@ -147,14 +144,14 @@ namespace ImageSharp get { this.CheckCoordinates(x, y); - return this.pixelBuffer[(y * this.Width) + x]; + return this.PixelBuffer[(y * this.Width) + x]; } [MethodImpl(MethodImplOptions.AggressiveInlining)] set { this.CheckCoordinates(x, y); - this.pixelBuffer[(y * this.Width) + x] = value; + this.PixelBuffer[(y * this.Width) + x] = value; } } @@ -168,7 +165,7 @@ namespace ImageSharp public ref TPixel GetPixelReference(int x, int y) { this.CheckCoordinates(x, y); - return ref this.pixelBuffer[(y * this.Width) + x]; + return ref this.PixelBuffer[(y * this.Width) + x]; } /// @@ -180,7 +177,7 @@ namespace ImageSharp public Span GetRowSpan(int y) { this.CheckCoordinates(y); - return this.span.Slice(y * this.Width, this.Width); + return this.Pixels.Slice(y * this.Width, this.Width); } /// @@ -193,7 +190,7 @@ namespace ImageSharp public Span GetRowSpan(int x, int y) { this.CheckCoordinates(x, y); - return this.span.Slice((y * this.Width) + x, this.Width - x); + return this.Pixels.Slice((y * this.Width) + x, this.Width - x); } /// @@ -237,7 +234,7 @@ namespace ImageSharp /// The target pixel buffer accessor. internal void CopyTo(PixelAccessor target) { - SpanHelper.Copy(this.span, target.PixelBuffer.Span); + SpanHelper.Copy(this.Pixels, target.PixelBuffer.Span); } /// @@ -251,12 +248,11 @@ namespace ImageSharp int newWidth = pixelSource.Width; int newHeight = pixelSource.Height; - // Push my memory into the accessor (which in turn unpins the old puffer ready for the images use) - TPixel[] newPixels = pixelSource.ReturnCurrentColorsAndReplaceThemInternally(this.Width, this.Height, this.pixelBuffer); + // Push my memory into the accessor (which in turn unpins the old buffer ready for the images use) + TPixel[] newPixels = pixelSource.ReturnCurrentColorsAndReplaceThemInternally(this.Width, this.Height, this.PixelBuffer); this.Width = newWidth; this.Height = newHeight; - this.pixelBuffer = newPixels; - this.span = new Span(this.pixelBuffer); + this.PixelBuffer = newPixels; } /// @@ -307,8 +303,7 @@ namespace ImageSharp /// private void RentPixels() { - this.pixelBuffer = PixelDataPool.Rent(this.Width * this.Height); - this.span = new Span(this.pixelBuffer); + this.PixelBuffer = PixelDataPool.Rent(this.Width * this.Height); } /// @@ -316,8 +311,8 @@ namespace ImageSharp /// private void ReturnPixels() { - PixelDataPool.Return(this.pixelBuffer); - this.pixelBuffer = null; + PixelDataPool.Return(this.PixelBuffer); + this.PixelBuffer = null; } /// @@ -325,7 +320,7 @@ namespace ImageSharp /// private void ClearPixels() { - Array.Clear(this.pixelBuffer, 0, this.Width * this.Height); + Array.Clear(this.PixelBuffer, 0, this.Width * this.Height); } /// diff --git a/src/ImageSharp/Image/PixelAccessor{TPixel}.cs b/src/ImageSharp/Image/PixelAccessor{TPixel}.cs index 1d776c258a..4baae86157 100644 --- a/src/ImageSharp/Image/PixelAccessor{TPixel}.cs +++ b/src/ImageSharp/Image/PixelAccessor{TPixel}.cs @@ -48,7 +48,7 @@ namespace ImageSharp Guard.MustBeGreaterThan(image.Width, 0, "image width"); Guard.MustBeGreaterThan(image.Height, 0, "image height"); - this.SetPixelBufferUnsafe(image.Width, image.Height, image.Pixels); + this.SetPixelBufferUnsafe(image.Width, image.Height, image.PixelBuffer); this.ParallelOptions = image.Configuration.ParallelOptions; } diff --git a/src/ImageSharp/Memory/Buffer2D.cs b/src/ImageSharp/Memory/Buffer2D.cs index e5ccfbd193..59cabb1bd4 100644 --- a/src/ImageSharp/Memory/Buffer2D.cs +++ b/src/ImageSharp/Memory/Buffer2D.cs @@ -69,7 +69,7 @@ namespace ImageSharp.Memory /// The instance public static Buffer2D CreateClean(int width, int height) { - Buffer2D buffer = new Buffer2D(width, height); + var buffer = new Buffer2D(width, height); buffer.Clear(); return buffer; } diff --git a/tests/ImageSharp.Tests/Processors/Filters/GrayscaleTest.cs b/tests/ImageSharp.Tests/Processors/Filters/GrayscaleTest.cs index 9a7d878546..2e82191ec5 100644 --- a/tests/ImageSharp.Tests/Processors/Filters/GrayscaleTest.cs +++ b/tests/ImageSharp.Tests/Processors/Filters/GrayscaleTest.cs @@ -25,9 +25,9 @@ namespace ImageSharp.Tests { image.Grayscale(value); byte[] data = new byte[3]; - foreach (TPixel p in image.Pixels) + for (int i = 0; i < image.Pixels.Length; i++) { - p.ToXyzBytes(data, 0); + image.Pixels[i].ToXyzBytes(data, 0); Assert.Equal(data[0], data[1]); Assert.Equal(data[1], data[2]); }