diff --git a/README.md b/README.md index ca24275469..5617681994 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 be 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/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/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); } diff --git a/src/ImageSharp/Dithering/ErrorDiffusion/ErrorDiffuser.cs b/src/ImageSharp/Dithering/ErrorDiffusion/ErrorDiffuser.cs index 7a5fabdb3a..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; @@ -71,7 +72,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 +80,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 @@ -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); } - - Vector4 coefficientVector = new Vector4(coefficient); - Vector4 offsetColor = pixels[matrixX, matrixY].ToVector4(); - Vector4 result = ((error * coefficientVector) / this.divisorVector) + offsetColor; - result.W = offsetColor.W; - - TPixel packed = default(TPixel); - packed.PackFromVector4(result); - pixels[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/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/Image/IImageBase{TPixel}.cs b/src/ImageSharp/Image/IImageBase{TPixel}.cs index 08d25709b3..8b4977b7dc 100644 --- a/src/ImageSharp/Image/IImageBase{TPixel}.cs +++ b/src/ImageSharp/Image/IImageBase{TPixel}.cs @@ -16,19 +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; } - - /// - /// Locks the image providing access to the pixels. - /// - /// It is imperative that the accessor is correctly disposed off after use. - /// - /// - /// The - PixelAccessor Lock(); + 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 4fd9d26cbf..508c73eb2b 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; @@ -31,10 +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; + internal TPixel[] PixelBuffer; +#pragma warning restore SA1401 // Fields must be private /// /// A value indicating whether this instance of the given entity has been disposed. @@ -110,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; } @@ -129,6 +132,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.Pixels.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.Pixels.Slice((y * this.Width) + x, this.Width - x); + } + /// /// Applies the processor. /// @@ -152,12 +216,27 @@ 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); } + /// + /// Copies the pixels to another of the same size. + /// + /// The target pixel buffer accessor. + internal void CopyTo(PixelAccessor target) + { + SpanHelper.Copy(this.Pixels, 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. /// @@ -169,11 +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.PixelBuffer = newPixels; } /// @@ -224,7 +303,7 @@ namespace ImageSharp /// private void RentPixels() { - this.pixelBuffer = PixelDataPool.Rent(this.Width * this.Height); + this.PixelBuffer = PixelDataPool.Rent(this.Width * this.Height); } /// @@ -232,8 +311,8 @@ namespace ImageSharp /// private void ReturnPixels() { - PixelDataPool.Return(this.pixelBuffer); - this.pixelBuffer = null; + PixelDataPool.Return(this.PixelBuffer); + this.PixelBuffer = null; } /// @@ -241,7 +320,45 @@ namespace ImageSharp /// private void ClearPixels() { - Array.Clear(this.pixelBuffer, 0, this.Width * this.Height); + 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..4baae86157 100644 --- a/src/ImageSharp/Image/PixelAccessor{TPixel}.cs +++ b/src/ImageSharp/Image/PixelAccessor{TPixel}.cs @@ -17,9 +17,16 @@ 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 { +#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. /// @@ -46,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; } @@ -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; @@ -128,12 +130,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); @@ -154,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 @@ -169,7 +173,7 @@ namespace ImageSharp /// public void Reset() { - this.pixelBuffer.Clear(); + this.PixelBuffer.Clear(); } /// @@ -241,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; } @@ -252,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); } /// @@ -423,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/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/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/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/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/ErrorDiffusionDitherProcessor.cs b/src/ImageSharp/Processing/Processors/Binarization/ErrorDiffusionDitherProcessor.cs index af2d9f760a..47811f0ec2 100644 --- a/src/ImageSharp/Processing/Processors/Binarization/ErrorDiffusionDitherProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Binarization/ErrorDiffusionDitherProcessor.cs @@ -85,18 +85,17 @@ 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; + Span row = source.GetRowSpan(offsetY); + + 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 = 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/Processing/Processors/Binarization/OrderedDitherProcessor.cs b/src/ImageSharp/Processing/Processors/Binarization/OrderedDitherProcessor.cs index c4d71d9afe..8983897778 100644 --- a/src/ImageSharp/Processing/Processors/Binarization/OrderedDitherProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Binarization/OrderedDitherProcessor.cs @@ -93,21 +93,17 @@ 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; + Span row = source.GetRowSpan(offsetY); - ArrayPool.Shared.Return(bytes); + for (int x = minX; x < maxX; x++) + { + int offsetX = x - startX; + TPixel sourceColor = row[offsetX]; + this.Dither.Dither(source, sourceColor, this.UpperColor, this.LowerColor, bytes, this.Index, offsetX, offsetY, maxX, maxY); } } } 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 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)); } }); 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/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); }); } 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 diff --git a/src/ImageSharp/Processing/Processors/Effects/ContrastProcessor.cs b/src/ImageSharp/Processing/Processors/Effects/ContrastProcessor.cs index 8308c57e2b..1daead6e50 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,26 @@ 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; + 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 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 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)); } } }); 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 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); } 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); } 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); } 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; } } 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); } } }); 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); } diff --git a/src/ImageSharp/Quantizers/OctreeQuantizer{TPixel}.cs b/src/ImageSharp/Quantizers/OctreeQuantizer{TPixel}.cs index 40bce74c3f..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; @@ -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. @@ -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. @@ -121,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))); } /// @@ -141,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); } @@ -490,7 +498,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..cf3ff94eec 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. @@ -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 48f33f98b9..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; @@ -54,35 +55,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,16 +89,18 @@ 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++) { + 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]); } } } @@ -114,7 +112,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 +153,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..aecca771c1 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. @@ -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. 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]; + } + } } } 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 @@ - + 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"); 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]); }