diff --git a/src/ImageSharp/Color/Color.cs b/src/ImageSharp/Color/Color.cs index 2547e6d873..4bdbe088ca 100644 --- a/src/ImageSharp/Color/Color.cs +++ b/src/ImageSharp/Color/Color.cs @@ -2,8 +2,6 @@ // Licensed under the Apache License, Version 2.0. using System; -using System.Buffers.Binary; -using System.Globalization; using System.Numerics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; diff --git a/src/ImageSharp/Common/Helpers/Buffer2DUtils.cs b/src/ImageSharp/Common/Helpers/Buffer2DUtils.cs new file mode 100644 index 0000000000..0c22aa68ff --- /dev/null +++ b/src/ImageSharp/Common/Helpers/Buffer2DUtils.cs @@ -0,0 +1,105 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +using SixLabors.ImageSharp.Memory; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Primitives; + +namespace SixLabors.ImageSharp +{ + /// + /// Extension methods for . + /// TODO: One day rewrite all this to use SIMD intrinsics. There's a lot of scope for improvement. + /// + internal static class Buffer2DUtils + { + /// + /// Computes the sum of vectors in weighted by the kernel weight values. + /// + /// The pixel format. + /// The 1D convolution kernel. + /// The source frame. + /// The target row. + /// The current row. + /// The current column. + /// The minimum working area row. + /// The maximum working area row. + /// The minimum working area column. + /// The maximum working area column. + public static void Convolve4( + Span kernel, + Buffer2D sourcePixels, + Span targetRow, + int row, + int column, + int minRow, + int maxRow, + int minColumn, + int maxColumn) + where TPixel : struct, IPixel + { + ComplexVector4 vector = default; + int kernelLength = kernel.Length; + int radiusY = kernelLength >> 1; + int sourceOffsetColumnBase = column + minColumn; + ref Complex64 baseRef = ref MemoryMarshal.GetReference(kernel); + + for (int i = 0; i < kernelLength; i++) + { + int offsetY = (row + i - radiusY).Clamp(minRow, maxRow); + int offsetX = sourceOffsetColumnBase.Clamp(minColumn, maxColumn); + Span sourceRowSpan = sourcePixels.GetRowSpan(offsetY); + var currentColor = sourceRowSpan[offsetX].ToVector4(); + + vector.Sum(Unsafe.Add(ref baseRef, i) * currentColor); + } + + targetRow[column] = vector; + } + + /// + /// Computes the sum of vectors in weighted by the kernel weight values. + /// + /// The 1D convolution kernel. + /// The source frame. + /// The target row. + /// The current row. + /// The current column. + /// The minimum working area row. + /// The maximum working area row. + /// The minimum working area column. + /// The maximum working area column. + public static void Convolve4( + Span kernel, + Buffer2D sourceValues, + Span targetRow, + int row, + int column, + int minRow, + int maxRow, + int minColumn, + int maxColumn) + { + ComplexVector4 vector = default; + int kernelLength = kernel.Length; + int radiusX = kernelLength >> 1; + int sourceOffsetColumnBase = column + minColumn; + + int offsetY = row.Clamp(minRow, maxRow); + ref ComplexVector4 sourceRef = ref MemoryMarshal.GetReference(sourceValues.GetRowSpan(offsetY)); + ref Complex64 baseRef = ref MemoryMarshal.GetReference(kernel); + + for (int x = 0; x < kernelLength; x++) + { + int offsetX = (sourceOffsetColumnBase + x - radiusX).Clamp(minColumn, maxColumn); + vector.Sum(Unsafe.Add(ref baseRef, x) * Unsafe.Add(ref sourceRef, offsetX)); + } + + targetRow[column] = vector; + } + } +} diff --git a/src/ImageSharp/Common/Helpers/DenseMatrixUtils.cs b/src/ImageSharp/Common/Helpers/DenseMatrixUtils.cs index 427b240057..c5c9ddebe1 100644 --- a/src/ImageSharp/Common/Helpers/DenseMatrixUtils.cs +++ b/src/ImageSharp/Common/Helpers/DenseMatrixUtils.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System; @@ -45,8 +45,6 @@ public static void Convolve2D3( int maxColumn) where TPixel : struct, IPixel { - Vector4 vector = default; - Convolve2DImpl( in matrixY, in matrixX, @@ -57,7 +55,7 @@ public static void Convolve2D3( maxRow, minColumn, maxColumn, - ref vector); + out Vector4 vector); ref Vector4 target = ref Unsafe.Add(ref targetRowRef, column); vector.W = target.W; @@ -95,8 +93,6 @@ public static void Convolve2D4( int maxColumn) where TPixel : struct, IPixel { - Vector4 vector = default; - Convolve2DImpl( in matrixY, in matrixX, @@ -107,7 +103,7 @@ public static void Convolve2D4( maxRow, minColumn, maxColumn, - ref vector); + out Vector4 vector); ref Vector4 target = ref Unsafe.Add(ref targetRowRef, column); Vector4Utils.UnPremultiply(ref vector); @@ -125,7 +121,7 @@ public static void Convolve2DImpl( int maxRow, int minColumn, int maxColumn, - ref Vector4 vector) + out Vector4 vector) where TPixel : struct, IPixel { Vector4 vectorY = default; @@ -281,4 +277,4 @@ private static void ConvolveImpl( } } } -} \ No newline at end of file +} diff --git a/src/ImageSharp/Common/Helpers/SimdUtils.BasicIntrinsics256.cs b/src/ImageSharp/Common/Helpers/SimdUtils.BasicIntrinsics256.cs index 85c9f00748..b16bbc86b6 100644 --- a/src/ImageSharp/Common/Helpers/SimdUtils.BasicIntrinsics256.cs +++ b/src/ImageSharp/Common/Helpers/SimdUtils.BasicIntrinsics256.cs @@ -2,7 +2,6 @@ // Licensed under the Apache License, Version 2.0. using System; -using System.Diagnostics; using System.Numerics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; diff --git a/src/ImageSharp/Common/Helpers/SimdUtils.cs b/src/ImageSharp/Common/Helpers/SimdUtils.cs index 867e7b9de1..45761a0d07 100644 --- a/src/ImageSharp/Common/Helpers/SimdUtils.cs +++ b/src/ImageSharp/Common/Helpers/SimdUtils.cs @@ -7,9 +7,6 @@ using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Tuples; - namespace SixLabors.ImageSharp { /// diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/IRawJpegData.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/IRawJpegData.cs index ace8d7215b..2f393fadae 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/IRawJpegData.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/IRawJpegData.cs @@ -2,7 +2,6 @@ // Licensed under the Apache License, Version 2.0. using System; -using System.Collections.Generic; using SixLabors.Primitives; diff --git a/src/ImageSharp/Formats/Jpeg/Components/Encoder/YCbCrForwardConverter{TPixel}.cs b/src/ImageSharp/Formats/Jpeg/Components/Encoder/YCbCrForwardConverter{TPixel}.cs index 301079b6ae..883a085b5a 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Encoder/YCbCrForwardConverter{TPixel}.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Encoder/YCbCrForwardConverter{TPixel}.cs @@ -4,7 +4,6 @@ using System; using System.Runtime.CompilerServices; -using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder diff --git a/src/ImageSharp/Formats/Jpeg/Components/GenericBlock8x8.cs b/src/ImageSharp/Formats/Jpeg/Components/GenericBlock8x8.cs index c795ccc8b5..3d1e22a99d 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/GenericBlock8x8.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/GenericBlock8x8.cs @@ -7,7 +7,6 @@ using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; -using SixLabors.Memory; // ReSharper disable InconsistentNaming namespace SixLabors.ImageSharp.Formats.Jpeg.Components diff --git a/src/ImageSharp/Memory/Buffer2DExtensions.cs b/src/ImageSharp/Memory/Buffer2DExtensions.cs index 61fcb99db2..096493f2ba 100644 --- a/src/ImageSharp/Memory/Buffer2DExtensions.cs +++ b/src/ImageSharp/Memory/Buffer2DExtensions.cs @@ -2,7 +2,6 @@ // Licensed under the Apache License, Version 2.0. using System; -using System.Buffers; using System.Diagnostics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; diff --git a/src/ImageSharp/Memory/Buffer2D{T}.cs b/src/ImageSharp/Memory/Buffer2D{T}.cs index 41a560cdb6..82a98bfc63 100644 --- a/src/ImageSharp/Memory/Buffer2D{T}.cs +++ b/src/ImageSharp/Memory/Buffer2D{T}.cs @@ -61,13 +61,31 @@ public Buffer2D(MemorySource memorySource, int width, int height) [MethodImpl(MethodImplOptions.AggressiveInlining)] get { - ImageSharp.DebugGuard.MustBeLessThan(x, this.Width, nameof(x)); - ImageSharp.DebugGuard.MustBeLessThan(y, this.Height, nameof(y)); + DebugGuard.MustBeLessThan(x, this.Width, nameof(x)); + DebugGuard.MustBeLessThan(y, this.Height, nameof(y)); + Span span = this.Span; return ref span[(this.Width * y) + x]; } } + /// + /// Creates a new instance that maps to a target rows interval from the current instance. + /// + /// The target vertical offset for the rows interval to retrieve. + /// The desired number of rows to extract. + /// The new instance with the requested rows interval. + public Buffer2D Slice(int y, int h) + { + DebugGuard.MustBeGreaterThanOrEqualTo(y, 0, nameof(y)); + DebugGuard.MustBeGreaterThan(h, 0, nameof(h)); + DebugGuard.MustBeLessThanOrEqualTo(y + h, this.Height, nameof(h)); + + Memory slice = this.Memory.Slice(y * this.Width, h * this.Width); + var memory = new MemorySource(slice); + return new Buffer2D(memory, this.Width, h); + } + /// /// Disposes the instance /// @@ -98,4 +116,4 @@ private static void SwapDimensionData(Buffer2D a, Buffer2D b) a.Height = bSize.Height; } } -} \ No newline at end of file +} diff --git a/src/ImageSharp/MetaData/Profiles/ICC/DataReader/IccDataReader.cs b/src/ImageSharp/MetaData/Profiles/ICC/DataReader/IccDataReader.cs index 7d694bec6e..bbfe909e01 100644 --- a/src/ImageSharp/MetaData/Profiles/ICC/DataReader/IccDataReader.cs +++ b/src/ImageSharp/MetaData/Profiles/ICC/DataReader/IccDataReader.cs @@ -2,7 +2,6 @@ // Licensed under the Apache License, Version 2.0. using System; -using System.Text; namespace SixLabors.ImageSharp.Metadata.Profiles.Icc { diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/Gray16.cs b/src/ImageSharp/PixelFormats/PixelImplementations/Gray16.cs index 195a8bedb0..dd12074352 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/Gray16.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Gray16.cs @@ -1,7 +1,6 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -using System; using System.Numerics; using System.Runtime.CompilerServices; diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/Rgba32.PixelOperations.cs b/src/ImageSharp/PixelFormats/PixelImplementations/Rgba32.PixelOperations.cs index 3da5d1855b..da02f374e1 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/Rgba32.PixelOperations.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Rgba32.PixelOperations.cs @@ -6,7 +6,6 @@ using System.Runtime.InteropServices; using SixLabors.ImageSharp.PixelFormats.Utils; -using SixLabors.Memory; namespace SixLabors.ImageSharp.PixelFormats { diff --git a/src/ImageSharp/PixelFormats/PixelOperations{TPixel}.cs b/src/ImageSharp/PixelFormats/PixelOperations{TPixel}.cs index 2b2d79c732..f557f348a1 100644 --- a/src/ImageSharp/PixelFormats/PixelOperations{TPixel}.cs +++ b/src/ImageSharp/PixelFormats/PixelOperations{TPixel}.cs @@ -4,8 +4,6 @@ using System; using System.Buffers; using System.Numerics; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; using SixLabors.ImageSharp.Memory; diff --git a/src/ImageSharp/PixelFormats/Utils/Vector4Converters.Default.cs b/src/ImageSharp/PixelFormats/Utils/Vector4Converters.Default.cs index 4c4e60276d..e67bd9d53e 100644 --- a/src/ImageSharp/PixelFormats/Utils/Vector4Converters.Default.cs +++ b/src/ImageSharp/PixelFormats/Utils/Vector4Converters.Default.cs @@ -6,8 +6,6 @@ using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -using SixLabors.ImageSharp.ColorSpaces.Companding; - namespace SixLabors.ImageSharp.PixelFormats.Utils { /// diff --git a/src/ImageSharp/PixelFormats/Utils/Vector4Converters.RgbaCompatible.cs b/src/ImageSharp/PixelFormats/Utils/Vector4Converters.RgbaCompatible.cs index fe8d7dec3b..0350c669ab 100644 --- a/src/ImageSharp/PixelFormats/Utils/Vector4Converters.RgbaCompatible.cs +++ b/src/ImageSharp/PixelFormats/Utils/Vector4Converters.RgbaCompatible.cs @@ -7,8 +7,6 @@ using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -using SixLabors.ImageSharp.ColorSpaces.Companding; - namespace SixLabors.ImageSharp.PixelFormats.Utils { /// diff --git a/src/ImageSharp/Primitives/Complex64.cs b/src/ImageSharp/Primitives/Complex64.cs new file mode 100644 index 0000000000..75905a9bd7 --- /dev/null +++ b/src/ImageSharp/Primitives/Complex64.cs @@ -0,0 +1,95 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Numerics; +using System.Runtime.CompilerServices; + +namespace SixLabors.ImageSharp.Primitives +{ + /// + /// Represents a complex number, where the real and imaginary parts are stored as values. + /// + /// + /// This is a more efficient version of the type. + /// + internal readonly struct Complex64 : IEquatable + { + /// + /// The real part of the complex number + /// + public readonly float Real; + + /// + /// The imaginary part of the complex number + /// + public readonly float Imaginary; + + /// + /// Initializes a new instance of the struct. + /// + /// The real part in the complex number. + /// The imaginary part in the complex number. + public Complex64(float real, float imaginary) + { + this.Real = real; + this.Imaginary = imaginary; + } + + /// + /// Performs the multiplication operation between a intance and a scalar. + /// + /// The value to multiply. + /// The scalar to use to multiply the value. + /// The result + [MethodImpl(InliningOptions.ShortMethod)] + public static Complex64 operator *(Complex64 value, float scalar) => new Complex64(value.Real * scalar, value.Imaginary * scalar); + + /// + /// Performs the multiplication operation between a intance and a . + /// + /// The value to multiply. + /// The instance to use to multiply the value. + /// The result + [MethodImpl(InliningOptions.ShortMethod)] + public static ComplexVector4 operator *(Complex64 value, Vector4 vector) + { + return new ComplexVector4 { Real = vector * value.Real, Imaginary = vector * value.Imaginary }; + } + + /// + /// Performs the multiplication operation between a intance and a . + /// + /// The value to multiply. + /// The instance to use to multiply the value. + /// The result + [MethodImpl(InliningOptions.ShortMethod)] + public static ComplexVector4 operator *(Complex64 value, ComplexVector4 vector) + { + Vector4 real = (value.Real * vector.Real) - (value.Imaginary * vector.Imaginary); + Vector4 imaginary = (value.Real * vector.Imaginary) + (value.Imaginary * vector.Real); + return new ComplexVector4 { Real = real, Imaginary = imaginary }; + } + + /// + public bool Equals(Complex64 other) + { + return this.Real.Equals(other.Real) && this.Imaginary.Equals(other.Imaginary); + } + + /// + public override bool Equals(object obj) => obj is Complex64 other && this.Equals(other); + + /// + public override int GetHashCode() + { + unchecked + { + return (this.Real.GetHashCode() * 397) ^ this.Imaginary.GetHashCode(); + } + } + + /// + public override string ToString() => $"{this.Real}{(this.Imaginary >= 0 ? "+" : string.Empty)}{this.Imaginary}j"; + } +} diff --git a/src/ImageSharp/Primitives/ComplexVector4.cs b/src/ImageSharp/Primitives/ComplexVector4.cs new file mode 100644 index 0000000000..b90da65b2d --- /dev/null +++ b/src/ImageSharp/Primitives/ComplexVector4.cs @@ -0,0 +1,63 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Numerics; +using System.Runtime.CompilerServices; + +namespace SixLabors.ImageSharp.Primitives +{ + /// + /// A vector with 4 values of type . + /// + internal struct ComplexVector4 : IEquatable + { + /// + /// The real part of the complex vector + /// + public Vector4 Real; + + /// + /// The imaginary part of the complex number + /// + public Vector4 Imaginary; + + /// + /// Sums the values in the input to the current instance + /// + /// The input to sum + [MethodImpl(InliningOptions.ShortMethod)] + public void Sum(in ComplexVector4 value) + { + this.Real += value.Real; + this.Imaginary += value.Imaginary; + } + + /// + /// Performs a weighted sum on the current instance according to the given parameters + /// + /// The 'a' parameter, for the real component + /// The 'b' parameter, for the imaginary component + /// The resulting value + [MethodImpl(InliningOptions.ShortMethod)] + public Vector4 WeightedSum(float a, float b) => (this.Real * a) + (this.Imaginary * b); + + /// + public bool Equals(ComplexVector4 other) + { + return this.Real.Equals(other.Real) && this.Imaginary.Equals(other.Imaginary); + } + + /// + public override bool Equals(object obj) => obj is ComplexVector4 other && this.Equals(other); + + /// + public override int GetHashCode() + { + unchecked + { + return (this.Real.GetHashCode() * 397) ^ this.Imaginary.GetHashCode(); + } + } + } +} diff --git a/src/ImageSharp/Processing/BokehBlurExecutionMode.cs b/src/ImageSharp/Processing/BokehBlurExecutionMode.cs new file mode 100644 index 0000000000..bc44dca03c --- /dev/null +++ b/src/ImageSharp/Processing/BokehBlurExecutionMode.cs @@ -0,0 +1,23 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.Processing.Processors.Convolution; + +namespace SixLabors.ImageSharp.Processing +{ + /// + /// An that indicates execution options for the . + /// + public enum BokehBlurExecutionMode + { + /// + /// Indicates that the maximum performance should be prioritized over memory usage. + /// + PreferMaximumPerformance, + + /// + /// Indicates that the memory usage should be prioritized over raw performance. + /// + PreferLowMemoryUsage + } +} diff --git a/src/ImageSharp/Processing/Extensions/AutoOrientExtensions.cs b/src/ImageSharp/Processing/Extensions/AutoOrientExtensions.cs index a831e2d9af..984081dffe 100644 --- a/src/ImageSharp/Processing/Extensions/AutoOrientExtensions.cs +++ b/src/ImageSharp/Processing/Extensions/AutoOrientExtensions.cs @@ -1,7 +1,6 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing.Processors.Transforms; namespace SixLabors.ImageSharp.Processing diff --git a/src/ImageSharp/Processing/Extensions/BlackWhiteExtensions.cs b/src/ImageSharp/Processing/Extensions/BlackWhiteExtensions.cs index ee34cd99e2..c148ccbcb9 100644 --- a/src/ImageSharp/Processing/Extensions/BlackWhiteExtensions.cs +++ b/src/ImageSharp/Processing/Extensions/BlackWhiteExtensions.cs @@ -1,7 +1,6 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing.Processors.Filters; using SixLabors.Primitives; diff --git a/src/ImageSharp/Processing/Extensions/BokehBlurExtensions.cs b/src/ImageSharp/Processing/Extensions/BokehBlurExtensions.cs new file mode 100644 index 0000000000..ef20f940af --- /dev/null +++ b/src/ImageSharp/Processing/Extensions/BokehBlurExtensions.cs @@ -0,0 +1,106 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.Processing.Processors.Convolution; +using SixLabors.Primitives; + +namespace SixLabors.ImageSharp.Processing +{ + /// + /// Adds bokeh blurring extensions to the type. + /// + public static class BokehBlurExtensions + { + /// + /// Applies a bokeh blur to the image. + /// + /// The image this method extends. + /// The to allow chaining of operations. + public static IImageProcessingContext BokehBlur(this IImageProcessingContext source) + => source.ApplyProcessor(new BokehBlurProcessor()); + + /// + /// Applies a bokeh blur to the image. + /// + /// The image this method extends. + /// The execution mode to use when applying the processor. + /// The to allow chaining of operations. + public static IImageProcessingContext BokehBlur(this IImageProcessingContext source, BokehBlurExecutionMode executionMode) + => source.ApplyProcessor(new BokehBlurProcessor(executionMode)); + + /// + /// Applies a bokeh blur to the image. + /// + /// The image this method extends. + /// The 'radius' value representing the size of the area to sample. + /// The 'components' value representing the number of kernels to use to approximate the bokeh effect. + /// The gamma highlight factor to use to emphasize bright spots in the source image + /// The to allow chaining of operations. + public static IImageProcessingContext BokehBlur(this IImageProcessingContext source, int radius, int components, float gamma) + => source.ApplyProcessor(new BokehBlurProcessor(radius, components, gamma)); + + /// + /// Applies a bokeh blur to the image. + /// + /// The image this method extends. + /// The 'radius' value representing the size of the area to sample. + /// The 'components' value representing the number of kernels to use to approximate the bokeh effect. + /// The gamma highlight factor to use to emphasize bright spots in the source image + /// The execution mode to use when applying the processor. + /// The to allow chaining of operations. + public static IImageProcessingContext BokehBlur(this IImageProcessingContext source, int radius, int components, float gamma, BokehBlurExecutionMode executionMode) + => source.ApplyProcessor(new BokehBlurProcessor(radius, components, gamma, executionMode)); + + /// + /// Applies a bokeh blur to the image. + /// + /// The image this method extends. + /// + /// The structure that specifies the portion of the image object to alter. + /// + /// The to allow chaining of operations. + public static IImageProcessingContext BokehBlur(this IImageProcessingContext source, Rectangle rectangle) + => source.ApplyProcessor(new BokehBlurProcessor(), rectangle); + + /// + /// Applies a bokeh blur to the image. + /// + /// The image this method extends. + /// + /// The structure that specifies the portion of the image object to alter. + /// + /// The execution mode to use when applying the processor. + /// The to allow chaining of operations. + public static IImageProcessingContext BokehBlur(this IImageProcessingContext source, Rectangle rectangle, BokehBlurExecutionMode executionMode) + => source.ApplyProcessor(new BokehBlurProcessor(executionMode), rectangle); + + /// + /// Applies a bokeh blur to the image. + /// + /// The image this method extends. + /// The 'radius' value representing the size of the area to sample. + /// The 'components' value representing the number of kernels to use to approximate the bokeh effect. + /// The gamma highlight factor to use to emphasize bright spots in the source image + /// + /// The structure that specifies the portion of the image object to alter. + /// + /// The to allow chaining of operations. + public static IImageProcessingContext BokehBlur(this IImageProcessingContext source, int radius, int components, float gamma, Rectangle rectangle) + => source.ApplyProcessor(new BokehBlurProcessor(radius, components, gamma), rectangle); + + /// + /// Applies a bokeh blur to the image. + /// + /// The image this method extends. + /// The 'radius' value representing the size of the area to sample. + /// The 'components' value representing the number of kernels to use to approximate the bokeh effect. + /// The gamma highlight factor to use to emphasize bright spots in the source image + /// The execution mode to use when applying the processor. + /// + /// The structure that specifies the portion of the image object to alter. + /// + /// The to allow chaining of operations. + public static IImageProcessingContext BokehBlur(this IImageProcessingContext source, int radius, int components, float gamma, BokehBlurExecutionMode executionMode, Rectangle rectangle) + => source.ApplyProcessor(new BokehBlurProcessor(radius, components, gamma, executionMode), rectangle); + } +} diff --git a/src/ImageSharp/Processing/Extensions/BoxBlurExtensions.cs b/src/ImageSharp/Processing/Extensions/BoxBlurExtensions.cs index f3400c24e6..42dfd425cc 100644 --- a/src/ImageSharp/Processing/Extensions/BoxBlurExtensions.cs +++ b/src/ImageSharp/Processing/Extensions/BoxBlurExtensions.cs @@ -1,7 +1,6 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing.Processors.Convolution; using SixLabors.Primitives; diff --git a/src/ImageSharp/Processing/Extensions/BrightnessExtensions.cs b/src/ImageSharp/Processing/Extensions/BrightnessExtensions.cs index db84091763..8e43f06c5a 100644 --- a/src/ImageSharp/Processing/Extensions/BrightnessExtensions.cs +++ b/src/ImageSharp/Processing/Extensions/BrightnessExtensions.cs @@ -1,7 +1,6 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing.Processors.Filters; using SixLabors.Primitives; diff --git a/src/ImageSharp/Processing/Extensions/FilterExtensions.cs b/src/ImageSharp/Processing/Extensions/FilterExtensions.cs index 5a66502ce5..662e3a6e16 100644 --- a/src/ImageSharp/Processing/Extensions/FilterExtensions.cs +++ b/src/ImageSharp/Processing/Extensions/FilterExtensions.cs @@ -1,7 +1,6 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Primitives; using SixLabors.ImageSharp.Processing.Processors.Filters; using SixLabors.Primitives; diff --git a/src/ImageSharp/Processing/Extensions/GaussianBlurExtensions.cs b/src/ImageSharp/Processing/Extensions/GaussianBlurExtensions.cs index e527a14b73..858e3213b1 100644 --- a/src/ImageSharp/Processing/Extensions/GaussianBlurExtensions.cs +++ b/src/ImageSharp/Processing/Extensions/GaussianBlurExtensions.cs @@ -1,7 +1,6 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing.Processors.Convolution; using SixLabors.Primitives; diff --git a/src/ImageSharp/Processing/Extensions/GrayscaleExtensions.cs b/src/ImageSharp/Processing/Extensions/GrayscaleExtensions.cs index a87341025d..a4bfaa516c 100644 --- a/src/ImageSharp/Processing/Extensions/GrayscaleExtensions.cs +++ b/src/ImageSharp/Processing/Extensions/GrayscaleExtensions.cs @@ -1,7 +1,6 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing.Processors; using SixLabors.ImageSharp.Processing.Processors.Filters; using SixLabors.Primitives; diff --git a/src/ImageSharp/Processing/Extensions/HueExtensions.cs b/src/ImageSharp/Processing/Extensions/HueExtensions.cs index 3c1239da67..3955ea7f6e 100644 --- a/src/ImageSharp/Processing/Extensions/HueExtensions.cs +++ b/src/ImageSharp/Processing/Extensions/HueExtensions.cs @@ -1,7 +1,6 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing.Processors.Filters; using SixLabors.Primitives; diff --git a/src/ImageSharp/Processing/Extensions/InvertExtensions.cs b/src/ImageSharp/Processing/Extensions/InvertExtensions.cs index c45f24c2ea..16c7a89178 100644 --- a/src/ImageSharp/Processing/Extensions/InvertExtensions.cs +++ b/src/ImageSharp/Processing/Extensions/InvertExtensions.cs @@ -1,7 +1,6 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing.Processors.Filters; using SixLabors.Primitives; diff --git a/src/ImageSharp/Processing/Extensions/KodachromeExtensions.cs b/src/ImageSharp/Processing/Extensions/KodachromeExtensions.cs index 810094a180..6c9b279835 100644 --- a/src/ImageSharp/Processing/Extensions/KodachromeExtensions.cs +++ b/src/ImageSharp/Processing/Extensions/KodachromeExtensions.cs @@ -1,7 +1,6 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing.Processors.Filters; using SixLabors.Primitives; diff --git a/src/ImageSharp/Processing/Extensions/LomographExtensions.cs b/src/ImageSharp/Processing/Extensions/LomographExtensions.cs index dd7ab21ec1..c2b6ac0804 100644 --- a/src/ImageSharp/Processing/Extensions/LomographExtensions.cs +++ b/src/ImageSharp/Processing/Extensions/LomographExtensions.cs @@ -1,7 +1,6 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing.Processors.Filters; using SixLabors.Primitives; diff --git a/src/ImageSharp/Processing/Extensions/OpacityExtensions.cs b/src/ImageSharp/Processing/Extensions/OpacityExtensions.cs index ecf6ce783e..9c67113ecf 100644 --- a/src/ImageSharp/Processing/Extensions/OpacityExtensions.cs +++ b/src/ImageSharp/Processing/Extensions/OpacityExtensions.cs @@ -1,7 +1,6 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing.Processors.Filters; using SixLabors.Primitives; diff --git a/src/ImageSharp/Processing/Extensions/PolaroidExtensions.cs b/src/ImageSharp/Processing/Extensions/PolaroidExtensions.cs index eace463579..6b6d43d5b8 100644 --- a/src/ImageSharp/Processing/Extensions/PolaroidExtensions.cs +++ b/src/ImageSharp/Processing/Extensions/PolaroidExtensions.cs @@ -1,7 +1,6 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing.Processors.Filters; using SixLabors.Primitives; diff --git a/src/ImageSharp/Processing/Extensions/ResizeExtensions.cs b/src/ImageSharp/Processing/Extensions/ResizeExtensions.cs index 4578b4353f..81b1c2c663 100644 --- a/src/ImageSharp/Processing/Extensions/ResizeExtensions.cs +++ b/src/ImageSharp/Processing/Extensions/ResizeExtensions.cs @@ -1,7 +1,6 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing.Processors.Transforms; using SixLabors.Primitives; diff --git a/src/ImageSharp/Processing/Extensions/RotateFlipExtensions.cs b/src/ImageSharp/Processing/Extensions/RotateFlipExtensions.cs index 4d5d90c30e..0e4ad4066c 100644 --- a/src/ImageSharp/Processing/Extensions/RotateFlipExtensions.cs +++ b/src/ImageSharp/Processing/Extensions/RotateFlipExtensions.cs @@ -1,8 +1,6 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -using SixLabors.ImageSharp.PixelFormats; - namespace SixLabors.ImageSharp.Processing { /// diff --git a/src/ImageSharp/Processing/Extensions/SaturateExtensions.cs b/src/ImageSharp/Processing/Extensions/SaturateExtensions.cs index e9ba820b6c..a94a9a407d 100644 --- a/src/ImageSharp/Processing/Extensions/SaturateExtensions.cs +++ b/src/ImageSharp/Processing/Extensions/SaturateExtensions.cs @@ -1,7 +1,6 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing.Processors.Filters; using SixLabors.Primitives; diff --git a/src/ImageSharp/Processing/Extensions/SepiaExtensions.cs b/src/ImageSharp/Processing/Extensions/SepiaExtensions.cs index 5ee5151fae..df32307f47 100644 --- a/src/ImageSharp/Processing/Extensions/SepiaExtensions.cs +++ b/src/ImageSharp/Processing/Extensions/SepiaExtensions.cs @@ -1,7 +1,6 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing.Processors.Filters; using SixLabors.Primitives; diff --git a/src/ImageSharp/Processing/Processors/Binarization/BinaryErrorDiffusionProcessor.cs b/src/ImageSharp/Processing/Processors/Binarization/BinaryErrorDiffusionProcessor.cs index 8129836641..840d1c1f46 100644 --- a/src/ImageSharp/Processing/Processors/Binarization/BinaryErrorDiffusionProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Binarization/BinaryErrorDiffusionProcessor.cs @@ -1,8 +1,6 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -using System; - using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing.Processors.Dithering; diff --git a/src/ImageSharp/Processing/Processors/Binarization/BinaryThresholdProcessor.cs b/src/ImageSharp/Processing/Processors/Binarization/BinaryThresholdProcessor.cs index 83701aa8a2..7f00d0219d 100644 --- a/src/ImageSharp/Processing/Processors/Binarization/BinaryThresholdProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Binarization/BinaryThresholdProcessor.cs @@ -1,8 +1,6 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -using System; - using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Processing.Processors.Binarization diff --git a/src/ImageSharp/Processing/Processors/Convolution/BokehBlurProcessor.cs b/src/ImageSharp/Processing/Processors/Convolution/BokehBlurProcessor.cs new file mode 100644 index 0000000000..7a750bdd4a --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Convolution/BokehBlurProcessor.cs @@ -0,0 +1,121 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.PixelFormats; + +namespace SixLabors.ImageSharp.Processing.Processors.Convolution +{ + /// + /// Applies bokeh blur processing to the image. + /// + public sealed class BokehBlurProcessor : IImageProcessor + { + /// + /// The default radius used by the parameterless constructor. + /// + public const int DefaultRadius = 32; + + /// + /// The default component count used by the parameterless constructor. + /// + public const int DefaultComponents = 2; + + /// + /// The default gamma used by the parameterless constructor. + /// + public const float DefaultGamma = 3F; + + /// + /// The default execution mode used by the parameterless constructor. + /// + public const BokehBlurExecutionMode DefaultExecutionMode = BokehBlurExecutionMode.PreferLowMemoryUsage; + + /// + /// Initializes a new instance of the class. + /// + public BokehBlurProcessor() + : this(DefaultRadius, DefaultComponents, DefaultGamma, DefaultExecutionMode) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The execution mode to use when applying the processor. + /// + public BokehBlurProcessor(BokehBlurExecutionMode executionMode) + : this(DefaultRadius, DefaultComponents, DefaultGamma, executionMode) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The 'radius' value representing the size of the area to sample. + /// + /// + /// The number of components to use to approximate the original 2D bokeh blur convolution kernel. + /// + /// + /// The gamma highlight factor to use to further process the image. + /// + public BokehBlurProcessor(int radius, int components, float gamma) + : this(radius, components, gamma, DefaultExecutionMode) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The 'radius' value representing the size of the area to sample. + /// + /// + /// The number of components to use to approximate the original 2D bokeh blur convolution kernel. + /// + /// + /// The gamma highlight factor to use to further process the image. + /// + /// + /// The execution mode to use when applying the processor. + /// + public BokehBlurProcessor(int radius, int components, float gamma, BokehBlurExecutionMode executionMode) + { + Guard.MustBeGreaterThanOrEqualTo(gamma, 1, nameof(gamma)); + + this.Radius = radius; + this.Components = components; + this.Gamma = gamma; + this.ExecutionMode = executionMode; + } + + /// + /// Gets the radius. + /// + public int Radius { get; } + + /// + /// Gets the number of components. + /// + public int Components { get; } + + /// + /// Gets the gamma highlight factor to use when applying the effect. + /// + public float Gamma { get; } + + /// + /// Gets the exection mode to use when applying the effect. + /// + public BokehBlurExecutionMode ExecutionMode { get; } + + /// + public IImageProcessor CreatePixelSpecificProcessor() + where TPixel : struct, IPixel + { + return new BokehBlurProcessor(this); + } + } +} diff --git a/src/ImageSharp/Processing/Processors/Convolution/BokehBlurProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Convolution/BokehBlurProcessor{TPixel}.cs new file mode 100644 index 0000000000..a083026c37 --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Convolution/BokehBlurProcessor{TPixel}.cs @@ -0,0 +1,585 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Numerics; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +using SixLabors.ImageSharp.Memory; +using SixLabors.ImageSharp.ParallelUtils; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Primitives; +using SixLabors.ImageSharp.Processing.Processors.Convolution.Parameters; +using SixLabors.Memory; +using SixLabors.Primitives; + +namespace SixLabors.ImageSharp.Processing.Processors.Convolution +{ + /// + /// Applies bokeh blur processing to the image. + /// + /// The pixel format. + /// This processor is based on the code from Mike Pound, see github.com/mikepound/convolve. + internal class BokehBlurProcessor : ImageProcessor + where TPixel : struct, IPixel + { + /// + /// The kernel radius. + /// + private readonly int radius; + + /// + /// The gamma highlight factor to use when applying the effect + /// + private readonly float gamma; + + /// + /// The execution mode to use when applying the effect + /// + private readonly BokehBlurExecutionMode executionMode; + + /// + /// The maximum size of the kernel in either direction + /// + private readonly int kernelSize; + + /// + /// The number of components to use when applying the bokeh blur + /// + private readonly int componentsCount; + + /// + /// The kernel parameters to use for the current instance (a: X, b: Y, A: Z, B: W) + /// + private readonly Vector4[] kernelParameters; + + /// + /// The kernel components for the current instance + /// + private readonly Complex64[][] kernels; + + /// + /// The scaling factor for kernel values + /// + private readonly float kernelsScale; + + /// + /// The mapping of initialized complex kernels and parameters, to speed up the initialization of new instances + /// + private static readonly ConcurrentDictionary Cache = new ConcurrentDictionary(); + + /// + /// Initializes a new instance of the class. + /// + /// The defining the processor parameters. + public BokehBlurProcessor(BokehBlurProcessor definition) + { + this.radius = definition.Radius; + this.kernelSize = (this.radius * 2) + 1; + this.componentsCount = definition.Components; + this.gamma = definition.Gamma; + this.executionMode = definition.ExecutionMode; + + // Reuse the initialized values from the cache, if possible + var parameters = new BokehBlurParameters(this.radius, this.componentsCount); + if (Cache.TryGetValue(parameters, out BokehBlurKernelData info)) + { + this.kernelParameters = info.Parameters; + this.kernelsScale = info.Scale; + this.kernels = info.Kernels; + } + else + { + // Initialize the complex kernels and parameters with the current arguments + (this.kernelParameters, this.kernelsScale) = this.GetParameters(); + this.kernels = this.CreateComplexKernels(); + this.NormalizeKernels(); + + // Store them in the cache for future use + Cache.TryAdd(parameters, new BokehBlurKernelData(this.kernelParameters, this.kernelsScale, this.kernels)); + } + } + + /// + /// Gets the complex kernels to use to apply the blur for the current instance + /// + public IReadOnlyList Kernels => this.kernels; + + /// + /// Gets the kernel parameters used to compute the pixel values from each complex pixel + /// + public IReadOnlyList KernelParameters => this.kernelParameters; + + /// + /// Gets the kernel scales to adjust the component values in each kernel + /// + private static IReadOnlyList KernelScales { get; } = new[] { 1.4f, 1.2f, 1.2f, 1.2f, 1.2f, 1.2f }; + + /// + /// Gets the available bokeh blur kernel parameters + /// + private static IReadOnlyList KernelComponents { get; } = new[] + { + // 1 component + new[] { new Vector4(0.862325f, 1.624835f, 0.767583f, 1.862321f) }, + + // 2 components + new[] + { + new Vector4(0.886528f, 5.268909f, 0.411259f, -0.548794f), + new Vector4(1.960518f, 1.558213f, 0.513282f, 4.56111f) + }, + + // 3 components + new[] + { + new Vector4(2.17649f, 5.043495f, 1.621035f, -2.105439f), + new Vector4(1.019306f, 9.027613f, -0.28086f, -0.162882f), + new Vector4(2.81511f, 1.597273f, -0.366471f, 10.300301f) + }, + + // 4 components + new[] + { + new Vector4(4.338459f, 1.553635f, -5.767909f, 46.164397f), + new Vector4(3.839993f, 4.693183f, 9.795391f, -15.227561f), + new Vector4(2.791880f, 8.178137f, -3.048324f, 0.302959f), + new Vector4(1.342190f, 12.328289f, 0.010001f, 0.244650f) + }, + + // 5 components + new[] + { + new Vector4(4.892608f, 1.685979f, -22.356787f, 85.91246f), + new Vector4(4.71187f, 4.998496f, 35.918936f, -28.875618f), + new Vector4(4.052795f, 8.244168f, -13.212253f, -1.578428f), + new Vector4(2.929212f, 11.900859f, 0.507991f, 1.816328f), + new Vector4(1.512961f, 16.116382f, 0.138051f, -0.01f) + }, + + // 6 components + new[] + { + new Vector4(5.143778f, 2.079813f, -82.326596f, 111.231024f), + new Vector4(5.612426f, 6.153387f, 113.878661f, 58.004879f), + new Vector4(5.982921f, 9.802895f, 39.479083f, -162.028887f), + new Vector4(6.505167f, 11.059237f, -71.286026f, 95.027069f), + new Vector4(3.869579f, 14.81052f, 1.405746f, -3.704914f), + new Vector4(2.201904f, 19.032909f, -0.152784f, -0.107988f) + } + }; + + /// + /// Gets the kernel parameters and scaling factor for the current count value in the current instance + /// + private (Vector4[] Parameters, float Scale) GetParameters() + { + // Prepare the kernel components + int index = Math.Max(0, Math.Min(this.componentsCount - 1, KernelComponents.Count)); + return (KernelComponents[index], KernelScales[index]); + } + + /// + /// Creates the collection of complex 1D kernels with the specified parameters + /// + private Complex64[][] CreateComplexKernels() + { + var kernels = new Complex64[this.kernelParameters.Length][]; + ref Vector4 baseRef = ref MemoryMarshal.GetReference(this.kernelParameters.AsSpan()); + for (int i = 0; i < this.kernelParameters.Length; i++) + { + ref Vector4 paramsRef = ref Unsafe.Add(ref baseRef, i); + kernels[i] = this.CreateComplex1DKernel(paramsRef.X, paramsRef.Y); + } + + return kernels; + } + + /// + /// Creates a complex 1D kernel with the specified parameters + /// + /// The exponential parameter for each complex component + /// The angle component for each complex component + private Complex64[] CreateComplex1DKernel(float a, float b) + { + var kernel = new Complex64[this.kernelSize]; + ref Complex64 baseRef = ref MemoryMarshal.GetReference(kernel.AsSpan()); + int r = this.radius, n = -r; + + for (int i = 0; i < this.kernelSize; i++, n++) + { + // Incrementally compute the range values + float value = n * this.kernelsScale * (1f / r); + value *= value; + + // Fill in the complex kernel values + Unsafe.Add(ref baseRef, i) = new Complex64( + MathF.Exp(-a * value) * MathF.Cos(b * value), + MathF.Exp(-a * value) * MathF.Sin(b * value)); + } + + return kernel; + } + + /// + /// Normalizes the kernels with respect to A * real + B * imaginary + /// + private void NormalizeKernels() + { + // Calculate the complex weighted sum + float total = 0; + Span kernelsSpan = this.kernels.AsSpan(); + ref Complex64[] baseKernelsRef = ref MemoryMarshal.GetReference(kernelsSpan); + ref Vector4 baseParamsRef = ref MemoryMarshal.GetReference(this.kernelParameters.AsSpan()); + + for (int i = 0; i < this.kernelParameters.Length; i++) + { + ref Complex64[] kernelRef = ref Unsafe.Add(ref baseKernelsRef, i); + int length = kernelRef.Length; + ref Complex64 valueRef = ref kernelRef[0]; + ref Vector4 paramsRef = ref Unsafe.Add(ref baseParamsRef, i); + + for (int j = 0; j < length; j++) + { + for (int k = 0; k < length; k++) + { + ref Complex64 jRef = ref Unsafe.Add(ref valueRef, j); + ref Complex64 kRef = ref Unsafe.Add(ref valueRef, k); + total += + (paramsRef.Z * ((jRef.Real * kRef.Real) - (jRef.Imaginary * kRef.Imaginary))) + + (paramsRef.W * ((jRef.Real * kRef.Imaginary) + (jRef.Imaginary * kRef.Real))); + } + } + } + + // Normalize the kernels + float scalar = 1f / MathF.Sqrt(total); + for (int i = 0; i < kernelsSpan.Length; i++) + { + ref Complex64[] kernelsRef = ref Unsafe.Add(ref baseKernelsRef, i); + int length = kernelsRef.Length; + ref Complex64 valueRef = ref kernelsRef[0]; + + for (int j = 0; j < length; j++) + { + Unsafe.Add(ref valueRef, j) *= scalar; + } + } + } + + /// + protected override void OnFrameApply(ImageFrame source, Rectangle sourceRectangle, Configuration configuration) + { + // Preliminary gamma highlight pass + this.ApplyGammaExposure(source.PixelBuffer, sourceRectangle, configuration); + + // Create a 0-filled buffer to use to store the result of the component convolutions + using (Buffer2D processing = configuration.MemoryAllocator.Allocate2D(source.Size(), AllocationOptions.Clean)) + { + if (this.executionMode == BokehBlurExecutionMode.PreferLowMemoryUsage) + { + // Memory usage priority: allocate a shared buffer and execute the second convolution in sequential mode + using (Buffer2D buffer = configuration.MemoryAllocator.Allocate2D(source.Width, source.Height + this.radius)) + using (Buffer2D firstPassBuffer = buffer.Slice(this.radius, source.Height)) + using (Buffer2D secondPassBuffer = buffer.Slice(0, source.Height)) + { + this.OnFrameApplyCore(source, sourceRectangle, configuration, processing, firstPassBuffer, secondPassBuffer); + } + } + else + { + // Performance priority: allocate two independent buffers and execute both convolutions in parallel mode + using (Buffer2D firstPassValues = configuration.MemoryAllocator.Allocate2D(source.Size())) + using (Buffer2D secondPassBuffer = configuration.MemoryAllocator.Allocate2D(source.Size())) + { + this.OnFrameApplyCore(source, sourceRectangle, configuration, processing, firstPassValues, secondPassBuffer); + } + } + + // Apply the inverse gamma exposure pass, and write the final pixel data + this.ApplyInverseGammaExposure(source.PixelBuffer, processing, sourceRectangle, configuration); + } + } + + /// + /// Computes and aggregates the convolution for each complex kernel component in the processor. + /// + /// The source image. Cannot be null. + /// The structure that specifies the portion of the image object to draw. + /// The configuration. + /// The buffer with the raw pixel data to use to aggregate the results of each convolution. + /// The complex buffer to use for the first 1D convolution pass for each kernel. + /// The complex buffer to use for the second 1D convolution pass for each kernel. + private void OnFrameApplyCore( + ImageFrame source, + Rectangle sourceRectangle, + Configuration configuration, + Buffer2D processingBuffer, + Buffer2D firstPassBuffer, + Buffer2D secondPassBuffer) + { + // Perform two 1D convolutions for each component in the current instance + ref Complex64[] baseRef = ref MemoryMarshal.GetReference(this.kernels.AsSpan()); + for (int i = 0; i < this.kernels.Length; i++) + { + // Compute the resulting complex buffer for the current component + var interest = Rectangle.Intersect(sourceRectangle, source.Bounds()); + Complex64[] kernel = Unsafe.Add(ref baseRef, i); + this.ApplyConvolution(firstPassBuffer, source.PixelBuffer, interest, kernel, configuration); + this.ApplyConvolution(secondPassBuffer, firstPassBuffer, interest, kernel, configuration); + + // Add the results of the convolution with the current kernel + Vector4 parameters = this.kernelParameters[i]; + this.SumProcessingPartials(processingBuffer, secondPassBuffer, sourceRectangle, configuration, parameters.Z, parameters.W); + } + } + + /// + /// Applies the process to the specified portion of the specified at the specified location + /// and with the specified size. + /// + /// The target values to use to store the results. + /// The source pixels. Cannot be null. + /// + /// The structure that specifies the portion of the image object to draw. + /// + /// The 1D kernel. + /// The + private void ApplyConvolution( + Buffer2D targetValues, + Buffer2D sourcePixels, + Rectangle sourceRectangle, + Complex64[] kernel, + Configuration configuration) + { + int startY = sourceRectangle.Y; + int endY = sourceRectangle.Bottom; + int startX = sourceRectangle.X; + int endX = sourceRectangle.Right; + int maxY = endY - 1; + int maxX = endX - 1; + + var workingRectangle = Rectangle.FromLTRB(startX, startY, endX, endY); + int width = workingRectangle.Width; + + ParallelHelper.IterateRows( + workingRectangle, + configuration, + rows => + { + for (int y = rows.Min; y < rows.Max; y++) + { + Span targetRowSpan = targetValues.GetRowSpan(y).Slice(startX); + + for (int x = 0; x < width; x++) + { + Buffer2DUtils.Convolve4(kernel, sourcePixels, targetRowSpan, y, x, startY, maxY, startX, maxX); + } + } + }); + } + + /// + /// Applies the process to the specified portion of the specified buffer at the specified location + /// and with the specified size. + /// + /// The target values to use to store the results. + /// The source complex values. Cannot be null. + /// + /// The structure that specifies the portion of the image object to draw. + /// + /// The 1D kernel. + /// The + private void ApplyConvolution( + Buffer2D targetValues, + Buffer2D sourceValues, + Rectangle sourceRectangle, + Complex64[] kernel, + Configuration configuration) + { + int startY = sourceRectangle.Y; + int endY = sourceRectangle.Bottom; + int startX = sourceRectangle.X; + int endX = sourceRectangle.Right; + int maxY = endY - 1; + int maxX = endX - 1; + + var workingRectangle = Rectangle.FromLTRB(startX, startY, endX, endY); + int width = workingRectangle.Width; + + if (this.executionMode == BokehBlurExecutionMode.PreferLowMemoryUsage) + { + configuration = configuration.Clone(); + configuration.MaxDegreeOfParallelism = 1; + } + + ParallelHelper.IterateRows( + workingRectangle, + configuration, + rows => + { + for (int y = rows.Min; y < rows.Max; y++) + { + Span targetRowSpan = targetValues.GetRowSpan(y).Slice(startX); + + for (int x = 0; x < width; x++) + { + Buffer2DUtils.Convolve4(kernel, sourceValues, targetRowSpan, y, x, startY, maxY, startX, maxX); + } + } + }); + } + + /// + /// Applies the gamma correction/highlight to the input pixel buffer. + /// + /// The target pixel buffer to adjust. + /// + /// The structure that specifies the portion of the image object to draw. + /// + /// The + private void ApplyGammaExposure( + Buffer2D targetPixels, + Rectangle sourceRectangle, + Configuration configuration) + { + int startY = sourceRectangle.Y; + int endY = sourceRectangle.Bottom; + int startX = sourceRectangle.X; + int endX = sourceRectangle.Right; + + var workingRectangle = Rectangle.FromLTRB(startX, startY, endX, endY); + int width = workingRectangle.Width; + float exp = this.gamma; + + ParallelHelper.IterateRowsWithTempBuffer( + workingRectangle, + configuration, + (rows, vectorBuffer) => + { + Span vectorSpan = vectorBuffer.Span; + int length = vectorSpan.Length; + + for (int y = rows.Min; y < rows.Max; y++) + { + Span targetRowSpan = targetPixels.GetRowSpan(y).Slice(startX); + PixelOperations.Instance.ToVector4(configuration, targetRowSpan.Slice(0, length), vectorSpan, PixelConversionModifiers.Premultiply); + ref Vector4 baseRef = ref MemoryMarshal.GetReference(vectorSpan); + + for (int x = 0; x < width; x++) + { + ref Vector4 v = ref Unsafe.Add(ref baseRef, x); + v.X = MathF.Pow(v.X, exp); + v.Y = MathF.Pow(v.Y, exp); + v.Z = MathF.Pow(v.Z, exp); + } + + PixelOperations.Instance.FromVector4Destructive(configuration, vectorSpan.Slice(0, length), targetRowSpan); + } + }); + } + + /// + /// Applies the inverse gamma correction/highlight pass, and converts the input buffer into pixel values. + /// + /// The target pixels to apply the process to. + /// The source values. Cannot be null. + /// + /// The structure that specifies the portion of the image object to draw. + /// + /// The + private void ApplyInverseGammaExposure( + Buffer2D targetPixels, + Buffer2D sourceValues, + Rectangle sourceRectangle, + Configuration configuration) + { + int startY = sourceRectangle.Y; + int endY = sourceRectangle.Bottom; + int startX = sourceRectangle.X; + int endX = sourceRectangle.Right; + + var workingRectangle = Rectangle.FromLTRB(startX, startY, endX, endY); + int width = workingRectangle.Width; + float expGamma = 1 / this.gamma; + + ParallelHelper.IterateRows( + workingRectangle, + configuration, + rows => + { + Vector4 low = Vector4.Zero; + var high = new Vector4(float.PositiveInfinity, float.PositiveInfinity, float.PositiveInfinity, float.PositiveInfinity); + + for (int y = rows.Min; y < rows.Max; y++) + { + Span targetPixelSpan = targetPixels.GetRowSpan(y).Slice(startX); + Span sourceRowSpan = sourceValues.GetRowSpan(y).Slice(startX); + ref Vector4 sourceRef = ref MemoryMarshal.GetReference(sourceRowSpan); + + for (int x = 0; x < width; x++) + { + ref Vector4 v = ref Unsafe.Add(ref sourceRef, x); + var clamp = Vector4.Clamp(v, low, high); + v.X = MathF.Pow(clamp.X, expGamma); + v.Y = MathF.Pow(clamp.Y, expGamma); + v.Z = MathF.Pow(clamp.Z, expGamma); + } + + PixelOperations.Instance.FromVector4Destructive(configuration, sourceRowSpan.Slice(0, width), targetPixelSpan, PixelConversionModifiers.Premultiply); + } + }); + } + + /// + /// Applies the process to the specified portion of the specified at the specified location + /// and with the specified size. + /// + /// The target instance to use to store the results. + /// The source complex pixels. Cannot be null. + /// + /// The structure that specifies the portion of the image object to draw. + /// + /// The + /// The weight factor for the real component of the complex pixel values. + /// The weight factor for the imaginary component of the complex pixel values. + private void SumProcessingPartials( + Buffer2D targetValues, + Buffer2D sourceValues, + Rectangle sourceRectangle, + Configuration configuration, + float z, + float w) + { + int startY = sourceRectangle.Y; + int endY = sourceRectangle.Bottom; + int startX = sourceRectangle.X; + int endX = sourceRectangle.Right; + + var workingRectangle = Rectangle.FromLTRB(startX, startY, endX, endY); + int width = workingRectangle.Width; + + ParallelHelper.IterateRows( + workingRectangle, + configuration, + rows => + { + for (int y = rows.Min; y < rows.Max; y++) + { + Span targetRowSpan = targetValues.GetRowSpan(y).Slice(startX); + Span sourceRowSpan = sourceValues.GetRowSpan(y).Slice(startX); + ref Vector4 baseTargetRef = ref MemoryMarshal.GetReference(targetRowSpan); + ref ComplexVector4 baseSourceRef = ref MemoryMarshal.GetReference(sourceRowSpan); + + for (int x = 0; x < width; x++) + { + Unsafe.Add(ref baseTargetRef, x) += Unsafe.Add(ref baseSourceRef, x).WeightedSum(z, w); + } + } + }); + } + } +} diff --git a/src/ImageSharp/Processing/Processors/Convolution/BoxBlurProcessor.cs b/src/ImageSharp/Processing/Processors/Convolution/BoxBlurProcessor.cs index 4e56e75d39..726947ac91 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/BoxBlurProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/BoxBlurProcessor.cs @@ -1,8 +1,7 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Primitives; namespace SixLabors.ImageSharp.Processing.Processors.Convolution { @@ -47,4 +46,4 @@ public IImageProcessor CreatePixelSpecificProcessor() return new BoxBlurProcessor(this); } } -} \ No newline at end of file +} diff --git a/src/ImageSharp/Processing/Processors/Convolution/GaussianBlurProcessor.cs b/src/ImageSharp/Processing/Processors/Convolution/GaussianBlurProcessor.cs index 764f4ca517..8504db1617 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/GaussianBlurProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/GaussianBlurProcessor.cs @@ -2,7 +2,6 @@ // Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Primitives; namespace SixLabors.ImageSharp.Processing.Processors.Convolution { diff --git a/src/ImageSharp/Processing/Processors/Convolution/GaussianSharpenProcessor.cs b/src/ImageSharp/Processing/Processors/Convolution/GaussianSharpenProcessor.cs index 23282af36d..75acc90e0c 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/GaussianSharpenProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/GaussianSharpenProcessor.cs @@ -1,7 +1,6 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -using System; using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Processing.Processors.Convolution diff --git a/src/ImageSharp/Processing/Processors/Convolution/Laplacian3x3Processor.cs b/src/ImageSharp/Processing/Processors/Convolution/Laplacian3x3Processor.cs index 9c9488fec0..ab6658f3b2 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/Laplacian3x3Processor.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/Laplacian3x3Processor.cs @@ -1,8 +1,6 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -using SixLabors.ImageSharp.PixelFormats; - namespace SixLabors.ImageSharp.Processing.Processors.Convolution { /// diff --git a/src/ImageSharp/Processing/Processors/Convolution/Parameters/BokehBlurKernelData.cs b/src/ImageSharp/Processing/Processors/Convolution/Parameters/BokehBlurKernelData.cs new file mode 100644 index 0000000000..4338bcf6b9 --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Convolution/Parameters/BokehBlurKernelData.cs @@ -0,0 +1,43 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System.Numerics; + +using SixLabors.ImageSharp.Primitives; + +namespace SixLabors.ImageSharp.Processing.Processors.Convolution.Parameters +{ + /// + /// A that contains data about a set of bokeh blur kernels + /// + internal readonly struct BokehBlurKernelData + { + /// + /// The kernel parameters to use for the current set of complex kernels + /// + public readonly Vector4[] Parameters; + + /// + /// The scaling factor for the kernel values + /// + public readonly float Scale; + + /// + /// The kernel components to apply the bokeh blur effect + /// + public readonly Complex64[][] Kernels; + + /// + /// Initializes a new instance of the struct. + /// + /// The kernel parameters + /// The kernel scale factor + /// The complex kernel components + public BokehBlurKernelData(Vector4[] parameters, float scale, Complex64[][] kernels) + { + this.Parameters = parameters; + this.Scale = scale; + this.Kernels = kernels; + } + } +} diff --git a/src/ImageSharp/Processing/Processors/Convolution/Parameters/BokehBlurParameters.cs b/src/ImageSharp/Processing/Processors/Convolution/Parameters/BokehBlurParameters.cs new file mode 100644 index 0000000000..73688c5869 --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Convolution/Parameters/BokehBlurParameters.cs @@ -0,0 +1,52 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; + +namespace SixLabors.ImageSharp.Processing.Processors.Convolution.Parameters +{ + /// + /// A that contains parameters to apply a bokeh blur filter + /// + internal readonly struct BokehBlurParameters : IEquatable + { + /// + /// The size of the convolution kernel to use when applying the bokeh blur + /// + public readonly int Radius; + + /// + /// The number of complex components to use to approximate the bokeh kernel + /// + public readonly int Components; + + /// + /// Initializes a new instance of the struct. + /// + /// The size of the kernel + /// The number of kernel components + public BokehBlurParameters(int radius, int components) + { + this.Radius = radius; + this.Components = components; + } + + /// + public bool Equals(BokehBlurParameters other) + { + return this.Radius.Equals(other.Radius) && this.Components.Equals(other.Components); + } + + /// + public override bool Equals(object obj) => obj is BokehBlurParameters other && this.Equals(other); + + /// + public override int GetHashCode() + { + unchecked + { + return (this.Radius.GetHashCode() * 397) ^ this.Components.GetHashCode(); + } + } + } +} diff --git a/src/ImageSharp/Processing/Processors/Convolution/RobertsCrossProcessor.cs b/src/ImageSharp/Processing/Processors/Convolution/RobertsCrossProcessor.cs index a9db37a076..ca7d8895a8 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/RobertsCrossProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/RobertsCrossProcessor.cs @@ -1,8 +1,6 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -using SixLabors.ImageSharp.PixelFormats; - namespace SixLabors.ImageSharp.Processing.Processors.Convolution { /// diff --git a/src/ImageSharp/Processing/Processors/Convolution/RobinsonProcessor.cs b/src/ImageSharp/Processing/Processors/Convolution/RobinsonProcessor.cs index fd73789027..6f5373fae5 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/RobinsonProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/RobinsonProcessor.cs @@ -1,9 +1,6 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Primitives; - namespace SixLabors.ImageSharp.Processing.Processors.Convolution { /// diff --git a/src/ImageSharp/Processing/Processors/Convolution/ScharrProcessor.cs b/src/ImageSharp/Processing/Processors/Convolution/ScharrProcessor.cs index ec0183dc63..da76aa971c 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/ScharrProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/ScharrProcessor.cs @@ -1,8 +1,6 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -using SixLabors.ImageSharp.PixelFormats; - namespace SixLabors.ImageSharp.Processing.Processors.Convolution { /// diff --git a/src/ImageSharp/Processing/Processors/Convolution/SobelProcessor.cs b/src/ImageSharp/Processing/Processors/Convolution/SobelProcessor.cs index 3d5d1e7bf1..5fb32f4e62 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/SobelProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/SobelProcessor.cs @@ -1,9 +1,6 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -using SixLabors.ImageSharp.PixelFormats; -using SixLabors.Primitives; - namespace SixLabors.ImageSharp.Processing.Processors.Convolution { /// diff --git a/src/ImageSharp/Processing/Processors/Dithering/ErrorDiffusionPaletteProcessor.cs b/src/ImageSharp/Processing/Processors/Dithering/ErrorDiffusionPaletteProcessor.cs index e0b79c2b20..e612b4bf03 100644 --- a/src/ImageSharp/Processing/Processors/Dithering/ErrorDiffusionPaletteProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Dithering/ErrorDiffusionPaletteProcessor.cs @@ -3,8 +3,6 @@ using System; -using SixLabors.ImageSharp.Processing.Processors.Binarization; - namespace SixLabors.ImageSharp.Processing.Processors.Dithering { /// diff --git a/src/ImageSharp/Processing/Processors/Filters/GrayscaleBt709Processor.cs b/src/ImageSharp/Processing/Processors/Filters/GrayscaleBt709Processor.cs index ae6d5f6f79..71d3f9c9cc 100644 --- a/src/ImageSharp/Processing/Processors/Filters/GrayscaleBt709Processor.cs +++ b/src/ImageSharp/Processing/Processors/Filters/GrayscaleBt709Processor.cs @@ -1,9 +1,6 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -using SixLabors.ImageSharp.PixelFormats; -using SixLabors.Primitives; - namespace SixLabors.ImageSharp.Processing.Processors.Filters { /// diff --git a/src/ImageSharp/Processing/Processors/Filters/KodachromeProcessor.cs b/src/ImageSharp/Processing/Processors/Filters/KodachromeProcessor.cs index 012b10ee03..30484f0590 100644 --- a/src/ImageSharp/Processing/Processors/Filters/KodachromeProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Filters/KodachromeProcessor.cs @@ -1,8 +1,6 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -using SixLabors.ImageSharp.PixelFormats; - namespace SixLabors.ImageSharp.Processing.Processors.Filters { /// diff --git a/src/ImageSharp/Processing/Processors/Filters/OpacityProcessor.cs b/src/ImageSharp/Processing/Processors/Filters/OpacityProcessor.cs index 922ca32330..a537b8f606 100644 --- a/src/ImageSharp/Processing/Processors/Filters/OpacityProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Filters/OpacityProcessor.cs @@ -1,8 +1,6 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -using SixLabors.ImageSharp.PixelFormats; - namespace SixLabors.ImageSharp.Processing.Processors.Filters { /// diff --git a/src/ImageSharp/Processing/Processors/Quantization/WebSafePaletteQuantizer.cs b/src/ImageSharp/Processing/Processors/Quantization/WebSafePaletteQuantizer.cs index 3f6dfde281..c912572f0e 100644 --- a/src/ImageSharp/Processing/Processors/Quantization/WebSafePaletteQuantizer.cs +++ b/src/ImageSharp/Processing/Processors/Quantization/WebSafePaletteQuantizer.cs @@ -1,7 +1,6 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing.Processors.Dithering; namespace SixLabors.ImageSharp.Processing.Processors.Quantization diff --git a/src/ImageSharp/Processing/Processors/Quantization/WernerPaletteQuantizer.cs b/src/ImageSharp/Processing/Processors/Quantization/WernerPaletteQuantizer.cs index d659ecabf7..cd320a9a36 100644 --- a/src/ImageSharp/Processing/Processors/Quantization/WernerPaletteQuantizer.cs +++ b/src/ImageSharp/Processing/Processors/Quantization/WernerPaletteQuantizer.cs @@ -1,7 +1,6 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing.Processors.Dithering; namespace SixLabors.ImageSharp.Processing.Processors.Quantization diff --git a/src/ImageSharp/Processing/Processors/Transforms/AutoOrientProcessor.cs b/src/ImageSharp/Processing/Processors/Transforms/AutoOrientProcessor.cs index 9bbbba843c..3eb0d998a2 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/AutoOrientProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/AutoOrientProcessor.cs @@ -2,7 +2,6 @@ // Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Processing.Processors; namespace SixLabors.ImageSharp.Processing.Processors.Transforms { diff --git a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernel.cs b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernel.cs index dce4e70d62..14bf552b9d 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernel.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernel.cs @@ -4,7 +4,6 @@ using System; using System.Numerics; using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; namespace SixLabors.ImageSharp.Processing.Processors.Transforms { diff --git a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernelMap.PeriodicKernelMap.cs b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernelMap.PeriodicKernelMap.cs index 4b81aaa64e..6d6e22a6a5 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernelMap.PeriodicKernelMap.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernelMap.PeriodicKernelMap.cs @@ -1,8 +1,6 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -using System; - using SixLabors.Memory; namespace SixLabors.ImageSharp.Processing.Processors.Transforms diff --git a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeProcessor{TPixel}.cs index cb30067401..99178b34cc 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeProcessor{TPixel}.cs @@ -2,12 +2,8 @@ // Licensed under the Apache License, Version 2.0. using System; -using System.Buffers; using System.Collections.Generic; using System.Linq; -using System.Numerics; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Memory; diff --git a/src/ImageSharp/Processing/Processors/Transforms/SkewProcessor.cs b/src/ImageSharp/Processing/Processors/Transforms/SkewProcessor.cs index cb73bb66c2..4b87d6d2c1 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/SkewProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/SkewProcessor.cs @@ -3,7 +3,6 @@ using System.Numerics; -using SixLabors.ImageSharp.PixelFormats; using SixLabors.Primitives; namespace SixLabors.ImageSharp.Processing.Processors.Transforms diff --git a/src/ImageSharp/Processing/Processors/Transforms/TransformProcessor.cs b/src/ImageSharp/Processing/Processors/Transforms/TransformProcessor.cs index ef508549ac..f6d3299fcd 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/TransformProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/TransformProcessor.cs @@ -2,7 +2,6 @@ // Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Processing.Processors; using SixLabors.Primitives; namespace SixLabors.ImageSharp.Processing.Processors.Transforms diff --git a/src/ImageSharp/Properties/AssemblyInfo.cs b/src/ImageSharp/Properties/AssemblyInfo.cs index 2862b45851..225de354ae 100644 --- a/src/ImageSharp/Properties/AssemblyInfo.cs +++ b/src/ImageSharp/Properties/AssemblyInfo.cs @@ -1,8 +1,6 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -using System.Runtime.CompilerServices; - // Redundant suppressing of SA1413 for Rider. [assembly: System.Diagnostics.CodeAnalysis.SuppressMessage( diff --git a/tests/ImageSharp.Tests/Processing/Processors/Convolution/BokehBlurTest.cs b/tests/ImageSharp.Tests/Processing/Processors/Convolution/BokehBlurTest.cs new file mode 100644 index 0000000000..3796c51a3b --- /dev/null +++ b/tests/ImageSharp.Tests/Processing/Processors/Convolution/BokehBlurTest.cs @@ -0,0 +1,156 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Text.RegularExpressions; + +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Primitives; +using SixLabors.ImageSharp.Processing; +using SixLabors.ImageSharp.Processing.Processors.Convolution; +using SixLabors.Primitives; + +using Xunit; +using Xunit.Abstractions; + +namespace SixLabors.ImageSharp.Tests.Processing.Processors.Convolution +{ + public class BokehBlurTest + { + private static readonly string Components10x2 = @" + [[ 0.00451261+0.0165137j 0.02161237-0.00299122j 0.00387479-0.02682816j + -0.02752798-0.01788438j -0.03553877+0.0154543j -0.01428268+0.04224722j + 0.01747482+0.04687464j 0.04243676+0.03451751j 0.05564306+0.01742537j + 0.06040984+0.00459225j 0.06136251+0.0j 0.06040984+0.00459225j + 0.05564306+0.01742537j 0.04243676+0.03451751j 0.01747482+0.04687464j + -0.01428268+0.04224722j -0.03553877+0.0154543j -0.02752798-0.01788438j + 0.00387479-0.02682816j 0.02161237-0.00299122j 0.00451261+0.0165137j ]] + [[-0.00227282+0.002851j -0.00152245+0.00604545j 0.00135338+0.00998296j + 0.00698622+0.01370844j 0.0153483+0.01605112j 0.02565295+0.01611732j + 0.03656958+0.01372368j 0.04662725+0.00954624j 0.05458942+0.00491277j + 0.05963937+0.00133843j 0.06136251+0.0j 0.05963937+0.00133843j + 0.05458942+0.00491277j 0.04662725+0.00954624j 0.03656958+0.01372368j + 0.02565295+0.01611732j 0.0153483+0.01605112j 0.00698622+0.01370844j + 0.00135338+0.00998296j -0.00152245+0.00604545j -0.00227282+0.002851j ]]"; + + [Fact] + public void VerifyComplexComponents() + { + // Get the saved components + var components = new List(); + foreach (Match match in Regex.Matches(Components10x2, @"\[\[(.*?)\]\]", RegexOptions.Singleline)) + { + string[] values = match.Groups[1].Value.Trim().Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries); + Complex64[] component = values.Select( + value => + { + Match pair = Regex.Match(value, @"([+-]?\d+\.\d+)([+-]?\d+\.\d+)j"); + return new Complex64( + float.Parse(pair.Groups[1].Value, CultureInfo.InvariantCulture), + float.Parse(pair.Groups[2].Value, CultureInfo.InvariantCulture)); + }).ToArray(); + components.Add(component); + } + + // Make sure the kernel components are the same + var definition = new BokehBlurProcessor(10, BokehBlurProcessor.DefaultComponents, BokehBlurProcessor.DefaultGamma); + var processor = new BokehBlurProcessor(definition); + Assert.Equal(components.Count, processor.Kernels.Count); + foreach ((Complex64[] a, Complex64[] b) in components.Zip(processor.Kernels, (a, b) => (a, b))) + { + Span spanA = a.AsSpan(), spanB = b.AsSpan(); + Assert.Equal(spanA.Length, spanB.Length); + for (int i = 0; i < spanA.Length; i++) + { + Assert.True(Math.Abs(Math.Abs(spanA[i].Real) - Math.Abs(spanB[i].Real)) < 0.0001f); + Assert.True(Math.Abs(Math.Abs(spanA[i].Imaginary) - Math.Abs(spanB[i].Imaginary)) < 0.0001f); + } + } + } + + public sealed class BokehBlurInfo : IXunitSerializable + { + public int Radius { get; set; } + + public int Components { get; set; } + + public float Gamma { get; set; } + + public void Deserialize(IXunitSerializationInfo info) + { + this.Radius = info.GetValue(nameof(this.Radius)); + this.Components = info.GetValue(nameof(this.Components)); + this.Gamma = info.GetValue(nameof(this.Gamma)); + } + + public void Serialize(IXunitSerializationInfo info) + { + info.AddValue(nameof(this.Radius), this.Radius, typeof(int)); + info.AddValue(nameof(this.Components), this.Components, typeof(int)); + info.AddValue(nameof(this.Gamma), this.Gamma, typeof(float)); + } + + public override string ToString() => $"R{this.Radius}_C{this.Components}_G{this.Gamma}"; + } + + public static readonly TheoryData BokehBlurValues = new TheoryData + { + new BokehBlurInfo { Radius = 8, Components = 1, Gamma = 1 }, + new BokehBlurInfo { Radius = 16, Components = 1, Gamma = 3 }, + new BokehBlurInfo { Radius = 16, Components = 2, Gamma = 3 } + }; + + public static readonly string[] TestFiles = + { + TestImages.Png.CalliphoraPartial, + TestImages.Png.Bike, + TestImages.Png.BikeGrayscale, + TestImages.Png.Cross, + }; + + [Theory] + [WithFileCollection(nameof(TestFiles), nameof(BokehBlurValues), PixelTypes.Rgba32)] + [WithSolidFilledImages(nameof(BokehBlurValues), 50, 50, "Red", PixelTypes.Rgba32)] + [WithTestPatternImages(nameof(BokehBlurValues), 200, 100, PixelTypes.Rgba32)] + [WithTestPatternImages(nameof(BokehBlurValues), 23, 31, PixelTypes.Rgba32)] + [WithTestPatternImages(nameof(BokehBlurValues), 30, 20, PixelTypes.Rgba32)] + public void BokehBlurFilterProcessor(TestImageProvider provider, BokehBlurInfo value) + where TPixel : struct, IPixel + { + provider.RunValidatingProcessorTest( + x => x.BokehBlur(value.Radius, value.Components, value.Gamma), + testOutputDetails: value.ToString(), + appendPixelTypeToFileName: false); + } + + [Theory] + [WithTestPatternImages(200, 200, PixelTypes.Bgr24 | PixelTypes.Bgra32 | PixelTypes.Gray8)] + public void BokehBlurFilterProcessor_WorksWithAllPixelTypes(TestImageProvider provider) + where TPixel : struct, IPixel + { + provider.RunValidatingProcessorTest( + x => x.BokehBlur(8, 2, 3), + appendSourceFileOrDescription: false); + } + + + [Theory] + [WithFileCollection(nameof(TestFiles), nameof(BokehBlurValues), PixelTypes.Rgba32)] + public void BokehBlurFilterProcessor_Bounded(TestImageProvider provider, BokehBlurInfo value) + where TPixel : struct, IPixel + { + provider.RunValidatingProcessorTest( + x => + { + Size size = x.GetCurrentSize(); + var bounds = new Rectangle(10, 10, size.Width / 2, size.Height / 2); + x.BokehBlur(value.Radius, value.Components, value.Gamma, bounds); + }, + testOutputDetails: value.ToString(), + appendPixelTypeToFileName: false); + } + } +} diff --git a/tests/Images/External b/tests/Images/External index acc32594c1..36f39bc624 160000 --- a/tests/Images/External +++ b/tests/Images/External @@ -1 +1 @@ -Subproject commit acc32594c125656840f8a17e69b0ebb49a370fa6 +Subproject commit 36f39bc624f8a49caf512077bf70cab30c2e5fb4 diff --git a/tests/Images/Input/Jpg/baseline/JpegSnoopReports/Floorplan.jpg.txt b/tests/Images/Input/Jpg/baseline/JpegSnoopReports/Floorplan.jpg.txt index 3afec1c893..a557d16c13 100644 --- a/tests/Images/Input/Jpg/baseline/JpegSnoopReports/Floorplan.jpg.txt +++ b/tests/Images/Input/Jpg/baseline/JpegSnoopReports/Floorplan.jpg.txt @@ -63,8 +63,8 @@ Start Offset: 0x00000000 Length = 12772 Identifier = [http://ns.adobe.com/xap/1.0/] XMP = - | - |Windows Photo Editor 10.0.10011.163842016-01-02T19:22:28 + | + |Windows Photo Editor 10.0.10011.163842016-01-02T19:22:28 *** Marker: DQT (xFFDB) *** Define a Quantization Table. diff --git a/tests/Images/Input/Jpg/baseline/JpegSnoopReports/badrst.jpg.txt b/tests/Images/Input/Jpg/baseline/JpegSnoopReports/badrst.jpg.txt index d7c49652e3..3ec02b50d9 100644 --- a/tests/Images/Input/Jpg/baseline/JpegSnoopReports/badrst.jpg.txt +++ b/tests/Images/Input/Jpg/baseline/JpegSnoopReports/badrst.jpg.txt @@ -54,8 +54,8 @@ Start Offset: 0x00000000 Length = 2464 Identifier = [http://ns.adobe.com/xap/1.0/] XMP = - | - |2016-02-28T11:17:08.057 + | + |2016-02-28T11:17:08.057 *** Marker: DQT (xFFDB) *** Define a Quantization Table.