Browse Source

Merge pull request #2219 from ynse01/median-filter

Implement Median Blur processor
pull/2226/head
James Jackson-South 3 years ago
committed by GitHub
parent
commit
ea97bac681
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 21
      src/ImageSharp/Primitives/DenseMatrix{T}.cs
  2. 41
      src/ImageSharp/Processing/Extensions/Convolution/MedianBlurExtensions.cs
  3. 99
      src/ImageSharp/Processing/Processors/Convolution/Kernel.cs
  4. 53
      src/ImageSharp/Processing/Processors/Convolution/MedianBlurProcessor.cs
  5. 59
      src/ImageSharp/Processing/Processors/Convolution/MedianBlurProcessor{TPixel}.cs
  6. 46
      src/ImageSharp/Processing/Processors/Convolution/MedianConvolutionState.cs
  7. 178
      src/ImageSharp/Processing/Processors/Convolution/MedianRowOperation{TPixel}.cs
  8. 34
      tests/ImageSharp.Tests/Processing/Convolution/MedianBlurTest.cs
  9. 18
      tests/ImageSharp.Tests/Processing/Processors/Convolution/MedianBlurTest.cs
  10. 3
      tests/Images/External/ReferenceOutput/Convolution/MedianBlurTest/InBox_Rgba32_CalliphoraPartial_3.png
  11. 3
      tests/Images/External/ReferenceOutput/Convolution/MedianBlurTest/InBox_Rgba32_CalliphoraPartial_5.png
  12. 3
      tests/Images/External/ReferenceOutput/Convolution/MedianBlurTest/InBox_Rgba32_Car_3.png
  13. 3
      tests/Images/External/ReferenceOutput/Convolution/MedianBlurTest/InBox_Rgba32_Car_5.png
  14. 3
      tests/Images/External/ReferenceOutput/Convolution/MedianBlurTest/InBox_Rgba32_blur_3.png
  15. 3
      tests/Images/External/ReferenceOutput/Convolution/MedianBlurTest/InBox_Rgba32_blur_5.png
  16. 3
      tests/Images/External/ReferenceOutput/Convolution/MedianBlurTest/OnFullImage_Rgba32_CalliphoraPartial_3.png
  17. 3
      tests/Images/External/ReferenceOutput/Convolution/MedianBlurTest/OnFullImage_Rgba32_CalliphoraPartial_5.png
  18. 3
      tests/Images/External/ReferenceOutput/Convolution/MedianBlurTest/OnFullImage_Rgba32_Car_3.png
  19. 3
      tests/Images/External/ReferenceOutput/Convolution/MedianBlurTest/OnFullImage_Rgba32_Car_5.png
  20. 3
      tests/Images/External/ReferenceOutput/Convolution/MedianBlurTest/OnFullImage_Rgba32_blur_3.png
  21. 3
      tests/Images/External/ReferenceOutput/Convolution/MedianBlurTest/OnFullImage_Rgba32_blur_5.png

21
src/ImageSharp/Primitives/DenseMatrix{T}.cs

@ -71,6 +71,27 @@ namespace SixLabors.ImageSharp
}
}
/// <summary>
/// Initializes a new instance of the <see cref=" DenseMatrix{T}"/> struct.
/// </summary>
/// <param name="columns">The number of columns.</param>
/// <param name="rows">The number of rows.</param>
/// <param name="data">The array to provide access to.</param>
public DenseMatrix(int columns, int rows, Span<T> 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);
}
/// <summary>
/// Gets the 1D representation of the dense matrix.
/// </summary>

