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/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..10fd5a5d65
--- /dev/null
+++ b/src/ImageSharp/Processing/Processors/Convolution/MedianBlurProcessor{TPixel}.cs
@@ -0,0 +1,147 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+
+using System;
+using System.Numerics;
+using System.Runtime.CompilerServices;
+using SixLabors.ImageSharp.Advanced;
+using SixLabors.ImageSharp.Memory;
+using SixLabors.ImageSharp.PixelFormats;
+
+namespace SixLabors.ImageSharp.Processing.Processors.Convolution
+{
+ ///
+ /// Applies an median filter.
+ ///
+ 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);
+
+ var interest = Rectangle.Intersect(this.SourceRectangle, source.Bounds());
+
+ // We use a rectangle with width set to 2 * kernelSize^2 + width, to allocate a buffer big enough
+ // for kernel source and target bulk pixel conversion.
+ var operationBounds = new Rectangle(interest.X, interest.Y, (2 * (kernelSize * kernelSize)) + interest.Width, interest.Height);
+
+ using var map = new KernelSamplingMap(this.Configuration.MemoryAllocator);
+ map.BuildSamplingOffsetMap(kernelSize, kernelSize, interest, this.definition.BorderWrapModeX, this.definition.BorderWrapModeY);
+
+ var operation = new MedianRowOperation(
+ interest,
+ targetPixels,
+ source.PixelBuffer,
+ map,
+ kernelSize,
+ this.Configuration,
+ this.definition.PreserveAlpha);
+
+ ParallelRowIterator.IterateRows, Vector4>(
+ this.Configuration,
+ operationBounds,
+ in operation);
+
+ Buffer2D.SwapOrCopyContent(source.PixelBuffer, targetPixels);
+ }
+
+ private void ProcessSingleRow(PixelAccessor access, KernelSamplingMap map, int y, Span dest)
+ {
+ int kernelSize = (2 * this.definition.Radius) + 1;
+ int kernelCount = kernelSize * kernelSize;
+ using var vectorsBuffer = this.Configuration.MemoryAllocator.Allocate(kernelCount);
+ var vectorsSpan = vectorsBuffer.Memory.Span;
+ using var rowBuffer = this.Configuration.MemoryAllocator.Allocate(dest.Length);
+ var rowSpan = rowBuffer.Memory.Span;
+ using var componentsBuffer = this.Configuration.MemoryAllocator.Allocate(kernelCount << 2);
+ var componentsSpan = componentsBuffer.Memory.Span;
+ var xs = componentsSpan.Slice(0, kernelCount);
+ var ys = componentsSpan.Slice(kernelCount, kernelCount);
+ var zs = componentsSpan.Slice(kernelCount << 1, kernelCount);
+ var ws = componentsSpan.Slice(kernelCount * 3, kernelCount);
+ var xOffsets = map.GetColumnOffsetSpan();
+ var yOffsets = map.GetRowOffsetSpan();
+ var baseYOffsetIndex = y * kernelSize;
+ for (var x = 0; x < access.Width; x++)
+ {
+ var baseXOffsetIndex = x * kernelSize;
+ var index = 0;
+ for (var w = 0; w < kernelSize; w++)
+ {
+ var j = yOffsets[baseYOffsetIndex + w];
+ var row = access.GetRowSpan(j);
+ for (var z = 0; z < kernelSize; z++)
+ {
+ var k = xOffsets[baseXOffsetIndex + z];
+ var pixel = row[k];
+ vectorsSpan[index + z] = pixel.ToVector4();
+ }
+
+ index += kernelSize;
+ }
+
+ rowSpan[x] = this.FindMedian4(vectorsSpan, xs, ys, zs, ws, kernelCount);
+ }
+
+ PixelOperations.Instance.FromVector4Destructive(this.Configuration, rowSpan, dest);
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private Vector4 FindMedian3(Span span, Span xs, Span ys, Span zs, Span ws, int stride)
+ {
+ Vector4 found = new Vector4(0f, 0f, 0f, 0f);
+ int halfLength = (span.Length + 1) >> 1;
+
+ // Find median of X component.
+ for (int i = 0; i < xs.Length; i++)
+ {
+ xs[i] = span[i].X;
+ ys[i] = span[i].Y;
+ zs[i] = span[i].Z;
+ }
+
+ xs.Sort();
+ ys.Sort();
+ zs.Sort();
+
+ return new Vector4(xs[halfLength], ys[halfLength], zs[halfLength], span[halfLength].W);
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private Vector4 FindMedian4(Span span, Span xs, Span ys, Span zs, Span ws, int stride)
+ {
+ Vector4 found = new Vector4(0f, 0f, 0f, 0f);
+ int halfLength = (span.Length + 1) >> 1;
+
+ // Find median of X component.
+ for (int i = 0; i < xs.Length; i++)
+ {
+ xs[i] = span[i].X;
+ ys[i] = span[i].Y;
+ zs[i] = span[i].Z;
+ ws[i] = span[i].W;
+ }
+
+ xs.Sort();
+ ys.Sort();
+ zs.Sort();
+ ws.Sort();
+
+ return new Vector4(xs[halfLength], ys[halfLength], zs[halfLength], ws[halfLength]);
+ }
+ }
+}
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..0678652ec9
--- /dev/null
+++ b/src/ImageSharp/Processing/Processors/Convolution/MedianRowOperation{TPixel}.cs
@@ -0,0 +1,140 @@
+// 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.
+ ///
+ 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 boundsLeft = this.bounds.Left;
+ int boundsWidth = this.bounds.Width;
+ int boundsRight = this.bounds.Right;
+ int kernelCount = this.kernelSize * this.kernelSize;
+ Span kernelBuffer = span.Slice(0, kernelCount);
+ Span channelVectorBuffer = span.Slice(kernelCount, kernelCount);
+ Span targetBuffer = span.Slice(kernelCount << 1, boundsWidth);
+
+ // Stack 4 channels of floats in the space of Vector4's.
+ Span channelBuffer = MemoryMarshal.Cast(channelVectorBuffer);
+ var xChannel = channelBuffer.Slice(0, kernelCount);
+ var yChannel = channelBuffer.Slice(this.yChannelStart, kernelCount);
+ var zChannel = channelBuffer.Slice(this.zChannelStart, kernelCount);
+ var wChannel = channelBuffer.Slice(this.wChannelStart, kernelCount);
+
+ var xOffsets = this.map.GetColumnOffsetSpan();
+ var yOffsets = this.map.GetRowOffsetSpan();
+ var baseXOffsetIndex = 0;
+ var baseYOffsetIndex = (y - this.bounds.Top) * this.kernelSize;
+
+ for (var x = boundsLeft; x < boundsRight; x++)
+ {
+ var index = 0;
+ for (var w = 0; w < this.kernelSize; w++)
+ {
+ var j = yOffsets[baseYOffsetIndex + w];
+ var row = this.sourcePixels.DangerousGetRowSpan(j);
+ for (var z = 0; z < this.kernelSize; z++)
+ {
+ var k = xOffsets[baseXOffsetIndex + z];
+ var pixel = row[k];
+ kernelBuffer[index + z] = pixel.ToVector4();
+ }
+
+ index += this.kernelSize;
+ }
+
+ targetBuffer[x - boundsLeft] = this.preserveAlpha ?
+ this.FindMedian3(kernelBuffer, xChannel, yChannel, zChannel, kernelCount) :
+ this.FindMedian4(kernelBuffer, xChannel, yChannel, zChannel, wChannel, kernelCount);
+ baseXOffsetIndex += this.kernelSize;
+ }
+
+ Span targetRowSpan = this.targetPixels.DangerousGetRowSpan(y).Slice(boundsLeft, boundsWidth);
+ PixelOperations.Instance.FromVector4Destructive(this.configuration, targetBuffer, targetRowSpan);
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private Vector4 FindMedian3(Span kernelSpan, Span xChannel, Span yChannel, Span zChannel, int stride)
+ {
+ 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();
+
+ return new Vector4(xChannel[halfLength], yChannel[halfLength], zChannel[halfLength], kernelSpan[halfLength].W);
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private Vector4 FindMedian4(Span kernelSpan, Span xChannel, Span yChannel, Span zChannel, Span wChannel, int stride)
+ {
+ 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..db1cbefb47
--- /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.Equal(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.Equal(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