Browse Source

Merge branch 'main' into heic-support

pull/2633/head
Ynse Hoornenborg 2 years ago
committed by GitHub
parent
commit
473adab76c
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 27
      .github/workflows/build-and-test.yml
  2. 3
      src/ImageSharp.ruleset
  3. 2
      src/ImageSharp/Formats/FormatConnectingMetadata.cs
  4. 2
      src/ImageSharp/Formats/Gif/GifEncoder.cs
  5. 100
      src/ImageSharp/Formats/Gif/GifEncoderCore.cs
  6. 43
      src/ImageSharp/Formats/IAnimatedImageEncoder.cs
  7. 50
      src/ImageSharp/Formats/IQuantizingImageEncoder.cs
  8. 2
      src/ImageSharp/Formats/ImageEncoder.cs
  9. 3
      src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs
  10. 3
      src/ImageSharp/Formats/Png/PngEncoder.cs
  11. 48
      src/ImageSharp/Formats/Png/PngEncoderCore.cs
  12. 22
      src/ImageSharp/Formats/QuantizingImageEncoder.cs
  13. 8
      src/ImageSharp/Formats/Webp/Lossless/Vp8LEncoder.cs
  14. 4
      src/ImageSharp/Formats/Webp/Lossy/Vp8Encoder.cs
  15. 20
      src/ImageSharp/Formats/Webp/Lossy/Vp8Encoding.cs
  16. 16
      src/ImageSharp/Formats/Webp/Lossy/YuvConversion.cs
  17. 4
      src/ImageSharp/Formats/Webp/WebpAnimationDecoder.cs
  18. 2
      src/ImageSharp/Formats/Webp/WebpEncoder.cs
  19. 50
      src/ImageSharp/Formats/Webp/WebpEncoderCore.cs
  20. 22
      src/ImageSharp/Formats/Webp/WebpFrameMetadata.cs
  21. 626
      src/ImageSharp/IO/ChunkedMemoryStream.cs
  22. 5
      src/ImageSharp/ImageSharp.csproj
  23. 4
      src/ImageSharp/Image{TPixel}.cs
  24. 2
      src/ImageSharp/Metadata/Profiles/Exif/ExifWriter.cs
  25. 2
      src/ImageSharp/Metadata/Profiles/ICC/DataReader/IccDataReader.TagDataEntry.cs
  26. 6
      src/ImageSharp/Metadata/Profiles/ICC/IccProfile.cs
  27. 8
      src/ImageSharp/Processing/Extensions/Convolution/BokehBlurExtensions.cs
  28. 14
      src/ImageSharp/Processing/Extensions/Convolution/BoxBlurExtensions.cs
  29. 89
      src/ImageSharp/Processing/Extensions/Convolution/ConvolutionExtensions.cs
  30. 124
      src/ImageSharp/Processing/Extensions/Convolution/DetectEdgesExtensions.cs
  31. 21
      src/ImageSharp/Processing/Extensions/Convolution/GaussianBlurExtensions.cs
  32. 28
      src/ImageSharp/Processing/Extensions/Convolution/GaussianSharpenExtensions.cs
  33. 17
      src/ImageSharp/Processing/Extensions/Convolution/MedianBlurExtensions.cs
  34. 6
      src/ImageSharp/Processing/Processors/Convolution/Convolution2DProcessor{TPixel}.cs
  35. 54
      src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor{TPixel}.cs
  36. 79
      src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessor.cs
  37. 44
      src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessor{TPixel}.cs
  38. 8
      src/ImageSharp/Processing/Processors/Convolution/EdgeDetectorCompassProcessor{TPixel}.cs
  39. 2
      src/ImageSharp/Processing/Processors/Convolution/EdgeDetectorProcessor{TPixel}.cs
  40. 20
      src/ImageSharp/Processing/Processors/Convolution/GaussianBlurProcessor{TPixel}.cs
  41. 18
      src/ImageSharp/Processing/Processors/Convolution/GaussianSharpenProcessor{TPixel}.cs
  42. 6
      src/ImageSharp/Processing/Processors/Convolution/Parameters/BokehBlurKernelDataProvider.cs
  43. 21
      tests/Directory.Build.targets
  44. 64
      tests/ImageSharp.Benchmarks/Bulk/Pad3Shuffle4Channel.cs
  45. 32
      tests/ImageSharp.Benchmarks/Bulk/Shuffle3Channel.cs
  46. 80
      tests/ImageSharp.Benchmarks/Bulk/Shuffle4Slice3Channel.cs
  47. 40
      tests/ImageSharp.Benchmarks/Bulk/ShuffleByte4Channel.cs
  48. 40
      tests/ImageSharp.Benchmarks/Bulk/ShuffleFloat4Channel.cs
  49. 10
      tests/ImageSharp.Benchmarks/Codecs/Tga/DecodeTga.cs
  50. 25
      tests/ImageSharp.Benchmarks/Codecs/Webp/DecodeWebp.cs
  51. 10
      tests/ImageSharp.Benchmarks/Codecs/Webp/EncodeWebp.cs
  52. 36
      tests/ImageSharp.Benchmarks/Config.HwIntrinsics.cs
  53. 6
      tests/ImageSharp.Benchmarks/ImageSharp.Benchmarks.csproj
  54. 59
      tests/ImageSharp.Benchmarks/LoadResizeSave/LoadResizeSaveStressRunner.cs
  55. 2
      tests/ImageSharp.Tests.ProfilingSandbox/ImageSharp.Tests.ProfilingSandbox.csproj
  56. 2
      tests/ImageSharp.Tests/Formats/Gif/GifEncoderTests.cs
  57. 4
      tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs
  58. 2
      tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs
  59. 50
      tests/ImageSharp.Tests/Formats/WebP/WebpEncoderTests.cs
  60. 163
      tests/ImageSharp.Tests/IO/ChunkedMemoryStreamTests.cs
  61. 6
      tests/ImageSharp.Tests/Image/NonSeekableStream.cs
  62. 2
      tests/ImageSharp.Tests/ImageSharp.Tests.csproj
  63. 12
      tests/ImageSharp.Tests/Processing/Convolution/DetectEdgesTest.cs
  64. 8
      tests/ImageSharp.Tests/Processing/Convolution/GaussianBlurTest.cs
  65. 8
      tests/ImageSharp.Tests/Processing/Convolution/GaussianSharpenTest.cs
  66. 6
      tests/ImageSharp.Tests/Processing/Convolution/MedianBlurTest.cs
  67. 42
      tests/ImageSharp.Tests/Processing/Normalization/MagickCompareTests.cs
  68. 3
      tests/ImageSharp.Tests/Processing/Processors/Convolution/BokehBlurTest.cs
  69. 50
      tests/ImageSharp.Tests/Processing/Processors/Convolution/ConvolutionTests.cs
  70. 2
      tests/ImageSharp.Tests/Processing/Processors/Convolution/GaussianBlurTest.cs
  71. 2
      tests/ImageSharp.Tests/Processing/Processors/Convolution/GaussianSharpenTest.cs
  72. 2
      tests/ImageSharp.Tests/Processing/Processors/Convolution/MedianBlurTest.cs
  73. 14
      tests/ImageSharp.Tests/TestUtilities/FeatureTesting/FeatureTestRunner.cs
  74. 39
      tests/ImageSharp.Tests/TestUtilities/ImageComparison/ImageComparingUtils.cs
  75. 2
      tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/MagickReferenceDecoder.cs
  76. 3
      tests/Images/External/ReferenceOutput/Convolution/ConvolutionTests/InBox_Rgba32_CalliphoraPartial_-1_-1_-1_-1_16_-1_-1_-1_-1.png
  77. 3
      tests/Images/External/ReferenceOutput/Convolution/ConvolutionTests/InBox_Rgba32_Car_-1_-1_-1_-1_16_-1_-1_-1_-1.png
  78. 3
      tests/Images/External/ReferenceOutput/Convolution/ConvolutionTests/InBox_Rgba32_blur_-1_-1_-1_-1_16_-1_-1_-1_-1.png
  79. 3
      tests/Images/External/ReferenceOutput/Convolution/ConvolutionTests/OnFullImage_Rgba32_CalliphoraPartial_-1_-1_-1_-1_16_-1_-1_-1_-1.png
  80. 3
      tests/Images/External/ReferenceOutput/Convolution/ConvolutionTests/OnFullImage_Rgba32_Car_-1_-1_-1_-1_16_-1_-1_-1_-1.png
  81. 3
      tests/Images/External/ReferenceOutput/Convolution/ConvolutionTests/OnFullImage_Rgba32_blur_-1_-1_-1_-1_16_-1_-1_-1_-1.png

27
.github/workflows/build-and-test.yml

@ -19,6 +19,31 @@ jobs:
isARM:
- ${{ contains(github.event.pull_request.labels.*.name, 'arch:arm32') || contains(github.event.pull_request.labels.*.name, 'arch:arm64') }}
options:
- os: ubuntu-latest
framework: net9.0
sdk: 9.0.x
sdk-preview: true
runtime: -x64
codecov: false
- os: macos-13 # macos-latest runs on arm64 runners where libgdiplus is unavailable
framework: net9.0
sdk: 9.0.x
sdk-preview: true
runtime: -x64
codecov: false
- os: windows-latest
framework: net9.0
sdk: 9.0.x
sdk-preview: true
runtime: -x64
codecov: false
- os: buildjet-4vcpu-ubuntu-2204-arm
framework: net9.0
sdk: 9.0.x
sdk-preview: true
runtime: -x64
codecov: false
- os: ubuntu-latest
framework: net8.0
sdk: 8.0.x
@ -100,7 +125,7 @@ jobs:
uses: actions/setup-dotnet@v4
with:
dotnet-version: |
8.0.x
9.0.x
- name: DotNet Build
if: ${{ matrix.options.sdk-preview != true }}

3
src/ImageSharp.ruleset

@ -1,4 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<RuleSet Name="ImageSharp" ToolsVersion="17.0">
<Include Path="..\shared-infrastructure\sixlabors.ruleset" Action="Default" />
<Rules AnalyzerId="Microsoft.CodeAnalysis.CSharp.NetAnalyzers" RuleNamespace="Microsoft.CodeAnalysis.CSharp.NetAnalyzers">
<Rule Id="CA2022" Action="Info" />
</Rules>
</RuleSet>

2
src/ImageSharp/Formats/FormatConnectingMetadata.cs

@ -45,7 +45,7 @@ public class FormatConnectingMetadata
/// Gets the default background color of the canvas when animating.
/// This color may be used to fill the unused space on the canvas around the frames,
/// as well as the transparent pixels of the first frame.
/// The background color is also used when the disposal mode is <see cref="FrameDisposalMode.RestoreToBackground"/>.
/// The background color is also used when a frame disposal mode is <see cref="FrameDisposalMode.RestoreToBackground"/>.
/// </summary>
/// <remarks>
/// Defaults to <see cref="Color.Transparent"/>.

2
src/ImageSharp/Formats/Gif/GifEncoder.cs

