public void IterateRowsWithTempBuffer_WithEffectiveMinimumPixelsLimit( int maxDegreeOfParallelism, int minimumPixelsProcessedPerTask, int width, int height, int expectedNumberOfSteps, int expectedStepLength, int expectedLastStepLength) { var parallelSettings = new ParallelExecutionSettings( maxDegreeOfParallelism, minimumPixelsProcessedPerTask, Configuration.Default.MemoryAllocator); var rectangle = new Rectangle(0, 0, width, height); int actualNumberOfSteps = 0; ParallelHelper.IterateRowsWithTempBuffer( rectangle, parallelSettings, (RowInterval rows, Memory <Vector4> buffer) => { 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); }); Assert.Equal(expectedNumberOfSteps, actualNumberOfSteps); }
/// <inheritdoc/> protected override void OnFrameApply(ImageFrame <TPixel> source, Rectangle sourceRectangle, Configuration configuration) { var interest = Rectangle.Intersect(sourceRectangle, source.Bounds()); int startX = interest.X; ColorMatrix matrix = this.definition.Matrix; ParallelHelper.IterateRowsWithTempBuffer <Vector4>( interest, configuration, (rows, vectorBuffer) => { for (int y = rows.Min; y < rows.Max; y++) { Span <Vector4> vectorSpan = vectorBuffer.Span; int length = vectorSpan.Length; Span <TPixel> rowSpan = source.GetPixelRowSpan(y).Slice(startX, length); PixelOperations <TPixel> .Instance.ToVector4(configuration, rowSpan, vectorSpan); Vector4Utils.Transform(vectorSpan, ref matrix); PixelOperations <TPixel> .Instance.FromVector4Destructive(configuration, vectorSpan, rowSpan); } }); }
public void IterateRowsWithTempBuffer_OverMinimumPixelsLimit_ShouldVisitAllRows( int maxDegreeOfParallelism, int minY, int maxY, int expectedStepLength, int expectedLastStepLength) { var parallelSettings = new ParallelExecutionSettings( maxDegreeOfParallelism, 1, Configuration.Default.MemoryAllocator); var rectangle = new Rectangle(0, minY, 10, maxY - minY); int[] expectedData = Enumerable.Repeat(0, minY).Concat(Enumerable.Range(minY, maxY - minY)).ToArray(); var actualData = new int[maxY]; ParallelHelper.IterateRowsWithTempBuffer( rectangle, parallelSettings, (RowInterval rows, Memory <Vector4> buffer) => { for (int y = rows.Min; y < rows.Max; y++) { actualData[y] = y; } }); Assert.Equal(expectedData, actualData); }
/// <inheritdoc/> protected override void OnFrameApply(ImageFrame <TPixel> source) { var interest = Rectangle.Intersect(this.SourceRectangle, source.Bounds()); int startX = interest.X; Configuration configuration = this.Configuration; PixelConversionModifiers modifiers = this.modifiers; ParallelHelper.IterateRowsWithTempBuffer <Vector4>( interest, this.Configuration, (rows, vectorBuffer) => { for (int y = rows.Min; y < rows.Max; y++) { Span <Vector4> vectorSpan = vectorBuffer.Span; int length = vectorSpan.Length; Span <TPixel> rowSpan = source.GetPixelRowSpan(y).Slice(startX, length); PixelOperations <TPixel> .Instance.ToVector4(configuration, rowSpan, vectorSpan, modifiers); // Run the user defined pixel shader on the current row of pixels this.ApplyPixelShader(vectorSpan, new Point(startX, y)); PixelOperations <TPixel> .Instance.FromVector4Destructive(configuration, vectorSpan, rowSpan, modifiers); } }); }
public void IterateRowsWithTempBufferRequiresValidRectangle(int width, int height) { var parallelSettings = default(ParallelExecutionSettings); var rect = new Rectangle(0, 0, width, height); ArgumentOutOfRangeException ex = Assert.Throws <ArgumentOutOfRangeException>( () => ParallelHelper.IterateRowsWithTempBuffer <Rgba32>(rect, parallelSettings, (rows, memory) => { })); Assert.Contains(width <= 0 ? "Width" : "Height", ex.Message); }
/// <inheritdoc/> protected override void OnFrameApply( ImageFrame <TPixel> source, Rectangle sourceRectangle, Configuration configuration) { DenseMatrix <float> matrixY = this.KernelY; DenseMatrix <float> matrixX = this.KernelX; var interest = Rectangle.Intersect(sourceRectangle, source.Bounds()); int startY = interest.Y; int endY = interest.Bottom; int startX = interest.X; int endX = interest.Right; int maxY = endY - 1; int maxX = endX - 1; using (Buffer2D <TPixel> targetPixels = configuration.MemoryAllocator.Allocate2D <TPixel>(source.Width, source.Height)) { source.CopyTo(targetPixels); var workingRectangle = Rectangle.FromLTRB(startX, startY, endX, endY); int width = workingRectangle.Width; ParallelHelper.IterateRowsWithTempBuffer <Vector4>( workingRectangle, configuration, (rows, vectorBuffer) => { Span <Vector4> vectorSpan = vectorBuffer.Span; int length = vectorSpan.Length; for (int y = rows.Min; y < rows.Max; y++) { Span <TPixel> targetRowSpan = targetPixels.GetRowSpan(y).Slice(startX); PixelOperations <TPixel> .Instance.ToVector4(targetRowSpan, vectorSpan, length); for (int x = 0; x < width; x++) { DenseMatrixUtils.Convolve2D(in matrixY, in matrixX, source.PixelBuffer, vectorSpan, y, x, maxY, maxX, startX); } PixelOperations <TPixel> .Instance.PackFromVector4(vectorSpan, targetRowSpan, length); }
public void IterateRowsWithTempBuffer_OverMinimumPixelsLimit( int maxDegreeOfParallelism, int minY, int maxY, int expectedStepLength, int expectedLastStepLength) { var parallelSettings = new ParallelExecutionSettings( maxDegreeOfParallelism, 1, Configuration.Default.MemoryAllocator); var rectangle = new Rectangle(0, minY, 10, maxY - minY); var bufferHashes = new ConcurrentBag <int>(); int actualNumberOfSteps = 0; ParallelHelper.IterateRowsWithTempBuffer( rectangle, parallelSettings, (RowInterval rows, Memory <Vector4> 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); }); Assert.Equal(maxDegreeOfParallelism, actualNumberOfSteps); int numberOfDifferentBuffers = bufferHashes.Distinct().Count(); Assert.Equal(actualNumberOfSteps, numberOfDifferentBuffers); }
/// <inheritdoc/> protected override void OnFrameApply(ImageFrame <TPixel> source) { // TODO: can we simplify the rectangle calculation? int startY = this.SourceRectangle.Y; int endY = this.SourceRectangle.Bottom; int startX = this.SourceRectangle.X; int endX = this.SourceRectangle.Right; TPixel glowColor = this.definition.GlowColor.ToPixel <TPixel>(); Vector2 center = Rectangle.Center(this.SourceRectangle); float finalRadius = this.definition.Radius.Calculate(source.Size()); float maxDistance = finalRadius > 0 ? MathF.Min(finalRadius, this.SourceRectangle.Width * .5F) : this.SourceRectangle.Width * .5F; // Align start/end positions. int minX = Math.Max(0, startX); int maxX = Math.Min(source.Width, endX); int minY = Math.Max(0, startY); int maxY = Math.Min(source.Height, endY); // Reset offset if necessary. if (minX > 0) { startX = 0; } if (minY > 0) { startY = 0; } int width = maxX - minX; int offsetX = minX - startX; var workingRect = Rectangle.FromLTRB(minX, minY, maxX, maxY); float blendPercentage = this.definition.GraphicsOptions.BlendPercentage; Configuration configuration = this.Configuration; MemoryAllocator memoryAllocator = configuration.MemoryAllocator; using (IMemoryOwner <TPixel> rowColors = memoryAllocator.Allocate <TPixel>(width)) { rowColors.GetSpan().Fill(glowColor); ParallelHelper.IterateRowsWithTempBuffer <float>( workingRect, configuration, (rows, amounts) => { Span <float> amountsSpan = amounts.Span; for (int y = rows.Min; y < rows.Max; y++) { int offsetY = y - startY; for (int i = 0; i < width; i++) { float distance = Vector2.Distance(center, new Vector2(i + offsetX, offsetY)); amountsSpan[i] = (blendPercentage * (1 - (.95F * (distance / maxDistance)))).Clamp(0, 1); } Span <TPixel> destination = source.GetPixelRowSpan(offsetY).Slice(offsetX, width); this.blender.Blend( configuration, destination, destination, rowColors.GetSpan(), amountsSpan); } }); } }
/// <inheritdoc/> protected override void OnFrameApply(ImageFrame <TPixel> source, ImageFrame <TPixel> destination, Rectangle sourceRectangle, Configuration configuration) { // Handle resize dimensions identical to the original if (source.Width == destination.Width && source.Height == destination.Height && sourceRectangle == this.ResizeRectangle) { // The cloned will be blank here copy all the pixel data over source.GetPixelSpan().CopyTo(destination.GetPixelSpan()); return; } int width = this.Width; int height = this.Height; int sourceX = sourceRectangle.X; int sourceY = sourceRectangle.Y; int startY = this.ResizeRectangle.Y; int endY = this.ResizeRectangle.Bottom; int startX = this.ResizeRectangle.X; int endX = this.ResizeRectangle.Right; int minX = Math.Max(0, startX); int maxX = Math.Min(width, endX); int minY = Math.Max(0, startY); int maxY = Math.Min(height, endY); if (this.Sampler is NearestNeighborResampler) { var workingRect = Rectangle.FromLTRB(minX, minY, maxX, maxY); // Scaling factors float widthFactor = sourceRectangle.Width / (float)this.ResizeRectangle.Width; float heightFactor = sourceRectangle.Height / (float)this.ResizeRectangle.Height; ParallelHelper.IterateRows( workingRect, configuration, rows => { for (int y = rows.Min; y < rows.Max; y++) { // Y coordinates of source points Span <TPixel> sourceRow = source.GetPixelRowSpan((int)(((y - startY) * heightFactor) + sourceY)); Span <TPixel> targetRow = destination.GetPixelRowSpan(y); for (int x = minX; x < maxX; x++) { // X coordinates of source points targetRow[x] = sourceRow[(int)(((x - startX) * widthFactor) + sourceX)]; } } }); return; } int sourceHeight = source.Height; PixelConversionModifiers conversionModifiers = PixelConversionModifiers.Premultiply; if (this.Compand) { conversionModifiers |= PixelConversionModifiers.Scale | PixelConversionModifiers.SRgbCompand; } // Interpolate the image using the calculated weights. // A 2-pass 1D algorithm appears to be faster than splitting a 1-pass 2D algorithm // First process the columns. Since we are not using multiple threads startY and endY // are the upper and lower bounds of the source rectangle. using (Buffer2D <Vector4> firstPassPixelsTransposed = source.MemoryAllocator.Allocate2D <Vector4>(sourceHeight, width)) { firstPassPixelsTransposed.MemorySource.Clear(); var processColsRect = new Rectangle(0, 0, source.Width, sourceRectangle.Bottom); ParallelHelper.IterateRowsWithTempBuffer <Vector4>( processColsRect, configuration, (rows, tempRowBuffer) => { for (int y = rows.Min; y < rows.Max; y++) { Span <TPixel> sourceRow = source.GetPixelRowSpan(y).Slice(sourceX); Span <Vector4> tempRowSpan = tempRowBuffer.Span.Slice(sourceX); PixelOperations <TPixel> .Instance.ToVector4(configuration, sourceRow, tempRowSpan, conversionModifiers); ref Vector4 firstPassBaseRef = ref firstPassPixelsTransposed.Span[y]; for (int x = minX; x < maxX; x++) { ResizeKernel kernel = this.horizontalKernelMap.GetKernel(x - startX); Unsafe.Add(ref firstPassBaseRef, x * sourceHeight) = kernel.Convolve(tempRowSpan); } } }); var processRowsRect = Rectangle.FromLTRB(0, minY, width, maxY); // Now process the rows. ParallelHelper.IterateRowsWithTempBuffer <Vector4>( processRowsRect, configuration, (rows, tempRowBuffer) => { Span <Vector4> tempRowSpan = tempRowBuffer.Span; for (int y = rows.Min; y < rows.Max; y++) { // Ensure offsets are normalized for cropping and padding. ResizeKernel kernel = this.verticalKernelMap.GetKernel(y - startY); ref Vector4 tempRowBase = ref MemoryMarshal.GetReference(tempRowSpan); for (int x = 0; x < width; x++) { Span <Vector4> firstPassColumn = firstPassPixelsTransposed.GetRowSpan(x).Slice(sourceY); // Destination color components Unsafe.Add(ref tempRowBase, x) = kernel.Convolve(firstPassColumn); } Span <TPixel> targetRowSpan = destination.GetPixelRowSpan(y); PixelOperations <TPixel> .Instance.FromVector4Destructive(configuration, tempRowSpan, targetRowSpan, conversionModifiers); } }); } }
/// <inheritdoc/> protected override void OnFrameApply(ImageFrame <TPixel> source, Rectangle sourceRectangle, Configuration configuration) { int startY = sourceRectangle.Y; int endY = sourceRectangle.Bottom; int startX = sourceRectangle.X; int endX = sourceRectangle.Right; TPixel vignetteColor = this.VignetteColor; Vector2 centre = Rectangle.Center(sourceRectangle); Size sourceSize = source.Size(); float finalRadiusX = this.RadiusX.Calculate(sourceSize); float finalRadiusY = this.RadiusY.Calculate(sourceSize); float rX = finalRadiusX > 0 ? MathF.Min(finalRadiusX, sourceRectangle.Width * .5F) : sourceRectangle.Width * .5F; float rY = finalRadiusY > 0 ? MathF.Min(finalRadiusY, sourceRectangle.Height * .5F) : sourceRectangle.Height * .5F; float maxDistance = MathF.Sqrt((rX * rX) + (rY * rY)); // Align start/end positions. int minX = Math.Max(0, startX); int maxX = Math.Min(source.Width, endX); int minY = Math.Max(0, startY); int maxY = Math.Min(source.Height, endY); // Reset offset if necessary. if (minX > 0) { startX = 0; } if (minY > 0) { startY = 0; } int width = maxX - minX; int offsetX = minX - startX; var workingRect = Rectangle.FromLTRB(minX, minY, maxX, maxY); using (IMemoryOwner <TPixel> rowColors = source.MemoryAllocator.Allocate <TPixel>(width)) { rowColors.GetSpan().Fill(vignetteColor); ParallelHelper.IterateRowsWithTempBuffer <float>( workingRect, configuration, (rows, amounts) => { Span <float> amountsSpan = amounts.Span; for (int y = rows.Min; y < rows.Max; y++) { int offsetY = y - startY; for (int i = 0; i < width; i++) { float distance = Vector2.Distance(centre, new Vector2(i + offsetX, offsetY)); amountsSpan[i] = (this.GraphicsOptions.BlendPercentage * (.9F * (distance / maxDistance))).Clamp( 0, 1); } Span <TPixel> destination = source.GetPixelRowSpan(offsetY).Slice(offsetX, width); this.blender.Blend( source.Configuration, destination, destination, rowColors.GetSpan(), amountsSpan); } }); } }