diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index a450aebf43..435c629bc6 100644 --- a/.github/workflows/build-and-test.yml +++ b/.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 }} diff --git a/src/ImageSharp.ruleset b/src/ImageSharp.ruleset index b609890200..dee0393cd7 100644 --- a/src/ImageSharp.ruleset +++ b/src/ImageSharp.ruleset @@ -1,4 +1,7 @@  + + + \ No newline at end of file diff --git a/src/ImageSharp/Formats/AnimatedImageFrameMetadata.cs b/src/ImageSharp/Formats/AnimatedImageFrameMetadata.cs deleted file mode 100644 index 8f8e187403..0000000000 --- a/src/ImageSharp/Formats/AnimatedImageFrameMetadata.cs +++ /dev/null @@ -1,32 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Formats; - -internal class AnimatedImageFrameMetadata -{ - /// - /// Gets or sets the frame color table. - /// - public ReadOnlyMemory? ColorTable { get; set; } - - /// - /// Gets or sets the frame color table mode. - /// - public FrameColorTableMode ColorTableMode { get; set; } - - /// - /// Gets or sets the duration of the frame. - /// - public TimeSpan Duration { get; set; } - - /// - /// Gets or sets the frame alpha blending mode. - /// - public FrameBlendMode BlendMode { get; set; } - - /// - /// Gets or sets the frame disposal mode. - /// - public FrameDisposalMode DisposalMode { get; set; } -} diff --git a/src/ImageSharp/Formats/AnimatedImageMetadata.cs b/src/ImageSharp/Formats/AnimatedImageMetadata.cs deleted file mode 100644 index ac3ca29f4f..0000000000 --- a/src/ImageSharp/Formats/AnimatedImageMetadata.cs +++ /dev/null @@ -1,33 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Formats; - -internal class AnimatedImageMetadata -{ - /// - /// Gets or sets the shared color table. - /// - public ReadOnlyMemory? ColorTable { get; set; } - - /// - /// Gets or sets the shared color table mode. - /// - public FrameColorTableMode ColorTableMode { get; set; } - - /// - /// Gets or sets 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 . - /// - public Color BackgroundColor { get; set; } - - /// - /// Gets or sets the number of times any animation is repeated. - /// - /// 0 means to repeat indefinitely, count is set as repeat n-1 times. Defaults to 1. - /// - /// - public ushort RepeatCount { get; set; } -} diff --git a/src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs b/src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs index 85786949d8..7c92d3e463 100644 --- a/src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs +++ b/src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs @@ -575,7 +575,9 @@ internal sealed class BmpEncoderCore { using IQuantizer frameQuantizer = this.quantizer.CreatePixelSpecificQuantizer(configuration, new QuantizerOptions() { - MaxColors = 16 + MaxColors = 16, + Dither = this.quantizer.Options.Dither, + DitherScale = this.quantizer.Options.DitherScale }); frameQuantizer.BuildPalette(this.pixelSamplingStrategy, image); @@ -623,7 +625,9 @@ internal sealed class BmpEncoderCore { using IQuantizer frameQuantizer = this.quantizer.CreatePixelSpecificQuantizer(configuration, new QuantizerOptions() { - MaxColors = 4 + MaxColors = 4, + Dither = this.quantizer.Options.Dither, + DitherScale = this.quantizer.Options.DitherScale }); frameQuantizer.BuildPalette(this.pixelSamplingStrategy, image); @@ -680,7 +684,9 @@ internal sealed class BmpEncoderCore { using IQuantizer frameQuantizer = this.quantizer.CreatePixelSpecificQuantizer(configuration, new QuantizerOptions() { - MaxColors = 2 + MaxColors = 2, + Dither = this.quantizer.Options.Dither, + DitherScale = this.quantizer.Options.DitherScale }); frameQuantizer.BuildPalette(this.pixelSamplingStrategy, image); diff --git a/src/ImageSharp/Formats/Bmp/BmpMetadata.cs b/src/ImageSharp/Formats/Bmp/BmpMetadata.cs index 68e99bdc5f..d0c60421c4 100644 --- a/src/ImageSharp/Formats/Bmp/BmpMetadata.cs +++ b/src/ImageSharp/Formats/Bmp/BmpMetadata.cs @@ -154,4 +154,10 @@ public class BmpMetadata : IFormatMetadata /// public BmpMetadata DeepClone() => new(this); + + /// + public void AfterImageApply(Image destination) + where TPixel : unmanaged, IPixel + { + } } diff --git a/src/ImageSharp/Formats/Cur/CurDecoderCore.cs b/src/ImageSharp/Formats/Cur/CurDecoderCore.cs index a8a51878e0..6fc8905279 100644 --- a/src/ImageSharp/Formats/Cur/CurDecoderCore.cs +++ b/src/ImageSharp/Formats/Cur/CurDecoderCore.cs @@ -35,10 +35,6 @@ internal sealed class CurDecoderCore : IconDecoderCore curMetadata.Compression = compression; curMetadata.BmpBitsPerPixel = bitsPerPixel; curMetadata.ColorTable = colorTable; - curMetadata.EncodingWidth = curFrameMetadata.EncodingWidth; - curMetadata.EncodingHeight = curFrameMetadata.EncodingHeight; - curMetadata.HotspotX = curFrameMetadata.HotspotX; - curMetadata.HotspotY = curFrameMetadata.HotspotY; } } } diff --git a/src/ImageSharp/Formats/Cur/CurFrameMetadata.cs b/src/ImageSharp/Formats/Cur/CurFrameMetadata.cs index 06cf426dc4..4e9a432b16 100644 --- a/src/ImageSharp/Formats/Cur/CurFrameMetadata.cs +++ b/src/ImageSharp/Formats/Cur/CurFrameMetadata.cs @@ -132,6 +132,16 @@ public class CurFrameMetadata : IFormatFrameMetadata EncodingHeight = this.EncodingHeight }; + /// + public void AfterFrameApply(ImageFrame source, ImageFrame destination) + where TPixel : unmanaged, IPixel + { + float ratioX = destination.Width / (float)source.Width; + float ratioY = destination.Height / (float)source.Height; + this.EncodingWidth = Scale(this.EncodingWidth, destination.Width, ratioX); + this.EncodingHeight = Scale(this.EncodingHeight, destination.Height, ratioY); + } + /// IDeepCloneable IDeepCloneable.DeepClone() => this.DeepClone(); @@ -222,4 +232,14 @@ public class CurFrameMetadata : IFormatFrameMetadata ColorType = color }; } + + private static byte Scale(byte? value, int destination, float ratio) + { + if (value is null) + { + return (byte)Math.Clamp(destination, 0, 255); + } + + return Math.Min((byte)MathF.Ceiling(value.Value * ratio), (byte)Math.Clamp(destination, 0, 255)); + } } diff --git a/src/ImageSharp/Formats/Cur/CurMetadata.cs b/src/ImageSharp/Formats/Cur/CurMetadata.cs index 6e97a8584a..19de7f434d 100644 --- a/src/ImageSharp/Formats/Cur/CurMetadata.cs +++ b/src/ImageSharp/Formats/Cur/CurMetadata.cs @@ -22,10 +22,6 @@ public class CurMetadata : IFormatMetadata private CurMetadata(CurMetadata other) { this.Compression = other.Compression; - this.HotspotX = other.HotspotX; - this.HotspotY = other.HotspotY; - this.EncodingWidth = other.EncodingWidth; - this.EncodingHeight = other.EncodingHeight; this.BmpBitsPerPixel = other.BmpBitsPerPixel; if (other.ColorTable?.Length > 0) @@ -39,28 +35,6 @@ public class CurMetadata : IFormatMetadata /// public IconFrameCompression Compression { get; set; } - /// - /// Gets or sets the horizontal coordinates of the hotspot in number of pixels from the left. Derived from the root frame. - /// - public ushort HotspotX { get; set; } - - /// - /// Gets or sets the vertical coordinates of the hotspot in number of pixels from the top. Derived from the root frame. - /// - public ushort HotspotY { get; set; } - - /// - /// Gets or sets the encoding width.
- /// Can be any number between 0 and 255. Value 0 means a frame height of 256 pixels or greater. Derived from the root frame. - ///
- public byte EncodingWidth { get; set; } - - /// - /// Gets or sets the encoding height.
- /// Can be any number between 0 and 255. Value 0 means a frame height of 256 pixels or greater. Derived from the root frame. - ///
- public byte EncodingHeight { get; set; } - /// /// Gets or sets the number of bits per pixel.
/// Used when is @@ -175,6 +149,12 @@ public class CurMetadata : IFormatMetadata ColorTable = this.ColorTable }; + /// + public void AfterImageApply(Image destination) + where TPixel : unmanaged, IPixel + { + } + /// IDeepCloneable IDeepCloneable.DeepClone() => this.DeepClone(); diff --git a/src/ImageSharp/Formats/FormatConnectingMetadata.cs b/src/ImageSharp/Formats/FormatConnectingMetadata.cs index baf0a35457..9cfe40f385 100644 --- a/src/ImageSharp/Formats/FormatConnectingMetadata.cs +++ b/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 . + /// The background color is also used when a frame disposal mode is . ///
/// /// Defaults to . diff --git a/src/ImageSharp/Formats/Gif/GifEncoder.cs b/src/ImageSharp/Formats/Gif/GifEncoder.cs index 6cb8f9d8ce..37b585c618 100644 --- a/src/ImageSharp/Formats/Gif/GifEncoder.cs +++ b/src/ImageSharp/Formats/Gif/GifEncoder.cs @@ -6,7 +6,7 @@ namespace SixLabors.ImageSharp.Formats.Gif; /// /// Image encoder for writing image data to a stream in gif format. /// -public sealed class GifEncoder : QuantizingImageEncoder +public sealed class GifEncoder : QuantizingAnimatedImageEncoder { /// /// Gets the color table mode: Global or local. diff --git a/src/ImageSharp/Formats/Gif/GifEncoderCore.cs b/src/ImageSharp/Formats/Gif/GifEncoderCore.cs index 11185d90b0..0ed7e8c98d 100644 --- a/src/ImageSharp/Formats/Gif/GifEncoderCore.cs +++ b/src/ImageSharp/Formats/Gif/GifEncoderCore.cs @@ -54,6 +54,19 @@ internal sealed class GifEncoderCore /// private readonly IPixelSamplingStrategy pixelSamplingStrategy; + /// + /// 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 . + /// + private readonly Color? backgroundColor; + + /// + /// The number of times any animation is repeated. + /// + private readonly ushort? repeatCount; + /// /// Initializes a new instance of the class. /// @@ -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; } /// @@ -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 image, ReadOnlyMemory globalPalette, int globalTransparencyIndex, - FrameDisposalMode previousDisposalMode) + FrameDisposalMode previousDisposalMode, + CancellationToken cancellationToken) where TPixel : unmanaged, IPixel { if (image.Frames.Count == 1) @@ -209,10 +234,20 @@ internal sealed class GifEncoderCore ImageFrame previousFrame = image.Frames.RootFrame; // This frame is reused to store de-duplicated pixel buffers. - using ImageFrame encodingFrame = new(previousFrame.Configuration, previousFrame.Size()); + using ImageFrame encodingFrame = new(previousFrame.Configuration, previousFrame.Size); for (int i = 1; i < image.Frames.Count; i++) { + if (cancellationToken.IsCancellationRequested) + { + if (hasPaletteQuantizer) + { + paletteQuantizer.Dispose(); + } + + return; + } + // Gather the metadata for this frame. ImageFrame currentFrame = image.Frames[i]; ImageFrame? nextFrame = i < image.Frames.Count - 1 ? image.Frames[i + 1] : null; @@ -291,6 +326,10 @@ internal sealed class GifEncoderCore ImageFrame? 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 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); /// - /// Returns the index of the most transparent color in the palette. + /// Returns the index of the transparent color in the palette. /// /// The current quantized frame. /// The current gif frame metadata. /// The pixel format. - /// - /// The . - /// + /// The . private static int GetTransparentIndex(IndexedImageFrame? quantized, GifFrameMetadata? metadata) where TPixel : unmanaged, IPixel { @@ -463,6 +500,47 @@ internal sealed class GifEncoderCore return index; } + /// + /// Returns the index of the background color in the palette. + /// + /// The current quantized frame. + /// The background color to match. + /// The index in the palette of the background color. + /// The pixel format. + /// The . + private static bool TryGetBackgroundIndex( + IndexedImageFrame? quantized, + Color? background, + out byte index) + where TPixel : unmanaged, IPixel + { + int match = -1; + if (quantized != null && background.HasValue) + { + TPixel backgroundPixel = background.Value.ToPixel(); + ReadOnlySpan 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; + } + /// /// Writes the file header signature and version to the stream. /// diff --git a/src/ImageSharp/Formats/Gif/GifFrameMetadata.cs b/src/ImageSharp/Formats/Gif/GifFrameMetadata.cs index f81329e973..5fe892c656 100644 --- a/src/ImageSharp/Formats/Gif/GifFrameMetadata.cs +++ b/src/ImageSharp/Formats/Gif/GifFrameMetadata.cs @@ -126,40 +126,15 @@ public class GifFrameMetadata : IFormatFrameMetadata }; } + /// + public void AfterFrameApply(ImageFrame source, ImageFrame destination) + where TPixel : unmanaged, IPixel + { + } + /// IDeepCloneable IDeepCloneable.DeepClone() => this.DeepClone(); /// public GifFrameMetadata DeepClone() => new(this); - - internal static GifFrameMetadata FromAnimatedMetadata(AnimatedImageFrameMetadata metadata) - { - // TODO: v4 How do I link the parent metadata to the frame metadata to get the global color table? - int index = -1; - const float background = 1f; - if (metadata.ColorTable.HasValue) - { - ReadOnlySpan colorTable = metadata.ColorTable.Value.Span; - for (int i = 0; i < colorTable.Length; i++) - { - Vector4 vector = colorTable[i].ToScaledVector4(); - if (vector.W < background) - { - index = i; - } - } - } - - bool hasTransparency = index >= 0; - - return new() - { - LocalColorTable = metadata.ColorTable, - ColorTableMode = metadata.ColorTableMode, - FrameDelay = (int)Math.Round(metadata.Duration.TotalMilliseconds / 10), - DisposalMode = metadata.DisposalMode, - HasTransparency = hasTransparency, - TransparencyIndex = hasTransparency ? unchecked((byte)index) : byte.MinValue, - }; - } } diff --git a/src/ImageSharp/Formats/Gif/GifMetadata.cs b/src/ImageSharp/Formats/Gif/GifMetadata.cs index 565038b55a..517609af45 100644 --- a/src/ImageSharp/Formats/Gif/GifMetadata.cs +++ b/src/ImageSharp/Formats/Gif/GifMetadata.cs @@ -130,6 +130,12 @@ public class GifMetadata : IFormatMetadata }; } + /// + public void AfterImageApply(Image destination) + where TPixel : unmanaged, IPixel + { + } + /// IDeepCloneable IDeepCloneable.DeepClone() => this.DeepClone(); diff --git a/src/ImageSharp/Formats/IAnimatedImageEncoder.cs b/src/ImageSharp/Formats/IAnimatedImageEncoder.cs new file mode 100644 index 0000000000..44431aa9a4 --- /dev/null +++ b/src/ImageSharp/Formats/IAnimatedImageEncoder.cs @@ -0,0 +1,43 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Formats; + +/// +/// Defines the contract for all image encoders that allow encoding animation sequences. +/// +public interface IAnimatedImageEncoder +{ + /// + /// 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 . + /// + Color? BackgroundColor { get; } + + /// + /// Gets the number of times any animation is repeated in supported encoders. + /// + ushort? RepeatCount { get; } + + /// + /// Gets a value indicating whether the root frame is shown as part of the animated sequence in supported encoders. + /// + bool? AnimateRootFrame { get; } +} + +/// +/// Acts as a base class for all image encoders that allow encoding animation sequences. +/// +public abstract class AnimatedImageEncoder : ImageEncoder, IAnimatedImageEncoder +{ + /// + public Color? BackgroundColor { get; init; } + + /// + public ushort? RepeatCount { get; init; } + + /// + public bool? AnimateRootFrame { get; init; } = true; +} diff --git a/src/ImageSharp/Formats/IFormatFrameMetadata.cs b/src/ImageSharp/Formats/IFormatFrameMetadata.cs index 4eef93ad34..20f27d050c 100644 --- a/src/ImageSharp/Formats/IFormatFrameMetadata.cs +++ b/src/ImageSharp/Formats/IFormatFrameMetadata.cs @@ -1,6 +1,8 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. +using SixLabors.ImageSharp.PixelFormats; + namespace SixLabors.ImageSharp.Formats; /// @@ -13,6 +15,15 @@ public interface IFormatFrameMetadata : IDeepCloneable /// /// The . FormatConnectingFrameMetadata ToFormatConnectingFrameMetadata(); + + /// + /// This method is called after a process has been applied to the image frame. + /// + /// The type of pixel format. + /// The source image frame. + /// The destination image frame. + void AfterFrameApply(ImageFrame source, ImageFrame destination) + where TPixel : unmanaged, IPixel; } /// diff --git a/src/ImageSharp/Formats/IFormatMetadata.cs b/src/ImageSharp/Formats/IFormatMetadata.cs index 8d695306e4..a351431c94 100644 --- a/src/ImageSharp/Formats/IFormatMetadata.cs +++ b/src/ImageSharp/Formats/IFormatMetadata.cs @@ -21,6 +21,14 @@ public interface IFormatMetadata : IDeepCloneable /// /// The . FormatConnectingMetadata ToFormatConnectingMetadata(); + + /// + /// This method is called after a process has been applied to the image. + /// + /// The type of pixel format. + /// The destination image . + void AfterImageApply(Image destination) + where TPixel : unmanaged, IPixel; } /// diff --git a/src/ImageSharp/Formats/IQuantizingImageEncoder.cs b/src/ImageSharp/Formats/IQuantizingImageEncoder.cs new file mode 100644 index 0000000000..e88b3ecf02 --- /dev/null +++ b/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; + +/// +/// Defines the contract for all image encoders that allow color palette generation via quantization. +/// +public interface IQuantizingImageEncoder +{ + /// + /// Gets the quantizer used to generate the color palette. + /// + IQuantizer? Quantizer { get; } + + /// + /// Gets the used for quantization when building color palettes. + /// + IPixelSamplingStrategy PixelSamplingStrategy { get; } +} + +/// +/// Acts as a base class for all image encoders that allow color palette generation via quantization. +/// +public abstract class QuantizingImageEncoder : ImageEncoder, IQuantizingImageEncoder +{ + /// + public IQuantizer? Quantizer { get; init; } + + /// + public IPixelSamplingStrategy PixelSamplingStrategy { get; init; } = new DefaultPixelSamplingStrategy(); +} + +/// +/// Acts as a base class for all image encoders that allow color palette generation via quantization when +/// encoding animation sequences. +/// +public abstract class QuantizingAnimatedImageEncoder : QuantizingImageEncoder, IAnimatedImageEncoder +{ + /// + public Color? BackgroundColor { get; } + + /// + public ushort? RepeatCount { get; } + + /// + public bool? AnimateRootFrame { get; } +} diff --git a/src/ImageSharp/Formats/Ico/IcoDecoderCore.cs b/src/ImageSharp/Formats/Ico/IcoDecoderCore.cs index 8b59974eb3..b8a1dded15 100644 --- a/src/ImageSharp/Formats/Ico/IcoDecoderCore.cs +++ b/src/ImageSharp/Formats/Ico/IcoDecoderCore.cs @@ -35,8 +35,6 @@ internal sealed class IcoDecoderCore : IconDecoderCore curMetadata.Compression = compression; curMetadata.BmpBitsPerPixel = bitsPerPixel; curMetadata.ColorTable = colorTable; - curMetadata.EncodingWidth = icoFrameMetadata.EncodingWidth; - curMetadata.EncodingHeight = icoFrameMetadata.EncodingHeight; } } } diff --git a/src/ImageSharp/Formats/Ico/IcoFrameMetadata.cs b/src/ImageSharp/Formats/Ico/IcoFrameMetadata.cs index c244e38981..a2d1c01391 100644 --- a/src/ImageSharp/Formats/Ico/IcoFrameMetadata.cs +++ b/src/ImageSharp/Formats/Ico/IcoFrameMetadata.cs @@ -125,6 +125,16 @@ public class IcoFrameMetadata : IFormatFrameMetadata EncodingHeight = this.EncodingHeight }; + /// + public void AfterFrameApply(ImageFrame source, ImageFrame destination) + where TPixel : unmanaged, IPixel + { + float ratioX = destination.Width / (float)source.Width; + float ratioY = destination.Height / (float)source.Height; + this.EncodingWidth = Scale(this.EncodingWidth, destination.Width, ratioX); + this.EncodingHeight = Scale(this.EncodingHeight, destination.Height, ratioY); + } + /// IDeepCloneable IDeepCloneable.DeepClone() => this.DeepClone(); @@ -217,4 +227,14 @@ public class IcoFrameMetadata : IFormatFrameMetadata ColorType = color }; } + + private static byte Scale(byte? value, int destination, float ratio) + { + if (value is null) + { + return (byte)Math.Clamp(destination, 0, 255); + } + + return Math.Min((byte)MathF.Ceiling(value.Value * ratio), (byte)Math.Clamp(destination, 0, 255)); + } } diff --git a/src/ImageSharp/Formats/Ico/IcoMetadata.cs b/src/ImageSharp/Formats/Ico/IcoMetadata.cs index 7e31468ecc..a6c2704b31 100644 --- a/src/ImageSharp/Formats/Ico/IcoMetadata.cs +++ b/src/ImageSharp/Formats/Ico/IcoMetadata.cs @@ -22,8 +22,6 @@ public class IcoMetadata : IFormatMetadata private IcoMetadata(IcoMetadata other) { this.Compression = other.Compression; - this.EncodingWidth = other.EncodingWidth; - this.EncodingHeight = other.EncodingHeight; this.BmpBitsPerPixel = other.BmpBitsPerPixel; if (other.ColorTable?.Length > 0) @@ -37,18 +35,6 @@ public class IcoMetadata : IFormatMetadata /// public IconFrameCompression Compression { get; set; } - /// - /// Gets or sets the encoding width.
- /// Can be any number between 0 and 255. Value 0 means a frame height of 256 pixels or greater. Derived from the root frame. - ///
- public byte EncodingWidth { get; set; } - - /// - /// Gets or sets the encoding height.
- /// Can be any number between 0 and 255. Value 0 means a frame height of 256 pixels or greater. Derived from the root frame. - ///
- public byte EncodingHeight { get; set; } - /// /// Gets or sets the number of bits per pixel.
/// Used when is @@ -163,6 +149,12 @@ public class IcoMetadata : IFormatMetadata ColorTable = this.ColorTable }; + /// + public void AfterImageApply(Image destination) + where TPixel : unmanaged, IPixel + { + } + /// IDeepCloneable IDeepCloneable.DeepClone() => this.DeepClone(); diff --git a/src/ImageSharp/Formats/ImageEncoder.cs b/src/ImageSharp/Formats/ImageEncoder.cs index fdaa5c35dc..a37a327174 100644 --- a/src/ImageSharp/Formats/ImageEncoder.cs +++ b/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); } diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanScanDecoder.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanScanDecoder.cs index bbd2bff53b..56e0f1e985 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanScanDecoder.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanScanDecoder.cs @@ -119,6 +119,8 @@ internal class HuffmanScanDecoder : IJpegScanDecoder this.frame.AllocateComponents(); + this.todo = this.restartInterval; + if (!this.frame.Progressive) { this.ParseBaselineData(); diff --git a/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs b/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs index ac527ff312..90e16f6dff 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs @@ -87,6 +87,8 @@ internal class HuffmanScanEncoder /// private readonly byte[] streamWriteBuffer; + private readonly int restartInterval; + /// /// Number of jagged bits stored in /// @@ -103,13 +105,16 @@ internal class HuffmanScanEncoder /// Initializes a new instance of the class. ///
/// Amount of encoded 8x8 blocks per single jpeg macroblock. + /// Numbers of MCUs between restart markers. /// Output stream for saving encoded data. - public HuffmanScanEncoder(int blocksPerCodingUnit, Stream outputStream) + public HuffmanScanEncoder(int blocksPerCodingUnit, int restartInterval, Stream outputStream) { int emitBufferByteLength = MaxBytesPerBlock * blocksPerCodingUnit; this.emitBuffer = new uint[emitBufferByteLength / sizeof(uint)]; this.emitWriteIndex = this.emitBuffer.Length; + this.restartInterval = restartInterval; + this.streamWriteBuffer = new byte[emitBufferByteLength * OutputBufferLengthMultiplier]; this.target = outputStream; @@ -211,6 +216,9 @@ internal class HuffmanScanEncoder ref HuffmanLut dcHuffmanTable = ref this.dcHuffmanTables[component.DcTableId]; ref HuffmanLut acHuffmanTable = ref this.acHuffmanTables[component.AcTableId]; + int restarts = 0; + int restartsToGo = this.restartInterval; + for (int i = 0; i < h; i++) { cancellationToken.ThrowIfCancellationRequested(); @@ -221,6 +229,13 @@ internal class HuffmanScanEncoder for (nuint k = 0; k < (uint)w; k++) { + if (this.restartInterval > 0 && restartsToGo == 0) + { + this.FlushRemainingBytes(); + this.WriteRestart(restarts % 8); + component.DcPredictor = 0; + } + this.WriteBlock( component, ref Unsafe.Add(ref blockRef, k), @@ -231,6 +246,133 @@ internal class HuffmanScanEncoder { this.FlushToStream(); } + + if (this.restartInterval > 0) + { + if (restartsToGo == 0) + { + restartsToGo = this.restartInterval; + restarts++; + } + + restartsToGo--; + } + } + } + + this.FlushRemainingBytes(); + } + + /// + /// Encodes the DC coefficients for a given component's blocks in a scan. + /// + /// The component whose DC coefficients need to be encoded. + /// The token to request cancellation. + public void EncodeDcScan(Component component, CancellationToken cancellationToken) + { + int h = component.HeightInBlocks; + int w = component.WidthInBlocks; + + ref HuffmanLut dcHuffmanTable = ref this.dcHuffmanTables[component.DcTableId]; + + int restarts = 0; + int restartsToGo = this.restartInterval; + + for (int i = 0; i < h; i++) + { + cancellationToken.ThrowIfCancellationRequested(); + + Span blockSpan = component.SpectralBlocks.DangerousGetRowSpan(y: i); + ref Block8x8 blockRef = ref MemoryMarshal.GetReference(blockSpan); + + for (nuint k = 0; k < (uint)w; k++) + { + if (this.restartInterval > 0 && restartsToGo == 0) + { + this.FlushRemainingBytes(); + this.WriteRestart(restarts % 8); + component.DcPredictor = 0; + } + + this.WriteDc( + component, + ref Unsafe.Add(ref blockRef, k), + ref dcHuffmanTable); + + if (this.IsStreamFlushNeeded) + { + this.FlushToStream(); + } + + if (this.restartInterval > 0) + { + if (restartsToGo == 0) + { + restartsToGo = this.restartInterval; + restarts++; + } + + restartsToGo--; + } + } + } + + this.FlushRemainingBytes(); + } + + /// + /// Encodes the AC coefficients for a specified range of blocks in a component's scan. + /// + /// The component whose AC coefficients need to be encoded. + /// The starting index of the AC coefficient range to encode. + /// The ending index of the AC coefficient range to encode. + /// The token to request cancellation. + public void EncodeAcScan(Component component, nint start, nint end, CancellationToken cancellationToken) + { + int h = component.HeightInBlocks; + int w = component.WidthInBlocks; + + int restarts = 0; + int restartsToGo = this.restartInterval; + + ref HuffmanLut acHuffmanTable = ref this.acHuffmanTables[component.AcTableId]; + + for (int i = 0; i < h; i++) + { + cancellationToken.ThrowIfCancellationRequested(); + + Span blockSpan = component.SpectralBlocks.DangerousGetRowSpan(y: i); + ref Block8x8 blockRef = ref MemoryMarshal.GetReference(blockSpan); + + for (nuint k = 0; k < (uint)w; k++) + { + if (this.restartInterval > 0 && restartsToGo == 0) + { + this.FlushRemainingBytes(); + this.WriteRestart(restarts % 8); + } + + this.WriteAcBlock( + ref Unsafe.Add(ref blockRef, k), + start, + end, + ref acHuffmanTable); + + if (this.IsStreamFlushNeeded) + { + this.FlushToStream(); + } + + if (this.restartInterval > 0) + { + if (restartsToGo == 0) + { + restartsToGo = this.restartInterval; + restarts++; + } + + restartsToGo--; + } } } @@ -250,6 +392,9 @@ internal class HuffmanScanEncoder int mcusPerColumn = frame.McusPerColumn; int mcusPerLine = frame.McusPerLine; + int restarts = 0; + int restartsToGo = this.restartInterval; + for (int j = 0; j < mcusPerColumn; j++) { cancellationToken.ThrowIfCancellationRequested(); @@ -260,6 +405,16 @@ internal class HuffmanScanEncoder // Encode spectral to binary for (int i = 0; i < mcusPerLine; i++) { + if (this.restartInterval > 0 && restartsToGo == 0) + { + this.FlushRemainingBytes(); + this.WriteRestart(restarts % 8); + foreach (var component in frame.Components) + { + component.DcPredictor = 0; + } + } + // Scan an interleaved mcu... process components in order int mcuCol = mcu % mcusPerLine; for (int k = 0; k < frame.Components.Length; k++) @@ -300,6 +455,17 @@ internal class HuffmanScanEncoder { this.FlushToStream(); } + + if (this.restartInterval > 0) + { + if (restartsToGo == 0) + { + restartsToGo = this.restartInterval; + restarts++; + } + + restartsToGo--; + } } } @@ -371,25 +537,29 @@ internal class HuffmanScanEncoder this.FlushRemainingBytes(); } - private void WriteBlock( + private void WriteDc( Component component, ref Block8x8 block, - ref HuffmanLut dcTable, - ref HuffmanLut acTable) + ref HuffmanLut dcTable) { // Emit the DC delta. int dc = block[0]; this.EmitHuffRLE(dcTable.Values, 0, dc - component.DcPredictor); component.DcPredictor = dc; + } + private void WriteAcBlock( + ref Block8x8 block, + nint start, + nint end, + ref HuffmanLut acTable) + { // Emit the AC components. int[] acHuffTable = acTable.Values; - nint lastValuableIndex = block.GetLastNonZeroIndex(); - int runLength = 0; ref short blockRef = ref Unsafe.As(ref block); - for (nint zig = 1; zig <= lastValuableIndex; zig++) + for (nint zig = start; zig < end; zig++) { const int zeroRun1 = 1 << 4; const int zeroRun16 = 16 << 4; @@ -413,14 +583,25 @@ internal class HuffmanScanEncoder } // if mcu block contains trailing zeros - we must write end of block (EOB) value indicating that current block is over - // this can be done for any number of trailing zeros, even when all 63 ac values are zero - // (Block8x8F.Size - 1) == 63 - last index of the mcu elements - if (lastValuableIndex != Block8x8F.Size - 1) + if (runLength > 0) { this.EmitHuff(acHuffTable, 0x00); } } + private void WriteBlock( + Component component, + ref Block8x8 block, + ref HuffmanLut dcTable, + ref HuffmanLut acTable) + { + this.WriteDc(component, ref block, ref dcTable); + this.WriteAcBlock(ref block, 1, 64, ref acTable); + } + + private void WriteRestart(int restart) => + this.target.Write([0xff, (byte)(JpegConstants.Markers.RST0 + restart)], 0, 2); + /// /// Emits the most significant count of bits to the buffer. /// diff --git a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs b/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs index 2320fe1791..707baa1a88 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs +++ b/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 diff --git a/src/ImageSharp/Formats/Jpeg/JpegEncoder.cs b/src/ImageSharp/Formats/Jpeg/JpegEncoder.cs index 0daaae112c..69f04f1dcf 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegEncoder.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegEncoder.cs @@ -13,6 +13,16 @@ public sealed class JpegEncoder : ImageEncoder ///
private int? quality; + /// + /// Backing field for + /// + private int progressiveScans = 4; + + /// + /// Backing field for + /// + private int restartInterval; + /// /// Gets the quality, that will be used to encode the image. Quality /// index must be between 1 and 100 (compression from max to min). @@ -33,6 +43,56 @@ public sealed class JpegEncoder : ImageEncoder } } + /// + /// Gets a value indicating whether progressive encoding is used. + /// + public bool Progressive { get; init; } + + /// + /// Gets number of scans per component for progressive encoding. + /// Defaults to 4. + /// + /// + /// Number of scans must be between 2 and 64. + /// There is at least one scan for the DC coefficients and one for the remaining 63 AC coefficients. + /// + /// Progressive scans must be in [2..64] range. + public int ProgressiveScans + { + get => this.progressiveScans; + init + { + if (value is < 2 or > 64) + { + throw new ArgumentException("Progressive scans must be in [2..64] range."); + } + + this.progressiveScans = value; + } + } + + /// + /// Gets numbers of MCUs between restart markers. + /// Defaults to 0. + /// + /// + /// Currently supported in progressive encoding only. + /// + /// Restart interval must be in [0..65535] range. + public int RestartInterval + { + get => this.restartInterval; + init + { + if (value is < 0 or > 65535) + { + throw new ArgumentException("Restart interval must be in [0..65535] range."); + } + + this.restartInterval = value; + } + } + /// /// Gets the component encoding mode. /// diff --git a/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs b/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs index a6ff623660..34028c2f83 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs @@ -100,12 +100,15 @@ internal sealed unsafe partial class JpegEncoderCore this.WriteStartOfFrame(image.Width, image.Height, frameConfig, buffer); // Write the Huffman tables. - HuffmanScanEncoder scanEncoder = new(frame.BlocksPerMcu, stream); + HuffmanScanEncoder scanEncoder = new(frame.BlocksPerMcu, this.encoder.RestartInterval, stream); this.WriteDefineHuffmanTables(frameConfig.HuffmanTables, scanEncoder, buffer); // Write the quantization tables. this.WriteDefineQuantizationTables(frameConfig.QuantizationTables, this.encoder.Quality, jpegMetadata, buffer); + // Write define restart interval + this.WriteDri(this.encoder.RestartInterval, buffer); + // Write scans with actual pixel data using SpectralConverter spectralConverter = new(frame, image, this.QuantizationTables); this.WriteHuffmanScans(frame, frameConfig, spectralConverter, scanEncoder, buffer, cancellationToken); @@ -426,6 +429,25 @@ internal sealed unsafe partial class JpegEncoderCore } } + /// + /// Writes the DRI marker + /// + /// Numbers of MCUs between restart markers. + /// Temporary buffer. + private void WriteDri(int restartInterval, Span buffer) + { + if (restartInterval <= 0) + { + return; + } + + this.WriteMarkerHeader(JpegConstants.Markers.DRI, 4, buffer); + + buffer[1] = (byte)(restartInterval & 0xff); + buffer[0] = (byte)(restartInterval >> 8); + this.outputStream.Write(buffer, 0, 2); + } + /// /// Writes the App1 header. /// @@ -563,7 +585,8 @@ internal sealed unsafe partial class JpegEncoderCore // Length (high byte, low byte), 8 + components * 3. int markerlen = 8 + (3 * components.Length); - this.WriteMarkerHeader(JpegConstants.Markers.SOF0, markerlen, buffer); + byte marker = this.encoder.Progressive ? JpegConstants.Markers.SOF2 : JpegConstants.Markers.SOF0; + this.WriteMarkerHeader(marker, markerlen, buffer); buffer[5] = (byte)components.Length; buffer[0] = 8; // Data Precision. 8 for now, 12 and 16 bit jpegs not supported buffer[1] = (byte)(height >> 8); @@ -597,7 +620,17 @@ internal sealed unsafe partial class JpegEncoderCore /// /// The collecction of component configuration items. /// Temporary buffer. - private void WriteStartOfScan(Span components, Span buffer) + private void WriteStartOfScan(Span components, Span buffer) => + this.WriteStartOfScan(components, buffer, 0x00, 0x3f); + + /// + /// Writes the StartOfScan marker. + /// + /// The collecction of component configuration items. + /// Temporary buffer. + /// Start of spectral selection + /// End of spectral selection + private void WriteStartOfScan(Span components, Span buffer, byte spectralStart, byte spectralEnd) { // Write the SOS (Start Of Scan) marker "\xff\xda" followed by 12 bytes: // - the marker length "\x00\x0c", @@ -630,8 +663,8 @@ internal sealed unsafe partial class JpegEncoderCore buffer[i2 + 6] = (byte)tableSelectors; } - buffer[sosSize - 1] = 0x00; // Ss - Start of spectral selection. - buffer[sosSize] = 0x3f; // Se - End of spectral selection. + buffer[sosSize - 1] = spectralStart; // Ss - Start of spectral selection. + buffer[sosSize] = spectralEnd; // Se - End of spectral selection. buffer[sosSize + 1] = 0x00; // Ah + Ah (Successive approximation bit position high + low) this.outputStream.Write(buffer, 0, sosSize + 2); } @@ -666,7 +699,14 @@ internal sealed unsafe partial class JpegEncoderCore CancellationToken cancellationToken) where TPixel : unmanaged, IPixel { - if (frame.Components.Length == 1) + if (this.encoder.Progressive) + { + frame.AllocateComponents(fullScan: true); + spectralConverter.ConvertFull(); + + this.WriteProgressiveScans(frame, frameConfig, encoder, buffer, cancellationToken); + } + else if (frame.Components.Length == 1) { frame.AllocateComponents(fullScan: false); @@ -694,6 +734,50 @@ internal sealed unsafe partial class JpegEncoderCore } } + /// + /// Writes the progressive scans + /// + /// The type of pixel format. + /// The current frame. + /// The frame configuration. + /// The scan encoder. + /// Temporary buffer. + /// The cancellation token. + private void WriteProgressiveScans( + JpegFrame frame, + JpegFrameConfig frameConfig, + HuffmanScanEncoder encoder, + Span buffer, + CancellationToken cancellationToken) + where TPixel : unmanaged, IPixel + { + Span components = frameConfig.Components; + + // Phase 1: DC scan + for (int i = 0; i < frame.Components.Length; i++) + { + this.WriteStartOfScan(components.Slice(i, 1), buffer, 0x00, 0x00); + + encoder.EncodeDcScan(frame.Components[i], cancellationToken); + } + + // Phase 2: AC scans + int acScans = this.encoder.ProgressiveScans - 1; + int valuesPerScan = 64 / acScans; + for (int scan = 0; scan < acScans; scan++) + { + int start = Math.Max(1, scan * valuesPerScan); + int end = scan == acScans - 1 ? 64 : (scan + 1) * valuesPerScan; + + for (int i = 0; i < components.Length; i++) + { + this.WriteStartOfScan(components.Slice(i, 1), buffer, (byte)start, (byte)(end - 1)); + + encoder.EncodeAcScan(frame.Components[i], start, end, cancellationToken); + } + } + } + /// /// Writes the header for a marker with the given length. /// diff --git a/src/ImageSharp/Formats/Jpeg/JpegMetadata.cs b/src/ImageSharp/Formats/Jpeg/JpegMetadata.cs index f2f34ec496..fe4855dc77 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegMetadata.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegMetadata.cs @@ -199,6 +199,12 @@ public class JpegMetadata : IFormatMetadata Quality = this.Quality, }; + /// + public void AfterImageApply(Image destination) + where TPixel : unmanaged, IPixel + { + } + /// IDeepCloneable IDeepCloneable.DeepClone() => this.DeepClone(); diff --git a/src/ImageSharp/Formats/Pbm/PbmMetadata.cs b/src/ImageSharp/Formats/Pbm/PbmMetadata.cs index fec4beda7c..d852f3c8eb 100644 --- a/src/ImageSharp/Formats/Pbm/PbmMetadata.cs +++ b/src/ImageSharp/Formats/Pbm/PbmMetadata.cs @@ -129,6 +129,12 @@ public class PbmMetadata : IFormatMetadata PixelTypeInfo = this.GetPixelTypeInfo(), }; + /// + public void AfterImageApply(Image destination) + where TPixel : unmanaged, IPixel + { + } + /// IDeepCloneable IDeepCloneable.DeepClone() => this.DeepClone(); diff --git a/src/ImageSharp/Formats/Png/PngEncoder.cs b/src/ImageSharp/Formats/Png/PngEncoder.cs index dcbaf3140d..d9f71e1b56 100644 --- a/src/ImageSharp/Formats/Png/PngEncoder.cs +++ b/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; /// /// Image encoder for writing image data to a stream in png format. /// -public class PngEncoder : QuantizingImageEncoder +public class PngEncoder : QuantizingAnimatedImageEncoder { /// /// Initializes a new instance of the class. diff --git a/src/ImageSharp/Formats/Png/PngEncoderCore.cs b/src/ImageSharp/Formats/Png/PngEncoderCore.cs index 4bbb68358f..398c80634c 100644 --- a/src/ImageSharp/Formats/Png/PngEncoderCore.cs +++ b/src/ImageSharp/Formats/Png/PngEncoderCore.cs @@ -123,6 +123,24 @@ internal sealed class PngEncoderCore : IDisposable /// private int derivedTransparencyIndex = -1; + /// + /// 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 . + /// + private readonly Color? backgroundColor; + + /// + /// The number of times any animation is repeated. + /// + private readonly ushort? repeatCount; + + /// + /// Whether the root frame is shown as part of the animated sequence. + /// + private readonly bool? animateRootFrame; + /// /// A reusable Crc32 hashing instance. /// @@ -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; } /// @@ -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); @@ -231,16 +256,24 @@ internal sealed class PngEncoderCore : IDisposable ImageFrame previousFrame = image.Frames.RootFrame; // This frame is reused to store de-duplicated pixel buffers. - using ImageFrame encodingFrame = new(image.Configuration, previousFrame.Size()); + using ImageFrame encodingFrame = new(image.Configuration, previousFrame.Size); for (; currentFrameIndex < image.Frames.Count; currentFrameIndex++) { + if (cancellationToken.IsCancellationRequested) + { + break; + } + ImageFrame? prev = previousDisposal == FrameDisposalMode.RestoreToBackground ? null : previousFrame; currentFrame = image.Frames[currentFrameIndex]; ImageFrame? 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 /// /// The type of the pixel. /// The cloned image frame where the transparent pixels will be changed. - private static void ClearTransparentPixels(ImageFrame clone) + /// The color to replace transparent pixels with. + private static void ClearTransparentPixels(ImageFrame clone, Color color) where TPixel : unmanaged, IPixel => clone.ProcessPixelRows(accessor => { // TODO: We should be able to speed this up with SIMD and masking. - Rgba32 transparent = Color.Transparent.ToPixel(); + Rgba32 transparent = color.ToPixel(); for (int y = 0; y < accessor.Height; y++) { Span span = accessor.GetRowSpan(y); diff --git a/src/ImageSharp/Formats/Png/PngFrameMetadata.cs b/src/ImageSharp/Formats/Png/PngFrameMetadata.cs index c142a1c8e0..b8086cd6d1 100644 --- a/src/ImageSharp/Formats/Png/PngFrameMetadata.cs +++ b/src/ImageSharp/Formats/Png/PngFrameMetadata.cs @@ -2,6 +2,7 @@ // Licensed under the Six Labors Split License. using SixLabors.ImageSharp.Formats.Png.Chunks; +using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Formats.Png; @@ -84,6 +85,12 @@ public class PngFrameMetadata : IFormatFrameMetadata }; } + /// + public void AfterFrameApply(ImageFrame source, ImageFrame destination) + where TPixel : unmanaged, IPixel + { + } + /// IDeepCloneable IDeepCloneable.DeepClone() => this.DeepClone(); diff --git a/src/ImageSharp/Formats/Png/PngMetadata.cs b/src/ImageSharp/Formats/Png/PngMetadata.cs index a7b3672ef5..00cba088cb 100644 --- a/src/ImageSharp/Formats/Png/PngMetadata.cs +++ b/src/ImageSharp/Formats/Png/PngMetadata.cs @@ -247,6 +247,12 @@ public class PngMetadata : IFormatMetadata RepeatCount = (ushort)Numerics.Clamp(this.RepeatCount, 0, ushort.MaxValue), }; + /// + public void AfterImageApply(Image destination) + where TPixel : unmanaged, IPixel + { + } + /// IDeepCloneable IDeepCloneable.DeepClone() => this.DeepClone(); diff --git a/src/ImageSharp/Formats/Qoi/QoiMetadata.cs b/src/ImageSharp/Formats/Qoi/QoiMetadata.cs index e2062014d7..e463d511d2 100644 --- a/src/ImageSharp/Formats/Qoi/QoiMetadata.cs +++ b/src/ImageSharp/Formats/Qoi/QoiMetadata.cs @@ -88,6 +88,12 @@ public class QoiMetadata : IFormatMetadata PixelTypeInfo = this.GetPixelTypeInfo() }; + /// + public void AfterImageApply(Image destination) + where TPixel : unmanaged, IPixel + { + } + /// IDeepCloneable IDeepCloneable.DeepClone() => this.DeepClone(); diff --git a/src/ImageSharp/Formats/QuantizingImageEncoder.cs b/src/ImageSharp/Formats/QuantizingImageEncoder.cs deleted file mode 100644 index 330d8988c7..0000000000 --- a/src/ImageSharp/Formats/QuantizingImageEncoder.cs +++ /dev/null @@ -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; - -/// -/// Acts as a base class for all image encoders that allow color palette generation via quantization. -/// -public abstract class QuantizingImageEncoder : ImageEncoder -{ - /// - /// Gets the quantizer used to generate the color palette. - /// - public IQuantizer? Quantizer { get; init; } - - /// - /// Gets the used for quantization when building color palettes. - /// - public IPixelSamplingStrategy PixelSamplingStrategy { get; init; } = new DefaultPixelSamplingStrategy(); -} diff --git a/src/ImageSharp/Formats/Tga/TgaMetadata.cs b/src/ImageSharp/Formats/Tga/TgaMetadata.cs index 58b5119523..8d40f86464 100644 --- a/src/ImageSharp/Formats/Tga/TgaMetadata.cs +++ b/src/ImageSharp/Formats/Tga/TgaMetadata.cs @@ -94,6 +94,12 @@ public class TgaMetadata : IFormatMetadata PixelTypeInfo = this.GetPixelTypeInfo() }; + /// + public void AfterImageApply(Image destination) + where TPixel : unmanaged, IPixel + { + } + /// IDeepCloneable IDeepCloneable.DeepClone() => this.DeepClone(); diff --git a/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs b/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs index 2356d45e47..d699a7b631 100644 --- a/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs +++ b/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs @@ -167,11 +167,18 @@ internal class TiffDecoderCore : ImageDecoderCore this.byteOrder = reader.ByteOrder; this.isBigTiff = reader.IsBigTiff; + Size? size = null; uint frameCount = 0; foreach (ExifProfile ifd in directories) { cancellationToken.ThrowIfCancellationRequested(); - ImageFrame frame = this.DecodeFrame(ifd, cancellationToken); + ImageFrame frame = this.DecodeFrame(ifd, size, cancellationToken); + + if (!size.HasValue) + { + size = frame.Size; + } + frames.Add(frame); framesMetadata.Add(frame.Metadata); @@ -181,19 +188,8 @@ internal class TiffDecoderCore : ImageDecoderCore } } + this.Dimensions = frames[0].Size; ImageMetadata metadata = TiffDecoderMetadataCreator.Create(framesMetadata, this.skipMetadata, reader.ByteOrder, reader.IsBigTiff); - - // TODO: Tiff frames can have different sizes. - ImageFrame root = frames[0]; - this.Dimensions = root.Size(); - foreach (ImageFrame frame in frames) - { - if (frame.Size() != root.Size()) - { - TiffThrowHelper.ThrowNotSupported("Images with different sizes are not supported"); - } - } - return new Image(this.configuration, metadata, frames); } catch @@ -215,17 +211,21 @@ internal class TiffDecoderCore : ImageDecoderCore IList directories = reader.Read(); List framesMetadata = []; - foreach (ExifProfile dir in directories) + int width = 0; + int height = 0; + + for (int i = 0; i < directories.Count; i++) { - framesMetadata.Add(this.CreateFrameMetadata(dir)); - } + (ImageFrameMetadata FrameMetadata, TiffFrameMetadata TiffMetadata) meta + = this.CreateFrameMetadata(directories[i]); - ExifProfile rootFrameExifProfile = directories[0]; + framesMetadata.Add(meta.FrameMetadata); - ImageMetadata metadata = TiffDecoderMetadataCreator.Create(framesMetadata, this.skipMetadata, reader.ByteOrder, reader.IsBigTiff); + width = Math.Max(width, meta.TiffMetadata.EncodingWidth); + height = Math.Max(height, meta.TiffMetadata.EncodingHeight); + } - int width = GetImageWidth(rootFrameExifProfile); - int height = GetImageHeight(rootFrameExifProfile); + ImageMetadata metadata = TiffDecoderMetadataCreator.Create(framesMetadata, this.skipMetadata, reader.ByteOrder, reader.IsBigTiff); return new ImageInfo(new(width, height), metadata, framesMetadata); } @@ -235,31 +235,46 @@ internal class TiffDecoderCore : ImageDecoderCore /// /// The pixel format. /// The IFD tags. + /// The previously determined root frame size if decoded. /// The token to monitor cancellation. /// The tiff frame. - private ImageFrame DecodeFrame(ExifProfile tags, CancellationToken cancellationToken) + private ImageFrame DecodeFrame(ExifProfile tags, Size? size, CancellationToken cancellationToken) where TPixel : unmanaged, IPixel { - ImageFrameMetadata imageFrameMetaData = this.CreateFrameMetadata(tags); - bool isTiled = this.VerifyAndParse(tags, imageFrameMetaData.GetTiffMetadata()); + (ImageFrameMetadata FrameMetadata, TiffFrameMetadata TiffFrameMetadata) metadata = this.CreateFrameMetadata(tags); + bool isTiled = this.VerifyAndParse(tags, metadata.TiffFrameMetadata); + + int width = metadata.TiffFrameMetadata.EncodingWidth; + int height = metadata.TiffFrameMetadata.EncodingHeight; + + // If size has a value and the width/height off the tiff is smaller we much capture the delta. + if (size.HasValue) + { + if (size.Value.Width < width || size.Value.Height < height) + { + TiffThrowHelper.ThrowNotSupported("Images with frames of size greater than the root frame are not supported."); + } + } + else + { + size = new Size(width, height); + } - int width = GetImageWidth(tags); - int height = GetImageHeight(tags); - ImageFrame frame = new(this.configuration, width, height, imageFrameMetaData); + ImageFrame frame = new(this.configuration, size.Value.Width, size.Value.Height, metadata.FrameMetadata); if (isTiled) { - this.DecodeImageWithTiles(tags, frame, cancellationToken); + this.DecodeImageWithTiles(tags, frame, width, height, cancellationToken); } else { - this.DecodeImageWithStrips(tags, frame, cancellationToken); + this.DecodeImageWithStrips(tags, frame, width, height, cancellationToken); } return frame; } - private ImageFrameMetadata CreateFrameMetadata(ExifProfile tags) + private (ImageFrameMetadata FrameMetadata, TiffFrameMetadata TiffMetadata) CreateFrameMetadata(ExifProfile tags) { ImageFrameMetadata imageFrameMetaData = new(); if (!this.skipMetadata) @@ -267,9 +282,10 @@ internal class TiffDecoderCore : ImageDecoderCore imageFrameMetaData.ExifProfile = tags; } - TiffFrameMetadata.Parse(imageFrameMetaData.GetTiffMetadata(), tags); + TiffFrameMetadata tiffMetadata = TiffFrameMetadata.Parse(tags); + imageFrameMetaData.SetFormatMetadata(TiffFormat.Instance, tiffMetadata); - return imageFrameMetaData; + return (imageFrameMetaData, tiffMetadata); } /// @@ -278,8 +294,10 @@ internal class TiffDecoderCore : ImageDecoderCore /// The pixel format. /// The IFD tags. /// The image frame to decode into. + /// The width in px units of the frame data. + /// The height in px units of the frame data. /// The token to monitor cancellation. - private void DecodeImageWithStrips(ExifProfile tags, ImageFrame frame, CancellationToken cancellationToken) + private void DecodeImageWithStrips(ExifProfile tags, ImageFrame frame, int width, int height, CancellationToken cancellationToken) where TPixel : unmanaged, IPixel { int rowsPerStrip; @@ -302,6 +320,8 @@ internal class TiffDecoderCore : ImageDecoderCore { this.DecodeStripsPlanar( frame, + width, + height, rowsPerStrip, stripOffsets, stripByteCounts, @@ -311,6 +331,8 @@ internal class TiffDecoderCore : ImageDecoderCore { this.DecodeStripsChunky( frame, + width, + height, rowsPerStrip, stripOffsets, stripByteCounts, @@ -324,13 +346,13 @@ internal class TiffDecoderCore : ImageDecoderCore /// The pixel format. /// The IFD tags. /// The image frame to decode into. + /// The width in px units of the frame data. + /// The height in px units of the frame data. /// The token to monitor cancellation. - private void DecodeImageWithTiles(ExifProfile tags, ImageFrame frame, CancellationToken cancellationToken) + private void DecodeImageWithTiles(ExifProfile tags, ImageFrame frame, int width, int height, CancellationToken cancellationToken) where TPixel : unmanaged, IPixel { Buffer2D pixels = frame.PixelBuffer; - int width = pixels.Width; - int height = pixels.Height; if (!tags.TryGetValue(ExifTag.TileWidth, out IExifValue valueWidth)) { @@ -384,11 +406,20 @@ internal class TiffDecoderCore : ImageDecoderCore /// /// The pixel format. /// The image frame to decode data into. + /// The width in px units of the frame data. + /// The height in px units of the frame data. /// The number of rows per strip of data. /// An array of byte offsets to each strip in the image. /// An array of the size of each strip (in bytes). /// The token to monitor cancellation. - private void DecodeStripsPlanar(ImageFrame frame, int rowsPerStrip, Span stripOffsets, Span stripByteCounts, CancellationToken cancellationToken) + private void DecodeStripsPlanar( + ImageFrame frame, + int width, + int height, + int rowsPerStrip, + Span stripOffsets, + Span stripByteCounts, + CancellationToken cancellationToken) where TPixel : unmanaged, IPixel { int stripsPerPixel = this.BitsPerSample.Channels; @@ -403,18 +434,18 @@ internal class TiffDecoderCore : ImageDecoderCore { for (int stripIndex = 0; stripIndex < stripBuffers.Length; stripIndex++) { - int uncompressedStripSize = this.CalculateStripBufferSize(frame.Width, rowsPerStrip, stripIndex); + int uncompressedStripSize = this.CalculateStripBufferSize(width, rowsPerStrip, stripIndex); stripBuffers[stripIndex] = this.memoryAllocator.Allocate(uncompressedStripSize); } - using TiffBaseDecompressor decompressor = this.CreateDecompressor(frame.Width, bitsPerPixel); + using TiffBaseDecompressor decompressor = this.CreateDecompressor(width, bitsPerPixel); TiffBasePlanarColorDecoder colorDecoder = this.CreatePlanarColorDecoder(); for (int i = 0; i < stripsPerPlane; i++) { cancellationToken.ThrowIfCancellationRequested(); - int stripHeight = i < stripsPerPlane - 1 || frame.Height % rowsPerStrip == 0 ? rowsPerStrip : frame.Height % rowsPerStrip; + int stripHeight = i < stripsPerPlane - 1 || height % rowsPerStrip == 0 ? rowsPerStrip : height % rowsPerStrip; int stripIndex = i; for (int planeIndex = 0; planeIndex < stripsPerPixel; planeIndex++) @@ -430,7 +461,7 @@ internal class TiffDecoderCore : ImageDecoderCore stripIndex += stripsPerPlane; } - colorDecoder.Decode(stripBuffers, pixels, 0, rowsPerStrip * i, frame.Width, stripHeight); + colorDecoder.Decode(stripBuffers, pixels, 0, rowsPerStrip * i, width, stripHeight); } } finally @@ -447,39 +478,48 @@ internal class TiffDecoderCore : ImageDecoderCore /// /// The pixel format. /// The image frame to decode data into. + /// The width in px units of the frame data. + /// The height in px units of the frame data. /// The rows per strip. /// The strip offsets. /// The strip byte counts. /// The token to monitor cancellation. - private void DecodeStripsChunky(ImageFrame frame, int rowsPerStrip, Span stripOffsets, Span stripByteCounts, CancellationToken cancellationToken) + private void DecodeStripsChunky( + ImageFrame frame, + int width, + int height, + int rowsPerStrip, + Span stripOffsets, + Span stripByteCounts, + CancellationToken cancellationToken) where TPixel : unmanaged, IPixel { // If the rowsPerStrip has the default value, which is effectively infinity. That is, the entire image is one strip. if (rowsPerStrip == TiffConstants.RowsPerStripInfinity) { - rowsPerStrip = frame.Height; + rowsPerStrip = height; } - int uncompressedStripSize = this.CalculateStripBufferSize(frame.Width, rowsPerStrip); + int uncompressedStripSize = this.CalculateStripBufferSize(width, rowsPerStrip); int bitsPerPixel = this.BitsPerPixel; using IMemoryOwner stripBuffer = this.memoryAllocator.Allocate(uncompressedStripSize, AllocationOptions.Clean); Span stripBufferSpan = stripBuffer.GetSpan(); Buffer2D pixels = frame.PixelBuffer; - using TiffBaseDecompressor decompressor = this.CreateDecompressor(frame.Width, bitsPerPixel); + using TiffBaseDecompressor decompressor = this.CreateDecompressor(width, bitsPerPixel); TiffBaseColorDecoder colorDecoder = this.CreateChunkyColorDecoder(); for (int stripIndex = 0; stripIndex < stripOffsets.Length; stripIndex++) { cancellationToken.ThrowIfCancellationRequested(); - int stripHeight = stripIndex < stripOffsets.Length - 1 || frame.Height % rowsPerStrip == 0 + int stripHeight = stripIndex < stripOffsets.Length - 1 || height % rowsPerStrip == 0 ? rowsPerStrip - : frame.Height % rowsPerStrip; + : height % rowsPerStrip; int top = rowsPerStrip * stripIndex; - if (top + stripHeight > frame.Height) + if (top + stripHeight > height) { // Make sure we ignore any strips that are not needed for the image (if too many are present). break; @@ -493,7 +533,7 @@ internal class TiffDecoderCore : ImageDecoderCore stripBufferSpan, cancellationToken); - colorDecoder.Decode(stripBufferSpan, pixels, 0, top, frame.Width, stripHeight); + colorDecoder.Decode(stripBufferSpan, pixels, 0, top, width, stripHeight); } } @@ -790,38 +830,6 @@ internal class TiffDecoderCore : ImageDecoderCore return bytesPerRow * height; } - /// - /// Gets the width of the image frame. - /// - /// The image frame exif profile. - /// The image width. - private static int GetImageWidth(ExifProfile exifProfile) - { - if (!exifProfile.TryGetValue(ExifTag.ImageWidth, out IExifValue width)) - { - TiffThrowHelper.ThrowInvalidImageContentException("The TIFF image frame is missing the ImageWidth"); - } - - DebugGuard.MustBeLessThanOrEqualTo((ulong)width.Value, (ulong)int.MaxValue, nameof(ExifTag.ImageWidth)); - - return (int)width.Value; - } - - /// - /// Gets the height of the image frame. - /// - /// The image frame exif profile. - /// The image height. - private static int GetImageHeight(ExifProfile exifProfile) - { - if (!exifProfile.TryGetValue(ExifTag.ImageLength, out IExifValue height)) - { - TiffThrowHelper.ThrowImageFormatException("The TIFF image frame is missing the ImageLength"); - } - - return (int)height.Value; - } - [MethodImpl(MethodImplOptions.AggressiveInlining)] private static int RoundUpToMultipleOfEight(int value) => (int)(((uint)value + 7) / 8); } diff --git a/src/ImageSharp/Formats/Tiff/TiffEncoderCore.cs b/src/ImageSharp/Formats/Tiff/TiffEncoderCore.cs index 5f91fd7393..b560067f3f 100644 --- a/src/ImageSharp/Formats/Tiff/TiffEncoderCore.cs +++ b/src/ImageSharp/Formats/Tiff/TiffEncoderCore.cs @@ -189,11 +189,22 @@ internal sealed class TiffEncoderCore long ifdOffset) where TPixel : unmanaged, IPixel { + // Get the width and height of the frame. + // This can differ from the frame bounds in-memory if the image represents only + // a subregion. + TiffFrameMetadata frameMetaData = frame.Metadata.GetTiffMetadata(); + int width = frameMetaData.EncodingWidth > 0 ? frameMetaData.EncodingWidth : frame.Width; + int height = frameMetaData.EncodingHeight > 0 ? frameMetaData.EncodingHeight : frame.Height; + + width = Math.Min(width, frame.Width); + height = Math.Min(height, frame.Height); + Size encodingSize = new(width, height); + using TiffBaseCompressor compressor = TiffCompressorFactory.Create( compression, writer.BaseStream, this.memoryAllocator, - frame.Width, + width, (int)bitsPerPixel, this.compressionLevel, this.HorizontalPredictor == TiffPredictor.Horizontal ? this.HorizontalPredictor.Value : TiffPredictor.None); @@ -202,6 +213,7 @@ internal sealed class TiffEncoderCore using TiffBaseColorWriter colorWriter = TiffColorWriterFactory.Create( this.PhotometricInterpretation, frame, + encodingSize, this.quantizer, this.pixelSamplingStrategy, this.memoryAllocator, @@ -209,7 +221,7 @@ internal sealed class TiffEncoderCore entriesCollector, (int)bitsPerPixel); - int rowsPerStrip = CalcRowsPerStrip(frame.Height, colorWriter.BytesPerRow, this.CompressionType); + int rowsPerStrip = CalcRowsPerStrip(height, colorWriter.BytesPerRow, this.CompressionType); colorWriter.Write(compressor, rowsPerStrip); @@ -222,7 +234,7 @@ internal sealed class TiffEncoderCore // Write the metadata for the frame entriesCollector.ProcessMetadata(frame, this.skipMetadata); - entriesCollector.ProcessFrameInfo(frame, imageMetadata); + entriesCollector.ProcessFrameInfo(frame, encodingSize, imageMetadata); entriesCollector.ProcessImageFormat(this); if (writer.Position % 2 != 0) diff --git a/src/ImageSharp/Formats/Tiff/TiffEncoderEntriesCollector.cs b/src/ImageSharp/Formats/Tiff/TiffEncoderEntriesCollector.cs index c8e28111ec..803b77fb0a 100644 --- a/src/ImageSharp/Formats/Tiff/TiffEncoderEntriesCollector.cs +++ b/src/ImageSharp/Formats/Tiff/TiffEncoderEntriesCollector.cs @@ -24,8 +24,8 @@ internal class TiffEncoderEntriesCollector public void ProcessMetadata(ImageFrame frame, bool skipMetadata) => new MetadataProcessor(this).Process(frame, skipMetadata); - public void ProcessFrameInfo(ImageFrame frame, ImageMetadata imageMetadata) - => new FrameInfoProcessor(this).Process(frame, imageMetadata); + public void ProcessFrameInfo(ImageFrame frame, Size encodingSize, ImageMetadata imageMetadata) + => new FrameInfoProcessor(this).Process(frame, encodingSize, imageMetadata); public void ProcessImageFormat(TiffEncoderCore encoder) => new ImageFormatProcessor(this).Process(encoder); @@ -267,16 +267,16 @@ internal class TiffEncoderEntriesCollector { } - public void Process(ImageFrame frame, ImageMetadata imageMetadata) + public void Process(ImageFrame frame, Size encodingSize, ImageMetadata imageMetadata) { this.Collector.AddOrReplace(new ExifLong(ExifTagValue.ImageWidth) { - Value = (uint)frame.Width + Value = (uint)encodingSize.Width }); this.Collector.AddOrReplace(new ExifLong(ExifTagValue.ImageLength) { - Value = (uint)frame.Height + Value = (uint)encodingSize.Height }); this.ProcessResolution(imageMetadata); diff --git a/src/ImageSharp/Formats/Tiff/TiffFrameMetadata.cs b/src/ImageSharp/Formats/Tiff/TiffFrameMetadata.cs index bb5da37411..189fee8b0c 100644 --- a/src/ImageSharp/Formats/Tiff/TiffFrameMetadata.cs +++ b/src/ImageSharp/Formats/Tiff/TiffFrameMetadata.cs @@ -3,6 +3,7 @@ using SixLabors.ImageSharp.Formats.Tiff.Constants; using SixLabors.ImageSharp.Metadata.Profiles.Exif; +using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Formats.Tiff; @@ -29,6 +30,8 @@ public class TiffFrameMetadata : IFormatFrameMetadata this.PhotometricInterpretation = other.PhotometricInterpretation; this.Predictor = other.Predictor; this.InkSet = other.InkSet; + this.EncodingWidth = other.EncodingWidth; + this.EncodingHeight = other.EncodingHeight; } /// @@ -61,13 +64,59 @@ public class TiffFrameMetadata : IFormatFrameMetadata /// public TiffInkSet? InkSet { get; set; } + /// + /// Gets or sets the encoding width. + /// + public int EncodingWidth { get; set; } + + /// + /// Gets or sets the encoding height. + /// + public int EncodingHeight { get; set; } + /// public static TiffFrameMetadata FromFormatConnectingFrameMetadata(FormatConnectingFrameMetadata metadata) - => new(); + { + TiffFrameMetadata frameMetadata = new(); + if (metadata.EncodingWidth.HasValue && metadata.EncodingHeight.HasValue) + { + frameMetadata.EncodingWidth = metadata.EncodingWidth.Value; + frameMetadata.EncodingHeight = metadata.EncodingHeight.Value; + } + + return frameMetadata; + } /// public FormatConnectingFrameMetadata ToFormatConnectingFrameMetadata() - => new(); + => new() + { + EncodingWidth = this.EncodingWidth, + EncodingHeight = this.EncodingHeight + }; + + /// + public void AfterFrameApply(ImageFrame source, ImageFrame destination) + where TPixel : unmanaged, IPixel + { + float ratioX = destination.Width / (float)source.Width; + float ratioY = destination.Height / (float)source.Height; + this.EncodingWidth = Scale(this.EncodingWidth, destination.Width, ratioX); + this.EncodingHeight = Scale(this.EncodingHeight, destination.Height, ratioY); + + // Overwrite the EXIF dimensional metadata with the encoding dimensions of the image. + destination.Metadata.ExifProfile?.SyncDimensions(this.EncodingWidth, this.EncodingHeight); + } + + private static int Scale(int value, int destination, float ratio) + { + if (value <= 0) + { + return destination; + } + + return Math.Min((int)MathF.Ceiling(value * ratio), destination); + } /// IDeepCloneable IDeepCloneable.DeepClone() => this.DeepClone(); @@ -93,43 +142,75 @@ public class TiffFrameMetadata : IFormatFrameMetadata /// /// The tiff frame meta data. /// The Exif profile containing tiff frame directory tags. - internal static void Parse(TiffFrameMetadata meta, ExifProfile profile) + private static void Parse(TiffFrameMetadata meta, ExifProfile profile) { - if (profile != null) + meta.EncodingWidth = GetImageWidth(profile); + meta.EncodingHeight = GetImageHeight(profile); + + if (profile.TryGetValue(ExifTag.BitsPerSample, out IExifValue? bitsPerSampleValue) + && TiffBitsPerSample.TryParse(bitsPerSampleValue.Value, out TiffBitsPerSample bitsPerSample)) + { + meta.BitsPerSample = bitsPerSample; + } + + meta.BitsPerPixel = meta.BitsPerSample.BitsPerPixel(); + + if (profile.TryGetValue(ExifTag.Compression, out IExifValue? compressionValue)) { - if (profile.TryGetValue(ExifTag.BitsPerSample, out IExifValue? bitsPerSampleValue) - && TiffBitsPerSample.TryParse(bitsPerSampleValue.Value, out TiffBitsPerSample bitsPerSample)) - { - meta.BitsPerSample = bitsPerSample; - } - - meta.BitsPerPixel = meta.BitsPerSample.BitsPerPixel(); - - if (profile.TryGetValue(ExifTag.Compression, out IExifValue? compressionValue)) - { - meta.Compression = (TiffCompression)compressionValue.Value; - } - - if (profile.TryGetValue(ExifTag.PhotometricInterpretation, out IExifValue? photometricInterpretationValue)) - { - meta.PhotometricInterpretation = (TiffPhotometricInterpretation)photometricInterpretationValue.Value; - } - - if (profile.TryGetValue(ExifTag.Predictor, out IExifValue? predictorValue)) - { - meta.Predictor = (TiffPredictor)predictorValue.Value; - } - - if (profile.TryGetValue(ExifTag.InkSet, out IExifValue? inkSetValue)) - { - meta.InkSet = (TiffInkSet)inkSetValue.Value; - } - - // TODO: Why do we remove this? Encoding should overwrite. - profile.RemoveValue(ExifTag.BitsPerSample); - profile.RemoveValue(ExifTag.Compression); - profile.RemoveValue(ExifTag.PhotometricInterpretation); - profile.RemoveValue(ExifTag.Predictor); + meta.Compression = (TiffCompression)compressionValue.Value; } + + if (profile.TryGetValue(ExifTag.PhotometricInterpretation, out IExifValue? photometricInterpretationValue)) + { + meta.PhotometricInterpretation = (TiffPhotometricInterpretation)photometricInterpretationValue.Value; + } + + if (profile.TryGetValue(ExifTag.Predictor, out IExifValue? predictorValue)) + { + meta.Predictor = (TiffPredictor)predictorValue.Value; + } + + if (profile.TryGetValue(ExifTag.InkSet, out IExifValue? inkSetValue)) + { + meta.InkSet = (TiffInkSet)inkSetValue.Value; + } + + // Remove values, we've explicitly captured them and they could change on encode. + profile.RemoveValue(ExifTag.BitsPerSample); + profile.RemoveValue(ExifTag.Compression); + profile.RemoveValue(ExifTag.PhotometricInterpretation); + profile.RemoveValue(ExifTag.Predictor); + } + + /// + /// Gets the width of the image frame. + /// + /// The image frame exif profile. + /// The image width. + private static int GetImageWidth(ExifProfile exifProfile) + { + if (!exifProfile.TryGetValue(ExifTag.ImageWidth, out IExifValue? width)) + { + TiffThrowHelper.ThrowInvalidImageContentException("The TIFF image frame is missing the ImageWidth"); + } + + DebugGuard.MustBeLessThanOrEqualTo((ulong)width.Value, (ulong)int.MaxValue, nameof(ExifTag.ImageWidth)); + + return (int)width.Value; + } + + /// + /// Gets the height of the image frame. + /// + /// The image frame exif profile. + /// The image height. + private static int GetImageHeight(ExifProfile exifProfile) + { + if (!exifProfile.TryGetValue(ExifTag.ImageLength, out IExifValue? height)) + { + TiffThrowHelper.ThrowImageFormatException("The TIFF image frame is missing the ImageLength"); + } + + return (int)height.Value; } } diff --git a/src/ImageSharp/Formats/Tiff/TiffMetadata.cs b/src/ImageSharp/Formats/Tiff/TiffMetadata.cs index cc70941d51..e965fcb4f6 100644 --- a/src/ImageSharp/Formats/Tiff/TiffMetadata.cs +++ b/src/ImageSharp/Formats/Tiff/TiffMetadata.cs @@ -180,6 +180,12 @@ public class TiffMetadata : IFormatMetadata PixelTypeInfo = this.GetPixelTypeInfo() }; + /// + public void AfterImageApply(Image destination) + where TPixel : unmanaged, IPixel + { + } + /// IDeepCloneable IDeepCloneable.DeepClone() => this.DeepClone(); diff --git a/src/ImageSharp/Formats/Tiff/Writers/TiffBaseColorWriter{TPixel}.cs b/src/ImageSharp/Formats/Tiff/Writers/TiffBaseColorWriter{TPixel}.cs index c4a7492553..9fd730f416 100644 --- a/src/ImageSharp/Formats/Tiff/Writers/TiffBaseColorWriter{TPixel}.cs +++ b/src/ImageSharp/Formats/Tiff/Writers/TiffBaseColorWriter{TPixel}.cs @@ -13,8 +13,15 @@ internal abstract class TiffBaseColorWriter : IDisposable { private bool isDisposed; - protected TiffBaseColorWriter(ImageFrame image, MemoryAllocator memoryAllocator, Configuration configuration, TiffEncoderEntriesCollector entriesCollector) + protected TiffBaseColorWriter( + ImageFrame image, + Size encodingSize, + MemoryAllocator memoryAllocator, + Configuration configuration, + TiffEncoderEntriesCollector entriesCollector) { + this.Width = encodingSize.Width; + this.Height = encodingSize.Height; this.Image = image; this.MemoryAllocator = memoryAllocator; this.Configuration = configuration; @@ -26,10 +33,20 @@ internal abstract class TiffBaseColorWriter : IDisposable /// public abstract int BitsPerPixel { get; } + /// + /// Gets the width of the portion of the image to be encoded. + /// + public int Width { get; } + + /// + /// Gets the height of the portion of the image to be encoded. + /// + public int Height { get; } + /// /// Gets the bytes per row. /// - public int BytesPerRow => (int)(((uint)(this.Image.Width * this.BitsPerPixel) + 7) / 8); + public int BytesPerRow => (int)(((uint)(this.Width * this.BitsPerPixel) + 7) / 8); protected ImageFrame Image { get; } @@ -42,18 +59,18 @@ internal abstract class TiffBaseColorWriter : IDisposable public virtual void Write(TiffBaseCompressor compressor, int rowsPerStrip) { DebugGuard.IsTrue(this.BytesPerRow == compressor.BytesPerRow, "bytes per row of the compressor does not match tiff color writer"); - int stripsCount = (this.Image.Height + rowsPerStrip - 1) / rowsPerStrip; + int stripsCount = (this.Height + rowsPerStrip - 1) / rowsPerStrip; uint[] stripOffsets = new uint[stripsCount]; uint[] stripByteCounts = new uint[stripsCount]; int stripIndex = 0; compressor.Initialize(rowsPerStrip); - for (int y = 0; y < this.Image.Height; y += rowsPerStrip) + for (int y = 0; y < this.Height; y += rowsPerStrip) { long offset = compressor.Output.Position; - int height = Math.Min(rowsPerStrip, this.Image.Height - y); + int height = Math.Min(rowsPerStrip, this.Height - y); this.EncodeStrip(y, height, compressor); long endOffset = compressor.Output.Position; diff --git a/src/ImageSharp/Formats/Tiff/Writers/TiffBiColorWriter{TPixel}.cs b/src/ImageSharp/Formats/Tiff/Writers/TiffBiColorWriter{TPixel}.cs index a6f4c31060..647ff8a1a3 100644 --- a/src/ImageSharp/Formats/Tiff/Writers/TiffBiColorWriter{TPixel}.cs +++ b/src/ImageSharp/Formats/Tiff/Writers/TiffBiColorWriter{TPixel}.cs @@ -21,11 +21,16 @@ internal sealed class TiffBiColorWriter : TiffBaseColorWriter private IMemoryOwner bitStrip; - public TiffBiColorWriter(ImageFrame image, MemoryAllocator memoryAllocator, Configuration configuration, TiffEncoderEntriesCollector entriesCollector) - : base(image, memoryAllocator, configuration, entriesCollector) + public TiffBiColorWriter( + ImageFrame image, + Size encodingSize, + MemoryAllocator memoryAllocator, + Configuration configuration, + TiffEncoderEntriesCollector entriesCollector) + : base(image, encodingSize, memoryAllocator, configuration, entriesCollector) { // Convert image to black and white. - this.imageBlackWhite = new Image(configuration, new ImageMetadata(), new[] { image.Clone() }); + this.imageBlackWhite = new Image(configuration, new ImageMetadata(), [image.Clone()]); this.imageBlackWhite.Mutate(img => img.BinaryDither(KnownDitherings.FloydSteinberg)); } @@ -35,9 +40,9 @@ internal sealed class TiffBiColorWriter : TiffBaseColorWriter /// protected override void EncodeStrip(int y, int height, TiffBaseCompressor compressor) { - int width = this.Image.Width; + int width = this.Width; - if (compressor.Method == TiffCompression.CcittGroup3Fax || compressor.Method == TiffCompression.Ccitt1D || compressor.Method == TiffCompression.CcittGroup4Fax) + if (compressor.Method is TiffCompression.CcittGroup3Fax or TiffCompression.Ccitt1D or TiffCompression.CcittGroup4Fax) { // Special case for T4BitCompressor. int stripPixels = width * height; @@ -77,9 +82,9 @@ internal sealed class TiffBiColorWriter : TiffBaseColorWriter int bitIndex = 0; int byteIndex = 0; Span outputRow = rows[(outputRowIdx * this.BytesPerRow)..]; - Span pixelsBlackWhiteRow = blackWhiteBuffer.DangerousGetRowSpan(row); + Span pixelsBlackWhiteRow = blackWhiteBuffer.DangerousGetRowSpan(row)[..width]; PixelOperations.Instance.ToL8Bytes(this.Configuration, pixelsBlackWhiteRow, pixelAsGraySpan, width); - for (int x = 0; x < this.Image.Width; x++) + for (int x = 0; x < this.Width; x++) { int shift = 7 - bitIndex; if (pixelAsGraySpan[x] == 255) diff --git a/src/ImageSharp/Formats/Tiff/Writers/TiffColorWriterFactory.cs b/src/ImageSharp/Formats/Tiff/Writers/TiffColorWriterFactory.cs index 96c8aeb324..31a1b0e414 100644 --- a/src/ImageSharp/Formats/Tiff/Writers/TiffColorWriterFactory.cs +++ b/src/ImageSharp/Formats/Tiff/Writers/TiffColorWriterFactory.cs @@ -13,6 +13,7 @@ internal static class TiffColorWriterFactory public static TiffBaseColorWriter Create( TiffPhotometricInterpretation? photometricInterpretation, ImageFrame image, + Size encodingSize, IQuantizer quantizer, IPixelSamplingStrategy pixelSamplingStrategy, MemoryAllocator memoryAllocator, @@ -20,22 +21,15 @@ internal static class TiffColorWriterFactory TiffEncoderEntriesCollector entriesCollector, int bitsPerPixel) where TPixel : unmanaged, IPixel - { - switch (photometricInterpretation) + => photometricInterpretation switch { - case TiffPhotometricInterpretation.PaletteColor: - return new TiffPaletteWriter(image, quantizer, pixelSamplingStrategy, memoryAllocator, configuration, entriesCollector, bitsPerPixel); - case TiffPhotometricInterpretation.BlackIsZero: - case TiffPhotometricInterpretation.WhiteIsZero: - return bitsPerPixel switch - { - 1 => new TiffBiColorWriter(image, memoryAllocator, configuration, entriesCollector), - 16 => new TiffGrayL16Writer(image, memoryAllocator, configuration, entriesCollector), - _ => new TiffGrayWriter(image, memoryAllocator, configuration, entriesCollector) - }; - - default: - return new TiffRgbWriter(image, memoryAllocator, configuration, entriesCollector); - } - } + TiffPhotometricInterpretation.PaletteColor => new TiffPaletteWriter(image, encodingSize, quantizer, pixelSamplingStrategy, memoryAllocator, configuration, entriesCollector, bitsPerPixel), + TiffPhotometricInterpretation.BlackIsZero or TiffPhotometricInterpretation.WhiteIsZero => bitsPerPixel switch + { + 1 => new TiffBiColorWriter(image, encodingSize, memoryAllocator, configuration, entriesCollector), + 16 => new TiffGrayL16Writer(image, encodingSize, memoryAllocator, configuration, entriesCollector), + _ => new TiffGrayWriter(image, encodingSize, memoryAllocator, configuration, entriesCollector) + }, + _ => new TiffRgbWriter(image, encodingSize, memoryAllocator, configuration, entriesCollector), + }; } diff --git a/src/ImageSharp/Formats/Tiff/Writers/TiffCompositeColorWriter{TPixel}.cs b/src/ImageSharp/Formats/Tiff/Writers/TiffCompositeColorWriter{TPixel}.cs index 007857148a..67dde493c5 100644 --- a/src/ImageSharp/Formats/Tiff/Writers/TiffCompositeColorWriter{TPixel}.cs +++ b/src/ImageSharp/Formats/Tiff/Writers/TiffCompositeColorWriter{TPixel}.cs @@ -12,35 +12,36 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Writers; /// /// The base class for composite color types: 8-bit gray, 24-bit RGB (4-bit gray, 16-bit (565/555) RGB, 32-bit RGB, CMYK, YCbCr). /// +/// The tpe of pixel format. internal abstract class TiffCompositeColorWriter : TiffBaseColorWriter where TPixel : unmanaged, IPixel { private IMemoryOwner rowBuffer; - protected TiffCompositeColorWriter(ImageFrame image, MemoryAllocator memoryAllocator, Configuration configuration, TiffEncoderEntriesCollector entriesCollector) - : base(image, memoryAllocator, configuration, entriesCollector) + protected TiffCompositeColorWriter( + ImageFrame image, + Size encodingSize, + MemoryAllocator memoryAllocator, + Configuration configuration, + TiffEncoderEntriesCollector entriesCollector) + : base(image, encodingSize, memoryAllocator, configuration, entriesCollector) { } protected override void EncodeStrip(int y, int height, TiffBaseCompressor compressor) { - if (this.rowBuffer == null) - { - this.rowBuffer = this.MemoryAllocator.Allocate(this.BytesPerRow * height); - } - - this.rowBuffer.Clear(); + (this.rowBuffer ??= this.MemoryAllocator.Allocate(this.BytesPerRow * height)).Clear(); Span outputRowSpan = this.rowBuffer.GetSpan()[..(this.BytesPerRow * height)]; - int width = this.Image.Width; + int width = this.Width; using IMemoryOwner stripPixelBuffer = this.MemoryAllocator.Allocate(height * width); Span stripPixels = stripPixelBuffer.GetSpan(); int lastRow = y + height; int stripPixelsRowIdx = 0; for (int row = y; row < lastRow; row++) { - Span stripPixelsRow = this.Image.PixelBuffer.DangerousGetRowSpan(row); + Span stripPixelsRow = this.Image.PixelBuffer.DangerousGetRowSpan(row)[..width]; stripPixelsRow.CopyTo(stripPixels.Slice(stripPixelsRowIdx * width, width)); stripPixelsRowIdx++; } diff --git a/src/ImageSharp/Formats/Tiff/Writers/TiffGrayL16Writer{TPixel}.cs b/src/ImageSharp/Formats/Tiff/Writers/TiffGrayL16Writer{TPixel}.cs index 3e0e074e95..857f551f41 100644 --- a/src/ImageSharp/Formats/Tiff/Writers/TiffGrayL16Writer{TPixel}.cs +++ b/src/ImageSharp/Formats/Tiff/Writers/TiffGrayL16Writer{TPixel}.cs @@ -9,8 +9,13 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Writers; internal sealed class TiffGrayL16Writer : TiffCompositeColorWriter where TPixel : unmanaged, IPixel { - public TiffGrayL16Writer(ImageFrame image, MemoryAllocator memoryAllocator, Configuration configuration, TiffEncoderEntriesCollector entriesCollector) - : base(image, memoryAllocator, configuration, entriesCollector) + public TiffGrayL16Writer( + ImageFrame image, + Size encodingSize, + MemoryAllocator memoryAllocator, + Configuration configuration, + TiffEncoderEntriesCollector entriesCollector) + : base(image, encodingSize, memoryAllocator, configuration, entriesCollector) { } @@ -18,5 +23,6 @@ internal sealed class TiffGrayL16Writer : TiffCompositeColorWriter 16; /// - protected override void EncodePixels(Span pixels, Span buffer) => PixelOperations.Instance.ToL16Bytes(this.Configuration, pixels, buffer, pixels.Length); + protected override void EncodePixels(Span pixels, Span buffer) + => PixelOperations.Instance.ToL16Bytes(this.Configuration, pixels, buffer, pixels.Length); } diff --git a/src/ImageSharp/Formats/Tiff/Writers/TiffGrayWriter{TPixel}.cs b/src/ImageSharp/Formats/Tiff/Writers/TiffGrayWriter{TPixel}.cs index b2a476b9aa..4a037f0d33 100644 --- a/src/ImageSharp/Formats/Tiff/Writers/TiffGrayWriter{TPixel}.cs +++ b/src/ImageSharp/Formats/Tiff/Writers/TiffGrayWriter{TPixel}.cs @@ -9,8 +9,13 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Writers; internal sealed class TiffGrayWriter : TiffCompositeColorWriter where TPixel : unmanaged, IPixel { - public TiffGrayWriter(ImageFrame image, MemoryAllocator memoryAllocator, Configuration configuration, TiffEncoderEntriesCollector entriesCollector) - : base(image, memoryAllocator, configuration, entriesCollector) + public TiffGrayWriter( + ImageFrame image, + Size encodingSize, + MemoryAllocator memoryAllocator, + Configuration configuration, + TiffEncoderEntriesCollector entriesCollector) + : base(image, encodingSize, memoryAllocator, configuration, entriesCollector) { } @@ -18,5 +23,6 @@ internal sealed class TiffGrayWriter : TiffCompositeColorWriter public override int BitsPerPixel => 8; /// - protected override void EncodePixels(Span pixels, Span buffer) => PixelOperations.Instance.ToL8Bytes(this.Configuration, pixels, buffer, pixels.Length); + protected override void EncodePixels(Span pixels, Span buffer) + => PixelOperations.Instance.ToL8Bytes(this.Configuration, pixels, buffer, pixels.Length); } diff --git a/src/ImageSharp/Formats/Tiff/Writers/TiffPaletteWriter{TPixel}.cs b/src/ImageSharp/Formats/Tiff/Writers/TiffPaletteWriter{TPixel}.cs index d9a0960d9b..da66373631 100644 --- a/src/ImageSharp/Formats/Tiff/Writers/TiffPaletteWriter{TPixel}.cs +++ b/src/ImageSharp/Formats/Tiff/Writers/TiffPaletteWriter{TPixel}.cs @@ -23,13 +23,14 @@ internal sealed class TiffPaletteWriter : TiffBaseColorWriter public TiffPaletteWriter( ImageFrame frame, + Size encodingSize, IQuantizer quantizer, IPixelSamplingStrategy pixelSamplingStrategy, MemoryAllocator memoryAllocator, Configuration configuration, TiffEncoderEntriesCollector entriesCollector, int bitsPerPixel) - : base(frame, memoryAllocator, configuration, entriesCollector) + : base(frame, encodingSize, memoryAllocator, configuration, entriesCollector) { DebugGuard.NotNull(quantizer, nameof(quantizer)); DebugGuard.NotNull(quantizer, nameof(pixelSamplingStrategy)); @@ -49,7 +50,7 @@ internal sealed class TiffPaletteWriter : TiffBaseColorWriter }); frameQuantizer.BuildPalette(pixelSamplingStrategy, frame); - this.quantizedFrame = frameQuantizer.QuantizeFrame(frame, frame.Bounds()); + this.quantizedFrame = frameQuantizer.QuantizeFrame(frame, new Rectangle(Point.Empty, encodingSize)); this.AddColorMapTag(); } @@ -60,7 +61,7 @@ internal sealed class TiffPaletteWriter : TiffBaseColorWriter /// protected override void EncodeStrip(int y, int height, TiffBaseCompressor compressor) { - int width = this.Image.Width; + int width = this.quantizedFrame.Width; if (this.BitsPerPixel == 4) { diff --git a/src/ImageSharp/Formats/Tiff/Writers/TiffRgbWriter{TPixel}.cs b/src/ImageSharp/Formats/Tiff/Writers/TiffRgbWriter{TPixel}.cs index 3494b6ceae..93c46a92e4 100644 --- a/src/ImageSharp/Formats/Tiff/Writers/TiffRgbWriter{TPixel}.cs +++ b/src/ImageSharp/Formats/Tiff/Writers/TiffRgbWriter{TPixel}.cs @@ -9,8 +9,13 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Writers; internal sealed class TiffRgbWriter : TiffCompositeColorWriter where TPixel : unmanaged, IPixel { - public TiffRgbWriter(ImageFrame image, MemoryAllocator memoryAllocator, Configuration configuration, TiffEncoderEntriesCollector entriesCollector) - : base(image, memoryAllocator, configuration, entriesCollector) + public TiffRgbWriter( + ImageFrame image, + Size encodingSize, + MemoryAllocator memoryAllocator, + Configuration configuration, + TiffEncoderEntriesCollector entriesCollector) + : base(image, encodingSize, memoryAllocator, configuration, entriesCollector) { } @@ -18,5 +23,6 @@ internal sealed class TiffRgbWriter : TiffCompositeColorWriter public override int BitsPerPixel => 24; /// - protected override void EncodePixels(Span pixels, Span buffer) => PixelOperations.Instance.ToRgb24Bytes(this.Configuration, pixels, buffer, pixels.Length); + protected override void EncodePixels(Span pixels, Span buffer) + => PixelOperations.Instance.ToRgb24Bytes(this.Configuration, pixels, buffer, pixels.Length); } diff --git a/src/ImageSharp/Formats/Webp/AlphaDecoder.cs b/src/ImageSharp/Formats/Webp/AlphaDecoder.cs index eccd9ede8e..a9e63a3d0e 100644 --- a/src/ImageSharp/Formats/Webp/AlphaDecoder.cs +++ b/src/ImageSharp/Formats/Webp/AlphaDecoder.cs @@ -183,7 +183,7 @@ internal class AlphaDecoder : IDisposable else { this.LosslessDecoder.DecodeImageData(this.Vp8LDec, this.Vp8LDec.Pixels.Memory.Span); - this.ExtractAlphaRows(this.Vp8LDec); + this.ExtractAlphaRows(this.Vp8LDec, this.Width); } } @@ -257,14 +257,15 @@ internal class AlphaDecoder : IDisposable /// Once the image-stream is decoded into ARGB color values, the transparency information will be extracted from the green channel of the ARGB quadruplet. /// /// The VP8L decoder. - private void ExtractAlphaRows(Vp8LDecoder dec) + /// The image width. + private void ExtractAlphaRows(Vp8LDecoder dec, int width) { int numRowsToProcess = dec.Height; - int width = dec.Width; Span input = dec.Pixels.Memory.Span; Span output = this.Alpha.Memory.Span; // Extract alpha (which is stored in the green plane). + // the final width (!= dec->width_) int pixelCount = width * numRowsToProcess; WebpLosslessDecoder.ApplyInverseTransforms(dec, input, this.memoryAllocator); ExtractGreen(input, output, pixelCount); diff --git a/src/ImageSharp/Formats/Webp/Lossless/LosslessUtils.cs b/src/ImageSharp/Formats/Webp/Lossless/LosslessUtils.cs index 024adb7c23..5287f0b753 100644 --- a/src/ImageSharp/Formats/Webp/Lossless/LosslessUtils.cs +++ b/src/ImageSharp/Formats/Webp/Lossless/LosslessUtils.cs @@ -269,7 +269,11 @@ internal static unsafe class LosslessUtils /// /// The transform data contains color table size and the entries in the color table. /// The pixel data to apply the reverse transform on. - public static void ColorIndexInverseTransform(Vp8LTransform transform, Span pixelData) + /// The resulting pixel data with the reversed transformation data. + public static void ColorIndexInverseTransform( + Vp8LTransform transform, + Span pixelData, + Span outputSpan) { int bitsPerPixel = 8 >> transform.Bits; int width = transform.XSize; @@ -282,7 +286,6 @@ internal static unsafe class LosslessUtils int countMask = pixelsPerByte - 1; int bitMask = (1 << bitsPerPixel) - 1; - uint[] decodedPixelData = new uint[width * height]; int pixelDataPos = 0; for (int y = 0; y < height; y++) { @@ -298,12 +301,12 @@ internal static unsafe class LosslessUtils packedPixels = GetArgbIndex(pixelData[pixelDataPos++]); } - decodedPixelData[decodedPixels++] = colorMap[(int)(packedPixels & bitMask)]; + outputSpan[decodedPixels++] = colorMap[(int)(packedPixels & bitMask)]; packedPixels >>= bitsPerPixel; } } - decodedPixelData.AsSpan().CopyTo(pixelData); + outputSpan.CopyTo(pixelData); } else { diff --git a/src/ImageSharp/Formats/Webp/Lossless/Vp8LEncoder.cs b/src/ImageSharp/Formats/Webp/Lossless/Vp8LEncoder.cs index 244691e77e..e077249696 100644 --- a/src/ImageSharp/Formats/Webp/Lossless/Vp8LEncoder.cs +++ b/src/ImageSharp/Formats/Webp/Lossless/Vp8LEncoder.cs @@ -236,7 +236,7 @@ internal class Vp8LEncoder : IDisposable /// public Vp8LHashChain HashChain { get; } - public WebpVp8X EncodeHeader(Image image, Stream stream, bool hasAnimation) + public WebpVp8X EncodeHeader(Image image, Stream stream, bool hasAnimation, ushort? repeatCount) where TPixel : unmanaged, IPixel { // 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); } diff --git a/src/ImageSharp/Formats/Webp/Lossless/WebpLosslessDecoder.cs b/src/ImageSharp/Formats/Webp/Lossless/WebpLosslessDecoder.cs index e4c2a7ddf6..6de3ae7497 100644 --- a/src/ImageSharp/Formats/Webp/Lossless/WebpLosslessDecoder.cs +++ b/src/ImageSharp/Formats/Webp/Lossless/WebpLosslessDecoder.cs @@ -684,6 +684,7 @@ internal sealed class WebpLosslessDecoder List transforms = decoder.Transforms; for (int i = transforms.Count - 1; i >= 0; i--) { + // TODO: Review these 1D allocations. They could conceivably exceed limits. Vp8LTransform transform = transforms[i]; switch (transform.TransformType) { @@ -701,7 +702,11 @@ internal sealed class WebpLosslessDecoder LosslessUtils.ColorSpaceInverseTransform(transform, pixelData); break; case Vp8LTransformType.ColorIndexingTransform: - LosslessUtils.ColorIndexInverseTransform(transform, pixelData); + using (IMemoryOwner output = memoryAllocator.Allocate(transform.XSize * transform.YSize, AllocationOptions.Clean)) + { + LosslessUtils.ColorIndexInverseTransform(transform, pixelData, output.GetSpan()); + } + break; } } diff --git a/src/ImageSharp/Formats/Webp/Lossy/Vp8Encoder.cs b/src/ImageSharp/Formats/Webp/Lossy/Vp8Encoder.cs index 3ad72f7d00..d22d357fe3 100644 --- a/src/ImageSharp/Formats/Webp/Lossy/Vp8Encoder.cs +++ b/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); } diff --git a/src/ImageSharp/Formats/Webp/Lossy/Vp8Encoding.cs b/src/ImageSharp/Formats/Webp/Lossy/Vp8Encoding.cs index 82f00e8760..c645816d4b 100644 --- a/src/ImageSharp/Formats/Webp/Lossy/Vp8Encoding.cs +++ b/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 dst, Span 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 dst, Span 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 dst, Span left, Span top, int size) { - if (left != default) + if (!left.IsEmpty) { - if (top != default) + if (!top.IsEmpty) { Span 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. diff --git a/src/ImageSharp/Formats/Webp/Lossy/YuvConversion.cs b/src/ImageSharp/Formats/Webp/Lossy/YuvConversion.cs index f8e664ed03..40146c6af8 100644 --- a/src/ImageSharp/Formats/Webp/Lossy/YuvConversion.cs +++ b/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 tmpTopDst = ru[(4 * 32)..]; Span tmpBottomDst = tmpTopDst[(4 * 32)..]; Span tmpTop = tmpBottomDst[(4 * 32)..]; - Span tmpBottom = (bottomY == default) ? null : tmpTop[32..]; + Span 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)..]); } diff --git a/src/ImageSharp/Formats/Webp/WebpAnimationDecoder.cs b/src/ImageSharp/Formats/Webp/WebpAnimationDecoder.cs index 72405e480e..bfaaa831e1 100644 --- a/src/ImageSharp/Formats/Webp/WebpAnimationDecoder.cs +++ b/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; } /// diff --git a/src/ImageSharp/Formats/Webp/WebpEncoder.cs b/src/ImageSharp/Formats/Webp/WebpEncoder.cs index bc93df3a5b..226719c792 100644 --- a/src/ImageSharp/Formats/Webp/WebpEncoder.cs +++ b/src/ImageSharp/Formats/Webp/WebpEncoder.cs @@ -6,7 +6,7 @@ namespace SixLabors.ImageSharp.Formats.Webp; /// /// Image encoder for writing an image to a stream in the Webp format. /// -public sealed class WebpEncoder : ImageEncoder +public sealed class WebpEncoder : AnimatedImageEncoder { /// /// Gets the webp file format used. Either lossless or lossy. diff --git a/src/ImageSharp/Formats/Webp/WebpEncoderCore.cs b/src/ImageSharp/Formats/Webp/WebpEncoderCore.cs index e37462fda4..37d2ae0a19 100644 --- a/src/ImageSharp/Formats/Webp/WebpEncoderCore.cs +++ b/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 /// private readonly WebpFileFormatType? fileFormat; + /// + /// 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 . + /// + private readonly Color? backgroundColor; + + /// + /// The number of times any animation is repeated. + /// + private readonly ushort? repeatCount; + /// /// The global configuration. /// @@ -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; } /// @@ -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 previousFrame = image.Frames.RootFrame; @@ -156,20 +170,28 @@ 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. - using ImageFrame encodingFrame = new(image.Configuration, previousFrame.Size()); + using ImageFrame encodingFrame = new(image.Configuration, previousFrame.Size); for (int i = 1; i < image.Frames.Count; i++) { + if (cancellationToken.IsCancellationRequested) + { + break; + } + ImageFrame? prev = previousDisposal == FrameDisposalMode.RestoreToBackground ? null : previousFrame; ImageFrame currentFrame = image.Frames[i]; ImageFrame? 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,22 +251,30 @@ internal sealed class WebpEncoderCore // Encode the first frame. ImageFrame 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); // Encode additional frames // This frame is reused to store de-duplicated pixel buffers. - using ImageFrame encodingFrame = new(image.Configuration, previousFrame.Size()); + using ImageFrame encodingFrame = new(image.Configuration, previousFrame.Size); for (int i = 1; i < image.Frames.Count; i++) { + if (cancellationToken.IsCancellationRequested) + { + break; + } + ImageFrame? prev = previousDisposal == FrameDisposalMode.RestoreToBackground ? null : previousFrame; ImageFrame currentFrame = image.Frames[i]; ImageFrame? 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); diff --git a/src/ImageSharp/Formats/Webp/WebpFrameMetadata.cs b/src/ImageSharp/Formats/Webp/WebpFrameMetadata.cs index 45e182d223..3f976a6401 100644 --- a/src/ImageSharp/Formats/Webp/WebpFrameMetadata.cs +++ b/src/ImageSharp/Formats/Webp/WebpFrameMetadata.cs @@ -1,6 +1,8 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. +using SixLabors.ImageSharp.PixelFormats; + namespace SixLabors.ImageSharp.Formats.Webp; /// @@ -22,19 +24,21 @@ public class WebpFrameMetadata : IFormatFrameMetadata private WebpFrameMetadata(WebpFrameMetadata other) { this.FrameDelay = other.FrameDelay; - this.DisposalMethod = other.DisposalMethod; - this.BlendMethod = other.BlendMethod; + this.DisposalMode = other.DisposalMode; + this.BlendMode = other.BlendMode; } /// - /// 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. /// - public FrameBlendMode BlendMethod { get; set; } + public FrameBlendMode BlendMode { get; set; } /// - /// 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. /// - public FrameDisposalMode DisposalMethod { get; set; } + public FrameDisposalMode DisposalMode { get; set; } /// /// Gets or sets the frame duration. The time to wait before displaying the next frame, @@ -47,8 +51,8 @@ public class WebpFrameMetadata : IFormatFrameMetadata => new() { FrameDelay = (uint)metadata.Duration.TotalMilliseconds, - BlendMethod = metadata.BlendMode, - DisposalMethod = GetMode(metadata.DisposalMode) + BlendMode = metadata.BlendMode, + DisposalMode = GetMode(metadata.DisposalMode) }; /// @@ -57,10 +61,16 @@ public class WebpFrameMetadata : IFormatFrameMetadata { ColorTableMode = FrameColorTableMode.Global, Duration = TimeSpan.FromMilliseconds(this.FrameDelay), - DisposalMode = this.DisposalMethod, - BlendMode = this.BlendMethod, + DisposalMode = this.DisposalMode, + BlendMode = this.BlendMode, }; + /// + public void AfterFrameApply(ImageFrame source, ImageFrame destination) + where TPixel : unmanaged, IPixel + { + } + /// IDeepCloneable IDeepCloneable.DeepClone() => this.DeepClone(); diff --git a/src/ImageSharp/Formats/Webp/WebpMetadata.cs b/src/ImageSharp/Formats/Webp/WebpMetadata.cs index 33ebbbf6dc..db57bd8f27 100644 --- a/src/ImageSharp/Formats/Webp/WebpMetadata.cs +++ b/src/ImageSharp/Formats/Webp/WebpMetadata.cs @@ -145,6 +145,12 @@ public class WebpMetadata : IFormatMetadata BackgroundColor = this.BackgroundColor }; + /// + public void AfterImageApply(Image destination) + where TPixel : unmanaged, IPixel + { + } + /// IDeepCloneable IDeepCloneable.DeepClone() => this.DeepClone(); diff --git a/src/ImageSharp/IO/ChunkedMemoryStream.cs b/src/ImageSharp/IO/ChunkedMemoryStream.cs index 2534548141..760d1d3345 100644 --- a/src/ImageSharp/IO/ChunkedMemoryStream.cs +++ b/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; /// 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; - /// /// Initializes a new instance of the class. /// /// The memory allocator. public ChunkedMemoryStream(MemoryAllocator allocator) - { - this.allocatorCapacity = allocator.GetBufferCapacityInBytes(); - this.allocator = allocator; - } + => this.memoryChunkBuffer = new(allocator); /// 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); } } /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] + public override void Flush() + { + } + + /// 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; } /// @@ -188,39 +90,13 @@ internal sealed class ChunkedMemoryStream : Stream => throw new NotSupportedException(); /// - 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); - } - } - - /// - public override void Flush() + public override int ReadByte() { + Unsafe.SkipInit(out byte b); + return this.Read(MemoryMarshal.CreateSpan(ref b, 1)) == 1 ? b : -1; } /// - [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)); } /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public override int Read(Span buffer) => this.ReadImpl(buffer); - - private int ReadImpl(Span buffer) + public override int Read(Span 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 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; - } - - /// - [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 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; } /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] + public override void WriteByte(byte value) + => this.Write(MemoryMarshal.CreateSpan(ref value, 1)); + + /// 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)); } /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public override void Write(ReadOnlySpan buffer) => this.WriteImpl(buffer); - - private void WriteImpl(ReadOnlySpan buffer) + public override void Write(ReadOnlySpan 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 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; } - /// - public override void WriteByte(byte value) + /// + /// Writes the entire contents of this memory stream to another stream. + /// + /// The stream to write this memory stream to. + /// is . + /// The current or target stream is closed. + 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 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; } /// - /// Copy entire buffer into an array. + /// Writes the stream contents to a byte array, regardless of the property. /// - /// The . + /// A new . 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; } - /// - /// Write remainder of this stream to another stream. - /// - /// The stream to write to. - public void WriteTo(Stream stream) + /// + 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 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 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 buffer = this.allocator.Allocate(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 buffer = + this.allocator.Allocate(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 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); - } } } diff --git a/src/ImageSharp/Image.Decode.cs b/src/ImageSharp/Image.Decode.cs index 7f58c6ecd8..d2ee0f9061 100644 --- a/src/ImageSharp/Image.Decode.cs +++ b/src/ImageSharp/Image.Decode.cs @@ -128,21 +128,18 @@ public abstract partial class Image // Does the given stream contain enough data to fit in the header for the format // and does that data match the format specification? // Individual formats should still check since they are public. - IImageFormat? format = null; foreach (IImageFormatDetector formatDetector in configuration.ImageFormatsManager.FormatDetectors) { if (formatDetector.HeaderSize <= headersBuffer.Length && formatDetector.TryDetectFormat(headersBuffer, out IImageFormat? attemptFormat)) { - format = attemptFormat; + return attemptFormat; } } - if (format is null) - { - ImageFormatManager.ThrowInvalidDecoder(configuration.ImageFormatsManager); - } + ImageFormatManager.ThrowInvalidDecoder(configuration.ImageFormatsManager); - return format; + // Need to write this otherwise compiler is not happy + return null; } /// diff --git a/src/ImageSharp/Image.cs b/src/ImageSharp/Image.cs index d4f773abe1..07b40a41a1 100644 --- a/src/ImageSharp/Image.cs +++ b/src/ImageSharp/Image.cs @@ -72,12 +72,12 @@ public abstract partial class Image : IDisposable, IConfigurationProvider /// /// Gets any metadata associated with the image. /// - public ImageMetadata Metadata { get; } + public ImageMetadata Metadata { get; private set; } /// /// Gets the size of the image in px units. /// - public Size Size { get; internal set; } + public Size Size { get; private set; } /// /// Gets the bounds of the image. @@ -185,6 +185,12 @@ public abstract partial class Image : IDisposable, IConfigurationProvider /// The . protected void UpdateSize(Size size) => this.Size = size; + /// + /// Updates the metadata of the image after mutation. + /// + /// The . + protected void UpdateMetadata(ImageMetadata metadata) => this.Metadata = metadata; + /// /// Disposes the object and frees resources for the Garbage Collector. /// diff --git a/src/ImageSharp/ImageFrame.cs b/src/ImageSharp/ImageFrame.cs index 2558e1a13a..fdde5019e1 100644 --- a/src/ImageSharp/ImageFrame.cs +++ b/src/ImageSharp/ImageFrame.cs @@ -25,25 +25,24 @@ public abstract partial class ImageFrame : IConfigurationProvider, IDisposable protected ImageFrame(Configuration configuration, int width, int height, ImageFrameMetadata metadata) { this.Configuration = configuration; - this.Width = width; - this.Height = height; + this.Size = new(width, height); this.Metadata = metadata; } /// - /// Gets the width. + /// Gets the frame width in px units. /// - public int Width { get; private set; } + public int Width => this.Size.Width; /// - /// Gets the height. + /// Gets the frame height in px units. /// - public int Height { get; private set; } + public int Height => this.Size.Height; /// /// Gets the metadata of the frame. /// - public ImageFrameMetadata Metadata { get; } + public ImageFrameMetadata Metadata { get; private set; } /// public Configuration Configuration { get; } @@ -51,8 +50,7 @@ public abstract partial class ImageFrame : IConfigurationProvider, IDisposable /// /// Gets the size of the frame. /// - /// The - public Size Size() => new(this.Width, this.Height); + public Size Size { get; private set; } /// /// Gets the bounds of the frame. @@ -77,12 +75,14 @@ public abstract partial class ImageFrame : IConfigurationProvider, IDisposable where TDestinationPixel : unmanaged, IPixel; /// - /// Updates the size of the image frame. + /// Updates the size of the image frame after mutation. /// - /// The size. - internal void UpdateSize(Size size) - { - this.Width = size.Width; - this.Height = size.Height; - } + /// The . + protected void UpdateSize(Size size) => this.Size = size; + + /// + /// Updates the metadata of the image frame after mutation. + /// + /// The . + protected void UpdateMetadata(ImageFrameMetadata metadata) => this.Metadata = metadata; } diff --git a/src/ImageSharp/ImageFrameCollection{TPixel}.cs b/src/ImageSharp/ImageFrameCollection{TPixel}.cs index e927fb0fac..ad7d719744 100644 --- a/src/ImageSharp/ImageFrameCollection{TPixel}.cs +++ b/src/ImageSharp/ImageFrameCollection{TPixel}.cs @@ -414,7 +414,7 @@ public sealed class ImageFrameCollection : ImageFrameCollection, IEnumer { ImageFrame result = new( this.parent.Configuration, - source.Size(), + source.Size, source.Metadata.DeepClone()); source.CopyPixelsTo(result.PixelBuffer.FastMemoryGroup); return result; diff --git a/src/ImageSharp/ImageFrame{TPixel}.cs b/src/ImageSharp/ImageFrame{TPixel}.cs index 0b6354d05d..2287f65cd8 100644 --- a/src/ImageSharp/ImageFrame{TPixel}.cs +++ b/src/ImageSharp/ImageFrame{TPixel}.cs @@ -322,7 +322,7 @@ public sealed class ImageFrame : ImageFrame, IPixelSource /// ImageFrame{TPixel}.CopyTo(): target must be of the same size! internal void CopyTo(Buffer2D target) { - if (this.Size() != target.Size()) + if (this.Size != target.Size()) { throw new ArgumentException("ImageFrame.CopyTo(): target must be of the same size!", nameof(target)); } @@ -331,17 +331,29 @@ public sealed class ImageFrame : ImageFrame, IPixelSource } /// - /// Switches the buffers used by the image and the pixelSource meaning that the Image will "own" the buffer from the pixelSource and the pixelSource will now own the Images buffer. + /// Switches the buffers used by the image and the pixel source meaning that the Image will "own" the buffer + /// from the pixelSource and the pixel source will now own the Image buffer. /// - /// The pixel source. - internal void SwapOrCopyPixelsBufferFrom(ImageFrame pixelSource) + /// The pixel source. + internal void SwapOrCopyPixelsBufferFrom(ImageFrame source) { - Guard.NotNull(pixelSource, nameof(pixelSource)); + Guard.NotNull(source, nameof(source)); - Buffer2D.SwapOrCopyContent(this.PixelBuffer, pixelSource.PixelBuffer); + Buffer2D.SwapOrCopyContent(this.PixelBuffer, source.PixelBuffer); this.UpdateSize(this.PixelBuffer.Size()); } + /// + /// Copies the metadata from the source image. + /// + /// The metadata source. + internal void CopyMetadataFrom(ImageFrame source) + { + Guard.NotNull(source, nameof(source)); + + this.UpdateMetadata(source.Metadata); + } + /// protected override void Dispose(bool disposing) { diff --git a/src/ImageSharp/ImageSharp.csproj b/src/ImageSharp/ImageSharp.csproj index d3c4034717..0d36340bf8 100644 --- a/src/ImageSharp/ImageSharp.csproj +++ b/src/ImageSharp/ImageSharp.csproj @@ -13,6 +13,7 @@ Image Resize Crop Gif Jpg Jpeg Bitmap Pbm Png Tga Tiff WebP NetCore A new, fully featured, fully managed, cross-platform, 2D graphics API for .NET Debug;Release + true @@ -29,14 +30,12 @@ - net8.0 - true + net8.0;net9.0 net8.0 - true diff --git a/src/ImageSharp/Image{TPixel}.cs b/src/ImageSharp/Image{TPixel}.cs index e12631cbd7..7ec7918381 100644 --- a/src/ImageSharp/Image{TPixel}.cs +++ b/src/ImageSharp/Image{TPixel}.cs @@ -160,7 +160,7 @@ public sealed class Image : Image /// /// Gets the root frame. /// - private IPixelSource PixelSourceUnsafe => this.frames.RootFrameUnsafe; + private ImageFrame PixelSourceUnsafe => this.frames.RootFrameUnsafe; /// /// Gets or sets the pixel at the specified position. @@ -324,7 +324,7 @@ public sealed class Image : Image } /// - /// Clones the current image + /// Clones the current image. /// /// Returns a new image with all the same metadata as the original. public Image Clone() => this.Clone(this.Configuration); @@ -395,22 +395,42 @@ public sealed class Image : Image } /// - /// Switches the buffers used by the image and the pixelSource meaning that the Image will "own" the buffer from the pixelSource and the pixelSource will now own the Images buffer. + /// Switches the buffers used by the image and the pixel source meaning that the Image will + /// "own" the buffer from the pixelSource and the pixel source will now own the Image buffer. /// - /// The pixel source. - internal void SwapOrCopyPixelsBuffersFrom(Image pixelSource) + /// The pixel source. + internal void SwapOrCopyPixelsBuffersFrom(Image source) { - Guard.NotNull(pixelSource, nameof(pixelSource)); + Guard.NotNull(source, nameof(source)); this.EnsureNotDisposed(); - ImageFrameCollection sourceFrames = pixelSource.Frames; + ImageFrameCollection sourceFrames = source.Frames; for (int i = 0; i < this.frames.Count; i++) { this.frames[i].SwapOrCopyPixelsBufferFrom(sourceFrames[i]); } - this.UpdateSize(pixelSource.Size); + this.UpdateSize(source.Size); + } + + /// + /// Copies the metadata from the source image. + /// + /// The metadata source. + internal void CopyMetadataFrom(Image source) + { + Guard.NotNull(source, nameof(source)); + + this.EnsureNotDisposed(); + + ImageFrameCollection sourceFrames = source.Frames; + for (int i = 0; i < this.frames.Count; i++) + { + this.frames[i].CopyMetadataFrom(sourceFrames[i]); + } + + this.UpdateMetadata(source.Metadata); } private static Size ValidateFramesAndGetSize(IEnumerable> frames) @@ -419,9 +439,9 @@ public sealed class Image : Image ImageFrame? rootFrame = frames.FirstOrDefault() ?? throw new ArgumentException("Must not be empty.", nameof(frames)); - Size rootSize = rootFrame.Size(); + Size rootSize = rootFrame.Size; - if (frames.Any(f => f.Size() != rootSize)) + if (frames.Any(f => f.Size != rootSize)) { throw new ArgumentException("The provided frames must be of the same size.", nameof(frames)); } diff --git a/src/ImageSharp/Metadata/ImageFrameMetadata.cs b/src/ImageSharp/Metadata/ImageFrameMetadata.cs index 9c0de1edbe..b24aa140fc 100644 --- a/src/ImageSharp/Metadata/ImageFrameMetadata.cs +++ b/src/ImageSharp/Metadata/ImageFrameMetadata.cs @@ -7,6 +7,7 @@ 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.Metadata; @@ -110,16 +111,23 @@ public sealed class ImageFrameMetadata : IDeepCloneable && this.formatMetadata.TryGetValue(this.DecodedImageFormat, out IFormatFrameMetadata? decodedMetadata)) { TFormatFrameMetadata derivedMeta = TFormatFrameMetadata.FromFormatConnectingFrameMetadata(decodedMetadata.ToFormatConnectingFrameMetadata()); - this.formatMetadata[key] = derivedMeta; + this.SetFormatMetadata(key, derivedMeta); return derivedMeta; } TFormatFrameMetadata newMeta = key.CreateDefaultFormatFrameMetadata(); - this.formatMetadata[key] = newMeta; + this.SetFormatMetadata(key, newMeta); return newMeta; } - internal void SetFormatMetadata(IImageFormat key, TFormatFrameMetadata value) + /// + /// Sets the metadata value associated with the specified key. + /// + /// The type of format metadata. + /// The type of format frame metadata. + /// The key of the value to set. + /// The value to set. + public void SetFormatMetadata(IImageFormat key, TFormatFrameMetadata value) where TFormatMetadata : class where TFormatFrameMetadata : class, IFormatFrameMetadata => this.formatMetadata[key] = value; @@ -143,4 +151,23 @@ public sealed class ImageFrameMetadata : IDeepCloneable /// Synchronizes the profiles with the current metadata. /// internal void SynchronizeProfiles() => this.ExifProfile?.Sync(this); + + /// + /// This method is called after a process has been applied to the image frame. + /// + /// The type of pixel format. + /// The source image frame. + /// The destination image frame. + internal void AfterFrameApply(ImageFrame source, ImageFrame destination) + where TPixel : unmanaged, IPixel + { + // Always updated using the full frame dimensions. + // Individual format frame metadata will update with sub region dimensions if appropriate. + this.ExifProfile?.SyncDimensions(destination.Width, destination.Height); + + foreach (KeyValuePair meta in this.formatMetadata) + { + meta.Value.AfterFrameApply(source, destination); + } + } } diff --git a/src/ImageSharp/Metadata/ImageMetadata.cs b/src/ImageSharp/Metadata/ImageMetadata.cs index 37557ba1dc..1961dbf192 100644 --- a/src/ImageSharp/Metadata/ImageMetadata.cs +++ b/src/ImageSharp/Metadata/ImageMetadata.cs @@ -230,6 +230,22 @@ public sealed class ImageMetadata : IDeepCloneable /// internal void SynchronizeProfiles() => this.ExifProfile?.Sync(this); + /// + /// This method is called after a process has been applied to the image. + /// + /// The type of pixel format. + /// The destination image. + internal void AfterImageApply(Image destination) + where TPixel : unmanaged, IPixel + { + this.ExifProfile?.SyncDimensions(destination.Width, destination.Height); + + foreach (KeyValuePair meta in this.formatMetadata) + { + meta.Value.AfterImageApply(destination); + } + } + internal PixelTypeInfo GetDecodedPixelTypeInfo() { // None found. Check if we have a decoded format to convert from. diff --git a/src/ImageSharp/Metadata/Profiles/Exif/ExifProfile.cs b/src/ImageSharp/Metadata/Profiles/Exif/ExifProfile.cs index 41d3c293b6..e91a69444d 100644 --- a/src/ImageSharp/Metadata/Profiles/Exif/ExifProfile.cs +++ b/src/ImageSharp/Metadata/Profiles/Exif/ExifProfile.cs @@ -3,6 +3,7 @@ using System.Diagnostics.CodeAnalysis; using SixLabors.ImageSharp.PixelFormats; +using static System.Runtime.InteropServices.JavaScript.JSType; namespace SixLabors.ImageSharp.Metadata.Profiles.Exif; @@ -298,6 +299,19 @@ public sealed class ExifProfile : IDeepCloneable this.SyncResolution(ExifTag.YResolution, metadata.VerticalResolution); } + internal void SyncDimensions(int width, int height) + { + if (this.TryGetValue(ExifTag.PixelXDimension, out _)) + { + this.SetValue(ExifTag.PixelXDimension, width); + } + + if (this.TryGetValue(ExifTag.PixelYDimension, out _)) + { + this.SetValue(ExifTag.PixelYDimension, height); + } + } + /// /// Synchronizes the profiles with the specified metadata. /// diff --git a/src/ImageSharp/Metadata/Profiles/Exif/ExifWriter.cs b/src/ImageSharp/Metadata/Profiles/Exif/ExifWriter.cs index 1d2dca8700..cf4a421b44 100644 --- a/src/ImageSharp/Metadata/Profiles/Exif/ExifWriter.cs +++ b/src/ImageSharp/Metadata/Profiles/Exif/ExifWriter.cs @@ -241,7 +241,7 @@ internal sealed class ExifWriter return true; } - private static uint GetLength(IList values) + private static uint GetLength(List values) { if (values.Count == 0) { diff --git a/src/ImageSharp/Metadata/Profiles/ICC/DataReader/IccDataReader.TagDataEntry.cs b/src/ImageSharp/Metadata/Profiles/ICC/DataReader/IccDataReader.TagDataEntry.cs index 559f77609a..c1b22e82bf 100644 --- a/src/ImageSharp/Metadata/Profiles/ICC/DataReader/IccDataReader.TagDataEntry.cs +++ b/src/ImageSharp/Metadata/Profiles/ICC/DataReader/IccDataReader.TagDataEntry.cs @@ -142,7 +142,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 diff --git a/src/ImageSharp/Metadata/Profiles/ICC/IccProfile.cs b/src/ImageSharp/Metadata/Profiles/ICC/IccProfile.cs index 12de0f7695..da015b2b07 100644 --- a/src/ImageSharp/Metadata/Profiles/ICC/IccProfile.cs +++ b/src/ImageSharp/Metadata/Profiles/ICC/IccProfile.cs @@ -155,9 +155,9 @@ public sealed class IccProfile : IDeepCloneable } 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; } diff --git a/src/ImageSharp/Processing/AffineTransformBuilder.cs b/src/ImageSharp/Processing/AffineTransformBuilder.cs index 59264698bd..6d1e8aaa55 100644 --- a/src/ImageSharp/Processing/AffineTransformBuilder.cs +++ b/src/ImageSharp/Processing/AffineTransformBuilder.cs @@ -11,8 +11,29 @@ namespace SixLabors.ImageSharp.Processing; /// public class AffineTransformBuilder { - private readonly List> transformMatrixFactories = new(); - private readonly List> boundsMatrixFactories = new(); + private readonly List> transformMatrixFactories = []; + + /// + /// Initializes a new instance of the class. + /// + public AffineTransformBuilder() + : this(TransformSpace.Pixel) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The to use when applying the affine transform. + /// + public AffineTransformBuilder(TransformSpace transformSpace) + => this.TransformSpace = transformSpace; + + /// + /// Gets the to use when applying the affine transform. + /// + public TransformSpace TransformSpace { get; } /// /// Prepends a rotation matrix using the given rotation angle in degrees @@ -31,8 +52,7 @@ public class AffineTransformBuilder /// The . public AffineTransformBuilder PrependRotationRadians(float radians) => this.Prepend( - size => TransformUtils.CreateRotationTransformMatrixRadians(radians, size), - size => TransformUtils.CreateRotationBoundsMatrixRadians(radians, size)); + size => TransformUtils.CreateRotationTransformMatrixRadians(radians, size, this.TransformSpace)); /// /// Prepends a rotation matrix using the given rotation in degrees at the given origin. @@ -68,9 +88,7 @@ public class AffineTransformBuilder /// The amount of rotation, in radians. /// The . public AffineTransformBuilder AppendRotationRadians(float radians) - => this.Append( - size => TransformUtils.CreateRotationTransformMatrixRadians(radians, size), - size => TransformUtils.CreateRotationBoundsMatrixRadians(radians, size)); + => this.Append(size => TransformUtils.CreateRotationTransformMatrixRadians(radians, size, this.TransformSpace)); /// /// Appends a rotation matrix using the given rotation in degrees at the given origin. @@ -145,9 +163,7 @@ public class AffineTransformBuilder /// The Y angle, in degrees. /// The . public AffineTransformBuilder PrependSkewDegrees(float degreesX, float degreesY) - => this.Prepend( - size => TransformUtils.CreateSkewTransformMatrixDegrees(degreesX, degreesY, size), - size => TransformUtils.CreateSkewBoundsMatrixDegrees(degreesX, degreesY, size)); + => this.PrependSkewRadians(GeometryUtilities.DegreeToRadian(degreesX), GeometryUtilities.DegreeToRadian(degreesY)); /// /// Prepends a centered skew matrix from the give angles in radians. @@ -156,9 +172,7 @@ public class AffineTransformBuilder /// The Y angle, in radians. /// The . public AffineTransformBuilder PrependSkewRadians(float radiansX, float radiansY) - => this.Prepend( - size => TransformUtils.CreateSkewTransformMatrixRadians(radiansX, radiansY, size), - size => TransformUtils.CreateSkewBoundsMatrixRadians(radiansX, radiansY, size)); + => this.Prepend(size => TransformUtils.CreateSkewTransformMatrixRadians(radiansX, radiansY, size, this.TransformSpace)); /// /// Prepends a skew matrix using the given angles in degrees at the given origin. @@ -187,9 +201,7 @@ public class AffineTransformBuilder /// The Y angle, in degrees. /// The . public AffineTransformBuilder AppendSkewDegrees(float degreesX, float degreesY) - => this.Append( - size => TransformUtils.CreateSkewTransformMatrixDegrees(degreesX, degreesY, size), - size => TransformUtils.CreateSkewBoundsMatrixDegrees(degreesX, degreesY, size)); + => this.AppendSkewRadians(GeometryUtilities.DegreeToRadian(degreesX), GeometryUtilities.DegreeToRadian(degreesY)); /// /// Appends a centered skew matrix from the give angles in radians. @@ -198,9 +210,7 @@ public class AffineTransformBuilder /// The Y angle, in radians. /// The . public AffineTransformBuilder AppendSkewRadians(float radiansX, float radiansY) - => this.Append( - size => TransformUtils.CreateSkewTransformMatrixRadians(radiansX, radiansY, size), - size => TransformUtils.CreateSkewBoundsMatrixRadians(radiansX, radiansY, size)); + => this.Append(size => TransformUtils.CreateSkewTransformMatrixRadians(radiansX, radiansY, size, this.TransformSpace)); /// /// Appends a skew matrix using the given angles in degrees at the given origin. @@ -267,7 +277,7 @@ public class AffineTransformBuilder public AffineTransformBuilder PrependMatrix(Matrix3x2 matrix) { CheckDegenerate(matrix); - return this.Prepend(_ => matrix, _ => matrix); + return this.Prepend(_ => matrix); } /// @@ -283,7 +293,7 @@ public class AffineTransformBuilder public AffineTransformBuilder AppendMatrix(Matrix3x2 matrix) { CheckDegenerate(matrix); - return this.Append(_ => matrix, _ => matrix); + return this.Append(_ => matrix); } /// @@ -291,7 +301,8 @@ public class AffineTransformBuilder /// /// The source image size. /// The . - public Matrix3x2 BuildMatrix(Size sourceSize) => this.BuildMatrix(new Rectangle(Point.Empty, sourceSize)); + public Matrix3x2 BuildMatrix(Size sourceSize) + => this.BuildMatrix(new Rectangle(Point.Empty, sourceSize)); /// /// Returns the combined transform matrix for a given source rectangle. @@ -335,18 +346,8 @@ public class AffineTransformBuilder /// The . public Size GetTransformedSize(Rectangle sourceRectangle) { - Size size = sourceRectangle.Size; - - // Translate the origin matrix to cater for source rectangle offsets. - Matrix3x2 matrix = Matrix3x2.CreateTranslation(-sourceRectangle.Location); - - foreach (Func factory in this.boundsMatrixFactories) - { - matrix *= factory(size); - CheckDegenerate(matrix); - } - - return TransformUtils.GetTransformedSize(size, matrix); + Matrix3x2 matrix = this.BuildMatrix(sourceRectangle); + return TransformUtils.GetTransformedSize(matrix, sourceRectangle.Size, this.TransformSpace); } private static void CheckDegenerate(Matrix3x2 matrix) @@ -357,17 +358,15 @@ public class AffineTransformBuilder } } - private AffineTransformBuilder Prepend(Func transformFactory, Func boundsFactory) + private AffineTransformBuilder Prepend(Func transformFactory) { this.transformMatrixFactories.Insert(0, transformFactory); - this.boundsMatrixFactories.Insert(0, boundsFactory); return this; } - private AffineTransformBuilder Append(Func transformFactory, Func boundsFactory) + private AffineTransformBuilder Append(Func transformFactory) { this.transformMatrixFactories.Add(transformFactory); - this.boundsMatrixFactories.Add(boundsFactory); return this; } } diff --git a/src/ImageSharp/Processing/Extensions/Convolution/BokehBlurExtensions.cs b/src/ImageSharp/Processing/Extensions/Convolution/BokehBlurExtensions.cs index e3e6f13ed6..71252e0bb0 100644 --- a/src/ImageSharp/Processing/Extensions/Convolution/BokehBlurExtensions.cs +++ b/src/ImageSharp/Processing/Extensions/Convolution/BokehBlurExtensions.cs @@ -44,13 +44,13 @@ public static class BokehBlurExtensions /// Applies a bokeh blur to the image. /// /// The current image processing context. - /// The 'radius' value representing the size of the area to sample. - /// The 'components' value representing the number of kernels to use to approximate the bokeh effect. - /// The gamma highlight factor to use to emphasize bright spots in the source image /// /// The structure that specifies the portion of the image object to alter. /// + /// The 'radius' value representing the size of the area to sample. + /// The 'components' value representing the number of kernels to use to approximate the bokeh effect. + /// The gamma highlight factor to use to emphasize bright spots in the source image /// The . - 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); } diff --git a/src/ImageSharp/Processing/Extensions/Convolution/BoxBlurExtensions.cs b/src/ImageSharp/Processing/Extensions/Convolution/BoxBlurExtensions.cs index 6611af742b..73e40b57aa 100644 --- a/src/ImageSharp/Processing/Extensions/Convolution/BoxBlurExtensions.cs +++ b/src/ImageSharp/Processing/Extensions/Convolution/BoxBlurExtensions.cs @@ -44,10 +44,10 @@ public static class BoxBlurExtensions /// Applies a box blur to the image. /// /// The current image processing context. - /// The 'radius' value representing the size of the area to sample. /// /// The structure that specifies the portion of the image object to alter. /// + /// The 'radius' value representing the size of the area to sample. /// /// The to use when mapping the pixels outside of the border, in X direction. /// @@ -55,9 +55,11 @@ public static class BoxBlurExtensions /// The to use when mapping the pixels outside of the border, in Y direction. /// /// The . - 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); } diff --git a/src/ImageSharp/Processing/Extensions/Convolution/ConvolutionExtensions.cs b/src/ImageSharp/Processing/Extensions/Convolution/ConvolutionExtensions.cs new file mode 100644 index 0000000000..2980ff44f0 --- /dev/null +++ b/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; + +/// +/// Defines general convolution extensions to apply on an +/// using Mutate/Clone. +/// +public static class ConvolutionExtensions +{ + /// + /// Applies a convolution filter to the image. + /// + /// The current image processing context. + /// The convolution kernel to apply. + /// The . + public static IImageProcessingContext Convolve(this IImageProcessingContext source, DenseMatrix kernelXY) + => Convolve(source, kernelXY, false); + + /// + /// Applies a convolution filter to the image. + /// + /// The current image processing context. + /// The convolution kernel to apply. + /// Whether the convolution filter is applied to alpha as well as the color channels. + /// The . + public static IImageProcessingContext Convolve(this IImageProcessingContext source, DenseMatrix kernelXY, bool preserveAlpha) + => Convolve(source, kernelXY, preserveAlpha, BorderWrappingMode.Repeat, BorderWrappingMode.Repeat); + + /// + /// Applies a convolution filter to the image. + /// + /// The current image processing context. + /// The convolution kernel to apply. + /// Whether the convolution filter is applied to alpha as well as the color channels. + /// The to use when mapping the pixels outside of the border, in X direction. + /// The to use when mapping the pixels outside of the border, in Y direction. + /// The . + public static IImageProcessingContext Convolve( + this IImageProcessingContext source, + DenseMatrix kernelXY, + bool preserveAlpha, + BorderWrappingMode borderWrapModeX, + BorderWrappingMode borderWrapModeY) + => source.ApplyProcessor(new ConvolutionProcessor(kernelXY, preserveAlpha, borderWrapModeX, borderWrapModeY)); + + /// + /// Applies a convolution filter to the image. + /// + /// The current image processing context. + /// The rectangle structure that specifies the portion of the image object to alter. + /// The convolution kernel to apply. + /// The . + public static IImageProcessingContext Convolve(this IImageProcessingContext source, Rectangle rectangle, DenseMatrix kernelXY) + => Convolve(source, rectangle, kernelXY, false); + + /// + /// Applies a convolution filter to the image. + /// + /// The current image processing context. + /// The rectangle structure that specifies the portion of the image object to alter. + /// The convolution kernel to apply. + /// Whether the convolution filter is applied to alpha as well as the color channels. + /// The . + public static IImageProcessingContext Convolve(this IImageProcessingContext source, Rectangle rectangle, DenseMatrix kernelXY, bool preserveAlpha) + => Convolve(source, rectangle, kernelXY, preserveAlpha, BorderWrappingMode.Repeat, BorderWrappingMode.Repeat); + + /// + /// Applies a convolution filter to the image. + /// + /// The current image processing context. + /// The rectangle structure that specifies the portion of the image object to alter. + /// The convolution kernel to apply. + /// Whether the convolution filter is applied to alpha as well as the color channels. + /// The to use when mapping the pixels outside of the border, in X direction. + /// The to use when mapping the pixels outside of the border, in Y direction. + /// The . + public static IImageProcessingContext Convolve( + this IImageProcessingContext source, + Rectangle rectangle, + DenseMatrix kernelXY, + bool preserveAlpha, + BorderWrappingMode borderWrapModeX, + BorderWrappingMode borderWrapModeY) + => source.ApplyProcessor(new ConvolutionProcessor(kernelXY, preserveAlpha, borderWrapModeX, borderWrapModeY), rectangle); +} diff --git a/src/ImageSharp/Processing/Extensions/Convolution/DetectEdgesExtensions.cs b/src/ImageSharp/Processing/Extensions/Convolution/DetectEdgesExtensions.cs index b044c3966f..c8fb230559 100644 --- a/src/ImageSharp/Processing/Extensions/Convolution/DetectEdgesExtensions.cs +++ b/src/ImageSharp/Processing/Extensions/Convolution/DetectEdgesExtensions.cs @@ -16,8 +16,8 @@ public static class DetectEdgesExtensions /// /// The current image processing context. /// The . - public static IImageProcessingContext DetectEdges(this IImageProcessingContext source) => - DetectEdges(source, KnownEdgeDetectorKernels.Sobel); + public static IImageProcessingContext DetectEdges(this IImageProcessingContext source) + => DetectEdges(source, KnownEdgeDetectorKernels.Sobel); /// /// Detects any edges within the image. @@ -28,10 +28,8 @@ public static class DetectEdgesExtensions /// The structure that specifies the portion of the image object to alter. /// /// The . - 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); /// /// Detects any edges within the image operating in grayscale mode. @@ -39,10 +37,8 @@ public static class DetectEdgesExtensions /// The current image processing context. /// The 2D edge detector kernel. /// The . - 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); /// /// Detects any edges within the image using a . @@ -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)); /// /// Detects any edges within the image operating in grayscale mode. /// /// The current image processing context. - /// The 2D edge detector kernel. /// /// The structure that specifies the portion of the image object to alter. /// + /// The 2D edge detector kernel. /// The . 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); /// /// Detects any edges within the image using a . /// /// The current image processing context. + /// + /// The structure that specifies the portion of the image object to alter. + /// /// The 2D edge detector kernel. /// /// Whether to convert the image to grayscale before performing edge detection. /// - /// - /// The structure that specifies the portion of the image object to alter. - /// /// The . 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); /// /// Detects any edges within the image operating in grayscale mode. @@ -107,10 +95,8 @@ public static class DetectEdgesExtensions /// The current image processing context. /// The edge detector kernel. /// The . - 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); /// /// Detects any edges within the image using a . @@ -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)); /// /// Detects any edges within the image operating in grayscale mode. /// /// The current image processing context. - /// The edge detector kernel. /// /// The structure that specifies the portion of the image object to alter. /// + /// The edge detector kernel. /// The . 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); /// /// Detects any edges within the image using a . /// /// The current image processing context. + /// + /// The structure that specifies the portion of the image object to alter. + /// /// The edge detector kernel. /// /// Whether to convert the image to grayscale before performing edge detection. /// - /// - /// The structure that specifies the portion of the image object to alter. - /// /// The . 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); /// /// Detects any edges within the image operating in grayscale mode. /// /// The current image processing context. - /// Thecompass edge detector kernel. + /// The compass edge detector kernel. /// The . - 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); /// /// Detects any edges within the image using a . /// /// The current image processing context. - /// Thecompass edge detector kernel. + /// The compass edge detector kernel. /// /// Whether to convert the image to grayscale before performing edge detection. /// @@ -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)); /// /// Detects any edges within the image operating in grayscale mode. /// /// The current image processing context. - /// Thecompass edge detector kernel. /// /// The structure that specifies the portion of the image object to alter. /// + /// The compass edge detector kernel. /// The . 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); /// /// Detects any edges within the image using a . /// /// The current image processing context. - /// Thecompass edge detector kernel. - /// - /// Whether to convert the image to grayscale before performing edge detection. - /// /// /// The structure that specifies the portion of the image object to alter. /// + /// The compass edge detector kernel. + /// + /// Whether to convert the image to grayscale before performing edge detection. + /// /// The . 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); } diff --git a/src/ImageSharp/Processing/Extensions/Convolution/GaussianBlurExtensions.cs b/src/ImageSharp/Processing/Extensions/Convolution/GaussianBlurExtensions.cs index b851482008..d406bf8d10 100644 --- a/src/ImageSharp/Processing/Extensions/Convolution/GaussianBlurExtensions.cs +++ b/src/ImageSharp/Processing/Extensions/Convolution/GaussianBlurExtensions.cs @@ -32,22 +32,25 @@ public static class GaussianBlurExtensions /// Applies a Gaussian blur to the image. /// /// The current image processing context. - /// The 'sigma' value representing the weight of the blur. /// /// The structure that specifies the portion of the image object to alter. /// + /// The 'sigma' value representing the weight of the blur. /// The . - 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); /// /// Applies a Gaussian blur to the image. /// /// The current image processing context. - /// The 'sigma' value representing the weight of the blur. /// /// The structure that specifies the portion of the image object to alter. /// + /// The 'sigma' value representing the weight of the blur. /// /// The to use when mapping the pixels outside of the border, in X direction. /// @@ -55,9 +58,11 @@ public static class GaussianBlurExtensions /// The to use when mapping the pixels outside of the border, in Y direction. /// /// The . - 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); } diff --git a/src/ImageSharp/Processing/Extensions/Convolution/GaussianSharpenExtensions.cs b/src/ImageSharp/Processing/Extensions/Convolution/GaussianSharpenExtensions.cs index 4a94df0963..9470cdbdc0 100644 --- a/src/ImageSharp/Processing/Extensions/Convolution/GaussianSharpenExtensions.cs +++ b/src/ImageSharp/Processing/Extensions/Convolution/GaussianSharpenExtensions.cs @@ -16,8 +16,8 @@ public static class GaussianSharpenExtensions /// /// The current image processing context. /// The . - public static IImageProcessingContext GaussianSharpen(this IImageProcessingContext source) => - source.ApplyProcessor(new GaussianSharpenProcessor()); + public static IImageProcessingContext GaussianSharpen(this IImageProcessingContext source) + => source.ApplyProcessor(new GaussianSharpenProcessor()); /// /// Applies a Gaussian sharpening filter to the image. @@ -25,32 +25,32 @@ public static class GaussianSharpenExtensions /// The current image processing context. /// The 'sigma' value representing the weight of the blur. /// The . - 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)); /// /// Applies a Gaussian sharpening filter to the image. /// /// The current image processing context. - /// The 'sigma' value representing the weight of the blur. /// /// The structure that specifies the portion of the image object to alter. /// + /// The 'sigma' value representing the weight of the blur. /// The . public static IImageProcessingContext GaussianSharpen( this IImageProcessingContext source, - float sigma, - Rectangle rectangle) => + Rectangle rectangle, + float sigma) => source.ApplyProcessor(new GaussianSharpenProcessor(sigma), rectangle); /// /// Applies a Gaussian sharpening filter to the image. /// /// The current image processing context. - /// The 'sigma' value representing the weight of the blur. /// /// The structure that specifies the portion of the image object to alter. /// + /// The 'sigma' value representing the weight of the blur. /// /// The to use when mapping the pixels outside of the border, in X direction. /// @@ -58,9 +58,11 @@ public static class GaussianSharpenExtensions /// The to use when mapping the pixels outside of the border, in Y direction. /// /// The . - 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); } diff --git a/src/ImageSharp/Processing/Extensions/Convolution/MedianBlurExtensions.cs b/src/ImageSharp/Processing/Extensions/Convolution/MedianBlurExtensions.cs index a08a398b75..bc6fef62a6 100644 --- a/src/ImageSharp/Processing/Extensions/Convolution/MedianBlurExtensions.cs +++ b/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. /// /// The . - 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)); /// /// Applies a median blur on the image. /// /// The current image processing context. + /// + /// The structure that specifies the portion of the image object to alter. + /// /// The radius of the area to find the median for. /// /// Whether the filter is applied to alpha as well as the color channels. /// - /// - /// The structure that specifies the portion of the image object to alter. - /// /// The . - 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); } diff --git a/src/ImageSharp/Processing/Processors/CloningImageProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/CloningImageProcessor{TPixel}.cs index aa000a10e7..abe32e3882 100644 --- a/src/ImageSharp/Processing/Processors/CloningImageProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/CloningImageProcessor{TPixel}.cs @@ -48,7 +48,6 @@ public abstract class CloningImageProcessor : ICloningImageProcessor clone = this.CreateTarget(); this.CheckFrameCount(this.Source, clone); - Configuration configuration = this.Configuration; this.BeforeImageApply(clone); for (int i = 0; i < this.Source.Frames.Count; i++) @@ -77,9 +76,10 @@ public abstract class CloningImageProcessor : ICloningImageProcessor)this).CloneAndExecute(); - // We now need to move the pixel data/size data from the clone to the source. + // We now need to move the pixel data/size data and any metadata from the clone to the source. this.CheckFrameCount(this.Source, clone); this.Source.SwapOrCopyPixelsBuffersFrom(clone); + this.Source.CopyMetadataFrom(clone); } finally { @@ -157,7 +157,7 @@ public abstract class CloningImageProcessor : ICloningImageProcessor[source.Frames.Count]; + ImageFrame[] destinationFrames = new ImageFrame[source.Frames.Count]; for (int i = 0; i < destinationFrames.Length; i++) { destinationFrames[i] = new ImageFrame( diff --git a/src/ImageSharp/Processing/Processors/Convolution/BokehBlurProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Convolution/BokehBlurProcessor{TPixel}.cs index e4b0a60ab0..5931b7c402 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/BokehBlurProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/BokehBlurProcessor{TPixel}.cs @@ -96,7 +96,7 @@ internal class BokehBlurProcessor : ImageProcessor } // Create a 0-filled buffer to use to store the result of the component convolutions - using Buffer2D processingBuffer = this.Configuration.MemoryAllocator.Allocate2D(source.Size(), AllocationOptions.Clean); + using Buffer2D processingBuffer = this.Configuration.MemoryAllocator.Allocate2D(source.Size, AllocationOptions.Clean); // Perform the 1D convolutions on all the kernel components and accumulate the results this.OnFrameApplyCore(source, sourceRectangle, this.Configuration, processingBuffer); @@ -134,7 +134,7 @@ internal class BokehBlurProcessor : ImageProcessor Buffer2D processingBuffer) { // Allocate the buffer with the intermediate convolution results - using Buffer2D firstPassBuffer = configuration.MemoryAllocator.Allocate2D(source.Size()); + using Buffer2D firstPassBuffer = configuration.MemoryAllocator.Allocate2D(source.Size); // Unlike in the standard 2 pass convolution processor, we use a rectangle of 1x the interest width // to speedup the actual convolution, by applying bulk pixel conversion and clamping calculation. diff --git a/src/ImageSharp/Processing/Processors/Convolution/Convolution2DProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Convolution/Convolution2DProcessor{TPixel}.cs index 8a7c424815..8f5ddd1690 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/Convolution2DProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/Convolution2DProcessor{TPixel}.cs @@ -62,14 +62,14 @@ internal class Convolution2DProcessor : ImageProcessor 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( + Convolution2DRowOperation operation = new( interest, targetPixels, source.PixelBuffer, diff --git a/src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor{TPixel}.cs index cc6e1e5fb2..cdd8ff8ae6 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor{TPixel}.cs @@ -35,18 +35,48 @@ internal class Convolution2PassProcessor : ImageProcessor Rectangle sourceRectangle, BorderWrappingMode borderWrapModeX, BorderWrappingMode borderWrapModeY) + : this(configuration, kernel, kernel, preserveAlpha, source, sourceRectangle, borderWrapModeX, borderWrapModeY) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The configuration which allows altering default behaviour or extending the library. + /// The 1D convolution kernel. X Direction + /// The 1D convolution kernel. Y Direction + /// Whether the convolution filter is applied to alpha as well as the color channels. + /// The source for the current processor instance. + /// The source area to process for the current processor instance. + /// The to use when mapping the pixels outside of the border, in X direction. + /// The to use when mapping the pixels outside of the border, in Y direction. + public Convolution2PassProcessor( + Configuration configuration, + float[] kernelX, + float[] kernelY, + bool preserveAlpha, + Image 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; } /// - /// Gets the convolution kernel. + /// Gets the convolution kernel. X direction. + /// + public float[] KernelX { get; } + + /// + /// Gets the convolution kernel. Y direction. /// - public float[] Kernel { get; } + public float[] KernelY { get; } /// /// Gets a value indicating whether the convolution filter is applied to alpha as well as the color channels. @@ -66,23 +96,23 @@ internal class Convolution2PassProcessor : ImageProcessor /// protected override void OnFrameApply(ImageFrame source) { - using Buffer2D firstPassPixels = this.Configuration.MemoryAllocator.Allocate2D(source.Size()); + using Buffer2D firstPassPixels = this.Configuration.MemoryAllocator.Allocate2D(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 : ImageProcessor 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 : ImageProcessor } /// - [MethodImpl(InliningOptions.ShortMethod)] + [MethodImpl(MethodImplOptions.AggressiveInlining)] public int GetRequiredBufferLength(Rectangle bounds) => 2 * bounds.Width; @@ -306,7 +336,7 @@ internal class Convolution2PassProcessor : ImageProcessor } /// - [MethodImpl(InliningOptions.ShortMethod)] + [MethodImpl(MethodImplOptions.AggressiveInlining)] public int GetRequiredBufferLength(Rectangle bounds) => 2 * bounds.Width; diff --git a/src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessor.cs b/src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessor.cs new file mode 100644 index 0000000000..995a5164d9 --- /dev/null +++ b/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; + +/// +/// Defines a processor that uses a 2 dimensional matrix to perform convolution against an image. +/// +public class ConvolutionProcessor : IImageProcessor +{ + /// + /// Initializes a new instance of the class. + /// + /// The 2d gradient operator. + /// Whether the convolution filter is applied to alpha as well as the color channels. + /// The to use when mapping the pixels outside of the border, in X direction. + /// The to use when mapping the pixels outside of the border, in Y direction. + public ConvolutionProcessor( + in DenseMatrix kernelXY, + bool preserveAlpha, + BorderWrappingMode borderWrapModeX, + BorderWrappingMode borderWrapModeY) + { + this.KernelXY = kernelXY; + this.PreserveAlpha = preserveAlpha; + this.BorderWrapModeX = borderWrapModeX; + this.BorderWrapModeY = borderWrapModeY; + } + + /// + /// Gets the 2d convolution kernel. + /// + public DenseMatrix KernelXY { get; } + + /// + /// Gets a value indicating whether the convolution filter is applied to alpha as well as the color channels. + /// + public bool PreserveAlpha { get; } + + /// + /// Gets the to use when mapping the pixels outside of the border, in X direction. + /// + public BorderWrappingMode BorderWrapModeX { get; } + + /// + /// Gets the to use when mapping the pixels outside of the border, in Y direction. + /// + public BorderWrappingMode BorderWrapModeY { get; } + + /// + public IImageProcessor CreatePixelSpecificProcessor(Configuration configuration, Image source, Rectangle sourceRectangle) + where TPixel : unmanaged, + IPixel + { + if (this.KernelXY.TryGetLinearlySeparableComponents(out float[]? kernelX, out float[]? kernelY)) + { + return new Convolution2PassProcessor( + configuration, + kernelX, + kernelY, + this.PreserveAlpha, + source, + sourceRectangle, + this.BorderWrapModeX, + this.BorderWrapModeY); + } + + return new ConvolutionProcessor( + configuration, + this.KernelXY, + this.PreserveAlpha, + source, + sourceRectangle, + this.BorderWrapModeX, + this.BorderWrapModeY); + } +} diff --git a/src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessor{TPixel}.cs index d059ebe030..9b4659929b 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessor{TPixel}.cs @@ -31,10 +31,34 @@ internal class ConvolutionProcessor : ImageProcessor bool preserveAlpha, Image source, Rectangle sourceRectangle) + : this(configuration, kernelXY, preserveAlpha, source, sourceRectangle, BorderWrappingMode.Repeat, BorderWrappingMode.Repeat) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The configuration which allows altering default behaviour or extending the library. + /// The 2d gradient operator. + /// Whether the convolution filter is applied to alpha as well as the color channels. + /// The source for the current processor instance. + /// The source area to process for the current processor instance. + /// The to use when mapping the pixels outside of the border, in X direction. + /// The to use when mapping the pixels outside of the border, in Y direction. + public ConvolutionProcessor( + Configuration configuration, + in DenseMatrix kernelXY, + bool preserveAlpha, + Image source, + Rectangle sourceRectangle, + BorderWrappingMode borderWrapModeX, + BorderWrappingMode borderWrapModeY) : base(configuration, source, sourceRectangle) { this.KernelXY = kernelXY; this.PreserveAlpha = preserveAlpha; + this.BorderWrapModeX = borderWrapModeX; + this.BorderWrapModeY = borderWrapModeY; } /// @@ -47,21 +71,31 @@ internal class ConvolutionProcessor : ImageProcessor /// public bool PreserveAlpha { get; } + /// + /// Gets the to use when mapping the pixels outside of the border, in X direction. + /// + public BorderWrappingMode BorderWrapModeX { get; } + + /// + /// Gets the to use when mapping the pixels outside of the border, in Y direction. + /// + public BorderWrappingMode BorderWrapModeY { get; } + /// protected override void OnFrameApply(ImageFrame source) { MemoryAllocator allocator = this.Configuration.MemoryAllocator; - using Buffer2D targetPixels = allocator.Allocate2D(source.Size()); + using Buffer2D targetPixels = allocator.Allocate2D(source.Size); 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( this.Configuration, interest, @@ -121,7 +155,7 @@ internal class ConvolutionProcessor : ImageProcessor ref Vector4 targetRowRef = ref MemoryMarshal.GetReference(span); Span 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); diff --git a/src/ImageSharp/Processing/Processors/Convolution/EdgeDetectorCompassProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Convolution/EdgeDetectorCompassProcessor{TPixel}.cs index ae891f3507..a1fa4db973 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/EdgeDetectorCompassProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/EdgeDetectorCompassProcessor{TPixel}.cs @@ -58,12 +58,12 @@ internal class EdgeDetectorCompassProcessor : ImageProcessor /// protected override void OnFrameApply(ImageFrame 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 cleanCopy = source.Clone(); - using (var processor = new ConvolutionProcessor(this.Configuration, in this.kernels[0], true, this.Source, interest)) + using (ConvolutionProcessor processor = new(this.Configuration, in this.kernels[0], true, this.Source, interest)) { processor.Apply(source); } @@ -78,12 +78,12 @@ internal class EdgeDetectorCompassProcessor : ImageProcessor { using ImageFrame pass = cleanCopy.Clone(); - using (var processor = new ConvolutionProcessor(this.Configuration, in this.kernels[i], true, this.Source, interest)) + using (ConvolutionProcessor 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, diff --git a/src/ImageSharp/Processing/Processors/Convolution/EdgeDetectorProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Convolution/EdgeDetectorProcessor{TPixel}.cs index c353f46b5f..3139d24bb4 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/EdgeDetectorProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/EdgeDetectorProcessor{TPixel}.cs @@ -53,7 +53,7 @@ internal class EdgeDetectorProcessor : ImageProcessor /// protected override void OnFrameApply(ImageFrame source) { - using var processor = new ConvolutionProcessor(this.Configuration, in this.kernelXY, true, this.Source, this.SourceRectangle); + using ConvolutionProcessor processor = new(this.Configuration, in this.kernelXY, true, this.Source, this.SourceRectangle); processor.Apply(source); } } diff --git a/src/ImageSharp/Processing/Processors/Convolution/GaussianBlurProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Convolution/GaussianBlurProcessor{TPixel}.cs index 6518375b9e..d762dd336b 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/GaussianBlurProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/GaussianBlurProcessor{TPixel}.cs @@ -12,24 +12,6 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution; internal class GaussianBlurProcessor : ImageProcessor where TPixel : unmanaged, IPixel { - /// - /// Initializes a new instance of the class. - /// - /// The configuration which allows altering default behaviour or extending the library. - /// The defining the processor parameters. - /// The source for the current processor instance. - /// The source area to process for the current processor instance. - public GaussianBlurProcessor( - Configuration configuration, - GaussianBlurProcessor definition, - Image source, - Rectangle sourceRectangle) - : base(configuration, source, sourceRectangle) - { - int kernelSize = (definition.Radius * 2) + 1; - this.Kernel = ConvolutionProcessorHelpers.CreateGaussianBlurKernel(kernelSize, definition.Sigma); - } - /// /// Initializes a new instance of the class. /// @@ -72,7 +54,7 @@ internal class GaussianBlurProcessor : ImageProcessor /// protected override void OnFrameApply(ImageFrame source) { - using var processor = new Convolution2PassProcessor(this.Configuration, this.Kernel, false, this.Source, this.SourceRectangle, this.BorderWrapModeX, this.BorderWrapModeY); + using Convolution2PassProcessor processor = new(this.Configuration, this.Kernel, false, this.Source, this.SourceRectangle, this.BorderWrapModeX, this.BorderWrapModeY); processor.Apply(source); } diff --git a/src/ImageSharp/Processing/Processors/Convolution/GaussianSharpenProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Convolution/GaussianSharpenProcessor{TPixel}.cs index a286201dff..bdb3a4b380 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/GaussianSharpenProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/GaussianSharpenProcessor{TPixel}.cs @@ -12,22 +12,6 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution; internal class GaussianSharpenProcessor : ImageProcessor where TPixel : unmanaged, IPixel { - /// - /// Initializes a new instance of the class. - /// - /// The configuration which allows altering default behaviour or extending the library. - /// The defining the processor parameters. - /// The source for the current processor instance. - /// The source area to process for the current processor instance. - public GaussianSharpenProcessor( - Configuration configuration, - GaussianSharpenProcessor definition, - Image source, - Rectangle sourceRectangle) - : this(configuration, definition, source, sourceRectangle, BorderWrappingMode.Repeat, BorderWrappingMode.Repeat) - { - } - /// /// Initializes a new instance of the class. /// @@ -70,7 +54,7 @@ internal class GaussianSharpenProcessor : ImageProcessor /// protected override void OnFrameApply(ImageFrame source) { - using var processor = new Convolution2PassProcessor(this.Configuration, this.Kernel, false, this.Source, this.SourceRectangle, this.BorderWrapModeX, this.BorderWrapModeY); + using Convolution2PassProcessor processor = new(this.Configuration, this.Kernel, false, this.Source, this.SourceRectangle, this.BorderWrapModeX, this.BorderWrapModeY); processor.Apply(source); } diff --git a/src/ImageSharp/Processing/Processors/Convolution/Parameters/BokehBlurKernelDataProvider.cs b/src/ImageSharp/Processing/Processors/Convolution/Parameters/BokehBlurKernelDataProvider.cs index a680393c8c..565a5746d1 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/Parameters/BokehBlurKernelDataProvider.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/Parameters/BokehBlurKernelDataProvider.cs @@ -21,12 +21,12 @@ internal static class BokehBlurKernelDataProvider /// /// Gets the kernel scales to adjust the component values in each kernel /// - private static IReadOnlyList KernelScales { get; } = new[] { 1.4f, 1.2f, 1.2f, 1.2f, 1.2f, 1.2f }; + private static float[] KernelScales { get; } = new[] { 1.4f, 1.2f, 1.2f, 1.2f, 1.2f, 1.2f }; /// /// Gets the available bokeh blur kernel parameters /// - private static IReadOnlyList KernelComponents { get; } = new[] + 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]); } diff --git a/src/ImageSharp/Processing/Processors/Effects/OilPaintingProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Effects/OilPaintingProcessor{TPixel}.cs index 1491fe073b..f811bae0f7 100644 --- a/src/ImageSharp/Processing/Processors/Effects/OilPaintingProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Effects/OilPaintingProcessor{TPixel}.cs @@ -38,7 +38,7 @@ internal class OilPaintingProcessor : ImageProcessor int levels = Math.Clamp(this.definition.Levels, 1, 255); int brushSize = Math.Clamp(this.definition.BrushSize, 1, Math.Min(source.Width, source.Height)); - using Buffer2D targetPixels = this.Configuration.MemoryAllocator.Allocate2D(source.Size()); + using Buffer2D targetPixels = this.Configuration.MemoryAllocator.Allocate2D(source.Size); source.CopyTo(targetPixels); diff --git a/src/ImageSharp/Processing/Processors/Transforms/Linear/AffineTransformProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Transforms/Linear/AffineTransformProcessor{TPixel}.cs index c5c2a778eb..888d513206 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/Linear/AffineTransformProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/Linear/AffineTransformProcessor{TPixel}.cs @@ -61,12 +61,12 @@ internal class AffineTransformProcessor : TransformProcessor, IR if (matrix.Equals(Matrix3x2.Identity)) { // The clone will be blank here copy all the pixel data over - var interest = Rectangle.Intersect(this.SourceRectangle, destination.Bounds()); + Rectangle interest = Rectangle.Intersect(this.SourceRectangle, destination.Bounds()); Buffer2DRegion sourceBuffer = source.PixelBuffer.GetRegion(interest); - Buffer2DRegion destbuffer = destination.PixelBuffer.GetRegion(interest); + Buffer2DRegion destinationBuffer = destination.PixelBuffer.GetRegion(interest); for (int y = 0; y < sourceBuffer.Height; y++) { - sourceBuffer.DangerousGetRowSpan(y).CopyTo(destbuffer.DangerousGetRowSpan(y)); + sourceBuffer.DangerousGetRowSpan(y).CopyTo(destinationBuffer.DangerousGetRowSpan(y)); } return; @@ -77,7 +77,7 @@ internal class AffineTransformProcessor : TransformProcessor, IR if (sampler is NearestNeighborResampler) { - var nnOperation = new NNAffineOperation( + NNAffineOperation nnOperation = new( source.PixelBuffer, Rectangle.Intersect(this.SourceRectangle, source.Bounds()), destination.PixelBuffer, @@ -91,7 +91,7 @@ internal class AffineTransformProcessor : TransformProcessor, IR return; } - var operation = new AffineOperation( + AffineOperation operation = new( configuration, source.PixelBuffer, Rectangle.Intersect(this.SourceRectangle, source.Bounds()), @@ -128,17 +128,17 @@ internal class AffineTransformProcessor : TransformProcessor, IR [MethodImpl(InliningOptions.ShortMethod)] public void Invoke(int y) { - Span destRow = this.destination.DangerousGetRowSpan(y); + Span destinationRowSpan = this.destination.DangerousGetRowSpan(y); - for (int x = 0; x < destRow.Length; x++) + for (int x = 0; x < destinationRowSpan.Length; x++) { - var point = Vector2.Transform(new Vector2(x, y), this.matrix); + Vector2 point = Vector2.Transform(new Vector2(x, y), this.matrix); int px = (int)MathF.Round(point.X); int py = (int)MathF.Round(point.Y); if (this.bounds.Contains(px, py)) { - destRow[x] = this.source.GetElementUnsafe(px, py); + destinationRowSpan[x] = this.source.GetElementUnsafe(px, py); } } } @@ -195,16 +195,16 @@ internal class AffineTransformProcessor : TransformProcessor, IR for (int y = rows.Min; y < rows.Max; y++) { - Span rowSpan = this.destination.DangerousGetRowSpan(y); + Span destinationRowSpan = this.destination.DangerousGetRowSpan(y); PixelOperations.Instance.ToVector4( this.configuration, - rowSpan, + destinationRowSpan, span, PixelConversionModifiers.Scale); for (int x = 0; x < span.Length; x++) { - var point = Vector2.Transform(new Vector2(x, y), matrix); + Vector2 point = Vector2.Transform(new Vector2(x, y), matrix); float pY = point.Y; float pX = point.X; @@ -221,13 +221,14 @@ internal class AffineTransformProcessor : TransformProcessor, IR Vector4 sum = Vector4.Zero; for (int yK = top; yK <= bottom; yK++) { + Span sourceRowSpan = this.source.DangerousGetRowSpan(yK); float yWeight = sampler.GetValue(yK - pY); for (int xK = left; xK <= right; xK++) { float xWeight = sampler.GetValue(xK - pX); - Vector4 current = this.source.GetElementUnsafe(xK, yK).ToScaledVector4(); + Vector4 current = sourceRowSpan[xK].ToScaledVector4(); Numerics.Premultiply(ref current); sum += current * xWeight * yWeight; } @@ -240,7 +241,7 @@ internal class AffineTransformProcessor : TransformProcessor, IR PixelOperations.Instance.FromVector4Destructive( this.configuration, span, - rowSpan, + destinationRowSpan, PixelConversionModifiers.Scale); } } diff --git a/src/ImageSharp/Processing/Processors/Transforms/Linear/GaussianEliminationSolver.cs b/src/ImageSharp/Processing/Processors/Transforms/Linear/GaussianEliminationSolver.cs new file mode 100644 index 0000000000..1190de4352 --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Transforms/Linear/GaussianEliminationSolver.cs @@ -0,0 +1,86 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Processing.Processors.Transforms.Linear; + +/// +/// Represents a solver for systems of linear equations using the Gaussian Elimination method. +/// This class applies Gaussian Elimination to transform the matrix into row echelon form and then performs back substitution to find the solution vector. +/// This implementation is based on: +/// +internal static class GaussianEliminationSolver +{ + /// + /// Solves the system of linear equations represented by the given matrix and result vector using Gaussian Elimination. + /// + /// The square matrix representing the coefficients of the linear equations. + /// The vector representing the constants on the right-hand side of the linear equations. + /// Thrown if the matrix is singular and cannot be solved. + /// + /// The matrix passed to this method must be a square matrix. + /// If the matrix is singular (i.e., has no unique solution), an will be thrown. + /// + public static void Solve(double[][] matrix, double[] result) + { + TransformToRowEchelonForm(matrix, result); + ApplyBackSubstitution(matrix, result); + } + + private static void TransformToRowEchelonForm(double[][] matrix, double[] result) + { + int colCount = matrix.Length; + int rowCount = matrix[0].Length; + int pivotRow = 0; + for (int pivotCol = 0; pivotCol < colCount; pivotCol++) + { + double maxValue = double.Abs(matrix[pivotRow][pivotCol]); + int maxIndex = pivotRow; + for (int r = pivotRow + 1; r < rowCount; r++) + { + double value = double.Abs(matrix[r][pivotCol]); + if (value > maxValue) + { + maxIndex = r; + maxValue = value; + } + } + + if (matrix[maxIndex][pivotCol] == 0) + { + throw new NotSupportedException("Matrix is singular and cannot be solve"); + } + + (matrix[pivotRow], matrix[maxIndex]) = (matrix[maxIndex], matrix[pivotRow]); + (result[pivotRow], result[maxIndex]) = (result[maxIndex], result[pivotRow]); + + for (int r = pivotRow + 1; r < rowCount; r++) + { + double fraction = matrix[r][pivotCol] / matrix[pivotRow][pivotCol]; + for (int c = pivotCol + 1; c < colCount; c++) + { + matrix[r][c] -= matrix[pivotRow][c] * fraction; + } + + result[r] -= result[pivotRow] * fraction; + matrix[r][pivotCol] = 0; + } + + pivotRow++; + } + } + + private static void ApplyBackSubstitution(double[][] matrix, double[] result) + { + int rowCount = matrix[0].Length; + + for (int row = rowCount - 1; row >= 0; row--) + { + result[row] /= matrix[row][row]; + + for (int r = 0; r < row; r++) + { + result[r] -= result[row] * matrix[r][row]; + } + } + } +} diff --git a/src/ImageSharp/Processing/Processors/Transforms/Linear/LinearTransformUtility.cs b/src/ImageSharp/Processing/Processors/Transforms/Linear/LinearTransformUtility.cs index b5eb202c18..1f68e32744 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/Linear/LinearTransformUtility.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/Linear/LinearTransformUtility.cs @@ -43,7 +43,7 @@ internal static class LinearTransformUtility /// The . [MethodImpl(InliningOptions.ShortMethod)] public static int GetRangeStart(float radius, float center, int min, int max) - => Numerics.Clamp((int)MathF.Ceiling(center - radius), min, max); + => Numerics.Clamp((int)MathF.Floor(center - radius), min, max); /// /// Gets the end position (inclusive) for a sampling range given @@ -56,5 +56,5 @@ internal static class LinearTransformUtility /// The . [MethodImpl(InliningOptions.ShortMethod)] public static int GetRangeEnd(float radius, float center, int min, int max) - => Numerics.Clamp((int)MathF.Floor(center + radius), min, max); + => Numerics.Clamp((int)MathF.Ceiling(center + radius), min, max); } diff --git a/src/ImageSharp/Processing/Processors/Transforms/Linear/ProjectiveTransformProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Transforms/Linear/ProjectiveTransformProcessor{TPixel}.cs index b741dc4ee6..068f69cebc 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/Linear/ProjectiveTransformProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/Linear/ProjectiveTransformProcessor{TPixel}.cs @@ -61,12 +61,12 @@ internal class ProjectiveTransformProcessor : TransformProcessor if (matrix.Equals(Matrix4x4.Identity)) { // The clone will be blank here copy all the pixel data over - var interest = Rectangle.Intersect(this.SourceRectangle, destination.Bounds()); + Rectangle interest = Rectangle.Intersect(this.SourceRectangle, destination.Bounds()); Buffer2DRegion sourceBuffer = source.PixelBuffer.GetRegion(interest); - Buffer2DRegion destbuffer = destination.PixelBuffer.GetRegion(interest); + Buffer2DRegion destinationBuffer = destination.PixelBuffer.GetRegion(interest); for (int y = 0; y < sourceBuffer.Height; y++) { - sourceBuffer.DangerousGetRowSpan(y).CopyTo(destbuffer.DangerousGetRowSpan(y)); + sourceBuffer.DangerousGetRowSpan(y).CopyTo(destinationBuffer.DangerousGetRowSpan(y)); } return; @@ -77,7 +77,7 @@ internal class ProjectiveTransformProcessor : TransformProcessor if (sampler is NearestNeighborResampler) { - var nnOperation = new NNProjectiveOperation( + NNProjectiveOperation nnOperation = new( source.PixelBuffer, Rectangle.Intersect(this.SourceRectangle, source.Bounds()), destination.PixelBuffer, @@ -91,7 +91,7 @@ internal class ProjectiveTransformProcessor : TransformProcessor return; } - var operation = new ProjectiveOperation( + ProjectiveOperation operation = new( configuration, source.PixelBuffer, Rectangle.Intersect(this.SourceRectangle, source.Bounds()), @@ -128,9 +128,9 @@ internal class ProjectiveTransformProcessor : TransformProcessor [MethodImpl(InliningOptions.ShortMethod)] public void Invoke(int y) { - Span destRow = this.destination.DangerousGetRowSpan(y); + Span destinationRowSpan = this.destination.DangerousGetRowSpan(y); - for (int x = 0; x < destRow.Length; x++) + for (int x = 0; x < destinationRowSpan.Length; x++) { Vector2 point = TransformUtils.ProjectiveTransform2D(x, y, this.matrix); int px = (int)MathF.Round(point.X); @@ -138,7 +138,7 @@ internal class ProjectiveTransformProcessor : TransformProcessor if (this.bounds.Contains(px, py)) { - destRow[x] = this.source.GetElementUnsafe(px, py); + destinationRowSpan[x] = this.source.GetElementUnsafe(px, py); } } } @@ -195,10 +195,10 @@ internal class ProjectiveTransformProcessor : TransformProcessor for (int y = rows.Min; y < rows.Max; y++) { - Span rowSpan = this.destination.DangerousGetRowSpan(y); + Span destinationRowSpan = this.destination.DangerousGetRowSpan(y); PixelOperations.Instance.ToVector4( this.configuration, - rowSpan, + destinationRowSpan, span, PixelConversionModifiers.Scale); @@ -221,13 +221,14 @@ internal class ProjectiveTransformProcessor : TransformProcessor Vector4 sum = Vector4.Zero; for (int yK = top; yK <= bottom; yK++) { + Span sourceRowSpan = this.source.DangerousGetRowSpan(yK); float yWeight = sampler.GetValue(yK - pY); for (int xK = left; xK <= right; xK++) { float xWeight = sampler.GetValue(xK - pX); - Vector4 current = this.source.GetElementUnsafe(xK, yK).ToScaledVector4(); + Vector4 current = sourceRowSpan[xK].ToScaledVector4(); Numerics.Premultiply(ref current); sum += current * xWeight * yWeight; } @@ -240,7 +241,7 @@ internal class ProjectiveTransformProcessor : TransformProcessor PixelOperations.Instance.FromVector4Destructive( this.configuration, span, - rowSpan, + destinationRowSpan, PixelConversionModifiers.Scale); } } diff --git a/src/ImageSharp/Processing/Processors/Transforms/Linear/RotateProcessor.cs b/src/ImageSharp/Processing/Processors/Transforms/Linear/RotateProcessor.cs index 6580636a24..0af2b268a1 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/Linear/RotateProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/Linear/RotateProcessor.cs @@ -28,15 +28,14 @@ public sealed class RotateProcessor : AffineTransformProcessor /// The source image size public RotateProcessor(float degrees, IResampler sampler, Size sourceSize) : this( - TransformUtils.CreateRotationTransformMatrixDegrees(degrees, sourceSize), - TransformUtils.CreateRotationBoundsMatrixDegrees(degrees, sourceSize), + TransformUtils.CreateRotationTransformMatrixDegrees(degrees, sourceSize, TransformSpace.Pixel), sampler, sourceSize) => this.Degrees = degrees; // Helper constructor - private RotateProcessor(Matrix3x2 rotationMatrix, Matrix3x2 boundsMatrix, IResampler sampler, Size sourceSize) - : base(rotationMatrix, sampler, TransformUtils.GetTransformedSize(sourceSize, boundsMatrix)) + private RotateProcessor(Matrix3x2 rotationMatrix, IResampler sampler, Size sourceSize) + : base(rotationMatrix, sampler, TransformUtils.GetTransformedSize(rotationMatrix, sourceSize, TransformSpace.Pixel)) { } diff --git a/src/ImageSharp/Processing/Processors/Transforms/Linear/SkewProcessor.cs b/src/ImageSharp/Processing/Processors/Transforms/Linear/SkewProcessor.cs index 97b18de6c8..0bbc8e0f60 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/Linear/SkewProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/Linear/SkewProcessor.cs @@ -30,8 +30,7 @@ public sealed class SkewProcessor : AffineTransformProcessor /// The source image size public SkewProcessor(float degreesX, float degreesY, IResampler sampler, Size sourceSize) : this( - TransformUtils.CreateSkewTransformMatrixDegrees(degreesX, degreesY, sourceSize), - TransformUtils.CreateSkewBoundsMatrixDegrees(degreesX, degreesY, sourceSize), + TransformUtils.CreateSkewTransformMatrixDegrees(degreesX, degreesY, sourceSize, TransformSpace.Pixel), sampler, sourceSize) { @@ -40,8 +39,8 @@ public sealed class SkewProcessor : AffineTransformProcessor } // Helper constructor: - private SkewProcessor(Matrix3x2 skewMatrix, Matrix3x2 boundsMatrix, IResampler sampler, Size sourceSize) - : base(skewMatrix, sampler, TransformUtils.GetTransformedSize(sourceSize, boundsMatrix)) + private SkewProcessor(Matrix3x2 skewMatrix, IResampler sampler, Size sourceSize) + : base(skewMatrix, sampler, TransformUtils.GetTransformedSize(skewMatrix, sourceSize, TransformSpace.Pixel)) { } diff --git a/src/ImageSharp/Processing/Processors/Transforms/TransformProcessorHelpers.cs b/src/ImageSharp/Processing/Processors/Transforms/TransformProcessorHelpers.cs deleted file mode 100644 index 0bb4920f0f..0000000000 --- a/src/ImageSharp/Processing/Processors/Transforms/TransformProcessorHelpers.cs +++ /dev/null @@ -1,39 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Metadata.Profiles.Exif; -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Processing.Processors.Transforms; - -/// -/// Contains helper methods for working with transforms. -/// -internal static class TransformProcessorHelpers -{ - /// - /// Updates the dimensional metadata of a transformed image - /// - /// The pixel format. - /// The image to update - public static void UpdateDimensionalMetadata(Image image) - where TPixel : unmanaged, IPixel - { - ExifProfile? profile = image.Metadata.ExifProfile; - if (profile is null) - { - return; - } - - // Only set the value if it already exists. - if (profile.TryGetValue(ExifTag.PixelXDimension, out _)) - { - profile.SetValue(ExifTag.PixelXDimension, image.Width); - } - - if (profile.TryGetValue(ExifTag.PixelYDimension, out _)) - { - profile.SetValue(ExifTag.PixelYDimension, image.Height); - } - } -} diff --git a/src/ImageSharp/Processing/Processors/Transforms/TransformProcessor.cs b/src/ImageSharp/Processing/Processors/Transforms/TransformProcessor{TPixel}.cs similarity index 80% rename from src/ImageSharp/Processing/Processors/Transforms/TransformProcessor.cs rename to src/ImageSharp/Processing/Processors/Transforms/TransformProcessor{TPixel}.cs index 0c2c29391b..bdfac00366 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/TransformProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/TransformProcessor{TPixel}.cs @@ -23,10 +23,17 @@ internal abstract class TransformProcessor : CloningImageProcessor + protected override void AfterFrameApply(ImageFrame source, ImageFrame destination) + { + base.AfterFrameApply(source, destination); + destination.Metadata.AfterFrameApply(source, destination); + } + /// protected override void AfterImageApply(Image destination) { - TransformProcessorHelpers.UpdateDimensionalMetadata(destination); base.AfterImageApply(destination); + destination.Metadata.AfterImageApply(destination); } } diff --git a/src/ImageSharp/Processing/Processors/Transforms/TransformUtils.cs b/src/ImageSharp/Processing/Processors/Transforms/TransformUtils.cs index 70112ab5a8..47b3250b87 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/TransformUtils.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/TransformUtils.cs @@ -3,6 +3,7 @@ using System.Numerics; using System.Runtime.CompilerServices; +using SixLabors.ImageSharp.Processing.Processors.Transforms.Linear; namespace SixLabors.ImageSharp.Processing.Processors.Transforms; @@ -68,6 +69,11 @@ internal static class TransformUtils [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector2 ProjectiveTransform2D(float x, float y, Matrix4x4 matrix) { + // The w component (v4.W) resulting from the transformation can be less than 0 in certain cases, + // such as when the point is transformed behind the camera in a perspective projection. + // However, in many 2D contexts, negative w values are not meaningful and could cause issues + // like flipped or distorted projections. To avoid this, we take the max of w and epsilon to ensure + // we don't divide by a very small or negative number, effectively treating any negative w as epsilon. const float epsilon = 0.0000001F; Vector4 v4 = Vector4.Transform(new Vector4(x, y, 0, 1F), matrix); return new Vector2(v4.X, v4.Y) / MathF.Max(v4.W, epsilon); @@ -78,48 +84,22 @@ internal static class TransformUtils /// /// The amount of rotation, in degrees. /// The source image size. + /// The to use when creating the centered matrix. /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Matrix3x2 CreateRotationTransformMatrixDegrees(float degrees, Size size) - => CreateCenteredTransformMatrix( - new Rectangle(Point.Empty, size), - Matrix3x2Extensions.CreateRotationDegrees(degrees, PointF.Empty)); + public static Matrix3x2 CreateRotationTransformMatrixDegrees(float degrees, Size size, TransformSpace transformSpace) + => CreateRotationTransformMatrixRadians(GeometryUtilities.DegreeToRadian(degrees), size, transformSpace); /// /// Creates a centered rotation transform matrix using the given rotation in radians and the source size. /// /// The amount of rotation, in radians. /// The source image size. + /// The to use when creating the centered matrix. /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Matrix3x2 CreateRotationTransformMatrixRadians(float radians, Size size) - => CreateCenteredTransformMatrix( - new Rectangle(Point.Empty, size), - Matrix3x2Extensions.CreateRotation(radians, PointF.Empty)); - - /// - /// Creates a centered rotation bounds matrix using the given rotation in degrees and the source size. - /// - /// The amount of rotation, in degrees. - /// The source image size. - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Matrix3x2 CreateRotationBoundsMatrixDegrees(float degrees, Size size) - => CreateCenteredBoundsMatrix( - new Rectangle(Point.Empty, size), - Matrix3x2Extensions.CreateRotationDegrees(degrees, PointF.Empty)); - - /// - /// Creates a centered rotation bounds matrix using the given rotation in radians and the source size. - /// - /// The amount of rotation, in radians. - /// The source image size. - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Matrix3x2 CreateRotationBoundsMatrixRadians(float radians, Size size) - => CreateCenteredBoundsMatrix( - new Rectangle(Point.Empty, size), - Matrix3x2Extensions.CreateRotation(radians, PointF.Empty)); + public static Matrix3x2 CreateRotationTransformMatrixRadians(float radians, Size size, TransformSpace transformSpace) + => CreateCenteredTransformMatrix(Matrix3x2Extensions.CreateRotation(radians, PointF.Empty), size, transformSpace); /// /// Creates a centered skew transform matrix from the give angles in degrees and the source size. @@ -127,12 +107,11 @@ internal static class TransformUtils /// The X angle, in degrees. /// The Y angle, in degrees. /// The source image size. + /// The to use when creating the centered matrix. /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Matrix3x2 CreateSkewTransformMatrixDegrees(float degreesX, float degreesY, Size size) - => CreateCenteredTransformMatrix( - new Rectangle(Point.Empty, size), - Matrix3x2Extensions.CreateSkewDegrees(degreesX, degreesY, PointF.Empty)); + public static Matrix3x2 CreateSkewTransformMatrixDegrees(float degreesX, float degreesY, Size size, TransformSpace transformSpace) + => CreateSkewTransformMatrixRadians(GeometryUtilities.DegreeToRadian(degreesX), GeometryUtilities.DegreeToRadian(degreesY), size, transformSpace); /// /// Creates a centered skew transform matrix from the give angles in radians and the source size. @@ -140,81 +119,37 @@ internal static class TransformUtils /// The X angle, in radians. /// The Y angle, in radians. /// The source image size. + /// The to use when creating the centered matrix. /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Matrix3x2 CreateSkewTransformMatrixRadians(float radiansX, float radiansY, Size size) - => CreateCenteredTransformMatrix( - new Rectangle(Point.Empty, size), - Matrix3x2Extensions.CreateSkew(radiansX, radiansY, PointF.Empty)); - - /// - /// Creates a centered skew bounds matrix from the give angles in degrees and the source size. - /// - /// The X angle, in degrees. - /// The Y angle, in degrees. - /// The source image size. - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Matrix3x2 CreateSkewBoundsMatrixDegrees(float degreesX, float degreesY, Size size) - => CreateCenteredBoundsMatrix( - new Rectangle(Point.Empty, size), - Matrix3x2Extensions.CreateSkewDegrees(degreesX, degreesY, PointF.Empty)); - - /// - /// Creates a centered skew bounds matrix from the give angles in radians and the source size. - /// - /// The X angle, in radians. - /// The Y angle, in radians. - /// The source image size. - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Matrix3x2 CreateSkewBoundsMatrixRadians(float radiansX, float radiansY, Size size) - => CreateCenteredBoundsMatrix( - new Rectangle(Point.Empty, size), - Matrix3x2Extensions.CreateSkew(radiansX, radiansY, PointF.Empty)); + public static Matrix3x2 CreateSkewTransformMatrixRadians(float radiansX, float radiansY, Size size, TransformSpace transformSpace) + => CreateCenteredTransformMatrix(Matrix3x2Extensions.CreateSkew(radiansX, radiansY, PointF.Empty), size, transformSpace); /// /// Gets the centered transform matrix based upon the source rectangle. /// - /// The source image bounds. /// The transformation matrix. + /// The source image size. + /// + /// The to use when creating the centered matrix. + /// /// The [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Matrix3x2 CreateCenteredTransformMatrix(Rectangle sourceRectangle, Matrix3x2 matrix) + public static Matrix3x2 CreateCenteredTransformMatrix(Matrix3x2 matrix, Size size, TransformSpace transformSpace) { - Rectangle destinationRectangle = GetTransformedBoundingRectangle(sourceRectangle, matrix); + Size transformSize = GetUnboundedTransformedSize(matrix, size, transformSpace); // We invert the matrix to handle the transformation from screen to world space. // This ensures scaling matrices are correct. Matrix3x2.Invert(matrix, out Matrix3x2 inverted); - // Centered transforms must be 0 based so we offset the bounds width and height. - Matrix3x2 translationToTargetCenter = Matrix3x2.CreateTranslation(new Vector2(-(destinationRectangle.Width - 1), -(destinationRectangle.Height - 1)) * .5F); - Matrix3x2 translateToSourceCenter = Matrix3x2.CreateTranslation(new Vector2(sourceRectangle.Width - 1, sourceRectangle.Height - 1) * .5F); - - // Translate back to world space. - Matrix3x2.Invert(translationToTargetCenter * inverted * translateToSourceCenter, out Matrix3x2 centered); - - return centered; - } - - /// - /// Gets the centered bounds matrix based upon the source rectangle. - /// - /// The source image bounds. - /// The transformation matrix. - /// The - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Matrix3x2 CreateCenteredBoundsMatrix(Rectangle sourceRectangle, Matrix3x2 matrix) - { - Rectangle destinationRectangle = GetTransformedBoundingRectangle(sourceRectangle, matrix); - - // We invert the matrix to handle the transformation from screen to world space. - // This ensures scaling matrices are correct. - Matrix3x2.Invert(matrix, out Matrix3x2 inverted); + // The source size is provided using the coordinate space of the source image. + // however the transform should always be applied in the pixel space. + // To account for this we offset by the size - 1 to translate to the pixel space. + float offset = transformSpace == TransformSpace.Pixel ? 1F : 0F; - Matrix3x2 translationToTargetCenter = Matrix3x2.CreateTranslation(new Vector2(-destinationRectangle.Width, -destinationRectangle.Height) * .5F); - Matrix3x2 translateToSourceCenter = Matrix3x2.CreateTranslation(new Vector2(sourceRectangle.Width, sourceRectangle.Height) * .5F); + Matrix3x2 translationToTargetCenter = Matrix3x2.CreateTranslation(new Vector2(-(transformSize.Width - offset), -(transformSize.Height - offset)) * .5F); + Matrix3x2 translateToSourceCenter = Matrix3x2.CreateTranslation(new Vector2(size.Width - offset, size.Height - offset) * .5F); // Translate back to world space. Matrix3x2.Invert(translationToTargetCenter * inverted * translateToSourceCenter, out Matrix3x2 centered); @@ -345,63 +280,192 @@ internal static class TransformUtils } /// - /// Returns the rectangle bounds relative to the source for the given transformation matrix. + /// Computes the projection matrix for a quad distortion transformation. /// /// The source rectangle. - /// The transformation matrix. - /// - /// The . - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Rectangle GetTransformedBoundingRectangle(Rectangle rectangle, Matrix3x2 matrix) + /// The top-left point of the distorted quad. + /// The top-right point of the distorted quad. + /// The bottom-right point of the distorted quad. + /// The bottom-left point of the distorted quad. + /// The to use when creating the matrix. + /// The computed projection matrix for the quad distortion. + /// + /// This method is based on the algorithm described in the following article: + /// + /// + public static Matrix4x4 CreateQuadDistortionMatrix( + Rectangle rectangle, + PointF topLeft, + PointF topRight, + PointF bottomRight, + PointF bottomLeft, + TransformSpace transformSpace) { - Rectangle transformed = GetTransformedRectangle(rectangle, matrix); - return new Rectangle(0, 0, transformed.Width, transformed.Height); + PointF p1 = new(rectangle.X, rectangle.Y); + PointF p2 = new(rectangle.X + rectangle.Width, rectangle.Y); + PointF p3 = new(rectangle.X + rectangle.Width, rectangle.Y + rectangle.Height); + PointF p4 = new(rectangle.X, rectangle.Y + rectangle.Height); + + PointF q1 = topLeft; + PointF q2 = topRight; + PointF q3 = bottomRight; + PointF q4 = bottomLeft; + + double[][] matrixData = + [ + [p1.X, p1.Y, 1, 0, 0, 0, -p1.X * q1.X, -p1.Y * q1.X], + [0, 0, 0, p1.X, p1.Y, 1, -p1.X * q1.Y, -p1.Y * q1.Y], + [p2.X, p2.Y, 1, 0, 0, 0, -p2.X * q2.X, -p2.Y * q2.X], + [0, 0, 0, p2.X, p2.Y, 1, -p2.X * q2.Y, -p2.Y * q2.Y], + [p3.X, p3.Y, 1, 0, 0, 0, -p3.X * q3.X, -p3.Y * q3.X], + [0, 0, 0, p3.X, p3.Y, 1, -p3.X * q3.Y, -p3.Y * q3.Y], + [p4.X, p4.Y, 1, 0, 0, 0, -p4.X * q4.X, -p4.Y * q4.X], + [0, 0, 0, p4.X, p4.Y, 1, -p4.X * q4.Y, -p4.Y * q4.Y], + ]; + + double[] b = + [ + q1.X, + q1.Y, + q2.X, + q2.Y, + q3.X, + q3.Y, + q4.X, + q4.Y, + ]; + + GaussianEliminationSolver.Solve(matrixData, b); + +#pragma warning disable SA1117 + Matrix4x4 projectionMatrix = new( + (float)b[0], (float)b[3], 0, (float)b[6], + (float)b[1], (float)b[4], 0, (float)b[7], + 0, 0, 1, 0, + (float)b[2], (float)b[5], 0, 1); +#pragma warning restore SA1117 + + // Check if the matrix involves only affine transformations by inspecting the relevant components. + // We want to use pixel space for calculations only if the transformation is purely 2D and does not include + // any perspective effects, non-standard scaling, or unusual translations that could distort the image. + if (transformSpace == TransformSpace.Pixel && IsAffineRotationOrSkew(projectionMatrix)) + { + if (projectionMatrix.M41 != 0) + { + projectionMatrix.M41--; + } + + if (projectionMatrix.M42 != 0) + { + projectionMatrix.M42--; + } + } + + return projectionMatrix; } /// - /// Returns the rectangle relative to the source for the given transformation matrix. + /// Returns the size relative to the source for the given transformation matrix. + /// + /// The transformation matrix. + /// The source size. + /// The to use when calculating the size. + /// The . + public static Size GetTransformedSize(Matrix3x2 matrix, Size size, TransformSpace transformSpace) + => GetTransformedSize(matrix, size, transformSpace, true); + + /// + /// Returns the size relative to the source for the given transformation matrix. /// - /// The source rectangle. /// The transformation matrix. + /// The source size. + /// The used when generating the matrix. /// - /// The . + /// The . /// - public static Rectangle GetTransformedRectangle(Rectangle rectangle, Matrix3x2 matrix) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Size GetTransformedSize(Matrix4x4 matrix, Size size, TransformSpace transformSpace) { - if (rectangle.Equals(default) || Matrix3x2.Identity.Equals(matrix)) + Guard.IsTrue(size.Width > 0 && size.Height > 0, nameof(size), "Source size dimensions cannot be 0!"); + + if (matrix.IsIdentity || matrix.Equals(default)) { - return rectangle; + return size; } - Vector2 tl = Vector2.Transform(new Vector2(rectangle.Left, rectangle.Top), matrix); - Vector2 tr = Vector2.Transform(new Vector2(rectangle.Right, rectangle.Top), matrix); - Vector2 bl = Vector2.Transform(new Vector2(rectangle.Left, rectangle.Bottom), matrix); - Vector2 br = Vector2.Transform(new Vector2(rectangle.Right, rectangle.Bottom), matrix); + // Check if the matrix involves only affine transformations by inspecting the relevant components. + // We want to use pixel space for calculations only if the transformation is purely 2D and does not include + // any perspective effects, non-standard scaling, or unusual translations that could distort the image. + bool usePixelSpace = transformSpace == TransformSpace.Pixel && IsAffineRotationOrSkew(matrix); + + // Define an offset size to translate between pixel space and coordinate space. + // When using pixel space, apply a scaling sensitive offset to translate to discrete pixel coordinates. + // When not using pixel space, use SizeF.Empty as the offset. + + // Compute scaling factors from the matrix + float scaleX = 1F / new Vector2(matrix.M11, matrix.M21).Length(); // sqrt(M11^2 + M21^2) + float scaleY = 1F / new Vector2(matrix.M12, matrix.M22).Length(); // sqrt(M12^2 + M22^2) + + // Apply the offset relative to the scale + SizeF offsetSize = usePixelSpace ? new SizeF(scaleX, scaleY) : SizeF.Empty; + + // Subtract the offset size to translate to the appropriate space (pixel or coordinate). + if (TryGetTransformedRectangle(new RectangleF(Point.Empty, size - offsetSize), matrix, out Rectangle bounds)) + { + // Add the offset size back to translate the transformed bounds to the correct space. + return Size.Ceiling(ConstrainSize(bounds) + offsetSize); + } - return GetBoundingRectangle(tl, tr, bl, br); + return size; } /// /// Returns the size relative to the source for the given transformation matrix. /// + /// The transformation matrix. /// The source size. + /// The to use when calculating the size. + /// The . + private static Size GetUnboundedTransformedSize(Matrix3x2 matrix, Size size, TransformSpace transformSpace) + => GetTransformedSize(matrix, size, transformSpace, false); + + /// + /// Returns the size relative to the source for the given transformation matrix. + /// /// The transformation matrix. + /// The source size. + /// The to use when calculating the size. + /// Whether to constrain the size to ensure that the dimensions are positive. /// /// The . /// - public static Size GetTransformedSize(Size size, Matrix3x2 matrix) + private static Size GetTransformedSize(Matrix3x2 matrix, Size size, TransformSpace transformSpace, bool constrain) { Guard.IsTrue(size.Width > 0 && size.Height > 0, nameof(size), "Source size dimensions cannot be 0!"); - if (matrix.Equals(default) || matrix.Equals(Matrix3x2.Identity)) + if (matrix.IsIdentity || matrix.Equals(default)) { return size; } - Rectangle rectangle = GetTransformedRectangle(new Rectangle(Point.Empty, size), matrix); + // Define an offset size to translate between coordinate space and pixel space. + // Compute scaling factors from the matrix + SizeF offsetSize = SizeF.Empty; + if (transformSpace == TransformSpace.Pixel) + { + float scaleX = 1F / new Vector2(matrix.M11, matrix.M21).Length(); // sqrt(M11^2 + M21^2) + float scaleY = 1F / new Vector2(matrix.M12, matrix.M22).Length(); // sqrt(M12^2 + M22^2) + offsetSize = new(scaleX, scaleY); + } + + // Subtract the offset size to translate to the pixel space. + if (TryGetTransformedRectangle(new RectangleF(Point.Empty, size - offsetSize), matrix, out Rectangle bounds)) + { + // Add the offset size back to translate the transformed bounds to the coordinate space. + return Size.Ceiling((constrain ? ConstrainSize(bounds) : bounds.Size) + offsetSize); + } - return ConstrainSize(rectangle); + return size; } /// @@ -409,46 +473,52 @@ internal static class TransformUtils /// /// The source rectangle. /// The transformation matrix. + /// The resulting bounding rectangle. /// - /// The . + /// if the transformation was successful; otherwise, . /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Rectangle GetTransformedRectangle(Rectangle rectangle, Matrix4x4 matrix) + private static bool TryGetTransformedRectangle(RectangleF rectangle, Matrix3x2 matrix, out Rectangle bounds) { - if (rectangle.Equals(default) || Matrix4x4.Identity.Equals(matrix)) + if (matrix.IsIdentity || rectangle.Equals(default)) { - return rectangle; + bounds = default; + return false; } - Vector2 tl = ProjectiveTransform2D(rectangle.Left, rectangle.Top, matrix); - Vector2 tr = ProjectiveTransform2D(rectangle.Right, rectangle.Top, matrix); - Vector2 bl = ProjectiveTransform2D(rectangle.Left, rectangle.Bottom, matrix); - Vector2 br = ProjectiveTransform2D(rectangle.Right, rectangle.Bottom, matrix); + Vector2 tl = Vector2.Transform(new Vector2(rectangle.Left, rectangle.Top), matrix); + Vector2 tr = Vector2.Transform(new Vector2(rectangle.Right, rectangle.Top), matrix); + Vector2 bl = Vector2.Transform(new Vector2(rectangle.Left, rectangle.Bottom), matrix); + Vector2 br = Vector2.Transform(new Vector2(rectangle.Right, rectangle.Bottom), matrix); - return GetBoundingRectangle(tl, tr, bl, br); + bounds = GetBoundingRectangle(tl, tr, bl, br); + return true; } /// - /// Returns the size relative to the source for the given transformation matrix. + /// Returns the rectangle relative to the source for the given transformation matrix. /// - /// The source size. + /// The source rectangle. /// The transformation matrix. + /// The resulting bounding rectangle. /// - /// The . + /// if the transformation was successful; otherwise, . /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Size GetTransformedSize(Size size, Matrix4x4 matrix) + private static bool TryGetTransformedRectangle(RectangleF rectangle, Matrix4x4 matrix, out Rectangle bounds) { - Guard.IsTrue(size.Width > 0 && size.Height > 0, nameof(size), "Source size dimensions cannot be 0!"); - - if (matrix.Equals(default) || matrix.Equals(Matrix4x4.Identity)) + if (matrix.IsIdentity || rectangle.Equals(default)) { - return size; + bounds = default; + return false; } - Rectangle rectangle = GetTransformedRectangle(new Rectangle(Point.Empty, size), matrix); + Vector2 tl = ProjectiveTransform2D(rectangle.Left, rectangle.Top, matrix); + Vector2 tr = ProjectiveTransform2D(rectangle.Right, rectangle.Top, matrix); + Vector2 bl = ProjectiveTransform2D(rectangle.Left, rectangle.Bottom, matrix); + Vector2 br = ProjectiveTransform2D(rectangle.Right, rectangle.Bottom, matrix); - return ConstrainSize(rectangle); + bounds = GetBoundingRectangle(tl, tr, bl, br); + return true; } [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -482,6 +552,51 @@ internal static class TransformUtils float right = MathF.Max(tl.X, MathF.Max(tr.X, MathF.Max(bl.X, br.X))); float bottom = MathF.Max(tl.Y, MathF.Max(tr.Y, MathF.Max(bl.Y, br.Y))); - return Rectangle.Round(RectangleF.FromLTRB(left, top, right, bottom)); + // Clamp the values to the nearest whole pixel. + return Rectangle.FromLTRB( + (int)Math.Floor(left), + (int)Math.Floor(top), + (int)Math.Ceiling(right), + (int)Math.Ceiling(bottom)); + } + + private static bool IsAffineRotationOrSkew(Matrix4x4 matrix) + { + const float epsilon = 1e-6f; + + // Check if the matrix is affine (last column should be [0, 0, 0, 1]) + if (Math.Abs(matrix.M14) > epsilon || + Math.Abs(matrix.M24) > epsilon || + Math.Abs(matrix.M34) > epsilon || + Math.Abs(matrix.M44 - 1f) > epsilon) + { + return false; + } + + // Translation component (M41, m42) are allowed, others are not. + if (Math.Abs(matrix.M43) > epsilon) + { + return false; + } + + // Extract the linear (rotation and skew) part of the matrix + // Upper-left 3x3 matrix + float m11 = matrix.M11, m12 = matrix.M12, m13 = matrix.M13; + float m21 = matrix.M21, m22 = matrix.M22, m23 = matrix.M23; + float m31 = matrix.M31, m32 = matrix.M32, m33 = matrix.M33; + + // Compute the determinant of the linear part + float determinant = (m11 * ((m22 * m33) - (m23 * m32))) - + (m12 * ((m21 * m33) - (m23 * m31))) + + (m13 * ((m21 * m32) - (m22 * m31))); + + // Check if the determinant is approximately ±1 (no scaling) + if (Math.Abs(Math.Abs(determinant) - 1f) > epsilon) + { + return false; + } + + // All checks passed; the matrix represents rotation and/or skew (with possible translation) + return true; } } diff --git a/src/ImageSharp/Processing/ProjectiveTransformBuilder.cs b/src/ImageSharp/Processing/ProjectiveTransformBuilder.cs index 0387adebb9..82b897ea5d 100644 --- a/src/ImageSharp/Processing/ProjectiveTransformBuilder.cs +++ b/src/ImageSharp/Processing/ProjectiveTransformBuilder.cs @@ -11,8 +11,29 @@ namespace SixLabors.ImageSharp.Processing; /// public class ProjectiveTransformBuilder { - private readonly List> transformMatrixFactories = new(); - private readonly List> boundsMatrixFactories = new(); + private readonly List> transformMatrixFactories = []; + + /// + /// Initializes a new instance of the class. + /// + public ProjectiveTransformBuilder() + : this(TransformSpace.Pixel) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The to use when applying the projective transform. + /// + public ProjectiveTransformBuilder(TransformSpace transformSpace) + => this.TransformSpace = transformSpace; + + /// + /// Gets the to use when applying the projective transform. + /// + public TransformSpace TransformSpace { get; } /// /// Prepends a matrix that performs a tapering projective transform. @@ -22,9 +43,7 @@ public class ProjectiveTransformBuilder /// The amount to taper. /// The . public ProjectiveTransformBuilder PrependTaper(TaperSide side, TaperCorner corner, float fraction) - => this.Prepend( - size => TransformUtils.CreateTaperMatrix(size, side, corner, fraction), - size => TransformUtils.CreateTaperMatrix(size, side, corner, fraction)); + => this.Prepend(size => TransformUtils.CreateTaperMatrix(size, side, corner, fraction)); /// /// Appends a matrix that performs a tapering projective transform. @@ -34,9 +53,7 @@ public class ProjectiveTransformBuilder /// The amount to taper. /// The . public ProjectiveTransformBuilder AppendTaper(TaperSide side, TaperCorner corner, float fraction) - => this.Append( - size => TransformUtils.CreateTaperMatrix(size, side, corner, fraction), - size => TransformUtils.CreateTaperMatrix(size, side, corner, fraction)); + => this.Append(size => TransformUtils.CreateTaperMatrix(size, side, corner, fraction)); /// /// Prepends a centered rotation matrix using the given rotation in degrees. @@ -52,9 +69,7 @@ public class ProjectiveTransformBuilder /// The amount of rotation, in radians. /// The . public ProjectiveTransformBuilder PrependRotationRadians(float radians) - => this.Prepend( - size => new Matrix4x4(TransformUtils.CreateRotationTransformMatrixRadians(radians, size)), - size => new Matrix4x4(TransformUtils.CreateRotationBoundsMatrixRadians(radians, size))); + => this.Prepend(size => new Matrix4x4(TransformUtils.CreateRotationTransformMatrixRadians(radians, size, this.TransformSpace))); /// /// Prepends a centered rotation matrix using the given rotation in degrees at the given origin. @@ -88,9 +103,7 @@ public class ProjectiveTransformBuilder /// The amount of rotation, in radians. /// The . public ProjectiveTransformBuilder AppendRotationRadians(float radians) - => this.Append( - size => new Matrix4x4(TransformUtils.CreateRotationTransformMatrixRadians(radians, size)), - size => new Matrix4x4(TransformUtils.CreateRotationBoundsMatrixRadians(radians, size))); + => this.Append(size => new Matrix4x4(TransformUtils.CreateRotationTransformMatrixRadians(radians, size, this.TransformSpace))); /// /// Appends a centered rotation matrix using the given rotation in degrees at the given origin. @@ -174,9 +187,7 @@ public class ProjectiveTransformBuilder /// The Y angle, in radians. /// The . public ProjectiveTransformBuilder PrependSkewRadians(float radiansX, float radiansY) - => this.Prepend( - size => new Matrix4x4(TransformUtils.CreateSkewTransformMatrixRadians(radiansX, radiansY, size)), - size => new Matrix4x4(TransformUtils.CreateSkewBoundsMatrixRadians(radiansX, radiansY, size))); + => this.Prepend(size => new Matrix4x4(TransformUtils.CreateSkewTransformMatrixRadians(radiansX, radiansY, size, this.TransformSpace))); /// /// Prepends a skew matrix using the given angles in degrees at the given origin. @@ -214,9 +225,7 @@ public class ProjectiveTransformBuilder /// The Y angle, in radians. /// The . public ProjectiveTransformBuilder AppendSkewRadians(float radiansX, float radiansY) - => this.Append( - size => new Matrix4x4(TransformUtils.CreateSkewTransformMatrixRadians(radiansX, radiansY, size)), - size => new Matrix4x4(TransformUtils.CreateSkewBoundsMatrixRadians(radiansX, radiansY, size))); + => this.Append(size => new Matrix4x4(TransformUtils.CreateSkewTransformMatrixRadians(radiansX, radiansY, size, this.TransformSpace))); /// /// Appends a skew matrix using the given angles in degrees at the given origin. @@ -270,6 +279,30 @@ public class ProjectiveTransformBuilder public ProjectiveTransformBuilder AppendTranslation(Vector2 position) => this.AppendMatrix(Matrix4x4.CreateTranslation(new Vector3(position, 0))); + /// + /// Prepends a quad distortion matrix using the specified corner points. + /// + /// The top-left corner point of the distorted quad. + /// The top-right corner point of the distorted quad. + /// The bottom-right corner point of the distorted quad. + /// The bottom-left corner point of the distorted quad. + /// The . + public ProjectiveTransformBuilder PrependQuadDistortion(PointF topLeft, PointF topRight, PointF bottomRight, PointF bottomLeft) + => this.Prepend(size => TransformUtils.CreateQuadDistortionMatrix( + new Rectangle(Point.Empty, size), topLeft, topRight, bottomRight, bottomLeft, this.TransformSpace)); + + /// + /// Appends a quad distortion matrix using the specified corner points. + /// + /// The top-left corner point of the distorted quad. + /// The top-right corner point of the distorted quad. + /// The bottom-right corner point of the distorted quad. + /// The bottom-left corner point of the distorted quad. + /// The . + public ProjectiveTransformBuilder AppendQuadDistortion(PointF topLeft, PointF topRight, PointF bottomRight, PointF bottomLeft) + => this.Append(size => TransformUtils.CreateQuadDistortionMatrix( + new Rectangle(Point.Empty, size), topLeft, topRight, bottomRight, bottomLeft, this.TransformSpace)); + /// /// Prepends a raw matrix. /// @@ -283,7 +316,7 @@ public class ProjectiveTransformBuilder public ProjectiveTransformBuilder PrependMatrix(Matrix4x4 matrix) { CheckDegenerate(matrix); - return this.Prepend(_ => matrix, _ => matrix); + return this.Prepend(_ => matrix); } /// @@ -299,7 +332,7 @@ public class ProjectiveTransformBuilder public ProjectiveTransformBuilder AppendMatrix(Matrix4x4 matrix) { CheckDegenerate(matrix); - return this.Append(_ => matrix, _ => matrix); + return this.Append(_ => matrix); } /// @@ -352,18 +385,8 @@ public class ProjectiveTransformBuilder /// The . public Size GetTransformedSize(Rectangle sourceRectangle) { - Size size = sourceRectangle.Size; - - // Translate the origin matrix to cater for source rectangle offsets. - Matrix4x4 matrix = Matrix4x4.CreateTranslation(new Vector3(-sourceRectangle.Location, 0)); - - foreach (Func factory in this.boundsMatrixFactories) - { - matrix *= factory(size); - CheckDegenerate(matrix); - } - - return TransformUtils.GetTransformedSize(size, matrix); + Matrix4x4 matrix = this.BuildMatrix(sourceRectangle); + return TransformUtils.GetTransformedSize(matrix, sourceRectangle.Size, this.TransformSpace); } private static void CheckDegenerate(Matrix4x4 matrix) @@ -374,17 +397,15 @@ public class ProjectiveTransformBuilder } } - private ProjectiveTransformBuilder Prepend(Func transformFactory, Func boundsFactory) + private ProjectiveTransformBuilder Prepend(Func transformFactory) { this.transformMatrixFactories.Insert(0, transformFactory); - this.boundsMatrixFactories.Insert(0, boundsFactory); return this; } - private ProjectiveTransformBuilder Append(Func transformFactory, Func boundsFactory) + private ProjectiveTransformBuilder Append(Func transformFactory) { this.transformMatrixFactories.Add(transformFactory); - this.boundsMatrixFactories.Add(boundsFactory); return this; } } diff --git a/src/ImageSharp/Processing/TransformSpace.cs b/src/ImageSharp/Processing/TransformSpace.cs new file mode 100644 index 0000000000..bca676bd88 --- /dev/null +++ b/src/ImageSharp/Processing/TransformSpace.cs @@ -0,0 +1,26 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Processing; + +/// +/// Represents the different spaces used in transformation operations. +/// +public enum TransformSpace +{ + /// + /// Coordinate space is a continuous, mathematical grid where objects and positions + /// are defined with precise, often fractional values. This space allows for fine-grained + /// transformations like scaling, rotation, and translation with high precision. + /// In coordinate space, an image can span from (0,0) to (4,4) for a 4x4 image, including the boundaries. + /// + Coordinate, + + /// + /// Pixel space is a discrete grid where each position corresponds to a specific pixel on the screen. + /// In this space, positions are defined by whole numbers, with no fractional values. + /// A 4x4 image in pixel space covers exactly 4 pixels wide and 4 pixels tall, ranging from (0,0) to (3,3). + /// Pixel space is used when rendering images to ensure that everything aligns with the actual pixels on the screen. + /// + Pixel +} diff --git a/tests/Directory.Build.targets b/tests/Directory.Build.targets index d6b35d003f..e20717cdd3 100644 --- a/tests/Directory.Build.targets +++ b/tests/Directory.Build.targets @@ -18,18 +18,23 @@ - - + + + + - - - - - + + + + + - + diff --git a/tests/ImageSharp.Benchmarks/Bulk/Pad3Shuffle4Channel.cs b/tests/ImageSharp.Benchmarks/Bulk/Pad3Shuffle4Channel.cs index 1b6663e70e..8728dd6715 100644 --- a/tests/ImageSharp.Benchmarks/Bulk/Pad3Shuffle4Channel.cs +++ b/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 | - | - | - | - | diff --git a/tests/ImageSharp.Benchmarks/Bulk/Shuffle3Channel.cs b/tests/ImageSharp.Benchmarks/Bulk/Shuffle3Channel.cs index 8b7b89eb36..e4c12900f5 100644 --- a/tests/ImageSharp.Benchmarks/Bulk/Shuffle3Channel.cs +++ b/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 | - | - | - | - | diff --git a/tests/ImageSharp.Benchmarks/Bulk/Shuffle4Slice3Channel.cs b/tests/ImageSharp.Benchmarks/Bulk/Shuffle4Slice3Channel.cs index 5ade55c73f..579e2c54db 100644 --- a/tests/ImageSharp.Benchmarks/Bulk/Shuffle4Slice3Channel.cs +++ b/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 | - | - | - | - | diff --git a/tests/ImageSharp.Benchmarks/Bulk/ShuffleByte4Channel.cs b/tests/ImageSharp.Benchmarks/Bulk/ShuffleByte4Channel.cs index 911c4e0a58..6a16bb5710 100644 --- a/tests/ImageSharp.Benchmarks/Bulk/ShuffleByte4Channel.cs +++ b/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 | - | - | - | - | diff --git a/tests/ImageSharp.Benchmarks/Bulk/ShuffleFloat4Channel.cs b/tests/ImageSharp.Benchmarks/Bulk/ShuffleFloat4Channel.cs index 5bb3cf9165..7cc894486f 100644 --- a/tests/ImageSharp.Benchmarks/Bulk/ShuffleFloat4Channel.cs +++ b/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 | - | - | - | - | diff --git a/tests/ImageSharp.Benchmarks/Codecs/Tga/DecodeTga.cs b/tests/ImageSharp.Benchmarks/Codecs/Tga/DecodeTga.cs index 2ebab5e001..7a3fc0b395 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/Tga/DecodeTga.cs +++ b/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(this.data); + using Image image = Image.Load(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; } diff --git a/tests/ImageSharp.Benchmarks/Codecs/Webp/DecodeWebp.cs b/tests/ImageSharp.Benchmarks/Codecs/Webp/DecodeWebp.cs index 6c71a62b50..4d6252c2b6 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/Webp/DecodeWebp.cs +++ b/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(memoryStream); + using MemoryStream memoryStream = new(this.webpLossyBytes); + using Image image = Image.Load(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(memoryStream); + using MemoryStream memoryStream = new(this.webpLosslessBytes); + using Image image = Image.Load(memoryStream); return image.Height; } diff --git a/tests/ImageSharp.Benchmarks/Codecs/Webp/EncodeWebp.cs b/tests/ImageSharp.Benchmarks/Codecs/Webp/EncodeWebp.cs index 5be46a2220..d78640549d 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/Webp/EncodeWebp.cs +++ b/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 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; diff --git a/tests/ImageSharp.Benchmarks/Config.HwIntrinsics.cs b/tests/ImageSharp.Benchmarks/Config.HwIntrinsics.cs index 92f8917cf8..e21d0c76d8 100644 --- a/tests/ImageSharp.Benchmarks/Config.HwIntrinsics.cs +++ b/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 { diff --git a/tests/ImageSharp.Benchmarks/ImageSharp.Benchmarks.csproj b/tests/ImageSharp.Benchmarks/ImageSharp.Benchmarks.csproj index 4408159ef4..37a991248c 100644 --- a/tests/ImageSharp.Benchmarks/ImageSharp.Benchmarks.csproj +++ b/tests/ImageSharp.Benchmarks/ImageSharp.Benchmarks.csproj @@ -23,7 +23,7 @@ - net8.0 + net8.0;net9.0 @@ -41,8 +41,8 @@ - - + + diff --git a/tests/ImageSharp.Benchmarks/LoadResizeSave/LoadResizeSaveStressRunner.cs b/tests/ImageSharp.Benchmarks/LoadResizeSave/LoadResizeSaveStressRunner.cs index a06784f1b1..e7d240acd3 100644 --- a/tests/ImageSharp.Benchmarks/LoadResizeSave/LoadResizeSaveStressRunner.cs +++ b/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 tasks = new(); + List 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); diff --git a/tests/ImageSharp.Tests.ProfilingSandbox/ImageSharp.Tests.ProfilingSandbox.csproj b/tests/ImageSharp.Tests.ProfilingSandbox/ImageSharp.Tests.ProfilingSandbox.csproj index b93d011910..832f3d171f 100644 --- a/tests/ImageSharp.Tests.ProfilingSandbox/ImageSharp.Tests.ProfilingSandbox.csproj +++ b/tests/ImageSharp.Tests.ProfilingSandbox/ImageSharp.Tests.ProfilingSandbox.csproj @@ -19,7 +19,7 @@ - net8.0 + net8.0;net9.0 diff --git a/tests/ImageSharp.Tests/Common/GaussianEliminationSolverTest.cs b/tests/ImageSharp.Tests/Common/GaussianEliminationSolverTest.cs new file mode 100644 index 0000000000..95b8d2013f --- /dev/null +++ b/tests/ImageSharp.Tests/Common/GaussianEliminationSolverTest.cs @@ -0,0 +1,66 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.ImageSharp.Processing.Processors.Transforms.Linear; + +namespace SixLabors.ImageSharp.Tests.Common; + +public class GaussianEliminationSolverTest +{ + [Theory] + [MemberData(nameof(MatrixTestData))] + public void CanSolve(double[][] matrix, double[] result, double[] expected) + { + GaussianEliminationSolver.Solve(matrix, result); + + for (int i = 0; i < expected.Length; i++) + { + Assert.Equal(result[i], expected[i], 4); + } + } + + public static TheoryData MatrixTestData + { + get + { + TheoryData data = []; + { + double[][] matrix = + [ + [2, 3, 4], + [1, 2, 3], + [3, -4, 0], + ]; + double[] result = [6, 4, 10]; + double[] expected = [18 / 11f, -14 / 11f, 18 / 11f]; + data.Add(matrix, result, expected); + } + + { + double[][] matrix = + [ + [1, 4, -1], + [2, 5, 8], + [1, 3, -3], + ]; + double[] result = [4, 15, 1]; + double[] expected = [1, 1, 1]; + data.Add(matrix, result, expected); + } + + { + double[][] matrix = + [ + [-1, 0, 0], + [0, 1, 0], + [0, 0, 1], + ]; + double[] result = [1, 2, 3]; + double[] expected = [-1, 2, 3]; + data.Add(matrix, result, expected); + } + + return data; + } + } +} diff --git a/tests/ImageSharp.Tests/Formats/Gif/GifEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Gif/GifEncoderTests.cs index 77ac51e8a1..c08db84eb6 100644 --- a/tests/ImageSharp.Tests/Formats/Gif/GifEncoderTests.cs +++ b/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); diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.cs index 1f4b3e4656..58b437af0f 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.cs @@ -160,6 +160,64 @@ public partial class JpegEncoderTests TestJpegEncoderCore(provider, colorType, 100, comparer); } + [Theory] + [WithFile(TestImages.Png.CalliphoraPartial, nameof(NonSubsampledEncodingSetups), PixelTypes.Rgb24)] + [WithFile(TestImages.Png.CalliphoraPartial, nameof(SubsampledEncodingSetups), PixelTypes.Rgb24)] + [WithFile(TestImages.Png.BikeGrayscale, nameof(LuminanceEncodingSetups), PixelTypes.L8)] + [WithFile(TestImages.Jpeg.Baseline.Cmyk, nameof(CmykEncodingSetups), PixelTypes.Rgb24)] + [WithFile(TestImages.Jpeg.Baseline.Ycck, nameof(YcckEncodingSetups), PixelTypes.Rgb24)] + public void EncodeProgressive_DefaultNumberOfScans(TestImageProvider provider, JpegColorType colorType, int quality, float tolerance) + where TPixel : unmanaged, IPixel + { + using Image image = provider.GetImage(); + + JpegEncoder encoder = new() + { + Quality = quality, + ColorType = colorType, + Progressive = true + }; + string info = $"{colorType}-Q{quality}"; + + ImageComparer comparer = new TolerantImageComparer(tolerance); + + // Does DebugSave & load reference CompareToReferenceInput(): + image.VerifyEncoder(provider, "jpeg", info, encoder, comparer, referenceImageExtension: "jpg"); + } + + [Theory] + [WithFile(TestImages.Png.CalliphoraPartial, nameof(NonSubsampledEncodingSetups), PixelTypes.Rgb24)] + [WithFile(TestImages.Png.CalliphoraPartial, nameof(SubsampledEncodingSetups), PixelTypes.Rgb24)] + [WithFile(TestImages.Png.BikeGrayscale, nameof(LuminanceEncodingSetups), PixelTypes.L8)] + [WithFile(TestImages.Jpeg.Baseline.Cmyk, nameof(CmykEncodingSetups), PixelTypes.Rgb24)] + [WithFile(TestImages.Jpeg.Baseline.Ycck, nameof(YcckEncodingSetups), PixelTypes.Rgb24)] + public void EncodeProgressive_CustomNumberOfScans(TestImageProvider provider, JpegColorType colorType, int quality, float tolerance) +where TPixel : unmanaged, IPixel + { + using Image image = provider.GetImage(); + + JpegEncoder encoder = new() + { + Quality = quality, + ColorType = colorType, + Progressive = true, + ProgressiveScans = 4, + RestartInterval = 7 + }; + string info = $"{colorType}-Q{quality}"; + + using MemoryStream ms = new(); + image.SaveAsJpeg(ms, encoder); + ms.Position = 0; + + // TEMP: Save decoded output as PNG so we can do a pixel compare. + using Image image2 = Image.Load(ms); + image2.DebugSave(provider, testOutputDetails: info, extension: "png"); + + ImageComparer comparer = new TolerantImageComparer(tolerance); + image.VerifyEncoder(provider, "jpeg", info, encoder, comparer, referenceImageExtension: "jpg"); + } + [Theory] [InlineData(JpegColorType.YCbCrRatio420)] [InlineData(JpegColorType.YCbCrRatio444)] diff --git a/tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs index 009327c17f..53a795c649 100644 --- a/tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs +++ b/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); diff --git a/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs index 97f02f3684..09cfe2cbea 100644 --- a/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs @@ -23,7 +23,6 @@ public class TiffDecoderTests : TiffDecoderBaseTester public static readonly string[] MultiframeTestImages = Multiframes; [Theory] - [WithFile(MultiframeDifferentSize, PixelTypes.Rgba32)] [WithFile(MultiframeDifferentVariants, PixelTypes.Rgba32)] [WithFile(Cmyk64BitDeflate, PixelTypes.Rgba32)] public void ThrowsNotSupported(TestImageProvider provider) @@ -596,6 +595,16 @@ public class TiffDecoderTests : TiffDecoderBaseTester Assert.Equal(1, image.Frames.Count); } + [Theory] + [WithFile(MultiFrameMipMap, PixelTypes.Rgba32)] + public void CanDecode_MultiFrameMipMap(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using Image image = provider.GetImage(TiffDecoder.Instance); + Assert.Equal(7, image.Frames.Count); + image.DebugSaveMultiFrame(provider); + } + [Theory] [WithFile(RgbJpegCompressed, PixelTypes.Rgba32)] [WithFile(RgbJpegCompressed2, PixelTypes.Rgba32)] @@ -710,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); } diff --git a/tests/ImageSharp.Tests/Formats/Tiff/TiffEncoderMultiframeTests.cs b/tests/ImageSharp.Tests/Formats/Tiff/TiffEncoderMultiframeTests.cs index dce6ebc38f..716b978a71 100644 --- a/tests/ImageSharp.Tests/Formats/Tiff/TiffEncoderMultiframeTests.cs +++ b/tests/ImageSharp.Tests/Formats/Tiff/TiffEncoderMultiframeTests.cs @@ -18,7 +18,6 @@ public class TiffEncoderMultiframeTests : TiffEncoderBaseTester where TPixel : unmanaged, IPixel => TestTiffEncoderCore(provider, TiffBitsPerPixel.Bit24, TiffPhotometricInterpretation.Rgb); [Theory] - [WithFile(MultiframeDifferentSize, PixelTypes.Rgba32)] [WithFile(MultiframeDifferentVariants, PixelTypes.Rgba32)] public void TiffEncoder_EncodeMultiframe_NotSupport(TestImageProvider provider) where TPixel : unmanaged, IPixel => Assert.Throws(() => TestTiffEncoderCore(provider, TiffBitsPerPixel.Bit24, TiffPhotometricInterpretation.Rgb)); diff --git a/tests/ImageSharp.Tests/Formats/Tiff/TiffEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Tiff/TiffEncoderTests.cs index 1972101164..0d59625ca7 100644 --- a/tests/ImageSharp.Tests/Formats/Tiff/TiffEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Tiff/TiffEncoderTests.cs @@ -4,6 +4,7 @@ using SixLabors.ImageSharp.Formats.Tiff; using SixLabors.ImageSharp.Formats.Tiff.Constants; using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing; using static SixLabors.ImageSharp.Tests.TestImages.Tiff; namespace SixLabors.ImageSharp.Tests.Formats.Tiff; @@ -292,6 +293,82 @@ public class TiffEncoderTests : TiffEncoderBaseTester Assert.Equal(expectedCompression, frameMetaData.Compression); } + [Theory] + [WithFile(MultiFrameMipMap, PixelTypes.Rgba32)] + public void TiffEncoder_EncodesMultiFrameMipMap(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using Image image = provider.GetImage(TiffDecoder.Instance); + Assert.Equal(7, image.Frames.Count); + + using MemoryStream memStream = new(); + image.SaveAsTiff(memStream); + + memStream.Position = 0; + using Image output = Image.Load(memStream); + + Assert.Equal(image.Size, output.Size); + Assert.Equal(image.Frames.Count, output.Frames.Count); + + for (int i = 0; i < image.Frames.Count; i++) + { + TiffFrameMetadata inputMetadata = image.Frames[i].Metadata.GetTiffMetadata(); + TiffFrameMetadata outputMetadata = output.Frames[i].Metadata.GetTiffMetadata(); + + Assert.Equal(inputMetadata.EncodingWidth, outputMetadata.EncodingWidth); + Assert.Equal(inputMetadata.EncodingHeight, outputMetadata.EncodingHeight); + } + } + + [Theory] + [WithFile(MultiFrameMipMap, PixelTypes.Rgba32)] + public void TiffEncoder_EncodesMultiFrameMipMap_WithScaling(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using Image image = provider.GetImage(TiffDecoder.Instance); + Assert.Equal(7, image.Frames.Count); + + Size size = image.Size; + + List encodedDimensions = []; + foreach (ImageFrame frame in image.Frames) + { + TiffFrameMetadata metadata = frame.Metadata.GetTiffMetadata(); + encodedDimensions.Add(new Size(metadata.EncodingWidth, metadata.EncodingHeight)); + } + + const int scale = 2; + image.Mutate(x => x.Resize(image.Width / scale, image.Height / scale)); + + using MemoryStream memStream = new(); + image.SaveAsTiff(memStream); + + memStream.Position = 0; + using Image output = Image.Load(memStream); + + Assert.Equal(image.Size, output.Size); + Assert.Equal(image.Frames.Count, output.Frames.Count); + + // The encoded dimensions should automatically be scaled down by the + // horizontal and vertical scaling factors. + float ratioX = output.Width / (float)size.Width; + float ratioY = output.Height / (float)size.Height; + + for (int i = 0; i < image.Frames.Count; i++) + { + TiffFrameMetadata inputMetadata = image.Frames[i].Metadata.GetTiffMetadata(); + TiffFrameMetadata outputMetadata = output.Frames[i].Metadata.GetTiffMetadata(); + + int expectedWidth = (int)MathF.Ceiling(encodedDimensions[i].Width * ratioX); + int expectedHeight = (int)MathF.Ceiling(encodedDimensions[i].Height * ratioY); + + Assert.Equal(expectedWidth, inputMetadata.EncodingWidth); + Assert.Equal(expectedHeight, inputMetadata.EncodingHeight); + Assert.Equal(inputMetadata.EncodingWidth, outputMetadata.EncodingWidth); + Assert.Equal(inputMetadata.EncodingHeight, outputMetadata.EncodingHeight); + } + } + // This makes sure, that when decoding a planar tiff, the planar configuration is not carried over to the encoded image. [Theory] [WithFile(FlowerRgb444Planar, PixelTypes.Rgba32)] diff --git a/tests/ImageSharp.Tests/Formats/WebP/WebpEncoderTests.cs b/tests/ImageSharp.Tests/Formats/WebP/WebpEncoderTests.cs index 10492af8a7..0aa56e252e 100644 --- a/tests/ImageSharp.Tests/Formats/WebP/WebpEncoderTests.cs +++ b/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; } } @@ -516,6 +516,21 @@ public class WebpEncoderTests image.VerifyEncoder(provider, "webp", string.Empty, encoder); } + // https://github.com/SixLabors/ImageSharp/issues/2801 + [Theory] + [WithFile(Lossy.Issue2801, PixelTypes.Rgba32)] + public void WebpDecoder_CanDecode_Issue2801(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + WebpEncoder encoder = new() + { + Quality = 100 + }; + + using Image image = provider.GetImage(); + image.VerifyEncoder(provider, "webp", string.Empty, encoder, ImageComparer.TolerantPercentage(0.0994F)); + } + public static void RunEncodeLossy_WithPeakImage() { TestImageProvider provider = TestImageProvider.File(TestImageLossyFullPath); @@ -531,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(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using Image 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(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using Image 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% diff --git a/tests/ImageSharp.Tests/IO/ChunkedMemoryStreamTests.cs b/tests/ImageSharp.Tests/IO/ChunkedMemoryStreamTests.cs index 1803cfddb9..390170cfef 100644 --- a/tests/ImageSharp.Tests/IO/ChunkedMemoryStreamTests.cs +++ b/tests/ImageSharp.Tests/IO/ChunkedMemoryStreamTests.cs @@ -13,6 +13,8 @@ namespace SixLabors.ImageSharp.Tests.IO; /// public class ChunkedMemoryStreamTests { + private readonly Random bufferFiller = new(123); + /// /// The default length in bytes of each buffer chunk when allocating large buffers. /// @@ -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(() => ms2.Read(null, 0, 0)); Assert.Throws(() => 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 bytArrRet; - Span bytArr = new byte[] { byte.MinValue, byte.MaxValue, 1, 2, 3, 4, 5, 6, 128, 250 }; + Span 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 bytArrRet; - Span bytArr = new byte[] { byte.MinValue, byte.MaxValue, 1, 2, 3, 4, 5, 6, 128, 250 }; + Span 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(() => 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(() => 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 allImageFiles = Directory.EnumerateFiles(TestEnvironment.InputImagesDirectoryFullPath, "*.*", SearchOption.AllDirectories) .Where(s => !s.EndsWith("txt", StringComparison.OrdinalIgnoreCase)); - var result = new List(); + List 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 AllTestImages = GetAllTestImages(); + public static IEnumerable AllTestImages { get; } = GetAllTestImages(); [Theory] [WithFileCollection(nameof(AllTestImages), PixelTypes.Rgba32)] @@ -334,40 +350,77 @@ public class ChunkedMemoryStreamTests ((TestImageProvider.FileProvider)provider).FilePath); using FileStream fs = File.OpenRead(fullPath); - using var nonSeekableStream = new NonSeekableStream(fs); + using NonSeekableStream nonSeekableStream = new(fs); + + using Image actual = Image.Load(nonSeekableStream); + + ImageComparer.Exact.VerifySimilarity(expected, actual); + expected.Dispose(); + } + + [Theory] + [WithFileCollection(nameof(AllTestImages), PixelTypes.Rgba32)] + public void EncoderIntegrationTest(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + if (!TestEnvironment.Is64BitProcess) + { + return; + } + + Image expected; + try + { + expected = provider.GetImage(); + } + catch + { + // The image is invalid + return; + } - var actual = Image.Load(nonSeekableStream); + string fullPath = Path.Combine( + TestEnvironment.InputImagesDirectoryFullPath, + ((TestImageProvider.FileProvider)provider).FilePath); + + using MemoryStream ms = new(); + using NonSeekableStream nonSeekableStream = new(ms); + expected.SaveAsWebp(nonSeekableStream); + + using Image actual = Image.Load(nonSeekableStream); ImageComparer.Exact.VerifySimilarity(expected, actual); + expected.Dispose(); } public static IEnumerable 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() }; } - 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)); } diff --git a/tests/ImageSharp.Tests/Image/NonSeekableStream.cs b/tests/ImageSharp.Tests/Image/NonSeekableStream.cs index 4b1f6e1568..2941490e9a 100644 --- a/tests/ImageSharp.Tests/Image/NonSeekableStream.cs +++ b/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); } diff --git a/tests/ImageSharp.Tests/ImageSharp.Tests.csproj b/tests/ImageSharp.Tests/ImageSharp.Tests.csproj index f59da8361c..b9e8725029 100644 --- a/tests/ImageSharp.Tests/ImageSharp.Tests.csproj +++ b/tests/ImageSharp.Tests/ImageSharp.Tests.csproj @@ -12,7 +12,7 @@ - net8.0 + net8.0;net9.0 diff --git a/tests/ImageSharp.Tests/Processing/Convolution/DetectEdgesTest.cs b/tests/ImageSharp.Tests/Processing/Convolution/DetectEdgesTest.cs index 2a1273ebb7..713daa0e9f 100644 --- a/tests/ImageSharp.Tests/Processing/Convolution/DetectEdgesTest.cs +++ b/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(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(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(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(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(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(this.rect); Assert.Equal(grayscale, processor.Grayscale); diff --git a/tests/ImageSharp.Tests/Processing/Convolution/GaussianBlurTest.cs b/tests/ImageSharp.Tests/Processing/Convolution/GaussianBlurTest.cs index 5142791603..410862ebfa 100644 --- a/tests/ImageSharp.Tests/Processing/Convolution/GaussianBlurTest.cs +++ b/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 processor = this.Verify(); 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 processor = this.Verify(); 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(this.rect); + this.operations.GaussianBlur(this.rect, 0.6f); + GaussianBlurProcessor processor = this.Verify(this.rect); Assert.Equal(.6f, processor.Sigma); } diff --git a/tests/ImageSharp.Tests/Processing/Convolution/GaussianSharpenTest.cs b/tests/ImageSharp.Tests/Processing/Convolution/GaussianSharpenTest.cs index b48ebac0dc..fd9e64c467 100644 --- a/tests/ImageSharp.Tests/Processing/Convolution/GaussianSharpenTest.cs +++ b/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 processor = this.Verify(); 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 processor = this.Verify(); 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(this.rect); + this.operations.GaussianSharpen(this.rect, 0.6f); + GaussianSharpenProcessor processor = this.Verify(this.rect); Assert.Equal(.6f, processor.Sigma); } diff --git a/tests/ImageSharp.Tests/Processing/Convolution/MedianBlurTest.cs b/tests/ImageSharp.Tests/Processing/Convolution/MedianBlurTest.cs index dc497628fb..77fbc70823 100644 --- a/tests/ImageSharp.Tests/Processing/Convolution/MedianBlurTest.cs +++ b/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 processor = this.Verify(); 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(this.rect); + this.operations.MedianBlur(this.rect, 5, false); + MedianBlurProcessor processor = this.Verify(this.rect); Assert.Equal(5, processor.Radius); Assert.False(processor.PreserveAlpha); diff --git a/tests/ImageSharp.Tests/Processing/Normalization/MagickCompareTests.cs b/tests/ImageSharp.Tests/Processing/Normalization/MagickCompareTests.cs index 5fb0a4e934..2b609a9b2a 100644 --- a/tests/ImageSharp.Tests/Processing/Normalization/MagickCompareTests.cs +++ b/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 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(magickImage); } - using (Image image = provider.GetImage()) + using Image 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(TestImageProvider provider) + private static FileStream LoadAsStream(TestImageProvider provider) where TPixel : unmanaged, ImageSharp.PixelFormats.IPixel { - string path = TestImageProvider.GetFilePathOrNull(provider); - if (path == null) - { - throw new InvalidOperationException("CompareToMagick() works only with file providers!"); - } + string path = TestImageProvider.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 ConvertImageFromMagick(MagickImage magickImage) + private static Image ConvertImageFromMagick(MagickImage magickImage) where TPixel : unmanaged, ImageSharp.PixelFormats.IPixel { Configuration configuration = Configuration.Default.Clone(); configuration.PreferContiguousImageBuffers = true; - var result = new Image(configuration, magickImage.Width, magickImage.Height); + Image result = new(configuration, (int)magickImage.Width, (int)magickImage.Height); Assert.True(result.DangerousTryGetSinglePixelMemory(out Memory resultPixels)); diff --git a/tests/ImageSharp.Tests/Processing/Processors/Convolution/BokehBlurTest.cs b/tests/ImageSharp.Tests/Processing/Processors/Convolution/BokehBlurTest.cs index c94983ecd5..f045c981eb 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Convolution/BokehBlurTest.cs +++ b/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), diff --git a/tests/ImageSharp.Tests/Processing/Processors/Convolution/ConvolutionTests.cs b/tests/ImageSharp.Tests/Processing/Processors/Convolution/ConvolutionTests.cs new file mode 100644 index 0000000000..0cb56b732d --- /dev/null +++ b/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> Values = new TheoryData> + { + // 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(TestImageProvider provider, DenseMatrix value) + where TPixel : unmanaged, IPixel + => provider.RunValidatingProcessorTest( + x => x.Convolve(value), + string.Join('_', value.Data), + ValidatorComparer); + + [Theory] + [WithFileCollection(nameof(InputImages), nameof(Values), PixelTypes.Rgba32)] + public void InBox(TestImageProvider provider, DenseMatrix value) + where TPixel : unmanaged, IPixel + => provider.RunRectangleConstrainedValidatingProcessorTest( + (x, rect) => x.Convolve(rect, value), + string.Join('_', value.Data), + ValidatorComparer); +} diff --git a/tests/ImageSharp.Tests/Processing/Processors/Convolution/GaussianBlurTest.cs b/tests/ImageSharp.Tests/Processing/Processors/Convolution/GaussianBlurTest.cs index 47ccaa46cd..0728bb5d84 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Convolution/GaussianBlurTest.cs +++ b/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); } diff --git a/tests/ImageSharp.Tests/Processing/Processors/Convolution/GaussianSharpenTest.cs b/tests/ImageSharp.Tests/Processing/Processors/Convolution/GaussianSharpenTest.cs index 96c58f7010..c6b7c82363 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Convolution/GaussianSharpenTest.cs +++ b/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); } diff --git a/tests/ImageSharp.Tests/Processing/Processors/Convolution/MedianBlurTest.cs b/tests/ImageSharp.Tests/Processing/Processors/Convolution/MedianBlurTest.cs index 7cc0ef9d50..048b843580 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Convolution/MedianBlurTest.cs +++ b/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); } diff --git a/tests/ImageSharp.Tests/Processing/Processors/Transforms/AffineTransformTests.cs b/tests/ImageSharp.Tests/Processing/Processors/Transforms/AffineTransformTests.cs index ab97dfbf6a..a7855e23aa 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Transforms/AffineTransformTests.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Transforms/AffineTransformTests.cs @@ -219,6 +219,24 @@ public class AffineTransformTests Assert.Equal(100, image.Height); } + [Theory] + [WithSolidFilledImages(4, 4, nameof(Color.Red), PixelTypes.Rgba32)] + public void Issue2753(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using Image image = provider.GetImage(); + + AffineTransformBuilder builder = + new AffineTransformBuilder().AppendRotationDegrees(270, new Vector2(3.5f, 3.5f)); + image.Mutate(x => x.BackgroundColor(Color.Red)); + image.Mutate(x => x = x.Transform(builder)); + + image.DebugSave(provider); + + Assert.Equal(4, image.Width); + Assert.Equal(8, image.Height); + } + [Theory] [WithTestPatternImages(100, 100, PixelTypes.Rgba32)] public void Identity(TestImageProvider provider) diff --git a/tests/ImageSharp.Tests/Processing/Transforms/ProjectiveTransformTests.cs b/tests/ImageSharp.Tests/Processing/Transforms/ProjectiveTransformTests.cs index 4aec530364..6b6db69c11 100644 --- a/tests/ImageSharp.Tests/Processing/Transforms/ProjectiveTransformTests.cs +++ b/tests/ImageSharp.Tests/Processing/Transforms/ProjectiveTransformTests.cs @@ -55,6 +55,14 @@ public class ProjectiveTransformTests { TaperSide.Right, TaperCorner.RightOrBottom }, }; + public static readonly TheoryData QuadDistortionData = new() + { + { new PointF(0, 0), new PointF(150, 0), new PointF(150, 150), new PointF(0, 150) }, // source == destination + { new PointF(25, 50), new PointF(210, 25), new PointF(140, 210), new PointF(15, 125) }, // Distortion + { new PointF(-50, -50), new PointF(200, -50), new PointF(200, 200), new PointF(-50, 200) }, // Scaling + { new PointF(150, 0), new PointF(150, 150), new PointF(0, 150), new PointF(0, 0) }, // Rotation + }; + public ProjectiveTransformTests(ITestOutputHelper output) => this.Output = output; [Theory] @@ -93,6 +101,24 @@ public class ProjectiveTransformTests } } + [Theory] + [WithTestPatternImages(nameof(QuadDistortionData), 150, 150, PixelTypes.Rgba32)] + public void Transform_WithQuadDistortion(TestImageProvider provider, PointF topLeft, PointF topRight, PointF bottomRight, PointF bottomLeft) + where TPixel : unmanaged, IPixel + { + using (Image image = provider.GetImage()) + { + ProjectiveTransformBuilder builder = new ProjectiveTransformBuilder() + .AppendQuadDistortion(topLeft, topRight, bottomRight, bottomLeft); + + image.Mutate(i => i.Transform(builder)); + + FormattableString testOutputDetails = $"{topLeft}-{topRight}-{bottomRight}-{bottomLeft}"; + image.DebugSave(provider, testOutputDetails); + image.CompareFirstFrameToReferenceOutput(TolerantComparer, provider, testOutputDetails); + } + } + [Theory] [WithSolidFilledImages(100, 100, 0, 0, 255, PixelTypes.Rgba32)] public void RawTransformMatchesDocumentedExample(TestImageProvider provider) @@ -128,11 +154,11 @@ public class ProjectiveTransformTests using (Image image = provider.GetImage()) { #pragma warning disable SA1117 // Parameters should be on same line or separate lines - var matrix = new Matrix4x4( - 0.260987f, -0.434909f, 0, -0.0022184f, - 0.373196f, 0.949882f, 0, -0.000312129f, - 0, 0, 1, 0, - 52, 165, 0, 1); + Matrix4x4 matrix = new( + 0.260987f, -0.434909f, 0, -0.0022184f, + 0.373196f, 0.949882f, 0, -0.000312129f, + 0, 0, 1, 0, + 52, 165, 0, 1); #pragma warning restore SA1117 // Parameters should be on same line or separate lines ProjectiveTransformBuilder builder = new ProjectiveTransformBuilder() diff --git a/tests/ImageSharp.Tests/Processing/Transforms/TransformBuilderTestBase.cs b/tests/ImageSharp.Tests/Processing/Transforms/TransformBuilderTestBase.cs index 9d256efc1c..f046c2503c 100644 --- a/tests/ImageSharp.Tests/Processing/Transforms/TransformBuilderTestBase.cs +++ b/tests/ImageSharp.Tests/Processing/Transforms/TransformBuilderTestBase.cs @@ -2,6 +2,7 @@ // Licensed under the Six Labors Split License. using System.Numerics; +using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Processing.Processors.Transforms; namespace SixLabors.ImageSharp.Tests.Processing.Transforms; @@ -97,7 +98,7 @@ public abstract class TransformBuilderTestBase this.AppendRotationDegrees(builder, degrees); // TODO: We should also test CreateRotationMatrixDegrees() (and all TransformUtils stuff!) for correctness - Matrix3x2 matrix = TransformUtils.CreateRotationTransformMatrixDegrees(degrees, size); + Matrix3x2 matrix = TransformUtils.CreateRotationTransformMatrixDegrees(degrees, size, TransformSpace.Pixel); var position = new Vector2(x, y); var expected = Vector2.Transform(position, matrix); @@ -151,7 +152,7 @@ public abstract class TransformBuilderTestBase this.AppendSkewDegrees(builder, degreesX, degreesY); - Matrix3x2 matrix = TransformUtils.CreateSkewTransformMatrixDegrees(degreesX, degreesY, size); + Matrix3x2 matrix = TransformUtils.CreateSkewTransformMatrixDegrees(degreesX, degreesY, size, TransformSpace.Pixel); var position = new Vector2(x, y); var expected = Vector2.Transform(position, matrix); diff --git a/tests/ImageSharp.Tests/Processing/Transforms/TransformsHelpersTest.cs b/tests/ImageSharp.Tests/Processing/Transforms/TransformsHelpersTest.cs deleted file mode 100644 index 21b92a01e8..0000000000 --- a/tests/ImageSharp.Tests/Processing/Transforms/TransformsHelpersTest.cs +++ /dev/null @@ -1,35 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Metadata.Profiles.Exif; -using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Processing.Processors.Transforms; -using SixLabors.ImageSharp.Tests.TestUtilities; - -namespace SixLabors.ImageSharp.Tests.Processing.Transforms; - -[Trait("Category", "Processors")] -public class TransformsHelpersTest -{ - [Fact] - public void HelperCanChangeExifDataType() - { - int xy = 1; - - using (var img = new Image(xy, xy)) - { - var profile = new ExifProfile(); - img.Metadata.ExifProfile = profile; - profile.SetValue(ExifTag.PixelXDimension, xy + ushort.MaxValue); - profile.SetValue(ExifTag.PixelYDimension, xy + ushort.MaxValue); - - Assert.Equal(ExifDataType.Long, profile.GetValue(ExifTag.PixelXDimension).DataType); - Assert.Equal(ExifDataType.Long, profile.GetValue(ExifTag.PixelYDimension).DataType); - - TransformProcessorHelpers.UpdateDimensionalMetadata(img); - - Assert.Equal(ExifDataType.Short, profile.GetValue(ExifTag.PixelXDimension).DataType); - Assert.Equal(ExifDataType.Short, profile.GetValue(ExifTag.PixelYDimension).DataType); - } - } -} diff --git a/tests/ImageSharp.Tests/TestImages.cs b/tests/ImageSharp.Tests/TestImages.cs index 79563cd7e9..022c0ed945 100644 --- a/tests/ImageSharp.Tests/TestImages.cs +++ b/tests/ImageSharp.Tests/TestImages.cs @@ -838,6 +838,7 @@ public static class TestImages public const string Issue2257 = "Webp/issues/Issue2257.webp"; public const string Issue2670 = "Webp/issues/Issue2670.webp"; public const string Issue2763 = "Webp/issues/Issue2763.png"; + public const string Issue2801 = "Webp/issues/Issue2801.webp"; } } diff --git a/tests/ImageSharp.Tests/TestUtilities/FeatureTesting/FeatureTestRunner.cs b/tests/ImageSharp.Tests/TestUtilities/FeatureTesting/FeatureTestRunner.cs index 07ad5e8f03..63126dcbca 100644 --- a/tests/ImageSharp.Tests/TestUtilities/FeatureTesting/FeatureTestRunner.cs +++ b/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, diff --git a/tests/ImageSharp.Tests/TestUtilities/ImageComparison/ExactImageComparer.cs b/tests/ImageSharp.Tests/TestUtilities/ImageComparison/ExactImageComparer.cs index aa8ab397d2..92fc06eff5 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ImageComparison/ExactImageComparer.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ImageComparison/ExactImageComparer.cs @@ -1,7 +1,6 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; @@ -16,7 +15,7 @@ public class ExactImageComparer : ImageComparer ImageFrame expected, ImageFrame actual) { - if (expected.Size() != actual.Size()) + if (expected.Size != actual.Size) { throw new InvalidOperationException("Calling ImageComparer is invalid when dimensions mismatch!"); } diff --git a/tests/ImageSharp.Tests/TestUtilities/ImageComparison/ImageComparingUtils.cs b/tests/ImageSharp.Tests/TestUtilities/ImageComparison/ImageComparingUtils.cs index f5c70b0885..05f65cfbbc 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ImageComparison/ImageComparingUtils.cs +++ b/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 { - string path = TestImageProvider.GetFilePathOrNull(provider); - if (path == null) - { - throw new InvalidOperationException("CompareToOriginal() works only with file providers!"); - } + string path = TestImageProvider.GetFilePathOrNull(provider) + ?? throw new InvalidOperationException("CompareToOriginal() works only with file providers!"); - var testFile = TestFile.Create(path); + TestFile testFile = TestFile.Create(path); using Image magickImage = DecodeWithMagick(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(configuration, magickImage.Width, magickImage.Height); + using MagickImage magickImage = new(fileInfo); + magickImage.AutoOrient(); + Image result = new(configuration, (int)magickImage.Width, (int)magickImage.Height); - Assert.True(result.DangerousTryGetSinglePixelMemory(out Memory resultPixels)); + Assert.True(result.DangerousTryGetSinglePixelMemory(out Memory resultPixels)); - using (IUnsafePixelCollection pixels = magickImage.GetPixelsUnsafe()) - { - byte[] data = pixels.ToByteArray(PixelMapping.RGBA); - - PixelOperations.Instance.FromRgba32Bytes( - configuration, - data, - resultPixels.Span, - resultPixels.Length); - } + using (IUnsafePixelCollection pixels = magickImage.GetPixelsUnsafe()) + { + byte[] data = pixels.ToByteArray(PixelMapping.RGBA); - return result; + PixelOperations.Instance.FromRgba32Bytes( + configuration, + data, + resultPixels.Span, + resultPixels.Length); } + + return result; } } diff --git a/tests/ImageSharp.Tests/TestUtilities/ImageComparison/TolerantImageComparer.cs b/tests/ImageSharp.Tests/TestUtilities/ImageComparison/TolerantImageComparer.cs index 93ed4c6fff..d057267da7 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ImageComparison/TolerantImageComparer.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ImageComparison/TolerantImageComparer.cs @@ -2,7 +2,6 @@ // Licensed under the Six Labors Split License. using System.Runtime.CompilerServices; -using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; @@ -58,7 +57,7 @@ public class TolerantImageComparer : ImageComparer public override ImageSimilarityReport CompareImagesOrFrames(int index, ImageFrame expected, ImageFrame actual) { - if (expected.Size() != actual.Size()) + if (expected.Size != actual.Size) { throw new InvalidOperationException("Calling ImageComparer is invalid when dimensions mismatch!"); } diff --git a/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/MagickReferenceDecoder.cs b/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/MagickReferenceDecoder.cs index f96dc19ee0..74015a4eff 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/MagickReferenceDecoder.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/MagickReferenceDecoder.cs @@ -67,7 +67,7 @@ public class MagickReferenceDecoder : ImageDecoder List> framesList = []; foreach (IMagickImage magicFrame in magickImageCollection) { - ImageFrame frame = new(configuration, magicFrame.Width, magicFrame.Height); + ImageFrame frame = new(configuration, (int)magicFrame.Width, (int)magicFrame.Height); framesList.Add(frame); MemoryGroup framePixels = frame.PixelBuffer.FastMemoryGroup; diff --git a/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_RotateScaleTranslate_Rgba32_TestPattern100x50_R(50)_S(1,1)_T(-20,-10).png b/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_RotateScaleTranslate_Rgba32_TestPattern100x50_R(50)_S(1,1)_T(-20,-10).png index 2ba0560e65..480c07da48 100644 --- a/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_RotateScaleTranslate_Rgba32_TestPattern100x50_R(50)_S(1,1)_T(-20,-10).png +++ b/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_RotateScaleTranslate_Rgba32_TestPattern100x50_R(50)_S(1,1)_T(-20,-10).png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1b926c8335eca5530d8704739cecae0799cc651139daedb1f88ac85b0ee1bd5d -size 9484 +oid sha256:5ae57ca0658b1ffa7aca9031f4ec065ab5a9813fb8a9c5acd221526df6a4f729 +size 9747 diff --git a/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_RotateScaleTranslate_Rgba32_TestPattern100x50_R(50)_S(1,1)_T(0,0).png b/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_RotateScaleTranslate_Rgba32_TestPattern100x50_R(50)_S(1,1)_T(0,0).png index d141a2e28c..d1ea99cf90 100644 --- a/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_RotateScaleTranslate_Rgba32_TestPattern100x50_R(50)_S(1,1)_T(0,0).png +++ b/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_RotateScaleTranslate_Rgba32_TestPattern100x50_R(50)_S(1,1)_T(0,0).png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:7f79389f79d91ac6749f21c27a592edfd2cff6efbb1d46296a26ae60d4e721f8 -size 10103 +oid sha256:0fced9def2b41cbbf215a49ea6ef6baf4c3c041fd180671eb209db5c6e7177e5 +size 10470 diff --git a/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_RotateScaleTranslate_Rgba32_TestPattern100x50_R(50)_S(1,1)_T(20,10).png b/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_RotateScaleTranslate_Rgba32_TestPattern100x50_R(50)_S(1,1)_T(20,10).png index d01fcb4a49..227f546515 100644 --- a/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_RotateScaleTranslate_Rgba32_TestPattern100x50_R(50)_S(1,1)_T(20,10).png +++ b/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_RotateScaleTranslate_Rgba32_TestPattern100x50_R(50)_S(1,1)_T(20,10).png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:322c7e061f8565efdc19642e27353ec3073ee43d8c17fbef8c13be3bb60d11dc -size 10190 +oid sha256:1e4cc16c2f1b439f8780dead04db01fed95f8e20b68270ae8e7a988af999e3db +size 10561 diff --git a/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_RotateScaleTranslate_Rgba32_TestPattern100x50_R(50)_S(1.1,1.3)_T(30,-20).png b/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_RotateScaleTranslate_Rgba32_TestPattern100x50_R(50)_S(1.1,1.3)_T(30,-20).png index 171080746b..b93742a858 100644 --- a/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_RotateScaleTranslate_Rgba32_TestPattern100x50_R(50)_S(1.1,1.3)_T(30,-20).png +++ b/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_RotateScaleTranslate_Rgba32_TestPattern100x50_R(50)_S(1.1,1.3)_T(30,-20).png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:544e7bac188d0869f98ec075fa0e73ab831e4dafe40c1520dce194df6a53c9b8 -size 12737 +oid sha256:06e3966550f1c3ae72796e5522f7829cf1f86daca469c479acf49e6fae72e3d0 +size 13227 diff --git a/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_RotateScaleTranslate_Rgba32_TestPattern100x50_R(50)_S(1.5,1.5)_T(0,0).png b/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_RotateScaleTranslate_Rgba32_TestPattern100x50_R(50)_S(1.5,1.5)_T(0,0).png index 07c65142a4..57c3b02ba7 100644 --- a/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_RotateScaleTranslate_Rgba32_TestPattern100x50_R(50)_S(1.5,1.5)_T(0,0).png +++ b/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_RotateScaleTranslate_Rgba32_TestPattern100x50_R(50)_S(1.5,1.5)_T(0,0).png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:2ccc08769974e4088702d2c95fd274af7e02095955953b424a6313d656a77735 -size 19974 +oid sha256:8ce5fefe04cc2a036fddcfcf038901a7a09b4ea5d0621a1e0d3abc8430953ae3 +size 20778 diff --git a/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_RotateScale_ManuallyCentered_Rgba32_TestPattern96x96_R(50)_S(0.8).png b/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_RotateScale_ManuallyCentered_Rgba32_TestPattern96x96_R(50)_S(0.8).png index 84fffa468f..b3bfc7ee51 100644 --- a/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_RotateScale_ManuallyCentered_Rgba32_TestPattern96x96_R(50)_S(0.8).png +++ b/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_RotateScale_ManuallyCentered_Rgba32_TestPattern96x96_R(50)_S(0.8).png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c1179b300b35d526bab148833ab6240f1207b8ade36674b1f47cc5a2d47a084c -size 10603 +oid sha256:b653c0fe761d351cb15b09f35da578a954d103dea7507e2c1d7c4ebf3bdac49a +size 10943 diff --git a/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_Bicubic.png b/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_Bicubic.png index 1f1d530517..a295c016d5 100644 --- a/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_Bicubic.png +++ b/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_Bicubic.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f666fe67ee4a1c7152fc6190affba95ea4cbd857d96bac0968e5f1fd89792d32 -size 13486 +oid sha256:3a17bb1653cc6d6ecc292ce0670c651bfea032f61c6a0e84636205bde53a86f8 +size 13536 diff --git a/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_Box.png b/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_Box.png index 0ce7ad5625..63214687d5 100644 --- a/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_Box.png +++ b/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_Box.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:5b05406a1d95f0709a7aaab7c1f57ba161b7907b76746f61788cfe527796a489 -size 4131 +oid sha256:b8970378312c0d479d618e4d5b8da54175c127db517fbe54f9057188d02cc735 +size 4165 diff --git a/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_CatmullRom.png b/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_CatmullRom.png index e93934b48d..a295c016d5 100644 --- a/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_CatmullRom.png +++ b/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_CatmullRom.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:be52d36cc8f616a781c8b1416ca0bf6207b9acd580e9c06e1ee5ad434d48ab38 -size 13481 +oid sha256:3a17bb1653cc6d6ecc292ce0670c651bfea032f61c6a0e84636205bde53a86f8 +size 13536 diff --git a/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_Hermite.png b/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_Hermite.png index 2a68a381d4..ef37b3e2d6 100644 --- a/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_Hermite.png +++ b/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_Hermite.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:33c99b8f0fb5d10a273a90946767f93ab6cd2dd1942f9829d695987db30dccfa -size 12488 +oid sha256:9bbf7ef00f98b410f309b3bf70ce87d3c6455666a26e89cd004744145a10408a +size 12559 diff --git a/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_Lanczos2.png b/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_Lanczos2.png index 08f89da07e..93a0ce6c54 100644 --- a/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_Lanczos2.png +++ b/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_Lanczos2.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:af2c0201c59065a500ae985e9b7ca164e5bcb4ce2d8d8305103398830472e07c -size 14206 +oid sha256:7f9ab86abad276d58bb029bd8e2c2aaffac5618322788cb3619577c7643e10d2 +size 14223 diff --git a/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_Lanczos3.png b/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_Lanczos3.png index 85d4871036..c2ca6bf57b 100644 --- a/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_Lanczos3.png +++ b/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_Lanczos3.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:4cef17988c4a3a667dede3dd86ed61d0507a84e5b846f52459683fd04e5a396a -size 17297 +oid sha256:05c4dc9af1fef422fd5ada2fa1459c26253e0fb5e5a13226fa2e7445ece32272 +size 17927 diff --git a/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_Lanczos5.png b/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_Lanczos5.png index 347cbf089f..ade9a1ccd8 100644 --- a/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_Lanczos5.png +++ b/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_Lanczos5.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:9699a81572c03c2bc47d8bbdd1d64df26f87df3d4ad59fb6f164f6e82786d91d -size 18853 +oid sha256:82b47e1cad2eea417b99a2e4b68a5ba1a6cd6703f360e8402f3dca8b92373ecc +size 18945 diff --git a/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_Lanczos8.png b/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_Lanczos8.png index 69fe0b1355..cf04e20363 100644 --- a/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_Lanczos8.png +++ b/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_Lanczos8.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:4fb1f59c5393debdff9bd4b7a6c222b7a0686e6d5ef24363e3d5c94ba9b5bc27 -size 20725 +oid sha256:b15ce5a201ee6b946de485a58d3d8e779b6841457e096b2bd7a92968a122f9af +size 20844 diff --git a/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_MitchellNetravali.png b/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_MitchellNetravali.png index 0fa8cf0c06..6be0fc0ff8 100644 --- a/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_MitchellNetravali.png +++ b/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_MitchellNetravali.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8ffa8ca6a60de9fe26a191edc2127886c61c072c1aa2b91fe3125512fe40e1b3 -size 13848 +oid sha256:a1622a48b3f4790d66b229ed29acd18504cedf68d0a548832665c28d47ea663b +size 13857 diff --git a/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_NearestNeighbor.png b/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_NearestNeighbor.png index 36e409ecb9..0064e973ff 100644 --- a/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_NearestNeighbor.png +++ b/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_NearestNeighbor.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:abf1d0f323795c0aaff0ff8b488d9866c5b2f7c64aad83701cb1f60e22668b0e -size 4161 +oid sha256:74df7b82e2148cfc8dae7e05c96009c0d70c09bf39cdc5ef9d727063d2a8cb3f +size 4154 diff --git a/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_Robidoux.png b/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_Robidoux.png index f8f102e4c8..5dd0c52255 100644 --- a/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_Robidoux.png +++ b/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_Robidoux.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:9b0f3c41248138bd501ae844e5b54fb9f49e5d22bab9b2ef0a0654034708b99f -size 14027 +oid sha256:cc740ccd76910e384ad84a780591652ac7ee0ea30abf7fd7f5b146f8ff380f07 +size 13991 diff --git a/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_RobidouxSharp.png b/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_RobidouxSharp.png index fc46cad7c0..a6e120e904 100644 --- a/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_RobidouxSharp.png +++ b/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_RobidouxSharp.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6a3f839d64984b9fda4125c6643f4699add6f95373a2194c5726ed3740565a47 -size 13725 +oid sha256:ccdc54e814604d4d339f6083091abf852aae65052ceb731af998208faddb5b0b +size 13744 diff --git a/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_Spline.png b/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_Spline.png index 58e879d4e3..d32c11d44c 100644 --- a/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_Spline.png +++ b/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_Spline.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:2ac143bc73612cecfffbec049a7b68234e7bf7581e680c3f996a977c6d671cc1 -size 14865 +oid sha256:cd24e0a52c7743ab7d3ed255e3757c2d5495b3f56198556a157df589b1fb67ca +size 14889 diff --git a/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_Triangle.png b/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_Triangle.png index fa25146d9c..72782b0b99 100644 --- a/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_Triangle.png +++ b/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_Triangle.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:4eb9dab20d5a03c0adde05a9b741d9e1b0fb8c3d79054a8bc5788de496e5c7f8 -size 12420 +oid sha256:878f1aab39b0b2405498c24146b8f81248b37b974e5ea7882e96174a034b645f +size 12374 diff --git a/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_Welch.png b/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_Welch.png index dc8ea690c2..6cedab729b 100644 --- a/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_Welch.png +++ b/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_Welch.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0f56ee78cc2fd698ac8ea84912648f0b49d4b4d66b439f6976447c56a44c2998 -size 16909 +oid sha256:dcc2bf4f7e0ab3d56ee71ac1e1855dababeb2e4ec167fd5dc264efdc9e727328 +size 17027 diff --git a/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_With_Custom_Dimensions_Rgba32_TestPattern100x100_0.0001.png b/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_With_Custom_Dimensions_Rgba32_TestPattern100x100_0.0001.png index 4b9953b670..7368a3b007 100644 --- a/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_With_Custom_Dimensions_Rgba32_TestPattern100x100_0.0001.png +++ b/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_With_Custom_Dimensions_Rgba32_TestPattern100x100_0.0001.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:dd3b29b530e221618f65cd5e493b21fe3c27804fde7664636b7bb002f72abbb2 -size 3663 +oid sha256:6c733878f4c0cc6075a01fbe7cb471f8b3e91c2c5eaf89309ea3c073d9cc4921 +size 854 diff --git a/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_With_Custom_Dimensions_Rgba32_TestPattern100x100_0.png b/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_With_Custom_Dimensions_Rgba32_TestPattern100x100_0.png index ce6e8ce9fa..bef0fad79e 100644 --- a/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_With_Custom_Dimensions_Rgba32_TestPattern100x100_0.png +++ b/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_With_Custom_Dimensions_Rgba32_TestPattern100x100_0.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:da8229605bda413676a42f587df250a743540e6e00c04eacb1e622f223e19595 -size 3564 +oid sha256:c86a0ceb875e02b58084fd95e5c439791af313e1fb273baf00b35187a2678d2f +size 657 diff --git a/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_With_Custom_Dimensions_Rgba32_TestPattern100x100_57.png b/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_With_Custom_Dimensions_Rgba32_TestPattern100x100_57.png index 5f4911e47c..da66b26768 100644 --- a/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_With_Custom_Dimensions_Rgba32_TestPattern100x100_57.png +++ b/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_With_Custom_Dimensions_Rgba32_TestPattern100x100_57.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a35757fef08a6fd9b37e719d5be7a82d5ff79f0395e082f697d9ebe9c7f03cc8 -size 5748 +oid sha256:af872886136893938aee82b1ac73e7a1820666a9a5f4bbf34159c09b3283169a +size 5520 diff --git a/tests/Images/External/ReferenceOutput/Convolution/ConvolutionTests/InBox_Rgba32_CalliphoraPartial_-1_-1_-1_-1_16_-1_-1_-1_-1.png b/tests/Images/External/ReferenceOutput/Convolution/ConvolutionTests/InBox_Rgba32_CalliphoraPartial_-1_-1_-1_-1_16_-1_-1_-1_-1.png new file mode 100644 index 0000000000..ffa8e7cd8a --- /dev/null +++ b/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 diff --git a/tests/Images/External/ReferenceOutput/Convolution/ConvolutionTests/InBox_Rgba32_Car_-1_-1_-1_-1_16_-1_-1_-1_-1.png b/tests/Images/External/ReferenceOutput/Convolution/ConvolutionTests/InBox_Rgba32_Car_-1_-1_-1_-1_16_-1_-1_-1_-1.png new file mode 100644 index 0000000000..682fd8b49b --- /dev/null +++ b/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 diff --git a/tests/Images/External/ReferenceOutput/Convolution/ConvolutionTests/InBox_Rgba32_blur_-1_-1_-1_-1_16_-1_-1_-1_-1.png b/tests/Images/External/ReferenceOutput/Convolution/ConvolutionTests/InBox_Rgba32_blur_-1_-1_-1_-1_16_-1_-1_-1_-1.png new file mode 100644 index 0000000000..55d592a60f --- /dev/null +++ b/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 diff --git a/tests/Images/External/ReferenceOutput/Convolution/ConvolutionTests/OnFullImage_Rgba32_CalliphoraPartial_-1_-1_-1_-1_16_-1_-1_-1_-1.png b/tests/Images/External/ReferenceOutput/Convolution/ConvolutionTests/OnFullImage_Rgba32_CalliphoraPartial_-1_-1_-1_-1_16_-1_-1_-1_-1.png new file mode 100644 index 0000000000..f01496ad76 --- /dev/null +++ b/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 diff --git a/tests/Images/External/ReferenceOutput/Convolution/ConvolutionTests/OnFullImage_Rgba32_Car_-1_-1_-1_-1_16_-1_-1_-1_-1.png b/tests/Images/External/ReferenceOutput/Convolution/ConvolutionTests/OnFullImage_Rgba32_Car_-1_-1_-1_-1_16_-1_-1_-1_-1.png new file mode 100644 index 0000000000..99e9737b51 --- /dev/null +++ b/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 diff --git a/tests/Images/External/ReferenceOutput/Convolution/ConvolutionTests/OnFullImage_Rgba32_blur_-1_-1_-1_-1_16_-1_-1_-1_-1.png b/tests/Images/External/ReferenceOutput/Convolution/ConvolutionTests/OnFullImage_Rgba32_blur_-1_-1_-1_-1_16_-1_-1_-1_-1.png new file mode 100644 index 0000000000..ecc09e70ff --- /dev/null +++ b/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 diff --git a/tests/Images/External/ReferenceOutput/Drawing/DrawImageTests/DrawTransformed.png b/tests/Images/External/ReferenceOutput/Drawing/DrawImageTests/DrawTransformed.png index 17238cf2f7..7e693a5839 100644 --- a/tests/Images/External/ReferenceOutput/Drawing/DrawImageTests/DrawTransformed.png +++ b/tests/Images/External/ReferenceOutput/Drawing/DrawImageTests/DrawTransformed.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:00836a98742d177a2376af32d8d858fcf9f26a4da5a311dd5faf5cd80f233c0b -size 184397 +oid sha256:0ba180567e820b145a13c9b26db9c777e95126adfe8e8cacec0ffe1060dcfe8d +size 184124 diff --git a/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/PerspectiveTransformMatchesCSS_Rgba32_Solid290x154_(0,0,255,255).png b/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/PerspectiveTransformMatchesCSS_Rgba32_Solid290x154_(0,0,255,255).png index e7ed4a95f5..f2e87f8509 100644 --- a/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/PerspectiveTransformMatchesCSS_Rgba32_Solid290x154_(0,0,255,255).png +++ b/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/PerspectiveTransformMatchesCSS_Rgba32_Solid290x154_(0,0,255,255).png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d40d0715f695dc55dc2e435cab2c999b657b59ead2e0cc8a95edf7cea7782750 -size 6842 +oid sha256:455b17bc432490435c2453424d17b92b77d036dcbc2b2ab06b960a398bd3136b +size 11119 diff --git a/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithQuadDistortion_Rgba32_TestPattern150x150_PointF [ X=-50, Y=-50 ]-PointF [ X=200, Y=-50 ]-PointF [ X=200, Y=200 ]-PointF [ X=-50, Y=200 ].png b/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithQuadDistortion_Rgba32_TestPattern150x150_PointF [ X=-50, Y=-50 ]-PointF [ X=200, Y=-50 ]-PointF [ X=200, Y=200 ]-PointF [ X=-50, Y=200 ].png new file mode 100644 index 0000000000..38c603855c --- /dev/null +++ b/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithQuadDistortion_Rgba32_TestPattern150x150_PointF [ X=-50, Y=-50 ]-PointF [ X=200, Y=-50 ]-PointF [ X=200, Y=200 ]-PointF [ X=-50, Y=200 ].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:abce6af307a81a8ebac8e502142b00b2615403b5570c8dbe7b6895cfdd1a6d60 +size 66879 diff --git a/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithQuadDistortion_Rgba32_TestPattern150x150_PointF [ X=0, Y=0 ]-PointF [ X=150, Y=0 ]-PointF [ X=150, Y=150 ]-PointF [ X=0, Y=150 ].png b/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithQuadDistortion_Rgba32_TestPattern150x150_PointF [ X=0, Y=0 ]-PointF [ X=150, Y=0 ]-PointF [ X=150, Y=150 ]-PointF [ X=0, Y=150 ].png new file mode 100644 index 0000000000..f7ea0d0060 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithQuadDistortion_Rgba32_TestPattern150x150_PointF [ X=0, Y=0 ]-PointF [ X=150, Y=0 ]-PointF [ X=150, Y=150 ]-PointF [ X=0, Y=150 ].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d4cda265a50aa26711efafdbcd947c9a01eff872611df5298920583f9a3d4224 +size 26458 diff --git a/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithQuadDistortion_Rgba32_TestPattern150x150_PointF [ X=150, Y=0 ]-PointF [ X=150, Y=150 ]-PointF [ X=0, Y=150 ]-PointF [ X=0, Y=0 ].png b/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithQuadDistortion_Rgba32_TestPattern150x150_PointF [ X=150, Y=0 ]-PointF [ X=150, Y=150 ]-PointF [ X=0, Y=150 ]-PointF [ X=0, Y=0 ].png new file mode 100644 index 0000000000..78c37cc448 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithQuadDistortion_Rgba32_TestPattern150x150_PointF [ X=150, Y=0 ]-PointF [ X=150, Y=150 ]-PointF [ X=0, Y=150 ]-PointF [ X=0, Y=0 ].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:278a488a858b8eda141493fe00c617eb1f664196853da8341d7e5b7f231ddce4 +size 24645 diff --git a/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithQuadDistortion_Rgba32_TestPattern150x150_PointF [ X=25, Y=50 ]-PointF [ X=210, Y=25 ]-PointF [ X=140, Y=210 ]-PointF [ X=15, Y=125 ].png b/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithQuadDistortion_Rgba32_TestPattern150x150_PointF [ X=25, Y=50 ]-PointF [ X=210, Y=25 ]-PointF [ X=140, Y=210 ]-PointF [ X=15, Y=125 ].png new file mode 100644 index 0000000000..b4740828d4 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithQuadDistortion_Rgba32_TestPattern150x150_PointF [ X=25, Y=50 ]-PointF [ X=210, Y=25 ]-PointF [ X=140, Y=210 ]-PointF [ X=15, Y=125 ].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e03e79e6fab3a9e43041e54640a04c7cc3677709e7d879f9f410cf8afc7547a7 +size 42691 diff --git a/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_Bicubic.png b/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_Bicubic.png index a83ec12e3e..3826753d53 100644 --- a/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_Bicubic.png +++ b/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_Bicubic.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ac88c089bb5106d9dc9ed8e764f6495b74e1cd98d1df61319934ed78a7578866 -size 13233 +oid sha256:543dbf5376386bf518830850645d69934e2ca17ab208ce3fd5274a6a172f5206 +size 10951 diff --git a/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_Box.png b/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_Box.png index 5518047e4d..f9aa1ffe03 100644 --- a/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_Box.png +++ b/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_Box.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:23d502e2d6b0eb5f12ed3262eb4654927cc937574ae1de61a1d89f6672592017 -size 11828 +oid sha256:0d0cf291ebf5d8cebab1cd76e2830e5e2d2e0d9a050f7187da72680ead39110c +size 2757 diff --git a/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_CatmullRom.png b/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_CatmullRom.png index a83ec12e3e..3826753d53 100644 --- a/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_CatmullRom.png +++ b/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_CatmullRom.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ac88c089bb5106d9dc9ed8e764f6495b74e1cd98d1df61319934ed78a7578866 -size 13233 +oid sha256:543dbf5376386bf518830850645d69934e2ca17ab208ce3fd5274a6a172f5206 +size 10951 diff --git a/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_Hermite.png b/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_Hermite.png index 29014117e3..2f9109ba38 100644 --- a/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_Hermite.png +++ b/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_Hermite.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:5f1e69bfd1f4e9479839d4bddfb2ecc68ff8cda9b15055427406691df94a16db -size 9583 +oid sha256:57698b6666029a55edf8bd35a7ba96f68d224988cf01308a3af1c6606ae9d0b1 +size 10174 diff --git a/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_Lanczos2.png b/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_Lanczos2.png index d417cad9c1..7dfec78983 100644 --- a/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_Lanczos2.png +++ b/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_Lanczos2.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:cd1d0350ca49ff1726c2a20769693698168497944413c653da6edcb6bc9a39e5 -size 17344 +oid sha256:fc7c9da04142a679887c714c43f1838eba0092a869140d234fce3412673207c6 +size 13575 diff --git a/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_Lanczos3.png b/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_Lanczos3.png index cfd3cd48ef..6e3b97f2df 100644 --- a/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_Lanczos3.png +++ b/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_Lanczos3.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:2dc8ed5f721d2d8ae81f1b70308511737b0e649a6b05a011342efce29fa5b1cb -size 23022 +oid sha256:d8b973f41f8afa39b94c71b307b7eb393953e2d083d56e1f0e8f43d6ab1f342a +size 16821 diff --git a/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_Lanczos5.png b/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_Lanczos5.png index 2578c537e8..6986c03912 100644 --- a/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_Lanczos5.png +++ b/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_Lanczos5.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a9514b25bbc875eb00b5c0dc4241f973d466075914a9ec4c4b64ba251323eb5a -size 22321 +oid sha256:122c1501e09516244f0db36e1cca373ff68514a18e84f57ed3072d52d6112e36 +size 17022 diff --git a/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_Lanczos8.png b/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_Lanczos8.png index 1f6609c6f0..76b53fabfb 100644 --- a/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_Lanczos8.png +++ b/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_Lanczos8.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:09138a5ec45480a72370eba3015e7dee33360712745cb2f00e36a5994c1e48a7 -size 24090 +oid sha256:12181516bce69c9302f15bba928fd530796449599cb9744a2411cc796788ee3b +size 18066 diff --git a/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_MitchellNetravali.png b/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_MitchellNetravali.png index 7e4f16695a..ae4242a42b 100644 --- a/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_MitchellNetravali.png +++ b/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_MitchellNetravali.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ed80155afab90710ebab4babbbb787402ceac4f9dee1ff270361e5e8e495c73a -size 13867 +oid sha256:1eb5accc5ada5b963ecef6ac15bfb1845f481e51aef63e06a522ea73bbeab945 +size 11194 diff --git a/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_NearestNeighbor.png b/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_NearestNeighbor.png index 3297f99e21..efb6a2deed 100644 --- a/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_NearestNeighbor.png +++ b/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_NearestNeighbor.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:96fa0ebee48290be808d5c7dec561bbcab49e5222667e175ffff01b3a175c586 -size 2308 +oid sha256:0418f0ea38ec19b407f2b5046d7ff0ed207189ad71db1e50e82c419d83620543 +size 2759 diff --git a/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_Robidoux.png b/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_Robidoux.png index 48c51e7c3c..976be43a3b 100644 --- a/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_Robidoux.png +++ b/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_Robidoux.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:19c3ce34e6bbd8e35ec979acc28510342dadf0bae3d91a9e1b8291cdff638465 -size 13873 +oid sha256:1233a9ab2c4b0b17b0248c3d40050c466330c22095287dfbdb8bf7dfbda4ff1f +size 11212 diff --git a/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_RobidouxSharp.png b/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_RobidouxSharp.png index 379b25ca0e..04fb2e87e0 100644 --- a/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_RobidouxSharp.png +++ b/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_RobidouxSharp.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:87676e4bf55e71815acc46bdeb3384d659fe6cb3ecc7b730fd9ecc8be00d181f -size 13847 +oid sha256:e2912d4e42c7b76d9ff48a49921d6472e351662597d69b88bc3708683c7933e3 +size 11221 diff --git a/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_Spline.png b/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_Spline.png index 4bcd2dc604..b35d68aaf8 100644 --- a/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_Spline.png +++ b/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_Spline.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:34a62df6b4e9756a7dfef965b669b522e2338b0c2e267d35e4a37fcd9d8a55c3 -size 14818 +oid sha256:51b05c38647e0c1d88cc722e4109a882305073a065d2a27ccd3bee82f727127d +size 11775 diff --git a/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_Triangle.png b/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_Triangle.png index 009fc4fa60..64b9c6aba4 100644 --- a/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_Triangle.png +++ b/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_Triangle.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:60cf2a3fae30bee16131092590e2a8fe139070b6cf98c3b98698e34ededb711f -size 11996 +oid sha256:b260e816b23a43d7efb7507897ba2f5dbb6a596dd67a5abd4c9a0c005e926ee0 +size 9748 diff --git a/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_Welch.png b/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_Welch.png index 568625cd6c..29b95bf525 100644 --- a/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_Welch.png +++ b/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_Welch.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c2140365f533762cfc4411105be04a13f7d85739d955152c3e1951c2c69c7d67 -size 19934 +oid sha256:50b03d627bb53048f5e56380500f116da4c503f5bb6a1b1d3c0d67ee4256d8f6 +size 15977 diff --git a/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithTaperMatrix_Rgba32_Solid30x30_(255,0,0,255)_Bottom-Both.png b/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithTaperMatrix_Rgba32_Solid30x30_(255,0,0,255)_Bottom-Both.png index 92a99f0360..54dca26397 100644 --- a/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithTaperMatrix_Rgba32_Solid30x30_(255,0,0,255)_Bottom-Both.png +++ b/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithTaperMatrix_Rgba32_Solid30x30_(255,0,0,255)_Bottom-Both.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b57bd912acfd243882163775196e0b02c7d0374e81319cb29902cd560b5c6053 -size 394 +oid sha256:96454548849147d7c7f0ca507c8521a7d5eaa7771f9f383cc836858870b52c57 +size 280 diff --git a/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithTaperMatrix_Rgba32_Solid30x30_(255,0,0,255)_Bottom-LeftOrTop.png b/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithTaperMatrix_Rgba32_Solid30x30_(255,0,0,255)_Bottom-LeftOrTop.png index cc89f5114b..41f94c9c7b 100644 --- a/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithTaperMatrix_Rgba32_Solid30x30_(255,0,0,255)_Bottom-LeftOrTop.png +++ b/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithTaperMatrix_Rgba32_Solid30x30_(255,0,0,255)_Bottom-LeftOrTop.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8827b81929abd9000fcfd3ce747b82fec4c778bcd29bb12abaaed5f8b0dfc945 -size 228 +oid sha256:e94d224fdb284b6f1ba21b8caa66174edd7e6a3027f9dd03f4757e08296e6508 +size 212 diff --git a/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithTaperMatrix_Rgba32_Solid30x30_(255,0,0,255)_Bottom-RightOrBottom.png b/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithTaperMatrix_Rgba32_Solid30x30_(255,0,0,255)_Bottom-RightOrBottom.png index 61128d7b83..49cd1c8375 100644 --- a/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithTaperMatrix_Rgba32_Solid30x30_(255,0,0,255)_Bottom-RightOrBottom.png +++ b/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithTaperMatrix_Rgba32_Solid30x30_(255,0,0,255)_Bottom-RightOrBottom.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:5819f0deb160a7c7bf090919a010a2f8a3c074b3b718f56fe56f1f6eae1fcdcd -size 227 +oid sha256:d1162be9fa1f31bee8d3cba05c1422a1945621a412be11cce13d376efd5c679c +size 173 diff --git a/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithTaperMatrix_Rgba32_Solid30x30_(255,0,0,255)_Left-Both.png b/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithTaperMatrix_Rgba32_Solid30x30_(255,0,0,255)_Left-Both.png index 846fe440ef..59f928178a 100644 --- a/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithTaperMatrix_Rgba32_Solid30x30_(255,0,0,255)_Left-Both.png +++ b/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithTaperMatrix_Rgba32_Solid30x30_(255,0,0,255)_Left-Both.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1381429147add3a9d3342bb2d618a47a1a5db997a384582a288705f68f5f937a -size 343 +oid sha256:0ed262e9b885af773a4a40a4506e678630670e208bf7f9ec10307e943b166bed +size 258 diff --git a/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithTaperMatrix_Rgba32_Solid30x30_(255,0,0,255)_Left-LeftOrTop.png b/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithTaperMatrix_Rgba32_Solid30x30_(255,0,0,255)_Left-LeftOrTop.png index 7b0692ffe2..57ee3dc2ff 100644 --- a/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithTaperMatrix_Rgba32_Solid30x30_(255,0,0,255)_Left-LeftOrTop.png +++ b/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithTaperMatrix_Rgba32_Solid30x30_(255,0,0,255)_Left-LeftOrTop.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e59892f86d307e28457ebd621fec84b8ab839cdc11afe38e978420c4173058e4 -size 236 +oid sha256:3a24f2cfc225d01294b8bbc5ca7d7f1738fb0b79217046eb9edf04e4c4c01851 +size 201 diff --git a/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithTaperMatrix_Rgba32_Solid30x30_(255,0,0,255)_Left-RightOrBottom.png b/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithTaperMatrix_Rgba32_Solid30x30_(255,0,0,255)_Left-RightOrBottom.png index 4c0d43feaf..7e47f43ff9 100644 --- a/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithTaperMatrix_Rgba32_Solid30x30_(255,0,0,255)_Left-RightOrBottom.png +++ b/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithTaperMatrix_Rgba32_Solid30x30_(255,0,0,255)_Left-RightOrBottom.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6d25a9407bb5a3c8ba7126c9082dfbc5f29c5b52fc46e1a21e1c8968ac9f1c11 -size 230 +oid sha256:938186fb3d0f468176988a9530efd22e66241a1361fff027005ec8a8ae323ff3 +size 197 diff --git a/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithTaperMatrix_Rgba32_Solid30x30_(255,0,0,255)_Right-Both.png b/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithTaperMatrix_Rgba32_Solid30x30_(255,0,0,255)_Right-Both.png index a832badcb0..0f756e7813 100644 --- a/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithTaperMatrix_Rgba32_Solid30x30_(255,0,0,255)_Right-Both.png +++ b/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithTaperMatrix_Rgba32_Solid30x30_(255,0,0,255)_Right-Both.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:398d1ca71075c541fdb88eb63f5889edea2259fdd0df643bed77e64baa246ceb -size 317 +oid sha256:4bc4b8ea7e7f10676d8de612fe6bc5144e100b95ff3fe7a1e3d4066a7684ce4d +size 239 diff --git a/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithTaperMatrix_Rgba32_Solid30x30_(255,0,0,255)_Right-LeftOrTop.png b/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithTaperMatrix_Rgba32_Solid30x30_(255,0,0,255)_Right-LeftOrTop.png index bdef30001b..b2d420886b 100644 --- a/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithTaperMatrix_Rgba32_Solid30x30_(255,0,0,255)_Right-LeftOrTop.png +++ b/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithTaperMatrix_Rgba32_Solid30x30_(255,0,0,255)_Right-LeftOrTop.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:62546cd019fa11a43d254936c12b5adcbe8eebe6ae012bc1024949df6b28303f -size 220 +oid sha256:345337f7dffa48d95251503ee2ae8e91db98b5cbe06b579d73c38a018c781544 +size 182 diff --git a/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithTaperMatrix_Rgba32_Solid30x30_(255,0,0,255)_Right-RightOrBottom.png b/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithTaperMatrix_Rgba32_Solid30x30_(255,0,0,255)_Right-RightOrBottom.png index 3012731317..4f0ad9d045 100644 --- a/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithTaperMatrix_Rgba32_Solid30x30_(255,0,0,255)_Right-RightOrBottom.png +++ b/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithTaperMatrix_Rgba32_Solid30x30_(255,0,0,255)_Right-RightOrBottom.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:27e444aff3db9c5a1149de73f3b0ed18e48c8dc372be13c3555dcda0ec4ad893 -size 221 +oid sha256:de4e2b71dade9dfb750a2c614a684963d6962958db79145c87fd23d9f0f8c005 +size 180 diff --git a/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithTaperMatrix_Rgba32_Solid30x30_(255,0,0,255)_Top-Both.png b/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithTaperMatrix_Rgba32_Solid30x30_(255,0,0,255)_Top-Both.png index dd6b926db4..78bdb8bbbe 100644 --- a/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithTaperMatrix_Rgba32_Solid30x30_(255,0,0,255)_Top-Both.png +++ b/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithTaperMatrix_Rgba32_Solid30x30_(255,0,0,255)_Top-Both.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6bdfa8eb65b5e54b9ccffb11acb2a3b7a5a434f1f22a2cdf792d892fd411f711 -size 399 +oid sha256:8d8b651663366e7543211635f337c229e2f88f1142886ea3a9b69587daaada97 +size 288 diff --git a/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithTaperMatrix_Rgba32_Solid30x30_(255,0,0,255)_Top-LeftOrTop.png b/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithTaperMatrix_Rgba32_Solid30x30_(255,0,0,255)_Top-LeftOrTop.png index fb98036ec4..7015a05571 100644 --- a/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithTaperMatrix_Rgba32_Solid30x30_(255,0,0,255)_Top-LeftOrTop.png +++ b/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithTaperMatrix_Rgba32_Solid30x30_(255,0,0,255)_Top-LeftOrTop.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e11a1af248350450454ba1cb415357e58f905de402a921b213a305990e8f57c3 -size 245 +oid sha256:8ab8df31f1716c05bb8687f79c7d1154f6cc6f65e3917abe60ecc42d0df173dc +size 215 diff --git a/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithTaperMatrix_Rgba32_Solid30x30_(255,0,0,255)_Top-RightOrBottom.png b/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithTaperMatrix_Rgba32_Solid30x30_(255,0,0,255)_Top-RightOrBottom.png index 0e4ef4c427..67a765e8db 100644 --- a/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithTaperMatrix_Rgba32_Solid30x30_(255,0,0,255)_Top-RightOrBottom.png +++ b/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithTaperMatrix_Rgba32_Solid30x30_(255,0,0,255)_Top-RightOrBottom.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e9cffb16fbff28901a3daf391469d32bf36f3c293aa870d169967f17611dea92 -size 241 +oid sha256:1a1671da9ea7702a37a866fabfb3ca0d746233ee108594198f23cb563af43ae6 +size 180 diff --git a/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_With_Custom_Dimensions_Rgba32_TestPattern100x100_0.0001.png b/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_With_Custom_Dimensions_Rgba32_TestPattern100x100_0.0001.png index e8efa8a980..9d7dc06c95 100644 --- a/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_With_Custom_Dimensions_Rgba32_TestPattern100x100_0.0001.png +++ b/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_With_Custom_Dimensions_Rgba32_TestPattern100x100_0.0001.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:39c25539c3c9b8926bf65c041df693a60617bbe8653bb72357bde5ab6342c59c -size 3618 +oid sha256:f5aef9cd3b8bfd9859e5d2401e82f89d89407ab2834b09c43f0a3229c735e92b +size 724 diff --git a/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_With_Custom_Dimensions_Rgba32_TestPattern100x100_0.png b/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_With_Custom_Dimensions_Rgba32_TestPattern100x100_0.png index ce6e8ce9fa..bef0fad79e 100644 --- a/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_With_Custom_Dimensions_Rgba32_TestPattern100x100_0.png +++ b/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_With_Custom_Dimensions_Rgba32_TestPattern100x100_0.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:da8229605bda413676a42f587df250a743540e6e00c04eacb1e622f223e19595 -size 3564 +oid sha256:c86a0ceb875e02b58084fd95e5c439791af313e1fb273baf00b35187a2678d2f +size 657 diff --git a/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_With_Custom_Dimensions_Rgba32_TestPattern100x100_57.png b/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_With_Custom_Dimensions_Rgba32_TestPattern100x100_57.png index 99a74e400a..4b2bb99d96 100644 --- a/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_With_Custom_Dimensions_Rgba32_TestPattern100x100_57.png +++ b/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_With_Custom_Dimensions_Rgba32_TestPattern100x100_57.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8b1fc95fdf07c7443147205afffb157aa82f94818cfbb833a615c42f584fbda0 -size 5070 +oid sha256:1e7dedec16ccd66543a1d44052a104957ba62099ba2f2ccc72285c233c2ae3fa +size 4411 diff --git a/tests/Images/External/ReferenceOutput/Transforms/RotateTests/Rotate_WithAngle_TestPattern100x50_-170.png b/tests/Images/External/ReferenceOutput/Transforms/RotateTests/Rotate_WithAngle_TestPattern100x50_-170.png index a9289abd0d..65bb77977b 100644 --- a/tests/Images/External/ReferenceOutput/Transforms/RotateTests/Rotate_WithAngle_TestPattern100x50_-170.png +++ b/tests/Images/External/ReferenceOutput/Transforms/RotateTests/Rotate_WithAngle_TestPattern100x50_-170.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:801067dfb19b2a9a1fbd14b771e780b475c21f3ccdc1e709bbc20d62061ad1d1 -size 8782 +oid sha256:3594547265b23603b1a76ff6bc6f0eab4af55d6e0070e53356123dfc7ae256f8 +size 9034 diff --git a/tests/Images/External/ReferenceOutput/Transforms/RotateTests/Rotate_WithAngle_TestPattern100x50_-50.png b/tests/Images/External/ReferenceOutput/Transforms/RotateTests/Rotate_WithAngle_TestPattern100x50_-50.png index 43fcd5df5e..7c54b1b074 100644 --- a/tests/Images/External/ReferenceOutput/Transforms/RotateTests/Rotate_WithAngle_TestPattern100x50_-50.png +++ b/tests/Images/External/ReferenceOutput/Transforms/RotateTests/Rotate_WithAngle_TestPattern100x50_-50.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ccbdd3dcdbc888923e85495fbd7837478cca813be6ecece63ee645bdf39d436f -size 10325 +oid sha256:5ae9ef073f3338b71d2a40fcf2e89d9b6ab62204d6de9b6a1f75f4705ee197f0 +size 10704 diff --git a/tests/Images/External/ReferenceOutput/Transforms/RotateTests/Rotate_WithAngle_TestPattern100x50_170.png b/tests/Images/External/ReferenceOutput/Transforms/RotateTests/Rotate_WithAngle_TestPattern100x50_170.png index 9a7f9866da..b6e930224e 100644 --- a/tests/Images/External/ReferenceOutput/Transforms/RotateTests/Rotate_WithAngle_TestPattern100x50_170.png +++ b/tests/Images/External/ReferenceOutput/Transforms/RotateTests/Rotate_WithAngle_TestPattern100x50_170.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:60d1be2ffa6d50f97561b92efe8d0c0337f9c121582e38c9ab9af75be8eed32d -size 8539 +oid sha256:994dda7da034595aa77d107652bea06c86077d24ef8a6883b18f1f509bb19928 +size 8906 diff --git a/tests/Images/External/ReferenceOutput/Transforms/RotateTests/Rotate_WithAngle_TestPattern100x50_50.png b/tests/Images/External/ReferenceOutput/Transforms/RotateTests/Rotate_WithAngle_TestPattern100x50_50.png index d141a2e28c..d1ea99cf90 100644 --- a/tests/Images/External/ReferenceOutput/Transforms/RotateTests/Rotate_WithAngle_TestPattern100x50_50.png +++ b/tests/Images/External/ReferenceOutput/Transforms/RotateTests/Rotate_WithAngle_TestPattern100x50_50.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:7f79389f79d91ac6749f21c27a592edfd2cff6efbb1d46296a26ae60d4e721f8 -size 10103 +oid sha256:0fced9def2b41cbbf215a49ea6ef6baf4c3c041fd180671eb209db5c6e7177e5 +size 10470 diff --git a/tests/Images/External/ReferenceOutput/Transforms/RotateTests/Rotate_WithAngle_TestPattern50x100_-170.png b/tests/Images/External/ReferenceOutput/Transforms/RotateTests/Rotate_WithAngle_TestPattern50x100_-170.png index 1d27e23958..2f3f0f17fe 100644 --- a/tests/Images/External/ReferenceOutput/Transforms/RotateTests/Rotate_WithAngle_TestPattern50x100_-170.png +++ b/tests/Images/External/ReferenceOutput/Transforms/RotateTests/Rotate_WithAngle_TestPattern50x100_-170.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:75fb59ea2947efb1abf73cd824e54b6e8f6271343bd2fdadb005b29476988921 -size 9423 +oid sha256:29c5f48f1ece0b12854b4c44fba84fdfc9ac5751cdf564a32478dcdaed43b2a4 +size 9798 diff --git a/tests/Images/External/ReferenceOutput/Transforms/RotateTests/Rotate_WithAngle_TestPattern50x100_-50.png b/tests/Images/External/ReferenceOutput/Transforms/RotateTests/Rotate_WithAngle_TestPattern50x100_-50.png index 628d0c889c..5242a9d985 100644 --- a/tests/Images/External/ReferenceOutput/Transforms/RotateTests/Rotate_WithAngle_TestPattern50x100_-50.png +++ b/tests/Images/External/ReferenceOutput/Transforms/RotateTests/Rotate_WithAngle_TestPattern50x100_-50.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:9c2d94791af40e001fc44b23eeecac7a606492c747b22ede0cdc7069ef121cb8 -size 11193 +oid sha256:c7de58474c3f386c4ec31a9088d561a513f82c08d1157132d735169b847b9680 +size 11579 diff --git a/tests/Images/External/ReferenceOutput/Transforms/RotateTests/Rotate_WithAngle_TestPattern50x100_170.png b/tests/Images/External/ReferenceOutput/Transforms/RotateTests/Rotate_WithAngle_TestPattern50x100_170.png index 0e927cfbd0..2af9d2fc27 100644 --- a/tests/Images/External/ReferenceOutput/Transforms/RotateTests/Rotate_WithAngle_TestPattern50x100_170.png +++ b/tests/Images/External/ReferenceOutput/Transforms/RotateTests/Rotate_WithAngle_TestPattern50x100_170.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:016de6e82b6fb03fd55168ea7fc12ab245d0e0387ca7c32d3ef1158a85a8facd -size 9330 +oid sha256:3ef9b7051d7a5733dfe2534fddefdc28dfbc49d087355f46c4d945b04f0e3936 +size 9672 diff --git a/tests/Images/External/ReferenceOutput/Transforms/RotateTests/Rotate_WithAngle_TestPattern50x100_50.png b/tests/Images/External/ReferenceOutput/Transforms/RotateTests/Rotate_WithAngle_TestPattern50x100_50.png index ac4d473624..83c02764fa 100644 --- a/tests/Images/External/ReferenceOutput/Transforms/RotateTests/Rotate_WithAngle_TestPattern50x100_50.png +++ b/tests/Images/External/ReferenceOutput/Transforms/RotateTests/Rotate_WithAngle_TestPattern50x100_50.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:9219cc118fe7195b730cbe2e6407cde54e6f4c7930a71b7418bc7c273aa4120c -size 11050 +oid sha256:825770c9b2e9f265d834eab6b40604df5508bf9bc5b4f82f5d3effd6d5a26935 +size 11434 diff --git a/tests/Images/External/ReferenceOutput/Transforms/SkewTests/Skew_IsNotBoundToSinglePixelType_Bgra32_TestPattern100x50_-20_-10.png b/tests/Images/External/ReferenceOutput/Transforms/SkewTests/Skew_IsNotBoundToSinglePixelType_Bgra32_TestPattern100x50_-20_-10.png index 147f9c9897..d6dba3f889 100644 --- a/tests/Images/External/ReferenceOutput/Transforms/SkewTests/Skew_IsNotBoundToSinglePixelType_Bgra32_TestPattern100x50_-20_-10.png +++ b/tests/Images/External/ReferenceOutput/Transforms/SkewTests/Skew_IsNotBoundToSinglePixelType_Bgra32_TestPattern100x50_-20_-10.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:2b243340f372220033b349a96cbd7ecd732395fa15e4d1ed62048d2031c42794 -size 8398 +oid sha256:1e283463b0f450dd72cf303acccf3dd1ff7a31fe401ff0f288d67c4baefca240 +size 8742 diff --git a/tests/Images/External/ReferenceOutput/Transforms/SkewTests/Skew_IsNotBoundToSinglePixelType_Bgra32_TestPattern100x50_20_10.png b/tests/Images/External/ReferenceOutput/Transforms/SkewTests/Skew_IsNotBoundToSinglePixelType_Bgra32_TestPattern100x50_20_10.png index d1252cb2c4..76bb244d52 100644 --- a/tests/Images/External/ReferenceOutput/Transforms/SkewTests/Skew_IsNotBoundToSinglePixelType_Bgra32_TestPattern100x50_20_10.png +++ b/tests/Images/External/ReferenceOutput/Transforms/SkewTests/Skew_IsNotBoundToSinglePixelType_Bgra32_TestPattern100x50_20_10.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:fdd2745aba2f09bb4a09e881339fe62774ce5658902aa9d83b3a1e0718260084 -size 8694 +oid sha256:485d9d9ef955a04af43d17e6bc3952e9bf65a9752b6cf8ba9cbbe8f772f05a18 +size 8995 diff --git a/tests/Images/External/ReferenceOutput/Transforms/SkewTests/Skew_IsNotBoundToSinglePixelType_Rgb24_TestPattern100x50_-20_-10.png b/tests/Images/External/ReferenceOutput/Transforms/SkewTests/Skew_IsNotBoundToSinglePixelType_Rgb24_TestPattern100x50_-20_-10.png index 235e95d8fc..c1c1d814fd 100644 --- a/tests/Images/External/ReferenceOutput/Transforms/SkewTests/Skew_IsNotBoundToSinglePixelType_Rgb24_TestPattern100x50_-20_-10.png +++ b/tests/Images/External/ReferenceOutput/Transforms/SkewTests/Skew_IsNotBoundToSinglePixelType_Rgb24_TestPattern100x50_-20_-10.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:281bd00550ab1cf9b05011750e6de330cdd42a8644ecf3b7c176bd5c6e94c59b -size 6098 +oid sha256:d3d749ac365764051ea16bc39d1aff84c06faf282359805b58bb97c9eed7f0bb +size 6400 diff --git a/tests/Images/External/ReferenceOutput/Transforms/SkewTests/Skew_IsNotBoundToSinglePixelType_Rgb24_TestPattern100x50_20_10.png b/tests/Images/External/ReferenceOutput/Transforms/SkewTests/Skew_IsNotBoundToSinglePixelType_Rgb24_TestPattern100x50_20_10.png index 3b63f58f36..27608881ed 100644 --- a/tests/Images/External/ReferenceOutput/Transforms/SkewTests/Skew_IsNotBoundToSinglePixelType_Rgb24_TestPattern100x50_20_10.png +++ b/tests/Images/External/ReferenceOutput/Transforms/SkewTests/Skew_IsNotBoundToSinglePixelType_Rgb24_TestPattern100x50_20_10.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e755ed61d7eab2c297f4b6592366b54ac801cbdb3d920741dfdd04dfaf73f9b9 -size 6086 +oid sha256:8d82f2a15502b0a29aa4df1077ec90c88f9211f283fdc0edd7b059ed9b387441 +size 6334 diff --git a/tests/Images/Input/Webp/issues/Issue2801.webp b/tests/Images/Input/Webp/issues/Issue2801.webp new file mode 100644 index 0000000000..a3b5fee6e0 --- /dev/null +++ b/tests/Images/Input/Webp/issues/Issue2801.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e90a0d853ddf70d823d8da44eb6c57081e955b1fb7f436a1fd88ca5e5c75a003 +size 261212