@ -6,7 +6,7 @@ namespace SixLabors.ImageSharp.Formats.Gif;
/// <summary>
/// Image encoder for writing image data to a stream in gif format.
/// </summary>
public sealed class GifEncoder : QuantizingImageEncoder
public sealed class GifEncoder : QuantizingAnimatedImageEncoder
{
/// <summary>
/// Gets the color table mode: Global or local.

100
src/ImageSharp/Formats/Gif/GifEncoderCore.cs

@ -54,6 +54,19 @@ internal sealed class GifEncoderCore
/// </summary>
private readonly IPixelSamplingStrategy pixelSamplingStrategy;
/// <summary>
/// The default background color of the canvas when animating.
/// This color may be used to fill the unused space on the canvas around the frames,
/// as well as the transparent pixels of the first frame.
/// The background color is also used when a frame disposal mode is <see cref="FrameDisposalMode.RestoreToBackground"/>.
/// </summary>
private readonly Color? backgroundColor;
/// <summary>
/// The number of times any animation is repeated.
/// </summary>
private readonly ushort? repeatCount;
/// <summary>
/// Initializes a new instance of the <see cref="GifEncoderCore"/> class.
/// </summary>
@ -68,6 +81,8 @@ internal sealed class GifEncoderCore
this.hasQuantizer = encoder.Quantizer is not null;
this.colorTableMode = encoder.ColorTableMode;
this.pixelSamplingStrategy = encoder.PixelSamplingStrategy;
this.backgroundColor = encoder.BackgroundColor;
this.repeatCount = encoder.RepeatCount;
}
/// <summary>
@ -141,9 +156,12 @@ internal sealed class GifEncoderCore
frameMetadata.TransparencyIndex = ClampIndex(derivedTransparencyIndex);
}
byte backgroundIndex = derivedTransparencyIndex >= 0
? frameMetadata.TransparencyIndex
: gifMetadata.BackgroundColorIndex;
if (!TryGetBackgroundIndex(quantized, this.backgroundColor, out byte backgroundIndex))
{
backgroundIndex = derivedTransparencyIndex >= 0
? frameMetadata.TransparencyIndex
: gifMetadata.BackgroundColorIndex;
}
// Get the number of bits.
int bitDepth = ColorNumerics.GetBitsNeededForColorDepth(quantized.Palette.Length);
@ -161,7 +179,7 @@ internal sealed class GifEncoderCore
// Write application extensions.
XmpProfile? xmpProfile = image.Metadata.XmpProfile ?? image.Frames.RootFrame.Metadata.XmpProfile;
this.WriteApplicationExtensions(stream, image.Frames.Count, gifMetadata.RepeatCount, xmpProfile);
this.WriteApplicationExtensions(stream, image.Frames.Count, this.repeatCount ?? gifMetadata.RepeatCount, xmpProfile);
}
this.EncodeFirstFrame(stream, frameMetadata, quantized);
@ -169,7 +187,13 @@ internal sealed class GifEncoderCore
// Capture the global palette for reuse on subsequent frames and cleanup the quantized frame.
TPixel[] globalPalette = image.Frames.Count == 1 ? [] : quantized.Palette.ToArray();
this.EncodeAdditionalFrames(stream, image, globalPalette, derivedTransparencyIndex, frameMetadata.DisposalMode);
this.EncodeAdditionalFrames(
stream,
image,
globalPalette,
derivedTransparencyIndex,
frameMetadata.DisposalMode,
cancellationToken);
stream.WriteByte(GifConstants.EndIntroducer);
@ -194,7 +218,8 @@ internal sealed class GifEncoderCore
Image<TPixel> image,
ReadOnlyMemory<TPixel> globalPalette,
int globalTransparencyIndex,
FrameDisposalMode previousDisposalMode)
FrameDisposalMode previousDisposalMode,
CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel>
{
if (image.Frames.Count == 1)
@ -213,6 +238,16 @@ internal sealed class GifEncoderCore
for (int i = 1; i < image.Frames.Count; i++)
{
if (cancellationToken.IsCancellationRequested)
{
if (hasPaletteQuantizer)
{
paletteQuantizer.Dispose();
}
return;
}
// Gather the metadata for this frame.
ImageFrame<TPixel> currentFrame = image.Frames[i];
ImageFrame<TPixel>? nextFrame = i < image.Frames.Count - 1 ? image.Frames[i + 1] : null;
@ -291,6 +326,10 @@ internal sealed class GifEncoderCore
ImageFrame<TPixel>? previous = previousDisposalMode == FrameDisposalMode.RestoreToBackground ? null : previousFrame;
Color background = metadata.DisposalMode == FrameDisposalMode.RestoreToBackground
? this.backgroundColor ?? Color.Transparent
: Color.Transparent;
// Deduplicate and quantize the frame capturing only required parts.
(bool difference, Rectangle bounds) =
AnimationUtilities.DeDuplicatePixels(
@ -299,7 +338,7 @@ internal sealed class GifEncoderCore
currentFrame,
nextFrame,
encodingFrame,
Color.Transparent,
background,
true);
using IndexedImageFrame<TPixel> quantized = this.QuantizeAdditionalFrameAndUpdateMetadata(
@ -428,14 +467,12 @@ internal sealed class GifEncoderCore
private static byte ClampIndex(int value) => (byte)Numerics.Clamp(value, byte.MinValue, byte.MaxValue);
/// <summary>
/// Returns the index of the most transparent color in the palette.
/// Returns the index of the transparent color in the palette.
/// </summary>
/// <param name="quantized">The current quantized frame.</param>
/// <param name="metadata">The current gif frame metadata.</param>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <returns>
/// The <see cref="int"/>.
/// </returns>
/// <returns>The <see cref="int"/>.</returns>
private static int GetTransparentIndex<TPixel>(IndexedImageFrame<TPixel>? quantized, GifFrameMetadata? metadata)
where TPixel : unmanaged, IPixel<TPixel>
{
@ -463,6 +500,47 @@ internal sealed class GifEncoderCore
return index;
}
/// <summary>
/// Returns the index of the background color in the palette.
/// </summary>
/// <param name="quantized">The current quantized frame.</param>
/// <param name="background">The background color to match.</param>
/// <param name="index">The index in the palette of the background color.</param>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <returns>The <see cref="bool"/>.</returns>
private static bool TryGetBackgroundIndex<TPixel>(
IndexedImageFrame<TPixel>? quantized,
Color? background,
out byte index)
where TPixel : unmanaged, IPixel<TPixel>
{
int match = -1;
if (quantized != null && background.HasValue)
{
TPixel backgroundPixel = background.Value.ToPixel<TPixel>();
ReadOnlySpan<TPixel> palette = quantized.Palette.Span;
for (int i = 0; i < palette.Length; i++)
{
if (!backgroundPixel.Equals(palette[i]))
{
continue;
}
match = i;
break;
}
}
if (match >= 0)
{
index = (byte)Numerics.Clamp(match, 0, 255);
return true;
}
index = 0;
return false;
}
/// <summary>
/// Writes the file header signature and version to the stream.
/// </summary>

43
src/ImageSharp/Formats/IAnimatedImageEncoder.cs

@ -0,0 +1,43 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
namespace SixLabors.ImageSharp.Formats;
/// <summary>
/// Defines the contract for all image encoders that allow encoding animation sequences.
/// </summary>
public interface IAnimatedImageEncoder
{
/// <summary>
/// Gets the default background color of the canvas when animating in supported encoders.
/// This color may be used to fill the unused space on the canvas around the frames,
/// as well as the transparent pixels of the first frame.
/// The background color is also used when a frame disposal mode is <see cref="FrameDisposalMode.RestoreToBackground"/>.
/// </summary>
Color? BackgroundColor { get; }
/// <summary>
/// Gets the number of times any animation is repeated in supported encoders.
/// </summary>
ushort? RepeatCount { get; }
/// <summary>
/// Gets a value indicating whether the root frame is shown as part of the animated sequence in supported encoders.
/// </summary>
bool? AnimateRootFrame { get; }
}
/// <summary>
/// Acts as a base class for all image encoders that allow encoding animation sequences.
/// </summary>
public abstract class AnimatedImageEncoder : ImageEncoder, IAnimatedImageEncoder
{
/// <inheritdoc/>
public Color? BackgroundColor { get; init; }
/// <inheritdoc/>
public ushort? RepeatCount { get; init; }
/// <inheritdoc/>
public bool? AnimateRootFrame { get; init; } = true;
}

50
src/ImageSharp/Formats/IQuantizingImageEncoder.cs

@ -0,0 +1,50 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using SixLabors.ImageSharp.Processing.Processors.Quantization;
namespace SixLabors.ImageSharp.Formats;
/// <summary>
/// Defines the contract for all image encoders that allow color palette generation via quantization.
/// </summary>
public interface IQuantizingImageEncoder
{
/// <summary>
/// Gets the quantizer used to generate the color palette.
/// </summary>
IQuantizer? Quantizer { get; }
/// <summary>
/// Gets the <see cref="IPixelSamplingStrategy"/> used for quantization when building color palettes.
/// </summary>
IPixelSamplingStrategy PixelSamplingStrategy { get; }
}
/// <summary>
/// Acts as a base class for all image encoders that allow color palette generation via quantization.
/// </summary>
public abstract class QuantizingImageEncoder : ImageEncoder, IQuantizingImageEncoder
{
/// <inheritdoc/>
public IQuantizer? Quantizer { get; init; }
/// <inheritdoc/>
public IPixelSamplingStrategy PixelSamplingStrategy { get; init; } = new DefaultPixelSamplingStrategy();
}
/// <summary>
/// Acts as a base class for all image encoders that allow color palette generation via quantization when
/// encoding animation sequences.
/// </summary>
public abstract class QuantizingAnimatedImageEncoder : QuantizingImageEncoder, IAnimatedImageEncoder
{
/// <inheritdoc/>
public Color? BackgroundColor { get; }
/// <inheritdoc/>
public ushort? RepeatCount { get; }
/// <inheritdoc/>
public bool? AnimateRootFrame { get; }
}

2
src/ImageSharp/Formats/ImageEncoder.cs

@ -51,7 +51,7 @@ public abstract class ImageEncoder : IImageEncoder
else
{
using ChunkedMemoryStream ms = new(configuration.MemoryAllocator);
this.Encode(image, stream, cancellationToken);
this.Encode(image, ms, cancellationToken);
ms.Position = 0;
ms.CopyTo(stream, configuration.StreamProcessingBufferSize);
}

3
src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs

@ -16,7 +16,6 @@ using SixLabors.ImageSharp.Metadata.Profiles.Exif;
using SixLabors.ImageSharp.Metadata.Profiles.Icc;
using SixLabors.ImageSharp.Metadata.Profiles.Iptc;
using SixLabors.ImageSharp.Metadata.Profiles.Xmp;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Formats.Jpeg;
@ -1473,7 +1472,7 @@ internal sealed class JpegDecoderCore : ImageDecoderCore, IRawJpegData
this.Frame.ComponentOrder[i / 2] = (byte)componentIndex;
IJpegComponent component = this.Frame.Components[componentIndex];
JpegComponent component = this.Frame.Components[componentIndex];
// 1 byte: Huffman table selectors.
// 4 bits - dc

3
src/ImageSharp/Formats/Png/PngEncoder.cs

@ -1,6 +1,5 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
#nullable disable
using SixLabors.ImageSharp.Processing.Processors.Quantization;
@ -9,7 +8,7 @@ namespace SixLabors.ImageSharp.Formats.Png;
/// <summary>
/// Image encoder for writing image data to a stream in png format.
/// </summary>
public class PngEncoder : QuantizingImageEncoder
public class PngEncoder : QuantizingAnimatedImageEncoder
{
/// <summary>
/// Initializes a new instance of the <see cref="PngEncoder"/> class.

48
src/ImageSharp/Formats/Png/PngEncoderCore.cs

@ -123,6 +123,24 @@ internal sealed class PngEncoderCore : IDisposable
/// </summary>
private int derivedTransparencyIndex = -1;
/// <summary>
/// The default background color of the canvas when animating.
/// This color may be used to fill the unused space on the canvas around the frames,
/// as well as the transparent pixels of the first frame.
/// The background color is also used when a frame disposal mode is <see cref="FrameDisposalMode.RestoreToBackground"/>.
/// </summary>
private readonly Color? backgroundColor;
/// <summary>
/// The number of times any animation is repeated.
/// </summary>
private readonly ushort? repeatCount;
/// <summary>
/// Whether the root frame is shown as part of the animated sequence.
/// </summary>
private readonly bool? animateRootFrame;
/// <summary>
/// A reusable Crc32 hashing instance.
/// </summary>
@ -139,6 +157,9 @@ internal sealed class PngEncoderCore : IDisposable
this.memoryAllocator = configuration.MemoryAllocator;
this.encoder = encoder;
this.quantizer = encoder.Quantizer;
this.backgroundColor = encoder.BackgroundColor;
this.repeatCount = encoder.RepeatCount;
this.animateRootFrame = encoder.AnimateRootFrame;
}
/// <summary>
@ -171,7 +192,7 @@ internal sealed class PngEncoderCore : IDisposable
if (clearTransparency)
{
currentFrame = clonedFrame = currentFrame.Clone();
ClearTransparentPixels(currentFrame);
ClearTransparentPixels(currentFrame, Color.Transparent);
}
// Do not move this. We require an accurate bit depth for the header chunk.
@ -194,11 +215,15 @@ internal sealed class PngEncoderCore : IDisposable
if (image.Frames.Count > 1)
{
this.WriteAnimationControlChunk(stream, (uint)(image.Frames.Count - (pngMetadata.AnimateRootFrame ? 0 : 1)), pngMetadata.RepeatCount);
this.WriteAnimationControlChunk(
stream,
(uint)(image.Frames.Count - (pngMetadata.AnimateRootFrame ? 0 : 1)),
this.repeatCount ?? pngMetadata.RepeatCount);
}
// If the first frame isn't animated, write it as usual and skip it when writing animated frames
if (!pngMetadata.AnimateRootFrame || image.Frames.Count == 1)
bool userAnimateRootFrame = this.animateRootFrame == true;
if ((!userAnimateRootFrame && !pngMetadata.AnimateRootFrame) || image.Frames.Count == 1)
{
FrameControl frameControl = new((uint)this.width, (uint)this.height);
this.WriteDataChunks(frameControl, currentFrame.PixelBuffer.GetRegion(), quantized, stream, false);
@ -235,12 +260,20 @@ internal sealed class PngEncoderCore : IDisposable
for (; currentFrameIndex < image.Frames.Count; currentFrameIndex++)
{
if (cancellationToken.IsCancellationRequested)
{
break;
}
ImageFrame<TPixel>? prev = previousDisposal == FrameDisposalMode.RestoreToBackground ? null : previousFrame;
currentFrame = image.Frames[currentFrameIndex];
ImageFrame<TPixel>? nextFrame = currentFrameIndex < image.Frames.Count - 1 ? image.Frames[currentFrameIndex + 1] : null;
frameMetadata = currentFrame.Metadata.GetPngMetadata();
bool blend = frameMetadata.BlendMode == FrameBlendMode.Over;
Color background = frameMetadata.DisposalMode == FrameDisposalMode.RestoreToBackground
? this.backgroundColor ?? Color.Transparent
: Color.Transparent;
(bool difference, Rectangle bounds) =
AnimationUtilities.DeDuplicatePixels(
@ -249,12 +282,12 @@ internal sealed class PngEncoderCore : IDisposable
currentFrame,
nextFrame,
encodingFrame,
Color.Transparent,
background,
blend);
if (clearTransparency)
{
ClearTransparentPixels(encodingFrame);
ClearTransparentPixels(encodingFrame, background);
}
// Each frame control sequence number must be incremented by the number of frame data chunks that follow.
@ -291,12 +324,13 @@ internal sealed class PngEncoderCore : IDisposable
/// </summary>
/// <typeparam name="TPixel">The type of the pixel.</typeparam>
/// <param name="clone">The cloned image frame where the transparent pixels will be changed.</param>
private static void ClearTransparentPixels<TPixel>(ImageFrame<TPixel> clone)
/// <param name="color">The color to replace transparent pixels with.</param>
private static void ClearTransparentPixels<TPixel>(ImageFrame<TPixel> clone, Color color)
where TPixel : unmanaged, IPixel<TPixel>
=> clone.ProcessPixelRows(accessor =>
{
// TODO: We should be able to speed this up with SIMD and masking.
Rgba32 transparent = Color.Transparent.ToPixel<Rgba32>();
Rgba32 transparent = color.ToPixel<Rgba32>();
for (int y = 0; y < accessor.Height; y++)
{
Span<TPixel> span = accessor.GetRowSpan(y);

22
src/ImageSharp/Formats/QuantizingImageEncoder.cs

@ -1,22 +0,0 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using SixLabors.ImageSharp.Processing.Processors.Quantization;
namespace SixLabors.ImageSharp.Formats;
/// <summary>
/// Acts as a base class for all image encoders that allow color palette generation via quantization.
/// </summary>
public abstract class QuantizingImageEncoder : ImageEncoder
{
/// <summary>
/// Gets the quantizer used to generate the color palette.
/// </summary>
public IQuantizer? Quantizer { get; init; }
/// <summary>
/// Gets the <see cref="IPixelSamplingStrategy"/> used for quantization when building color palettes.
/// </summary>
public IPixelSamplingStrategy PixelSamplingStrategy { get; init; } = new DefaultPixelSamplingStrategy();
}

8
src/ImageSharp/Formats/Webp/Lossless/Vp8LEncoder.cs

@ -236,7 +236,7 @@ internal class Vp8LEncoder : IDisposable
/// </summary>
public Vp8LHashChain HashChain { get; }
public WebpVp8X EncodeHeader<TPixel>(Image<TPixel> image, Stream stream, bool hasAnimation)
public WebpVp8X EncodeHeader<TPixel>(Image<TPixel> image, Stream stream, bool hasAnimation, ushort? repeatCount)
where TPixel : unmanaged, IPixel<TPixel>
{
// Write bytes from the bit-writer buffer to the stream.
@ -258,7 +258,7 @@ internal class Vp8LEncoder : IDisposable
if (hasAnimation)
{
WebpMetadata webpMetadata = image.Metadata.GetWebpMetadata();
BitWriterBase.WriteAnimationParameter(stream, webpMetadata.BackgroundColor, webpMetadata.RepeatCount);
BitWriterBase.WriteAnimationParameter(stream, webpMetadata.BackgroundColor, repeatCount ?? webpMetadata.RepeatCount);
}
return vp8x;
@ -315,8 +315,8 @@ internal class Vp8LEncoder : IDisposable
(uint)bounds.Width,
(uint)bounds.Height,
frameMetadata.FrameDelay,
frameMetadata.BlendMethod,
frameMetadata.DisposalMethod)
frameMetadata.BlendMode,
frameMetadata.DisposalMode)
.WriteHeaderTo(stream);
}

4
src/ImageSharp/Formats/Webp/Lossy/Vp8Encoder.cs

@ -495,8 +495,8 @@ internal class Vp8Encoder : IDisposable
(uint)bounds.Width,
(uint)bounds.Height,
frameMetadata.FrameDelay,
frameMetadata.BlendMethod,
frameMetadata.DisposalMethod)
frameMetadata.BlendMode,
frameMetadata.DisposalMode)
.WriteHeaderTo(stream);
}

20
src/ImageSharp/Formats/Webp/Lossy/Vp8Encoding.cs

@ -667,12 +667,12 @@ internal static unsafe class Vp8Encoding
// V block.
dst = dst[8..];
if (top != default)
if (!top.IsEmpty)
{
top = top[8..];
}
if (left != default)
if (!left.IsEmpty)
{
left = left[16..];
}
@ -701,7 +701,7 @@ internal static unsafe class Vp8Encoding
private static void VerticalPred(Span<byte> dst, Span<byte> top, int size)
{
if (top != default)
if (!top.IsEmpty)
{
for (int j = 0; j < size; j++)
{
@ -716,7 +716,7 @@ internal static unsafe class Vp8Encoding
public static void HorizontalPred(Span<byte> dst, Span<byte> left, int size)
{
if (left != default)
if (!left.IsEmpty)
{
left = left[1..]; // in the reference implementation, left starts at - 1.
for (int j = 0; j < size; j++)
@ -732,9 +732,9 @@ internal static unsafe class Vp8Encoding
public static void TrueMotion(Span<byte> dst, Span<byte> left, Span<byte> top, int size)
{
if (left != default)
if (!left.IsEmpty)
{
if (top != default)
if (!top.IsEmpty)
{
Span<byte> clip = Clip1.AsSpan(255 - left[0]); // left [0] instead of left[-1], original left starts at -1
for (int y = 0; y < size; y++)
@ -759,7 +759,7 @@ internal static unsafe class Vp8Encoding
// is equivalent to VE prediction where you just copy the top samples.
// Note that if top samples are not available, the default value is
// then 129, and not 127 as in the VerticalPred case.
if (top != default)
if (!top.IsEmpty)
{
VerticalPred(dst, top, size);
}
@ -774,14 +774,14 @@ internal static unsafe class Vp8Encoding
{
int dc = 0;
int j;
if (top != default)
if (!top.IsEmpty)
{
for (j = 0; j < size; j++)
{
dc += top[j];
}
if (left != default)
if (!left.IsEmpty)
{
// top and left present.
left = left[1..]; // in the reference implementation, left starts at -1.
@ -798,7 +798,7 @@ internal static unsafe class Vp8Encoding
dc = (dc + round) >> shift;
}
else if (left != default)
else if (!left.IsEmpty)
{
// left but no top.
left = left[1..]; // in the reference implementation, left starts at -1.

16
src/ImageSharp/Formats/Webp/Lossy/YuvConversion.cs

@ -48,7 +48,7 @@ internal static class YuvConversion
uint uv0 = ((3 * tluv) + luv + 0x00020002u) >> 2;
YuvToBgr(topY[0], (int)(uv0 & 0xff), (int)(uv0 >> 16), topDst);
if (bottomY != default)
if (!bottomY.IsEmpty)
{
uv0 = ((3 * luv) + tluv + 0x00020002u) >> 2;
YuvToBgr(bottomY[0], (int)uv0 & 0xff, (int)(uv0 >> 16), bottomDst);
@ -69,7 +69,7 @@ internal static class YuvConversion
YuvToBgr(topY[xMul2 - 1], (int)(uv0 & 0xff), (int)(uv0 >> 16), topDst[((xMul2 - 1) * xStep)..]);
YuvToBgr(topY[xMul2 - 0], (int)(uv1 & 0xff), (int)(uv1 >> 16), topDst[((xMul2 - 0) * xStep)..]);
if (bottomY != default)
if (!bottomY.IsEmpty)
{
uv0 = (diag03 + luv) >> 1;
uv1 = (diag12 + uv) >> 1;
@ -85,7 +85,7 @@ internal static class YuvConversion
{
uv0 = ((3 * tluv) + luv + 0x00020002u) >> 2;
YuvToBgr(topY[len - 1], (int)(uv0 & 0xff), (int)(uv0 >> 16), topDst[((len - 1) * xStep)..]);
if (bottomY != default)
if (!bottomY.IsEmpty)
{
uv0 = ((3 * luv) + tluv + 0x00020002u) >> 2;
YuvToBgr(bottomY[len - 1], (int)(uv0 & 0xff), (int)(uv0 >> 16), bottomDst[((len - 1) * xStep)..]);
@ -120,7 +120,7 @@ internal static class YuvConversion
int u0t = (topU[0] + uDiag) >> 1;
int v0t = (topV[0] + vDiag) >> 1;
YuvToBgr(topY[0], u0t, v0t, topDst);
if (bottomY != default)
if (!bottomY.IsEmpty)
{
int u0b = (curU[0] + uDiag) >> 1;
int v0b = (curV[0] + vDiag) >> 1;
@ -134,7 +134,7 @@ internal static class YuvConversion
ref byte topVRef = ref MemoryMarshal.GetReference(topV);
ref byte curURef = ref MemoryMarshal.GetReference(curU);
ref byte curVRef = ref MemoryMarshal.GetReference(curV);
if (bottomY != default)
if (!bottomY.IsEmpty)
{
for (pos = 1, uvPos = 0; pos + 32 + 1 <= len; pos += 32, uvPos += 16)
{
@ -160,12 +160,12 @@ internal static class YuvConversion
Span<byte> tmpTopDst = ru[(4 * 32)..];
Span<byte> tmpBottomDst = tmpTopDst[(4 * 32)..];
Span<byte> tmpTop = tmpBottomDst[(4 * 32)..];
Span<byte> tmpBottom = (bottomY == default) ? null : tmpTop[32..];
Span<byte> tmpBottom = bottomY.IsEmpty ? null : tmpTop[32..];
UpSampleLastBlock(topU[uvPos..], curU[uvPos..], leftOver, ru);
UpSampleLastBlock(topV[uvPos..], curV[uvPos..], leftOver, rv);
topY[pos..len].CopyTo(tmpTop);
if (bottomY != default)
if (!bottomY.IsEmpty)
{
bottomY[pos..len].CopyTo(tmpBottom);
ConvertYuvToBgrWithBottomYSse41(tmpTop, tmpBottom, tmpTopDst, tmpBottomDst, ru, rv, 0, xStep);
@ -176,7 +176,7 @@ internal static class YuvConversion
}
tmpTopDst[..((len - pos) * xStep)].CopyTo(topDst[(pos * xStep)..]);
if (bottomY != default)
if (!bottomY.IsEmpty)
{
tmpBottomDst[..((len - pos) * xStep)].CopyTo(bottomDst[(pos * xStep)..]);
}

4
src/ImageSharp/Formats/Webp/WebpAnimationDecoder.cs

@ -220,8 +220,8 @@ internal class WebpAnimationDecoder : IDisposable
{
WebpFrameMetadata frameMetadata = meta.GetWebpMetadata();
frameMetadata.FrameDelay = frameData.Duration;
frameMetadata.BlendMethod = frameData.BlendingMethod;
frameMetadata.DisposalMethod = frameData.DisposalMethod;
frameMetadata.BlendMode = frameData.BlendingMethod;
frameMetadata.DisposalMode = frameData.DisposalMethod;
}
/// <summary>

2
src/ImageSharp/Formats/Webp/WebpEncoder.cs

@ -6,7 +6,7 @@ namespace SixLabors.ImageSharp.Formats.Webp;
/// <summary>
/// Image encoder for writing an image to a stream in the Webp format.
/// </summary>
public sealed class WebpEncoder : ImageEncoder
public sealed class WebpEncoder : AnimatedImageEncoder
{
/// <summary>
/// Gets the webp file format used. Either lossless or lossy.

50
src/ImageSharp/Formats/Webp/WebpEncoderCore.cs

@ -5,7 +5,6 @@ using SixLabors.ImageSharp.Formats.Webp.Chunks;
using SixLabors.ImageSharp.Formats.Webp.Lossless;
using SixLabors.ImageSharp.Formats.Webp.Lossy;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.Metadata;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Formats.Webp;
@ -78,6 +77,19 @@ internal sealed class WebpEncoderCore
/// </summary>
private readonly WebpFileFormatType? fileFormat;
/// <summary>
/// The default background color of the canvas when animating.
/// This color may be used to fill the unused space on the canvas around the frames,
/// as well as the transparent pixels of the first frame.
/// The background color is also used when a frame disposal mode is <see cref="FrameDisposalMode.RestoreToBackground"/>.
/// </summary>
private readonly Color? backgroundColor;
/// <summary>
/// The number of times any animation is repeated.
/// </summary>
private readonly ushort? repeatCount;
/// <summary>
/// The global configuration.
/// </summary>
@ -103,6 +115,8 @@ internal sealed class WebpEncoderCore
this.skipMetadata = encoder.SkipMetadata;
this.nearLossless = encoder.NearLossless;
this.nearLosslessQuality = encoder.NearLosslessQuality;
this.backgroundColor = encoder.BackgroundColor;
this.repeatCount = encoder.RepeatCount;
}
/// <summary>
@ -147,7 +161,7 @@ internal sealed class WebpEncoderCore
long initialPosition = stream.Position;
bool hasAlpha = false;
WebpVp8X vp8x = encoder.EncodeHeader(image, stream, hasAnimation);
WebpVp8X vp8x = encoder.EncodeHeader(image, stream, hasAnimation, this.repeatCount);
// Encode the first frame.
ImageFrame<TPixel> previousFrame = image.Frames.RootFrame;
@ -156,7 +170,7 @@ internal sealed class WebpEncoderCore
if (hasAnimation)
{
FrameDisposalMode previousDisposal = frameMetadata.DisposalMethod;
FrameDisposalMode previousDisposal = frameMetadata.DisposalMode;
// Encode additional frames
// This frame is reused to store de-duplicated pixel buffers.
@ -164,12 +178,20 @@ internal sealed class WebpEncoderCore
for (int i = 1; i < image.Frames.Count; i++)
{
if (cancellationToken.IsCancellationRequested)
{
break;
}
ImageFrame<TPixel>? prev = previousDisposal == FrameDisposalMode.RestoreToBackground ? null : previousFrame;
ImageFrame<TPixel> currentFrame = image.Frames[i];
ImageFrame<TPixel>? nextFrame = i < image.Frames.Count - 1 ? image.Frames[i + 1] : null;
frameMetadata = currentFrame.Metadata.GetWebpMetadata();
bool blend = frameMetadata.BlendMethod == FrameBlendMode.Over;
bool blend = frameMetadata.BlendMode == FrameBlendMode.Over;
Color background = frameMetadata.DisposalMode == FrameDisposalMode.RestoreToBackground
? this.backgroundColor ?? Color.Transparent
: Color.Transparent;
(bool difference, Rectangle bounds) =
AnimationUtilities.DeDuplicatePixels(
@ -178,7 +200,7 @@ internal sealed class WebpEncoderCore
currentFrame,
nextFrame,
encodingFrame,
Color.Transparent,
background,
blend,
ClampingMode.Even);
@ -197,7 +219,7 @@ internal sealed class WebpEncoderCore
hasAlpha |= animatedEncoder.Encode(encodingFrame, bounds, frameMetadata, stream, hasAnimation);
previousFrame = currentFrame;
previousDisposal = frameMetadata.DisposalMethod;
previousDisposal = frameMetadata.DisposalMode;
}
}
@ -229,7 +251,7 @@ internal sealed class WebpEncoderCore
// Encode the first frame.
ImageFrame<TPixel> previousFrame = image.Frames.RootFrame;
WebpFrameMetadata frameMetadata = previousFrame.Metadata.GetWebpMetadata();
FrameDisposalMode previousDisposal = frameMetadata.DisposalMethod;
FrameDisposalMode previousDisposal = frameMetadata.DisposalMode;
hasAlpha |= encoder.EncodeAnimation(previousFrame, stream, previousFrame.Bounds(), frameMetadata);
@ -239,12 +261,20 @@ internal sealed class WebpEncoderCore
for (int i = 1; i < image.Frames.Count; i++)
{
if (cancellationToken.IsCancellationRequested)
{
break;
}
ImageFrame<TPixel>? prev = previousDisposal == FrameDisposalMode.RestoreToBackground ? null : previousFrame;
ImageFrame<TPixel> currentFrame = image.Frames[i];
ImageFrame<TPixel>? nextFrame = i < image.Frames.Count - 1 ? image.Frames[i + 1] : null;
frameMetadata = currentFrame.Metadata.GetWebpMetadata();
bool blend = frameMetadata.BlendMethod == FrameBlendMode.Over;
bool blend = frameMetadata.BlendMode == FrameBlendMode.Over;
Color background = frameMetadata.DisposalMode == FrameDisposalMode.RestoreToBackground
? this.backgroundColor ?? Color.Transparent
: Color.Transparent;
(bool difference, Rectangle bounds) =
AnimationUtilities.DeDuplicatePixels(
@ -253,7 +283,7 @@ internal sealed class WebpEncoderCore
currentFrame,
nextFrame,
encodingFrame,
Color.Transparent,
background,
blend,
ClampingMode.Even);
@ -273,7 +303,7 @@ internal sealed class WebpEncoderCore
hasAlpha |= animatedEncoder.EncodeAnimation(encodingFrame, stream, bounds, frameMetadata);
previousFrame = currentFrame;
previousDisposal = frameMetadata.DisposalMethod;
previousDisposal = frameMetadata.DisposalMode;
}
encoder.EncodeFooter(image, in vp8x, hasAlpha, stream, initialPosition);

22
src/ImageSharp/Formats/Webp/WebpFrameMetadata.cs

@ -24,19 +24,21 @@ public class WebpFrameMetadata : IFormatFrameMetadata<WebpFrameMetadata>
private WebpFrameMetadata(WebpFrameMetadata other)
{
this.FrameDelay = other.FrameDelay;
this.DisposalMethod = other.DisposalMethod;
this.BlendMethod = other.BlendMethod;
this.DisposalMode = other.DisposalMode;
this.BlendMode = other.BlendMode;
}
/// <summary>
/// Gets or sets how transparent pixels of the current frame are to be blended with corresponding pixels of the previous canvas.
/// Gets or sets how transparent pixels of the current frame are to be blended with corresponding pixels
/// of the previous canvas.
/// </summary>
public FrameBlendMode BlendMethod { get; set; }
public FrameBlendMode BlendMode { get; set; }
/// <summary>
/// Gets or sets how the current frame is to be treated after it has been displayed (before rendering the next frame) on the canvas.
/// Gets or sets how the current frame is to be treated after it has been displayed
/// (before rendering the next frame) on the canvas.
/// </summary>
public FrameDisposalMode DisposalMethod { get; set; }
public FrameDisposalMode DisposalMode { get; set; }
/// <summary>
/// Gets or sets the frame duration. The time to wait before displaying the next frame,
@ -49,8 +51,8 @@ public class WebpFrameMetadata : IFormatFrameMetadata<WebpFrameMetadata>
=> new()
{
FrameDelay = (uint)metadata.Duration.TotalMilliseconds,
BlendMethod = metadata.BlendMode,
DisposalMethod = GetMode(metadata.DisposalMode)
BlendMode = metadata.BlendMode,
DisposalMode = GetMode(metadata.DisposalMode)
};
/// <inheritdoc/>
@ -59,8 +61,8 @@ public class WebpFrameMetadata : IFormatFrameMetadata<WebpFrameMetadata>
{
ColorTableMode = FrameColorTableMode.Global,
Duration = TimeSpan.FromMilliseconds(this.FrameDelay),
DisposalMode = this.DisposalMethod,
BlendMode = this.BlendMethod,
DisposalMode = this.DisposalMode,
BlendMode = this.BlendMode,
};
/// <inheritdoc/>

626
src/ImageSharp/IO/ChunkedMemoryStream.cs

@ -3,6 +3,7 @@
using System.Buffers;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using SixLabors.ImageSharp.Memory;
namespace SixLabors.ImageSharp.IO;
@ -14,42 +15,19 @@ namespace SixLabors.ImageSharp.IO;
/// </summary>
internal sealed class ChunkedMemoryStream : Stream
{
// The memory allocator.
private readonly MemoryAllocator allocator;
// Data
private MemoryChunk? memoryChunk;
// The total number of allocated chunks
private int chunkCount;
// The length of the largest contiguous buffer that can be handled by the allocator.
private readonly int allocatorCapacity;
// Has the stream been disposed.
private readonly MemoryChunkBuffer memoryChunkBuffer;
private long length;
private long position;
private int bufferIndex;
private int chunkIndex;
private bool isDisposed;
// Current chunk to write to
private MemoryChunk? writeChunk;
// Offset into chunk to write to
private int writeOffset;
// Current chunk to read from
private MemoryChunk? readChunk;
// Offset into chunk to read from
private int readOffset;
/// <summary>
/// Initializes a new instance of the <see cref="ChunkedMemoryStream"/> class.
/// </summary>
/// <param name="allocator">The memory allocator.</param>
public ChunkedMemoryStream(MemoryAllocator allocator)
{
this.allocatorCapacity = allocator.GetBufferCapacityInBytes();
this.allocator = allocator;
}
=> this.memoryChunkBuffer = new(allocator);
/// <inheritdoc/>
public override bool CanRead => !this.isDisposed;
@ -66,25 +44,7 @@ internal sealed class ChunkedMemoryStream : Stream
get
{
this.EnsureNotDisposed();
int length = 0;
MemoryChunk? chunk = this.memoryChunk;
while (chunk != null)
{
MemoryChunk? next = chunk.Next;
if (next != null)
{
length += chunk.Length;
}
else
{
length += this.writeOffset;
}
chunk = next;
}
return length;
return this.length;
}
}
@ -94,93 +54,35 @@ internal sealed class ChunkedMemoryStream : Stream
get
{
this.EnsureNotDisposed();
if (this.readChunk is null)
{
return 0;
}
int pos = 0;
MemoryChunk? chunk = this.memoryChunk;
while (chunk != this.readChunk && chunk is not null)
{
pos += chunk.Length;
chunk = chunk.Next;
}
pos += this.readOffset;
return pos;
return this.position;
}
set
{
this.EnsureNotDisposed();
if (value < 0)
{
ThrowArgumentOutOfRange(nameof(value));
}
// Back up current position in case new position is out of range
MemoryChunk? backupReadChunk = this.readChunk;
int backupReadOffset = this.readOffset;
this.readChunk = null;
this.readOffset = 0;
int leftUntilAtPos = (int)value;
MemoryChunk? chunk = this.memoryChunk;
while (chunk != null)
{
if ((leftUntilAtPos < chunk.Length)
|| ((leftUntilAtPos == chunk.Length)
&& (chunk.Next is null)))
{
// The desired position is in this chunk
this.readChunk = chunk;
this.readOffset = leftUntilAtPos;
break;
}
leftUntilAtPos -= chunk.Length;
chunk = chunk.Next;
}
if (this.readChunk is null)
{
// Position is out of range
this.readChunk = backupReadChunk;
this.readOffset = backupReadOffset;
}
this.SetPosition(value);
}
}
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public override void Flush()
{
}
/// <inheritdoc/>
public override long Seek(long offset, SeekOrigin origin)
{
this.EnsureNotDisposed();
switch (origin)
this.Position = origin switch
{
case SeekOrigin.Begin:
this.Position = offset;
break;
case SeekOrigin.Current:
this.Position += offset;
break;
case SeekOrigin.End:
this.Position = this.Length + offset;
break;
default:
ThrowInvalidSeek();
break;
}
SeekOrigin.Begin => (int)offset,
SeekOrigin.Current => (int)(this.Position + offset),
SeekOrigin.End => (int)(this.Length + offset),
_ => throw new ArgumentOutOfRangeException(nameof(offset)),
};
return this.Position;
return this.position;
}
/// <inheritdoc/>
@ -188,39 +90,13 @@ internal sealed class ChunkedMemoryStream : Stream
=> throw new NotSupportedException();
/// <inheritdoc/>
protected override void Dispose(bool disposing)
{
if (this.isDisposed)
{
return;
}
try
{
this.isDisposed = true;
if (disposing)
{
ReleaseMemoryChunks(this.memoryChunk);
}
this.memoryChunk = null;
this.writeChunk = null;
this.readChunk = null;
this.chunkCount = 0;
}
finally
{
base.Dispose(disposing);
}
}
/// <inheritdoc/>
public override void Flush()
public override int ReadByte()
{
Unsafe.SkipInit(out byte b);
return this.Read(MemoryMarshal.CreateSpan(ref b, 1)) == 1 ? b : -1;
}
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public override int Read(byte[] buffer, int offset, int count)
{
Guard.NotNull(buffer, nameof(buffer));
@ -230,111 +106,70 @@ internal sealed class ChunkedMemoryStream : Stream
const string bufferMessage = "Offset subtracted from the buffer length is less than count.";
Guard.IsFalse(buffer.Length - offset < count, nameof(buffer), bufferMessage);
return this.ReadImpl(buffer.AsSpan(offset, count));
return this.Read(buffer.AsSpan(offset, count));
}
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public override int Read(Span<byte> buffer) => this.ReadImpl(buffer);
private int ReadImpl(Span<byte> buffer)
public override int Read(Span<byte> buffer)
{
this.EnsureNotDisposed();
if (this.readChunk is null)
{
if (this.memoryChunk is null)
{
return 0;
}
int offset = 0;
int count = buffer.Length;
this.readChunk = this.memoryChunk;
this.readOffset = 0;
long remaining = this.length - this.position;
if (remaining <= 0)
{
// Already at the end of the stream, nothing to read
return 0;
}
IMemoryOwner<byte> chunkBuffer = this.readChunk.Buffer;
int chunkSize = this.readChunk.Length;
if (this.readChunk.Next is null)
if (remaining > count)
{
chunkSize = this.writeOffset;
remaining = count;
}
// 'remaining' can be less than the provided buffer length.
int bytesToRead = (int)remaining;
int bytesRead = 0;
int offset = 0;
int count = buffer.Length;
while (count > 0)
while (bytesToRead > 0 && this.bufferIndex != this.memoryChunkBuffer.Length)
{
if (this.readOffset == chunkSize)
bool moveToNextChunk = false;
MemoryChunk chunk = this.memoryChunkBuffer[this.bufferIndex];
int n = bytesToRead;
int remainingBytesInCurrentChunk = chunk.Length - this.chunkIndex;
if (n >= remainingBytesInCurrentChunk)
{
// Exit if no more chunks are currently available
if (this.readChunk.Next is null)
{
break;
}
this.readChunk = this.readChunk.Next;
this.readOffset = 0;
chunkBuffer = this.readChunk.Buffer;
chunkSize = this.readChunk.Length;
if (this.readChunk.Next is null)
{
chunkSize = this.writeOffset;
}
n = remainingBytesInCurrentChunk;
moveToNextChunk = true;
}
int readCount = Math.Min(count, chunkSize - this.readOffset);
chunkBuffer.Slice(this.readOffset, readCount).CopyTo(buffer[offset..]);
offset += readCount;
count -= readCount;
this.readOffset += readCount;
bytesRead += readCount;
}
return bytesRead;
}
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public override int ReadByte()
{
this.EnsureNotDisposed();
// Read n bytes from the current chunk
chunk.Buffer.Memory.Span.Slice(this.chunkIndex, n).CopyTo(buffer.Slice(offset, n));
bytesToRead -= n;
offset += n;
bytesRead += n;
if (this.readChunk is null)
{
if (this.memoryChunk is null)
if (moveToNextChunk)
{
return 0;
this.chunkIndex = 0;
this.bufferIndex++;
}
this.readChunk = this.memoryChunk;
this.readOffset = 0;
}
IMemoryOwner<byte> chunkBuffer = this.readChunk.Buffer;
int chunkSize = this.readChunk.Length;
if (this.readChunk.Next is null)
{
chunkSize = this.writeOffset;
}
if (this.readOffset == chunkSize)
{
// Exit if no more chunks are currently available
if (this.readChunk.Next is null)
else
{
return -1;
this.chunkIndex += n;
}
this.readChunk = this.readChunk.Next;
this.readOffset = 0;
chunkBuffer = this.readChunk.Buffer;
}
return chunkBuffer.GetSpan()[this.readOffset++];
this.position += bytesRead;
return bytesRead;
}
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public override void WriteByte(byte value)
=> this.Write(MemoryMarshal.CreateSpan(ref value, 1));
/// <inheritdoc/>
public override void Write(byte[] buffer, int offset, int count)
{
Guard.NotNull(buffer, nameof(buffer));
@ -344,157 +179,198 @@ internal sealed class ChunkedMemoryStream : Stream
const string bufferMessage = "Offset subtracted from the buffer length is less than count.";
Guard.IsFalse(buffer.Length - offset < count, nameof(buffer), bufferMessage);
this.WriteImpl(buffer.AsSpan(offset, count));
this.Write(buffer.AsSpan(offset, count));
}
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public override void Write(ReadOnlySpan<byte> buffer) => this.WriteImpl(buffer);
private void WriteImpl(ReadOnlySpan<byte> buffer)
public override void Write(ReadOnlySpan<byte> buffer)
{
this.EnsureNotDisposed();
if (this.memoryChunk is null)
int offset = 0;
int count = buffer.Length;
long remaining = this.memoryChunkBuffer.Length - this.position;
// Ensure we have enough capacity to write the data.
while (remaining < count)
{
this.memoryChunk = this.AllocateMemoryChunk();
this.writeChunk = this.memoryChunk;
this.writeOffset = 0;
this.memoryChunkBuffer.Expand();
remaining = this.memoryChunkBuffer.Length - this.position;
}
Guard.NotNull(this.writeChunk);
Span<byte> chunkBuffer = this.writeChunk.Buffer.GetSpan();
int chunkSize = this.writeChunk.Length;
int count = buffer.Length;
int offset = 0;
while (count > 0)
int bytesToWrite = count;
int bytesWritten = 0;
while (bytesToWrite > 0 && this.bufferIndex != this.memoryChunkBuffer.Length)
{
if (this.writeOffset == chunkSize)
bool moveToNextChunk = false;
MemoryChunk chunk = this.memoryChunkBuffer[this.bufferIndex];
int n = bytesToWrite;
int remainingBytesInCurrentChunk = chunk.Length - this.chunkIndex;
if (n >= remainingBytesInCurrentChunk)
{
// Allocate a new chunk if the current one is full
this.writeChunk.Next = this.AllocateMemoryChunk();
this.writeChunk = this.writeChunk.Next;
this.writeOffset = 0;
chunkBuffer = this.writeChunk.Buffer.GetSpan();
chunkSize = this.writeChunk.Length;
n = remainingBytesInCurrentChunk;
moveToNextChunk = true;
}
int copyCount = Math.Min(count, chunkSize - this.writeOffset);
buffer.Slice(offset, copyCount).CopyTo(chunkBuffer[this.writeOffset..]);
// Write n bytes to the current chunk
buffer.Slice(offset, n).CopyTo(chunk.Buffer.Slice(this.chunkIndex, n));
bytesToWrite -= n;
offset += n;
bytesWritten += n;
offset += copyCount;
count -= copyCount;
this.writeOffset += copyCount;
if (moveToNextChunk)
{
this.chunkIndex = 0;
this.bufferIndex++;
}
else
{
this.chunkIndex += n;
}
}
this.position += bytesWritten;
this.length += bytesWritten;
}
/// <inheritdoc/>
public override void WriteByte(byte value)
/// <summary>
/// Writes the entire contents of this memory stream to another stream.
/// </summary>
/// <param name="stream">The stream to write this memory stream to.</param>
/// <exception cref="ArgumentNullException"><paramref name="stream"/> is <see langword="null"/>.</exception>
/// <exception cref="ObjectDisposedException">The current or target stream is closed.</exception>
public void WriteTo(Stream stream)
{
Guard.NotNull(stream, nameof(stream));
this.EnsureNotDisposed();
if (this.memoryChunk is null)
this.Position = 0;
long remaining = this.length - this.position;
if (remaining <= 0)
{
this.memoryChunk = this.AllocateMemoryChunk();
this.writeChunk = this.memoryChunk;
this.writeOffset = 0;
// Already at the end of the stream, nothing to read
return;
}
Guard.NotNull(this.writeChunk);
int bytesToRead = (int)remaining;
int bytesRead = 0;
while (bytesToRead > 0 && this.bufferIndex != this.memoryChunkBuffer.Length)
{
bool moveToNextChunk = false;
MemoryChunk chunk = this.memoryChunkBuffer[this.bufferIndex];
int n = bytesToRead;
int remainingBytesInCurrentChunk = chunk.Length - this.chunkIndex;
if (n >= remainingBytesInCurrentChunk)
{
n = remainingBytesInCurrentChunk;
moveToNextChunk = true;
}
IMemoryOwner<byte> chunkBuffer = this.writeChunk.Buffer;
int chunkSize = this.writeChunk.Length;
// Read n bytes from the current chunk
stream.Write(chunk.Buffer.Memory.Span.Slice(this.chunkIndex, n));
bytesToRead -= n;
bytesRead += n;
if (this.writeOffset == chunkSize)
{
// Allocate a new chunk if the current one is full
this.writeChunk.Next = this.AllocateMemoryChunk();
this.writeChunk = this.writeChunk.Next;
this.writeOffset = 0;
chunkBuffer = this.writeChunk.Buffer;
if (moveToNextChunk)
{
this.chunkIndex = 0;
this.bufferIndex++;
}
else
{
this.chunkIndex += n;
}
}
chunkBuffer.GetSpan()[this.writeOffset++] = value;
this.position += bytesRead;
}
/// <summary>
/// Copy entire buffer into an array.
/// Writes the stream contents to a byte array, regardless of the <see cref="Position"/> property.
/// </summary>
/// <returns>The <see cref="T:byte[]"/>.</returns>
/// <returns>A new <see cref="T:byte[]"/>.</returns>
public byte[] ToArray()
{
int length = (int)this.Length; // This will throw if stream is closed
byte[] copy = new byte[this.Length];
MemoryChunk? backupReadChunk = this.readChunk;
int backupReadOffset = this.readOffset;
this.readChunk = this.memoryChunk;
this.readOffset = 0;
this.Read(copy, 0, length);
this.readChunk = backupReadChunk;
this.readOffset = backupReadOffset;
this.EnsureNotDisposed();
long position = this.position;
byte[] copy = new byte[this.length];
this.Position = 0;
_ = this.Read(copy, 0, copy.Length);
this.Position = position;
return copy;
}
/// <summary>
/// Write remainder of this stream to another stream.
/// </summary>
/// <param name="stream">The stream to write to.</param>
public void WriteTo(Stream stream)
/// <inheritdoc/>
protected override void Dispose(bool disposing)
{
this.EnsureNotDisposed();
Guard.NotNull(stream, nameof(stream));
if (this.isDisposed)
{
return;
}
if (this.readChunk is null)
try
{
if (this.memoryChunk is null)
this.isDisposed = true;
if (disposing)
{
return;
this.memoryChunkBuffer.Dispose();
}
this.readChunk = this.memoryChunk;
this.readOffset = 0;
this.bufferIndex = 0;
this.chunkIndex = 0;
this.position = 0;
this.length = 0;
}
finally
{
base.Dispose(disposing);
}
}
private void SetPosition(long value)
{
long newPosition = value;
if (newPosition < 0)
{
throw new ArgumentOutOfRangeException(nameof(value));
}
IMemoryOwner<byte> chunkBuffer = this.readChunk.Buffer;
int chunkSize = this.readChunk.Length;
if (this.readChunk.Next is null)
this.position = newPosition;
// Find the current chunk & current chunk index
int currentChunkIndex = 0;
long offset = newPosition;
// If the new position is greater than the length of the stream, set the position to the end of the stream
if (offset > 0 && offset >= this.memoryChunkBuffer.Length)
{
chunkSize = this.writeOffset;
this.bufferIndex = this.memoryChunkBuffer.ChunkCount - 1;
this.chunkIndex = this.memoryChunkBuffer[this.bufferIndex].Length - 1;
return;
}
// Following code mirrors Read() logic (readChunk/readOffset should
// point just past last byte of last chunk when done)
// loop until end of chunks is found
while (true)
// Loop through the current chunks, as we increment the chunk index, we subtract the length of the chunk
// from the offset. Once the offset is less than the length of the chunk, we have found the correct chunk.
while (offset != 0)
{
if (this.readOffset == chunkSize)
int chunkLength = this.memoryChunkBuffer[currentChunkIndex].Length;
if (offset < chunkLength)
{
// Exit if no more chunks are currently available
if (this.readChunk.Next is null)
{
break;
}
this.readChunk = this.readChunk.Next;
this.readOffset = 0;
chunkBuffer = this.readChunk.Buffer;
chunkSize = this.readChunk.Length;
if (this.readChunk.Next is null)
{
chunkSize = this.writeOffset;
}
// Found the correct chunk and the corresponding index
break;
}
int writeCount = chunkSize - this.readOffset;
stream.Write(chunkBuffer.GetSpan(), this.readOffset, writeCount);
this.readOffset = chunkSize;
offset -= chunkLength;
currentChunkIndex++;
}
this.bufferIndex = currentChunkIndex;
// Safe to cast here as we know the offset is less than the chunk length.
this.chunkIndex = (int)offset;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
@ -507,48 +383,66 @@ internal sealed class ChunkedMemoryStream : Stream
}
[MethodImpl(MethodImplOptions.NoInlining)]
private static void ThrowDisposed() => throw new ObjectDisposedException(null, "The stream is closed.");
private static void ThrowDisposed() => throw new ObjectDisposedException(nameof(ChunkedMemoryStream), "The stream is closed.");
[MethodImpl(MethodImplOptions.NoInlining)]
private static void ThrowArgumentOutOfRange(string value) => throw new ArgumentOutOfRangeException(value);
private sealed class MemoryChunkBuffer : IDisposable
{
private readonly List<MemoryChunk> memoryChunks = new();
private readonly MemoryAllocator allocator;
private readonly int allocatorCapacity;
private bool isDisposed;
[MethodImpl(MethodImplOptions.NoInlining)]
private static void ThrowInvalidSeek() => throw new ArgumentException("Invalid seek origin.");
public MemoryChunkBuffer(MemoryAllocator allocator)
{
this.allocatorCapacity = allocator.GetBufferCapacityInBytes();
this.allocator = allocator;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private MemoryChunk AllocateMemoryChunk()
{
// Tweak our buffer sizes to take the minimum of the provided buffer sizes
// or the allocator buffer capacity which provides us with the largest
// available contiguous buffer size.
IMemoryOwner<byte> buffer = this.allocator.Allocate<byte>(Math.Min(this.allocatorCapacity, GetChunkSize(this.chunkCount++)));
public int ChunkCount => this.memoryChunks.Count;
return new MemoryChunk(buffer)
public long Length { get; private set; }
public MemoryChunk this[int index] => this.memoryChunks[index];
public void Expand()
{
Next = null,
Length = buffer.Length()
};
}
IMemoryOwner<byte> buffer =
this.allocator.Allocate<byte>(Math.Min(this.allocatorCapacity, GetChunkSize(this.ChunkCount)));
private static void ReleaseMemoryChunks(MemoryChunk? chunk)
{
while (chunk != null)
MemoryChunk chunk = new(buffer)
{
Length = buffer.Length()
};
this.memoryChunks.Add(chunk);
this.Length += chunk.Length;
}
public void Dispose()
{
chunk.Dispose();
chunk = chunk.Next;
if (!this.isDisposed)
{
foreach (MemoryChunk chunk in this.memoryChunks)
{
chunk.Dispose();
}
this.memoryChunks.Clear();
this.Length = 0;
this.isDisposed = true;
}
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static int GetChunkSize(int i)
{
// Increment chunks sizes with moderate speed, but without using too many buffers from the same ArrayPool bucket of the default MemoryAllocator.
// https://github.com/SixLabors/ImageSharp/pull/2006#issuecomment-1066244720
#pragma warning disable IDE1006 // Naming Styles
const int _128K = 1 << 17;
const int _4M = 1 << 22;
return i < 16 ? _128K * (1 << (int)((uint)i / 4)) : _4M;
#pragma warning restore IDE1006 // Naming Styles
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static int GetChunkSize(int i)
{
// Increment chunks sizes with moderate speed, but without using too many buffers from the
// same ArrayPool bucket of the default MemoryAllocator.
// https://github.com/SixLabors/ImageSharp/pull/2006#issuecomment-1066244720
const int b128K = 1 << 17;
const int b4M = 1 << 22;
return i < 16 ? b128K * (1 << (int)((uint)i / 4)) : b4M;
}
}
private sealed class MemoryChunk : IDisposable
@ -559,27 +453,15 @@ internal sealed class ChunkedMemoryStream : Stream
public IMemoryOwner<byte> Buffer { get; }
public MemoryChunk? Next { get; set; }
public int Length { get; init; }
private void Dispose(bool disposing)
public void Dispose()
{
if (!this.isDisposed)
{
if (disposing)
{
this.Buffer.Dispose();
}
this.Buffer.Dispose();
this.isDisposed = true;
}
}
public void Dispose()
{
this.Dispose(disposing: true);
GC.SuppressFinalize(this);
}
}
}

5
src/ImageSharp/ImageSharp.csproj

@ -13,6 +13,7 @@
<PackageTags>Image Resize Crop Gif Jpg Jpeg Bitmap Pbm Png Tga Tiff WebP NetCore</PackageTags>
<Description>A new, fully featured, fully managed, cross-platform, 2D graphics API for .NET</Description>
<Configurations>Debug;Release</Configurations>
<IsTrimmable>true</IsTrimmable>
</PropertyGroup>
<!-- This enables the nullable analysis and treats all nullable warnings as error-->
@ -29,14 +30,12 @@
<Choose>
<When Condition="$(SIXLABORS_TESTING_PREVIEW) == true">
<PropertyGroup>
<TargetFrameworks>net8.0</TargetFrameworks>
<IsTrimmable>true</IsTrimmable>
<TargetFrameworks>net8.0;net9.0</TargetFrameworks>
</PropertyGroup>
</When>
<Otherwise>
<PropertyGroup>
<TargetFrameworks>net8.0</TargetFrameworks>
<IsTrimmable>true</IsTrimmable>
</PropertyGroup>
</Otherwise>
</Choose>

4
src/ImageSharp/Image{TPixel}.cs

@ -160,7 +160,7 @@ public sealed class Image<TPixel> : Image
/// <summary>
/// Gets the root frame.
/// </summary>
private IPixelSource<TPixel> PixelSourceUnsafe => this.frames.RootFrameUnsafe;
private ImageFrame<TPixel> PixelSourceUnsafe => this.frames.RootFrameUnsafe;
/// <summary>
/// Gets or sets the pixel at the specified position.
@ -324,7 +324,7 @@ public sealed class Image<TPixel> : Image
}
/// <summary>
/// Clones the current image
/// Clones the current image.
/// </summary>
/// <returns>Returns a new image with all the same metadata as the original.</returns>
public Image<TPixel> Clone() => this.Clone(this.Configuration);

2
src/ImageSharp/Metadata/Profiles/Exif/ExifWriter.cs

@ -241,7 +241,7 @@ internal sealed class ExifWriter
return true;
}
private static uint GetLength(IList<IExifValue> values)
private static uint GetLength(List<IExifValue> values)
{
if (values.Count == 0)
{

2
src/ImageSharp/Metadata/Profiles/ICC/DataReader/IccDataReader.TagDataEntry.cs

@ -144,7 +144,7 @@ internal sealed partial class IccDataReader
ushort channelCount = this.ReadUInt16();
var colorant = (IccColorantEncoding)this.ReadUInt16();
if (Enum.IsDefined(typeof(IccColorantEncoding), colorant) && colorant != IccColorantEncoding.Unknown)
if (Enum.IsDefined(colorant) && colorant != IccColorantEncoding.Unknown)
{
// The type is known and so are the values (they are constant)
// channelCount should always be 3 but it doesn't really matter if it's not

6
src/ImageSharp/Metadata/Profiles/ICC/IccProfile.cs

@ -155,9 +155,9 @@ public sealed class IccProfile : IDeepCloneable<IccProfile>
}
return arrayValid &&
Enum.IsDefined(typeof(IccColorSpaceType), this.Header.DataColorSpace) &&
Enum.IsDefined(typeof(IccColorSpaceType), this.Header.ProfileConnectionSpace) &&
Enum.IsDefined(typeof(IccRenderingIntent), this.Header.RenderingIntent) &&
Enum.IsDefined(this.Header.DataColorSpace) &&
Enum.IsDefined(this.Header.ProfileConnectionSpace) &&
Enum.IsDefined(this.Header.RenderingIntent) &&
this.Header.Size is >= minSize and < maxSize;
}

8
src/ImageSharp/Processing/Extensions/Convolution/BokehBlurExtensions.cs

@ -44,13 +44,13 @@ public static class BokehBlurExtensions
/// Applies a bokeh blur to the image.
/// </summary>
/// <param name="source">The current image processing context.</param>
/// <param name="radius">The 'radius' value representing the size of the area to sample.</param>
/// <param name="components">The 'components' value representing the number of kernels to use to approximate the bokeh effect.</param>
/// <param name="gamma">The gamma highlight factor to use to emphasize bright spots in the source image</param>
/// <param name="rectangle">
/// The <see cref="Rectangle"/> structure that specifies the portion of the image object to alter.
/// </param>
/// <param name="radius">The 'radius' value representing the size of the area to sample.</param>
/// <param name="components">The 'components' value representing the number of kernels to use to approximate the bokeh effect.</param>
/// <param name="gamma">The gamma highlight factor to use to emphasize bright spots in the source image</param>
/// <returns>The <see cref="IImageProcessingContext"/>.</returns>
public static IImageProcessingContext BokehBlur(this IImageProcessingContext source, int radius, int components, float gamma, Rectangle rectangle)
public static IImageProcessingContext BokehBlur(this IImageProcessingContext source, Rectangle rectangle, int radius, int components, float gamma)
=> source.ApplyProcessor(new BokehBlurProcessor(radius, components, gamma), rectangle);
}

14
src/ImageSharp/Processing/Extensions/Convolution/BoxBlurExtensions.cs

@ -44,10 +44,10 @@ public static class BoxBlurExtensions
/// Applies a box blur to the image.
/// </summary>
/// <param name="source">The current image processing context.</param>
/// <param name="radius">The 'radius' value representing the size of the area to sample.</param>
/// <param name="rectangle">
/// The <see cref="Rectangle"/> structure that specifies the portion of the image object to alter.
/// </param>
/// <param name="radius">The 'radius' value representing the size of the area to sample.</param>
/// <param name="borderWrapModeX">
/// The <see cref="BorderWrappingMode"/> to use when mapping the pixels outside of the border, in X direction.
/// </param>
@ -55,9 +55,11 @@ public static class BoxBlurExtensions
/// The <see cref="BorderWrappingMode"/> to use when mapping the pixels outside of the border, in Y direction.
/// </param>
/// <returns>The <see cref="IImageProcessingContext"/>.</returns>
public static IImageProcessingContext BoxBlur(this IImageProcessingContext source, int radius, Rectangle rectangle, BorderWrappingMode borderWrapModeX, BorderWrappingMode borderWrapModeY)
{
var processor = new BoxBlurProcessor(radius, borderWrapModeX, borderWrapModeY);
return source.ApplyProcessor(processor, rectangle);
}
public static IImageProcessingContext BoxBlur(
this IImageProcessingContext source,
Rectangle rectangle,
int radius,
BorderWrappingMode borderWrapModeX,
BorderWrappingMode borderWrapModeY)
=> source.ApplyProcessor(new BoxBlurProcessor(radius, borderWrapModeX, borderWrapModeY), rectangle);
}

89
src/ImageSharp/Processing/Extensions/Convolution/ConvolutionExtensions.cs

@ -0,0 +1,89 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using SixLabors.ImageSharp.Processing.Processors.Convolution;
namespace SixLabors.ImageSharp.Processing.Extensions.Convolution;
/// <summary>
/// Defines general convolution extensions to apply on an <see cref="Image"/>
/// using Mutate/Clone.
/// </summary>
public static class ConvolutionExtensions
{
/// <summary>
/// Applies a convolution filter to the image.
/// </summary>
/// <param name="source">The current image processing context.</param>
/// <param name="kernelXY">The convolution kernel to apply.</param>
/// <returns>The <see cref="IImageProcessingContext"/>.</returns>
public static IImageProcessingContext Convolve(this IImageProcessingContext source, DenseMatrix<float> kernelXY)
=> Convolve(source, kernelXY, false);
/// <summary>
/// Applies a convolution filter to the image.
/// </summary>
/// <param name="source">The current image processing context.</param>
/// <param name="kernelXY">The convolution kernel to apply.</param>
/// <param name="preserveAlpha">Whether the convolution filter is applied to alpha as well as the color channels.</param>
/// <returns>The <see cref="IImageProcessingContext"/>.</returns>
public static IImageProcessingContext Convolve(this IImageProcessingContext source, DenseMatrix<float> kernelXY, bool preserveAlpha)
=> Convolve(source, kernelXY, preserveAlpha, BorderWrappingMode.Repeat, BorderWrappingMode.Repeat);
/// <summary>
/// Applies a convolution filter to the image.
/// </summary>
/// <param name="source">The current image processing context.</param>
/// <param name="kernelXY">The convolution kernel to apply.</param>
/// <param name="preserveAlpha">Whether the convolution filter is applied to alpha as well as the color channels.</param>
/// <param name="borderWrapModeX">The <see cref="BorderWrappingMode"/> to use when mapping the pixels outside of the border, in X direction.</param>
/// <param name="borderWrapModeY">The <see cref="BorderWrappingMode"/> to use when mapping the pixels outside of the border, in Y direction.</param>
/// <returns>The <see cref="IImageProcessingContext"/>.</returns>
public static IImageProcessingContext Convolve(
this IImageProcessingContext source,
DenseMatrix<float> kernelXY,
bool preserveAlpha,
BorderWrappingMode borderWrapModeX,
BorderWrappingMode borderWrapModeY)
=> source.ApplyProcessor(new ConvolutionProcessor(kernelXY, preserveAlpha, borderWrapModeX, borderWrapModeY));
/// <summary>
/// Applies a convolution filter to the image.
/// </summary>
/// <param name="source">The current image processing context.</param>
/// <param name="rectangle">The rectangle structure that specifies the portion of the image object to alter.</param>
/// <param name="kernelXY">The convolution kernel to apply.</param>
/// <returns>The <see cref="IImageProcessingContext"/>.</returns>
public static IImageProcessingContext Convolve(this IImageProcessingContext source, Rectangle rectangle, DenseMatrix<float> kernelXY)
=> Convolve(source, rectangle, kernelXY, false);
/// <summary>
/// Applies a convolution filter to the image.
/// </summary>
/// <param name="source">The current image processing context.</param>
/// <param name="rectangle">The rectangle structure that specifies the portion of the image object to alter.</param>
/// <param name="kernelXY">The convolution kernel to apply.</param>
/// <param name="preserveAlpha">Whether the convolution filter is applied to alpha as well as the color channels.</param>
/// <returns>The <see cref="IImageProcessingContext"/>.</returns>
public static IImageProcessingContext Convolve(this IImageProcessingContext source, Rectangle rectangle, DenseMatrix<float> kernelXY, bool preserveAlpha)
=> Convolve(source, rectangle, kernelXY, preserveAlpha, BorderWrappingMode.Repeat, BorderWrappingMode.Repeat);
/// <summary>
/// Applies a convolution filter to the image.
/// </summary>
/// <param name="source">The current image processing context.</param>
/// <param name="rectangle">The rectangle structure that specifies the portion of the image object to alter.</param>
/// <param name="kernelXY">The convolution kernel to apply.</param>
/// <param name="preserveAlpha">Whether the convolution filter is applied to alpha as well as the color channels.</param>
/// <param name="borderWrapModeX">The <see cref="BorderWrappingMode"/> to use when mapping the pixels outside of the border, in X direction.</param>
/// <param name="borderWrapModeY">The <see cref="BorderWrappingMode"/> to use when mapping the pixels outside of the border, in Y direction.</param>
/// <returns>The <see cref="IImageProcessingContext"/>.</returns>
public static IImageProcessingContext Convolve(
this IImageProcessingContext source,
Rectangle rectangle,
DenseMatrix<float> kernelXY,
bool preserveAlpha,
BorderWrappingMode borderWrapModeX,
BorderWrappingMode borderWrapModeY)
=> source.ApplyProcessor(new ConvolutionProcessor(kernelXY, preserveAlpha, borderWrapModeX, borderWrapModeY), rectangle);
}

124
src/ImageSharp/Processing/Extensions/Convolution/DetectEdgesExtensions.cs

@ -16,8 +16,8 @@ public static class DetectEdgesExtensions
/// </summary>
/// <param name="source">The current image processing context.</param>
/// <returns>The <see cref="IImageProcessingContext"/>.</returns>
public static IImageProcessingContext DetectEdges(this IImageProcessingContext source) =>
DetectEdges(source, KnownEdgeDetectorKernels.Sobel);
public static IImageProcessingContext DetectEdges(this IImageProcessingContext source)
=> DetectEdges(source, KnownEdgeDetectorKernels.Sobel);
/// <summary>
/// Detects any edges within the image.
@ -28,10 +28,8 @@ public static class DetectEdgesExtensions
/// The <see cref="Rectangle"/> structure that specifies the portion of the image object to alter.
/// </param>
/// <returns>The <see cref="IImageProcessingContext"/>.</returns>
public static IImageProcessingContext DetectEdges(
this IImageProcessingContext source,
Rectangle rectangle) =>
DetectEdges(source, KnownEdgeDetectorKernels.Sobel, rectangle);
public static IImageProcessingContext DetectEdges(this IImageProcessingContext source, Rectangle rectangle)
=> DetectEdges(source, rectangle, KnownEdgeDetectorKernels.Sobel);
/// <summary>
/// Detects any edges within the image operating in grayscale mode.
@ -39,10 +37,8 @@ public static class DetectEdgesExtensions
/// <param name="source">The current image processing context.</param>
/// <param name="kernel">The 2D edge detector kernel.</param>
/// <returns>The <see cref="IImageProcessingContext"/>.</returns>
public static IImageProcessingContext DetectEdges(
this IImageProcessingContext source,
EdgeDetector2DKernel kernel) =>
DetectEdges(source, kernel, true);
public static IImageProcessingContext DetectEdges(this IImageProcessingContext source, EdgeDetector2DKernel kernel)
=> DetectEdges(source, kernel, true);
/// <summary>
/// Detects any edges within the image using a <see cref="EdgeDetector2DKernel"/>.
@ -57,49 +53,41 @@ public static class DetectEdgesExtensions
this IImageProcessingContext source,
EdgeDetector2DKernel kernel,
bool grayscale)
{
var processor = new EdgeDetector2DProcessor(kernel, grayscale);
source.ApplyProcessor(processor);
return source;
}
=> source.ApplyProcessor(new EdgeDetector2DProcessor(kernel, grayscale));
/// <summary>
/// Detects any edges within the image operating in grayscale mode.
/// </summary>
/// <param name="source">The current image processing context.</param>
/// <param name="kernel">The 2D edge detector kernel.</param>
/// <param name="rectangle">
/// The <see cref="Rectangle"/> structure that specifies the portion of the image object to alter.
/// </param>
/// <param name="kernel">The 2D edge detector kernel.</param>
/// <returns>The <see cref="IImageProcessingContext"/>.</returns>
public static IImageProcessingContext DetectEdges(
this IImageProcessingContext source,
EdgeDetector2DKernel kernel,
Rectangle rectangle) =>
DetectEdges(source, kernel, true, rectangle);
Rectangle rectangle,
EdgeDetector2DKernel kernel)
=> DetectEdges(source, rectangle, kernel, true);
/// <summary>
/// Detects any edges within the image using a <see cref="EdgeDetector2DKernel"/>.
/// </summary>
/// <param name="source">The current image processing context.</param>
/// <param name="rectangle">
/// The <see cref="Rectangle"/> structure that specifies the portion of the image object to alter.
/// </param>
/// <param name="kernel">The 2D edge detector kernel.</param>
/// <param name="grayscale">
/// Whether to convert the image to grayscale before performing edge detection.
/// </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"/>.</returns>
public static IImageProcessingContext DetectEdges(
this IImageProcessingContext source,
Rectangle rectangle,
EdgeDetector2DKernel kernel,
bool grayscale,
Rectangle rectangle)
{
var processor = new EdgeDetector2DProcessor(kernel, grayscale);
source.ApplyProcessor(processor, rectangle);
return source;
}
bool grayscale)
=> source.ApplyProcessor(new EdgeDetector2DProcessor(kernel, grayscale), rectangle);
/// <summary>
/// Detects any edges within the image operating in grayscale mode.
@ -107,10 +95,8 @@ public static class DetectEdgesExtensions
/// <param name="source">The current image processing context.</param>
/// <param name="kernel">The edge detector kernel.</param>
/// <returns>The <see cref="IImageProcessingContext"/>.</returns>
public static IImageProcessingContext DetectEdges(
this IImageProcessingContext source,
EdgeDetectorKernel kernel) =>
DetectEdges(source, kernel, true);
public static IImageProcessingContext DetectEdges(this IImageProcessingContext source, EdgeDetectorKernel kernel)
=> DetectEdges(source, kernel, true);
/// <summary>
/// Detects any edges within the image using a <see cref="EdgeDetectorKernel"/>.
@ -125,66 +111,56 @@ public static class DetectEdgesExtensions
this IImageProcessingContext source,
EdgeDetectorKernel kernel,
bool grayscale)
{
var processor = new EdgeDetectorProcessor(kernel, grayscale);
source.ApplyProcessor(processor);
return source;
}
=> source.ApplyProcessor(new EdgeDetectorProcessor(kernel, grayscale));
/// <summary>
/// Detects any edges within the image operating in grayscale mode.
/// </summary>
/// <param name="source">The current image processing context.</param>
/// <param name="kernel">The edge detector kernel.</param>
/// <param name="rectangle">
/// The <see cref="Rectangle"/> structure that specifies the portion of the image object to alter.
/// </param>
/// <param name="kernel">The edge detector kernel.</param>
/// <returns>The <see cref="IImageProcessingContext"/>.</returns>
public static IImageProcessingContext DetectEdges(
this IImageProcessingContext source,
EdgeDetectorKernel kernel,
Rectangle rectangle) =>
DetectEdges(source, kernel, true, rectangle);
Rectangle rectangle,
EdgeDetectorKernel kernel)
=> DetectEdges(source, rectangle, kernel, true);
/// <summary>
/// Detects any edges within the image using a <see cref="EdgeDetectorKernel"/>.
/// </summary>
/// <param name="source">The current image processing context.</param>
/// <param name="rectangle">
/// The <see cref="Rectangle"/> structure that specifies the portion of the image object to alter.
/// </param>
/// <param name="kernel">The edge detector kernel.</param>
/// <param name="grayscale">
/// Whether to convert the image to grayscale before performing edge detection.
/// </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"/>.</returns>
public static IImageProcessingContext DetectEdges(
this IImageProcessingContext source,
Rectangle rectangle,
EdgeDetectorKernel kernel,
bool grayscale,
Rectangle rectangle)
{
var processor = new EdgeDetectorProcessor(kernel, grayscale);
source.ApplyProcessor(processor, rectangle);
return source;
}
bool grayscale)
=> source.ApplyProcessor(new EdgeDetectorProcessor(kernel, grayscale), rectangle);
/// <summary>
/// Detects any edges within the image operating in grayscale mode.
/// </summary>
/// <param name="source">The current image processing context.</param>
/// <param name="kernel">Thecompass edge detector kernel.</param>
/// <param name="kernel">The compass edge detector kernel.</param>
/// <returns>The <see cref="IImageProcessingContext"/>.</returns>
public static IImageProcessingContext DetectEdges(
this IImageProcessingContext source,
EdgeDetectorCompassKernel kernel) =>
DetectEdges(source, kernel, true);
public static IImageProcessingContext DetectEdges(this IImageProcessingContext source, EdgeDetectorCompassKernel kernel)
=> DetectEdges(source, kernel, true);
/// <summary>
/// Detects any edges within the image using a <see cref="EdgeDetectorCompassKernel"/>.
/// </summary>
/// <param name="source">The current image processing context.</param>
/// <param name="kernel">Thecompass edge detector kernel.</param>
/// <param name="kernel">The compass edge detector kernel.</param>
/// <param name="grayscale">
/// Whether to convert the image to grayscale before performing edge detection.
/// </param>
@ -193,47 +169,39 @@ public static class DetectEdgesExtensions
this IImageProcessingContext source,
EdgeDetectorCompassKernel kernel,
bool grayscale)
{
var processor = new EdgeDetectorCompassProcessor(kernel, grayscale);
source.ApplyProcessor(processor);
return source;
}
=> source.ApplyProcessor(new EdgeDetectorCompassProcessor(kernel, grayscale));
/// <summary>
/// Detects any edges within the image operating in grayscale mode.
/// </summary>
/// <param name="source">The current image processing context.</param>
/// <param name="kernel">Thecompass edge detector kernel.</param>
/// <param name="rectangle">
/// The <see cref="Rectangle"/> structure that specifies the portion of the image object to alter.
/// </param>
/// <param name="kernel">The compass edge detector kernel.</param>
/// <returns>The <see cref="IImageProcessingContext"/>.</returns>
public static IImageProcessingContext DetectEdges(
this IImageProcessingContext source,
EdgeDetectorCompassKernel kernel,
Rectangle rectangle) =>
DetectEdges(source, kernel, true, rectangle);
Rectangle rectangle,
EdgeDetectorCompassKernel kernel)
=> DetectEdges(source, rectangle, kernel, true);
/// <summary>
/// Detects any edges within the image using a <see cref="EdgeDetectorCompassKernel"/>.
/// </summary>
/// <param name="source">The current image processing context.</param>
/// <param name="kernel">Thecompass edge detector kernel.</param>
/// <param name="grayscale">
/// Whether to convert the image to grayscale before performing edge detection.
/// </param>
/// <param name="rectangle">
/// The <see cref="Rectangle"/> structure that specifies the portion of the image object to alter.
/// </param>
/// <param name="kernel">The compass edge detector kernel.</param>
/// <param name="grayscale">
/// Whether to convert the image to grayscale before performing edge detection.
/// </param>
/// <returns>The <see cref="IImageProcessingContext"/>.</returns>
public static IImageProcessingContext DetectEdges(
this IImageProcessingContext source,
Rectangle rectangle,
EdgeDetectorCompassKernel kernel,
bool grayscale,
Rectangle rectangle)
{
var processor = new EdgeDetectorCompassProcessor(kernel, grayscale);
source.ApplyProcessor(processor, rectangle);
return source;
}
bool grayscale)
=> source.ApplyProcessor(new EdgeDetectorCompassProcessor(kernel, grayscale), rectangle);
}

21
src/ImageSharp/Processing/Extensions/Convolution/GaussianBlurExtensions.cs

@ -32,22 +32,25 @@ public static class GaussianBlurExtensions
/// Applies a Gaussian blur to the image.
/// </summary>
/// <param name="source">The current image processing context.</param>
/// <param name="sigma">The 'sigma' value representing the weight of the blur.</param>
/// <param name="rectangle">
/// The <see cref="Rectangle"/> structure that specifies the portion of the image object to alter.
/// </param>
/// <param name="sigma">The 'sigma' value representing the weight of the blur.</param>
/// <returns>The <see cref="IImageProcessingContext"/>.</returns>
public static IImageProcessingContext GaussianBlur(this IImageProcessingContext source, float sigma, Rectangle rectangle)
public static IImageProcessingContext GaussianBlur(
this IImageProcessingContext source,
Rectangle rectangle,
float sigma)
=> source.ApplyProcessor(new GaussianBlurProcessor(sigma), rectangle);
/// <summary>
/// Applies a Gaussian blur to the image.
/// </summary>
/// <param name="source">The current image processing context.</param>
/// <param name="sigma">The 'sigma' value representing the weight of the blur.</param>
/// <param name="rectangle">
/// The <see cref="Rectangle"/> structure that specifies the portion of the image object to alter.
/// </param>
/// <param name="sigma">The 'sigma' value representing the weight of the blur.</param>
/// <param name="borderWrapModeX">
/// The <see cref="BorderWrappingMode"/> to use when mapping the pixels outside of the border, in X direction.
/// </param>
@ -55,9 +58,11 @@ public static class GaussianBlurExtensions
/// The <see cref="BorderWrappingMode"/> to use when mapping the pixels outside of the border, in Y direction.
/// </param>
/// <returns>The <see cref="IImageProcessingContext"/>.</returns>
public static IImageProcessingContext GaussianBlur(this IImageProcessingContext source, float sigma, Rectangle rectangle, BorderWrappingMode borderWrapModeX, BorderWrappingMode borderWrapModeY)
{
var processor = new GaussianBlurProcessor(sigma, borderWrapModeX, borderWrapModeY);
return source.ApplyProcessor(processor, rectangle);
}
public static IImageProcessingContext GaussianBlur(
this IImageProcessingContext source,
Rectangle rectangle,
float sigma,
BorderWrappingMode borderWrapModeX,
BorderWrappingMode borderWrapModeY)
=> source.ApplyProcessor(new GaussianBlurProcessor(sigma, borderWrapModeX, borderWrapModeY), rectangle);
}

28
src/ImageSharp/Processing/Extensions/Convolution/GaussianSharpenExtensions.cs

@ -16,8 +16,8 @@ public static class GaussianSharpenExtensions
/// </summary>
/// <param name="source">The current image processing context.</param>
/// <returns>The <see cref="IImageProcessingContext"/>.</returns>
public static IImageProcessingContext GaussianSharpen(this IImageProcessingContext source) =>
source.ApplyProcessor(new GaussianSharpenProcessor());
public static IImageProcessingContext GaussianSharpen(this IImageProcessingContext source)
=> source.ApplyProcessor(new GaussianSharpenProcessor());
/// <summary>
/// Applies a Gaussian sharpening filter to the image.
@ -25,32 +25,32 @@ public static class GaussianSharpenExtensions
/// <param name="source">The current image processing context.</param>
/// <param name="sigma">The 'sigma' value representing the weight of the blur.</param>
/// <returns>The <see cref="IImageProcessingContext"/>.</returns>
public static IImageProcessingContext GaussianSharpen(this IImageProcessingContext source, float sigma) =>
source.ApplyProcessor(new GaussianSharpenProcessor(sigma));
public static IImageProcessingContext GaussianSharpen(this IImageProcessingContext source, float sigma)
=> source.ApplyProcessor(new GaussianSharpenProcessor(sigma));
/// <summary>
/// Applies a Gaussian sharpening filter to the image.
/// </summary>
/// <param name="source">The current image processing context.</param>
/// <param name="sigma">The 'sigma' value representing the weight of the blur.</param>
/// <param name="rectangle">
/// The <see cref="Rectangle"/> structure that specifies the portion of the image object to alter.
/// </param>
/// <param name="sigma">The 'sigma' value representing the weight of the blur.</param>
/// <returns>The <see cref="IImageProcessingContext"/>.</returns>
public static IImageProcessingContext GaussianSharpen(
this IImageProcessingContext source,
float sigma,
Rectangle rectangle) =>
Rectangle rectangle,
float sigma) =>
source.ApplyProcessor(new GaussianSharpenProcessor(sigma), rectangle);
/// <summary>
/// Applies a Gaussian sharpening filter to the image.
/// </summary>
/// <param name="source">The current image processing context.</param>
/// <param name="sigma">The 'sigma' value representing the weight of the blur.</param>
/// <param name="rectangle">
/// The <see cref="Rectangle"/> structure that specifies the portion of the image object to alter.
/// </param>
/// <param name="sigma">The 'sigma' value representing the weight of the blur.</param>
/// <param name="borderWrapModeX">
/// The <see cref="BorderWrappingMode"/> to use when mapping the pixels outside of the border, in X direction.
/// </param>
@ -58,9 +58,11 @@ public static class GaussianSharpenExtensions
/// The <see cref="BorderWrappingMode"/> to use when mapping the pixels outside of the border, in Y direction.
/// </param>
/// <returns>The <see cref="IImageProcessingContext"/>.</returns>
public static IImageProcessingContext GaussianSharpen(this IImageProcessingContext source, float sigma, Rectangle rectangle, BorderWrappingMode borderWrapModeX, BorderWrappingMode borderWrapModeY)
{
var processor = new GaussianSharpenProcessor(sigma, borderWrapModeX, borderWrapModeY);
return source.ApplyProcessor(processor, rectangle);
}
public static IImageProcessingContext GaussianSharpen(
this IImageProcessingContext source,
Rectangle rectangle,
float sigma,
BorderWrappingMode borderWrapModeX,
BorderWrappingMode borderWrapModeY)
=> source.ApplyProcessor(new GaussianSharpenProcessor(sigma, borderWrapModeX, borderWrapModeY), rectangle);
}

17
src/ImageSharp/Processing/Extensions/Convolution/MedianBlurExtensions.cs

@ -20,21 +20,28 @@ public static class MedianBlurExtensions
/// Whether the filter is applied to alpha as well as the color channels.
/// </param>
/// <returns>The <see cref="IImageProcessingContext"/>.</returns>
public static IImageProcessingContext MedianBlur(this IImageProcessingContext source, int radius, bool preserveAlpha)
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 current image processing context.</param>
/// <param name="rectangle">
/// The <see cref="Rectangle"/> structure that specifies the portion of the image object to alter.
/// </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"/>.</returns>
public static IImageProcessingContext MedianBlur(this IImageProcessingContext source, int radius, bool preserveAlpha, Rectangle rectangle)
public static IImageProcessingContext MedianBlur(
this IImageProcessingContext source,
Rectangle rectangle,
int radius,
bool preserveAlpha)
=> source.ApplyProcessor(new MedianBlurProcessor(radius, preserveAlpha), rectangle);
}

6
src/ImageSharp/Processing/Processors/Convolution/Convolution2DProcessor{TPixel}.cs

@ -62,14 +62,14 @@ internal class Convolution2DProcessor<TPixel> : ImageProcessor<TPixel>
source.CopyTo(targetPixels);
var interest = Rectangle.Intersect(this.SourceRectangle, source.Bounds());
Rectangle interest = Rectangle.Intersect(this.SourceRectangle, source.Bounds());
using (var map = new KernelSamplingMap(allocator))
using (KernelSamplingMap map = new(allocator))
{
// Since the kernel sizes are identical we can use a single map.
map.BuildSamplingOffsetMap(this.KernelY, interest);
var operation = new Convolution2DRowOperation<TPixel>(
Convolution2DRowOperation<TPixel> operation = new(
interest,
targetPixels,
source.PixelBuffer,

54
src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor{TPixel}.cs

@ -35,18 +35,48 @@ internal class Convolution2PassProcessor<TPixel> : ImageProcessor<TPixel>
Rectangle sourceRectangle,
BorderWrappingMode borderWrapModeX,
BorderWrappingMode borderWrapModeY)
: this(configuration, kernel, kernel, preserveAlpha, source, sourceRectangle, borderWrapModeX, borderWrapModeY)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="Convolution2PassProcessor{TPixel}"/> class.
/// </summary>
/// <param name="configuration">The configuration which allows altering default behaviour or extending the library.</param>
/// <param name="kernelX">The 1D convolution kernel. X Direction</param>
/// <param name="kernelY">The 1D convolution kernel. Y Direction</param>
/// <param name="preserveAlpha">Whether the convolution filter is applied to alpha as well as the color channels.</param>
/// <param name="source">The source <see cref="Image{TPixel}"/> for the current processor instance.</param>
/// <param name="sourceRectangle">The source area to process for the current processor instance.</param>
/// <param name="borderWrapModeX">The <see cref="BorderWrappingMode"/> to use when mapping the pixels outside of the border, in X direction.</param>
/// <param name="borderWrapModeY">The <see cref="BorderWrappingMode"/> to use when mapping the pixels outside of the border, in Y direction.</param>
public Convolution2PassProcessor(
Configuration configuration,
float[] kernelX,
float[] kernelY,
bool preserveAlpha,
Image<TPixel> source,
Rectangle sourceRectangle,
BorderWrappingMode borderWrapModeX,
BorderWrappingMode borderWrapModeY)
: base(configuration, source, sourceRectangle)
{
this.Kernel = kernel;
this.KernelX = kernelX;
this.KernelY = kernelY;
this.PreserveAlpha = preserveAlpha;
this.BorderWrapModeX = borderWrapModeX;
this.BorderWrapModeY = borderWrapModeY;
}
/// <summary>
/// Gets the convolution kernel.
/// Gets the convolution kernel. X direction.
/// </summary>
public float[] KernelX { get; }
/// <summary>
/// Gets the convolution kernel. Y direction.
/// </summary>
public float[] Kernel { get; }
public float[] KernelY { get; }
/// <summary>
/// Gets a value indicating whether the convolution filter is applied to alpha as well as the color channels.
@ -68,21 +98,21 @@ internal class Convolution2PassProcessor<TPixel> : ImageProcessor<TPixel>
{
using Buffer2D<TPixel> firstPassPixels = this.Configuration.MemoryAllocator.Allocate2D<TPixel>(source.Size);
var interest = Rectangle.Intersect(this.SourceRectangle, source.Bounds());
Rectangle interest = Rectangle.Intersect(this.SourceRectangle, source.Bounds());
// We can create a single sampling map with the size as if we were using the non separated 2D kernel
// the two 1D kernels represent, and reuse it across both convolution steps, like in the bokeh blur.
using var mapXY = new KernelSamplingMap(this.Configuration.MemoryAllocator);
using KernelSamplingMap mapXY = new(this.Configuration.MemoryAllocator);
mapXY.BuildSamplingOffsetMap(this.Kernel.Length, this.Kernel.Length, interest, this.BorderWrapModeX, this.BorderWrapModeY);
mapXY.BuildSamplingOffsetMap(this.KernelX.Length, this.KernelX.Length, interest, this.BorderWrapModeX, this.BorderWrapModeY);
// Horizontal convolution
var horizontalOperation = new HorizontalConvolutionRowOperation(
HorizontalConvolutionRowOperation horizontalOperation = new(
interest,
firstPassPixels,
source.PixelBuffer,
mapXY,
this.Kernel,
this.KernelX,
this.Configuration,
this.PreserveAlpha);
@ -92,12 +122,12 @@ internal class Convolution2PassProcessor<TPixel> : ImageProcessor<TPixel>
in horizontalOperation);
// Vertical convolution
var verticalOperation = new VerticalConvolutionRowOperation(
VerticalConvolutionRowOperation verticalOperation = new(
interest,
source.PixelBuffer,
firstPassPixels,
mapXY,
this.Kernel,
this.KernelY,
this.Configuration,
this.PreserveAlpha);
@ -140,7 +170,7 @@ internal class Convolution2PassProcessor<TPixel> : ImageProcessor<TPixel>
}
/// <inheritdoc/>
[MethodImpl(InliningOptions.ShortMethod)]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public int GetRequiredBufferLength(Rectangle bounds)
=> 2 * bounds.Width;
@ -306,7 +336,7 @@ internal class Convolution2PassProcessor<TPixel> : ImageProcessor<TPixel>
}
/// <inheritdoc/>
[MethodImpl(InliningOptions.ShortMethod)]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public int GetRequiredBufferLength(Rectangle bounds)
=> 2 * bounds.Width;

79
src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessor.cs

@ -0,0 +1,79 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Processing.Processors.Convolution;
/// <summary>
/// Defines a processor that uses a 2 dimensional matrix to perform convolution against an image.
/// </summary>
public class ConvolutionProcessor : IImageProcessor
{
/// <summary>
/// Initializes a new instance of the <see cref="ConvolutionProcessor"/> class.
/// </summary>
/// <param name="kernelXY">The 2d gradient operator.</param>
/// <param name="preserveAlpha">Whether the convolution filter is applied to alpha as well as the color channels.</param>
/// <param name="borderWrapModeX">The <see cref="BorderWrappingMode"/> to use when mapping the pixels outside of the border, in X direction.</param>
/// <param name="borderWrapModeY">The <see cref="BorderWrappingMode"/> to use when mapping the pixels outside of the border, in Y direction.</param>
public ConvolutionProcessor(
in DenseMatrix<float> kernelXY,
bool preserveAlpha,
BorderWrappingMode borderWrapModeX,
BorderWrappingMode borderWrapModeY)
{
this.KernelXY = kernelXY;
this.PreserveAlpha = preserveAlpha;
this.BorderWrapModeX = borderWrapModeX;
this.BorderWrapModeY = borderWrapModeY;
}
/// <summary>
/// Gets the 2d convolution kernel.
/// </summary>
public DenseMatrix<float> KernelXY { get; }
/// <summary>
/// Gets a value indicating whether the convolution 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>
{
if (this.KernelXY.TryGetLinearlySeparableComponents(out float[]? kernelX, out float[]? kernelY))
{
return new Convolution2PassProcessor<TPixel>(
configuration,
kernelX,
kernelY,
this.PreserveAlpha,
source,
sourceRectangle,
this.BorderWrapModeX,
this.BorderWrapModeY);
}
return new ConvolutionProcessor<TPixel>(
configuration,
this.KernelXY,
this.PreserveAlpha,
source,
sourceRectangle,
this.BorderWrapModeX,
this.BorderWrapModeY);
}
}

44
src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessor{TPixel}.cs

@ -31,10 +31,34 @@ internal class ConvolutionProcessor<TPixel> : ImageProcessor<TPixel>
bool preserveAlpha,
Image<TPixel> source,
Rectangle sourceRectangle)
: this(configuration, kernelXY, preserveAlpha, source, sourceRectangle, BorderWrappingMode.Repeat, BorderWrappingMode.Repeat)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="ConvolutionProcessor{TPixel}"/> class.
/// </summary>
/// <param name="configuration">The configuration which allows altering default behaviour or extending the library.</param>
/// <param name="kernelXY">The 2d gradient operator.</param>
/// <param name="preserveAlpha">Whether the convolution filter is applied to alpha as well as the color channels.</param>
/// <param name="source">The source <see cref="Image{TPixel}"/> for the current processor instance.</param>
/// <param name="sourceRectangle">The source area to process for the current processor instance.</param>
/// <param name="borderWrapModeX">The <see cref="BorderWrappingMode"/> to use when mapping the pixels outside of the border, in X direction.</param>
/// <param name="borderWrapModeY">The <see cref="BorderWrappingMode"/> to use when mapping the pixels outside of the border, in Y direction.</param>
public ConvolutionProcessor(
Configuration configuration,
in DenseMatrix<float> kernelXY,
bool preserveAlpha,
Image<TPixel> source,
Rectangle sourceRectangle,
BorderWrappingMode borderWrapModeX,
BorderWrappingMode borderWrapModeY)
: base(configuration, source, sourceRectangle)
{
this.KernelXY = kernelXY;
this.PreserveAlpha = preserveAlpha;
this.BorderWrapModeX = borderWrapModeX;
this.BorderWrapModeY = borderWrapModeY;
}
/// <summary>
@ -47,6 +71,16 @@ internal class ConvolutionProcessor<TPixel> : ImageProcessor<TPixel>
/// </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/>
protected override void OnFrameApply(ImageFrame<TPixel> source)
{
@ -55,13 +89,13 @@ internal class ConvolutionProcessor<TPixel> : ImageProcessor<TPixel>
source.CopyTo(targetPixels);
var interest = Rectangle.Intersect(this.SourceRectangle, source.Bounds());
Rectangle interest = Rectangle.Intersect(this.SourceRectangle, source.Bounds());
using (var map = new KernelSamplingMap(allocator))
using (KernelSamplingMap map = new(allocator))
{
map.BuildSamplingOffsetMap(this.KernelXY, interest);
map.BuildSamplingOffsetMap(this.KernelXY.Rows, this.KernelXY.Columns, interest, this.BorderWrapModeX, this.BorderWrapModeY);
var operation = new RowOperation(interest, targetPixels, source.PixelBuffer, map, this.KernelXY, this.Configuration, this.PreserveAlpha);
RowOperation operation = new(interest, targetPixels, source.PixelBuffer, map, this.KernelXY, this.Configuration, this.PreserveAlpha);
ParallelRowIterator.IterateRows<RowOperation, Vector4>(
this.Configuration,
interest,
@ -121,7 +155,7 @@ internal class ConvolutionProcessor<TPixel> : ImageProcessor<TPixel>
ref Vector4 targetRowRef = ref MemoryMarshal.GetReference(span);
Span<TPixel> targetRowSpan = this.targetPixels.DangerousGetRowSpan(y).Slice(boundsX, boundsWidth);
var state = new ConvolutionState(in this.kernel, this.map);
ConvolutionState state = new(in this.kernel, this.map);
int row = y - this.bounds.Y;
ref int sampleRowBase = ref state.GetSampleRow((uint)row);

8
src/ImageSharp/Processing/Processors/Convolution/EdgeDetectorCompassProcessor{TPixel}.cs

@ -58,12 +58,12 @@ internal class EdgeDetectorCompassProcessor<TPixel> : ImageProcessor<TPixel>
/// <inheritdoc />
protected override void OnFrameApply(ImageFrame<TPixel> source)
{
var interest = Rectangle.Intersect(this.SourceRectangle, source.Bounds());
Rectangle interest = Rectangle.Intersect(this.SourceRectangle, source.Bounds());
// We need a clean copy for each pass to start from
using ImageFrame<TPixel> cleanCopy = source.Clone();
using (var processor = new ConvolutionProcessor<TPixel>(this.Configuration, in this.kernels[0], true, this.Source, interest))
using (ConvolutionProcessor<TPixel> processor = new(this.Configuration, in this.kernels[0], true, this.Source, interest))
{
processor.Apply(source);
}
@ -78,12 +78,12 @@ internal class EdgeDetectorCompassProcessor<TPixel> : ImageProcessor<TPixel>
{
using ImageFrame<TPixel> pass = cleanCopy.Clone();
using (var processor = new ConvolutionProcessor<TPixel>(this.Configuration, in this.kernels[i], true, this.Source, interest))
using (ConvolutionProcessor<TPixel> processor = new(this.Configuration, in this.kernels[i], true, this.Source, interest))
{
processor.Apply(pass);
}
var operation = new RowOperation(source.PixelBuffer, pass.PixelBuffer, interest);
RowOperation operation = new(source.PixelBuffer, pass.PixelBuffer, interest);
ParallelRowIterator.IterateRows(
this.Configuration,
interest,

2
src/ImageSharp/Processing/Processors/Convolution/EdgeDetectorProcessor{TPixel}.cs

@ -53,7 +53,7 @@ internal class EdgeDetectorProcessor<TPixel> : ImageProcessor<TPixel>
/// <inheritdoc/>
protected override void OnFrameApply(ImageFrame<TPixel> source)
{
using var processor = new ConvolutionProcessor<TPixel>(this.Configuration, in this.kernelXY, true, this.Source, this.SourceRectangle);
using ConvolutionProcessor<TPixel> processor = new(this.Configuration, in this.kernelXY, true, this.Source, this.SourceRectangle);
processor.Apply(source);
}
}

20
src/ImageSharp/Processing/Processors/Convolution/GaussianBlurProcessor{TPixel}.cs

@ -12,24 +12,6 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution;
internal class GaussianBlurProcessor<TPixel> : ImageProcessor<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
/// <summary>
/// Initializes a new instance of the <see cref="GaussianBlurProcessor{TPixel}"/> class.
/// </summary>
/// <param name="configuration">The configuration which allows altering default behaviour or extending the library.</param>
/// <param name="definition">The <see cref="GaussianBlurProcessor"/> defining the processor parameters.</param>
/// <param name="source">The source <see cref="Image{TPixel}"/> for the current processor instance.</param>
/// <param name="sourceRectangle">The source area to process for the current processor instance.</param>
public GaussianBlurProcessor(
Configuration configuration,
GaussianBlurProcessor definition,
Image<TPixel> source,
Rectangle sourceRectangle)
: base(configuration, source, sourceRectangle)
{
int kernelSize = (definition.Radius * 2) + 1;
this.Kernel = ConvolutionProcessorHelpers.CreateGaussianBlurKernel(kernelSize, definition.Sigma);
}
/// <summary>
/// Initializes a new instance of the <see cref="GaussianBlurProcessor{TPixel}"/> class.
/// </summary>
@ -72,7 +54,7 @@ internal class GaussianBlurProcessor<TPixel> : ImageProcessor<TPixel>
/// <inheritdoc/>
protected override void OnFrameApply(ImageFrame<TPixel> source)
{
using var processor = new Convolution2PassProcessor<TPixel>(this.Configuration, this.Kernel, false, this.Source, this.SourceRectangle, this.BorderWrapModeX, this.BorderWrapModeY);
using Convolution2PassProcessor<TPixel> processor = new(this.Configuration, this.Kernel, false, this.Source, this.SourceRectangle, this.BorderWrapModeX, this.BorderWrapModeY);
processor.Apply(source);
}

18
src/ImageSharp/Processing/Processors/Convolution/GaussianSharpenProcessor{TPixel}.cs

@ -12,22 +12,6 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution;
internal class GaussianSharpenProcessor<TPixel> : ImageProcessor<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
/// <summary>
/// Initializes a new instance of the <see cref="GaussianSharpenProcessor{TPixel}"/> class.
/// </summary>
/// <param name="configuration">The configuration which allows altering default behaviour or extending the library.</param>
/// <param name="definition">The <see cref="GaussianSharpenProcessor"/> defining the processor parameters.</param>
/// <param name="source">The source <see cref="Image{TPixel}"/> for the current processor instance.</param>
/// <param name="sourceRectangle">The source area to process for the current processor instance.</param>
public GaussianSharpenProcessor(
Configuration configuration,
GaussianSharpenProcessor definition,
Image<TPixel> source,
Rectangle sourceRectangle)
: this(configuration, definition, source, sourceRectangle, BorderWrappingMode.Repeat, BorderWrappingMode.Repeat)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="GaussianSharpenProcessor{TPixel}"/> class.
/// </summary>
@ -70,7 +54,7 @@ internal class GaussianSharpenProcessor<TPixel> : ImageProcessor<TPixel>
/// <inheritdoc/>
protected override void OnFrameApply(ImageFrame<TPixel> source)
{
using var processor = new Convolution2PassProcessor<TPixel>(this.Configuration, this.Kernel, false, this.Source, this.SourceRectangle, this.BorderWrapModeX, this.BorderWrapModeY);
using Convolution2PassProcessor<TPixel> processor = new(this.Configuration, this.Kernel, false, this.Source, this.SourceRectangle, this.BorderWrapModeX, this.BorderWrapModeY);
processor.Apply(source);
}

6
src/ImageSharp/Processing/Processors/Convolution/Parameters/BokehBlurKernelDataProvider.cs

@ -21,12 +21,12 @@ internal static class BokehBlurKernelDataProvider
/// <summary>
/// Gets the kernel scales to adjust the component values in each kernel
/// </summary>
private static IReadOnlyList<float> KernelScales { get; } = new[] { 1.4f, 1.2f, 1.2f, 1.2f, 1.2f, 1.2f };
private static float[] KernelScales { get; } = new[] { 1.4f, 1.2f, 1.2f, 1.2f, 1.2f, 1.2f };
/// <summary>
/// Gets the available bokeh blur kernel parameters
/// </summary>
private static IReadOnlyList<Vector4[]> KernelComponents { get; } = new[]
private static Vector4[][] KernelComponents { get; } = new[]
{
// 1 component
new[] { new Vector4(0.862325f, 1.624835f, 0.767583f, 1.862321f) },
@ -112,7 +112,7 @@ internal static class BokehBlurKernelDataProvider
private static (Vector4[] Parameters, float Scale) GetParameters(int componentsCount)
{
// Prepare the kernel components
int index = Math.Max(0, Math.Min(componentsCount - 1, KernelComponents.Count));
int index = Math.Max(0, Math.Min(componentsCount - 1, KernelComponents.Length));
return (KernelComponents[index], KernelScales[index]);
}

21
tests/Directory.Build.targets

@ -18,18 +18,23 @@
<ItemGroup>
<!-- Test Dependencies -->
<PackageReference Update="Colourful" Version="3.1.0" />
<PackageReference Update="Magick.NET-Q16-AnyCPU" Version="13.4.0" />
<PackageReference Update="Colourful" Version="3.2.0" />
<!--
Do not update to 14+ yet. There's differnce in how the BMP decoder handles rounding in 16 bit images.
See https://github.com/ImageMagick/ImageMagick/commit/27a0a9c37f18af9c8d823a3ea076f600843b553c
-->
<PackageReference Update="Magick.NET-Q16-AnyCPU" Version="13.10.0" />
<PackageReference Update="Microsoft.DotNet.RemoteExecutor" Version="8.0.0-beta.23580.1" />
<PackageReference Update="Microsoft.DotNet.XUnitExtensions" Version="8.0.0-beta.23580.1" />
<PackageReference Update="Moq" Version="4.20.70" />
<PackageReference Update="NetVips" Version="2.4.0" />
<PackageReference Update="NetVips.Native" Version="8.15.0" />
<PackageReference Update="PhotoSauce.MagicScaler" Version="0.14.0" />
<PackageReference Update="Pfim" Version="0.11.2" />
<PackageReference Update="Moq" Version="4.20.72" />
<PackageReference Update="NetVips" Version="3.0.0" />
<PackageReference Update="NetVips.Native" Version="8.16.0" />
<PackageReference Update="PhotoSauce.MagicScaler" Version="0.14.2" />
<PackageReference Update="Pfim" Version="0.11.3" />
<PackageReference Update="runtime.osx.10.10-x64.CoreCompat.System.Drawing" Version="6.0.5.128" Condition="'$(IsOSX)'=='true'" />
<PackageReference Update="SharpZipLib" Version="1.4.2" />
<PackageReference Update="SkiaSharp" Version="2.88.6" />
<PackageReference Update="SkiaSharp" Version="2.88.9" />
<PackageReference Update="System.Drawing.Common" Version="6.0.0" />
<PackageReference Update="System.IO.Compression" Version="4.3.0" />
</ItemGroup>

64
tests/ImageSharp.Benchmarks/Bulk/Pad3Shuffle4Channel.cs

@ -47,37 +47,37 @@ public class Pad3Shuffle4Channel
//
// | Method | Job | EnvironmentVariables | Count | Mean | Error | StdDev | Median | Ratio | RatioSD | Gen 0 | Gen 1 | Gen 2 | Allocated |
// |------------------------- |------------------- |-------------------------------------------------- |------ |------------:|----------:|----------:|------------:|------:|--------:|------:|------:|------:|----------:|
// | Pad3Shuffle4 | 1. No HwIntrinsics | COMPlus_EnableHWIntrinsic=0,COMPlus_FeatureSIMD=0 | 96 | 120.64 ns | 7.190 ns | 21.200 ns | 114.26 ns | 1.00 | 0.00 | - | - | - | - |
// | Pad3Shuffle4 | 1. No HwIntrinsics | DOTNET_EnableHWIntrinsic=0,DOTNET_FeatureSIMD=0 | 96 | 120.64 ns | 7.190 ns | 21.200 ns | 114.26 ns | 1.00 | 0.00 | - | - | - | - |
// | Pad3Shuffle4 | 2. AVX | Empty | 96 | 23.63 ns | 0.175 ns | 0.155 ns | 23.65 ns | 0.15 | 0.01 | - | - | - | - |
// | Pad3Shuffle4 | 3. SSE | COMPlus_EnableAVX=0 | 96 | 25.25 ns | 0.356 ns | 0.298 ns | 25.27 ns | 0.17 | 0.01 | - | - | - | - |
// | Pad3Shuffle4 | 3. SSE | DOTNET_EnableAVX=0 | 96 | 25.25 ns | 0.356 ns | 0.298 ns | 25.27 ns | 0.17 | 0.01 | - | - | - | - |
// | | | | | | | | | | | | | | |
// | Pad3Shuffle4FastFallback | 1. No HwIntrinsics | COMPlus_EnableHWIntrinsic=0,COMPlus_FeatureSIMD=0 | 96 | 14.80 ns | 0.358 ns | 1.032 ns | 14.64 ns | 1.00 | 0.00 | - | - | - | - |
// | Pad3Shuffle4FastFallback | 1. No HwIntrinsics | DOTNET_EnableHWIntrinsic=0,DOTNET_FeatureSIMD=0 | 96 | 14.80 ns | 0.358 ns | 1.032 ns | 14.64 ns | 1.00 | 0.00 | - | - | - | - |
// | Pad3Shuffle4FastFallback | 2. AVX | Empty | 96 | 24.84 ns | 0.376 ns | 0.333 ns | 24.74 ns | 1.57 | 0.06 | - | - | - | - |
// | Pad3Shuffle4FastFallback | 3. SSE | COMPlus_EnableAVX=0 | 96 | 24.58 ns | 0.471 ns | 0.704 ns | 24.38 ns | 1.60 | 0.09 | - | - | - | - |
// | Pad3Shuffle4FastFallback | 3. SSE | DOTNET_EnableAVX=0 | 96 | 24.58 ns | 0.471 ns | 0.704 ns | 24.38 ns | 1.60 | 0.09 | - | - | - | - |
// | | | | | | | | | | | | | | |
// | Pad3Shuffle4 | 1. No HwIntrinsics | COMPlus_EnableHWIntrinsic=0,COMPlus_FeatureSIMD=0 | 384 | 258.92 ns | 4.873 ns | 4.069 ns | 257.95 ns | 1.00 | 0.00 | - | - | - | - |
// | Pad3Shuffle4 | 1. No HwIntrinsics | DOTNET_EnableHWIntrinsic=0,DOTNET_FeatureSIMD=0 | 384 | 258.92 ns | 4.873 ns | 4.069 ns | 257.95 ns | 1.00 | 0.00 | - | - | - | - |
// | Pad3Shuffle4 | 2. AVX | Empty | 384 | 41.41 ns | 0.859 ns | 1.204 ns | 41.33 ns | 0.16 | 0.00 | - | - | - | - |
// | Pad3Shuffle4 | 3. SSE | COMPlus_EnableAVX=0 | 384 | 40.74 ns | 0.848 ns | 0.793 ns | 40.48 ns | 0.16 | 0.00 | - | - | - | - |
// | Pad3Shuffle4 | 3. SSE | DOTNET_EnableAVX=0 | 384 | 40.74 ns | 0.848 ns | 0.793 ns | 40.48 ns | 0.16 | 0.00 | - | - | - | - |
// | | | | | | | | | | | | | | |
// | Pad3Shuffle4FastFallback | 1. No HwIntrinsics | COMPlus_EnableHWIntrinsic=0,COMPlus_FeatureSIMD=0 | 384 | 74.50 ns | 0.490 ns | 0.383 ns | 74.49 ns | 1.00 | 0.00 | - | - | - | - |
// | Pad3Shuffle4FastFallback | 1. No HwIntrinsics | DOTNET_EnableHWIntrinsic=0,DOTNET_FeatureSIMD=0 | 384 | 74.50 ns | 0.490 ns | 0.383 ns | 74.49 ns | 1.00 | 0.00 | - | - | - | - |
// | Pad3Shuffle4FastFallback | 2. AVX | Empty | 384 | 40.74 ns | 0.624 ns | 0.584 ns | 40.72 ns | 0.55 | 0.01 | - | - | - | - |
// | Pad3Shuffle4FastFallback | 3. SSE | COMPlus_EnableAVX=0 | 384 | 38.28 ns | 0.534 ns | 0.417 ns | 38.22 ns | 0.51 | 0.01 | - | - | - | - |
// | Pad3Shuffle4FastFallback | 3. SSE | DOTNET_EnableAVX=0 | 384 | 38.28 ns | 0.534 ns | 0.417 ns | 38.22 ns | 0.51 | 0.01 | - | - | - | - |
// | | | | | | | | | | | | | | |
// | Pad3Shuffle4 | 1. No HwIntrinsics | COMPlus_EnableHWIntrinsic=0,COMPlus_FeatureSIMD=0 | 768 | 503.91 ns | 6.466 ns | 6.048 ns | 501.58 ns | 1.00 | 0.00 | - | - | - | - |
// | Pad3Shuffle4 | 1. No HwIntrinsics | DOTNET_EnableHWIntrinsic=0,DOTNET_FeatureSIMD=0 | 768 | 503.91 ns | 6.466 ns | 6.048 ns | 501.58 ns | 1.00 | 0.00 | - | - | - | - |
// | Pad3Shuffle4 | 2. AVX | Empty | 768 | 62.86 ns | 0.332 ns | 0.277 ns | 62.80 ns | 0.12 | 0.00 | - | - | - | - |
// | Pad3Shuffle4 | 3. SSE | COMPlus_EnableAVX=0 | 768 | 64.59 ns | 0.469 ns | 0.415 ns | 64.62 ns | 0.13 | 0.00 | - | - | - | - |
// | Pad3Shuffle4 | 3. SSE | DOTNET_EnableAVX=0 | 768 | 64.59 ns | 0.469 ns | 0.415 ns | 64.62 ns | 0.13 | 0.00 | - | - | - | - |
// | | | | | | | | | | | | | | |
// | Pad3Shuffle4FastFallback | 1. No HwIntrinsics | COMPlus_EnableHWIntrinsic=0,COMPlus_FeatureSIMD=0 | 768 | 110.51 ns | 0.592 ns | 0.554 ns | 110.33 ns | 1.00 | 0.00 | - | - | - | - |
// | Pad3Shuffle4FastFallback | 1. No HwIntrinsics | DOTNET_EnableHWIntrinsic=0,DOTNET_FeatureSIMD=0 | 768 | 110.51 ns | 0.592 ns | 0.554 ns | 110.33 ns | 1.00 | 0.00 | - | - | - | - |
// | Pad3Shuffle4FastFallback | 2. AVX | Empty | 768 | 64.72 ns | 1.306 ns | 1.090 ns | 64.51 ns | 0.59 | 0.01 | - | - | - | - |
// | Pad3Shuffle4FastFallback | 3. SSE | COMPlus_EnableAVX=0 | 768 | 62.11 ns | 0.816 ns | 0.682 ns | 61.98 ns | 0.56 | 0.01 | - | - | - | - |
// | Pad3Shuffle4FastFallback | 3. SSE | DOTNET_EnableAVX=0 | 768 | 62.11 ns | 0.816 ns | 0.682 ns | 61.98 ns | 0.56 | 0.01 | - | - | - | - |
// | | | | | | | | | | | | | | |
// | Pad3Shuffle4 | 1. No HwIntrinsics | COMPlus_EnableHWIntrinsic=0,COMPlus_FeatureSIMD=0 | 1536 | 1,005.84 ns | 13.176 ns | 12.325 ns | 1,004.70 ns | 1.00 | 0.00 | - | - | - | - |
// | Pad3Shuffle4 | 1. No HwIntrinsics | DOTNET_EnableHWIntrinsic=0,DOTNET_FeatureSIMD=0 | 1536 | 1,005.84 ns | 13.176 ns | 12.325 ns | 1,004.70 ns | 1.00 | 0.00 | - | - | - | - |
// | Pad3Shuffle4 | 2. AVX | Empty | 1536 | 110.05 ns | 0.256 ns | 0.214 ns | 110.04 ns | 0.11 | 0.00 | - | - | - | - |
// | Pad3Shuffle4 | 3. SSE | COMPlus_EnableAVX=0 | 1536 | 110.23 ns | 0.545 ns | 0.483 ns | 110.09 ns | 0.11 | 0.00 | - | - | - | - |
// | Pad3Shuffle4 | 3. SSE | DOTNET_EnableAVX=0 | 1536 | 110.23 ns | 0.545 ns | 0.483 ns | 110.09 ns | 0.11 | 0.00 | - | - | - | - |
// | | | | | | | | | | | | | | |
// | Pad3Shuffle4FastFallback | 1. No HwIntrinsics | COMPlus_EnableHWIntrinsic=0,COMPlus_FeatureSIMD=0 | 1536 | 220.37 ns | 1.601 ns | 1.419 ns | 220.13 ns | 1.00 | 0.00 | - | - | - | - |
// | Pad3Shuffle4FastFallback | 1. No HwIntrinsics | DOTNET_EnableHWIntrinsic=0,DOTNET_FeatureSIMD=0 | 1536 | 220.37 ns | 1.601 ns | 1.419 ns | 220.13 ns | 1.00 | 0.00 | - | - | - | - |
// | Pad3Shuffle4FastFallback | 2. AVX | Empty | 1536 | 111.54 ns | 2.173 ns | 2.901 ns | 111.27 ns | 0.51 | 0.01 | - | - | - | - |
// | Pad3Shuffle4FastFallback | 3. SSE | COMPlus_EnableAVX=0 | 1536 | 110.23 ns | 0.456 ns | 0.427 ns | 110.25 ns | 0.50 | 0.00 | - | - | - | - |
// | Pad3Shuffle4FastFallback | 3. SSE | DOTNET_EnableAVX=0 | 1536 | 110.23 ns | 0.456 ns | 0.427 ns | 110.25 ns | 0.50 | 0.00 | - | - | - | - |
// 2023-02-21
// ##########
@ -94,34 +94,34 @@ public class Pad3Shuffle4Channel
// | Method | Job | EnvironmentVariables | Count | Mean | Error | StdDev | Ratio | Gen 0 | Gen 1 | Gen 2 | Allocated |
// |------------------------- |------------------- |-------------------------------------------------- |------ |----------:|---------:|---------:|------:|------:|------:|------:|----------:|
// | Pad3Shuffle4 | 1. No HwIntrinsics | COMPlus_EnableHWIntrinsic=0,COMPlus_FeatureSIMD=0 | 96 | 57.45 ns | 0.126 ns | 0.118 ns | 1.00 | - | - | - | - |
// | Pad3Shuffle4 | 2. SSE | COMPlus_EnableAVX=0 | 96 | 14.70 ns | 0.105 ns | 0.098 ns | 0.26 | - | - | - | - |
// | Pad3Shuffle4 | 1. No HwIntrinsics | DOTNET_EnableHWIntrinsic=0,DOTNET_FeatureSIMD=0 | 96 | 57.45 ns | 0.126 ns | 0.118 ns | 1.00 | - | - | - | - |
// | Pad3Shuffle4 | 2. SSE | DOTNET_EnableAVX=0 | 96 | 14.70 ns | 0.105 ns | 0.098 ns | 0.26 | - | - | - | - |
// | Pad3Shuffle4 | 3. AVX | Empty | 96 | 14.63 ns | 0.070 ns | 0.062 ns | 0.25 | - | - | - | - |
// | | | | | | | | | | | | |
// | Pad3Shuffle4FastFallback | 1. No HwIntrinsics | COMPlus_EnableHWIntrinsic=0,COMPlus_FeatureSIMD=0 | 96 | 12.08 ns | 0.028 ns | 0.025 ns | 1.00 | - | - | - | - |
// | Pad3Shuffle4FastFallback | 2. SSE | COMPlus_EnableAVX=0 | 96 | 14.04 ns | 0.050 ns | 0.044 ns | 1.16 | - | - | - | - |
// | Pad3Shuffle4FastFallback | 1. No HwIntrinsics | DOTNET_EnableHWIntrinsic=0,DOTNET_FeatureSIMD=0 | 96 | 12.08 ns | 0.028 ns | 0.025 ns | 1.00 | - | - | - | - |
// | Pad3Shuffle4FastFallback | 2. SSE | DOTNET_EnableAVX=0 | 96 | 14.04 ns | 0.050 ns | 0.044 ns | 1.16 | - | - | - | - |
// | Pad3Shuffle4FastFallback | 3. AVX | Empty | 96 | 13.90 ns | 0.086 ns | 0.080 ns | 1.15 | - | - | - | - |
// | | | | | | | | | | | | |
// | Pad3Shuffle4 | 1. No HwIntrinsics | COMPlus_EnableHWIntrinsic=0,COMPlus_FeatureSIMD=0 | 384 | 202.67 ns | 2.010 ns | 1.678 ns | 1.00 | - | - | - | - |
// | Pad3Shuffle4 | 2. SSE | COMPlus_EnableAVX=0 | 384 | 25.54 ns | 0.060 ns | 0.053 ns | 0.13 | - | - | - | - |
// | Pad3Shuffle4 | 1. No HwIntrinsics | DOTNET_EnableHWIntrinsic=0,DOTNET_FeatureSIMD=0 | 384 | 202.67 ns | 2.010 ns | 1.678 ns | 1.00 | - | - | - | - |
// | Pad3Shuffle4 | 2. SSE | DOTNET_EnableAVX=0 | 384 | 25.54 ns | 0.060 ns | 0.053 ns | 0.13 | - | - | - | - |
// | Pad3Shuffle4 | 3. AVX | Empty | 384 | 25.72 ns | 0.139 ns | 0.130 ns | 0.13 | - | - | - | - |
// | | | | | | | | | | | | |
// | Pad3Shuffle4FastFallback | 1. No HwIntrinsics | COMPlus_EnableHWIntrinsic=0,COMPlus_FeatureSIMD=0 | 384 | 60.35 ns | 0.080 ns | 0.071 ns | 1.00 | - | - | - | - |
// | Pad3Shuffle4FastFallback | 2. SSE | COMPlus_EnableAVX=0 | 384 | 25.18 ns | 0.388 ns | 0.324 ns | 0.42 | - | - | - | - |
// | Pad3Shuffle4FastFallback | 1. No HwIntrinsics | DOTNET_EnableHWIntrinsic=0,DOTNET_FeatureSIMD=0 | 384 | 60.35 ns | 0.080 ns | 0.071 ns | 1.00 | - | - | - | - |
// | Pad3Shuffle4FastFallback | 2. SSE | DOTNET_EnableAVX=0 | 384 | 25.18 ns | 0.388 ns | 0.324 ns | 0.42 | - | - | - | - |
// | Pad3Shuffle4FastFallback | 3. AVX | Empty | 384 | 26.21 ns | 0.067 ns | 0.059 ns | 0.43 | - | - | - | - |
// | | | | | | | | | | | | |
// | Pad3Shuffle4 | 1. No HwIntrinsics | COMPlus_EnableHWIntrinsic=0,COMPlus_FeatureSIMD=0 | 768 | 393.88 ns | 1.353 ns | 1.199 ns | 1.00 | - | - | - | - |
// | Pad3Shuffle4 | 2. SSE | COMPlus_EnableAVX=0 | 768 | 39.44 ns | 0.230 ns | 0.204 ns | 0.10 | - | - | - | - |
// | Pad3Shuffle4 | 1. No HwIntrinsics | DOTNET_EnableHWIntrinsic=0,DOTNET_FeatureSIMD=0 | 768 | 393.88 ns | 1.353 ns | 1.199 ns | 1.00 | - | - | - | - |
// | Pad3Shuffle4 | 2. SSE | DOTNET_EnableAVX=0 | 768 | 39.44 ns | 0.230 ns | 0.204 ns | 0.10 | - | - | - | - |
// | Pad3Shuffle4 | 3. AVX | Empty | 768 | 39.51 ns | 0.108 ns | 0.101 ns | 0.10 | - | - | - | - |
// | | | | | | | | | | | | |
// | Pad3Shuffle4FastFallback | 1. No HwIntrinsics | COMPlus_EnableHWIntrinsic=0,COMPlus_FeatureSIMD=0 | 768 | 112.02 ns | 0.140 ns | 0.131 ns | 1.00 | - | - | - | - |
// | Pad3Shuffle4FastFallback | 2. SSE | COMPlus_EnableAVX=0 | 768 | 38.60 ns | 0.091 ns | 0.080 ns | 0.34 | - | - | - | - |
// | Pad3Shuffle4FastFallback | 1. No HwIntrinsics | DOTNET_EnableHWIntrinsic=0,DOTNET_FeatureSIMD=0 | 768 | 112.02 ns | 0.140 ns | 0.131 ns | 1.00 | - | - | - | - |
// | Pad3Shuffle4FastFallback | 2. SSE | DOTNET_EnableAVX=0 | 768 | 38.60 ns | 0.091 ns | 0.080 ns | 0.34 | - | - | - | - |
// | Pad3Shuffle4FastFallback | 3. AVX | Empty | 768 | 38.18 ns | 0.100 ns | 0.084 ns | 0.34 | - | - | - | - |
// | | | | | | | | | | | | |
// | Pad3Shuffle4 | 1. No HwIntrinsics | COMPlus_EnableHWIntrinsic=0,COMPlus_FeatureSIMD=0 | 1536 | 777.95 ns | 1.719 ns | 1.342 ns | 1.00 | - | - | - | - |
// | Pad3Shuffle4 | 2. SSE | COMPlus_EnableAVX=0 | 1536 | 73.11 ns | 0.090 ns | 0.075 ns | 0.09 | - | - | - | - |
// | Pad3Shuffle4 | 1. No HwIntrinsics | DOTNET_EnableHWIntrinsic=0,DOTNET_FeatureSIMD=0 | 1536 | 777.95 ns | 1.719 ns | 1.342 ns | 1.00 | - | - | - | - |
// | Pad3Shuffle4 | 2. SSE | DOTNET_EnableAVX=0 | 1536 | 73.11 ns | 0.090 ns | 0.075 ns | 0.09 | - | - | - | - |
// | Pad3Shuffle4 | 3. AVX | Empty | 1536 | 73.41 ns | 0.125 ns | 0.117 ns | 0.09 | - | - | - | - |
// | | | | | | | | | | | | |
// | Pad3Shuffle4FastFallback | 1. No HwIntrinsics | COMPlus_EnableHWIntrinsic=0,COMPlus_FeatureSIMD=0 | 1536 | 218.14 ns | 0.377 ns | 0.334 ns | 1.00 | - | - | - | - |
// | Pad3Shuffle4FastFallback | 2. SSE | COMPlus_EnableAVX=0 | 1536 | 72.55 ns | 1.418 ns | 1.184 ns | 0.33 | - | - | - | - |
// | Pad3Shuffle4FastFallback | 1. No HwIntrinsics | DOTNET_EnableHWIntrinsic=0,DOTNET_FeatureSIMD=0 | 1536 | 218.14 ns | 0.377 ns | 0.334 ns | 1.00 | - | - | - | - |
// | Pad3Shuffle4FastFallback | 2. SSE | DOTNET_EnableAVX=0 | 1536 | 72.55 ns | 1.418 ns | 1.184 ns | 0.33 | - | - | - | - |
// | Pad3Shuffle4FastFallback | 3. AVX | Empty | 1536 | 73.15 ns | 0.330 ns | 0.292 ns | 0.34 | - | - | - | - |

32
tests/ImageSharp.Benchmarks/Bulk/Shuffle3Channel.cs

@ -43,21 +43,21 @@ public class Shuffle3Channel
//
// | Method | Job | EnvironmentVariables | Count | Mean | Error | StdDev | Median | Ratio | RatioSD | Gen 0 | Gen 1 | Gen 2 | Allocated |
// |--------------------- |------------------- |-------------------------------------------------- |------ |----------:|---------:|---------:|----------:|------:|--------:|------:|------:|------:|----------:|
// | Shuffle3 | 1. No HwIntrinsics | COMPlus_EnableHWIntrinsic=0,COMPlus_FeatureSIMD=0 | 96 | 48.46 ns | 1.034 ns | 2.438 ns | 47.46 ns | 1.00 | 0.00 | - | - | - | - |
// | Shuffle3 | 1. No HwIntrinsics | DOTNET_EnableHWIntrinsic=0,DOTNET_FeatureSIMD=0 | 96 | 48.46 ns | 1.034 ns | 2.438 ns | 47.46 ns | 1.00 | 0.00 | - | - | - | - |
// | Shuffle3 | 2. AVX | Empty | 96 | 32.42 ns | 0.537 ns | 0.476 ns | 32.34 ns | 0.66 | 0.04 | - | - | - | - |
// | Shuffle3 | 3. SSE | COMPlus_EnableAVX=0 | 96 | 32.51 ns | 0.373 ns | 0.349 ns | 32.56 ns | 0.66 | 0.03 | - | - | - | - |
// | Shuffle3 | 3. SSE | DOTNET_EnableAVX=0 | 96 | 32.51 ns | 0.373 ns | 0.349 ns | 32.56 ns | 0.66 | 0.03 | - | - | - | - |
// | | | | | | | | | | | | | | |
// | Shuffle3 | 1. No HwIntrinsics | COMPlus_EnableHWIntrinsic=0,COMPlus_FeatureSIMD=0 | 384 | 199.04 ns | 1.512 ns | 1.180 ns | 199.17 ns | 1.00 | 0.00 | - | - | - | - |
// | Shuffle3 | 1. No HwIntrinsics | DOTNET_EnableHWIntrinsic=0,DOTNET_FeatureSIMD=0 | 384 | 199.04 ns | 1.512 ns | 1.180 ns | 199.17 ns | 1.00 | 0.00 | - | - | - | - |
// | Shuffle3 | 2. AVX | Empty | 384 | 71.20 ns | 2.654 ns | 7.784 ns | 69.60 ns | 0.41 | 0.02 | - | - | - | - |
// | Shuffle3 | 3. SSE | COMPlus_EnableAVX=0 | 384 | 63.23 ns | 0.569 ns | 0.505 ns | 63.21 ns | 0.32 | 0.00 | - | - | - | - |
// | Shuffle3 | 3. SSE | DOTNET_EnableAVX=0 | 384 | 63.23 ns | 0.569 ns | 0.505 ns | 63.21 ns | 0.32 | 0.00 | - | - | - | - |
// | | | | | | | | | | | | | | |
// | Shuffle3 | 1. No HwIntrinsics | COMPlus_EnableHWIntrinsic=0,COMPlus_FeatureSIMD=0 | 768 | 391.28 ns | 5.087 ns | 3.972 ns | 391.22 ns | 1.00 | 0.00 | - | - | - | - |
// | Shuffle3 | 1. No HwIntrinsics | DOTNET_EnableHWIntrinsic=0,DOTNET_FeatureSIMD=0 | 768 | 391.28 ns | 5.087 ns | 3.972 ns | 391.22 ns | 1.00 | 0.00 | - | - | - | - |
// | Shuffle3 | 2. AVX | Empty | 768 | 109.12 ns | 2.149 ns | 2.010 ns | 108.66 ns | 0.28 | 0.01 | - | - | - | - |
// | Shuffle3 | 3. SSE | COMPlus_EnableAVX=0 | 768 | 106.51 ns | 0.734 ns | 0.613 ns | 106.56 ns | 0.27 | 0.00 | - | - | - | - |
// | Shuffle3 | 3. SSE | DOTNET_EnableAVX=0 | 768 | 106.51 ns | 0.734 ns | 0.613 ns | 106.56 ns | 0.27 | 0.00 | - | - | - | - |
// | | | | | | | | | | | | | | |
// | Shuffle3 | 1. No HwIntrinsics | COMPlus_EnableHWIntrinsic=0,COMPlus_FeatureSIMD=0 | 1536 | 773.70 ns | 5.516 ns | 4.890 ns | 772.96 ns | 1.00 | 0.00 | - | - | - | - |
// | Shuffle3 | 1. No HwIntrinsics | DOTNET_EnableHWIntrinsic=0,DOTNET_FeatureSIMD=0 | 1536 | 773.70 ns | 5.516 ns | 4.890 ns | 772.96 ns | 1.00 | 0.00 | - | - | - | - |
// | Shuffle3 | 2. AVX | Empty | 1536 | 190.41 ns | 1.090 ns | 0.851 ns | 190.38 ns | 0.25 | 0.00 | - | - | - | - |
// | Shuffle3 | 3. SSE | COMPlus_EnableAVX=0 | 1536 | 190.94 ns | 0.985 ns | 0.769 ns | 190.85 ns | 0.25 | 0.00 | - | - | - | - |
// | Shuffle3 | 3. SSE | DOTNET_EnableAVX=0 | 1536 | 190.94 ns | 0.985 ns | 0.769 ns | 190.85 ns | 0.25 | 0.00 | - | - | - | - |
// 2023-02-21
// ##########
@ -74,18 +74,18 @@ public class Shuffle3Channel
// | Method | Job | EnvironmentVariables | Count | Mean | Error | StdDev | Ratio | Gen 0 | Gen 1 | Gen 2 | Allocated |
// |--------- |------------------- |-------------------------------------------------- |------ |----------:|---------:|---------:|------:|------:|------:|------:|----------:|
// | Shuffle3 | 1. No HwIntrinsics | COMPlus_EnableHWIntrinsic=0,COMPlus_FeatureSIMD=0 | 96 | 44.55 ns | 0.564 ns | 0.528 ns | 1.00 | - | - | - | - |
// | Shuffle3 | 2. SSE | COMPlus_EnableAVX=0 | 96 | 15.46 ns | 0.064 ns | 0.060 ns | 0.35 | - | - | - | - |
// | Shuffle3 | 1. No HwIntrinsics | DOTNET_EnableHWIntrinsic=0,DOTNET_FeatureSIMD=0 | 96 | 44.55 ns | 0.564 ns | 0.528 ns | 1.00 | - | - | - | - |
// | Shuffle3 | 2. SSE | DOTNET_EnableAVX=0 | 96 | 15.46 ns | 0.064 ns | 0.060 ns | 0.35 | - | - | - | - |
// | Shuffle3 | 3. AVX | Empty | 96 | 15.18 ns | 0.056 ns | 0.053 ns | 0.34 | - | - | - | - |
// | | | | | | | | | | | | |
// | Shuffle3 | 1. No HwIntrinsics | COMPlus_EnableHWIntrinsic=0,COMPlus_FeatureSIMD=0 | 384 | 155.68 ns | 0.539 ns | 0.504 ns | 1.00 | - | - | - | - |
// | Shuffle3 | 2. SSE | COMPlus_EnableAVX=0 | 384 | 30.04 ns | 0.100 ns | 0.089 ns | 0.19 | - | - | - | - |
// | Shuffle3 | 1. No HwIntrinsics | DOTNET_EnableHWIntrinsic=0,DOTNET_FeatureSIMD=0 | 384 | 155.68 ns | 0.539 ns | 0.504 ns | 1.00 | - | - | - | - |
// | Shuffle3 | 2. SSE | DOTNET_EnableAVX=0 | 384 | 30.04 ns | 0.100 ns | 0.089 ns | 0.19 | - | - | - | - |
// | Shuffle3 | 3. AVX | Empty | 384 | 29.70 ns | 0.061 ns | 0.054 ns | 0.19 | - | - | - | - |
// | | | | | | | | | | | | |
// | Shuffle3 | 1. No HwIntrinsics | COMPlus_EnableHWIntrinsic=0,COMPlus_FeatureSIMD=0 | 768 | 302.76 ns | 1.023 ns | 0.957 ns | 1.00 | - | - | - | - |
// | Shuffle3 | 2. SSE | COMPlus_EnableAVX=0 | 768 | 50.24 ns | 0.098 ns | 0.092 ns | 0.17 | - | - | - | - |
// | Shuffle3 | 1. No HwIntrinsics | DOTNET_EnableHWIntrinsic=0,DOTNET_FeatureSIMD=0 | 768 | 302.76 ns | 1.023 ns | 0.957 ns | 1.00 | - | - | - | - |
// | Shuffle3 | 2. SSE | DOTNET_EnableAVX=0 | 768 | 50.24 ns | 0.098 ns | 0.092 ns | 0.17 | - | - | - | - |
// | Shuffle3 | 3. AVX | Empty | 768 | 49.28 ns | 0.156 ns | 0.131 ns | 0.16 | - | - | - | - |
// | | | | | | | | | | | | |
// | Shuffle3 | 1. No HwIntrinsics | COMPlus_EnableHWIntrinsic=0,COMPlus_FeatureSIMD=0 | 1536 | 596.53 ns | 2.675 ns | 2.503 ns | 1.00 | - | - | - | - |
// | Shuffle3 | 2. SSE | COMPlus_EnableAVX=0 | 1536 | 94.09 ns | 0.312 ns | 0.260 ns | 0.16 | - | - | - | - |
// | Shuffle3 | 1. No HwIntrinsics | DOTNET_EnableHWIntrinsic=0,DOTNET_FeatureSIMD=0 | 1536 | 596.53 ns | 2.675 ns | 2.503 ns | 1.00 | - | - | - | - |
// | Shuffle3 | 2. SSE | DOTNET_EnableAVX=0 | 1536 | 94.09 ns | 0.312 ns | 0.260 ns | 0.16 | - | - | - | - |
// | Shuffle3 | 3. AVX | Empty | 1536 | 93.57 ns | 0.196 ns | 0.183 ns | 0.16 | - | - | - | - |

80
tests/ImageSharp.Benchmarks/Bulk/Shuffle4Slice3Channel.cs

@ -47,45 +47,45 @@ public class Shuffle4Slice3Channel
//
// | Method | Job | EnvironmentVariables | Count | Mean | Error | StdDev | Median | Ratio | RatioSD | Gen 0 | Gen 1 | Gen 2 | Allocated |
// |--------------------------- |------------------- |-------------------------------------------------- |------ |----------:|---------:|---------:|----------:|------:|--------:|------:|------:|------:|----------:|
// | Shuffle4Slice3 | 1. No HwIntrinsics | COMPlus_EnableHWIntrinsic=0,COMPlus_FeatureSIMD=0 | 128 | 56.44 ns | 2.843 ns | 8.382 ns | 56.70 ns | 1.00 | 0.00 | - | - | - | - |
// | Shuffle4Slice3 | 1. No HwIntrinsics | DOTNET_EnableHWIntrinsic=0,DOTNET_FeatureSIMD=0 | 128 | 56.44 ns | 2.843 ns | 8.382 ns | 56.70 ns | 1.00 | 0.00 | - | - | - | - |
// | Shuffle4Slice3 | 2. AVX | Empty | 128 | 27.15 ns | 0.556 ns | 0.762 ns | 27.34 ns | 0.41 | 0.03 | - | - | - | - |
// | Shuffle4Slice3 | 3. SSE | COMPlus_EnableAVX=0 | 128 | 26.36 ns | 0.321 ns | 0.268 ns | 26.26 ns | 0.38 | 0.02 | - | - | - | - |
// | Shuffle4Slice3 | 3. SSE | DOTNET_EnableAVX=0 | 128 | 26.36 ns | 0.321 ns | 0.268 ns | 26.26 ns | 0.38 | 0.02 | - | - | - | - |
// | | | | | | | | | | | | | | |
// | Shuffle4Slice3FastFallback | 1. No HwIntrinsics | COMPlus_EnableHWIntrinsic=0,COMPlus_FeatureSIMD=0 | 128 | 25.85 ns | 0.494 ns | 0.462 ns | 25.84 ns | 1.00 | 0.00 | - | - | - | - |
// | Shuffle4Slice3FastFallback | 1. No HwIntrinsics | DOTNET_EnableHWIntrinsic=0,DOTNET_FeatureSIMD=0 | 128 | 25.85 ns | 0.494 ns | 0.462 ns | 25.84 ns | 1.00 | 0.00 | - | - | - | - |
// | Shuffle4Slice3FastFallback | 2. AVX | Empty | 128 | 26.15 ns | 0.113 ns | 0.106 ns | 26.16 ns | 1.01 | 0.02 | - | - | - | - |
// | Shuffle4Slice3FastFallback | 3. SSE | COMPlus_EnableAVX=0 | 128 | 25.57 ns | 0.078 ns | 0.061 ns | 25.56 ns | 0.99 | 0.02 | - | - | - | - |
// | Shuffle4Slice3FastFallback | 3. SSE | DOTNET_EnableAVX=0 | 128 | 25.57 ns | 0.078 ns | 0.061 ns | 25.56 ns | 0.99 | 0.02 | - | - | - | - |
// | | | | | | | | | | | | | | |
// | Shuffle4Slice3 | 1. No HwIntrinsics | COMPlus_EnableHWIntrinsic=0,COMPlus_FeatureSIMD=0 | 256 | 97.47 ns | 0.327 ns | 0.289 ns | 97.35 ns | 1.00 | 0.00 | - | - | - | - |
// | Shuffle4Slice3 | 1. No HwIntrinsics | DOTNET_EnableHWIntrinsic=0,DOTNET_FeatureSIMD=0 | 256 | 97.47 ns | 0.327 ns | 0.289 ns | 97.35 ns | 1.00 | 0.00 | - | - | - | - |
// | Shuffle4Slice3 | 2. AVX | Empty | 256 | 32.61 ns | 0.107 ns | 0.095 ns | 32.62 ns | 0.33 | 0.00 | - | - | - | - |
// | Shuffle4Slice3 | 3. SSE | COMPlus_EnableAVX=0 | 256 | 33.21 ns | 0.169 ns | 0.150 ns | 33.15 ns | 0.34 | 0.00 | - | - | - | - |
// | Shuffle4Slice3 | 3. SSE | DOTNET_EnableAVX=0 | 256 | 33.21 ns | 0.169 ns | 0.150 ns | 33.15 ns | 0.34 | 0.00 | - | - | - | - |
// | | | | | | | | | | | | | | |
// | Shuffle4Slice3FastFallback | 1. No HwIntrinsics | COMPlus_EnableHWIntrinsic=0,COMPlus_FeatureSIMD=0 | 256 | 52.34 ns | 0.779 ns | 0.729 ns | 51.94 ns | 1.00 | 0.00 | - | - | - | - |
// | Shuffle4Slice3FastFallback | 1. No HwIntrinsics | DOTNET_EnableHWIntrinsic=0,DOTNET_FeatureSIMD=0 | 256 | 52.34 ns | 0.779 ns | 0.729 ns | 51.94 ns | 1.00 | 0.00 | - | - | - | - |
// | Shuffle4Slice3FastFallback | 2. AVX | Empty | 256 | 32.16 ns | 0.111 ns | 0.104 ns | 32.16 ns | 0.61 | 0.01 | - | - | - | - |
// | Shuffle4Slice3FastFallback | 3. SSE | COMPlus_EnableAVX=0 | 256 | 33.61 ns | 0.342 ns | 0.319 ns | 33.62 ns | 0.64 | 0.01 | - | - | - | - |
// | Shuffle4Slice3FastFallback | 3. SSE | DOTNET_EnableAVX=0 | 256 | 33.61 ns | 0.342 ns | 0.319 ns | 33.62 ns | 0.64 | 0.01 | - | - | - | - |
// | | | | | | | | | | | | | | |
// | Shuffle4Slice3 | 1. No HwIntrinsics | COMPlus_EnableHWIntrinsic=0,COMPlus_FeatureSIMD=0 | 512 | 210.74 ns | 3.825 ns | 5.956 ns | 207.70 ns | 1.00 | 0.00 | - | - | - | - |
// | Shuffle4Slice3 | 1. No HwIntrinsics | DOTNET_EnableHWIntrinsic=0,DOTNET_FeatureSIMD=0 | 512 | 210.74 ns | 3.825 ns | 5.956 ns | 207.70 ns | 1.00 | 0.00 | - | - | - | - |
// | Shuffle4Slice3 | 2. AVX | Empty | 512 | 51.03 ns | 0.535 ns | 0.501 ns | 51.18 ns | 0.24 | 0.01 | - | - | - | - |
// | Shuffle4Slice3 | 3. SSE | COMPlus_EnableAVX=0 | 512 | 66.60 ns | 1.313 ns | 1.613 ns | 65.93 ns | 0.31 | 0.01 | - | - | - | - |
// | Shuffle4Slice3 | 3. SSE | DOTNET_EnableAVX=0 | 512 | 66.60 ns | 1.313 ns | 1.613 ns | 65.93 ns | 0.31 | 0.01 | - | - | - | - |
// | | | | | | | | | | | | | | |
// | Shuffle4Slice3FastFallback | 1. No HwIntrinsics | COMPlus_EnableHWIntrinsic=0,COMPlus_FeatureSIMD=0 | 512 | 119.12 ns | 1.905 ns | 1.689 ns | 118.52 ns | 1.00 | 0.00 | - | - | - | - |
// | Shuffle4Slice3FastFallback | 1. No HwIntrinsics | DOTNET_EnableHWIntrinsic=0,DOTNET_FeatureSIMD=0 | 512 | 119.12 ns | 1.905 ns | 1.689 ns | 118.52 ns | 1.00 | 0.00 | - | - | - | - |
// | Shuffle4Slice3FastFallback | 2. AVX | Empty | 512 | 50.33 ns | 0.382 ns | 0.339 ns | 50.41 ns | 0.42 | 0.01 | - | - | - | - |
// | Shuffle4Slice3FastFallback | 3. SSE | COMPlus_EnableAVX=0 | 512 | 49.25 ns | 0.555 ns | 0.492 ns | 49.26 ns | 0.41 | 0.01 | - | - | - | - |
// | Shuffle4Slice3FastFallback | 3. SSE | DOTNET_EnableAVX=0 | 512 | 49.25 ns | 0.555 ns | 0.492 ns | 49.26 ns | 0.41 | 0.01 | - | - | - | - |
// | | | | | | | | | | | | | | |
// | Shuffle4Slice3 | 1. No HwIntrinsics | COMPlus_EnableHWIntrinsic=0,COMPlus_FeatureSIMD=0 | 1024 | 423.55 ns | 4.891 ns | 4.336 ns | 423.27 ns | 1.00 | 0.00 | - | - | - | - |
// | Shuffle4Slice3 | 1. No HwIntrinsics | DOTNET_EnableHWIntrinsic=0,DOTNET_FeatureSIMD=0 | 1024 | 423.55 ns | 4.891 ns | 4.336 ns | 423.27 ns | 1.00 | 0.00 | - | - | - | - |
// | Shuffle4Slice3 | 2. AVX | Empty | 1024 | 77.13 ns | 1.355 ns | 2.264 ns | 76.19 ns | 0.19 | 0.01 | - | - | - | - |
// | Shuffle4Slice3 | 3. SSE | COMPlus_EnableAVX=0 | 1024 | 79.39 ns | 0.103 ns | 0.086 ns | 79.37 ns | 0.19 | 0.00 | - | - | - | - |
// | Shuffle4Slice3 | 3. SSE | DOTNET_EnableAVX=0 | 1024 | 79.39 ns | 0.103 ns | 0.086 ns | 79.37 ns | 0.19 | 0.00 | - | - | - | - |
// | | | | | | | | | | | | | | |
// | Shuffle4Slice3FastFallback | 1. No HwIntrinsics | COMPlus_EnableHWIntrinsic=0,COMPlus_FeatureSIMD=0 | 1024 | 226.57 ns | 2.930 ns | 2.598 ns | 226.10 ns | 1.00 | 0.00 | - | - | - | - |
// | Shuffle4Slice3FastFallback | 1. No HwIntrinsics | DOTNET_EnableHWIntrinsic=0,DOTNET_FeatureSIMD=0 | 1024 | 226.57 ns | 2.930 ns | 2.598 ns | 226.10 ns | 1.00 | 0.00 | - | - | - | - |
// | Shuffle4Slice3FastFallback | 2. AVX | Empty | 1024 | 80.25 ns | 1.647 ns | 2.082 ns | 80.98 ns | 0.35 | 0.01 | - | - | - | - |
// | Shuffle4Slice3FastFallback | 3. SSE | COMPlus_EnableAVX=0 | 1024 | 84.99 ns | 1.234 ns | 1.155 ns | 85.60 ns | 0.38 | 0.01 | - | - | - | - |
// | Shuffle4Slice3FastFallback | 3. SSE | DOTNET_EnableAVX=0 | 1024 | 84.99 ns | 1.234 ns | 1.155 ns | 85.60 ns | 0.38 | 0.01 | - | - | - | - |
// | | | | | | | | | | | | | | |
// | Shuffle4Slice3 | 1. No HwIntrinsics | COMPlus_EnableHWIntrinsic=0,COMPlus_FeatureSIMD=0 | 2048 | 794.96 ns | 1.735 ns | 1.538 ns | 795.15 ns | 1.00 | 0.00 | - | - | - | - |
// | Shuffle4Slice3 | 1. No HwIntrinsics | DOTNET_EnableHWIntrinsic=0,DOTNET_FeatureSIMD=0 | 2048 | 794.96 ns | 1.735 ns | 1.538 ns | 795.15 ns | 1.00 | 0.00 | - | - | - | - |
// | Shuffle4Slice3 | 2. AVX | Empty | 2048 | 128.41 ns | 0.417 ns | 0.390 ns | 128.24 ns | 0.16 | 0.00 | - | - | - | - |
// | Shuffle4Slice3 | 3. SSE | COMPlus_EnableAVX=0 | 2048 | 127.24 ns | 0.294 ns | 0.229 ns | 127.23 ns | 0.16 | 0.00 | - | - | - | - |
// | Shuffle4Slice3 | 3. SSE | DOTNET_EnableAVX=0 | 2048 | 127.24 ns | 0.294 ns | 0.229 ns | 127.23 ns | 0.16 | 0.00 | - | - | - | - |
// | | | | | | | | | | | | | | |
// | Shuffle4Slice3FastFallback | 1. No HwIntrinsics | COMPlus_EnableHWIntrinsic=0,COMPlus_FeatureSIMD=0 | 2048 | 382.97 ns | 1.064 ns | 0.831 ns | 382.87 ns | 1.00 | 0.00 | - | - | - | - |
// | Shuffle4Slice3FastFallback | 1. No HwIntrinsics | DOTNET_EnableHWIntrinsic=0,DOTNET_FeatureSIMD=0 | 2048 | 382.97 ns | 1.064 ns | 0.831 ns | 382.87 ns | 1.00 | 0.00 | - | - | - | - |
// | Shuffle4Slice3FastFallback | 2. AVX | Empty | 2048 | 126.93 ns | 0.382 ns | 0.339 ns | 126.94 ns | 0.33 | 0.00 | - | - | - | - |
// | Shuffle4Slice3FastFallback | 3. SSE | COMPlus_EnableAVX=0 | 2048 | 149.36 ns | 1.875 ns | 1.754 ns | 149.33 ns | 0.39 | 0.00 | - | - | - | - |
// | Shuffle4Slice3FastFallback | 3. SSE | DOTNET_EnableAVX=0 | 2048 | 149.36 ns | 1.875 ns | 1.754 ns | 149.33 ns | 0.39 | 0.00 | - | - | - | - |
// 2023-02-21
// ##########
@ -102,42 +102,42 @@ public class Shuffle4Slice3Channel
//
// | Method | Job | EnvironmentVariables | Count | Mean | Error | StdDev | Ratio | Gen 0 | Gen 1 | Gen 2 | Allocated |
// |--------------------------- |------------------- |-------------------------------------------------- |------ |----------:|---------:|---------:|------:|------:|------:|------:|----------:|
// | Shuffle4Slice3 | 1. No HwIntrinsics | COMPlus_EnableHWIntrinsic=0,COMPlus_FeatureSIMD=0 | 128 | 45.59 ns | 0.166 ns | 0.147 ns | 1.00 | - | - | - | - |
// | Shuffle4Slice3 | 2. SSE | COMPlus_EnableAVX=0 | 128 | 15.62 ns | 0.056 ns | 0.052 ns | 0.34 | - | - | - | - |
// | Shuffle4Slice3 | 1. No HwIntrinsics | DOTNET_EnableHWIntrinsic=0,DOTNET_FeatureSIMD=0 | 128 | 45.59 ns | 0.166 ns | 0.147 ns | 1.00 | - | - | - | - |
// | Shuffle4Slice3 | 2. SSE | DOTNET_EnableAVX=0 | 128 | 15.62 ns | 0.056 ns | 0.052 ns | 0.34 | - | - | - | - |
// | Shuffle4Slice3 | 3. AVX | Empty | 128 | 16.37 ns | 0.047 ns | 0.040 ns | 0.36 | - | - | - | - |
// | | | | | | | | | | | | |
// | Shuffle4Slice3FastFallback | 1. No HwIntrinsics | COMPlus_EnableHWIntrinsic=0,COMPlus_FeatureSIMD=0 | 128 | 13.23 ns | 0.028 ns | 0.026 ns | 1.00 | - | - | - | - |
// | Shuffle4Slice3FastFallback | 2. SSE | COMPlus_EnableAVX=0 | 128 | 14.41 ns | 0.013 ns | 0.012 ns | 1.09 | - | - | - | - |
// | Shuffle4Slice3FastFallback | 1. No HwIntrinsics | DOTNET_EnableHWIntrinsic=0,DOTNET_FeatureSIMD=0 | 128 | 13.23 ns | 0.028 ns | 0.026 ns | 1.00 | - | - | - | - |
// | Shuffle4Slice3FastFallback | 2. SSE | DOTNET_EnableAVX=0 | 128 | 14.41 ns | 0.013 ns | 0.012 ns | 1.09 | - | - | - | - |
// | Shuffle4Slice3FastFallback | 3. AVX | Empty | 128 | 14.70 ns | 0.050 ns | 0.047 ns | 1.11 | - | - | - | - |
// | | | | | | | | | | | | |
// | Shuffle4Slice3 | 1. No HwIntrinsics | COMPlus_EnableHWIntrinsic=0,COMPlus_FeatureSIMD=0 | 256 | 85.48 ns | 0.192 ns | 0.179 ns | 1.00 | - | - | - | - |
// | Shuffle4Slice3 | 2. SSE | COMPlus_EnableAVX=0 | 256 | 19.18 ns | 0.230 ns | 0.204 ns | 0.22 | - | - | - | - |
// | Shuffle4Slice3 | 1. No HwIntrinsics | DOTNET_EnableHWIntrinsic=0,DOTNET_FeatureSIMD=0 | 256 | 85.48 ns | 0.192 ns | 0.179 ns | 1.00 | - | - | - | - |
// | Shuffle4Slice3 | 2. SSE | DOTNET_EnableAVX=0 | 256 | 19.18 ns | 0.230 ns | 0.204 ns | 0.22 | - | - | - | - |
// | Shuffle4Slice3 | 3. AVX | Empty | 256 | 18.66 ns | 0.017 ns | 0.015 ns | 0.22 | - | - | - | - |
// | | | | | | | | | | | | |
// | Shuffle4Slice3FastFallback | 1. No HwIntrinsics | COMPlus_EnableHWIntrinsic=0,COMPlus_FeatureSIMD=0 | 256 | 24.34 ns | 0.078 ns | 0.073 ns | 1.00 | - | - | - | - |
// | Shuffle4Slice3FastFallback | 2. SSE | COMPlus_EnableAVX=0 | 256 | 18.58 ns | 0.061 ns | 0.057 ns | 0.76 | - | - | - | - |
// | Shuffle4Slice3FastFallback | 1. No HwIntrinsics | DOTNET_EnableHWIntrinsic=0,DOTNET_FeatureSIMD=0 | 256 | 24.34 ns | 0.078 ns | 0.073 ns | 1.00 | - | - | - | - |
// | Shuffle4Slice3FastFallback | 2. SSE | DOTNET_EnableAVX=0 | 256 | 18.58 ns | 0.061 ns | 0.057 ns | 0.76 | - | - | - | - |
// | Shuffle4Slice3FastFallback | 3. AVX | Empty | 256 | 19.23 ns | 0.018 ns | 0.016 ns | 0.79 | - | - | - | - |
// | | | | | | | | | | | | |
// | Shuffle4Slice3 | 1. No HwIntrinsics | COMPlus_EnableHWIntrinsic=0,COMPlus_FeatureSIMD=0 | 512 | 165.31 ns | 0.742 ns | 0.694 ns | 1.00 | - | - | - | - |
// | Shuffle4Slice3 | 2. SSE | COMPlus_EnableAVX=0 | 512 | 28.10 ns | 0.077 ns | 0.068 ns | 0.17 | - | - | - | - |
// | Shuffle4Slice3 | 1. No HwIntrinsics | DOTNET_EnableHWIntrinsic=0,DOTNET_FeatureSIMD=0 | 512 | 165.31 ns | 0.742 ns | 0.694 ns | 1.00 | - | - | - | - |
// | Shuffle4Slice3 | 2. SSE | DOTNET_EnableAVX=0 | 512 | 28.10 ns | 0.077 ns | 0.068 ns | 0.17 | - | - | - | - |
// | Shuffle4Slice3 | 3. AVX | Empty | 512 | 28.99 ns | 0.018 ns | 0.014 ns | 0.18 | - | - | - | - |
// | | | | | | | | | | | | |
// | Shuffle4Slice3FastFallback | 1. No HwIntrinsics | COMPlus_EnableHWIntrinsic=0,COMPlus_FeatureSIMD=0 | 512 | 53.45 ns | 0.270 ns | 0.226 ns | 1.00 | - | - | - | - |
// | Shuffle4Slice3FastFallback | 2. SSE | COMPlus_EnableAVX=0 | 512 | 27.50 ns | 0.034 ns | 0.028 ns | 0.51 | - | - | - | - |
// | Shuffle4Slice3FastFallback | 1. No HwIntrinsics | DOTNET_EnableHWIntrinsic=0,DOTNET_FeatureSIMD=0 | 512 | 53.45 ns | 0.270 ns | 0.226 ns | 1.00 | - | - | - | - |
// | Shuffle4Slice3FastFallback | 2. SSE | DOTNET_EnableAVX=0 | 512 | 27.50 ns | 0.034 ns | 0.028 ns | 0.51 | - | - | - | - |
// | Shuffle4Slice3FastFallback | 3. AVX | Empty | 512 | 28.76 ns | 0.017 ns | 0.015 ns | 0.54 | - | - | - | - |
// | | | | | | | | | | | | |
// | Shuffle4Slice3 | 1. No HwIntrinsics | COMPlus_EnableHWIntrinsic=0,COMPlus_FeatureSIMD=0 | 1024 | 323.87 ns | 0.549 ns | 0.487 ns | 1.00 | - | - | - | - |
// | Shuffle4Slice3 | 2. SSE | COMPlus_EnableAVX=0 | 1024 | 40.81 ns | 0.056 ns | 0.050 ns | 0.13 | - | - | - | - |
// | Shuffle4Slice3 | 1. No HwIntrinsics | DOTNET_EnableHWIntrinsic=0,DOTNET_FeatureSIMD=0 | 1024 | 323.87 ns | 0.549 ns | 0.487 ns | 1.00 | - | - | - | - |
// | Shuffle4Slice3 | 2. SSE | DOTNET_EnableAVX=0 | 1024 | 40.81 ns | 0.056 ns | 0.050 ns | 0.13 | - | - | - | - |
// | Shuffle4Slice3 | 3. AVX | Empty | 1024 | 39.95 ns | 0.075 ns | 0.067 ns | 0.12 | - | - | - | - |
// | | | | | | | | | | | | |
// | Shuffle4Slice3FastFallback | 1. No HwIntrinsics | COMPlus_EnableHWIntrinsic=0,COMPlus_FeatureSIMD=0 | 1024 | 101.37 ns | 0.080 ns | 0.067 ns | 1.00 | - | - | - | - |
// | Shuffle4Slice3FastFallback | 2. SSE | COMPlus_EnableAVX=0 | 1024 | 40.72 ns | 0.049 ns | 0.041 ns | 0.40 | - | - | - | - |
// | Shuffle4Slice3FastFallback | 1. No HwIntrinsics | DOTNET_EnableHWIntrinsic=0,DOTNET_FeatureSIMD=0 | 1024 | 101.37 ns | 0.080 ns | 0.067 ns | 1.00 | - | - | - | - |
// | Shuffle4Slice3FastFallback | 2. SSE | DOTNET_EnableAVX=0 | 1024 | 40.72 ns | 0.049 ns | 0.041 ns | 0.40 | - | - | - | - |
// | Shuffle4Slice3FastFallback | 3. AVX | Empty | 1024 | 39.78 ns | 0.029 ns | 0.027 ns | 0.39 | - | - | - | - |
// | | | | | | | | | | | | |
// | Shuffle4Slice3 | 1. No HwIntrinsics | COMPlus_EnableHWIntrinsic=0,COMPlus_FeatureSIMD=0 | 2048 | 642.95 ns | 2.067 ns | 1.933 ns | 1.00 | - | - | - | - |
// | Shuffle4Slice3 | 2. SSE | COMPlus_EnableAVX=0 | 2048 | 73.19 ns | 0.082 ns | 0.077 ns | 0.11 | - | - | - | - |
// | Shuffle4Slice3 | 1. No HwIntrinsics | DOTNET_EnableHWIntrinsic=0,DOTNET_FeatureSIMD=0 | 2048 | 642.95 ns | 2.067 ns | 1.933 ns | 1.00 | - | - | - | - |
// | Shuffle4Slice3 | 2. SSE | DOTNET_EnableAVX=0 | 2048 | 73.19 ns | 0.082 ns | 0.077 ns | 0.11 | - | - | - | - |
// | Shuffle4Slice3 | 3. AVX | Empty | 2048 | 69.83 ns | 0.319 ns | 0.267 ns | 0.11 | - | - | - | - |
// | | | | | | | | | | | | |
// | Shuffle4Slice3FastFallback | 1. No HwIntrinsics | COMPlus_EnableHWIntrinsic=0,COMPlus_FeatureSIMD=0 | 2048 | 196.85 ns | 0.238 ns | 0.211 ns | 1.00 | - | - | - | - |
// | Shuffle4Slice3FastFallback | 2. SSE | COMPlus_EnableAVX=0 | 2048 | 72.89 ns | 0.117 ns | 0.098 ns | 0.37 | - | - | - | - |
// | Shuffle4Slice3FastFallback | 1. No HwIntrinsics | DOTNET_EnableHWIntrinsic=0,DOTNET_FeatureSIMD=0 | 2048 | 196.85 ns | 0.238 ns | 0.211 ns | 1.00 | - | - | - | - |
// | Shuffle4Slice3FastFallback | 2. SSE | DOTNET_EnableAVX=0 | 2048 | 72.89 ns | 0.117 ns | 0.098 ns | 0.37 | - | - | - | - |
// | Shuffle4Slice3FastFallback | 3. AVX | Empty | 2048 | 69.59 ns | 0.073 ns | 0.061 ns | 0.35 | - | - | - | - |

40
tests/ImageSharp.Benchmarks/Bulk/ShuffleByte4Channel.cs

@ -42,25 +42,25 @@ public class ShuffleByte4Channel
//
// | Method | Job | EnvironmentVariables | Count | Mean | Error | StdDev | Ratio | RatioSD | Gen 0 | Gen 1 | Gen 2 | Allocated |
// |---------------- |------------------- |-------------------------------------------------- |------ |----------:|---------:|---------:|------:|--------:|------:|------:|------:|----------:|
// | Shuffle4Channel | 1. No HwIntrinsics | COMPlus_EnableHWIntrinsic=0,COMPlus_FeatureSIMD=0 | 128 | 17.39 ns | 0.187 ns | 0.175 ns | 1.00 | 0.00 | - | - | - | - |
// | Shuffle4Channel | 1. No HwIntrinsics | DOTNET_EnableHWIntrinsic=0,DOTNET_FeatureSIMD=0 | 128 | 17.39 ns | 0.187 ns | 0.175 ns | 1.00 | 0.00 | - | - | - | - |
// | Shuffle4Channel | 2. AVX | Empty | 128 | 21.72 ns | 0.299 ns | 0.279 ns | 1.25 | 0.02 | - | - | - | - |
// | Shuffle4Channel | 3. SSE | COMPlus_EnableAVX=0 | 128 | 18.10 ns | 0.346 ns | 0.289 ns | 1.04 | 0.02 | - | - | - | - |
// | Shuffle4Channel | 3. SSE | DOTNET_EnableAVX=0 | 128 | 18.10 ns | 0.346 ns | 0.289 ns | 1.04 | 0.02 | - | - | - | - |
// | | | | | | | | | | | | | |
// | Shuffle4Channel | 1. No HwIntrinsics | COMPlus_EnableHWIntrinsic=0,COMPlus_FeatureSIMD=0 | 256 | 35.51 ns | 0.711 ns | 0.790 ns | 1.00 | 0.00 | - | - | - | - |
// | Shuffle4Channel | 1. No HwIntrinsics | DOTNET_EnableHWIntrinsic=0,DOTNET_FeatureSIMD=0 | 256 | 35.51 ns | 0.711 ns | 0.790 ns | 1.00 | 0.00 | - | - | - | - |
// | Shuffle4Channel | 2. AVX | Empty | 256 | 23.90 ns | 0.508 ns | 0.820 ns | 0.69 | 0.02 | - | - | - | - |
// | Shuffle4Channel | 3. SSE | COMPlus_EnableAVX=0 | 256 | 20.40 ns | 0.133 ns | 0.111 ns | 0.57 | 0.01 | - | - | - | - |
// | Shuffle4Channel | 3. SSE | DOTNET_EnableAVX=0 | 256 | 20.40 ns | 0.133 ns | 0.111 ns | 0.57 | 0.01 | - | - | - | - |
// | | | | | | | | | | | | | |
// | Shuffle4Channel | 1. No HwIntrinsics | COMPlus_EnableHWIntrinsic=0,COMPlus_FeatureSIMD=0 | 512 | 73.39 ns | 0.310 ns | 0.259 ns | 1.00 | 0.00 | - | - | - | - |
// | Shuffle4Channel | 1. No HwIntrinsics | DOTNET_EnableHWIntrinsic=0,DOTNET_FeatureSIMD=0 | 512 | 73.39 ns | 0.310 ns | 0.259 ns | 1.00 | 0.00 | - | - | - | - |
// | Shuffle4Channel | 2. AVX | Empty | 512 | 26.10 ns | 0.418 ns | 0.391 ns | 0.36 | 0.01 | - | - | - | - |
// | Shuffle4Channel | 3. SSE | COMPlus_EnableAVX=0 | 512 | 27.59 ns | 0.556 ns | 0.571 ns | 0.38 | 0.01 | - | - | - | - |
// | Shuffle4Channel | 3. SSE | DOTNET_EnableAVX=0 | 512 | 27.59 ns | 0.556 ns | 0.571 ns | 0.38 | 0.01 | - | - | - | - |
// | | | | | | | | | | | | | |
// | Shuffle4Channel | 1. No HwIntrinsics | COMPlus_EnableHWIntrinsic=0,COMPlus_FeatureSIMD=0 | 1024 | 150.64 ns | 2.903 ns | 2.716 ns | 1.00 | 0.00 | - | - | - | - |
// | Shuffle4Channel | 1. No HwIntrinsics | DOTNET_EnableHWIntrinsic=0,DOTNET_FeatureSIMD=0 | 1024 | 150.64 ns | 2.903 ns | 2.716 ns | 1.00 | 0.00 | - | - | - | - |
// | Shuffle4Channel | 2. AVX | Empty | 1024 | 38.67 ns | 0.801 ns | 1.889 ns | 0.24 | 0.02 | - | - | - | - |
// | Shuffle4Channel | 3. SSE | COMPlus_EnableAVX=0 | 1024 | 47.13 ns | 0.948 ns | 1.054 ns | 0.31 | 0.01 | - | - | - | - |
// | Shuffle4Channel | 3. SSE | DOTNET_EnableAVX=0 | 1024 | 47.13 ns | 0.948 ns | 1.054 ns | 0.31 | 0.01 | - | - | - | - |
// | | | | | | | | | | | | | |
// | Shuffle4Channel | 1. No HwIntrinsics | COMPlus_EnableHWIntrinsic=0,COMPlus_FeatureSIMD=0 | 2048 | 315.29 ns | 5.206 ns | 6.583 ns | 1.00 | 0.00 | - | - | - | - |
// | Shuffle4Channel | 1. No HwIntrinsics | DOTNET_EnableHWIntrinsic=0,DOTNET_FeatureSIMD=0 | 2048 | 315.29 ns | 5.206 ns | 6.583 ns | 1.00 | 0.00 | - | - | - | - |
// | Shuffle4Channel | 2. AVX | Empty | 2048 | 57.37 ns | 1.152 ns | 1.078 ns | 0.18 | 0.01 | - | - | - | - |
// | Shuffle4Channel | 3. SSE | COMPlus_EnableAVX=0 | 2048 | 65.75 ns | 1.198 ns | 1.600 ns | 0.21 | 0.01 | - | - | - | - |
// | Shuffle4Channel | 3. SSE | DOTNET_EnableAVX=0 | 2048 | 65.75 ns | 1.198 ns | 1.600 ns | 0.21 | 0.01 | - | - | - | - |
// 2023-02-21
// ##########
@ -77,22 +77,22 @@ public class ShuffleByte4Channel
//
// | Method | Job | EnvironmentVariables | Count | Mean | Error | StdDev | Ratio | RatioSD | Gen 0 | Gen 1 | Gen 2 | Allocated |
// |---------------- |------------------- |-------------------------------------------------- |------ |----------:|---------:|---------:|------:|--------:|------:|------:|------:|----------:|
// | Shuffle4Channel | 1. No HwIntrinsics | COMPlus_EnableHWIntrinsic=0,COMPlus_FeatureSIMD=0 | 128 | 10.76 ns | 0.033 ns | 0.029 ns | 1.00 | 0.00 | - | - | - | - |
// | Shuffle4Channel | 2. SSE | COMPlus_EnableAVX=0 | 128 | 11.39 ns | 0.045 ns | 0.040 ns | 1.06 | 0.01 | - | - | - | - |
// | Shuffle4Channel | 1. No HwIntrinsics | DOTNET_EnableHWIntrinsic=0,DOTNET_FeatureSIMD=0 | 128 | 10.76 ns | 0.033 ns | 0.029 ns | 1.00 | 0.00 | - | - | - | - |
// | Shuffle4Channel | 2. SSE | DOTNET_EnableAVX=0 | 128 | 11.39 ns | 0.045 ns | 0.040 ns | 1.06 | 0.01 | - | - | - | - |
// | Shuffle4Channel | 3. AVX | Empty | 128 | 14.05 ns | 0.029 ns | 0.024 ns | 1.31 | 0.00 | - | - | - | - |
// | | | | | | | | | | | | | |
// | Shuffle4Channel | 1. No HwIntrinsics | COMPlus_EnableHWIntrinsic=0,COMPlus_FeatureSIMD=0 | 256 | 32.09 ns | 0.655 ns | 1.000 ns | 1.00 | 0.00 | - | - | - | - |
// | Shuffle4Channel | 2. SSE | COMPlus_EnableAVX=0 | 256 | 14.03 ns | 0.047 ns | 0.041 ns | 0.44 | 0.02 | - | - | - | - |
// | Shuffle4Channel | 1. No HwIntrinsics | DOTNET_EnableHWIntrinsic=0,DOTNET_FeatureSIMD=0 | 256 | 32.09 ns | 0.655 ns | 1.000 ns | 1.00 | 0.00 | - | - | - | - |
// | Shuffle4Channel | 2. SSE | DOTNET_EnableAVX=0 | 256 | 14.03 ns | 0.047 ns | 0.041 ns | 0.44 | 0.02 | - | - | - | - |
// | Shuffle4Channel | 3. AVX | Empty | 256 | 15.18 ns | 0.052 ns | 0.043 ns | 0.48 | 0.03 | - | - | - | - |
// | | | | | | | | | | | | | |
// | Shuffle4Channel | 1. No HwIntrinsics | COMPlus_EnableHWIntrinsic=0,COMPlus_FeatureSIMD=0 | 512 | 59.26 ns | 0.084 ns | 0.070 ns | 1.00 | 0.00 | - | - | - | - |
// | Shuffle4Channel | 2. SSE | COMPlus_EnableAVX=0 | 512 | 18.80 ns | 0.036 ns | 0.034 ns | 0.32 | 0.00 | - | - | - | - |
// | Shuffle4Channel | 1. No HwIntrinsics | DOTNET_EnableHWIntrinsic=0,DOTNET_FeatureSIMD=0 | 512 | 59.26 ns | 0.084 ns | 0.070 ns | 1.00 | 0.00 | - | - | - | - |
// | Shuffle4Channel | 2. SSE | DOTNET_EnableAVX=0 | 512 | 18.80 ns | 0.036 ns | 0.034 ns | 0.32 | 0.00 | - | - | - | - |
// | Shuffle4Channel | 3. AVX | Empty | 512 | 17.69 ns | 0.038 ns | 0.034 ns | 0.30 | 0.00 | - | - | - | - |
// | | | | | | | | | | | | | |
// | Shuffle4Channel | 1. No HwIntrinsics | COMPlus_EnableHWIntrinsic=0,COMPlus_FeatureSIMD=0 | 1024 | 112.48 ns | 0.285 ns | 0.253 ns | 1.00 | 0.00 | - | - | - | - |
// | Shuffle4Channel | 2. SSE | COMPlus_EnableAVX=0 | 1024 | 31.57 ns | 0.041 ns | 0.036 ns | 0.28 | 0.00 | - | - | - | - |
// | Shuffle4Channel | 1. No HwIntrinsics | DOTNET_EnableHWIntrinsic=0,DOTNET_FeatureSIMD=0 | 1024 | 112.48 ns | 0.285 ns | 0.253 ns | 1.00 | 0.00 | - | - | - | - |
// | Shuffle4Channel | 2. SSE | DOTNET_EnableAVX=0 | 1024 | 31.57 ns | 0.041 ns | 0.036 ns | 0.28 | 0.00 | - | - | - | - |
// | Shuffle4Channel | 3. AVX | Empty | 1024 | 28.41 ns | 0.068 ns | 0.064 ns | 0.25 | 0.00 | - | - | - | - |
// | | | | | | | | | | | | | |
// | Shuffle4Channel | 1. No HwIntrinsics | COMPlus_EnableHWIntrinsic=0,COMPlus_FeatureSIMD=0 | 2048 | 218.59 ns | 0.303 ns | 0.283 ns | 1.00 | 0.00 | - | - | - | - |
// | Shuffle4Channel | 2. SSE | COMPlus_EnableAVX=0 | 2048 | 53.04 ns | 0.106 ns | 0.099 ns | 0.24 | 0.00 | - | - | - | - |
// | Shuffle4Channel | 1. No HwIntrinsics | DOTNET_EnableHWIntrinsic=0,DOTNET_FeatureSIMD=0 | 2048 | 218.59 ns | 0.303 ns | 0.283 ns | 1.00 | 0.00 | - | - | - | - |
// | Shuffle4Channel | 2. SSE | DOTNET_EnableAVX=0 | 2048 | 53.04 ns | 0.106 ns | 0.099 ns | 0.24 | 0.00 | - | - | - | - |
// | Shuffle4Channel | 3. AVX | Empty | 2048 | 34.74 ns | 0.061 ns | 0.054 ns | 0.16 | 0.00 | - | - | - | - |

40
tests/ImageSharp.Benchmarks/Bulk/ShuffleFloat4Channel.cs

@ -42,25 +42,25 @@ public class ShuffleFloat4Channel
//
// | Method | Job | EnvironmentVariables | Count | Mean | Error | StdDev | Ratio | Gen 0 | Gen 1 | Gen 2 | Allocated |
// |---------------- |------------------- |-------------------------------------------------- |------ |-----------:|----------:|----------:|------:|------:|------:|------:|----------:|
// | Shuffle4Channel | 1. No HwIntrinsics | COMPlus_EnableHWIntrinsic=0,COMPlus_FeatureSIMD=0 | 128 | 63.647 ns | 0.5475 ns | 0.4853 ns | 1.00 | - | - | - | - |
// | Shuffle4Channel | 1. No HwIntrinsics | DOTNET_EnableHWIntrinsic=0,DOTNET_FeatureSIMD=0 | 128 | 63.647 ns | 0.5475 ns | 0.4853 ns | 1.00 | - | - | - | - |
// | Shuffle4Channel | 2. AVX | Empty | 128 | 9.818 ns | 0.1457 ns | 0.1292 ns | 0.15 | - | - | - | - |
// | Shuffle4Channel | 3. SSE | COMPlus_EnableAVX=0 | 128 | 15.267 ns | 0.1005 ns | 0.0940 ns | 0.24 | - | - | - | - |
// | Shuffle4Channel | 3. SSE | DOTNET_EnableAVX=0 | 128 | 15.267 ns | 0.1005 ns | 0.0940 ns | 0.24 | - | - | - | - |
// | | | | | | | | | | | | |
// | Shuffle4Channel | 1. No HwIntrinsics | COMPlus_EnableHWIntrinsic=0,COMPlus_FeatureSIMD=0 | 256 | 125.586 ns | 1.9312 ns | 1.8064 ns | 1.00 | - | - | - | - |
// | Shuffle4Channel | 1. No HwIntrinsics | DOTNET_EnableHWIntrinsic=0,DOTNET_FeatureSIMD=0 | 256 | 125.586 ns | 1.9312 ns | 1.8064 ns | 1.00 | - | - | - | - |
// | Shuffle4Channel | 2. AVX | Empty | 256 | 15.878 ns | 0.1983 ns | 0.1758 ns | 0.13 | - | - | - | - |
// | Shuffle4Channel | 3. SSE | COMPlus_EnableAVX=0 | 256 | 29.170 ns | 0.2925 ns | 0.2442 ns | 0.23 | - | - | - | - |
// | Shuffle4Channel | 3. SSE | DOTNET_EnableAVX=0 | 256 | 29.170 ns | 0.2925 ns | 0.2442 ns | 0.23 | - | - | - | - |
// | | | | | | | | | | | | |
// | Shuffle4Channel | 1. No HwIntrinsics | COMPlus_EnableHWIntrinsic=0,COMPlus_FeatureSIMD=0 | 512 | 263.859 ns | 2.6660 ns | 2.3634 ns | 1.00 | - | - | - | - |
// | Shuffle4Channel | 1. No HwIntrinsics | DOTNET_EnableHWIntrinsic=0,DOTNET_FeatureSIMD=0 | 512 | 263.859 ns | 2.6660 ns | 2.3634 ns | 1.00 | - | - | - | - |
// | Shuffle4Channel | 2. AVX | Empty | 512 | 29.452 ns | 0.3334 ns | 0.3118 ns | 0.11 | - | - | - | - |
// | Shuffle4Channel | 3. SSE | COMPlus_EnableAVX=0 | 512 | 52.912 ns | 0.1932 ns | 0.1713 ns | 0.20 | - | - | - | - |
// | Shuffle4Channel | 3. SSE | DOTNET_EnableAVX=0 | 512 | 52.912 ns | 0.1932 ns | 0.1713 ns | 0.20 | - | - | - | - |
// | | | | | | | | | | | | |
// | Shuffle4Channel | 1. No HwIntrinsics | COMPlus_EnableHWIntrinsic=0,COMPlus_FeatureSIMD=0 | 1024 | 495.717 ns | 1.9850 ns | 1.8567 ns | 1.00 | - | - | - | - |
// | Shuffle4Channel | 1. No HwIntrinsics | DOTNET_EnableHWIntrinsic=0,DOTNET_FeatureSIMD=0 | 1024 | 495.717 ns | 1.9850 ns | 1.8567 ns | 1.00 | - | - | - | - |
// | Shuffle4Channel | 2. AVX | Empty | 1024 | 53.757 ns | 0.3212 ns | 0.2847 ns | 0.11 | - | - | - | - |
// | Shuffle4Channel | 3. SSE | COMPlus_EnableAVX=0 | 1024 | 107.815 ns | 1.6201 ns | 1.3528 ns | 0.22 | - | - | - | - |
// | Shuffle4Channel | 3. SSE | DOTNET_EnableAVX=0 | 1024 | 107.815 ns | 1.6201 ns | 1.3528 ns | 0.22 | - | - | - | - |
// | | | | | | | | | | | | |
// | Shuffle4Channel | 1. No HwIntrinsics | COMPlus_EnableHWIntrinsic=0,COMPlus_FeatureSIMD=0 | 2048 | 980.134 ns | 3.7407 ns | 3.1237 ns | 1.00 | - | - | - | - |
// | Shuffle4Channel | 1. No HwIntrinsics | DOTNET_EnableHWIntrinsic=0,DOTNET_FeatureSIMD=0 | 2048 | 980.134 ns | 3.7407 ns | 3.1237 ns | 1.00 | - | - | - | - |
// | Shuffle4Channel | 2. AVX | Empty | 2048 | 105.120 ns | 0.6140 ns | 0.5443 ns | 0.11 | - | - | - | - |
// | Shuffle4Channel | 3. SSE | COMPlus_EnableAVX=0 | 2048 | 216.473 ns | 2.3268 ns | 2.0627 ns | 0.22 | - | - | - | - |
// | Shuffle4Channel | 3. SSE | DOTNET_EnableAVX=0 | 2048 | 216.473 ns | 2.3268 ns | 2.0627 ns | 0.22 | - | - | - | - |
// 2023-02-21
// ##########
@ -77,22 +77,22 @@ public class ShuffleFloat4Channel
//
// | Method | Job | EnvironmentVariables | Count | Mean | Error | StdDev | Ratio | Gen 0 | Gen 1 | Gen 2 | Allocated |
// |---------------- |------------------- |-------------------------------------------------- |------ |-----------:|----------:|----------:|------:|------:|------:|------:|----------:|
// | Shuffle4Channel | 1. No HwIntrinsics | COMPlus_EnableHWIntrinsic=0,COMPlus_FeatureSIMD=0 | 128 | 57.819 ns | 0.2360 ns | 0.1970 ns | 1.00 | - | - | - | - |
// | Shuffle4Channel | 2. SSE | COMPlus_EnableAVX=0 | 128 | 11.564 ns | 0.0234 ns | 0.0195 ns | 0.20 | - | - | - | - |
// | Shuffle4Channel | 1. No HwIntrinsics | DOTNET_EnableHWIntrinsic=0,DOTNET_FeatureSIMD=0 | 128 | 57.819 ns | 0.2360 ns | 0.1970 ns | 1.00 | - | - | - | - |
// | Shuffle4Channel | 2. SSE | DOTNET_EnableAVX=0 | 128 | 11.564 ns | 0.0234 ns | 0.0195 ns | 0.20 | - | - | - | - |
// | Shuffle4Channel | 3. AVX | Empty | 128 | 7.770 ns | 0.0696 ns | 0.0617 ns | 0.13 | - | - | - | - |
// | | | | | | | | | | | | |
// | Shuffle4Channel | 1. No HwIntrinsics | COMPlus_EnableHWIntrinsic=0,COMPlus_FeatureSIMD=0 | 256 | 105.282 ns | 0.2713 ns | 0.2405 ns | 1.00 | - | - | - | - |
// | Shuffle4Channel | 2. SSE | COMPlus_EnableAVX=0 | 256 | 19.867 ns | 0.0393 ns | 0.0348 ns | 0.19 | - | - | - | - |
// | Shuffle4Channel | 1. No HwIntrinsics | DOTNET_EnableHWIntrinsic=0,DOTNET_FeatureSIMD=0 | 256 | 105.282 ns | 0.2713 ns | 0.2405 ns | 1.00 | - | - | - | - |
// | Shuffle4Channel | 2. SSE | DOTNET_EnableAVX=0 | 256 | 19.867 ns | 0.0393 ns | 0.0348 ns | 0.19 | - | - | - | - |
// | Shuffle4Channel | 3. AVX | Empty | 256 | 17.586 ns | 0.0582 ns | 0.0544 ns | 0.17 | - | - | - | - |
// | | | | | | | | | | | | |
// | Shuffle4Channel | 1. No HwIntrinsics | COMPlus_EnableHWIntrinsic=0,COMPlus_FeatureSIMD=0 | 512 | 200.799 ns | 0.5678 ns | 0.5033 ns | 1.00 | - | - | - | - |
// | Shuffle4Channel | 2. SSE | COMPlus_EnableAVX=0 | 512 | 41.137 ns | 0.1524 ns | 0.1351 ns | 0.20 | - | - | - | - |
// | Shuffle4Channel | 1. No HwIntrinsics | DOTNET_EnableHWIntrinsic=0,DOTNET_FeatureSIMD=0 | 512 | 200.799 ns | 0.5678 ns | 0.5033 ns | 1.00 | - | - | - | - |
// | Shuffle4Channel | 2. SSE | DOTNET_EnableAVX=0 | 512 | 41.137 ns | 0.1524 ns | 0.1351 ns | 0.20 | - | - | - | - |
// | Shuffle4Channel | 3. AVX | Empty | 512 | 24.040 ns | 0.0445 ns | 0.0395 ns | 0.12 | - | - | - | - |
// | | | | | | | | | | | | |
// | Shuffle4Channel | 1. No HwIntrinsics | COMPlus_EnableHWIntrinsic=0,COMPlus_FeatureSIMD=0 | 1024 | 401.046 ns | 0.5865 ns | 0.5199 ns | 1.00 | - | - | - | - |
// | Shuffle4Channel | 2. SSE | COMPlus_EnableAVX=0 | 1024 | 94.904 ns | 0.4633 ns | 0.4334 ns | 0.24 | - | - | - | - |
// | Shuffle4Channel | 1. No HwIntrinsics | DOTNET_EnableHWIntrinsic=0,DOTNET_FeatureSIMD=0 | 1024 | 401.046 ns | 0.5865 ns | 0.5199 ns | 1.00 | - | - | - | - |
// | Shuffle4Channel | 2. SSE | DOTNET_EnableAVX=0 | 1024 | 94.904 ns | 0.4633 ns | 0.4334 ns | 0.24 | - | - | - | - |
// | Shuffle4Channel | 3. AVX | Empty | 1024 | 68.456 ns | 0.1192 ns | 0.0996 ns | 0.17 | - | - | - | - |
// | | | | | | | | | | | | |
// | Shuffle4Channel | 1. No HwIntrinsics | COMPlus_EnableHWIntrinsic=0,COMPlus_FeatureSIMD=0 | 2048 | 772.297 ns | 0.6270 ns | 0.5558 ns | 1.00 | - | - | - | - |
// | Shuffle4Channel | 2. SSE | COMPlus_EnableAVX=0 | 2048 | 184.561 ns | 0.4319 ns | 0.4040 ns | 0.24 | - | - | - | - |
// | Shuffle4Channel | 1. No HwIntrinsics | DOTNET_EnableHWIntrinsic=0,DOTNET_FeatureSIMD=0 | 2048 | 772.297 ns | 0.6270 ns | 0.5558 ns | 1.00 | - | - | - | - |
// | Shuffle4Channel | 2. SSE | DOTNET_EnableAVX=0 | 2048 | 184.561 ns | 0.4319 ns | 0.4040 ns | 0.24 | - | - | - | - |
// | Shuffle4Channel | 3. AVX | Empty | 2048 | 133.634 ns | 1.7864 ns | 1.8345 ns | 0.17 | - | - | - | - |

10
tests/ImageSharp.Benchmarks/Codecs/Tga/DecodeTga.cs

@ -29,22 +29,22 @@ public class DecodeTga
[Benchmark(Baseline = true, Description = "ImageMagick Tga")]
public int TgaImageMagick()
{
var settings = new MagickReadSettings { Format = MagickFormat.Tga };
using var image = new MagickImage(new MemoryStream(this.data), settings);
return image.Width;
MagickReadSettings settings = new() { Format = MagickFormat.Tga };
using MagickImage image = new(new MemoryStream(this.data), settings);
return (int)image.Width;
}
[Benchmark(Description = "ImageSharp Tga")]
public int TgaImageSharp()
{
using var image = Image.Load<Bgr24>(this.data);
using Image<Bgr24> image = Image.Load<Bgr24>(this.data);
return image.Width;
}
[Benchmark(Description = "Pfim Tga")]
public int TgaPfim()
{
using var image = Targa.Create(this.data, this.pfimConfig);
using Targa image = Targa.Create(this.data, this.pfimConfig);
return image.Width;
}

25
tests/ImageSharp.Benchmarks/Codecs/Webp/DecodeWebp.cs

@ -44,34 +44,35 @@ public class DecodeWebp
[Benchmark(Description = "Magick Lossy Webp")]
public int WebpLossyMagick()
{
var settings = new MagickReadSettings { Format = MagickFormat.WebP };
using var memoryStream = new MemoryStream(this.webpLossyBytes);
using var image = new MagickImage(memoryStream, settings);
return image.Width;
MagickReadSettings settings = new() { Format = MagickFormat.WebP };
using MemoryStream memoryStream = new(this.webpLossyBytes);
using MagickImage image = new(memoryStream, settings);
return (int)image.Width;
}
[Benchmark(Description = "ImageSharp Lossy Webp")]
public int WebpLossy()
{
using var memoryStream = new MemoryStream(this.webpLossyBytes);
using var image = Image.Load<Rgba32>(memoryStream);
using MemoryStream memoryStream = new(this.webpLossyBytes);
using Image<Rgba32> image = Image.Load<Rgba32>(memoryStream);
return image.Height;
}
[Benchmark(Description = "Magick Lossless Webp")]
public int WebpLosslessMagick()
{
var settings = new MagickReadSettings { Format = MagickFormat.WebP };
using var memoryStream = new MemoryStream(this.webpLossyBytes);
using var image = new MagickImage(memoryStream, settings);
return image.Width;
MagickReadSettings settings = new()
{ Format = MagickFormat.WebP };
using MemoryStream memoryStream = new(this.webpLossyBytes);
using MagickImage image = new(memoryStream, settings);
return (int)image.Width;
}
[Benchmark(Description = "ImageSharp Lossless Webp")]
public int WebpLossless()
{
using var memoryStream = new MemoryStream(this.webpLosslessBytes);
using var image = Image.Load<Rgba32>(memoryStream);
using MemoryStream memoryStream = new(this.webpLosslessBytes);
using Image<Rgba32> image = Image.Load<Rgba32>(memoryStream);
return image.Height;
}

10
tests/ImageSharp.Benchmarks/Codecs/Webp/EncodeWebp.cs

@ -13,7 +13,9 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs;
[MarkdownExporter]
[HtmlExporter]
[Config(typeof(Config.Short))]
#pragma warning disable CA1001 // Types that own disposable fields should be disposable
public class EncodeWebp
#pragma warning restore CA1001 // Types that own disposable fields should be disposable
{
private MagickImage webpMagick;
private Image<Rgba32> webp;
@ -52,10 +54,7 @@ public class EncodeWebp
AlphaCompression = WebPAlphaCompression.None,
FilterStrength = 60,
SnsStrength = 50,
Pass = 1,
// 100 means off.
NearLossless = 100
Pass = 1
};
this.webpMagick.Quality = 75;
@ -85,9 +84,6 @@ public class EncodeWebp
{
Lossless = true,
Method = 4,
// 100 means off.
NearLossless = 100
};
this.webpMagick.Quality = 75;

36
tests/ImageSharp.Benchmarks/Config.HwIntrinsics.cs

@ -33,24 +33,24 @@ public partial class Config
// `FeatureSIMD` ends up impacting all SIMD support(including `System.Numerics`) but not things
// like `LZCNT`, `BMI1`, or `BMI2`
// `EnableSSE3_4` is a legacy switch that exists for compat and is basically the same as `EnableSSE3`
private const string EnableAES = "COMPlus_EnableAES";
private const string EnableAVX = "COMPlus_EnableAVX";
private const string EnableAVX2 = "COMPlus_EnableAVX2";
private const string EnableBMI1 = "COMPlus_EnableBMI1";
private const string EnableBMI2 = "COMPlus_EnableBMI2";
private const string EnableFMA = "COMPlus_EnableFMA";
private const string EnableHWIntrinsic = "COMPlus_EnableHWIntrinsic";
private const string EnableLZCNT = "COMPlus_EnableLZCNT";
private const string EnablePCLMULQDQ = "COMPlus_EnablePCLMULQDQ";
private const string EnablePOPCNT = "COMPlus_EnablePOPCNT";
private const string EnableSSE = "COMPlus_EnableSSE";
private const string EnableSSE2 = "COMPlus_EnableSSE2";
private const string EnableSSE3 = "COMPlus_EnableSSE3";
private const string EnableSSE3_4 = "COMPlus_EnableSSE3_4";
private const string EnableSSE41 = "COMPlus_EnableSSE41";
private const string EnableSSE42 = "COMPlus_EnableSSE42";
private const string EnableSSSE3 = "COMPlus_EnableSSSE3";
private const string FeatureSIMD = "COMPlus_FeatureSIMD";
private const string EnableAES = "DOTNET_EnableAES";
private const string EnableAVX = "DOTNET_EnableAVX";
private const string EnableAVX2 = "DOTNET_EnableAVX2";
private const string EnableBMI1 = "DOTNET_EnableBMI1";
private const string EnableBMI2 = "DOTNET_EnableBMI2";
private const string EnableFMA = "DOTNET_EnableFMA";
private const string EnableHWIntrinsic = "DOTNET_EnableHWIntrinsic";
private const string EnableLZCNT = "DOTNET_EnableLZCNT";
private const string EnablePCLMULQDQ = "DOTNET_EnablePCLMULQDQ";
private const string EnablePOPCNT = "DOTNET_EnablePOPCNT";
private const string EnableSSE = "DOTNET_EnableSSE";
private const string EnableSSE2 = "DOTNET_EnableSSE2";
private const string EnableSSE3 = "DOTNET_EnableSSE3";
private const string EnableSSE3_4 = "DOTNET_EnableSSE3_4";
private const string EnableSSE41 = "DOTNET_EnableSSE41";
private const string EnableSSE42 = "DOTNET_EnableSSE42";
private const string EnableSSSE3 = "DOTNET_EnableSSSE3";
private const string FeatureSIMD = "DOTNET_FeatureSIMD";
public class HwIntrinsics_SSE_AVX : Config
{

6
tests/ImageSharp.Benchmarks/ImageSharp.Benchmarks.csproj

@ -23,7 +23,7 @@
<Choose>
<When Condition="$(SIXLABORS_TESTING_PREVIEW) == true">
<PropertyGroup>
<TargetFrameworks>net8.0</TargetFrameworks>
<TargetFrameworks>net8.0;net9.0</TargetFrameworks>
</PropertyGroup>
</When>
<Otherwise>
@ -41,8 +41,8 @@
<ItemGroup>
<PackageReference Include="Magick.NET-Q16-AnyCPU" />
<PackageReference Include="BenchmarkDotNet" Version="0.13.10" />
<PackageReference Include="BenchmarkDotNet.Diagnostics.Windows" Version="0.13.10" Condition="'$(IsWindows)'=='true'" />
<PackageReference Include="BenchmarkDotNet" Version="0.14.0" />
<PackageReference Include="BenchmarkDotNet.Diagnostics.Windows" Version="0.14.0" Condition="'$(IsWindows)'=='true'" />
<PackageReference Include="Colourful" />
<PackageReference Include="NetVips" />
<PackageReference Include="NetVips.Native" />

59
tests/ImageSharp.Benchmarks/LoadResizeSave/LoadResizeSaveStressRunner.cs

@ -19,6 +19,7 @@ using SystemDrawingImage = System.Drawing.Image;
namespace SixLabors.ImageSharp.Benchmarks.LoadResizeSave;
[Flags]
public enum JpegKind
{
Baseline = 1,
@ -30,7 +31,7 @@ public class LoadResizeSaveStressRunner
{
private const int Quality = 75;
// Set the quality for ImagSharp
// Set the quality for ImageSharp
private readonly JpegEncoder imageSharpJpegEncoder = new() { Quality = Quality };
private readonly ImageCodecInfo systemDrawingJpegCodec =
ImageCodecInfo.GetImageEncoders().First(codec => codec.FormatID == ImageFormat.Jpeg.Guid);
@ -126,7 +127,7 @@ public class LoadResizeSaveStressRunner
: Environment.ProcessorCount;
int partitionSize = (int)Math.Ceiling((double)this.Images.Length / maxDegreeOfParallelism);
List<Task> tasks = new();
List<Task> tasks = [];
for (int i = 0; i < this.Images.Length; i += partitionSize)
{
int end = Math.Min(i + partitionSize, this.Images.Length);
@ -176,13 +177,13 @@ public class LoadResizeSaveStressRunner
public void SystemDrawingResize(string input)
{
using var image = SystemDrawingImage.FromFile(input, true);
using SystemDrawingImage image = SystemDrawingImage.FromFile(input, true);
this.LogImageProcessed(image.Width, image.Height);
(int Width, int Height) scaled = this.ScaledSize(image.Width, image.Height, this.ThumbnailSize);
var resized = new Bitmap(scaled.Width, scaled.Height);
using var graphics = Graphics.FromImage(resized);
using var attributes = new ImageAttributes();
(int width, int height) = this.ScaledSize(image.Width, image.Height, this.ThumbnailSize);
Bitmap resized = new(width, height);
using Graphics graphics = Graphics.FromImage(resized);
using ImageAttributes attributes = new();
attributes.SetWrapMode(WrapMode.TileFlipXY);
graphics.PixelOffsetMode = PixelOffsetMode.HighQuality;
graphics.CompositingMode = CompositingMode.SourceCopy;
@ -191,8 +192,8 @@ public class LoadResizeSaveStressRunner
graphics.DrawImage(image, System.Drawing.Rectangle.FromLTRB(0, 0, resized.Width, resized.Height), 0, 0, image.Width, image.Height, GraphicsUnit.Pixel, attributes);
// Save the results
using var encoderParams = new EncoderParameters(1);
using var qualityParam = new EncoderParameter(Encoder.Quality, (long)Quality);
using EncoderParameters encoderParams = new(1);
using EncoderParameter qualityParam = new(Encoder.Quality, (long)Quality);
encoderParams.Param[0] = qualityParam;
resized.Save(this.OutputPath(input), this.systemDrawingJpegCodec, encoderParams);
}
@ -223,7 +224,7 @@ public class LoadResizeSaveStressRunner
public async Task ImageSharpResizeAsync(string input)
{
using FileStream output = File.Open(this.OutputPath(input), FileMode.Create);
await using FileStream output = File.Open(this.OutputPath(input), FileMode.Create);
// Resize it to fit a 150x150 square.
DecoderOptions options = new()
@ -246,7 +247,7 @@ public class LoadResizeSaveStressRunner
public void MagickResize(string input)
{
using var image = new MagickImage(input);
using MagickImage image = new(input);
this.LogImageProcessed(image.Width, image.Height);
// Resize it to fit a 150x150 square
@ -264,7 +265,7 @@ public class LoadResizeSaveStressRunner
public void MagicScalerResize(string input)
{
var settings = new ProcessImageSettings()
ProcessImageSettings settings = new()
{
Width = this.ThumbnailSize,
Height = this.ThumbnailSize,
@ -273,19 +274,19 @@ public class LoadResizeSaveStressRunner
};
// TODO: Is there a way to capture input dimensions for IncreaseTotalMegapixels?
using var output = new FileStream(this.OutputPath(input), FileMode.Create);
using FileStream output = new(this.OutputPath(input), FileMode.Create);
MagicImageProcessor.ProcessImage(input, output, settings);
}
public void SkiaCanvasResize(string input)
{
using var original = SKBitmap.Decode(input);
using SKBitmap original = SKBitmap.Decode(input);
this.LogImageProcessed(original.Width, original.Height);
(int Width, int Height) scaled = this.ScaledSize(original.Width, original.Height, this.ThumbnailSize);
using var surface = SKSurface.Create(new SKImageInfo(scaled.Width, scaled.Height, original.ColorType, original.AlphaType));
using var paint = new SKPaint() { FilterQuality = SKFilterQuality.High };
(int width, int height) = this.ScaledSize(original.Width, original.Height, this.ThumbnailSize);
using SKSurface surface = SKSurface.Create(new SKImageInfo(width, height, original.ColorType, original.AlphaType));
using SKPaint paint = new() { FilterQuality = SKFilterQuality.High };
SKCanvas canvas = surface.Canvas;
canvas.Scale((float)scaled.Width / original.Width);
canvas.Scale((float)width / original.Width);
canvas.DrawBitmap(original, 0, 0, paint);
canvas.Flush();
@ -297,16 +298,16 @@ public class LoadResizeSaveStressRunner
public void SkiaBitmapResize(string input)
{
using var original = SKBitmap.Decode(input);
using SKBitmap original = SKBitmap.Decode(input);
this.LogImageProcessed(original.Width, original.Height);
(int Width, int Height) scaled = this.ScaledSize(original.Width, original.Height, this.ThumbnailSize);
using var resized = original.Resize(new SKImageInfo(scaled.Width, scaled.Height), SKFilterQuality.High);
(int width, int height) = this.ScaledSize(original.Width, original.Height, this.ThumbnailSize);
using SKBitmap resized = original.Resize(new SKImageInfo(width, height), SKFilterQuality.High);
if (resized == null)
{
return;
}
using var image = SKImage.FromBitmap(resized);
using SKImage image = SKImage.FromBitmap(resized);
using FileStream output = File.OpenWrite(this.OutputPath(input));
image.Encode(SKEncodedImageFormat.Jpeg, Quality)
.SaveTo(output);
@ -314,21 +315,21 @@ public class LoadResizeSaveStressRunner
public void SkiaBitmapDecodeToTargetSize(string input)
{
using var codec = SKCodec.Create(input);
using SKCodec codec = SKCodec.Create(input);
SKImageInfo info = codec.Info;
this.LogImageProcessed(info.Width, info.Height);
(int Width, int Height) scaled = this.ScaledSize(info.Width, info.Height, this.ThumbnailSize);
SKSizeI supportedScale = codec.GetScaledDimensions((float)scaled.Width / info.Width);
(int width, int height) = this.ScaledSize(info.Width, info.Height, this.ThumbnailSize);
SKSizeI supportedScale = codec.GetScaledDimensions((float)width / info.Width);
using var original = SKBitmap.Decode(codec, new SKImageInfo(supportedScale.Width, supportedScale.Height));
using SKBitmap resized = original.Resize(new SKImageInfo(scaled.Width, scaled.Height), SKFilterQuality.High);
using SKBitmap original = SKBitmap.Decode(codec, new SKImageInfo(supportedScale.Width, supportedScale.Height));
using SKBitmap resized = original.Resize(new SKImageInfo(width, height), SKFilterQuality.High);
if (resized == null)
{
return;
}
using var image = SKImage.FromBitmap(resized);
using SKImage image = SKImage.FromBitmap(resized);
using FileStream output = File.OpenWrite(this.OutputPath(input, nameof(this.SkiaBitmapDecodeToTargetSize)));
image.Encode(SKEncodedImageFormat.Jpeg, Quality)
@ -338,7 +339,7 @@ public class LoadResizeSaveStressRunner
public void NetVipsResize(string input)
{
// Thumbnail to fit a 150x150 square
using var thumb = NetVipsImage.Thumbnail(input, this.ThumbnailSize, this.ThumbnailSize);
using NetVipsImage thumb = NetVipsImage.Thumbnail(input, this.ThumbnailSize, this.ThumbnailSize);
// Save the results
thumb.Jpegsave(this.OutputPath(input), q: Quality, keep: NetVips.Enums.ForeignKeep.None);

2
tests/ImageSharp.Tests.ProfilingSandbox/ImageSharp.Tests.ProfilingSandbox.csproj

@ -19,7 +19,7 @@
<Choose>
<When Condition="$(SIXLABORS_TESTING_PREVIEW) == true">
<PropertyGroup>
<TargetFrameworks>net8.0</TargetFrameworks>
<TargetFrameworks>net8.0;net9.0</TargetFrameworks>
</PropertyGroup>
</When>
<Otherwise>

2
tests/ImageSharp.Tests/Formats/Gif/GifEncoderTests.cs

@ -334,7 +334,7 @@ public class GifEncoderTests
Assert.Equal(webpF.FrameDelay, (uint)(gifF.FrameDelay * 10));
switch (webpF.DisposalMethod)
switch (webpF.DisposalMode)
{
case FrameDisposalMode.RestoreToBackground:
Assert.Equal(FrameDisposalMode.RestoreToBackground, gifF.DisposalMode);

4
tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs

@ -524,7 +524,7 @@ public partial class PngEncoderTests
Assert.Equal(webpF.FrameDelay, (uint)(pngF.FrameDelay.ToDouble() * 1000));
switch (webpF.BlendMethod)
switch (webpF.BlendMode)
{
case FrameBlendMode.Source:
Assert.Equal(FrameBlendMode.Source, pngF.BlendMode);
@ -535,7 +535,7 @@ public partial class PngEncoderTests
break;
}
switch (webpF.DisposalMethod)
switch (webpF.DisposalMode)
{
case FrameDisposalMode.RestoreToBackground:
Assert.Equal(FrameDisposalMode.RestoreToBackground, pngF.DisposalMode);

2
tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs

@ -719,7 +719,7 @@ public class TiffDecoderTests : TiffDecoderBaseTester
// ImageMagick cannot decode this image.
image.DebugSave(provider);
image.CompareToReferenceOutput(
ImageComparer.Exact,
ImageComparer.TolerantPercentage(0.0018F), // NET 9+ Uses zlib-ng to decompress, which manages to decode 2 extra pixels.
provider,
appendPixelTypeToFileName: false);
}

50
tests/ImageSharp.Tests/Formats/WebP/WebpEncoderTests.cs

@ -98,13 +98,13 @@ public class WebpEncoderTests
switch (gifF.DisposalMode)
{
case FrameDisposalMode.RestoreToBackground:
Assert.Equal(FrameDisposalMode.RestoreToBackground, webpF.DisposalMethod);
Assert.Equal(FrameDisposalMode.RestoreToBackground, webpF.DisposalMode);
break;
case FrameDisposalMode.RestoreToPrevious:
case FrameDisposalMode.Unspecified:
case FrameDisposalMode.DoNotDispose:
default:
Assert.Equal(FrameDisposalMode.DoNotDispose, webpF.DisposalMethod);
Assert.Equal(FrameDisposalMode.DoNotDispose, webpF.DisposalMode);
break;
}
}
@ -147,22 +147,22 @@ public class WebpEncoderTests
switch (pngF.BlendMode)
{
case FrameBlendMode.Source:
Assert.Equal(FrameBlendMode.Source, webpF.BlendMethod);
Assert.Equal(FrameBlendMode.Source, webpF.BlendMode);
break;
case FrameBlendMode.Over:
default:
Assert.Equal(FrameBlendMode.Over, webpF.BlendMethod);
Assert.Equal(FrameBlendMode.Over, webpF.BlendMode);
break;
}
switch (pngF.DisposalMode)
{
case FrameDisposalMode.RestoreToBackground:
Assert.Equal(FrameDisposalMode.RestoreToBackground, webpF.DisposalMethod);
Assert.Equal(FrameDisposalMode.RestoreToBackground, webpF.DisposalMode);
break;
case FrameDisposalMode.DoNotDispose:
default:
Assert.Equal(FrameDisposalMode.DoNotDispose, webpF.DisposalMethod);
Assert.Equal(FrameDisposalMode.DoNotDispose, webpF.DisposalMode);
break;
}
}
@ -546,6 +546,44 @@ public class WebpEncoderTests
[Fact]
public void RunEncodeLossy_WithPeakImage_WithoutHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunEncodeLossy_WithPeakImage, HwIntrinsics.DisableHWIntrinsic);
[Theory]
[WithFile(TestPatternOpaque, PixelTypes.Rgba32)]
public void CanSave_NonSeekableStream<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel>
{
using Image<TPixel> image = provider.GetImage();
WebpEncoder encoder = new();
using MemoryStream seekable = new();
image.Save(seekable, encoder);
using MemoryStream memoryStream = new();
using NonSeekableStream nonSeekable = new(memoryStream);
image.Save(nonSeekable, encoder);
Assert.True(seekable.ToArray().SequenceEqual(memoryStream.ToArray()));
}
[Theory]
[WithFile(TestPatternOpaque, PixelTypes.Rgba32)]
public async Task CanSave_NonSeekableStream_Async<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel>
{
using Image<TPixel> image = provider.GetImage();
WebpEncoder encoder = new();
await using MemoryStream seekable = new();
image.Save(seekable, encoder);
await using MemoryStream memoryStream = new();
await using NonSeekableStream nonSeekable = new(memoryStream);
await image.SaveAsync(nonSeekable, encoder);
Assert.True(seekable.ToArray().SequenceEqual(memoryStream.ToArray()));
}
private static ImageComparer GetComparer(int quality)
{
float tolerance = 0.01f; // ~1.0%

163
tests/ImageSharp.Tests/IO/ChunkedMemoryStreamTests.cs

@ -13,6 +13,8 @@ namespace SixLabors.ImageSharp.Tests.IO;
/// </summary>
public class ChunkedMemoryStreamTests
{
private readonly Random bufferFiller = new(123);
/// <summary>
/// The default length in bytes of each buffer chunk when allocating large buffers.
/// </summary>
@ -30,7 +32,7 @@ public class ChunkedMemoryStreamTests
[Fact]
public void MemoryStream_GetPositionTest_Negative()
{
using var ms = new ChunkedMemoryStream(this.allocator);
using ChunkedMemoryStream ms = new(this.allocator);
long iCurrentPos = ms.Position;
for (int i = -1; i > -6; i--)
{
@ -42,7 +44,7 @@ public class ChunkedMemoryStreamTests
[Fact]
public void MemoryStream_ReadTest_Negative()
{
var ms2 = new ChunkedMemoryStream(this.allocator);
ChunkedMemoryStream ms2 = new(this.allocator);
Assert.Throws<ArgumentNullException>(() => ms2.Read(null, 0, 0));
Assert.Throws<ArgumentOutOfRangeException>(() => ms2.Read(new byte[] { 1 }, -1, 0));
@ -64,7 +66,7 @@ public class ChunkedMemoryStreamTests
public void MemoryStream_ReadByteTest(int length)
{
using MemoryStream ms = this.CreateTestStream(length);
using var cms = new ChunkedMemoryStream(this.allocator);
using ChunkedMemoryStream cms = new(this.allocator);
ms.CopyTo(cms);
cms.Position = 0;
@ -85,7 +87,7 @@ public class ChunkedMemoryStreamTests
public void MemoryStream_ReadByteBufferTest(int length)
{
using MemoryStream ms = this.CreateTestStream(length);
using var cms = new ChunkedMemoryStream(this.allocator);
using ChunkedMemoryStream cms = new(this.allocator);
ms.CopyTo(cms);
cms.Position = 0;
@ -105,10 +107,11 @@ public class ChunkedMemoryStreamTests
[InlineData(DefaultSmallChunkSize * 4)]
[InlineData((int)(DefaultSmallChunkSize * 5.5))]
[InlineData(DefaultSmallChunkSize * 16)]
[InlineData(DefaultSmallChunkSize * 32)]
public void MemoryStream_ReadByteBufferSpanTest(int length)
{
using MemoryStream ms = this.CreateTestStream(length);
using var cms = new ChunkedMemoryStream(this.allocator);
using ChunkedMemoryStream cms = new(this.allocator);
ms.CopyTo(cms);
cms.Position = 0;
@ -122,18 +125,24 @@ public class ChunkedMemoryStreamTests
}
}
[Fact]
public void MemoryStream_WriteToTests()
[Theory]
[InlineData(DefaultSmallChunkSize)]
[InlineData((int)(DefaultSmallChunkSize * 1.5))]
[InlineData(DefaultSmallChunkSize * 4)]
[InlineData((int)(DefaultSmallChunkSize * 5.5))]
[InlineData(DefaultSmallChunkSize * 16)]
[InlineData(DefaultSmallChunkSize * 32)]
public void MemoryStream_WriteToTests(int length)
{
using (var ms2 = new ChunkedMemoryStream(this.allocator))
using (ChunkedMemoryStream ms2 = new(this.allocator))
{
byte[] bytArrRet;
byte[] bytArr = new byte[] { byte.MinValue, byte.MaxValue, 1, 2, 3, 4, 5, 6, 128, 250 };
byte[] bytArr = this.CreateTestBuffer(length);
// [] Write to memoryStream, check the memoryStream
ms2.Write(bytArr, 0, bytArr.Length);
using var readonlyStream = new ChunkedMemoryStream(this.allocator);
using ChunkedMemoryStream readonlyStream = new(this.allocator);
ms2.WriteTo(readonlyStream);
readonlyStream.Flush();
readonlyStream.Position = 0;
@ -146,11 +155,11 @@ public class ChunkedMemoryStreamTests
}
// [] Write to memoryStream, check the memoryStream
using (var ms2 = new ChunkedMemoryStream(this.allocator))
using (var ms3 = new ChunkedMemoryStream(this.allocator))
using (ChunkedMemoryStream ms2 = new(this.allocator))
using (ChunkedMemoryStream ms3 = new(this.allocator))
{
byte[] bytArrRet;
byte[] bytArr = new byte[] { byte.MinValue, byte.MaxValue, 1, 2, 3, 4, 5, 6, 128, 250 };
byte[] bytArr = this.CreateTestBuffer(length);
ms2.Write(bytArr, 0, bytArr.Length);
ms2.WriteTo(ms3);
@ -164,21 +173,29 @@ public class ChunkedMemoryStreamTests
}
}
[Fact]
public void MemoryStream_WriteToSpanTests()
[Theory]
[InlineData(DefaultSmallChunkSize)]
[InlineData((int)(DefaultSmallChunkSize * 1.5))]
[InlineData(DefaultSmallChunkSize * 4)]
[InlineData((int)(DefaultSmallChunkSize * 5.5))]
[InlineData(DefaultSmallChunkSize * 16)]
[InlineData(DefaultSmallChunkSize * 32)]
public void MemoryStream_WriteToSpanTests(int length)
{
using (var ms2 = new ChunkedMemoryStream(this.allocator))
using (ChunkedMemoryStream ms2 = new(this.allocator))
{
Span<byte> bytArrRet;
Span<byte> bytArr = new byte[] { byte.MinValue, byte.MaxValue, 1, 2, 3, 4, 5, 6, 128, 250 };
Span<byte> bytArr = this.CreateTestBuffer(length);
// [] Write to memoryStream, check the memoryStream
ms2.Write(bytArr, 0, bytArr.Length);
using var readonlyStream = new ChunkedMemoryStream(this.allocator);
using ChunkedMemoryStream readonlyStream = new(this.allocator);
ms2.WriteTo(readonlyStream);
readonlyStream.Flush();
readonlyStream.Position = 0;
bytArrRet = new byte[(int)readonlyStream.Length];
readonlyStream.Read(bytArrRet, 0, (int)readonlyStream.Length);
for (int i = 0; i < bytArr.Length; i++)
@ -188,13 +205,14 @@ public class ChunkedMemoryStreamTests
}
// [] Write to memoryStream, check the memoryStream
using (var ms2 = new ChunkedMemoryStream(this.allocator))
using (var ms3 = new ChunkedMemoryStream(this.allocator))
using (ChunkedMemoryStream ms2 = new(this.allocator))
using (ChunkedMemoryStream ms3 = new(this.allocator))
{
Span<byte> bytArrRet;
Span<byte> bytArr = new byte[] { byte.MinValue, byte.MaxValue, 1, 2, 3, 4, 5, 6, 128, 250 };
Span<byte> bytArr = this.CreateTestBuffer(length);
ms2.Write(bytArr, 0, bytArr.Length);
ms2.WriteTo(ms3);
ms3.Position = 0;
bytArrRet = new byte[(int)ms3.Length];
@ -209,37 +227,35 @@ public class ChunkedMemoryStreamTests
[Fact]
public void MemoryStream_WriteByteTests()
{
using (var ms2 = new ChunkedMemoryStream(this.allocator))
{
byte[] bytArrRet;
byte[] bytArr = new byte[] { byte.MinValue, byte.MaxValue, 1, 2, 3, 4, 5, 6, 128, 250 };
using ChunkedMemoryStream ms2 = new(this.allocator);
byte[] bytArrRet;
byte[] bytArr = new byte[] { byte.MinValue, byte.MaxValue, 1, 2, 3, 4, 5, 6, 128, 250 };
for (int i = 0; i < bytArr.Length; i++)
{
ms2.WriteByte(bytArr[i]);
}
for (int i = 0; i < bytArr.Length; i++)
{
ms2.WriteByte(bytArr[i]);
}
using var readonlyStream = new ChunkedMemoryStream(this.allocator);
ms2.WriteTo(readonlyStream);
readonlyStream.Flush();
readonlyStream.Position = 0;
bytArrRet = new byte[(int)readonlyStream.Length];
readonlyStream.Read(bytArrRet, 0, (int)readonlyStream.Length);
for (int i = 0; i < bytArr.Length; i++)
{
Assert.Equal(bytArr[i], bytArrRet[i]);
}
using ChunkedMemoryStream readonlyStream = new(this.allocator);
ms2.WriteTo(readonlyStream);
readonlyStream.Flush();
readonlyStream.Position = 0;
bytArrRet = new byte[(int)readonlyStream.Length];
readonlyStream.Read(bytArrRet, 0, (int)readonlyStream.Length);
for (int i = 0; i < bytArr.Length; i++)
{
Assert.Equal(bytArr[i], bytArrRet[i]);
}
}
[Fact]
public void MemoryStream_WriteToTests_Negative()
{
using var ms2 = new ChunkedMemoryStream(this.allocator);
using ChunkedMemoryStream ms2 = new(this.allocator);
Assert.Throws<ArgumentNullException>(() => ms2.WriteTo(null));
ms2.Write(new byte[] { 1 }, 0, 1);
var readonlyStream = new MemoryStream(new byte[1028], false);
MemoryStream readonlyStream = new(new byte[1028], false);
Assert.Throws<NotSupportedException>(() => ms2.WriteTo(readonlyStream));
readonlyStream.Dispose();
@ -286,7 +302,7 @@ public class ChunkedMemoryStreamTests
[MemberData(nameof(CopyToData))]
public void CopyTo(Stream source, byte[] expected)
{
using var destination = new ChunkedMemoryStream(this.allocator);
using ChunkedMemoryStream destination = new(this.allocator);
source.CopyTo(destination);
Assert.InRange(source.Position, source.Length, int.MaxValue); // Copying the data should have read to the end of the stream or stayed past the end.
Assert.Equal(expected, destination.ToArray());
@ -297,16 +313,16 @@ public class ChunkedMemoryStreamTests
IEnumerable<string> allImageFiles = Directory.EnumerateFiles(TestEnvironment.InputImagesDirectoryFullPath, "*.*", SearchOption.AllDirectories)
.Where(s => !s.EndsWith("txt", StringComparison.OrdinalIgnoreCase));
var result = new List<string>();
List<string> result = new();
foreach (string path in allImageFiles)
{
result.Add(path.Substring(TestEnvironment.InputImagesDirectoryFullPath.Length));
result.Add(path[TestEnvironment.InputImagesDirectoryFullPath.Length..]);
}
return result;
}
public static IEnumerable<string> AllTestImages = GetAllTestImages();
public static IEnumerable<string> AllTestImages { get; } = GetAllTestImages();
[Theory]
[WithFileCollection(nameof(AllTestImages), PixelTypes.Rgba32)]
@ -334,40 +350,77 @@ public class ChunkedMemoryStreamTests
((TestImageProvider<TPixel>.FileProvider)provider).FilePath);
using FileStream fs = File.OpenRead(fullPath);
using var nonSeekableStream = new NonSeekableStream(fs);
using NonSeekableStream nonSeekableStream = new(fs);
using Image<TPixel> actual = Image.Load<TPixel>(nonSeekableStream);
ImageComparer.Exact.VerifySimilarity(expected, actual);
expected.Dispose();
}
[Theory]
[WithFileCollection(nameof(AllTestImages), PixelTypes.Rgba32)]
public void EncoderIntegrationTest<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel>
{
if (!TestEnvironment.Is64BitProcess)
{
return;
}
Image<TPixel> expected;
try
{
expected = provider.GetImage();
}
catch
{
// The image is invalid
return;
}
var actual = Image.Load<TPixel>(nonSeekableStream);
string fullPath = Path.Combine(
TestEnvironment.InputImagesDirectoryFullPath,
((TestImageProvider<TPixel>.FileProvider)provider).FilePath);
using MemoryStream ms = new();
using NonSeekableStream nonSeekableStream = new(ms);
expected.SaveAsWebp(nonSeekableStream);
using Image<TPixel> actual = Image.Load<TPixel>(nonSeekableStream);
ImageComparer.Exact.VerifySimilarity(expected, actual);
expected.Dispose();
}
public static IEnumerable<object[]> CopyToData()
{
// Stream is positioned @ beginning of data
byte[] data1 = new byte[] { 1, 2, 3 };
var stream1 = new MemoryStream(data1);
MemoryStream stream1 = new(data1);
yield return new object[] { stream1, data1 };
// Stream is positioned in the middle of data
byte[] data2 = new byte[] { 0xff, 0xf3, 0xf0 };
var stream2 = new MemoryStream(data2) { Position = 1 };
MemoryStream stream2 = new(data2) { Position = 1 };
yield return new object[] { stream2, new byte[] { 0xf3, 0xf0 } };
// Stream is positioned after end of data
byte[] data3 = data2;
var stream3 = new MemoryStream(data3) { Position = data3.Length + 1 };
MemoryStream stream3 = new(data3) { Position = data3.Length + 1 };
yield return new object[] { stream3, Array.Empty<byte>() };
}
private MemoryStream CreateTestStream(int length)
private byte[] CreateTestBuffer(int length)
{
byte[] buffer = new byte[length];
var random = new Random();
random.NextBytes(buffer);
return new MemoryStream(buffer);
this.bufferFiller.NextBytes(buffer);
return buffer;
}
private MemoryStream CreateTestStream(int length)
=> new(this.CreateTestBuffer(length));
}

6
tests/ImageSharp.Tests/Image/NonSeekableStream.cs

@ -1,4 +1,4 @@
// Copyright (c) Six Labors.
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
namespace SixLabors.ImageSharp.Tests;
@ -14,7 +14,7 @@ internal class NonSeekableStream : Stream
public override bool CanSeek => false;
public override bool CanWrite => false;
public override bool CanWrite => this.dataStream.CanWrite;
public override bool CanTimeout => this.dataStream.CanTimeout;
@ -91,5 +91,5 @@ internal class NonSeekableStream : Stream
=> throw new NotSupportedException();
public override void Write(byte[] buffer, int offset, int count)
=> throw new NotImplementedException();
=> this.dataStream.Write(buffer, offset, count);
}

2
tests/ImageSharp.Tests/ImageSharp.Tests.csproj

@ -12,7 +12,7 @@
<Choose>
<When Condition="$(SIXLABORS_TESTING_PREVIEW) == true">
<PropertyGroup>
<TargetFrameworks>net8.0</TargetFrameworks>
<TargetFrameworks>net8.0;net9.0</TargetFrameworks>
</PropertyGroup>
</When>
<Otherwise>

12
tests/ImageSharp.Tests/Processing/Convolution/DetectEdgesTest.cs

@ -59,7 +59,7 @@ public class DetectEdgesTest : BaseImageOperationsExtensionTest
[MemberData(nameof(EdgeDetector2DKernelData))]
public void DetectEdges_Rect_EdgeDetector2DProcessor_DefaultGrayScale_Set(EdgeDetector2DKernel kernel, bool _)
{
this.operations.DetectEdges(kernel, this.rect);
this.operations.DetectEdges(this.rect, kernel);
EdgeDetector2DProcessor processor = this.Verify<EdgeDetector2DProcessor>(this.rect);
Assert.True(processor.Grayscale);
@ -81,7 +81,7 @@ public class DetectEdgesTest : BaseImageOperationsExtensionTest
[MemberData(nameof(EdgeDetector2DKernelData))]
public void DetectEdges_Rect_EdgeDetector2DProcessorSet(EdgeDetector2DKernel kernel, bool grayscale)
{
this.operations.DetectEdges(kernel, grayscale, this.rect);
this.operations.DetectEdges(this.rect, kernel, grayscale);
EdgeDetector2DProcessor processor = this.Verify<EdgeDetector2DProcessor>(this.rect);
Assert.Equal(grayscale, processor.Grayscale);
@ -114,7 +114,7 @@ public class DetectEdgesTest : BaseImageOperationsExtensionTest
[MemberData(nameof(EdgeDetectorKernelData))]
public void DetectEdges_Rect_EdgeDetectorProcessor_DefaultGrayScale_Set(EdgeDetectorKernel kernel, bool _)
{
this.operations.DetectEdges(kernel, this.rect);
this.operations.DetectEdges(this.rect, kernel);
EdgeDetectorProcessor processor = this.Verify<EdgeDetectorProcessor>(this.rect);
Assert.True(processor.Grayscale);
@ -136,7 +136,7 @@ public class DetectEdgesTest : BaseImageOperationsExtensionTest
[MemberData(nameof(EdgeDetectorKernelData))]
public void DetectEdges_Rect_EdgeDetectorProcessorSet(EdgeDetectorKernel kernel, bool grayscale)
{
this.operations.DetectEdges(kernel, grayscale, this.rect);
this.operations.DetectEdges(this.rect, kernel, grayscale);
EdgeDetectorProcessor processor = this.Verify<EdgeDetectorProcessor>(this.rect);
Assert.Equal(grayscale, processor.Grayscale);
@ -167,7 +167,7 @@ public class DetectEdgesTest : BaseImageOperationsExtensionTest
[MemberData(nameof(EdgeDetectorCompassKernelData))]
public void DetectEdges_Rect_EdgeDetectorCompassProcessor_DefaultGrayScale_Set(EdgeDetectorCompassKernel kernel, bool _)
{
this.operations.DetectEdges(kernel, this.rect);
this.operations.DetectEdges(this.rect, kernel);
EdgeDetectorCompassProcessor processor = this.Verify<EdgeDetectorCompassProcessor>(this.rect);
Assert.True(processor.Grayscale);
@ -189,7 +189,7 @@ public class DetectEdgesTest : BaseImageOperationsExtensionTest
[MemberData(nameof(EdgeDetectorCompassKernelData))]
public void DetectEdges_Rect_EdgeDetectorCompassProcessorSet(EdgeDetectorCompassKernel kernel, bool grayscale)
{
this.operations.DetectEdges(kernel, grayscale, this.rect);
this.operations.DetectEdges(this.rect, kernel, grayscale);
EdgeDetectorCompassProcessor processor = this.Verify<EdgeDetectorCompassProcessor>(this.rect);
Assert.Equal(grayscale, processor.Grayscale);

8
tests/ImageSharp.Tests/Processing/Convolution/GaussianBlurTest.cs

@ -13,7 +13,7 @@ public class GaussianBlurTest : BaseImageOperationsExtensionTest
public void GaussianBlur_GaussianBlurProcessorDefaultsSet()
{
this.operations.GaussianBlur();
var processor = this.Verify<GaussianBlurProcessor>();
GaussianBlurProcessor processor = this.Verify<GaussianBlurProcessor>();
Assert.Equal(3f, processor.Sigma);
}
@ -22,7 +22,7 @@ public class GaussianBlurTest : BaseImageOperationsExtensionTest
public void GaussianBlur_amount_GaussianBlurProcessorDefaultsSet()
{
this.operations.GaussianBlur(0.2f);
var processor = this.Verify<GaussianBlurProcessor>();
GaussianBlurProcessor processor = this.Verify<GaussianBlurProcessor>();
Assert.Equal(.2f, processor.Sigma);
}
@ -30,8 +30,8 @@ public class GaussianBlurTest : BaseImageOperationsExtensionTest
[Fact]
public void GaussianBlur_amount_rect_GaussianBlurProcessorDefaultsSet()
{
this.operations.GaussianBlur(0.6f, this.rect);
var processor = this.Verify<GaussianBlurProcessor>(this.rect);
this.operations.GaussianBlur(this.rect, 0.6f);
GaussianBlurProcessor processor = this.Verify<GaussianBlurProcessor>(this.rect);
Assert.Equal(.6f, processor.Sigma);
}

8
tests/ImageSharp.Tests/Processing/Convolution/GaussianSharpenTest.cs

@ -13,7 +13,7 @@ public class GaussianSharpenTest : BaseImageOperationsExtensionTest
public void GaussianSharpen_GaussianSharpenProcessorDefaultsSet()
{
this.operations.GaussianSharpen();
var processor = this.Verify<GaussianSharpenProcessor>();
GaussianSharpenProcessor processor = this.Verify<GaussianSharpenProcessor>();
Assert.Equal(3f, processor.Sigma);
}
@ -22,7 +22,7 @@ public class GaussianSharpenTest : BaseImageOperationsExtensionTest
public void GaussianSharpen_amount_GaussianSharpenProcessorDefaultsSet()
{
this.operations.GaussianSharpen(0.2f);
var processor = this.Verify<GaussianSharpenProcessor>();
GaussianSharpenProcessor processor = this.Verify<GaussianSharpenProcessor>();
Assert.Equal(.2f, processor.Sigma);
}
@ -30,8 +30,8 @@ public class GaussianSharpenTest : BaseImageOperationsExtensionTest
[Fact]
public void GaussianSharpen_amount_rect_GaussianSharpenProcessorDefaultsSet()
{
this.operations.GaussianSharpen(0.6f, this.rect);
var processor = this.Verify<GaussianSharpenProcessor>(this.rect);
this.operations.GaussianSharpen(this.rect, 0.6f);
GaussianSharpenProcessor processor = this.Verify<GaussianSharpenProcessor>(this.rect);
Assert.Equal(.6f, processor.Sigma);
}

6
tests/ImageSharp.Tests/Processing/Convolution/MedianBlurTest.cs

@ -13,7 +13,7 @@ public class MedianBlurTest : BaseImageOperationsExtensionTest
public void Median_radius_MedianProcessorDefaultsSet()
{
this.operations.MedianBlur(3, true);
var processor = this.Verify<MedianBlurProcessor>();
MedianBlurProcessor processor = this.Verify<MedianBlurProcessor>();
Assert.Equal(3, processor.Radius);
Assert.True(processor.PreserveAlpha);
@ -22,8 +22,8 @@ public class MedianBlurTest : BaseImageOperationsExtensionTest
[Fact]
public void Median_radius_rect_MedianProcessorDefaultsSet()
{
this.operations.MedianBlur(5, false, this.rect);
var processor = this.Verify<MedianBlurProcessor>(this.rect);
this.operations.MedianBlur(this.rect, 5, false);
MedianBlurProcessor processor = this.Verify<MedianBlurProcessor>(this.rect);
Assert.Equal(5, processor.Radius);
Assert.False(processor.PreserveAlpha);

42
tests/ImageSharp.Tests/Processing/Normalization/MagickCompareTests.cs

@ -1,13 +1,12 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using ImageMagick;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing;
using SixLabors.ImageSharp.Processing.Processors.Normalization;
using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison;
using ImageMagick;
namespace SixLabors.ImageSharp.Tests.Processing.Normalization;
// ReSharper disable InconsistentNaming
@ -22,46 +21,43 @@ public class MagickCompareTests
Image<TPixel> imageFromMagick;
using (Stream stream = LoadAsStream(provider))
{
var magickImage = new MagickImage(stream);
using MagickImage magickImage = new(stream);
// Apply Auto Level using the Grey (BT.709) channel.
magickImage.AutoLevel(Channels.Gray);
imageFromMagick = ConvertImageFromMagick<TPixel>(magickImage);
}
using (Image<TPixel> image = provider.GetImage())
using Image<TPixel> image = provider.GetImage();
HistogramEqualizationOptions options = new()
{
var options = new HistogramEqualizationOptions
{
Method = HistogramEqualizationMethod.AutoLevel,
LuminanceLevels = 256,
SyncChannels = true
};
image.Mutate(x => x.HistogramEqualization(options));
image.DebugSave(provider);
ExactImageComparer.Instance.CompareImages(imageFromMagick, image);
}
Method = HistogramEqualizationMethod.AutoLevel,
LuminanceLevels = 256,
SyncChannels = true
};
image.Mutate(x => x.HistogramEqualization(options));
image.DebugSave(provider);
ExactImageComparer.Instance.CompareImages(imageFromMagick, image);
imageFromMagick.Dispose();
}
private Stream LoadAsStream<TPixel>(TestImageProvider<TPixel> provider)
private static FileStream LoadAsStream<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, ImageSharp.PixelFormats.IPixel<TPixel>
{
string path = TestImageProvider<TPixel>.GetFilePathOrNull(provider);
if (path == null)
{
throw new InvalidOperationException("CompareToMagick() works only with file providers!");
}
string path = TestImageProvider<TPixel>.GetFilePathOrNull(provider)
?? throw new InvalidOperationException("CompareToMagick() works only with file providers!");
var testFile = TestFile.Create(path);
TestFile testFile = TestFile.Create(path);
return new FileStream(testFile.FullPath, FileMode.Open);
}
private Image<TPixel> ConvertImageFromMagick<TPixel>(MagickImage magickImage)
private static Image<TPixel> ConvertImageFromMagick<TPixel>(MagickImage magickImage)
where TPixel : unmanaged, ImageSharp.PixelFormats.IPixel<TPixel>
{
Configuration configuration = Configuration.Default.Clone();
configuration.PreferContiguousImageBuffers = true;
var result = new Image<TPixel>(configuration, magickImage.Width, magickImage.Height);
Image<TPixel> result = new(configuration, (int)magickImage.Width, (int)magickImage.Height);
Assert.True(result.DangerousTryGetSinglePixelMemory(out Memory<TPixel> resultPixels));

3
tests/ImageSharp.Tests/Processing/Processors/Convolution/BokehBlurTest.cs

@ -3,7 +3,6 @@
using System.Globalization;
using System.Text.RegularExpressions;
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing;
using SixLabors.ImageSharp.Processing.Processors.Convolution;
@ -165,7 +164,7 @@ public class BokehBlurTest
{
Size size = x.GetCurrentSize();
Rectangle bounds = new(10, 10, size.Width / 2, size.Height / 2);
x.BokehBlur(value.Radius, value.Components, value.Gamma, bounds);
x.BokehBlur(bounds, value.Radius, value.Components, value.Gamma);
},
testOutputDetails: value.ToString(),
ImageComparer.TolerantPercentage(0.05f),

50
tests/ImageSharp.Tests/Processing/Processors/Convolution/ConvolutionTests.cs

@ -0,0 +1,50 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing.Extensions.Convolution;
using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison;
namespace SixLabors.ImageSharp.Tests.Processing.Processors.Convolution;
[GroupOutput("Convolution")]
public class ConvolutionTests
{
private static readonly ImageComparer ValidatorComparer = ImageComparer.TolerantPercentage(0.05F);
public static readonly TheoryData<DenseMatrix<float>> Values = new TheoryData<DenseMatrix<float>>
{
// Sharpening kernel.
new float[,]
{
{ -1, -1, -1 },
{ -1, 16, -1 },
{ -1, -1, -1 }
}
};
public static readonly string[] InputImages =
[
TestImages.Bmp.Car,
TestImages.Png.CalliphoraPartial,
TestImages.Png.Blur
];
[Theory]
[WithFileCollection(nameof(InputImages), nameof(Values), PixelTypes.Rgba32)]
public void OnFullImage<TPixel>(TestImageProvider<TPixel> provider, DenseMatrix<float> value)
where TPixel : unmanaged, IPixel<TPixel>
=> provider.RunValidatingProcessorTest(
x => x.Convolve(value),
string.Join('_', value.Data),
ValidatorComparer);
[Theory]
[WithFileCollection(nameof(InputImages), nameof(Values), PixelTypes.Rgba32)]
public void InBox<TPixel>(TestImageProvider<TPixel> provider, DenseMatrix<float> value)
where TPixel : unmanaged, IPixel<TPixel>
=> provider.RunRectangleConstrainedValidatingProcessorTest(
(x, rect) => x.Convolve(rect, value),
string.Join('_', value.Data),
ValidatorComparer);
}

2
tests/ImageSharp.Tests/Processing/Processors/Convolution/GaussianBlurTest.cs

@ -12,5 +12,5 @@ public class GaussianBlurTest : Basic1ParameterConvolutionTests
protected override void Apply(IImageProcessingContext ctx, int value) => ctx.GaussianBlur(value);
protected override void Apply(IImageProcessingContext ctx, int value, Rectangle bounds) =>
ctx.GaussianBlur(value, bounds);
ctx.GaussianBlur(bounds, value);
}

2
tests/ImageSharp.Tests/Processing/Processors/Convolution/GaussianSharpenTest.cs

@ -12,5 +12,5 @@ public class GaussianSharpenTest : Basic1ParameterConvolutionTests
protected override void Apply(IImageProcessingContext ctx, int value) => ctx.GaussianSharpen(value);
protected override void Apply(IImageProcessingContext ctx, int value, Rectangle bounds) =>
ctx.GaussianSharpen(value, bounds);
ctx.GaussianSharpen(bounds, value);
}

2
tests/ImageSharp.Tests/Processing/Processors/Convolution/MedianBlurTest.cs

@ -12,5 +12,5 @@ public class MedianBlurTest : Basic1ParameterConvolutionTests
protected override void Apply(IImageProcessingContext ctx, int value) => ctx.MedianBlur(value, true);
protected override void Apply(IImageProcessingContext ctx, int value, Rectangle bounds) =>
ctx.MedianBlur(value, true, bounds);
ctx.MedianBlur(bounds, value, true);
}

14
tests/ImageSharp.Tests/TestUtilities/FeatureTesting/FeatureTestRunner.cs

@ -104,7 +104,7 @@ public static class FeatureTestRunner
ProcessStartInfo processStartInfo = new();
if (intrinsic.Key != HwIntrinsics.AllowAll)
{
processStartInfo.Environment[$"COMPlus_{intrinsic.Value}"] = "0";
processStartInfo.Environment[$"DOTNET_{intrinsic.Value}"] = "0";
RemoteExecutor.Invoke(
action,
@ -148,7 +148,7 @@ public static class FeatureTestRunner
ProcessStartInfo processStartInfo = new();
if (intrinsic.Key != HwIntrinsics.AllowAll)
{
processStartInfo.Environment[$"COMPlus_{intrinsic.Value}"] = "0";
processStartInfo.Environment[$"DOTNET_{intrinsic.Value}"] = "0";
RemoteExecutor.Invoke(
action,
@ -192,7 +192,7 @@ public static class FeatureTestRunner
ProcessStartInfo processStartInfo = new();
if (intrinsic.Key != HwIntrinsics.AllowAll)
{
processStartInfo.Environment[$"COMPlus_{intrinsic.Value}"] = "0";
processStartInfo.Environment[$"DOTNET_{intrinsic.Value}"] = "0";
RemoteExecutor.Invoke(
action,
@ -241,7 +241,7 @@ public static class FeatureTestRunner
ProcessStartInfo processStartInfo = new();
if (intrinsic.Key != HwIntrinsics.AllowAll)
{
processStartInfo.Environment[$"COMPlus_{intrinsic.Value}"] = "0";
processStartInfo.Environment[$"DOTNET_{intrinsic.Value}"] = "0";
RemoteExecutor.Invoke(
action,
@ -288,7 +288,7 @@ public static class FeatureTestRunner
ProcessStartInfo processStartInfo = new();
if (intrinsic.Key != HwIntrinsics.AllowAll)
{
processStartInfo.Environment[$"COMPlus_{intrinsic.Value}"] = "0";
processStartInfo.Environment[$"DOTNET_{intrinsic.Value}"] = "0";
RemoteExecutor.Invoke(
action,
@ -333,7 +333,7 @@ public static class FeatureTestRunner
ProcessStartInfo processStartInfo = new();
if (intrinsic.Key != HwIntrinsics.AllowAll)
{
processStartInfo.Environment[$"COMPlus_{intrinsic.Value}"] = "0";
processStartInfo.Environment[$"DOTNET_{intrinsic.Value}"] = "0";
RemoteExecutor.Invoke(
action,
@ -379,7 +379,7 @@ public static class FeatureTestRunner
ProcessStartInfo processStartInfo = new();
if (intrinsic.Key != HwIntrinsics.AllowAll)
{
processStartInfo.Environment[$"COMPlus_{intrinsic.Value}"] = "0";
processStartInfo.Environment[$"DOTNET_{intrinsic.Value}"] = "0";
RemoteExecutor.Invoke(
action,

39
tests/ImageSharp.Tests/TestUtilities/ImageComparison/ImageComparingUtils.cs

@ -15,13 +15,10 @@ public static class ImageComparingUtils
float compareTolerance = 0.01f)
where TPixel : unmanaged, ImageSharp.PixelFormats.IPixel<TPixel>
{
string path = TestImageProvider<TPixel>.GetFilePathOrNull(provider);
if (path == null)
{
throw new InvalidOperationException("CompareToOriginal() works only with file providers!");
}
string path = TestImageProvider<TPixel>.GetFilePathOrNull(provider)
?? throw new InvalidOperationException("CompareToOriginal() works only with file providers!");
var testFile = TestFile.Create(path);
TestFile testFile = TestFile.Create(path);
using Image<Rgba32> magickImage = DecodeWithMagick<Rgba32>(new FileInfo(testFile.FullPath));
if (useExactComparer)
{
@ -38,25 +35,23 @@ public static class ImageComparingUtils
{
Configuration configuration = Configuration.Default.Clone();
configuration.PreferContiguousImageBuffers = true;
using (var magickImage = new MagickImage(fileInfo))
{
magickImage.AutoOrient();
var result = new Image<TPixel>(configuration, magickImage.Width, magickImage.Height);
using MagickImage magickImage = new(fileInfo);
magickImage.AutoOrient();
Image<TPixel> result = new(configuration, (int)magickImage.Width, (int)magickImage.Height);
Assert.True(result.DangerousTryGetSinglePixelMemory(out Memory<TPixel> resultPixels));
Assert.True(result.DangerousTryGetSinglePixelMemory(out Memory<TPixel> resultPixels));
using (IUnsafePixelCollection<ushort> pixels = magickImage.GetPixelsUnsafe())
{
byte[] data = pixels.ToByteArray(PixelMapping.RGBA);
PixelOperations<TPixel>.Instance.FromRgba32Bytes(
configuration,
data,
resultPixels.Span,
resultPixels.Length);
}
using (IUnsafePixelCollection<ushort> pixels = magickImage.GetPixelsUnsafe())
{
byte[] data = pixels.ToByteArray(PixelMapping.RGBA);
return result;
PixelOperations<TPixel>.Instance.FromRgba32Bytes(
configuration,
data,
resultPixels.Span,
resultPixels.Length);
}
return result;
}
}

2
tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/MagickReferenceDecoder.cs

@ -67,7 +67,7 @@ public class MagickReferenceDecoder : ImageDecoder
List<ImageFrame<TPixel>> framesList = [];
foreach (IMagickImage<ushort> magicFrame in magickImageCollection)
{
ImageFrame<TPixel> frame = new(configuration, magicFrame.Width, magicFrame.Height);
ImageFrame<TPixel> frame = new(configuration, (int)magicFrame.Width, (int)magicFrame.Height);
framesList.Add(frame);
MemoryGroup<TPixel> framePixels = frame.PixelBuffer.FastMemoryGroup;

3
tests/Images/External/ReferenceOutput/Convolution/ConvolutionTests/InBox_Rgba32_CalliphoraPartial_-1_-1_-1_-1_16_-1_-1_-1_-1.png

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:b311f117167e17b4611d525aef57146a6757db08f2a36fa093f45f237df9e1a2
size 326809

3
tests/Images/External/ReferenceOutput/Convolution/ConvolutionTests/InBox_Rgba32_Car_-1_-1_-1_-1_16_-1_-1_-1_-1.png

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:e0eabd5f7dd2f258d04d3f1db8ee4d958754541e0009ae183d6e512f408e3201
size 249497

3
tests/Images/External/ReferenceOutput/Convolution/ConvolutionTests/InBox_Rgba32_blur_-1_-1_-1_-1_16_-1_-1_-1_-1.png

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:048b4a1679852d34d6f74f01b750461ec9db2d3e7e3aa3d2d89d48e5725aa560
size 142367

3
tests/Images/External/ReferenceOutput/Convolution/ConvolutionTests/OnFullImage_Rgba32_CalliphoraPartial_-1_-1_-1_-1_16_-1_-1_-1_-1.png

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:a6f63d117433a93e30b9b41f68f676bb53eb1761f3c665f178ef07ec2c9626c3
size 338527

3
tests/Images/External/ReferenceOutput/Convolution/ConvolutionTests/OnFullImage_Rgba32_Car_-1_-1_-1_-1_16_-1_-1_-1_-1.png

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:d72a3994fc6dcc461da3af5176d5bf62c95b7abeffcbf5547172377db3b0d9ac
size 287158

3
tests/Images/External/ReferenceOutput/Convolution/ConvolutionTests/OnFullImage_Rgba32_blur_-1_-1_-1_-1_16_-1_-1_-1_-1.png

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:b3fd7906f11e87a2b0043cde179317f53e9a2bf02d9e64763be8f9923d60f057
size 257492
Loading…
Cancel
Save