/// <summary> /// Swaps the image at the Y-axis, which goes vertically through the middle at half of the width of the image. /// </summary> /// <param name="source">The source image to apply the process to.</param> /// <param name="configuration">The configuration.</param> private void FlipY(ImageFrame <TPixel> source, Configuration configuration) { int width = source.Width; int height = source.Height; int halfWidth = (int)Math.Ceiling(width * .5F); using (Buffer2D <TPixel> targetPixels = configuration.MemoryAllocator.Allocate2D <TPixel>(source.Size())) { ParallelFor.WithConfiguration( 0, height, configuration, y => { Span <TPixel> sourceRow = source.GetPixelRowSpan(y); Span <TPixel> targetRow = targetPixels.GetRowSpan(y); for (int x = 0; x < halfWidth; x++) { int newX = width - x - 1; targetRow[x] = sourceRow[newX]; targetRow[newX] = sourceRow[x]; } }); Buffer2D <TPixel> .SwapOrCopyContent(source.PixelBuffer, targetPixels); } }
/// <summary> /// Swaps the image at the X-axis, which goes horizontally through the middle at half the height of the image. /// </summary> /// <param name="source">The source image to apply the process to.</param> /// <param name="configuration">The configuration.</param> private void FlipX(ImageFrame <TPixel> source, Configuration configuration) { int height = source.Height; int halfHeight = (int)Math.Ceiling(source.Height * .5F); using (Buffer2D <TPixel> targetPixels = configuration.MemoryAllocator.Allocate2D <TPixel>(source.Size())) { ParallelFor.WithConfiguration( 0, halfHeight, configuration, y => { int newY = height - y - 1; Span <TPixel> sourceRow = source.GetPixelRowSpan(y); Span <TPixel> altSourceRow = source.GetPixelRowSpan(newY); Span <TPixel> targetRow = targetPixels.GetRowSpan(y); Span <TPixel> altTargetRow = targetPixels.GetRowSpan(newY); sourceRow.CopyTo(altTargetRow); altSourceRow.CopyTo(targetRow); }); Buffer2D <TPixel> .SwapOrCopyContent(source.PixelBuffer, targetPixels); } }
/// <summary> /// Switches the buffers used by the image and the pixelSource meaning that the Image will "own" the buffer from the pixelSource and the pixelSource will now own the Images buffer. /// </summary> /// <param name="pixelSource">The pixel source.</param> internal void SwapOrCopyPixelsBufferFrom(ImageFrame <TPixel> pixelSource) { Guard.NotNull(pixelSource, nameof(pixelSource)); Buffer2D <TPixel> .SwapOrCopyContent(this.PixelBuffer, pixelSource.PixelBuffer); this.UpdateSize(this.PixelBuffer.Size()); }
public void WhenDestIsNotMemoryOwner_DifferentSize_Throws(bool sourceIsOwner) { var data = new Rgba32[21]; var color = new Rgba32(1, 2, 3, 4); using var destOwner = new TestMemoryManager <Rgba32>(data); using var dest = new Buffer2D <Rgba32>(MemoryGroup <Rgba32> .Wrap(destOwner.Memory), 21, 1); using Buffer2D <Rgba32> source = this.MemoryAllocator.Allocate2D <Rgba32>(22, 1); source.FastMemoryGroup[0].Span[10] = color; // Act: Assert.ThrowsAny <InvalidOperationException>(() => Buffer2D <Rgba32> .SwapOrCopyContent(dest, source)); Assert.Equal(color, source.MemoryGroup[0].Span[10]); Assert.NotEqual(color, dest.MemoryGroup[0].Span[10]); }
public void WhenDestIsNotAllocated_SameSize_ShouldCopy(bool sourceIsAllocated) { var data = new Rgba32[21]; var color = new Rgba32(1, 2, 3, 4); using var destOwner = new TestMemoryManager <Rgba32>(data); using var dest = new Buffer2D <Rgba32>(MemoryGroup <Rgba32> .Wrap(destOwner.Memory), 21, 1); using Buffer2D <Rgba32> source = this.MemoryAllocator.Allocate2D <Rgba32>(21, 1); source.FastMemoryGroup[0].Span[10] = color; // Act: bool swap = Buffer2D <Rgba32> .SwapOrCopyContent(dest, source); // Assert: Assert.False(swap); Assert.Equal(color, dest.MemoryGroup[0].Span[10]); Assert.NotEqual(source.FastMemoryGroup[0], dest.FastMemoryGroup[0]); }
public void WhenBothAreMemoryOwners_ShouldSwap() { this.MemoryAllocator.BufferCapacityInBytes = sizeof(int) * 50; using Buffer2D <int> a = this.MemoryAllocator.Allocate2D <int>(48, 2); using Buffer2D <int> b = this.MemoryAllocator.Allocate2D <int>(50, 2); Memory <int> a0 = a.FastMemoryGroup[0]; Memory <int> a1 = a.FastMemoryGroup[1]; Memory <int> b0 = b.FastMemoryGroup[0]; Memory <int> b1 = b.FastMemoryGroup[1]; bool swap = Buffer2D <int> .SwapOrCopyContent(a, b); Assert.True(swap); Assert.Equal(b0, a.FastMemoryGroup[0]); Assert.Equal(b1, a.FastMemoryGroup[1]); Assert.Equal(a0, b.FastMemoryGroup[0]); Assert.Equal(a1, b.FastMemoryGroup[1]); Assert.NotEqual(a.FastMemoryGroup[0], b.FastMemoryGroup[0]); }
/// <inheritdoc/> protected override void OnFrameApply(ImageFrame <TPixel> source) { int brushSize = this.definition.BrushSize; if (brushSize <= 0 || brushSize > source.Height || brushSize > source.Width) { throw new ArgumentOutOfRangeException(nameof(brushSize)); } using Buffer2D <TPixel> targetPixels = this.Configuration.MemoryAllocator.Allocate2D <TPixel>(source.Size()); source.CopyTo(targetPixels); var operation = new RowIntervalOperation(this.SourceRectangle, targetPixels, source, this.Configuration, brushSize >> 1, this.definition.Levels); ParallelRowIterator.IterateRowIntervals( this.Configuration, this.SourceRectangle, in operation); Buffer2D <TPixel> .SwapOrCopyContent(source.PixelBuffer, targetPixels); }
public void WhenBothAreMemoryOwners_ShouldReplaceViews() { using Buffer2D <int> a = this.MemoryAllocator.Allocate2D <int>(100, 1); using Buffer2D <int> b = this.MemoryAllocator.Allocate2D <int>(100, 2); a.FastMemoryGroup[0].Span[42] = 1; b.FastMemoryGroup[0].Span[33] = 2; MemoryGroupView <int> aView0 = (MemoryGroupView <int>)a.MemoryGroup; MemoryGroupView <int> bView0 = (MemoryGroupView <int>)b.MemoryGroup; Buffer2D <int> .SwapOrCopyContent(a, b); Assert.False(aView0.IsValid); Assert.False(bView0.IsValid); Assert.ThrowsAny <InvalidOperationException>(() => _ = aView0[0].Span); Assert.ThrowsAny <InvalidOperationException>(() => _ = bView0[0].Span); Assert.True(a.MemoryGroup.IsValid); Assert.True(b.MemoryGroup.IsValid); Assert.Equal(2, a.MemoryGroup[0].Span[33]); Assert.Equal(1, b.MemoryGroup[0].Span[42]); }
public void SwapOrCopyContent_WhenBothAllocated() { using (Buffer2D <int> a = this.MemoryAllocator.Allocate2D <int>(10, 5, AllocationOptions.Clean)) using (Buffer2D <int> b = this.MemoryAllocator.Allocate2D <int>(3, 7, AllocationOptions.Clean)) { a[1, 3] = 666; b[1, 3] = 444; Memory <int> aa = a.FastMemoryGroup.Single(); Memory <int> bb = b.FastMemoryGroup.Single(); Buffer2D <int> .SwapOrCopyContent(a, b); Assert.Equal(bb, a.FastMemoryGroup.Single()); Assert.Equal(aa, b.FastMemoryGroup.Single()); Assert.Equal(new Size(3, 7), a.Size()); Assert.Equal(new Size(10, 5), b.Size()); Assert.Equal(666, b[1, 3]); Assert.Equal(444, a[1, 3]); } }
public void SwapOrCopyContent_WhenDestinationIsOwned_ShouldNotSwapInDisposedSourceBuffer() { using var destData = MemoryGroup <int> .Wrap(new int[100]); using var dest = new Buffer2D <int>(destData, 10, 10); using (Buffer2D <int> source = this.MemoryAllocator.Allocate2D <int>(10, 10, AllocationOptions.Clean)) { source[0, 0] = 1; dest[0, 0] = 2; Buffer2D <int> .SwapOrCopyContent(dest, source); } int actual1 = dest.DangerousGetRowSpan(0)[0]; int actual2 = dest.DangerousGetRowSpan(0)[0]; int actual3 = dest.GetSafeRowMemory(0).Span[0]; int actual5 = dest[0, 0]; Assert.Equal(1, actual1); Assert.Equal(1, actual2); Assert.Equal(1, actual3); Assert.Equal(1, actual5); }
/// <inheritdoc/> protected override void OnFrameApply(ImageFrame <TPixel> source, Rectangle sourceRectangle, Configuration configuration) { if (this.BrushSize <= 0 || this.BrushSize > source.Height || this.BrushSize > source.Width) { throw new ArgumentOutOfRangeException(nameof(this.BrushSize)); } int startY = sourceRectangle.Y; int endY = sourceRectangle.Bottom; int startX = sourceRectangle.X; int endX = sourceRectangle.Right; int maxY = endY - 1; int maxX = endX - 1; int radius = this.BrushSize >> 1; int levels = this.Levels; using (Buffer2D <TPixel> targetPixels = configuration.MemoryAllocator.Allocate2D <TPixel>(source.Size())) { source.CopyTo(targetPixels); Parallel.For( startY, maxY, configuration.ParallelOptions, y => { Span <TPixel> sourceRow = source.GetPixelRowSpan(y); Span <TPixel> targetRow = targetPixels.GetRowSpan(y); for (int x = startX; x < endX; x++) { int maxIntensity = 0; int maxIndex = 0; int[] intensityBin = new int[levels]; float[] redBin = new float[levels]; float[] blueBin = new float[levels]; float[] greenBin = new float[levels]; for (int fy = 0; fy <= radius; fy++) { int fyr = fy - radius; int offsetY = y + fyr; offsetY = offsetY.Clamp(0, maxY); Span <TPixel> sourceOffsetRow = source.GetPixelRowSpan(offsetY); for (int fx = 0; fx <= radius; fx++) { int fxr = fx - radius; int offsetX = x + fxr; offsetX = offsetX.Clamp(0, maxX); var vector = sourceOffsetRow[offsetX].ToVector4(); float sourceRed = vector.X; float sourceBlue = vector.Z; float sourceGreen = vector.Y; int currentIntensity = (int)MathF.Round((sourceBlue + sourceGreen + sourceRed) / 3F * (levels - 1)); intensityBin[currentIntensity]++; blueBin[currentIntensity] += sourceBlue; greenBin[currentIntensity] += sourceGreen; redBin[currentIntensity] += sourceRed; if (intensityBin[currentIntensity] > maxIntensity) { maxIntensity = intensityBin[currentIntensity]; maxIndex = currentIntensity; } } float red = MathF.Abs(redBin[maxIndex] / maxIntensity); float green = MathF.Abs(greenBin[maxIndex] / maxIntensity); float blue = MathF.Abs(blueBin[maxIndex] / maxIntensity); ref TPixel pixel = ref targetRow[x]; pixel.PackFromVector4(new Vector4(red, green, blue, sourceRow[x].ToVector4().W)); } } }); Buffer2D <TPixel> .SwapOrCopyContent(source.PixelBuffer, targetPixels); } }
/// <inheritdoc/> protected override void OnFrameApply(ImageFrame <TPixel> source, Rectangle sourceRectangle, Configuration configuration) { int kernelLength = this.KernelXY.Rows; int radius = kernelLength >> 1; int startY = sourceRectangle.Y; int endY = sourceRectangle.Bottom; int startX = sourceRectangle.X; int endX = sourceRectangle.Right; int maxY = endY - 1; int maxX = endX - 1; using (Buffer2D <TPixel> targetPixels = configuration.MemoryAllocator.Allocate2D <TPixel>(source.Size())) { source.CopyTo(targetPixels); Parallel.For( startY, endY, configuration.ParallelOptions, y => { Span <TPixel> sourceRow = source.GetPixelRowSpan(y); Span <TPixel> targetRow = targetPixels.GetRowSpan(y); for (int x = startX; x < endX; x++) { float red = 0; float green = 0; float blue = 0; // Apply each matrix multiplier to the color components for each pixel. for (int fy = 0; fy < kernelLength; fy++) { int fyr = fy - radius; int offsetY = y + fyr; offsetY = offsetY.Clamp(0, maxY); Span <TPixel> sourceOffsetRow = source.GetPixelRowSpan(offsetY); for (int fx = 0; fx < kernelLength; fx++) { int fxr = fx - radius; int offsetX = x + fxr; offsetX = offsetX.Clamp(0, maxX); Vector4 currentColor = sourceOffsetRow[offsetX].ToVector4().Premultiply(); currentColor *= this.KernelXY[fy, fx]; red += currentColor.X; green += currentColor.Y; blue += currentColor.Z; } } ref TPixel pixel = ref targetRow[x]; pixel.PackFromVector4(new Vector4(red, green, blue, sourceRow[x].ToVector4().W).UnPremultiply()); } }); Buffer2D <TPixel> .SwapOrCopyContent(source.PixelBuffer, targetPixels); } }
/// <inheritdoc/> protected override void OnFrameApply( ImageFrame <TPixel> source, Rectangle sourceRectangle, Configuration configuration) { int kernelYHeight = this.KernelY.Rows; int kernelYWidth = this.KernelY.Columns; int kernelXHeight = this.KernelX.Rows; int kernelXWidth = this.KernelX.Columns; int radiusY = kernelYHeight >> 1; int radiusX = kernelXWidth >> 1; int startY = sourceRectangle.Y; int endY = sourceRectangle.Bottom; int startX = sourceRectangle.X; int endX = sourceRectangle.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); ParallelHelper.IterateRows( workingRectangle, configuration, rows => { for (int y = rows.Min; y < rows.Max; y++) { Span <TPixel> sourceRow = source.GetPixelRowSpan(y); Span <TPixel> targetRow = targetPixels.GetRowSpan(y); for (int x = startX; x < endX; x++) { float rX = 0; float gX = 0; float bX = 0; float rY = 0; float gY = 0; float bY = 0; // Apply each matrix multiplier to the color components for each pixel. for (int fy = 0; fy < kernelYHeight; fy++) { int fyr = fy - radiusY; int offsetY = y + fyr; offsetY = offsetY.Clamp(0, maxY); Span <TPixel> sourceOffsetRow = source.GetPixelRowSpan(offsetY); for (int fx = 0; fx < kernelXWidth; fx++) { int fxr = fx - radiusX; int offsetX = x + fxr; offsetX = offsetX.Clamp(0, maxX); Vector4 currentColor = sourceOffsetRow[offsetX].ToVector4().Premultiply(); if (fy < kernelXHeight) { Vector4 kx = this.KernelX[fy, fx] * currentColor; rX += kx.X; gX += kx.Y; bX += kx.Z; } if (fx < kernelYWidth) { Vector4 ky = this.KernelY[fy, fx] * currentColor; rY += ky.X; gY += ky.Y; bY += ky.Z; } } } float red = MathF.Sqrt((rX * rX) + (rY * rY)); float green = MathF.Sqrt((gX * gX) + (gY * gY)); float blue = MathF.Sqrt((bX * bX) + (bY * bY)); ref TPixel pixel = ref targetRow[x]; pixel.PackFromVector4( new Vector4(red, green, blue, sourceRow[x].ToVector4().W).UnPremultiply()); } } }); Buffer2D <TPixel> .SwapOrCopyContent(source.PixelBuffer, targetPixels); } }
/// <inheritdoc/> protected override void OnFrameApply(ImageFrame <TPixel> source) { MemoryAllocator memoryAllocator = this.Configuration.MemoryAllocator; var parallelOptions = new ParallelOptions { MaxDegreeOfParallelism = this.Configuration.MaxDegreeOfParallelism }; int tileWidth = source.Width / this.Tiles; int tileHeight = tileWidth; int pixelInTile = tileWidth * tileHeight; int halfTileHeight = tileHeight / 2; int halfTileWidth = halfTileHeight; var slidingWindowInfos = new SlidingWindowInfos(tileWidth, tileHeight, halfTileWidth, halfTileHeight, pixelInTile); using (Buffer2D <TPixel> targetPixels = this.Configuration.MemoryAllocator.Allocate2D <TPixel>(source.Width, source.Height)) { // Process the inner tiles, which do not require to check the borders. Parallel.For( halfTileWidth, source.Width - halfTileWidth, parallelOptions, this.ProcessSlidingWindow( source, memoryAllocator, targetPixels, slidingWindowInfos, yStart: halfTileHeight, yEnd: source.Height - halfTileHeight, useFastPath: true, this.Configuration)); // Process the left border of the image. Parallel.For( 0, halfTileWidth, parallelOptions, this.ProcessSlidingWindow( source, memoryAllocator, targetPixels, slidingWindowInfos, yStart: 0, yEnd: source.Height, useFastPath: false, this.Configuration)); // Process the right border of the image. Parallel.For( source.Width - halfTileWidth, source.Width, parallelOptions, this.ProcessSlidingWindow( source, memoryAllocator, targetPixels, slidingWindowInfos, yStart: 0, yEnd: source.Height, useFastPath: false, this.Configuration)); // Process the top border of the image. Parallel.For( halfTileWidth, source.Width - halfTileWidth, parallelOptions, this.ProcessSlidingWindow( source, memoryAllocator, targetPixels, slidingWindowInfos, yStart: 0, yEnd: halfTileHeight, useFastPath: false, this.Configuration)); // Process the bottom border of the image. Parallel.For( halfTileWidth, source.Width - halfTileWidth, parallelOptions, this.ProcessSlidingWindow( source, memoryAllocator, targetPixels, slidingWindowInfos, yStart: source.Height - halfTileHeight, yEnd: source.Height, useFastPath: false, this.Configuration)); Buffer2D <TPixel> .SwapOrCopyContent(source.PixelBuffer, targetPixels); } }
/// <inheritdoc/> protected override void OnFrameApply(ImageFrame <TPixel> source) { MemoryAllocator memoryAllocator = this.Configuration.MemoryAllocator; var parallelOptions = new ParallelOptions { MaxDegreeOfParallelism = this.Configuration.MaxDegreeOfParallelism }; int tileWidth = source.Width / this.Tiles; int tileHeight = tileWidth; int pixelInTile = tileWidth * tileHeight; int halfTileHeight = tileHeight / 2; int halfTileWidth = halfTileHeight; var slidingWindowInfos = new SlidingWindowInfos(tileWidth, tileHeight, halfTileWidth, halfTileHeight, pixelInTile); // TODO: If the process was able to be switched to operate in parallel rows instead of columns // then we could take advantage of batching and allocate per-row buffers only once per batch. using Buffer2D <TPixel> targetPixels = this.Configuration.MemoryAllocator.Allocate2D <TPixel>(source.Width, source.Height); // Process the inner tiles, which do not require to check the borders. var innerOperation = new SlidingWindowOperation( this.Configuration, this, source, memoryAllocator, targetPixels, slidingWindowInfos, yStart: halfTileHeight, yEnd: source.Height - halfTileHeight, useFastPath: true); Parallel.For( halfTileWidth, source.Width - halfTileWidth, parallelOptions, innerOperation.Invoke); // Process the left border of the image. var leftBorderOperation = new SlidingWindowOperation( this.Configuration, this, source, memoryAllocator, targetPixels, slidingWindowInfos, yStart: 0, yEnd: source.Height, useFastPath: false); Parallel.For( 0, halfTileWidth, parallelOptions, leftBorderOperation.Invoke); // Process the right border of the image. var rightBorderOperation = new SlidingWindowOperation( this.Configuration, this, source, memoryAllocator, targetPixels, slidingWindowInfos, yStart: 0, yEnd: source.Height, useFastPath: false); Parallel.For( source.Width - halfTileWidth, source.Width, parallelOptions, rightBorderOperation.Invoke); // Process the top border of the image. var topBorderOperation = new SlidingWindowOperation( this.Configuration, this, source, memoryAllocator, targetPixels, slidingWindowInfos, yStart: 0, yEnd: halfTileHeight, useFastPath: false); Parallel.For( halfTileWidth, source.Width - halfTileWidth, parallelOptions, topBorderOperation.Invoke); // Process the bottom border of the image. var bottomBorderOperation = new SlidingWindowOperation( this.Configuration, this, source, memoryAllocator, targetPixels, slidingWindowInfos, yStart: source.Height - halfTileHeight, yEnd: source.Height, useFastPath: false); Parallel.For( halfTileWidth, source.Width - halfTileWidth, parallelOptions, bottomBorderOperation.Invoke); Buffer2D <TPixel> .SwapOrCopyContent(source.PixelBuffer, targetPixels); }