mirror of https://github.com/SixLabors/ImageSharp
committed by
GitHub
21 changed files with 585 additions and 0 deletions
@ -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); |
|||
} |
|||
} |
|||
@ -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."); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -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); |
|||
} |
|||
} |
|||
@ -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); |
|||
} |
|||
} |
|||
} |
|||
@ -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); |
|||
} |
|||
} |
|||
@ -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]); |
|||
} |
|||
} |
|||
} |
|||
@ -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); |
|||
} |
|||
} |
|||
} |
|||
@ -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); |
|||
} |
|||
} |
|||
@ -0,0 +1,3 @@ |
|||
version https://git-lfs.github.com/spec/v1 |
|||
oid sha256:ea5c7e0191cd4cf12067b462f13a7466fac94e94c12fa9d9b291f3d9677a14b4 |
|||
size 331353 |
|||
@ -0,0 +1,3 @@ |
|||
version https://git-lfs.github.com/spec/v1 |
|||
oid sha256:b6c5492f42eb5ffaeb7878b47b7654bb2a08bf91f13fe4ca8186892a849ba516 |
|||
size 322752 |
|||
@ -0,0 +1,3 @@ |
|||
version https://git-lfs.github.com/spec/v1 |
|||
oid sha256:a1fe499ba5b6f4f9ffa73d898be45e2ee13fc7c7c65b5f3366569a280546cb49 |
|||
size 234179 |
|||
@ -0,0 +1,3 @@ |
|||
version https://git-lfs.github.com/spec/v1 |
|||
oid sha256:fab69dad4a26739fe7dd2167d4b5ec9581b9d1bd9fef9b3df0cf2a195d03efc7 |
|||
size 227933 |
|||
@ -0,0 +1,3 @@ |
|||
version https://git-lfs.github.com/spec/v1 |
|||
oid sha256:6c06cefc194ab21aabaf256d8a65b42d62b3a22c1a141f8d706a4c2958cec22e |
|||
size 194915 |
|||
@ -0,0 +1,3 @@ |
|||
version https://git-lfs.github.com/spec/v1 |
|||
oid sha256:8a7ec3721962f469ad9c057a464f2c79ee64c583e227c3956d27be7240fa0ab7 |
|||
size 194709 |
|||
@ -0,0 +1,3 @@ |
|||
version https://git-lfs.github.com/spec/v1 |
|||
oid sha256:14b12f2df171142c9ccb368673c1809a6efa2cf0c4a2bb685ca9012e02b54532 |
|||
size 225209 |
|||
@ -0,0 +1,3 @@ |
|||
version https://git-lfs.github.com/spec/v1 |
|||
oid sha256:d5c532fee91fd812fb36f4ae2f05552de4d66f863ee7c1becb33e6e26e6fb6ba |
|||
size 189577 |
|||
@ -0,0 +1,3 @@ |
|||
version https://git-lfs.github.com/spec/v1 |
|||
oid sha256:1c9adda439083b795e163d3a54108750ee921010fe03ef9b39bec794a26f8fe4 |
|||
size 207101 |
|||
@ -0,0 +1,3 @@ |
|||
version https://git-lfs.github.com/spec/v1 |
|||
oid sha256:b0a86d9ec30d609756b402bed229e38bbcd30878c49224d6f6821a240d460608 |
|||
size 192432 |
|||
@ -0,0 +1,3 @@ |
|||
version https://git-lfs.github.com/spec/v1 |
|||
oid sha256:48cbc90a9fb90c12882e52d2999f1d41da89230c18d9b0a9d06ee2917acee1b8 |
|||
size 149396 |
|||
@ -0,0 +1,3 @@ |
|||
version https://git-lfs.github.com/spec/v1 |
|||
oid sha256:6cfddcda78110c29f8ddf0cc8cc8089e77bef8f13c468c8fee5eda18779defb4 |
|||
size 143547 |
|||
Loading…
Reference in new issue