Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions src/ImageSharp/Primitives/DenseMatrix{T}.cs
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,27 @@ public DenseMatrix(T[,] data)
}
}

/// <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>
Expand Down
Original file line number Diff line number Diff line change
@@ -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 changes: 99 additions & 0 deletions src/ImageSharp/Processing/Processors/Convolution/Kernel.cs
Original file line number Diff line number Diff line change
@@ -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.");
}
}
}
}
Original file line number Diff line number Diff line change
@@ -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);
}
}
Original file line number Diff line number Diff line change
@@ -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);
}
}
}
Original file line number Diff line number Diff line change
@@ -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);
}
}
Loading