From 8fb9c466d8cd4a886c3912e86194eb90d7a29468 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Thu, 27 Feb 2020 01:53:32 +0100 Subject: [PATCH 01/64] Renamed ParallelRowIterator.IterateRows to IterateRowIntervals --- src/ImageSharp/Advanced/ParallelRowIterator.cs | 12 ++++++------ src/ImageSharp/ImageFrame{TPixel}.cs | 2 +- .../BinaryThresholdProcessor{TPixel}.cs | 2 +- .../Convolution/BokehBlurProcessor{TPixel}.cs | 8 ++++---- .../Convolution2DProcessor{TPixel}.cs | 2 +- .../Convolution2PassProcessor{TPixel}.cs | 4 ++-- .../ConvolutionProcessor{TPixel}.cs | 2 +- .../EdgeDetectorCompassProcessor{TPixel}.cs | 2 +- .../Processors/Dithering/OrderedDither.cs | 4 ++-- .../DrawImageProcessor{TPixelBg,TPixelFg}.cs | 2 +- .../Effects/OilPaintingProcessor{TPixel}.cs | 2 +- ...elRowDelegateProcessor{TPixel,TDelegate}.cs | 2 +- .../Filters/FilterProcessor{TPixel}.cs | 2 +- ...veHistogramEqualizationProcessor{TPixel}.cs | 4 ++-- ...alHistogramEqualizationProcessor{TPixel}.cs | 4 ++-- .../BackgroundColorProcessor{TPixel}.cs | 2 +- .../Overlays/GlowProcessor{TPixel}.cs | 2 +- .../Overlays/VignetteProcessor{TPixel}.cs | 2 +- .../Quantization/FrameQuantizerExtensions.cs | 2 +- .../Quantization/QuantizeProcessor{TPixel}.cs | 2 +- .../Transforms/CropProcessor{TPixel}.cs | 2 +- .../Linear/AffineTransformProcessor{TPixel}.cs | 4 ++-- .../Transforms/Linear/FlipProcessor{TPixel}.cs | 2 +- .../ProjectiveTransformProcessor{TPixel}.cs | 4 ++-- .../Linear/RotateProcessor{TPixel}.cs | 6 +++--- .../Resize/ResizeProcessor{TPixel}.cs | 2 +- .../Helpers/ParallelRowIteratorTests.cs | 18 +++++++++--------- .../TestUtilities/TestImageExtensions.cs | 2 +- 28 files changed, 52 insertions(+), 52 deletions(-) diff --git a/src/ImageSharp/Advanced/ParallelRowIterator.cs b/src/ImageSharp/Advanced/ParallelRowIterator.cs index 123784c57..70b48aee9 100644 --- a/src/ImageSharp/Advanced/ParallelRowIterator.cs +++ b/src/ImageSharp/Advanced/ParallelRowIterator.cs @@ -25,11 +25,11 @@ namespace SixLabors.ImageSharp.Advanced /// The . /// The operation defining the iteration logic on a single . [MethodImpl(InliningOptions.ShortMethod)] - public static void IterateRows(Configuration configuration, Rectangle rectangle, in T operation) + public static void IterateRowIntervals(Configuration configuration, Rectangle rectangle, in T operation) where T : struct, IRowIntervalOperation { var parallelSettings = ParallelExecutionSettings.FromConfiguration(configuration); - IterateRows(rectangle, in parallelSettings, in operation); + IterateRowIntervals(rectangle, in parallelSettings, in operation); } /// @@ -39,7 +39,7 @@ namespace SixLabors.ImageSharp.Advanced /// The . /// The . /// The operation defining the iteration logic on a single . - public static void IterateRows( + public static void IterateRowIntervals( Rectangle rectangle, in ParallelExecutionSettings parallelSettings, in T operation) @@ -84,12 +84,12 @@ namespace SixLabors.ImageSharp.Advanced /// The to get the parallel settings from. /// The . /// The operation defining the iteration logic on a single . - public static void IterateRows(Configuration configuration, Rectangle rectangle, in T operation) + public static void IterateRowIntervals(Configuration configuration, Rectangle rectangle, in T operation) where T : struct, IRowIntervalOperation where TBuffer : unmanaged { var parallelSettings = ParallelExecutionSettings.FromConfiguration(configuration); - IterateRows(rectangle, in parallelSettings, in operation); + IterateRowIntervals(rectangle, in parallelSettings, in operation); } /// @@ -101,7 +101,7 @@ namespace SixLabors.ImageSharp.Advanced /// The . /// The . /// The operation defining the iteration logic on a single . - public static void IterateRows( + public static void IterateRowIntervals( Rectangle rectangle, in ParallelExecutionSettings parallelSettings, in T operation) diff --git a/src/ImageSharp/ImageFrame{TPixel}.cs b/src/ImageSharp/ImageFrame{TPixel}.cs index 85488c12d..f5f1e4079 100644 --- a/src/ImageSharp/ImageFrame{TPixel}.cs +++ b/src/ImageSharp/ImageFrame{TPixel}.cs @@ -277,7 +277,7 @@ namespace SixLabors.ImageSharp var target = new ImageFrame(configuration, this.Width, this.Height, this.Metadata.DeepClone()); var operation = new RowIntervalOperation(this, target, configuration); - ParallelRowIterator.IterateRows( + ParallelRowIterator.IterateRowIntervals( configuration, this.Bounds(), in operation); diff --git a/src/ImageSharp/Processing/Processors/Binarization/BinaryThresholdProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Binarization/BinaryThresholdProcessor{TPixel}.cs index ed14a44e9..c3189427f 100644 --- a/src/ImageSharp/Processing/Processors/Binarization/BinaryThresholdProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Binarization/BinaryThresholdProcessor{TPixel}.cs @@ -45,7 +45,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Binarization bool isAlphaOnly = typeof(TPixel) == typeof(A8); var operation = new RowIntervalOperation(interest, source, upper, lower, threshold, isAlphaOnly); - ParallelRowIterator.IterateRows( + ParallelRowIterator.IterateRowIntervals( configuration, interest, in operation); diff --git a/src/ImageSharp/Processing/Processors/Convolution/BokehBlurProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Convolution/BokehBlurProcessor{TPixel}.cs index 36d36223a..9a910ae29 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/BokehBlurProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/BokehBlurProcessor{TPixel}.cs @@ -73,7 +73,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution { // Preliminary gamma highlight pass var gammaOperation = new ApplyGammaExposureRowIntervalOperation(this.SourceRectangle, source.PixelBuffer, this.Configuration, this.gamma); - ParallelRowIterator.IterateRows( + ParallelRowIterator.IterateRowIntervals( this.Configuration, this.SourceRectangle, in gammaOperation); @@ -88,7 +88,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution // Apply the inverse gamma exposure pass, and write the final pixel data var operation = new ApplyInverseGammaExposureRowIntervalOperation(this.SourceRectangle, source.PixelBuffer, processingBuffer, this.Configuration, inverseGamma); - ParallelRowIterator.IterateRows( + ParallelRowIterator.IterateRowIntervals( this.Configuration, this.SourceRectangle, in operation); @@ -121,14 +121,14 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution // Compute the vertical 1D convolution var verticalOperation = new ApplyVerticalConvolutionRowIntervalOperation(sourceRectangle, firstPassBuffer, source.PixelBuffer, kernel); - ParallelRowIterator.IterateRows( + ParallelRowIterator.IterateRowIntervals( configuration, sourceRectangle, in verticalOperation); // Compute the horizontal 1D convolutions and accumulate the partial results on the target buffer var horizontalOperation = new ApplyHorizontalConvolutionRowIntervalOperation(sourceRectangle, processingBuffer, firstPassBuffer, kernel, parameters.Z, parameters.W); - ParallelRowIterator.IterateRows( + ParallelRowIterator.IterateRowIntervals( configuration, sourceRectangle, in horizontalOperation); diff --git a/src/ImageSharp/Processing/Processors/Convolution/Convolution2DProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Convolution/Convolution2DProcessor{TPixel}.cs index 1c4987c79..219c81617 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/Convolution2DProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/Convolution2DProcessor{TPixel}.cs @@ -67,7 +67,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution var interest = Rectangle.Intersect(this.SourceRectangle, source.Bounds()); var operation = new RowIntervalOperation(interest, targetPixels, source.PixelBuffer, this.KernelY, this.KernelX, this.Configuration, this.PreserveAlpha); - ParallelRowIterator.IterateRows( + ParallelRowIterator.IterateRowIntervals( this.Configuration, interest, in operation); diff --git a/src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor{TPixel}.cs index 33a8ab7d1..833c03308 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor{TPixel}.cs @@ -65,14 +65,14 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution // Horizontal convolution var horizontalOperation = new RowIntervalOperation(interest, firstPassPixels, source.PixelBuffer, this.KernelX, this.Configuration, this.PreserveAlpha); - ParallelRowIterator.IterateRows( + ParallelRowIterator.IterateRowIntervals( this.Configuration, interest, in horizontalOperation); // Vertical convolution var verticalOperation = new RowIntervalOperation(interest, source.PixelBuffer, firstPassPixels, this.KernelY, this.Configuration, this.PreserveAlpha); - ParallelRowIterator.IterateRows( + ParallelRowIterator.IterateRowIntervals( this.Configuration, interest, in verticalOperation); diff --git a/src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessor{TPixel}.cs index 542ee389b..fae222714 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessor{TPixel}.cs @@ -57,7 +57,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution var interest = Rectangle.Intersect(this.SourceRectangle, source.Bounds()); var operation = new RowIntervalOperation(interest, targetPixels, source.PixelBuffer, this.KernelXY, this.Configuration, this.PreserveAlpha); - ParallelRowIterator.IterateRows( + ParallelRowIterator.IterateRowIntervals( this.Configuration, interest, in operation); diff --git a/src/ImageSharp/Processing/Processors/Convolution/EdgeDetectorCompassProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Convolution/EdgeDetectorCompassProcessor{TPixel}.cs index c4da1e4b0..f15acd39a 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/EdgeDetectorCompassProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/EdgeDetectorCompassProcessor{TPixel}.cs @@ -79,7 +79,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution } var operation = new RowIntervalOperation(source.PixelBuffer, pass.PixelBuffer, interest); - ParallelRowIterator.IterateRows( + ParallelRowIterator.IterateRowIntervals( this.Configuration, interest, in operation); diff --git a/src/ImageSharp/Processing/Processors/Dithering/OrderedDither.cs b/src/ImageSharp/Processing/Processors/Dithering/OrderedDither.cs index 69e323bd5..4e5cfefdf 100644 --- a/src/ImageSharp/Processing/Processors/Dithering/OrderedDither.cs +++ b/src/ImageSharp/Processing/Processors/Dithering/OrderedDither.cs @@ -121,7 +121,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering palette, ImageMaths.GetBitsNeededForColorDepth(palette.Span.Length)); - ParallelRowIterator.IterateRows( + ParallelRowIterator.IterateRowIntervals( quantizer.Configuration, bounds, in ditherOperation); @@ -145,7 +145,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering scale, ImageMaths.GetBitsNeededForColorDepth(palette.Span.Length)); - ParallelRowIterator.IterateRows( + ParallelRowIterator.IterateRowIntervals( configuration, bounds, in ditherOperation); diff --git a/src/ImageSharp/Processing/Processors/Drawing/DrawImageProcessor{TPixelBg,TPixelFg}.cs b/src/ImageSharp/Processing/Processors/Drawing/DrawImageProcessor{TPixelBg,TPixelFg}.cs index c1ce30cae..7a26a03a1 100644 --- a/src/ImageSharp/Processing/Processors/Drawing/DrawImageProcessor{TPixelBg,TPixelFg}.cs +++ b/src/ImageSharp/Processing/Processors/Drawing/DrawImageProcessor{TPixelBg,TPixelFg}.cs @@ -100,7 +100,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Drawing } var operation = new RowIntervalOperation(source, targetImage, blender, configuration, minX, width, locationY, targetX, this.Opacity); - ParallelRowIterator.IterateRows( + ParallelRowIterator.IterateRowIntervals( configuration, workingRect, in operation); diff --git a/src/ImageSharp/Processing/Processors/Effects/OilPaintingProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Effects/OilPaintingProcessor{TPixel}.cs index 50c0a22d3..42bd4de6d 100644 --- a/src/ImageSharp/Processing/Processors/Effects/OilPaintingProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Effects/OilPaintingProcessor{TPixel}.cs @@ -48,7 +48,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Effects source.CopyTo(targetPixels); var operation = new RowIntervalOperation(this.SourceRectangle, targetPixels, source, this.Configuration, brushSize >> 1, this.definition.Levels); - ParallelRowIterator.IterateRows( + ParallelRowIterator.IterateRowIntervals( this.Configuration, this.SourceRectangle, in operation); diff --git a/src/ImageSharp/Processing/Processors/Effects/PixelRowDelegateProcessor{TPixel,TDelegate}.cs b/src/ImageSharp/Processing/Processors/Effects/PixelRowDelegateProcessor{TPixel,TDelegate}.cs index 44ade727a..f94cd5080 100644 --- a/src/ImageSharp/Processing/Processors/Effects/PixelRowDelegateProcessor{TPixel,TDelegate}.cs +++ b/src/ImageSharp/Processing/Processors/Effects/PixelRowDelegateProcessor{TPixel,TDelegate}.cs @@ -52,7 +52,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Effects var interest = Rectangle.Intersect(this.SourceRectangle, source.Bounds()); var operation = new RowIntervalOperation(interest.X, source, this.Configuration, this.modifiers, this.rowDelegate); - ParallelRowIterator.IterateRows( + ParallelRowIterator.IterateRowIntervals( this.Configuration, interest, in operation); diff --git a/src/ImageSharp/Processing/Processors/Filters/FilterProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Filters/FilterProcessor{TPixel}.cs index 19142ceb0..a677f6027 100644 --- a/src/ImageSharp/Processing/Processors/Filters/FilterProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Filters/FilterProcessor{TPixel}.cs @@ -38,7 +38,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Filters var interest = Rectangle.Intersect(this.SourceRectangle, source.Bounds()); var operation = new RowIntervalOperation(interest.X, source, this.definition.Matrix, this.Configuration); - ParallelRowIterator.IterateRows( + ParallelRowIterator.IterateRowIntervals( this.Configuration, interest, in operation); diff --git a/src/ImageSharp/Processing/Processors/Normalization/AdaptiveHistogramEqualizationProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Normalization/AdaptiveHistogramEqualizationProcessor{TPixel}.cs index eb666a4f1..362a62ff9 100644 --- a/src/ImageSharp/Processing/Processors/Normalization/AdaptiveHistogramEqualizationProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Normalization/AdaptiveHistogramEqualizationProcessor{TPixel}.cs @@ -81,7 +81,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization } var operation = new RowIntervalOperation(cdfData, tileYStartPositions, tileWidth, tileHeight, tileCount, halfTileWidth, luminanceLevels, source); - ParallelRowIterator.IterateRows( + ParallelRowIterator.IterateRowIntervals( this.Configuration, new Rectangle(0, 0, sourceWidth, tileYStartPositions.Count), in operation); @@ -522,7 +522,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization this.luminanceLevels, source); - ParallelRowIterator.IterateRows( + ParallelRowIterator.IterateRowIntervals( this.configuration, new Rectangle(0, 0, this.sourceWidth, this.tileYStartPositions.Count), in operation); diff --git a/src/ImageSharp/Processing/Processors/Normalization/GlobalHistogramEqualizationProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Normalization/GlobalHistogramEqualizationProcessor{TPixel}.cs index 07fa55c5d..0567151bc 100644 --- a/src/ImageSharp/Processing/Processors/Normalization/GlobalHistogramEqualizationProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Normalization/GlobalHistogramEqualizationProcessor{TPixel}.cs @@ -53,7 +53,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization // Build the histogram of the grayscale levels var grayscaleOperation = new GrayscaleLevelsRowIntervalOperation(interest, histogramBuffer, source, this.LuminanceLevels); - ParallelRowIterator.IterateRows( + ParallelRowIterator.IterateRowIntervals( this.Configuration, interest, in grayscaleOperation); @@ -76,7 +76,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization // Apply the cdf to each pixel of the image var cdfOperation = new CdfApplicationRowIntervalOperation(interest, cdfBuffer, source, this.LuminanceLevels, numberOfPixelsMinusCdfMin); - ParallelRowIterator.IterateRows( + ParallelRowIterator.IterateRowIntervals( this.Configuration, interest, in cdfOperation); diff --git a/src/ImageSharp/Processing/Processors/Overlays/BackgroundColorProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Overlays/BackgroundColorProcessor{TPixel}.cs index b796016d1..3f4174d6e 100644 --- a/src/ImageSharp/Processing/Processors/Overlays/BackgroundColorProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Overlays/BackgroundColorProcessor{TPixel}.cs @@ -50,7 +50,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Overlays PixelBlender blender = PixelOperations.Instance.GetPixelBlender(graphicsOptions); var operation = new RowIntervalOperation(configuration, interest, blender, amount, colors, source); - ParallelRowIterator.IterateRows( + ParallelRowIterator.IterateRowIntervals( configuration, interest, in operation); diff --git a/src/ImageSharp/Processing/Processors/Overlays/GlowProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Overlays/GlowProcessor{TPixel}.cs index 21a4e1345..ff148e839 100644 --- a/src/ImageSharp/Processing/Processors/Overlays/GlowProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Overlays/GlowProcessor{TPixel}.cs @@ -56,7 +56,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Overlays rowColors.GetSpan().Fill(glowColor); var operation = new RowIntervalOperation(configuration, interest, rowColors, this.blender, center, maxDistance, blendPercent, source); - ParallelRowIterator.IterateRows( + ParallelRowIterator.IterateRowIntervals( configuration, interest, in operation); diff --git a/src/ImageSharp/Processing/Processors/Overlays/VignetteProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Overlays/VignetteProcessor{TPixel}.cs index 3515a2891..ce69de2fd 100644 --- a/src/ImageSharp/Processing/Processors/Overlays/VignetteProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Overlays/VignetteProcessor{TPixel}.cs @@ -64,7 +64,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Overlays rowColors.GetSpan().Fill(vignetteColor); var operation = new RowIntervalOperation(configuration, interest, rowColors, this.blender, center, maxDistance, blendPercent, source); - ParallelRowIterator.IterateRows( + ParallelRowIterator.IterateRowIntervals( configuration, interest, in operation); diff --git a/src/ImageSharp/Processing/Processors/Quantization/FrameQuantizerExtensions.cs b/src/ImageSharp/Processing/Processors/Quantization/FrameQuantizerExtensions.cs index 5b49fe9e8..8dd6d984e 100644 --- a/src/ImageSharp/Processing/Processors/Quantization/FrameQuantizerExtensions.cs +++ b/src/ImageSharp/Processing/Processors/Quantization/FrameQuantizerExtensions.cs @@ -74,7 +74,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization if (dither is null) { var operation = new RowIntervalOperation(quantizer, source, output, bounds, palette); - ParallelRowIterator.IterateRows( + ParallelRowIterator.IterateRowIntervals( quantizer.Configuration, bounds, in operation); diff --git a/src/ImageSharp/Processing/Processors/Quantization/QuantizeProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Quantization/QuantizeProcessor{TPixel}.cs index bfcc26ae2..da191728f 100644 --- a/src/ImageSharp/Processing/Processors/Quantization/QuantizeProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Quantization/QuantizeProcessor{TPixel}.cs @@ -42,7 +42,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization using QuantizedFrame quantized = frameQuantizer.QuantizeFrame(source, interest); var operation = new RowIntervalOperation(this.SourceRectangle, source, quantized); - ParallelRowIterator.IterateRows( + ParallelRowIterator.IterateRowIntervals( configuration, interest, in operation); diff --git a/src/ImageSharp/Processing/Processors/Transforms/CropProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Transforms/CropProcessor{TPixel}.cs index a80eef2bc..d72790ea1 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/CropProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/CropProcessor{TPixel}.cs @@ -53,7 +53,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms var operation = new RowIntervalOperation(bounds, source, destination); - ParallelRowIterator.IterateRows( + ParallelRowIterator.IterateRowIntervals( bounds, in parallelSettings, in operation); diff --git a/src/ImageSharp/Processing/Processors/Transforms/Linear/AffineTransformProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Transforms/Linear/AffineTransformProcessor{TPixel}.cs index 72bfa4c0b..26475922f 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/Linear/AffineTransformProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/Linear/AffineTransformProcessor{TPixel}.cs @@ -72,7 +72,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms if (sampler is NearestNeighborResampler) { var nnOperation = new NNAffineOperation(source, destination, matrix); - ParallelRowIterator.IterateRows( + ParallelRowIterator.IterateRowIntervals( configuration, destination.Bounds(), in nnOperation); @@ -105,7 +105,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms radialExtents, maxSourceExtents); - ParallelRowIterator.IterateRows, Vector4>( + ParallelRowIterator.IterateRowIntervals, Vector4>( configuration, destination.Bounds(), in operation); diff --git a/src/ImageSharp/Processing/Processors/Transforms/Linear/FlipProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Transforms/Linear/FlipProcessor{TPixel}.cs index 877fa074e..f5fde8a22 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/Linear/FlipProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/Linear/FlipProcessor{TPixel}.cs @@ -77,7 +77,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms private void FlipY(ImageFrame source, Configuration configuration) { var operation = new RowIntervalOperation(source); - ParallelRowIterator.IterateRows( + ParallelRowIterator.IterateRowIntervals( configuration, source.Bounds(), in operation); diff --git a/src/ImageSharp/Processing/Processors/Transforms/Linear/ProjectiveTransformProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Transforms/Linear/ProjectiveTransformProcessor{TPixel}.cs index b3315fa55..07178117a 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/Linear/ProjectiveTransformProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/Linear/ProjectiveTransformProcessor{TPixel}.cs @@ -72,7 +72,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms if (sampler is NearestNeighborResampler) { var nnOperation = new NNProjectiveOperation(source, destination, matrix); - ParallelRowIterator.IterateRows( + ParallelRowIterator.IterateRowIntervals( configuration, destination.Bounds(), in nnOperation); @@ -105,7 +105,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms radialExtents, maxSourceExtents); - ParallelRowIterator.IterateRows, Vector4>( + ParallelRowIterator.IterateRowIntervals, Vector4>( configuration, destination.Bounds(), in operation); diff --git a/src/ImageSharp/Processing/Processors/Transforms/Linear/RotateProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Transforms/Linear/RotateProcessor{TPixel}.cs index 198e142d0..d6755e8ba 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/Linear/RotateProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/Linear/RotateProcessor{TPixel}.cs @@ -132,7 +132,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms private void Rotate180(ImageFrame source, ImageFrame destination, Configuration configuration) { var operation = new Rotate180RowIntervalOperation(source.Width, source.Height, source, destination); - ParallelRowIterator.IterateRows( + ParallelRowIterator.IterateRowIntervals( configuration, source.Bounds(), in operation); @@ -147,7 +147,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms private void Rotate270(ImageFrame source, ImageFrame destination, Configuration configuration) { var operation = new Rotate270RowIntervalOperation(destination.Bounds(), source.Width, source.Height, source, destination); - ParallelRowIterator.IterateRows( + ParallelRowIterator.IterateRowIntervals( configuration, source.Bounds(), in operation); @@ -162,7 +162,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms private void Rotate90(ImageFrame source, ImageFrame destination, Configuration configuration) { var operation = new Rotate90RowIntervalOperation(destination.Bounds(), source.Width, source.Height, source, destination); - ParallelRowIterator.IterateRows( + ParallelRowIterator.IterateRowIntervals( configuration, source.Bounds(), in operation); diff --git a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeProcessor{TPixel}.cs index 1a6b8030d..c0dc88688 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeProcessor{TPixel}.cs @@ -152,7 +152,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms source, destination); - ParallelRowIterator.IterateRows( + ParallelRowIterator.IterateRowIntervals( configuration, interest, in operation); diff --git a/tests/ImageSharp.Tests/Helpers/ParallelRowIteratorTests.cs b/tests/ImageSharp.Tests/Helpers/ParallelRowIteratorTests.cs index 332a141e9..08d64a738 100644 --- a/tests/ImageSharp.Tests/Helpers/ParallelRowIteratorTests.cs +++ b/tests/ImageSharp.Tests/Helpers/ParallelRowIteratorTests.cs @@ -78,7 +78,7 @@ namespace SixLabors.ImageSharp.Tests.Helpers var operation = new TestRowIntervalOperation(RowAction); - ParallelRowIterator.IterateRows( + ParallelRowIterator.IterateRowIntervals( rectangle, in parallelSettings, in operation); @@ -116,7 +116,7 @@ namespace SixLabors.ImageSharp.Tests.Helpers var operation = new TestRowIntervalOperation(RowAction); - ParallelRowIterator.IterateRows( + ParallelRowIterator.IterateRowIntervals( rectangle, in parallelSettings, in operation); @@ -157,7 +157,7 @@ namespace SixLabors.ImageSharp.Tests.Helpers var operation = new TestRowIntervalOperation(RowAction); - ParallelRowIterator.IterateRows, Vector4>( + ParallelRowIterator.IterateRowIntervals, Vector4>( rectangle, in parallelSettings, in operation); @@ -195,7 +195,7 @@ namespace SixLabors.ImageSharp.Tests.Helpers var operation = new TestRowIntervalOperation(RowAction); - ParallelRowIterator.IterateRows, Vector4>( + ParallelRowIterator.IterateRowIntervals, Vector4>( rectangle, in parallelSettings, in operation); @@ -249,7 +249,7 @@ namespace SixLabors.ImageSharp.Tests.Helpers var operation = new TestRowIntervalOperation(RowAction); - ParallelRowIterator.IterateRows( + ParallelRowIterator.IterateRowIntervals( rectangle, in parallelSettings, in operation); @@ -291,7 +291,7 @@ namespace SixLabors.ImageSharp.Tests.Helpers var operation = new TestRowIntervalOperation(RowAction); - ParallelRowIterator.IterateRows, Vector4>( + ParallelRowIterator.IterateRowIntervals, Vector4>( rectangle, in parallelSettings, in operation); @@ -355,7 +355,7 @@ namespace SixLabors.ImageSharp.Tests.Helpers var operation = new TestRowIntervalOperation(RowAction); - ParallelRowIterator.IterateRows( + ParallelRowIterator.IterateRowIntervals( rect, settings, in operation); @@ -383,7 +383,7 @@ namespace SixLabors.ImageSharp.Tests.Helpers var operation = new TestRowIntervalOperation(RowAction); ArgumentOutOfRangeException ex = Assert.Throws( - () => ParallelRowIterator.IterateRows(rect, in parallelSettings, in operation)); + () => ParallelRowIterator.IterateRowIntervals(rect, in parallelSettings, in operation)); Assert.Contains(width <= 0 ? "Width" : "Height", ex.Message); } @@ -406,7 +406,7 @@ namespace SixLabors.ImageSharp.Tests.Helpers var operation = new TestRowIntervalOperation(RowAction); ArgumentOutOfRangeException ex = Assert.Throws( - () => ParallelRowIterator.IterateRows, Rgba32>(rect, in parallelSettings, in operation)); + () => ParallelRowIterator.IterateRowIntervals, Rgba32>(rect, in parallelSettings, in operation)); Assert.Contains(width <= 0 ? "Width" : "Height", ex.Message); } diff --git a/tests/ImageSharp.Tests/TestUtilities/TestImageExtensions.cs b/tests/ImageSharp.Tests/TestUtilities/TestImageExtensions.cs index 502a5bf46..460516568 100644 --- a/tests/ImageSharp.Tests/TestUtilities/TestImageExtensions.cs +++ b/tests/ImageSharp.Tests/TestUtilities/TestImageExtensions.cs @@ -713,7 +713,7 @@ namespace SixLabors.ImageSharp.Tests var operation = new RowOperation(configuration, sourceRectangle, source); - ParallelRowIterator.IterateRows( + ParallelRowIterator.IterateRowIntervals( configuration, sourceRectangle, in operation); From 0d90e57e218b1360cfbc85a4ccea8c2035fbb230 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Thu, 27 Feb 2020 12:46:45 +0100 Subject: [PATCH 02/64] Removed unnecessary using directives --- src/ImageSharp/Advanced/IRowIntervalOperation.cs | 2 -- src/ImageSharp/Advanced/IRowIntervalOperation{TBuffer}.cs | 2 -- 2 files changed, 4 deletions(-) diff --git a/src/ImageSharp/Advanced/IRowIntervalOperation.cs b/src/ImageSharp/Advanced/IRowIntervalOperation.cs index 3e1b08621..980ed91a7 100644 --- a/src/ImageSharp/Advanced/IRowIntervalOperation.cs +++ b/src/ImageSharp/Advanced/IRowIntervalOperation.cs @@ -1,8 +1,6 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -using System; -using System.Runtime.CompilerServices; using SixLabors.ImageSharp.Memory; namespace SixLabors.ImageSharp.Advanced diff --git a/src/ImageSharp/Advanced/IRowIntervalOperation{TBuffer}.cs b/src/ImageSharp/Advanced/IRowIntervalOperation{TBuffer}.cs index c18842a92..47fcf253e 100644 --- a/src/ImageSharp/Advanced/IRowIntervalOperation{TBuffer}.cs +++ b/src/ImageSharp/Advanced/IRowIntervalOperation{TBuffer}.cs @@ -2,8 +2,6 @@ // Licensed under the Apache License, Version 2.0. using System; -using System.Buffers; -using System.Runtime.CompilerServices; using SixLabors.ImageSharp.Memory; namespace SixLabors.ImageSharp.Advanced From 872015fe5585bfd7b2b948d8d69acb474da977fa Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Thu, 27 Feb 2020 12:46:58 +0100 Subject: [PATCH 03/64] Added single row value delegate interfaces --- src/ImageSharp/Advanced/IRowAction.cs | 17 ++++++++++++++ .../Advanced/IRowAction{TBuffer}.cs | 22 +++++++++++++++++++ 2 files changed, 39 insertions(+) create mode 100644 src/ImageSharp/Advanced/IRowAction.cs create mode 100644 src/ImageSharp/Advanced/IRowAction{TBuffer}.cs diff --git a/src/ImageSharp/Advanced/IRowAction.cs b/src/ImageSharp/Advanced/IRowAction.cs new file mode 100644 index 000000000..b541160af --- /dev/null +++ b/src/ImageSharp/Advanced/IRowAction.cs @@ -0,0 +1,17 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Advanced +{ + /// + /// Defines the contract for an action that operates on a row. + /// + public interface IRowAction + { + /// + /// Invokes the method passing the row y coordinate. + /// + /// The row y coordinate. + void Invoke(int y); + } +} diff --git a/src/ImageSharp/Advanced/IRowAction{TBuffer}.cs b/src/ImageSharp/Advanced/IRowAction{TBuffer}.cs new file mode 100644 index 000000000..aff043aa2 --- /dev/null +++ b/src/ImageSharp/Advanced/IRowAction{TBuffer}.cs @@ -0,0 +1,22 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; + +namespace SixLabors.ImageSharp.Advanced +{ + /// + /// Defines the contract for an action that operates on a row with a temporary buffer. + /// + /// The type of buffer elements. + public interface IRowAction + where TBuffer : unmanaged + { + /// + /// Invokes the method passing the row and a buffer. + /// + /// The row y coordinate. + /// The contiguous region of memory. + void Invoke(int y, Span span); + } +} From ec8942605481b87d95c89299fb104b2751f919c1 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Thu, 27 Feb 2020 12:47:07 +0100 Subject: [PATCH 04/64] Added single row value delegate wrappers --- .../Advanced/ParallelRowIterator.Wrappers.cs | 75 +++++++++++++++++++ 1 file changed, 75 insertions(+) diff --git a/src/ImageSharp/Advanced/ParallelRowIterator.Wrappers.cs b/src/ImageSharp/Advanced/ParallelRowIterator.Wrappers.cs index adbad0d66..bd30d4ff4 100644 --- a/src/ImageSharp/Advanced/ParallelRowIterator.Wrappers.cs +++ b/src/ImageSharp/Advanced/ParallelRowIterator.Wrappers.cs @@ -38,6 +38,81 @@ namespace SixLabors.ImageSharp.Advanced } } + private readonly struct WrappingRowAction + where T : struct, IRowAction + { + private readonly IterationParameters info; + private readonly T action; + + [MethodImpl(InliningOptions.ShortMethod)] + public WrappingRowAction(in IterationParameters info, in T action) + { + this.info = info; + this.action = action; + } + + [MethodImpl(InliningOptions.ShortMethod)] + public void Invoke(int i) + { + int yMin = this.info.MinY + (i * this.info.StepY); + + if (yMin >= this.info.MaxY) + { + return; + } + + int yMax = Math.Min(yMin + this.info.StepY, this.info.MaxY); + + for (int y = yMin; y < yMax; y++) + { + // Skip the safety copy when invoking a potentially impure method on a readonly field + Unsafe.AsRef(this.action).Invoke(y); + } + } + } + + private readonly struct WrappingRowAction + where T : struct, IRowAction + where TBuffer : unmanaged + { + private readonly IterationParameters info; + private readonly MemoryAllocator allocator; + private readonly T action; + + [MethodImpl(InliningOptions.ShortMethod)] + public WrappingRowAction( + in IterationParameters info, + MemoryAllocator allocator, + in T action) + { + this.info = info; + this.allocator = allocator; + this.action = action; + } + + [MethodImpl(InliningOptions.ShortMethod)] + public void Invoke(int i) + { + int yMin = this.info.MinY + (i * this.info.StepY); + + if (yMin >= this.info.MaxY) + { + return; + } + + int yMax = Math.Min(yMin + this.info.StepY, this.info.MaxY); + + using IMemoryOwner buffer = this.allocator.Allocate(this.info.MaxX); + + Span span = buffer.Memory.Span; + + for (int y = yMin; y < yMax; y++) + { + Unsafe.AsRef(this.action).Invoke(y, span); + } + } + } + private readonly struct RowIntervalOperationWrapper where T : struct, IRowIntervalOperation { From 9db12514726c40700eb4f3e720c69cfe95c8048e Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Thu, 27 Feb 2020 12:54:40 +0100 Subject: [PATCH 05/64] Renamed new APIs --- .../Advanced/{IRowAction.cs => IRowOperation.cs} | 2 +- ...ction{TBuffer}.cs => IRowOperation{TBuffer}.cs} | 2 +- .../Advanced/ParallelRowIterator.Wrappers.cs | 14 +++++++------- 3 files changed, 9 insertions(+), 9 deletions(-) rename src/ImageSharp/Advanced/{IRowAction.cs => IRowOperation.cs} (92%) rename src/ImageSharp/Advanced/{IRowAction{TBuffer}.cs => IRowOperation{TBuffer}.cs} (94%) diff --git a/src/ImageSharp/Advanced/IRowAction.cs b/src/ImageSharp/Advanced/IRowOperation.cs similarity index 92% rename from src/ImageSharp/Advanced/IRowAction.cs rename to src/ImageSharp/Advanced/IRowOperation.cs index b541160af..0a6065e4b 100644 --- a/src/ImageSharp/Advanced/IRowAction.cs +++ b/src/ImageSharp/Advanced/IRowOperation.cs @@ -6,7 +6,7 @@ namespace SixLabors.ImageSharp.Advanced /// /// Defines the contract for an action that operates on a row. /// - public interface IRowAction + public interface IRowOperation { /// /// Invokes the method passing the row y coordinate. diff --git a/src/ImageSharp/Advanced/IRowAction{TBuffer}.cs b/src/ImageSharp/Advanced/IRowOperation{TBuffer}.cs similarity index 94% rename from src/ImageSharp/Advanced/IRowAction{TBuffer}.cs rename to src/ImageSharp/Advanced/IRowOperation{TBuffer}.cs index aff043aa2..7a13930fa 100644 --- a/src/ImageSharp/Advanced/IRowAction{TBuffer}.cs +++ b/src/ImageSharp/Advanced/IRowOperation{TBuffer}.cs @@ -9,7 +9,7 @@ namespace SixLabors.ImageSharp.Advanced /// Defines the contract for an action that operates on a row with a temporary buffer. /// /// The type of buffer elements. - public interface IRowAction + public interface IRowOperation where TBuffer : unmanaged { /// diff --git a/src/ImageSharp/Advanced/ParallelRowIterator.Wrappers.cs b/src/ImageSharp/Advanced/ParallelRowIterator.Wrappers.cs index bd30d4ff4..7c3861fdf 100644 --- a/src/ImageSharp/Advanced/ParallelRowIterator.Wrappers.cs +++ b/src/ImageSharp/Advanced/ParallelRowIterator.Wrappers.cs @@ -38,14 +38,14 @@ namespace SixLabors.ImageSharp.Advanced } } - private readonly struct WrappingRowAction - where T : struct, IRowAction + private readonly struct RowOperationWrapper + where T : struct, IRowOperation { private readonly IterationParameters info; private readonly T action; [MethodImpl(InliningOptions.ShortMethod)] - public WrappingRowAction(in IterationParameters info, in T action) + public RowOperationWrapper(in IterationParameters info, in T action) { this.info = info; this.action = action; @@ -71,8 +71,8 @@ namespace SixLabors.ImageSharp.Advanced } } - private readonly struct WrappingRowAction - where T : struct, IRowAction + private readonly struct RowOperationWrapper + where T : struct, IRowOperation where TBuffer : unmanaged { private readonly IterationParameters info; @@ -80,7 +80,7 @@ namespace SixLabors.ImageSharp.Advanced private readonly T action; [MethodImpl(InliningOptions.ShortMethod)] - public WrappingRowAction( + public RowOperationWrapper( in IterationParameters info, MemoryAllocator allocator, in T action) @@ -102,7 +102,7 @@ namespace SixLabors.ImageSharp.Advanced int yMax = Math.Min(yMin + this.info.StepY, this.info.MaxY); - using IMemoryOwner buffer = this.allocator.Allocate(this.info.MaxX); + using IMemoryOwner buffer = this.allocator.Allocate(this.info.Width); Span span = buffer.Memory.Span; From d922602a26fa139b97dee86681b7dfcec188df68 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Thu, 27 Feb 2020 12:54:51 +0100 Subject: [PATCH 06/64] Introduced single row parallel helpers --- .../Advanced/ParallelRowIterator.cs | 138 +++++++++++++++++- 1 file changed, 134 insertions(+), 4 deletions(-) diff --git a/src/ImageSharp/Advanced/ParallelRowIterator.cs b/src/ImageSharp/Advanced/ParallelRowIterator.cs index 70b48aee9..86442ede7 100644 --- a/src/ImageSharp/Advanced/ParallelRowIterator.cs +++ b/src/ImageSharp/Advanced/ParallelRowIterator.cs @@ -17,6 +17,137 @@ namespace SixLabors.ImageSharp.Advanced /// public static partial class ParallelRowIterator { + /// + /// Iterate through the rows of a rectangle in optimized batches. + /// + /// The type of row operation to perform. + /// The to get the parallel settings from. + /// The . + /// The operation defining the iteration logic on a single row. + [MethodImpl(InliningOptions.ShortMethod)] + public static void IterateRows(Configuration configuration, Rectangle rectangle, in T operation) + where T : struct, IRowOperation + { + var parallelSettings = ParallelExecutionSettings.FromConfiguration(configuration); + IterateRows(rectangle, in parallelSettings, in operation); + } + + /// + /// Iterate through the rows of a rectangle in optimized batches. + /// + /// The type of row operation to perform. + /// The . + /// The . + /// The operation defining the iteration logic on a single row. + public static void IterateRows( + Rectangle rectangle, + in ParallelExecutionSettings parallelSettings, + in T operation) + where T : struct, IRowOperation + { + ValidateRectangle(rectangle); + + int top = rectangle.Top; + int bottom = rectangle.Bottom; + int width = rectangle.Width; + int height = rectangle.Height; + + int maxSteps = DivideCeil(width * height, parallelSettings.MinimumPixelsProcessedPerTask); + int numOfSteps = Math.Min(parallelSettings.MaxDegreeOfParallelism, maxSteps); + + // Avoid TPL overhead in this trivial case: + if (numOfSteps == 1) + { + for (int y = top; y < bottom; y++) + { + Unsafe.AsRef(operation).Invoke(y); + } + + return; + } + + int verticalStep = DivideCeil(rectangle.Height, numOfSteps); + var parallelOptions = new ParallelOptions { MaxDegreeOfParallelism = numOfSteps }; + var info = new IterationParameters(top, bottom, verticalStep); + var wrappingOperation = new RowOperationWrapper(in info, in operation); + + Parallel.For( + 0, + numOfSteps, + parallelOptions, + wrappingOperation.Invoke); + } + + /// + /// Iterate through the rows of a rectangle in optimized batches. + /// instantiating a temporary buffer for each invocation. + /// + /// The type of row operation to perform. + /// The type of buffer elements. + /// The to get the parallel settings from. + /// The . + /// The operation defining the iteration logic on a single row. + public static void IterateRows(Configuration configuration, Rectangle rectangle, in T operation) + where T : struct, IRowOperation + where TBuffer : unmanaged + { + var parallelSettings = ParallelExecutionSettings.FromConfiguration(configuration); + IterateRows(rectangle, in parallelSettings, in operation); + } + + /// + /// Iterate through the rows of a rectangle in optimized batches. + /// instantiating a temporary buffer for each invocation. + /// + /// The type of row operation to perform. + /// The type of buffer elements. + /// The . + /// The . + /// The operation defining the iteration logic on a single row. + public static void IterateRows( + Rectangle rectangle, + in ParallelExecutionSettings parallelSettings, + in T operation) + where T : struct, IRowOperation + where TBuffer : unmanaged + { + ValidateRectangle(rectangle); + + int top = rectangle.Top; + int bottom = rectangle.Bottom; + int width = rectangle.Width; + int height = rectangle.Height; + + int maxSteps = DivideCeil(width * height, parallelSettings.MinimumPixelsProcessedPerTask); + int numOfSteps = Math.Min(parallelSettings.MaxDegreeOfParallelism, maxSteps); + MemoryAllocator allocator = parallelSettings.MemoryAllocator; + + // Avoid TPL overhead in this trivial case: + if (numOfSteps == 1) + { + using IMemoryOwner buffer = allocator.Allocate(width); + Span span = buffer.Memory.Span; + + for (int y = top; y < bottom; y++) + { + Unsafe.AsRef(operation).Invoke(y, span); + } + + return; + } + + int verticalStep = DivideCeil(height, numOfSteps); + var parallelOptions = new ParallelOptions { MaxDegreeOfParallelism = numOfSteps }; + var info = new IterationParameters(top, bottom, verticalStep, width); + var wrappingOperation = new RowOperationWrapper(in info, allocator, in operation); + + Parallel.For( + 0, + numOfSteps, + parallelOptions, + wrappingOperation.Invoke); + } + /// /// Iterate through the rows of a rectangle in optimized batches defined by -s. /// @@ -123,10 +254,9 @@ namespace SixLabors.ImageSharp.Advanced if (numOfSteps == 1) { var rows = new RowInterval(top, bottom); - using (IMemoryOwner buffer = allocator.Allocate(width)) - { - Unsafe.AsRef(operation).Invoke(in rows, buffer.Memory.Span); - } + using IMemoryOwner buffer = allocator.Allocate(width); + + Unsafe.AsRef(operation).Invoke(in rows, buffer.Memory.Span); return; } From 086040cd815d3929f333c4cd135d5b77cb68e16f Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Thu, 27 Feb 2020 13:00:17 +0100 Subject: [PATCH 07/64] Removed IterationParameters type --- .../Advanced/ParallelRowIterator.Wrappers.cs | 107 ++++++++++-------- .../Advanced/ParallelRowIterator.cs | 12 +- 2 files changed, 64 insertions(+), 55 deletions(-) diff --git a/src/ImageSharp/Advanced/ParallelRowIterator.Wrappers.cs b/src/ImageSharp/Advanced/ParallelRowIterator.Wrappers.cs index 7c3861fdf..3f0f77ca3 100644 --- a/src/ImageSharp/Advanced/ParallelRowIterator.Wrappers.cs +++ b/src/ImageSharp/Advanced/ParallelRowIterator.Wrappers.cs @@ -17,51 +17,38 @@ namespace SixLabors.ImageSharp.Advanced /// public static partial class ParallelRowIterator { - private readonly struct IterationParameters - { - public readonly int MinY; - public readonly int MaxY; - public readonly int StepY; - public readonly int Width; - - public IterationParameters(int minY, int maxY, int stepY) - : this(minY, maxY, stepY, 0) - { - } - - public IterationParameters(int minY, int maxY, int stepY, int width) - { - this.MinY = minY; - this.MaxY = maxY; - this.StepY = stepY; - this.Width = width; - } - } - private readonly struct RowOperationWrapper where T : struct, IRowOperation { - private readonly IterationParameters info; + private readonly int minY; + private readonly int maxY; + private readonly int stepY; private readonly T action; [MethodImpl(InliningOptions.ShortMethod)] - public RowOperationWrapper(in IterationParameters info, in T action) + public RowOperationWrapper( + int minY, + int maxY, + int stepY, + in T action) { - this.info = info; + this.minY = minY; + this.maxY = maxY; + this.stepY = stepY; this.action = action; } [MethodImpl(InliningOptions.ShortMethod)] public void Invoke(int i) { - int yMin = this.info.MinY + (i * this.info.StepY); + int yMin = this.minY + (i * this.stepY); - if (yMin >= this.info.MaxY) + if (yMin >= this.maxY) { return; } - int yMax = Math.Min(yMin + this.info.StepY, this.info.MaxY); + int yMax = Math.Min(yMin + this.stepY, this.maxY); for (int y = yMin; y < yMax; y++) { @@ -75,17 +62,26 @@ namespace SixLabors.ImageSharp.Advanced where T : struct, IRowOperation where TBuffer : unmanaged { - private readonly IterationParameters info; + private readonly int minY; + private readonly int maxY; + private readonly int stepY; + private readonly int width; private readonly MemoryAllocator allocator; private readonly T action; [MethodImpl(InliningOptions.ShortMethod)] public RowOperationWrapper( - in IterationParameters info, + int minY, + int maxY, + int stepY, + int width, MemoryAllocator allocator, in T action) { - this.info = info; + this.minY = minY; + this.maxY = maxY; + this.stepY = stepY; + this.width = width; this.allocator = allocator; this.action = action; } @@ -93,16 +89,16 @@ namespace SixLabors.ImageSharp.Advanced [MethodImpl(InliningOptions.ShortMethod)] public void Invoke(int i) { - int yMin = this.info.MinY + (i * this.info.StepY); + int yMin = this.minY + (i * this.stepY); - if (yMin >= this.info.MaxY) + if (yMin >= this.maxY) { return; } - int yMax = Math.Min(yMin + this.info.StepY, this.info.MaxY); + int yMax = Math.Min(yMin + this.stepY, this.maxY); - using IMemoryOwner buffer = this.allocator.Allocate(this.info.Width); + using IMemoryOwner buffer = this.allocator.Allocate(this.width); Span span = buffer.Memory.Span; @@ -116,27 +112,35 @@ namespace SixLabors.ImageSharp.Advanced private readonly struct RowIntervalOperationWrapper where T : struct, IRowIntervalOperation { - private readonly IterationParameters info; + private readonly int minY; + private readonly int maxY; + private readonly int stepY; private readonly T operation; [MethodImpl(InliningOptions.ShortMethod)] - public RowIntervalOperationWrapper(in IterationParameters info, in T operation) + public RowIntervalOperationWrapper( + int minY, + int maxY, + int stepY, + in T operation) { - this.info = info; + this.minY = minY; + this.maxY = maxY; + this.stepY = stepY; this.operation = operation; } [MethodImpl(InliningOptions.ShortMethod)] public void Invoke(int i) { - int yMin = this.info.MinY + (i * this.info.StepY); + int yMin = this.minY + (i * this.stepY); - if (yMin >= this.info.MaxY) + if (yMin >= this.maxY) { return; } - int yMax = Math.Min(yMin + this.info.StepY, this.info.MaxY); + int yMax = Math.Min(yMin + this.stepY, this.maxY); var rows = new RowInterval(yMin, yMax); // Skip the safety copy when invoking a potentially impure method on a readonly field @@ -148,17 +152,26 @@ namespace SixLabors.ImageSharp.Advanced where T : struct, IRowIntervalOperation where TBuffer : unmanaged { - private readonly IterationParameters info; + private readonly int minY; + private readonly int maxY; + private readonly int stepY; + private readonly int width; private readonly MemoryAllocator allocator; private readonly T operation; [MethodImpl(InliningOptions.ShortMethod)] public RowIntervalOperationWrapper( - in IterationParameters info, + int minY, + int maxY, + int stepY, + int width, MemoryAllocator allocator, in T operation) { - this.info = info; + this.minY = minY; + this.maxY = maxY; + this.stepY = stepY; + this.width = width; this.allocator = allocator; this.operation = operation; } @@ -166,17 +179,17 @@ namespace SixLabors.ImageSharp.Advanced [MethodImpl(InliningOptions.ShortMethod)] public void Invoke(int i) { - int yMin = this.info.MinY + (i * this.info.StepY); + int yMin = this.minY + (i * this.stepY); - if (yMin >= this.info.MaxY) + if (yMin >= this.maxY) { return; } - int yMax = Math.Min(yMin + this.info.StepY, this.info.MaxY); + int yMax = Math.Min(yMin + this.stepY, this.maxY); var rows = new RowInterval(yMin, yMax); - using IMemoryOwner buffer = this.allocator.Allocate(this.info.Width); + using IMemoryOwner buffer = this.allocator.Allocate(this.width); Unsafe.AsRef(in this.operation).Invoke(in rows, buffer.Memory.Span); } diff --git a/src/ImageSharp/Advanced/ParallelRowIterator.cs b/src/ImageSharp/Advanced/ParallelRowIterator.cs index 86442ede7..fb85de986 100644 --- a/src/ImageSharp/Advanced/ParallelRowIterator.cs +++ b/src/ImageSharp/Advanced/ParallelRowIterator.cs @@ -68,8 +68,7 @@ namespace SixLabors.ImageSharp.Advanced int verticalStep = DivideCeil(rectangle.Height, numOfSteps); var parallelOptions = new ParallelOptions { MaxDegreeOfParallelism = numOfSteps }; - var info = new IterationParameters(top, bottom, verticalStep); - var wrappingOperation = new RowOperationWrapper(in info, in operation); + var wrappingOperation = new RowOperationWrapper(top, bottom, verticalStep, in operation); Parallel.For( 0, @@ -138,8 +137,7 @@ namespace SixLabors.ImageSharp.Advanced int verticalStep = DivideCeil(height, numOfSteps); var parallelOptions = new ParallelOptions { MaxDegreeOfParallelism = numOfSteps }; - var info = new IterationParameters(top, bottom, verticalStep, width); - var wrappingOperation = new RowOperationWrapper(in info, allocator, in operation); + var wrappingOperation = new RowOperationWrapper(top, bottom, verticalStep, width, allocator, in operation); Parallel.For( 0, @@ -196,8 +194,7 @@ namespace SixLabors.ImageSharp.Advanced int verticalStep = DivideCeil(rectangle.Height, numOfSteps); var parallelOptions = new ParallelOptions { MaxDegreeOfParallelism = numOfSteps }; - var info = new IterationParameters(top, bottom, verticalStep); - var wrappingOperation = new RowIntervalOperationWrapper(in info, in operation); + var wrappingOperation = new RowIntervalOperationWrapper(top, bottom, verticalStep, in operation); Parallel.For( 0, @@ -263,8 +260,7 @@ namespace SixLabors.ImageSharp.Advanced int verticalStep = DivideCeil(height, numOfSteps); var parallelOptions = new ParallelOptions { MaxDegreeOfParallelism = numOfSteps }; - var info = new IterationParameters(top, bottom, verticalStep, width); - var wrappingOperation = new RowIntervalOperationWrapper(in info, allocator, in operation); + var wrappingOperation = new RowIntervalOperationWrapper(top, bottom, verticalStep, width, allocator, in operation); Parallel.For( 0, From 9803663d7399b2155e1467be68f4eb7db4172b75 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Thu, 27 Feb 2020 13:22:16 +0100 Subject: [PATCH 08/64] Refactored BinaryThresholdProcessor --- .../BinaryThresholdProcessor{TPixel}.cs | 30 ++++++++----------- 1 file changed, 13 insertions(+), 17 deletions(-) diff --git a/src/ImageSharp/Processing/Processors/Binarization/BinaryThresholdProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Binarization/BinaryThresholdProcessor{TPixel}.cs index c3189427f..c46137e76 100644 --- a/src/ImageSharp/Processing/Processors/Binarization/BinaryThresholdProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Binarization/BinaryThresholdProcessor{TPixel}.cs @@ -4,7 +4,6 @@ using System; using System.Runtime.CompilerServices; using SixLabors.ImageSharp.Advanced; -using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Processing.Processors.Binarization @@ -44,8 +43,8 @@ namespace SixLabors.ImageSharp.Processing.Processors.Binarization var interest = Rectangle.Intersect(sourceRectangle, source.Bounds()); bool isAlphaOnly = typeof(TPixel) == typeof(A8); - var operation = new RowIntervalOperation(interest, source, upper, lower, threshold, isAlphaOnly); - ParallelRowIterator.IterateRowIntervals( + var operation = new RowOperation(interest, source, upper, lower, threshold, isAlphaOnly); + ParallelRowIterator.IterateRows( configuration, interest, in operation); @@ -54,7 +53,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Binarization /// /// A implementing the clone logic for . /// - private readonly struct RowIntervalOperation : IRowIntervalOperation + private readonly struct RowOperation : IRowOperation { private readonly ImageFrame source; private readonly TPixel upper; @@ -65,7 +64,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Binarization private readonly bool isAlphaOnly; [MethodImpl(InliningOptions.ShortMethod)] - public RowIntervalOperation( + public RowOperation( Rectangle bounds, ImageFrame source, TPixel upper, @@ -84,22 +83,19 @@ namespace SixLabors.ImageSharp.Processing.Processors.Binarization /// [MethodImpl(InliningOptions.ShortMethod)] - public void Invoke(in RowInterval rows) + public void Invoke(int y) { Rgba32 rgba = default; - for (int y = rows.Min; y < rows.Max; y++) - { - Span row = this.source.GetPixelRowSpan(y); + Span row = this.source.GetPixelRowSpan(y); - for (int x = this.minX; x < this.maxX; x++) - { - ref TPixel color = ref row[x]; - color.ToRgba32(ref rgba); + for (int x = this.minX; x < this.maxX; x++) + { + ref TPixel color = ref row[x]; + color.ToRgba32(ref rgba); - // Convert to grayscale using ITU-R Recommendation BT.709 if required - byte luminance = this.isAlphaOnly ? rgba.A : ImageMaths.Get8BitBT709Luminance(rgba.R, rgba.G, rgba.B); - color = luminance >= this.threshold ? this.upper : this.lower; - } + // Convert to grayscale using ITU-R Recommendation BT.709 if required + byte luminance = this.isAlphaOnly ? rgba.A : ImageMaths.Get8BitBT709Luminance(rgba.R, rgba.G, rgba.B); + color = luminance >= this.threshold ? this.upper : this.lower; } } } From b6748f93f7db0379d19da558fad3fc27ee8974b2 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Thu, 27 Feb 2020 13:22:56 +0100 Subject: [PATCH 09/64] Micro-optimization in BinaryThresholdProcessor --- .../Binarization/BinaryThresholdProcessor{TPixel}.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/ImageSharp/Processing/Processors/Binarization/BinaryThresholdProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Binarization/BinaryThresholdProcessor{TPixel}.cs index c46137e76..7a5390564 100644 --- a/src/ImageSharp/Processing/Processors/Binarization/BinaryThresholdProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Binarization/BinaryThresholdProcessor{TPixel}.cs @@ -3,6 +3,7 @@ using System; using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.PixelFormats; @@ -87,10 +88,11 @@ namespace SixLabors.ImageSharp.Processing.Processors.Binarization { Rgba32 rgba = default; Span row = this.source.GetPixelRowSpan(y); + ref TPixel rowRef = ref MemoryMarshal.GetReference(row); for (int x = this.minX; x < this.maxX; x++) { - ref TPixel color = ref row[x]; + ref TPixel color = ref Unsafe.Add(ref rowRef, x); color.ToRgba32(ref rgba); // Convert to grayscale using ITU-R Recommendation BT.709 if required From 36f354d07ab222fc553e89879df9d47ded5ebc1a Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Thu, 27 Feb 2020 13:23:13 +0100 Subject: [PATCH 10/64] Refactored BokehBlurProcessor --- .../Convolution/BokehBlurProcessor{TPixel}.cs | 114 ++++++++---------- 1 file changed, 51 insertions(+), 63 deletions(-) diff --git a/src/ImageSharp/Processing/Processors/Convolution/BokehBlurProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Convolution/BokehBlurProcessor{TPixel}.cs index 9a910ae29..e60063ee0 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/BokehBlurProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/BokehBlurProcessor{TPixel}.cs @@ -72,8 +72,8 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution protected override void OnFrameApply(ImageFrame source) { // Preliminary gamma highlight pass - var gammaOperation = new ApplyGammaExposureRowIntervalOperation(this.SourceRectangle, source.PixelBuffer, this.Configuration, this.gamma); - ParallelRowIterator.IterateRowIntervals( + var gammaOperation = new ApplyGammaExposureRowOperation(this.SourceRectangle, source.PixelBuffer, this.Configuration, this.gamma); + ParallelRowIterator.IterateRows( this.Configuration, this.SourceRectangle, in gammaOperation); @@ -87,8 +87,8 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution float inverseGamma = 1 / this.gamma; // Apply the inverse gamma exposure pass, and write the final pixel data - var operation = new ApplyInverseGammaExposureRowIntervalOperation(this.SourceRectangle, source.PixelBuffer, processingBuffer, this.Configuration, inverseGamma); - ParallelRowIterator.IterateRowIntervals( + var operation = new ApplyInverseGammaExposureRowOperation(this.SourceRectangle, source.PixelBuffer, processingBuffer, this.Configuration, inverseGamma); + ParallelRowIterator.IterateRows( this.Configuration, this.SourceRectangle, in operation); @@ -120,15 +120,15 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution Vector4 parameters = Unsafe.Add(ref paramsRef, i); // Compute the vertical 1D convolution - var verticalOperation = new ApplyVerticalConvolutionRowIntervalOperation(sourceRectangle, firstPassBuffer, source.PixelBuffer, kernel); - ParallelRowIterator.IterateRowIntervals( + var verticalOperation = new ApplyVerticalConvolutionRowOperation(sourceRectangle, firstPassBuffer, source.PixelBuffer, kernel); + ParallelRowIterator.IterateRows( configuration, sourceRectangle, in verticalOperation); // Compute the horizontal 1D convolutions and accumulate the partial results on the target buffer - var horizontalOperation = new ApplyHorizontalConvolutionRowIntervalOperation(sourceRectangle, processingBuffer, firstPassBuffer, kernel, parameters.Z, parameters.W); - ParallelRowIterator.IterateRowIntervals( + var horizontalOperation = new ApplyHorizontalConvolutionRowOperation(sourceRectangle, processingBuffer, firstPassBuffer, kernel, parameters.Z, parameters.W); + ParallelRowIterator.IterateRows( configuration, sourceRectangle, in horizontalOperation); @@ -138,7 +138,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution /// /// A implementing the vertical convolution logic for . /// - private readonly struct ApplyVerticalConvolutionRowIntervalOperation : IRowIntervalOperation + private readonly struct ApplyVerticalConvolutionRowOperation : IRowOperation { private readonly Rectangle bounds; private readonly Buffer2D targetValues; @@ -148,7 +148,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution private readonly int maxX; [MethodImpl(InliningOptions.ShortMethod)] - public ApplyVerticalConvolutionRowIntervalOperation( + public ApplyVerticalConvolutionRowOperation( Rectangle bounds, Buffer2D targetValues, Buffer2D sourcePixels, @@ -164,16 +164,13 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution /// [MethodImpl(InliningOptions.ShortMethod)] - public void Invoke(in RowInterval rows) + public void Invoke(int y) { - for (int y = rows.Min; y < rows.Max; y++) - { - Span targetRowSpan = this.targetValues.GetRowSpan(y).Slice(this.bounds.X); + Span targetRowSpan = this.targetValues.GetRowSpan(y).Slice(this.bounds.X); - for (int x = 0; x < this.bounds.Width; x++) - { - Buffer2DUtils.Convolve4(this.kernel, this.sourcePixels, targetRowSpan, y, x, this.bounds.Y, this.maxY, this.bounds.X, this.maxX); - } + for (int x = 0; x < this.bounds.Width; x++) + { + Buffer2DUtils.Convolve4(this.kernel, this.sourcePixels, targetRowSpan, y, x, this.bounds.Y, this.maxY, this.bounds.X, this.maxX); } } } @@ -181,7 +178,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution /// /// A implementing the horizontal convolution logic for . /// - private readonly struct ApplyHorizontalConvolutionRowIntervalOperation : IRowIntervalOperation + private readonly struct ApplyHorizontalConvolutionRowOperation : IRowOperation { private readonly Rectangle bounds; private readonly Buffer2D targetValues; @@ -193,7 +190,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution private readonly int maxX; [MethodImpl(InliningOptions.ShortMethod)] - public ApplyHorizontalConvolutionRowIntervalOperation( + public ApplyHorizontalConvolutionRowOperation( Rectangle bounds, Buffer2D targetValues, Buffer2D sourceValues, @@ -213,16 +210,13 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution /// [MethodImpl(InliningOptions.ShortMethod)] - public void Invoke(in RowInterval rows) + public void Invoke(int y) { - for (int y = rows.Min; y < rows.Max; y++) - { - Span targetRowSpan = this.targetValues.GetRowSpan(y).Slice(this.bounds.X); + Span targetRowSpan = this.targetValues.GetRowSpan(y).Slice(this.bounds.X); - for (int x = 0; x < this.bounds.Width; x++) - { - Buffer2DUtils.Convolve4AndAccumulatePartials(this.kernel, this.sourceValues, targetRowSpan, y, x, this.bounds.Y, this.maxY, this.bounds.X, this.maxX, this.z, this.w); - } + for (int x = 0; x < this.bounds.Width; x++) + { + Buffer2DUtils.Convolve4AndAccumulatePartials(this.kernel, this.sourceValues, targetRowSpan, y, x, this.bounds.Y, this.maxY, this.bounds.X, this.maxX, this.z, this.w); } } } @@ -230,7 +224,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution /// /// A implementing the gamma exposure logic for . /// - private readonly struct ApplyGammaExposureRowIntervalOperation : IRowIntervalOperation + private readonly struct ApplyGammaExposureRowOperation : IRowOperation { private readonly Rectangle bounds; private readonly Buffer2D targetPixels; @@ -238,7 +232,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution private readonly float gamma; [MethodImpl(InliningOptions.ShortMethod)] - public ApplyGammaExposureRowIntervalOperation( + public ApplyGammaExposureRowOperation( Rectangle bounds, Buffer2D targetPixels, Configuration configuration, @@ -252,31 +246,28 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution /// [MethodImpl(InliningOptions.ShortMethod)] - public void Invoke(in RowInterval rows, Span span) + public void Invoke(int y, Span span) { - for (int y = rows.Min; y < rows.Max; y++) + Span targetRowSpan = this.targetPixels.GetRowSpan(y).Slice(this.bounds.X); + PixelOperations.Instance.ToVector4(this.configuration, targetRowSpan.Slice(0, span.Length), span, PixelConversionModifiers.Premultiply); + ref Vector4 baseRef = ref MemoryMarshal.GetReference(span); + + for (int x = 0; x < this.bounds.Width; x++) { - Span targetRowSpan = this.targetPixels.GetRowSpan(y).Slice(this.bounds.X); - PixelOperations.Instance.ToVector4(this.configuration, targetRowSpan.Slice(0, span.Length), span, PixelConversionModifiers.Premultiply); - ref Vector4 baseRef = ref MemoryMarshal.GetReference(span); - - for (int x = 0; x < this.bounds.Width; x++) - { - ref Vector4 v = ref Unsafe.Add(ref baseRef, x); - v.X = MathF.Pow(v.X, this.gamma); - v.Y = MathF.Pow(v.Y, this.gamma); - v.Z = MathF.Pow(v.Z, this.gamma); - } - - PixelOperations.Instance.FromVector4Destructive(this.configuration, span, targetRowSpan); + ref Vector4 v = ref Unsafe.Add(ref baseRef, x); + v.X = MathF.Pow(v.X, this.gamma); + v.Y = MathF.Pow(v.Y, this.gamma); + v.Z = MathF.Pow(v.Z, this.gamma); } + + PixelOperations.Instance.FromVector4Destructive(this.configuration, span, targetRowSpan); } } /// /// A implementing the inverse gamma exposure logic for . /// - private readonly struct ApplyInverseGammaExposureRowIntervalOperation : IRowIntervalOperation + private readonly struct ApplyInverseGammaExposureRowOperation : IRowOperation { private readonly Rectangle bounds; private readonly Buffer2D targetPixels; @@ -285,7 +276,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution private readonly float inverseGamma; [MethodImpl(InliningOptions.ShortMethod)] - public ApplyInverseGammaExposureRowIntervalOperation( + public ApplyInverseGammaExposureRowOperation( Rectangle bounds, Buffer2D targetPixels, Buffer2D sourceValues, @@ -301,28 +292,25 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution /// [MethodImpl(InliningOptions.ShortMethod)] - public void Invoke(in RowInterval rows) + public void Invoke(int y) { Vector4 low = Vector4.Zero; var high = new Vector4(float.PositiveInfinity, float.PositiveInfinity, float.PositiveInfinity, float.PositiveInfinity); - for (int y = rows.Min; y < rows.Max; y++) + Span targetPixelSpan = this.targetPixels.GetRowSpan(y).Slice(this.bounds.X); + Span sourceRowSpan = this.sourceValues.GetRowSpan(y).Slice(this.bounds.X); + ref Vector4 sourceRef = ref MemoryMarshal.GetReference(sourceRowSpan); + + for (int x = 0; x < this.bounds.Width; x++) { - Span targetPixelSpan = this.targetPixels.GetRowSpan(y).Slice(this.bounds.X); - Span sourceRowSpan = this.sourceValues.GetRowSpan(y).Slice(this.bounds.X); - ref Vector4 sourceRef = ref MemoryMarshal.GetReference(sourceRowSpan); - - for (int x = 0; x < this.bounds.Width; x++) - { - ref Vector4 v = ref Unsafe.Add(ref sourceRef, x); - var clamp = Vector4.Clamp(v, low, high); - v.X = MathF.Pow(clamp.X, this.inverseGamma); - v.Y = MathF.Pow(clamp.Y, this.inverseGamma); - v.Z = MathF.Pow(clamp.Z, this.inverseGamma); - } - - PixelOperations.Instance.FromVector4Destructive(this.configuration, sourceRowSpan.Slice(0, this.bounds.Width), targetPixelSpan, PixelConversionModifiers.Premultiply); + ref Vector4 v = ref Unsafe.Add(ref sourceRef, x); + var clamp = Vector4.Clamp(v, low, high); + v.X = MathF.Pow(clamp.X, this.inverseGamma); + v.Y = MathF.Pow(clamp.Y, this.inverseGamma); + v.Z = MathF.Pow(clamp.Z, this.inverseGamma); } + + PixelOperations.Instance.FromVector4Destructive(this.configuration, sourceRowSpan.Slice(0, this.bounds.Width), targetPixelSpan, PixelConversionModifiers.Premultiply); } } } From b90f087c7d2981f7b6c2717ebb0c96ed119c659f Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Thu, 27 Feb 2020 13:23:27 +0100 Subject: [PATCH 11/64] Refactored Convolution2DProcessor --- .../Convolution2DProcessor{TPixel}.cs | 78 +++++++++---------- 1 file changed, 37 insertions(+), 41 deletions(-) diff --git a/src/ImageSharp/Processing/Processors/Convolution/Convolution2DProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Convolution/Convolution2DProcessor{TPixel}.cs index 219c81617..bd0c917dd 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/Convolution2DProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/Convolution2DProcessor{TPixel}.cs @@ -65,9 +65,9 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution source.CopyTo(targetPixels); var interest = Rectangle.Intersect(this.SourceRectangle, source.Bounds()); - var operation = new RowIntervalOperation(interest, targetPixels, source.PixelBuffer, this.KernelY, this.KernelX, this.Configuration, this.PreserveAlpha); + var operation = new RowOperation(interest, targetPixels, source.PixelBuffer, this.KernelY, this.KernelX, this.Configuration, this.PreserveAlpha); - ParallelRowIterator.IterateRowIntervals( + ParallelRowIterator.IterateRows( this.Configuration, interest, in operation); @@ -78,7 +78,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution /// /// A implementing the convolution logic for . /// - private readonly struct RowIntervalOperation : IRowIntervalOperation + private readonly struct RowOperation : IRowOperation { private readonly Rectangle bounds; private readonly int maxY; @@ -91,7 +91,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution private readonly bool preserveAlpha; [MethodImpl(InliningOptions.ShortMethod)] - public RowIntervalOperation( + public RowOperation( Rectangle bounds, Buffer2D targetPixels, Buffer2D sourcePixels, @@ -113,52 +113,48 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution /// [MethodImpl(InliningOptions.ShortMethod)] - public void Invoke(in RowInterval rows, Span span) + public void Invoke(int y, Span span) { ref Vector4 spanRef = ref MemoryMarshal.GetReference(span); + Span targetRowSpan = this.targetPixels.GetRowSpan(y).Slice(this.bounds.X); + PixelOperations.Instance.ToVector4(this.configuration, targetRowSpan.Slice(0, span.Length), span); - for (int y = rows.Min; y < rows.Max; y++) + if (this.preserveAlpha) { - Span targetRowSpan = this.targetPixels.GetRowSpan(y).Slice(this.bounds.X); - PixelOperations.Instance.ToVector4(this.configuration, targetRowSpan.Slice(0, span.Length), span); - - if (this.preserveAlpha) + for (int x = 0; x < this.bounds.Width; x++) { - for (int x = 0; x < this.bounds.Width; x++) - { - DenseMatrixUtils.Convolve2D3( - in this.kernelY, - in this.kernelX, - this.sourcePixels, - ref spanRef, - y, - x, - this.bounds.Y, - this.maxY, - this.bounds.X, - this.maxX); - } + DenseMatrixUtils.Convolve2D3( + in this.kernelY, + in this.kernelX, + this.sourcePixels, + ref spanRef, + y, + x, + this.bounds.Y, + this.maxY, + this.bounds.X, + this.maxX); } - else + } + else + { + for (int x = 0; x < this.bounds.Width; x++) { - for (int x = 0; x < this.bounds.Width; x++) - { - DenseMatrixUtils.Convolve2D4( - in this.kernelY, - in this.kernelX, - this.sourcePixels, - ref spanRef, - y, - x, - this.bounds.Y, - this.maxY, - this.bounds.X, - this.maxX); - } + DenseMatrixUtils.Convolve2D4( + in this.kernelY, + in this.kernelX, + this.sourcePixels, + ref spanRef, + y, + x, + this.bounds.Y, + this.maxY, + this.bounds.X, + this.maxX); } - - PixelOperations.Instance.FromVector4Destructive(this.configuration, span, targetRowSpan); } + + PixelOperations.Instance.FromVector4Destructive(this.configuration, span, targetRowSpan); } } } From 05bd0312a864edbcf9bc44ec6416cea516cbddc3 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Thu, 27 Feb 2020 13:23:38 +0100 Subject: [PATCH 12/64] Refactored Convolution2PassProcessor --- .../Convolution2PassProcessor{TPixel}.cs | 79 +++++++++---------- 1 file changed, 38 insertions(+), 41 deletions(-) diff --git a/src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor{TPixel}.cs index 833c03308..38945dd20 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor{TPixel}.cs @@ -64,15 +64,15 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution var interest = Rectangle.Intersect(this.SourceRectangle, source.Bounds()); // Horizontal convolution - var horizontalOperation = new RowIntervalOperation(interest, firstPassPixels, source.PixelBuffer, this.KernelX, this.Configuration, this.PreserveAlpha); - ParallelRowIterator.IterateRowIntervals( + var horizontalOperation = new RowOperation(interest, firstPassPixels, source.PixelBuffer, this.KernelX, this.Configuration, this.PreserveAlpha); + ParallelRowIterator.IterateRows( this.Configuration, interest, in horizontalOperation); // Vertical convolution - var verticalOperation = new RowIntervalOperation(interest, source.PixelBuffer, firstPassPixels, this.KernelY, this.Configuration, this.PreserveAlpha); - ParallelRowIterator.IterateRowIntervals( + var verticalOperation = new RowOperation(interest, source.PixelBuffer, firstPassPixels, this.KernelY, this.Configuration, this.PreserveAlpha); + ParallelRowIterator.IterateRows( this.Configuration, interest, in verticalOperation); @@ -81,7 +81,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution /// /// A implementing the convolution logic for . /// - private readonly struct RowIntervalOperation : IRowIntervalOperation + private readonly struct RowOperation : IRowOperation { private readonly Rectangle bounds; private readonly Buffer2D targetPixels; @@ -91,7 +91,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution private readonly bool preserveAlpha; [MethodImpl(InliningOptions.ShortMethod)] - public RowIntervalOperation( + public RowOperation( Rectangle bounds, Buffer2D targetPixels, Buffer2D sourcePixels, @@ -109,53 +109,50 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution /// [MethodImpl(InliningOptions.ShortMethod)] - public void Invoke(in RowInterval rows, Span span) + public void Invoke(int y, Span span) { ref Vector4 spanRef = ref MemoryMarshal.GetReference(span); int maxY = this.bounds.Bottom - 1; int maxX = this.bounds.Right - 1; - for (int y = rows.Min; y < rows.Max; y++) - { - Span targetRowSpan = this.targetPixels.GetRowSpan(y).Slice(this.bounds.X); - PixelOperations.Instance.ToVector4(this.configuration, targetRowSpan.Slice(0, span.Length), span); + Span targetRowSpan = this.targetPixels.GetRowSpan(y).Slice(this.bounds.X); + PixelOperations.Instance.ToVector4(this.configuration, targetRowSpan.Slice(0, span.Length), span); - if (this.preserveAlpha) + if (this.preserveAlpha) + { + for (int x = 0; x < this.bounds.Width; x++) { - for (int x = 0; x < this.bounds.Width; x++) - { - DenseMatrixUtils.Convolve3( - in this.kernel, - this.sourcePixels, - ref spanRef, - y, - x, - this.bounds.Y, - maxY, - this.bounds.X, - maxX); - } + DenseMatrixUtils.Convolve3( + in this.kernel, + this.sourcePixels, + ref spanRef, + y, + x, + this.bounds.Y, + maxY, + this.bounds.X, + maxX); } - else + } + else + { + for (int x = 0; x < this.bounds.Width; x++) { - for (int x = 0; x < this.bounds.Width; x++) - { - DenseMatrixUtils.Convolve4( - in this.kernel, - this.sourcePixels, - ref spanRef, - y, - x, - this.bounds.Y, - maxY, - this.bounds.X, - maxX); - } + DenseMatrixUtils.Convolve4( + in this.kernel, + this.sourcePixels, + ref spanRef, + y, + x, + this.bounds.Y, + maxY, + this.bounds.X, + maxX); } - - PixelOperations.Instance.FromVector4Destructive(this.configuration, span, targetRowSpan); } + + PixelOperations.Instance.FromVector4Destructive(this.configuration, span, targetRowSpan); } } } From 577d59790af8cb5f70563335659124f125b0766c Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Thu, 27 Feb 2020 13:23:48 +0100 Subject: [PATCH 13/64] Refactored ConvolutionProcessor --- .../ConvolutionProcessor{TPixel}.cs | 75 +++++++++---------- 1 file changed, 36 insertions(+), 39 deletions(-) diff --git a/src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessor{TPixel}.cs index fae222714..c529641d2 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessor{TPixel}.cs @@ -56,8 +56,8 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution source.CopyTo(targetPixels); var interest = Rectangle.Intersect(this.SourceRectangle, source.Bounds()); - var operation = new RowIntervalOperation(interest, targetPixels, source.PixelBuffer, this.KernelXY, this.Configuration, this.PreserveAlpha); - ParallelRowIterator.IterateRowIntervals( + var operation = new RowOperation(interest, targetPixels, source.PixelBuffer, this.KernelXY, this.Configuration, this.PreserveAlpha); + ParallelRowIterator.IterateRows( this.Configuration, interest, in operation); @@ -68,7 +68,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution /// /// A implementing the convolution logic for . /// - private readonly struct RowIntervalOperation : IRowIntervalOperation + private readonly struct RowOperation : IRowOperation { private readonly Rectangle bounds; private readonly int maxY; @@ -80,7 +80,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution private readonly bool preserveAlpha; [MethodImpl(InliningOptions.ShortMethod)] - public RowIntervalOperation( + public RowOperation( Rectangle bounds, Buffer2D targetPixels, Buffer2D sourcePixels, @@ -100,50 +100,47 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution /// [MethodImpl(InliningOptions.ShortMethod)] - public void Invoke(in RowInterval rows, Span span) + public void Invoke(int y, Span span) { ref Vector4 spanRef = ref MemoryMarshal.GetReference(span); - for (int y = rows.Min; y < rows.Max; y++) - { - Span targetRowSpan = this.targetPixels.GetRowSpan(y).Slice(this.bounds.X); - PixelOperations.Instance.ToVector4(this.configuration, targetRowSpan.Slice(0, span.Length), span); + Span targetRowSpan = this.targetPixels.GetRowSpan(y).Slice(this.bounds.X); + PixelOperations.Instance.ToVector4(this.configuration, targetRowSpan.Slice(0, span.Length), span); - if (this.preserveAlpha) + if (this.preserveAlpha) + { + for (int x = 0; x < this.bounds.Width; x++) { - for (int x = 0; x < this.bounds.Width; x++) - { - DenseMatrixUtils.Convolve3( - in this.kernel, - this.sourcePixels, - ref spanRef, - y, - x, - this.bounds.Y, - this.maxY, - this.bounds.X, - this.maxX); - } + DenseMatrixUtils.Convolve3( + in this.kernel, + this.sourcePixels, + ref spanRef, + y, + x, + this.bounds.Y, + this.maxY, + this.bounds.X, + this.maxX); } - else + } + else + { + for (int x = 0; x < this.bounds.Width; x++) { - for (int x = 0; x < this.bounds.Width; x++) - { - DenseMatrixUtils.Convolve4( - in this.kernel, - this.sourcePixels, - ref spanRef, - y, - x, - this.bounds.Y, - this.maxY, - this.bounds.X, - this.maxX); - } + DenseMatrixUtils.Convolve4( + in this.kernel, + this.sourcePixels, + ref spanRef, + y, + x, + this.bounds.Y, + this.maxY, + this.bounds.X, + this.maxX); } - - PixelOperations.Instance.FromVector4Destructive(this.configuration, span, targetRowSpan); } + + PixelOperations.Instance.FromVector4Destructive(this.configuration, span, targetRowSpan); } } } From ee572da8306bb99bbdb131bc4adc905cd8ab75af Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Thu, 27 Feb 2020 13:23:59 +0100 Subject: [PATCH 14/64] Refactored EdgeDetectorCompassProcessor --- .../EdgeDetectorCompassProcessor{TPixel}.cs | 31 +++++++++---------- 1 file changed, 14 insertions(+), 17 deletions(-) diff --git a/src/ImageSharp/Processing/Processors/Convolution/EdgeDetectorCompassProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Convolution/EdgeDetectorCompassProcessor{TPixel}.cs index f15acd39a..bfd7ff1e6 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/EdgeDetectorCompassProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/EdgeDetectorCompassProcessor{TPixel}.cs @@ -78,8 +78,8 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution processor.Apply(pass); } - var operation = new RowIntervalOperation(source.PixelBuffer, pass.PixelBuffer, interest); - ParallelRowIterator.IterateRowIntervals( + var operation = new RowOperation(source.PixelBuffer, pass.PixelBuffer, interest); + ParallelRowIterator.IterateRows( this.Configuration, interest, in operation); @@ -89,7 +89,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution /// /// A implementing the convolution logic for . /// - private readonly struct RowIntervalOperation : IRowIntervalOperation + private readonly struct RowOperation : IRowOperation { private readonly Buffer2D targetPixels; private readonly Buffer2D passPixels; @@ -97,7 +97,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution private readonly int maxX; [MethodImpl(InliningOptions.ShortMethod)] - public RowIntervalOperation( + public RowOperation( Buffer2D targetPixels, Buffer2D passPixels, Rectangle bounds) @@ -110,23 +110,20 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution /// [MethodImpl(InliningOptions.ShortMethod)] - public void Invoke(in RowInterval rows) + public void Invoke(int y) { - for (int y = rows.Min; y < rows.Max; y++) - { - ref TPixel passPixelsBase = ref MemoryMarshal.GetReference(this.passPixels.GetRowSpan(y)); - ref TPixel targetPixelsBase = ref MemoryMarshal.GetReference(this.targetPixels.GetRowSpan(y)); + ref TPixel passPixelsBase = ref MemoryMarshal.GetReference(this.passPixels.GetRowSpan(y)); + ref TPixel targetPixelsBase = ref MemoryMarshal.GetReference(this.targetPixels.GetRowSpan(y)); - for (int x = this.minX; x < this.maxX; x++) - { - // Grab the max components of the two pixels - ref TPixel currentPassPixel = ref Unsafe.Add(ref passPixelsBase, x); - ref TPixel currentTargetPixel = ref Unsafe.Add(ref targetPixelsBase, x); + for (int x = this.minX; x < this.maxX; x++) + { + // Grab the max components of the two pixels + ref TPixel currentPassPixel = ref Unsafe.Add(ref passPixelsBase, x); + ref TPixel currentTargetPixel = ref Unsafe.Add(ref targetPixelsBase, x); - var pixelValue = Vector4.Max(currentPassPixel.ToVector4(), currentTargetPixel.ToVector4()); + var pixelValue = Vector4.Max(currentPassPixel.ToVector4(), currentTargetPixel.ToVector4()); - currentTargetPixel.FromVector4(pixelValue); - } + currentTargetPixel.FromVector4(pixelValue); } } } From aa6e091fd9134884cb22ff3264e2199a50209d76 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Thu, 27 Feb 2020 19:29:54 +0100 Subject: [PATCH 15/64] Refactored DrawImageProcessor --- .../DrawImageProcessor{TPixelBg,TPixelFg}.cs | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/src/ImageSharp/Processing/Processors/Drawing/DrawImageProcessor{TPixelBg,TPixelFg}.cs b/src/ImageSharp/Processing/Processors/Drawing/DrawImageProcessor{TPixelBg,TPixelFg}.cs index 7a26a03a1..2bca1ffc0 100644 --- a/src/ImageSharp/Processing/Processors/Drawing/DrawImageProcessor{TPixelBg,TPixelFg}.cs +++ b/src/ImageSharp/Processing/Processors/Drawing/DrawImageProcessor{TPixelBg,TPixelFg}.cs @@ -99,8 +99,8 @@ namespace SixLabors.ImageSharp.Processing.Processors.Drawing "Cannot draw image because the source image does not overlap the target image."); } - var operation = new RowIntervalOperation(source, targetImage, blender, configuration, minX, width, locationY, targetX, this.Opacity); - ParallelRowIterator.IterateRowIntervals( + var operation = new RowOperation(source, targetImage, blender, configuration, minX, width, locationY, targetX, this.Opacity); + ParallelRowIterator.IterateRows( configuration, workingRect, in operation); @@ -109,7 +109,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Drawing /// /// A implementing the draw logic for . /// - private readonly struct RowIntervalOperation : IRowIntervalOperation + private readonly struct RowOperation : IRowOperation { private readonly ImageFrame sourceFrame; private readonly Image targetImage; @@ -122,7 +122,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Drawing private readonly float opacity; [MethodImpl(InliningOptions.ShortMethod)] - public RowIntervalOperation( + public RowOperation( ImageFrame sourceFrame, Image targetImage, PixelBlender blender, @@ -146,14 +146,11 @@ namespace SixLabors.ImageSharp.Processing.Processors.Drawing /// [MethodImpl(InliningOptions.ShortMethod)] - public void Invoke(in RowInterval rows) + public void Invoke(int y) { - for (int y = rows.Min; y < rows.Max; y++) - { - Span background = this.sourceFrame.GetPixelRowSpan(y).Slice(this.minX, this.width); - Span foreground = this.targetImage.GetPixelRowSpan(y - this.locationY).Slice(this.targetX, this.width); - this.blender.Blend(this.configuration, background, background, foreground, this.opacity); - } + Span background = this.sourceFrame.GetPixelRowSpan(y).Slice(this.minX, this.width); + Span foreground = this.targetImage.GetPixelRowSpan(y - this.locationY).Slice(this.targetX, this.width); + this.blender.Blend(this.configuration, background, background, foreground, this.opacity); } } } From 08db1f0391ccdf6b1990f5730f12106cc86974d0 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Thu, 27 Feb 2020 19:31:47 +0100 Subject: [PATCH 16/64] Refactored PixelRowDelegateProcessor --- ...lRowDelegateProcessor{TPixel,TDelegate}.cs | 23 ++++++++----------- 1 file changed, 10 insertions(+), 13 deletions(-) diff --git a/src/ImageSharp/Processing/Processors/Effects/PixelRowDelegateProcessor{TPixel,TDelegate}.cs b/src/ImageSharp/Processing/Processors/Effects/PixelRowDelegateProcessor{TPixel,TDelegate}.cs index f94cd5080..d4b6f11f3 100644 --- a/src/ImageSharp/Processing/Processors/Effects/PixelRowDelegateProcessor{TPixel,TDelegate}.cs +++ b/src/ImageSharp/Processing/Processors/Effects/PixelRowDelegateProcessor{TPixel,TDelegate}.cs @@ -50,9 +50,9 @@ namespace SixLabors.ImageSharp.Processing.Processors.Effects protected override void OnFrameApply(ImageFrame source) { var interest = Rectangle.Intersect(this.SourceRectangle, source.Bounds()); - var operation = new RowIntervalOperation(interest.X, source, this.Configuration, this.modifiers, this.rowDelegate); + var operation = new RowOperation(interest.X, source, this.Configuration, this.modifiers, this.rowDelegate); - ParallelRowIterator.IterateRowIntervals( + ParallelRowIterator.IterateRows( this.Configuration, interest, in operation); @@ -61,7 +61,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Effects /// /// A implementing the convolution logic for . /// - private readonly struct RowIntervalOperation : IRowIntervalOperation + private readonly struct RowOperation : IRowOperation { private readonly int startX; private readonly ImageFrame source; @@ -70,7 +70,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Effects private readonly TDelegate rowProcessor; [MethodImpl(InliningOptions.ShortMethod)] - public RowIntervalOperation( + public RowOperation( int startX, ImageFrame source, Configuration configuration, @@ -86,18 +86,15 @@ namespace SixLabors.ImageSharp.Processing.Processors.Effects /// [MethodImpl(InliningOptions.ShortMethod)] - public void Invoke(in RowInterval rows, Span span) + public void Invoke(int y, Span span) { - for (int y = rows.Min; y < rows.Max; y++) - { - Span rowSpan = this.source.GetPixelRowSpan(y).Slice(this.startX, span.Length); - PixelOperations.Instance.ToVector4(this.configuration, rowSpan, span, this.modifiers); + Span rowSpan = this.source.GetPixelRowSpan(y).Slice(this.startX, span.Length); + PixelOperations.Instance.ToVector4(this.configuration, rowSpan, span, this.modifiers); - // Run the user defined pixel shader to the current row of pixels - Unsafe.AsRef(this.rowProcessor).Invoke(span, new Point(this.startX, y)); + // Run the user defined pixel shader to the current row of pixels + Unsafe.AsRef(this.rowProcessor).Invoke(span, new Point(this.startX, y)); - PixelOperations.Instance.FromVector4Destructive(this.configuration, span, rowSpan, this.modifiers); - } + PixelOperations.Instance.FromVector4Destructive(this.configuration, span, rowSpan, this.modifiers); } } } From f066dc0bbd26ae6a62beb81dab861b9230ce67ea Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Thu, 27 Feb 2020 19:32:52 +0100 Subject: [PATCH 17/64] Refactored FilterProcessor --- .../Filters/FilterProcessor{TPixel}.cs | 21 ++++++++----------- 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/src/ImageSharp/Processing/Processors/Filters/FilterProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Filters/FilterProcessor{TPixel}.cs index a677f6027..8d62a5c05 100644 --- a/src/ImageSharp/Processing/Processors/Filters/FilterProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Filters/FilterProcessor{TPixel}.cs @@ -36,9 +36,9 @@ namespace SixLabors.ImageSharp.Processing.Processors.Filters protected override void OnFrameApply(ImageFrame source) { var interest = Rectangle.Intersect(this.SourceRectangle, source.Bounds()); - var operation = new RowIntervalOperation(interest.X, source, this.definition.Matrix, this.Configuration); + var operation = new RowOperation(interest.X, source, this.definition.Matrix, this.Configuration); - ParallelRowIterator.IterateRowIntervals( + ParallelRowIterator.IterateRows( this.Configuration, interest, in operation); @@ -47,7 +47,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Filters /// /// A implementing the convolution logic for . /// - private readonly struct RowIntervalOperation : IRowIntervalOperation + private readonly struct RowOperation : IRowOperation { private readonly int startX; private readonly ImageFrame source; @@ -55,7 +55,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Filters private readonly Configuration configuration; [MethodImpl(InliningOptions.ShortMethod)] - public RowIntervalOperation( + public RowOperation( int startX, ImageFrame source, ColorMatrix matrix, @@ -69,17 +69,14 @@ namespace SixLabors.ImageSharp.Processing.Processors.Filters /// [MethodImpl(InliningOptions.ShortMethod)] - public void Invoke(in RowInterval rows, Span span) + public void Invoke(int y, Span span) { - for (int y = rows.Min; y < rows.Max; y++) - { - Span rowSpan = this.source.GetPixelRowSpan(y).Slice(this.startX, span.Length); - PixelOperations.Instance.ToVector4(this.configuration, rowSpan, span); + Span rowSpan = this.source.GetPixelRowSpan(y).Slice(this.startX, span.Length); + PixelOperations.Instance.ToVector4(this.configuration, rowSpan, span); - Vector4Utils.Transform(span, ref Unsafe.AsRef(this.matrix)); + Vector4Utils.Transform(span, ref Unsafe.AsRef(this.matrix)); - PixelOperations.Instance.FromVector4Destructive(this.configuration, span, rowSpan); - } + PixelOperations.Instance.FromVector4Destructive(this.configuration, span, rowSpan); } } } From babbcd8f7cecf4d2804ffdaa88c25b31637ed628 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Thu, 27 Feb 2020 19:36:32 +0100 Subject: [PATCH 18/64] Refactored GlobalHistogramEqualizationProcessor --- ...lHistogramEqualizationProcessor{TPixel}.cs | 50 ++++++++----------- 1 file changed, 22 insertions(+), 28 deletions(-) diff --git a/src/ImageSharp/Processing/Processors/Normalization/GlobalHistogramEqualizationProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Normalization/GlobalHistogramEqualizationProcessor{TPixel}.cs index 0567151bc..9bf8b9b74 100644 --- a/src/ImageSharp/Processing/Processors/Normalization/GlobalHistogramEqualizationProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Normalization/GlobalHistogramEqualizationProcessor{TPixel}.cs @@ -52,8 +52,8 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization using IMemoryOwner histogramBuffer = memoryAllocator.Allocate(this.LuminanceLevels, AllocationOptions.Clean); // Build the histogram of the grayscale levels - var grayscaleOperation = new GrayscaleLevelsRowIntervalOperation(interest, histogramBuffer, source, this.LuminanceLevels); - ParallelRowIterator.IterateRowIntervals( + var grayscaleOperation = new GrayscaleLevelsRowOperation(interest, histogramBuffer, source, this.LuminanceLevels); + ParallelRowIterator.IterateRows( this.Configuration, interest, in grayscaleOperation); @@ -75,8 +75,8 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization float numberOfPixelsMinusCdfMin = numberOfPixels - cdfMin; // Apply the cdf to each pixel of the image - var cdfOperation = new CdfApplicationRowIntervalOperation(interest, cdfBuffer, source, this.LuminanceLevels, numberOfPixelsMinusCdfMin); - ParallelRowIterator.IterateRowIntervals( + var cdfOperation = new CdfApplicationRowOperation(interest, cdfBuffer, source, this.LuminanceLevels, numberOfPixelsMinusCdfMin); + ParallelRowIterator.IterateRows( this.Configuration, interest, in cdfOperation); @@ -85,7 +85,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization /// /// A implementing the grayscale levels logic for . /// - private readonly struct GrayscaleLevelsRowIntervalOperation : IRowIntervalOperation + private readonly struct GrayscaleLevelsRowOperation : IRowOperation { private readonly Rectangle bounds; private readonly IMemoryOwner histogramBuffer; @@ -93,7 +93,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization private readonly int luminanceLevels; [MethodImpl(InliningOptions.ShortMethod)] - public GrayscaleLevelsRowIntervalOperation( + public GrayscaleLevelsRowOperation( Rectangle bounds, IMemoryOwner histogramBuffer, ImageFrame source, @@ -107,18 +107,15 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization /// [MethodImpl(InliningOptions.ShortMethod)] - public void Invoke(in RowInterval rows) + public void Invoke(int y) { ref int histogramBase = ref MemoryMarshal.GetReference(this.histogramBuffer.GetSpan()); - for (int y = rows.Min; y < rows.Max; y++) - { - ref TPixel pixelBase = ref MemoryMarshal.GetReference(this.source.GetPixelRowSpan(y)); + ref TPixel pixelBase = ref MemoryMarshal.GetReference(this.source.GetPixelRowSpan(y)); - for (int x = 0; x < this.bounds.Width; x++) - { - int luminance = GetLuminance(Unsafe.Add(ref pixelBase, x), this.luminanceLevels); - Unsafe.Add(ref histogramBase, luminance)++; - } + for (int x = 0; x < this.bounds.Width; x++) + { + int luminance = GetLuminance(Unsafe.Add(ref pixelBase, x), this.luminanceLevels); + Unsafe.Add(ref histogramBase, luminance)++; } } } @@ -126,7 +123,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization /// /// A implementing the cdf application levels logic for . /// - private readonly struct CdfApplicationRowIntervalOperation : IRowIntervalOperation + private readonly struct CdfApplicationRowOperation : IRowOperation { private readonly Rectangle bounds; private readonly IMemoryOwner cdfBuffer; @@ -135,7 +132,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization private readonly float numberOfPixelsMinusCdfMin; [MethodImpl(InliningOptions.ShortMethod)] - public CdfApplicationRowIntervalOperation( + public CdfApplicationRowOperation( Rectangle bounds, IMemoryOwner cdfBuffer, ImageFrame source, @@ -151,20 +148,17 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization /// [MethodImpl(InliningOptions.ShortMethod)] - public void Invoke(in RowInterval rows) + public void Invoke(int y) { ref int cdfBase = ref MemoryMarshal.GetReference(this.cdfBuffer.GetSpan()); - for (int y = rows.Min; y < rows.Max; y++) + ref TPixel pixelBase = ref MemoryMarshal.GetReference(this.source.GetPixelRowSpan(y)); + + for (int x = 0; x < this.bounds.Width; x++) { - ref TPixel pixelBase = ref MemoryMarshal.GetReference(this.source.GetPixelRowSpan(y)); - - for (int x = 0; x < this.bounds.Width; x++) - { - ref TPixel pixel = ref Unsafe.Add(ref pixelBase, x); - int luminance = GetLuminance(pixel, this.luminanceLevels); - float luminanceEqualized = Unsafe.Add(ref cdfBase, luminance) / this.numberOfPixelsMinusCdfMin; - pixel.FromVector4(new Vector4(luminanceEqualized, luminanceEqualized, luminanceEqualized, pixel.ToVector4().W)); - } + ref TPixel pixel = ref Unsafe.Add(ref pixelBase, x); + int luminance = GetLuminance(pixel, this.luminanceLevels); + float luminanceEqualized = Unsafe.Add(ref cdfBase, luminance) / this.numberOfPixelsMinusCdfMin; + pixel.FromVector4(new Vector4(luminanceEqualized, luminanceEqualized, luminanceEqualized, pixel.ToVector4().W)); } } } From 08dc224f5d4876b5685bf6b285963be1e1409a81 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Thu, 27 Feb 2020 19:37:31 +0100 Subject: [PATCH 19/64] Refactored BackgroundColorProcessor --- .../BackgroundColorProcessor{TPixel}.cs | 35 +++++++++---------- 1 file changed, 16 insertions(+), 19 deletions(-) diff --git a/src/ImageSharp/Processing/Processors/Overlays/BackgroundColorProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Overlays/BackgroundColorProcessor{TPixel}.cs index 3f4174d6e..1c6b78025 100644 --- a/src/ImageSharp/Processing/Processors/Overlays/BackgroundColorProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Overlays/BackgroundColorProcessor{TPixel}.cs @@ -49,14 +49,14 @@ namespace SixLabors.ImageSharp.Processing.Processors.Overlays PixelBlender blender = PixelOperations.Instance.GetPixelBlender(graphicsOptions); - var operation = new RowIntervalOperation(configuration, interest, blender, amount, colors, source); - ParallelRowIterator.IterateRowIntervals( + var operation = new RowOperation(configuration, interest, blender, amount, colors, source); + ParallelRowIterator.IterateRows( configuration, interest, in operation); } - private readonly struct RowIntervalOperation : IRowIntervalOperation + private readonly struct RowOperation : IRowOperation { private readonly Configuration configuration; private readonly Rectangle bounds; @@ -66,7 +66,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Overlays private readonly ImageFrame source; [MethodImpl(InliningOptions.ShortMethod)] - public RowIntervalOperation( + public RowOperation( Configuration configuration, Rectangle bounds, PixelBlender blender, @@ -83,23 +83,20 @@ namespace SixLabors.ImageSharp.Processing.Processors.Overlays } [MethodImpl(InliningOptions.ShortMethod)] - public void Invoke(in RowInterval rows) + public void Invoke(int y) { - for (int y = rows.Min; y < rows.Max; y++) - { - Span destination = - this.source.GetPixelRowSpan(y) - .Slice(this.bounds.X, this.bounds.Width); + Span destination = + this.source.GetPixelRowSpan(y) + .Slice(this.bounds.X, this.bounds.Width); - // Switch color & destination in the 2nd and 3rd places because we are - // applying the target color under the current one. - this.blender.Blend( - this.configuration, - destination, - this.colors.GetSpan(), - destination, - this.amount.GetSpan()); - } + // Switch color & destination in the 2nd and 3rd places because we are + // applying the target color under the current one. + this.blender.Blend( + this.configuration, + destination, + this.colors.GetSpan(), + destination, + this.amount.GetSpan()); } } } From 7fed77d4e07fd2bfe9ac48074ccc8ffd3430779d Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Thu, 27 Feb 2020 19:38:13 +0100 Subject: [PATCH 20/64] Refactored GlowProcessor --- .../Overlays/GlowProcessor{TPixel}.cs | 35 +++++++++---------- 1 file changed, 16 insertions(+), 19 deletions(-) diff --git a/src/ImageSharp/Processing/Processors/Overlays/GlowProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Overlays/GlowProcessor{TPixel}.cs index ff148e839..efa5ddf88 100644 --- a/src/ImageSharp/Processing/Processors/Overlays/GlowProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Overlays/GlowProcessor{TPixel}.cs @@ -55,14 +55,14 @@ namespace SixLabors.ImageSharp.Processing.Processors.Overlays using IMemoryOwner rowColors = allocator.Allocate(interest.Width); rowColors.GetSpan().Fill(glowColor); - var operation = new RowIntervalOperation(configuration, interest, rowColors, this.blender, center, maxDistance, blendPercent, source); - ParallelRowIterator.IterateRowIntervals( + var operation = new RowOperation(configuration, interest, rowColors, this.blender, center, maxDistance, blendPercent, source); + ParallelRowIterator.IterateRows( configuration, interest, in operation); } - private readonly struct RowIntervalOperation : IRowIntervalOperation + private readonly struct RowOperation : IRowOperation { private readonly Configuration configuration; private readonly Rectangle bounds; @@ -74,7 +74,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Overlays private readonly ImageFrame source; [MethodImpl(InliningOptions.ShortMethod)] - public RowIntervalOperation( + public RowOperation( Configuration configuration, Rectangle bounds, IMemoryOwner colors, @@ -95,27 +95,24 @@ namespace SixLabors.ImageSharp.Processing.Processors.Overlays } [MethodImpl(InliningOptions.ShortMethod)] - public void Invoke(in RowInterval rows, Span span) + public void Invoke(int y, Span span) { Span colorSpan = this.colors.GetSpan(); - for (int y = rows.Min; y < rows.Max; y++) + for (int i = 0; i < this.bounds.Width; i++) { - for (int i = 0; i < this.bounds.Width; i++) - { - float distance = Vector2.Distance(this.center, new Vector2(i + this.bounds.X, y)); - span[i] = (this.blendPercent * (1 - (.95F * (distance / this.maxDistance)))).Clamp(0, 1); - } + float distance = Vector2.Distance(this.center, new Vector2(i + this.bounds.X, y)); + span[i] = (this.blendPercent * (1 - (.95F * (distance / this.maxDistance)))).Clamp(0, 1); + } - Span destination = this.source.GetPixelRowSpan(y).Slice(this.bounds.X, this.bounds.Width); + Span destination = this.source.GetPixelRowSpan(y).Slice(this.bounds.X, this.bounds.Width); - this.blender.Blend( - this.configuration, - destination, - destination, - colorSpan, - span); - } + this.blender.Blend( + this.configuration, + destination, + destination, + colorSpan, + span); } } } From 851f491aa19c889d8e031fb8bdfbdfda0cbd7484 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Thu, 27 Feb 2020 19:38:52 +0100 Subject: [PATCH 21/64] Refactored VignetteProcessor --- .../Overlays/VignetteProcessor{TPixel}.cs | 37 +++++++++---------- 1 file changed, 17 insertions(+), 20 deletions(-) diff --git a/src/ImageSharp/Processing/Processors/Overlays/VignetteProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Overlays/VignetteProcessor{TPixel}.cs index ce69de2fd..de7bce169 100644 --- a/src/ImageSharp/Processing/Processors/Overlays/VignetteProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Overlays/VignetteProcessor{TPixel}.cs @@ -63,14 +63,14 @@ namespace SixLabors.ImageSharp.Processing.Processors.Overlays using IMemoryOwner rowColors = allocator.Allocate(interest.Width); rowColors.GetSpan().Fill(vignetteColor); - var operation = new RowIntervalOperation(configuration, interest, rowColors, this.blender, center, maxDistance, blendPercent, source); - ParallelRowIterator.IterateRowIntervals( + var operation = new RowOperation(configuration, interest, rowColors, this.blender, center, maxDistance, blendPercent, source); + ParallelRowIterator.IterateRows( configuration, interest, in operation); } - private readonly struct RowIntervalOperation : IRowIntervalOperation + private readonly struct RowOperation : IRowOperation { private readonly Configuration configuration; private readonly Rectangle bounds; @@ -82,7 +82,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Overlays private readonly ImageFrame source; [MethodImpl(InliningOptions.ShortMethod)] - public RowIntervalOperation( + public RowOperation( Configuration configuration, Rectangle bounds, IMemoryOwner colors, @@ -103,27 +103,24 @@ namespace SixLabors.ImageSharp.Processing.Processors.Overlays } [MethodImpl(InliningOptions.ShortMethod)] - public void Invoke(in RowInterval rows, Span span) + public void Invoke(int y, Span span) { Span colorSpan = this.colors.GetSpan(); - for (int y = rows.Min; y < rows.Max; y++) + for (int i = 0; i < this.bounds.Width; i++) { - for (int i = 0; i < this.bounds.Width; i++) - { - float distance = Vector2.Distance(this.center, new Vector2(i + this.bounds.X, y)); - span[i] = (this.blendPercent * (.9F * (distance / this.maxDistance))).Clamp(0, 1); - } - - Span destination = this.source.GetPixelRowSpan(y).Slice(this.bounds.X, this.bounds.Width); - - this.blender.Blend( - this.configuration, - destination, - destination, - colorSpan, - span); + float distance = Vector2.Distance(this.center, new Vector2(i + this.bounds.X, y)); + span[i] = (this.blendPercent * (.9F * (distance / this.maxDistance))).Clamp(0, 1); } + + Span destination = this.source.GetPixelRowSpan(y).Slice(this.bounds.X, this.bounds.Width); + + this.blender.Blend( + this.configuration, + destination, + destination, + colorSpan, + span); } } } From a71bcd2a21ecf0f4165fb35c6e33de910248b1a6 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Thu, 27 Feb 2020 19:47:03 +0100 Subject: [PATCH 22/64] Refactored AffineTransformProcessor --- .../AffineTransformProcessor{TPixel}.cs | 83 +++++++++---------- 1 file changed, 39 insertions(+), 44 deletions(-) diff --git a/src/ImageSharp/Processing/Processors/Transforms/Linear/AffineTransformProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Transforms/Linear/AffineTransformProcessor{TPixel}.cs index 26475922f..338064d23 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/Linear/AffineTransformProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/Linear/AffineTransformProcessor{TPixel}.cs @@ -72,7 +72,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms if (sampler is NearestNeighborResampler) { var nnOperation = new NNAffineOperation(source, destination, matrix); - ParallelRowIterator.IterateRowIntervals( + ParallelRowIterator.IterateRows( configuration, destination.Bounds(), in nnOperation); @@ -105,13 +105,13 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms radialExtents, maxSourceExtents); - ParallelRowIterator.IterateRowIntervals, Vector4>( + ParallelRowIterator.IterateRows, Vector4>( configuration, destination.Bounds(), in operation); } - private readonly struct NNAffineOperation : IRowIntervalOperation + private readonly struct NNAffineOperation : IRowOperation { private readonly ImageFrame source; private readonly ImageFrame destination; @@ -133,28 +133,25 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms } [MethodImpl(InliningOptions.ShortMethod)] - public void Invoke(in RowInterval rows) + public void Invoke(int y) { - for (int y = rows.Min; y < rows.Max; y++) + Span destRow = this.destination.GetPixelRowSpan(y); + + for (int x = 0; x < this.maxX; x++) { - Span destRow = this.destination.GetPixelRowSpan(y); + var point = Vector2.Transform(new Vector2(x, y), this.matrix); + int px = (int)MathF.Round(point.X); + int py = (int)MathF.Round(point.Y); - for (int x = 0; x < this.maxX; x++) + if (this.bounds.Contains(px, py)) { - var point = Vector2.Transform(new Vector2(x, y), this.matrix); - int px = (int)MathF.Round(point.X); - int py = (int)MathF.Round(point.Y); - - if (this.bounds.Contains(px, py)) - { - destRow[x] = this.source[px, py]; - } + destRow[x] = this.source[px, py]; } } } } - private readonly struct AffineOperation : IRowIntervalOperation + private readonly struct AffineOperation : IRowOperation where TResampler : struct, IResampler { private readonly Configuration configuration; @@ -193,41 +190,39 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms } [MethodImpl(InliningOptions.ShortMethod)] - public void Invoke(in RowInterval rows, Span span) + public void Invoke(int y, Span span) { Buffer2D sourceBuffer = this.source.PixelBuffer; - for (int y = rows.Min; y < rows.Max; y++) - { - PixelOperations.Instance.ToVector4( - this.configuration, - this.destination.GetPixelRowSpan(y), - span); - ref float yKernelSpanRef = ref MemoryMarshal.GetReference(this.yKernelBuffer.GetRowSpan(y)); - ref float xKernelSpanRef = ref MemoryMarshal.GetReference(this.xKernelBuffer.GetRowSpan(y)); + PixelOperations.Instance.ToVector4( + this.configuration, + this.destination.GetPixelRowSpan(y), + span); - for (int x = 0; x < this.maxX; x++) - { - // Use the single precision position to calculate correct bounding pixels - // otherwise we get rogue pixels outside of the bounds. - var point = Vector2.Transform(new Vector2(x, y), this.matrix); - LinearTransformUtilities.Convolve( - in this.sampler, - point, - sourceBuffer, - span, - x, - ref yKernelSpanRef, - ref xKernelSpanRef, - this.radialExtents, - this.maxSourceExtents); - } + ref float yKernelSpanRef = ref MemoryMarshal.GetReference(this.yKernelBuffer.GetRowSpan(y)); + ref float xKernelSpanRef = ref MemoryMarshal.GetReference(this.xKernelBuffer.GetRowSpan(y)); - PixelOperations.Instance.FromVector4Destructive( - this.configuration, + for (int x = 0; x < this.maxX; x++) + { + // Use the single precision position to calculate correct bounding pixels + // otherwise we get rogue pixels outside of the bounds. + var point = Vector2.Transform(new Vector2(x, y), this.matrix); + LinearTransformUtilities.Convolve( + in this.sampler, + point, + sourceBuffer, span, - this.destination.GetPixelRowSpan(y)); + x, + ref yKernelSpanRef, + ref xKernelSpanRef, + this.radialExtents, + this.maxSourceExtents); } + + PixelOperations.Instance.FromVector4Destructive( + this.configuration, + span, + this.destination.GetPixelRowSpan(y)); } } } From 679d36d9c08897bf99b22a678ef87c78747a2143 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Thu, 27 Feb 2020 19:47:53 +0100 Subject: [PATCH 23/64] Refactored FlipProcessor --- .../Transforms/Linear/FlipProcessor{TPixel}.cs | 17 +++++------------ 1 file changed, 5 insertions(+), 12 deletions(-) diff --git a/src/ImageSharp/Processing/Processors/Transforms/Linear/FlipProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Transforms/Linear/FlipProcessor{TPixel}.cs index f5fde8a22..30d413288 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/Linear/FlipProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/Linear/FlipProcessor{TPixel}.cs @@ -5,7 +5,6 @@ using System; using System.Buffers; using System.Runtime.CompilerServices; using SixLabors.ImageSharp.Advanced; -using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Processing.Processors.Transforms @@ -76,28 +75,22 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms /// The configuration. private void FlipY(ImageFrame source, Configuration configuration) { - var operation = new RowIntervalOperation(source); - ParallelRowIterator.IterateRowIntervals( + var operation = new RowOperation(source); + ParallelRowIterator.IterateRows( configuration, source.Bounds(), in operation); } - private readonly struct RowIntervalOperation : IRowIntervalOperation + private readonly struct RowOperation : IRowOperation { private readonly ImageFrame source; [MethodImpl(InliningOptions.ShortMethod)] - public RowIntervalOperation(ImageFrame source) => this.source = source; + public RowOperation(ImageFrame source) => this.source = source; [MethodImpl(InliningOptions.ShortMethod)] - public void Invoke(in RowInterval rows) - { - for (int y = rows.Min; y < rows.Max; y++) - { - this.source.GetPixelRowSpan(y).Reverse(); - } - } + public void Invoke(int y) => this.source.GetPixelRowSpan(y).Reverse(); } } } From eaab2391123a3777a9ff38de5733b9f722628c9b Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Thu, 27 Feb 2020 19:48:48 +0100 Subject: [PATCH 24/64] Refactored ProjectiveTransformProcessor --- .../ProjectiveTransformProcessor{TPixel}.cs | 83 +++++++++---------- 1 file changed, 39 insertions(+), 44 deletions(-) diff --git a/src/ImageSharp/Processing/Processors/Transforms/Linear/ProjectiveTransformProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Transforms/Linear/ProjectiveTransformProcessor{TPixel}.cs index 07178117a..879bfe7ee 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/Linear/ProjectiveTransformProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/Linear/ProjectiveTransformProcessor{TPixel}.cs @@ -72,7 +72,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms if (sampler is NearestNeighborResampler) { var nnOperation = new NNProjectiveOperation(source, destination, matrix); - ParallelRowIterator.IterateRowIntervals( + ParallelRowIterator.IterateRows( configuration, destination.Bounds(), in nnOperation); @@ -105,13 +105,13 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms radialExtents, maxSourceExtents); - ParallelRowIterator.IterateRowIntervals, Vector4>( + ParallelRowIterator.IterateRows, Vector4>( configuration, destination.Bounds(), in operation); } - private readonly struct NNProjectiveOperation : IRowIntervalOperation + private readonly struct NNProjectiveOperation : IRowOperation { private readonly ImageFrame source; private readonly ImageFrame destination; @@ -133,28 +133,25 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms } [MethodImpl(InliningOptions.ShortMethod)] - public void Invoke(in RowInterval rows) + public void Invoke(int y) { - for (int y = rows.Min; y < rows.Max; y++) + Span destRow = this.destination.GetPixelRowSpan(y); + + for (int x = 0; x < this.maxX; x++) { - Span destRow = this.destination.GetPixelRowSpan(y); + Vector2 point = TransformUtilities.ProjectiveTransform2D(x, y, this.matrix); + int px = (int)MathF.Round(point.X); + int py = (int)MathF.Round(point.Y); - for (int x = 0; x < this.maxX; x++) + if (this.bounds.Contains(px, py)) { - Vector2 point = TransformUtilities.ProjectiveTransform2D(x, y, this.matrix); - int px = (int)MathF.Round(point.X); - int py = (int)MathF.Round(point.Y); - - if (this.bounds.Contains(px, py)) - { - destRow[x] = this.source[px, py]; - } + destRow[x] = this.source[px, py]; } } } } - private readonly struct ProjectiveOperation : IRowIntervalOperation + private readonly struct ProjectiveOperation : IRowOperation where TResampler : struct, IResampler { private readonly Configuration configuration; @@ -193,41 +190,39 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms } [MethodImpl(InliningOptions.ShortMethod)] - public void Invoke(in RowInterval rows, Span span) + public void Invoke(int y, Span span) { Buffer2D sourceBuffer = this.source.PixelBuffer; - for (int y = rows.Min; y < rows.Max; y++) - { - PixelOperations.Instance.ToVector4( - this.configuration, - this.destination.GetPixelRowSpan(y), - span); - ref float yKernelSpanRef = ref MemoryMarshal.GetReference(this.yKernelBuffer.GetRowSpan(y)); - ref float xKernelSpanRef = ref MemoryMarshal.GetReference(this.xKernelBuffer.GetRowSpan(y)); + PixelOperations.Instance.ToVector4( + this.configuration, + this.destination.GetPixelRowSpan(y), + span); - for (int x = 0; x < this.maxX; x++) - { - // Use the single precision position to calculate correct bounding pixels - // otherwise we get rogue pixels outside of the bounds. - Vector2 point = TransformUtilities.ProjectiveTransform2D(x, y, this.matrix); - LinearTransformUtilities.Convolve( - in this.sampler, - point, - sourceBuffer, - span, - x, - ref yKernelSpanRef, - ref xKernelSpanRef, - this.radialExtents, - this.maxSourceExtents); - } + ref float yKernelSpanRef = ref MemoryMarshal.GetReference(this.yKernelBuffer.GetRowSpan(y)); + ref float xKernelSpanRef = ref MemoryMarshal.GetReference(this.xKernelBuffer.GetRowSpan(y)); - PixelOperations.Instance.FromVector4Destructive( - this.configuration, + for (int x = 0; x < this.maxX; x++) + { + // Use the single precision position to calculate correct bounding pixels + // otherwise we get rogue pixels outside of the bounds. + Vector2 point = TransformUtilities.ProjectiveTransform2D(x, y, this.matrix); + LinearTransformUtilities.Convolve( + in this.sampler, + point, + sourceBuffer, span, - this.destination.GetPixelRowSpan(y)); + x, + ref yKernelSpanRef, + ref xKernelSpanRef, + this.radialExtents, + this.maxSourceExtents); } + + PixelOperations.Instance.FromVector4Destructive( + this.configuration, + span, + this.destination.GetPixelRowSpan(y)); } } } From f8f771dd916397133964a815b26597c9610b78d8 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Thu, 27 Feb 2020 19:49:49 +0100 Subject: [PATCH 25/64] Refactored RotateProcessor --- .../Linear/RotateProcessor{TPixel}.cs | 46 ++++++++----------- 1 file changed, 20 insertions(+), 26 deletions(-) diff --git a/src/ImageSharp/Processing/Processors/Transforms/Linear/RotateProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Transforms/Linear/RotateProcessor{TPixel}.cs index d6755e8ba..04d259e40 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/Linear/RotateProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/Linear/RotateProcessor{TPixel}.cs @@ -131,8 +131,8 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms /// The configuration. private void Rotate180(ImageFrame source, ImageFrame destination, Configuration configuration) { - var operation = new Rotate180RowIntervalOperation(source.Width, source.Height, source, destination); - ParallelRowIterator.IterateRowIntervals( + var operation = new Rotate180RowOperation(source.Width, source.Height, source, destination); + ParallelRowIterator.IterateRows( configuration, source.Bounds(), in operation); @@ -161,14 +161,14 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms /// The configuration. private void Rotate90(ImageFrame source, ImageFrame destination, Configuration configuration) { - var operation = new Rotate90RowIntervalOperation(destination.Bounds(), source.Width, source.Height, source, destination); - ParallelRowIterator.IterateRowIntervals( + var operation = new Rotate90RowOperation(destination.Bounds(), source.Width, source.Height, source, destination); + ParallelRowIterator.IterateRows( configuration, source.Bounds(), in operation); } - private readonly struct Rotate180RowIntervalOperation : IRowIntervalOperation + private readonly struct Rotate180RowOperation : IRowOperation { private readonly int width; private readonly int height; @@ -176,7 +176,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms private readonly ImageFrame destination; [MethodImpl(InliningOptions.ShortMethod)] - public Rotate180RowIntervalOperation( + public Rotate180RowOperation( int width, int height, ImageFrame source, @@ -189,17 +189,14 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms } [MethodImpl(InliningOptions.ShortMethod)] - public void Invoke(in RowInterval rows) + public void Invoke(int y) { - for (int y = rows.Min; y < rows.Max; y++) - { - Span sourceRow = this.source.GetPixelRowSpan(y); - Span targetRow = this.destination.GetPixelRowSpan(this.height - y - 1); + Span sourceRow = this.source.GetPixelRowSpan(y); + Span targetRow = this.destination.GetPixelRowSpan(this.height - y - 1); - for (int x = 0; x < this.width; x++) - { - targetRow[this.width - x - 1] = sourceRow[x]; - } + for (int x = 0; x < this.width; x++) + { + targetRow[this.width - x - 1] = sourceRow[x]; } } } @@ -248,7 +245,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms } } - private readonly struct Rotate90RowIntervalOperation : IRowIntervalOperation + private readonly struct Rotate90RowOperation : IRowOperation { private readonly Rectangle bounds; private readonly int width; @@ -257,7 +254,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms private readonly ImageFrame destination; [MethodImpl(InliningOptions.ShortMethod)] - public Rotate90RowIntervalOperation( + public Rotate90RowOperation( Rectangle bounds, int width, int height, @@ -272,18 +269,15 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms } [MethodImpl(InliningOptions.ShortMethod)] - public void Invoke(in RowInterval rows) + public void Invoke(int y) { - for (int y = rows.Min; y < rows.Max; y++) + Span sourceRow = this.source.GetPixelRowSpan(y); + int newX = this.height - y - 1; + for (int x = 0; x < this.width; x++) { - Span sourceRow = this.source.GetPixelRowSpan(y); - int newX = this.height - y - 1; - for (int x = 0; x < this.width; x++) + if (this.bounds.Contains(newX, x)) { - if (this.bounds.Contains(newX, x)) - { - this.destination[newX, x] = sourceRow[x]; - } + this.destination[newX, x] = sourceRow[x]; } } } From 1350c08873127941e17ef72b34cc61effaf47edd Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Thu, 27 Feb 2020 19:51:36 +0100 Subject: [PATCH 26/64] Refactored ResizeProcessor --- .../Resize/ResizeProcessor{TPixel}.cs | 29 +++++++++---------- 1 file changed, 13 insertions(+), 16 deletions(-) diff --git a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeProcessor{TPixel}.cs index c0dc88688..a4f32f749 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeProcessor{TPixel}.cs @@ -13,7 +13,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms /// Implements resizing of images using various resamplers. /// /// The pixel format. - internal partial class ResizeProcessor : TransformProcessor, IResamplingTransformImageProcessor + internal class ResizeProcessor : TransformProcessor, IResamplingTransformImageProcessor where TPixel : struct, IPixel { private readonly int destinationWidth; @@ -144,7 +144,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms float widthFactor = sourceRectangle.Width / (float)destinationRectangle.Width; float heightFactor = sourceRectangle.Height / (float)destinationRectangle.Height; - var operation = new NNRowIntervalOperation( + var operation = new NNRowOperation( sourceRectangle, destinationRectangle, widthFactor, @@ -152,7 +152,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms source, destination); - ParallelRowIterator.IterateRowIntervals( + ParallelRowIterator.IterateRows( configuration, interest, in operation); @@ -193,7 +193,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms } } - private readonly struct NNRowIntervalOperation : IRowIntervalOperation + private readonly struct NNRowOperation : IRowOperation { private readonly Rectangle sourceBounds; private readonly Rectangle destinationBounds; @@ -203,7 +203,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms private readonly ImageFrame destination; [MethodImpl(InliningOptions.ShortMethod)] - public NNRowIntervalOperation( + public NNRowOperation( Rectangle sourceBounds, Rectangle destinationBounds, float widthFactor, @@ -220,7 +220,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms } [MethodImpl(InliningOptions.ShortMethod)] - public void Invoke(in RowInterval rows) + public void Invoke(int y) { int sourceX = this.sourceBounds.X; int sourceY = this.sourceBounds.Y; @@ -229,17 +229,14 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms int destLeft = this.destinationBounds.Left; int destRight = this.destinationBounds.Right; - for (int y = rows.Min; y < rows.Max; y++) + // Y coordinates of source points + Span sourceRow = this.source.GetPixelRowSpan((int)(((y - destY) * this.heightFactor) + sourceY)); + Span targetRow = this.destination.GetPixelRowSpan(y); + + for (int x = destLeft; x < destRight; x++) { - // Y coordinates of source points - Span sourceRow = this.source.GetPixelRowSpan((int)(((y - destY) * this.heightFactor) + sourceY)); - Span targetRow = this.destination.GetPixelRowSpan(y); - - for (int x = destLeft; x < destRight; x++) - { - // X coordinates of source points - targetRow[x] = sourceRow[(int)(((x - destX) * this.widthFactor) + sourceX)]; - } + // X coordinates of source points + targetRow[x] = sourceRow[(int)(((x - destX) * this.widthFactor) + sourceX)]; } } } From ab6cf8d4314df3416e3c2341d25b065b64246064 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Thu, 27 Feb 2020 19:52:21 +0100 Subject: [PATCH 27/64] Refactored CropProcessor --- .../Transforms/CropProcessor{TPixel}.cs | 21 ++++++++----------- 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/src/ImageSharp/Processing/Processors/Transforms/CropProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Transforms/CropProcessor{TPixel}.cs index d72790ea1..75509283f 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/CropProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/CropProcessor{TPixel}.cs @@ -51,9 +51,9 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms ParallelExecutionSettings parallelSettings = ParallelExecutionSettings.FromConfiguration(this.Configuration).MultiplyMinimumPixelsPerTask(4); - var operation = new RowIntervalOperation(bounds, source, destination); + var operation = new RowOperation(bounds, source, destination); - ParallelRowIterator.IterateRowIntervals( + ParallelRowIterator.IterateRows( bounds, in parallelSettings, in operation); @@ -62,20 +62,20 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms /// /// A implementing the processor logic for . /// - private readonly struct RowIntervalOperation : IRowIntervalOperation + private readonly struct RowOperation : IRowOperation { private readonly Rectangle bounds; private readonly ImageFrame source; private readonly ImageFrame destination; /// - /// Initializes a new instance of the struct. + /// Initializes a new instance of the struct. /// /// The target processing bounds for the current instance. /// The source for the current instance. /// The destination for the current instance. [MethodImpl(InliningOptions.ShortMethod)] - public RowIntervalOperation(Rectangle bounds, ImageFrame source, ImageFrame destination) + public RowOperation(Rectangle bounds, ImageFrame source, ImageFrame destination) { this.bounds = bounds; this.source = source; @@ -84,14 +84,11 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms /// [MethodImpl(InliningOptions.ShortMethod)] - public void Invoke(in RowInterval rows) + public void Invoke(int y) { - for (int y = rows.Min; y < rows.Max; y++) - { - Span sourceRow = this.source.GetPixelRowSpan(y).Slice(this.bounds.Left); - Span targetRow = this.destination.GetPixelRowSpan(y - this.bounds.Top); - sourceRow.Slice(0, this.bounds.Width).CopyTo(targetRow); - } + Span sourceRow = this.source.GetPixelRowSpan(y).Slice(this.bounds.Left); + Span targetRow = this.destination.GetPixelRowSpan(y - this.bounds.Top); + sourceRow.Slice(0, this.bounds.Width).CopyTo(targetRow); } } } From 65114123ce2066544b3725ec282a74db526bbc66 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Thu, 27 Feb 2020 20:01:10 +0100 Subject: [PATCH 28/64] Refactored byte[] array in ZigZag type --- src/ImageSharp/Formats/Jpeg/Components/ZigZag.cs | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/src/ImageSharp/Formats/Jpeg/Components/ZigZag.cs b/src/ImageSharp/Formats/Jpeg/Components/ZigZag.cs index 059e2052b..cd639f602 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/ZigZag.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/ZigZag.cs @@ -34,12 +34,11 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components public fixed byte Data[Size]; /// - /// Unzig maps from the zigzag ordering to the natural ordering. For example, - /// unzig[3] is the column and row of the fourth element in zigzag order. The - /// value is 16, which means first column (16%8 == 0) and third row (16/8 == 2). + /// Gets the unzigs map, which maps from the zigzag ordering to the natural ordering. + /// For example, unzig[3] is the column and row of the fourth element in zigzag order. + /// The value is 16, which means first column (16%8 == 0) and third row (16/8 == 2). /// - private static readonly byte[] Unzig = - new byte[Size] + private static ReadOnlySpan Unzig => new byte[] { 0, 1, 8, 16, 9, 2, 3, 10, 17, 24, 32, 25, 18, 11, 4, 5, @@ -75,8 +74,11 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components public static ZigZag CreateUnzigTable() { ZigZag result = default; - byte* unzigPtr = result.Data; - Marshal.Copy(Unzig, 0, (IntPtr)unzigPtr, Size); + ref byte sourceRef = ref MemoryMarshal.GetReference(Unzig); + ref byte destinationRef = ref Unsafe.AsRef(result.Data); + + Unsafe.CopyBlock(ref sourceRef, ref destinationRef, Size); + return result; } From 803f62825708d480466debc9262c461e4576c04d Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Thu, 27 Feb 2020 20:06:56 +0100 Subject: [PATCH 29/64] Refactored byte[] array in DeflaterHuffman type --- .../Formats/Png/Zlib/DeflaterHuffman.cs | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/src/ImageSharp/Formats/Png/Zlib/DeflaterHuffman.cs b/src/ImageSharp/Formats/Png/Zlib/DeflaterHuffman.cs index e8dd8a520..c10eafaf3 100644 --- a/src/ImageSharp/Formats/Png/Zlib/DeflaterHuffman.cs +++ b/src/ImageSharp/Formats/Png/Zlib/DeflaterHuffman.cs @@ -40,8 +40,6 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib // probability, to avoid transmitting the lengths for unused bit length codes. private static readonly int[] BitLengthOrder = { 16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15 }; - private static readonly byte[] Bit4Reverse = { 0, 8, 4, 12, 2, 10, 6, 14, 1, 9, 5, 13, 3, 11, 7, 15 }; - private static readonly short[] StaticLCodes; private static readonly byte[] StaticLLength; private static readonly short[] StaticDCodes; @@ -128,6 +126,8 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib this.pinnedLiteralBuffer = (short*)this.literalBufferHandle.Pointer; } + private static ReadOnlySpan Bit4Reverse => new byte[] { 0, 8, 4, 12, 2, 10, 6, 14, 1, 9, 5, 13, 3, 11, 7, 15 }; + /// /// Gets the pending buffer to use. /// @@ -363,10 +363,17 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib [MethodImpl(InliningOptions.ShortMethod)] public static short BitReverse(int toReverse) { - return (short)(Bit4Reverse[toReverse & 0xF] << 12 - | Bit4Reverse[(toReverse >> 4) & 0xF] << 8 - | Bit4Reverse[(toReverse >> 8) & 0xF] << 4 - | Bit4Reverse[toReverse >> 12]); + DebugGuard.MustBeLessThan(toReverse & 0xF, Bit4Reverse.Length, nameof(toReverse)); + DebugGuard.MustBeLessThan((toReverse >> 4) & 0xF, Bit4Reverse.Length, nameof(toReverse)); + DebugGuard.MustBeLessThan((toReverse >> 8) & 0xF, Bit4Reverse.Length, nameof(toReverse)); + DebugGuard.MustBeLessThan(toReverse >> 12, Bit4Reverse.Length, nameof(toReverse)); + + ref byte bit4ReverseRef = ref MemoryMarshal.GetReference(Bit4Reverse); + + return (short)(Unsafe.Add(ref bit4ReverseRef, toReverse & 0xF) << 12 + | Unsafe.Add(ref bit4ReverseRef, (toReverse >> 4) & 0xF) << 8 + | Unsafe.Add(ref bit4ReverseRef, (toReverse >> 8) & 0xF) << 4 + | Unsafe.Add(ref bit4ReverseRef, toReverse >> 12)); } /// From ad80ae2d852e7c924821df7640598f93d7a40fc9 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Thu, 27 Feb 2020 20:10:43 +0100 Subject: [PATCH 30/64] Refactored byte[] array in PngConstants type --- src/ImageSharp/Common/Extensions/StreamExtensions.cs | 1 - src/ImageSharp/Formats/Png/PngConstants.cs | 5 +++-- src/ImageSharp/Formats/Png/PngEncoderCore.cs | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/ImageSharp/Common/Extensions/StreamExtensions.cs b/src/ImageSharp/Common/Extensions/StreamExtensions.cs index 971bff322..bed960a64 100644 --- a/src/ImageSharp/Common/Extensions/StreamExtensions.cs +++ b/src/ImageSharp/Common/Extensions/StreamExtensions.cs @@ -2,7 +2,6 @@ // Licensed under the Apache License, Version 2.0. using System; -using System.Buffers; using System.IO; using SixLabors.ImageSharp.Memory; diff --git a/src/ImageSharp/Formats/Png/PngConstants.cs b/src/ImageSharp/Formats/Png/PngConstants.cs index 632460ec4..4ad698e32 100644 --- a/src/ImageSharp/Formats/Png/PngConstants.cs +++ b/src/ImageSharp/Formats/Png/PngConstants.cs @@ -1,6 +1,7 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. +using System; using System.Collections.Generic; using System.Text; @@ -37,9 +38,9 @@ namespace SixLabors.ImageSharp.Formats.Png public static readonly IEnumerable FileExtensions = new[] { "png" }; /// - /// The header bytes identifying a Png. + /// Gets the header bytes identifying a Png. /// - public static readonly byte[] HeaderBytes = + public static ReadOnlySpan HeaderBytes => new byte[] { 0x89, // Set the high bit. 0x50, // P diff --git a/src/ImageSharp/Formats/Png/PngEncoderCore.cs b/src/ImageSharp/Formats/Png/PngEncoderCore.cs index 5f14d483b..32fe8acdc 100644 --- a/src/ImageSharp/Formats/Png/PngEncoderCore.cs +++ b/src/ImageSharp/Formats/Png/PngEncoderCore.cs @@ -149,7 +149,7 @@ namespace SixLabors.ImageSharp.Formats.Png QuantizedFrame quantized = PngEncoderOptionsHelpers.CreateQuantizedFrame(this.options, image); this.bitDepth = PngEncoderOptionsHelpers.CalculateBitDepth(this.options, image, quantized); - stream.Write(PngConstants.HeaderBytes, 0, PngConstants.HeaderBytes.Length); + stream.Write(PngConstants.HeaderBytes); this.WriteHeaderChunk(stream); this.WritePaletteChunk(stream, quantized); From 1991339b0babdb3f4833d43fecc60fed95dfa3ef Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Thu, 27 Feb 2020 20:12:13 +0100 Subject: [PATCH 31/64] Refactored byte[] array in ExifConstants type --- src/ImageSharp/Metadata/Profiles/Exif/ExifConstants.cs | 10 ++++++---- src/ImageSharp/Metadata/Profiles/Exif/ExifWriter.cs | 2 +- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/src/ImageSharp/Metadata/Profiles/Exif/ExifConstants.cs b/src/ImageSharp/Metadata/Profiles/Exif/ExifConstants.cs index c7112c47a..c58b224e4 100644 --- a/src/ImageSharp/Metadata/Profiles/Exif/ExifConstants.cs +++ b/src/ImageSharp/Metadata/Profiles/Exif/ExifConstants.cs @@ -1,11 +1,13 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. +using System; + namespace SixLabors.ImageSharp.Metadata.Profiles.Exif { internal static class ExifConstants { - public static readonly byte[] LittleEndianByteOrderMarker = + public static ReadOnlySpan LittleEndianByteOrderMarker => new byte[] { (byte)'I', (byte)'I', @@ -13,7 +15,7 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif 0x00, }; - public static readonly byte[] BigEndianByteOrderMarker = + public static ReadOnlySpan BigEndianByteOrderMarker => new byte[] { (byte)'M', (byte)'M', @@ -21,4 +23,4 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif 0x2A }; } -} \ No newline at end of file +} diff --git a/src/ImageSharp/Metadata/Profiles/Exif/ExifWriter.cs b/src/ImageSharp/Metadata/Profiles/Exif/ExifWriter.cs index c068461b2..b00813730 100644 --- a/src/ImageSharp/Metadata/Profiles/Exif/ExifWriter.cs +++ b/src/ImageSharp/Metadata/Profiles/Exif/ExifWriter.cs @@ -77,7 +77,7 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif int i = 0; // The byte order marker for little-endian, followed by the number 42 and a 0 - ExifConstants.LittleEndianByteOrderMarker.AsSpan().CopyTo(result.AsSpan(start: i)); + ExifConstants.LittleEndianByteOrderMarker.CopyTo(result.AsSpan(start: i)); i += ExifConstants.LittleEndianByteOrderMarker.Length; uint ifdOffset = ((uint)i - startIndex) + 4U; From 620fc169a54d2788f1ec4f5fd568de294917c395 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Thu, 27 Feb 2020 20:14:27 +0100 Subject: [PATCH 32/64] Refactored byte[] array in Octree type --- .../OctreeFrameQuantizer{TPixel}.cs | 37 +++++++++++-------- 1 file changed, 21 insertions(+), 16 deletions(-) diff --git a/src/ImageSharp/Processing/Processors/Quantization/OctreeFrameQuantizer{TPixel}.cs b/src/ImageSharp/Processing/Processors/Quantization/OctreeFrameQuantizer{TPixel}.cs index 2b8ef3f0b..2fea9615f 100644 --- a/src/ImageSharp/Processing/Processors/Quantization/OctreeFrameQuantizer{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Quantization/OctreeFrameQuantizer{TPixel}.cs @@ -6,6 +6,7 @@ using System.Buffers; using System.Collections.Generic; using System.Numerics; using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; @@ -111,21 +112,6 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization /// private sealed class Octree { - /// - /// Mask used when getting the appropriate pixels for a given node. - /// - private static readonly byte[] Mask = new byte[] - { - 0b10000000, - 0b1000000, - 0b100000, - 0b10000, - 0b1000, - 0b100, - 0b10, - 0b1 - }; - /// /// The root of the Octree /// @@ -162,6 +148,21 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization this.previousNode = null; } + /// + /// Gets the mask used when getting the appropriate pixels for a given node. + /// + private static ReadOnlySpan Mask => new byte[] + { + 0b10000000, + 0b1000000, + 0b100000, + 0b10000, + 0b1000, + 0b100, + 0b10, + 0b1 + }; + /// /// Gets or sets the number of leaves in the tree /// @@ -513,8 +514,12 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization [MethodImpl(InliningOptions.ShortMethod)] private static int GetColorIndex(ref Rgba32 color, int level) { + DebugGuard.MustBeLessThan(level, Mask.Length, nameof(level)); + int shift = 7 - level; - byte mask = Mask[level]; + ref byte maskRef = ref MemoryMarshal.GetReference(Mask); + byte mask = Unsafe.Add(ref maskRef, level); + return ((color.R & mask) >> shift) | ((color.G & mask) >> (shift - 1)) | ((color.B & mask) >> (shift - 2)); From a76fbfed32670de9b4db76c3dd4d4e11c3f6f10f Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Thu, 27 Feb 2020 20:19:46 +0100 Subject: [PATCH 33/64] Refactored byte[] arrays in GifConstants type --- src/ImageSharp/Formats/Gif/GifConstants.cs | 31 +++++++++++++------ src/ImageSharp/Formats/Gif/GifEncoderCore.cs | 2 +- .../GifNetscapeLoopingApplicationExtension.cs | 2 +- 3 files changed, 23 insertions(+), 12 deletions(-) diff --git a/src/ImageSharp/Formats/Gif/GifConstants.cs b/src/ImageSharp/Formats/Gif/GifConstants.cs index c9d631da0..c945ce122 100644 --- a/src/ImageSharp/Formats/Gif/GifConstants.cs +++ b/src/ImageSharp/Formats/Gif/GifConstants.cs @@ -1,6 +1,7 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. +using System; using System.Collections.Generic; using System.Text; @@ -21,11 +22,6 @@ namespace SixLabors.ImageSharp.Formats.Gif /// public const string FileVersion = "89a"; - /// - /// The ASCII encoded bytes used to identify the GIF file. - /// - internal static readonly byte[] MagicNumber = Encoding.ASCII.GetBytes(FileType + FileVersion); - /// /// The extension block introducer !. /// @@ -51,11 +47,6 @@ namespace SixLabors.ImageSharp.Formats.Gif /// public const string NetscapeApplicationIdentification = "NETSCAPE2.0"; - /// - /// The ASCII encoded application identification bytes. - /// - internal static readonly byte[] NetscapeApplicationIdentificationBytes = Encoding.ASCII.GetBytes(NetscapeApplicationIdentification); - /// /// The Netscape looping application sub block size. /// @@ -101,6 +92,26 @@ namespace SixLabors.ImageSharp.Formats.Gif /// public static readonly Encoding Encoding = Encoding.ASCII; + /// + /// Gets the ASCII encoded bytes used to identify the GIF file (combining and ). + /// + internal static ReadOnlySpan MagicNumber => new[] + { + (byte)'G', (byte)'I', (byte)'F', + (byte)'8', (byte)'9', (byte)'a' + }; + + /// + /// Gets the ASCII encoded application identification bytes (representing ). + /// + internal static ReadOnlySpan NetscapeApplicationIdentificationBytes => new[] + { + (byte)'N', (byte)'E', (byte)'T', + (byte)'S', (byte)'C', (byte)'A', + (byte)'P', (byte)'E', + (byte)'2', (byte)'.', (byte)'0' + }; + /// /// The collection of mimetypes that equate to a Gif. /// diff --git a/src/ImageSharp/Formats/Gif/GifEncoderCore.cs b/src/ImageSharp/Formats/Gif/GifEncoderCore.cs index 3a0fa5169..e9506714f 100644 --- a/src/ImageSharp/Formats/Gif/GifEncoderCore.cs +++ b/src/ImageSharp/Formats/Gif/GifEncoderCore.cs @@ -238,7 +238,7 @@ namespace SixLabors.ImageSharp.Formats.Gif /// /// The stream to write to. [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void WriteHeader(Stream stream) => stream.Write(GifConstants.MagicNumber, 0, GifConstants.MagicNumber.Length); + private void WriteHeader(Stream stream) => stream.Write(GifConstants.MagicNumber); /// /// Writes the logical screen descriptor to the stream. diff --git a/src/ImageSharp/Formats/Gif/Sections/GifNetscapeLoopingApplicationExtension.cs b/src/ImageSharp/Formats/Gif/Sections/GifNetscapeLoopingApplicationExtension.cs index 8af5b81c2..5e26370ba 100644 --- a/src/ImageSharp/Formats/Gif/Sections/GifNetscapeLoopingApplicationExtension.cs +++ b/src/ImageSharp/Formats/Gif/Sections/GifNetscapeLoopingApplicationExtension.cs @@ -29,7 +29,7 @@ namespace SixLabors.ImageSharp.Formats.Gif buffer[0] = GifConstants.ApplicationBlockSize; // Write NETSCAPE2.0 - GifConstants.NetscapeApplicationIdentificationBytes.AsSpan().CopyTo(buffer.Slice(1, 11)); + GifConstants.NetscapeApplicationIdentificationBytes.CopyTo(buffer.Slice(1, 11)); // Application Data ---- buffer[12] = 3; // Application block length (always 3) From 3196561c8fe94c40b5db13e3a649d1a03a9a5c59 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Thu, 27 Feb 2020 20:24:49 +0100 Subject: [PATCH 34/64] Refactored byte[] arrays in ProfileResolver type --- .../Components/Decoder/ProfileResolver.cs | 35 +++++++++++++------ .../Formats/Jpg/ProfileResolverTests.cs | 12 +++---- 2 files changed, 30 insertions(+), 17 deletions(-) diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ProfileResolver.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ProfileResolver.cs index 54633a5d7..87b486ea6 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ProfileResolver.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ProfileResolver.cs @@ -1,8 +1,7 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System; -using System.Text; namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder { @@ -12,24 +11,38 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder internal static class ProfileResolver { /// - /// Describes the JFIF specific markers. + /// Gets the JFIF specific markers. /// - public static readonly byte[] JFifMarker = Encoding.ASCII.GetBytes("JFIF\0"); + public static ReadOnlySpan JFifMarker => new[] + { + (byte)'J', (byte)'F', (byte)'I', (byte)'F', (byte)'\0' + }; /// - /// Describes the ICC specific markers. + /// Gets the ICC specific markers. /// - public static readonly byte[] IccMarker = Encoding.ASCII.GetBytes("ICC_PROFILE\0"); + public static ReadOnlySpan IccMarker => new[] + { + (byte)'I', (byte)'C', (byte)'C', (byte)'_', + (byte)'P', (byte)'R', (byte)'O', (byte)'F', + (byte)'I', (byte)'L', (byte)'E', (byte)'\0' + }; /// - /// Describes the EXIF specific markers. + /// Gets the EXIF specific markers. /// - public static readonly byte[] ExifMarker = Encoding.ASCII.GetBytes("Exif\0\0"); + public static ReadOnlySpan ExifMarker => new[] + { + (byte)'E', (byte)'x', (byte)'i', (byte)'f', (byte)'\0', (byte)'\0' + }; /// - /// Describes Adobe specific markers . + /// Gets the Adobe specific markers . /// - public static readonly byte[] AdobeMarker = Encoding.ASCII.GetBytes("Adobe"); + public static ReadOnlySpan AdobeMarker => new[] + { + (byte)'A', (byte)'d', (byte)'o', (byte)'b', (byte)'e' + }; /// /// Returns a value indicating whether the passed bytes are a match to the profile identifier. @@ -43,4 +56,4 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder && bytesToCheck.Slice(0, profileIdentifier.Length).SequenceEqual(profileIdentifier); } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/Formats/Jpg/ProfileResolverTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/ProfileResolverTests.cs index c908abc50..fb09065b0 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/ProfileResolverTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/ProfileResolverTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System.Text; @@ -19,25 +19,25 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg [Fact] public void ProfileResolverHasCorrectJFifMarker() { - Assert.Equal(JFifMarker, ProfileResolver.JFifMarker); + Assert.Equal(JFifMarker, ProfileResolver.JFifMarker.ToArray()); } [Fact] public void ProfileResolverHasCorrectExifMarker() { - Assert.Equal(ExifMarker, ProfileResolver.ExifMarker); + Assert.Equal(ExifMarker, ProfileResolver.ExifMarker.ToArray()); } [Fact] public void ProfileResolverHasCorrectIccMarker() { - Assert.Equal(IccMarker, ProfileResolver.IccMarker); + Assert.Equal(IccMarker, ProfileResolver.IccMarker.ToArray()); } [Fact] public void ProfileResolverHasCorrectAdobeMarker() { - Assert.Equal(AdobeMarker, ProfileResolver.AdobeMarker); + Assert.Equal(AdobeMarker, ProfileResolver.AdobeMarker.ToArray()); } [Fact] @@ -76,4 +76,4 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg Assert.False(ProfileResolver.IsProfile(AdobeMarker, ProfileResolver.IccMarker)); } } -} \ No newline at end of file +} From f36f9ee727be14e1fd47cc12cf1b03b79039bbb7 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Thu, 27 Feb 2020 20:25:34 +0100 Subject: [PATCH 35/64] Code style tweaks --- src/ImageSharp/Formats/Gif/GifConstants.cs | 20 +++++++-------- src/ImageSharp/Formats/Png/PngConstants.cs | 30 +++++++++++----------- 2 files changed, 25 insertions(+), 25 deletions(-) diff --git a/src/ImageSharp/Formats/Gif/GifConstants.cs b/src/ImageSharp/Formats/Gif/GifConstants.cs index c945ce122..06c4b3fc6 100644 --- a/src/ImageSharp/Formats/Gif/GifConstants.cs +++ b/src/ImageSharp/Formats/Gif/GifConstants.cs @@ -92,6 +92,16 @@ namespace SixLabors.ImageSharp.Formats.Gif /// public static readonly Encoding Encoding = Encoding.ASCII; + /// + /// The collection of mimetypes that equate to a Gif. + /// + public static readonly IEnumerable MimeTypes = new[] { "image/gif" }; + + /// + /// The collection of file extensions that equate to a Gif. + /// + public static readonly IEnumerable FileExtensions = new[] { "gif" }; + /// /// Gets the ASCII encoded bytes used to identify the GIF file (combining and ). /// @@ -111,15 +121,5 @@ namespace SixLabors.ImageSharp.Formats.Gif (byte)'P', (byte)'E', (byte)'2', (byte)'.', (byte)'0' }; - - /// - /// The collection of mimetypes that equate to a Gif. - /// - public static readonly IEnumerable MimeTypes = new[] { "image/gif" }; - - /// - /// The collection of file extensions that equate to a Gif. - /// - public static readonly IEnumerable FileExtensions = new[] { "gif" }; } } diff --git a/src/ImageSharp/Formats/Png/PngConstants.cs b/src/ImageSharp/Formats/Png/PngConstants.cs index 4ad698e32..247bb3c75 100644 --- a/src/ImageSharp/Formats/Png/PngConstants.cs +++ b/src/ImageSharp/Formats/Png/PngConstants.cs @@ -37,21 +37,6 @@ namespace SixLabors.ImageSharp.Formats.Png /// public static readonly IEnumerable FileExtensions = new[] { "png" }; - /// - /// Gets the header bytes identifying a Png. - /// - public static ReadOnlySpan HeaderBytes => new byte[] - { - 0x89, // Set the high bit. - 0x50, // P - 0x4E, // N - 0x47, // G - 0x0D, // Line ending CRLF - 0x0A, // Line ending CRLF - 0x1A, // EOF - 0x0A // LF - }; - /// /// The header bytes as a big-endian coded ulong. /// @@ -78,5 +63,20 @@ namespace SixLabors.ImageSharp.Formats.Png /// The minimum length of a keyword in a text chunk is 1 byte. /// public const int MinTextKeywordLength = 1; + + /// + /// Gets the header bytes identifying a Png. + /// + public static ReadOnlySpan HeaderBytes => new byte[] + { + 0x89, // Set the high bit. + 0x50, // P + 0x4E, // N + 0x47, // G + 0x0D, // Line ending CRLF + 0x0A, // Line ending CRLF + 0x1A, // EOF + 0x0A // LF + }; } } From 23a80a3483c6ca98e21d1b7f2f634bde61543933 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Thu, 27 Feb 2020 21:26:43 +0100 Subject: [PATCH 36/64] Fixed a build error --- src/ImageSharp/Common/Extensions/StreamExtensions.cs | 3 +++ .../Metadata/Profiles/Exif/ExifProfileTests.cs | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/ImageSharp/Common/Extensions/StreamExtensions.cs b/src/ImageSharp/Common/Extensions/StreamExtensions.cs index bed960a64..5d8668257 100644 --- a/src/ImageSharp/Common/Extensions/StreamExtensions.cs +++ b/src/ImageSharp/Common/Extensions/StreamExtensions.cs @@ -4,6 +4,9 @@ using System; using System.IO; using SixLabors.ImageSharp.Memory; +#if !SUPPORTS_SPAN_STREAM +using System.Buffers; +#endif namespace SixLabors.ImageSharp { diff --git a/tests/ImageSharp.Tests/Metadata/Profiles/Exif/ExifProfileTests.cs b/tests/ImageSharp.Tests/Metadata/Profiles/Exif/ExifProfileTests.cs index 79bf76545..7069b0346 100644 --- a/tests/ImageSharp.Tests/Metadata/Profiles/Exif/ExifProfileTests.cs +++ b/tests/ImageSharp.Tests/Metadata/Profiles/Exif/ExifProfileTests.cs @@ -394,7 +394,7 @@ namespace SixLabors.ImageSharp.Tests public void ProfileToByteArray() { // Arrange - byte[] exifBytesWithoutExifCode = ExifConstants.LittleEndianByteOrderMarker; + byte[] exifBytesWithoutExifCode = ExifConstants.LittleEndianByteOrderMarker.ToArray(); ExifProfile expectedProfile = CreateExifProfile(); var expectedProfileTags = expectedProfile.Values.Select(x => x.Tag).ToList(); From e908e0a2a2cac01b2d8be390223dca729fb6d5eb Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Fri, 28 Feb 2020 01:03:38 +0100 Subject: [PATCH 37/64] Minor code changes to improve clarity --- src/ImageSharp/Formats/Jpeg/Components/ZigZag.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ImageSharp/Formats/Jpeg/Components/ZigZag.cs b/src/ImageSharp/Formats/Jpeg/Components/ZigZag.cs index cd639f602..669abad28 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/ZigZag.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/ZigZag.cs @@ -77,7 +77,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components ref byte sourceRef = ref MemoryMarshal.GetReference(Unzig); ref byte destinationRef = ref Unsafe.AsRef(result.Data); - Unsafe.CopyBlock(ref sourceRef, ref destinationRef, Size); + Unzig.CopyTo(new Span(result.Data, Size)); return result; } From b73dac301e404927f130f97bb4b5673e0891d71a Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Fri, 28 Feb 2020 02:19:23 +0100 Subject: [PATCH 38/64] Added input validation to DeflaterHuffman unsafe offsetting --- .../Formats/Png/Zlib/DeflaterHuffman.cs | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/src/ImageSharp/Formats/Png/Zlib/DeflaterHuffman.cs b/src/ImageSharp/Formats/Png/Zlib/DeflaterHuffman.cs index c10eafaf3..169215be2 100644 --- a/src/ImageSharp/Formats/Png/Zlib/DeflaterHuffman.cs +++ b/src/ImageSharp/Formats/Png/Zlib/DeflaterHuffman.cs @@ -363,10 +363,20 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib [MethodImpl(InliningOptions.ShortMethod)] public static short BitReverse(int toReverse) { - DebugGuard.MustBeLessThan(toReverse & 0xF, Bit4Reverse.Length, nameof(toReverse)); - DebugGuard.MustBeLessThan((toReverse >> 4) & 0xF, Bit4Reverse.Length, nameof(toReverse)); - DebugGuard.MustBeLessThan((toReverse >> 8) & 0xF, Bit4Reverse.Length, nameof(toReverse)); - DebugGuard.MustBeLessThan(toReverse >> 12, Bit4Reverse.Length, nameof(toReverse)); + /* Use unsafe offsetting and manually validate the input index to reduce the + * total number of conditional branches. There are two main cases to test here: + * 1. In the first 3, the input value (or some combination of it) is combined + * with & 0xF, which results in a maximum value of 0xF no matter what the + * input value was. That is 15, which is always in range for the target span. + * As a result, no input validation is needed at all in this case. + * 2. There are two cases where the input value might cause an invalid access: + * when it is either negative, or greater than 15 << 12. We can test both + * conditions in a single pass by casting the input value to uint, and checking + * whether that value is lower than 15 << 12. If it was a negative value (2-complement), + * the test will fail as the uint cast will result in a much larger value. + * If the value was simply too high, the test will fail as expected. + * Doing this reduces the total number of index checks from 4 down to just 1. */ + Guard.MustBeLessThanOrEqualTo((uint)toReverse, 15 << 12, nameof(toReverse)); ref byte bit4ReverseRef = ref MemoryMarshal.GetReference(Bit4Reverse); From e8807771c8ae0dbaa325037bc8c3da6c7ae33ff5 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Fri, 28 Feb 2020 15:45:16 +1100 Subject: [PATCH 39/64] Simplify API --- src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs | 2 +- src/ImageSharp/Formats/Png/PngEncoderCore.cs | 4 +- .../Processors/Dithering/ErrorDither.cs | 30 ++++--- .../Processors/Dithering/IDither.cs | 21 ++--- .../IPaletteDitherImageProcessor{TPixel}.cs | 39 ++++++++++ .../Processors/Dithering/OrderedDither.cs | 77 ++++++++---------- .../PaletteDitherProcessor{TPixel}.cs | 78 ++++++++++++------- .../Quantization/EuclideanPixelMap{TPixel}.cs | 7 +- .../Quantization/FrameQuantizerExtensions.cs | 34 ++++---- .../Quantization/IFrameQuantizer{TPixel}.cs | 6 +- .../Quantization/QuantizedFrame{TPixel}.cs | 11 +-- 11 files changed, 170 insertions(+), 139 deletions(-) create mode 100644 src/ImageSharp/Processing/Processors/Dithering/IPaletteDitherImageProcessor{TPixel}.cs diff --git a/src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs b/src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs index 1b3e0228a..1b7d2e5be 100644 --- a/src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs +++ b/src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs @@ -360,7 +360,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp for (int y = image.Height - 1; y >= 0; y--) { - ReadOnlySpan pixelSpan = quantized.GetRowSpan(y); + ReadOnlySpan pixelSpan = quantized.GetPixelRowSpan(y); stream.Write(pixelSpan); for (int i = 0; i < this.padding; i++) diff --git a/src/ImageSharp/Formats/Png/PngEncoderCore.cs b/src/ImageSharp/Formats/Png/PngEncoderCore.cs index 5f14d483b..f8226fc3f 100644 --- a/src/ImageSharp/Formats/Png/PngEncoderCore.cs +++ b/src/ImageSharp/Formats/Png/PngEncoderCore.cs @@ -380,7 +380,7 @@ namespace SixLabors.ImageSharp.Formats.Png if (this.bitDepth < 8) { - PngEncoderHelpers.ScaleDownFrom8BitArray(quantized.GetRowSpan(row), this.currentScanline.GetSpan(), this.bitDepth); + PngEncoderHelpers.ScaleDownFrom8BitArray(quantized.GetPixelRowSpan(row), this.currentScanline.GetSpan(), this.bitDepth); } else { @@ -987,7 +987,7 @@ namespace SixLabors.ImageSharp.Formats.Png row += Adam7.RowIncrement[pass]) { // collect data - ReadOnlySpan srcRow = quantized.GetRowSpan(row); + ReadOnlySpan srcRow = quantized.GetPixelRowSpan(row); for (int col = startCol, i = 0; col < width; col += Adam7.ColumnIncrement[pass]) diff --git a/src/ImageSharp/Processing/Processors/Dithering/ErrorDither.cs b/src/ImageSharp/Processing/Processors/Dithering/ErrorDither.cs index 079a22ecc..2d7e138f4 100644 --- a/src/ImageSharp/Processing/Processors/Dithering/ErrorDither.cs +++ b/src/ImageSharp/Processing/Processors/Dithering/ErrorDither.cs @@ -89,29 +89,26 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering [MethodImpl(InliningOptions.ShortMethod)] public void ApplyQuantizationDither( ref TFrameQuantizer quantizer, - ReadOnlyMemory palette, ImageFrame source, - Memory output, + QuantizedFrame destination, Rectangle bounds) where TFrameQuantizer : struct, IFrameQuantizer where TPixel : struct, IPixel { - Span outputSpan = output.Span; - ReadOnlySpan paletteSpan = palette.Span; - int width = bounds.Width; + ReadOnlySpan paletteSpan = destination.Palette.Span; int offsetY = bounds.Top; int offsetX = bounds.Left; float scale = quantizer.Options.DitherScale; for (int y = bounds.Top; y < bounds.Bottom; y++) { - Span row = source.GetPixelRowSpan(y); - int rowStart = (y - offsetY) * width; + Span sourceRow = source.GetPixelRowSpan(y); + Span destinationRow = destination.GetPixelRowSpan(y - offsetY); for (int x = bounds.Left; x < bounds.Right; x++) { - TPixel sourcePixel = row[x]; - outputSpan[rowStart + x - offsetX] = quantizer.GetQuantizedColor(sourcePixel, paletteSpan, out TPixel transformed); + TPixel sourcePixel = sourceRow[x]; + destinationRow[x - offsetX] = quantizer.GetQuantizedColor(sourcePixel, paletteSpan, out TPixel transformed); this.Dither(source, bounds, sourcePixel, transformed, x, y, scale); } } @@ -119,23 +116,22 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering /// [MethodImpl(InliningOptions.ShortMethod)] - public void ApplyPaletteDither( - Configuration configuration, - ReadOnlyMemory palette, + public void ApplyPaletteDither( + in TPaletteDitherImageProcessor processor, ImageFrame source, - Rectangle bounds, - float scale) + Rectangle bounds) + where TPaletteDitherImageProcessor : struct, IPaletteDitherImageProcessor where TPixel : struct, IPixel { - var pixelMap = new EuclideanPixelMap(palette); - + float scale = processor.DitherScale; + ReadOnlySpan palette = processor.Palette.Span; for (int y = bounds.Top; y < bounds.Bottom; y++) { Span row = source.GetPixelRowSpan(y); for (int x = bounds.Left; x < bounds.Right; x++) { TPixel sourcePixel = row[x]; - pixelMap.GetClosestColor(sourcePixel, out TPixel transformed); + TPixel transformed = processor.GetPaletteColor(sourcePixel, palette); this.Dither(source, bounds, sourcePixel, transformed, x, y, scale); row[x] = transformed; } diff --git a/src/ImageSharp/Processing/Processors/Dithering/IDither.cs b/src/ImageSharp/Processing/Processors/Dithering/IDither.cs index fc8ee32f7..4f2b93efb 100644 --- a/src/ImageSharp/Processing/Processors/Dithering/IDither.cs +++ b/src/ImageSharp/Processing/Processors/Dithering/IDither.cs @@ -1,7 +1,6 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -using System; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing.Processors.Quantization; @@ -19,15 +18,13 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering /// The type of frame quantizer. /// The pixel format. /// The frame quantizer. - /// The quantized palette. /// The source image. - /// The output target + /// The destination quantized frame. /// The region of interest bounds. void ApplyQuantizationDither( ref TFrameQuantizer quantizer, - ReadOnlyMemory palette, ImageFrame source, - Memory output, + QuantizedFrame destination, Rectangle bounds) where TFrameQuantizer : struct, IFrameQuantizer where TPixel : struct, IPixel; @@ -36,18 +33,16 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering /// Transforms the image frame applying a dither matrix. /// This method should be treated as destructive, altering the input pixels. /// + /// The type of palette dithering processor. /// The pixel format. - /// The configuration. - /// The quantized palette. + /// The palette dithering processor. /// The source image. /// The region of interest bounds. - /// The dithering scale used to adjust the amount of dither. Range 0..1. - void ApplyPaletteDither( - Configuration configuration, - ReadOnlyMemory palette, + void ApplyPaletteDither( + in TPaletteDitherImageProcessor processor, ImageFrame source, - Rectangle bounds, - float scale) + Rectangle bounds) + where TPaletteDitherImageProcessor : struct, IPaletteDitherImageProcessor where TPixel : struct, IPixel; } } diff --git a/src/ImageSharp/Processing/Processors/Dithering/IPaletteDitherImageProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Dithering/IPaletteDitherImageProcessor{TPixel}.cs new file mode 100644 index 000000000..73a6c8f4f --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Dithering/IPaletteDitherImageProcessor{TPixel}.cs @@ -0,0 +1,39 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using SixLabors.ImageSharp.PixelFormats; + +namespace SixLabors.ImageSharp.Processing.Processors.Dithering +{ + /// + /// Implements an algorithm to alter the pixels of an image via palette dithering. + /// + /// The pixel format. + public interface IPaletteDitherImageProcessor + where TPixel : struct, IPixel + { + /// + /// Gets the configration instance to use when performing operations. + /// + public Configuration Configuration { get; } + + /// + /// Gets the dithering palette. + /// + ReadOnlyMemory Palette { get; } + + /// + /// Gets the dithering scale used to adjust the amount of dither. Range 0..1. + /// + float DitherScale { get; } + + /// + /// Returns the color from the dithering palette corresponding to the given color. + /// + /// The color to match. + /// The output color palette. + /// The match. + TPixel GetPaletteColor(TPixel color, ReadOnlySpan palette); + } +} diff --git a/src/ImageSharp/Processing/Processors/Dithering/OrderedDither.cs b/src/ImageSharp/Processing/Processors/Dithering/OrderedDither.cs index 69e323bd5..854898e62 100644 --- a/src/ImageSharp/Processing/Processors/Dithering/OrderedDither.cs +++ b/src/ImageSharp/Processing/Processors/Dithering/OrderedDither.cs @@ -105,9 +105,8 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering [MethodImpl(InliningOptions.ShortMethod)] public void ApplyQuantizationDither( ref TFrameQuantizer quantizer, - ReadOnlyMemory palette, ImageFrame source, - Memory output, + QuantizedFrame destination, Rectangle bounds) where TFrameQuantizer : struct, IFrameQuantizer where TPixel : struct, IPixel @@ -116,10 +115,8 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering ref quantizer, in Unsafe.AsRef(this), source, - output, - bounds, - palette, - ImageMaths.GetBitsNeededForColorDepth(palette.Span.Length)); + destination, + bounds); ParallelRowIterator.IterateRows( quantizer.Configuration, @@ -129,24 +126,21 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering /// [MethodImpl(InliningOptions.ShortMethod)] - public void ApplyPaletteDither( - Configuration configuration, - ReadOnlyMemory palette, + public void ApplyPaletteDither( + in TPaletteDitherImageProcessor processor, ImageFrame source, - Rectangle bounds, - float scale) + Rectangle bounds) + where TPaletteDitherImageProcessor : struct, IPaletteDitherImageProcessor where TPixel : struct, IPixel { - var ditherOperation = new PaletteDitherRowIntervalOperation( + var ditherOperation = new PaletteDitherRowIntervalOperation( + in processor, in Unsafe.AsRef(this), source, - bounds, - palette, - scale, - ImageMaths.GetBitsNeededForColorDepth(palette.Span.Length)); + bounds); ParallelRowIterator.IterateRows( - configuration, + processor.Configuration, bounds, in ditherOperation); } @@ -207,7 +201,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering private readonly TFrameQuantizer quantizer; private readonly OrderedDither dither; private readonly ImageFrame source; - private readonly Memory output; + private readonly QuantizedFrame destination; private readonly Rectangle bounds; private readonly ReadOnlyMemory palette; private readonly int bitDepth; @@ -217,84 +211,79 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering ref TFrameQuantizer quantizer, in OrderedDither dither, ImageFrame source, - Memory output, - Rectangle bounds, - ReadOnlyMemory palette, - int bitDepth) + QuantizedFrame destination, + Rectangle bounds) { this.quantizer = quantizer; this.dither = dither; this.source = source; - this.output = output; + this.destination = destination; this.bounds = bounds; - this.palette = palette; - this.bitDepth = bitDepth; + this.palette = destination.Palette; + this.bitDepth = ImageMaths.GetBitsNeededForColorDepth(destination.Palette.Span.Length); } [MethodImpl(InliningOptions.ShortMethod)] public void Invoke(in RowInterval rows) { ReadOnlySpan paletteSpan = this.palette.Span; - Span outputSpan = this.output.Span; - int width = this.bounds.Width; int offsetY = this.bounds.Top; int offsetX = this.bounds.Left; float scale = this.quantizer.Options.DitherScale; for (int y = rows.Min; y < rows.Max; y++) { - Span row = this.source.GetPixelRowSpan(y); - int rowStart = (y - offsetY) * width; + Span sourceRow = this.source.GetPixelRowSpan(y); + Span destinationRow = this.destination.GetPixelRowSpan(y - offsetY); - // TODO: This can be a bulk operation. for (int x = this.bounds.Left; x < this.bounds.Right; x++) { - TPixel dithered = this.dither.Dither(row[x], x, y, this.bitDepth, scale); - outputSpan[rowStart + x - offsetX] = this.quantizer.GetQuantizedColor(dithered, paletteSpan, out TPixel _); + TPixel dithered = this.dither.Dither(sourceRow[x], x, y, this.bitDepth, scale); + destinationRow[x - offsetX] = this.quantizer.GetQuantizedColor(dithered, paletteSpan, out TPixel _); } } } } - private readonly struct PaletteDitherRowIntervalOperation : IRowIntervalOperation + private readonly struct PaletteDitherRowIntervalOperation : IRowIntervalOperation + where TPaletteDitherImageProcessor : struct, IPaletteDitherImageProcessor where TPixel : struct, IPixel { + private readonly TPaletteDitherImageProcessor processor; private readonly OrderedDither dither; private readonly ImageFrame source; private readonly Rectangle bounds; - private readonly EuclideanPixelMap pixelMap; private readonly float scale; private readonly int bitDepth; [MethodImpl(InliningOptions.ShortMethod)] public PaletteDitherRowIntervalOperation( + in TPaletteDitherImageProcessor processor, in OrderedDither dither, ImageFrame source, - Rectangle bounds, - ReadOnlyMemory palette, - float scale, - int bitDepth) + Rectangle bounds) { + this.processor = processor; this.dither = dither; this.source = source; this.bounds = bounds; - this.pixelMap = new EuclideanPixelMap(palette); - this.scale = scale; - this.bitDepth = bitDepth; + this.scale = processor.DitherScale; + this.bitDepth = ImageMaths.GetBitsNeededForColorDepth(processor.Palette.Span.Length); } [MethodImpl(InliningOptions.ShortMethod)] public void Invoke(in RowInterval rows) { + ReadOnlySpan paletteSpan = this.processor.Palette.Span; for (int y = rows.Min; y < rows.Max; y++) { Span row = this.source.GetPixelRowSpan(y); for (int x = this.bounds.Left; x < this.bounds.Right; x++) { - TPixel dithered = this.dither.Dither(row[x], x, y, this.bitDepth, this.scale); - this.pixelMap.GetClosestColor(dithered, out TPixel transformed); - row[x] = transformed; + ref TPixel sourcePixel = ref row[x]; + TPixel dithered = this.dither.Dither(sourcePixel, x, y, this.bitDepth, this.scale); + sourcePixel = this.processor.GetPaletteColor(dithered, paletteSpan); } } } diff --git a/src/ImageSharp/Processing/Processors/Dithering/PaletteDitherProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Dithering/PaletteDitherProcessor{TPixel}.cs index 118352ec3..04351d34f 100644 --- a/src/ImageSharp/Processing/Processors/Dithering/PaletteDitherProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Dithering/PaletteDitherProcessor{TPixel}.cs @@ -3,7 +3,9 @@ using System; using System.Buffers; +using System.Runtime.CompilerServices; using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing.Processors.Quantization; namespace SixLabors.ImageSharp.Processing.Processors.Dithering { @@ -14,11 +16,9 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering internal sealed class PaletteDitherProcessor : ImageProcessor where TPixel : struct, IPixel { - private readonly int paletteLength; + private readonly DitherProcessor ditherProcessor; private readonly IDither dither; - private readonly float ditherScale; - private readonly ReadOnlyMemory sourcePalette; - private IMemoryOwner palette; + private IMemoryOwner paletteMemory; private bool isDisposed; /// @@ -31,37 +31,23 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering public PaletteDitherProcessor(Configuration configuration, PaletteDitherProcessor definition, Image source, Rectangle sourceRectangle) : base(configuration, source, sourceRectangle) { - this.paletteLength = definition.Palette.Span.Length; this.dither = definition.Dither; - this.ditherScale = definition.DitherScale; - this.sourcePalette = definition.Palette; - } - /// - protected override void OnFrameApply(ImageFrame source) - { - var interest = Rectangle.Intersect(this.SourceRectangle, source.Bounds()); + ReadOnlySpan sourcePalette = definition.Palette.Span; + this.paletteMemory = this.Configuration.MemoryAllocator.Allocate(sourcePalette.Length); + Color.ToPixel(this.Configuration, sourcePalette, this.paletteMemory.Memory.Span); - this.dither.ApplyPaletteDither( + this.ditherProcessor = new DitherProcessor( this.Configuration, - this.palette.Memory, - source, - interest, - this.ditherScale); + this.paletteMemory.Memory, + definition.DitherScale); } /// - protected override void BeforeFrameApply(ImageFrame source) + protected override void OnFrameApply(ImageFrame source) { - // Lazy init palettes: - if (this.palette is null) - { - this.palette = this.Configuration.MemoryAllocator.Allocate(this.paletteLength); - ReadOnlySpan sourcePalette = this.sourcePalette.Span; - Color.ToPixel(this.Configuration, sourcePalette, this.palette.Memory.Span); - } - - base.BeforeFrameApply(source); + var interest = Rectangle.Intersect(this.SourceRectangle, source.Bounds()); + this.dither.ApplyPaletteDither(in this.ditherProcessor, source, interest); } /// @@ -74,13 +60,47 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering if (disposing) { - this.palette?.Dispose(); + this.paletteMemory?.Dispose(); } - this.palette = null; + this.paletteMemory = null; this.isDisposed = true; base.Dispose(disposing); } + + /// + /// Used to allow inlining of calls to + /// . + /// + private readonly struct DitherProcessor : IPaletteDitherImageProcessor + { + private readonly EuclideanPixelMap pixelMap; + + [MethodImpl(InliningOptions.ShortMethod)] + public DitherProcessor( + Configuration configuration, + ReadOnlyMemory palette, + float ditherScale) + { + this.Configuration = configuration; + this.pixelMap = new EuclideanPixelMap(palette); + this.Palette = palette; + this.DitherScale = ditherScale; + } + + public Configuration Configuration { get; } + + public ReadOnlyMemory Palette { get; } + + public float DitherScale { get; } + + [MethodImpl(InliningOptions.ShortMethod)] + public TPixel GetPaletteColor(TPixel color, ReadOnlySpan palette) + { + this.pixelMap.GetClosestColor(color, out TPixel match); + return match; + } + } } } diff --git a/src/ImageSharp/Processing/Processors/Quantization/EuclideanPixelMap{TPixel}.cs b/src/ImageSharp/Processing/Processors/Quantization/EuclideanPixelMap{TPixel}.cs index a5e8d70b0..720923a16 100644 --- a/src/ImageSharp/Processing/Processors/Quantization/EuclideanPixelMap{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Quantization/EuclideanPixelMap{TPixel}.cs @@ -24,6 +24,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization /// Initializes a new instance of the struct. /// /// The color palette to map from. + [MethodImpl(InliningOptions.ShortMethod)] public EuclideanPixelMap(ReadOnlyMemory palette) { Guard.MustBeGreaterThan(palette.Length, 0, nameof(palette)); @@ -40,7 +41,11 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization } /// - public ReadOnlyMemory Palette { get; } + public ReadOnlyMemory Palette + { + [MethodImpl(InliningOptions.ShortMethod)] + get; + } /// public override bool Equals(object obj) diff --git a/src/ImageSharp/Processing/Processors/Quantization/FrameQuantizerExtensions.cs b/src/ImageSharp/Processing/Processors/Quantization/FrameQuantizerExtensions.cs index 5b49fe9e8..7bf7e9b35 100644 --- a/src/ImageSharp/Processing/Processors/Quantization/FrameQuantizerExtensions.cs +++ b/src/ImageSharp/Processing/Processors/Quantization/FrameQuantizerExtensions.cs @@ -41,18 +41,17 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization MemoryAllocator memoryAllocator = quantizer.Configuration.MemoryAllocator; var quantizedFrame = new QuantizedFrame(memoryAllocator, interest.Width, interest.Height, palette); - Memory output = quantizedFrame.GetWritablePixelMemory(); if (quantizer.Options.Dither is null) { - SecondPass(ref quantizer, source, interest, output, palette); + SecondPass(ref quantizer, source, quantizedFrame, interest); } else { // We clone the image as we don't want to alter the original via error diffusion based dithering. using (ImageFrame clone = source.Clone()) { - SecondPass(ref quantizer, clone, interest, output, palette); + SecondPass(ref quantizer, clone, quantizedFrame, interest); } } @@ -63,9 +62,8 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization private static void SecondPass( ref TFrameQuantizer quantizer, ImageFrame source, - Rectangle bounds, - Memory output, - ReadOnlyMemory palette) + QuantizedFrame destination, + Rectangle bounds) where TFrameQuantizer : struct, IFrameQuantizer where TPixel : struct, IPixel { @@ -73,7 +71,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization if (dither is null) { - var operation = new RowIntervalOperation(quantizer, source, output, bounds, palette); + var operation = new RowIntervalOperation(quantizer, source, destination, bounds); ParallelRowIterator.IterateRows( quantizer.Configuration, bounds, @@ -82,7 +80,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization return; } - dither.ApplyQuantizationDither(ref quantizer, palette, source, output, bounds); + dither.ApplyQuantizationDither(ref quantizer, source, destination, bounds); } private readonly struct RowIntervalOperation : IRowIntervalOperation @@ -91,7 +89,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization { private readonly TFrameQuantizer quantizer; private readonly ImageFrame source; - private readonly Memory output; + private readonly QuantizedFrame destination; private readonly Rectangle bounds; private readonly ReadOnlyMemory palette; @@ -99,35 +97,31 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization public RowIntervalOperation( in TFrameQuantizer quantizer, ImageFrame source, - Memory output, - Rectangle bounds, - ReadOnlyMemory palette) + QuantizedFrame destination, + Rectangle bounds) { this.quantizer = quantizer; this.source = source; - this.output = output; + this.destination = destination; this.bounds = bounds; - this.palette = palette; + this.palette = destination.Palette; } [MethodImpl(InliningOptions.ShortMethod)] public void Invoke(in RowInterval rows) { ReadOnlySpan paletteSpan = this.palette.Span; - Span outputSpan = this.output.Span; - int width = this.bounds.Width; int offsetY = this.bounds.Top; int offsetX = this.bounds.Left; for (int y = rows.Min; y < rows.Max; y++) { - Span row = this.source.GetPixelRowSpan(y); - int rowStart = (y - offsetY) * width; + Span sourceRow = this.source.GetPixelRowSpan(y); + Span destinationRow = this.destination.GetPixelRowSpan(y - offsetY); - // TODO: This can be a bulk operation. for (int x = this.bounds.Left; x < this.bounds.Right; x++) { - outputSpan[rowStart + x - offsetX] = this.quantizer.GetQuantizedColor(row[x], paletteSpan, out TPixel _); + destinationRow[x - offsetX] = this.quantizer.GetQuantizedColor(sourceRow[x], paletteSpan, out TPixel _); } } } diff --git a/src/ImageSharp/Processing/Processors/Quantization/IFrameQuantizer{TPixel}.cs b/src/ImageSharp/Processing/Processors/Quantization/IFrameQuantizer{TPixel}.cs index d3091c3b0..fdf4ab7a6 100644 --- a/src/ImageSharp/Processing/Processors/Quantization/IFrameQuantizer{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Quantization/IFrameQuantizer{TPixel}.cs @@ -31,9 +31,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization /// /// A representing a quantized version of the source frame pixels. /// - QuantizedFrame QuantizeFrame( - ImageFrame source, - Rectangle bounds); + QuantizedFrame QuantizeFrame(ImageFrame source, Rectangle bounds); /// /// Builds the quantized palette from the given image frame and bounds. @@ -44,7 +42,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization ReadOnlyMemory BuildPalette(ImageFrame source, Rectangle bounds); /// - /// Returns the index and color from the quantized palette corresponding to the give to the given color. + /// Returns the index and color from the quantized palette corresponding to the given color. /// /// The color to match. /// The output color palette. diff --git a/src/ImageSharp/Processing/Processors/Quantization/QuantizedFrame{TPixel}.cs b/src/ImageSharp/Processing/Processors/Quantization/QuantizedFrame{TPixel}.cs index fccc799bb..93569a6ce 100644 --- a/src/ImageSharp/Processing/Processors/Quantization/QuantizedFrame{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Quantization/QuantizedFrame{TPixel}.cs @@ -57,16 +57,16 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization /// /// The [MethodImpl(InliningOptions.ShortMethod)] - public ReadOnlySpan GetPixelSpan() => this.pixels.GetSpan(); + public Span GetPixelSpan() => this.pixels.GetSpan(); /// /// Gets the representation of the pixels as a of contiguous memory /// at row beginning from the the first pixel on that row. /// /// The row. - /// The pixel row as a . + /// The pixel row as a . [MethodImpl(InliningOptions.ShortMethod)] - public ReadOnlySpan GetRowSpan(int rowIndex) + public Span GetPixelRowSpan(int rowIndex) => this.GetPixelSpan().Slice(rowIndex * this.Width, this.Width); /// @@ -82,10 +82,5 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization this.pixels = null; this.Palette = null; } - - /// - /// Get the non-readonly memory of pixel data so can fill it. - /// - internal Memory GetWritablePixelMemory() => this.pixels.Memory; } } From 5e34a3642f83849d1b9ce889ce1450cb83c872da Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Fri, 28 Feb 2020 16:00:11 +1100 Subject: [PATCH 40/64] Update IPaletteDitherImageProcessor{TPixel}.cs --- .../Dithering/IPaletteDitherImageProcessor{TPixel}.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ImageSharp/Processing/Processors/Dithering/IPaletteDitherImageProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Dithering/IPaletteDitherImageProcessor{TPixel}.cs index 73a6c8f4f..3e4bf4d83 100644 --- a/src/ImageSharp/Processing/Processors/Dithering/IPaletteDitherImageProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Dithering/IPaletteDitherImageProcessor{TPixel}.cs @@ -11,7 +11,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering /// /// The pixel format. public interface IPaletteDitherImageProcessor - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { /// /// Gets the configration instance to use when performing operations. From 7583a15197ae22cd0bdf22822b54ef340851378e Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Fri, 28 Feb 2020 12:55:08 +0100 Subject: [PATCH 41/64] Fixed a test in the DeflaterHuffman type --- src/ImageSharp/Formats/Png/Zlib/DeflaterHuffman.cs | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/ImageSharp/Formats/Png/Zlib/DeflaterHuffman.cs b/src/ImageSharp/Formats/Png/Zlib/DeflaterHuffman.cs index 169215be2..2c470054a 100644 --- a/src/ImageSharp/Formats/Png/Zlib/DeflaterHuffman.cs +++ b/src/ImageSharp/Formats/Png/Zlib/DeflaterHuffman.cs @@ -371,12 +371,14 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib * As a result, no input validation is needed at all in this case. * 2. There are two cases where the input value might cause an invalid access: * when it is either negative, or greater than 15 << 12. We can test both - * conditions in a single pass by casting the input value to uint, and checking - * whether that value is lower than 15 << 12. If it was a negative value (2-complement), - * the test will fail as the uint cast will result in a much larger value. - * If the value was simply too high, the test will fail as expected. + * conditions in a single pass by casting the input value to uint and right + * shifting it by 12, which also preserves the sign. If it is a negative + * value (2-complement), the test will fail as the uint cast will result + * in a much larger value. If the value was simply too high, the test will + * fail as expected. We can't simply check whether the value is lower than + * 15 << 12, because higher values are acceptable in the first 3 accesses. * Doing this reduces the total number of index checks from 4 down to just 1. */ - Guard.MustBeLessThanOrEqualTo((uint)toReverse, 15 << 12, nameof(toReverse)); + Guard.MustBeLessThanOrEqualTo((uint)(toReverse >> 12), 15, nameof(toReverse)); ref byte bit4ReverseRef = ref MemoryMarshal.GetReference(Bit4Reverse); From 5ef91f62d15c0b4c72b3bb32ebf8e4b0f990b0cd Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Fri, 28 Feb 2020 13:03:22 +0100 Subject: [PATCH 42/64] Refactored DeflaterHuffman.BitLengthOrder to ReadOnlySpan --- .../Formats/Png/Zlib/DeflaterHuffman.cs | 22 ++++++++++++++----- 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/src/ImageSharp/Formats/Png/Zlib/DeflaterHuffman.cs b/src/ImageSharp/Formats/Png/Zlib/DeflaterHuffman.cs index 2c470054a..250e7b329 100644 --- a/src/ImageSharp/Formats/Png/Zlib/DeflaterHuffman.cs +++ b/src/ImageSharp/Formats/Png/Zlib/DeflaterHuffman.cs @@ -36,10 +36,6 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib private const int EofSymbol = 256; - // The lengths of the bit length codes are sent in order of decreasing - // probability, to avoid transmitting the lengths for unused bit length codes. - private static readonly int[] BitLengthOrder = { 16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15 }; - private static readonly short[] StaticLCodes; private static readonly byte[] StaticLLength; private static readonly short[] StaticDCodes; @@ -126,6 +122,11 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib this.pinnedLiteralBuffer = (short*)this.literalBufferHandle.Pointer; } + /// + /// Gets the lengths of the bit length codes are sent in order of decreasing probability, to avoid transmitting the lengths for unused bit length codes. + /// + private static ReadOnlySpan BitLengthOrder => new byte[] { 16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15 }; + private static ReadOnlySpan Bit4Reverse => new byte[] { 0, 8, 4, 12, 2, 10, 6, 14, 1, 9, 5, 13, 3, 11, 7, 15 }; /// @@ -158,9 +159,14 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib this.Pending.WriteBits(this.literalTree.NumCodes - 257, 5); this.Pending.WriteBits(this.distTree.NumCodes - 1, 5); this.Pending.WriteBits(blTreeCodes - 4, 4); + + ref byte bitLengthOrderRef = ref MemoryMarshal.GetReference(BitLengthOrder); + for (int rank = 0; rank < blTreeCodes; rank++) { - this.Pending.WriteBits(this.blTree.Length[BitLengthOrder[rank]], 3); + ref byte bitsRef = ref Unsafe.Add(ref bitLengthOrderRef, rank); + + this.Pending.WriteBits(this.blTree.Length[bitsRef], 3); } this.literalTree.WriteTree(this.Pending, this.blTree); @@ -249,10 +255,14 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib // Build bitlen tree this.blTree.BuildTree(); + ref byte bitLengthOrderRef = ref MemoryMarshal.GetReference(BitLengthOrder); int blTreeCodes = 4; + for (int i = 18; i > blTreeCodes; i--) { - if (this.blTree.Length[BitLengthOrder[i]] > 0) + ref byte bits = ref Unsafe.Add(ref bitLengthOrderRef, i); + + if (this.blTree.Length[bits] > 0) { blTreeCodes = i + 1; } From 4b49864981460368243399a84eff0f440b3785cf Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Fri, 28 Feb 2020 16:25:55 +0100 Subject: [PATCH 43/64] Reintroduced some bounds checks for additional security --- src/ImageSharp/Formats/Png/Zlib/DeflaterHuffman.cs | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/src/ImageSharp/Formats/Png/Zlib/DeflaterHuffman.cs b/src/ImageSharp/Formats/Png/Zlib/DeflaterHuffman.cs index 250e7b329..35bacb849 100644 --- a/src/ImageSharp/Formats/Png/Zlib/DeflaterHuffman.cs +++ b/src/ImageSharp/Formats/Png/Zlib/DeflaterHuffman.cs @@ -160,13 +160,9 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib this.Pending.WriteBits(this.distTree.NumCodes - 1, 5); this.Pending.WriteBits(blTreeCodes - 4, 4); - ref byte bitLengthOrderRef = ref MemoryMarshal.GetReference(BitLengthOrder); - for (int rank = 0; rank < blTreeCodes; rank++) { - ref byte bitsRef = ref Unsafe.Add(ref bitLengthOrderRef, rank); - - this.Pending.WriteBits(this.blTree.Length[bitsRef], 3); + this.Pending.WriteBits(this.blTree.Length[BitLengthOrder[rank]], 3); } this.literalTree.WriteTree(this.Pending, this.blTree); @@ -255,14 +251,11 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib // Build bitlen tree this.blTree.BuildTree(); - ref byte bitLengthOrderRef = ref MemoryMarshal.GetReference(BitLengthOrder); int blTreeCodes = 4; for (int i = 18; i > blTreeCodes; i--) { - ref byte bits = ref Unsafe.Add(ref bitLengthOrderRef, i); - - if (this.blTree.Length[bits] > 0) + if (this.blTree.Length[BitLengthOrder[i]] > 0) { blTreeCodes = i + 1; } From 7724ebc140121f38037ba8b6d4027a534a06a272 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Fri, 28 Feb 2020 16:30:24 +0100 Subject: [PATCH 44/64] Minor micro-optimization in DeflaterHuffman type --- src/ImageSharp/Formats/Png/Zlib/DeflaterHuffman.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/ImageSharp/Formats/Png/Zlib/DeflaterHuffman.cs b/src/ImageSharp/Formats/Png/Zlib/DeflaterHuffman.cs index 35bacb849..543a1fe30 100644 --- a/src/ImageSharp/Formats/Png/Zlib/DeflaterHuffman.cs +++ b/src/ImageSharp/Formats/Png/Zlib/DeflaterHuffman.cs @@ -381,14 +381,15 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib * fail as expected. We can't simply check whether the value is lower than * 15 << 12, because higher values are acceptable in the first 3 accesses. * Doing this reduces the total number of index checks from 4 down to just 1. */ - Guard.MustBeLessThanOrEqualTo((uint)(toReverse >> 12), 15, nameof(toReverse)); + int toReverseRightShiftBy12 = toReverse >> 12; + Guard.MustBeLessThanOrEqualTo((uint)toReverseRightShiftBy12, 15, nameof(toReverse)); ref byte bit4ReverseRef = ref MemoryMarshal.GetReference(Bit4Reverse); return (short)(Unsafe.Add(ref bit4ReverseRef, toReverse & 0xF) << 12 | Unsafe.Add(ref bit4ReverseRef, (toReverse >> 4) & 0xF) << 8 | Unsafe.Add(ref bit4ReverseRef, (toReverse >> 8) & 0xF) << 4 - | Unsafe.Add(ref bit4ReverseRef, toReverse >> 12)); + | Unsafe.Add(ref bit4ReverseRef, toReverseRightShiftBy12)); } /// From 49103ced46722a594007bd70e90203fee95b7d49 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Sat, 29 Feb 2020 01:07:03 +0100 Subject: [PATCH 45/64] implicit & safer execution of PrepareRemoteExecutor --- .../Formats/Jpg/JpegDecoderTests.cs | 5 --- .../ArrayPoolMemoryAllocatorTests.cs | 5 --- .../Processors/Convolution/BokehBlurTest.cs | 5 --- .../Attributes/ImageDataAttributeBase.cs | 7 ++++ .../TestUtilities/TestEnvironment.cs | 32 ++++++++++++++++--- 5 files changed, 35 insertions(+), 19 deletions(-) diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs index 103c9d077..25cf5dd37 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs @@ -27,11 +27,6 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg private const float ProgressiveTolerance = 0.2F / 100; - static JpegDecoderTests() - { - TestEnvironment.PrepareRemoteExecutor(); - } - private static ImageComparer GetImageComparer(TestImageProvider provider) where TPixel : unmanaged, IPixel { diff --git a/tests/ImageSharp.Tests/Memory/Allocators/ArrayPoolMemoryAllocatorTests.cs b/tests/ImageSharp.Tests/Memory/Allocators/ArrayPoolMemoryAllocatorTests.cs index 1e079fcf5..8db79fca0 100644 --- a/tests/ImageSharp.Tests/Memory/Allocators/ArrayPoolMemoryAllocatorTests.cs +++ b/tests/ImageSharp.Tests/Memory/Allocators/ArrayPoolMemoryAllocatorTests.cs @@ -28,11 +28,6 @@ namespace SixLabors.ImageSharp.Tests.Memory.Allocators /// private static MemoryAllocatorFixture StaticFixture { get; } = new MemoryAllocatorFixture(); - static ArrayPoolMemoryAllocatorTests() - { - TestEnvironment.PrepareRemoteExecutor(); - } - public class BufferTests : BufferTestSuite { public BufferTests() diff --git a/tests/ImageSharp.Tests/Processing/Processors/Convolution/BokehBlurTest.cs b/tests/ImageSharp.Tests/Processing/Processors/Convolution/BokehBlurTest.cs index ebbecab93..9dc135016 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Convolution/BokehBlurTest.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Convolution/BokehBlurTest.cs @@ -19,11 +19,6 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Convolution { public class BokehBlurTest { - static BokehBlurTest() - { - TestEnvironment.PrepareRemoteExecutor(); - } - private static readonly string Components10x2 = @" [[ 0.00451261+0.0165137j 0.02161237-0.00299122j 0.00387479-0.02682816j -0.02752798-0.01788438j -0.03553877+0.0154543j -0.01428268+0.04224722j diff --git a/tests/ImageSharp.Tests/TestUtilities/Attributes/ImageDataAttributeBase.cs b/tests/ImageSharp.Tests/TestUtilities/Attributes/ImageDataAttributeBase.cs index 976bb9a96..85b178c73 100644 --- a/tests/ImageSharp.Tests/TestUtilities/Attributes/ImageDataAttributeBase.cs +++ b/tests/ImageSharp.Tests/TestUtilities/Attributes/ImageDataAttributeBase.cs @@ -18,6 +18,13 @@ namespace SixLabors.ImageSharp.Tests protected readonly PixelTypes PixelTypes; + static ImageDataAttributeBase() + { + // ImageDataAttributes are used in almost all tests, thus a good place to enforce the execution of + // TestEnvironment static constructor before anything else is done. + TestEnvironment.EnsureSharedInitializersDone(); + } + /// /// Initializes a new instance of the class. /// diff --git a/tests/ImageSharp.Tests/TestUtilities/TestEnvironment.cs b/tests/ImageSharp.Tests/TestUtilities/TestEnvironment.cs index ea880cb38..4152d3bc6 100644 --- a/tests/ImageSharp.Tests/TestUtilities/TestEnvironment.cs +++ b/tests/ImageSharp.Tests/TestUtilities/TestEnvironment.cs @@ -34,6 +34,11 @@ namespace SixLabors.ImageSharp.Tests private static readonly Lazy NetCoreVersionLazy = new Lazy(GetNetCoreVersion); + static TestEnvironment() + { + PrepareRemoteExecutor(); + } + /// /// Gets the .NET Core version, if running on .NET Core, otherwise returns an empty string. /// @@ -111,6 +116,13 @@ namespace SixLabors.ImageSharp.Tests internal static bool IsFramework => string.IsNullOrEmpty(NetCoreVersion); + /// + /// A dummy operation to enforce the execution of the static constructor. + /// + internal static void EnsureSharedInitializersDone() + { + } + /// /// Creates the image output directory. /// @@ -141,7 +153,7 @@ namespace SixLabors.ImageSharp.Tests /// When running in 32 bits, enforces 32 bit execution of Microsoft.DotNet.RemoteExecutor.exe /// with the help of CorFlags.exe found in Windows SDK. /// - internal static void PrepareRemoteExecutor() + private static void PrepareRemoteExecutor() { if (!IsFramework) { @@ -153,12 +165,11 @@ namespace SixLabors.ImageSharp.Tests if (File.Exists(remoteExecutorConfigPath)) { - // already prepared + // Already initialized return; } string testProjectConfigPath = TestAssemblyFile.FullName + ".config"; - File.Copy(testProjectConfigPath, remoteExecutorConfigPath); if (Is64BitProcess) @@ -184,7 +195,17 @@ namespace SixLabors.ImageSharp.Tests string remoteExecutorPath = Path.Combine(TestAssemblyFile.DirectoryName, "Microsoft.DotNet.RemoteExecutor.exe"); - string args = $"{remoteExecutorPath} /32Bit+ /Force"; + string remoteExecutorTmpPath = $"{remoteExecutorPath}._tmp"; + + if (File.Exists(remoteExecutorTmpPath)) + { + // Already initialized + return; + } + + File.Copy(remoteExecutorPath, remoteExecutorTmpPath); + + string args = $"{remoteExecutorTmpPath} /32Bit+ /Force"; var si = new ProcessStartInfo() { @@ -206,6 +227,9 @@ namespace SixLabors.ImageSharp.Tests $@"Failed to run {si.FileName} {si.Arguments}:\n STDOUT: {standardOutput}\n STDERR: {standardError}"); } + File.Delete(remoteExecutorPath); + File.Copy(remoteExecutorTmpPath, remoteExecutorPath); + static FileInfo Find(DirectoryInfo root, string name) { FileInfo fi = root.EnumerateFiles(name).FirstOrDefault(); From a0d76247a18984273893e17cfa62e130309d3c4d Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Sun, 1 Mar 2020 00:35:56 +1100 Subject: [PATCH 47/64] Dump progress so far --- src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs | 2 +- src/ImageSharp/Formats/Gif/GifEncoder.cs | 3 +- src/ImageSharp/Formats/Gif/GifEncoderCore.cs | 54 +++++++--------- src/ImageSharp/Formats/Gif/LzwEncoder.cs | 2 +- src/ImageSharp/Formats/Png/PngEncoderCore.cs | 2 +- .../Processors/Dithering/ErrorDither.cs | 2 +- .../Processors/Dithering/OrderedDither.cs | 6 +- .../Quantization/EuclideanPixelMap{TPixel}.cs | 33 +++++----- .../Quantization/FrameQuantizerExtensions.cs | 18 +++--- .../Quantization/IFrameQuantizer{TPixel}.cs | 6 +- .../OctreeFrameQuantizer{TPixel}.cs | 61 ++++++++++--------- .../PaletteFrameQuantizer{TPixel}.cs | 56 ++++++++++++++--- .../Quantization/PaletteQuantizer.cs | 16 ++--- .../Quantization/QuantizeProcessor{TPixel}.cs | 2 +- .../Quantization/QuantizedFrame{TPixel}.cs | 18 ++++-- .../Quantization/WuFrameQuantizer{TPixel}.cs | 56 ++++++----------- .../ImageSharp.Benchmarks/Codecs/EncodeGif.cs | 14 ++--- .../Quantization/QuantizedImageTests.cs | 2 +- .../Quantization/WuQuantizerTests.cs | 8 +-- 19 files changed, 186 insertions(+), 175 deletions(-) diff --git a/src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs b/src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs index 0d25210a9..ed5ed4293 100644 --- a/src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs +++ b/src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs @@ -339,7 +339,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp using IFrameQuantizer quantizer = this.quantizer.CreateFrameQuantizer(this.configuration); using QuantizedFrame quantized = quantizer.QuantizeFrame(image, image.Bounds()); - ReadOnlySpan quantizedColors = quantized.Palette.Span; + ReadOnlySpan quantizedColors = quantized.Palette; var color = default(Rgba32); // TODO: Use bulk conversion here for better perf diff --git a/src/ImageSharp/Formats/Gif/GifEncoder.cs b/src/ImageSharp/Formats/Gif/GifEncoder.cs index 978609d7f..53c4c6f3f 100644 --- a/src/ImageSharp/Formats/Gif/GifEncoder.cs +++ b/src/ImageSharp/Formats/Gif/GifEncoder.cs @@ -4,6 +4,7 @@ using System.IO; using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Processing.Processors.Quantization; namespace SixLabors.ImageSharp.Formats.Gif @@ -17,7 +18,7 @@ namespace SixLabors.ImageSharp.Formats.Gif /// Gets or sets the quantizer for reducing the color count. /// Defaults to the /// - public IQuantizer Quantizer { get; set; } = new OctreeQuantizer(); + public IQuantizer Quantizer { get; set; } = KnownQuantizers.Octree; /// /// Gets or sets the color table mode: Global or local. diff --git a/src/ImageSharp/Formats/Gif/GifEncoderCore.cs b/src/ImageSharp/Formats/Gif/GifEncoderCore.cs index e32910d37..87317a3ef 100644 --- a/src/ImageSharp/Formats/Gif/GifEncoderCore.cs +++ b/src/ImageSharp/Formats/Gif/GifEncoderCore.cs @@ -119,7 +119,7 @@ namespace SixLabors.ImageSharp.Formats.Gif } // Clean up. - quantized?.Dispose(); + quantized.Dispose(); // TODO: Write extension etc stream.WriteByte(GifConstants.EndIntroducer); @@ -142,11 +142,9 @@ namespace SixLabors.ImageSharp.Formats.Gif } else { - using (var paletteFrameQuantizer = new PaletteFrameQuantizer(this.configuration, this.quantizer.Options, quantized.Palette)) - using (QuantizedFrame paletteQuantized = paletteFrameQuantizer.QuantizeFrame(frame, frame.Bounds())) - { - this.WriteImageData(paletteQuantized, stream); - } + using var paletteFrameQuantizer = new PaletteFrameQuantizer(this.configuration, this.quantizer.Options, quantized.Palette); + using QuantizedFrame paletteQuantized = paletteFrameQuantizer.QuantizeFrame(frame, frame.Bounds()); + this.WriteImageData(paletteQuantized, stream); } } } @@ -156,8 +154,9 @@ namespace SixLabors.ImageSharp.Formats.Gif { ImageFrame previousFrame = null; GifFrameMetadata previousMeta = null; - foreach (ImageFrame frame in image.Frames) + for (int i = 0; i < image.Frames.Count; i++) { + ImageFrame frame = image.Frames[i]; ImageFrameMetadata metadata = frame.Metadata; GifFrameMetadata frameMetadata = metadata.GetGifMetadata(); if (quantized is null) @@ -173,17 +172,13 @@ namespace SixLabors.ImageSharp.Formats.Gif MaxColors = frameMetadata.ColorTableLength }; - using (IFrameQuantizer frameQuantizer = this.quantizer.CreateFrameQuantizer(this.configuration, options)) - { - quantized = frameQuantizer.QuantizeFrame(frame, frame.Bounds()); - } + using IFrameQuantizer frameQuantizer = this.quantizer.CreateFrameQuantizer(this.configuration, options); + quantized = frameQuantizer.QuantizeFrame(frame, frame.Bounds()); } else { - using (IFrameQuantizer frameQuantizer = this.quantizer.CreateFrameQuantizer(this.configuration)) - { - quantized = frameQuantizer.QuantizeFrame(frame, frame.Bounds()); - } + using IFrameQuantizer frameQuantizer = this.quantizer.CreateFrameQuantizer(this.configuration); + quantized = frameQuantizer.QuantizeFrame(frame, frame.Bounds()); } } @@ -193,7 +188,7 @@ namespace SixLabors.ImageSharp.Formats.Gif this.WriteColorTable(quantized, stream); this.WriteImageData(quantized, stream); - quantized?.Dispose(); + quantized.Dispose(); quantized = null; // So next frame can regenerate it previousFrame = frame; previousMeta = frameMetadata; @@ -219,7 +214,7 @@ namespace SixLabors.ImageSharp.Formats.Gif { Span rgbaSpan = rgbaBuffer.GetSpan(); ref Rgba32 paletteRef = ref MemoryMarshal.GetReference(rgbaSpan); - PixelOperations.Instance.ToRgba32(this.configuration, quantized.Palette.Span, rgbaSpan); + PixelOperations.Instance.ToRgba32(this.configuration, quantized.Palette, rgbaSpan); for (int i = quantized.Palette.Length - 1; i >= 0; i--) { @@ -391,7 +386,8 @@ namespace SixLabors.ImageSharp.Formats.Gif /// /// The extension to write to the stream. /// The stream to write to. - public void WriteExtension(IGifExtension extension, Stream stream) + private void WriteExtension(TGifExtension extension, Stream stream) + where TGifExtension : struct, IGifExtension { this.buffer[0] = GifConstants.ExtensionIntroducer; this.buffer[1] = extension.Label; @@ -444,15 +440,13 @@ namespace SixLabors.ImageSharp.Formats.Gif int colorTableLength = ImageMaths.GetColorCountForBitDepth(this.bitDepth) * 3; int pixelCount = image.Palette.Length; - using (IManagedByteBuffer colorTable = this.memoryAllocator.AllocateManagedByteBuffer(colorTableLength)) - { - PixelOperations.Instance.ToRgb24Bytes( - this.configuration, - image.Palette.Span, - colorTable.GetSpan(), - pixelCount); - stream.Write(colorTable.Array, 0, colorTableLength); - } + using IManagedByteBuffer colorTable = this.memoryAllocator.AllocateManagedByteBuffer(colorTableLength); + PixelOperations.Instance.ToRgb24Bytes( + this.configuration, + image.Palette, + colorTable.GetSpan(), + pixelCount); + stream.Write(colorTable.Array, 0, colorTableLength); } /// @@ -464,10 +458,8 @@ namespace SixLabors.ImageSharp.Formats.Gif private void WriteImageData(QuantizedFrame image, Stream stream) where TPixel : unmanaged, IPixel { - using (var encoder = new LzwEncoder(this.memoryAllocator, (byte)this.bitDepth)) - { - encoder.Encode(image.GetPixelSpan(), stream); - } + using var encoder = new LzwEncoder(this.memoryAllocator, (byte)this.bitDepth); + encoder.Encode(image.GetPixelSpan(), stream); } } } diff --git a/src/ImageSharp/Formats/Gif/LzwEncoder.cs b/src/ImageSharp/Formats/Gif/LzwEncoder.cs index eda0c5fb8..056076bf0 100644 --- a/src/ImageSharp/Formats/Gif/LzwEncoder.cs +++ b/src/ImageSharp/Formats/Gif/LzwEncoder.cs @@ -274,7 +274,7 @@ namespace SixLabors.ImageSharp.Formats.Gif ent = this.NextPixel(indexedPixels); - // TODO: PERF: It looks likt hshift could be calculated once statically. + // TODO: PERF: It looks like hshift could be calculated once statically. hshift = 0; for (fcode = this.hsize; fcode < 65536; fcode *= 2) { diff --git a/src/ImageSharp/Formats/Png/PngEncoderCore.cs b/src/ImageSharp/Formats/Png/PngEncoderCore.cs index ce624f768..ed2fe143b 100644 --- a/src/ImageSharp/Formats/Png/PngEncoderCore.cs +++ b/src/ImageSharp/Formats/Png/PngEncoderCore.cs @@ -555,7 +555,7 @@ namespace SixLabors.ImageSharp.Formats.Png } // Grab the palette and write it to the stream. - ReadOnlySpan palette = quantized.Palette.Span; + ReadOnlySpan palette = quantized.Palette; int paletteLength = Math.Min(palette.Length, 256); int colorTableLength = paletteLength * 3; bool anyAlpha = false; diff --git a/src/ImageSharp/Processing/Processors/Dithering/ErrorDither.cs b/src/ImageSharp/Processing/Processors/Dithering/ErrorDither.cs index 3d18ef358..9217d1c3f 100644 --- a/src/ImageSharp/Processing/Processors/Dithering/ErrorDither.cs +++ b/src/ImageSharp/Processing/Processors/Dithering/ErrorDither.cs @@ -95,7 +95,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering where TFrameQuantizer : struct, IFrameQuantizer where TPixel : unmanaged, IPixel { - ReadOnlySpan paletteSpan = destination.Palette.Span; + ReadOnlySpan paletteSpan = destination.Palette; int offsetY = bounds.Top; int offsetX = bounds.Left; float scale = quantizer.Options.DitherScale; diff --git a/src/ImageSharp/Processing/Processors/Dithering/OrderedDither.cs b/src/ImageSharp/Processing/Processors/Dithering/OrderedDither.cs index 00ee4a7e6..c5b4135f5 100644 --- a/src/ImageSharp/Processing/Processors/Dithering/OrderedDither.cs +++ b/src/ImageSharp/Processing/Processors/Dithering/OrderedDither.cs @@ -203,7 +203,6 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering private readonly ImageFrame source; private readonly QuantizedFrame destination; private readonly Rectangle bounds; - private readonly ReadOnlyMemory palette; private readonly int bitDepth; [MethodImpl(InliningOptions.ShortMethod)] @@ -219,14 +218,13 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering this.source = source; this.destination = destination; this.bounds = bounds; - this.palette = destination.Palette; - this.bitDepth = ImageMaths.GetBitsNeededForColorDepth(destination.Palette.Span.Length); + this.bitDepth = ImageMaths.GetBitsNeededForColorDepth(destination.Palette.Length); } [MethodImpl(InliningOptions.ShortMethod)] public void Invoke(in RowInterval rows) { - ReadOnlySpan paletteSpan = this.palette.Span; + ReadOnlySpan paletteSpan = this.destination.Palette; int offsetY = this.bounds.Top; int offsetX = this.bounds.Left; float scale = this.quantizer.Options.DitherScale; diff --git a/src/ImageSharp/Processing/Processors/Quantization/EuclideanPixelMap{TPixel}.cs b/src/ImageSharp/Processing/Processors/Quantization/EuclideanPixelMap{TPixel}.cs index 7a789164f..615a7238b 100644 --- a/src/ImageSharp/Processing/Processors/Quantization/EuclideanPixelMap{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Quantization/EuclideanPixelMap{TPixel}.cs @@ -10,7 +10,7 @@ using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Processing.Processors.Quantization { /// - /// Gets the closest color to the supplied color based upon the Eucladean distance. + /// Gets the closest color to the supplied color based upon the Euclidean distance. /// TODO: Expose this somehow. /// /// The pixel format. @@ -62,18 +62,17 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization ReadOnlySpan paletteSpan = this.Palette.Span; // Check if the color is in the lookup table - if (this.distanceCache.TryGetValue(color, out int index)) + if (!this.distanceCache.TryGetValue(color, out int index)) { - match = paletteSpan[index]; - return index; + return this.GetClosestColorSlow(color, paletteSpan, out match); } - return this.GetClosestColorSlow(color, paletteSpan, out match); + match = paletteSpan[index]; + return index; } /// - public override int GetHashCode() - => this.vectorCache.GetHashCode(); + public override int GetHashCode() => this.vectorCache.GetHashCode(); [MethodImpl(InliningOptions.ShortMethod)] private int GetClosestColorSlow(TPixel color, ReadOnlySpan palette, out TPixel match) @@ -88,17 +87,19 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization Vector4 candidate = this.vectorCache[i]; float distance = Vector4.DistanceSquared(vector, candidate); + if (!(distance < leastDistance)) + { + continue; + } + // Less than... assign. - if (distance < leastDistance) + index = i; + leastDistance = distance; + + // And if it's an exact match, exit the loop + if (distance == 0) { - index = i; - leastDistance = distance; - - // And if it's an exact match, exit the loop - if (distance == 0) - { - break; - } + break; } } diff --git a/src/ImageSharp/Processing/Processors/Quantization/FrameQuantizerExtensions.cs b/src/ImageSharp/Processing/Processors/Quantization/FrameQuantizerExtensions.cs index a589f7524..d6d8b98da 100644 --- a/src/ImageSharp/Processing/Processors/Quantization/FrameQuantizerExtensions.cs +++ b/src/ImageSharp/Processing/Processors/Quantization/FrameQuantizerExtensions.cs @@ -20,7 +20,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization /// /// The type of frame quantizer. /// The pixel format. - /// The frame + /// The frame quantizer. /// The source image frame to quantize. /// The bounds within the frame to quantize. /// @@ -37,7 +37,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization var interest = Rectangle.Intersect(source.Bounds(), bounds); // Collect the palette. Required before the second pass runs. - ReadOnlyMemory palette = quantizer.BuildPalette(source, interest); + ReadOnlySpan palette = quantizer.BuildPalette(source, interest); MemoryAllocator memoryAllocator = quantizer.Configuration.MemoryAllocator; var quantizedFrame = new QuantizedFrame(memoryAllocator, interest.Width, interest.Height, palette); @@ -49,10 +49,8 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization else { // We clone the image as we don't want to alter the original via error diffusion based dithering. - using (ImageFrame clone = source.Clone()) - { - SecondPass(ref quantizer, clone, quantizedFrame, interest); - } + using ImageFrame clone = source.Clone(); + SecondPass(ref quantizer, clone, quantizedFrame, interest); } return quantizedFrame; @@ -71,7 +69,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization if (dither is null) { - var operation = new RowIntervalOperation(quantizer, source, destination, bounds); + var operation = new RowIntervalOperation(ref quantizer, source, destination, bounds); ParallelRowIterator.IterateRows( quantizer.Configuration, bounds, @@ -91,11 +89,10 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization private readonly ImageFrame source; private readonly QuantizedFrame destination; private readonly Rectangle bounds; - private readonly ReadOnlyMemory palette; [MethodImpl(InliningOptions.ShortMethod)] public RowIntervalOperation( - in TFrameQuantizer quantizer, + ref TFrameQuantizer quantizer, ImageFrame source, QuantizedFrame destination, Rectangle bounds) @@ -104,13 +101,12 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization this.source = source; this.destination = destination; this.bounds = bounds; - this.palette = destination.Palette; } [MethodImpl(InliningOptions.ShortMethod)] public void Invoke(in RowInterval rows) { - ReadOnlySpan paletteSpan = this.palette.Span; + ReadOnlySpan paletteSpan = this.destination.Palette; int offsetY = this.bounds.Top; int offsetX = this.bounds.Left; diff --git a/src/ImageSharp/Processing/Processors/Quantization/IFrameQuantizer{TPixel}.cs b/src/ImageSharp/Processing/Processors/Quantization/IFrameQuantizer{TPixel}.cs index 57e8fc3f3..506767277 100644 --- a/src/ImageSharp/Processing/Processors/Quantization/IFrameQuantizer{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Quantization/IFrameQuantizer{TPixel}.cs @@ -38,8 +38,8 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization /// /// The source image frame. /// The region of interest bounds. - /// The palette. - ReadOnlyMemory BuildPalette(ImageFrame source, Rectangle bounds); + /// The palette. + ReadOnlySpan BuildPalette(ImageFrame source, Rectangle bounds); /// /// Returns the index and color from the quantized palette corresponding to the given color. @@ -48,7 +48,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization /// The output color palette. /// The matched color. /// The index. - public byte GetQuantizedColor(TPixel color, ReadOnlySpan palette, out TPixel match); + byte GetQuantizedColor(TPixel color, ReadOnlySpan palette, out TPixel match); // TODO: Enable bulk operations. // void GetQuantizedColors(ReadOnlySpan colors, ReadOnlySpan palette, Span indices, Span matches); diff --git a/src/ImageSharp/Processing/Processors/Quantization/OctreeFrameQuantizer{TPixel}.cs b/src/ImageSharp/Processing/Processors/Quantization/OctreeFrameQuantizer{TPixel}.cs index 1b7c9edd6..946ae399b 100644 --- a/src/ImageSharp/Processing/Processors/Quantization/OctreeFrameQuantizer{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Quantization/OctreeFrameQuantizer{TPixel}.cs @@ -3,7 +3,6 @@ using System; using System.Buffers; -using System.Collections.Generic; using System.Numerics; using System.Runtime.CompilerServices; using SixLabors.ImageSharp.Advanced; @@ -22,8 +21,10 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization { private readonly int colors; private readonly Octree octree; + private IMemoryOwner palette; private EuclideanPixelMap pixelMap; private readonly bool isDithering; + private bool isDisposed; /// /// Initializes a new instance of the struct. @@ -41,8 +42,10 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization this.colors = this.Options.MaxColors; this.octree = new Octree(ImageMaths.GetBitsNeededForColorDepth(this.colors).Clamp(1, 8)); + this.palette = configuration.MemoryAllocator.Allocate(this.colors, AllocationOptions.Clean); this.pixelMap = default; this.isDithering = !(this.Options.Dither is null); + this.isDisposed = false; } /// @@ -53,12 +56,12 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization /// [MethodImpl(InliningOptions.ShortMethod)] - public QuantizedFrame QuantizeFrame(ImageFrame source, Rectangle bounds) - => FrameQuantizerExtensions.QuantizeFrame(ref this, source, bounds); + public readonly QuantizedFrame QuantizeFrame(ImageFrame source, Rectangle bounds) + => FrameQuantizerExtensions.QuantizeFrame(ref Unsafe.AsRef(this), source, bounds); /// [MethodImpl(InliningOptions.ShortMethod)] - public ReadOnlyMemory BuildPalette(ImageFrame source, Rectangle bounds) + public ReadOnlySpan BuildPalette(ImageFrame source, Rectangle bounds) { using IMemoryOwner buffer = this.Configuration.MemoryAllocator.Allocate(bounds.Width); Span bufferSpan = buffer.GetSpan(); @@ -78,15 +81,18 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization } } - TPixel[] palette = this.octree.Palletize(this.colors); - this.pixelMap = new EuclideanPixelMap(palette); + Span paletteSpan = this.palette.GetSpan(); + this.octree.Palletize(paletteSpan, this.colors); - return palette; + // TODO: Cannot make method readonly due to this line. + this.pixelMap = new EuclideanPixelMap(this.palette.Memory); + + return paletteSpan; } /// [MethodImpl(InliningOptions.ShortMethod)] - public byte GetQuantizedColor(TPixel color, ReadOnlySpan palette, out TPixel match) + public readonly byte GetQuantizedColor(TPixel color, ReadOnlySpan palette, out TPixel match) { // Octree only maps the RGB component of a color // so cannot tell the difference between a fully transparent @@ -104,6 +110,12 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization /// public void Dispose() { + if (!this.isDisposed) + { + this.isDisposed = true; + this.palette.Dispose(); + this.palette = null; + } } /// @@ -116,14 +128,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization /// private static readonly byte[] Mask = new byte[] { - 0b10000000, - 0b1000000, - 0b100000, - 0b10000, - 0b1000, - 0b100, - 0b10, - 0b1 + 0b10000000, 0b1000000, 0b100000, 0b10000, 0b1000, 0b100, 0b10, 0b1 }; /// @@ -216,26 +221,18 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization /// /// Convert the nodes in the Octree to a palette with a maximum of colorCount colors /// + /// The palette to fill. /// The maximum number of colors - /// - /// An with the palletized colors - /// [MethodImpl(InliningOptions.ShortMethod)] - public TPixel[] Palletize(int colorCount) + public void Palletize(Span palette, int colorCount) { while (this.Leaves > colorCount - 1) { this.Reduce(); } - // Now palletize the nodes - var palette = new TPixel[colorCount]; - int paletteIndex = 0; this.root.ConstructPalette(palette, ref paletteIndex); - - // And return the palette - return palette; } /// @@ -437,12 +434,16 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization /// The palette /// The current palette index [MethodImpl(InliningOptions.ColdPath)] - public void ConstructPalette(TPixel[] palette, ref int index) + public void ConstructPalette(Span palette, ref int index) { if (this.leaf) { // Set the color of the palette entry - var vector = Vector3.Clamp(new Vector3(this.red, this.green, this.blue) / this.pixelCount, Vector3.Zero, new Vector3(255)); + var vector = Vector3.Clamp( + new Vector3(this.red, this.green, this.blue) / this.pixelCount, + Vector3.Zero, + new Vector3(255)); + TPixel pixel = default; pixel.FromRgba32(new Rgba32((byte)vector.X, (byte)vector.Y, (byte)vector.Z, byte.MaxValue)); palette[index] = pixel; @@ -516,8 +517,8 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization int shift = 7 - level; byte mask = Mask[level]; return ((color.R & mask) >> shift) - | ((color.G & mask) >> (shift - 1)) - | ((color.B & mask) >> (shift - 2)); + | ((color.G & mask) >> (shift - 1)) + | ((color.B & mask) >> (shift - 2)); } /// diff --git a/src/ImageSharp/Processing/Processors/Quantization/PaletteFrameQuantizer{TPixel}.cs b/src/ImageSharp/Processing/Processors/Quantization/PaletteFrameQuantizer{TPixel}.cs index 3200dfab8..7960f728a 100644 --- a/src/ImageSharp/Processing/Processors/Quantization/PaletteFrameQuantizer{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Quantization/PaletteFrameQuantizer{TPixel}.cs @@ -2,7 +2,9 @@ // Licensed under the Apache License, Version 2.0. using System; +using System.Buffers; using System.Runtime.CompilerServices; +using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Processing.Processors.Quantization @@ -15,8 +17,9 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization internal struct PaletteFrameQuantizer : IFrameQuantizer where TPixel : unmanaged, IPixel { - private readonly ReadOnlyMemory palette; + private IMemoryOwner paletteOwner; private readonly EuclideanPixelMap pixelMap; + private bool isDisposed; /// /// Initializes a new instance of the struct. @@ -25,7 +28,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization /// The quantizer options defining quantization rules. /// A containing all colors in the palette. [MethodImpl(InliningOptions.ShortMethod)] - public PaletteFrameQuantizer(Configuration configuration, QuantizerOptions options, ReadOnlyMemory colors) + public PaletteFrameQuantizer(Configuration configuration, QuantizerOptions options, ReadOnlySpan colors) { Guard.NotNull(configuration, nameof(configuration)); Guard.NotNull(options, nameof(options)); @@ -33,8 +36,35 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization this.Configuration = configuration; this.Options = options; - this.palette = colors; - this.pixelMap = new EuclideanPixelMap(colors); + int maxLength = Math.Min(colors.Length, options.MaxColors); + this.paletteOwner = configuration.MemoryAllocator.Allocate(maxLength); + Color.ToPixel(configuration, colors, this.paletteOwner.GetSpan()); + + this.pixelMap = new EuclideanPixelMap(this.paletteOwner.Memory); + this.isDisposed = false; + } + + /// + /// Initializes a new instance of the struct. + /// + /// The configuration which allows altering default behaviour or extending the library. + /// The quantizer options defining quantization rules. + /// A containing all colors in the palette. + [MethodImpl(InliningOptions.ShortMethod)] + public PaletteFrameQuantizer(Configuration configuration, QuantizerOptions options, ReadOnlySpan palette) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.NotNull(options, nameof(options)); + + this.Configuration = configuration; + this.Options = options; + + int maxLength = Math.Min(palette.Length, options.MaxColors); + this.paletteOwner = configuration.MemoryAllocator.Allocate(maxLength); + palette.CopyTo(this.paletteOwner.GetSpan()); + + this.pixelMap = new EuclideanPixelMap(this.paletteOwner.Memory); + this.isDisposed = false; } /// @@ -45,22 +75,30 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization /// [MethodImpl(InliningOptions.ShortMethod)] - public QuantizedFrame QuantizeFrame(ImageFrame source, Rectangle bounds) - => FrameQuantizerExtensions.QuantizeFrame(ref this, source, bounds); + public readonly QuantizedFrame QuantizeFrame(ImageFrame source, Rectangle bounds) + => FrameQuantizerExtensions.QuantizeFrame(ref Unsafe.AsRef(this), source, bounds); /// [MethodImpl(InliningOptions.ShortMethod)] - public ReadOnlyMemory BuildPalette(ImageFrame source, Rectangle bounds) - => this.palette; + public readonly ReadOnlySpan BuildPalette(ImageFrame source, Rectangle bounds) + => this.paletteOwner.GetSpan(); /// [MethodImpl(InliningOptions.ShortMethod)] - public byte GetQuantizedColor(TPixel color, ReadOnlySpan palette, out TPixel match) + public readonly byte GetQuantizedColor(TPixel color, ReadOnlySpan palette, out TPixel match) => (byte)this.pixelMap.GetClosestColor(color, out match); /// public void Dispose() { + if (this.isDisposed) + { + return; + } + + this.isDisposed = true; + this.paletteOwner.Dispose(); + this.paletteOwner = null; } } } diff --git a/src/ImageSharp/Processing/Processors/Quantization/PaletteQuantizer.cs b/src/ImageSharp/Processing/Processors/Quantization/PaletteQuantizer.cs index 07fa6e41e..75a0f3938 100644 --- a/src/ImageSharp/Processing/Processors/Quantization/PaletteQuantizer.cs +++ b/src/ImageSharp/Processing/Processors/Quantization/PaletteQuantizer.cs @@ -11,6 +11,8 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization /// public class PaletteQuantizer : IQuantizer { + private readonly ReadOnlyMemory palette; + /// /// Initializes a new instance of the class. /// @@ -30,15 +32,10 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization Guard.MustBeGreaterThan(palette.Length, 0, nameof(palette)); Guard.NotNull(options, nameof(options)); - this.Palette = palette; + this.palette = palette; this.Options = options; } - /// - /// Gets the color palette. - /// - public ReadOnlyMemory Palette { get; } - /// public QuantizerOptions Options { get; } @@ -52,12 +49,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization where TPixel : unmanaged, IPixel { Guard.NotNull(options, nameof(options)); - - int length = Math.Min(this.Palette.Span.Length, options.MaxColors); - var palette = new TPixel[length]; - - Color.ToPixel(configuration, this.Palette.Span, palette.AsSpan()); - return new PaletteFrameQuantizer(configuration, options, palette); + return new PaletteFrameQuantizer(configuration, options, this.palette.Span); } } } diff --git a/src/ImageSharp/Processing/Processors/Quantization/QuantizeProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Quantization/QuantizeProcessor{TPixel}.cs index 5a0116a03..04586807e 100644 --- a/src/ImageSharp/Processing/Processors/Quantization/QuantizeProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Quantization/QuantizeProcessor{TPixel}.cs @@ -69,7 +69,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization public void Invoke(in RowInterval rows) { ReadOnlySpan quantizedPixelSpan = this.quantized.GetPixelSpan(); - ReadOnlySpan paletteSpan = this.quantized.Palette.Span; + ReadOnlySpan paletteSpan = this.quantized.Palette; int offsetY = this.bounds.Top; int offsetX = this.bounds.Left; int width = this.bounds.Width; diff --git a/src/ImageSharp/Processing/Processors/Quantization/QuantizedFrame{TPixel}.cs b/src/ImageSharp/Processing/Processors/Quantization/QuantizedFrame{TPixel}.cs index 07e89a2b0..cda0546e4 100644 --- a/src/ImageSharp/Processing/Processors/Quantization/QuantizedFrame{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Quantization/QuantizedFrame{TPixel}.cs @@ -16,6 +16,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization public sealed class QuantizedFrame : IDisposable where TPixel : unmanaged, IPixel { + private IMemoryOwner palette; private IMemoryOwner pixels; private bool isDisposed; @@ -26,15 +27,17 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization /// The image width. /// The image height. /// The color palette. - internal QuantizedFrame(MemoryAllocator memoryAllocator, int width, int height, ReadOnlyMemory palette) + internal QuantizedFrame(MemoryAllocator memoryAllocator, int width, int height, ReadOnlySpan palette) { Guard.MustBeGreaterThan(width, 0, nameof(width)); Guard.MustBeGreaterThan(height, 0, nameof(height)); this.Width = width; this.Height = height; - this.Palette = palette; this.pixels = memoryAllocator.AllocateManagedByteBuffer(width * height, AllocationOptions.Clean); + + this.palette = memoryAllocator.Allocate(palette.Length); + palette.CopyTo(this.palette.GetSpan()); } /// @@ -50,7 +53,11 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization /// /// Gets the color palette of this . /// - public ReadOnlyMemory Palette { get; private set; } + public ReadOnlySpan Palette + { + [MethodImpl(InliningOptions.ShortMethod)] + get { return this.palette.GetSpan(); } + } /// /// Gets the pixels of this . @@ -72,15 +79,16 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization /// public void Dispose() { - if (this.isDisposed) + if (!this.isDisposed) { return; } this.isDisposed = true; this.pixels?.Dispose(); + this.palette?.Dispose(); this.pixels = null; - this.Palette = null; + this.palette = null; } } } diff --git a/src/ImageSharp/Processing/Processors/Quantization/WuFrameQuantizer{TPixel}.cs b/src/ImageSharp/Processing/Processors/Quantization/WuFrameQuantizer{TPixel}.cs index 177f7e859..505477258 100644 --- a/src/ImageSharp/Processing/Processors/Quantization/WuFrameQuantizer{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Quantization/WuFrameQuantizer{TPixel}.cs @@ -65,30 +65,13 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization /// private const int TableLength = IndexCount * IndexCount * IndexCount * IndexAlphaCount; - /// - /// Color moments. - /// private IMemoryOwner moments; - - /// - /// Color space tag. - /// private IMemoryOwner tag; - - /// - /// Maximum allowed color depth - /// + private IMemoryOwner palette; private int colors; - - /// - /// The color cube representing the image palette - /// private readonly Box[] colorCube; - private EuclideanPixelMap pixelMap; - private readonly bool isDithering; - private bool isDisposed; /// @@ -104,10 +87,11 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization this.Configuration = configuration; this.Options = options; + this.colors = this.Options.MaxColors; this.memoryAllocator = this.Configuration.MemoryAllocator; this.moments = this.memoryAllocator.Allocate(TableLength, AllocationOptions.Clean); this.tag = this.memoryAllocator.Allocate(TableLength, AllocationOptions.Clean); - this.colors = this.Options.MaxColors; + this.palette = this.memoryAllocator.Allocate(this.colors, AllocationOptions.Clean); this.colorCube = new Box[this.colors]; this.isDisposed = false; this.pixelMap = default; @@ -122,19 +106,18 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization /// [MethodImpl(InliningOptions.ShortMethod)] - public QuantizedFrame QuantizeFrame(ImageFrame source, Rectangle bounds) - => FrameQuantizerExtensions.QuantizeFrame(ref this, source, bounds); + public readonly QuantizedFrame QuantizeFrame(ImageFrame source, Rectangle bounds) + => FrameQuantizerExtensions.QuantizeFrame(ref Unsafe.AsRef(this), source, bounds); /// - public ReadOnlyMemory BuildPalette(ImageFrame source, Rectangle bounds) + public ReadOnlySpan BuildPalette(ImageFrame source, Rectangle bounds) { this.Build3DHistogram(source, bounds); this.Get3DMoments(this.memoryAllocator); this.BuildCube(); - var palette = new TPixel[this.colors]; ReadOnlySpan momentsSpan = this.moments.GetSpan(); - + Span paletteSpan = this.palette.GetSpan(); for (int k = 0; k < this.colors; k++) { this.Mark(ref this.colorCube[k], (byte)k); @@ -143,17 +126,18 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization if (moment.Weight > 0) { - ref TPixel color = ref palette[k]; + ref TPixel color = ref paletteSpan[k]; color.FromScaledVector4(moment.Normalize()); } } - this.pixelMap = new EuclideanPixelMap(palette); - return palette; + // TODO: Cannot make methods readonly due to this line. + this.pixelMap = new EuclideanPixelMap(this.palette.Memory); + return paletteSpan; } /// - public byte GetQuantizedColor(TPixel color, ReadOnlySpan palette, out TPixel match) + public readonly byte GetQuantizedColor(TPixel color, ReadOnlySpan palette, out TPixel match) { if (!this.isDithering) { @@ -177,16 +161,16 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization /// public void Dispose() { - if (this.isDisposed) + if (!this.isDisposed) { - return; + this.isDisposed = true; + this.moments?.Dispose(); + this.tag?.Dispose(); + this.palette?.Dispose(); + this.moments = null; + this.tag = null; + this.palette = null; } - - this.isDisposed = true; - this.moments?.Dispose(); - this.tag?.Dispose(); - this.moments = null; - this.tag = null; } /// diff --git a/tests/ImageSharp.Benchmarks/Codecs/EncodeGif.cs b/tests/ImageSharp.Benchmarks/Codecs/EncodeGif.cs index 5e91f98eb..71405890c 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/EncodeGif.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/EncodeGif.cs @@ -21,6 +21,12 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs private SDImage bmpDrawing; private Image bmpCore; + // Try to get as close to System.Drawing's output as possible + private readonly GifEncoder encoder = new GifEncoder + { + Quantizer = new WebSafePaletteQuantizer(new QuantizerOptions { Dither = KnownDitherings.Bayer4x4 }) + }; + [GlobalSetup] public void ReadImages() { @@ -53,15 +59,9 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs [Benchmark(Description = "ImageSharp Gif")] public void GifCore() { - // Try to get as close to System.Drawing's output as possible - var options = new GifEncoder - { - Quantizer = new WebSafePaletteQuantizer(new QuantizerOptions { Dither = KnownDitherings.Bayer4x4 }) - }; - using (var memoryStream = new MemoryStream()) { - this.bmpCore.SaveAsGif(memoryStream, options); + this.bmpCore.SaveAsGif(memoryStream, this.encoder); } } } diff --git a/tests/ImageSharp.Tests/Quantization/QuantizedImageTests.cs b/tests/ImageSharp.Tests/Quantization/QuantizedImageTests.cs index cd93ab0cf..4085d9943 100644 --- a/tests/ImageSharp.Tests/Quantization/QuantizedImageTests.cs +++ b/tests/ImageSharp.Tests/Quantization/QuantizedImageTests.cs @@ -116,7 +116,7 @@ namespace SixLabors.ImageSharp.Tests // Transparent pixels are much more likely to be found at the end of a palette int index = -1; Rgba32 trans = default; - ReadOnlySpan paletteSpan = quantized.Palette.Span; + ReadOnlySpan paletteSpan = quantized.Palette; for (int i = paletteSpan.Length - 1; i >= 0; i--) { paletteSpan[i].ToRgba32(ref trans); diff --git a/tests/ImageSharp.Tests/Quantization/WuQuantizerTests.cs b/tests/ImageSharp.Tests/Quantization/WuQuantizerTests.cs index f3bcd0b95..34888f1db 100644 --- a/tests/ImageSharp.Tests/Quantization/WuQuantizerTests.cs +++ b/tests/ImageSharp.Tests/Quantization/WuQuantizerTests.cs @@ -26,7 +26,7 @@ namespace SixLabors.ImageSharp.Tests.Quantization Assert.Equal(1, result.Palette.Length); Assert.Equal(1, result.GetPixelSpan().Length); - Assert.Equal(Color.Black, (Color)result.Palette.Span[0]); + Assert.Equal(Color.Black, (Color)result.Palette[0]); Assert.Equal(0, result.GetPixelSpan()[0]); } @@ -45,7 +45,7 @@ namespace SixLabors.ImageSharp.Tests.Quantization Assert.Equal(1, result.Palette.Length); Assert.Equal(1, result.GetPixelSpan().Length); - Assert.Equal(default, result.Palette.Span[0]); + Assert.Equal(default, result.Palette[0]); Assert.Equal(0, result.GetPixelSpan()[0]); } @@ -92,7 +92,7 @@ namespace SixLabors.ImageSharp.Tests.Quantization var actualImage = new Image(1, 256); - ReadOnlySpan paletteSpan = result.Palette.Span; + ReadOnlySpan paletteSpan = result.Palette; int paletteCount = result.Palette.Length - 1; for (int y = 0; y < actualImage.Height; y++) { @@ -157,7 +157,7 @@ namespace SixLabors.ImageSharp.Tests.Quantization Assert.Equal(4 * 8, result.Palette.Length); Assert.Equal(256, result.GetPixelSpan().Length); - ReadOnlySpan paletteSpan = result.Palette.Span; + ReadOnlySpan paletteSpan = result.Palette; int paletteCount = result.Palette.Length - 1; for (int y = 0; y < actualImage.Height; y++) { From 4071c65f4c7e9364c3e8ee08c978136c7a5b0d17 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Sun, 1 Mar 2020 22:27:49 +1100 Subject: [PATCH 48/64] Better performance --- .../DiscontiguousBuffers/IMemoryGroup{T}.cs | 4 +- .../Processors/Dithering/ErrorDither.cs | 2 +- .../IPaletteDitherImageProcessor{TPixel}.cs | 4 +- .../Dithering/OrderedDither.KnownTypes.cs | 2 +- .../Processors/Dithering/OrderedDither.cs | 4 +- .../PaletteDitherProcessor{TPixel}.cs | 2 +- .../Quantization/EuclideanPixelMap{TPixel}.cs | 36 +++++++++--------- .../Quantization/FrameQuantizerExtensions.cs | 2 +- .../OctreeFrameQuantizer{TPixel}.cs | 2 +- .../Quantization/OctreeQuantizer.cs | 4 +- .../PaletteFrameQuantizer{TPixel}.cs | 4 +- .../Quantization/PaletteQuantizer.cs | 3 +- .../Quantization/WebSafePaletteQuantizer.cs | 4 +- .../Quantization/WernerPaletteQuantizer.cs | 4 +- .../Quantization/WuFrameQuantizer{TPixel}.cs | 38 +++++++++---------- .../Processors/Quantization/WuQuantizer.cs | 4 +- 16 files changed, 64 insertions(+), 55 deletions(-) diff --git a/src/ImageSharp/Memory/DiscontiguousBuffers/IMemoryGroup{T}.cs b/src/ImageSharp/Memory/DiscontiguousBuffers/IMemoryGroup{T}.cs index 2649b7fb1..89aca914d 100644 --- a/src/ImageSharp/Memory/DiscontiguousBuffers/IMemoryGroup{T}.cs +++ b/src/ImageSharp/Memory/DiscontiguousBuffers/IMemoryGroup{T}.cs @@ -18,12 +18,12 @@ namespace SixLabors.ImageSharp.Memory /// Gets the number of elements per contiguous sub-buffer preceding the last buffer. /// The last buffer is allowed to be smaller. /// - public int BufferLength { get; } + int BufferLength { get; } /// /// Gets the aggregate number of elements in the group. /// - public long TotalLength { get; } + long TotalLength { get; } /// /// Gets a value indicating whether the group has been invalidated. diff --git a/src/ImageSharp/Processing/Processors/Dithering/ErrorDither.cs b/src/ImageSharp/Processing/Processors/Dithering/ErrorDither.cs index 9217d1c3f..6aa6eeca6 100644 --- a/src/ImageSharp/Processing/Processors/Dithering/ErrorDither.cs +++ b/src/ImageSharp/Processing/Processors/Dithering/ErrorDither.cs @@ -131,7 +131,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering for (int x = bounds.Left; x < bounds.Right; x++) { TPixel sourcePixel = row[x]; - TPixel transformed = processor.GetPaletteColor(sourcePixel, palette); + TPixel transformed = Unsafe.AsRef(processor).GetPaletteColor(sourcePixel, palette); this.Dither(source, bounds, sourcePixel, transformed, x, y, scale); row[x] = transformed; } diff --git a/src/ImageSharp/Processing/Processors/Dithering/IPaletteDitherImageProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Dithering/IPaletteDitherImageProcessor{TPixel}.cs index 3e4bf4d83..a890e929d 100644 --- a/src/ImageSharp/Processing/Processors/Dithering/IPaletteDitherImageProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Dithering/IPaletteDitherImageProcessor{TPixel}.cs @@ -14,9 +14,9 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering where TPixel : unmanaged, IPixel { /// - /// Gets the configration instance to use when performing operations. + /// Gets the configuration instance to use when performing operations. /// - public Configuration Configuration { get; } + Configuration Configuration { get; } /// /// Gets the dithering palette. diff --git a/src/ImageSharp/Processing/Processors/Dithering/OrderedDither.KnownTypes.cs b/src/ImageSharp/Processing/Processors/Dithering/OrderedDither.KnownTypes.cs index d3e710782..f6026a64f 100644 --- a/src/ImageSharp/Processing/Processors/Dithering/OrderedDither.KnownTypes.cs +++ b/src/ImageSharp/Processing/Processors/Dithering/OrderedDither.KnownTypes.cs @@ -6,7 +6,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering /// /// An ordered dithering matrix with equal sides of arbitrary length /// - public readonly partial struct OrderedDither : IDither + public readonly partial struct OrderedDither { /// /// Applies order dithering using the 2x2 Bayer dithering matrix. diff --git a/src/ImageSharp/Processing/Processors/Dithering/OrderedDither.cs b/src/ImageSharp/Processing/Processors/Dithering/OrderedDither.cs index c5b4135f5..9e97fe7e6 100644 --- a/src/ImageSharp/Processing/Processors/Dithering/OrderedDither.cs +++ b/src/ImageSharp/Processing/Processors/Dithering/OrderedDither.cs @@ -237,7 +237,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering for (int x = this.bounds.Left; x < this.bounds.Right; x++) { TPixel dithered = this.dither.Dither(sourceRow[x], x, y, this.bitDepth, scale); - destinationRow[x - offsetX] = this.quantizer.GetQuantizedColor(dithered, paletteSpan, out TPixel _); + destinationRow[x - offsetX] = Unsafe.AsRef(this.quantizer).GetQuantizedColor(dithered, paletteSpan, out TPixel _); } } } @@ -281,7 +281,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering { ref TPixel sourcePixel = ref row[x]; TPixel dithered = this.dither.Dither(sourcePixel, x, y, this.bitDepth, this.scale); - sourcePixel = this.processor.GetPaletteColor(dithered, paletteSpan); + sourcePixel = Unsafe.AsRef(this.processor).GetPaletteColor(dithered, paletteSpan); } } } diff --git a/src/ImageSharp/Processing/Processors/Dithering/PaletteDitherProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Dithering/PaletteDitherProcessor{TPixel}.cs index d25048a7f..4d4ccf1ab 100644 --- a/src/ImageSharp/Processing/Processors/Dithering/PaletteDitherProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Dithering/PaletteDitherProcessor{TPixel}.cs @@ -84,7 +84,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering float ditherScale) { this.Configuration = configuration; - this.pixelMap = new EuclideanPixelMap(palette); + this.pixelMap = new EuclideanPixelMap(configuration, palette); this.Palette = palette; this.DitherScale = ditherScale; } diff --git a/src/ImageSharp/Processing/Processors/Quantization/EuclideanPixelMap{TPixel}.cs b/src/ImageSharp/Processing/Processors/Quantization/EuclideanPixelMap{TPixel}.cs index 615a7238b..714788629 100644 --- a/src/ImageSharp/Processing/Processors/Quantization/EuclideanPixelMap{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Quantization/EuclideanPixelMap{TPixel}.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Concurrent; using System.Numerics; using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Processing.Processors.Quantization @@ -17,27 +18,25 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization internal readonly struct EuclideanPixelMap : IPixelMap, IEquatable> where TPixel : unmanaged, IPixel { - private readonly ConcurrentDictionary vectorCache; + private readonly Vector4[] vectorCache; private readonly ConcurrentDictionary distanceCache; /// /// Initializes a new instance of the struct. /// + /// The configuration. /// The color palette to map from. [MethodImpl(InliningOptions.ShortMethod)] - public EuclideanPixelMap(ReadOnlyMemory palette) + public EuclideanPixelMap(Configuration configuration, ReadOnlyMemory palette) { Guard.MustBeGreaterThan(palette.Length, 0, nameof(palette)); this.Palette = palette; ReadOnlySpan paletteSpan = this.Palette.Span; - this.vectorCache = new ConcurrentDictionary(); + this.vectorCache = new Vector4[paletteSpan.Length]; this.distanceCache = new ConcurrentDictionary(); - for (int i = 0; i < paletteSpan.Length; i++) - { - this.vectorCache[i] = paletteSpan[i].ToScaledVector4(); - } + PixelOperations.Instance.ToVector4(configuration, paletteSpan, this.vectorCache); } /// @@ -81,31 +80,32 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization int index = 0; float leastDistance = float.MaxValue; Vector4 vector = color.ToScaledVector4(); + ref TPixel paletteRef = ref MemoryMarshal.GetReference(palette); + ref Vector4 vectorCacheRef = ref MemoryMarshal.GetReference(this.vectorCache); for (int i = 0; i < palette.Length; i++) { - Vector4 candidate = this.vectorCache[i]; + Vector4 candidate = Unsafe.Add(ref vectorCacheRef, i); float distance = Vector4.DistanceSquared(vector, candidate); - if (!(distance < leastDistance)) + // If it's an exact match, exit the loop + if (distance == 0) { - continue; + index = i; + break; } - // Less than... assign. - index = i; - leastDistance = distance; - - // And if it's an exact match, exit the loop - if (distance == 0) + if (distance < leastDistance) { - break; + // Less than... assign. + index = i; + leastDistance = distance; } } // Now I have the index, pop it into the cache for next time this.distanceCache[color] = index; - match = palette[index]; + match = Unsafe.Add(ref paletteRef, index); return index; } } diff --git a/src/ImageSharp/Processing/Processors/Quantization/FrameQuantizerExtensions.cs b/src/ImageSharp/Processing/Processors/Quantization/FrameQuantizerExtensions.cs index d6d8b98da..ef97f57e3 100644 --- a/src/ImageSharp/Processing/Processors/Quantization/FrameQuantizerExtensions.cs +++ b/src/ImageSharp/Processing/Processors/Quantization/FrameQuantizerExtensions.cs @@ -117,7 +117,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization for (int x = this.bounds.Left; x < this.bounds.Right; x++) { - destinationRow[x - offsetX] = this.quantizer.GetQuantizedColor(sourceRow[x], paletteSpan, out TPixel _); + destinationRow[x - offsetX] = Unsafe.AsRef(this.quantizer).GetQuantizedColor(sourceRow[x], paletteSpan, out TPixel _); } } } diff --git a/src/ImageSharp/Processing/Processors/Quantization/OctreeFrameQuantizer{TPixel}.cs b/src/ImageSharp/Processing/Processors/Quantization/OctreeFrameQuantizer{TPixel}.cs index 946ae399b..e80449b09 100644 --- a/src/ImageSharp/Processing/Processors/Quantization/OctreeFrameQuantizer{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Quantization/OctreeFrameQuantizer{TPixel}.cs @@ -85,7 +85,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization this.octree.Palletize(paletteSpan, this.colors); // TODO: Cannot make method readonly due to this line. - this.pixelMap = new EuclideanPixelMap(this.palette.Memory); + this.pixelMap = new EuclideanPixelMap(this.Configuration, this.palette.Memory); return paletteSpan; } diff --git a/src/ImageSharp/Processing/Processors/Quantization/OctreeQuantizer.cs b/src/ImageSharp/Processing/Processors/Quantization/OctreeQuantizer.cs index 3328fd6c7..9e04edef0 100644 --- a/src/ImageSharp/Processing/Processors/Quantization/OctreeQuantizer.cs +++ b/src/ImageSharp/Processing/Processors/Quantization/OctreeQuantizer.cs @@ -11,12 +11,14 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization /// public class OctreeQuantizer : IQuantizer { + private static readonly QuantizerOptions DefaultOptions = new QuantizerOptions(); + /// /// Initializes a new instance of the class /// using the default . /// public OctreeQuantizer() - : this(new QuantizerOptions()) + : this(DefaultOptions) { } diff --git a/src/ImageSharp/Processing/Processors/Quantization/PaletteFrameQuantizer{TPixel}.cs b/src/ImageSharp/Processing/Processors/Quantization/PaletteFrameQuantizer{TPixel}.cs index 7960f728a..3dbf77a3a 100644 --- a/src/ImageSharp/Processing/Processors/Quantization/PaletteFrameQuantizer{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Quantization/PaletteFrameQuantizer{TPixel}.cs @@ -40,7 +40,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization this.paletteOwner = configuration.MemoryAllocator.Allocate(maxLength); Color.ToPixel(configuration, colors, this.paletteOwner.GetSpan()); - this.pixelMap = new EuclideanPixelMap(this.paletteOwner.Memory); + this.pixelMap = new EuclideanPixelMap(configuration, this.paletteOwner.Memory); this.isDisposed = false; } @@ -63,7 +63,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization this.paletteOwner = configuration.MemoryAllocator.Allocate(maxLength); palette.CopyTo(this.paletteOwner.GetSpan()); - this.pixelMap = new EuclideanPixelMap(this.paletteOwner.Memory); + this.pixelMap = new EuclideanPixelMap(configuration, this.paletteOwner.Memory); this.isDisposed = false; } diff --git a/src/ImageSharp/Processing/Processors/Quantization/PaletteQuantizer.cs b/src/ImageSharp/Processing/Processors/Quantization/PaletteQuantizer.cs index 75a0f3938..e95f8c5db 100644 --- a/src/ImageSharp/Processing/Processors/Quantization/PaletteQuantizer.cs +++ b/src/ImageSharp/Processing/Processors/Quantization/PaletteQuantizer.cs @@ -11,6 +11,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization /// public class PaletteQuantizer : IQuantizer { + private static readonly QuantizerOptions DefaultOptions = new QuantizerOptions(); private readonly ReadOnlyMemory palette; /// @@ -18,7 +19,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization /// /// The color palette. public PaletteQuantizer(ReadOnlyMemory palette) - : this(palette, new QuantizerOptions()) + : this(palette, DefaultOptions) { } diff --git a/src/ImageSharp/Processing/Processors/Quantization/WebSafePaletteQuantizer.cs b/src/ImageSharp/Processing/Processors/Quantization/WebSafePaletteQuantizer.cs index 8aa634b9f..d95ed5aab 100644 --- a/src/ImageSharp/Processing/Processors/Quantization/WebSafePaletteQuantizer.cs +++ b/src/ImageSharp/Processing/Processors/Quantization/WebSafePaletteQuantizer.cs @@ -10,11 +10,13 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization /// public class WebSafePaletteQuantizer : PaletteQuantizer { + private static readonly QuantizerOptions DefaultOptions = new QuantizerOptions(); + /// /// Initializes a new instance of the class. /// public WebSafePaletteQuantizer() - : this(new QuantizerOptions()) + : this(DefaultOptions) { } diff --git a/src/ImageSharp/Processing/Processors/Quantization/WernerPaletteQuantizer.cs b/src/ImageSharp/Processing/Processors/Quantization/WernerPaletteQuantizer.cs index 168c837d5..8f8e38dd9 100644 --- a/src/ImageSharp/Processing/Processors/Quantization/WernerPaletteQuantizer.cs +++ b/src/ImageSharp/Processing/Processors/Quantization/WernerPaletteQuantizer.cs @@ -9,11 +9,13 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization /// public class WernerPaletteQuantizer : PaletteQuantizer { + private static readonly QuantizerOptions DefaultOptions = new QuantizerOptions(); + /// /// Initializes a new instance of the class. /// public WernerPaletteQuantizer() - : this(new QuantizerOptions()) + : this(DefaultOptions) { } diff --git a/src/ImageSharp/Processing/Processors/Quantization/WuFrameQuantizer{TPixel}.cs b/src/ImageSharp/Processing/Processors/Quantization/WuFrameQuantizer{TPixel}.cs index 505477258..6f98ce121 100644 --- a/src/ImageSharp/Processing/Processors/Quantization/WuFrameQuantizer{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Quantization/WuFrameQuantizer{TPixel}.cs @@ -132,30 +132,30 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization } // TODO: Cannot make methods readonly due to this line. - this.pixelMap = new EuclideanPixelMap(this.palette.Memory); + this.pixelMap = new EuclideanPixelMap(this.Configuration, this.palette.Memory); return paletteSpan; } /// public readonly byte GetQuantizedColor(TPixel color, ReadOnlySpan palette, out TPixel match) { - if (!this.isDithering) + if (this.isDithering) { - Rgba32 rgba = default; - color.ToRgba32(ref rgba); - - int r = rgba.R >> (8 - IndexBits); - int g = rgba.G >> (8 - IndexBits); - int b = rgba.B >> (8 - IndexBits); - int a = rgba.A >> (8 - IndexAlphaBits); - - ReadOnlySpan tagSpan = this.tag.GetSpan(); - byte index = tagSpan[GetPaletteIndex(r + 1, g + 1, b + 1, a + 1)]; - match = palette[index]; - return index; + return (byte)this.pixelMap.GetClosestColor(color, out match); } - return (byte)this.pixelMap.GetClosestColor(color, out match); + Rgba32 rgba = default; + color.ToRgba32(ref rgba); + + int r = rgba.R >> (8 - IndexBits); + int g = rgba.G >> (8 - IndexBits); + int b = rgba.B >> (8 - IndexBits); + int a = rgba.A >> (8 - IndexAlphaBits); + + ReadOnlySpan tagSpan = this.tag.GetSpan(); + byte index = tagSpan[GetPaletteIndex(r + 1, g + 1, b + 1, a + 1)]; + match = palette[index]; + return index; } /// @@ -376,11 +376,11 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization /// /// Converts the histogram into moments so that we can rapidly calculate the sums of the above quantities over any desired box. /// - /// The memory allocator used for allocating buffers. - private void Get3DMoments(MemoryAllocator memoryAllocator) + /// The memory allocator used for allocating buffers. + private void Get3DMoments(MemoryAllocator allocator) { - using IMemoryOwner volume = memoryAllocator.Allocate(IndexCount * IndexAlphaCount); - using IMemoryOwner area = memoryAllocator.Allocate(IndexAlphaCount); + using IMemoryOwner volume = allocator.Allocate(IndexCount * IndexAlphaCount); + using IMemoryOwner area = allocator.Allocate(IndexAlphaCount); Span momentSpan = this.moments.GetSpan(); Span volumeSpan = volume.GetSpan(); diff --git a/src/ImageSharp/Processing/Processors/Quantization/WuQuantizer.cs b/src/ImageSharp/Processing/Processors/Quantization/WuQuantizer.cs index 872e6d5bd..d2e33aa1f 100644 --- a/src/ImageSharp/Processing/Processors/Quantization/WuQuantizer.cs +++ b/src/ImageSharp/Processing/Processors/Quantization/WuQuantizer.cs @@ -10,12 +10,14 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization /// public class WuQuantizer : IQuantizer { + private static readonly QuantizerOptions DefaultOptions = new QuantizerOptions(); + /// /// Initializes a new instance of the class /// using the default . /// public WuQuantizer() - : this(new QuantizerOptions()) + : this(DefaultOptions) { } From fb4c47413bc7c02571c48ccefa181b3caa666c82 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Tue, 3 Mar 2020 23:25:56 +1100 Subject: [PATCH 49/64] Simplify, fix color mapping, and refactor for performance. --- src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs | 6 +- src/ImageSharp/Formats/Gif/GifEncoderCore.cs | 24 +++++-- src/ImageSharp/Formats/Png/PngEncoderCore.cs | 2 +- .../Processors/Dithering/ErrorDither.cs | 19 +++--- .../IPaletteDitherImageProcessor{TPixel}.cs | 3 +- .../Processors/Dithering/OrderedDither.cs | 17 +++-- .../PaletteDitherProcessor{TPixel}.cs | 9 ++- .../Quantization/EuclideanPixelMap{TPixel}.cs | 68 +++++++++---------- .../Quantization/FrameQuantizerExtensions.cs | 12 ++-- .../Quantization/IFrameQuantizer{TPixel}.cs | 3 +- .../Quantization/IPixelMap{TPixel}.cs | 30 -------- .../OctreeFrameQuantizer{TPixel}.cs | 26 ++++--- .../PaletteFrameQuantizer{TPixel}.cs | 53 +++------------ .../Quantization/PaletteQuantizer.cs | 14 +++- .../Quantization/QuantizeProcessor{TPixel}.cs | 2 +- .../Quantization/QuantizedFrame{TPixel}.cs | 4 +- .../Quantization/WuFrameQuantizer{TPixel}.cs | 10 +-- .../Formats/Gif/GifEncoderTests.cs | 18 +++++ .../Quantization/QuantizedImageTests.cs | 2 +- .../Quantization/WuQuantizerTests.cs | 8 +-- 20 files changed, 154 insertions(+), 176 deletions(-) delete mode 100644 src/ImageSharp/Processing/Processors/Quantization/IPixelMap{TPixel}.cs diff --git a/src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs b/src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs index ed5ed4293..3d5854ce5 100644 --- a/src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs +++ b/src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs @@ -336,10 +336,10 @@ namespace SixLabors.ImageSharp.Formats.Bmp private void Write8BitColor(Stream stream, ImageFrame image, Span colorPalette) where TPixel : unmanaged, IPixel { - using IFrameQuantizer quantizer = this.quantizer.CreateFrameQuantizer(this.configuration); - using QuantizedFrame quantized = quantizer.QuantizeFrame(image, image.Bounds()); + using IFrameQuantizer frameQuantizer = this.quantizer.CreateFrameQuantizer(this.configuration); + using QuantizedFrame quantized = frameQuantizer.QuantizeFrame(image, image.Bounds()); - ReadOnlySpan quantizedColors = quantized.Palette; + ReadOnlySpan quantizedColors = quantized.Palette.Span; var color = default(Rgba32); // TODO: Use bulk conversion here for better perf diff --git a/src/ImageSharp/Formats/Gif/GifEncoderCore.cs b/src/ImageSharp/Formats/Gif/GifEncoderCore.cs index 87317a3ef..dc74353e3 100644 --- a/src/ImageSharp/Formats/Gif/GifEncoderCore.cs +++ b/src/ImageSharp/Formats/Gif/GifEncoderCore.cs @@ -128,6 +128,11 @@ namespace SixLabors.ImageSharp.Formats.Gif private void EncodeGlobal(Image image, QuantizedFrame quantized, int transparencyIndex, Stream stream) where TPixel : unmanaged, IPixel { + // The palette quantizer can reuse the same pixel map across multiple frames + // since the palette is unchanging. This allows a reduction of memory usage across + // multi frame gifs using a global palette. + EuclideanPixelMap pixelMap = default; + bool pixelMapSet = false; for (int i = 0; i < image.Frames.Count; i++) { ImageFrame frame = image.Frames[i]; @@ -142,7 +147,13 @@ namespace SixLabors.ImageSharp.Formats.Gif } else { - using var paletteFrameQuantizer = new PaletteFrameQuantizer(this.configuration, this.quantizer.Options, quantized.Palette); + if (!pixelMapSet) + { + pixelMapSet = true; + pixelMap = new EuclideanPixelMap(this.configuration, quantized.Palette, quantized.Palette.Span.Length); + } + + using var paletteFrameQuantizer = new PaletteFrameQuantizer(this.configuration, this.quantizer.Options, pixelMap); using QuantizedFrame paletteQuantized = paletteFrameQuantizer.QuantizeFrame(frame, frame.Bounds()); this.WriteImageData(paletteQuantized, stream); } @@ -214,7 +225,7 @@ namespace SixLabors.ImageSharp.Formats.Gif { Span rgbaSpan = rgbaBuffer.GetSpan(); ref Rgba32 paletteRef = ref MemoryMarshal.GetReference(rgbaSpan); - PixelOperations.Instance.ToRgba32(this.configuration, quantized.Palette, rgbaSpan); + PixelOperations.Instance.ToRgba32(this.configuration, quantized.Palette.Span, rgbaSpan); for (int i = quantized.Palette.Length - 1; i >= 0; i--) { @@ -321,8 +332,9 @@ namespace SixLabors.ImageSharp.Formats.Gif return; } - foreach (string comment in metadata.Comments) + for (var i = 0; i < metadata.Comments.Count; i++) { + string comment = metadata.Comments[i]; this.buffer[0] = GifConstants.ExtensionIntroducer; this.buffer[1] = GifConstants.CommentLabel; stream.Write(this.buffer, 0, 2); @@ -330,7 +342,9 @@ namespace SixLabors.ImageSharp.Formats.Gif // Comment will be stored in chunks of 255 bytes, if it exceeds this size. ReadOnlySpan commentSpan = comment.AsSpan(); int idx = 0; - for (; idx <= comment.Length - GifConstants.MaxCommentSubBlockLength; idx += GifConstants.MaxCommentSubBlockLength) + for (; + idx <= comment.Length - GifConstants.MaxCommentSubBlockLength; + idx += GifConstants.MaxCommentSubBlockLength) { WriteCommentSubBlock(stream, commentSpan, idx, GifConstants.MaxCommentSubBlockLength); } @@ -443,7 +457,7 @@ namespace SixLabors.ImageSharp.Formats.Gif using IManagedByteBuffer colorTable = this.memoryAllocator.AllocateManagedByteBuffer(colorTableLength); PixelOperations.Instance.ToRgb24Bytes( this.configuration, - image.Palette, + image.Palette.Span, colorTable.GetSpan(), pixelCount); stream.Write(colorTable.Array, 0, colorTableLength); diff --git a/src/ImageSharp/Formats/Png/PngEncoderCore.cs b/src/ImageSharp/Formats/Png/PngEncoderCore.cs index ed2fe143b..ce624f768 100644 --- a/src/ImageSharp/Formats/Png/PngEncoderCore.cs +++ b/src/ImageSharp/Formats/Png/PngEncoderCore.cs @@ -555,7 +555,7 @@ namespace SixLabors.ImageSharp.Formats.Png } // Grab the palette and write it to the stream. - ReadOnlySpan palette = quantized.Palette; + ReadOnlySpan palette = quantized.Palette.Span; int paletteLength = Math.Min(palette.Length, 256); int colorTableLength = paletteLength * 3; bool anyAlpha = false; diff --git a/src/ImageSharp/Processing/Processors/Dithering/ErrorDither.cs b/src/ImageSharp/Processing/Processors/Dithering/ErrorDither.cs index 6aa6eeca6..9d0c563da 100644 --- a/src/ImageSharp/Processing/Processors/Dithering/ErrorDither.cs +++ b/src/ImageSharp/Processing/Processors/Dithering/ErrorDither.cs @@ -4,6 +4,7 @@ using System; using System.Numerics; using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing.Processors.Quantization; @@ -95,20 +96,19 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering where TFrameQuantizer : struct, IFrameQuantizer where TPixel : unmanaged, IPixel { - ReadOnlySpan paletteSpan = destination.Palette; int offsetY = bounds.Top; int offsetX = bounds.Left; float scale = quantizer.Options.DitherScale; for (int y = bounds.Top; y < bounds.Bottom; y++) { - Span sourceRow = source.GetPixelRowSpan(y); - Span destinationRow = destination.GetPixelRowSpan(y - offsetY); + ref TPixel sourceRowRef = ref MemoryMarshal.GetReference(source.GetPixelRowSpan(y)); + ref byte destinationRowRef = ref MemoryMarshal.GetReference(destination.GetPixelRowSpan(y - offsetY)); for (int x = bounds.Left; x < bounds.Right; x++) { - TPixel sourcePixel = sourceRow[x]; - destinationRow[x - offsetX] = quantizer.GetQuantizedColor(sourcePixel, paletteSpan, out TPixel transformed); + TPixel sourcePixel = Unsafe.Add(ref sourceRowRef, x); + Unsafe.Add(ref destinationRowRef, x - offsetX) = quantizer.GetQuantizedColor(sourcePixel, out TPixel transformed); this.Dither(source, bounds, sourcePixel, transformed, x, y, scale); } } @@ -124,16 +124,15 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering where TPixel : unmanaged, IPixel { float scale = processor.DitherScale; - ReadOnlySpan palette = processor.Palette.Span; for (int y = bounds.Top; y < bounds.Bottom; y++) { - Span row = source.GetPixelRowSpan(y); + ref TPixel sourceRowRef = ref MemoryMarshal.GetReference(source.GetPixelRowSpan(y)); for (int x = bounds.Left; x < bounds.Right; x++) { - TPixel sourcePixel = row[x]; - TPixel transformed = Unsafe.AsRef(processor).GetPaletteColor(sourcePixel, palette); + ref TPixel sourcePixel = ref Unsafe.Add(ref sourceRowRef, x); + TPixel transformed = Unsafe.AsRef(processor).GetPaletteColor(sourcePixel); this.Dither(source, bounds, sourcePixel, transformed, x, y, scale); - row[x] = transformed; + sourcePixel = transformed; } } } diff --git a/src/ImageSharp/Processing/Processors/Dithering/IPaletteDitherImageProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Dithering/IPaletteDitherImageProcessor{TPixel}.cs index a890e929d..a8e08fa3f 100644 --- a/src/ImageSharp/Processing/Processors/Dithering/IPaletteDitherImageProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Dithering/IPaletteDitherImageProcessor{TPixel}.cs @@ -32,8 +32,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering /// Returns the color from the dithering palette corresponding to the given color. /// /// The color to match. - /// The output color palette. /// The match. - TPixel GetPaletteColor(TPixel color, ReadOnlySpan palette); + TPixel GetPaletteColor(TPixel color); } } diff --git a/src/ImageSharp/Processing/Processors/Dithering/OrderedDither.cs b/src/ImageSharp/Processing/Processors/Dithering/OrderedDither.cs index 9e97fe7e6..64fe230f3 100644 --- a/src/ImageSharp/Processing/Processors/Dithering/OrderedDither.cs +++ b/src/ImageSharp/Processing/Processors/Dithering/OrderedDither.cs @@ -3,6 +3,7 @@ using System; using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; @@ -224,20 +225,19 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering [MethodImpl(InliningOptions.ShortMethod)] public void Invoke(in RowInterval rows) { - ReadOnlySpan paletteSpan = this.destination.Palette; int offsetY = this.bounds.Top; int offsetX = this.bounds.Left; float scale = this.quantizer.Options.DitherScale; for (int y = rows.Min; y < rows.Max; y++) { - Span sourceRow = this.source.GetPixelRowSpan(y); - Span destinationRow = this.destination.GetPixelRowSpan(y - offsetY); + ref TPixel sourceRowRef = ref MemoryMarshal.GetReference(this.source.GetPixelRowSpan(y)); + ref byte destinationRowRef = ref MemoryMarshal.GetReference(this.destination.GetPixelRowSpan(y - offsetY)); for (int x = this.bounds.Left; x < this.bounds.Right; x++) { - TPixel dithered = this.dither.Dither(sourceRow[x], x, y, this.bitDepth, scale); - destinationRow[x - offsetX] = Unsafe.AsRef(this.quantizer).GetQuantizedColor(dithered, paletteSpan, out TPixel _); + TPixel dithered = this.dither.Dither(Unsafe.Add(ref sourceRowRef, x), x, y, this.bitDepth, scale); + Unsafe.Add(ref destinationRowRef, x - offsetX) = Unsafe.AsRef(this.quantizer).GetQuantizedColor(dithered, out TPixel _); } } } @@ -272,16 +272,15 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering [MethodImpl(InliningOptions.ShortMethod)] public void Invoke(in RowInterval rows) { - ReadOnlySpan paletteSpan = this.processor.Palette.Span; for (int y = rows.Min; y < rows.Max; y++) { - Span row = this.source.GetPixelRowSpan(y); + ref TPixel sourceRowRef = ref MemoryMarshal.GetReference(this.source.GetPixelRowSpan(y)); for (int x = this.bounds.Left; x < this.bounds.Right; x++) { - ref TPixel sourcePixel = ref row[x]; + ref TPixel sourcePixel = ref Unsafe.Add(ref sourceRowRef, x); TPixel dithered = this.dither.Dither(sourcePixel, x, y, this.bitDepth, this.scale); - sourcePixel = Unsafe.AsRef(this.processor).GetPaletteColor(dithered, paletteSpan); + sourcePixel = Unsafe.AsRef(this.processor).GetPaletteColor(dithered); } } } diff --git a/src/ImageSharp/Processing/Processors/Dithering/PaletteDitherProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Dithering/PaletteDitherProcessor{TPixel}.cs index 4d4ccf1ab..6b5ffabf4 100644 --- a/src/ImageSharp/Processing/Processors/Dithering/PaletteDitherProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Dithering/PaletteDitherProcessor{TPixel}.cs @@ -4,6 +4,7 @@ using System; using System.Buffers; using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing.Processors.Quantization; @@ -39,6 +40,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering this.ditherProcessor = new DitherProcessor( this.Configuration, + Rectangle.Intersect(this.SourceRectangle, source.Bounds()), this.paletteMemory.Memory, definition.DitherScale); } @@ -71,7 +73,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering /// /// Used to allow inlining of calls to - /// . + /// . /// private readonly struct DitherProcessor : IPaletteDitherImageProcessor { @@ -80,11 +82,12 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering [MethodImpl(InliningOptions.ShortMethod)] public DitherProcessor( Configuration configuration, + Rectangle bounds, ReadOnlyMemory palette, float ditherScale) { this.Configuration = configuration; - this.pixelMap = new EuclideanPixelMap(configuration, palette); + this.pixelMap = new EuclideanPixelMap(configuration, palette, palette.Span.Length); this.Palette = palette; this.DitherScale = ditherScale; } @@ -96,7 +99,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering public float DitherScale { get; } [MethodImpl(InliningOptions.ShortMethod)] - public TPixel GetPaletteColor(TPixel color, ReadOnlySpan palette) + public TPixel GetPaletteColor(TPixel color) { this.pixelMap.GetClosestColor(color, out TPixel match); return match; diff --git a/src/ImageSharp/Processing/Processors/Quantization/EuclideanPixelMap{TPixel}.cs b/src/ImageSharp/Processing/Processors/Quantization/EuclideanPixelMap{TPixel}.cs index 714788629..6c23ba356 100644 --- a/src/ImageSharp/Processing/Processors/Quantization/EuclideanPixelMap{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Quantization/EuclideanPixelMap{TPixel}.cs @@ -2,88 +2,86 @@ // Licensed under the Apache License, Version 2.0. using System; +using System.Buffers; using System.Collections.Concurrent; using System.Numerics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; +using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Processing.Processors.Quantization { /// /// Gets the closest color to the supplied color based upon the Euclidean distance. - /// TODO: Expose this somehow. /// /// The pixel format. - internal readonly struct EuclideanPixelMap : IPixelMap, IEquatable> + internal readonly struct EuclideanPixelMap where TPixel : unmanaged, IPixel { private readonly Vector4[] vectorCache; private readonly ConcurrentDictionary distanceCache; + private readonly ReadOnlyMemory palette; + private readonly int length; /// /// Initializes a new instance of the struct. /// /// The configuration. /// The color palette to map from. + /// The length of the color palette. [MethodImpl(InliningOptions.ShortMethod)] - public EuclideanPixelMap(Configuration configuration, ReadOnlyMemory palette) + public EuclideanPixelMap(Configuration configuration, ReadOnlyMemory palette, int length) { - Guard.MustBeGreaterThan(palette.Length, 0, nameof(palette)); - - this.Palette = palette; - ReadOnlySpan paletteSpan = this.Palette.Span; - this.vectorCache = new Vector4[paletteSpan.Length]; - this.distanceCache = new ConcurrentDictionary(); + this.palette = palette; + this.length = length; + ReadOnlySpan paletteSpan = this.palette.Span.Slice(0, this.length); + this.vectorCache = new Vector4[length]; + // Use the same rules across all target frameworks. + this.distanceCache = new ConcurrentDictionary(Environment.ProcessorCount, 31); PixelOperations.Instance.ToVector4(configuration, paletteSpan, this.vectorCache); } - /// - public ReadOnlyMemory Palette - { - [MethodImpl(InliningOptions.ShortMethod)] - get; - } - - /// - public override bool Equals(object obj) - => obj is EuclideanPixelMap map && this.Equals(map); - - /// - public bool Equals(EuclideanPixelMap other) - => this.Palette.Equals(other.Palette); + /// + /// Returns the palette span. + /// + /// The . + [MethodImpl(InliningOptions.ShortMethod)] + public ReadOnlySpan GetPaletteSpan() => this.palette.Span.Slice(0, this.length); - /// + /// + /// Returns the closest color in the palette and the index of that pixel. + /// The palette contents must match the one used in the constructor. + /// + /// The color to match. + /// The matched color. + /// The index. [MethodImpl(InliningOptions.ShortMethod)] public int GetClosestColor(TPixel color, out TPixel match) { - ReadOnlySpan paletteSpan = this.Palette.Span; + ref TPixel paletteRef = ref MemoryMarshal.GetReference(this.GetPaletteSpan()); // Check if the color is in the lookup table if (!this.distanceCache.TryGetValue(color, out int index)) { - return this.GetClosestColorSlow(color, paletteSpan, out match); + return this.GetClosestColorSlow(color, ref paletteRef, out match); } - match = paletteSpan[index]; + match = Unsafe.Add(ref paletteRef, index); return index; } - /// - public override int GetHashCode() => this.vectorCache.GetHashCode(); - [MethodImpl(InliningOptions.ShortMethod)] - private int GetClosestColorSlow(TPixel color, ReadOnlySpan palette, out TPixel match) + private int GetClosestColorSlow(TPixel color, ref TPixel paletteRef, out TPixel match) { // Loop through the palette and find the nearest match. int index = 0; float leastDistance = float.MaxValue; - Vector4 vector = color.ToScaledVector4(); - ref TPixel paletteRef = ref MemoryMarshal.GetReference(palette); + var vector = color.ToVector4(); ref Vector4 vectorCacheRef = ref MemoryMarshal.GetReference(this.vectorCache); - for (int i = 0; i < palette.Length; i++) + for (int i = 0; i < this.length; i++) { Vector4 candidate = Unsafe.Add(ref vectorCacheRef, i); float distance = Vector4.DistanceSquared(vector, candidate); @@ -108,5 +106,5 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization match = Unsafe.Add(ref paletteRef, index); return index; } - } + } } diff --git a/src/ImageSharp/Processing/Processors/Quantization/FrameQuantizerExtensions.cs b/src/ImageSharp/Processing/Processors/Quantization/FrameQuantizerExtensions.cs index ef97f57e3..f695a705e 100644 --- a/src/ImageSharp/Processing/Processors/Quantization/FrameQuantizerExtensions.cs +++ b/src/ImageSharp/Processing/Processors/Quantization/FrameQuantizerExtensions.cs @@ -3,6 +3,7 @@ using System; using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; @@ -40,20 +41,20 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization ReadOnlySpan palette = quantizer.BuildPalette(source, interest); MemoryAllocator memoryAllocator = quantizer.Configuration.MemoryAllocator; - var quantizedFrame = new QuantizedFrame(memoryAllocator, interest.Width, interest.Height, palette); + var destination = new QuantizedFrame(memoryAllocator, interest.Width, interest.Height, palette); if (quantizer.Options.Dither is null) { - SecondPass(ref quantizer, source, quantizedFrame, interest); + SecondPass(ref quantizer, source, destination, interest); } else { // We clone the image as we don't want to alter the original via error diffusion based dithering. using ImageFrame clone = source.Clone(); - SecondPass(ref quantizer, clone, quantizedFrame, interest); + SecondPass(ref quantizer, clone, destination, interest); } - return quantizedFrame; + return destination; } [MethodImpl(InliningOptions.ShortMethod)] @@ -106,7 +107,6 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization [MethodImpl(InliningOptions.ShortMethod)] public void Invoke(in RowInterval rows) { - ReadOnlySpan paletteSpan = this.destination.Palette; int offsetY = this.bounds.Top; int offsetX = this.bounds.Left; @@ -117,7 +117,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization for (int x = this.bounds.Left; x < this.bounds.Right; x++) { - destinationRow[x - offsetX] = Unsafe.AsRef(this.quantizer).GetQuantizedColor(sourceRow[x], paletteSpan, out TPixel _); + destinationRow[x - offsetX] = Unsafe.AsRef(this.quantizer).GetQuantizedColor(sourceRow[x], out TPixel _); } } } diff --git a/src/ImageSharp/Processing/Processors/Quantization/IFrameQuantizer{TPixel}.cs b/src/ImageSharp/Processing/Processors/Quantization/IFrameQuantizer{TPixel}.cs index 506767277..d49852cf1 100644 --- a/src/ImageSharp/Processing/Processors/Quantization/IFrameQuantizer{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Quantization/IFrameQuantizer{TPixel}.cs @@ -45,10 +45,9 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization /// Returns the index and color from the quantized palette corresponding to the given color. /// /// The color to match. - /// The output color palette. /// The matched color. /// The index. - byte GetQuantizedColor(TPixel color, ReadOnlySpan palette, out TPixel match); + byte GetQuantizedColor(TPixel color, out TPixel match); // TODO: Enable bulk operations. // void GetQuantizedColors(ReadOnlySpan colors, ReadOnlySpan palette, Span indices, Span matches); diff --git a/src/ImageSharp/Processing/Processors/Quantization/IPixelMap{TPixel}.cs b/src/ImageSharp/Processing/Processors/Quantization/IPixelMap{TPixel}.cs deleted file mode 100644 index b421dce21..000000000 --- a/src/ImageSharp/Processing/Processors/Quantization/IPixelMap{TPixel}.cs +++ /dev/null @@ -1,30 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using System; -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Processing.Processors.Quantization -{ - /// - /// Allows the mapping of input colors to colors within a given palette. - /// TODO: Expose this somehow. - /// - /// The pixel format. - internal interface IPixelMap - where TPixel : unmanaged, IPixel - { - /// - /// Gets the color palette containing colors to match. - /// - ReadOnlyMemory Palette { get; } - - /// - /// Returns the closest color in the palette and the index of that pixel. - /// - /// The color to match. - /// The matched color. - /// The index. - int GetClosestColor(TPixel color, out TPixel match); - } -} diff --git a/src/ImageSharp/Processing/Processors/Quantization/OctreeFrameQuantizer{TPixel}.cs b/src/ImageSharp/Processing/Processors/Quantization/OctreeFrameQuantizer{TPixel}.cs index e80449b09..cc6a3a485 100644 --- a/src/ImageSharp/Processing/Processors/Quantization/OctreeFrameQuantizer{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Quantization/OctreeFrameQuantizer{TPixel}.cs @@ -5,6 +5,7 @@ using System; using System.Buffers; using System.Numerics; using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; @@ -82,29 +83,32 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization } Span paletteSpan = this.palette.GetSpan(); - this.octree.Palletize(paletteSpan, this.colors); + int paletteIndex = 0; + this.octree.Palletize(paletteSpan, this.colors, ref paletteIndex); - // TODO: Cannot make method readonly due to this line. - this.pixelMap = new EuclideanPixelMap(this.Configuration, this.palette.Memory); + // Length of reduced palette + transparency. + paletteSpan = paletteSpan.Slice(0, Math.Min(paletteIndex + 2, QuantizerConstants.MaxColors)); + this.pixelMap = new EuclideanPixelMap(this.Configuration, this.palette.Memory, paletteSpan.Length); return paletteSpan; } /// [MethodImpl(InliningOptions.ShortMethod)] - public readonly byte GetQuantizedColor(TPixel color, ReadOnlySpan palette, out TPixel match) + public readonly byte GetQuantizedColor(TPixel color, out TPixel match) { // Octree only maps the RGB component of a color // so cannot tell the difference between a fully transparent // pixel and a black one. - if (!this.isDithering && !color.Equals(default)) + if (this.isDithering || color.Equals(default)) { - var index = (byte)this.octree.GetPaletteIndex(color); - match = palette[index]; - return index; + return (byte)this.pixelMap.GetClosestColor(color, out match); } - return (byte)this.pixelMap.GetClosestColor(color, out match); + ref TPixel paletteRef = ref MemoryMarshal.GetReference(this.pixelMap.GetPaletteSpan()); + var index = (byte)this.octree.GetPaletteIndex(color); + match = Unsafe.Add(ref paletteRef, index); + return index; } /// @@ -223,15 +227,15 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization /// /// The palette to fill. /// The maximum number of colors + /// The palette index, used to calculate the final size of the palette. [MethodImpl(InliningOptions.ShortMethod)] - public void Palletize(Span palette, int colorCount) + public void Palletize(Span palette, int colorCount, ref int paletteIndex) { while (this.Leaves > colorCount - 1) { this.Reduce(); } - int paletteIndex = 0; this.root.ConstructPalette(palette, ref paletteIndex); } diff --git a/src/ImageSharp/Processing/Processors/Quantization/PaletteFrameQuantizer{TPixel}.cs b/src/ImageSharp/Processing/Processors/Quantization/PaletteFrameQuantizer{TPixel}.cs index 3dbf77a3a..11570beef 100644 --- a/src/ImageSharp/Processing/Processors/Quantization/PaletteFrameQuantizer{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Quantization/PaletteFrameQuantizer{TPixel}.cs @@ -4,6 +4,7 @@ using System; using System.Buffers; using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; @@ -17,54 +18,26 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization internal struct PaletteFrameQuantizer : IFrameQuantizer where TPixel : unmanaged, IPixel { - private IMemoryOwner paletteOwner; private readonly EuclideanPixelMap pixelMap; - private bool isDisposed; /// /// Initializes a new instance of the struct. /// /// The configuration which allows altering default behaviour or extending the library. /// The quantizer options defining quantization rules. - /// A containing all colors in the palette. + /// The pixel map for looking up color matches from a predefined palette. [MethodImpl(InliningOptions.ShortMethod)] - public PaletteFrameQuantizer(Configuration configuration, QuantizerOptions options, ReadOnlySpan colors) + public PaletteFrameQuantizer( + Configuration configuration, + QuantizerOptions options, + EuclideanPixelMap pixelMap) { Guard.NotNull(configuration, nameof(configuration)); Guard.NotNull(options, nameof(options)); this.Configuration = configuration; this.Options = options; - - int maxLength = Math.Min(colors.Length, options.MaxColors); - this.paletteOwner = configuration.MemoryAllocator.Allocate(maxLength); - Color.ToPixel(configuration, colors, this.paletteOwner.GetSpan()); - - this.pixelMap = new EuclideanPixelMap(configuration, this.paletteOwner.Memory); - this.isDisposed = false; - } - - /// - /// Initializes a new instance of the struct. - /// - /// The configuration which allows altering default behaviour or extending the library. - /// The quantizer options defining quantization rules. - /// A containing all colors in the palette. - [MethodImpl(InliningOptions.ShortMethod)] - public PaletteFrameQuantizer(Configuration configuration, QuantizerOptions options, ReadOnlySpan palette) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.NotNull(options, nameof(options)); - - this.Configuration = configuration; - this.Options = options; - - int maxLength = Math.Min(palette.Length, options.MaxColors); - this.paletteOwner = configuration.MemoryAllocator.Allocate(maxLength); - palette.CopyTo(this.paletteOwner.GetSpan()); - - this.pixelMap = new EuclideanPixelMap(configuration, this.paletteOwner.Memory); - this.isDisposed = false; + this.pixelMap = pixelMap; } /// @@ -81,24 +54,16 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization /// [MethodImpl(InliningOptions.ShortMethod)] public readonly ReadOnlySpan BuildPalette(ImageFrame source, Rectangle bounds) - => this.paletteOwner.GetSpan(); + => this.pixelMap.GetPaletteSpan(); /// [MethodImpl(InliningOptions.ShortMethod)] - public readonly byte GetQuantizedColor(TPixel color, ReadOnlySpan palette, out TPixel match) + public readonly byte GetQuantizedColor(TPixel color, out TPixel match) => (byte)this.pixelMap.GetClosestColor(color, out match); /// public void Dispose() { - if (this.isDisposed) - { - return; - } - - this.isDisposed = true; - this.paletteOwner.Dispose(); - this.paletteOwner = null; } } } diff --git a/src/ImageSharp/Processing/Processors/Quantization/PaletteQuantizer.cs b/src/ImageSharp/Processing/Processors/Quantization/PaletteQuantizer.cs index e95f8c5db..7bae8787b 100644 --- a/src/ImageSharp/Processing/Processors/Quantization/PaletteQuantizer.cs +++ b/src/ImageSharp/Processing/Processors/Quantization/PaletteQuantizer.cs @@ -12,7 +12,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization public class PaletteQuantizer : IQuantizer { private static readonly QuantizerOptions DefaultOptions = new QuantizerOptions(); - private readonly ReadOnlyMemory palette; + private readonly ReadOnlyMemory colorPalette; /// /// Initializes a new instance of the class. @@ -33,7 +33,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization Guard.MustBeGreaterThan(palette.Length, 0, nameof(palette)); Guard.NotNull(options, nameof(options)); - this.palette = palette; + this.colorPalette = palette; this.Options = options; } @@ -50,7 +50,15 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization where TPixel : unmanaged, IPixel { Guard.NotNull(options, nameof(options)); - return new PaletteFrameQuantizer(configuration, options, this.palette.Span); + + // The palette quantizer can reuse the same pixel map across multiple frames + // since the palette is unchanging. This allows a reduction of memory usage across + // multi frame gifs using a global palette. + int length = Math.Min(this.colorPalette.Length, options.MaxColors); + var palette = new TPixel[length]; + Color.ToPixel(configuration, this.colorPalette.Span, palette.AsSpan()); + var pixelMap = new EuclideanPixelMap(configuration, palette, length); + return new PaletteFrameQuantizer(configuration, options, pixelMap); } } } diff --git a/src/ImageSharp/Processing/Processors/Quantization/QuantizeProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Quantization/QuantizeProcessor{TPixel}.cs index 04586807e..5a0116a03 100644 --- a/src/ImageSharp/Processing/Processors/Quantization/QuantizeProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Quantization/QuantizeProcessor{TPixel}.cs @@ -69,7 +69,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization public void Invoke(in RowInterval rows) { ReadOnlySpan quantizedPixelSpan = this.quantized.GetPixelSpan(); - ReadOnlySpan paletteSpan = this.quantized.Palette; + ReadOnlySpan paletteSpan = this.quantized.Palette.Span; int offsetY = this.bounds.Top; int offsetX = this.bounds.Left; int width = this.bounds.Width; diff --git a/src/ImageSharp/Processing/Processors/Quantization/QuantizedFrame{TPixel}.cs b/src/ImageSharp/Processing/Processors/Quantization/QuantizedFrame{TPixel}.cs index cda0546e4..d5facbe63 100644 --- a/src/ImageSharp/Processing/Processors/Quantization/QuantizedFrame{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Quantization/QuantizedFrame{TPixel}.cs @@ -53,10 +53,10 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization /// /// Gets the color palette of this . /// - public ReadOnlySpan Palette + public ReadOnlyMemory Palette { [MethodImpl(InliningOptions.ShortMethod)] - get { return this.palette.GetSpan(); } + get { return this.palette.Memory; } } /// diff --git a/src/ImageSharp/Processing/Processors/Quantization/WuFrameQuantizer{TPixel}.cs b/src/ImageSharp/Processing/Processors/Quantization/WuFrameQuantizer{TPixel}.cs index 6f98ce121..f50282f9a 100644 --- a/src/ImageSharp/Processing/Processors/Quantization/WuFrameQuantizer{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Quantization/WuFrameQuantizer{TPixel}.cs @@ -5,6 +5,7 @@ using System; using System.Buffers; using System.Numerics; using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; @@ -131,13 +132,13 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization } } - // TODO: Cannot make methods readonly due to this line. - this.pixelMap = new EuclideanPixelMap(this.Configuration, this.palette.Memory); + paletteSpan = paletteSpan.Slice(0, this.colors); + this.pixelMap = new EuclideanPixelMap(this.Configuration, this.palette.Memory, paletteSpan.Length); return paletteSpan; } /// - public readonly byte GetQuantizedColor(TPixel color, ReadOnlySpan palette, out TPixel match) + public readonly byte GetQuantizedColor(TPixel color, out TPixel match) { if (this.isDithering) { @@ -154,7 +155,8 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization ReadOnlySpan tagSpan = this.tag.GetSpan(); byte index = tagSpan[GetPaletteIndex(r + 1, g + 1, b + 1, a + 1)]; - match = palette[index]; + ref TPixel paletteRef = ref MemoryMarshal.GetReference(this.pixelMap.GetPaletteSpan()); + match = Unsafe.Add(ref paletteRef, index); return index; } diff --git a/tests/ImageSharp.Tests/Formats/Gif/GifEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Gif/GifEncoderTests.cs index 588f65254..4adffca4f 100644 --- a/tests/ImageSharp.Tests/Formats/Gif/GifEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Gif/GifEncoderTests.cs @@ -5,6 +5,7 @@ using System.IO; using SixLabors.ImageSharp.Formats.Gif; using SixLabors.ImageSharp.Metadata; using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Processing.Processors.Quantization; using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; using Xunit; @@ -25,6 +26,23 @@ namespace SixLabors.ImageSharp.Tests.Formats.Gif { TestImages.Gif.Ratio4x1, 4, 1, PixelResolutionUnit.AspectRatio } }; + [Theory] + [WithFile(TestImages.Bmp.Car, PixelTypes.Rgba32)] + public void EncodeAllocationCheck(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + GifEncoder encoder = new GifEncoder + { + Quantizer = new WebSafePaletteQuantizer(new QuantizerOptions { Dither = KnownDitherings.Bayer4x4 }) + }; + + using (Image image = provider.GetImage()) + { + // Always save as we need to compare the encoded output. + provider.Utility.SaveTestOutputFile(image, "gif", encoder); + } + } + [Theory] [WithTestPatternImages(100, 100, TestPixelTypes, false)] [WithTestPatternImages(100, 100, TestPixelTypes, false)] diff --git a/tests/ImageSharp.Tests/Quantization/QuantizedImageTests.cs b/tests/ImageSharp.Tests/Quantization/QuantizedImageTests.cs index 4085d9943..cd93ab0cf 100644 --- a/tests/ImageSharp.Tests/Quantization/QuantizedImageTests.cs +++ b/tests/ImageSharp.Tests/Quantization/QuantizedImageTests.cs @@ -116,7 +116,7 @@ namespace SixLabors.ImageSharp.Tests // Transparent pixels are much more likely to be found at the end of a palette int index = -1; Rgba32 trans = default; - ReadOnlySpan paletteSpan = quantized.Palette; + ReadOnlySpan paletteSpan = quantized.Palette.Span; for (int i = paletteSpan.Length - 1; i >= 0; i--) { paletteSpan[i].ToRgba32(ref trans); diff --git a/tests/ImageSharp.Tests/Quantization/WuQuantizerTests.cs b/tests/ImageSharp.Tests/Quantization/WuQuantizerTests.cs index 34888f1db..f3bcd0b95 100644 --- a/tests/ImageSharp.Tests/Quantization/WuQuantizerTests.cs +++ b/tests/ImageSharp.Tests/Quantization/WuQuantizerTests.cs @@ -26,7 +26,7 @@ namespace SixLabors.ImageSharp.Tests.Quantization Assert.Equal(1, result.Palette.Length); Assert.Equal(1, result.GetPixelSpan().Length); - Assert.Equal(Color.Black, (Color)result.Palette[0]); + Assert.Equal(Color.Black, (Color)result.Palette.Span[0]); Assert.Equal(0, result.GetPixelSpan()[0]); } @@ -45,7 +45,7 @@ namespace SixLabors.ImageSharp.Tests.Quantization Assert.Equal(1, result.Palette.Length); Assert.Equal(1, result.GetPixelSpan().Length); - Assert.Equal(default, result.Palette[0]); + Assert.Equal(default, result.Palette.Span[0]); Assert.Equal(0, result.GetPixelSpan()[0]); } @@ -92,7 +92,7 @@ namespace SixLabors.ImageSharp.Tests.Quantization var actualImage = new Image(1, 256); - ReadOnlySpan paletteSpan = result.Palette; + ReadOnlySpan paletteSpan = result.Palette.Span; int paletteCount = result.Palette.Length - 1; for (int y = 0; y < actualImage.Height; y++) { @@ -157,7 +157,7 @@ namespace SixLabors.ImageSharp.Tests.Quantization Assert.Equal(4 * 8, result.Palette.Length); Assert.Equal(256, result.GetPixelSpan().Length); - ReadOnlySpan paletteSpan = result.Palette; + ReadOnlySpan paletteSpan = result.Palette.Span; int paletteCount = result.Palette.Length - 1; for (int y = 0; y < actualImage.Height; y++) { From f73ac898567a58520a24354556a68e3f13014d14 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Wed, 4 Mar 2020 00:25:22 +1100 Subject: [PATCH 50/64] Cleanup and update references. --- .../Processors/Quantization/EuclideanPixelMap{TPixel}.cs | 4 +--- .../Processors/Quantization/FrameQuantizerExtensions.cs | 1 - .../Processors/Quantization/PaletteFrameQuantizer{TPixel}.cs | 3 --- .../Processing/Processors/Quantization/PaletteQuantizer.cs | 2 ++ tests/Images/External | 2 +- 5 files changed, 4 insertions(+), 8 deletions(-) diff --git a/src/ImageSharp/Processing/Processors/Quantization/EuclideanPixelMap{TPixel}.cs b/src/ImageSharp/Processing/Processors/Quantization/EuclideanPixelMap{TPixel}.cs index 6c23ba356..84a204bba 100644 --- a/src/ImageSharp/Processing/Processors/Quantization/EuclideanPixelMap{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Quantization/EuclideanPixelMap{TPixel}.cs @@ -2,12 +2,10 @@ // Licensed under the Apache License, Version 2.0. using System; -using System.Buffers; using System.Collections.Concurrent; using System.Numerics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Processing.Processors.Quantization @@ -106,5 +104,5 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization match = Unsafe.Add(ref paletteRef, index); return index; } - } + } } diff --git a/src/ImageSharp/Processing/Processors/Quantization/FrameQuantizerExtensions.cs b/src/ImageSharp/Processing/Processors/Quantization/FrameQuantizerExtensions.cs index f695a705e..139ed6e9e 100644 --- a/src/ImageSharp/Processing/Processors/Quantization/FrameQuantizerExtensions.cs +++ b/src/ImageSharp/Processing/Processors/Quantization/FrameQuantizerExtensions.cs @@ -3,7 +3,6 @@ using System; using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; diff --git a/src/ImageSharp/Processing/Processors/Quantization/PaletteFrameQuantizer{TPixel}.cs b/src/ImageSharp/Processing/Processors/Quantization/PaletteFrameQuantizer{TPixel}.cs index 11570beef..a9a938562 100644 --- a/src/ImageSharp/Processing/Processors/Quantization/PaletteFrameQuantizer{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Quantization/PaletteFrameQuantizer{TPixel}.cs @@ -2,10 +2,7 @@ // Licensed under the Apache License, Version 2.0. using System; -using System.Buffers; using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Processing.Processors.Quantization diff --git a/src/ImageSharp/Processing/Processors/Quantization/PaletteQuantizer.cs b/src/ImageSharp/Processing/Processors/Quantization/PaletteQuantizer.cs index 7bae8787b..e856c389c 100644 --- a/src/ImageSharp/Processing/Processors/Quantization/PaletteQuantizer.cs +++ b/src/ImageSharp/Processing/Processors/Quantization/PaletteQuantizer.cs @@ -56,7 +56,9 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization // multi frame gifs using a global palette. int length = Math.Min(this.colorPalette.Length, options.MaxColors); var palette = new TPixel[length]; + Color.ToPixel(configuration, this.colorPalette.Span, palette.AsSpan()); + var pixelMap = new EuclideanPixelMap(configuration, palette, length); return new PaletteFrameQuantizer(configuration, options, pixelMap); } diff --git a/tests/Images/External b/tests/Images/External index f8a76fd3a..1fea1ceab 160000 --- a/tests/Images/External +++ b/tests/Images/External @@ -1 +1 @@ -Subproject commit f8a76fd3a900b90c98df67ac896574383a4d09f3 +Subproject commit 1fea1ceab89e87cc5f11376fa46164d3d27566c0 From 171e629ac941062a4a17c514b6eca112c1a0f546 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Wed, 4 Mar 2020 00:51:41 +0100 Subject: [PATCH 51/64] extend EncodeGif benchmark --- tests/ImageSharp.Benchmarks/Codecs/EncodeGif.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tests/ImageSharp.Benchmarks/Codecs/EncodeGif.cs b/tests/ImageSharp.Benchmarks/Codecs/EncodeGif.cs index 71405890c..70c85ef02 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/EncodeGif.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/EncodeGif.cs @@ -27,12 +27,15 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs Quantizer = new WebSafePaletteQuantizer(new QuantizerOptions { Dither = KnownDitherings.Bayer4x4 }) }; + [Params(TestImages.Bmp.Car, TestImages.Png.Rgb48Bpp)] + public string TestImage { get; set; } + [GlobalSetup] public void ReadImages() { if (this.bmpStream == null) { - this.bmpStream = File.OpenRead(Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, TestImages.Bmp.Car)); + this.bmpStream = File.OpenRead(Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, this.TestImage)); this.bmpCore = Image.Load(this.bmpStream); this.bmpStream.Position = 0; this.bmpDrawing = SDImage.FromStream(this.bmpStream); From 7803585ab3ee1b76601456e527fcade9ebeb7d35 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Wed, 4 Mar 2020 01:34:27 +0100 Subject: [PATCH 52/64] Revert "Merge branch 'af/fast-dev-hack' into js/dither-quantize-updates" This reverts commit 87327172656a9898690ca3e91a0907d8799b7900, reversing changes made to d9596ef33b213fd65210e2c6789be3d61188d2c0. --- src/ImageSharp/ImageSharp.csproj | 3 +-- tests/ImageSharp.Benchmarks/ImageSharp.Benchmarks.csproj | 3 +-- .../ImageSharp.Tests.ProfilingSandbox.csproj | 3 +-- tests/ImageSharp.Tests/ImageSharp.Tests.csproj | 3 +-- 4 files changed, 4 insertions(+), 8 deletions(-) diff --git a/src/ImageSharp/ImageSharp.csproj b/src/ImageSharp/ImageSharp.csproj index 0d803475a..be0e9032b 100644 --- a/src/ImageSharp/ImageSharp.csproj +++ b/src/ImageSharp/ImageSharp.csproj @@ -10,8 +10,7 @@ $(packageversion) 0.0.1 - - netcoreapp3.1 + netcoreapp3.1;netcoreapp2.1;netstandard2.1;netstandard2.0;netstandard1.3;net472 true true diff --git a/tests/ImageSharp.Benchmarks/ImageSharp.Benchmarks.csproj b/tests/ImageSharp.Benchmarks/ImageSharp.Benchmarks.csproj index 101d27956..f380d0a6a 100644 --- a/tests/ImageSharp.Benchmarks/ImageSharp.Benchmarks.csproj +++ b/tests/ImageSharp.Benchmarks/ImageSharp.Benchmarks.csproj @@ -5,8 +5,7 @@ ImageSharp.Benchmarks Exe SixLabors.ImageSharp.Benchmarks - - netcoreapp3.1 + netcoreapp3.1;netcoreapp2.1;net472 false false diff --git a/tests/ImageSharp.Tests.ProfilingSandbox/ImageSharp.Tests.ProfilingSandbox.csproj b/tests/ImageSharp.Tests.ProfilingSandbox/ImageSharp.Tests.ProfilingSandbox.csproj index f9e6c9da5..7c8031693 100644 --- a/tests/ImageSharp.Tests.ProfilingSandbox/ImageSharp.Tests.ProfilingSandbox.csproj +++ b/tests/ImageSharp.Tests.ProfilingSandbox/ImageSharp.Tests.ProfilingSandbox.csproj @@ -8,8 +8,7 @@ false SixLabors.ImageSharp.Tests.ProfilingSandbox win7-x64 - - netcoreapp3.1 + netcoreapp3.1;netcoreapp2.1;net472 SixLabors.ImageSharp.Tests.ProfilingSandbox.Program false diff --git a/tests/ImageSharp.Tests/ImageSharp.Tests.csproj b/tests/ImageSharp.Tests/ImageSharp.Tests.csproj index d5c8ef16e..fdb280ca9 100644 --- a/tests/ImageSharp.Tests/ImageSharp.Tests.csproj +++ b/tests/ImageSharp.Tests/ImageSharp.Tests.csproj @@ -2,8 +2,7 @@ - - netcoreapp3.1 + netcoreapp3.1;netcoreapp2.1;net472 True True SixLabors.ImageSharp.Tests From a73a63becc3ba40ab76de99c988d46c0b5bc19a3 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Thu, 5 Mar 2020 15:16:20 +1100 Subject: [PATCH 53/64] Clean up quantized frame API --- src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs | 2 +- src/ImageSharp/Formats/Gif/GifEncoderCore.cs | 25 ++-- src/ImageSharp/Formats/Png/PngEncoderCore.cs | 22 ++-- .../Formats/Png/PngEncoderOptionsHelpers.cs | 4 +- .../ArrayPoolMemoryAllocator.Buffer{T}.cs | 2 +- .../Processors/Dithering/ErrorDither.cs | 4 +- .../Processors/Dithering/IDither.cs | 2 +- .../Processors/Dithering/OrderedDither.cs | 57 ++++----- .../PaletteDitherProcessor{TPixel}.cs | 4 +- .../Quantization/EuclideanPixelMap{TPixel}.cs | 29 ++--- .../Quantization/FrameQuantizerExtensions.cs | 18 ++- .../Quantization/IFrameQuantizer{TPixel}.cs | 20 +-- .../Quantization/IndexedImageFrame{TPixel}.cs | 115 ++++++++++++++++++ .../OctreeFrameQuantizer{TPixel}.cs | 30 ++--- .../PaletteFrameQuantizer{TPixel}.cs | 6 +- .../Quantization/PaletteQuantizer.cs | 2 +- .../Quantization/QuantizeProcessor{TPixel}.cs | 8 +- .../Quantization/QuantizedFrame{TPixel}.cs | 94 -------------- .../Quantization/WuFrameQuantizer{TPixel}.cs | 68 +++++------ .../Quantization/QuantizedImageTests.cs | 20 +-- .../Quantization/WuQuantizerTests.cs | 30 ++--- 21 files changed, 285 insertions(+), 277 deletions(-) create mode 100644 src/ImageSharp/Processing/Processors/Quantization/IndexedImageFrame{TPixel}.cs delete mode 100644 src/ImageSharp/Processing/Processors/Quantization/QuantizedFrame{TPixel}.cs diff --git a/src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs b/src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs index 3d5854ce5..7d2799503 100644 --- a/src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs +++ b/src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs @@ -337,7 +337,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp where TPixel : unmanaged, IPixel { using IFrameQuantizer frameQuantizer = this.quantizer.CreateFrameQuantizer(this.configuration); - using QuantizedFrame quantized = frameQuantizer.QuantizeFrame(image, image.Bounds()); + using IndexedImageFrame quantized = frameQuantizer.QuantizeFrame(image, image.Bounds()); ReadOnlySpan quantizedColors = quantized.Palette.Span; var color = default(Rgba32); diff --git a/src/ImageSharp/Formats/Gif/GifEncoderCore.cs b/src/ImageSharp/Formats/Gif/GifEncoderCore.cs index dc74353e3..29e2e8fe2 100644 --- a/src/ImageSharp/Formats/Gif/GifEncoderCore.cs +++ b/src/ImageSharp/Formats/Gif/GifEncoderCore.cs @@ -79,7 +79,7 @@ namespace SixLabors.ImageSharp.Formats.Gif bool useGlobalTable = this.colorTableMode == GifColorTableMode.Global; // Quantize the image returning a palette. - QuantizedFrame quantized; + IndexedImageFrame quantized; using (IFrameQuantizer frameQuantizer = this.quantizer.CreateFrameQuantizer(this.configuration)) { quantized = frameQuantizer.QuantizeFrame(image.Frames.RootFrame, image.Bounds()); @@ -125,7 +125,7 @@ namespace SixLabors.ImageSharp.Formats.Gif stream.WriteByte(GifConstants.EndIntroducer); } - private void EncodeGlobal(Image image, QuantizedFrame quantized, int transparencyIndex, Stream stream) + private void EncodeGlobal(Image image, IndexedImageFrame quantized, int transparencyIndex, Stream stream) where TPixel : unmanaged, IPixel { // The palette quantizer can reuse the same pixel map across multiple frames @@ -150,17 +150,17 @@ namespace SixLabors.ImageSharp.Formats.Gif if (!pixelMapSet) { pixelMapSet = true; - pixelMap = new EuclideanPixelMap(this.configuration, quantized.Palette, quantized.Palette.Span.Length); + pixelMap = new EuclideanPixelMap(this.configuration, quantized.Palette); } using var paletteFrameQuantizer = new PaletteFrameQuantizer(this.configuration, this.quantizer.Options, pixelMap); - using QuantizedFrame paletteQuantized = paletteFrameQuantizer.QuantizeFrame(frame, frame.Bounds()); + using IndexedImageFrame paletteQuantized = paletteFrameQuantizer.QuantizeFrame(frame, frame.Bounds()); this.WriteImageData(paletteQuantized, stream); } } } - private void EncodeLocal(Image image, QuantizedFrame quantized, Stream stream) + private void EncodeLocal(Image image, IndexedImageFrame quantized, Stream stream) where TPixel : unmanaged, IPixel { ImageFrame previousFrame = null; @@ -214,10 +214,10 @@ namespace SixLabors.ImageSharp.Formats.Gif /// /// The . /// - private int GetTransparentIndex(QuantizedFrame quantized) + private int GetTransparentIndex(IndexedImageFrame quantized) where TPixel : unmanaged, IPixel { - // Transparent pixels are much more likely to be found at the end of a palette + // Transparent pixels are much more likely to be found at the end of a palette. int index = -1; int length = quantized.Palette.Length; @@ -447,19 +447,20 @@ namespace SixLabors.ImageSharp.Formats.Gif /// The pixel format. /// The to encode. /// The stream to write to. - private void WriteColorTable(QuantizedFrame image, Stream stream) + private void WriteColorTable(IndexedImageFrame image, Stream stream) where TPixel : unmanaged, IPixel { // The maximum number of colors for the bit depth int colorTableLength = ImageMaths.GetColorCountForBitDepth(this.bitDepth) * 3; int pixelCount = image.Palette.Length; - using IManagedByteBuffer colorTable = this.memoryAllocator.AllocateManagedByteBuffer(colorTableLength); + using IManagedByteBuffer colorTable = this.memoryAllocator.AllocateManagedByteBuffer(colorTableLength, AllocationOptions.Clean); PixelOperations.Instance.ToRgb24Bytes( this.configuration, image.Palette.Span, colorTable.GetSpan(), pixelCount); + stream.Write(colorTable.Array, 0, colorTableLength); } @@ -467,13 +468,13 @@ namespace SixLabors.ImageSharp.Formats.Gif /// Writes the image pixel data to the stream. /// /// The pixel format. - /// The containing indexed pixels. + /// The containing indexed pixels. /// The stream to write to. - private void WriteImageData(QuantizedFrame image, Stream stream) + private void WriteImageData(IndexedImageFrame image, Stream stream) where TPixel : unmanaged, IPixel { using var encoder = new LzwEncoder(this.memoryAllocator, (byte)this.bitDepth); - encoder.Encode(image.GetPixelSpan(), stream); + encoder.Encode(image.GetPixelBufferSpan(), stream); } } } diff --git a/src/ImageSharp/Formats/Png/PngEncoderCore.cs b/src/ImageSharp/Formats/Png/PngEncoderCore.cs index ce624f768..6caaa1df0 100644 --- a/src/ImageSharp/Formats/Png/PngEncoderCore.cs +++ b/src/ImageSharp/Formats/Png/PngEncoderCore.cs @@ -146,7 +146,7 @@ namespace SixLabors.ImageSharp.Formats.Png ImageMetadata metadata = image.Metadata; PngMetadata pngMetadata = metadata.GetPngMetadata(); PngEncoderOptionsHelpers.AdjustOptions(this.options, pngMetadata, out this.use16Bit, out this.bytesPerPixel); - QuantizedFrame quantized = PngEncoderOptionsHelpers.CreateQuantizedFrame(this.options, image); + IndexedImageFrame quantized = PngEncoderOptionsHelpers.CreateQuantizedFrame(this.options, image); this.bitDepth = PngEncoderOptionsHelpers.CalculateBitDepth(this.options, image, quantized); stream.Write(PngConstants.HeaderBytes, 0, PngConstants.HeaderBytes.Length); @@ -371,7 +371,7 @@ namespace SixLabors.ImageSharp.Formats.Png /// The row span. /// The quantized pixels. Can be null. /// The row. - private void CollectPixelBytes(ReadOnlySpan rowSpan, QuantizedFrame quantized, int row) + private void CollectPixelBytes(ReadOnlySpan rowSpan, IndexedImageFrame quantized, int row) where TPixel : unmanaged, IPixel { switch (this.options.ColorType) @@ -385,7 +385,7 @@ namespace SixLabors.ImageSharp.Formats.Png else { int stride = this.currentScanline.Length(); - quantized.GetPixelSpan().Slice(row * stride, stride).CopyTo(this.currentScanline.GetSpan()); + quantized.GetPixelBufferSpan().Slice(row * stride, stride).CopyTo(this.currentScanline.GetSpan()); } break; @@ -440,7 +440,7 @@ namespace SixLabors.ImageSharp.Formats.Png /// The quantized pixels. Can be null. /// The row. /// The - private IManagedByteBuffer EncodePixelRow(ReadOnlySpan rowSpan, QuantizedFrame quantized, int row) + private IManagedByteBuffer EncodePixelRow(ReadOnlySpan rowSpan, IndexedImageFrame quantized, int row) where TPixel : unmanaged, IPixel { this.CollectPixelBytes(rowSpan, quantized, row); @@ -546,17 +546,17 @@ namespace SixLabors.ImageSharp.Formats.Png /// The pixel format. /// The containing image data. /// The quantized frame. - private void WritePaletteChunk(Stream stream, QuantizedFrame quantized) + private void WritePaletteChunk(Stream stream, IndexedImageFrame quantized) where TPixel : unmanaged, IPixel { - if (quantized == null) + if (quantized is null) { return; } // Grab the palette and write it to the stream. ReadOnlySpan palette = quantized.Palette.Span; - int paletteLength = Math.Min(palette.Length, 256); + int paletteLength = Math.Min(palette.Length, QuantizerConstants.MaxColors); int colorTableLength = paletteLength * 3; bool anyAlpha = false; @@ -565,7 +565,7 @@ namespace SixLabors.ImageSharp.Formats.Png { ref byte colorTableRef = ref MemoryMarshal.GetReference(colorTable.GetSpan()); ref byte alphaTableRef = ref MemoryMarshal.GetReference(alphaTable.GetSpan()); - ReadOnlySpan quantizedSpan = quantized.GetPixelSpan(); + ReadOnlySpan quantizedSpan = quantized.GetPixelBufferSpan(); Rgba32 rgba = default; @@ -783,7 +783,7 @@ namespace SixLabors.ImageSharp.Formats.Png /// The image. /// The quantized pixel data. Can be null. /// The stream. - private void WriteDataChunks(ImageFrame pixels, QuantizedFrame quantized, Stream stream) + private void WriteDataChunks(ImageFrame pixels, IndexedImageFrame quantized, Stream stream) where TPixel : unmanaged, IPixel { byte[] buffer; @@ -881,7 +881,7 @@ namespace SixLabors.ImageSharp.Formats.Png /// The pixels. /// The quantized pixels span. /// The deflate stream. - private void EncodePixels(ImageFrame pixels, QuantizedFrame quantized, ZlibDeflateStream deflateStream) + private void EncodePixels(ImageFrame pixels, IndexedImageFrame quantized, ZlibDeflateStream deflateStream) where TPixel : unmanaged, IPixel { int bytesPerScanline = this.CalculateScanlineLength(this.width); @@ -960,7 +960,7 @@ namespace SixLabors.ImageSharp.Formats.Png /// The type of the pixel. /// The quantized. /// The deflate stream. - private void EncodeAdam7IndexedPixels(QuantizedFrame quantized, ZlibDeflateStream deflateStream) + private void EncodeAdam7IndexedPixels(IndexedImageFrame quantized, ZlibDeflateStream deflateStream) where TPixel : unmanaged, IPixel { int width = quantized.Width; diff --git a/src/ImageSharp/Formats/Png/PngEncoderOptionsHelpers.cs b/src/ImageSharp/Formats/Png/PngEncoderOptionsHelpers.cs index 20b8c41c9..3f490ca6f 100644 --- a/src/ImageSharp/Formats/Png/PngEncoderOptionsHelpers.cs +++ b/src/ImageSharp/Formats/Png/PngEncoderOptionsHelpers.cs @@ -53,7 +53,7 @@ namespace SixLabors.ImageSharp.Formats.Png /// The type of the pixel. /// The options. /// The image. - public static QuantizedFrame CreateQuantizedFrame( + public static IndexedImageFrame CreateQuantizedFrame( PngEncoderOptions options, Image image) where TPixel : unmanaged, IPixel @@ -94,7 +94,7 @@ namespace SixLabors.ImageSharp.Formats.Png public static byte CalculateBitDepth( PngEncoderOptions options, Image image, - QuantizedFrame quantizedFrame) + IndexedImageFrame quantizedFrame) where TPixel : unmanaged, IPixel { byte bitDepth; diff --git a/src/ImageSharp/Memory/Allocators/ArrayPoolMemoryAllocator.Buffer{T}.cs b/src/ImageSharp/Memory/Allocators/ArrayPoolMemoryAllocator.Buffer{T}.cs index 7a8b4f8bd..16ca4de67 100644 --- a/src/ImageSharp/Memory/Allocators/ArrayPoolMemoryAllocator.Buffer{T}.cs +++ b/src/ImageSharp/Memory/Allocators/ArrayPoolMemoryAllocator.Buffer{T}.cs @@ -48,7 +48,7 @@ namespace SixLabors.ImageSharp.Memory /// public override Span GetSpan() { - if (this.Data == null) + if (this.Data is null) { throw new ObjectDisposedException("ArrayPoolMemoryAllocator.Buffer"); } diff --git a/src/ImageSharp/Processing/Processors/Dithering/ErrorDither.cs b/src/ImageSharp/Processing/Processors/Dithering/ErrorDither.cs index 9d0c563da..7d30bada6 100644 --- a/src/ImageSharp/Processing/Processors/Dithering/ErrorDither.cs +++ b/src/ImageSharp/Processing/Processors/Dithering/ErrorDither.cs @@ -91,7 +91,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering public void ApplyQuantizationDither( ref TFrameQuantizer quantizer, ImageFrame source, - QuantizedFrame destination, + IndexedImageFrame destination, Rectangle bounds) where TFrameQuantizer : struct, IFrameQuantizer where TPixel : unmanaged, IPixel @@ -103,7 +103,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering for (int y = bounds.Top; y < bounds.Bottom; y++) { ref TPixel sourceRowRef = ref MemoryMarshal.GetReference(source.GetPixelRowSpan(y)); - ref byte destinationRowRef = ref MemoryMarshal.GetReference(destination.GetPixelRowSpan(y - offsetY)); + ref byte destinationRowRef = ref MemoryMarshal.GetReference(destination.GetWritablePixelRowSpanUnsafe(y - offsetY)); for (int x = bounds.Left; x < bounds.Right; x++) { diff --git a/src/ImageSharp/Processing/Processors/Dithering/IDither.cs b/src/ImageSharp/Processing/Processors/Dithering/IDither.cs index f7057b8f3..8f9d82537 100644 --- a/src/ImageSharp/Processing/Processors/Dithering/IDither.cs +++ b/src/ImageSharp/Processing/Processors/Dithering/IDither.cs @@ -24,7 +24,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering void ApplyQuantizationDither( ref TFrameQuantizer quantizer, ImageFrame source, - QuantizedFrame destination, + IndexedImageFrame destination, Rectangle bounds) where TFrameQuantizer : struct, IFrameQuantizer where TPixel : unmanaged, IPixel; diff --git a/src/ImageSharp/Processing/Processors/Dithering/OrderedDither.cs b/src/ImageSharp/Processing/Processors/Dithering/OrderedDither.cs index 3e25e2a02..6862cff00 100644 --- a/src/ImageSharp/Processing/Processors/Dithering/OrderedDither.cs +++ b/src/ImageSharp/Processing/Processors/Dithering/OrderedDither.cs @@ -5,7 +5,6 @@ using System; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using SixLabors.ImageSharp.Advanced; -using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing.Processors.Quantization; @@ -107,19 +106,19 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering public void ApplyQuantizationDither( ref TFrameQuantizer quantizer, ImageFrame source, - QuantizedFrame destination, + IndexedImageFrame destination, Rectangle bounds) where TFrameQuantizer : struct, IFrameQuantizer where TPixel : unmanaged, IPixel { - var ditherOperation = new QuantizeDitherRowIntervalOperation( + var ditherOperation = new QuantizeDitherRowOperation( ref quantizer, in Unsafe.AsRef(this), source, destination, bounds); - ParallelRowIterator.IterateRowIntervals( + ParallelRowIterator.IterateRows( quantizer.Configuration, bounds, in ditherOperation); @@ -134,13 +133,13 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering where TPaletteDitherImageProcessor : struct, IPaletteDitherImageProcessor where TPixel : unmanaged, IPixel { - var ditherOperation = new PaletteDitherRowIntervalOperation( + var ditherOperation = new PaletteDitherRowOperation( in processor, in Unsafe.AsRef(this), source, bounds); - ParallelRowIterator.IterateRowIntervals( + ParallelRowIterator.IterateRows( processor.Configuration, bounds, in ditherOperation); @@ -195,23 +194,23 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering public override int GetHashCode() => HashCode.Combine(this.thresholdMatrix, this.modulusX, this.modulusY); - private readonly struct QuantizeDitherRowIntervalOperation : IRowIntervalOperation + private readonly struct QuantizeDitherRowOperation : IRowOperation where TFrameQuantizer : struct, IFrameQuantizer where TPixel : unmanaged, IPixel { private readonly TFrameQuantizer quantizer; private readonly OrderedDither dither; private readonly ImageFrame source; - private readonly QuantizedFrame destination; + private readonly IndexedImageFrame destination; private readonly Rectangle bounds; private readonly int bitDepth; [MethodImpl(InliningOptions.ShortMethod)] - public QuantizeDitherRowIntervalOperation( + public QuantizeDitherRowOperation( ref TFrameQuantizer quantizer, in OrderedDither dither, ImageFrame source, - QuantizedFrame destination, + IndexedImageFrame destination, Rectangle bounds) { this.quantizer = quantizer; @@ -223,27 +222,24 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering } [MethodImpl(InliningOptions.ShortMethod)] - public void Invoke(in RowInterval rows) + public void Invoke(int y) { int offsetY = this.bounds.Top; int offsetX = this.bounds.Left; float scale = this.quantizer.Options.DitherScale; - for (int y = rows.Min; y < rows.Max; y++) + ref TPixel sourceRowRef = ref MemoryMarshal.GetReference(this.source.GetPixelRowSpan(y)); + ref byte destinationRowRef = ref MemoryMarshal.GetReference(this.destination.GetWritablePixelRowSpanUnsafe(y - offsetY)); + + for (int x = this.bounds.Left; x < this.bounds.Right; x++) { - ref TPixel sourceRowRef = ref MemoryMarshal.GetReference(this.source.GetPixelRowSpan(y)); - ref byte destinationRowRef = ref MemoryMarshal.GetReference(this.destination.GetPixelRowSpan(y - offsetY)); - - for (int x = this.bounds.Left; x < this.bounds.Right; x++) - { - TPixel dithered = this.dither.Dither(Unsafe.Add(ref sourceRowRef, x), x, y, this.bitDepth, scale); - Unsafe.Add(ref destinationRowRef, x - offsetX) = Unsafe.AsRef(this.quantizer).GetQuantizedColor(dithered, out TPixel _); - } + TPixel dithered = this.dither.Dither(Unsafe.Add(ref sourceRowRef, x), x, y, this.bitDepth, scale); + Unsafe.Add(ref destinationRowRef, x - offsetX) = Unsafe.AsRef(this.quantizer).GetQuantizedColor(dithered, out TPixel _); } } } - private readonly struct PaletteDitherRowIntervalOperation : IRowIntervalOperation + private readonly struct PaletteDitherRowOperation : IRowOperation where TPaletteDitherImageProcessor : struct, IPaletteDitherImageProcessor where TPixel : unmanaged, IPixel { @@ -255,7 +251,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering private readonly int bitDepth; [MethodImpl(InliningOptions.ShortMethod)] - public PaletteDitherRowIntervalOperation( + public PaletteDitherRowOperation( in TPaletteDitherImageProcessor processor, in OrderedDither dither, ImageFrame source, @@ -270,18 +266,15 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering } [MethodImpl(InliningOptions.ShortMethod)] - public void Invoke(in RowInterval rows) + public void Invoke(int y) { - for (int y = rows.Min; y < rows.Max; y++) + ref TPixel sourceRowRef = ref MemoryMarshal.GetReference(this.source.GetPixelRowSpan(y)); + + for (int x = this.bounds.Left; x < this.bounds.Right; x++) { - ref TPixel sourceRowRef = ref MemoryMarshal.GetReference(this.source.GetPixelRowSpan(y)); - - for (int x = this.bounds.Left; x < this.bounds.Right; x++) - { - ref TPixel sourcePixel = ref Unsafe.Add(ref sourceRowRef, x); - TPixel dithered = this.dither.Dither(sourcePixel, x, y, this.bitDepth, this.scale); - sourcePixel = Unsafe.AsRef(this.processor).GetPaletteColor(dithered); - } + ref TPixel sourcePixel = ref Unsafe.Add(ref sourceRowRef, x); + TPixel dithered = this.dither.Dither(sourcePixel, x, y, this.bitDepth, this.scale); + sourcePixel = Unsafe.AsRef(this.processor).GetPaletteColor(dithered); } } } diff --git a/src/ImageSharp/Processing/Processors/Dithering/PaletteDitherProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Dithering/PaletteDitherProcessor{TPixel}.cs index 6b5ffabf4..1f554536c 100644 --- a/src/ImageSharp/Processing/Processors/Dithering/PaletteDitherProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Dithering/PaletteDitherProcessor{TPixel}.cs @@ -40,7 +40,6 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering this.ditherProcessor = new DitherProcessor( this.Configuration, - Rectangle.Intersect(this.SourceRectangle, source.Bounds()), this.paletteMemory.Memory, definition.DitherScale); } @@ -82,12 +81,11 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering [MethodImpl(InliningOptions.ShortMethod)] public DitherProcessor( Configuration configuration, - Rectangle bounds, ReadOnlyMemory palette, float ditherScale) { this.Configuration = configuration; - this.pixelMap = new EuclideanPixelMap(configuration, palette, palette.Span.Length); + this.pixelMap = new EuclideanPixelMap(configuration, palette); this.Palette = palette; this.DitherScale = ditherScale; } diff --git a/src/ImageSharp/Processing/Processors/Quantization/EuclideanPixelMap{TPixel}.cs b/src/ImageSharp/Processing/Processors/Quantization/EuclideanPixelMap{TPixel}.cs index 84a204bba..775e0aa23 100644 --- a/src/ImageSharp/Processing/Processors/Quantization/EuclideanPixelMap{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Quantization/EuclideanPixelMap{TPixel}.cs @@ -19,34 +19,32 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization { private readonly Vector4[] vectorCache; private readonly ConcurrentDictionary distanceCache; - private readonly ReadOnlyMemory palette; - private readonly int length; /// /// Initializes a new instance of the struct. /// /// The configuration. /// The color palette to map from. - /// The length of the color palette. [MethodImpl(InliningOptions.ShortMethod)] - public EuclideanPixelMap(Configuration configuration, ReadOnlyMemory palette, int length) + public EuclideanPixelMap(Configuration configuration, ReadOnlyMemory palette) { - this.palette = palette; - this.length = length; - ReadOnlySpan paletteSpan = this.palette.Span.Slice(0, this.length); - this.vectorCache = new Vector4[length]; + this.Palette = palette; + this.vectorCache = new Vector4[palette.Length]; // Use the same rules across all target frameworks. this.distanceCache = new ConcurrentDictionary(Environment.ProcessorCount, 31); - PixelOperations.Instance.ToVector4(configuration, paletteSpan, this.vectorCache); + PixelOperations.Instance.ToVector4(configuration, this.Palette.Span, this.vectorCache); } /// - /// Returns the palette span. + /// Gets the color palette of this . + /// The palette memory is owned by the palette source that created it. /// - /// The . - [MethodImpl(InliningOptions.ShortMethod)] - public ReadOnlySpan GetPaletteSpan() => this.palette.Span.Slice(0, this.length); + public ReadOnlyMemory Palette + { + [MethodImpl(InliningOptions.ShortMethod)] + get; + } /// /// Returns the closest color in the palette and the index of that pixel. @@ -58,7 +56,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization [MethodImpl(InliningOptions.ShortMethod)] public int GetClosestColor(TPixel color, out TPixel match) { - ref TPixel paletteRef = ref MemoryMarshal.GetReference(this.GetPaletteSpan()); + ref TPixel paletteRef = ref MemoryMarshal.GetReference(this.Palette.Span); // Check if the color is in the lookup table if (!this.distanceCache.TryGetValue(color, out int index)) @@ -78,8 +76,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization float leastDistance = float.MaxValue; var vector = color.ToVector4(); ref Vector4 vectorCacheRef = ref MemoryMarshal.GetReference(this.vectorCache); - - for (int i = 0; i < this.length; i++) + for (int i = 0; i < this.Palette.Length; i++) { Vector4 candidate = Unsafe.Add(ref vectorCacheRef, i); float distance = Vector4.DistanceSquared(vector, candidate); diff --git a/src/ImageSharp/Processing/Processors/Quantization/FrameQuantizerExtensions.cs b/src/ImageSharp/Processing/Processors/Quantization/FrameQuantizerExtensions.cs index 88973c44b..26da6a397 100644 --- a/src/ImageSharp/Processing/Processors/Quantization/FrameQuantizerExtensions.cs +++ b/src/ImageSharp/Processing/Processors/Quantization/FrameQuantizerExtensions.cs @@ -24,9 +24,9 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization /// The source image frame to quantize. /// The bounds within the frame to quantize. /// - /// A representing a quantized version of the source frame pixels. + /// A representing a quantized version of the source frame pixels. /// - public static QuantizedFrame QuantizeFrame( + public static IndexedImageFrame QuantizeFrame( ref TFrameQuantizer quantizer, ImageFrame source, Rectangle bounds) @@ -37,10 +37,8 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization var interest = Rectangle.Intersect(source.Bounds(), bounds); // Collect the palette. Required before the second pass runs. - ReadOnlySpan palette = quantizer.BuildPalette(source, interest); - MemoryAllocator memoryAllocator = quantizer.Configuration.MemoryAllocator; - - var destination = new QuantizedFrame(memoryAllocator, interest.Width, interest.Height, palette); + ReadOnlyMemory palette = quantizer.BuildPalette(source, interest); + var destination = new IndexedImageFrame(quantizer.Configuration, interest.Width, interest.Height, palette); if (quantizer.Options.Dither is null) { @@ -60,7 +58,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization private static void SecondPass( ref TFrameQuantizer quantizer, ImageFrame source, - QuantizedFrame destination, + IndexedImageFrame destination, Rectangle bounds) where TFrameQuantizer : struct, IFrameQuantizer where TPixel : unmanaged, IPixel @@ -87,14 +85,14 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization { private readonly TFrameQuantizer quantizer; private readonly ImageFrame source; - private readonly QuantizedFrame destination; + private readonly IndexedImageFrame destination; private readonly Rectangle bounds; [MethodImpl(InliningOptions.ShortMethod)] public RowIntervalOperation( ref TFrameQuantizer quantizer, ImageFrame source, - QuantizedFrame destination, + IndexedImageFrame destination, Rectangle bounds) { this.quantizer = quantizer; @@ -112,7 +110,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization for (int y = rows.Min; y < rows.Max; y++) { Span sourceRow = this.source.GetPixelRowSpan(y); - Span destinationRow = this.destination.GetPixelRowSpan(y - offsetY); + Span destinationRow = this.destination.GetWritablePixelRowSpanUnsafe(y - offsetY); for (int x = this.bounds.Left; x < this.bounds.Right; x++) { diff --git a/src/ImageSharp/Processing/Processors/Quantization/IFrameQuantizer{TPixel}.cs b/src/ImageSharp/Processing/Processors/Quantization/IFrameQuantizer{TPixel}.cs index d49852cf1..64caad3e2 100644 --- a/src/ImageSharp/Processing/Processors/Quantization/IFrameQuantizer{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Quantization/IFrameQuantizer{TPixel}.cs @@ -23,23 +23,23 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization /// QuantizerOptions Options { get; } + /// + /// Builds the quantized palette from the given image frame and bounds. + /// + /// The source image frame. + /// The region of interest bounds. + /// The palette. + ReadOnlyMemory BuildPalette(ImageFrame source, Rectangle bounds); + /// /// Quantizes an image frame and return the resulting output pixels. /// /// The source image frame to quantize. /// The bounds within the frame to quantize. /// - /// A representing a quantized version of the source frame pixels. + /// A representing a quantized version of the source frame pixels. /// - QuantizedFrame QuantizeFrame(ImageFrame source, Rectangle bounds); - - /// - /// Builds the quantized palette from the given image frame and bounds. - /// - /// The source image frame. - /// The region of interest bounds. - /// The palette. - ReadOnlySpan BuildPalette(ImageFrame source, Rectangle bounds); + IndexedImageFrame QuantizeFrame(ImageFrame source, Rectangle bounds); /// /// Returns the index and color from the quantized palette corresponding to the given color. diff --git a/src/ImageSharp/Processing/Processors/Quantization/IndexedImageFrame{TPixel}.cs b/src/ImageSharp/Processing/Processors/Quantization/IndexedImageFrame{TPixel}.cs new file mode 100644 index 000000000..42aadb5fd --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Quantization/IndexedImageFrame{TPixel}.cs @@ -0,0 +1,115 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Buffers; +using System.Runtime.CompilerServices; +using SixLabors.ImageSharp.Memory; +using SixLabors.ImageSharp.PixelFormats; + +namespace SixLabors.ImageSharp.Processing.Processors.Quantization +{ + /// + /// A pixel-specific image frame where each pixel buffer value represents an index in a color palette. + /// + /// The pixel format. + public sealed class IndexedImageFrame : IDisposable + where TPixel : unmanaged, IPixel + { + private IMemoryOwner pixelsOwner; + private IMemoryOwner paletteOwner; + private bool isDisposed; + + /// + /// Initializes a new instance of the class. + /// + /// + /// The configuration which allows altering default behaviour or extending the library. + /// + /// The frame width. + /// The frame height. + /// The color palette. + internal IndexedImageFrame(Configuration configuration, int width, int height, ReadOnlyMemory palette) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.MustBeGreaterThan(width, 0, nameof(width)); + Guard.MustBeGreaterThan(height, 0, nameof(height)); + + this.Configuration = configuration; + this.Width = width; + this.Height = height; + this.pixelsOwner = configuration.MemoryAllocator.AllocateManagedByteBuffer(width * height); + + // Copy the palette over. We want the lifetime of this frame to be independant of any palette source. + this.paletteOwner = configuration.MemoryAllocator.Allocate(palette.Length); + palette.Span.CopyTo(this.paletteOwner.GetSpan()); + this.Palette = this.paletteOwner.Memory.Slice(0, palette.Length); + } + + /// + /// Gets the configuration which allows altering default behaviour or extending the library. + /// + public Configuration Configuration { get; } + + /// + /// Gets the width of this . + /// + public int Width { get; } + + /// + /// Gets the height of this . + /// + public int Height { get; } + + /// + /// Gets the color palette of this . + /// + public ReadOnlyMemory Palette { get; } + + /// + /// Gets the pixels of this . + /// + /// The + [MethodImpl(InliningOptions.ShortMethod)] + public ReadOnlySpan GetPixelBufferSpan() => this.pixelsOwner.GetSpan(); // TODO: Buffer2D + + /// + /// Gets the representation of the pixels as a of contiguous memory + /// at row beginning from the the first pixel on that row. + /// + /// The row index in the pixel buffer. + /// The pixel row as a . + [MethodImpl(InliningOptions.ShortMethod)] + public ReadOnlySpan GetPixelRowSpan(int rowIndex) + => this.GetWritablePixelRowSpanUnsafe(rowIndex); + + /// + /// + /// Gets the representation of the pixels as a of contiguous memory + /// at row beginning from the the first pixel on that row. + /// + /// + /// Note: Values written to this span are not sanitized against the palette length. + /// Care should be taken during assignment to prevent out-of-bounds errors. + /// + /// + /// The row index in the pixel buffer. + /// The pixel row as a . + [MethodImpl(InliningOptions.ShortMethod)] + public Span GetWritablePixelRowSpanUnsafe(int rowIndex) + => this.pixelsOwner.GetSpan().Slice(rowIndex * this.Width, this.Width); + + /// + public void Dispose() + { + if (!this.isDisposed) + { + this.isDisposed = true; + this.pixelsOwner.Dispose(); + this.paletteOwner.Dispose(); + this.pixelsOwner = null; + this.paletteOwner = null; + } + } + } +} diff --git a/src/ImageSharp/Processing/Processors/Quantization/OctreeFrameQuantizer{TPixel}.cs b/src/ImageSharp/Processing/Processors/Quantization/OctreeFrameQuantizer{TPixel}.cs index cc6a3a485..6c31fca7f 100644 --- a/src/ImageSharp/Processing/Processors/Quantization/OctreeFrameQuantizer{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Quantization/OctreeFrameQuantizer{TPixel}.cs @@ -20,9 +20,9 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization public struct OctreeFrameQuantizer : IFrameQuantizer where TPixel : unmanaged, IPixel { - private readonly int colors; + private readonly int maxColors; private readonly Octree octree; - private IMemoryOwner palette; + private IMemoryOwner paletteOwner; private EuclideanPixelMap pixelMap; private readonly bool isDithering; private bool isDisposed; @@ -41,9 +41,9 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization this.Configuration = configuration; this.Options = options; - this.colors = this.Options.MaxColors; - this.octree = new Octree(ImageMaths.GetBitsNeededForColorDepth(this.colors).Clamp(1, 8)); - this.palette = configuration.MemoryAllocator.Allocate(this.colors, AllocationOptions.Clean); + this.maxColors = this.Options.MaxColors; + this.octree = new Octree(ImageMaths.GetBitsNeededForColorDepth(this.maxColors).Clamp(1, 8)); + this.paletteOwner = configuration.MemoryAllocator.Allocate(this.maxColors, AllocationOptions.Clean); this.pixelMap = default; this.isDithering = !(this.Options.Dither is null); this.isDisposed = false; @@ -57,12 +57,12 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization /// [MethodImpl(InliningOptions.ShortMethod)] - public readonly QuantizedFrame QuantizeFrame(ImageFrame source, Rectangle bounds) + public readonly IndexedImageFrame QuantizeFrame(ImageFrame source, Rectangle bounds) => FrameQuantizerExtensions.QuantizeFrame(ref Unsafe.AsRef(this), source, bounds); /// [MethodImpl(InliningOptions.ShortMethod)] - public ReadOnlySpan BuildPalette(ImageFrame source, Rectangle bounds) + public ReadOnlyMemory BuildPalette(ImageFrame source, Rectangle bounds) { using IMemoryOwner buffer = this.Configuration.MemoryAllocator.Allocate(bounds.Width); Span bufferSpan = buffer.GetSpan(); @@ -82,15 +82,15 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization } } - Span paletteSpan = this.palette.GetSpan(); + Span paletteSpan = this.paletteOwner.GetSpan(); int paletteIndex = 0; - this.octree.Palletize(paletteSpan, this.colors, ref paletteIndex); + this.octree.Palletize(paletteSpan, this.maxColors, ref paletteIndex); // Length of reduced palette + transparency. - paletteSpan = paletteSpan.Slice(0, Math.Min(paletteIndex + 2, QuantizerConstants.MaxColors)); - this.pixelMap = new EuclideanPixelMap(this.Configuration, this.palette.Memory, paletteSpan.Length); + ReadOnlyMemory result = this.paletteOwner.Memory.Slice(0, Math.Min(paletteIndex + 2, QuantizerConstants.MaxColors)); + this.pixelMap = new EuclideanPixelMap(this.Configuration, result); - return paletteSpan; + return result; } /// @@ -105,7 +105,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization return (byte)this.pixelMap.GetClosestColor(color, out match); } - ref TPixel paletteRef = ref MemoryMarshal.GetReference(this.pixelMap.GetPaletteSpan()); + ref TPixel paletteRef = ref MemoryMarshal.GetReference(this.pixelMap.Palette.Span); var index = (byte)this.octree.GetPaletteIndex(color); match = Unsafe.Add(ref paletteRef, index); return index; @@ -117,8 +117,8 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization if (!this.isDisposed) { this.isDisposed = true; - this.palette.Dispose(); - this.palette = null; + this.paletteOwner.Dispose(); + this.paletteOwner = null; } } diff --git a/src/ImageSharp/Processing/Processors/Quantization/PaletteFrameQuantizer{TPixel}.cs b/src/ImageSharp/Processing/Processors/Quantization/PaletteFrameQuantizer{TPixel}.cs index a9a938562..d37116855 100644 --- a/src/ImageSharp/Processing/Processors/Quantization/PaletteFrameQuantizer{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Quantization/PaletteFrameQuantizer{TPixel}.cs @@ -45,13 +45,13 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization /// [MethodImpl(InliningOptions.ShortMethod)] - public readonly QuantizedFrame QuantizeFrame(ImageFrame source, Rectangle bounds) + public readonly IndexedImageFrame QuantizeFrame(ImageFrame source, Rectangle bounds) => FrameQuantizerExtensions.QuantizeFrame(ref Unsafe.AsRef(this), source, bounds); /// [MethodImpl(InliningOptions.ShortMethod)] - public readonly ReadOnlySpan BuildPalette(ImageFrame source, Rectangle bounds) - => this.pixelMap.GetPaletteSpan(); + public readonly ReadOnlyMemory BuildPalette(ImageFrame source, Rectangle bounds) + => this.pixelMap.Palette; /// [MethodImpl(InliningOptions.ShortMethod)] diff --git a/src/ImageSharp/Processing/Processors/Quantization/PaletteQuantizer.cs b/src/ImageSharp/Processing/Processors/Quantization/PaletteQuantizer.cs index e856c389c..c14ea6153 100644 --- a/src/ImageSharp/Processing/Processors/Quantization/PaletteQuantizer.cs +++ b/src/ImageSharp/Processing/Processors/Quantization/PaletteQuantizer.cs @@ -59,7 +59,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization Color.ToPixel(configuration, this.colorPalette.Span, palette.AsSpan()); - var pixelMap = new EuclideanPixelMap(configuration, palette, length); + var pixelMap = new EuclideanPixelMap(configuration, palette); return new PaletteFrameQuantizer(configuration, options, pixelMap); } } diff --git a/src/ImageSharp/Processing/Processors/Quantization/QuantizeProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Quantization/QuantizeProcessor{TPixel}.cs index cbef19300..4583b7cff 100644 --- a/src/ImageSharp/Processing/Processors/Quantization/QuantizeProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Quantization/QuantizeProcessor{TPixel}.cs @@ -39,7 +39,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization Configuration configuration = this.Configuration; using IFrameQuantizer frameQuantizer = this.quantizer.CreateFrameQuantizer(configuration); - using QuantizedFrame quantized = frameQuantizer.QuantizeFrame(source, interest); + using IndexedImageFrame quantized = frameQuantizer.QuantizeFrame(source, interest); var operation = new RowIntervalOperation(this.SourceRectangle, source, quantized); ParallelRowIterator.IterateRowIntervals( @@ -52,13 +52,13 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization { private readonly Rectangle bounds; private readonly ImageFrame source; - private readonly QuantizedFrame quantized; + private readonly IndexedImageFrame quantized; [MethodImpl(InliningOptions.ShortMethod)] public RowIntervalOperation( Rectangle bounds, ImageFrame source, - QuantizedFrame quantized) + IndexedImageFrame quantized) { this.bounds = bounds; this.source = source; @@ -68,7 +68,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization [MethodImpl(InliningOptions.ShortMethod)] public void Invoke(in RowInterval rows) { - ReadOnlySpan quantizedPixelSpan = this.quantized.GetPixelSpan(); + ReadOnlySpan quantizedPixelSpan = this.quantized.GetPixelBufferSpan(); ReadOnlySpan paletteSpan = this.quantized.Palette.Span; int offsetY = this.bounds.Top; int offsetX = this.bounds.Left; diff --git a/src/ImageSharp/Processing/Processors/Quantization/QuantizedFrame{TPixel}.cs b/src/ImageSharp/Processing/Processors/Quantization/QuantizedFrame{TPixel}.cs deleted file mode 100644 index d5facbe63..000000000 --- a/src/ImageSharp/Processing/Processors/Quantization/QuantizedFrame{TPixel}.cs +++ /dev/null @@ -1,94 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using System; -using System.Buffers; -using System.Runtime.CompilerServices; -using SixLabors.ImageSharp.Memory; -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Processing.Processors.Quantization -{ - /// - /// Represents a quantized image frame where the pixels indexed by a color palette. - /// - /// The pixel format. - public sealed class QuantizedFrame : IDisposable - where TPixel : unmanaged, IPixel - { - private IMemoryOwner palette; - private IMemoryOwner pixels; - private bool isDisposed; - - /// - /// Initializes a new instance of the class. - /// - /// Used to allocated memory for image processing operations. - /// The image width. - /// The image height. - /// The color palette. - internal QuantizedFrame(MemoryAllocator memoryAllocator, int width, int height, ReadOnlySpan palette) - { - Guard.MustBeGreaterThan(width, 0, nameof(width)); - Guard.MustBeGreaterThan(height, 0, nameof(height)); - - this.Width = width; - this.Height = height; - this.pixels = memoryAllocator.AllocateManagedByteBuffer(width * height, AllocationOptions.Clean); - - this.palette = memoryAllocator.Allocate(palette.Length); - palette.CopyTo(this.palette.GetSpan()); - } - - /// - /// Gets the width of this . - /// - public int Width { get; } - - /// - /// Gets the height of this . - /// - public int Height { get; } - - /// - /// Gets the color palette of this . - /// - public ReadOnlyMemory Palette - { - [MethodImpl(InliningOptions.ShortMethod)] - get { return this.palette.Memory; } - } - - /// - /// Gets the pixels of this . - /// - /// The - [MethodImpl(InliningOptions.ShortMethod)] - public Span GetPixelSpan() => this.pixels.GetSpan(); - - /// - /// Gets the representation of the pixels as a of contiguous memory - /// at row beginning from the the first pixel on that row. - /// - /// The row. - /// The pixel row as a . - [MethodImpl(InliningOptions.ShortMethod)] - public Span GetPixelRowSpan(int rowIndex) - => this.GetPixelSpan().Slice(rowIndex * this.Width, this.Width); - - /// - public void Dispose() - { - if (!this.isDisposed) - { - return; - } - - this.isDisposed = true; - this.pixels?.Dispose(); - this.palette?.Dispose(); - this.pixels = null; - this.palette = null; - } - } -} diff --git a/src/ImageSharp/Processing/Processors/Quantization/WuFrameQuantizer{TPixel}.cs b/src/ImageSharp/Processing/Processors/Quantization/WuFrameQuantizer{TPixel}.cs index f50282f9a..60f3a0a2a 100644 --- a/src/ImageSharp/Processing/Processors/Quantization/WuFrameQuantizer{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Quantization/WuFrameQuantizer{TPixel}.cs @@ -66,10 +66,10 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization /// private const int TableLength = IndexCount * IndexCount * IndexCount * IndexAlphaCount; - private IMemoryOwner moments; - private IMemoryOwner tag; - private IMemoryOwner palette; - private int colors; + private IMemoryOwner momentsOwner; + private IMemoryOwner tagsOwner; + private IMemoryOwner paletteOwner; + private int maxColors; private readonly Box[] colorCube; private EuclideanPixelMap pixelMap; private readonly bool isDithering; @@ -88,12 +88,12 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization this.Configuration = configuration; this.Options = options; - this.colors = this.Options.MaxColors; + this.maxColors = this.Options.MaxColors; this.memoryAllocator = this.Configuration.MemoryAllocator; - this.moments = this.memoryAllocator.Allocate(TableLength, AllocationOptions.Clean); - this.tag = this.memoryAllocator.Allocate(TableLength, AllocationOptions.Clean); - this.palette = this.memoryAllocator.Allocate(this.colors, AllocationOptions.Clean); - this.colorCube = new Box[this.colors]; + this.momentsOwner = this.memoryAllocator.Allocate(TableLength, AllocationOptions.Clean); + this.tagsOwner = this.memoryAllocator.Allocate(TableLength, AllocationOptions.Clean); + this.paletteOwner = this.memoryAllocator.Allocate(this.maxColors, AllocationOptions.Clean); + this.colorCube = new Box[this.maxColors]; this.isDisposed = false; this.pixelMap = default; this.isDithering = this.isDithering = !(this.Options.Dither is null); @@ -107,19 +107,19 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization /// [MethodImpl(InliningOptions.ShortMethod)] - public readonly QuantizedFrame QuantizeFrame(ImageFrame source, Rectangle bounds) + public readonly IndexedImageFrame QuantizeFrame(ImageFrame source, Rectangle bounds) => FrameQuantizerExtensions.QuantizeFrame(ref Unsafe.AsRef(this), source, bounds); /// - public ReadOnlySpan BuildPalette(ImageFrame source, Rectangle bounds) + public ReadOnlyMemory BuildPalette(ImageFrame source, Rectangle bounds) { this.Build3DHistogram(source, bounds); this.Get3DMoments(this.memoryAllocator); this.BuildCube(); - ReadOnlySpan momentsSpan = this.moments.GetSpan(); - Span paletteSpan = this.palette.GetSpan(); - for (int k = 0; k < this.colors; k++) + ReadOnlySpan momentsSpan = this.momentsOwner.GetSpan(); + Span paletteSpan = this.paletteOwner.GetSpan(); + for (int k = 0; k < this.maxColors; k++) { this.Mark(ref this.colorCube[k], (byte)k); @@ -132,9 +132,9 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization } } - paletteSpan = paletteSpan.Slice(0, this.colors); - this.pixelMap = new EuclideanPixelMap(this.Configuration, this.palette.Memory, paletteSpan.Length); - return paletteSpan; + ReadOnlyMemory result = this.paletteOwner.Memory.Slice(0, this.maxColors); + this.pixelMap = new EuclideanPixelMap(this.Configuration, result); + return result; } /// @@ -153,9 +153,9 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization int b = rgba.B >> (8 - IndexBits); int a = rgba.A >> (8 - IndexAlphaBits); - ReadOnlySpan tagSpan = this.tag.GetSpan(); + ReadOnlySpan tagSpan = this.tagsOwner.GetSpan(); byte index = tagSpan[GetPaletteIndex(r + 1, g + 1, b + 1, a + 1)]; - ref TPixel paletteRef = ref MemoryMarshal.GetReference(this.pixelMap.GetPaletteSpan()); + ref TPixel paletteRef = ref MemoryMarshal.GetReference(this.pixelMap.Palette.Span); match = Unsafe.Add(ref paletteRef, index); return index; } @@ -166,12 +166,12 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization if (!this.isDisposed) { this.isDisposed = true; - this.moments?.Dispose(); - this.tag?.Dispose(); - this.palette?.Dispose(); - this.moments = null; - this.tag = null; - this.palette = null; + this.momentsOwner?.Dispose(); + this.tagsOwner?.Dispose(); + this.paletteOwner?.Dispose(); + this.momentsOwner = null; + this.tagsOwner = null; + this.paletteOwner = null; } } @@ -350,7 +350,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization /// The bounds within the source image to quantize. private void Build3DHistogram(ImageFrame source, Rectangle bounds) { - Span momentSpan = this.moments.GetSpan(); + Span momentSpan = this.momentsOwner.GetSpan(); // Build up the 3-D color histogram using IMemoryOwner buffer = this.memoryAllocator.Allocate(bounds.Width); @@ -384,7 +384,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization using IMemoryOwner volume = allocator.Allocate(IndexCount * IndexAlphaCount); using IMemoryOwner area = allocator.Allocate(IndexAlphaCount); - Span momentSpan = this.moments.GetSpan(); + Span momentSpan = this.momentsOwner.GetSpan(); Span volumeSpan = volume.GetSpan(); Span areaSpan = area.GetSpan(); int baseIndex = GetPaletteIndex(1, 0, 0, 0); @@ -426,7 +426,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization /// The . private double Variance(ref Box cube) { - ReadOnlySpan momentSpan = this.moments.GetSpan(); + ReadOnlySpan momentSpan = this.momentsOwner.GetSpan(); Moment volume = Volume(ref cube, momentSpan); Moment variance = @@ -467,7 +467,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization /// The . private float Maximize(ref Box cube, int direction, int first, int last, out int cut, Moment whole) { - ReadOnlySpan momentSpan = this.moments.GetSpan(); + ReadOnlySpan momentSpan = this.momentsOwner.GetSpan(); Moment bottom = Bottom(ref cube, direction, momentSpan); float max = 0F; @@ -513,7 +513,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization /// Returns a value indicating whether the box has been split. private bool Cut(ref Box set1, ref Box set2) { - ReadOnlySpan momentSpan = this.moments.GetSpan(); + ReadOnlySpan momentSpan = this.momentsOwner.GetSpan(); Moment whole = Volume(ref set1, momentSpan); float maxR = this.Maximize(ref set1, 3, set1.RMin + 1, set1.RMax, out int cutR, whole); @@ -598,7 +598,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization /// A label. private void Mark(ref Box cube, byte label) { - Span tagSpan = this.tag.GetSpan(); + Span tagSpan = this.tagsOwner.GetSpan(); for (int r = cube.RMin + 1; r <= cube.RMax; r++) { @@ -620,7 +620,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization /// private void BuildCube() { - Span vv = stackalloc double[this.colors]; + Span vv = stackalloc double[this.maxColors]; ref Box cube = ref this.colorCube[0]; cube.RMin = cube.GMin = cube.BMin = cube.AMin = 0; @@ -629,7 +629,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization int next = 0; - for (int i = 1; i < this.colors; i++) + for (int i = 1; i < this.maxColors; i++) { ref Box nextCube = ref this.colorCube[next]; ref Box currentCube = ref this.colorCube[i]; @@ -658,7 +658,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization if (temp <= 0D) { - this.colors = i + 1; + this.maxColors = i + 1; break; } } diff --git a/tests/ImageSharp.Tests/Quantization/QuantizedImageTests.cs b/tests/ImageSharp.Tests/Quantization/QuantizedImageTests.cs index cd93ab0cf..7e4eced8f 100644 --- a/tests/ImageSharp.Tests/Quantization/QuantizedImageTests.cs +++ b/tests/ImageSharp.Tests/Quantization/QuantizedImageTests.cs @@ -71,10 +71,10 @@ namespace SixLabors.ImageSharp.Tests foreach (ImageFrame frame in image.Frames) { using (IFrameQuantizer frameQuantizer = quantizer.CreateFrameQuantizer(this.Configuration)) - using (QuantizedFrame quantized = frameQuantizer.QuantizeFrame(frame, frame.Bounds())) + using (IndexedImageFrame quantized = frameQuantizer.QuantizeFrame(frame, frame.Bounds())) { int index = this.GetTransparentIndex(quantized); - Assert.Equal(index, quantized.GetPixelSpan()[0]); + Assert.Equal(index, quantized.GetPixelBufferSpan()[0]); } } } @@ -101,27 +101,27 @@ namespace SixLabors.ImageSharp.Tests foreach (ImageFrame frame in image.Frames) { using (IFrameQuantizer frameQuantizer = quantizer.CreateFrameQuantizer(this.Configuration)) - using (QuantizedFrame quantized = frameQuantizer.QuantizeFrame(frame, frame.Bounds())) + using (IndexedImageFrame quantized = frameQuantizer.QuantizeFrame(frame, frame.Bounds())) { int index = this.GetTransparentIndex(quantized); - Assert.Equal(index, quantized.GetPixelSpan()[0]); + Assert.Equal(index, quantized.GetPixelBufferSpan()[0]); } } } } - private int GetTransparentIndex(QuantizedFrame quantized) + private int GetTransparentIndex(IndexedImageFrame quantized) where TPixel : unmanaged, IPixel { // Transparent pixels are much more likely to be found at the end of a palette int index = -1; - Rgba32 trans = default; ReadOnlySpan paletteSpan = quantized.Palette.Span; - for (int i = paletteSpan.Length - 1; i >= 0; i--) - { - paletteSpan[i].ToRgba32(ref trans); + Span colorSpan = stackalloc Rgba32[QuantizerConstants.MaxColors].Slice(0, paletteSpan.Length); - if (trans.Equals(default)) + PixelOperations.Instance.ToRgba32(quantized.Configuration, paletteSpan, colorSpan); + for (int i = colorSpan.Length - 1; i >= 0; i--) + { + if (colorSpan[i].Equals(default)) { index = i; } diff --git a/tests/ImageSharp.Tests/Quantization/WuQuantizerTests.cs b/tests/ImageSharp.Tests/Quantization/WuQuantizerTests.cs index f3bcd0b95..2a0a02d94 100644 --- a/tests/ImageSharp.Tests/Quantization/WuQuantizerTests.cs +++ b/tests/ImageSharp.Tests/Quantization/WuQuantizerTests.cs @@ -21,13 +21,13 @@ namespace SixLabors.ImageSharp.Tests.Quantization ImageFrame frame = image.Frames.RootFrame; using IFrameQuantizer frameQuantizer = quantizer.CreateFrameQuantizer(config); - using QuantizedFrame result = frameQuantizer.QuantizeFrame(frame, frame.Bounds()); + using IndexedImageFrame result = frameQuantizer.QuantizeFrame(frame, frame.Bounds()); Assert.Equal(1, result.Palette.Length); - Assert.Equal(1, result.GetPixelSpan().Length); + Assert.Equal(1, result.GetPixelBufferSpan().Length); Assert.Equal(Color.Black, (Color)result.Palette.Span[0]); - Assert.Equal(0, result.GetPixelSpan()[0]); + Assert.Equal(0, result.GetPixelBufferSpan()[0]); } [Fact] @@ -40,13 +40,13 @@ namespace SixLabors.ImageSharp.Tests.Quantization ImageFrame frame = image.Frames.RootFrame; using IFrameQuantizer frameQuantizer = quantizer.CreateFrameQuantizer(config); - using QuantizedFrame result = frameQuantizer.QuantizeFrame(frame, frame.Bounds()); + using IndexedImageFrame result = frameQuantizer.QuantizeFrame(frame, frame.Bounds()); Assert.Equal(1, result.Palette.Length); - Assert.Equal(1, result.GetPixelSpan().Length); + Assert.Equal(1, result.GetPixelBufferSpan().Length); Assert.Equal(default, result.Palette.Span[0]); - Assert.Equal(0, result.GetPixelSpan()[0]); + Assert.Equal(0, result.GetPixelBufferSpan()[0]); } [Fact] @@ -85,19 +85,19 @@ namespace SixLabors.ImageSharp.Tests.Quantization ImageFrame frame = image.Frames.RootFrame; using IFrameQuantizer frameQuantizer = quantizer.CreateFrameQuantizer(config); - using QuantizedFrame result = frameQuantizer.QuantizeFrame(frame, frame.Bounds()); + using IndexedImageFrame result = frameQuantizer.QuantizeFrame(frame, frame.Bounds()); Assert.Equal(256, result.Palette.Length); - Assert.Equal(256, result.GetPixelSpan().Length); + Assert.Equal(256, result.GetPixelBufferSpan().Length); var actualImage = new Image(1, 256); ReadOnlySpan paletteSpan = result.Palette.Span; - int paletteCount = result.Palette.Length - 1; + int paletteCount = paletteSpan.Length - 1; for (int y = 0; y < actualImage.Height; y++) { Span row = actualImage.GetPixelRowSpan(y); - ReadOnlySpan quantizedPixelSpan = result.GetPixelSpan(); + ReadOnlySpan quantizedPixelSpan = result.GetPixelBufferSpan(); int yy = y * actualImage.Width; for (int x = 0; x < actualImage.Width; x++) @@ -123,7 +123,7 @@ namespace SixLabors.ImageSharp.Tests.Quantization ImageFrame frame = image.Frames.RootFrame; using IFrameQuantizer frameQuantizer = quantizer.CreateFrameQuantizer(config); - using QuantizedFrame result = frameQuantizer.QuantizeFrame(frame, frame.Bounds()); + using IndexedImageFrame result = frameQuantizer.QuantizeFrame(frame, frame.Bounds()); Assert.Equal(48, result.Palette.Length); } @@ -152,17 +152,17 @@ namespace SixLabors.ImageSharp.Tests.Quantization ImageFrame frame = image.Frames.RootFrame; using (IFrameQuantizer frameQuantizer = quantizer.CreateFrameQuantizer(config)) - using (QuantizedFrame result = frameQuantizer.QuantizeFrame(frame, frame.Bounds())) + using (IndexedImageFrame result = frameQuantizer.QuantizeFrame(frame, frame.Bounds())) { Assert.Equal(4 * 8, result.Palette.Length); - Assert.Equal(256, result.GetPixelSpan().Length); + Assert.Equal(256, result.GetPixelBufferSpan().Length); ReadOnlySpan paletteSpan = result.Palette.Span; - int paletteCount = result.Palette.Length - 1; + int paletteCount = paletteSpan.Length - 1; for (int y = 0; y < actualImage.Height; y++) { Span row = actualImage.GetPixelRowSpan(y); - ReadOnlySpan quantizedPixelSpan = result.GetPixelSpan(); + ReadOnlySpan quantizedPixelSpan = result.GetPixelBufferSpan(); int yy = y * actualImage.Width; for (int x = 0; x < actualImage.Width; x++) From 2cc1747e0bfd9af5fedbb3ee24b61e33f71fb25d Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Thu, 5 Mar 2020 16:34:44 +1100 Subject: [PATCH 54/64] Introduce palette property --- src/ImageSharp/Formats/Png/PngEncoderCore.cs | 3 +- .../PaletteDitherProcessor{TPixel}.cs | 16 ++++---- ...tensions.cs => FrameQuantizerUtilities.cs} | 38 ++++++++++++++++--- .../Quantization/IFrameQuantizer{TPixel}.cs | 11 +++++- .../OctreeFrameQuantizer{TPixel}.cs | 22 ++++++++--- .../PaletteFrameQuantizer{TPixel}.cs | 10 +++-- .../Quantization/WuFrameQuantizer{TPixel}.cs | 22 ++++++++--- 7 files changed, 91 insertions(+), 31 deletions(-) rename src/ImageSharp/Processing/Processors/Quantization/{FrameQuantizerExtensions.cs => FrameQuantizerUtilities.cs} (77%) diff --git a/src/ImageSharp/Formats/Png/PngEncoderCore.cs b/src/ImageSharp/Formats/Png/PngEncoderCore.cs index 6caaa1df0..8a26fb51c 100644 --- a/src/ImageSharp/Formats/Png/PngEncoderCore.cs +++ b/src/ImageSharp/Formats/Png/PngEncoderCore.cs @@ -384,8 +384,7 @@ namespace SixLabors.ImageSharp.Formats.Png } else { - int stride = this.currentScanline.Length(); - quantized.GetPixelBufferSpan().Slice(row * stride, stride).CopyTo(this.currentScanline.GetSpan()); + quantized.GetPixelRowSpan(row).CopyTo(this.currentScanline.GetSpan()); } break; diff --git a/src/ImageSharp/Processing/Processors/Dithering/PaletteDitherProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Dithering/PaletteDitherProcessor{TPixel}.cs index 1f554536c..e0dd4eae1 100644 --- a/src/ImageSharp/Processing/Processors/Dithering/PaletteDitherProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Dithering/PaletteDitherProcessor{TPixel}.cs @@ -4,7 +4,6 @@ using System; using System.Buffers; using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing.Processors.Quantization; @@ -19,7 +18,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering { private readonly DitherProcessor ditherProcessor; private readonly IDither dither; - private IMemoryOwner paletteMemory; + private IMemoryOwner paletteOwner; private bool isDisposed; /// @@ -35,12 +34,12 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering this.dither = definition.Dither; ReadOnlySpan sourcePalette = definition.Palette.Span; - this.paletteMemory = this.Configuration.MemoryAllocator.Allocate(sourcePalette.Length); - Color.ToPixel(this.Configuration, sourcePalette, this.paletteMemory.Memory.Span); + this.paletteOwner = this.Configuration.MemoryAllocator.Allocate(sourcePalette.Length); + Color.ToPixel(this.Configuration, sourcePalette, this.paletteOwner.Memory.Span); this.ditherProcessor = new DitherProcessor( this.Configuration, - this.paletteMemory.Memory, + this.paletteOwner.Memory, definition.DitherScale); } @@ -59,14 +58,13 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering return; } + this.isDisposed = true; if (disposing) { - this.paletteMemory?.Dispose(); + this.paletteOwner.Dispose(); } - this.paletteMemory = null; - - this.isDisposed = true; + this.paletteOwner = null; base.Dispose(disposing); } diff --git a/src/ImageSharp/Processing/Processors/Quantization/FrameQuantizerExtensions.cs b/src/ImageSharp/Processing/Processors/Quantization/FrameQuantizerUtilities.cs similarity index 77% rename from src/ImageSharp/Processing/Processors/Quantization/FrameQuantizerExtensions.cs rename to src/ImageSharp/Processing/Processors/Quantization/FrameQuantizerUtilities.cs index 26da6a397..4d75042ea 100644 --- a/src/ImageSharp/Processing/Processors/Quantization/FrameQuantizerExtensions.cs +++ b/src/ImageSharp/Processing/Processors/Quantization/FrameQuantizerUtilities.cs @@ -11,10 +11,28 @@ using SixLabors.ImageSharp.Processing.Processors.Dithering; namespace SixLabors.ImageSharp.Processing.Processors.Quantization { /// - /// Contains extension methods for frame quantizers. + /// Contains utility methods for instances. /// - public static class FrameQuantizerExtensions + public static class FrameQuantizerUtilities { + /// + /// Helper method for throwing an exception when a frame quantizer palette has + /// been requested but not built yet. + /// + /// The pixel format. + /// The frame quantizer palette. + /// + /// The palette has not been built via + /// + public static void CheckPaletteState(in ReadOnlyMemory palette) + where TPixel : unmanaged, IPixel + { + if (palette.Equals(default)) + { + throw new InvalidOperationException("Frame Quantizer palette has not been built."); + } + } + /// /// Quantizes an image frame and return the resulting output pixels. /// @@ -37,8 +55,13 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization var interest = Rectangle.Intersect(source.Bounds(), bounds); // Collect the palette. Required before the second pass runs. - ReadOnlyMemory palette = quantizer.BuildPalette(source, interest); - var destination = new IndexedImageFrame(quantizer.Configuration, interest.Width, interest.Height, palette); + quantizer.BuildPalette(source, interest); + + var destination = new IndexedImageFrame( + quantizer.Configuration, + interest.Width, + interest.Height, + quantizer.Palette); if (quantizer.Options.Dither is null) { @@ -67,7 +90,12 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization if (dither is null) { - var operation = new RowIntervalOperation(ref quantizer, source, destination, bounds); + var operation = new RowIntervalOperation( + ref quantizer, + source, + destination, + bounds); + ParallelRowIterator.IterateRowIntervals( quantizer.Configuration, bounds, diff --git a/src/ImageSharp/Processing/Processors/Quantization/IFrameQuantizer{TPixel}.cs b/src/ImageSharp/Processing/Processors/Quantization/IFrameQuantizer{TPixel}.cs index 64caad3e2..cc87715eb 100644 --- a/src/ImageSharp/Processing/Processors/Quantization/IFrameQuantizer{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Quantization/IFrameQuantizer{TPixel}.cs @@ -23,13 +23,20 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization /// QuantizerOptions Options { get; } + /// + /// Gets the quantized color palette. + /// + /// + /// The palette has not been built via . + /// + ReadOnlyMemory Palette { get; } + /// /// Builds the quantized palette from the given image frame and bounds. /// /// The source image frame. /// The region of interest bounds. - /// The palette. - ReadOnlyMemory BuildPalette(ImageFrame source, Rectangle bounds); + void BuildPalette(ImageFrame source, Rectangle bounds); /// /// Quantizes an image frame and return the resulting output pixels. diff --git a/src/ImageSharp/Processing/Processors/Quantization/OctreeFrameQuantizer{TPixel}.cs b/src/ImageSharp/Processing/Processors/Quantization/OctreeFrameQuantizer{TPixel}.cs index 6c31fca7f..433ac4567 100644 --- a/src/ImageSharp/Processing/Processors/Quantization/OctreeFrameQuantizer{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Quantization/OctreeFrameQuantizer{TPixel}.cs @@ -23,6 +23,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization private readonly int maxColors; private readonly Octree octree; private IMemoryOwner paletteOwner; + private ReadOnlyMemory palette; private EuclideanPixelMap pixelMap; private readonly bool isDithering; private bool isDisposed; @@ -44,6 +45,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization this.maxColors = this.Options.MaxColors; this.octree = new Octree(ImageMaths.GetBitsNeededForColorDepth(this.maxColors).Clamp(1, 8)); this.paletteOwner = configuration.MemoryAllocator.Allocate(this.maxColors, AllocationOptions.Clean); + this.palette = default; this.pixelMap = default; this.isDithering = !(this.Options.Dither is null); this.isDisposed = false; @@ -56,13 +58,18 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization public QuantizerOptions Options { get; } /// - [MethodImpl(InliningOptions.ShortMethod)] - public readonly IndexedImageFrame QuantizeFrame(ImageFrame source, Rectangle bounds) - => FrameQuantizerExtensions.QuantizeFrame(ref Unsafe.AsRef(this), source, bounds); + public ReadOnlyMemory Palette + { + get + { + FrameQuantizerUtilities.CheckPaletteState(in this.palette); + return this.palette; + } + } /// [MethodImpl(InliningOptions.ShortMethod)] - public ReadOnlyMemory BuildPalette(ImageFrame source, Rectangle bounds) + public void BuildPalette(ImageFrame source, Rectangle bounds) { using IMemoryOwner buffer = this.Configuration.MemoryAllocator.Allocate(bounds.Width); Span bufferSpan = buffer.GetSpan(); @@ -90,9 +97,14 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization ReadOnlyMemory result = this.paletteOwner.Memory.Slice(0, Math.Min(paletteIndex + 2, QuantizerConstants.MaxColors)); this.pixelMap = new EuclideanPixelMap(this.Configuration, result); - return result; + this.palette = result; } + /// + [MethodImpl(InliningOptions.ShortMethod)] + public readonly IndexedImageFrame QuantizeFrame(ImageFrame source, Rectangle bounds) + => FrameQuantizerUtilities.QuantizeFrame(ref Unsafe.AsRef(this), source, bounds); + /// [MethodImpl(InliningOptions.ShortMethod)] public readonly byte GetQuantizedColor(TPixel color, out TPixel match) diff --git a/src/ImageSharp/Processing/Processors/Quantization/PaletteFrameQuantizer{TPixel}.cs b/src/ImageSharp/Processing/Processors/Quantization/PaletteFrameQuantizer{TPixel}.cs index d37116855..ade73e2d0 100644 --- a/src/ImageSharp/Processing/Processors/Quantization/PaletteFrameQuantizer{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Quantization/PaletteFrameQuantizer{TPixel}.cs @@ -43,15 +43,19 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization /// public QuantizerOptions Options { get; } + /// + public ReadOnlyMemory Palette => this.pixelMap.Palette; + /// [MethodImpl(InliningOptions.ShortMethod)] public readonly IndexedImageFrame QuantizeFrame(ImageFrame source, Rectangle bounds) - => FrameQuantizerExtensions.QuantizeFrame(ref Unsafe.AsRef(this), source, bounds); + => FrameQuantizerUtilities.QuantizeFrame(ref Unsafe.AsRef(this), source, bounds); /// [MethodImpl(InliningOptions.ShortMethod)] - public readonly ReadOnlyMemory BuildPalette(ImageFrame source, Rectangle bounds) - => this.pixelMap.Palette; + public void BuildPalette(ImageFrame source, Rectangle bounds) + { + } /// [MethodImpl(InliningOptions.ShortMethod)] diff --git a/src/ImageSharp/Processing/Processors/Quantization/WuFrameQuantizer{TPixel}.cs b/src/ImageSharp/Processing/Processors/Quantization/WuFrameQuantizer{TPixel}.cs index 60f3a0a2a..67a46375d 100644 --- a/src/ImageSharp/Processing/Processors/Quantization/WuFrameQuantizer{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Quantization/WuFrameQuantizer{TPixel}.cs @@ -69,6 +69,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization private IMemoryOwner momentsOwner; private IMemoryOwner tagsOwner; private IMemoryOwner paletteOwner; + private ReadOnlyMemory palette; private int maxColors; private readonly Box[] colorCube; private EuclideanPixelMap pixelMap; @@ -93,6 +94,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization this.momentsOwner = this.memoryAllocator.Allocate(TableLength, AllocationOptions.Clean); this.tagsOwner = this.memoryAllocator.Allocate(TableLength, AllocationOptions.Clean); this.paletteOwner = this.memoryAllocator.Allocate(this.maxColors, AllocationOptions.Clean); + this.palette = default; this.colorCube = new Box[this.maxColors]; this.isDisposed = false; this.pixelMap = default; @@ -106,12 +108,17 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization public QuantizerOptions Options { get; } /// - [MethodImpl(InliningOptions.ShortMethod)] - public readonly IndexedImageFrame QuantizeFrame(ImageFrame source, Rectangle bounds) - => FrameQuantizerExtensions.QuantizeFrame(ref Unsafe.AsRef(this), source, bounds); + public ReadOnlyMemory Palette + { + get + { + FrameQuantizerUtilities.CheckPaletteState(in this.palette); + return this.palette; + } + } /// - public ReadOnlyMemory BuildPalette(ImageFrame source, Rectangle bounds) + public void BuildPalette(ImageFrame source, Rectangle bounds) { this.Build3DHistogram(source, bounds); this.Get3DMoments(this.memoryAllocator); @@ -134,9 +141,14 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization ReadOnlyMemory result = this.paletteOwner.Memory.Slice(0, this.maxColors); this.pixelMap = new EuclideanPixelMap(this.Configuration, result); - return result; + this.palette = result; } + /// + [MethodImpl(InliningOptions.ShortMethod)] + public readonly IndexedImageFrame QuantizeFrame(ImageFrame source, Rectangle bounds) + => FrameQuantizerUtilities.QuantizeFrame(ref Unsafe.AsRef(this), source, bounds); + /// public readonly byte GetQuantizedColor(TPixel color, out TPixel match) { From 9acbb10f5a907a8caa076c87deaec2394eb75102 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Thu, 5 Mar 2020 17:07:25 +1100 Subject: [PATCH 55/64] Faster png palette encoding. --- src/ImageSharp/Formats/Png/PngEncoderCore.cs | 65 +++++++++---------- .../Quantization/IndexedImageFrame{TPixel}.cs | 1 + 2 files changed, 31 insertions(+), 35 deletions(-) diff --git a/src/ImageSharp/Formats/Png/PngEncoderCore.cs b/src/ImageSharp/Formats/Png/PngEncoderCore.cs index 8a26fb51c..25ccf7bd1 100644 --- a/src/ImageSharp/Formats/Png/PngEncoderCore.cs +++ b/src/ImageSharp/Formats/Png/PngEncoderCore.cs @@ -555,49 +555,44 @@ namespace SixLabors.ImageSharp.Formats.Png // Grab the palette and write it to the stream. ReadOnlySpan palette = quantized.Palette.Span; - int paletteLength = Math.Min(palette.Length, QuantizerConstants.MaxColors); - int colorTableLength = paletteLength * 3; - bool anyAlpha = false; + int paletteLength = palette.Length; + int colorTableLength = paletteLength * Unsafe.SizeOf(); + bool hasAlpha = false; - using (IManagedByteBuffer colorTable = this.memoryAllocator.AllocateManagedByteBuffer(colorTableLength)) - using (IManagedByteBuffer alphaTable = this.memoryAllocator.AllocateManagedByteBuffer(paletteLength)) - { - ref byte colorTableRef = ref MemoryMarshal.GetReference(colorTable.GetSpan()); - ref byte alphaTableRef = ref MemoryMarshal.GetReference(alphaTable.GetSpan()); - ReadOnlySpan quantizedSpan = quantized.GetPixelBufferSpan(); - - Rgba32 rgba = default; - - for (int i = 0; i < paletteLength; i++) - { - if (quantizedSpan.IndexOf((byte)i) > -1) - { - int offset = i * 3; - palette[i].ToRgba32(ref rgba); + using IManagedByteBuffer colorTable = this.memoryAllocator.AllocateManagedByteBuffer(colorTableLength); + using IManagedByteBuffer alphaTable = this.memoryAllocator.AllocateManagedByteBuffer(paletteLength); - byte alpha = rgba.A; + ref Rgb24 colorTableRef = ref MemoryMarshal.GetReference(MemoryMarshal.Cast(colorTable.GetSpan())); + ref byte alphaTableRef = ref MemoryMarshal.GetReference(alphaTable.GetSpan()); - Unsafe.Add(ref colorTableRef, offset) = rgba.R; - Unsafe.Add(ref colorTableRef, offset + 1) = rgba.G; - Unsafe.Add(ref colorTableRef, offset + 2) = rgba.B; + // Bulk convert our palette to RGBA to allow assignment to tables. + // Palette length maxes out at 256 so safe to stackalloc. + Span rgbaPaletteSpan = stackalloc Rgba32[palette.Length]; + PixelOperations.Instance.ToRgba32(quantized.Configuration, quantized.Palette.Span, rgbaPaletteSpan); + ref Rgba32 rgbaPaletteRef = ref MemoryMarshal.GetReference(rgbaPaletteSpan); - if (alpha > this.options.Threshold) - { - alpha = byte.MaxValue; - } + // Loop, assign, and extract alpha values from the palette. + for (int i = 0; i < paletteLength; i++) + { + Rgba32 rgba = Unsafe.Add(ref rgbaPaletteRef, i); + byte alpha = rgba.A; - anyAlpha = anyAlpha || alpha < byte.MaxValue; - Unsafe.Add(ref alphaTableRef, i) = alpha; - } + Unsafe.Add(ref colorTableRef, i) = rgba.Rgb; + if (alpha > this.options.Threshold) + { + alpha = byte.MaxValue; } - this.WriteChunk(stream, PngChunkType.Palette, colorTable.Array, 0, colorTableLength); + hasAlpha = hasAlpha || alpha < byte.MaxValue; + Unsafe.Add(ref alphaTableRef, i) = alpha; + } - // Write the transparency data - if (anyAlpha) - { - this.WriteChunk(stream, PngChunkType.Transparency, alphaTable.Array, 0, paletteLength); - } + this.WriteChunk(stream, PngChunkType.Palette, colorTable.Array, 0, colorTableLength); + + // Write the transparency data + if (hasAlpha) + { + this.WriteChunk(stream, PngChunkType.Transparency, alphaTable.Array, 0, paletteLength); } } diff --git a/src/ImageSharp/Processing/Processors/Quantization/IndexedImageFrame{TPixel}.cs b/src/ImageSharp/Processing/Processors/Quantization/IndexedImageFrame{TPixel}.cs index 42aadb5fd..ac737f452 100644 --- a/src/ImageSharp/Processing/Processors/Quantization/IndexedImageFrame{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Quantization/IndexedImageFrame{TPixel}.cs @@ -32,6 +32,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization internal IndexedImageFrame(Configuration configuration, int width, int height, ReadOnlyMemory palette) { Guard.NotNull(configuration, nameof(configuration)); + Guard.MustBeLessThanOrEqualTo(palette.Length, QuantizerConstants.MaxColors, nameof(palette)); Guard.MustBeGreaterThan(width, 0, nameof(width)); Guard.MustBeGreaterThan(height, 0, nameof(height)); From 3d6d39a75d9bbdbc6963750e6235a506266c6e5b Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Thu, 5 Mar 2020 17:33:44 +1100 Subject: [PATCH 56/64] Faster Gif transparency lookup. --- src/ImageSharp/Formats/Gif/GifEncoderCore.cs | 29 ++++++++------------ 1 file changed, 12 insertions(+), 17 deletions(-) diff --git a/src/ImageSharp/Formats/Gif/GifEncoderCore.cs b/src/ImageSharp/Formats/Gif/GifEncoderCore.cs index 29e2e8fe2..dcd0be34b 100644 --- a/src/ImageSharp/Formats/Gif/GifEncoderCore.cs +++ b/src/ImageSharp/Formats/Gif/GifEncoderCore.cs @@ -2,7 +2,6 @@ // Licensed under the Apache License, Version 2.0. using System; -using System.Buffers; using System.IO; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; @@ -86,7 +85,7 @@ namespace SixLabors.ImageSharp.Formats.Gif } // Get the number of bits. - this.bitDepth = ImageMaths.GetBitsNeededForColorDepth(quantized.Palette.Length).Clamp(1, 8); + this.bitDepth = ImageMaths.GetBitsNeededForColorDepth(quantized.Palette.Length); // Write the header. this.WriteHeader(stream); @@ -193,7 +192,7 @@ namespace SixLabors.ImageSharp.Formats.Gif } } - this.bitDepth = ImageMaths.GetBitsNeededForColorDepth(quantized.Palette.Length).Clamp(1, 8); + this.bitDepth = ImageMaths.GetBitsNeededForColorDepth(quantized.Palette.Length); this.WriteGraphicalControlExtension(frameMetadata, this.GetTransparentIndex(quantized), stream); this.WriteImageDescriptor(frame, true, stream); this.WriteColorTable(quantized, stream); @@ -218,21 +217,18 @@ namespace SixLabors.ImageSharp.Formats.Gif where TPixel : unmanaged, IPixel { // Transparent pixels are much more likely to be found at the end of a palette. + // Palette length maxes out at 256 so safe to stackalloc. int index = -1; - int length = quantized.Palette.Length; + ReadOnlySpan paletteSpan = quantized.Palette.Span; + Span rgbaSpan = stackalloc Rgba32[paletteSpan.Length]; + PixelOperations.Instance.ToRgba32(quantized.Configuration, paletteSpan, rgbaSpan); + ref Rgba32 rgbaSpanRef = ref MemoryMarshal.GetReference(rgbaSpan); - using (IMemoryOwner rgbaBuffer = this.memoryAllocator.Allocate(length)) + for (int i = rgbaSpan.Length - 1; i >= 0; i--) { - Span rgbaSpan = rgbaBuffer.GetSpan(); - ref Rgba32 paletteRef = ref MemoryMarshal.GetReference(rgbaSpan); - PixelOperations.Instance.ToRgba32(this.configuration, quantized.Palette.Span, rgbaSpan); - - for (int i = quantized.Palette.Length - 1; i >= 0; i--) + if (Unsafe.Add(ref rgbaSpanRef, i).Equals(default)) { - if (Unsafe.Add(ref paletteRef, i).Equals(default)) - { - index = i; - } + index = i; } } @@ -451,15 +447,14 @@ namespace SixLabors.ImageSharp.Formats.Gif where TPixel : unmanaged, IPixel { // The maximum number of colors for the bit depth - int colorTableLength = ImageMaths.GetColorCountForBitDepth(this.bitDepth) * 3; - int pixelCount = image.Palette.Length; + int colorTableLength = ImageMaths.GetColorCountForBitDepth(this.bitDepth) * Unsafe.SizeOf(); using IManagedByteBuffer colorTable = this.memoryAllocator.AllocateManagedByteBuffer(colorTableLength, AllocationOptions.Clean); PixelOperations.Instance.ToRgb24Bytes( this.configuration, image.Palette.Span, colorTable.GetSpan(), - pixelCount); + image.Palette.Length); stream.Write(colorTable.Array, 0, colorTableLength); } From 773f773f6d203db38553cabd0ec6554bb9248ea6 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Thu, 5 Mar 2020 17:33:51 +1100 Subject: [PATCH 57/64] Cleanup --- src/ImageSharp/Formats/Png/PngEncoderCore.cs | 2 +- .../Formats/Gif/GifEncoderTests.cs | 18 ------------------ 2 files changed, 1 insertion(+), 19 deletions(-) diff --git a/src/ImageSharp/Formats/Png/PngEncoderCore.cs b/src/ImageSharp/Formats/Png/PngEncoderCore.cs index 25ccf7bd1..8dbfc25d7 100644 --- a/src/ImageSharp/Formats/Png/PngEncoderCore.cs +++ b/src/ImageSharp/Formats/Png/PngEncoderCore.cs @@ -567,7 +567,7 @@ namespace SixLabors.ImageSharp.Formats.Png // Bulk convert our palette to RGBA to allow assignment to tables. // Palette length maxes out at 256 so safe to stackalloc. - Span rgbaPaletteSpan = stackalloc Rgba32[palette.Length]; + Span rgbaPaletteSpan = stackalloc Rgba32[paletteLength]; PixelOperations.Instance.ToRgba32(quantized.Configuration, quantized.Palette.Span, rgbaPaletteSpan); ref Rgba32 rgbaPaletteRef = ref MemoryMarshal.GetReference(rgbaPaletteSpan); diff --git a/tests/ImageSharp.Tests/Formats/Gif/GifEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Gif/GifEncoderTests.cs index 4adffca4f..588f65254 100644 --- a/tests/ImageSharp.Tests/Formats/Gif/GifEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Gif/GifEncoderTests.cs @@ -5,7 +5,6 @@ using System.IO; using SixLabors.ImageSharp.Formats.Gif; using SixLabors.ImageSharp.Metadata; using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Processing.Processors.Quantization; using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; using Xunit; @@ -26,23 +25,6 @@ namespace SixLabors.ImageSharp.Tests.Formats.Gif { TestImages.Gif.Ratio4x1, 4, 1, PixelResolutionUnit.AspectRatio } }; - [Theory] - [WithFile(TestImages.Bmp.Car, PixelTypes.Rgba32)] - public void EncodeAllocationCheck(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - GifEncoder encoder = new GifEncoder - { - Quantizer = new WebSafePaletteQuantizer(new QuantizerOptions { Dither = KnownDitherings.Bayer4x4 }) - }; - - using (Image image = provider.GetImage()) - { - // Always save as we need to compare the encoded output. - provider.Utility.SaveTestOutputFile(image, "gif", encoder); - } - } - [Theory] [WithTestPatternImages(100, 100, TestPixelTypes, false)] [WithTestPatternImages(100, 100, TestPixelTypes, false)] From 32725907db6fd285413572329e2a9a94fd3b4cbb Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Fri, 6 Mar 2020 15:55:06 +1100 Subject: [PATCH 58/64] tem remove submodule to reset dirty --- .gitmodules | 3 --- shared-infrastructure | 1 - 2 files changed, 4 deletions(-) delete mode 160000 shared-infrastructure diff --git a/.gitmodules b/.gitmodules index 55389121f..e7972649f 100644 --- a/.gitmodules +++ b/.gitmodules @@ -2,6 +2,3 @@ path = tests/Images/External url = https://github.com/SixLabors/Imagesharp.Tests.Images.git branch = master -[submodule "shared-infrastructure"] - path = shared-infrastructure - url = https://github.com/SixLabors/SharedInfrastructure diff --git a/shared-infrastructure b/shared-infrastructure deleted file mode 160000 index 36b2d55f5..000000000 --- a/shared-infrastructure +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 36b2d55f5bb0d91024955bd26ba220ee41cc96e5 From 893ae087c661bf0dd68aa98dde2d61004573d50b Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Fri, 6 Mar 2020 17:07:38 +1100 Subject: [PATCH 59/64] Fix module, import configs, automate T4 builds --- .editorconfig | 4 +--- .gitmodules | 3 +++ ImageSharp.sln | 1 + shared-infrastructure | 1 + src/Directory.Build.props | 2 ++ src/Directory.Build.targets | 16 ++++++++++++++++ src/ImageSharp/Common/Helpers/Guard.cs | 2 +- src/ImageSharp/ImageSharp.csproj | 9 ++++++++- .../PorterDuffFunctions.Generated.cs | 9 --------- .../Image/ImageFrameCollectionTests.Generic.cs | 8 ++++---- .../ImageFrameCollectionTests.NonGeneric.cs | 4 ++-- 11 files changed, 39 insertions(+), 20 deletions(-) create mode 160000 shared-infrastructure diff --git a/.editorconfig b/.editorconfig index 06e698247..0e4883082 100644 --- a/.editorconfig +++ b/.editorconfig @@ -368,8 +368,6 @@ csharp_style_throw_expression = true:suggestion csharp_style_unused_value_expression_statement_preference = discard_variable:silent csharp_style_unused_value_assignment_preference = discard_variable:suggestion -csharp_style_var_for_built_in_types = false:silent +csharp_style_var_for_built_in_types = never csharp_style_var_when_type_is_apparent = true:warning csharp_style_var_elsewhere = false:warning - -csharp_prefer_simple_using_statement = false:silent diff --git a/.gitmodules b/.gitmodules index e7972649f..55389121f 100644 --- a/.gitmodules +++ b/.gitmodules @@ -2,3 +2,6 @@ path = tests/Images/External url = https://github.com/SixLabors/Imagesharp.Tests.Images.git branch = master +[submodule "shared-infrastructure"] + path = shared-infrastructure + url = https://github.com/SixLabors/SharedInfrastructure diff --git a/ImageSharp.sln b/ImageSharp.sln index 40878c575..f1d4afef4 100644 --- a/ImageSharp.sln +++ b/ImageSharp.sln @@ -334,6 +334,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ImageSharp.Tests.ProfilingS EndProject Global GlobalSection(SharedMSBuildProjectFiles) = preSolution + shared-infrastructure\src\SharedInfrastructure\SharedInfrastructure.projitems*{2aa31a1f-142c-43f4-8687-09abca4b3a26}*SharedItemsImports = 5 shared-infrastructure\src\SharedInfrastructure\SharedInfrastructure.projitems*{68a8cc40-6aed-4e96-b524-31b1158fdeea}*SharedItemsImports = 13 EndGlobalSection GlobalSection(SolutionConfigurationPlatforms) = preSolution diff --git a/shared-infrastructure b/shared-infrastructure new file mode 160000 index 000000000..8dfef29f1 --- /dev/null +++ b/shared-infrastructure @@ -0,0 +1 @@ +Subproject commit 8dfef29f1838da76be9596f1a2f1be6d93e453d3 diff --git a/src/Directory.Build.props b/src/Directory.Build.props index 2c13e469f..a78a75d42 100644 --- a/src/Directory.Build.props +++ b/src/Directory.Build.props @@ -34,4 +34,6 @@ + + diff --git a/src/Directory.Build.targets b/src/Directory.Build.targets index 68d4f8949..e2c289c10 100644 --- a/src/Directory.Build.targets +++ b/src/Directory.Build.targets @@ -52,4 +52,20 @@ + + + + + + + + + + + + + + true + + diff --git a/src/ImageSharp/Common/Helpers/Guard.cs b/src/ImageSharp/Common/Helpers/Guard.cs index 1d215d286..3ab1b199a 100644 --- a/src/ImageSharp/Common/Helpers/Guard.cs +++ b/src/ImageSharp/Common/Helpers/Guard.cs @@ -22,7 +22,7 @@ namespace SixLabors { if (!value.GetType().GetTypeInfo().IsValueType) { - ThrowArgumentException("Type must be a struct.", parameterName); + ThrowHelper.ThrowArgumentException("Type must be a struct.", parameterName); } } } diff --git a/src/ImageSharp/ImageSharp.csproj b/src/ImageSharp/ImageSharp.csproj index be0e9032b..7872bb5c5 100644 --- a/src/ImageSharp/ImageSharp.csproj +++ b/src/ImageSharp/ImageSharp.csproj @@ -37,6 +37,11 @@ + + True + True + Guard.Numeric.tt + True True @@ -202,6 +207,9 @@ DefaultPixelBlenders.Generated.cs TextTemplatingFileGenerator + + Guard.Numeric.cs + @@ -209,5 +217,4 @@ - diff --git a/src/ImageSharp/PixelFormats/PixelBlenders/PorterDuffFunctions.Generated.cs b/src/ImageSharp/PixelFormats/PixelBlenders/PorterDuffFunctions.Generated.cs index 94127432b..8184f1577 100644 --- a/src/ImageSharp/PixelFormats/PixelBlenders/PorterDuffFunctions.Generated.cs +++ b/src/ImageSharp/PixelFormats/PixelBlenders/PorterDuffFunctions.Generated.cs @@ -13,7 +13,6 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders - /// /// Returns the result of the "NormalSrc" compositing equation. /// @@ -419,7 +418,6 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders return dest; } - /// /// Returns the result of the "MultiplySrc" compositing equation. /// @@ -825,7 +823,6 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders return dest; } - /// /// Returns the result of the "AddSrc" compositing equation. /// @@ -1231,7 +1228,6 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders return dest; } - /// /// Returns the result of the "SubtractSrc" compositing equation. /// @@ -1637,7 +1633,6 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders return dest; } - /// /// Returns the result of the "ScreenSrc" compositing equation. /// @@ -2043,7 +2038,6 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders return dest; } - /// /// Returns the result of the "DarkenSrc" compositing equation. /// @@ -2449,7 +2443,6 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders return dest; } - /// /// Returns the result of the "LightenSrc" compositing equation. /// @@ -2855,7 +2848,6 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders return dest; } - /// /// Returns the result of the "OverlaySrc" compositing equation. /// @@ -3261,7 +3253,6 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders return dest; } - /// /// Returns the result of the "HardLightSrc" compositing equation. /// diff --git a/tests/ImageSharp.Tests/Image/ImageFrameCollectionTests.Generic.cs b/tests/ImageSharp.Tests/Image/ImageFrameCollectionTests.Generic.cs index 0b2274581..9ea6a305c 100644 --- a/tests/ImageSharp.Tests/Image/ImageFrameCollectionTests.Generic.cs +++ b/tests/ImageSharp.Tests/Image/ImageFrameCollectionTests.Generic.cs @@ -43,7 +43,7 @@ namespace SixLabors.ImageSharp.Tests this.Collection.AddFrame((ImageFrame)null); }); - Assert.StartsWith("Value cannot be null.", ex.Message); + Assert.StartsWith("Parameter \"frame\" must be not null.", ex.Message); } [Fact] @@ -57,7 +57,7 @@ namespace SixLabors.ImageSharp.Tests this.Collection.AddFrame(data); }); - Assert.StartsWith("Value cannot be null.", ex.Message); + Assert.StartsWith("Parameter \"source\" must be not null.", ex.Message); } [Fact] @@ -69,7 +69,7 @@ namespace SixLabors.ImageSharp.Tests this.Collection.AddFrame(new Rgba32[0]); }); - Assert.StartsWith("Value 0 must be greater than or equal to 100.", ex.Message); + Assert.StartsWith($"Parameter \"data\" ({typeof(int)}) must be greater than or equal to {100}, was {0}", ex.Message); } [Fact] @@ -93,7 +93,7 @@ namespace SixLabors.ImageSharp.Tests this.Collection.InsertFrame(1, null); }); - Assert.StartsWith("Value cannot be null.", ex.Message); + Assert.StartsWith("Parameter \"frame\" must be not null.", ex.Message); } [Fact] diff --git a/tests/ImageSharp.Tests/Image/ImageFrameCollectionTests.NonGeneric.cs b/tests/ImageSharp.Tests/Image/ImageFrameCollectionTests.NonGeneric.cs index b0008d394..08e6f8e1f 100644 --- a/tests/ImageSharp.Tests/Image/ImageFrameCollectionTests.NonGeneric.cs +++ b/tests/ImageSharp.Tests/Image/ImageFrameCollectionTests.NonGeneric.cs @@ -90,7 +90,7 @@ namespace SixLabors.ImageSharp.Tests this.Collection.AddFrame(null); }); - Assert.StartsWith("Value cannot be null.", ex.Message); + Assert.StartsWith("Parameter \"source\" must be not null.", ex.Message); } [Fact] @@ -114,7 +114,7 @@ namespace SixLabors.ImageSharp.Tests this.Collection.InsertFrame(1, null); }); - Assert.StartsWith("Value cannot be null.", ex.Message); + Assert.StartsWith("Parameter \"source\" must be not null.", ex.Message); } [Fact] From b8a535ae43e6a5bdaa1a0622e98f45183a604209 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Fri, 6 Mar 2020 20:39:54 +1100 Subject: [PATCH 60/64] Manually include compiled file in source via auto copy. --- src/Directory.Build.targets | 19 +- .../Common/Helpers/Guard.Numeric.cs | 1154 +++++++++++++++++ src/ImageSharp/ImageSharp.csproj | 8 +- 3 files changed, 1179 insertions(+), 2 deletions(-) create mode 100644 src/ImageSharp/Common/Helpers/Guard.Numeric.cs diff --git a/src/Directory.Build.targets b/src/Directory.Build.targets index e2c289c10..055a6803e 100644 --- a/src/Directory.Build.targets +++ b/src/Directory.Build.targets @@ -55,17 +55,34 @@ + + - + + + + diff --git a/src/ImageSharp/Common/Helpers/Guard.Numeric.cs b/src/ImageSharp/Common/Helpers/Guard.Numeric.cs new file mode 100644 index 000000000..72262e0b9 --- /dev/null +++ b/src/ImageSharp/Common/Helpers/Guard.Numeric.cs @@ -0,0 +1,1154 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Runtime.CompilerServices; + +namespace SixLabors +{ + /// + /// Provides methods to protect against invalid parameters. + /// + internal static partial class Guard + { + /// + /// Ensures that the specified value is less than a maximum value. + /// + /// The target value, which should be validated. + /// The maximum value. + /// The name of the parameter that is to be checked. + /// + /// is greater than the maximum value. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void MustBeLessThan(byte value, byte max, string parameterName) + { + if (value >= max) + { + ThrowHelper.ThrowArgumentOutOfRangeExceptionForMustBeLessThan(value, max, parameterName); + } + } + + /// + /// Verifies that the specified value is less than or equal to a maximum value + /// and throws an exception if it is not. + /// + /// The target value, which should be validated. + /// The maximum value. + /// The name of the parameter that is to be checked. + /// + /// is greater than the maximum value. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void MustBeLessThanOrEqualTo(byte value, byte max, string parameterName) + { + if (value > max) + { + ThrowHelper.ThrowArgumentOutOfRangeExceptionForMustBeLessThanOrEqualTo(value, max, parameterName); + } + } + + /// + /// Verifies that the specified value is greater than a minimum value + /// and throws an exception if it is not. + /// + /// The target value, which should be validated. + /// The minimum value. + /// The name of the parameter that is to be checked. + /// + /// is less than the minimum value. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void MustBeGreaterThan(byte value, byte min, string parameterName) + { + if (value <= min) + { + ThrowHelper.ThrowArgumentOutOfRangeExceptionForMustBeGreaterThan(value, min, parameterName); + } + } + + /// + /// Verifies that the specified value is greater than or equal to a minimum value + /// and throws an exception if it is not. + /// + /// The target value, which should be validated. + /// The minimum value. + /// The name of the parameter that is to be checked. + /// + /// is less than the minimum value. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void MustBeGreaterThanOrEqualTo(byte value, byte min, string parameterName) + { + if (value < min) + { + ThrowHelper.ThrowArgumentOutOfRangeExceptionForMustBeGreaterThanOrEqualTo(value, min, parameterName); + } + } + + /// + /// Verifies that the specified value is greater than or equal to a minimum value and less than + /// or equal to a maximum value and throws an exception if it is not. + /// + /// The target value, which should be validated. + /// The minimum value. + /// The maximum value. + /// The name of the parameter that is to be checked. + /// + /// is less than the minimum value of greater than the maximum value. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void MustBeBetweenOrEqualTo(byte value, byte min, byte max, string parameterName) + { + if (value < min || value > max) + { + ThrowHelper.ThrowArgumentOutOfRangeExceptionForMustBeBetweenOrEqualTo(value, min, max, parameterName); + } + } + + /// + /// Ensures that the specified value is less than a maximum value. + /// + /// The target value, which should be validated. + /// The maximum value. + /// The name of the parameter that is to be checked. + /// + /// is greater than the maximum value. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void MustBeLessThan(sbyte value, sbyte max, string parameterName) + { + if (value >= max) + { + ThrowHelper.ThrowArgumentOutOfRangeExceptionForMustBeLessThan(value, max, parameterName); + } + } + + /// + /// Verifies that the specified value is less than or equal to a maximum value + /// and throws an exception if it is not. + /// + /// The target value, which should be validated. + /// The maximum value. + /// The name of the parameter that is to be checked. + /// + /// is greater than the maximum value. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void MustBeLessThanOrEqualTo(sbyte value, sbyte max, string parameterName) + { + if (value > max) + { + ThrowHelper.ThrowArgumentOutOfRangeExceptionForMustBeLessThanOrEqualTo(value, max, parameterName); + } + } + + /// + /// Verifies that the specified value is greater than a minimum value + /// and throws an exception if it is not. + /// + /// The target value, which should be validated. + /// The minimum value. + /// The name of the parameter that is to be checked. + /// + /// is less than the minimum value. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void MustBeGreaterThan(sbyte value, sbyte min, string parameterName) + { + if (value <= min) + { + ThrowHelper.ThrowArgumentOutOfRangeExceptionForMustBeGreaterThan(value, min, parameterName); + } + } + + /// + /// Verifies that the specified value is greater than or equal to a minimum value + /// and throws an exception if it is not. + /// + /// The target value, which should be validated. + /// The minimum value. + /// The name of the parameter that is to be checked. + /// + /// is less than the minimum value. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void MustBeGreaterThanOrEqualTo(sbyte value, sbyte min, string parameterName) + { + if (value < min) + { + ThrowHelper.ThrowArgumentOutOfRangeExceptionForMustBeGreaterThanOrEqualTo(value, min, parameterName); + } + } + + /// + /// Verifies that the specified value is greater than or equal to a minimum value and less than + /// or equal to a maximum value and throws an exception if it is not. + /// + /// The target value, which should be validated. + /// The minimum value. + /// The maximum value. + /// The name of the parameter that is to be checked. + /// + /// is less than the minimum value of greater than the maximum value. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void MustBeBetweenOrEqualTo(sbyte value, sbyte min, sbyte max, string parameterName) + { + if (value < min || value > max) + { + ThrowHelper.ThrowArgumentOutOfRangeExceptionForMustBeBetweenOrEqualTo(value, min, max, parameterName); + } + } + + /// + /// Ensures that the specified value is less than a maximum value. + /// + /// The target value, which should be validated. + /// The maximum value. + /// The name of the parameter that is to be checked. + /// + /// is greater than the maximum value. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void MustBeLessThan(short value, short max, string parameterName) + { + if (value >= max) + { + ThrowHelper.ThrowArgumentOutOfRangeExceptionForMustBeLessThan(value, max, parameterName); + } + } + + /// + /// Verifies that the specified value is less than or equal to a maximum value + /// and throws an exception if it is not. + /// + /// The target value, which should be validated. + /// The maximum value. + /// The name of the parameter that is to be checked. + /// + /// is greater than the maximum value. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void MustBeLessThanOrEqualTo(short value, short max, string parameterName) + { + if (value > max) + { + ThrowHelper.ThrowArgumentOutOfRangeExceptionForMustBeLessThanOrEqualTo(value, max, parameterName); + } + } + + /// + /// Verifies that the specified value is greater than a minimum value + /// and throws an exception if it is not. + /// + /// The target value, which should be validated. + /// The minimum value. + /// The name of the parameter that is to be checked. + /// + /// is less than the minimum value. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void MustBeGreaterThan(short value, short min, string parameterName) + { + if (value <= min) + { + ThrowHelper.ThrowArgumentOutOfRangeExceptionForMustBeGreaterThan(value, min, parameterName); + } + } + + /// + /// Verifies that the specified value is greater than or equal to a minimum value + /// and throws an exception if it is not. + /// + /// The target value, which should be validated. + /// The minimum value. + /// The name of the parameter that is to be checked. + /// + /// is less than the minimum value. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void MustBeGreaterThanOrEqualTo(short value, short min, string parameterName) + { + if (value < min) + { + ThrowHelper.ThrowArgumentOutOfRangeExceptionForMustBeGreaterThanOrEqualTo(value, min, parameterName); + } + } + + /// + /// Verifies that the specified value is greater than or equal to a minimum value and less than + /// or equal to a maximum value and throws an exception if it is not. + /// + /// The target value, which should be validated. + /// The minimum value. + /// The maximum value. + /// The name of the parameter that is to be checked. + /// + /// is less than the minimum value of greater than the maximum value. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void MustBeBetweenOrEqualTo(short value, short min, short max, string parameterName) + { + if (value < min || value > max) + { + ThrowHelper.ThrowArgumentOutOfRangeExceptionForMustBeBetweenOrEqualTo(value, min, max, parameterName); + } + } + + /// + /// Ensures that the specified value is less than a maximum value. + /// + /// The target value, which should be validated. + /// The maximum value. + /// The name of the parameter that is to be checked. + /// + /// is greater than the maximum value. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void MustBeLessThan(ushort value, ushort max, string parameterName) + { + if (value >= max) + { + ThrowHelper.ThrowArgumentOutOfRangeExceptionForMustBeLessThan(value, max, parameterName); + } + } + + /// + /// Verifies that the specified value is less than or equal to a maximum value + /// and throws an exception if it is not. + /// + /// The target value, which should be validated. + /// The maximum value. + /// The name of the parameter that is to be checked. + /// + /// is greater than the maximum value. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void MustBeLessThanOrEqualTo(ushort value, ushort max, string parameterName) + { + if (value > max) + { + ThrowHelper.ThrowArgumentOutOfRangeExceptionForMustBeLessThanOrEqualTo(value, max, parameterName); + } + } + + /// + /// Verifies that the specified value is greater than a minimum value + /// and throws an exception if it is not. + /// + /// The target value, which should be validated. + /// The minimum value. + /// The name of the parameter that is to be checked. + /// + /// is less than the minimum value. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void MustBeGreaterThan(ushort value, ushort min, string parameterName) + { + if (value <= min) + { + ThrowHelper.ThrowArgumentOutOfRangeExceptionForMustBeGreaterThan(value, min, parameterName); + } + } + + /// + /// Verifies that the specified value is greater than or equal to a minimum value + /// and throws an exception if it is not. + /// + /// The target value, which should be validated. + /// The minimum value. + /// The name of the parameter that is to be checked. + /// + /// is less than the minimum value. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void MustBeGreaterThanOrEqualTo(ushort value, ushort min, string parameterName) + { + if (value < min) + { + ThrowHelper.ThrowArgumentOutOfRangeExceptionForMustBeGreaterThanOrEqualTo(value, min, parameterName); + } + } + + /// + /// Verifies that the specified value is greater than or equal to a minimum value and less than + /// or equal to a maximum value and throws an exception if it is not. + /// + /// The target value, which should be validated. + /// The minimum value. + /// The maximum value. + /// The name of the parameter that is to be checked. + /// + /// is less than the minimum value of greater than the maximum value. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void MustBeBetweenOrEqualTo(ushort value, ushort min, ushort max, string parameterName) + { + if (value < min || value > max) + { + ThrowHelper.ThrowArgumentOutOfRangeExceptionForMustBeBetweenOrEqualTo(value, min, max, parameterName); + } + } + + /// + /// Ensures that the specified value is less than a maximum value. + /// + /// The target value, which should be validated. + /// The maximum value. + /// The name of the parameter that is to be checked. + /// + /// is greater than the maximum value. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void MustBeLessThan(char value, char max, string parameterName) + { + if (value >= max) + { + ThrowHelper.ThrowArgumentOutOfRangeExceptionForMustBeLessThan(value, max, parameterName); + } + } + + /// + /// Verifies that the specified value is less than or equal to a maximum value + /// and throws an exception if it is not. + /// + /// The target value, which should be validated. + /// The maximum value. + /// The name of the parameter that is to be checked. + /// + /// is greater than the maximum value. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void MustBeLessThanOrEqualTo(char value, char max, string parameterName) + { + if (value > max) + { + ThrowHelper.ThrowArgumentOutOfRangeExceptionForMustBeLessThanOrEqualTo(value, max, parameterName); + } + } + + /// + /// Verifies that the specified value is greater than a minimum value + /// and throws an exception if it is not. + /// + /// The target value, which should be validated. + /// The minimum value. + /// The name of the parameter that is to be checked. + /// + /// is less than the minimum value. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void MustBeGreaterThan(char value, char min, string parameterName) + { + if (value <= min) + { + ThrowHelper.ThrowArgumentOutOfRangeExceptionForMustBeGreaterThan(value, min, parameterName); + } + } + + /// + /// Verifies that the specified value is greater than or equal to a minimum value + /// and throws an exception if it is not. + /// + /// The target value, which should be validated. + /// The minimum value. + /// The name of the parameter that is to be checked. + /// + /// is less than the minimum value. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void MustBeGreaterThanOrEqualTo(char value, char min, string parameterName) + { + if (value < min) + { + ThrowHelper.ThrowArgumentOutOfRangeExceptionForMustBeGreaterThanOrEqualTo(value, min, parameterName); + } + } + + /// + /// Verifies that the specified value is greater than or equal to a minimum value and less than + /// or equal to a maximum value and throws an exception if it is not. + /// + /// The target value, which should be validated. + /// The minimum value. + /// The maximum value. + /// The name of the parameter that is to be checked. + /// + /// is less than the minimum value of greater than the maximum value. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void MustBeBetweenOrEqualTo(char value, char min, char max, string parameterName) + { + if (value < min || value > max) + { + ThrowHelper.ThrowArgumentOutOfRangeExceptionForMustBeBetweenOrEqualTo(value, min, max, parameterName); + } + } + + /// + /// Ensures that the specified value is less than a maximum value. + /// + /// The target value, which should be validated. + /// The maximum value. + /// The name of the parameter that is to be checked. + /// + /// is greater than the maximum value. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void MustBeLessThan(int value, int max, string parameterName) + { + if (value >= max) + { + ThrowHelper.ThrowArgumentOutOfRangeExceptionForMustBeLessThan(value, max, parameterName); + } + } + + /// + /// Verifies that the specified value is less than or equal to a maximum value + /// and throws an exception if it is not. + /// + /// The target value, which should be validated. + /// The maximum value. + /// The name of the parameter that is to be checked. + /// + /// is greater than the maximum value. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void MustBeLessThanOrEqualTo(int value, int max, string parameterName) + { + if (value > max) + { + ThrowHelper.ThrowArgumentOutOfRangeExceptionForMustBeLessThanOrEqualTo(value, max, parameterName); + } + } + + /// + /// Verifies that the specified value is greater than a minimum value + /// and throws an exception if it is not. + /// + /// The target value, which should be validated. + /// The minimum value. + /// The name of the parameter that is to be checked. + /// + /// is less than the minimum value. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void MustBeGreaterThan(int value, int min, string parameterName) + { + if (value <= min) + { + ThrowHelper.ThrowArgumentOutOfRangeExceptionForMustBeGreaterThan(value, min, parameterName); + } + } + + /// + /// Verifies that the specified value is greater than or equal to a minimum value + /// and throws an exception if it is not. + /// + /// The target value, which should be validated. + /// The minimum value. + /// The name of the parameter that is to be checked. + /// + /// is less than the minimum value. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void MustBeGreaterThanOrEqualTo(int value, int min, string parameterName) + { + if (value < min) + { + ThrowHelper.ThrowArgumentOutOfRangeExceptionForMustBeGreaterThanOrEqualTo(value, min, parameterName); + } + } + + /// + /// Verifies that the specified value is greater than or equal to a minimum value and less than + /// or equal to a maximum value and throws an exception if it is not. + /// + /// The target value, which should be validated. + /// The minimum value. + /// The maximum value. + /// The name of the parameter that is to be checked. + /// + /// is less than the minimum value of greater than the maximum value. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void MustBeBetweenOrEqualTo(int value, int min, int max, string parameterName) + { + if (value < min || value > max) + { + ThrowHelper.ThrowArgumentOutOfRangeExceptionForMustBeBetweenOrEqualTo(value, min, max, parameterName); + } + } + + /// + /// Ensures that the specified value is less than a maximum value. + /// + /// The target value, which should be validated. + /// The maximum value. + /// The name of the parameter that is to be checked. + /// + /// is greater than the maximum value. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void MustBeLessThan(uint value, uint max, string parameterName) + { + if (value >= max) + { + ThrowHelper.ThrowArgumentOutOfRangeExceptionForMustBeLessThan(value, max, parameterName); + } + } + + /// + /// Verifies that the specified value is less than or equal to a maximum value + /// and throws an exception if it is not. + /// + /// The target value, which should be validated. + /// The maximum value. + /// The name of the parameter that is to be checked. + /// + /// is greater than the maximum value. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void MustBeLessThanOrEqualTo(uint value, uint max, string parameterName) + { + if (value > max) + { + ThrowHelper.ThrowArgumentOutOfRangeExceptionForMustBeLessThanOrEqualTo(value, max, parameterName); + } + } + + /// + /// Verifies that the specified value is greater than a minimum value + /// and throws an exception if it is not. + /// + /// The target value, which should be validated. + /// The minimum value. + /// The name of the parameter that is to be checked. + /// + /// is less than the minimum value. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void MustBeGreaterThan(uint value, uint min, string parameterName) + { + if (value <= min) + { + ThrowHelper.ThrowArgumentOutOfRangeExceptionForMustBeGreaterThan(value, min, parameterName); + } + } + + /// + /// Verifies that the specified value is greater than or equal to a minimum value + /// and throws an exception if it is not. + /// + /// The target value, which should be validated. + /// The minimum value. + /// The name of the parameter that is to be checked. + /// + /// is less than the minimum value. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void MustBeGreaterThanOrEqualTo(uint value, uint min, string parameterName) + { + if (value < min) + { + ThrowHelper.ThrowArgumentOutOfRangeExceptionForMustBeGreaterThanOrEqualTo(value, min, parameterName); + } + } + + /// + /// Verifies that the specified value is greater than or equal to a minimum value and less than + /// or equal to a maximum value and throws an exception if it is not. + /// + /// The target value, which should be validated. + /// The minimum value. + /// The maximum value. + /// The name of the parameter that is to be checked. + /// + /// is less than the minimum value of greater than the maximum value. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void MustBeBetweenOrEqualTo(uint value, uint min, uint max, string parameterName) + { + if (value < min || value > max) + { + ThrowHelper.ThrowArgumentOutOfRangeExceptionForMustBeBetweenOrEqualTo(value, min, max, parameterName); + } + } + + /// + /// Ensures that the specified value is less than a maximum value. + /// + /// The target value, which should be validated. + /// The maximum value. + /// The name of the parameter that is to be checked. + /// + /// is greater than the maximum value. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void MustBeLessThan(float value, float max, string parameterName) + { + if (value >= max) + { + ThrowHelper.ThrowArgumentOutOfRangeExceptionForMustBeLessThan(value, max, parameterName); + } + } + + /// + /// Verifies that the specified value is less than or equal to a maximum value + /// and throws an exception if it is not. + /// + /// The target value, which should be validated. + /// The maximum value. + /// The name of the parameter that is to be checked. + /// + /// is greater than the maximum value. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void MustBeLessThanOrEqualTo(float value, float max, string parameterName) + { + if (value > max) + { + ThrowHelper.ThrowArgumentOutOfRangeExceptionForMustBeLessThanOrEqualTo(value, max, parameterName); + } + } + + /// + /// Verifies that the specified value is greater than a minimum value + /// and throws an exception if it is not. + /// + /// The target value, which should be validated. + /// The minimum value. + /// The name of the parameter that is to be checked. + /// + /// is less than the minimum value. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void MustBeGreaterThan(float value, float min, string parameterName) + { + if (value <= min) + { + ThrowHelper.ThrowArgumentOutOfRangeExceptionForMustBeGreaterThan(value, min, parameterName); + } + } + + /// + /// Verifies that the specified value is greater than or equal to a minimum value + /// and throws an exception if it is not. + /// + /// The target value, which should be validated. + /// The minimum value. + /// The name of the parameter that is to be checked. + /// + /// is less than the minimum value. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void MustBeGreaterThanOrEqualTo(float value, float min, string parameterName) + { + if (value < min) + { + ThrowHelper.ThrowArgumentOutOfRangeExceptionForMustBeGreaterThanOrEqualTo(value, min, parameterName); + } + } + + /// + /// Verifies that the specified value is greater than or equal to a minimum value and less than + /// or equal to a maximum value and throws an exception if it is not. + /// + /// The target value, which should be validated. + /// The minimum value. + /// The maximum value. + /// The name of the parameter that is to be checked. + /// + /// is less than the minimum value of greater than the maximum value. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void MustBeBetweenOrEqualTo(float value, float min, float max, string parameterName) + { + if (value < min || value > max) + { + ThrowHelper.ThrowArgumentOutOfRangeExceptionForMustBeBetweenOrEqualTo(value, min, max, parameterName); + } + } + + /// + /// Ensures that the specified value is less than a maximum value. + /// + /// The target value, which should be validated. + /// The maximum value. + /// The name of the parameter that is to be checked. + /// + /// is greater than the maximum value. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void MustBeLessThan(long value, long max, string parameterName) + { + if (value >= max) + { + ThrowHelper.ThrowArgumentOutOfRangeExceptionForMustBeLessThan(value, max, parameterName); + } + } + + /// + /// Verifies that the specified value is less than or equal to a maximum value + /// and throws an exception if it is not. + /// + /// The target value, which should be validated. + /// The maximum value. + /// The name of the parameter that is to be checked. + /// + /// is greater than the maximum value. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void MustBeLessThanOrEqualTo(long value, long max, string parameterName) + { + if (value > max) + { + ThrowHelper.ThrowArgumentOutOfRangeExceptionForMustBeLessThanOrEqualTo(value, max, parameterName); + } + } + + /// + /// Verifies that the specified value is greater than a minimum value + /// and throws an exception if it is not. + /// + /// The target value, which should be validated. + /// The minimum value. + /// The name of the parameter that is to be checked. + /// + /// is less than the minimum value. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void MustBeGreaterThan(long value, long min, string parameterName) + { + if (value <= min) + { + ThrowHelper.ThrowArgumentOutOfRangeExceptionForMustBeGreaterThan(value, min, parameterName); + } + } + + /// + /// Verifies that the specified value is greater than or equal to a minimum value + /// and throws an exception if it is not. + /// + /// The target value, which should be validated. + /// The minimum value. + /// The name of the parameter that is to be checked. + /// + /// is less than the minimum value. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void MustBeGreaterThanOrEqualTo(long value, long min, string parameterName) + { + if (value < min) + { + ThrowHelper.ThrowArgumentOutOfRangeExceptionForMustBeGreaterThanOrEqualTo(value, min, parameterName); + } + } + + /// + /// Verifies that the specified value is greater than or equal to a minimum value and less than + /// or equal to a maximum value and throws an exception if it is not. + /// + /// The target value, which should be validated. + /// The minimum value. + /// The maximum value. + /// The name of the parameter that is to be checked. + /// + /// is less than the minimum value of greater than the maximum value. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void MustBeBetweenOrEqualTo(long value, long min, long max, string parameterName) + { + if (value < min || value > max) + { + ThrowHelper.ThrowArgumentOutOfRangeExceptionForMustBeBetweenOrEqualTo(value, min, max, parameterName); + } + } + + /// + /// Ensures that the specified value is less than a maximum value. + /// + /// The target value, which should be validated. + /// The maximum value. + /// The name of the parameter that is to be checked. + /// + /// is greater than the maximum value. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void MustBeLessThan(ulong value, ulong max, string parameterName) + { + if (value >= max) + { + ThrowHelper.ThrowArgumentOutOfRangeExceptionForMustBeLessThan(value, max, parameterName); + } + } + + /// + /// Verifies that the specified value is less than or equal to a maximum value + /// and throws an exception if it is not. + /// + /// The target value, which should be validated. + /// The maximum value. + /// The name of the parameter that is to be checked. + /// + /// is greater than the maximum value. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void MustBeLessThanOrEqualTo(ulong value, ulong max, string parameterName) + { + if (value > max) + { + ThrowHelper.ThrowArgumentOutOfRangeExceptionForMustBeLessThanOrEqualTo(value, max, parameterName); + } + } + + /// + /// Verifies that the specified value is greater than a minimum value + /// and throws an exception if it is not. + /// + /// The target value, which should be validated. + /// The minimum value. + /// The name of the parameter that is to be checked. + /// + /// is less than the minimum value. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void MustBeGreaterThan(ulong value, ulong min, string parameterName) + { + if (value <= min) + { + ThrowHelper.ThrowArgumentOutOfRangeExceptionForMustBeGreaterThan(value, min, parameterName); + } + } + + /// + /// Verifies that the specified value is greater than or equal to a minimum value + /// and throws an exception if it is not. + /// + /// The target value, which should be validated. + /// The minimum value. + /// The name of the parameter that is to be checked. + /// + /// is less than the minimum value. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void MustBeGreaterThanOrEqualTo(ulong value, ulong min, string parameterName) + { + if (value < min) + { + ThrowHelper.ThrowArgumentOutOfRangeExceptionForMustBeGreaterThanOrEqualTo(value, min, parameterName); + } + } + + /// + /// Verifies that the specified value is greater than or equal to a minimum value and less than + /// or equal to a maximum value and throws an exception if it is not. + /// + /// The target value, which should be validated. + /// The minimum value. + /// The maximum value. + /// The name of the parameter that is to be checked. + /// + /// is less than the minimum value of greater than the maximum value. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void MustBeBetweenOrEqualTo(ulong value, ulong min, ulong max, string parameterName) + { + if (value < min || value > max) + { + ThrowHelper.ThrowArgumentOutOfRangeExceptionForMustBeBetweenOrEqualTo(value, min, max, parameterName); + } + } + + /// + /// Ensures that the specified value is less than a maximum value. + /// + /// The target value, which should be validated. + /// The maximum value. + /// The name of the parameter that is to be checked. + /// + /// is greater than the maximum value. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void MustBeLessThan(double value, double max, string parameterName) + { + if (value >= max) + { + ThrowHelper.ThrowArgumentOutOfRangeExceptionForMustBeLessThan(value, max, parameterName); + } + } + + /// + /// Verifies that the specified value is less than or equal to a maximum value + /// and throws an exception if it is not. + /// + /// The target value, which should be validated. + /// The maximum value. + /// The name of the parameter that is to be checked. + /// + /// is greater than the maximum value. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void MustBeLessThanOrEqualTo(double value, double max, string parameterName) + { + if (value > max) + { + ThrowHelper.ThrowArgumentOutOfRangeExceptionForMustBeLessThanOrEqualTo(value, max, parameterName); + } + } + + /// + /// Verifies that the specified value is greater than a minimum value + /// and throws an exception if it is not. + /// + /// The target value, which should be validated. + /// The minimum value. + /// The name of the parameter that is to be checked. + /// + /// is less than the minimum value. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void MustBeGreaterThan(double value, double min, string parameterName) + { + if (value <= min) + { + ThrowHelper.ThrowArgumentOutOfRangeExceptionForMustBeGreaterThan(value, min, parameterName); + } + } + + /// + /// Verifies that the specified value is greater than or equal to a minimum value + /// and throws an exception if it is not. + /// + /// The target value, which should be validated. + /// The minimum value. + /// The name of the parameter that is to be checked. + /// + /// is less than the minimum value. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void MustBeGreaterThanOrEqualTo(double value, double min, string parameterName) + { + if (value < min) + { + ThrowHelper.ThrowArgumentOutOfRangeExceptionForMustBeGreaterThanOrEqualTo(value, min, parameterName); + } + } + + /// + /// Verifies that the specified value is greater than or equal to a minimum value and less than + /// or equal to a maximum value and throws an exception if it is not. + /// + /// The target value, which should be validated. + /// The minimum value. + /// The maximum value. + /// The name of the parameter that is to be checked. + /// + /// is less than the minimum value of greater than the maximum value. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void MustBeBetweenOrEqualTo(double value, double min, double max, string parameterName) + { + if (value < min || value > max) + { + ThrowHelper.ThrowArgumentOutOfRangeExceptionForMustBeBetweenOrEqualTo(value, min, max, parameterName); + } + } + + /// + /// Ensures that the specified value is less than a maximum value. + /// + /// The target value, which should be validated. + /// The maximum value. + /// The name of the parameter that is to be checked. + /// + /// is greater than the maximum value. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void MustBeLessThan(decimal value, decimal max, string parameterName) + { + if (value >= max) + { + ThrowHelper.ThrowArgumentOutOfRangeExceptionForMustBeLessThan(value, max, parameterName); + } + } + + /// + /// Verifies that the specified value is less than or equal to a maximum value + /// and throws an exception if it is not. + /// + /// The target value, which should be validated. + /// The maximum value. + /// The name of the parameter that is to be checked. + /// + /// is greater than the maximum value. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void MustBeLessThanOrEqualTo(decimal value, decimal max, string parameterName) + { + if (value > max) + { + ThrowHelper.ThrowArgumentOutOfRangeExceptionForMustBeLessThanOrEqualTo(value, max, parameterName); + } + } + + /// + /// Verifies that the specified value is greater than a minimum value + /// and throws an exception if it is not. + /// + /// The target value, which should be validated. + /// The minimum value. + /// The name of the parameter that is to be checked. + /// + /// is less than the minimum value. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void MustBeGreaterThan(decimal value, decimal min, string parameterName) + { + if (value <= min) + { + ThrowHelper.ThrowArgumentOutOfRangeExceptionForMustBeGreaterThan(value, min, parameterName); + } + } + + /// + /// Verifies that the specified value is greater than or equal to a minimum value + /// and throws an exception if it is not. + /// + /// The target value, which should be validated. + /// The minimum value. + /// The name of the parameter that is to be checked. + /// + /// is less than the minimum value. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void MustBeGreaterThanOrEqualTo(decimal value, decimal min, string parameterName) + { + if (value < min) + { + ThrowHelper.ThrowArgumentOutOfRangeExceptionForMustBeGreaterThanOrEqualTo(value, min, parameterName); + } + } + + /// + /// Verifies that the specified value is greater than or equal to a minimum value and less than + /// or equal to a maximum value and throws an exception if it is not. + /// + /// The target value, which should be validated. + /// The minimum value. + /// The maximum value. + /// The name of the parameter that is to be checked. + /// + /// is less than the minimum value of greater than the maximum value. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void MustBeBetweenOrEqualTo(decimal value, decimal min, decimal max, string parameterName) + { + if (value < min || value > max) + { + ThrowHelper.ThrowArgumentOutOfRangeExceptionForMustBeBetweenOrEqualTo(value, min, max, parameterName); + } + } + } +} diff --git a/src/ImageSharp/ImageSharp.csproj b/src/ImageSharp/ImageSharp.csproj index 7872bb5c5..24d4f4a00 100644 --- a/src/ImageSharp/ImageSharp.csproj +++ b/src/ImageSharp/ImageSharp.csproj @@ -37,11 +37,16 @@ + True True Guard.Numeric.tt - + + True True @@ -209,6 +214,7 @@ Guard.Numeric.cs + TextTemplatingFileGenerator From 0d1ec74129f10fdd6988c0e8bc59f4ac22b60b27 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Fri, 6 Mar 2020 22:36:29 +1100 Subject: [PATCH 61/64] Remove stackalloc --- src/ImageSharp/Formats/Gif/GifEncoderCore.cs | 6 ++++-- src/ImageSharp/Formats/Png/PngEncoderCore.cs | 4 ++-- .../Processors/Quantization/WuFrameQuantizer{TPixel}.cs | 4 +++- 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/src/ImageSharp/Formats/Gif/GifEncoderCore.cs b/src/ImageSharp/Formats/Gif/GifEncoderCore.cs index a591eaf3b..887540930 100644 --- a/src/ImageSharp/Formats/Gif/GifEncoderCore.cs +++ b/src/ImageSharp/Formats/Gif/GifEncoderCore.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. using System; +using System.Buffers; using System.IO; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; @@ -217,10 +218,11 @@ namespace SixLabors.ImageSharp.Formats.Gif where TPixel : unmanaged, IPixel { // Transparent pixels are much more likely to be found at the end of a palette. - // Palette length maxes out at 256 so safe to stackalloc. int index = -1; ReadOnlySpan paletteSpan = quantized.Palette.Span; - Span rgbaSpan = stackalloc Rgba32[paletteSpan.Length]; + + using IMemoryOwner rgbaOwner = quantized.Configuration.MemoryAllocator.Allocate(paletteSpan.Length); + Span rgbaSpan = rgbaOwner.GetSpan(); PixelOperations.Instance.ToRgba32(quantized.Configuration, paletteSpan, rgbaSpan); ref Rgba32 rgbaSpanRef = ref MemoryMarshal.GetReference(rgbaSpan); diff --git a/src/ImageSharp/Formats/Png/PngEncoderCore.cs b/src/ImageSharp/Formats/Png/PngEncoderCore.cs index 2d8d66c33..45e1ffd2d 100644 --- a/src/ImageSharp/Formats/Png/PngEncoderCore.cs +++ b/src/ImageSharp/Formats/Png/PngEncoderCore.cs @@ -566,8 +566,8 @@ namespace SixLabors.ImageSharp.Formats.Png ref byte alphaTableRef = ref MemoryMarshal.GetReference(alphaTable.GetSpan()); // Bulk convert our palette to RGBA to allow assignment to tables. - // Palette length maxes out at 256 so safe to stackalloc. - Span rgbaPaletteSpan = stackalloc Rgba32[paletteLength]; + using IMemoryOwner rgbaOwner = quantized.Configuration.MemoryAllocator.Allocate(paletteLength); + Span rgbaPaletteSpan = rgbaOwner.GetSpan(); PixelOperations.Instance.ToRgba32(quantized.Configuration, quantized.Palette.Span, rgbaPaletteSpan); ref Rgba32 rgbaPaletteRef = ref MemoryMarshal.GetReference(rgbaPaletteSpan); diff --git a/src/ImageSharp/Processing/Processors/Quantization/WuFrameQuantizer{TPixel}.cs b/src/ImageSharp/Processing/Processors/Quantization/WuFrameQuantizer{TPixel}.cs index 67a46375d..d15db74e6 100644 --- a/src/ImageSharp/Processing/Processors/Quantization/WuFrameQuantizer{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Quantization/WuFrameQuantizer{TPixel}.cs @@ -632,7 +632,9 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization /// private void BuildCube() { - Span vv = stackalloc double[this.maxColors]; + // Store the volume variance. + using IMemoryOwner vvOwner = this.Configuration.MemoryAllocator.Allocate(this.maxColors); + Span vv = vvOwner.GetSpan(); ref Box cube = ref this.colorCube[0]; cube.RMin = cube.GMin = cube.BMin = cube.AMin = 0; From d0d71b545cdfdaa35d1b311b6f1d4a001d6e97fc Mon Sep 17 00:00:00 2001 From: Greg Date: Sat, 7 Mar 2020 21:18:03 +0100 Subject: [PATCH 62/64] Fix typos in FilterExtensions.cs --- .../Processing/Extensions/Filters/FilterExtensions.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/ImageSharp/Processing/Extensions/Filters/FilterExtensions.cs b/src/ImageSharp/Processing/Extensions/Filters/FilterExtensions.cs index 088f61884..f89540e24 100644 --- a/src/ImageSharp/Processing/Extensions/Filters/FilterExtensions.cs +++ b/src/ImageSharp/Processing/Extensions/Filters/FilterExtensions.cs @@ -12,7 +12,7 @@ namespace SixLabors.ImageSharp.Processing public static class FilterExtensions { /// - /// Filters an image but the given color matrix + /// Filters an image by the given color matrix /// /// The image this method extends. /// The filter color matrix @@ -21,7 +21,7 @@ namespace SixLabors.ImageSharp.Processing => source.ApplyProcessor(new FilterProcessor(matrix)); /// - /// Filters an image but the given color matrix + /// Filters an image by the given color matrix /// /// The image this method extends. /// The filter color matrix @@ -32,4 +32,4 @@ namespace SixLabors.ImageSharp.Processing public static IImageProcessingContext Filter(this IImageProcessingContext source, ColorMatrix matrix, Rectangle rectangle) => source.ApplyProcessor(new FilterProcessor(matrix), rectangle); } -} \ No newline at end of file +} From cc1316d1821ed1a1514d4a7c791a6be96ea24a11 Mon Sep 17 00:00:00 2001 From: Stuart Lang Date: Sat, 7 Mar 2020 23:26:12 +0000 Subject: [PATCH 63/64] Correct xml docs --- .../ColorSpaces/Conversion/ColorSpaceConverter.LinearRgb.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.LinearRgb.cs b/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.LinearRgb.cs index 4be3f0079..5b312f4f8 100644 --- a/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.LinearRgb.cs +++ b/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.LinearRgb.cs @@ -248,7 +248,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } /// - /// Performs the bulk conversion from into . + /// Performs the bulk conversion from into . /// /// The span to the source colors /// The span to the destination colors @@ -435,4 +435,4 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion return this.ToLinearRgb(rgb); } } -} \ No newline at end of file +} From 74de9bc1741eb9c1fbc720a4705b96b5f8f071df Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Mon, 9 Mar 2020 20:08:26 +1100 Subject: [PATCH 64/64] Update submodule and remove build copy. --- shared-infrastructure | 2 +- src/Directory.Build.targets | 13 - .../Common/Helpers/Guard.Numeric.cs | 1154 ----------------- src/ImageSharp/ImageSharp.csproj | 16 +- 4 files changed, 2 insertions(+), 1183 deletions(-) delete mode 100644 src/ImageSharp/Common/Helpers/Guard.Numeric.cs diff --git a/shared-infrastructure b/shared-infrastructure index 8dfef29f1..ea561c249 160000 --- a/shared-infrastructure +++ b/shared-infrastructure @@ -1 +1 @@ -Subproject commit 8dfef29f1838da76be9596f1a2f1be6d93e453d3 +Subproject commit ea561c249ba86352fe3b69e612b8072f3652eacb diff --git a/src/Directory.Build.targets b/src/Directory.Build.targets index 055a6803e..d7171aa0f 100644 --- a/src/Directory.Build.targets +++ b/src/Directory.Build.targets @@ -55,14 +55,6 @@ - - @@ -70,11 +62,6 @@ - - diff --git a/src/ImageSharp/Common/Helpers/Guard.Numeric.cs b/src/ImageSharp/Common/Helpers/Guard.Numeric.cs deleted file mode 100644 index 72262e0b9..000000000 --- a/src/ImageSharp/Common/Helpers/Guard.Numeric.cs +++ /dev/null @@ -1,1154 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using System; -using System.Runtime.CompilerServices; - -namespace SixLabors -{ - /// - /// Provides methods to protect against invalid parameters. - /// - internal static partial class Guard - { - /// - /// Ensures that the specified value is less than a maximum value. - /// - /// The target value, which should be validated. - /// The maximum value. - /// The name of the parameter that is to be checked. - /// - /// is greater than the maximum value. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void MustBeLessThan(byte value, byte max, string parameterName) - { - if (value >= max) - { - ThrowHelper.ThrowArgumentOutOfRangeExceptionForMustBeLessThan(value, max, parameterName); - } - } - - /// - /// Verifies that the specified value is less than or equal to a maximum value - /// and throws an exception if it is not. - /// - /// The target value, which should be validated. - /// The maximum value. - /// The name of the parameter that is to be checked. - /// - /// is greater than the maximum value. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void MustBeLessThanOrEqualTo(byte value, byte max, string parameterName) - { - if (value > max) - { - ThrowHelper.ThrowArgumentOutOfRangeExceptionForMustBeLessThanOrEqualTo(value, max, parameterName); - } - } - - /// - /// Verifies that the specified value is greater than a minimum value - /// and throws an exception if it is not. - /// - /// The target value, which should be validated. - /// The minimum value. - /// The name of the parameter that is to be checked. - /// - /// is less than the minimum value. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void MustBeGreaterThan(byte value, byte min, string parameterName) - { - if (value <= min) - { - ThrowHelper.ThrowArgumentOutOfRangeExceptionForMustBeGreaterThan(value, min, parameterName); - } - } - - /// - /// Verifies that the specified value is greater than or equal to a minimum value - /// and throws an exception if it is not. - /// - /// The target value, which should be validated. - /// The minimum value. - /// The name of the parameter that is to be checked. - /// - /// is less than the minimum value. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void MustBeGreaterThanOrEqualTo(byte value, byte min, string parameterName) - { - if (value < min) - { - ThrowHelper.ThrowArgumentOutOfRangeExceptionForMustBeGreaterThanOrEqualTo(value, min, parameterName); - } - } - - /// - /// Verifies that the specified value is greater than or equal to a minimum value and less than - /// or equal to a maximum value and throws an exception if it is not. - /// - /// The target value, which should be validated. - /// The minimum value. - /// The maximum value. - /// The name of the parameter that is to be checked. - /// - /// is less than the minimum value of greater than the maximum value. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void MustBeBetweenOrEqualTo(byte value, byte min, byte max, string parameterName) - { - if (value < min || value > max) - { - ThrowHelper.ThrowArgumentOutOfRangeExceptionForMustBeBetweenOrEqualTo(value, min, max, parameterName); - } - } - - /// - /// Ensures that the specified value is less than a maximum value. - /// - /// The target value, which should be validated. - /// The maximum value. - /// The name of the parameter that is to be checked. - /// - /// is greater than the maximum value. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void MustBeLessThan(sbyte value, sbyte max, string parameterName) - { - if (value >= max) - { - ThrowHelper.ThrowArgumentOutOfRangeExceptionForMustBeLessThan(value, max, parameterName); - } - } - - /// - /// Verifies that the specified value is less than or equal to a maximum value - /// and throws an exception if it is not. - /// - /// The target value, which should be validated. - /// The maximum value. - /// The name of the parameter that is to be checked. - /// - /// is greater than the maximum value. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void MustBeLessThanOrEqualTo(sbyte value, sbyte max, string parameterName) - { - if (value > max) - { - ThrowHelper.ThrowArgumentOutOfRangeExceptionForMustBeLessThanOrEqualTo(value, max, parameterName); - } - } - - /// - /// Verifies that the specified value is greater than a minimum value - /// and throws an exception if it is not. - /// - /// The target value, which should be validated. - /// The minimum value. - /// The name of the parameter that is to be checked. - /// - /// is less than the minimum value. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void MustBeGreaterThan(sbyte value, sbyte min, string parameterName) - { - if (value <= min) - { - ThrowHelper.ThrowArgumentOutOfRangeExceptionForMustBeGreaterThan(value, min, parameterName); - } - } - - /// - /// Verifies that the specified value is greater than or equal to a minimum value - /// and throws an exception if it is not. - /// - /// The target value, which should be validated. - /// The minimum value. - /// The name of the parameter that is to be checked. - /// - /// is less than the minimum value. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void MustBeGreaterThanOrEqualTo(sbyte value, sbyte min, string parameterName) - { - if (value < min) - { - ThrowHelper.ThrowArgumentOutOfRangeExceptionForMustBeGreaterThanOrEqualTo(value, min, parameterName); - } - } - - /// - /// Verifies that the specified value is greater than or equal to a minimum value and less than - /// or equal to a maximum value and throws an exception if it is not. - /// - /// The target value, which should be validated. - /// The minimum value. - /// The maximum value. - /// The name of the parameter that is to be checked. - /// - /// is less than the minimum value of greater than the maximum value. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void MustBeBetweenOrEqualTo(sbyte value, sbyte min, sbyte max, string parameterName) - { - if (value < min || value > max) - { - ThrowHelper.ThrowArgumentOutOfRangeExceptionForMustBeBetweenOrEqualTo(value, min, max, parameterName); - } - } - - /// - /// Ensures that the specified value is less than a maximum value. - /// - /// The target value, which should be validated. - /// The maximum value. - /// The name of the parameter that is to be checked. - /// - /// is greater than the maximum value. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void MustBeLessThan(short value, short max, string parameterName) - { - if (value >= max) - { - ThrowHelper.ThrowArgumentOutOfRangeExceptionForMustBeLessThan(value, max, parameterName); - } - } - - /// - /// Verifies that the specified value is less than or equal to a maximum value - /// and throws an exception if it is not. - /// - /// The target value, which should be validated. - /// The maximum value. - /// The name of the parameter that is to be checked. - /// - /// is greater than the maximum value. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void MustBeLessThanOrEqualTo(short value, short max, string parameterName) - { - if (value > max) - { - ThrowHelper.ThrowArgumentOutOfRangeExceptionForMustBeLessThanOrEqualTo(value, max, parameterName); - } - } - - /// - /// Verifies that the specified value is greater than a minimum value - /// and throws an exception if it is not. - /// - /// The target value, which should be validated. - /// The minimum value. - /// The name of the parameter that is to be checked. - /// - /// is less than the minimum value. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void MustBeGreaterThan(short value, short min, string parameterName) - { - if (value <= min) - { - ThrowHelper.ThrowArgumentOutOfRangeExceptionForMustBeGreaterThan(value, min, parameterName); - } - } - - /// - /// Verifies that the specified value is greater than or equal to a minimum value - /// and throws an exception if it is not. - /// - /// The target value, which should be validated. - /// The minimum value. - /// The name of the parameter that is to be checked. - /// - /// is less than the minimum value. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void MustBeGreaterThanOrEqualTo(short value, short min, string parameterName) - { - if (value < min) - { - ThrowHelper.ThrowArgumentOutOfRangeExceptionForMustBeGreaterThanOrEqualTo(value, min, parameterName); - } - } - - /// - /// Verifies that the specified value is greater than or equal to a minimum value and less than - /// or equal to a maximum value and throws an exception if it is not. - /// - /// The target value, which should be validated. - /// The minimum value. - /// The maximum value. - /// The name of the parameter that is to be checked. - /// - /// is less than the minimum value of greater than the maximum value. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void MustBeBetweenOrEqualTo(short value, short min, short max, string parameterName) - { - if (value < min || value > max) - { - ThrowHelper.ThrowArgumentOutOfRangeExceptionForMustBeBetweenOrEqualTo(value, min, max, parameterName); - } - } - - /// - /// Ensures that the specified value is less than a maximum value. - /// - /// The target value, which should be validated. - /// The maximum value. - /// The name of the parameter that is to be checked. - /// - /// is greater than the maximum value. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void MustBeLessThan(ushort value, ushort max, string parameterName) - { - if (value >= max) - { - ThrowHelper.ThrowArgumentOutOfRangeExceptionForMustBeLessThan(value, max, parameterName); - } - } - - /// - /// Verifies that the specified value is less than or equal to a maximum value - /// and throws an exception if it is not. - /// - /// The target value, which should be validated. - /// The maximum value. - /// The name of the parameter that is to be checked. - /// - /// is greater than the maximum value. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void MustBeLessThanOrEqualTo(ushort value, ushort max, string parameterName) - { - if (value > max) - { - ThrowHelper.ThrowArgumentOutOfRangeExceptionForMustBeLessThanOrEqualTo(value, max, parameterName); - } - } - - /// - /// Verifies that the specified value is greater than a minimum value - /// and throws an exception if it is not. - /// - /// The target value, which should be validated. - /// The minimum value. - /// The name of the parameter that is to be checked. - /// - /// is less than the minimum value. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void MustBeGreaterThan(ushort value, ushort min, string parameterName) - { - if (value <= min) - { - ThrowHelper.ThrowArgumentOutOfRangeExceptionForMustBeGreaterThan(value, min, parameterName); - } - } - - /// - /// Verifies that the specified value is greater than or equal to a minimum value - /// and throws an exception if it is not. - /// - /// The target value, which should be validated. - /// The minimum value. - /// The name of the parameter that is to be checked. - /// - /// is less than the minimum value. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void MustBeGreaterThanOrEqualTo(ushort value, ushort min, string parameterName) - { - if (value < min) - { - ThrowHelper.ThrowArgumentOutOfRangeExceptionForMustBeGreaterThanOrEqualTo(value, min, parameterName); - } - } - - /// - /// Verifies that the specified value is greater than or equal to a minimum value and less than - /// or equal to a maximum value and throws an exception if it is not. - /// - /// The target value, which should be validated. - /// The minimum value. - /// The maximum value. - /// The name of the parameter that is to be checked. - /// - /// is less than the minimum value of greater than the maximum value. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void MustBeBetweenOrEqualTo(ushort value, ushort min, ushort max, string parameterName) - { - if (value < min || value > max) - { - ThrowHelper.ThrowArgumentOutOfRangeExceptionForMustBeBetweenOrEqualTo(value, min, max, parameterName); - } - } - - /// - /// Ensures that the specified value is less than a maximum value. - /// - /// The target value, which should be validated. - /// The maximum value. - /// The name of the parameter that is to be checked. - /// - /// is greater than the maximum value. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void MustBeLessThan(char value, char max, string parameterName) - { - if (value >= max) - { - ThrowHelper.ThrowArgumentOutOfRangeExceptionForMustBeLessThan(value, max, parameterName); - } - } - - /// - /// Verifies that the specified value is less than or equal to a maximum value - /// and throws an exception if it is not. - /// - /// The target value, which should be validated. - /// The maximum value. - /// The name of the parameter that is to be checked. - /// - /// is greater than the maximum value. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void MustBeLessThanOrEqualTo(char value, char max, string parameterName) - { - if (value > max) - { - ThrowHelper.ThrowArgumentOutOfRangeExceptionForMustBeLessThanOrEqualTo(value, max, parameterName); - } - } - - /// - /// Verifies that the specified value is greater than a minimum value - /// and throws an exception if it is not. - /// - /// The target value, which should be validated. - /// The minimum value. - /// The name of the parameter that is to be checked. - /// - /// is less than the minimum value. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void MustBeGreaterThan(char value, char min, string parameterName) - { - if (value <= min) - { - ThrowHelper.ThrowArgumentOutOfRangeExceptionForMustBeGreaterThan(value, min, parameterName); - } - } - - /// - /// Verifies that the specified value is greater than or equal to a minimum value - /// and throws an exception if it is not. - /// - /// The target value, which should be validated. - /// The minimum value. - /// The name of the parameter that is to be checked. - /// - /// is less than the minimum value. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void MustBeGreaterThanOrEqualTo(char value, char min, string parameterName) - { - if (value < min) - { - ThrowHelper.ThrowArgumentOutOfRangeExceptionForMustBeGreaterThanOrEqualTo(value, min, parameterName); - } - } - - /// - /// Verifies that the specified value is greater than or equal to a minimum value and less than - /// or equal to a maximum value and throws an exception if it is not. - /// - /// The target value, which should be validated. - /// The minimum value. - /// The maximum value. - /// The name of the parameter that is to be checked. - /// - /// is less than the minimum value of greater than the maximum value. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void MustBeBetweenOrEqualTo(char value, char min, char max, string parameterName) - { - if (value < min || value > max) - { - ThrowHelper.ThrowArgumentOutOfRangeExceptionForMustBeBetweenOrEqualTo(value, min, max, parameterName); - } - } - - /// - /// Ensures that the specified value is less than a maximum value. - /// - /// The target value, which should be validated. - /// The maximum value. - /// The name of the parameter that is to be checked. - /// - /// is greater than the maximum value. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void MustBeLessThan(int value, int max, string parameterName) - { - if (value >= max) - { - ThrowHelper.ThrowArgumentOutOfRangeExceptionForMustBeLessThan(value, max, parameterName); - } - } - - /// - /// Verifies that the specified value is less than or equal to a maximum value - /// and throws an exception if it is not. - /// - /// The target value, which should be validated. - /// The maximum value. - /// The name of the parameter that is to be checked. - /// - /// is greater than the maximum value. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void MustBeLessThanOrEqualTo(int value, int max, string parameterName) - { - if (value > max) - { - ThrowHelper.ThrowArgumentOutOfRangeExceptionForMustBeLessThanOrEqualTo(value, max, parameterName); - } - } - - /// - /// Verifies that the specified value is greater than a minimum value - /// and throws an exception if it is not. - /// - /// The target value, which should be validated. - /// The minimum value. - /// The name of the parameter that is to be checked. - /// - /// is less than the minimum value. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void MustBeGreaterThan(int value, int min, string parameterName) - { - if (value <= min) - { - ThrowHelper.ThrowArgumentOutOfRangeExceptionForMustBeGreaterThan(value, min, parameterName); - } - } - - /// - /// Verifies that the specified value is greater than or equal to a minimum value - /// and throws an exception if it is not. - /// - /// The target value, which should be validated. - /// The minimum value. - /// The name of the parameter that is to be checked. - /// - /// is less than the minimum value. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void MustBeGreaterThanOrEqualTo(int value, int min, string parameterName) - { - if (value < min) - { - ThrowHelper.ThrowArgumentOutOfRangeExceptionForMustBeGreaterThanOrEqualTo(value, min, parameterName); - } - } - - /// - /// Verifies that the specified value is greater than or equal to a minimum value and less than - /// or equal to a maximum value and throws an exception if it is not. - /// - /// The target value, which should be validated. - /// The minimum value. - /// The maximum value. - /// The name of the parameter that is to be checked. - /// - /// is less than the minimum value of greater than the maximum value. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void MustBeBetweenOrEqualTo(int value, int min, int max, string parameterName) - { - if (value < min || value > max) - { - ThrowHelper.ThrowArgumentOutOfRangeExceptionForMustBeBetweenOrEqualTo(value, min, max, parameterName); - } - } - - /// - /// Ensures that the specified value is less than a maximum value. - /// - /// The target value, which should be validated. - /// The maximum value. - /// The name of the parameter that is to be checked. - /// - /// is greater than the maximum value. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void MustBeLessThan(uint value, uint max, string parameterName) - { - if (value >= max) - { - ThrowHelper.ThrowArgumentOutOfRangeExceptionForMustBeLessThan(value, max, parameterName); - } - } - - /// - /// Verifies that the specified value is less than or equal to a maximum value - /// and throws an exception if it is not. - /// - /// The target value, which should be validated. - /// The maximum value. - /// The name of the parameter that is to be checked. - /// - /// is greater than the maximum value. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void MustBeLessThanOrEqualTo(uint value, uint max, string parameterName) - { - if (value > max) - { - ThrowHelper.ThrowArgumentOutOfRangeExceptionForMustBeLessThanOrEqualTo(value, max, parameterName); - } - } - - /// - /// Verifies that the specified value is greater than a minimum value - /// and throws an exception if it is not. - /// - /// The target value, which should be validated. - /// The minimum value. - /// The name of the parameter that is to be checked. - /// - /// is less than the minimum value. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void MustBeGreaterThan(uint value, uint min, string parameterName) - { - if (value <= min) - { - ThrowHelper.ThrowArgumentOutOfRangeExceptionForMustBeGreaterThan(value, min, parameterName); - } - } - - /// - /// Verifies that the specified value is greater than or equal to a minimum value - /// and throws an exception if it is not. - /// - /// The target value, which should be validated. - /// The minimum value. - /// The name of the parameter that is to be checked. - /// - /// is less than the minimum value. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void MustBeGreaterThanOrEqualTo(uint value, uint min, string parameterName) - { - if (value < min) - { - ThrowHelper.ThrowArgumentOutOfRangeExceptionForMustBeGreaterThanOrEqualTo(value, min, parameterName); - } - } - - /// - /// Verifies that the specified value is greater than or equal to a minimum value and less than - /// or equal to a maximum value and throws an exception if it is not. - /// - /// The target value, which should be validated. - /// The minimum value. - /// The maximum value. - /// The name of the parameter that is to be checked. - /// - /// is less than the minimum value of greater than the maximum value. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void MustBeBetweenOrEqualTo(uint value, uint min, uint max, string parameterName) - { - if (value < min || value > max) - { - ThrowHelper.ThrowArgumentOutOfRangeExceptionForMustBeBetweenOrEqualTo(value, min, max, parameterName); - } - } - - /// - /// Ensures that the specified value is less than a maximum value. - /// - /// The target value, which should be validated. - /// The maximum value. - /// The name of the parameter that is to be checked. - /// - /// is greater than the maximum value. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void MustBeLessThan(float value, float max, string parameterName) - { - if (value >= max) - { - ThrowHelper.ThrowArgumentOutOfRangeExceptionForMustBeLessThan(value, max, parameterName); - } - } - - /// - /// Verifies that the specified value is less than or equal to a maximum value - /// and throws an exception if it is not. - /// - /// The target value, which should be validated. - /// The maximum value. - /// The name of the parameter that is to be checked. - /// - /// is greater than the maximum value. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void MustBeLessThanOrEqualTo(float value, float max, string parameterName) - { - if (value > max) - { - ThrowHelper.ThrowArgumentOutOfRangeExceptionForMustBeLessThanOrEqualTo(value, max, parameterName); - } - } - - /// - /// Verifies that the specified value is greater than a minimum value - /// and throws an exception if it is not. - /// - /// The target value, which should be validated. - /// The minimum value. - /// The name of the parameter that is to be checked. - /// - /// is less than the minimum value. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void MustBeGreaterThan(float value, float min, string parameterName) - { - if (value <= min) - { - ThrowHelper.ThrowArgumentOutOfRangeExceptionForMustBeGreaterThan(value, min, parameterName); - } - } - - /// - /// Verifies that the specified value is greater than or equal to a minimum value - /// and throws an exception if it is not. - /// - /// The target value, which should be validated. - /// The minimum value. - /// The name of the parameter that is to be checked. - /// - /// is less than the minimum value. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void MustBeGreaterThanOrEqualTo(float value, float min, string parameterName) - { - if (value < min) - { - ThrowHelper.ThrowArgumentOutOfRangeExceptionForMustBeGreaterThanOrEqualTo(value, min, parameterName); - } - } - - /// - /// Verifies that the specified value is greater than or equal to a minimum value and less than - /// or equal to a maximum value and throws an exception if it is not. - /// - /// The target value, which should be validated. - /// The minimum value. - /// The maximum value. - /// The name of the parameter that is to be checked. - /// - /// is less than the minimum value of greater than the maximum value. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void MustBeBetweenOrEqualTo(float value, float min, float max, string parameterName) - { - if (value < min || value > max) - { - ThrowHelper.ThrowArgumentOutOfRangeExceptionForMustBeBetweenOrEqualTo(value, min, max, parameterName); - } - } - - /// - /// Ensures that the specified value is less than a maximum value. - /// - /// The target value, which should be validated. - /// The maximum value. - /// The name of the parameter that is to be checked. - /// - /// is greater than the maximum value. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void MustBeLessThan(long value, long max, string parameterName) - { - if (value >= max) - { - ThrowHelper.ThrowArgumentOutOfRangeExceptionForMustBeLessThan(value, max, parameterName); - } - } - - /// - /// Verifies that the specified value is less than or equal to a maximum value - /// and throws an exception if it is not. - /// - /// The target value, which should be validated. - /// The maximum value. - /// The name of the parameter that is to be checked. - /// - /// is greater than the maximum value. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void MustBeLessThanOrEqualTo(long value, long max, string parameterName) - { - if (value > max) - { - ThrowHelper.ThrowArgumentOutOfRangeExceptionForMustBeLessThanOrEqualTo(value, max, parameterName); - } - } - - /// - /// Verifies that the specified value is greater than a minimum value - /// and throws an exception if it is not. - /// - /// The target value, which should be validated. - /// The minimum value. - /// The name of the parameter that is to be checked. - /// - /// is less than the minimum value. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void MustBeGreaterThan(long value, long min, string parameterName) - { - if (value <= min) - { - ThrowHelper.ThrowArgumentOutOfRangeExceptionForMustBeGreaterThan(value, min, parameterName); - } - } - - /// - /// Verifies that the specified value is greater than or equal to a minimum value - /// and throws an exception if it is not. - /// - /// The target value, which should be validated. - /// The minimum value. - /// The name of the parameter that is to be checked. - /// - /// is less than the minimum value. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void MustBeGreaterThanOrEqualTo(long value, long min, string parameterName) - { - if (value < min) - { - ThrowHelper.ThrowArgumentOutOfRangeExceptionForMustBeGreaterThanOrEqualTo(value, min, parameterName); - } - } - - /// - /// Verifies that the specified value is greater than or equal to a minimum value and less than - /// or equal to a maximum value and throws an exception if it is not. - /// - /// The target value, which should be validated. - /// The minimum value. - /// The maximum value. - /// The name of the parameter that is to be checked. - /// - /// is less than the minimum value of greater than the maximum value. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void MustBeBetweenOrEqualTo(long value, long min, long max, string parameterName) - { - if (value < min || value > max) - { - ThrowHelper.ThrowArgumentOutOfRangeExceptionForMustBeBetweenOrEqualTo(value, min, max, parameterName); - } - } - - /// - /// Ensures that the specified value is less than a maximum value. - /// - /// The target value, which should be validated. - /// The maximum value. - /// The name of the parameter that is to be checked. - /// - /// is greater than the maximum value. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void MustBeLessThan(ulong value, ulong max, string parameterName) - { - if (value >= max) - { - ThrowHelper.ThrowArgumentOutOfRangeExceptionForMustBeLessThan(value, max, parameterName); - } - } - - /// - /// Verifies that the specified value is less than or equal to a maximum value - /// and throws an exception if it is not. - /// - /// The target value, which should be validated. - /// The maximum value. - /// The name of the parameter that is to be checked. - /// - /// is greater than the maximum value. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void MustBeLessThanOrEqualTo(ulong value, ulong max, string parameterName) - { - if (value > max) - { - ThrowHelper.ThrowArgumentOutOfRangeExceptionForMustBeLessThanOrEqualTo(value, max, parameterName); - } - } - - /// - /// Verifies that the specified value is greater than a minimum value - /// and throws an exception if it is not. - /// - /// The target value, which should be validated. - /// The minimum value. - /// The name of the parameter that is to be checked. - /// - /// is less than the minimum value. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void MustBeGreaterThan(ulong value, ulong min, string parameterName) - { - if (value <= min) - { - ThrowHelper.ThrowArgumentOutOfRangeExceptionForMustBeGreaterThan(value, min, parameterName); - } - } - - /// - /// Verifies that the specified value is greater than or equal to a minimum value - /// and throws an exception if it is not. - /// - /// The target value, which should be validated. - /// The minimum value. - /// The name of the parameter that is to be checked. - /// - /// is less than the minimum value. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void MustBeGreaterThanOrEqualTo(ulong value, ulong min, string parameterName) - { - if (value < min) - { - ThrowHelper.ThrowArgumentOutOfRangeExceptionForMustBeGreaterThanOrEqualTo(value, min, parameterName); - } - } - - /// - /// Verifies that the specified value is greater than or equal to a minimum value and less than - /// or equal to a maximum value and throws an exception if it is not. - /// - /// The target value, which should be validated. - /// The minimum value. - /// The maximum value. - /// The name of the parameter that is to be checked. - /// - /// is less than the minimum value of greater than the maximum value. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void MustBeBetweenOrEqualTo(ulong value, ulong min, ulong max, string parameterName) - { - if (value < min || value > max) - { - ThrowHelper.ThrowArgumentOutOfRangeExceptionForMustBeBetweenOrEqualTo(value, min, max, parameterName); - } - } - - /// - /// Ensures that the specified value is less than a maximum value. - /// - /// The target value, which should be validated. - /// The maximum value. - /// The name of the parameter that is to be checked. - /// - /// is greater than the maximum value. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void MustBeLessThan(double value, double max, string parameterName) - { - if (value >= max) - { - ThrowHelper.ThrowArgumentOutOfRangeExceptionForMustBeLessThan(value, max, parameterName); - } - } - - /// - /// Verifies that the specified value is less than or equal to a maximum value - /// and throws an exception if it is not. - /// - /// The target value, which should be validated. - /// The maximum value. - /// The name of the parameter that is to be checked. - /// - /// is greater than the maximum value. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void MustBeLessThanOrEqualTo(double value, double max, string parameterName) - { - if (value > max) - { - ThrowHelper.ThrowArgumentOutOfRangeExceptionForMustBeLessThanOrEqualTo(value, max, parameterName); - } - } - - /// - /// Verifies that the specified value is greater than a minimum value - /// and throws an exception if it is not. - /// - /// The target value, which should be validated. - /// The minimum value. - /// The name of the parameter that is to be checked. - /// - /// is less than the minimum value. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void MustBeGreaterThan(double value, double min, string parameterName) - { - if (value <= min) - { - ThrowHelper.ThrowArgumentOutOfRangeExceptionForMustBeGreaterThan(value, min, parameterName); - } - } - - /// - /// Verifies that the specified value is greater than or equal to a minimum value - /// and throws an exception if it is not. - /// - /// The target value, which should be validated. - /// The minimum value. - /// The name of the parameter that is to be checked. - /// - /// is less than the minimum value. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void MustBeGreaterThanOrEqualTo(double value, double min, string parameterName) - { - if (value < min) - { - ThrowHelper.ThrowArgumentOutOfRangeExceptionForMustBeGreaterThanOrEqualTo(value, min, parameterName); - } - } - - /// - /// Verifies that the specified value is greater than or equal to a minimum value and less than - /// or equal to a maximum value and throws an exception if it is not. - /// - /// The target value, which should be validated. - /// The minimum value. - /// The maximum value. - /// The name of the parameter that is to be checked. - /// - /// is less than the minimum value of greater than the maximum value. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void MustBeBetweenOrEqualTo(double value, double min, double max, string parameterName) - { - if (value < min || value > max) - { - ThrowHelper.ThrowArgumentOutOfRangeExceptionForMustBeBetweenOrEqualTo(value, min, max, parameterName); - } - } - - /// - /// Ensures that the specified value is less than a maximum value. - /// - /// The target value, which should be validated. - /// The maximum value. - /// The name of the parameter that is to be checked. - /// - /// is greater than the maximum value. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void MustBeLessThan(decimal value, decimal max, string parameterName) - { - if (value >= max) - { - ThrowHelper.ThrowArgumentOutOfRangeExceptionForMustBeLessThan(value, max, parameterName); - } - } - - /// - /// Verifies that the specified value is less than or equal to a maximum value - /// and throws an exception if it is not. - /// - /// The target value, which should be validated. - /// The maximum value. - /// The name of the parameter that is to be checked. - /// - /// is greater than the maximum value. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void MustBeLessThanOrEqualTo(decimal value, decimal max, string parameterName) - { - if (value > max) - { - ThrowHelper.ThrowArgumentOutOfRangeExceptionForMustBeLessThanOrEqualTo(value, max, parameterName); - } - } - - /// - /// Verifies that the specified value is greater than a minimum value - /// and throws an exception if it is not. - /// - /// The target value, which should be validated. - /// The minimum value. - /// The name of the parameter that is to be checked. - /// - /// is less than the minimum value. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void MustBeGreaterThan(decimal value, decimal min, string parameterName) - { - if (value <= min) - { - ThrowHelper.ThrowArgumentOutOfRangeExceptionForMustBeGreaterThan(value, min, parameterName); - } - } - - /// - /// Verifies that the specified value is greater than or equal to a minimum value - /// and throws an exception if it is not. - /// - /// The target value, which should be validated. - /// The minimum value. - /// The name of the parameter that is to be checked. - /// - /// is less than the minimum value. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void MustBeGreaterThanOrEqualTo(decimal value, decimal min, string parameterName) - { - if (value < min) - { - ThrowHelper.ThrowArgumentOutOfRangeExceptionForMustBeGreaterThanOrEqualTo(value, min, parameterName); - } - } - - /// - /// Verifies that the specified value is greater than or equal to a minimum value and less than - /// or equal to a maximum value and throws an exception if it is not. - /// - /// The target value, which should be validated. - /// The minimum value. - /// The maximum value. - /// The name of the parameter that is to be checked. - /// - /// is less than the minimum value of greater than the maximum value. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void MustBeBetweenOrEqualTo(decimal value, decimal min, decimal max, string parameterName) - { - if (value < min || value > max) - { - ThrowHelper.ThrowArgumentOutOfRangeExceptionForMustBeBetweenOrEqualTo(value, min, max, parameterName); - } - } - } -} diff --git a/src/ImageSharp/ImageSharp.csproj b/src/ImageSharp/ImageSharp.csproj index 24d4f4a00..baf4a2ce1 100644 --- a/src/ImageSharp/ImageSharp.csproj +++ b/src/ImageSharp/ImageSharp.csproj @@ -36,17 +36,7 @@ - - - - True - True - Guard.Numeric.tt - - + True True @@ -212,10 +202,6 @@ DefaultPixelBlenders.Generated.cs TextTemplatingFileGenerator - - Guard.Numeric.cs - TextTemplatingFileGenerator -