41
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
{
/// <summary>
/// Defines extensions that allow the applying of the median blur on an <see cref="Image"/>
/// using Mutate/Clone.
/// </summary>
public static class MedianBlurExtensions
{
/// <summary>
/// Applies a median blur on the image.
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="radius">The radius of the area to find the median for.</param>
/// <param name="preserveAlpha">
/// Whether the filter is applied to alpha as well as the color channels.
/// </param>
/// <returns>The <see cref="IImageProcessingContext"/> to allow chaining of operations.</returns>
public static IImageProcessingContext MedianBlur(this IImageProcessingContext source, int radius, bool preserveAlpha)
=> source.ApplyProcessor(new MedianBlurProcessor(radius, preserveAlpha));
/// <summary>
/// Applies a median blur on the image.
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="radius">The radius of the area to find the median for.</param>
/// <param name="preserveAlpha">
/// Whether the filter is applied to alpha as well as the color channels.
/// </param>
/// <param name="rectangle">
/// The <see cref="Rectangle"/> structure that specifies the portion of the image object to alter.
/// </param>
/// <returns>The <see cref="IImageProcessingContext"/> to allow chaining of operations.</returns>
public static IImageProcessingContext MedianBlur(this IImageProcessingContext source, int radius, bool preserveAlpha, Rectangle rectangle)
=> source.ApplyProcessor(new MedianBlurProcessor(radius, preserveAlpha), rectangle);
}
}

99
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
{
/// <summary>
/// A stack only, readonly, kernel matrix that can be indexed without
/// bounds checks when compiled in release mode.
/// </summary>
/// <typeparam name="T">The type of each element in the kernel.</typeparam>
internal readonly ref struct Kernel<T>
where T : struct, IEquatable<T>
{
private readonly Span<T> values;
public Kernel(DenseMatrix<T> 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<T> 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.");
}
}
}
}

53
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
{
/// <summary>
/// Applies an median filter.
/// </summary>
public sealed class MedianBlurProcessor : IImageProcessor
{
/// <summary>
/// Initializes a new instance of the <see cref="MedianBlurProcessor"/> class.
/// </summary>
/// <param name="radius">
/// The 'radius' value representing the size of the area to filter over.
/// </param>
/// <param name="preserveAlpha">
/// Whether the filter is applied to alpha as well as the color channels.
/// </param>
public MedianBlurProcessor(int radius, bool preserveAlpha)
{
this.Radius = radius;
this.PreserveAlpha = preserveAlpha;
}
/// <summary>
/// Gets the size of the area to find the median of.
/// </summary>
public int Radius { get; }
/// <summary>
/// Gets a value indicating whether the filter is applied to alpha as well as the color channels.
/// </summary>
public bool PreserveAlpha { get; }
/// <summary>
/// Gets the <see cref="BorderWrappingMode"/> to use when mapping the pixels outside of the border, in X direction.
/// </summary>
public BorderWrappingMode BorderWrapModeX { get; }
/// <summary>
/// Gets the <see cref="BorderWrappingMode"/> to use when mapping the pixels outside of the border, in Y direction.
/// </summary>
public BorderWrappingMode BorderWrapModeY { get; }
/// <inheritdoc />
public IImageProcessor<TPixel> CreatePixelSpecificProcessor<TPixel>(Configuration configuration, Image<TPixel> source, Rectangle sourceRectangle)
where TPixel : unmanaged, IPixel<TPixel>
=> new MedianBlurProcessor<TPixel>(configuration, this, source, sourceRectangle);
}
}

59
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
{
/// <summary>
/// Applies an median filter.
/// </summary>
/// <typeparam name="TPixel">The type of pixel format.</typeparam>
internal sealed class MedianBlurProcessor<TPixel> : ImageProcessor<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
private readonly MedianBlurProcessor definition;
public MedianBlurProcessor(Configuration configuration, MedianBlurProcessor definition, Image<TPixel> source, Rectangle sourceRectangle)
: base(configuration, source, sourceRectangle) => this.definition = definition;
protected override void OnFrameApply(ImageFrame<TPixel> source)
{
int kernelSize = (2 * this.definition.Radius) + 1;
MemoryAllocator allocator = this.Configuration.MemoryAllocator;
using Buffer2D<TPixel> targetPixels = allocator.Allocate2D<TPixel>(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<TPixel> operation = new(
interest,
targetPixels,
source.PixelBuffer,
map,
kernelSize,
this.Configuration,
this.definition.PreserveAlpha);
ParallelRowIterator.IterateRows<MedianRowOperation<TPixel>, Vector4>(
this.Configuration,
operationBounds,
in operation);
Buffer2D<TPixel>.SwapOrCopyContent(source.PixelBuffer, targetPixels);
}
}
}

