-
-
Notifications
You must be signed in to change notification settings - Fork 888
Implement Median Blur processor #2219
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from 13 commits
Commits
Show all changes
18 commits
Select commit
Hold shift + click to select a range
8bbec22
Merge pull request #2 from SixLabors/main
ynse01 4af75b3
Median Blur
ynse01 626488e
Preserve alpha in separate loop
ynse01 eb3d3a5
Clean up dead code
ynse01 2959db0
Solve build warnings
ynse01 3572f51
Merge branch 'main' into median-filter
ynse01 f53f5f0
Merge branch 'median-filter' of https://github.com/ynse01/ImageSharp …
ynse01 cd25825
Merge branch 'main' into median-filter
JimBobSquarePants 0e03797
Merge branch 'main' into median-filter
JimBobSquarePants ea474d7
Solving IDE0008 warnings
ynse01 a6daaf9
Merge branch 'median-filter' of https://github.com/ynse01/ImageSharp …
ynse01 22af95a
Minor rework
ynse01 f97a2cc
Use ConvolutionState and Unsafe.Add
ynse01 bc1162e
Bulk convert source rows to Vector4
ynse01 bb3acac
Remove dead code
ynse01 d59618b
Merge branch 'main' into median-filter
JimBobSquarePants c359b53
Merge branch 'main' into median-filter
JimBobSquarePants b6fb196
Update to match new build rules.
JimBobSquarePants File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
41 changes: 41 additions & 0 deletions
41
src/ImageSharp/Processing/Extensions/Convolution/MedianBlurExtensions.cs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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); | ||
| } | ||
| } |
92 changes: 92 additions & 0 deletions
92
src/ImageSharp/Processing/Processors/Convolution/Kernel.cs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,92 @@ | ||
| // 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> | ||
| 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 | ||
| { | ||
| return 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)] | ||
| public void SetValue(int row, int column, T value) | ||
| { | ||
| this.SetValue((row * this.Columns) + column, value); | ||
| } | ||
|
|
||
| [MethodImpl(MethodImplOptions.AggressiveInlining)] | ||
| public void SetValue(int index, T value) | ||
| { | ||
| 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."); | ||
| } | ||
| } | ||
| } | ||
| } |
53 changes: 53 additions & 0 deletions
53
src/ImageSharp/Processing/Processors/Convolution/MedianBlurProcessor.cs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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); | ||
| } | ||
| } |
60 changes: 60 additions & 0 deletions
60
src/ImageSharp/Processing/Processors/Convolution/MedianBlurProcessor{TPixel}.cs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,60 @@ | ||
| // 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> | ||
| 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 to 2 * kernelSize^2 + width, to allocate a buffer big enough | ||
| // for kernel source and target bulk pixel conversion. | ||
| Rectangle operationBounds = new Rectangle(interest.X, interest.Y, (2 * (kernelSize * kernelSize)) + interest.Width, interest.Height); | ||
|
|
||
| using KernelSamplingMap map = new KernelSamplingMap(this.Configuration.MemoryAllocator); | ||
| map.BuildSamplingOffsetMap(kernelSize, kernelSize, interest, this.definition.BorderWrapModeX, this.definition.BorderWrapModeY); | ||
|
|
||
| var operation = new MedianRowOperation<TPixel>( | ||
| 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 changes: 46 additions & 0 deletions
46
src/ImageSharp/Processing/Processors/Convolution/MedianConvolutionState.cs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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); | ||
| } | ||
| } |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you use 2x interest width here you can use the extra buffer space to allow bulk per-row
ToVector4()against the source row in the operation.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Well, it's not that simple I think, as we need more rows for a single kernel.
I think I get your point about caching the
Vector4converted pixels, I'll take the challenge of making it work!There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
On the multiple source rows that Median needs, the conversion is now done with BulkOperations. I think this is what you meant.