diff --git a/src/ImageSharp/Advanced/ParallelRowIterator.cs b/src/ImageSharp/Advanced/ParallelRowIterator.cs index beef99c1c..d7939478b 100644 --- a/src/ImageSharp/Advanced/ParallelRowIterator.cs +++ b/src/ImageSharp/Advanced/ParallelRowIterator.cs @@ -21,15 +21,15 @@ namespace SixLabors.ImageSharp.Advanced /// Iterate through the rows of a rectangle in optimized batches defined by -s. /// /// The type of row operation to perform. - /// The . /// The to get the parallel settings from. - /// The method body defining the iteration logic on a single . + /// The . + /// The operation defining the iteration logic on a single . [MethodImpl(InliningOptions.ShortMethod)] - public static void IterateRows(Rectangle rectangle, Configuration configuration, in T body) + public static void IterateRows(Configuration configuration, Rectangle rectangle, in T operation) where T : struct, IRowIntervalOperation { var parallelSettings = ParallelExecutionSettings.FromConfiguration(configuration); - IterateRows(rectangle, in parallelSettings, in body); + IterateRows(rectangle, in parallelSettings, in operation); } /// @@ -38,7 +38,7 @@ namespace SixLabors.ImageSharp.Advanced /// The type of row operation to perform. /// The . /// The . - /// The method body defining the iteration logic on a single . + /// The operation defining the iteration logic on a single . public static void IterateRows( Rectangle rectangle, in ParallelExecutionSettings parallelSettings, @@ -81,10 +81,10 @@ namespace SixLabors.ImageSharp.Advanced /// /// The type of row operation to perform. /// The type of buffer elements. - /// The . /// The to get the parallel settings from. - /// The method body defining the iteration logic on a single . - public static void IterateRows(Rectangle rectangle, Configuration configuration, in T operation) + /// The . + /// The operation defining the iteration logic on a single . + public static void IterateRows(Configuration configuration, Rectangle rectangle, in T operation) where T : struct, IRowIntervalOperation where TBuffer : unmanaged { @@ -100,7 +100,7 @@ namespace SixLabors.ImageSharp.Advanced /// The type of buffer elements. /// The . /// The . - /// The method body defining the iteration logic on a single . + /// The operation defining the iteration logic on a single . public static void IterateRows( Rectangle rectangle, in ParallelExecutionSettings parallelSettings, @@ -143,118 +143,6 @@ namespace SixLabors.ImageSharp.Advanced rowOperation.Invoke); } - /// - /// Iterate through the rows of a rectangle in optimized batches defined by -s. - /// - /// The . - /// The to get the parallel settings from. - /// The method body defining the iteration logic on a single . - internal static void IterateRows(Rectangle rectangle, Configuration configuration, Action body) - { - var parallelSettings = ParallelExecutionSettings.FromConfiguration(configuration); - IterateRows(rectangle, in parallelSettings, body); - } - - /// - /// Iterate through the rows of a rectangle in optimized batches defined by -s. - /// - /// The . - /// The . - /// The method body defining the iteration logic on a single . - internal static void IterateRows( - Rectangle rectangle, - in ParallelExecutionSettings parallelSettings, - Action body) - { - 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) - { - var rows = new RowInterval(top, bottom); - body(rows); - return; - } - - int verticalStep = DivideCeil(rectangle.Height, numOfSteps); - var parallelOptions = new ParallelOptions { MaxDegreeOfParallelism = numOfSteps }; - var rowInfo = new WrappingRowIntervalInfo(top, bottom, verticalStep); - var rowAction = new WrappingRowIntervalOperation(in rowInfo, body); - - Parallel.For( - 0, - numOfSteps, - parallelOptions, - rowAction.Invoke); - } - - /// - /// Iterate through the rows of a rectangle in optimized batches defined by -s - /// instantiating a temporary buffer for each invocation. - /// - internal static void IterateRows( - Rectangle rectangle, - Configuration configuration, - Action> body) - where TBuffer : unmanaged - { - var parallelSettings = ParallelExecutionSettings.FromConfiguration(configuration); - IterateRows(rectangle, in parallelSettings, body); - } - - /// - /// Iterate through the rows of a rectangle in optimized batches defined by -s - /// instantiating a temporary buffer for each invocation. - /// - internal static void IterateRows( - Rectangle rectangle, - in ParallelExecutionSettings parallelSettings, - Action> body) - 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) - { - var rows = new RowInterval(top, bottom); - using (IMemoryOwner buffer = allocator.Allocate(width)) - { - body(rows, buffer.Memory); - } - - return; - } - - int verticalStep = DivideCeil(rectangle.Height, numOfSteps); - var parallelOptions = new ParallelOptions { MaxDegreeOfParallelism = numOfSteps }; - var rowInfo = new WrappingRowIntervalInfo(top, bottom, verticalStep, width); - var rowAction = new WrappingRowIntervalBufferOperation(in rowInfo, allocator, body); - - Parallel.For( - 0, - numOfSteps, - parallelOptions, - rowAction.Invoke); - } - [MethodImpl(InliningOptions.ShortMethod)] private static int DivideCeil(int dividend, int divisor) => 1 + ((dividend - 1) / divisor); diff --git a/src/ImageSharp/ImageFrame{TPixel}.cs b/src/ImageSharp/ImageFrame{TPixel}.cs index e3dbad505..a2de8d671 100644 --- a/src/ImageSharp/ImageFrame{TPixel}.cs +++ b/src/ImageSharp/ImageFrame{TPixel}.cs @@ -259,11 +259,12 @@ namespace SixLabors.ImageSharp } var target = new ImageFrame(configuration, this.Width, this.Height, this.Metadata.DeepClone()); + var operation = new RowIntervalOperation(this, target, configuration); ParallelRowIterator.IterateRows( - this.Bounds(), configuration, - new RowIntervalOperation(this, target, configuration)); + this.Bounds(), + in operation); return target; } diff --git a/src/ImageSharp/Processing/Processors/Binarization/BinaryThresholdProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Binarization/BinaryThresholdProcessor{TPixel}.cs index 129edbb03..52be6abe2 100644 --- a/src/ImageSharp/Processing/Processors/Binarization/BinaryThresholdProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Binarization/BinaryThresholdProcessor{TPixel}.cs @@ -50,11 +50,11 @@ namespace SixLabors.ImageSharp.Processing.Processors.Binarization bool isAlphaOnly = typeof(TPixel) == typeof(A8); var workingRect = Rectangle.FromLTRB(startX, startY, endX, endY); - + var operation = new RowIntervalOperation(source, upper, lower, threshold, startX, endX, isAlphaOnly); ParallelRowIterator.IterateRows( - workingRect, configuration, - new RowIntervalOperation(source, upper, lower, threshold, startX, endX, isAlphaOnly)); + workingRect, + in operation); } /// diff --git a/src/ImageSharp/Processing/Processors/Convolution/BokehBlurProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Convolution/BokehBlurProcessor{TPixel}.cs index 3fb62ed19..16acc2407 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/BokehBlurProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/BokehBlurProcessor{TPixel}.cs @@ -268,10 +268,11 @@ 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.IterateRows( - this.SourceRectangle, this.Configuration, - new ApplyGammaExposureRowIntervalOperation(this.SourceRectangle, source.PixelBuffer, this.Configuration, this.gamma)); + this.SourceRectangle, + in gammaOperation); // Create a 0-filled buffer to use to store the result of the component convolutions using Buffer2D processingBuffer = this.Configuration.MemoryAllocator.Allocate2D(source.Size(), AllocationOptions.Clean); @@ -282,10 +283,11 @@ 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.IterateRows( - this.SourceRectangle, this.Configuration, - new ApplyInverseGammaExposureRowIntervalOperation(this.SourceRectangle, source.PixelBuffer, processingBuffer, this.Configuration, inverseGamma)); + this.SourceRectangle, + in operation); } /// @@ -314,16 +316,18 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution Vector4 parameters = Unsafe.Add(ref paramsRef, i); // Compute the vertical 1D convolution + var verticalOperation = new ApplyVerticalConvolutionRowIntervalOperation(ref sourceRectangle, firstPassBuffer, source.PixelBuffer, kernel); ParallelRowIterator.IterateRows( - sourceRectangle, configuration, - new ApplyVerticalConvolutionRowIntervalOperation(ref sourceRectangle, firstPassBuffer, source.PixelBuffer, kernel)); + sourceRectangle, + in verticalOperation); // Compute the horizontal 1D convolutions and accumulate the partial results on the target buffer + var horizontalOperation = new ApplyHorizontalConvolutionRowIntervalOperation(ref sourceRectangle, processingBuffer, firstPassBuffer, kernel, parameters.Z, parameters.W); ParallelRowIterator.IterateRows( - sourceRectangle, configuration, - new ApplyHorizontalConvolutionRowIntervalOperation(ref sourceRectangle, processingBuffer, firstPassBuffer, kernel, parameters.Z, parameters.W)); + sourceRectangle, + in horizontalOperation); } } diff --git a/src/ImageSharp/Processing/Processors/Convolution/Convolution2DProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Convolution/Convolution2DProcessor{TPixel}.cs index 0340482fe..d8179c6d5 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/Convolution2DProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/Convolution2DProcessor{TPixel}.cs @@ -65,11 +65,12 @@ 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); ParallelRowIterator.IterateRows( - interest, this.Configuration, - new RowIntervalOperation(interest, targetPixels, source.PixelBuffer, this.KernelY, this.KernelX, this.Configuration, this.PreserveAlpha)); + interest, + in operation); Buffer2D.SwapOrCopyContent(source.PixelBuffer, targetPixels); } diff --git a/src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor{TPixel}.cs index 3cbbf8c46..fb477e2d6 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor{TPixel}.cs @@ -64,16 +64,18 @@ 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.IterateRows( - interest, this.Configuration, - new RowIntervalOperation(interest, firstPassPixels, source.PixelBuffer, this.KernelX, this.Configuration, this.PreserveAlpha)); + interest, + in horizontalOperation); // Vertical convolution + var verticalOperation = new RowIntervalOperation(interest, source.PixelBuffer, firstPassPixels, this.KernelY, this.Configuration, this.PreserveAlpha); ParallelRowIterator.IterateRows( - interest, this.Configuration, - new RowIntervalOperation(interest, source.PixelBuffer, firstPassPixels, this.KernelY, this.Configuration, this.PreserveAlpha)); + interest, + in verticalOperation); } /// diff --git a/src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessor{TPixel}.cs index 2774a2f88..feb27ac62 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessor{TPixel}.cs @@ -56,11 +56,11 @@ 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.IterateRows( - interest, this.Configuration, - new RowIntervalOperation(interest, targetPixels, source.PixelBuffer, this.KernelXY, this.Configuration, this.PreserveAlpha)); + interest, + in operation); Buffer2D.SwapOrCopyContent(source.PixelBuffer, targetPixels); } diff --git a/src/ImageSharp/Processing/Processors/Convolution/EdgeDetectorCompassProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Convolution/EdgeDetectorCompassProcessor{TPixel}.cs index f390ee1ff..fde669ea8 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/EdgeDetectorCompassProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/EdgeDetectorCompassProcessor{TPixel}.cs @@ -102,10 +102,11 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution processor.Apply(pass); } + var operation = new RowIntervalOperation(source.PixelBuffer, pass.PixelBuffer, minX, maxX, shiftY, shiftX); ParallelRowIterator.IterateRows( - Rectangle.FromLTRB(minX, minY, maxX, maxY), this.Configuration, - new RowIntervalOperation(source.PixelBuffer, pass.PixelBuffer, minX, maxX, shiftY, shiftX)); + Rectangle.FromLTRB(minX, minY, maxX, maxY), + in operation); } } diff --git a/src/ImageSharp/Processing/Processors/Drawing/DrawImageProcessor{TPixelBg,TPixelFg}.cs b/src/ImageSharp/Processing/Processors/Drawing/DrawImageProcessor{TPixelBg,TPixelFg}.cs index 88fa6bec3..c1ce30cae 100644 --- a/src/ImageSharp/Processing/Processors/Drawing/DrawImageProcessor{TPixelBg,TPixelFg}.cs +++ b/src/ImageSharp/Processing/Processors/Drawing/DrawImageProcessor{TPixelBg,TPixelFg}.cs @@ -99,10 +99,11 @@ 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.IterateRows( - workingRect, configuration, - new RowIntervalOperation(source, targetImage, blender, configuration, minX, width, locationY, targetX, this.Opacity)); + workingRect, + in operation); } /// diff --git a/src/ImageSharp/Processing/Processors/Effects/OilPaintingProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Effects/OilPaintingProcessor{TPixel}.cs index 1c3be4e63..50c0a22d3 100644 --- a/src/ImageSharp/Processing/Processors/Effects/OilPaintingProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Effects/OilPaintingProcessor{TPixel}.cs @@ -47,10 +47,11 @@ 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( - this.SourceRectangle, this.Configuration, - new RowIntervalOperation(this.SourceRectangle, targetPixels, source, this.Configuration, brushSize >> 1, this.definition.Levels)); + this.SourceRectangle, + in operation); Buffer2D.SwapOrCopyContent(source.PixelBuffer, targetPixels); } diff --git a/src/ImageSharp/Processing/Processors/Effects/PixelRowDelegateProcessor{TPixel,TDelegate}.cs b/src/ImageSharp/Processing/Processors/Effects/PixelRowDelegateProcessor{TPixel,TDelegate}.cs index 3f8dcd8d9..a22af8ce2 100644 --- a/src/ImageSharp/Processing/Processors/Effects/PixelRowDelegateProcessor{TPixel,TDelegate}.cs +++ b/src/ImageSharp/Processing/Processors/Effects/PixelRowDelegateProcessor{TPixel,TDelegate}.cs @@ -50,11 +50,12 @@ 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); ParallelRowIterator.IterateRows( - interest, this.Configuration, - new RowIntervalOperation(interest.X, source, this.Configuration, this.modifiers, this.rowDelegate)); + interest, + in operation); } /// diff --git a/src/ImageSharp/Processing/Processors/Filters/FilterProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Filters/FilterProcessor{TPixel}.cs index 159a1f981..cae8b14b8 100644 --- a/src/ImageSharp/Processing/Processors/Filters/FilterProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Filters/FilterProcessor{TPixel}.cs @@ -36,11 +36,12 @@ 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); ParallelRowIterator.IterateRows( - interest, this.Configuration, - new RowIntervalOperation(interest.X, source, this.definition.Matrix, 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 aa97e3a7e..eb666a4f1 100644 --- a/src/ImageSharp/Processing/Processors/Normalization/AdaptiveHistogramEqualizationProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Normalization/AdaptiveHistogramEqualizationProcessor{TPixel}.cs @@ -80,10 +80,11 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization yStart += tileHeight; } + var operation = new RowIntervalOperation(cdfData, tileYStartPositions, tileWidth, tileHeight, tileCount, halfTileWidth, luminanceLevels, source); ParallelRowIterator.IterateRows( - new Rectangle(0, 0, sourceWidth, tileYStartPositions.Count), this.Configuration, - new RowIntervalOperation(cdfData, tileYStartPositions, tileWidth, tileHeight, tileCount, halfTileWidth, luminanceLevels, source)); + new Rectangle(0, 0, sourceWidth, tileYStartPositions.Count), + in operation); ref TPixel pixelsBase = ref source.GetPixelReference(0, 0); @@ -510,7 +511,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization public void CalculateLookupTables(ImageFrame source, HistogramEqualizationProcessor processor) { - var rowOperation = new RowIntervalOperation( + var operation = new RowIntervalOperation( processor, this.memoryAllocator, this.cdfMinBuffer2D, @@ -522,9 +523,9 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization source); ParallelRowIterator.IterateRows( - new Rectangle(0, 0, this.sourceWidth, this.tileYStartPositions.Count), this.configuration, - in rowOperation); + new Rectangle(0, 0, this.sourceWidth, this.tileYStartPositions.Count), + in operation); } [MethodImpl(InliningOptions.ShortMethod)] diff --git a/src/ImageSharp/Processing/Processors/Normalization/GlobalHistogramEqualizationProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Normalization/GlobalHistogramEqualizationProcessor{TPixel}.cs index 38da9c8d4..d7ea80737 100644 --- a/src/ImageSharp/Processing/Processors/Normalization/GlobalHistogramEqualizationProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Normalization/GlobalHistogramEqualizationProcessor{TPixel}.cs @@ -52,10 +52,11 @@ 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(workingRect, histogramBuffer, source, this.LuminanceLevels); ParallelRowIterator.IterateRows( - workingRect, this.Configuration, - new GrayscaleLevelsRowIntervalOperation(workingRect, histogramBuffer, source, this.LuminanceLevels)); + workingRect, + in grayscaleOperation); Span histogram = histogramBuffer.GetSpan(); if (this.ClipHistogramEnabled) @@ -74,10 +75,11 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization float numberOfPixelsMinusCdfMin = numberOfPixels - cdfMin; // Apply the cdf to each pixel of the image + var cdfOperation = new CdfApplicationRowIntervalOperation(workingRect, cdfBuffer, source, this.LuminanceLevels, numberOfPixelsMinusCdfMin); ParallelRowIterator.IterateRows( - workingRect, this.Configuration, - new CdfApplicationRowIntervalOperation(workingRect, cdfBuffer, source, this.LuminanceLevels, numberOfPixelsMinusCdfMin)); + workingRect, + in cdfOperation); } /// diff --git a/src/ImageSharp/Processing/Processors/Overlays/BackgroundColorProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Overlays/BackgroundColorProcessor{TPixel}.cs index f26e30e52..b796016d1 100644 --- a/src/ImageSharp/Processing/Processors/Overlays/BackgroundColorProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Overlays/BackgroundColorProcessor{TPixel}.cs @@ -49,10 +49,11 @@ namespace SixLabors.ImageSharp.Processing.Processors.Overlays PixelBlender blender = PixelOperations.Instance.GetPixelBlender(graphicsOptions); + var operation = new RowIntervalOperation(configuration, interest, blender, amount, colors, source); ParallelRowIterator.IterateRows( - interest, configuration, - new RowIntervalOperation(configuration, interest, blender, amount, colors, source)); + interest, + in operation); } private readonly struct RowIntervalOperation : IRowIntervalOperation diff --git a/src/ImageSharp/Processing/Processors/Overlays/GlowProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Overlays/GlowProcessor{TPixel}.cs index ccb2eed60..83d9bd1ef 100644 --- a/src/ImageSharp/Processing/Processors/Overlays/GlowProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Overlays/GlowProcessor{TPixel}.cs @@ -55,10 +55,11 @@ 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.IterateRows( - interest, configuration, - new RowIntervalOperation(configuration, interest, rowColors, this.blender, center, maxDistance, blendPercent, source)); + interest, + in operation); } private readonly struct RowIntervalOperation : IRowIntervalOperation diff --git a/src/ImageSharp/Processing/Processors/Overlays/VignetteProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Overlays/VignetteProcessor{TPixel}.cs index fd842766d..b36e6b534 100644 --- a/src/ImageSharp/Processing/Processors/Overlays/VignetteProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Overlays/VignetteProcessor{TPixel}.cs @@ -63,10 +63,11 @@ 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.IterateRows( - interest, configuration, - new RowIntervalOperation(configuration, interest, rowColors, this.blender, center, maxDistance, blendPercent, source)); + interest, + in operation); } private readonly struct RowIntervalOperation : IRowIntervalOperation diff --git a/src/ImageSharp/Processing/Processors/Transforms/AffineTransformProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Transforms/AffineTransformProcessor{TPixel}.cs index 85ac8dba5..2b579541c 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/AffineTransformProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/AffineTransformProcessor{TPixel}.cs @@ -58,20 +58,22 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms if (this.resampler is NearestNeighborResampler) { + var nnOperation = new NearestNeighborRowIntervalOperation(this.SourceRectangle, ref matrix, width, source, destination); ParallelRowIterator.IterateRows( - targetBounds, configuration, - new NearestNeighborRowIntervalOperation(this.SourceRectangle, ref matrix, width, source, destination)); + targetBounds, + in nnOperation); return; } using var kernelMap = new TransformKernelMap(configuration, source.Size(), destination.Size(), this.resampler); + var operation = new RowIntervalOperation(configuration, kernelMap, ref matrix, width, source, destination); ParallelRowIterator.IterateRows( - targetBounds, configuration, - new RowIntervalOperation(configuration, kernelMap, ref matrix, width, source, destination)); + targetBounds, + in operation); } /// diff --git a/src/ImageSharp/Processing/Processors/Transforms/FlipProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Transforms/FlipProcessor{TPixel}.cs index 58be1ef0c..877fa074e 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/FlipProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/FlipProcessor{TPixel}.cs @@ -76,10 +76,11 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms /// The configuration. private void FlipY(ImageFrame source, Configuration configuration) { + var operation = new RowIntervalOperation(source); ParallelRowIterator.IterateRows( - source.Bounds(), configuration, - new RowIntervalOperation(source)); + source.Bounds(), + in operation); } private readonly struct RowIntervalOperation : IRowIntervalOperation diff --git a/src/ImageSharp/Processing/Processors/Transforms/ProjectiveTransformProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Transforms/ProjectiveTransformProcessor{TPixel}.cs index 4e5b87b84..3969a8c3e 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/ProjectiveTransformProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/ProjectiveTransformProcessor{TPixel}.cs @@ -60,20 +60,22 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms { Rectangle sourceBounds = this.SourceRectangle; + var nnOperation = new NearestNeighborRowIntervalOperation(ref sourceBounds, ref matrix, width, source, destination); ParallelRowIterator.IterateRows( - targetBounds, configuration, - new NearestNeighborRowIntervalOperation(ref sourceBounds, ref matrix, width, source, destination)); + targetBounds, + in nnOperation); return; } using var kernelMap = new TransformKernelMap(configuration, source.Size(), destination.Size(), this.resampler); + var operation = new RowIntervalOperation(configuration, kernelMap, ref matrix, width, source, destination); ParallelRowIterator.IterateRows( - targetBounds, configuration, - new RowIntervalOperation(configuration, kernelMap, ref matrix, width, source, destination)); + targetBounds, + in operation); } private readonly struct NearestNeighborRowIntervalOperation : IRowIntervalOperation diff --git a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeProcessor{TPixel}.cs index 27a2cca51..53810a5cc 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeProcessor{TPixel}.cs @@ -95,10 +95,11 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms float widthFactor = sourceRectangle.Width / (float)this.targetRectangle.Width; float heightFactor = sourceRectangle.Height / (float)this.targetRectangle.Height; + var operation = new RowIntervalOperation(sourceRectangle, this.targetRectangle, widthFactor, heightFactor, source, destination); ParallelRowIterator.IterateRows( - interest, configuration, - new RowIntervalOperation(sourceRectangle, this.targetRectangle, widthFactor, heightFactor, source, destination)); + interest, + in operation); return; } diff --git a/src/ImageSharp/Processing/Processors/Transforms/RotateProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Transforms/RotateProcessor{TPixel}.cs index 59cdf4f10..086314a26 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/RotateProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/RotateProcessor{TPixel}.cs @@ -17,7 +17,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms internal class RotateProcessor : AffineTransformProcessor where TPixel : struct, IPixel { - private float degrees; + private readonly float degrees; /// /// Initializes a new instance of the class. @@ -131,10 +131,11 @@ 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.IterateRows( - source.Bounds(), configuration, - new Rotate180RowIntervalOperation(source.Width, source.Height, source, destination)); + source.Bounds(), + in operation); } /// @@ -145,10 +146,11 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms /// The configuration. private void Rotate270(ImageFrame source, ImageFrame destination, Configuration configuration) { + var operation = new Rotate270RowIntervalOperation(destination.Bounds(), source.Width, source.Height, source, destination); ParallelRowIterator.IterateRows( - source.Bounds(), configuration, - new Rotate270RowIntervalOperation(destination.Bounds(), source.Width, source.Height, source, destination)); + source.Bounds(), + in operation); } /// @@ -159,10 +161,11 @@ 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.IterateRows( - source.Bounds(), configuration, - new Rotate90RowIntervalOperation(destination.Bounds(), source.Width, source.Height, source, destination)); + source.Bounds(), + in operation); } private readonly struct Rotate180RowIntervalOperation : IRowIntervalOperation diff --git a/tests/ImageSharp.Tests/Helpers/ParallelRowIteratorTests.cs b/tests/ImageSharp.Tests/Helpers/ParallelRowIteratorTests.cs index 243ffe220..80ac384fd 100644 --- a/tests/ImageSharp.Tests/Helpers/ParallelRowIteratorTests.cs +++ b/tests/ImageSharp.Tests/Helpers/ParallelRowIteratorTests.cs @@ -10,7 +10,6 @@ using System.Threading; using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; - using Xunit; using Xunit.Abstractions; @@ -30,20 +29,20 @@ namespace SixLabors.ImageSharp.Tests.Helpers /// public static TheoryData IterateRows_OverMinimumPixelsLimit_Data = new TheoryData - { - { 1, 0, 100, -1, 100, 1 }, - { 2, 0, 9, 5, 4, 2 }, - { 4, 0, 19, 5, 4, 4 }, - { 2, 10, 19, 5, 4, 2 }, - { 4, 0, 200, 50, 50, 4 }, - { 4, 123, 323, 50, 50, 4 }, - { 4, 0, 1201, 301, 298, 4 }, - { 8, 10, 236, 29, 23, 8 }, - { 16, 0, 209, 14, 13, 15 }, - { 24, 0, 209, 9, 2, 24 }, - { 32, 0, 209, 7, 6, 30 }, - { 64, 0, 209, 4, 1, 53 }, - }; + { + { 1, 0, 100, -1, 100, 1 }, + { 2, 0, 9, 5, 4, 2 }, + { 4, 0, 19, 5, 4, 4 }, + { 2, 10, 19, 5, 4, 2 }, + { 4, 0, 200, 50, 50, 4 }, + { 4, 123, 323, 50, 50, 4 }, + { 4, 0, 1201, 301, 298, 4 }, + { 8, 10, 236, 29, 23, 8 }, + { 16, 0, 209, 14, 13, 15 }, + { 24, 0, 209, 9, 2, 24 }, + { 32, 0, 209, 7, 6, 30 }, + { 64, 0, 209, 4, 1, 53 }, + }; [Theory] [MemberData(nameof(IterateRows_OverMinimumPixelsLimit_Data))] @@ -64,20 +63,24 @@ namespace SixLabors.ImageSharp.Tests.Helpers int actualNumberOfSteps = 0; - ParallelRowIterator.IterateRows( - rectangle, - parallelSettings, - rows => - { - Assert.True(rows.Min >= minY); - Assert.True(rows.Max <= maxY); + void RowAction(RowInterval rows) + { + Assert.True(rows.Min >= minY); + Assert.True(rows.Max <= maxY); + + int step = rows.Max - rows.Min; + int expected = rows.Max < maxY ? expectedStepLength : expectedLastStepLength; - int step = rows.Max - rows.Min; - int expected = rows.Max < maxY ? expectedStepLength : expectedLastStepLength; + Interlocked.Increment(ref actualNumberOfSteps); + Assert.Equal(expected, step); + } - Interlocked.Increment(ref actualNumberOfSteps); - Assert.Equal(expected, step); - }); + var operation = new TestRowIntervalOperation(RowAction); + + ParallelRowIterator.IterateRows( + rectangle, + in parallelSettings, + in operation); Assert.Equal(expectedNumberOfSteps, actualNumberOfSteps); } @@ -102,16 +105,20 @@ namespace SixLabors.ImageSharp.Tests.Helpers int[] expectedData = Enumerable.Repeat(0, minY).Concat(Enumerable.Range(minY, maxY - minY)).ToArray(); var actualData = new int[maxY]; + void RowAction(RowInterval rows) + { + for (int y = rows.Min; y < rows.Max; y++) + { + actualData[y] = y; + } + } + + var operation = new TestRowIntervalOperation(RowAction); + ParallelRowIterator.IterateRows( rectangle, - parallelSettings, - rows => - { - for (int y = rows.Min; y < rows.Max; y++) - { - actualData[y] = y; - } - }); + in parallelSettings, + in operation); Assert.Equal(expectedData, actualData); } @@ -136,22 +143,27 @@ namespace SixLabors.ImageSharp.Tests.Helpers var bufferHashes = new ConcurrentBag(); int actualNumberOfSteps = 0; - ParallelRowIterator.IterateRows( - rectangle, - parallelSettings, - (RowInterval rows, Memory buffer) => - { - Assert.True(rows.Min >= minY); - Assert.True(rows.Max <= maxY); - bufferHashes.Add(buffer.GetHashCode()); + void RowAction(RowInterval rows, Memory buffer) + { + Assert.True(rows.Min >= minY); + Assert.True(rows.Max <= maxY); + + bufferHashes.Add(buffer.GetHashCode()); + + int step = rows.Max - rows.Min; + int expected = rows.Max < maxY ? expectedStepLength : expectedLastStepLength; + + Interlocked.Increment(ref actualNumberOfSteps); + Assert.Equal(expected, step); + } - int step = rows.Max - rows.Min; - int expected = rows.Max < maxY ? expectedStepLength : expectedLastStepLength; + var operation = new TestRowIntervalOperation(RowAction); - Interlocked.Increment(ref actualNumberOfSteps); - Assert.Equal(expected, step); - }); + ParallelRowIterator.IterateRows, Vector4>( + rectangle, + in parallelSettings, + in operation); Assert.Equal(expectedNumberOfSteps, actualNumberOfSteps); @@ -179,31 +191,35 @@ namespace SixLabors.ImageSharp.Tests.Helpers int[] expectedData = Enumerable.Repeat(0, minY).Concat(Enumerable.Range(minY, maxY - minY)).ToArray(); var actualData = new int[maxY]; - ParallelRowIterator.IterateRows( + void RowAction(RowInterval rows, Memory buffer) + { + for (int y = rows.Min; y < rows.Max; y++) + { + actualData[y] = y; + } + } + + var operation = new TestRowIntervalOperation(RowAction); + + ParallelRowIterator.IterateRows, Vector4>( rectangle, - parallelSettings, - (RowInterval rows, Memory buffer) => - { - for (int y = rows.Min; y < rows.Max; y++) - { - actualData[y] = y; - } - }); + in parallelSettings, + in operation); Assert.Equal(expectedData, actualData); } public static TheoryData IterateRows_WithEffectiveMinimumPixelsLimit_Data = new TheoryData - { - { 2, 200, 50, 2, 1, -1, 2 }, - { 2, 200, 200, 1, 1, -1, 1 }, - { 4, 200, 100, 4, 2, 2, 2 }, - { 4, 300, 100, 8, 3, 3, 2 }, - { 2, 5000, 1, 4500, 1, -1, 4500 }, - { 2, 5000, 1, 5000, 1, -1, 5000 }, - { 2, 5000, 1, 5001, 2, 2501, 2500 }, - }; + { + { 2, 200, 50, 2, 1, -1, 2 }, + { 2, 200, 200, 1, 1, -1, 1 }, + { 4, 200, 100, 4, 2, 2, 2 }, + { 4, 300, 100, 8, 3, 3, 2 }, + { 2, 5000, 1, 4500, 1, -1, 4500 }, + { 2, 5000, 1, 5000, 1, -1, 5000 }, + { 2, 5000, 1, 5001, 2, 2501, 2500 }, + }; [Theory] [MemberData(nameof(IterateRows_WithEffectiveMinimumPixelsLimit_Data))] @@ -225,20 +241,24 @@ namespace SixLabors.ImageSharp.Tests.Helpers int actualNumberOfSteps = 0; - ParallelRowIterator.IterateRows( - rectangle, - parallelSettings, - rows => - { - Assert.True(rows.Min >= 0); - Assert.True(rows.Max <= height); + void RowAction(RowInterval rows) + { + Assert.True(rows.Min >= 0); + Assert.True(rows.Max <= height); + + int step = rows.Max - rows.Min; + int expected = rows.Max < height ? expectedStepLength : expectedLastStepLength; + + Interlocked.Increment(ref actualNumberOfSteps); + Assert.Equal(expected, step); + } - int step = rows.Max - rows.Min; - int expected = rows.Max < height ? expectedStepLength : expectedLastStepLength; + var operation = new TestRowIntervalOperation(RowAction); - Interlocked.Increment(ref actualNumberOfSteps); - Assert.Equal(expected, step); - }); + ParallelRowIterator.IterateRows( + rectangle, + in parallelSettings, + in operation); Assert.Equal(expectedNumberOfSteps, actualNumberOfSteps); } @@ -262,33 +282,38 @@ namespace SixLabors.ImageSharp.Tests.Helpers var rectangle = new Rectangle(0, 0, width, height); int actualNumberOfSteps = 0; - ParallelRowIterator.IterateRows( - rectangle, - parallelSettings, - (RowInterval rows, Memory buffer) => - { - Assert.True(rows.Min >= 0); - Assert.True(rows.Max <= height); - int step = rows.Max - rows.Min; - int expected = rows.Max < height ? expectedStepLength : expectedLastStepLength; + void RowAction(RowInterval rows, Memory buffer) + { + Assert.True(rows.Min >= 0); + Assert.True(rows.Max <= height); - Interlocked.Increment(ref actualNumberOfSteps); - Assert.Equal(expected, step); - }); + int step = rows.Max - rows.Min; + int expected = rows.Max < height ? expectedStepLength : expectedLastStepLength; + + Interlocked.Increment(ref actualNumberOfSteps); + Assert.Equal(expected, step); + } + + var operation = new TestRowIntervalOperation(RowAction); + + ParallelRowIterator.IterateRows, Vector4>( + rectangle, + in parallelSettings, + in operation); Assert.Equal(expectedNumberOfSteps, actualNumberOfSteps); } public static readonly TheoryData IterateRectangularBuffer_Data = new TheoryData - { - { 8, 582, 453, 10, 10, 291, 226 }, // boundary data from DetectEdgesTest.DetectEdges_InBox - { 2, 582, 453, 10, 10, 291, 226 }, - { 16, 582, 453, 10, 10, 291, 226 }, - { 16, 582, 453, 10, 10, 1, 226 }, - { 16, 1, 453, 0, 10, 1, 226 }, - }; + { + { 8, 582, 453, 10, 10, 291, 226 }, // boundary data from DetectEdgesTest.DetectEdges_InBox + { 2, 582, 453, 10, 10, 291, 226 }, + { 16, 582, 453, 10, 10, 291, 226 }, + { 16, 582, 453, 10, 10, 1, 226 }, + { 16, 1, 453, 0, 10, 1, 226 }, + }; [Theory] [MemberData(nameof(IterateRectangularBuffer_Data))] @@ -325,17 +350,21 @@ namespace SixLabors.ImageSharp.Tests.Helpers // Fill actual data using IterateRows: var settings = new ParallelExecutionSettings(maxDegreeOfParallelism, memoryAllocator); + void RowAction(RowInterval rows) + { + this.output.WriteLine(rows.ToString()); + for (int y = rows.Min; y < rows.Max; y++) + { + FillRow(y, actual); + } + } + + var operation = new TestRowIntervalOperation(RowAction); + ParallelRowIterator.IterateRows( rect, settings, - rows => - { - this.output.WriteLine(rows.ToString()); - for (int y = rows.Min; y < rows.Max; y++) - { - FillRow(y, actual); - } - }); + in operation); // Assert: TestImageExtensions.CompareBuffers(expected.GetSpan(), actual.GetSpan()); @@ -353,8 +382,14 @@ namespace SixLabors.ImageSharp.Tests.Helpers var rect = new Rectangle(0, 0, width, height); + void RowAction(RowInterval rows) + { + } + + var operation = new TestRowIntervalOperation(RowAction); + ArgumentOutOfRangeException ex = Assert.Throws( - () => ParallelRowIterator.IterateRows(rect, parallelSettings, rows => { })); + () => ParallelRowIterator.IterateRows(rect, in parallelSettings, in operation)); Assert.Contains(width <= 0 ? "Width" : "Height", ex.Message); } @@ -370,10 +405,38 @@ namespace SixLabors.ImageSharp.Tests.Helpers var rect = new Rectangle(0, 0, width, height); + void RowAction(RowInterval rows, Memory memory) + { + } + + var operation = new TestRowIntervalOperation(RowAction); + ArgumentOutOfRangeException ex = Assert.Throws( - () => ParallelRowIterator.IterateRows(rect, parallelSettings, (rows, memory) => { })); + () => ParallelRowIterator.IterateRows, Rgba32>(rect, in parallelSettings, in operation)); Assert.Contains(width <= 0 ? "Width" : "Height", ex.Message); } + + private readonly struct TestRowIntervalOperation : IRowIntervalOperation + { + private readonly Action action; + + public TestRowIntervalOperation(Action action) + => this.action = action; + + public void Invoke(in RowInterval rows) => this.action(rows); + } + + private readonly struct TestRowIntervalOperation : IRowIntervalOperation + where TBuffer : unmanaged + { + private readonly Action> action; + + public TestRowIntervalOperation(Action> action) + => this.action = action; + + public void Invoke(in RowInterval rows, Memory memory) + => this.action(rows, memory); + } } } diff --git a/tests/ImageSharp.Tests/TestUtilities/TestImageExtensions.cs b/tests/ImageSharp.Tests/TestUtilities/TestImageExtensions.cs index 9aaca3e3f..d570b4d05 100644 --- a/tests/ImageSharp.Tests/TestUtilities/TestImageExtensions.cs +++ b/tests/ImageSharp.Tests/TestUtilities/TestImageExtensions.cs @@ -702,25 +702,44 @@ namespace SixLabors.ImageSharp.Tests { Rectangle sourceRectangle = this.SourceRectangle; Configuration configuration = this.Configuration; - ParallelRowIterator.IterateRows( - sourceRectangle, + + var operation = new RowOperation(configuration, sourceRectangle, source); + + ParallelRowIterator.IterateRows( configuration, - (rows, temp) => + sourceRectangle, + in operation); + } + + private readonly struct RowOperation : IRowIntervalOperation + { + private readonly Configuration configuration; + private readonly Rectangle bounds; + private readonly ImageFrame source; + + public RowOperation(Configuration configuration, Rectangle bounds, ImageFrame source) + { + this.configuration = configuration; + this.bounds = bounds; + this.source = source; + } + + public void Invoke(in RowInterval rows, Memory memory) + { + Span tempSpan = memory.Span; + for (int y = rows.Min; y < rows.Max; y++) + { + Span rowSpan = this.source.GetPixelRowSpan(y).Slice(this.bounds.Left, this.bounds.Width); + PixelOperations.Instance.ToVector4(this.configuration, rowSpan, tempSpan, PixelConversionModifiers.Scale); + for (int i = 0; i < tempSpan.Length; i++) { - Span tempSpan = temp.Span; - for (int y = rows.Min; y < rows.Max; y++) - { - Span rowSpan = source.GetPixelRowSpan(y).Slice(sourceRectangle.Left, sourceRectangle.Width); - PixelOperations.Instance.ToVector4(configuration, rowSpan, tempSpan, PixelConversionModifiers.Scale); - for (int i = 0; i < tempSpan.Length; i++) - { - ref Vector4 v = ref tempSpan[i]; - v.W = 1F; - } - - PixelOperations.Instance.FromVector4Destructive(configuration, tempSpan, rowSpan, PixelConversionModifiers.Scale); - } - }); + ref Vector4 v = ref tempSpan[i]; + v.W = 1F; + } + + PixelOperations.Instance.FromVector4Destructive(this.configuration, tempSpan, rowSpan, PixelConversionModifiers.Scale); + } + } } } }