46
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
{
/// <summary>
/// A stack only struct used for reducing reference indirection during convolution operations.
/// </summary>
internal readonly ref struct MedianConvolutionState
{
private readonly Span<int> rowOffsetMap;
private readonly Span<int> columnOffsetMap;
private readonly int kernelHeight;
private readonly int kernelWidth;
public MedianConvolutionState(
in DenseMatrix<Vector4> kernel,
KernelSamplingMap map)
{
this.Kernel = new Kernel<Vector4>(kernel);
this.kernelHeight = kernel.Rows;
this.kernelWidth = kernel.Columns;
this.rowOffsetMap = map.GetRowOffsetSpan();
this.columnOffsetMap = map.GetColumnOffsetSpan();
}
public readonly Kernel<Vector4> 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);
}
}

178
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
{
/// <summary>
/// Applies an median filter.
/// </summary>
/// <typeparam name="TPixel">The type of pixel format.</typeparam>
internal readonly struct MedianRowOperation<TPixel> : IRowOperation<Vector4>
where TPixel : unmanaged, IPixel<TPixel>
{
private readonly int yChannelStart;
private readonly int zChannelStart;
private readonly int wChannelStart;
private readonly Configuration configuration;
private readonly Rectangle bounds;
private readonly Buffer2D<TPixel> targetPixels;
private readonly Buffer2D<TPixel> sourcePixels;
private readonly KernelSamplingMap map;
private readonly int kernelSize;
private readonly bool preserveAlpha;
public MedianRowOperation(Rectangle bounds, Buffer2D<TPixel> targetPixels, Buffer2D<TPixel> 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<Vector4> 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<Vector4> kernelBuffer = span[..kernelCount];
Span<Vector4> channelVectorBuffer = span.Slice(kernelCount, kernelCount);
Span<Vector4> sourceVectorBuffer = span.Slice(kernelCount << 1, this.kernelSize * boundsWidth);
Span<Vector4> targetBuffer = span.Slice((kernelCount << 1) + sourceVectorBuffer.Length, boundsWidth);
// Stack 4 channels of floats in the space of Vector4's.
Span<float> channelBuffer = MemoryMarshal.Cast<Vector4, float>(channelVectorBuffer);
Span<float> xChannel = channelBuffer[..kernelCount];
Span<float> yChannel = channelBuffer.Slice(this.yChannelStart, kernelCount);
Span<float> zChannel = channelBuffer.Slice(this.zChannelStart, kernelCount);
DenseMatrix<Vector4> 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<TPixel> sourceRow = this.sourcePixels.DangerousGetRowSpan(currentYIndex).Slice(boundsX, boundsWidth);
Span<Vector4> sourceVectorRow = sourceVectorBuffer.Slice(i * boundsWidth, boundsWidth);
PixelOperations<TPixel>.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<Vector4> 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<float> 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<Vector4> 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<TPixel> targetRowSpan = this.targetPixels.DangerousGetRowSpan(y).Slice(boundsX, boundsWidth);
PixelOperations<TPixel>.Instance.FromVector4Destructive(this.configuration, targetBuffer, targetRowSpan);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static Vector4 FindMedian3(ReadOnlySpan<Vector4> kernelSpan, Span<float> xChannel, Span<float> yChannel, Span<float> 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<Vector4> kernelSpan, Span<float> xChannel, Span<float> yChannel, Span<float> zChannel, Span<float> 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]);
}
}
}

34
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<MedianBlurProcessor>();
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<MedianBlurProcessor>(this.rect);
Assert.Equal(5, processor.Radius);
Assert.False(processor.PreserveAlpha);
}
}
}

18
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);
}
}

3
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

3
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

3
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

3
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

3
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

3
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

3
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

3
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

3
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

3
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

3
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

3
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
Loading…
Cancel
Save