diff --git a/src/ImageSharp/Primitives/DenseMatrix{T}.cs b/src/ImageSharp/Primitives/DenseMatrix{T}.cs index 7eee4a1b08..63ca6f7564 100644 --- a/src/ImageSharp/Primitives/DenseMatrix{T}.cs +++ b/src/ImageSharp/Primitives/DenseMatrix{T}.cs @@ -71,6 +71,27 @@ public DenseMatrix(T[,] data) } } + /// + /// Initializes a new instance of the struct. + /// + /// The number of columns. + /// The number of rows. + /// The array to provide access to. + public DenseMatrix(int columns, int rows, Span data) + { + Guard.MustBeGreaterThan(rows, 0, nameof(this.Rows)); + Guard.MustBeGreaterThan(columns, 0, nameof(this.Columns)); + Guard.IsTrue(rows * columns == data.Length, nameof(data), "Length should be equal to ros * columns"); + + this.Rows = rows; + this.Columns = columns; + this.Size = new Size(columns, rows); + this.Count = this.Columns * this.Rows; + this.Data = new T[this.Columns * this.Rows]; + + data.CopyTo(this.Data); + } + /// /// Gets the 1D representation of the dense matrix. /// diff --git a/src/ImageSharp/Processing/Extensions/Convolution/MedianBlurExtensions.cs b/src/ImageSharp/Processing/Extensions/Convolution/MedianBlurExtensions.cs new file mode 100644 index 0000000000..a80a119004 --- /dev/null +++ b/src/ImageSharp/Processing/Extensions/Convolution/MedianBlurExtensions.cs @@ -0,0 +1,41 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.ImageSharp.Processing.Processors.Convolution; + +namespace SixLabors.ImageSharp.Processing +{ + /// + /// Defines extensions that allow the applying of the median blur on an + /// using Mutate/Clone. + /// + public static class MedianBlurExtensions + { + /// + /// Applies a median blur on the image. + /// + /// The image this method extends. + /// The radius of the area to find the median for. + /// + /// Whether the filter is applied to alpha as well as the color channels. + /// + /// The to allow chaining of operations. + public static IImageProcessingContext MedianBlur(this IImageProcessingContext source, int radius, bool preserveAlpha) + => source.ApplyProcessor(new MedianBlurProcessor(radius, preserveAlpha)); + + /// + /// Applies a median blur on the image. + /// + /// The image this method extends. + /// The radius of the area to find the median for. + /// + /// Whether the filter is applied to alpha as well as the color channels. + /// + /// + /// The structure that specifies the portion of the image object to alter. + /// + /// The to allow chaining of operations. + public static IImageProcessingContext MedianBlur(this IImageProcessingContext source, int radius, bool preserveAlpha, Rectangle rectangle) + => source.ApplyProcessor(new MedianBlurProcessor(radius, preserveAlpha), rectangle); + } +} diff --git a/src/ImageSharp/Processing/Processors/Convolution/Kernel.cs b/src/ImageSharp/Processing/Processors/Convolution/Kernel.cs new file mode 100644 index 0000000000..032a8ce445 --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Convolution/Kernel.cs @@ -0,0 +1,99 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System; +using System.Diagnostics; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace SixLabors.ImageSharp.Processing.Processors.Convolution +{ + /// + /// A stack only, readonly, kernel matrix that can be indexed without + /// bounds checks when compiled in release mode. + /// + /// The type of each element in the kernel. + internal readonly ref struct Kernel + where T : struct, IEquatable + { + private readonly Span values; + + public Kernel(DenseMatrix matrix) + { + this.Columns = matrix.Columns; + this.Rows = matrix.Rows; + this.values = matrix.Span; + } + + public int Columns + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get; + } + + public int Rows + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get; + } + + public ReadOnlySpan Span + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => this.values; + } + + public T this[int row, int column] + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get + { + this.CheckCoordinates(row, column); + ref T vBase = ref MemoryMarshal.GetReference(this.values); + return Unsafe.Add(ref vBase, (row * this.Columns) + column); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + set + { + this.CheckCoordinates(row, column); + ref T vBase = ref MemoryMarshal.GetReference(this.values); + Unsafe.Add(ref vBase, (row * this.Columns) + column) = value; + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void SetValue(int index, T value) + { + this.CheckIndex(index); + ref T vBase = ref MemoryMarshal.GetReference(this.values); + Unsafe.Add(ref vBase, index) = value; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Clear() => this.values.Clear(); + + [Conditional("DEBUG")] + private void CheckCoordinates(int row, int column) + { + if (row < 0 || row >= this.Rows) + { + throw new ArgumentOutOfRangeException(nameof(row), row, $"{row} is outside the matrix bounds."); + } + + if (column < 0 || column >= this.Columns) + { + throw new ArgumentOutOfRangeException(nameof(column), column, $"{column} is outside the matrix bounds."); + } + } + + [Conditional("DEBUG")] + private void CheckIndex(int index) + { + if (index < 0 || index >= this.values.Length) + { + throw new ArgumentOutOfRangeException(nameof(index), index, $"{index} is outside the matrix bounds."); + } + } + } +} diff --git a/src/ImageSharp/Processing/Processors/Convolution/MedianBlurProcessor.cs b/src/ImageSharp/Processing/Processors/Convolution/MedianBlurProcessor.cs new file mode 100644 index 0000000000..a3819f5e95 --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Convolution/MedianBlurProcessor.cs @@ -0,0 +1,53 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.ImageSharp.PixelFormats; + +namespace SixLabors.ImageSharp.Processing.Processors.Convolution +{ + /// + /// Applies an median filter. + /// + public sealed class MedianBlurProcessor : IImageProcessor + { + /// + /// Initializes a new instance of the class. + /// + /// + /// The 'radius' value representing the size of the area to filter over. + /// + /// + /// Whether the filter is applied to alpha as well as the color channels. + /// + public MedianBlurProcessor(int radius, bool preserveAlpha) + { + this.Radius = radius; + this.PreserveAlpha = preserveAlpha; + } + + /// + /// Gets the size of the area to find the median of. + /// + public int Radius { get; } + + /// + /// Gets a value indicating whether the filter is applied to alpha as well as the color channels. + /// + public bool PreserveAlpha { get; } + + /// + /// Gets the to use when mapping the pixels outside of the border, in X direction. + /// + public BorderWrappingMode BorderWrapModeX { get; } + + /// + /// Gets the to use when mapping the pixels outside of the border, in Y direction. + /// + public BorderWrappingMode BorderWrapModeY { get; } + + /// + public IImageProcessor CreatePixelSpecificProcessor(Configuration configuration, Image source, Rectangle sourceRectangle) + where TPixel : unmanaged, IPixel + => new MedianBlurProcessor(configuration, this, source, sourceRectangle); + } +} diff --git a/src/ImageSharp/Processing/Processors/Convolution/MedianBlurProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Convolution/MedianBlurProcessor{TPixel}.cs new file mode 100644 index 0000000000..8e2540faf8 --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Convolution/MedianBlurProcessor{TPixel}.cs @@ -0,0 +1,59 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Numerics; +using SixLabors.ImageSharp.Advanced; +using SixLabors.ImageSharp.Memory; +using SixLabors.ImageSharp.PixelFormats; + +namespace SixLabors.ImageSharp.Processing.Processors.Convolution +{ + /// + /// Applies an median filter. + /// + /// The type of pixel format. + internal sealed class MedianBlurProcessor : ImageProcessor + where TPixel : unmanaged, IPixel + { + private readonly MedianBlurProcessor definition; + + public MedianBlurProcessor(Configuration configuration, MedianBlurProcessor definition, Image source, Rectangle sourceRectangle) + : base(configuration, source, sourceRectangle) => this.definition = definition; + + protected override void OnFrameApply(ImageFrame source) + { + int kernelSize = (2 * this.definition.Radius) + 1; + + MemoryAllocator allocator = this.Configuration.MemoryAllocator; + using Buffer2D targetPixels = allocator.Allocate2D(source.Width, source.Height); + + source.CopyTo(targetPixels); + + Rectangle interest = Rectangle.Intersect(this.SourceRectangle, source.Bounds()); + + // We use a rectangle with width set wider, to allocate a buffer big enough + // for kernel source, channel buffers, source rows and target bulk pixel conversion. + int operationWidth = (2 * kernelSize * kernelSize) + interest.Width + (kernelSize * interest.Width); + Rectangle operationBounds = new(interest.X, interest.Y, operationWidth, interest.Height); + + using KernelSamplingMap map = new(this.Configuration.MemoryAllocator); + map.BuildSamplingOffsetMap(kernelSize, kernelSize, interest, this.definition.BorderWrapModeX, this.definition.BorderWrapModeY); + + MedianRowOperation operation = new( + interest, + targetPixels, + source.PixelBuffer, + map, + kernelSize, + this.Configuration, + this.definition.PreserveAlpha); + + ParallelRowIterator.IterateRows, Vector4>( + this.Configuration, + operationBounds, + in operation); + + Buffer2D.SwapOrCopyContent(source.PixelBuffer, targetPixels); + } + } +} diff --git a/src/ImageSharp/Processing/Processors/Convolution/MedianConvolutionState.cs b/src/ImageSharp/Processing/Processors/Convolution/MedianConvolutionState.cs new file mode 100644 index 0000000000..f4aa68e22f --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Convolution/MedianConvolutionState.cs @@ -0,0 +1,46 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System; +using System.Numerics; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace SixLabors.ImageSharp.Processing.Processors.Convolution +{ + /// + /// A stack only struct used for reducing reference indirection during convolution operations. + /// + internal readonly ref struct MedianConvolutionState + { + private readonly Span rowOffsetMap; + private readonly Span columnOffsetMap; + private readonly int kernelHeight; + private readonly int kernelWidth; + + public MedianConvolutionState( + in DenseMatrix kernel, + KernelSamplingMap map) + { + this.Kernel = new Kernel(kernel); + this.kernelHeight = kernel.Rows; + this.kernelWidth = kernel.Columns; + this.rowOffsetMap = map.GetRowOffsetSpan(); + this.columnOffsetMap = map.GetColumnOffsetSpan(); + } + + public readonly Kernel Kernel + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly ref int GetSampleRow(int row) + => ref Unsafe.Add(ref MemoryMarshal.GetReference(this.rowOffsetMap), row * this.kernelHeight); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly ref int GetSampleColumn(int column) + => ref Unsafe.Add(ref MemoryMarshal.GetReference(this.columnOffsetMap), column * this.kernelWidth); + } +} diff --git a/src/ImageSharp/Processing/Processors/Convolution/MedianRowOperation{TPixel}.cs b/src/ImageSharp/Processing/Processors/Convolution/MedianRowOperation{TPixel}.cs new file mode 100644 index 0000000000..90dce4dad9 --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Convolution/MedianRowOperation{TPixel}.cs @@ -0,0 +1,178 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System; +using System.Numerics; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using SixLabors.ImageSharp.Advanced; +using SixLabors.ImageSharp.Memory; +using SixLabors.ImageSharp.PixelFormats; + +namespace SixLabors.ImageSharp.Processing.Processors.Convolution +{ + /// + /// Applies an median filter. + /// + /// The type of pixel format. + internal readonly struct MedianRowOperation : IRowOperation + where TPixel : unmanaged, IPixel + { + private readonly int yChannelStart; + private readonly int zChannelStart; + private readonly int wChannelStart; + private readonly Configuration configuration; + private readonly Rectangle bounds; + private readonly Buffer2D targetPixels; + private readonly Buffer2D sourcePixels; + private readonly KernelSamplingMap map; + private readonly int kernelSize; + private readonly bool preserveAlpha; + + public MedianRowOperation(Rectangle bounds, Buffer2D targetPixels, Buffer2D sourcePixels, KernelSamplingMap map, int kernelSize, Configuration configuration, bool preserveAlpha) + { + this.bounds = bounds; + this.configuration = configuration; + this.targetPixels = targetPixels; + this.sourcePixels = sourcePixels; + this.map = map; + this.kernelSize = kernelSize; + this.preserveAlpha = preserveAlpha; + int kernelCount = this.kernelSize * this.kernelSize; + this.yChannelStart = kernelCount; + this.zChannelStart = this.yChannelStart + kernelCount; + this.wChannelStart = this.zChannelStart + kernelCount; + } + + public void Invoke(int y, Span span) + { + // Span has kernelSize^2 followed by bound width. + int boundsX = this.bounds.X; + int boundsWidth = this.bounds.Width; + int kernelCount = this.kernelSize * this.kernelSize; + Span kernelBuffer = span[..kernelCount]; + Span channelVectorBuffer = span.Slice(kernelCount, kernelCount); + Span sourceVectorBuffer = span.Slice(kernelCount << 1, this.kernelSize * boundsWidth); + Span targetBuffer = span.Slice((kernelCount << 1) + sourceVectorBuffer.Length, boundsWidth); + + // Stack 4 channels of floats in the space of Vector4's. + Span channelBuffer = MemoryMarshal.Cast(channelVectorBuffer); + Span xChannel = channelBuffer[..kernelCount]; + Span yChannel = channelBuffer.Slice(this.yChannelStart, kernelCount); + Span zChannel = channelBuffer.Slice(this.zChannelStart, kernelCount); + + DenseMatrix kernel = new(this.kernelSize, this.kernelSize, kernelBuffer); + + int row = y - this.bounds.Y; + MedianConvolutionState state = new(in kernel, this.map); + ref int sampleRowBase = ref state.GetSampleRow(row); + ref Vector4 targetBase = ref MemoryMarshal.GetReference(targetBuffer); + + // First convert the required source rows to Vector4. + for (int i = 0; i < this.kernelSize; i++) + { + int currentYIndex = Unsafe.Add(ref sampleRowBase, i); + Span sourceRow = this.sourcePixels.DangerousGetRowSpan(currentYIndex).Slice(boundsX, boundsWidth); + Span sourceVectorRow = sourceVectorBuffer.Slice(i * boundsWidth, boundsWidth); + PixelOperations.Instance.ToVector4(this.configuration, sourceRow, sourceVectorRow); + } + + if (this.preserveAlpha) + { + for (int x = 0; x < boundsWidth; x++) + { + int index = 0; + ref int sampleColumnBase = ref state.GetSampleColumn(x); + ref Vector4 target = ref Unsafe.Add(ref targetBase, x); + for (int kY = 0; kY < state.Kernel.Rows; kY++) + { + Span sourceRow = sourceVectorBuffer[(kY * boundsWidth)..]; + ref Vector4 sourceRowBase = ref MemoryMarshal.GetReference(sourceRow); + for (int kX = 0; kX < state.Kernel.Columns; kX++) + { + int currentXIndex = Unsafe.Add(ref sampleColumnBase, kX) - boundsX; + Vector4 pixel = Unsafe.Add(ref sourceRowBase, currentXIndex); + state.Kernel.SetValue(index, pixel); + index++; + } + } + + target = FindMedian3(state.Kernel.Span, xChannel, yChannel, zChannel); + } + } + else + { + Span wChannel = channelBuffer.Slice(this.wChannelStart, kernelCount); + for (int x = 0; x < boundsWidth; x++) + { + int index = 0; + ref int sampleColumnBase = ref state.GetSampleColumn(x); + ref Vector4 target = ref Unsafe.Add(ref targetBase, x); + for (int kY = 0; kY < state.Kernel.Rows; kY++) + { + Span sourceRow = sourceVectorBuffer[(kY * boundsWidth)..]; + ref Vector4 sourceRowBase = ref MemoryMarshal.GetReference(sourceRow); + for (int kX = 0; kX < state.Kernel.Columns; kX++) + { + int currentXIndex = Unsafe.Add(ref sampleColumnBase, kX) - boundsX; + Vector4 pixel = Unsafe.Add(ref sourceRowBase, currentXIndex); + state.Kernel.SetValue(index, pixel); + index++; + } + } + + target = FindMedian4(state.Kernel.Span, xChannel, yChannel, zChannel, wChannel); + } + } + + Span targetRowSpan = this.targetPixels.DangerousGetRowSpan(y).Slice(boundsX, boundsWidth); + PixelOperations.Instance.FromVector4Destructive(this.configuration, targetBuffer, targetRowSpan); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Vector4 FindMedian3(ReadOnlySpan kernelSpan, Span xChannel, Span yChannel, Span zChannel) + { + int halfLength = (kernelSpan.Length + 1) >> 1; + + // Split color channels + for (int i = 0; i < xChannel.Length; i++) + { + xChannel[i] = kernelSpan[i].X; + yChannel[i] = kernelSpan[i].Y; + zChannel[i] = kernelSpan[i].Z; + } + + // Sort each channel serarately. + xChannel.Sort(); + yChannel.Sort(); + zChannel.Sort(); + + // Taking the W value from the source pixels, where the middle index in the kernelSpan is by definition the resulting pixel. + // This will preserve the alpha value. + return new Vector4(xChannel[halfLength], yChannel[halfLength], zChannel[halfLength], kernelSpan[halfLength].W); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Vector4 FindMedian4(ReadOnlySpan kernelSpan, Span xChannel, Span yChannel, Span zChannel, Span wChannel) + { + int halfLength = (kernelSpan.Length + 1) >> 1; + + // Split color channels + for (int i = 0; i < xChannel.Length; i++) + { + xChannel[i] = kernelSpan[i].X; + yChannel[i] = kernelSpan[i].Y; + zChannel[i] = kernelSpan[i].Z; + wChannel[i] = kernelSpan[i].W; + } + + // Sort each channel serarately. + xChannel.Sort(); + yChannel.Sort(); + zChannel.Sort(); + wChannel.Sort(); + + return new Vector4(xChannel[halfLength], yChannel[halfLength], zChannel[halfLength], wChannel[halfLength]); + } + } +} diff --git a/tests/ImageSharp.Tests/Processing/Convolution/MedianBlurTest.cs b/tests/ImageSharp.Tests/Processing/Convolution/MedianBlurTest.cs new file mode 100644 index 0000000000..aac1a1eca8 --- /dev/null +++ b/tests/ImageSharp.Tests/Processing/Convolution/MedianBlurTest.cs @@ -0,0 +1,34 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.ImageSharp.Processing; +using SixLabors.ImageSharp.Processing.Processors.Convolution; + +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Processing.Convolution +{ + [Trait("Category", "Processors")] + public class MedianBlurTest : BaseImageOperationsExtensionTest + { + [Fact] + public void Median_radius_MedianProcessorDefaultsSet() + { + this.operations.MedianBlur(3, true); + var processor = this.Verify(); + + Assert.Equal(3, processor.Radius); + Assert.True(processor.PreserveAlpha); + } + + [Fact] + public void Median_radius_rect_MedianProcessorDefaultsSet() + { + this.operations.MedianBlur(5, false, this.rect); + var processor = this.Verify(this.rect); + + Assert.Equal(5, processor.Radius); + Assert.False(processor.PreserveAlpha); + } + } +} diff --git a/tests/ImageSharp.Tests/Processing/Processors/Convolution/MedianBlurTest.cs b/tests/ImageSharp.Tests/Processing/Processors/Convolution/MedianBlurTest.cs new file mode 100644 index 0000000000..f63fbbcf2b --- /dev/null +++ b/tests/ImageSharp.Tests/Processing/Processors/Convolution/MedianBlurTest.cs @@ -0,0 +1,18 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.ImageSharp.Processing; +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Processing.Processors.Convolution +{ + [Trait("Category", "Processors")] + [GroupOutput("Convolution")] + public class MedianBlurTest : Basic1ParameterConvolutionTests + { + protected override void Apply(IImageProcessingContext ctx, int value) => ctx.MedianBlur(value, true); + + protected override void Apply(IImageProcessingContext ctx, int value, Rectangle bounds) => + ctx.MedianBlur(value, true, bounds); + } +} diff --git a/tests/Images/External/ReferenceOutput/Convolution/MedianBlurTest/InBox_Rgba32_CalliphoraPartial_3.png b/tests/Images/External/ReferenceOutput/Convolution/MedianBlurTest/InBox_Rgba32_CalliphoraPartial_3.png new file mode 100644 index 0000000000..a81fa6d088 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/Convolution/MedianBlurTest/InBox_Rgba32_CalliphoraPartial_3.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ea5c7e0191cd4cf12067b462f13a7466fac94e94c12fa9d9b291f3d9677a14b4 +size 331353 diff --git a/tests/Images/External/ReferenceOutput/Convolution/MedianBlurTest/InBox_Rgba32_CalliphoraPartial_5.png b/tests/Images/External/ReferenceOutput/Convolution/MedianBlurTest/InBox_Rgba32_CalliphoraPartial_5.png new file mode 100644 index 0000000000..cdb13d561c --- /dev/null +++ b/tests/Images/External/ReferenceOutput/Convolution/MedianBlurTest/InBox_Rgba32_CalliphoraPartial_5.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b6c5492f42eb5ffaeb7878b47b7654bb2a08bf91f13fe4ca8186892a849ba516 +size 322752 diff --git a/tests/Images/External/ReferenceOutput/Convolution/MedianBlurTest/InBox_Rgba32_Car_3.png b/tests/Images/External/ReferenceOutput/Convolution/MedianBlurTest/InBox_Rgba32_Car_3.png new file mode 100644 index 0000000000..647e65ee78 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/Convolution/MedianBlurTest/InBox_Rgba32_Car_3.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a1fe499ba5b6f4f9ffa73d898be45e2ee13fc7c7c65b5f3366569a280546cb49 +size 234179 diff --git a/tests/Images/External/ReferenceOutput/Convolution/MedianBlurTest/InBox_Rgba32_Car_5.png b/tests/Images/External/ReferenceOutput/Convolution/MedianBlurTest/InBox_Rgba32_Car_5.png new file mode 100644 index 0000000000..99230049ee --- /dev/null +++ b/tests/Images/External/ReferenceOutput/Convolution/MedianBlurTest/InBox_Rgba32_Car_5.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:fab69dad4a26739fe7dd2167d4b5ec9581b9d1bd9fef9b3df0cf2a195d03efc7 +size 227933 diff --git a/tests/Images/External/ReferenceOutput/Convolution/MedianBlurTest/InBox_Rgba32_blur_3.png b/tests/Images/External/ReferenceOutput/Convolution/MedianBlurTest/InBox_Rgba32_blur_3.png new file mode 100644 index 0000000000..c3f8c111cd --- /dev/null +++ b/tests/Images/External/ReferenceOutput/Convolution/MedianBlurTest/InBox_Rgba32_blur_3.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6c06cefc194ab21aabaf256d8a65b42d62b3a22c1a141f8d706a4c2958cec22e +size 194915 diff --git a/tests/Images/External/ReferenceOutput/Convolution/MedianBlurTest/InBox_Rgba32_blur_5.png b/tests/Images/External/ReferenceOutput/Convolution/MedianBlurTest/InBox_Rgba32_blur_5.png new file mode 100644 index 0000000000..a01e8b8bf3 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/Convolution/MedianBlurTest/InBox_Rgba32_blur_5.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8a7ec3721962f469ad9c057a464f2c79ee64c583e227c3956d27be7240fa0ab7 +size 194709 diff --git a/tests/Images/External/ReferenceOutput/Convolution/MedianBlurTest/OnFullImage_Rgba32_CalliphoraPartial_3.png b/tests/Images/External/ReferenceOutput/Convolution/MedianBlurTest/OnFullImage_Rgba32_CalliphoraPartial_3.png new file mode 100644 index 0000000000..c95d213173 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/Convolution/MedianBlurTest/OnFullImage_Rgba32_CalliphoraPartial_3.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:14b12f2df171142c9ccb368673c1809a6efa2cf0c4a2bb685ca9012e02b54532 +size 225209 diff --git a/tests/Images/External/ReferenceOutput/Convolution/MedianBlurTest/OnFullImage_Rgba32_CalliphoraPartial_5.png b/tests/Images/External/ReferenceOutput/Convolution/MedianBlurTest/OnFullImage_Rgba32_CalliphoraPartial_5.png new file mode 100644 index 0000000000..8edc0a6471 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/Convolution/MedianBlurTest/OnFullImage_Rgba32_CalliphoraPartial_5.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d5c532fee91fd812fb36f4ae2f05552de4d66f863ee7c1becb33e6e26e6fb6ba +size 189577 diff --git a/tests/Images/External/ReferenceOutput/Convolution/MedianBlurTest/OnFullImage_Rgba32_Car_3.png b/tests/Images/External/ReferenceOutput/Convolution/MedianBlurTest/OnFullImage_Rgba32_Car_3.png new file mode 100644 index 0000000000..999da06eed --- /dev/null +++ b/tests/Images/External/ReferenceOutput/Convolution/MedianBlurTest/OnFullImage_Rgba32_Car_3.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1c9adda439083b795e163d3a54108750ee921010fe03ef9b39bec794a26f8fe4 +size 207101 diff --git a/tests/Images/External/ReferenceOutput/Convolution/MedianBlurTest/OnFullImage_Rgba32_Car_5.png b/tests/Images/External/ReferenceOutput/Convolution/MedianBlurTest/OnFullImage_Rgba32_Car_5.png new file mode 100644 index 0000000000..9de4f0b2c1 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/Convolution/MedianBlurTest/OnFullImage_Rgba32_Car_5.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b0a86d9ec30d609756b402bed229e38bbcd30878c49224d6f6821a240d460608 +size 192432 diff --git a/tests/Images/External/ReferenceOutput/Convolution/MedianBlurTest/OnFullImage_Rgba32_blur_3.png b/tests/Images/External/ReferenceOutput/Convolution/MedianBlurTest/OnFullImage_Rgba32_blur_3.png new file mode 100644 index 0000000000..1d5b97ee25 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/Convolution/MedianBlurTest/OnFullImage_Rgba32_blur_3.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:48cbc90a9fb90c12882e52d2999f1d41da89230c18d9b0a9d06ee2917acee1b8 +size 149396 diff --git a/tests/Images/External/ReferenceOutput/Convolution/MedianBlurTest/OnFullImage_Rgba32_blur_5.png b/tests/Images/External/ReferenceOutput/Convolution/MedianBlurTest/OnFullImage_Rgba32_blur_5.png new file mode 100644 index 0000000000..bb6cfb065b --- /dev/null +++ b/tests/Images/External/ReferenceOutput/Convolution/MedianBlurTest/OnFullImage_Rgba32_blur_5.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6cfddcda78110c29f8ddf0cc8cc8089e77bef8f13c468c8fee5eda18779defb4 +size 143547