From 328e0465db6dfa46bc90d93eedb6fc4f85eec86f Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Wed, 15 Nov 2023 12:40:49 +1000 Subject: [PATCH 01/24] Wire up connectors and gif encoder --- .../Formats/AnimatedImageFrameMetadata.cs | 93 +++++++++++++++++++ .../Formats/AnimatedImageMetadata.cs | 32 +++++++ src/ImageSharp/Formats/Gif/GifEncoderCore.cs | 70 ++++++++++++-- .../Formats/Gif/GifFrameMetadata.cs | 40 ++++++++ src/ImageSharp/Formats/Gif/GifMetadata.cs | 26 ++++++ .../Formats/Gif/MetadataExtensions.cs | 44 ++++++++- .../Formats/Png/Chunks/AnimationControl.cs | 14 +-- .../Formats/Png/MetadataExtensions.cs | 48 +++++++++- src/ImageSharp/Formats/Png/PngDecoderCore.cs | 4 +- .../Formats/Png/PngDisposalMethod.cs | 6 +- src/ImageSharp/Formats/Png/PngEncoderCore.cs | 4 +- src/ImageSharp/Formats/Png/PngMetadata.cs | 3 +- .../Formats/Webp/BitWriter/BitWriterBase.cs | 2 +- .../Formats/Webp/Chunks/WebpFrameData.cs | 8 +- .../Formats/Webp/Lossless/Vp8LEncoder.cs | 2 +- .../Formats/Webp/Lossy/Vp8Encoder.cs | 2 +- .../Formats/Webp/MetadataExtensions.cs | 48 ++++++++++ .../Formats/Webp/WebpAnimationDecoder.cs | 6 +- .../Formats/Webp/WebpBlendingMethod.cs | 12 +-- .../Formats/Webp/WebpDisposalMethod.cs | 4 +- src/ImageSharp/Formats/Webp/WebpMetadata.cs | 14 +-- src/ImageSharp/Metadata/FrameDecodingMode.cs | 20 ---- src/ImageSharp/Metadata/ImageMetadata.cs | 26 ++++++ .../Formats/Gif/GifEncoderTests.cs | 4 +- .../Formats/Png/PngEncoderTests.cs | 5 + .../Formats/Png/PngFrameMetadataTests.cs | 4 +- .../Formats/WebP/WebpDecoderTests.cs | 4 +- 27 files changed, 465 insertions(+), 80 deletions(-) create mode 100644 src/ImageSharp/Formats/AnimatedImageFrameMetadata.cs create mode 100644 src/ImageSharp/Formats/AnimatedImageMetadata.cs delete mode 100644 src/ImageSharp/Metadata/FrameDecodingMode.cs diff --git a/src/ImageSharp/Formats/AnimatedImageFrameMetadata.cs b/src/ImageSharp/Formats/AnimatedImageFrameMetadata.cs new file mode 100644 index 000000000..5f4015180 --- /dev/null +++ b/src/ImageSharp/Formats/AnimatedImageFrameMetadata.cs @@ -0,0 +1,93 @@ +// 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; } +} + +#pragma warning disable SA1201 // Elements should appear in the correct order +internal enum FrameBlendMode +#pragma warning restore SA1201 // Elements should appear in the correct order +{ + /// + /// Do not blend. Render the current frame on the canvas by overwriting the rectangle covered by the current frame. + /// + Source = 0, + + /// + /// Blend the current frame with the previous frame in the animation sequence within the rectangle covered + /// by the current frame. + /// If the current has any transparent areas, the corresponding areas of the previous frame will be visible + /// through these transparent regions. + /// + Over = 1 +} + +internal enum FrameDisposalMode +{ + /// + /// No disposal specified. + /// The decoder is not required to take any action. + /// + Unspecified = 0, + + /// + /// Do not dispose. The current frame is not disposed of, or in other words, not cleared or altered when moving to + /// the next frame. This means that the next frame is drawn over the current frame, and if the next frame contains + /// transparency, the previous frame will be visible through these transparent areas. + /// + DoNotDispose = 1, + + /// + /// Restore to background color. When transitioning to the next frame, the area occupied by the current frame is + /// filled with the background color specified in the image metadata. + /// This effectively erases the current frame by replacing it with the background color before the next frame is displayed. + /// + RestoreToBackground = 2, + + /// + /// Restore to previous. This method restores the area affected by the current frame to what it was before the + /// current frame was displayed. It essentially "undoes" the current frame, reverting to the state of the image + /// before the frame was displayed, then the next frame is drawn. This is useful for animations where only a small + /// part of the image changes from frame to frame. + /// + RestoreToPrevious = 3 +} + +internal enum FrameColorTableMode +{ + /// + /// The frame uses the shared color table specified by the image metadata. + /// + Global, + + /// + /// The frame uses a color table specified by the frame metadata. + /// + Local +} diff --git a/src/ImageSharp/Formats/AnimatedImageMetadata.cs b/src/ImageSharp/Formats/AnimatedImageMetadata.cs new file mode 100644 index 000000000..d89ec41f0 --- /dev/null +++ b/src/ImageSharp/Formats/AnimatedImageMetadata.cs @@ -0,0 +1,32 @@ +// 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/Gif/GifEncoderCore.cs b/src/ImageSharp/Formats/Gif/GifEncoderCore.cs index 926cc091c..33942ce54 100644 --- a/src/ImageSharp/Formats/Gif/GifEncoderCore.cs +++ b/src/ImageSharp/Formats/Gif/GifEncoderCore.cs @@ -8,6 +8,8 @@ using System.Runtime.InteropServices; using System.Runtime.Intrinsics; using System.Runtime.Intrinsics.X86; using SixLabors.ImageSharp.Advanced; +using SixLabors.ImageSharp.Formats.Png; +using SixLabors.ImageSharp.Formats.Webp; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Metadata; using SixLabors.ImageSharp.Metadata.Profiles.Xmp; @@ -86,8 +88,7 @@ internal sealed class GifEncoderCore : IImageEncoderInternals Guard.NotNull(image, nameof(image)); Guard.NotNull(stream, nameof(stream)); - ImageMetadata metadata = image.Metadata; - GifMetadata gifMetadata = metadata.GetGifMetadata(); + GifMetadata gifMetadata = GetGifMetadata(image); this.colorTableMode ??= gifMetadata.ColorTableMode; bool useGlobalTable = this.colorTableMode == GifColorTableMode.Global; @@ -96,7 +97,7 @@ internal sealed class GifEncoderCore : IImageEncoderInternals // Work out if there is an explicit transparent index set for the frame. We use that to ensure the // correct value is set for the background index when quantizing. - image.Frames.RootFrame.Metadata.TryGetGifMetadata(out GifFrameMetadata? frameMetadata); + GifFrameMetadata? frameMetadata = GetGifFrameMetadata(image.Frames.RootFrame, -1); int transparencyIndex = GetTransparentIndex(quantized, frameMetadata); if (this.quantizer is null) @@ -140,7 +141,7 @@ internal sealed class GifEncoderCore : IImageEncoderInternals // Get the number of bits. int bitDepth = ColorNumerics.GetBitsNeededForColorDepth(quantized.Palette.Length); - this.WriteLogicalScreenDescriptor(metadata, image.Width, image.Height, backgroundIndex, useGlobalTable, bitDepth, stream); + this.WriteLogicalScreenDescriptor(image.Metadata, image.Width, image.Height, backgroundIndex, useGlobalTable, bitDepth, stream); if (useGlobalTable) { @@ -164,15 +165,69 @@ internal sealed class GifEncoderCore : IImageEncoderInternals quantized.Dispose(); - this.EncodeAdditionalFrames(stream, image, globalPalette); + this.EncodeAdditionalFrames(stream, image, globalPalette, transparencyIndex); stream.WriteByte(GifConstants.EndIntroducer); } + private static GifMetadata GetGifMetadata(Image image) + where TPixel : unmanaged, IPixel + { + if (image.Metadata.TryGetGifMetadata(out GifMetadata? gif)) + { + return gif; + } + + if (image.Metadata.TryGetPngMetadata(out PngMetadata? png)) + { + AnimatedImageMetadata ani = png.ToAnimatedImageMetadata(); + return GifMetadata.FromAnimatedMetadata(ani); + } + + if (image.Metadata.TryGetWebpMetadata(out WebpMetadata? webp)) + { + AnimatedImageMetadata ani = webp.ToAnimatedImageMetadata(); + return GifMetadata.FromAnimatedMetadata(ani); + } + + return new(); + } + + private static GifFrameMetadata? GetGifFrameMetadata(ImageFrame frame, int transparencyIndex) + where TPixel : unmanaged, IPixel + { + if (frame.Metadata.TryGetGifFrameMetadata(out GifFrameMetadata? gif)) + { + return gif; + } + + GifFrameMetadata? metadata = null; + if (frame.Metadata.TryGetPngFrameMetadata(out PngFrameMetadata? png)) + { + AnimatedImageFrameMetadata ani = png.ToAnimatedImageFrameMetadata(); + metadata = GifFrameMetadata.FromAnimatedMetadata(ani); + } + + if (frame.Metadata.TryGetWebpFrameMetadata(out WebpFrameMetadata? webp)) + { + AnimatedImageFrameMetadata ani = webp.ToAnimatedImageFrameMetadata(); + metadata = GifFrameMetadata.FromAnimatedMetadata(ani); + } + + if (metadata?.ColorTableMode == GifColorTableMode.Global && transparencyIndex > -1) + { + metadata.HasTransparency = true; + metadata.TransparencyIndex = unchecked((byte)transparencyIndex); + } + + return metadata; + } + private void EncodeAdditionalFrames( Stream stream, Image image, - ReadOnlyMemory globalPalette) + ReadOnlyMemory globalPalette, + int globalTransparencyIndex) where TPixel : unmanaged, IPixel { if (image.Frames.Count == 1) @@ -195,8 +250,7 @@ internal sealed class GifEncoderCore : IImageEncoderInternals { // Gather the metadata for this frame. ImageFrame currentFrame = image.Frames[i]; - ImageFrameMetadata metadata = currentFrame.Metadata; - metadata.TryGetGifMetadata(out GifFrameMetadata? gifMetadata); + GifFrameMetadata? gifMetadata = GetGifFrameMetadata(currentFrame, globalTransparencyIndex); bool useLocal = this.colorTableMode == GifColorTableMode.Local || (gifMetadata?.ColorTableMode == GifColorTableMode.Local); if (!useLocal && !hasPaletteQuantizer && i > 0) diff --git a/src/ImageSharp/Formats/Gif/GifFrameMetadata.cs b/src/ImageSharp/Formats/Gif/GifFrameMetadata.cs index faabf7dfa..f8734bb5a 100644 --- a/src/ImageSharp/Formats/Gif/GifFrameMetadata.cs +++ b/src/ImageSharp/Formats/Gif/GifFrameMetadata.cs @@ -1,6 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. +using System.Numerics; using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Formats.Gif; @@ -76,4 +77,43 @@ public class GifFrameMetadata : IDeepCloneable /// public IDeepCloneable DeepClone() => new GifFrameMetadata(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; + float background = 1f; + if (metadata.ColorTable.HasValue) + { + ReadOnlySpan colorTable = metadata.ColorTable.Value.Span; + for (int i = 0; i < colorTable.Length; i++) + { + Vector4 vector = (Vector4)colorTable[i]; + if (vector.W < background) + { + index = i; + } + } + } + + bool hasTransparency = index >= 0; + + return new() + { + LocalColorTable = metadata.ColorTable, + ColorTableMode = metadata.ColorTableMode == FrameColorTableMode.Global ? GifColorTableMode.Global : GifColorTableMode.Local, + FrameDelay = (int)Math.Round(metadata.Duration.TotalMilliseconds / 10), + DisposalMethod = GetMode(metadata.DisposalMode), + HasTransparency = hasTransparency, + TransparencyIndex = hasTransparency ? unchecked((byte)index) : byte.MinValue, + }; + } + + private static GifDisposalMethod GetMode(FrameDisposalMode mode) => mode switch + { + FrameDisposalMode.DoNotDispose => GifDisposalMethod.NotDispose, + FrameDisposalMode.RestoreToBackground => GifDisposalMethod.RestoreToBackground, + FrameDisposalMode.RestoreToPrevious => GifDisposalMethod.RestoreToPrevious, + _ => GifDisposalMethod.Unspecified, + }; } diff --git a/src/ImageSharp/Formats/Gif/GifMetadata.cs b/src/ImageSharp/Formats/Gif/GifMetadata.cs index d25e2a5cc..1331edee8 100644 --- a/src/ImageSharp/Formats/Gif/GifMetadata.cs +++ b/src/ImageSharp/Formats/Gif/GifMetadata.cs @@ -71,4 +71,30 @@ public class GifMetadata : IDeepCloneable /// public IDeepCloneable DeepClone() => new GifMetadata(this); + + internal static GifMetadata FromAnimatedMetadata(AnimatedImageMetadata metadata) + { + int index = 0; + Color background = metadata.BackgroundColor; + if (metadata.ColorTable.HasValue) + { + ReadOnlySpan colorTable = metadata.ColorTable.Value.Span; + for (int i = 0; i < colorTable.Length; i++) + { + if (background == colorTable[i]) + { + index = i; + break; + } + } + } + + return new() + { + GlobalColorTable = metadata.ColorTable, + ColorTableMode = metadata.ColorTableMode == FrameColorTableMode.Global ? GifColorTableMode.Global : GifColorTableMode.Local, + RepeatCount = metadata.RepeatCount, + BackgroundColorIndex = (byte)Numerics.Clamp(index, 0, 255), + }; + } } diff --git a/src/ImageSharp/Formats/Gif/MetadataExtensions.cs b/src/ImageSharp/Formats/Gif/MetadataExtensions.cs index 9ba95952e..f4eaffe6b 100644 --- a/src/ImageSharp/Formats/Gif/MetadataExtensions.cs +++ b/src/ImageSharp/Formats/Gif/MetadataExtensions.cs @@ -2,6 +2,7 @@ // Licensed under the Six Labors Split License. using System.Diagnostics.CodeAnalysis; +using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Formats.Gif; using SixLabors.ImageSharp.Metadata; @@ -20,6 +21,21 @@ public static partial class MetadataExtensions public static GifMetadata GetGifMetadata(this ImageMetadata source) => source.GetFormatMetadata(GifFormat.Instance); + /// + /// Gets the gif format specific metadata for the image. + /// + /// The metadata this method extends. + /// + /// When this method returns, contains the metadata associated with the specified image, + /// if found; otherwise, the default value for the type of the metadata parameter. + /// This parameter is passed uninitialized. + /// + /// + /// if the gif metadata exists; otherwise, . + /// + public static bool TryGetGifMetadata(this ImageMetadata source, [NotNullWhen(true)] out GifMetadata? metadata) + => source.TryGetFormatMetadata(GifFormat.Instance, out metadata); + /// /// Gets the gif format specific metadata for the image frame. /// @@ -40,6 +56,32 @@ public static partial class MetadataExtensions /// /// if the gif frame metadata exists; otherwise, . /// - public static bool TryGetGifMetadata(this ImageFrameMetadata source, [NotNullWhen(true)] out GifFrameMetadata? metadata) + public static bool TryGetGifFrameMetadata(this ImageFrameMetadata source, [NotNullWhen(true)] out GifFrameMetadata? metadata) => source.TryGetFormatMetadata(GifFormat.Instance, out metadata); + + internal static AnimatedImageMetadata ToAnimatedImageMetadata(this GifMetadata source) + => new() + { + ColorTable = source.GlobalColorTable, + ColorTableMode = source.ColorTableMode == GifColorTableMode.Global ? FrameColorTableMode.Global : FrameColorTableMode.Local, + RepeatCount = source.RepeatCount, + }; + + internal static AnimatedImageFrameMetadata ToAnimatedImageFrameMetadata(this GifFrameMetadata source) + => new() + { + ColorTable = source.LocalColorTable, + ColorTableMode = source.ColorTableMode == GifColorTableMode.Global ? FrameColorTableMode.Global : FrameColorTableMode.Local, + Duration = TimeSpan.FromMilliseconds(source.FrameDelay * 10), + DisposalMode = GetMode(source.DisposalMethod), + BlendMode = FrameBlendMode.Source, + }; + + private static FrameDisposalMode GetMode(GifDisposalMethod method) => method switch + { + GifDisposalMethod.NotDispose => FrameDisposalMode.DoNotDispose, + GifDisposalMethod.RestoreToBackground => FrameDisposalMode.RestoreToBackground, + GifDisposalMethod.RestoreToPrevious => FrameDisposalMode.RestoreToPrevious, + _ => FrameDisposalMode.Unspecified, + }; } diff --git a/src/ImageSharp/Formats/Png/Chunks/AnimationControl.cs b/src/ImageSharp/Formats/Png/Chunks/AnimationControl.cs index a9f99a9e4..cd78f8088 100644 --- a/src/ImageSharp/Formats/Png/Chunks/AnimationControl.cs +++ b/src/ImageSharp/Formats/Png/Chunks/AnimationControl.cs @@ -9,7 +9,7 @@ internal readonly struct AnimationControl { public const int Size = 8; - public AnimationControl(int numberFrames, int numberPlays) + public AnimationControl(uint numberFrames, uint numberPlays) { this.NumberFrames = numberFrames; this.NumberPlays = numberPlays; @@ -18,12 +18,12 @@ internal readonly struct AnimationControl /// /// Gets the number of frames /// - public int NumberFrames { get; } + public uint NumberFrames { get; } /// /// Gets the number of times to loop this APNG. 0 indicates infinite looping. /// - public int NumberPlays { get; } + public uint NumberPlays { get; } /// /// Writes the acTL to the given buffer. @@ -31,8 +31,8 @@ internal readonly struct AnimationControl /// The buffer to write to. public void WriteTo(Span buffer) { - BinaryPrimitives.WriteInt32BigEndian(buffer[..4], this.NumberFrames); - BinaryPrimitives.WriteInt32BigEndian(buffer[4..8], this.NumberPlays); + BinaryPrimitives.WriteInt32BigEndian(buffer[..4], (int)this.NumberFrames); + BinaryPrimitives.WriteInt32BigEndian(buffer[4..8], (int)this.NumberPlays); } /// @@ -42,6 +42,6 @@ internal readonly struct AnimationControl /// The parsed acTL. public static AnimationControl Parse(ReadOnlySpan data) => new( - numberFrames: BinaryPrimitives.ReadInt32BigEndian(data[..4]), - numberPlays: BinaryPrimitives.ReadInt32BigEndian(data[4..8])); + numberFrames: BinaryPrimitives.ReadUInt32BigEndian(data[..4]), + numberPlays: BinaryPrimitives.ReadUInt32BigEndian(data[4..8])); } diff --git a/src/ImageSharp/Formats/Png/MetadataExtensions.cs b/src/ImageSharp/Formats/Png/MetadataExtensions.cs index f24b8d1b5..4a606d3a4 100644 --- a/src/ImageSharp/Formats/Png/MetadataExtensions.cs +++ b/src/ImageSharp/Formats/Png/MetadataExtensions.cs @@ -2,6 +2,7 @@ // Licensed under the Six Labors Split License. using System.Diagnostics.CodeAnalysis; +using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Formats.Png; using SixLabors.ImageSharp.Metadata; @@ -20,17 +21,56 @@ public static partial class MetadataExtensions public static PngMetadata GetPngMetadata(this ImageMetadata source) => source.GetFormatMetadata(PngFormat.Instance); /// - /// Gets the aPng format specific metadata for the image frame. + /// Gets the png format specific metadata for the image. + /// + /// The metadata this method extends. + /// The metadata. + /// + /// if the png metadata exists; otherwise, . + /// + public static bool TryGetPngMetadata(this ImageMetadata source, [NotNullWhen(true)] out PngMetadata? metadata) + => source.TryGetFormatMetadata(PngFormat.Instance, out metadata); + + /// + /// Gets the png format specific metadata for the image frame. /// /// The metadata this method extends. /// The . public static PngFrameMetadata GetPngFrameMetadata(this ImageFrameMetadata source) => source.GetFormatMetadata(PngFormat.Instance); /// - /// Gets the aPng format specific metadata for the image frame. + /// Gets the png format specific metadata for the image frame. /// /// The metadata this method extends. /// The metadata. - /// The . - public static bool TryGetPngFrameMetadata(this ImageFrameMetadata source, [NotNullWhen(true)] out PngFrameMetadata? metadata) => source.TryGetFormatMetadata(PngFormat.Instance, out metadata); + /// + /// if the png frame metadata exists; otherwise, . + /// + public static bool TryGetPngFrameMetadata(this ImageFrameMetadata source, [NotNullWhen(true)] out PngFrameMetadata? metadata) + => source.TryGetFormatMetadata(PngFormat.Instance, out metadata); + + internal static AnimatedImageMetadata ToAnimatedImageMetadata(this PngMetadata source) + => new() + { + ColorTable = source.ColorTable, + ColorTableMode = FrameColorTableMode.Global, + RepeatCount = (ushort)Numerics.Clamp(source.RepeatCount, 0, ushort.MaxValue), + }; + + internal static AnimatedImageFrameMetadata ToAnimatedImageFrameMetadata(this PngFrameMetadata source) + => new() + { + ColorTableMode = FrameColorTableMode.Global, + Duration = TimeSpan.FromMilliseconds(source.FrameDelay.ToDouble() * 1000), + DisposalMode = GetMode(source.DisposalMethod), + BlendMode = source.BlendMethod == PngBlendMethod.Source ? FrameBlendMode.Source : FrameBlendMode.Over, + }; + + private static FrameDisposalMode GetMode(PngDisposalMethod method) => method switch + { + PngDisposalMethod.None => FrameDisposalMode.DoNotDispose, + PngDisposalMethod.RestoreToBackground => FrameDisposalMode.RestoreToBackground, + PngDisposalMethod.RestoreToPrevious => FrameDisposalMode.RestoreToPrevious, + _ => FrameDisposalMode.Unspecified, + }; } diff --git a/src/ImageSharp/Formats/Png/PngDecoderCore.cs b/src/ImageSharp/Formats/Png/PngDecoderCore.cs index d8305a3f5..7d573efb6 100644 --- a/src/ImageSharp/Formats/Png/PngDecoderCore.cs +++ b/src/ImageSharp/Formats/Png/PngDecoderCore.cs @@ -621,8 +621,8 @@ internal sealed class PngDecoderCore : IImageDecoderInternals frame = image.Frames.AddFrame(previousFrame ?? image.Frames.RootFrame); // If the first `fcTL` chunk uses a `dispose_op` of APNG_DISPOSE_OP_PREVIOUS it should be treated as APNG_DISPOSE_OP_BACKGROUND. - if (previousFrameControl.DisposeOperation == PngDisposalMethod.Background - || (previousFrame is null && previousFrameControl.DisposeOperation == PngDisposalMethod.Previous)) + if (previousFrameControl.DisposeOperation == PngDisposalMethod.RestoreToBackground + || (previousFrame is null && previousFrameControl.DisposeOperation == PngDisposalMethod.RestoreToPrevious)) { Rectangle restoreArea = previousFrameControl.Bounds; Rectangle interest = Rectangle.Intersect(frame.Bounds(), restoreArea); diff --git a/src/ImageSharp/Formats/Png/PngDisposalMethod.cs b/src/ImageSharp/Formats/Png/PngDisposalMethod.cs index 17391de95..a431e8941 100644 --- a/src/ImageSharp/Formats/Png/PngDisposalMethod.cs +++ b/src/ImageSharp/Formats/Png/PngDisposalMethod.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors. +// Copyright (c) Six Labors. // Licensed under the Six Labors Split License. namespace SixLabors.ImageSharp.Formats.Png; @@ -16,10 +16,10 @@ public enum PngDisposalMethod /// /// The frame's region of the output buffer is to be cleared to fully transparent black before rendering the next frame. /// - Background, + RestoreToBackground, /// /// The frame's region of the output buffer is to be reverted to the previous contents before rendering the next frame. /// - Previous + RestoreToPrevious } diff --git a/src/ImageSharp/Formats/Png/PngEncoderCore.cs b/src/ImageSharp/Formats/Png/PngEncoderCore.cs index 04e3b1d84..be6991bab 100644 --- a/src/ImageSharp/Formats/Png/PngEncoderCore.cs +++ b/src/ImageSharp/Formats/Png/PngEncoderCore.cs @@ -176,7 +176,7 @@ internal sealed class PngEncoderCore : IImageEncoderInternals, IDisposable if (image.Frames.Count > 1) { - this.WriteAnimationControlChunk(stream, image.Frames.Count, pngMetadata.RepeatCount); + this.WriteAnimationControlChunk(stream, (uint)image.Frames.Count, pngMetadata.RepeatCount); // TODO: We should attempt to optimize the output by clipping the indexed result to // non-transparent bounds. That way we can assign frame control bounds and encode @@ -621,7 +621,7 @@ internal sealed class PngEncoderCore : IImageEncoderInternals, IDisposable /// The containing image data. /// The number of frames. /// The number of times to loop this APNG. - private void WriteAnimationControlChunk(Stream stream, int framesCount, int playsCount) + private void WriteAnimationControlChunk(Stream stream, uint framesCount, uint playsCount) { AnimationControl acTL = new(framesCount, playsCount); diff --git a/src/ImageSharp/Formats/Png/PngMetadata.cs b/src/ImageSharp/Formats/Png/PngMetadata.cs index b113dbfc1..6110cdd0c 100644 --- a/src/ImageSharp/Formats/Png/PngMetadata.cs +++ b/src/ImageSharp/Formats/Png/PngMetadata.cs @@ -2,7 +2,6 @@ // Licensed under the Six Labors Split License. using SixLabors.ImageSharp.Formats.Png.Chunks; -using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Formats.Png; @@ -82,7 +81,7 @@ public class PngMetadata : IDeepCloneable /// /// Gets or sets the number of times to loop this APNG. 0 indicates infinite looping. /// - public int RepeatCount { get; set; } + public uint RepeatCount { get; set; } /// public IDeepCloneable DeepClone() => new PngMetadata(this); diff --git a/src/ImageSharp/Formats/Webp/BitWriter/BitWriterBase.cs b/src/ImageSharp/Formats/Webp/BitWriter/BitWriterBase.cs index d502fd606..49b059b07 100644 --- a/src/ImageSharp/Formats/Webp/BitWriter/BitWriterBase.cs +++ b/src/ImageSharp/Formats/Webp/BitWriter/BitWriterBase.cs @@ -160,7 +160,7 @@ internal abstract class BitWriterBase /// The number of times to loop the animation. If it is 0, this means infinitely. public static void WriteAnimationParameter(Stream stream, Color background, ushort loopCount) { - WebpAnimationParameter chunk = new(background.ToRgba32().Rgba, loopCount); + WebpAnimationParameter chunk = new(background.ToBgra32().PackedValue, loopCount); chunk.WriteTo(stream); } diff --git a/src/ImageSharp/Formats/Webp/Chunks/WebpFrameData.cs b/src/ImageSharp/Formats/Webp/Chunks/WebpFrameData.cs index f22a3fd54..aee518326 100644 --- a/src/ImageSharp/Formats/Webp/Chunks/WebpFrameData.cs +++ b/src/ImageSharp/Formats/Webp/Chunks/WebpFrameData.cs @@ -32,8 +32,8 @@ internal readonly struct WebpFrameData width, height, duration, - (flags & 2) != 0 ? WebpBlendingMethod.DoNotBlend : WebpBlendingMethod.AlphaBlending, - (flags & 1) == 1 ? WebpDisposalMethod.Dispose : WebpDisposalMethod.DoNotDispose) + (flags & 2) == 0 ? WebpBlendingMethod.Over : WebpBlendingMethod.Source, + (flags & 1) == 1 ? WebpDisposalMethod.RestoreToBackground : WebpDisposalMethod.None) { } @@ -93,13 +93,13 @@ internal readonly struct WebpFrameData { byte flags = 0; - if (this.BlendingMethod is WebpBlendingMethod.DoNotBlend) + if (this.BlendingMethod is WebpBlendingMethod.Source) { // Set blending flag. flags |= 2; } - if (this.DisposalMethod is WebpDisposalMethod.Dispose) + if (this.DisposalMethod is WebpDisposalMethod.RestoreToBackground) { // Set disposal flag. flags |= 1; diff --git a/src/ImageSharp/Formats/Webp/Lossless/Vp8LEncoder.cs b/src/ImageSharp/Formats/Webp/Lossless/Vp8LEncoder.cs index 4fdbb31d3..0821be577 100644 --- a/src/ImageSharp/Formats/Webp/Lossless/Vp8LEncoder.cs +++ b/src/ImageSharp/Formats/Webp/Lossless/Vp8LEncoder.cs @@ -260,7 +260,7 @@ internal class Vp8LEncoder : IDisposable if (hasAnimation) { WebpMetadata webpMetadata = metadata.GetWebpMetadata(); - BitWriterBase.WriteAnimationParameter(stream, webpMetadata.AnimationBackground, webpMetadata.AnimationLoopCount); + BitWriterBase.WriteAnimationParameter(stream, webpMetadata.BackgroundColor, webpMetadata.RepeatCount); } } diff --git a/src/ImageSharp/Formats/Webp/Lossy/Vp8Encoder.cs b/src/ImageSharp/Formats/Webp/Lossy/Vp8Encoder.cs index 98e50bb9c..3fea72c07 100644 --- a/src/ImageSharp/Formats/Webp/Lossy/Vp8Encoder.cs +++ b/src/ImageSharp/Formats/Webp/Lossy/Vp8Encoder.cs @@ -334,7 +334,7 @@ internal class Vp8Encoder : IDisposable if (hasAnimation) { WebpMetadata webpMetadata = metadata.GetWebpMetadata(); - BitWriterBase.WriteAnimationParameter(stream, webpMetadata.AnimationBackground, webpMetadata.AnimationLoopCount); + BitWriterBase.WriteAnimationParameter(stream, webpMetadata.BackgroundColor, webpMetadata.RepeatCount); } } diff --git a/src/ImageSharp/Formats/Webp/MetadataExtensions.cs b/src/ImageSharp/Formats/Webp/MetadataExtensions.cs index 7f0920f2d..44da191d2 100644 --- a/src/ImageSharp/Formats/Webp/MetadataExtensions.cs +++ b/src/ImageSharp/Formats/Webp/MetadataExtensions.cs @@ -1,6 +1,8 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. +using System.Diagnostics.CodeAnalysis; +using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Formats.Webp; using SixLabors.ImageSharp.Metadata; @@ -18,10 +20,56 @@ public static partial class MetadataExtensions /// The . public static WebpMetadata GetWebpMetadata(this ImageMetadata metadata) => metadata.GetFormatMetadata(WebpFormat.Instance); + /// + /// Gets the webp format specific metadata for the image. + /// + /// The metadata this method extends. + /// The metadata. + /// + /// if the webp metadata exists; otherwise, . + /// + public static bool TryGetWebpMetadata(this ImageMetadata source, [NotNullWhen(true)] out WebpMetadata? metadata) + => source.TryGetFormatMetadata(WebpFormat.Instance, out metadata); + /// /// Gets the webp format specific metadata for the image frame. /// /// The metadata this method extends. /// The . public static WebpFrameMetadata GetWebpMetadata(this ImageFrameMetadata metadata) => metadata.GetFormatMetadata(WebpFormat.Instance); + + /// + /// Gets the webp format specific metadata for the image frame. + /// + /// The metadata this method extends. + /// The metadata. + /// + /// if the webp frame metadata exists; otherwise, . + /// + public static bool TryGetWebpFrameMetadata(this ImageFrameMetadata source, [NotNullWhen(true)] out WebpFrameMetadata? metadata) + => source.TryGetFormatMetadata(WebpFormat.Instance, out metadata); + + internal static AnimatedImageMetadata ToAnimatedImageMetadata(this WebpMetadata source) + => new() + { + ColorTableMode = FrameColorTableMode.Global, + RepeatCount = source.RepeatCount, + BackgroundColor = source.BackgroundColor + }; + + internal static AnimatedImageFrameMetadata ToAnimatedImageFrameMetadata(this WebpFrameMetadata source) + => new() + { + ColorTableMode = FrameColorTableMode.Global, + Duration = TimeSpan.FromMilliseconds(source.FrameDelay), + DisposalMode = GetMode(source.DisposalMethod), + BlendMode = source.BlendMethod == WebpBlendingMethod.Over ? FrameBlendMode.Over : FrameBlendMode.Source, + }; + + private static FrameDisposalMode GetMode(WebpDisposalMethod method) => method switch + { + WebpDisposalMethod.RestoreToBackground => FrameDisposalMode.RestoreToBackground, + WebpDisposalMethod.None => FrameDisposalMode.DoNotDispose, + _ => FrameDisposalMode.DoNotDispose, + }; } diff --git a/src/ImageSharp/Formats/Webp/WebpAnimationDecoder.cs b/src/ImageSharp/Formats/Webp/WebpAnimationDecoder.cs index 66e69d9a4..f081cfcd8 100644 --- a/src/ImageSharp/Formats/Webp/WebpAnimationDecoder.cs +++ b/src/ImageSharp/Formats/Webp/WebpAnimationDecoder.cs @@ -89,7 +89,7 @@ internal class WebpAnimationDecoder : IDisposable this.metadata = new ImageMetadata(); this.webpMetadata = this.metadata.GetWebpMetadata(); - this.webpMetadata.AnimationLoopCount = features.AnimationLoopCount; + this.webpMetadata.RepeatCount = features.AnimationLoopCount; Span buffer = stackalloc byte[4]; uint frameCount = 0; @@ -195,14 +195,14 @@ internal class WebpAnimationDecoder : IDisposable Rectangle regionRectangle = frameData.Bounds; - if (frameData.DisposalMethod is WebpDisposalMethod.Dispose) + if (frameData.DisposalMethod is WebpDisposalMethod.RestoreToBackground) { this.RestoreToBackground(imageFrame, backgroundColor); } using Buffer2D decodedImageFrame = this.DecodeImageFrameData(frameData, webpInfo); - bool blend = previousFrame != null && frameData.BlendingMethod == WebpBlendingMethod.AlphaBlending; + bool blend = previousFrame != null && frameData.BlendingMethod == WebpBlendingMethod.Over; DrawDecodedImageFrameOnCanvas(decodedImageFrame, imageFrame, regionRectangle, blend); previousFrame = currentFrame ?? image.Frames.RootFrame; diff --git a/src/ImageSharp/Formats/Webp/WebpBlendingMethod.cs b/src/ImageSharp/Formats/Webp/WebpBlendingMethod.cs index cbd0e9a8c..482d62cd2 100644 --- a/src/ImageSharp/Formats/Webp/WebpBlendingMethod.cs +++ b/src/ImageSharp/Formats/Webp/WebpBlendingMethod.cs @@ -9,14 +9,14 @@ namespace SixLabors.ImageSharp.Formats.Webp; public enum WebpBlendingMethod { /// - /// Use alpha blending. After disposing of the previous frame, render the current frame on the canvas using alpha-blending. - /// If the current frame does not have an alpha channel, assume alpha value of 255, effectively replacing the rectangle. + /// Do not blend. After disposing of the previous frame, + /// render the current frame on the canvas by overwriting the rectangle covered by the current frame. /// - AlphaBlending = 0, + Source = 0, /// - /// Do not blend. After disposing of the previous frame, - /// render the current frame on the canvas by overwriting the rectangle covered by the current frame. + /// Use alpha blending. After disposing of the previous frame, render the current frame on the canvas using alpha-blending. + /// If the current frame does not have an alpha channel, assume alpha value of 255, effectively replacing the rectangle. /// - DoNotBlend = 1 + Over = 1, } diff --git a/src/ImageSharp/Formats/Webp/WebpDisposalMethod.cs b/src/ImageSharp/Formats/Webp/WebpDisposalMethod.cs index d409973a9..397c2ee50 100644 --- a/src/ImageSharp/Formats/Webp/WebpDisposalMethod.cs +++ b/src/ImageSharp/Formats/Webp/WebpDisposalMethod.cs @@ -11,10 +11,10 @@ public enum WebpDisposalMethod /// /// Do not dispose. Leave the canvas as is. /// - DoNotDispose = 0, + None = 0, /// /// Dispose to background color. Fill the rectangle on the canvas covered by the current frame with background color specified in the ANIM chunk. /// - Dispose = 1 + RestoreToBackground = 1 } diff --git a/src/ImageSharp/Formats/Webp/WebpMetadata.cs b/src/ImageSharp/Formats/Webp/WebpMetadata.cs index a6bb0a7b8..9d0d8d08d 100644 --- a/src/ImageSharp/Formats/Webp/WebpMetadata.cs +++ b/src/ImageSharp/Formats/Webp/WebpMetadata.cs @@ -22,8 +22,8 @@ public class WebpMetadata : IDeepCloneable private WebpMetadata(WebpMetadata other) { this.FileFormat = other.FileFormat; - this.AnimationLoopCount = other.AnimationLoopCount; - this.AnimationBackground = other.AnimationBackground; + this.RepeatCount = other.RepeatCount; + this.BackgroundColor = other.BackgroundColor; } /// @@ -34,15 +34,15 @@ public class WebpMetadata : IDeepCloneable /// /// Gets or sets the loop count. The number of times to loop the animation. 0 means infinitely. /// - public ushort AnimationLoopCount { get; set; } = 1; + public ushort RepeatCount { get; set; } = 1; /// - /// Gets or sets the default background color of the canvas in [Blue, Green, Red, Alpha] byte order. - /// This color MAY be used to fill the unused space on the canvas around the frames, + /// 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 method is 1. + /// The background color is also used when the Disposal method is . /// - public Color AnimationBackground { get; set; } + public Color BackgroundColor { get; set; } /// public IDeepCloneable DeepClone() => new WebpMetadata(this); diff --git a/src/ImageSharp/Metadata/FrameDecodingMode.cs b/src/ImageSharp/Metadata/FrameDecodingMode.cs deleted file mode 100644 index 3a5965489..000000000 --- a/src/ImageSharp/Metadata/FrameDecodingMode.cs +++ /dev/null @@ -1,20 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Metadata; - -/// -/// Enumerated frame process modes to apply to multi-frame images. -/// -public enum FrameDecodingMode -{ - /// - /// Decodes all the frames of a multi-frame image. - /// - All, - - /// - /// Decodes only the first frame of a multi-frame image. - /// - First -} diff --git a/src/ImageSharp/Metadata/ImageMetadata.cs b/src/ImageSharp/Metadata/ImageMetadata.cs index f54fc5c7a..e1284b50e 100644 --- a/src/ImageSharp/Metadata/ImageMetadata.cs +++ b/src/ImageSharp/Metadata/ImageMetadata.cs @@ -183,6 +183,32 @@ public sealed class ImageMetadata : IDeepCloneable return newMeta; } + /// + /// Gets the metadata value associated with the specified key. + /// + /// The type of format metadata. + /// The key of the value to get. + /// + /// When this method returns, contains the metadata associated with the specified key, + /// if the key is found; otherwise, the default value for the type of the metadata parameter. + /// This parameter is passed uninitialized. + /// + /// + /// if the frame metadata exists for the specified key; otherwise, . + /// + public bool TryGetFormatMetadata(IImageFormat key, out TFormatMetadata? metadata) + where TFormatMetadata : class, IDeepCloneable + { + if (this.formatMetadata.TryGetValue(key, out IDeepCloneable? meta)) + { + metadata = (TFormatMetadata)meta; + return true; + } + + metadata = default; + return false; + } + /// public ImageMetadata DeepClone() => new(this); diff --git a/tests/ImageSharp.Tests/Formats/Gif/GifEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Gif/GifEncoderTests.cs index 31001e31b..65d186c91 100644 --- a/tests/ImageSharp.Tests/Formats/Gif/GifEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Gif/GifEncoderTests.cs @@ -245,7 +245,7 @@ public class GifEncoderTests int count = 0; foreach (ImageFrame frame in image.Frames) { - if (frame.Metadata.TryGetGifMetadata(out GifFrameMetadata _)) + if (frame.Metadata.TryGetGifFrameMetadata(out GifFrameMetadata _)) { count++; } @@ -261,7 +261,7 @@ public class GifEncoderTests count = 0; foreach (ImageFrame frame in image2.Frames) { - if (frame.Metadata.TryGetGifMetadata(out GifFrameMetadata _)) + if (frame.Metadata.TryGetGifFrameMetadata(out GifFrameMetadata _)) { count++; } diff --git a/tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs index 92c07a27a..a6840b33e 100644 --- a/tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs @@ -3,6 +3,7 @@ // ReSharper disable InconsistentNaming using SixLabors.ImageSharp.Formats; +using SixLabors.ImageSharp.Formats.Gif; using SixLabors.ImageSharp.Formats.Png; using SixLabors.ImageSharp.Metadata; using SixLabors.ImageSharp.PixelFormats; @@ -453,6 +454,10 @@ public partial class PngEncoderTests memStream.Position = 0; image.DebugSave(provider: provider, encoder: PngEncoder, null, false); + image.DebugSave(provider: provider, encoder: new GifEncoder(), "gif", false); + + string path = provider.Utility.GetTestOutputFileName("gif"); + image.Save(path); using Image output = Image.Load(memStream); ImageComparer.Exact.VerifySimilarity(output, image); diff --git a/tests/ImageSharp.Tests/Formats/Png/PngFrameMetadataTests.cs b/tests/ImageSharp.Tests/Formats/Png/PngFrameMetadataTests.cs index e29585c2d..9ba261728 100644 --- a/tests/ImageSharp.Tests/Formats/Png/PngFrameMetadataTests.cs +++ b/tests/ImageSharp.Tests/Formats/Png/PngFrameMetadataTests.cs @@ -14,7 +14,7 @@ public class PngFrameMetadataTests PngFrameMetadata meta = new() { FrameDelay = new(1, 0), - DisposalMethod = PngDisposalMethod.Background, + DisposalMethod = PngDisposalMethod.RestoreToBackground, BlendMethod = PngBlendMethod.Over, }; @@ -25,7 +25,7 @@ public class PngFrameMetadataTests Assert.True(meta.BlendMethod.Equals(clone.BlendMethod)); clone.FrameDelay = new(2, 1); - clone.DisposalMethod = PngDisposalMethod.Previous; + clone.DisposalMethod = PngDisposalMethod.RestoreToPrevious; clone.BlendMethod = PngBlendMethod.Source; Assert.False(meta.FrameDelay.Equals(clone.FrameDelay)); diff --git a/tests/ImageSharp.Tests/Formats/WebP/WebpDecoderTests.cs b/tests/ImageSharp.Tests/Formats/WebP/WebpDecoderTests.cs index 4b03671e1..6301f341c 100644 --- a/tests/ImageSharp.Tests/Formats/WebP/WebpDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/WebP/WebpDecoderTests.cs @@ -307,7 +307,7 @@ public class WebpDecoderTests image.DebugSaveMultiFrame(provider); image.CompareToReferenceOutputMultiFrame(provider, ImageComparer.Exact); - Assert.Equal(0, webpMetaData.AnimationLoopCount); + Assert.Equal(0, webpMetaData.RepeatCount); Assert.Equal(150U, frameMetaData.FrameDelay); Assert.Equal(12, image.Frames.Count); } @@ -324,7 +324,7 @@ public class WebpDecoderTests image.DebugSaveMultiFrame(provider); image.CompareToReferenceOutputMultiFrame(provider, ImageComparer.Tolerant(0.04f)); - Assert.Equal(0, webpMetaData.AnimationLoopCount); + Assert.Equal(0, webpMetaData.RepeatCount); Assert.Equal(150U, frameMetaData.FrameDelay); Assert.Equal(12, image.Frames.Count); } From a486558ed19a8fda1c60bf50e9e2e5d258b8bcbd Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Fri, 17 Nov 2023 13:19:02 +1000 Subject: [PATCH 02/24] Complete Webp and add tests --- src/ImageSharp/Formats/Gif/GifEncoderCore.cs | 20 +++- .../Formats/Gif/MetadataExtensions.cs | 13 ++- .../Formats/Png/MetadataExtensions.cs | 18 ++- src/ImageSharp/Formats/Png/PngDecoderCore.cs | 4 +- .../Formats/Png/PngDisposalMethod.cs | 2 +- src/ImageSharp/Formats/Png/PngEncoderCore.cs | 59 +++++++++- .../Formats/Png/PngFrameMetadata.cs | 18 ++- src/ImageSharp/Formats/Png/PngMetadata.cs | 33 ++++++ .../Formats/Webp/Chunks/WebpFrameData.cs | 2 +- .../Formats/Webp/Lossless/Vp8LEncoder.cs | 4 +- .../Formats/Webp/Lossy/Vp8Encoder.cs | 4 +- .../Formats/Webp/MetadataExtensions.cs | 2 +- .../Formats/Webp/WebpCommonUtils.cs | 56 ++++++++- .../Formats/Webp/WebpDisposalMethod.cs | 2 +- .../Formats/Webp/WebpEncoderCore.cs | 2 +- .../Formats/Webp/WebpFrameMetadata.cs | 8 ++ src/ImageSharp/Formats/Webp/WebpMetadata.cs | 8 ++ .../Quantization/EuclideanPixelMap{TPixel}.cs | 6 +- .../Formats/Gif/GifEncoderTests.cs | 92 ++++++++++++++- .../Formats/Png/PngEncoderTests.cs | 109 +++++++++++++++++- .../Formats/WebP/WebpEncoderTests.cs | 93 +++++++++++++++ 21 files changed, 515 insertions(+), 40 deletions(-) diff --git a/src/ImageSharp/Formats/Gif/GifEncoderCore.cs b/src/ImageSharp/Formats/Gif/GifEncoderCore.cs index 33942ce54..d22b960ec 100644 --- a/src/ImageSharp/Formats/Gif/GifEncoderCore.cs +++ b/src/ImageSharp/Formats/Gif/GifEncoderCore.cs @@ -190,19 +190,20 @@ internal sealed class GifEncoderCore : IImageEncoderInternals return GifMetadata.FromAnimatedMetadata(ani); } + // Return explicit new instance so we do not mutate the original metadata. return new(); } private static GifFrameMetadata? GetGifFrameMetadata(ImageFrame frame, int transparencyIndex) where TPixel : unmanaged, IPixel { - if (frame.Metadata.TryGetGifFrameMetadata(out GifFrameMetadata? gif)) + if (frame.Metadata.TryGetGifMetadata(out GifFrameMetadata? gif)) { return gif; } GifFrameMetadata? metadata = null; - if (frame.Metadata.TryGetPngFrameMetadata(out PngFrameMetadata? png)) + if (frame.Metadata.TryGetPngMetadata(out PngFrameMetadata? png)) { AnimatedImageFrameMetadata ani = png.ToAnimatedImageFrameMetadata(); metadata = GifFrameMetadata.FromAnimatedMetadata(ani); @@ -342,7 +343,20 @@ internal sealed class GifEncoderCore : IImageEncoderInternals } } - this.DeDuplicatePixels(previousFrame, currentFrame, encodingFrame, replacement); + // We can't deduplicate here as we need the background pixels to be present in the buffer. + if (metadata?.DisposalMethod == GifDisposalMethod.RestoreToBackground) + { + for (int y = 0; y < currentFrame.PixelBuffer.Height; y++) + { + Span sourceRow = currentFrame.PixelBuffer.DangerousGetRowSpan(y); + Span destinationRow = encodingFrame.PixelBuffer.DangerousGetRowSpan(y); + sourceRow.CopyTo(destinationRow); + } + } + else + { + this.DeDuplicatePixels(previousFrame, currentFrame, encodingFrame, replacement); + } IndexedImageFrame quantized; if (useLocal) diff --git a/src/ImageSharp/Formats/Gif/MetadataExtensions.cs b/src/ImageSharp/Formats/Gif/MetadataExtensions.cs index f4eaffe6b..c7f9f84c8 100644 --- a/src/ImageSharp/Formats/Gif/MetadataExtensions.cs +++ b/src/ImageSharp/Formats/Gif/MetadataExtensions.cs @@ -56,16 +56,25 @@ public static partial class MetadataExtensions /// /// if the gif frame metadata exists; otherwise, . /// - public static bool TryGetGifFrameMetadata(this ImageFrameMetadata source, [NotNullWhen(true)] out GifFrameMetadata? metadata) + public static bool TryGetGifMetadata(this ImageFrameMetadata source, [NotNullWhen(true)] out GifFrameMetadata? metadata) => source.TryGetFormatMetadata(GifFormat.Instance, out metadata); internal static AnimatedImageMetadata ToAnimatedImageMetadata(this GifMetadata source) - => new() + { + Color background = Color.Transparent; + if (source.GlobalColorTable != null) + { + background = source.GlobalColorTable.Value.Span[source.BackgroundColorIndex]; + } + + return new() { ColorTable = source.GlobalColorTable, ColorTableMode = source.ColorTableMode == GifColorTableMode.Global ? FrameColorTableMode.Global : FrameColorTableMode.Local, RepeatCount = source.RepeatCount, + BackgroundColor = background, }; + } internal static AnimatedImageFrameMetadata ToAnimatedImageFrameMetadata(this GifFrameMetadata source) => new() diff --git a/src/ImageSharp/Formats/Png/MetadataExtensions.cs b/src/ImageSharp/Formats/Png/MetadataExtensions.cs index 4a606d3a4..b6313bffe 100644 --- a/src/ImageSharp/Formats/Png/MetadataExtensions.cs +++ b/src/ImageSharp/Formats/Png/MetadataExtensions.cs @@ -36,7 +36,7 @@ public static partial class MetadataExtensions /// /// The metadata this method extends. /// The . - public static PngFrameMetadata GetPngFrameMetadata(this ImageFrameMetadata source) => source.GetFormatMetadata(PngFormat.Instance); + public static PngFrameMetadata GetPngMetadata(this ImageFrameMetadata source) => source.GetFormatMetadata(PngFormat.Instance); /// /// Gets the png format specific metadata for the image frame. @@ -46,7 +46,7 @@ public static partial class MetadataExtensions /// /// if the png frame metadata exists; otherwise, . /// - public static bool TryGetPngFrameMetadata(this ImageFrameMetadata source, [NotNullWhen(true)] out PngFrameMetadata? metadata) + public static bool TryGetPngMetadata(this ImageFrameMetadata source, [NotNullWhen(true)] out PngFrameMetadata? metadata) => source.TryGetFormatMetadata(PngFormat.Instance, out metadata); internal static AnimatedImageMetadata ToAnimatedImageMetadata(this PngMetadata source) @@ -58,17 +58,25 @@ public static partial class MetadataExtensions }; internal static AnimatedImageFrameMetadata ToAnimatedImageFrameMetadata(this PngFrameMetadata source) - => new() + { + double delay = source.FrameDelay.ToDouble(); + if (double.IsNaN(delay)) + { + delay = 0; + } + + return new() { ColorTableMode = FrameColorTableMode.Global, - Duration = TimeSpan.FromMilliseconds(source.FrameDelay.ToDouble() * 1000), + Duration = TimeSpan.FromMilliseconds(delay * 1000), DisposalMode = GetMode(source.DisposalMethod), BlendMode = source.BlendMethod == PngBlendMethod.Source ? FrameBlendMode.Source : FrameBlendMode.Over, }; + } private static FrameDisposalMode GetMode(PngDisposalMethod method) => method switch { - PngDisposalMethod.None => FrameDisposalMode.DoNotDispose, + PngDisposalMethod.DoNotDispose => FrameDisposalMode.DoNotDispose, PngDisposalMethod.RestoreToBackground => FrameDisposalMode.RestoreToBackground, PngDisposalMethod.RestoreToPrevious => FrameDisposalMode.RestoreToPrevious, _ => FrameDisposalMode.Unspecified, diff --git a/src/ImageSharp/Formats/Png/PngDecoderCore.cs b/src/ImageSharp/Formats/Png/PngDecoderCore.cs index 7d573efb6..b0706b14c 100644 --- a/src/ImageSharp/Formats/Png/PngDecoderCore.cs +++ b/src/ImageSharp/Formats/Png/PngDecoderCore.cs @@ -581,7 +581,7 @@ internal sealed class PngDecoderCore : IImageDecoderInternals this.header.Height, metadata); - PngFrameMetadata frameMetadata = image.Frames.RootFrame.Metadata.GetPngFrameMetadata(); + PngFrameMetadata frameMetadata = image.Frames.RootFrame.Metadata.GetPngMetadata(); frameMetadata.FromChunk(in frameControl); this.bytesPerPixel = this.CalculateBytesPerPixel(); @@ -630,7 +630,7 @@ internal sealed class PngDecoderCore : IImageDecoderInternals pixelRegion.Clear(); } - PngFrameMetadata frameMetadata = frame.Metadata.GetPngFrameMetadata(); + PngFrameMetadata frameMetadata = frame.Metadata.GetPngMetadata(); frameMetadata.FromChunk(currentFrameControl); this.previousScanline?.Dispose(); diff --git a/src/ImageSharp/Formats/Png/PngDisposalMethod.cs b/src/ImageSharp/Formats/Png/PngDisposalMethod.cs index a431e8941..1537c5ced 100644 --- a/src/ImageSharp/Formats/Png/PngDisposalMethod.cs +++ b/src/ImageSharp/Formats/Png/PngDisposalMethod.cs @@ -11,7 +11,7 @@ public enum PngDisposalMethod /// /// No disposal is done on this frame before rendering the next; the contents of the output buffer are left as is. /// - None, + DoNotDispose, /// /// The frame's region of the output buffer is to be cleared to fully transparent black before rendering the next frame. diff --git a/src/ImageSharp/Formats/Png/PngEncoderCore.cs b/src/ImageSharp/Formats/Png/PngEncoderCore.cs index be6991bab..a779718a0 100644 --- a/src/ImageSharp/Formats/Png/PngEncoderCore.cs +++ b/src/ImageSharp/Formats/Png/PngEncoderCore.cs @@ -7,8 +7,10 @@ using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using SixLabors.ImageSharp.Common.Helpers; using SixLabors.ImageSharp.Compression.Zlib; +using SixLabors.ImageSharp.Formats.Gif; using SixLabors.ImageSharp.Formats.Png.Chunks; using SixLabors.ImageSharp.Formats.Png.Filters; +using SixLabors.ImageSharp.Formats.Webp; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Metadata; using SixLabors.ImageSharp.PixelFormats; @@ -137,7 +139,7 @@ internal sealed class PngEncoderCore : IImageEncoderInternals, IDisposable /// The to encode the image data to. /// The token to request cancellation. public void Encode(Image image, Stream stream, CancellationToken cancellationToken) - where TPixel : unmanaged, IPixel + where TPixel : unmanaged, IPixel { Guard.NotNull(image, nameof(image)); Guard.NotNull(stream, nameof(stream)); @@ -146,7 +148,7 @@ internal sealed class PngEncoderCore : IImageEncoderInternals, IDisposable this.height = image.Height; ImageMetadata metadata = image.Metadata; - PngMetadata pngMetadata = metadata.GetFormatMetadata(PngFormat.Instance); + PngMetadata pngMetadata = GetPngMetadata(image); this.SanitizeAndSetEncoderOptions(this.encoder, pngMetadata, out this.use16Bit, out this.bytesPerPixel); stream.Write(PngConstants.HeaderBytes); @@ -234,6 +236,54 @@ internal sealed class PngEncoderCore : IImageEncoderInternals, IDisposable this.currentScanline?.Dispose(); } + private static PngMetadata GetPngMetadata(Image image) + where TPixel : unmanaged, IPixel + { + if (image.Metadata.TryGetPngMetadata(out PngMetadata? png)) + { + return png; + } + + if (image.Metadata.TryGetGifMetadata(out GifMetadata? gif)) + { + AnimatedImageMetadata ani = gif.ToAnimatedImageMetadata(); + return PngMetadata.FromAnimatedMetadata(ani); + } + + if (image.Metadata.TryGetWebpMetadata(out WebpMetadata? webp)) + { + AnimatedImageMetadata ani = webp.ToAnimatedImageMetadata(); + return PngMetadata.FromAnimatedMetadata(ani); + } + + // Return explicit new instance so we do not mutate the original metadata. + return new(); + } + + private static PngFrameMetadata GetPngFrameMetadata(ImageFrame frame) + where TPixel : unmanaged, IPixel + { + if (frame.Metadata.TryGetPngMetadata(out PngFrameMetadata? png)) + { + return png; + } + + if (frame.Metadata.TryGetGifMetadata(out GifFrameMetadata? gif)) + { + AnimatedImageFrameMetadata ani = gif.ToAnimatedImageFrameMetadata(); + return PngFrameMetadata.FromAnimatedMetadata(ani); + } + + if (frame.Metadata.TryGetWebpFrameMetadata(out WebpFrameMetadata? webp)) + { + AnimatedImageFrameMetadata ani = webp.ToAnimatedImageFrameMetadata(); + return PngFrameMetadata.FromAnimatedMetadata(ani); + } + + // Return explicit new instance so we do not mutate the original metadata. + return new(); + } + /// /// Convert transparent pixels, to transparent black pixels, which can yield to better compression in some cases. /// @@ -985,9 +1035,10 @@ internal sealed class PngEncoderCore : IImageEncoderInternals, IDisposable /// The containing image data. /// The image frame. /// The frame sequence number. - private FrameControl WriteFrameControlChunk(Stream stream, ImageFrame imageFrame, uint sequenceNumber) + private FrameControl WriteFrameControlChunk(Stream stream, ImageFrame imageFrame, uint sequenceNumber) + where TPixel : unmanaged, IPixel { - PngFrameMetadata frameMetadata = imageFrame.Metadata.GetPngFrameMetadata(); + PngFrameMetadata frameMetadata = GetPngFrameMetadata(imageFrame); // TODO: If we can clip the indexed frame for transparent bounds we can set properties here. FrameControl fcTL = new( diff --git a/src/ImageSharp/Formats/Png/PngFrameMetadata.cs b/src/ImageSharp/Formats/Png/PngFrameMetadata.cs index ca4d8c1f4..dbda4d73c 100644 --- a/src/ImageSharp/Formats/Png/PngFrameMetadata.cs +++ b/src/ImageSharp/Formats/Png/PngFrameMetadata.cs @@ -34,7 +34,7 @@ public class PngFrameMetadata : IDeepCloneable /// wait before continuing with the processing of the Data Stream. /// The clock starts ticking immediately after the graphic is rendered. /// - public Rational FrameDelay { get; set; } + public Rational FrameDelay { get; set; } = new(0); /// /// Gets or sets the type of frame area disposal to be done after rendering this frame @@ -59,4 +59,20 @@ public class PngFrameMetadata : IDeepCloneable /// public IDeepCloneable DeepClone() => new PngFrameMetadata(this); + + internal static PngFrameMetadata FromAnimatedMetadata(AnimatedImageFrameMetadata metadata) + => new() + { + FrameDelay = new(metadata.Duration.TotalMilliseconds / 1000), + DisposalMethod = GetMode(metadata.DisposalMode), + BlendMethod = metadata.BlendMode == FrameBlendMode.Source ? PngBlendMethod.Source : PngBlendMethod.Over, + }; + + private static PngDisposalMethod GetMode(FrameDisposalMode mode) => mode switch + { + FrameDisposalMode.RestoreToBackground => PngDisposalMethod.RestoreToBackground, + FrameDisposalMode.RestoreToPrevious => PngDisposalMethod.RestoreToPrevious, + FrameDisposalMode.DoNotDispose => PngDisposalMethod.DoNotDispose, + _ => PngDisposalMethod.DoNotDispose, + }; } diff --git a/src/ImageSharp/Formats/Png/PngMetadata.cs b/src/ImageSharp/Formats/Png/PngMetadata.cs index 6110cdd0c..8e2691c10 100644 --- a/src/ImageSharp/Formats/Png/PngMetadata.cs +++ b/src/ImageSharp/Formats/Png/PngMetadata.cs @@ -85,4 +85,37 @@ public class PngMetadata : IDeepCloneable /// public IDeepCloneable DeepClone() => new PngMetadata(this); + + internal static PngMetadata FromAnimatedMetadata(AnimatedImageMetadata metadata) + { + // Should the conversion be from a format that uses a 24bit palette entries (gif) + // we need to clone and adjust the color table to allow for transparency. + ReadOnlyMemory? colorTable = metadata.ColorTable; + if (metadata.ColorTable.HasValue) + { + Color[] clone = metadata.ColorTable.Value.ToArray(); + for (int i = 0; i < clone.Length; i++) + { + ref Color c = ref clone[i]; + if (c == metadata.BackgroundColor) + { + // Png treats background as fully empty + c = default; + break; + } + } + + colorTable = clone; + } + + return new() + { + ColorType = colorTable.HasValue ? PngColorType.Palette : null, + BitDepth = colorTable.HasValue + ? (PngBitDepth)Numerics.Clamp(ColorNumerics.GetBitsNeededForColorDepth(colorTable.Value.Length), 1, 8) + : null, + ColorTable = colorTable, + RepeatCount = metadata.RepeatCount, + }; + } } diff --git a/src/ImageSharp/Formats/Webp/Chunks/WebpFrameData.cs b/src/ImageSharp/Formats/Webp/Chunks/WebpFrameData.cs index aee518326..230f69c32 100644 --- a/src/ImageSharp/Formats/Webp/Chunks/WebpFrameData.cs +++ b/src/ImageSharp/Formats/Webp/Chunks/WebpFrameData.cs @@ -33,7 +33,7 @@ internal readonly struct WebpFrameData height, duration, (flags & 2) == 0 ? WebpBlendingMethod.Over : WebpBlendingMethod.Source, - (flags & 1) == 1 ? WebpDisposalMethod.RestoreToBackground : WebpDisposalMethod.None) + (flags & 1) == 1 ? WebpDisposalMethod.RestoreToBackground : WebpDisposalMethod.DoNotDispose) { } diff --git a/src/ImageSharp/Formats/Webp/Lossless/Vp8LEncoder.cs b/src/ImageSharp/Formats/Webp/Lossless/Vp8LEncoder.cs index 0821be577..b9e2519fa 100644 --- a/src/ImageSharp/Formats/Webp/Lossless/Vp8LEncoder.cs +++ b/src/ImageSharp/Formats/Webp/Lossless/Vp8LEncoder.cs @@ -259,7 +259,7 @@ internal class Vp8LEncoder : IDisposable if (hasAnimation) { - WebpMetadata webpMetadata = metadata.GetWebpMetadata(); + WebpMetadata webpMetadata = WebpCommonUtils.GetWebpMetadata(image); BitWriterBase.WriteAnimationParameter(stream, webpMetadata.BackgroundColor, webpMetadata.RepeatCount); } } @@ -307,7 +307,7 @@ internal class Vp8LEncoder : IDisposable if (hasAnimation) { - WebpFrameMetadata frameMetadata = frame.Metadata.GetWebpMetadata(); + WebpFrameMetadata frameMetadata = WebpCommonUtils.GetWebpFrameMetadata(frame); // TODO: If we can clip the indexed frame for transparent bounds we can set properties here. prevPosition = new WebpFrameData( diff --git a/src/ImageSharp/Formats/Webp/Lossy/Vp8Encoder.cs b/src/ImageSharp/Formats/Webp/Lossy/Vp8Encoder.cs index 3fea72c07..e6148a066 100644 --- a/src/ImageSharp/Formats/Webp/Lossy/Vp8Encoder.cs +++ b/src/ImageSharp/Formats/Webp/Lossy/Vp8Encoder.cs @@ -333,7 +333,7 @@ internal class Vp8Encoder : IDisposable if (hasAnimation) { - WebpMetadata webpMetadata = metadata.GetWebpMetadata(); + WebpMetadata webpMetadata = WebpCommonUtils.GetWebpMetadata(image); BitWriterBase.WriteAnimationParameter(stream, webpMetadata.BackgroundColor, webpMetadata.RepeatCount); } } @@ -477,7 +477,7 @@ internal class Vp8Encoder : IDisposable if (hasAnimation) { - WebpFrameMetadata frameMetadata = frame.Metadata.GetWebpMetadata(); + WebpFrameMetadata frameMetadata = WebpCommonUtils.GetWebpFrameMetadata(frame); // TODO: If we can clip the indexed frame for transparent bounds we can set properties here. prevPosition = new WebpFrameData( diff --git a/src/ImageSharp/Formats/Webp/MetadataExtensions.cs b/src/ImageSharp/Formats/Webp/MetadataExtensions.cs index 44da191d2..10c72a3d9 100644 --- a/src/ImageSharp/Formats/Webp/MetadataExtensions.cs +++ b/src/ImageSharp/Formats/Webp/MetadataExtensions.cs @@ -69,7 +69,7 @@ public static partial class MetadataExtensions private static FrameDisposalMode GetMode(WebpDisposalMethod method) => method switch { WebpDisposalMethod.RestoreToBackground => FrameDisposalMode.RestoreToBackground, - WebpDisposalMethod.None => FrameDisposalMode.DoNotDispose, + WebpDisposalMethod.DoNotDispose => FrameDisposalMode.DoNotDispose, _ => FrameDisposalMode.DoNotDispose, }; } diff --git a/src/ImageSharp/Formats/Webp/WebpCommonUtils.cs b/src/ImageSharp/Formats/Webp/WebpCommonUtils.cs index 1a8fcbafc..bb7dd6f27 100644 --- a/src/ImageSharp/Formats/Webp/WebpCommonUtils.cs +++ b/src/ImageSharp/Formats/Webp/WebpCommonUtils.cs @@ -4,6 +4,8 @@ using System.Runtime.InteropServices; using System.Runtime.Intrinsics; using System.Runtime.Intrinsics.X86; +using SixLabors.ImageSharp.Formats.Gif; +using SixLabors.ImageSharp.Formats.Png; using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Formats.Webp; @@ -13,6 +15,54 @@ namespace SixLabors.ImageSharp.Formats.Webp; /// internal static class WebpCommonUtils { + public static WebpMetadata GetWebpMetadata(Image image) + where TPixel : unmanaged, IPixel + { + if (image.Metadata.TryGetWebpMetadata(out WebpMetadata? webp)) + { + return webp; + } + + if (image.Metadata.TryGetGifMetadata(out GifMetadata? gif)) + { + AnimatedImageMetadata ani = gif.ToAnimatedImageMetadata(); + return WebpMetadata.FromAnimatedMetadata(ani); + } + + if (image.Metadata.TryGetPngMetadata(out PngMetadata? png)) + { + AnimatedImageMetadata ani = png.ToAnimatedImageMetadata(); + return WebpMetadata.FromAnimatedMetadata(ani); + } + + // Return explicit new instance so we do not mutate the original metadata. + return new(); + } + + public static WebpFrameMetadata GetWebpFrameMetadata(ImageFrame frame) + where TPixel : unmanaged, IPixel + { + if (frame.Metadata.TryGetWebpFrameMetadata(out WebpFrameMetadata? webp)) + { + return webp; + } + + if (frame.Metadata.TryGetGifMetadata(out GifFrameMetadata? gif)) + { + AnimatedImageFrameMetadata ani = gif.ToAnimatedImageFrameMetadata(); + return WebpFrameMetadata.FromAnimatedMetadata(ani); + } + + if (frame.Metadata.TryGetPngMetadata(out PngFrameMetadata? png)) + { + AnimatedImageFrameMetadata ani = png.ToAnimatedImageFrameMetadata(); + return WebpFrameMetadata.FromAnimatedMetadata(ani); + } + + // Return explicit new instance so we do not mutate the original metadata. + return new(); + } + /// /// Checks if the pixel row is not opaque. /// @@ -27,7 +77,7 @@ internal static class WebpCommonUtils int length = (row.Length * 4) - 3; fixed (byte* src = rowBytes) { - var alphaMaskVector256 = Vector256.Create(0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255); + Vector256 alphaMaskVector256 = Vector256.Create(0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255); Vector256 all0x80Vector256 = Vector256.Create((byte)0x80).AsByte(); for (; i + 128 <= length; i += 128) @@ -124,7 +174,7 @@ internal static class WebpCommonUtils private static unsafe bool IsNoneOpaque64Bytes(byte* src, int i) { - var alphaMask = Vector128.Create(0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255); + Vector128 alphaMask = Vector128.Create(0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255); Vector128 a0 = Sse2.LoadVector128(src + i).AsByte(); Vector128 a1 = Sse2.LoadVector128(src + i + 16).AsByte(); @@ -144,7 +194,7 @@ internal static class WebpCommonUtils private static unsafe bool IsNoneOpaque32Bytes(byte* src, int i) { - var alphaMask = Vector128.Create(0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255); + Vector128 alphaMask = Vector128.Create(0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255); Vector128 a0 = Sse2.LoadVector128(src + i).AsByte(); Vector128 a1 = Sse2.LoadVector128(src + i + 16).AsByte(); diff --git a/src/ImageSharp/Formats/Webp/WebpDisposalMethod.cs b/src/ImageSharp/Formats/Webp/WebpDisposalMethod.cs index 397c2ee50..47cc83951 100644 --- a/src/ImageSharp/Formats/Webp/WebpDisposalMethod.cs +++ b/src/ImageSharp/Formats/Webp/WebpDisposalMethod.cs @@ -11,7 +11,7 @@ public enum WebpDisposalMethod /// /// Do not dispose. Leave the canvas as is. /// - None = 0, + DoNotDispose = 0, /// /// Dispose to background color. Fill the rectangle on the canvas covered by the current frame with background color specified in the ANIM chunk. diff --git a/src/ImageSharp/Formats/Webp/WebpEncoderCore.cs b/src/ImageSharp/Formats/Webp/WebpEncoderCore.cs index 47712071b..837487047 100644 --- a/src/ImageSharp/Formats/Webp/WebpEncoderCore.cs +++ b/src/ImageSharp/Formats/Webp/WebpEncoderCore.cs @@ -123,7 +123,7 @@ internal sealed class WebpEncoderCore : IImageEncoderInternals } else { - WebpMetadata webpMetadata = image.Metadata.GetWebpMetadata(); + WebpMetadata webpMetadata = WebpCommonUtils.GetWebpMetadata(image); lossless = webpMetadata.FileFormat == WebpFileFormatType.Lossless; } diff --git a/src/ImageSharp/Formats/Webp/WebpFrameMetadata.cs b/src/ImageSharp/Formats/Webp/WebpFrameMetadata.cs index ef21d8b6f..667b8f8f4 100644 --- a/src/ImageSharp/Formats/Webp/WebpFrameMetadata.cs +++ b/src/ImageSharp/Formats/Webp/WebpFrameMetadata.cs @@ -44,4 +44,12 @@ public class WebpFrameMetadata : IDeepCloneable /// public IDeepCloneable DeepClone() => new WebpFrameMetadata(this); + + internal static WebpFrameMetadata FromAnimatedMetadata(AnimatedImageFrameMetadata metadata) + => new() + { + FrameDelay = (uint)metadata.Duration.Milliseconds, + BlendMethod = metadata.BlendMode == FrameBlendMode.Source ? WebpBlendingMethod.Source : WebpBlendingMethod.Over, + DisposalMethod = metadata.DisposalMode == FrameDisposalMode.RestoreToBackground ? WebpDisposalMethod.RestoreToBackground : WebpDisposalMethod.DoNotDispose + }; } diff --git a/src/ImageSharp/Formats/Webp/WebpMetadata.cs b/src/ImageSharp/Formats/Webp/WebpMetadata.cs index 9d0d8d08d..536ea0929 100644 --- a/src/ImageSharp/Formats/Webp/WebpMetadata.cs +++ b/src/ImageSharp/Formats/Webp/WebpMetadata.cs @@ -46,4 +46,12 @@ public class WebpMetadata : IDeepCloneable /// public IDeepCloneable DeepClone() => new WebpMetadata(this); + + internal static WebpMetadata FromAnimatedMetadata(AnimatedImageMetadata metadata) + => new() + { + FileFormat = WebpFileFormatType.Lossless, + BackgroundColor = metadata.BackgroundColor, + RepeatCount = metadata.RepeatCount + }; } diff --git a/src/ImageSharp/Processing/Processors/Quantization/EuclideanPixelMap{TPixel}.cs b/src/ImageSharp/Processing/Processors/Quantization/EuclideanPixelMap{TPixel}.cs index f75664903..8aa166d16 100644 --- a/src/ImageSharp/Processing/Processors/Quantization/EuclideanPixelMap{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Quantization/EuclideanPixelMap{TPixel}.cs @@ -200,7 +200,7 @@ internal sealed class EuclideanPixelMap : IDisposable } [MethodImpl(InliningOptions.ShortMethod)] - public void Add(Rgba32 rgba, byte index) + public readonly void Add(Rgba32 rgba, byte index) { int r = rgba.R >> RgbShift; int g = rgba.G >> RgbShift; @@ -211,7 +211,7 @@ internal sealed class EuclideanPixelMap : IDisposable } [MethodImpl(InliningOptions.ShortMethod)] - public bool TryGetValue(Rgba32 rgba, out short match) + public readonly bool TryGetValue(Rgba32 rgba, out short match) { int r = rgba.R >> RgbShift; int g = rgba.G >> RgbShift; @@ -226,7 +226,7 @@ internal sealed class EuclideanPixelMap : IDisposable /// Clears the cache resetting each entry to empty. /// [MethodImpl(InliningOptions.ShortMethod)] - public void Clear() => this.table.GetSpan().Fill(-1); + public readonly void Clear() => this.table.GetSpan().Fill(-1); [MethodImpl(InliningOptions.ShortMethod)] private static int GetPaletteIndex(int r, int g, int b, int a) diff --git a/tests/ImageSharp.Tests/Formats/Gif/GifEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Gif/GifEncoderTests.cs index 65d186c91..cd485b5fa 100644 --- a/tests/ImageSharp.Tests/Formats/Gif/GifEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Gif/GifEncoderTests.cs @@ -2,6 +2,8 @@ // Licensed under the Six Labors Split License. using SixLabors.ImageSharp.Formats.Gif; +using SixLabors.ImageSharp.Formats.Png; +using SixLabors.ImageSharp.Formats.Webp; using SixLabors.ImageSharp.Metadata; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing.Processors.Quantization; @@ -245,7 +247,7 @@ public class GifEncoderTests int count = 0; foreach (ImageFrame frame in image.Frames) { - if (frame.Metadata.TryGetGifFrameMetadata(out GifFrameMetadata _)) + if (frame.Metadata.TryGetGifMetadata(out GifFrameMetadata _)) { count++; } @@ -261,7 +263,7 @@ public class GifEncoderTests count = 0; foreach (ImageFrame frame in image2.Frames) { - if (frame.Metadata.TryGetGifFrameMetadata(out GifFrameMetadata _)) + if (frame.Metadata.TryGetGifMetadata(out GifFrameMetadata _)) { count++; } @@ -269,4 +271,90 @@ public class GifEncoderTests Assert.Equal(image2.Frames.Count, count); } + + [Theory] + [WithFile(TestImages.Png.APng, PixelTypes.Rgba32)] + public void Encode_AnimatedFormatTransform_FromPng(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using Image image = provider.GetImage(PngDecoder.Instance); + + using MemoryStream memStream = new(); + image.Save(memStream, new GifEncoder()); + memStream.Position = 0; + + using Image output = Image.Load(memStream); + + // TODO: Find a better way to compare. + // The image has been visually checked but the quantization and frame trimming pattern used in the gif encoder + // means we cannot use an exact comparison nor replicate using the quantizing processor. + ImageComparer.TolerantPercentage(1.51f).VerifySimilarity(output, image); + + PngMetadata png = image.Metadata.GetPngMetadata(); + GifMetadata gif = output.Metadata.GetGifMetadata(); + + Assert.Equal(png.RepeatCount, gif.RepeatCount); + + for (int i = 0; i < image.Frames.Count; i++) + { + PngFrameMetadata pngF = image.Frames[i].Metadata.GetPngMetadata(); + GifFrameMetadata gifF = output.Frames[i].Metadata.GetGifMetadata(); + + Assert.Equal((int)(pngF.FrameDelay.ToDouble() * 100), gifF.FrameDelay); + + switch (pngF.DisposalMethod) + { + case PngDisposalMethod.RestoreToBackground: + Assert.Equal(GifDisposalMethod.RestoreToBackground, gifF.DisposalMethod); + break; + case PngDisposalMethod.DoNotDispose: + default: + Assert.Equal(GifDisposalMethod.NotDispose, gifF.DisposalMethod); + break; + } + } + } + + [Theory] + [WithFile(TestImages.Webp.Lossless.Animated, PixelTypes.Rgba32)] + public void Encode_AnimatedFormatTransform_FromWebp(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using Image image = provider.GetImage(WebpDecoder.Instance); + + using MemoryStream memStream = new(); + image.Save(memStream, new GifEncoder()); + memStream.Position = 0; + + using Image output = Image.Load(memStream); + + // TODO: Find a better way to compare. + // The image has been visually checked but the quantization and frame trimming pattern used in the gif encoder + // means we cannot use an exact comparison nor replicate using the quantizing processor. + ImageComparer.TolerantPercentage(0.776f).VerifySimilarity(output, image); + + WebpMetadata webp = image.Metadata.GetWebpMetadata(); + GifMetadata gif = output.Metadata.GetGifMetadata(); + + Assert.Equal(webp.RepeatCount, gif.RepeatCount); + + for (int i = 0; i < image.Frames.Count; i++) + { + WebpFrameMetadata webpF = image.Frames[i].Metadata.GetWebpMetadata(); + GifFrameMetadata gifF = output.Frames[i].Metadata.GetGifMetadata(); + + Assert.Equal(webpF.FrameDelay, (uint)(gifF.FrameDelay * 10)); + + switch (webpF.DisposalMethod) + { + case WebpDisposalMethod.RestoreToBackground: + Assert.Equal(GifDisposalMethod.RestoreToBackground, gifF.DisposalMethod); + break; + case WebpDisposalMethod.DoNotDispose: + default: + Assert.Equal(GifDisposalMethod.NotDispose, gifF.DisposalMethod); + break; + } + } + } } diff --git a/tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs index a6840b33e..45dd30b3b 100644 --- a/tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs @@ -5,6 +5,7 @@ using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Formats.Gif; using SixLabors.ImageSharp.Formats.Png; +using SixLabors.ImageSharp.Formats.Webp; using SixLabors.ImageSharp.Metadata; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing.Processors.Quantization; @@ -454,10 +455,6 @@ public partial class PngEncoderTests memStream.Position = 0; image.DebugSave(provider: provider, encoder: PngEncoder, null, false); - image.DebugSave(provider: provider, encoder: new GifEncoder(), "gif", false); - - string path = provider.Utility.GetTestOutputFileName("gif"); - image.Save(path); using Image output = Image.Load(memStream); ImageComparer.Exact.VerifySimilarity(output, image); @@ -472,8 +469,8 @@ public partial class PngEncoderTests for (int i = 0; i < image.Frames.Count; i++) { - PngFrameMetadata originalFrameMetadata = image.Frames[i].Metadata.GetPngFrameMetadata(); - PngFrameMetadata outputFrameMetadata = output.Frames[i].Metadata.GetPngFrameMetadata(); + PngFrameMetadata originalFrameMetadata = image.Frames[i].Metadata.GetPngMetadata(); + PngFrameMetadata outputFrameMetadata = output.Frames[i].Metadata.GetPngMetadata(); Assert.Equal(originalFrameMetadata.FrameDelay, outputFrameMetadata.FrameDelay); Assert.Equal(originalFrameMetadata.BlendMethod, outputFrameMetadata.BlendMethod); @@ -481,6 +478,106 @@ public partial class PngEncoderTests } } + [Theory] + [WithFile(TestImages.Gif.Giphy, PixelTypes.Rgba32)] + public void Encode_AnimatedFormatTransform_FromGif(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using Image image = provider.GetImage(GifDecoder.Instance); + using MemoryStream memStream = new(); + + image.Save(memStream, PngEncoder); + memStream.Position = 0; + + image.Save(provider.Utility.GetTestOutputFileName("png"), new PngEncoder()); + image.Save(provider.Utility.GetTestOutputFileName("gif"), new GifEncoder()); + + using Image output = Image.Load(memStream); + + // TODO: Find a better way to compare. + // The image has been visually checked but the quantization pattern used in the png encoder + // means we cannot use an exact comparison nor replicate using the quantizing processor. + ImageComparer.TolerantPercentage(0.12f).VerifySimilarity(output, image); + + GifMetadata gif = image.Metadata.GetGifMetadata(); + PngMetadata png = output.Metadata.GetPngMetadata(); + + Assert.Equal(gif.RepeatCount, png.RepeatCount); + + for (int i = 0; i < image.Frames.Count; i++) + { + GifFrameMetadata gifF = image.Frames[i].Metadata.GetGifMetadata(); + PngFrameMetadata pngF = output.Frames[i].Metadata.GetPngMetadata(); + + Assert.Equal(gifF.FrameDelay, (int)(pngF.FrameDelay.ToDouble() * 100)); + + switch (gifF.DisposalMethod) + { + case GifDisposalMethod.RestoreToBackground: + Assert.Equal(PngDisposalMethod.RestoreToBackground, pngF.DisposalMethod); + break; + case GifDisposalMethod.RestoreToPrevious: + Assert.Equal(PngDisposalMethod.RestoreToPrevious, pngF.DisposalMethod); + break; + case GifDisposalMethod.Unspecified: + case GifDisposalMethod.NotDispose: + default: + Assert.Equal(PngDisposalMethod.DoNotDispose, pngF.DisposalMethod); + break; + } + } + } + + [Theory] + [WithFile(TestImages.Webp.Lossless.Animated, PixelTypes.Rgba32)] + public void Encode_AnimatedFormatTransform_FromWebp(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using Image image = provider.GetImage(WebpDecoder.Instance); + + using MemoryStream memStream = new(); + image.Save(memStream, PngEncoder); + memStream.Position = 0; + + using Image output = Image.Load(memStream); + ImageComparer.Exact.VerifySimilarity(output, image); + + WebpMetadata webp = image.Metadata.GetWebpMetadata(); + PngMetadata png = output.Metadata.GetPngMetadata(); + + Assert.Equal(webp.RepeatCount, png.RepeatCount); + + for (int i = 0; i < image.Frames.Count; i++) + { + WebpFrameMetadata webpF = image.Frames[i].Metadata.GetWebpMetadata(); + PngFrameMetadata pngF = output.Frames[i].Metadata.GetPngMetadata(); + + Assert.Equal(webpF.FrameDelay, (uint)(pngF.FrameDelay.ToDouble() * 1000)); + + switch (webpF.BlendMethod) + { + case WebpBlendingMethod.Source: + Assert.Equal(PngBlendMethod.Source, pngF.BlendMethod); + break; + case WebpBlendingMethod.Over: + default: + Assert.Equal(PngBlendMethod.Over, pngF.BlendMethod); + break; + } + + switch (webpF.DisposalMethod) + { + case WebpDisposalMethod.RestoreToBackground: + Assert.Equal(PngDisposalMethod.RestoreToBackground, pngF.DisposalMethod); + break; + case WebpDisposalMethod.DoNotDispose: + default: + Assert.Equal(PngDisposalMethod.DoNotDispose, pngF.DisposalMethod); + break; + } + } + } + [Theory] [MemberData(nameof(PngTrnsFiles))] public void Encode_PreserveTrns(string imagePath, PngBitDepth pngBitDepth, PngColorType pngColorType) diff --git a/tests/ImageSharp.Tests/Formats/WebP/WebpEncoderTests.cs b/tests/ImageSharp.Tests/Formats/WebP/WebpEncoderTests.cs index 0ad684b27..0fafdbe16 100644 --- a/tests/ImageSharp.Tests/Formats/WebP/WebpEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/WebP/WebpEncoderTests.cs @@ -2,6 +2,8 @@ // Licensed under the Six Labors Split License. using System.Runtime.InteropServices; +using SixLabors.ImageSharp.Formats.Gif; +using SixLabors.ImageSharp.Formats.Png; using SixLabors.ImageSharp.Formats.Webp; using SixLabors.ImageSharp.Metadata; using SixLabors.ImageSharp.PixelFormats; @@ -60,6 +62,97 @@ public class WebpEncoderTests encoded.CompareToReferenceOutput(ImageComparer.Tolerant(0.01f), provider, null, "webp"); } + [Theory] + [WithFile(TestImages.Gif.Giphy, PixelTypes.Rgba32)] + public void Encode_AnimatedFormatTransform_FromGif(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using Image image = provider.GetImage(GifDecoder.Instance); + using MemoryStream memStream = new(); + + image.Save(memStream, new WebpEncoder()); + memStream.Position = 0; + + using Image output = Image.Load(memStream); + + ImageComparer.Exact.VerifySimilarity(output, image); + + GifMetadata gif = image.Metadata.GetGifMetadata(); + WebpMetadata webp = output.Metadata.GetWebpMetadata(); + + Assert.Equal(gif.RepeatCount, webp.RepeatCount); + + for (int i = 0; i < image.Frames.Count; i++) + { + GifFrameMetadata gifF = image.Frames[i].Metadata.GetGifMetadata(); + WebpFrameMetadata webpF = output.Frames[i].Metadata.GetWebpMetadata(); + + Assert.Equal(gifF.FrameDelay, (int)(webpF.FrameDelay / 10)); + + switch (gifF.DisposalMethod) + { + case GifDisposalMethod.RestoreToBackground: + Assert.Equal(WebpDisposalMethod.RestoreToBackground, webpF.DisposalMethod); + break; + case GifDisposalMethod.RestoreToPrevious: + case GifDisposalMethod.Unspecified: + case GifDisposalMethod.NotDispose: + default: + Assert.Equal(WebpDisposalMethod.DoNotDispose, webpF.DisposalMethod); + break; + } + } + } + + [Theory] + [WithFile(TestImages.Png.APng, PixelTypes.Rgba32)] + public void Encode_AnimatedFormatTransform_FromPng(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using Image image = provider.GetImage(PngDecoder.Instance); + + using MemoryStream memStream = new(); + image.Save(memStream, new WebpEncoder()); + memStream.Position = 0; + + using Image output = Image.Load(memStream); + ImageComparer.Exact.VerifySimilarity(output, image); + PngMetadata png = image.Metadata.GetPngMetadata(); + WebpMetadata webp = output.Metadata.GetWebpMetadata(); + + Assert.Equal(png.RepeatCount, webp.RepeatCount); + + for (int i = 0; i < image.Frames.Count; i++) + { + PngFrameMetadata pngF = image.Frames[i].Metadata.GetPngMetadata(); + WebpFrameMetadata webpF = output.Frames[i].Metadata.GetWebpMetadata(); + + Assert.Equal((uint)(pngF.FrameDelay.ToDouble() * 1000), webpF.FrameDelay); + + switch (pngF.BlendMethod) + { + case PngBlendMethod.Source: + Assert.Equal(WebpBlendingMethod.Source, webpF.BlendMethod); + break; + case PngBlendMethod.Over: + default: + Assert.Equal(WebpBlendingMethod.Over, webpF.BlendMethod); + break; + } + + switch (pngF.DisposalMethod) + { + case PngDisposalMethod.RestoreToBackground: + Assert.Equal(WebpDisposalMethod.RestoreToBackground, webpF.DisposalMethod); + break; + case PngDisposalMethod.DoNotDispose: + default: + Assert.Equal(WebpDisposalMethod.DoNotDispose, webpF.DisposalMethod); + break; + } + } + } + [Theory] [WithFile(Flag, PixelTypes.Rgba32, WebpFileFormatType.Lossy)] // If its not a webp input image, it should default to lossy. [WithFile(Lossless.NoTransform1, PixelTypes.Rgba32, WebpFileFormatType.Lossless)] From 7f4b4575210bb580826ea0fefad37c4b605a9590 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Fri, 17 Nov 2023 13:31:36 +1000 Subject: [PATCH 03/24] Default loop count should be 1 --- src/ImageSharp/Formats/Png/PngMetadata.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ImageSharp/Formats/Png/PngMetadata.cs b/src/ImageSharp/Formats/Png/PngMetadata.cs index 8e2691c10..7f4052846 100644 --- a/src/ImageSharp/Formats/Png/PngMetadata.cs +++ b/src/ImageSharp/Formats/Png/PngMetadata.cs @@ -81,7 +81,7 @@ public class PngMetadata : IDeepCloneable /// /// Gets or sets the number of times to loop this APNG. 0 indicates infinite looping. /// - public uint RepeatCount { get; set; } + public uint RepeatCount { get; set; } = 1; /// public IDeepCloneable DeepClone() => new PngMetadata(this); From 958c9c9b10a8ca0fbc7b39c1a470a820d8941305 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Tue, 21 Nov 2023 21:16:57 +1000 Subject: [PATCH 04/24] Deduper works --- src/ImageSharp/Formats/AnimationUtilities.cs | 157 ++++++ src/ImageSharp/Formats/Gif/GifEncoder.cs | 2 - src/ImageSharp/Formats/Gif/GifEncoderCore.cs | 479 ++++-------------- src/ImageSharp/Formats/Gif/LzwEncoder.cs | 12 +- .../Processors/Dithering/ErrorDither.cs | 14 +- .../Quantization/EuclideanPixelMap{TPixel}.cs | 79 +-- .../Quantization/QuantizerUtilities.cs | 4 +- .../Formats/Gif/GifEncoderTests.cs | 18 +- tests/ImageSharp.Tests/TestImages.cs | 20 +- tests/Images/Input/Gif/mixed-disposal.gif | 3 + 10 files changed, 363 insertions(+), 425 deletions(-) create mode 100644 src/ImageSharp/Formats/AnimationUtilities.cs create mode 100644 tests/Images/Input/Gif/mixed-disposal.gif diff --git a/src/ImageSharp/Formats/AnimationUtilities.cs b/src/ImageSharp/Formats/AnimationUtilities.cs new file mode 100644 index 000000000..1bca34eae --- /dev/null +++ b/src/ImageSharp/Formats/AnimationUtilities.cs @@ -0,0 +1,157 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Buffers; +using System.Numerics; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Runtime.Intrinsics; +using System.Runtime.Intrinsics.X86; +using SixLabors.ImageSharp.Advanced; +using SixLabors.ImageSharp.Memory; +using SixLabors.ImageSharp.PixelFormats; + +namespace SixLabors.ImageSharp.Formats; + +/// +/// Utility methods for animated formats. +/// +internal static class AnimationUtilities +{ + /// + /// Deduplicates pixels between the previous and current frame returning only the changed pixels and bounds. + /// + /// The type of pixel format. + /// The configuration. + /// The previous frame if present. + /// The current frame. + /// The resultant output. + /// The value to use when replacing duplicate pixels. + /// The representing the operation result. + public static (bool Difference, Rectangle Bounds) DeDuplicatePixels( + Configuration configuration, + ImageFrame? previousFrame, + ImageFrame currentFrame, + ImageFrame resultFrame, + Vector4 replacement) + where TPixel : unmanaged, IPixel + { + MemoryAllocator memoryAllocator = configuration.MemoryAllocator; + IMemoryOwner buffers = memoryAllocator.Allocate(currentFrame.Width * 3, AllocationOptions.Clean); + Span previous = buffers.GetSpan()[..currentFrame.Width]; + Span current = buffers.GetSpan().Slice(currentFrame.Width, currentFrame.Width); + Span result = buffers.GetSpan()[(currentFrame.Width * 2)..]; + + int top = int.MinValue; + int bottom = int.MaxValue; + int left = int.MaxValue; + int right = int.MinValue; + + bool hasDiff = false; + for (int y = 0; y < currentFrame.Height; y++) + { + if (previousFrame != null) + { + PixelOperations.Instance.ToVector4(configuration, previousFrame.DangerousGetPixelRowMemory(y).Span, previous, PixelConversionModifiers.Scale); + } + + PixelOperations.Instance.ToVector4(configuration, currentFrame.DangerousGetPixelRowMemory(y).Span, current, PixelConversionModifiers.Scale); + + ref Vector256 previousBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(previous)); + ref Vector256 currentBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(current)); + ref Vector256 resultBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(result)); + + Vector256 replacement256 = Vector256.Create(replacement.X, replacement.Y, replacement.Z, replacement.W, replacement.X, replacement.Y, replacement.Z, replacement.W); + + int size = Unsafe.SizeOf(); + + bool hasRowDiff = false; + int i = 0; + uint x = 0; + int length = current.Length; + int remaining = current.Length; + if (Avx2.IsSupported && remaining >= 2) + { + while (remaining >= 2) + { + Vector256 p = Unsafe.Add(ref previousBase, x); + Vector256 c = Unsafe.Add(ref currentBase, x); + + // Compare the previous and current pixels + Vector256 neq = Avx.CompareEqual(p, c); + Vector256 mask = neq.AsInt32(); + + neq = Avx.Xor(neq, Vector256.AllBitsSet); + int m = Avx2.MoveMask(neq.AsByte()); + if (m != 0) + { + // If is diff is found, the left side is marked by the min of previously found left side and the diff position. + // The right is the max of the previously found right side and the diff position + 1. + int diff = (int)(i + (uint)(BitOperations.TrailingZeroCount(m) / size)); + left = Math.Min(left, diff); + right = Math.Max(right, diff + 1); + hasRowDiff = true; + hasDiff = true; + } + + // Capture the original alpha values. + mask = Avx2.HorizontalAdd(mask, mask); + mask = Avx2.HorizontalAdd(mask, mask); + mask = Avx2.CompareEqual(mask, Vector256.Create(-4)); + + Vector256 r = Avx.BlendVariable(c, replacement256, mask.AsSingle()); + Unsafe.Add(ref resultBase, x) = r; + + x++; + i += 2; + remaining -= 2; + } + } + + for (i = remaining; i > 0; i--) + { + x = (uint)(length - i); + + Vector4 p = Unsafe.Add(ref Unsafe.As, Vector4>(ref previousBase), x); + Vector4 c = Unsafe.Add(ref Unsafe.As, Vector4>(ref currentBase), x); + ref Vector4 r = ref Unsafe.Add(ref Unsafe.As, Vector4>(ref resultBase), x); + + if (p != c) + { + r = c; + + // If is diff is found, the left side is marked by the min of previously found left side and the diff position. + // The right is the max of the previously found right side and the diff position + 1. + left = Math.Min(left, (int)x); + right = Math.Max(right, (int)x + 1); + hasRowDiff = true; + hasDiff = true; + } + else + { + r = replacement; + } + } + + if (hasRowDiff) + { + if (top == int.MinValue) + { + top = y; + } + + bottom = y + 1; + } + + PixelOperations.Instance.FromVector4Destructive(configuration, result, resultFrame.DangerousGetPixelRowMemory(y).Span, PixelConversionModifiers.Scale); + } + + Rectangle bounds = Rectangle.FromLTRB( + left = Numerics.Clamp(left, 0, resultFrame.Width - 1), + top = Numerics.Clamp(top, 0, resultFrame.Height - 1), + Numerics.Clamp(right, left + 1, resultFrame.Width), + Numerics.Clamp(bottom, top + 1, resultFrame.Height)); + + return new(hasDiff, bounds); + } +} diff --git a/src/ImageSharp/Formats/Gif/GifEncoder.cs b/src/ImageSharp/Formats/Gif/GifEncoder.cs index 150ee9ccf..ab05548ac 100644 --- a/src/ImageSharp/Formats/Gif/GifEncoder.cs +++ b/src/ImageSharp/Formats/Gif/GifEncoder.cs @@ -1,8 +1,6 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using SixLabors.ImageSharp.Advanced; - namespace SixLabors.ImageSharp.Formats.Gif; /// diff --git a/src/ImageSharp/Formats/Gif/GifEncoderCore.cs b/src/ImageSharp/Formats/Gif/GifEncoderCore.cs index d22b960ec..2bc3e53f7 100644 --- a/src/ImageSharp/Formats/Gif/GifEncoderCore.cs +++ b/src/ImageSharp/Formats/Gif/GifEncoderCore.cs @@ -4,9 +4,6 @@ using System.Buffers; using System.Numerics; using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -using System.Runtime.Intrinsics; -using System.Runtime.Intrinsics.X86; using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Formats.Png; using SixLabors.ImageSharp.Formats.Webp; @@ -97,8 +94,7 @@ internal sealed class GifEncoderCore : IImageEncoderInternals // Work out if there is an explicit transparent index set for the frame. We use that to ensure the // correct value is set for the background index when quantizing. - GifFrameMetadata? frameMetadata = GetGifFrameMetadata(image.Frames.RootFrame, -1); - int transparencyIndex = GetTransparentIndex(quantized, frameMetadata); + GifFrameMetadata frameMetadata = GetGifFrameMetadata(image.Frames.RootFrame, -1); if (this.quantizer is null) { @@ -106,6 +102,7 @@ internal sealed class GifEncoderCore : IImageEncoderInternals if (gifMetadata.ColorTableMode == GifColorTableMode.Global && gifMetadata.GlobalColorTable?.Length > 0) { // We avoid dithering by default to preserve the original colors. + int transparencyIndex = GetTransparentIndex(quantized, frameMetadata); this.quantizer = new PaletteQuantizer(gifMetadata.GlobalColorTable.Value, new() { Dither = null }, transparencyIndex); } else @@ -132,13 +129,17 @@ internal sealed class GifEncoderCore : IImageEncoderInternals WriteHeader(stream); // Write the LSD. - transparencyIndex = GetTransparentIndex(quantized, frameMetadata); - byte backgroundIndex = unchecked((byte)transparencyIndex); - if (transparencyIndex == -1) + int derivedTransparencyIndex = GetTransparentIndex(quantized, null); + if (derivedTransparencyIndex >= 0) { - backgroundIndex = gifMetadata.BackgroundColorIndex; + frameMetadata.HasTransparency = true; + frameMetadata.TransparencyIndex = ClampIndex(derivedTransparencyIndex); } + byte backgroundIndex = derivedTransparencyIndex >= 0 + ? frameMetadata.TransparencyIndex + : gifMetadata.BackgroundColorIndex; + // Get the number of bits. int bitDepth = ColorNumerics.GetBitsNeededForColorDepth(quantized.Palette.Length); this.WriteLogicalScreenDescriptor(image.Metadata, image.Width, image.Height, backgroundIndex, useGlobalTable, bitDepth, stream); @@ -158,16 +159,16 @@ internal sealed class GifEncoderCore : IImageEncoderInternals this.WriteApplicationExtensions(stream, image.Frames.Count, gifMetadata.RepeatCount, xmpProfile); } - this.EncodeFirstFrame(stream, frameMetadata, quantized, transparencyIndex); + this.EncodeFirstFrame(stream, frameMetadata, quantized); // Capture the global palette for reuse on subsequent frames and cleanup the quantized frame. TPixel[] globalPalette = image.Frames.Count == 1 ? Array.Empty() : quantized.Palette.ToArray(); - quantized.Dispose(); - - this.EncodeAdditionalFrames(stream, image, globalPalette, transparencyIndex); + this.EncodeAdditionalFrames(stream, image, globalPalette, derivedTransparencyIndex, frameMetadata.DisposalMethod); stream.WriteByte(GifConstants.EndIntroducer); + + quantized.Dispose(); } private static GifMetadata GetGifMetadata(Image image) @@ -194,12 +195,12 @@ internal sealed class GifEncoderCore : IImageEncoderInternals return new(); } - private static GifFrameMetadata? GetGifFrameMetadata(ImageFrame frame, int transparencyIndex) + private static GifFrameMetadata GetGifFrameMetadata(ImageFrame frame, int transparencyIndex) where TPixel : unmanaged, IPixel { if (frame.Metadata.TryGetGifMetadata(out GifFrameMetadata? gif)) { - return gif; + return (GifFrameMetadata)gif.DeepClone(); } GifFrameMetadata? metadata = null; @@ -218,17 +219,18 @@ internal sealed class GifEncoderCore : IImageEncoderInternals if (metadata?.ColorTableMode == GifColorTableMode.Global && transparencyIndex > -1) { metadata.HasTransparency = true; - metadata.TransparencyIndex = unchecked((byte)transparencyIndex); + metadata.TransparencyIndex = ClampIndex(transparencyIndex); } - return metadata; + return metadata ?? new(); } private void EncodeAdditionalFrames( Stream stream, Image image, ReadOnlyMemory globalPalette, - int globalTransparencyIndex) + int globalTransparencyIndex, + GifDisposalMethod previousDisposalMethod) where TPixel : unmanaged, IPixel { if (image.Frames.Count == 1) @@ -251,15 +253,15 @@ internal sealed class GifEncoderCore : IImageEncoderInternals { // Gather the metadata for this frame. ImageFrame currentFrame = image.Frames[i]; - GifFrameMetadata? gifMetadata = GetGifFrameMetadata(currentFrame, globalTransparencyIndex); - bool useLocal = this.colorTableMode == GifColorTableMode.Local || (gifMetadata?.ColorTableMode == GifColorTableMode.Local); + GifFrameMetadata gifMetadata = GetGifFrameMetadata(currentFrame, globalTransparencyIndex); + bool useLocal = this.colorTableMode == GifColorTableMode.Local || (gifMetadata.ColorTableMode == GifColorTableMode.Local); if (!useLocal && !hasPaletteQuantizer && i > 0) { // The palette quantizer can reuse the same global pixel map across multiple frames since the palette is unchanging. // This allows a reduction of memory usage across multi-frame gifs using a global palette // and also allows use to reuse the cache from previous runs. - int transparencyIndex = gifMetadata?.HasTransparency == true ? gifMetadata.TransparencyIndex : -1; + int transparencyIndex = gifMetadata.HasTransparency ? gifMetadata.TransparencyIndex : -1; paletteQuantizer = new(this.configuration, this.quantizer!.Options, globalPalette, transparencyIndex); hasPaletteQuantizer = true; } @@ -271,9 +273,11 @@ internal sealed class GifEncoderCore : IImageEncoderInternals encodingFrame, useLocal, gifMetadata, - paletteQuantizer); + paletteQuantizer, + previousDisposalMethod); previousFrame = currentFrame; + previousDisposalMethod = gifMetadata.DisposalMethod; } if (hasPaletteQuantizer) @@ -284,16 +288,15 @@ internal sealed class GifEncoderCore : IImageEncoderInternals private void EncodeFirstFrame( Stream stream, - GifFrameMetadata? metadata, - IndexedImageFrame quantized, - int transparencyIndex) + GifFrameMetadata metadata, + IndexedImageFrame quantized) where TPixel : unmanaged, IPixel { - this.WriteGraphicalControlExtension(metadata, transparencyIndex, stream); + this.WriteGraphicalControlExtension(metadata, stream); Buffer2D indices = ((IPixelSource)quantized).PixelBuffer; Rectangle interest = indices.FullRectangle(); - bool useLocal = this.colorTableMode == GifColorTableMode.Local || (metadata?.ColorTableMode == GifColorTableMode.Local); + bool useLocal = this.colorTableMode == GifColorTableMode.Local || (metadata.ColorTableMode == GifColorTableMode.Local); int bitDepth = ColorNumerics.GetBitsNeededForColorDepth(quantized.Palette.Length); this.WriteImageDescriptor(interest, useLocal, bitDepth, stream); @@ -303,7 +306,7 @@ internal sealed class GifEncoderCore : IImageEncoderInternals this.WriteColorTable(quantized, bitDepth, stream); } - this.WriteImageData(indices, interest, stream, quantized.Palette.Length, transparencyIndex); + this.WriteImageData(indices, stream, quantized.Palette.Length, metadata.TransparencyIndex); } private void EncodeAdditionalFrame( @@ -312,371 +315,121 @@ internal sealed class GifEncoderCore : IImageEncoderInternals ImageFrame currentFrame, ImageFrame encodingFrame, bool useLocal, - GifFrameMetadata? metadata, - PaletteQuantizer globalPaletteQuantizer) + GifFrameMetadata metadata, + PaletteQuantizer globalPaletteQuantizer, + GifDisposalMethod previousDisposal) where TPixel : unmanaged, IPixel { // Capture any explicit transparency index from the metadata. // We use it to determine the value to use to replace duplicate pixels. - int transparencyIndex = metadata?.HasTransparency == true ? metadata.TransparencyIndex : -1; - Vector4 replacement = Vector4.Zero; - if (transparencyIndex >= 0) - { - if (useLocal) - { - if (metadata?.LocalColorTable?.Length > 0) - { - ReadOnlySpan palette = metadata.LocalColorTable.Value.Span; - if (transparencyIndex < palette.Length) - { - replacement = palette[transparencyIndex].ToScaledVector4(); - } - } - } - else - { - ReadOnlySpan palette = globalPaletteQuantizer.Palette.Span; - if (transparencyIndex < palette.Length) - { - replacement = palette[transparencyIndex].ToScaledVector4(); - } - } - } + int transparencyIndex = metadata.HasTransparency ? metadata.TransparencyIndex : -1; - // We can't deduplicate here as we need the background pixels to be present in the buffer. - if (metadata?.DisposalMethod == GifDisposalMethod.RestoreToBackground) - { - for (int y = 0; y < currentFrame.PixelBuffer.Height; y++) - { - Span sourceRow = currentFrame.PixelBuffer.DangerousGetRowSpan(y); - Span destinationRow = encodingFrame.PixelBuffer.DangerousGetRowSpan(y); - sourceRow.CopyTo(destinationRow); - } - } - else - { - this.DeDuplicatePixels(previousFrame, currentFrame, encodingFrame, replacement); - } + ImageFrame? previous = previousDisposal == GifDisposalMethod.RestoreToBackground ? null : previousFrame; - IndexedImageFrame quantized; - if (useLocal) - { - // Reassign using the current frame and details. - if (metadata?.LocalColorTable?.Length > 0) - { - // We can use the color data from the decoded metadata here. - // We avoid dithering by default to preserve the original colors. - ReadOnlyMemory palette = metadata.LocalColorTable.Value; - PaletteQuantizer quantizer = new(palette, new() { Dither = null }, transparencyIndex); - using IQuantizer frameQuantizer = quantizer.CreatePixelSpecificQuantizer(this.configuration, quantizer.Options); - quantized = frameQuantizer.BuildPaletteAndQuantizeFrame(encodingFrame, encodingFrame.Bounds()); - } - else - { - // We must quantize the frame to generate a local color table. - IQuantizer quantizer = this.hasQuantizer ? this.quantizer! : KnownQuantizers.Octree; - using IQuantizer frameQuantizer = quantizer.CreatePixelSpecificQuantizer(this.configuration, quantizer.Options); - quantized = frameQuantizer.BuildPaletteAndQuantizeFrame(encodingFrame, encodingFrame.Bounds()); - } - } - else - { - // Quantize the image using the global palette. - // Individual frames, though using the shared palette, can use a different transparent index to represent transparency. - globalPaletteQuantizer.SetTransparentIndex(transparencyIndex); - quantized = globalPaletteQuantizer.QuantizeFrame(encodingFrame, encodingFrame.Bounds()); - } + // Deduplicate and quantize the frame capturing only required parts. + (bool difference, Rectangle bounds) = AnimationUtilities.DeDuplicatePixels(this.configuration, previous, currentFrame, encodingFrame, Vector4.Zero); - // Recalculate the transparency index as depending on the quantizer used could have a new value. - transparencyIndex = GetTransparentIndex(quantized, metadata); - - // Trim down the buffer to the minimum size required. - Buffer2D indices = ((IPixelSource)quantized).PixelBuffer; - Rectangle interest = TrimTransparentPixels(indices, transparencyIndex); + using IndexedImageFrame quantized = this.QuantizeAdditionalFrameAndUpdateMetadata( + encodingFrame, + bounds, + metadata, + useLocal, + globalPaletteQuantizer, + difference, + transparencyIndex); - this.WriteGraphicalControlExtension(metadata, transparencyIndex, stream); + this.WriteGraphicalControlExtension(metadata, stream); int bitDepth = ColorNumerics.GetBitsNeededForColorDepth(quantized.Palette.Length); - this.WriteImageDescriptor(interest, useLocal, bitDepth, stream); + this.WriteImageDescriptor(bounds, useLocal, bitDepth, stream); if (useLocal) { this.WriteColorTable(quantized, bitDepth, stream); } - this.WriteImageData(indices, interest, stream, quantized.Palette.Length, transparencyIndex); + Buffer2D indices = ((IPixelSource)quantized).PixelBuffer; + this.WriteImageData(indices, stream, quantized.Palette.Length, metadata.TransparencyIndex); } - private void DeDuplicatePixels( - ImageFrame backgroundFrame, - ImageFrame sourceFrame, - ImageFrame resultFrame, - Vector4 replacement) + private IndexedImageFrame QuantizeAdditionalFrameAndUpdateMetadata( + ImageFrame encodingFrame, + Rectangle bounds, + GifFrameMetadata metadata, + bool useLocal, + PaletteQuantizer globalPaletteQuantizer, + bool hasDuplicates, + int transparencyIndex) where TPixel : unmanaged, IPixel { - IMemoryOwner buffers = this.memoryAllocator.Allocate(backgroundFrame.Width * 3); - Span background = buffers.GetSpan()[..backgroundFrame.Width]; - Span source = buffers.GetSpan()[backgroundFrame.Width..]; - Span result = buffers.GetSpan()[(backgroundFrame.Width * 2)..]; - - // TODO: This algorithm is greedy and will always replace matching colors, however, theoretically, if the proceeding color - // is the same, but not replaced, you would actually be better of not replacing it since longer runs compress better. - // This would require a more complex algorithm. - for (int y = 0; y < backgroundFrame.Height; y++) - { - PixelOperations.Instance.ToVector4(this.configuration, backgroundFrame.DangerousGetPixelRowMemory(y).Span, background, PixelConversionModifiers.Scale); - PixelOperations.Instance.ToVector4(this.configuration, sourceFrame.DangerousGetPixelRowMemory(y).Span, source, PixelConversionModifiers.Scale); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - ref Vector256 resultBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(result)); - - uint x = 0; - int remaining = background.Length; - if (Avx2.IsSupported && remaining >= 2) - { - Vector256 replacement256 = Vector256.Create(replacement.X, replacement.Y, replacement.Z, replacement.W, replacement.X, replacement.Y, replacement.Z, replacement.W); - - while (remaining >= 2) - { - Vector256 b = Unsafe.Add(ref backgroundBase, x); - Vector256 s = Unsafe.Add(ref sourceBase, x); - - Vector256 m = Avx.CompareEqual(b, s).AsInt32(); - - m = Avx2.HorizontalAdd(m, m); - m = Avx2.HorizontalAdd(m, m); - m = Avx2.CompareEqual(m, Vector256.Create(-4)); - - Unsafe.Add(ref resultBase, x) = Avx.BlendVariable(s, replacement256, m.AsSingle()); - - x++; - remaining -= 2; - } - } - - for (int i = remaining; i >= 0; i--) - { - x = (uint)i; - Vector4 b = Unsafe.Add(ref Unsafe.As, Vector4>(ref backgroundBase), x); - Vector4 s = Unsafe.Add(ref Unsafe.As, Vector4>(ref sourceBase), x); - ref Vector4 r = ref Unsafe.Add(ref Unsafe.As, Vector4>(ref resultBase), x); - r = (b == s) ? replacement : s; - } - - PixelOperations.Instance.FromVector4Destructive(this.configuration, result, resultFrame.DangerousGetPixelRowMemory(y).Span, PixelConversionModifiers.Scale); - } - } - - private static Rectangle TrimTransparentPixels(Buffer2D buffer, int transparencyIndex) - { - if (transparencyIndex < 0) - { - return buffer.FullRectangle(); - } - - byte trimmableIndex = unchecked((byte)transparencyIndex); - - int top = int.MinValue; - int bottom = int.MaxValue; - int left = int.MaxValue; - int right = int.MinValue; - int minY = -1; - bool isTransparentRow = true; - - // Run through the buffer in a single pass. Use variables to track the min/max values. - for (int y = 0; y < buffer.Height; y++) + IndexedImageFrame quantized; + if (useLocal) { - isTransparentRow = true; - Span rowSpan = buffer.DangerousGetRowSpan(y); - ref byte rowPtr = ref MemoryMarshal.GetReference(rowSpan); - nint rowLength = (nint)(uint)rowSpan.Length; - nint x = 0; - -#if NET7_0_OR_GREATER - if (Vector128.IsHardwareAccelerated && rowLength >= Vector128.Count) - { - Vector256 trimmableVec256 = Vector256.Create(trimmableIndex); - - if (Vector256.IsHardwareAccelerated && rowLength >= Vector256.Count) - { - do - { - Vector256 vec = Vector256.LoadUnsafe(ref rowPtr, (nuint)x); - Vector256 notEquals = ~Vector256.Equals(vec, trimmableVec256); - uint mask = notEquals.ExtractMostSignificantBits(); - - if (mask != 0) - { - isTransparentRow = false; - nint start = x + (nint)uint.TrailingZeroCount(mask); - nint end = (nint)uint.LeadingZeroCount(mask); - - // end is from the end, but we need the index from the beginning - end = x + Vector256.Count - 1 - end; - - left = Math.Min(left, (int)start); - right = Math.Max(right, (int)end); - } - - x += Vector256.Count; - } - while (x <= rowLength - Vector256.Count); - } - - Vector128 trimmableVec = Vector256.IsHardwareAccelerated - ? trimmableVec256.GetLower() - : Vector128.Create(trimmableIndex); - - while (x <= rowLength - Vector128.Count) - { - Vector128 vec = Vector128.LoadUnsafe(ref rowPtr, (nuint)x); - Vector128 notEquals = ~Vector128.Equals(vec, trimmableVec); - uint mask = notEquals.ExtractMostSignificantBits(); - - if (mask != 0) - { - isTransparentRow = false; - nint start = x + (nint)uint.TrailingZeroCount(mask); - nint end = (nint)uint.LeadingZeroCount(mask) - Vector128.Count; - - // end is from the end, but we need the index from the beginning - end = x + Vector128.Count - 1 - end; - - left = Math.Min(left, (int)start); - right = Math.Max(right, (int)end); - } - - x += Vector128.Count; - } - } -#else - if (Sse41.IsSupported && rowLength >= Vector128.Count) + // Reassign using the current frame and details. + if (metadata.LocalColorTable?.Length > 0) { - Vector256 trimmableVec256 = Vector256.Create(trimmableIndex); + // We can use the color data from the decoded metadata here. + // We avoid dithering by default to preserve the original colors. + ReadOnlyMemory palette = metadata.LocalColorTable.Value; - if (Avx2.IsSupported && rowLength >= Vector256.Count) + if (hasDuplicates && !metadata.HasTransparency) { - do - { - Vector256 vec = Unsafe.ReadUnaligned>(ref Unsafe.Add(ref rowPtr, x)); - Vector256 notEquals = Avx2.CompareEqual(vec, trimmableVec256); - notEquals = Avx2.Xor(notEquals, Vector256.AllBitsSet); - int mask = Avx2.MoveMask(notEquals); - - if (mask != 0) - { - isTransparentRow = false; - nint start = x + (nint)(uint)BitOperations.TrailingZeroCount(mask); - nint end = (nint)(uint)BitOperations.LeadingZeroCount((uint)mask); - - // end is from the end, but we need the index from the beginning - end = x + Vector256.Count - 1 - end; - - left = Math.Min(left, (int)start); - right = Math.Max(right, (int)end); - } - - x += Vector256.Count; - } - while (x <= rowLength - Vector256.Count); + // A difference was captured but the metadata does not have transparency. + metadata.HasTransparency = true; + transparencyIndex = palette.Length; + metadata.TransparencyIndex = ClampIndex(transparencyIndex); } - Vector128 trimmableVec = Sse41.IsSupported - ? trimmableVec256.GetLower() - : Vector128.Create(trimmableIndex); - - while (x <= rowLength - Vector128.Count) - { - Vector128 vec = Unsafe.ReadUnaligned>(ref Unsafe.Add(ref rowPtr, x)); - Vector128 notEquals = Sse2.CompareEqual(vec, trimmableVec); - notEquals = Sse2.Xor(notEquals, Vector128.AllBitsSet); - int mask = Sse2.MoveMask(notEquals); - - if (mask != 0) - { - isTransparentRow = false; - nint start = x + (nint)(uint)BitOperations.TrailingZeroCount(mask); - nint end = (nint)(uint)BitOperations.LeadingZeroCount((uint)mask) - Vector128.Count; - - // end is from the end, but we need the index from the beginning - end = x + Vector128.Count - 1 - end; - - left = Math.Min(left, (int)start); - right = Math.Max(right, (int)end); - } - - x += Vector128.Count; - } + PaletteQuantizer quantizer = new(palette, new() { Dither = null }, transparencyIndex); + using IQuantizer frameQuantizer = quantizer.CreatePixelSpecificQuantizer(this.configuration, quantizer.Options); + quantized = frameQuantizer.BuildPaletteAndQuantizeFrame(encodingFrame, bounds); } -#endif - for (; x < rowLength; ++x) + else { - if (Unsafe.Add(ref rowPtr, x) != trimmableIndex) - { - isTransparentRow = false; - left = Math.Min(left, (int)x); - right = Math.Max(right, (int)x); - } - } + // We must quantize the frame to generate a local color table. + IQuantizer quantizer = this.hasQuantizer ? this.quantizer! : KnownQuantizers.Octree; + using IQuantizer frameQuantizer = quantizer.CreatePixelSpecificQuantizer(this.configuration, quantizer.Options); + quantized = frameQuantizer.BuildPaletteAndQuantizeFrame(encodingFrame, bounds); - if (!isTransparentRow) - { - if (y == 0) + // The transparency index derived by the quantizer might differ from the index + // within the metadata. We need to update the metadata to reflect this. + int derivedTransparencyIndex = GetTransparentIndex(quantized, null); + if (derivedTransparencyIndex < 0) { - // First row is opaque. - // Capture to prevent over assignment when a match is found below. - top = 0; + // If no index is found set to the palette length, this trick allows us to fake transparency without an explicit index. + derivedTransparencyIndex = quantized.Palette.Length; } - // The minimum top bounds have already been captured. - // Increment the bottom to include the current opaque row. - if (minY < 0 && top != 0) - { - // Increment to the first opaque row. - top++; - } + metadata.TransparencyIndex = ClampIndex(derivedTransparencyIndex); - minY = top; - bottom = y; - } - else - { - // We've yet to hit an opaque row. Capture the top position. - if (minY < 0) + if (hasDuplicates) { - top = Math.Max(top, y); + metadata.HasTransparency = true; } - - bottom = Math.Min(bottom, y); } } - - if (left == int.MaxValue) - { - left = 0; - } - - if (right == int.MinValue) + else { - right = buffer.Width; - } + // Quantize the image using the global palette. + // Individual frames, though using the shared palette, can use a different transparent index to represent transparency. - if (top == bottom || left == right) - { - // The entire image is transparent. - return buffer.FullRectangle(); - } + // A difference was captured but the metadata does not have transparency. + if (hasDuplicates && !metadata.HasTransparency) + { + metadata.HasTransparency = true; + transparencyIndex = globalPaletteQuantizer.Palette.Length; + metadata.TransparencyIndex = ClampIndex(transparencyIndex); + } - if (!isTransparentRow) - { - // Last row is opaque. - bottom = buffer.Height; + globalPaletteQuantizer.SetTransparentIndex(transparencyIndex); + quantized = globalPaletteQuantizer.QuantizeFrame(encodingFrame, bounds); } - return Rectangle.FromLTRB(left, top, Math.Min(right + 1, buffer.Width), Math.Min(bottom + 1, buffer.Height)); + return quantized; } + 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. /// @@ -868,30 +621,19 @@ internal sealed class GifEncoderCore : IImageEncoderInternals /// Writes the optional graphics control extension to the stream. /// /// The metadata of the image or frame. - /// The index of the color in the color palette to make transparent. /// The stream to write to. - private void WriteGraphicalControlExtension(GifFrameMetadata? metadata, int transparencyIndex, Stream stream) + private void WriteGraphicalControlExtension(GifFrameMetadata metadata, Stream stream) { - GifFrameMetadata? data = metadata; - bool hasTransparency; - if (metadata is null) - { - data = new(); - hasTransparency = transparencyIndex >= 0; - } - else - { - hasTransparency = metadata.HasTransparency; - } + bool hasTransparency = metadata.HasTransparency; byte packedValue = GifGraphicControlExtension.GetPackedValue( - disposalMethod: data!.DisposalMethod, + disposalMethod: metadata.DisposalMethod, transparencyFlag: hasTransparency); GifGraphicControlExtension extension = new( packed: packedValue, - delayTime: (ushort)data.FrameDelay, - transparencyIndex: hasTransparency ? unchecked((byte)transparencyIndex) : byte.MinValue); + delayTime: (ushort)metadata.FrameDelay, + transparencyIndex: hasTransparency ? metadata.TransparencyIndex : byte.MinValue); this.WriteExtension(extension, stream); } @@ -992,14 +734,11 @@ internal sealed class GifEncoderCore : IImageEncoderInternals /// Writes the image pixel data to the stream. /// /// The containing indexed pixels. - /// The region of interest. /// The stream to write to. /// The length of the frame color palette. /// The index of the color used to represent transparency. - private void WriteImageData(Buffer2D indices, Rectangle interest, Stream stream, int paletteLength, int transparencyIndex) + private void WriteImageData(Buffer2D indices, Stream stream, int paletteLength, int transparencyIndex) { - Buffer2DRegion region = indices.GetRegion(interest); - // Pad the bit depth when required for encoding the image data. // This is a common trick which allows to use out of range indexes for transparency and avoid allocating a larger color palette // as decoders skip indexes that are out of range. @@ -1008,6 +747,6 @@ internal sealed class GifEncoderCore : IImageEncoderInternals : 0; using LzwEncoder encoder = new(this.memoryAllocator, ColorNumerics.GetBitsNeededForColorDepth(paletteLength + padding)); - encoder.Encode(region, stream); + encoder.Encode(indices, stream); } } diff --git a/src/ImageSharp/Formats/Gif/LzwEncoder.cs b/src/ImageSharp/Formats/Gif/LzwEncoder.cs index 4b40c44e4..d4050810d 100644 --- a/src/ImageSharp/Formats/Gif/LzwEncoder.cs +++ b/src/ImageSharp/Formats/Gif/LzwEncoder.cs @@ -186,7 +186,7 @@ internal sealed class LzwEncoder : IDisposable /// /// The 2D buffer of indexed pixels. /// The stream to write to. - public void Encode(Buffer2DRegion indexedPixels, Stream stream) + public void Encode(Buffer2D indexedPixels, Stream stream) { // Write "initial code size" byte stream.WriteByte((byte)this.initialCodeSize); @@ -204,7 +204,7 @@ internal sealed class LzwEncoder : IDisposable /// The number of bits /// See [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static int GetMaxcode(int bitCount) => (1 << bitCount) - 1; + private static int GetMaxCode(int bitCount) => (1 << bitCount) - 1; /// /// Add a character to the end of the current packet, and if it is 254 characters, @@ -249,7 +249,7 @@ internal sealed class LzwEncoder : IDisposable /// The 2D buffer of indexed pixels. /// The initial bits. /// The stream to write to. - private void Compress(Buffer2DRegion indexedPixels, int initialBits, Stream stream) + private void Compress(Buffer2D indexedPixels, int initialBits, Stream stream) { // Set up the globals: globalInitialBits - initial number of bits this.globalInitialBits = initialBits; @@ -257,7 +257,7 @@ internal sealed class LzwEncoder : IDisposable // Set up the necessary values this.clearFlag = false; this.bitCount = this.globalInitialBits; - this.maxCode = GetMaxcode(this.bitCount); + this.maxCode = GetMaxCode(this.bitCount); this.clearCode = 1 << (initialBits - 1); this.eofCode = this.clearCode + 1; this.freeEntry = this.clearCode + 2; @@ -383,7 +383,7 @@ internal sealed class LzwEncoder : IDisposable { if (this.clearFlag) { - this.maxCode = GetMaxcode(this.bitCount = this.globalInitialBits); + this.maxCode = GetMaxCode(this.bitCount = this.globalInitialBits); this.clearFlag = false; } else @@ -391,7 +391,7 @@ internal sealed class LzwEncoder : IDisposable ++this.bitCount; this.maxCode = this.bitCount == MaxBits ? MaxMaxCode - : GetMaxcode(this.bitCount); + : GetMaxCode(this.bitCount); } } diff --git a/src/ImageSharp/Processing/Processors/Dithering/ErrorDither.cs b/src/ImageSharp/Processing/Processors/Dithering/ErrorDither.cs index 754aac90e..3c56d0243 100644 --- a/src/ImageSharp/Processing/Processors/Dithering/ErrorDither.cs +++ b/src/ImageSharp/Processing/Processors/Dithering/ErrorDither.cs @@ -107,15 +107,15 @@ public readonly partial struct ErrorDither : IDither, IEquatable, I float scale = quantizer.Options.DitherScale; Buffer2D sourceBuffer = source.PixelBuffer; - for (int y = bounds.Top; y < bounds.Bottom; y++) + for (int y = 0; y < destination.Height; y++) { - ref TPixel sourceRowRef = ref MemoryMarshal.GetReference(sourceBuffer.DangerousGetRowSpan(y)); - ref byte destinationRowRef = ref MemoryMarshal.GetReference(destination.GetWritablePixelRowSpanUnsafe(y - offsetY)); + ReadOnlySpan sourceRow = sourceBuffer.DangerousGetRowSpan(y + offsetY); + Span destinationRow = destination.GetWritablePixelRowSpanUnsafe(y); - for (int x = bounds.Left; x < bounds.Right; x++) + for (int x = 0; x < destinationRow.Length; x++) { - TPixel sourcePixel = Unsafe.Add(ref sourceRowRef, (uint)x); - Unsafe.Add(ref destinationRowRef, (uint)(x - offsetX)) = quantizer.GetQuantizedColor(sourcePixel, out TPixel transformed); + TPixel sourcePixel = sourceRow[x + offsetX]; + destinationRow[x] = quantizer.GetQuantizedColor(sourcePixel, out TPixel transformed); this.Dither(source, bounds, sourcePixel, transformed, x, y, scale); } } @@ -200,7 +200,7 @@ public readonly partial struct ErrorDither : IDither, IEquatable, I } ref TPixel pixel = ref rowSpan[targetX]; - var result = pixel.ToVector4(); + Vector4 result = pixel.ToVector4(); result += error * coefficient; pixel.FromVector4(result); diff --git a/src/ImageSharp/Processing/Processors/Quantization/EuclideanPixelMap{TPixel}.cs b/src/ImageSharp/Processing/Processors/Quantization/EuclideanPixelMap{TPixel}.cs index 8aa166d16..3c7d57670 100644 --- a/src/ImageSharp/Processing/Processors/Quantization/EuclideanPixelMap{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Quantization/EuclideanPixelMap{TPixel}.cs @@ -2,7 +2,6 @@ // Licensed under the Six Labors Split License. using System.Buffers; -using System.Numerics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using SixLabors.ImageSharp.Memory; @@ -23,6 +22,7 @@ internal sealed class EuclideanPixelMap : IDisposable { private Rgba32[] rgbaPalette; private int transparentIndex; + private readonly TPixel transparentMatch; /// /// Do not make this readonly! Struct value would be always copied on non-readonly method calls. @@ -54,8 +54,9 @@ internal sealed class EuclideanPixelMap : IDisposable this.cache = new ColorDistanceCache(configuration.MemoryAllocator); PixelOperations.Instance.ToRgba32(configuration, this.Palette.Span, this.rgbaPalette); - // If the provided transparentIndex is outside of the palette, silently ignore it. - this.transparentIndex = transparentIndex < this.Palette.Length ? transparentIndex : -1; + this.transparentIndex = transparentIndex; + Unsafe.SkipInit(out this.transparentMatch); + this.transparentMatch.FromRgba32(default); } /// @@ -97,32 +98,40 @@ internal sealed class EuclideanPixelMap : IDisposable this.Palette = palette; this.rgbaPalette = new Rgba32[palette.Length]; PixelOperations.Instance.ToRgba32(this.configuration, this.Palette.Span, this.rgbaPalette); + this.transparentIndex = -1; this.cache.Clear(); } /// - /// Allows setting the transparent index after construction. If the provided transparentIndex is outside of the palette, silently ignore it. + /// Allows setting the transparent index after construction. /// /// An explicit index at which to match transparent pixels. - public void SetTransparentIndex(int index) => this.transparentIndex = index < this.Palette.Length ? index : -1; + public void SetTransparentIndex(int index) + { + if (index != this.transparentIndex) + { + this.cache.Clear(); + } + + this.transparentIndex = index; + } [MethodImpl(InliningOptions.ShortMethod)] private int GetClosestColorSlow(Rgba32 rgba, ref TPixel paletteRef, out TPixel match) { // Loop through the palette and find the nearest match. int index = 0; - float leastDistance = float.MaxValue; if (this.transparentIndex >= 0 && rgba == default) { // We have explicit instructions. No need to search. index = this.transparentIndex; - DebugGuard.MustBeLessThan(index, this.Palette.Length, nameof(index)); this.cache.Add(rgba, (byte)index); - match = Unsafe.Add(ref paletteRef, (uint)index); + match = this.transparentMatch; return index; } + float leastDistance = float.MaxValue; for (int i = 0; i < this.rgbaPalette.Length; i++) { Rgba32 candidate = this.rgbaPalette[i]; @@ -175,18 +184,24 @@ internal sealed class EuclideanPixelMap : IDisposable /// The granularity of the cache has been determined based upon the current /// suite of test images and provides the lowest possible memory usage while /// providing enough match accuracy. - /// Entry count is currently limited to 2335905 entries (4671810 bytes ~4.45MB). + /// Entry count is currently limited to 4601025 entries (8MB). /// /// private unsafe struct ColorDistanceCache : IDisposable { - private const int IndexBits = 5; - private const int IndexAlphaBits = 6; - private const int IndexCount = (1 << IndexBits) + 1; - private const int IndexAlphaCount = (1 << IndexAlphaBits) + 1; - private const int RgbShift = 8 - IndexBits; - private const int AlphaShift = 8 - IndexAlphaBits; - private const int Entries = IndexCount * IndexCount * IndexCount * IndexAlphaCount; + private const int IndexRBits = 5; + private const int IndexGBits = 5; + private const int IndexBBits = 6; + private const int IndexABits = 6; + private const int IndexRCount = (1 << IndexRBits) + 1; + private const int IndexGCount = (1 << IndexGBits) + 1; + private const int IndexBCount = (1 << IndexBBits) + 1; + private const int IndexACount = (1 << IndexABits) + 1; + private const int RShift = 8 - IndexRBits; + private const int GShift = 8 - IndexGBits; + private const int BShift = 8 - IndexBBits; + private const int AShift = 8 - IndexABits; + private const int Entries = IndexRCount * IndexGCount * IndexBCount * IndexACount; private MemoryHandle tableHandle; private readonly IMemoryOwner table; private readonly short* tablePointer; @@ -202,22 +217,14 @@ internal sealed class EuclideanPixelMap : IDisposable [MethodImpl(InliningOptions.ShortMethod)] public readonly void Add(Rgba32 rgba, byte index) { - int r = rgba.R >> RgbShift; - int g = rgba.G >> RgbShift; - int b = rgba.B >> RgbShift; - int a = rgba.A >> AlphaShift; - int idx = GetPaletteIndex(r, g, b, a); + int idx = GetPaletteIndex(rgba); this.tablePointer[idx] = index; } [MethodImpl(InliningOptions.ShortMethod)] public readonly bool TryGetValue(Rgba32 rgba, out short match) { - int r = rgba.R >> RgbShift; - int g = rgba.G >> RgbShift; - int b = rgba.B >> RgbShift; - int a = rgba.A >> AlphaShift; - int idx = GetPaletteIndex(r, g, b, a); + int idx = GetPaletteIndex(rgba); match = this.tablePointer[idx]; return match > -1; } @@ -229,15 +236,17 @@ internal sealed class EuclideanPixelMap : IDisposable public readonly void Clear() => this.table.GetSpan().Fill(-1); [MethodImpl(InliningOptions.ShortMethod)] - private static int GetPaletteIndex(int r, int g, int b, int a) - => (r << ((IndexBits << 1) + IndexAlphaBits)) - + (r << (IndexBits + IndexAlphaBits + 1)) - + (g << (IndexBits + IndexAlphaBits)) - + (r << (IndexBits << 1)) - + (r << (IndexBits + 1)) - + (g << IndexBits) - + ((r + g + b) << IndexAlphaBits) - + r + g + b + a; + private static int GetPaletteIndex(Rgba32 rgba) + { + int rIndex = rgba.R >> RShift; + int gIndex = rgba.G >> GShift; + int bIndex = rgba.B >> BShift; + int aIndex = rgba.A >> AShift; + + return (aIndex * (IndexRCount * IndexGCount * IndexBCount)) + + (rIndex * (IndexGCount * IndexBCount)) + + (gIndex * IndexBCount) + bIndex; + } public void Dispose() { diff --git a/src/ImageSharp/Processing/Processors/Quantization/QuantizerUtilities.cs b/src/ImageSharp/Processing/Processors/Quantization/QuantizerUtilities.cs index a7edec662..10ffe68dc 100644 --- a/src/ImageSharp/Processing/Processors/Quantization/QuantizerUtilities.cs +++ b/src/ImageSharp/Processing/Processors/Quantization/QuantizerUtilities.cs @@ -156,10 +156,10 @@ public static class QuantizerUtilities for (int y = 0; y < destination.Height; y++) { - Span sourceRow = sourceBuffer.DangerousGetRowSpan(y + offsetY); + ReadOnlySpan sourceRow = sourceBuffer.DangerousGetRowSpan(y + offsetY); Span destinationRow = destination.GetWritablePixelRowSpanUnsafe(y); - for (int x = 0; x < destination.Width; x++) + for (int x = 0; x < destinationRow.Length; x++) { destinationRow[x] = Unsafe.AsRef(quantizer).GetQuantizedColor(sourceRow[x + offsetX], out TPixel _); } diff --git a/tests/ImageSharp.Tests/Formats/Gif/GifEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Gif/GifEncoderTests.cs index cd485b5fa..ef04a4fba 100644 --- a/tests/ImageSharp.Tests/Formats/Gif/GifEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Gif/GifEncoderTests.cs @@ -226,8 +226,6 @@ public class GifEncoderTests } Assert.Equal(iMeta.FrameDelay, cMeta.FrameDelay); - Assert.Equal(iMeta.HasTransparency, cMeta.HasTransparency); - Assert.Equal(iMeta.TransparencyIndex, cMeta.TransparencyIndex); } image.Dispose(); @@ -328,6 +326,8 @@ public class GifEncoderTests using Image output = Image.Load(memStream); + image.Save(provider.Utility.GetTestOutputFileName("gif"), new GifEncoder()); + // TODO: Find a better way to compare. // The image has been visually checked but the quantization and frame trimming pattern used in the gif encoder // means we cannot use an exact comparison nor replicate using the quantizing processor. @@ -357,4 +357,18 @@ public class GifEncoderTests } } } + + public static string[] Animated => TestImages.Gif.Animated; + + [Theory]//(Skip = "Enable for visual animated testing")] + [WithFileCollection(nameof(Animated), PixelTypes.Rgba32)] + public void Encode_Animated_VisualTest(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using Image image = provider.GetImage(); + + //image.DebugSaveMultiFrame(provider); + + provider.Utility.SaveTestOutputFile(image, "gif", new GifEncoder() { ColorTableMode = GifColorTableMode.Local}, "animated"); + } } diff --git a/tests/ImageSharp.Tests/TestImages.cs b/tests/ImageSharp.Tests/TestImages.cs index 6ad93adfb..5b49e5aba 100644 --- a/tests/ImageSharp.Tests/TestImages.cs +++ b/tests/ImageSharp.Tests/TestImages.cs @@ -479,6 +479,7 @@ public static class TestImages public const string Ratio1x4 = "Gif/base_1x4.gif"; public const string LargeComment = "Gif/large_comment.gif"; public const string GlobalQuantizationTest = "Gif/GlobalQuantizationTest.gif"; + public const string MixedDisposal = "Gif/mixed-disposal.gif"; // Test images from https://github.com/robert-ancell/pygif/tree/master/test-suite public const string ZeroSize = "Gif/image-zero-size.gif"; @@ -508,7 +509,24 @@ public static class TestImages public const string Issue2198 = "Gif/issues/issue_2198.gif"; } - public static readonly string[] All = { Rings, Giphy, Cheers, Trans, Kumin, Leo, Ratio4x1, Ratio1x4 }; + public static readonly string[] Animated = + { + Giphy, + Cheers, + Kumin, + Leo, + MixedDisposal, + GlobalQuantizationTest, + Issues.Issue2198, + Issues.Issue2288_A, + Issues.Issue2288_B, + Issues.Issue2288_C, + Issues.Issue2288_D, + Issues.Issue2450_A, + Issues.Issue2450_B, + Issues.BadDescriptorWidth, + Issues.Issue1530 + }; } public static class Tga diff --git a/tests/Images/Input/Gif/mixed-disposal.gif b/tests/Images/Input/Gif/mixed-disposal.gif new file mode 100644 index 000000000..07ca32e91 --- /dev/null +++ b/tests/Images/Input/Gif/mixed-disposal.gif @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:bd7d4093d6d75aa149418936bac73f66c3d81d9e01252993f321ee792514a47a +size 16636 From a42f6b65daf61c19e07ee3ead2b5c128f80f115d Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Thu, 23 Nov 2023 13:41:46 +1000 Subject: [PATCH 05/24] Enable dedup for png --- src/ImageSharp/Formats/AnimationUtilities.cs | 11 +- src/ImageSharp/Formats/Gif/GifDecoderCore.cs | 2 + src/ImageSharp/Formats/Gif/GifEncoderCore.cs | 2 - .../Formats/Gif/MetadataExtensions.cs | 2 +- src/ImageSharp/Formats/Png/PngEncoderCore.cs | 169 +++++++++--------- 5 files changed, 97 insertions(+), 89 deletions(-) diff --git a/src/ImageSharp/Formats/AnimationUtilities.cs b/src/ImageSharp/Formats/AnimationUtilities.cs index 1bca34eae..ee9a85ac4 100644 --- a/src/ImageSharp/Formats/AnimationUtilities.cs +++ b/src/ImageSharp/Formats/AnimationUtilities.cs @@ -85,11 +85,12 @@ internal static class AnimationUtilities int m = Avx2.MoveMask(neq.AsByte()); if (m != 0) { - // If is diff is found, the left side is marked by the min of previously found left side and the diff position. - // The right is the max of the previously found right side and the diff position + 1. - int diff = (int)(i + (uint)(BitOperations.TrailingZeroCount(m) / size)); - left = Math.Min(left, diff); - right = Math.Max(right, diff + 1); + // If is diff is found, the left side is marked by the min of previously found left side and the start position. + // The right is the max of the previously found right side and the end position. + int start = i + (BitOperations.TrailingZeroCount(m) / size); + int end = i + (2 - (BitOperations.LeadingZeroCount((uint)m) / size)); + left = Math.Min(left, start); + right = Math.Max(right, end); hasRowDiff = true; hasDiff = true; } diff --git a/src/ImageSharp/Formats/Gif/GifDecoderCore.cs b/src/ImageSharp/Formats/Gif/GifDecoderCore.cs index bc41c89dc..aecbbbbc7 100644 --- a/src/ImageSharp/Formats/Gif/GifDecoderCore.cs +++ b/src/ImageSharp/Formats/Gif/GifDecoderCore.cs @@ -797,6 +797,8 @@ internal sealed class GifDecoderCore : IImageDecoderInternals this.gifMetadata.GlobalColorTable = colorTable; } } + + this.gifMetadata.BackgroundColorIndex = this.logicalScreenDescriptor.BackgroundColorIndex; } private unsafe struct ScratchBuffer diff --git a/src/ImageSharp/Formats/Gif/GifEncoderCore.cs b/src/ImageSharp/Formats/Gif/GifEncoderCore.cs index 2bc3e53f7..24bb3c00e 100644 --- a/src/ImageSharp/Formats/Gif/GifEncoderCore.cs +++ b/src/ImageSharp/Formats/Gif/GifEncoderCore.cs @@ -245,8 +245,6 @@ internal sealed class GifEncoderCore : IImageEncoderInternals ImageFrame previousFrame = image.Frames.RootFrame; // This frame is reused to store de-duplicated pixel buffers. - // This is more expensive memory-wise than de-duplicating indexed buffer but allows us to deduplicate - // frames using both local and global palettes. using ImageFrame encodingFrame = new(previousFrame.Configuration, previousFrame.Size()); for (int i = 1; i < image.Frames.Count; i++) diff --git a/src/ImageSharp/Formats/Gif/MetadataExtensions.cs b/src/ImageSharp/Formats/Gif/MetadataExtensions.cs index c7f9f84c8..1b9b6ac58 100644 --- a/src/ImageSharp/Formats/Gif/MetadataExtensions.cs +++ b/src/ImageSharp/Formats/Gif/MetadataExtensions.cs @@ -83,7 +83,7 @@ public static partial class MetadataExtensions ColorTableMode = source.ColorTableMode == GifColorTableMode.Global ? FrameColorTableMode.Global : FrameColorTableMode.Local, Duration = TimeSpan.FromMilliseconds(source.FrameDelay * 10), DisposalMode = GetMode(source.DisposalMethod), - BlendMode = FrameBlendMode.Source, + BlendMode = FrameBlendMode.Over, }; private static FrameDisposalMode GetMode(GifDisposalMethod method) => method switch diff --git a/src/ImageSharp/Formats/Png/PngEncoderCore.cs b/src/ImageSharp/Formats/Png/PngEncoderCore.cs index a779718a0..016c42233 100644 --- a/src/ImageSharp/Formats/Png/PngEncoderCore.cs +++ b/src/ImageSharp/Formats/Png/PngEncoderCore.cs @@ -3,6 +3,7 @@ using System.Buffers; using System.Buffers.Binary; +using System.Numerics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using SixLabors.ImageSharp.Common.Helpers; @@ -118,6 +119,11 @@ internal sealed class PngEncoderCore : IImageEncoderInternals, IDisposable /// private IQuantizer? quantizer; + /// + /// Any explicit quantized transparent index provided by the background color. + /// + private int derivedTransparencyIndex = -1; + /// /// Initializes a new instance of the class. /// @@ -164,7 +170,11 @@ internal sealed class PngEncoderCore : IImageEncoderInternals, IDisposable } // Do not move this. We require an accurate bit depth for the header chunk. - IndexedImageFrame? quantized = this.CreateQuantizedImageAndUpdateBitDepth(pngMetadata, currentFrame, null); + IndexedImageFrame? quantized = this.CreateQuantizedImageAndUpdateBitDepth( + pngMetadata, + currentFrame, + currentFrame.Bounds(), + null); this.WriteHeaderChunk(stream); this.WriteGammaChunk(stream); @@ -180,44 +190,51 @@ internal sealed class PngEncoderCore : IImageEncoderInternals, IDisposable { this.WriteAnimationControlChunk(stream, (uint)image.Frames.Count, pngMetadata.RepeatCount); - // TODO: We should attempt to optimize the output by clipping the indexed result to - // non-transparent bounds. That way we can assign frame control bounds and encode - // less data. See GifEncoder for the implementation there. - // Write the first frame. - FrameControl frameControl = this.WriteFrameControlChunk(stream, currentFrame, 0); - this.WriteDataChunks(frameControl, currentFrame, quantized, stream, false); + PngFrameMetadata frameMetadata = GetPngFrameMetadata(currentFrame); + PngDisposalMethod previousDisposal = frameMetadata.DisposalMethod; + FrameControl frameControl = this.WriteFrameControlChunk(stream, frameMetadata, currentFrame.Bounds(), 0); + this.WriteDataChunks(frameControl, currentFrame.PixelBuffer.GetRegion(), quantized, stream, false); // Capture the global palette for reuse on subsequent frames. ReadOnlyMemory? previousPalette = quantized?.Palette.ToArray(); // Write following frames. uint increment = 0; + ImageFrame previousFrame = image.Frames.RootFrame; + + // This frame is reused to store de-duplicated pixel buffers. + using ImageFrame encodingFrame = new(image.Configuration, previousFrame.Size()); + for (int i = 1; i < image.Frames.Count; i++) { currentFrame = image.Frames[i]; + frameMetadata = GetPngFrameMetadata(currentFrame); + + ImageFrame? prev = previousDisposal == PngDisposalMethod.RestoreToBackground ? null : previousFrame; + (bool difference, Rectangle bounds) = AnimationUtilities.DeDuplicatePixels(image.Configuration, prev, currentFrame, encodingFrame, Vector4.Zero); + if (clearTransparency) { - // Dispose of previous clone and reassign. - clonedFrame?.Dispose(); - currentFrame = clonedFrame = currentFrame.Clone(); - ClearTransparentPixels(currentFrame); + ClearTransparentPixels(encodingFrame); } - // Each frame control sequence number must be incremented by the - // number of frame data chunks that follow. - frameControl = this.WriteFrameControlChunk(stream, currentFrame, (uint)i + increment); + // Each frame control sequence number must be incremented by the number of frame data chunks that follow. + frameControl = this.WriteFrameControlChunk(stream, frameMetadata, bounds, (uint)i + increment); // Dispose of previous quantized frame and reassign. quantized?.Dispose(); - quantized = this.CreateQuantizedImageAndUpdateBitDepth(pngMetadata, currentFrame, previousPalette); - increment += this.WriteDataChunks(frameControl, currentFrame, quantized, stream, true); + quantized = this.CreateQuantizedImageAndUpdateBitDepth(pngMetadata, encodingFrame, bounds, previousPalette); + increment += this.WriteDataChunks(frameControl, encodingFrame.PixelBuffer.GetRegion(bounds), quantized, stream, true); + + previousFrame = currentFrame; + previousDisposal = frameMetadata.DisposalMethod; } } else { FrameControl frameControl = new((uint)this.width, (uint)this.height); - this.WriteDataChunks(frameControl, currentFrame, quantized, stream, false); + this.WriteDataChunks(frameControl, currentFrame.PixelBuffer.GetRegion(), quantized, stream, false); } this.WriteEndChunk(stream); @@ -317,15 +334,17 @@ internal sealed class PngEncoderCore : IImageEncoderInternals, IDisposable /// The type of the pixel. /// The image metadata. /// The frame to quantize. + /// The area of interest within the frame. /// Any previously derived palette. /// The quantized image. private IndexedImageFrame? CreateQuantizedImageAndUpdateBitDepth( PngMetadata metadata, ImageFrame frame, + Rectangle bounds, ReadOnlyMemory? previousPalette) where TPixel : unmanaged, IPixel { - IndexedImageFrame? quantized = this.CreateQuantizedFrame(this.encoder, this.colorType, this.bitDepth, metadata, frame, previousPalette); + IndexedImageFrame? quantized = this.CreateQuantizedFrame(this.encoder, this.colorType, this.bitDepth, metadata, frame, bounds, previousPalette); this.bitDepth = CalculateBitDepth(this.colorType, this.bitDepth, quantized); return quantized; } @@ -1033,20 +1052,17 @@ internal sealed class PngEncoderCore : IImageEncoderInternals, IDisposable /// Writes the animation control chunk to the stream. /// /// The containing image data. - /// The image frame. + /// The frame metadata. + /// The frame area of interest. /// The frame sequence number. - private FrameControl WriteFrameControlChunk(Stream stream, ImageFrame imageFrame, uint sequenceNumber) - where TPixel : unmanaged, IPixel + private FrameControl WriteFrameControlChunk(Stream stream, PngFrameMetadata frameMetadata, Rectangle bounds, uint sequenceNumber) { - PngFrameMetadata frameMetadata = GetPngFrameMetadata(imageFrame); - - // TODO: If we can clip the indexed frame for transparent bounds we can set properties here. FrameControl fcTL = new( sequenceNumber: sequenceNumber, - width: (uint)imageFrame.Width, - height: (uint)imageFrame.Height, - xOffset: 0, - yOffset: 0, + width: (uint)bounds.Width, + height: (uint)bounds.Height, + xOffset: (uint)bounds.Left, + yOffset: (uint)bounds.Top, delayNumerator: (ushort)frameMetadata.FrameDelay.Numerator, delayDenominator: (ushort)frameMetadata.FrameDelay.Denominator, disposeOperation: frameMetadata.DisposalMethod, @@ -1064,11 +1080,11 @@ internal sealed class PngEncoderCore : IImageEncoderInternals, IDisposable /// /// The pixel format. /// The frame control - /// The frame. + /// The image frame. /// The quantized pixel data. Can be null. /// The stream. /// Is writing fdAT or IDAT. - private uint WriteDataChunks(FrameControl frameControl, ImageFrame pixels, IndexedImageFrame? quantized, Stream stream, bool isFrame) + private uint WriteDataChunks(FrameControl frameControl, Buffer2DRegion frame, IndexedImageFrame? quantized, Stream stream, bool isFrame) where TPixel : unmanaged, IPixel { byte[] buffer; @@ -1082,16 +1098,16 @@ internal sealed class PngEncoderCore : IImageEncoderInternals, IDisposable { if (quantized is not null) { - this.EncodeAdam7IndexedPixels(frameControl, quantized, deflateStream); + this.EncodeAdam7IndexedPixels(quantized, deflateStream); } else { - this.EncodeAdam7Pixels(frameControl, pixels, deflateStream); + this.EncodeAdam7Pixels(frame, deflateStream); } } else { - this.EncodePixels(frameControl, pixels, quantized, deflateStream); + this.EncodePixels(frame, quantized, deflateStream); } } @@ -1156,54 +1172,43 @@ internal sealed class PngEncoderCore : IImageEncoderInternals, IDisposable /// Encodes the pixels. /// /// The type of the pixel. - /// The frame control - /// The pixels. - /// The quantized pixels span. + /// The image frame pixel buffer. + /// The quantized pixels. /// The deflate stream. - private void EncodePixels(FrameControl frameControl, ImageFrame pixels, IndexedImageFrame? quantized, ZlibDeflateStream deflateStream) + private void EncodePixels(Buffer2DRegion pixels, IndexedImageFrame? quantized, ZlibDeflateStream deflateStream) where TPixel : unmanaged, IPixel { - int width = (int)frameControl.Width; - int height = (int)frameControl.Height; - - int bytesPerScanline = this.CalculateScanlineLength(width); + int bytesPerScanline = this.CalculateScanlineLength(pixels.Width); int filterLength = bytesPerScanline + 1; this.AllocateScanlineBuffers(bytesPerScanline); using IMemoryOwner filterBuffer = this.memoryAllocator.Allocate(filterLength, AllocationOptions.Clean); using IMemoryOwner attemptBuffer = this.memoryAllocator.Allocate(filterLength, AllocationOptions.Clean); - pixels.ProcessPixelRows(accessor => + Span filter = filterBuffer.GetSpan(); + Span attempt = attemptBuffer.GetSpan(); + for (int y = 0; y < pixels.Height; y++) { - Span filter = filterBuffer.GetSpan(); - Span attempt = attemptBuffer.GetSpan(); - for (int y = (int)frameControl.YOffset; y < frameControl.YMax; y++) - { - this.CollectAndFilterPixelRow(accessor.GetRowSpan(y), ref filter, ref attempt, quantized, y); - deflateStream.Write(filter); - this.SwapScanlineBuffers(); - } - }); + this.CollectAndFilterPixelRow(pixels.DangerousGetRowSpan(y), ref filter, ref attempt, quantized, y); + deflateStream.Write(filter); + this.SwapScanlineBuffers(); + } } /// /// Interlaced encoding the pixels. /// /// The type of the pixel. - /// The frame control - /// The image frame. + /// The image frame pixel buffer. /// The deflate stream. - private void EncodeAdam7Pixels(FrameControl frameControl, ImageFrame frame, ZlibDeflateStream deflateStream) + private void EncodeAdam7Pixels(Buffer2DRegion pixels, ZlibDeflateStream deflateStream) where TPixel : unmanaged, IPixel { - int width = (int)frameControl.XMax; - int height = (int)frameControl.YMax; - Buffer2D pixelBuffer = frame.PixelBuffer; for (int pass = 0; pass < 7; pass++) { - int startRow = Adam7.FirstRow[pass] + (int)frameControl.YOffset; - int startCol = Adam7.FirstColumn[pass] + (int)frameControl.XOffset; - int blockWidth = Adam7.ComputeBlockWidth(width, pass); + int startRow = Adam7.FirstRow[pass]; + int startCol = Adam7.FirstColumn[pass]; + int blockWidth = Adam7.ComputeBlockWidth(pixels.Width, pass); int bytesPerScanline = this.bytesPerPixel <= 1 ? ((blockWidth * this.bitDepth) + 7) / 8 @@ -1220,13 +1225,13 @@ internal sealed class PngEncoderCore : IImageEncoderInternals, IDisposable Span filter = filterBuffer.GetSpan(); Span attempt = attemptBuffer.GetSpan(); - for (int row = startRow; row < height; row += Adam7.RowIncrement[pass]) + for (int row = startRow; row < pixels.Height; row += Adam7.RowIncrement[pass]) { // Collect pixel data - Span srcRow = pixelBuffer.DangerousGetRowSpan(row); - for (int col = startCol, i = 0; col < frameControl.XMax; col += Adam7.ColumnIncrement[pass]) + Span srcRow = pixels.DangerousGetRowSpan(row); + for (int col = startCol, i = 0; col < pixels.Width; col += Adam7.ColumnIncrement[pass], i++) { - block[i++] = srcRow[col]; + block[i] = srcRow[col]; } // Encode data @@ -1244,19 +1249,16 @@ internal sealed class PngEncoderCore : IImageEncoderInternals, IDisposable /// Interlaced encoding the quantized (indexed, with palette) pixels. /// /// The type of the pixel. - /// The frame control /// The quantized. /// The deflate stream. - private void EncodeAdam7IndexedPixels(FrameControl frameControl, IndexedImageFrame quantized, ZlibDeflateStream deflateStream) + private void EncodeAdam7IndexedPixels(IndexedImageFrame quantized, ZlibDeflateStream deflateStream) where TPixel : unmanaged, IPixel { - int width = (int)frameControl.Width; - int endRow = (int)frameControl.YMax; for (int pass = 0; pass < 7; pass++) { - int startRow = Adam7.FirstRow[pass] + (int)frameControl.YOffset; - int startCol = Adam7.FirstColumn[pass] + (int)frameControl.XOffset; - int blockWidth = Adam7.ComputeBlockWidth(width, pass); + int startRow = Adam7.FirstRow[pass]; + int startCol = Adam7.FirstColumn[pass]; + int blockWidth = Adam7.ComputeBlockWidth(quantized.Width, pass); int bytesPerScanline = this.bytesPerPixel <= 1 ? ((blockWidth * this.bitDepth) + 7) / 8 @@ -1274,16 +1276,13 @@ internal sealed class PngEncoderCore : IImageEncoderInternals, IDisposable Span filter = filterBuffer.GetSpan(); Span attempt = attemptBuffer.GetSpan(); - for (int row = startRow; row < endRow; row += Adam7.RowIncrement[pass]) + for (int row = startRow; row < quantized.Height; row += Adam7.RowIncrement[pass]) { // Collect data ReadOnlySpan srcRow = quantized.DangerousGetRowSpan(row); - for (int col = startCol, i = 0; - col < frameControl.XMax; - col += Adam7.ColumnIncrement[pass]) + for (int col = startCol, i = 0; col < quantized.Width; col += Adam7.ColumnIncrement[pass], i++) { block[i] = srcRow[col]; - i++; } // Encode data @@ -1455,6 +1454,7 @@ internal sealed class PngEncoderCore : IImageEncoderInternals, IDisposable /// The bits per component. /// The image metadata. /// The frame to quantize. + /// The frame area of interest. /// Any previously derived palette. private IndexedImageFrame? CreateQuantizedFrame( QuantizingImageEncoder encoder, @@ -1462,6 +1462,7 @@ internal sealed class PngEncoderCore : IImageEncoderInternals, IDisposable byte bitDepth, PngMetadata metadata, ImageFrame frame, + Rectangle bounds, ReadOnlyMemory? previousPalette) where TPixel : unmanaged, IPixel { @@ -1473,9 +1474,13 @@ internal sealed class PngEncoderCore : IImageEncoderInternals, IDisposable if (previousPalette is not null) { // Use the previously derived palette created by quantizing the root frame to quantize the current frame. - using PaletteQuantizer paletteQuantizer = new(this.configuration, this.quantizer!.Options, previousPalette.Value, -1); + using PaletteQuantizer paletteQuantizer = new( + this.configuration, + this.quantizer!.Options, + previousPalette.Value, + this.derivedTransparencyIndex); paletteQuantizer.BuildPalette(encoder.PixelSamplingStrategy, frame); - return paletteQuantizer.QuantizeFrame(frame, frame.Bounds()); + return paletteQuantizer.QuantizeFrame(frame, bounds); } // Use the metadata to determine what quantization depth to use if no quantizer has been set. @@ -1483,8 +1488,10 @@ internal sealed class PngEncoderCore : IImageEncoderInternals, IDisposable { if (metadata.ColorTable is not null) { - // Use the provided palette. The caller is responsible for setting values. - this.quantizer = new PaletteQuantizer(metadata.ColorTable.Value); + // We can use the color data from the decoded metadata here. + // We avoid dithering by default to preserve the original colors. + this.derivedTransparencyIndex = metadata.ColorTable.Value.Span.IndexOf(Color.Transparent); + this.quantizer = new PaletteQuantizer(metadata.ColorTable.Value, new() { Dither = null }, this.derivedTransparencyIndex); } else { @@ -1496,7 +1503,7 @@ internal sealed class PngEncoderCore : IImageEncoderInternals, IDisposable using IQuantizer frameQuantizer = this.quantizer.CreatePixelSpecificQuantizer(frame.Configuration); frameQuantizer.BuildPalette(encoder.PixelSamplingStrategy, frame); - return frameQuantizer.QuantizeFrame(frame, frame.Bounds()); + return frameQuantizer.QuantizeFrame(frame, bounds); } /// From cc0727b286ba760003e719b07b3a0c84fd6eafa7 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Thu, 23 Nov 2023 23:40:10 +1000 Subject: [PATCH 06/24] Add dedup to webp --- src/ImageSharp/Formats/Webp/AlphaEncoder.cs | 37 +++++----- .../Formats/Webp/BitWriter/BitWriterBase.cs | 5 +- .../Formats/Webp/Chunks/WebpFrameData.cs | 10 +-- .../Formats/Webp/Lossless/Vp8LEncoder.cs | 69 ++++++++---------- .../Formats/Webp/Lossy/Vp8Encoder.cs | 60 ++++++++------- .../Formats/Webp/Lossy/YuvConversion.cs | 15 ++-- .../Formats/Webp/WebpAnimationDecoder.cs | 2 +- .../Formats/Webp/WebpEncoderCore.cs | 73 +++++++++++++++---- .../Formats/WebP/YuvConversionTests.cs | 5 +- 9 files changed, 154 insertions(+), 122 deletions(-) diff --git a/src/ImageSharp/Formats/Webp/AlphaEncoder.cs b/src/ImageSharp/Formats/Webp/AlphaEncoder.cs index cbd2aa8e7..46030dde3 100644 --- a/src/ImageSharp/Formats/Webp/AlphaEncoder.cs +++ b/src/ImageSharp/Formats/Webp/AlphaEncoder.cs @@ -27,7 +27,7 @@ internal static class AlphaEncoder /// The size in bytes of the alpha data. /// The encoded alpha data. public static IMemoryOwner EncodeAlpha( - ImageFrame frame, + Buffer2DRegion frame, Configuration configuration, MemoryAllocator memoryAllocator, bool skipMetadata, @@ -35,8 +35,6 @@ internal static class AlphaEncoder out int size) where TPixel : unmanaged, IPixel { - int width = frame.Width; - int height = frame.Height; IMemoryOwner alphaData = ExtractAlphaChannel(frame, configuration, memoryAllocator); if (compress) @@ -46,8 +44,8 @@ internal static class AlphaEncoder using Vp8LEncoder lossLessEncoder = new( memoryAllocator, configuration, - width, - height, + frame.Width, + frame.Height, quality, skipMetadata, effort, @@ -58,14 +56,14 @@ internal static class AlphaEncoder // The transparency information will be stored in the green channel of the ARGB quadruplet. // The green channel is allowed extra transformation steps in the specification -- unlike the other channels, // that can improve compression. - using ImageFrame alphaAsFrame = DispatchAlphaToGreen(frame, alphaData.GetSpan()); + using ImageFrame alphaAsFrame = DispatchAlphaToGreen(configuration, frame, alphaData.GetSpan()); - size = lossLessEncoder.EncodeAlphaImageData(alphaAsFrame, alphaData); + size = lossLessEncoder.EncodeAlphaImageData(alphaAsFrame.PixelBuffer.GetRegion(), alphaData); return alphaData; } - size = width * height; + size = frame.Width * frame.Height; return alphaData; } @@ -73,25 +71,28 @@ internal static class AlphaEncoder /// Store the transparency in the green channel. /// /// The pixel format. - /// The to encode from. + /// The configuration. + /// The pixel buffer to encode from. /// A byte sequence of length width * height, containing all the 8-bit transparency values in scan order. /// The transparency frame. - private static ImageFrame DispatchAlphaToGreen(ImageFrame frame, Span alphaData) + private static ImageFrame DispatchAlphaToGreen(Configuration configuration, Buffer2DRegion frame, Span alphaData) where TPixel : unmanaged, IPixel { int width = frame.Width; int height = frame.Height; - ImageFrame alphaAsFrame = new ImageFrame(Configuration.Default, width, height); + ImageFrame alphaAsFrame = new(configuration, width, height); for (int y = 0; y < height; y++) { - Memory rowBuffer = alphaAsFrame.DangerousGetPixelRowMemory(y); - Span pixelRow = rowBuffer.Span; + Memory rowBuffer = alphaAsFrame.DangerousGetPixelRowMemory(y); + Span pixelRow = rowBuffer.Span; Span alphaRow = alphaData.Slice(y * width, width); + + // TODO: This can be probably simd optimized. for (int x = 0; x < width; x++) { // Leave A/R/B channels zero'd. - pixelRow[x] = new Rgba32(0, alphaRow[x], 0, 0); + pixelRow[x] = new Bgra32(0, alphaRow[x], 0, 0); } } @@ -106,12 +107,12 @@ internal static class AlphaEncoder /// The global configuration. /// The memory manager. /// A byte sequence of length width * height, containing all the 8-bit transparency values in scan order. - private static IMemoryOwner ExtractAlphaChannel(ImageFrame frame, Configuration configuration, MemoryAllocator memoryAllocator) + private static IMemoryOwner ExtractAlphaChannel(Buffer2DRegion frame, Configuration configuration, MemoryAllocator memoryAllocator) where TPixel : unmanaged, IPixel { - Buffer2D imageBuffer = frame.PixelBuffer; - int height = frame.Height; int width = frame.Width; + int height = frame.Height; + IMemoryOwner alphaDataBuffer = memoryAllocator.Allocate(width * height); Span alphaData = alphaDataBuffer.GetSpan(); @@ -120,7 +121,7 @@ internal static class AlphaEncoder for (int y = 0; y < height; y++) { - Span rowSpan = imageBuffer.DangerousGetRowSpan(y); + Span rowSpan = frame.DangerousGetRowSpan(y); PixelOperations.Instance.ToRgba32(configuration, rowSpan, rgbaRow); int offset = y * width; for (int x = 0; x < width; x++) diff --git a/src/ImageSharp/Formats/Webp/BitWriter/BitWriterBase.cs b/src/ImageSharp/Formats/Webp/BitWriter/BitWriterBase.cs index 49b059b07..9ffda0f51 100644 --- a/src/ImageSharp/Formats/Webp/BitWriter/BitWriterBase.cs +++ b/src/ImageSharp/Formats/Webp/BitWriter/BitWriterBase.cs @@ -1,7 +1,6 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System.Diagnostics; using SixLabors.ImageSharp.Common.Helpers; using SixLabors.ImageSharp.Formats.Webp.Chunks; using SixLabors.ImageSharp.Metadata.Profiles.Exif; @@ -100,9 +99,7 @@ internal abstract class BitWriterBase bool hasAnimation) { // Write file size later - long pos = RiffHelper.BeginWriteRiffFile(stream, WebpConstants.WebpFourCc); - - Debug.Assert(pos is 4, "Stream should be written from position 0."); + RiffHelper.BeginWriteRiffFile(stream, WebpConstants.WebpFourCc); // Write VP8X, header if necessary. bool isVp8X = exifProfile != null || xmpProfile != null || iccProfile != null || hasAlpha || hasAnimation; diff --git a/src/ImageSharp/Formats/Webp/Chunks/WebpFrameData.cs b/src/ImageSharp/Formats/Webp/Chunks/WebpFrameData.cs index 230f69c32..c8c4a74a0 100644 --- a/src/ImageSharp/Formats/Webp/Chunks/WebpFrameData.cs +++ b/src/ImageSharp/Formats/Webp/Chunks/WebpFrameData.cs @@ -83,7 +83,7 @@ internal readonly struct WebpFrameData /// public WebpDisposalMethod DisposalMethod { get; } - public Rectangle Bounds => new((int)this.X * 2, (int)this.Y * 2, (int)this.Width, (int)this.Height); + public Rectangle Bounds => new((int)this.X, (int)this.Y, (int)this.Width, (int)this.Height); /// /// Writes the animation frame() to the stream. @@ -107,8 +107,8 @@ internal readonly struct WebpFrameData long pos = RiffHelper.BeginWriteChunk(stream, (uint)WebpChunkType.FrameData); - WebpChunkParsingUtils.WriteUInt24LittleEndian(stream, this.X); - WebpChunkParsingUtils.WriteUInt24LittleEndian(stream, this.Y); + WebpChunkParsingUtils.WriteUInt24LittleEndian(stream, this.X / 2); + WebpChunkParsingUtils.WriteUInt24LittleEndian(stream, this.Y / 2); WebpChunkParsingUtils.WriteUInt24LittleEndian(stream, this.Width - 1); WebpChunkParsingUtils.WriteUInt24LittleEndian(stream, this.Height - 1); WebpChunkParsingUtils.WriteUInt24LittleEndian(stream, this.Duration); @@ -128,8 +128,8 @@ internal readonly struct WebpFrameData WebpFrameData data = new( dataSize: WebpChunkParsingUtils.ReadChunkSize(stream, buffer), - x: WebpChunkParsingUtils.ReadUInt24LittleEndian(stream, buffer), - y: WebpChunkParsingUtils.ReadUInt24LittleEndian(stream, buffer), + x: WebpChunkParsingUtils.ReadUInt24LittleEndian(stream, buffer) * 2, + y: WebpChunkParsingUtils.ReadUInt24LittleEndian(stream, buffer) * 2, width: WebpChunkParsingUtils.ReadUInt24LittleEndian(stream, buffer) + 1, height: WebpChunkParsingUtils.ReadUInt24LittleEndian(stream, buffer) + 1, duration: WebpChunkParsingUtils.ReadUInt24LittleEndian(stream, buffer), diff --git a/src/ImageSharp/Formats/Webp/Lossless/Vp8LEncoder.cs b/src/ImageSharp/Formats/Webp/Lossless/Vp8LEncoder.cs index b9e2519fa..518c09ff4 100644 --- a/src/ImageSharp/Formats/Webp/Lossless/Vp8LEncoder.cs +++ b/src/ImageSharp/Formats/Webp/Lossless/Vp8LEncoder.cs @@ -240,7 +240,7 @@ internal class Vp8LEncoder : IDisposable public void EncodeHeader(Image image, Stream stream, bool hasAnimation) where TPixel : unmanaged, IPixel { - // Write bytes from the bitwriter buffer to the stream. + // Write bytes from the bit-writer buffer to the stream. ImageMetadata metadata = image.Metadata; metadata.SyncProfiles(); @@ -267,7 +267,7 @@ internal class Vp8LEncoder : IDisposable public void EncodeFooter(Image image, Stream stream) where TPixel : unmanaged, IPixel { - // Write bytes from the bitwriter buffer to the stream. + // Write bytes from the bit-writer buffer to the stream. ImageMetadata metadata = image.Metadata; ExifProfile exifProfile = this.skipMetadata ? null : metadata.ExifProfile; @@ -280,26 +280,25 @@ internal class Vp8LEncoder : IDisposable /// Encodes the image as lossless webp to the specified stream. /// /// The pixel format. - /// The to encode from. + /// The image frame to encode from. + /// The region of interest within the frame to encode. + /// The frame metadata. /// The to encode the image data to. /// Flag indicating, if an animation parameter is present. - public void Encode(ImageFrame frame, Stream stream, bool hasAnimation) + public void Encode(ImageFrame frame, Rectangle bounds, WebpFrameMetadata frameMetadata, Stream stream, bool hasAnimation) where TPixel : unmanaged, IPixel { - int width = frame.Width; - int height = frame.Height; - // Convert image pixels to bgra array. - bool hasAlpha = this.ConvertPixelsToBgra(frame, width, height); + bool hasAlpha = this.ConvertPixelsToBgra(frame.PixelBuffer.GetRegion(bounds)); // Write the image size. - this.WriteImageSize(width, height); + this.WriteImageSize(bounds.Width, bounds.Height); // Write the non-trivial Alpha flag and lossless version. this.WriteAlphaAndVersion(hasAlpha); // Encode the main image stream. - this.EncodeStream(frame); + this.EncodeStream(bounds.Width, bounds.Height); this.bitWriter.Finish(); @@ -307,21 +306,18 @@ internal class Vp8LEncoder : IDisposable if (hasAnimation) { - WebpFrameMetadata frameMetadata = WebpCommonUtils.GetWebpFrameMetadata(frame); - - // TODO: If we can clip the indexed frame for transparent bounds we can set properties here. prevPosition = new WebpFrameData( - 0, - 0, - (uint)frame.Width, - (uint)frame.Height, + (uint)bounds.Left, + (uint)bounds.Top, + (uint)bounds.Width, + (uint)bounds.Height, frameMetadata.FrameDelay, frameMetadata.BlendMethod, frameMetadata.DisposalMethod) .WriteHeaderTo(stream); } - // Write bytes from the bitwriter buffer to the stream. + // Write bytes from the bit-writer buffer to the stream. this.bitWriter.WriteEncodedImageToStream(stream); if (hasAnimation) @@ -334,12 +330,12 @@ internal class Vp8LEncoder : IDisposable /// Encodes the alpha image data using the webp lossless compression. /// /// The type of the pixel. - /// The to encode from. + /// The alpha-pixel data to encode from. /// The destination buffer to write the encoded alpha data to. /// The size of the compressed data in bytes. /// If the size of the data is the same as the pixel count, the compression would not yield in smaller data and is left uncompressed. /// - public int EncodeAlphaImageData(ImageFrame frame, IMemoryOwner alphaData) + public int EncodeAlphaImageData(Buffer2DRegion frame, IMemoryOwner alphaData) where TPixel : unmanaged, IPixel { int width = frame.Width; @@ -347,10 +343,10 @@ internal class Vp8LEncoder : IDisposable int pixelCount = width * height; // Convert image pixels to bgra array. - this.ConvertPixelsToBgra(frame, width, height); + this.ConvertPixelsToBgra(frame); // The image-stream will NOT contain any headers describing the image dimension, the dimension is already known. - this.EncodeStream(frame); + this.EncodeStream(width, height); this.bitWriter.Finish(); int size = this.bitWriter.NumBytes; if (size >= pixelCount) @@ -364,7 +360,7 @@ internal class Vp8LEncoder : IDisposable } /// - /// Writes the image size to the bitwriter buffer. + /// Writes the image size to the bit writer buffer. /// /// The input image width. /// The input image height. @@ -381,7 +377,7 @@ internal class Vp8LEncoder : IDisposable } /// - /// Writes a flag indicating if alpha channel is used and the VP8L version to the bitwriter buffer. + /// Writes a flag indicating if alpha channel is used and the VP8L version to the bit-writer buffer. /// /// Indicates if a alpha channel is present. private void WriteAlphaAndVersion(bool hasAlpha) @@ -393,14 +389,10 @@ internal class Vp8LEncoder : IDisposable /// /// Encodes the image stream using lossless webp format. /// - /// The pixel type. - /// The frame to encode. - private void EncodeStream(ImageFrame frame) - where TPixel : unmanaged, IPixel + /// The image frame width. + /// The image frame height. + private void EncodeStream(int width, int height) { - int width = frame.Width; - int height = frame.Height; - Span bgra = this.Bgra.GetSpan(); Span encodedData = this.EncodedData.GetSpan(); bool lowEffort = this.method == 0; @@ -508,23 +500,20 @@ internal class Vp8LEncoder : IDisposable /// Converts the pixels of the image to bgra. /// /// The type of the pixels. - /// The frame to convert. - /// The width of the image. - /// The height of the image. + /// The frame pixel buffer to convert. /// true, if the image is non opaque. - private bool ConvertPixelsToBgra(ImageFrame frame, int width, int height) + private bool ConvertPixelsToBgra(Buffer2DRegion pixels) where TPixel : unmanaged, IPixel { - Buffer2D imageBuffer = frame.PixelBuffer; bool nonOpaque = false; Span bgra = this.Bgra.GetSpan(); Span bgraBytes = MemoryMarshal.Cast(bgra); - int widthBytes = width * 4; - for (int y = 0; y < height; y++) + int widthBytes = pixels.Width * 4; + for (int y = 0; y < pixels.Height; y++) { - Span rowSpan = imageBuffer.DangerousGetRowSpan(y); + Span rowSpan = pixels.DangerousGetRowSpan(y); Span rowBytes = bgraBytes.Slice(y * widthBytes, widthBytes); - PixelOperations.Instance.ToBgra32Bytes(this.configuration, rowSpan, rowBytes, width); + PixelOperations.Instance.ToBgra32Bytes(this.configuration, rowSpan, rowBytes, pixels.Width); if (!nonOpaque) { Span rowBgra = MemoryMarshal.Cast(rowBytes); diff --git a/src/ImageSharp/Formats/Webp/Lossy/Vp8Encoder.cs b/src/ImageSharp/Formats/Webp/Lossy/Vp8Encoder.cs index e6148a066..2b74c300a 100644 --- a/src/ImageSharp/Formats/Webp/Lossy/Vp8Encoder.cs +++ b/src/ImageSharp/Formats/Webp/Lossy/Vp8Encoder.cs @@ -351,44 +351,53 @@ internal class Vp8Encoder : IDisposable } /// - /// Encodes the image to the specified stream from the . + /// Encodes the animated image frame to the specified stream. /// /// The pixel format. - /// The to encode from. - /// The to encode the image data to. - public void EncodeAnimation(ImageFrame frame, Stream stream) + /// The image frame to encode from. + /// The stream to encode the image data to. + /// The region of interest within the frame to encode. + /// The frame metadata. + public void EncodeAnimation(ImageFrame frame, Stream stream, Rectangle bounds, WebpFrameMetadata frameMetadata) where TPixel : unmanaged, IPixel => - this.Encode(frame, stream, true, null); + this.Encode(stream, frame, bounds, frameMetadata, true, null); /// - /// Encodes the image to the specified stream from the . + /// Encodes the static image frame to the specified stream. /// /// The pixel format. - /// The to encode from. - /// The to encode the image data to. - public void EncodeStatic(Image image, Stream stream) - where TPixel : unmanaged, IPixel => - this.Encode(image.Frames.RootFrame, stream, false, image); + /// The stream to encode the image data to. + /// The image to encode from. + public void EncodeStatic(Stream stream, Image image) + where TPixel : unmanaged, IPixel + { + ImageFrame frame = image.Frames.RootFrame; + this.Encode(stream, frame, image.Bounds, WebpCommonUtils.GetWebpFrameMetadata(frame), false, image); + } /// - /// Encodes the image to the specified stream from the . + /// Encodes the image to the specified stream. /// /// The pixel format. - /// The to encode from. - /// The to encode the image data to. + /// The stream to encode the image data to. + /// The image frame to encode from. + /// The region of interest within the frame to encode. + /// The frame metadata. /// Flag indicating, if an animation parameter is present. - /// The to encode from. - private void Encode(ImageFrame frame, Stream stream, bool hasAnimation, Image image) + /// The image to encode from. + private void Encode(Stream stream, ImageFrame frame, Rectangle bounds, WebpFrameMetadata frameMetadata, bool hasAnimation, Image image) where TPixel : unmanaged, IPixel { - int width = frame.Width; - int height = frame.Height; + int width = bounds.Width; + int height = bounds.Height; int pixelCount = width * height; Span y = this.Y.GetSpan(); Span u = this.U.GetSpan(); Span v = this.V.GetSpan(); - bool hasAlpha = YuvConversion.ConvertRgbToYuv(frame, this.configuration, this.memoryAllocator, y, u, v); + + Buffer2DRegion pixels = frame.PixelBuffer.GetRegion(bounds); + bool hasAlpha = YuvConversion.ConvertRgbToYuv(pixels, this.configuration, this.memoryAllocator, y, u, v); if (!hasAnimation) { @@ -456,7 +465,7 @@ internal class Vp8Encoder : IDisposable { // TODO: This can potentially run in an separate task. encodedAlphaData = AlphaEncoder.EncodeAlpha( - frame, + pixels, this.configuration, this.memoryAllocator, this.skipMetadata, @@ -477,14 +486,11 @@ internal class Vp8Encoder : IDisposable if (hasAnimation) { - WebpFrameMetadata frameMetadata = WebpCommonUtils.GetWebpFrameMetadata(frame); - - // TODO: If we can clip the indexed frame for transparent bounds we can set properties here. prevPosition = new WebpFrameData( - 0, - 0, - (uint)frame.Width, - (uint)frame.Height, + (uint)bounds.X, + (uint)bounds.Y, + (uint)bounds.Width, + (uint)bounds.Height, frameMetadata.FrameDelay, frameMetadata.BlendMethod, frameMetadata.DisposalMethod) diff --git a/src/ImageSharp/Formats/Webp/Lossy/YuvConversion.cs b/src/ImageSharp/Formats/Webp/Lossy/YuvConversion.cs index d669a37b7..f8e664ed0 100644 --- a/src/ImageSharp/Formats/Webp/Lossy/YuvConversion.cs +++ b/src/ImageSharp/Formats/Webp/Lossy/YuvConversion.cs @@ -259,7 +259,7 @@ internal static class YuvConversion } /// - /// Converts the RGB values of the image to YUV. + /// Converts the pixel values of the image to YUV. /// /// The pixel type of the image. /// The frame to convert. @@ -269,12 +269,11 @@ internal static class YuvConversion /// Span to store the u component of the image. /// Span to store the v component of the image. /// true, if the image contains alpha data. - public static bool ConvertRgbToYuv(ImageFrame frame, Configuration configuration, MemoryAllocator memoryAllocator, Span y, Span u, Span v) + public static bool ConvertRgbToYuv(Buffer2DRegion frame, Configuration configuration, MemoryAllocator memoryAllocator, Span y, Span u, Span v) where TPixel : unmanaged, IPixel { - Buffer2D imageBuffer = frame.PixelBuffer; - int width = imageBuffer.Width; - int height = imageBuffer.Height; + int width = frame.Width; + int height = frame.Height; int uvWidth = (width + 1) >> 1; // Temporary storage for accumulated R/G/B values during conversion to U/V. @@ -289,8 +288,8 @@ internal static class YuvConversion bool hasAlpha = false; for (rowIndex = 0; rowIndex < height - 1; rowIndex += 2) { - Span rowSpan = imageBuffer.DangerousGetRowSpan(rowIndex); - Span nextRowSpan = imageBuffer.DangerousGetRowSpan(rowIndex + 1); + Span rowSpan = frame.DangerousGetRowSpan(rowIndex); + Span nextRowSpan = frame.DangerousGetRowSpan(rowIndex + 1); PixelOperations.Instance.ToBgra32(configuration, rowSpan, bgraRow0); PixelOperations.Instance.ToBgra32(configuration, nextRowSpan, bgraRow1); @@ -320,7 +319,7 @@ internal static class YuvConversion // Extra last row. if ((height & 1) != 0) { - Span rowSpan = imageBuffer.DangerousGetRowSpan(rowIndex); + Span rowSpan = frame.DangerousGetRowSpan(rowIndex); PixelOperations.Instance.ToBgra32(configuration, rowSpan, bgraRow0); ConvertRgbaToY(bgraRow0, y[(rowIndex * width)..], width); diff --git a/src/ImageSharp/Formats/Webp/WebpAnimationDecoder.cs b/src/ImageSharp/Formats/Webp/WebpAnimationDecoder.cs index f081cfcd8..d85096c2e 100644 --- a/src/ImageSharp/Formats/Webp/WebpAnimationDecoder.cs +++ b/src/ImageSharp/Formats/Webp/WebpAnimationDecoder.cs @@ -253,7 +253,7 @@ internal class WebpAnimationDecoder : IDisposable private Buffer2D DecodeImageFrameData(WebpFrameData frameData, WebpImageInfo webpInfo) where TPixel : unmanaged, IPixel { - ImageFrame decodedFrame = new(Configuration.Default, (int)frameData.Width, (int)frameData.Height); + ImageFrame decodedFrame = new(this.configuration, (int)frameData.Width, (int)frameData.Height); try { diff --git a/src/ImageSharp/Formats/Webp/WebpEncoderCore.cs b/src/ImageSharp/Formats/Webp/WebpEncoderCore.cs index 837487047..7357e097c 100644 --- a/src/ImageSharp/Formats/Webp/WebpEncoderCore.cs +++ b/src/ImageSharp/Formats/Webp/WebpEncoderCore.cs @@ -1,6 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. +using System.Numerics; using SixLabors.ImageSharp.Formats.Webp.Lossless; using SixLabors.ImageSharp.Formats.Webp.Lossy; using SixLabors.ImageSharp.Memory; @@ -129,6 +130,8 @@ internal sealed class WebpEncoderCore : IImageEncoderInternals if (lossless) { + bool hasAnimation = image.Frames.Count > 1; + using Vp8LEncoder encoder = new( this.memoryAllocator, this.configuration, @@ -141,17 +144,34 @@ internal sealed class WebpEncoderCore : IImageEncoderInternals this.nearLossless, this.nearLosslessQuality); - bool hasAnimation = image.Frames.Count > 1; encoder.EncodeHeader(image, stream, hasAnimation); + + // Encode the first frame. + ImageFrame previousFrame = image.Frames.RootFrame; + WebpFrameMetadata frameMetadata = WebpCommonUtils.GetWebpFrameMetadata(previousFrame); + encoder.Encode(previousFrame, previousFrame.Bounds(), frameMetadata, stream, hasAnimation); + if (hasAnimation) { - foreach (ImageFrame imageFrame in image.Frames) + WebpDisposalMethod previousDisposal = frameMetadata.DisposalMethod; + + // Encode additional frames + // This frame is reused to store de-duplicated pixel buffers. + using ImageFrame encodingFrame = new(image.Configuration, previousFrame.Size()); + + for (int i = 1; i < image.Frames.Count; i++) { - using Vp8LEncoder enc = new( + ImageFrame currentFrame = image.Frames[i]; + frameMetadata = WebpCommonUtils.GetWebpFrameMetadata(currentFrame); + + ImageFrame? prev = previousDisposal == WebpDisposalMethod.RestoreToBackground ? null : previousFrame; + (bool difference, Rectangle bounds) = AnimationUtilities.DeDuplicatePixels(image.Configuration, prev, currentFrame, encodingFrame, Vector4.Zero); + + using Vp8LEncoder animatedEncoder = new( this.memoryAllocator, this.configuration, - image.Width, - image.Height, + bounds.Width, + bounds.Height, this.quality, this.skipMetadata, this.method, @@ -159,13 +179,12 @@ internal sealed class WebpEncoderCore : IImageEncoderInternals this.nearLossless, this.nearLosslessQuality); - enc.Encode(imageFrame, stream, true); + animatedEncoder.Encode(encodingFrame, bounds, frameMetadata, stream, hasAnimation); + + previousFrame = currentFrame; + previousDisposal = frameMetadata.DisposalMethod; } } - else - { - encoder.Encode(image.Frames.RootFrame, stream, false); - } encoder.EncodeFooter(image, stream); } @@ -183,17 +202,36 @@ internal sealed class WebpEncoderCore : IImageEncoderInternals this.filterStrength, this.spatialNoiseShaping, this.alphaCompression); + if (image.Frames.Count > 1) { + // TODO: What about alpha here? encoder.EncodeHeader(image, stream, false, true); - foreach (ImageFrame imageFrame in image.Frames) + // Encode the first frame. + ImageFrame previousFrame = image.Frames.RootFrame; + WebpFrameMetadata frameMetadata = WebpCommonUtils.GetWebpFrameMetadata(previousFrame); + WebpDisposalMethod previousDisposal = frameMetadata.DisposalMethod; + + 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()); + + for (int i = 1; i < image.Frames.Count; i++) { - using Vp8Encoder enc = new( + ImageFrame currentFrame = image.Frames[i]; + frameMetadata = WebpCommonUtils.GetWebpFrameMetadata(currentFrame); + + ImageFrame? prev = previousDisposal == WebpDisposalMethod.RestoreToBackground ? null : previousFrame; + (bool difference, Rectangle bounds) = AnimationUtilities.DeDuplicatePixels(image.Configuration, prev, currentFrame, encodingFrame, Vector4.Zero); + + using Vp8Encoder animatedEncoder = new( this.memoryAllocator, this.configuration, - image.Width, - image.Height, + bounds.Width, + bounds.Height, this.quality, this.skipMetadata, this.method, @@ -202,12 +240,15 @@ internal sealed class WebpEncoderCore : IImageEncoderInternals this.spatialNoiseShaping, this.alphaCompression); - enc.EncodeAnimation(imageFrame, stream); + animatedEncoder.EncodeAnimation(encodingFrame, stream, bounds, frameMetadata); + + previousFrame = currentFrame; + previousDisposal = frameMetadata.DisposalMethod; } } else { - encoder.EncodeStatic(image, stream); + encoder.EncodeStatic(stream, image); } encoder.EncodeFooter(image, stream); diff --git a/tests/ImageSharp.Tests/Formats/WebP/YuvConversionTests.cs b/tests/ImageSharp.Tests/Formats/WebP/YuvConversionTests.cs index 433b280bc..3ae6601b1 100644 --- a/tests/ImageSharp.Tests/Formats/WebP/YuvConversionTests.cs +++ b/tests/ImageSharp.Tests/Formats/WebP/YuvConversionTests.cs @@ -1,7 +1,6 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Formats.Webp; using SixLabors.ImageSharp.Formats.Webp.Lossy; using SixLabors.ImageSharp.Memory; @@ -143,7 +142,7 @@ public class YuvConversionTests }; // act - YuvConversion.ConvertRgbToYuv(image.Frames.RootFrame, config, memoryAllocator, y, u, v); + YuvConversion.ConvertRgbToYuv(image.Frames.RootFrame.PixelBuffer.GetRegion(), config, memoryAllocator, y, u, v); // assert Assert.True(expectedY.AsSpan().SequenceEqual(y)); @@ -249,7 +248,7 @@ public class YuvConversionTests }; // act - YuvConversion.ConvertRgbToYuv(image.Frames.RootFrame, config, memoryAllocator, y, u, v); + YuvConversion.ConvertRgbToYuv(image.Frames.RootFrame.PixelBuffer.GetRegion(), config, memoryAllocator, y, u, v); // assert Assert.True(expectedY.AsSpan().SequenceEqual(y)); From 4b852e6528e76a8746a7b51d8d12dcd654afcb85 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Fri, 24 Nov 2023 21:58:10 +1000 Subject: [PATCH 07/24] Update tests, fix issues --- src/ImageSharp/Formats/AnimationUtilities.cs | 50 +++++++++++++++---- .../Formats/Gif/MetadataExtensions.cs | 2 +- .../Formats/Png/Chunks/PngPhysical.cs | 2 +- src/ImageSharp/Formats/Png/PngDecoderCore.cs | 31 +++++++++--- src/ImageSharp/Formats/Png/PngEncoderCore.cs | 14 +++++- src/ImageSharp/Formats/Png/PngMetadata.cs | 2 +- .../Formats/Webp/Chunks/WebpFrameData.cs | 14 +++--- .../Formats/Webp/MetadataExtensions.cs | 2 +- .../Formats/Webp/WebpAnimationDecoder.cs | 2 +- ...bpBlendingMethod.cs => WebpBlendMethod.cs} | 4 +- .../Formats/Webp/WebpCommonUtils.cs | 4 +- .../Formats/Webp/WebpEncoderCore.cs | 25 +++++++++- .../Formats/Webp/WebpFrameMetadata.cs | 4 +- .../Formats/GeneralFormatTests.cs | 14 +++--- .../Formats/Gif/GifEncoderTests.cs | 9 ++-- .../Formats/Png/PngEncoderTests.cs | 11 ++-- .../Formats/WebP/WebpEncoderTests.cs | 10 ++-- .../TestUtilities/ImagingTestCaseUtility.cs | 4 +- ...de_8BitColor_WithOctreeQuantizer_rgb32.bmp | 2 +- ...Encode_8BitColor_WithWuQuantizer_rgb32.bmp | 2 +- ...onFilterInBox_Rgba32_CalliphoraPartial.png | 4 +- ...erFilterInBox_Rgba32_CalliphoraPartial.png | 4 +- ...DependOnSinglePixelType_Bgra32_filter0.png | 4 +- ...tDependOnSinglePixelType_Rgb24_filter0.png | 4 +- ...DependOnSinglePixelType_Rgba32_filter0.png | 4 +- ...ndOnSinglePixelType_RgbaVector_filter0.png | 4 +- ...rksWithAllErrorDiffusers_Bike_Atkinson.png | 4 +- ..._WorksWithAllErrorDiffusers_Bike_Burks.png | 4 +- ...hAllErrorDiffusers_Bike_FloydSteinberg.png | 4 +- ...lErrorDiffusers_Bike_JarvisJudiceNinke.png | 4 +- ...orksWithAllErrorDiffusers_Bike_Sierra2.png | 4 +- ...orksWithAllErrorDiffusers_Bike_Sierra3.png | 4 +- ...sWithAllErrorDiffusers_Bike_SierraLite.png | 4 +- ...thAllErrorDiffusers_Bike_StevensonArce.png | 4 +- ...WorksWithAllErrorDiffusers_Bike_Stucki.png | 4 +- ...orDiffusers_CalliphoraPartial_Atkinson.png | 4 +- ...ErrorDiffusers_CalliphoraPartial_Burks.png | 4 +- ...users_CalliphoraPartial_FloydSteinberg.png | 4 +- ...rs_CalliphoraPartial_JarvisJudiceNinke.png | 4 +- ...rorDiffusers_CalliphoraPartial_Sierra2.png | 4 +- ...rorDiffusers_CalliphoraPartial_Sierra3.png | 4 +- ...Diffusers_CalliphoraPartial_SierraLite.png | 4 +- ...fusers_CalliphoraPartial_StevensonArce.png | 4 +- ...rrorDiffusers_CalliphoraPartial_Stucki.png | 4 +- ...DependOnSinglePixelType_Bgra32_filter0.png | 4 +- ...tDependOnSinglePixelType_Rgb24_filter0.png | 4 +- ...DependOnSinglePixelType_Rgba32_filter0.png | 4 +- ...ndOnSinglePixelType_RgbaVector_filter0.png | 4 +- ..._WorksWithAllDitherers_Bike_Bayer16x16.png | 4 +- ...er_WorksWithAllDitherers_Bike_Bayer2x2.png | 4 +- ...er_WorksWithAllDitherers_Bike_Bayer4x4.png | 4 +- ...er_WorksWithAllDitherers_Bike_Bayer8x8.png | 4 +- ..._WorksWithAllDitherers_Bike_Ordered3x3.png | 4 +- ...Ditherers_CalliphoraPartial_Bayer16x16.png | 4 +- ...llDitherers_CalliphoraPartial_Bayer2x2.png | 4 +- ...llDitherers_CalliphoraPartial_Bayer4x4.png | 4 +- ...llDitherers_CalliphoraPartial_Bayer8x8.png | 4 +- ...Ditherers_CalliphoraPartial_Ordered3x3.png | 4 +- ...zed_Encode_Artifacts_Rgba32_issue_2469.png | 4 +- ...InBox_Bike_OctreeQuantizer_ErrorDither.png | 4 +- ...ionInBox_Bike_OctreeQuantizer_NoDither.png | 4 +- ...Box_Bike_OctreeQuantizer_OrderedDither.png | 4 +- ...ke_WebSafePaletteQuantizer_ErrorDither.png | 4 +- ..._Bike_WebSafePaletteQuantizer_NoDither.png | 4 +- ..._WebSafePaletteQuantizer_OrderedDither.png | 4 +- ...ike_WernerPaletteQuantizer_ErrorDither.png | 4 +- ...x_Bike_WernerPaletteQuantizer_NoDither.png | 4 +- ...e_WernerPaletteQuantizer_OrderedDither.png | 4 +- ...tionInBox_Bike_WuQuantizer_ErrorDither.png | 4 +- ...izationInBox_Bike_WuQuantizer_NoDither.png | 4 +- ...onInBox_Bike_WuQuantizer_OrderedDither.png | 4 +- ...oraPartial_OctreeQuantizer_ErrorDither.png | 4 +- ...iphoraPartial_OctreeQuantizer_NoDither.png | 4 +- ...aPartial_OctreeQuantizer_OrderedDither.png | 4 +- ...al_WebSafePaletteQuantizer_ErrorDither.png | 4 +- ...rtial_WebSafePaletteQuantizer_NoDither.png | 4 +- ..._WebSafePaletteQuantizer_OrderedDither.png | 4 +- ...ial_WernerPaletteQuantizer_ErrorDither.png | 4 +- ...artial_WernerPaletteQuantizer_NoDither.png | 4 +- ...l_WernerPaletteQuantizer_OrderedDither.png | 4 +- ...liphoraPartial_WuQuantizer_ErrorDither.png | 4 +- ...CalliphoraPartial_WuQuantizer_NoDither.png | 4 +- ...phoraPartial_WuQuantizer_OrderedDither.png | 4 +- ...david_OctreeQuantizer_ErrorDither_0.25.png | 4 +- ..._david_OctreeQuantizer_ErrorDither_0.5.png | 4 +- ...david_OctreeQuantizer_ErrorDither_0.75.png | 4 +- ...le_david_OctreeQuantizer_ErrorDither_0.png | 4 +- ...le_david_OctreeQuantizer_ErrorDither_1.png | 4 +- ...vid_OctreeQuantizer_OrderedDither_0.25.png | 4 +- ...avid_OctreeQuantizer_OrderedDither_0.5.png | 4 +- ...vid_OctreeQuantizer_OrderedDither_0.75.png | 4 +- ..._david_OctreeQuantizer_OrderedDither_0.png | 4 +- ..._david_OctreeQuantizer_OrderedDither_1.png | 4 +- ...bSafePaletteQuantizer_ErrorDither_0.25.png | 4 +- ...ebSafePaletteQuantizer_ErrorDither_0.5.png | 4 +- ...bSafePaletteQuantizer_ErrorDither_0.75.png | 4 +- ..._WebSafePaletteQuantizer_ErrorDither_0.png | 4 +- ..._WebSafePaletteQuantizer_ErrorDither_1.png | 4 +- ...afePaletteQuantizer_OrderedDither_0.25.png | 4 +- ...SafePaletteQuantizer_OrderedDither_0.5.png | 4 +- ...afePaletteQuantizer_OrderedDither_0.75.png | 4 +- ...ebSafePaletteQuantizer_OrderedDither_0.png | 4 +- ...ebSafePaletteQuantizer_OrderedDither_1.png | 4 +- ...ernerPaletteQuantizer_ErrorDither_0.25.png | 4 +- ...WernerPaletteQuantizer_ErrorDither_0.5.png | 4 +- ...ernerPaletteQuantizer_ErrorDither_0.75.png | 4 +- ...d_WernerPaletteQuantizer_ErrorDither_0.png | 4 +- ...d_WernerPaletteQuantizer_ErrorDither_1.png | 4 +- ...nerPaletteQuantizer_OrderedDither_0.25.png | 4 +- ...rnerPaletteQuantizer_OrderedDither_0.5.png | 4 +- ...nerPaletteQuantizer_OrderedDither_0.75.png | 4 +- ...WernerPaletteQuantizer_OrderedDither_0.png | 4 +- ...WernerPaletteQuantizer_OrderedDither_1.png | 4 +- ...ale_david_WuQuantizer_ErrorDither_0.25.png | 4 +- ...cale_david_WuQuantizer_ErrorDither_0.5.png | 4 +- ...ale_david_WuQuantizer_ErrorDither_0.75.png | 4 +- ...gScale_david_WuQuantizer_ErrorDither_0.png | 4 +- ...gScale_david_WuQuantizer_ErrorDither_1.png | 4 +- ...e_david_WuQuantizer_OrderedDither_0.25.png | 4 +- ...le_david_WuQuantizer_OrderedDither_0.5.png | 4 +- ...e_david_WuQuantizer_OrderedDither_0.75.png | 4 +- ...cale_david_WuQuantizer_OrderedDither_0.png | 4 +- ...cale_david_WuQuantizer_OrderedDither_1.png | 4 +- ...ation_Bike_OctreeQuantizer_ErrorDither.png | 4 +- ...tization_Bike_OctreeQuantizer_NoDither.png | 4 +- ...ion_Bike_OctreeQuantizer_OrderedDither.png | 4 +- ...ke_WebSafePaletteQuantizer_ErrorDither.png | 4 +- ..._Bike_WebSafePaletteQuantizer_NoDither.png | 4 +- ..._WebSafePaletteQuantizer_OrderedDither.png | 4 +- ...ike_WernerPaletteQuantizer_ErrorDither.png | 4 +- ...n_Bike_WernerPaletteQuantizer_NoDither.png | 4 +- ...e_WernerPaletteQuantizer_OrderedDither.png | 4 +- ...ntization_Bike_WuQuantizer_ErrorDither.png | 4 +- ...Quantization_Bike_WuQuantizer_NoDither.png | 4 +- ...ization_Bike_WuQuantizer_OrderedDither.png | 4 +- ...oraPartial_OctreeQuantizer_ErrorDither.png | 4 +- ...iphoraPartial_OctreeQuantizer_NoDither.png | 4 +- ...aPartial_OctreeQuantizer_OrderedDither.png | 4 +- ...al_WebSafePaletteQuantizer_ErrorDither.png | 4 +- ...rtial_WebSafePaletteQuantizer_NoDither.png | 4 +- ..._WebSafePaletteQuantizer_OrderedDither.png | 4 +- ...ial_WernerPaletteQuantizer_ErrorDither.png | 4 +- ...artial_WernerPaletteQuantizer_NoDither.png | 4 +- ...l_WernerPaletteQuantizer_OrderedDither.png | 4 +- ...liphoraPartial_WuQuantizer_ErrorDither.png | 4 +- ...CalliphoraPartial_WuQuantizer_NoDither.png | 4 +- ...phoraPartial_WuQuantizer_OrderedDither.png | 4 +- 147 files changed, 398 insertions(+), 318 deletions(-) rename src/ImageSharp/Formats/Webp/{WebpBlendingMethod.cs => WebpBlendMethod.cs} (92%) diff --git a/src/ImageSharp/Formats/AnimationUtilities.cs b/src/ImageSharp/Formats/AnimationUtilities.cs index ee9a85ac4..b66efd7f5 100644 --- a/src/ImageSharp/Formats/AnimationUtilities.cs +++ b/src/ImageSharp/Formats/AnimationUtilities.cs @@ -27,15 +27,19 @@ internal static class AnimationUtilities /// The current frame. /// The resultant output. /// The value to use when replacing duplicate pixels. + /// The clamping bound to apply when calculating difference bounds. /// The representing the operation result. public static (bool Difference, Rectangle Bounds) DeDuplicatePixels( Configuration configuration, ImageFrame? previousFrame, ImageFrame currentFrame, ImageFrame resultFrame, - Vector4 replacement) + Vector4 replacement, + ClampingMode clampingMode = ClampingMode.None) where TPixel : unmanaged, IPixel { + // TODO: This would be faster (but more complicated to find diff bounds) if we operated on Rgba32. + // If someone wants to do that, they have my unlimited thanks. MemoryAllocator memoryAllocator = configuration.MemoryAllocator; IMemoryOwner buffers = memoryAllocator.Allocate(currentFrame.Width * 3, AllocationOptions.Clean); Span previous = buffers.GetSpan()[..currentFrame.Width]; @@ -78,10 +82,11 @@ internal static class AnimationUtilities Vector256 c = Unsafe.Add(ref currentBase, x); // Compare the previous and current pixels - Vector256 neq = Avx.CompareEqual(p, c); - Vector256 mask = neq.AsInt32(); + Vector256 mask = Avx2.CompareEqual(p.AsInt32(), c.AsInt32()); + mask = Avx2.CompareEqual(mask.AsInt64(), Vector256.AllBitsSet).AsInt32(); + mask = Avx2.And(mask, Avx2.Shuffle(mask, 0b_01_00_11_10)).AsInt32(); - neq = Avx.Xor(neq, Vector256.AllBitsSet); + Vector256 neq = Avx2.Xor(mask.AsInt64(), Vector256.AllBitsSet).AsInt32(); int m = Avx2.MoveMask(neq.AsByte()); if (m != 0) { @@ -95,11 +100,7 @@ internal static class AnimationUtilities hasDiff = true; } - // Capture the original alpha values. - mask = Avx2.HorizontalAdd(mask, mask); - mask = Avx2.HorizontalAdd(mask, mask); - mask = Avx2.CompareEqual(mask, Vector256.Create(-4)); - + // Replace the pixel value with the replacement if the full pixel is matched. Vector256 r = Avx.BlendVariable(c, replacement256, mask.AsSingle()); Unsafe.Add(ref resultBase, x) = r; @@ -153,6 +154,37 @@ internal static class AnimationUtilities Numerics.Clamp(right, left + 1, resultFrame.Width), Numerics.Clamp(bottom, top + 1, resultFrame.Height)); + // Webp requires even bounds + if (clampingMode == ClampingMode.Even) + { + bounds.Width = Math.Min(resultFrame.Width, bounds.Width + (bounds.X & 1)); + bounds.Height = Math.Min(resultFrame.Height, bounds.Height + (bounds.Y & 1)); + bounds.X = Math.Max(0, bounds.X - (bounds.X & 1)); + bounds.Y = Math.Max(0, bounds.Y - (bounds.Y & 1)); + } + return new(hasDiff, bounds); } + + public static void CopySource(ImageFrame source, ImageFrame destination, Rectangle bounds) + where TPixel : unmanaged, IPixel + { + Buffer2DRegion sourceBuffer = source.PixelBuffer.GetRegion(bounds); + Buffer2DRegion destBuffer = destination.PixelBuffer.GetRegion(bounds); + for (int y = 0; y < destination.Height; y++) + { + Span sourceRow = sourceBuffer.DangerousGetRowSpan(y); + Span destRow = destBuffer.DangerousGetRowSpan(y); + sourceRow.CopyTo(destRow); + } + } +} + +#pragma warning disable SA1201 // Elements should appear in the correct order +internal enum ClampingMode +#pragma warning restore SA1201 // Elements should appear in the correct order +{ + None, + + Even, } diff --git a/src/ImageSharp/Formats/Gif/MetadataExtensions.cs b/src/ImageSharp/Formats/Gif/MetadataExtensions.cs index 1b9b6ac58..16f788e3d 100644 --- a/src/ImageSharp/Formats/Gif/MetadataExtensions.cs +++ b/src/ImageSharp/Formats/Gif/MetadataExtensions.cs @@ -83,7 +83,7 @@ public static partial class MetadataExtensions ColorTableMode = source.ColorTableMode == GifColorTableMode.Global ? FrameColorTableMode.Global : FrameColorTableMode.Local, Duration = TimeSpan.FromMilliseconds(source.FrameDelay * 10), DisposalMode = GetMode(source.DisposalMethod), - BlendMode = FrameBlendMode.Over, + BlendMode = source.DisposalMethod == GifDisposalMethod.RestoreToBackground ? FrameBlendMode.Source : FrameBlendMode.Over, }; private static FrameDisposalMode GetMode(GifDisposalMethod method) => method switch diff --git a/src/ImageSharp/Formats/Png/Chunks/PngPhysical.cs b/src/ImageSharp/Formats/Png/Chunks/PngPhysical.cs index 784788248..8af0ac8ca 100644 --- a/src/ImageSharp/Formats/Png/Chunks/PngPhysical.cs +++ b/src/ImageSharp/Formats/Png/Chunks/PngPhysical.cs @@ -61,10 +61,10 @@ internal readonly struct PngPhysical /// The constructed PngPhysicalChunkData instance. public static PngPhysical FromMetadata(ImageMetadata meta) { - byte unitSpecifier = 0; uint x; uint y; + byte unitSpecifier; switch (meta.ResolutionUnits) { case PixelResolutionUnit.AspectRatio: diff --git a/src/ImageSharp/Formats/Png/PngDecoderCore.cs b/src/ImageSharp/Formats/Png/PngDecoderCore.cs index b0706b14c..cb4f599eb 100644 --- a/src/ImageSharp/Formats/Png/PngDecoderCore.cs +++ b/src/ImageSharp/Formats/Png/PngDecoderCore.cs @@ -217,7 +217,7 @@ internal sealed class PngDecoderCore : IImageDecoderInternals chunk.Length - 4, currentFrame, pngMetadata, - this.ReadNextDataChunkAndSkipSeq, + this.ReadNextFrameDataChunk, currentFrameControl.Value, cancellationToken); @@ -1719,19 +1719,34 @@ internal sealed class PngDecoderCore : IImageDecoderInternals } /// - /// Reads the next data chunk and skip sequence number. + /// Reads the next animated frame data chunk. /// /// Count of bytes in the next data chunk, or 0 if there are no more data chunks left. - private int ReadNextDataChunkAndSkipSeq() + private int ReadNextFrameDataChunk() { - int length = this.ReadNextDataChunk(); - if (this.ReadNextDataChunk() is 0) + if (this.nextChunk != null) { - return length; + return 0; } - this.currentStream.Position += 4; // Skip sequence number - return length - 4; + Span buffer = stackalloc byte[20]; + + _ = this.currentStream.Read(buffer, 0, 4); + + if (this.TryReadChunk(buffer, out PngChunk chunk)) + { + if (chunk.Type is PngChunkType.FrameData) + { + chunk.Data?.Dispose(); + + this.currentStream.Position += 4; // Skip sequence number + return chunk.Length - 4; + } + + this.nextChunk = chunk; + } + + return 0; } /// diff --git a/src/ImageSharp/Formats/Png/PngEncoderCore.cs b/src/ImageSharp/Formats/Png/PngEncoderCore.cs index 016c42233..7908109e8 100644 --- a/src/ImageSharp/Formats/Png/PngEncoderCore.cs +++ b/src/ImageSharp/Formats/Png/PngEncoderCore.cs @@ -214,6 +214,16 @@ internal sealed class PngEncoderCore : IImageEncoderInternals, IDisposable ImageFrame? prev = previousDisposal == PngDisposalMethod.RestoreToBackground ? null : previousFrame; (bool difference, Rectangle bounds) = AnimationUtilities.DeDuplicatePixels(image.Configuration, prev, currentFrame, encodingFrame, Vector4.Zero); + if (difference && previousDisposal != PngDisposalMethod.RestoreToBackground) + { + if (frameMetadata.BlendMethod == PngBlendMethod.Source) + { + // We've potentially introduced transparency within our area of interest + // so we need to overwrite the changed area with the full data. + AnimationUtilities.CopySource(currentFrame, encodingFrame, bounds); + } + } + if (clearTransparency) { ClearTransparentPixels(encodingFrame); @@ -258,7 +268,7 @@ internal sealed class PngEncoderCore : IImageEncoderInternals, IDisposable { if (image.Metadata.TryGetPngMetadata(out PngMetadata? png)) { - return png; + return (PngMetadata)png.DeepClone(); } if (image.Metadata.TryGetGifMetadata(out GifMetadata? gif)) @@ -282,7 +292,7 @@ internal sealed class PngEncoderCore : IImageEncoderInternals, IDisposable { if (frame.Metadata.TryGetPngMetadata(out PngFrameMetadata? png)) { - return png; + return (PngFrameMetadata)png.DeepClone(); } if (frame.Metadata.TryGetGifMetadata(out GifFrameMetadata? gif)) diff --git a/src/ImageSharp/Formats/Png/PngMetadata.cs b/src/ImageSharp/Formats/Png/PngMetadata.cs index 7f4052846..128237684 100644 --- a/src/ImageSharp/Formats/Png/PngMetadata.cs +++ b/src/ImageSharp/Formats/Png/PngMetadata.cs @@ -100,7 +100,7 @@ public class PngMetadata : IDeepCloneable if (c == metadata.BackgroundColor) { // Png treats background as fully empty - c = default; + c = Color.Transparent; break; } } diff --git a/src/ImageSharp/Formats/Webp/Chunks/WebpFrameData.cs b/src/ImageSharp/Formats/Webp/Chunks/WebpFrameData.cs index c8c4a74a0..5ed7aab1e 100644 --- a/src/ImageSharp/Formats/Webp/Chunks/WebpFrameData.cs +++ b/src/ImageSharp/Formats/Webp/Chunks/WebpFrameData.cs @@ -12,7 +12,7 @@ internal readonly struct WebpFrameData /// public const uint HeaderSize = 16; - public WebpFrameData(uint dataSize, uint x, uint y, uint width, uint height, uint duration, WebpBlendingMethod blendingMethod, WebpDisposalMethod disposalMethod) + public WebpFrameData(uint dataSize, uint x, uint y, uint width, uint height, uint duration, WebpBlendMethod blendingMethod, WebpDisposalMethod disposalMethod) { this.DataSize = dataSize; this.X = x; @@ -32,12 +32,12 @@ internal readonly struct WebpFrameData width, height, duration, - (flags & 2) == 0 ? WebpBlendingMethod.Over : WebpBlendingMethod.Source, + (flags & 2) == 0 ? WebpBlendMethod.Over : WebpBlendMethod.Source, (flags & 1) == 1 ? WebpDisposalMethod.RestoreToBackground : WebpDisposalMethod.DoNotDispose) { } - public WebpFrameData(uint x, uint y, uint width, uint height, uint duration, WebpBlendingMethod blendingMethod, WebpDisposalMethod disposalMethod) + public WebpFrameData(uint x, uint y, uint width, uint height, uint duration, WebpBlendMethod blendingMethod, WebpDisposalMethod disposalMethod) : this(0, x, y, width, height, duration, blendingMethod, disposalMethod) { } @@ -76,7 +76,7 @@ internal readonly struct WebpFrameData /// /// Gets how transparent pixels of the current frame are to be blended with corresponding pixels of the previous canvas. /// - public WebpBlendingMethod BlendingMethod { get; } + public WebpBlendMethod BlendingMethod { get; } /// /// Gets how the current frame is to be treated after it has been displayed (before rendering the next frame) on the canvas. @@ -93,7 +93,7 @@ internal readonly struct WebpFrameData { byte flags = 0; - if (this.BlendingMethod is WebpBlendingMethod.Source) + if (this.BlendingMethod is WebpBlendMethod.Source) { // Set blending flag. flags |= 2; @@ -107,8 +107,8 @@ internal readonly struct WebpFrameData long pos = RiffHelper.BeginWriteChunk(stream, (uint)WebpChunkType.FrameData); - WebpChunkParsingUtils.WriteUInt24LittleEndian(stream, this.X / 2); - WebpChunkParsingUtils.WriteUInt24LittleEndian(stream, this.Y / 2); + WebpChunkParsingUtils.WriteUInt24LittleEndian(stream, (uint)Math.Round(this.X / 2f)); + WebpChunkParsingUtils.WriteUInt24LittleEndian(stream, (uint)Math.Round(this.Y / 2f)); WebpChunkParsingUtils.WriteUInt24LittleEndian(stream, this.Width - 1); WebpChunkParsingUtils.WriteUInt24LittleEndian(stream, this.Height - 1); WebpChunkParsingUtils.WriteUInt24LittleEndian(stream, this.Duration); diff --git a/src/ImageSharp/Formats/Webp/MetadataExtensions.cs b/src/ImageSharp/Formats/Webp/MetadataExtensions.cs index 10c72a3d9..731d3f1ff 100644 --- a/src/ImageSharp/Formats/Webp/MetadataExtensions.cs +++ b/src/ImageSharp/Formats/Webp/MetadataExtensions.cs @@ -63,7 +63,7 @@ public static partial class MetadataExtensions ColorTableMode = FrameColorTableMode.Global, Duration = TimeSpan.FromMilliseconds(source.FrameDelay), DisposalMode = GetMode(source.DisposalMethod), - BlendMode = source.BlendMethod == WebpBlendingMethod.Over ? FrameBlendMode.Over : FrameBlendMode.Source, + BlendMode = source.BlendMethod == WebpBlendMethod.Over ? FrameBlendMode.Over : FrameBlendMode.Source, }; private static FrameDisposalMode GetMode(WebpDisposalMethod method) => method switch diff --git a/src/ImageSharp/Formats/Webp/WebpAnimationDecoder.cs b/src/ImageSharp/Formats/Webp/WebpAnimationDecoder.cs index d85096c2e..65f1a4da4 100644 --- a/src/ImageSharp/Formats/Webp/WebpAnimationDecoder.cs +++ b/src/ImageSharp/Formats/Webp/WebpAnimationDecoder.cs @@ -202,7 +202,7 @@ internal class WebpAnimationDecoder : IDisposable using Buffer2D decodedImageFrame = this.DecodeImageFrameData(frameData, webpInfo); - bool blend = previousFrame != null && frameData.BlendingMethod == WebpBlendingMethod.Over; + bool blend = previousFrame != null && frameData.BlendingMethod == WebpBlendMethod.Over; DrawDecodedImageFrameOnCanvas(decodedImageFrame, imageFrame, regionRectangle, blend); previousFrame = currentFrame ?? image.Frames.RootFrame; diff --git a/src/ImageSharp/Formats/Webp/WebpBlendingMethod.cs b/src/ImageSharp/Formats/Webp/WebpBlendMethod.cs similarity index 92% rename from src/ImageSharp/Formats/Webp/WebpBlendingMethod.cs rename to src/ImageSharp/Formats/Webp/WebpBlendMethod.cs index 482d62cd2..f16f7650c 100644 --- a/src/ImageSharp/Formats/Webp/WebpBlendingMethod.cs +++ b/src/ImageSharp/Formats/Webp/WebpBlendMethod.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors. +// Copyright (c) Six Labors. // Licensed under the Six Labors Split License. namespace SixLabors.ImageSharp.Formats.Webp; @@ -6,7 +6,7 @@ namespace SixLabors.ImageSharp.Formats.Webp; /// /// Indicates how transparent pixels of the current frame are to be blended with corresponding pixels of the previous canvas. /// -public enum WebpBlendingMethod +public enum WebpBlendMethod { /// /// Do not blend. After disposing of the previous frame, diff --git a/src/ImageSharp/Formats/Webp/WebpCommonUtils.cs b/src/ImageSharp/Formats/Webp/WebpCommonUtils.cs index bb7dd6f27..49482260b 100644 --- a/src/ImageSharp/Formats/Webp/WebpCommonUtils.cs +++ b/src/ImageSharp/Formats/Webp/WebpCommonUtils.cs @@ -20,7 +20,7 @@ internal static class WebpCommonUtils { if (image.Metadata.TryGetWebpMetadata(out WebpMetadata? webp)) { - return webp; + return (WebpMetadata)webp.DeepClone(); } if (image.Metadata.TryGetGifMetadata(out GifMetadata? gif)) @@ -44,7 +44,7 @@ internal static class WebpCommonUtils { if (frame.Metadata.TryGetWebpFrameMetadata(out WebpFrameMetadata? webp)) { - return webp; + return (WebpFrameMetadata)webp.DeepClone(); } if (frame.Metadata.TryGetGifMetadata(out GifFrameMetadata? gif)) diff --git a/src/ImageSharp/Formats/Webp/WebpEncoderCore.cs b/src/ImageSharp/Formats/Webp/WebpEncoderCore.cs index 7357e097c..db12d7c67 100644 --- a/src/ImageSharp/Formats/Webp/WebpEncoderCore.cs +++ b/src/ImageSharp/Formats/Webp/WebpEncoderCore.cs @@ -165,7 +165,18 @@ internal sealed class WebpEncoderCore : IImageEncoderInternals frameMetadata = WebpCommonUtils.GetWebpFrameMetadata(currentFrame); ImageFrame? prev = previousDisposal == WebpDisposalMethod.RestoreToBackground ? null : previousFrame; - (bool difference, Rectangle bounds) = AnimationUtilities.DeDuplicatePixels(image.Configuration, prev, currentFrame, encodingFrame, Vector4.Zero); + + (bool difference, Rectangle bounds) = AnimationUtilities.DeDuplicatePixels(image.Configuration, prev, currentFrame, encodingFrame, Vector4.Zero, ClampingMode.Even); + + if (difference && previousDisposal != WebpDisposalMethod.RestoreToBackground) + { + if (frameMetadata.BlendMethod == WebpBlendMethod.Source) + { + // We've potentially introduced transparency within our area of interest + // so we need to overwrite the changed area with the full data. + AnimationUtilities.CopySource(currentFrame, encodingFrame, bounds); + } + } using Vp8LEncoder animatedEncoder = new( this.memoryAllocator, @@ -225,7 +236,17 @@ internal sealed class WebpEncoderCore : IImageEncoderInternals frameMetadata = WebpCommonUtils.GetWebpFrameMetadata(currentFrame); ImageFrame? prev = previousDisposal == WebpDisposalMethod.RestoreToBackground ? null : previousFrame; - (bool difference, Rectangle bounds) = AnimationUtilities.DeDuplicatePixels(image.Configuration, prev, currentFrame, encodingFrame, Vector4.Zero); + (bool difference, Rectangle bounds) = AnimationUtilities.DeDuplicatePixels(image.Configuration, prev, currentFrame, encodingFrame, Vector4.Zero, ClampingMode.Even); + + if (difference && previousDisposal != WebpDisposalMethod.RestoreToBackground) + { + if (frameMetadata.BlendMethod == WebpBlendMethod.Source) + { + // We've potentially introduced transparency within our area of interest + // so we need to overwrite the changed area with the full data. + AnimationUtilities.CopySource(currentFrame, encodingFrame, bounds); + } + } using Vp8Encoder animatedEncoder = new( this.memoryAllocator, diff --git a/src/ImageSharp/Formats/Webp/WebpFrameMetadata.cs b/src/ImageSharp/Formats/Webp/WebpFrameMetadata.cs index 667b8f8f4..422ad6bc7 100644 --- a/src/ImageSharp/Formats/Webp/WebpFrameMetadata.cs +++ b/src/ImageSharp/Formats/Webp/WebpFrameMetadata.cs @@ -29,7 +29,7 @@ public class WebpFrameMetadata : IDeepCloneable /// /// Gets or sets how transparent pixels of the current frame are to be blended with corresponding pixels of the previous canvas. /// - public WebpBlendingMethod BlendMethod { get; set; } + public WebpBlendMethod BlendMethod { 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. @@ -49,7 +49,7 @@ public class WebpFrameMetadata : IDeepCloneable => new() { FrameDelay = (uint)metadata.Duration.Milliseconds, - BlendMethod = metadata.BlendMode == FrameBlendMode.Source ? WebpBlendingMethod.Source : WebpBlendingMethod.Over, + BlendMethod = metadata.BlendMode == FrameBlendMode.Source ? WebpBlendMethod.Source : WebpBlendMethod.Over, DisposalMethod = metadata.DisposalMode == FrameDisposalMode.RestoreToBackground ? WebpDisposalMethod.RestoreToBackground : WebpDisposalMethod.DoNotDispose }; } diff --git a/tests/ImageSharp.Tests/Formats/GeneralFormatTests.cs b/tests/ImageSharp.Tests/Formats/GeneralFormatTests.cs index 1d84d6600..27511f7be 100644 --- a/tests/ImageSharp.Tests/Formats/GeneralFormatTests.cs +++ b/tests/ImageSharp.Tests/Formats/GeneralFormatTests.cs @@ -162,37 +162,37 @@ public class GeneralFormatTests foreach (TestFile file in Files) { using Image image = file.CreateRgba32Image(); - using (FileStream output = File.OpenWrite(Path.Combine(path, $"{file.FileNameWithoutExtension}.bmp"))) + using (FileStream output = File.Create(Path.Combine(path, $"{file.FileNameWithoutExtension}.bmp"))) { image.SaveAsBmp(output); } - using (FileStream output = File.OpenWrite(Path.Combine(path, $"{file.FileNameWithoutExtension}.jpg"))) + using (FileStream output = File.Create(Path.Combine(path, $"{file.FileNameWithoutExtension}.jpg"))) { image.SaveAsJpeg(output); } - using (FileStream output = File.OpenWrite(Path.Combine(path, $"{file.FileNameWithoutExtension}.pbm"))) + using (FileStream output = File.Create(Path.Combine(path, $"{file.FileNameWithoutExtension}.pbm"))) { image.SaveAsPbm(output); } - using (FileStream output = File.OpenWrite(Path.Combine(path, $"{file.FileNameWithoutExtension}.png"))) + using (FileStream output = File.Create(Path.Combine(path, $"{file.FileNameWithoutExtension}.png"))) { image.SaveAsPng(output); } - using (FileStream output = File.OpenWrite(Path.Combine(path, $"{file.FileNameWithoutExtension}.gif"))) + using (FileStream output = File.Create(Path.Combine(path, $"{file.FileNameWithoutExtension}.gif"))) { image.SaveAsGif(output); } - using (FileStream output = File.OpenWrite(Path.Combine(path, $"{file.FileNameWithoutExtension}.tga"))) + using (FileStream output = File.Create(Path.Combine(path, $"{file.FileNameWithoutExtension}.tga"))) { image.SaveAsTga(output); } - using (FileStream output = File.OpenWrite(Path.Combine(path, $"{file.FileNameWithoutExtension}.tiff"))) + using (FileStream output = File.Create(Path.Combine(path, $"{file.FileNameWithoutExtension}.tiff"))) { image.SaveAsTiff(output); } diff --git a/tests/ImageSharp.Tests/Formats/Gif/GifEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Gif/GifEncoderTests.cs index ef04a4fba..d6d63baab 100644 --- a/tests/ImageSharp.Tests/Formats/Gif/GifEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Gif/GifEncoderTests.cs @@ -360,15 +360,16 @@ public class GifEncoderTests public static string[] Animated => TestImages.Gif.Animated; - [Theory]//(Skip = "Enable for visual animated testing")] + [Theory(Skip = "Enable for visual animated testing")] [WithFileCollection(nameof(Animated), PixelTypes.Rgba32)] public void Encode_Animated_VisualTest(TestImageProvider provider) where TPixel : unmanaged, IPixel { using Image image = provider.GetImage(); - //image.DebugSaveMultiFrame(provider); - - provider.Utility.SaveTestOutputFile(image, "gif", new GifEncoder() { ColorTableMode = GifColorTableMode.Local}, "animated"); + provider.Utility.SaveTestOutputFile(image, "webp", new WebpEncoder() { FileFormat = WebpFileFormatType.Lossless }, "animated"); + provider.Utility.SaveTestOutputFile(image, "webp", new WebpEncoder() { FileFormat = WebpFileFormatType.Lossy }, "animated-lossy"); + provider.Utility.SaveTestOutputFile(image, "png", new PngEncoder(), "animated"); + provider.Utility.SaveTestOutputFile(image, "gif", new GifEncoder(), "animated"); } } diff --git a/tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs index 45dd30b3b..f8fc774b7 100644 --- a/tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs @@ -479,19 +479,16 @@ public partial class PngEncoderTests } [Theory] - [WithFile(TestImages.Gif.Giphy, PixelTypes.Rgba32)] + [WithFile(TestImages.Gif.Leo, PixelTypes.Rgba32)] public void Encode_AnimatedFormatTransform_FromGif(TestImageProvider provider) where TPixel : unmanaged, IPixel { using Image image = provider.GetImage(GifDecoder.Instance); - using MemoryStream memStream = new(); + using MemoryStream memStream = new(); image.Save(memStream, PngEncoder); memStream.Position = 0; - image.Save(provider.Utility.GetTestOutputFileName("png"), new PngEncoder()); - image.Save(provider.Utility.GetTestOutputFileName("gif"), new GifEncoder()); - using Image output = Image.Load(memStream); // TODO: Find a better way to compare. @@ -556,10 +553,10 @@ public partial class PngEncoderTests switch (webpF.BlendMethod) { - case WebpBlendingMethod.Source: + case WebpBlendMethod.Source: Assert.Equal(PngBlendMethod.Source, pngF.BlendMethod); break; - case WebpBlendingMethod.Over: + case WebpBlendMethod.Over: default: Assert.Equal(PngBlendMethod.Over, pngF.BlendMethod); break; diff --git a/tests/ImageSharp.Tests/Formats/WebP/WebpEncoderTests.cs b/tests/ImageSharp.Tests/Formats/WebP/WebpEncoderTests.cs index 0fafdbe16..6baacb38c 100644 --- a/tests/ImageSharp.Tests/Formats/WebP/WebpEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/WebP/WebpEncoderTests.cs @@ -63,7 +63,7 @@ public class WebpEncoderTests } [Theory] - [WithFile(TestImages.Gif.Giphy, PixelTypes.Rgba32)] + [WithFile(TestImages.Gif.Leo, PixelTypes.Rgba32)] public void Encode_AnimatedFormatTransform_FromGif(TestImageProvider provider) where TPixel : unmanaged, IPixel { @@ -115,6 +115,10 @@ public class WebpEncoderTests image.Save(memStream, new WebpEncoder()); memStream.Position = 0; + provider.Utility.SaveTestOutputFile(image, "gif", new GifEncoder()); + provider.Utility.SaveTestOutputFile(image, "png", new PngEncoder()); + provider.Utility.SaveTestOutputFile(image, "webp", new WebpEncoder()); + using Image output = Image.Load(memStream); ImageComparer.Exact.VerifySimilarity(output, image); PngMetadata png = image.Metadata.GetPngMetadata(); @@ -132,11 +136,11 @@ public class WebpEncoderTests switch (pngF.BlendMethod) { case PngBlendMethod.Source: - Assert.Equal(WebpBlendingMethod.Source, webpF.BlendMethod); + Assert.Equal(WebpBlendMethod.Source, webpF.BlendMethod); break; case PngBlendMethod.Over: default: - Assert.Equal(WebpBlendingMethod.Over, webpF.BlendMethod); + Assert.Equal(WebpBlendMethod.Over, webpF.BlendMethod); break; } diff --git a/tests/ImageSharp.Tests/TestUtilities/ImagingTestCaseUtility.cs b/tests/ImageSharp.Tests/TestUtilities/ImagingTestCaseUtility.cs index 9b100047f..2243c852d 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ImagingTestCaseUtility.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ImagingTestCaseUtility.cs @@ -171,7 +171,7 @@ public class ImagingTestCaseUtility encoder ??= TestEnvironment.GetReferenceEncoder(path); - using (FileStream stream = File.OpenWrite(path)) + using (FileStream stream = File.Create(path)) { image.Save(stream, encoder); } @@ -227,7 +227,7 @@ public class ImagingTestCaseUtility { using Image frameImage = image.Frames.CloneFrame(file.Index); string filePath = file.FileName; - using FileStream stream = File.OpenWrite(filePath); + using FileStream stream = File.Create(filePath); frameImage.Save(stream, encoder); } diff --git a/tests/Images/External/ReferenceOutput/BmpEncoderTests/Encode_8BitColor_WithOctreeQuantizer_rgb32.bmp b/tests/Images/External/ReferenceOutput/BmpEncoderTests/Encode_8BitColor_WithOctreeQuantizer_rgb32.bmp index 2b8e05b07..d484eace0 100644 --- a/tests/Images/External/ReferenceOutput/BmpEncoderTests/Encode_8BitColor_WithOctreeQuantizer_rgb32.bmp +++ b/tests/Images/External/ReferenceOutput/BmpEncoderTests/Encode_8BitColor_WithOctreeQuantizer_rgb32.bmp @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:11375b15df083d98335f4a4baf0717e7fdd6b21ab2132a6815cadc787ac17e7d +oid sha256:23a9d9233314ec08bd3e464f245e69d96566cbb12d2dba36c69bba483d6ba6b8 size 9270 diff --git a/tests/Images/External/ReferenceOutput/BmpEncoderTests/Encode_8BitColor_WithWuQuantizer_rgb32.bmp b/tests/Images/External/ReferenceOutput/BmpEncoderTests/Encode_8BitColor_WithWuQuantizer_rgb32.bmp index f7eb06c55..6896c4fa6 100644 --- a/tests/Images/External/ReferenceOutput/BmpEncoderTests/Encode_8BitColor_WithWuQuantizer_rgb32.bmp +++ b/tests/Images/External/ReferenceOutput/BmpEncoderTests/Encode_8BitColor_WithWuQuantizer_rgb32.bmp @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e063e97cd8a000de6830adcc3961a7dc41785d40cd4d83af10ca38d96e071362 +oid sha256:3052831cb18fecc26f54e29dfe19b538d2e0d3c104ddd7ec5bc8e0adcf56693c size 9270 diff --git a/tests/Images/External/ReferenceOutput/DitherTests/ApplyDiffusionFilterInBox_Rgba32_CalliphoraPartial.png b/tests/Images/External/ReferenceOutput/DitherTests/ApplyDiffusionFilterInBox_Rgba32_CalliphoraPartial.png index dd2f49f08..39bb7e52b 100644 --- a/tests/Images/External/ReferenceOutput/DitherTests/ApplyDiffusionFilterInBox_Rgba32_CalliphoraPartial.png +++ b/tests/Images/External/ReferenceOutput/DitherTests/ApplyDiffusionFilterInBox_Rgba32_CalliphoraPartial.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:cafc426ac8e8d02a87f67c90e8c1976c5fae0e12b49deae52ad08476f7ed49a4 -size 266391 +oid sha256:e89597ab9aa006d026a560d1482350739bd93604d9c6726f6730d782c017a0a3 +size 273049 diff --git a/tests/Images/External/ReferenceOutput/DitherTests/ApplyDitherFilterInBox_Rgba32_CalliphoraPartial.png b/tests/Images/External/ReferenceOutput/DitherTests/ApplyDitherFilterInBox_Rgba32_CalliphoraPartial.png index f226b166e..133112867 100644 --- a/tests/Images/External/ReferenceOutput/DitherTests/ApplyDitherFilterInBox_Rgba32_CalliphoraPartial.png +++ b/tests/Images/External/ReferenceOutput/DitherTests/ApplyDitherFilterInBox_Rgba32_CalliphoraPartial.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:98115a7087aced0c28cefa32a57bc72be245886cabeefc4ff7faf7984236218c -size 271226 +oid sha256:59d331efd9e6d926eaf90bed1f76b3ba55b2a42d2f83fc512a985cdea97781ec +size 271152 diff --git a/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_ShouldNotDependOnSinglePixelType_Bgra32_filter0.png b/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_ShouldNotDependOnSinglePixelType_Bgra32_filter0.png index daa4b5e43..cac3b9c42 100644 --- a/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_ShouldNotDependOnSinglePixelType_Bgra32_filter0.png +++ b/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_ShouldNotDependOnSinglePixelType_Bgra32_filter0.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0369747820c86bb692fc7b75f3519095c9b2a58a885ebd37c871c103d08405a0 -size 720 +oid sha256:3cee43ebaefd94bcd993b8548f734a0a44b948a532263b8d2ee41b0cd42ab7a9 +size 727 diff --git a/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_ShouldNotDependOnSinglePixelType_Rgb24_filter0.png b/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_ShouldNotDependOnSinglePixelType_Rgb24_filter0.png index daa4b5e43..cac3b9c42 100644 --- a/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_ShouldNotDependOnSinglePixelType_Rgb24_filter0.png +++ b/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_ShouldNotDependOnSinglePixelType_Rgb24_filter0.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0369747820c86bb692fc7b75f3519095c9b2a58a885ebd37c871c103d08405a0 -size 720 +oid sha256:3cee43ebaefd94bcd993b8548f734a0a44b948a532263b8d2ee41b0cd42ab7a9 +size 727 diff --git a/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_ShouldNotDependOnSinglePixelType_Rgba32_filter0.png b/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_ShouldNotDependOnSinglePixelType_Rgba32_filter0.png index daa4b5e43..cac3b9c42 100644 --- a/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_ShouldNotDependOnSinglePixelType_Rgba32_filter0.png +++ b/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_ShouldNotDependOnSinglePixelType_Rgba32_filter0.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0369747820c86bb692fc7b75f3519095c9b2a58a885ebd37c871c103d08405a0 -size 720 +oid sha256:3cee43ebaefd94bcd993b8548f734a0a44b948a532263b8d2ee41b0cd42ab7a9 +size 727 diff --git a/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_ShouldNotDependOnSinglePixelType_RgbaVector_filter0.png b/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_ShouldNotDependOnSinglePixelType_RgbaVector_filter0.png index d8f9b640d..4a3a1e874 100644 --- a/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_ShouldNotDependOnSinglePixelType_RgbaVector_filter0.png +++ b/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_ShouldNotDependOnSinglePixelType_RgbaVector_filter0.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f63aebed17504ef50d96ac7e58dc41f5227a83a38810359ed8e9cecda137183b -size 720 +oid sha256:2f6df6c76c0f3795c439147fd49d658e52fae5c24a071ee0f4fed7aa096d87d1 +size 719 diff --git a/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_Bike_Atkinson.png b/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_Bike_Atkinson.png index 3656e32db..5db7bd5ef 100644 --- a/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_Bike_Atkinson.png +++ b/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_Bike_Atkinson.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:471eaf2e532b40592c86dc816709d3ae4bbd64892006e00fd611ef6869d3b934 -size 52070 +oid sha256:c419d6b6cb589f95bff06514e3a5da37d7b5704aa3f9e6f4365fe5e00c6d81d1 +size 50670 diff --git a/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_Bike_Burks.png b/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_Bike_Burks.png index 7cafd50c1..b2c857801 100644 --- a/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_Bike_Burks.png +++ b/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_Bike_Burks.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:91fb9966a4b3eaefd5533ddf0b98ec08fbf8cbc263e4ebd438895e6d4129dd03 -size 61447 +oid sha256:90c337c38076fb597ecde87e0e8b5b95c369a90a99e64c5add3b31fc778cb52d +size 61178 diff --git a/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_Bike_FloydSteinberg.png b/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_Bike_FloydSteinberg.png index 5d0c82e05..7ceb6114a 100644 --- a/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_Bike_FloydSteinberg.png +++ b/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_Bike_FloydSteinberg.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d74faa8d188a2915739de64ba9d71b2132b53c8d154db22510c524ae757578a5 -size 61183 +oid sha256:7ce4b978b800820275635ae8da59212661c74a1b6ba9050ebf052c87315aedef +size 62107 diff --git a/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_Bike_JarvisJudiceNinke.png b/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_Bike_JarvisJudiceNinke.png index 584e677e2..9f2a2482a 100644 --- a/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_Bike_JarvisJudiceNinke.png +++ b/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_Bike_JarvisJudiceNinke.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:080cc89d1d6568a2c9b707bf05428ab5febd2951e37223f96e349cc6646d32aa -size 56070 +oid sha256:e44a87274d4f4fc5f1a0a849f27108ff8b98410f5d1e5e328236a9c79b9af51b +size 56175 diff --git a/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_Bike_Sierra2.png b/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_Bike_Sierra2.png index 641ecaca1..a8a0eda5e 100644 --- a/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_Bike_Sierra2.png +++ b/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_Bike_Sierra2.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c7589986c1a762d52fe8ffc252e9938ff0e3a9e00b91ea7f5e36d4335b2b7870 -size 58502 +oid sha256:542285d1c2a375f64680173ca2736399d061009413e8aacf62f785425357f868 +size 58538 diff --git a/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_Bike_Sierra3.png b/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_Bike_Sierra3.png index 61bbf2b15..58a5909c0 100644 --- a/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_Bike_Sierra3.png +++ b/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_Bike_Sierra3.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:934042746c3a9b652069da26b479e2be7cbdb17ab20e41c5e271013a76e96e46 -size 58480 +oid sha256:b4aa18018421bda728ec9adab4eff235d4f4c683a0bb5328dab386833685ab35 +size 57616 diff --git a/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_Bike_SierraLite.png b/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_Bike_SierraLite.png index 42e595b0a..847aab49c 100644 --- a/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_Bike_SierraLite.png +++ b/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_Bike_SierraLite.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:03d5d5cbf1b2c0be736aa2bf726ad4bb04fca77aff393edb9663a7915a794264 -size 62418 +oid sha256:ec9d5fe07a99a995e889870ac4f6d264360adf1dbc5cb656d5657e096fe7f393 +size 61838 diff --git a/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_Bike_StevensonArce.png b/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_Bike_StevensonArce.png index 5cd6eca10..d8cb185c9 100644 --- a/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_Bike_StevensonArce.png +++ b/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_Bike_StevensonArce.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:19a0d8667bfd01e18adbfca778e868ea7a6c43d427f9ae40eb4281d438ef509c -size 54464 +oid sha256:b2dd1df6e5fb97b5fdeab6450713432ee1e0a318953b11ab25f4618bde369792 +size 54658 diff --git a/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_Bike_Stucki.png b/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_Bike_Stucki.png index 5a9779640..6df5a227b 100644 --- a/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_Bike_Stucki.png +++ b/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_Bike_Stucki.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:11c1056e013292e0543598f5690625b9bac0420a15fd1f37f6484daa3b8326fa -size 60074 +oid sha256:ba66ef358ebcec36932e6d2874827846b31585fcbc0683e26c548fc3706e27e3 +size 57294 diff --git a/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_CalliphoraPartial_Atkinson.png b/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_CalliphoraPartial_Atkinson.png index d0c319642..c4a20b45a 100644 --- a/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_CalliphoraPartial_Atkinson.png +++ b/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_CalliphoraPartial_Atkinson.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:3fcf9b7e4ee34e80e8811f94940aff09a5392c21019fc86b145d16fd9c6b1cd2 -size 57501 +oid sha256:9f777e6d61a882da066a00b9d268be5fdbf49bdbccb568eb5bb3d959fe9f4fb0 +size 57454 diff --git a/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_CalliphoraPartial_Burks.png b/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_CalliphoraPartial_Burks.png index 773ff203a..0c5249484 100644 --- a/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_CalliphoraPartial_Burks.png +++ b/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_CalliphoraPartial_Burks.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:4f0d9a43d8a47e00f6e5932b57f99565370a7239496fdbe162fb774497c4ef2a -size 59377 +oid sha256:2b00d60902d54f911a9b4ab9be225ab589073b9f0efe06019895a75e487eadec +size 59415 diff --git a/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_CalliphoraPartial_FloydSteinberg.png b/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_CalliphoraPartial_FloydSteinberg.png index a41b9989f..036448778 100644 --- a/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_CalliphoraPartial_FloydSteinberg.png +++ b/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_CalliphoraPartial_FloydSteinberg.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d4a64da29f144d4d4c525ea45e56819e02a46030ae09542be01fdd8ffc85a295 -size 60377 +oid sha256:ded9cb4986e620f5fc11486ccdf81934dff7b3cea7db7e292929d339ecc21714 +size 60116 diff --git a/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_CalliphoraPartial_JarvisJudiceNinke.png b/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_CalliphoraPartial_JarvisJudiceNinke.png index 39fc93541..587929f1f 100644 --- a/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_CalliphoraPartial_JarvisJudiceNinke.png +++ b/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_CalliphoraPartial_JarvisJudiceNinke.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:90fc8048141b2182e4851a48ac5a79c96210eab9e56468fe06f90e7e70a7c180 -size 58539 +oid sha256:1518a2c1840a8d88767a45c7ba08b904ea471c7422fbb5f2b623f1f219992f83 +size 58479 diff --git a/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_CalliphoraPartial_Sierra2.png b/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_CalliphoraPartial_Sierra2.png index e7bd1c6f3..0cccb2c77 100644 --- a/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_CalliphoraPartial_Sierra2.png +++ b/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_CalliphoraPartial_Sierra2.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b312bd18eba03a37121bbcfb3b285f97fe22283b51256883ce0235bb8605b757 -size 58616 +oid sha256:01c6000407f5302a5dd27a88053f795cdb101c791f92e983e6859792ef8a0935 +size 58879 diff --git a/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_CalliphoraPartial_Sierra3.png b/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_CalliphoraPartial_Sierra3.png index f3155ba80..7150bcc1f 100644 --- a/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_CalliphoraPartial_Sierra3.png +++ b/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_CalliphoraPartial_Sierra3.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:750ccd26984a4d5a370c1af6ca5dd1c9c5c6c66e693f7645130fd1669e3b7b4e -size 58923 +oid sha256:11438d8eef23ae6ff5b7d37f0ccc9b6bfe744abb2dc95785a5769c306e51d11b +size 58908 diff --git a/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_CalliphoraPartial_SierraLite.png b/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_CalliphoraPartial_SierraLite.png index d5cbbd3e0..d1919cbd1 100644 --- a/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_CalliphoraPartial_SierraLite.png +++ b/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_CalliphoraPartial_SierraLite.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f9d3777a936883a2177a964f24d9ac86c8a106c375583bc9a8fbeb0ec39a7dc6 -size 60610 +oid sha256:57584582f914c0c059899f9241de006526a14081093b647972c363b00de62a57 +size 60492 diff --git a/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_CalliphoraPartial_StevensonArce.png b/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_CalliphoraPartial_StevensonArce.png index 5b83ace20..6778314d5 100644 --- a/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_CalliphoraPartial_StevensonArce.png +++ b/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_CalliphoraPartial_StevensonArce.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f638821c29d852d6fabe4cc4cfe802e386024835ad07ee496a7bec7a930e851b -size 57886 +oid sha256:adfc7192b03c29addbc9c19e9eefc5c122d35d3d0fae7dfa65d3390ee5f60502 +size 57887 diff --git a/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_CalliphoraPartial_Stucki.png b/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_CalliphoraPartial_Stucki.png index 46dace67b..5c8c2f3cc 100644 --- a/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_CalliphoraPartial_Stucki.png +++ b/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_CalliphoraPartial_Stucki.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c6e86bfc1594ec4cb8f89a1c92a42778c59aa755ce170a97afb8cab3e623aa79 -size 58376 +oid sha256:9735370ed168043d1fd176a258f08fcd705dcefc1d5e6841d012e843ec34d7a9 +size 58485 diff --git a/tests/Images/External/ReferenceOutput/DitherTests/DitherFilter_ShouldNotDependOnSinglePixelType_Bgra32_filter0.png b/tests/Images/External/ReferenceOutput/DitherTests/DitherFilter_ShouldNotDependOnSinglePixelType_Bgra32_filter0.png index 909af9b6d..e2e4147f6 100644 --- a/tests/Images/External/ReferenceOutput/DitherTests/DitherFilter_ShouldNotDependOnSinglePixelType_Bgra32_filter0.png +++ b/tests/Images/External/ReferenceOutput/DitherTests/DitherFilter_ShouldNotDependOnSinglePixelType_Bgra32_filter0.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f7e849620a297e29ba11014c54430db01d851e4192650f6e39e0410591244cb5 -size 865 +oid sha256:a3253003b088c9975725cf321c2fc827547a5feb199f2d1aa515c69bde59deb7 +size 871 diff --git a/tests/Images/External/ReferenceOutput/DitherTests/DitherFilter_ShouldNotDependOnSinglePixelType_Rgb24_filter0.png b/tests/Images/External/ReferenceOutput/DitherTests/DitherFilter_ShouldNotDependOnSinglePixelType_Rgb24_filter0.png index 909af9b6d..aa0e9a482 100644 --- a/tests/Images/External/ReferenceOutput/DitherTests/DitherFilter_ShouldNotDependOnSinglePixelType_Rgb24_filter0.png +++ b/tests/Images/External/ReferenceOutput/DitherTests/DitherFilter_ShouldNotDependOnSinglePixelType_Rgb24_filter0.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f7e849620a297e29ba11014c54430db01d851e4192650f6e39e0410591244cb5 -size 865 +oid sha256:bb3e3b9b3001e76505fb0e2db7ad200cad2a016c06f1993c60c3cab42c134863 +size 867 diff --git a/tests/Images/External/ReferenceOutput/DitherTests/DitherFilter_ShouldNotDependOnSinglePixelType_Rgba32_filter0.png b/tests/Images/External/ReferenceOutput/DitherTests/DitherFilter_ShouldNotDependOnSinglePixelType_Rgba32_filter0.png index 909af9b6d..e2e4147f6 100644 --- a/tests/Images/External/ReferenceOutput/DitherTests/DitherFilter_ShouldNotDependOnSinglePixelType_Rgba32_filter0.png +++ b/tests/Images/External/ReferenceOutput/DitherTests/DitherFilter_ShouldNotDependOnSinglePixelType_Rgba32_filter0.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f7e849620a297e29ba11014c54430db01d851e4192650f6e39e0410591244cb5 -size 865 +oid sha256:a3253003b088c9975725cf321c2fc827547a5feb199f2d1aa515c69bde59deb7 +size 871 diff --git a/tests/Images/External/ReferenceOutput/DitherTests/DitherFilter_ShouldNotDependOnSinglePixelType_RgbaVector_filter0.png b/tests/Images/External/ReferenceOutput/DitherTests/DitherFilter_ShouldNotDependOnSinglePixelType_RgbaVector_filter0.png index 909af9b6d..e2e4147f6 100644 --- a/tests/Images/External/ReferenceOutput/DitherTests/DitherFilter_ShouldNotDependOnSinglePixelType_RgbaVector_filter0.png +++ b/tests/Images/External/ReferenceOutput/DitherTests/DitherFilter_ShouldNotDependOnSinglePixelType_RgbaVector_filter0.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f7e849620a297e29ba11014c54430db01d851e4192650f6e39e0410591244cb5 -size 865 +oid sha256:a3253003b088c9975725cf321c2fc827547a5feb199f2d1aa515c69bde59deb7 +size 871 diff --git a/tests/Images/External/ReferenceOutput/DitherTests/DitherFilter_WorksWithAllDitherers_Bike_Bayer16x16.png b/tests/Images/External/ReferenceOutput/DitherTests/DitherFilter_WorksWithAllDitherers_Bike_Bayer16x16.png index 5961b0384..cb4ce6138 100644 --- a/tests/Images/External/ReferenceOutput/DitherTests/DitherFilter_WorksWithAllDitherers_Bike_Bayer16x16.png +++ b/tests/Images/External/ReferenceOutput/DitherTests/DitherFilter_WorksWithAllDitherers_Bike_Bayer16x16.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:58c03e354b108033873e2a4c0b043ce15919c4d0630e6ca72ff70b89cbedb979 -size 44239 +oid sha256:5f844bf243df10178cdc8bd0d49fa8e7722476373c5b2cbc494bdc53eaa7a116 +size 44133 diff --git a/tests/Images/External/ReferenceOutput/DitherTests/DitherFilter_WorksWithAllDitherers_Bike_Bayer2x2.png b/tests/Images/External/ReferenceOutput/DitherTests/DitherFilter_WorksWithAllDitherers_Bike_Bayer2x2.png index a2bbce465..7669d9686 100644 --- a/tests/Images/External/ReferenceOutput/DitherTests/DitherFilter_WorksWithAllDitherers_Bike_Bayer2x2.png +++ b/tests/Images/External/ReferenceOutput/DitherTests/DitherFilter_WorksWithAllDitherers_Bike_Bayer2x2.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f987f4d270568facefc11eee7f81dd156af56c26b69fe3a6d2d2e9818652befa -size 43116 +oid sha256:b80161aa7723ac8804a2679fbb1fc8327961166475e1a3567ee7bf1f61b43307 +size 41530 diff --git a/tests/Images/External/ReferenceOutput/DitherTests/DitherFilter_WorksWithAllDitherers_Bike_Bayer4x4.png b/tests/Images/External/ReferenceOutput/DitherTests/DitherFilter_WorksWithAllDitherers_Bike_Bayer4x4.png index 727a45d0b..cc825e03f 100644 --- a/tests/Images/External/ReferenceOutput/DitherTests/DitherFilter_WorksWithAllDitherers_Bike_Bayer4x4.png +++ b/tests/Images/External/ReferenceOutput/DitherTests/DitherFilter_WorksWithAllDitherers_Bike_Bayer4x4.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ebdad83936e50bbb00fd74b7dd7d2f5a480bb7347aa3d151e7827107cd279bac -size 44441 +oid sha256:69e745d55bb37c75c5651713648fb9d2d761b8790b86b67e9a553a7ae6328f0a +size 43625 diff --git a/tests/Images/External/ReferenceOutput/DitherTests/DitherFilter_WorksWithAllDitherers_Bike_Bayer8x8.png b/tests/Images/External/ReferenceOutput/DitherTests/DitherFilter_WorksWithAllDitherers_Bike_Bayer8x8.png index 5f9b599b7..9710298a3 100644 --- a/tests/Images/External/ReferenceOutput/DitherTests/DitherFilter_WorksWithAllDitherers_Bike_Bayer8x8.png +++ b/tests/Images/External/ReferenceOutput/DitherTests/DitherFilter_WorksWithAllDitherers_Bike_Bayer8x8.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ccdf5937c30999e3b09071200de2e1db63b606ad9cbf6f7677a7499fb0b52963 -size 44252 +oid sha256:a2a534bc0862e0d7325e9d6bbdf1c73ddc356bd621dfdc64a9c0c04399a93e00 +size 43807 diff --git a/tests/Images/External/ReferenceOutput/DitherTests/DitherFilter_WorksWithAllDitherers_Bike_Ordered3x3.png b/tests/Images/External/ReferenceOutput/DitherTests/DitherFilter_WorksWithAllDitherers_Bike_Ordered3x3.png index b50fce4dc..e6fc9aa0f 100644 --- a/tests/Images/External/ReferenceOutput/DitherTests/DitherFilter_WorksWithAllDitherers_Bike_Ordered3x3.png +++ b/tests/Images/External/ReferenceOutput/DitherTests/DitherFilter_WorksWithAllDitherers_Bike_Ordered3x3.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:baf70b732646d7c6cec60cfbe569ec673418dfb2dd0b5937bccfb91d9821d586 -size 45053 +oid sha256:4ba2d0740f6d614725125c393ed8cf19e0dc648a82b114de5b8531a97e828351 +size 43868 diff --git a/tests/Images/External/ReferenceOutput/DitherTests/DitherFilter_WorksWithAllDitherers_CalliphoraPartial_Bayer16x16.png b/tests/Images/External/ReferenceOutput/DitherTests/DitherFilter_WorksWithAllDitherers_CalliphoraPartial_Bayer16x16.png index dfdb4f642..3251f0466 100644 --- a/tests/Images/External/ReferenceOutput/DitherTests/DitherFilter_WorksWithAllDitherers_CalliphoraPartial_Bayer16x16.png +++ b/tests/Images/External/ReferenceOutput/DitherTests/DitherFilter_WorksWithAllDitherers_CalliphoraPartial_Bayer16x16.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f6a1eae610ed730e4cec41693829929ba8db674886c2bd558f1b8893d2b76802 -size 51201 +oid sha256:a980061a57ac57351b4ccedb34f915319373759753cf2d28fb604a968b87fb2f +size 50640 diff --git a/tests/Images/External/ReferenceOutput/DitherTests/DitherFilter_WorksWithAllDitherers_CalliphoraPartial_Bayer2x2.png b/tests/Images/External/ReferenceOutput/DitherTests/DitherFilter_WorksWithAllDitherers_CalliphoraPartial_Bayer2x2.png index c37ea9dca..7bf58cd3e 100644 --- a/tests/Images/External/ReferenceOutput/DitherTests/DitherFilter_WorksWithAllDitherers_CalliphoraPartial_Bayer2x2.png +++ b/tests/Images/External/ReferenceOutput/DitherTests/DitherFilter_WorksWithAllDitherers_CalliphoraPartial_Bayer2x2.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ba674e0236c2e146c64a7f3e224c702030769304cd0fd624d1989536da341659 -size 52814 +oid sha256:4b64630a74a435fde316a2d6be140ef54b04fecbf5fa76e31014d92c9cd925db +size 52298 diff --git a/tests/Images/External/ReferenceOutput/DitherTests/DitherFilter_WorksWithAllDitherers_CalliphoraPartial_Bayer4x4.png b/tests/Images/External/ReferenceOutput/DitherTests/DitherFilter_WorksWithAllDitherers_CalliphoraPartial_Bayer4x4.png index d1ccc680b..c7e3a0589 100644 --- a/tests/Images/External/ReferenceOutput/DitherTests/DitherFilter_WorksWithAllDitherers_CalliphoraPartial_Bayer4x4.png +++ b/tests/Images/External/ReferenceOutput/DitherTests/DitherFilter_WorksWithAllDitherers_CalliphoraPartial_Bayer4x4.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:316231c8d837f864cf62dcc79fdce698dc8c45c0327372de42c2b89eac1d9f81 -size 51851 +oid sha256:3535fa1a240cc7c11e9bb233333894f8782ec67ac36708c8ee3baf905c2d3f65 +size 51268 diff --git a/tests/Images/External/ReferenceOutput/DitherTests/DitherFilter_WorksWithAllDitherers_CalliphoraPartial_Bayer8x8.png b/tests/Images/External/ReferenceOutput/DitherTests/DitherFilter_WorksWithAllDitherers_CalliphoraPartial_Bayer8x8.png index 35ab5b75b..687cd1dbc 100644 --- a/tests/Images/External/ReferenceOutput/DitherTests/DitherFilter_WorksWithAllDitherers_CalliphoraPartial_Bayer8x8.png +++ b/tests/Images/External/ReferenceOutput/DitherTests/DitherFilter_WorksWithAllDitherers_CalliphoraPartial_Bayer8x8.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b58144146585f50960dfd6ac5dc3f52238160287ae5f9b18c6796962cc3d2fd2 -size 51550 +oid sha256:e2d47f277bc904ed8c3b3bbfe636b647884511de39661bfd34cdcb2abe9f6a13 +size 50818 diff --git a/tests/Images/External/ReferenceOutput/DitherTests/DitherFilter_WorksWithAllDitherers_CalliphoraPartial_Ordered3x3.png b/tests/Images/External/ReferenceOutput/DitherTests/DitherFilter_WorksWithAllDitherers_CalliphoraPartial_Ordered3x3.png index fde570043..dd21dc76d 100644 --- a/tests/Images/External/ReferenceOutput/DitherTests/DitherFilter_WorksWithAllDitherers_CalliphoraPartial_Ordered3x3.png +++ b/tests/Images/External/ReferenceOutput/DitherTests/DitherFilter_WorksWithAllDitherers_CalliphoraPartial_Ordered3x3.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6515be764d7026a87cfeea2d58344c404e4f15908139a25f413d51cc7cc61a0c -size 52216 +oid sha256:ed86ea3844a13de5247bcb409c70ca4a8051cb18b6a3c298a5665f4573f89490 +size 51756 diff --git a/tests/Images/External/ReferenceOutput/PngEncoderTests/Issue2469_Quantized_Encode_Artifacts_Rgba32_issue_2469.png b/tests/Images/External/ReferenceOutput/PngEncoderTests/Issue2469_Quantized_Encode_Artifacts_Rgba32_issue_2469.png index 48e4261f1..6ea363a34 100644 --- a/tests/Images/External/ReferenceOutput/PngEncoderTests/Issue2469_Quantized_Encode_Artifacts_Rgba32_issue_2469.png +++ b/tests/Images/External/ReferenceOutput/PngEncoderTests/Issue2469_Quantized_Encode_Artifacts_Rgba32_issue_2469.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ef5a85b2adde25b5f343a18420fe787f5e159029a361a15ef2d6322eb7bb81fb -size 944597 +oid sha256:968a3cfdec62a89823e711f7ed15e3c456a8e44b8f9d46268dd312693e04be00 +size 1028911 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_OctreeQuantizer_ErrorDither.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_OctreeQuantizer_ErrorDither.png index a7730d4e6..939897ba9 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_OctreeQuantizer_ErrorDither.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_OctreeQuantizer_ErrorDither.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f4d36c8f7e5d5c0d798af5fb6bfad28ed0d628b880bea81efe0d54ac1fde86b2 -size 265268 +oid sha256:62b08eae3bbcc99c2dace7156dae8e37786416e1e428455417b3efc7cdcbd56a +size 240453 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_OctreeQuantizer_NoDither.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_OctreeQuantizer_NoDither.png index d993923d4..5afed5704 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_OctreeQuantizer_NoDither.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_OctreeQuantizer_NoDither.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:9f165908729d723818b6c5843bd75298d987448e2cd4278dfe3f388a62025add -size 238396 +oid sha256:f240153a429da26fbe2ffcbce71ceeae47225c261002c242c34b2747823d0f9a +size 231151 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_OctreeQuantizer_OrderedDither.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_OctreeQuantizer_OrderedDither.png index 223d3bc01..0a026b217 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_OctreeQuantizer_OrderedDither.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_OctreeQuantizer_OrderedDither.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:34eaa0696da00838e591b2c48e7797641521f7f3feb01abbd774591c4dd6f200 -size 265546 +oid sha256:7700134ca070ccef3f027b70534a811e4a388d05b421ca91d9176d84310ddfc0 +size 237380 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_WebSafePaletteQuantizer_ErrorDither.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_WebSafePaletteQuantizer_ErrorDither.png index 367db5ea1..977181666 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_WebSafePaletteQuantizer_ErrorDither.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_WebSafePaletteQuantizer_ErrorDither.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:531594a2e47710545d96d0fd9a8cc86983233420172e0ced28df050df1a5e039 -size 239844 +oid sha256:4e46eddb5225c37cfe20a4d94af0eabd1def78eb379a17d9cdae5fae0866d2bb +size 208360 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_WebSafePaletteQuantizer_NoDither.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_WebSafePaletteQuantizer_NoDither.png index 922c2bf9b..977181666 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_WebSafePaletteQuantizer_NoDither.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_WebSafePaletteQuantizer_NoDither.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:4f1462733e02d499b0d8c61ab835a27c7fee560fdc7fc521d20ec09bb4ccc80f -size 216030 +oid sha256:4e46eddb5225c37cfe20a4d94af0eabd1def78eb379a17d9cdae5fae0866d2bb +size 208360 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_WebSafePaletteQuantizer_OrderedDither.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_WebSafePaletteQuantizer_OrderedDither.png index 29c93d14e..cce645585 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_WebSafePaletteQuantizer_OrderedDither.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_WebSafePaletteQuantizer_OrderedDither.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:7e6d91a3ec4f974af675dc360fd5fd623ec8773cdbc88c0a3a6506880838718a -size 226727 +oid sha256:344efea18d09c8c9b23d7c06a8e865c271dc6acdc9330b589d9ebe15b48c285f +size 215670 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_WernerPaletteQuantizer_ErrorDither.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_WernerPaletteQuantizer_ErrorDither.png index f8b5e6133..a5433fa6e 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_WernerPaletteQuantizer_ErrorDither.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_WernerPaletteQuantizer_ErrorDither.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:620463c14de12abb4f2cab3ee6259ad8cbb24c688212729535f41ebf492a8836 -size 224490 +oid sha256:c32da9b861ac38306d1bfcae6384e4cdf449cfdb9de1fee3fdb6975e4096aea6 +size 212659 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_WernerPaletteQuantizer_NoDither.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_WernerPaletteQuantizer_NoDither.png index dbfab2b50..a5433fa6e 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_WernerPaletteQuantizer_NoDither.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_WernerPaletteQuantizer_NoDither.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c68eba122814b5470e5f2e03e34190ff79e84e4b431ad8227355ce7ffcd4a6a7 -size 220192 +oid sha256:c32da9b861ac38306d1bfcae6384e4cdf449cfdb9de1fee3fdb6975e4096aea6 +size 212659 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_WernerPaletteQuantizer_OrderedDither.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_WernerPaletteQuantizer_OrderedDither.png index 86655af42..284b8ad04 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_WernerPaletteQuantizer_OrderedDither.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_WernerPaletteQuantizer_OrderedDither.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6dbd3189b559941f91dd6e0aa15b34a3e5081477400678c2396c6a66d398876f -size 230883 +oid sha256:60fcd8cf74e595b9f5add7ef6924e4ed801f3835b7d2873bbc79e0b61b98bb0f +size 218566 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_WuQuantizer_ErrorDither.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_WuQuantizer_ErrorDither.png index a9e5e18df..430f98d00 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_WuQuantizer_ErrorDither.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_WuQuantizer_ErrorDither.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:274c3e57f186c47bb070dfd2a79b8353032f9d91d03a3ab9ecb3aec13fdd9855 -size 273333 +oid sha256:3a650e180ee2f073f5d06ef343f5d14cde568777a21d8f419dae6c23f36bfb66 +size 255237 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_WuQuantizer_NoDither.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_WuQuantizer_NoDither.png index d8a1178ad..f75e87831 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_WuQuantizer_NoDither.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_WuQuantizer_NoDither.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:df63a3d12e2998d5242b64169ac86e3df7ab4be585a80daddc3e3888dfcb7095 -size 262298 +oid sha256:934c4a8fd38d2d360dd1b69627043895975ee33ed63e2b1e514b8c6f8451c3f0 +size 253854 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_WuQuantizer_OrderedDither.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_WuQuantizer_OrderedDither.png index 76946ee06..558f8a545 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_WuQuantizer_OrderedDither.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_WuQuantizer_OrderedDither.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:457a0b4e27a09440ff4e13792b68fb5a9da82b7ce6129ea15a5ea8dcd99bd522 -size 274300 +oid sha256:38a6c8caeef772c51508954483810ecf4aa13168a8ec33af266cde028ee19f75 +size 249584 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_OctreeQuantizer_ErrorDither.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_OctreeQuantizer_ErrorDither.png index b5cb6c0fa..fb9b5513c 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_OctreeQuantizer_ErrorDither.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_OctreeQuantizer_ErrorDither.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:fd007786164af8f410bd9738c0a072fc75d1f9b50145e5c191c9e3df345341a5 -size 318778 +oid sha256:649e8a71ba3328af6a48a0afe8747433bce459bed50ba5f4108343265f07cdf2 +size 314905 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_OctreeQuantizer_NoDither.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_OctreeQuantizer_NoDither.png index 7e3080562..77d1cc115 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_OctreeQuantizer_NoDither.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_OctreeQuantizer_NoDither.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0203ecb9e4665e7c3992b7da4777c6d35b539790506fc9ca2acbcbc2bdb5db18 -size 303979 +oid sha256:d484a8d20c288de3f30b6c8ea2cecb6893d618d96e8a9079c0003ee49c798967 +size 303608 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_OctreeQuantizer_OrderedDither.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_OctreeQuantizer_OrderedDither.png index 5626fa1b8..34ba72d10 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_OctreeQuantizer_OrderedDither.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_OctreeQuantizer_OrderedDither.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:62cdce27fc46a38a16995df8ed1501f65091d69315288479b1d613b1d87c8239 -size 321123 +oid sha256:6b935deeff7a6f9be835f1975513b0063090d52e5d4178da1ce8575909729164 +size 320831 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_WebSafePaletteQuantizer_ErrorDither.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_WebSafePaletteQuantizer_ErrorDither.png index 3ae9d369d..c575778e0 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_WebSafePaletteQuantizer_ErrorDither.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_WebSafePaletteQuantizer_ErrorDither.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:08ace2edc5089a7742c1c8005126dcce850b1adf9c017b12db6892f4daeef1bb -size 271721 +oid sha256:c14e3f64a9fa9487c4828bc931338525663177828d65f31928b1acf6f5c1d3a7 +size 269542 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_WebSafePaletteQuantizer_NoDither.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_WebSafePaletteQuantizer_NoDither.png index 020562673..c575778e0 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_WebSafePaletteQuantizer_NoDither.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_WebSafePaletteQuantizer_NoDither.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:3a2aae04edebcaca9b95f30963201794887fa0eac954b64c68bfe529b14fa9be -size 269397 +oid sha256:c14e3f64a9fa9487c4828bc931338525663177828d65f31928b1acf6f5c1d3a7 +size 269542 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_WebSafePaletteQuantizer_OrderedDither.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_WebSafePaletteQuantizer_OrderedDither.png index 68d91fc43..02f91294f 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_WebSafePaletteQuantizer_OrderedDither.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_WebSafePaletteQuantizer_OrderedDither.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:2f3e9a338a5ae37c88ce0c348e0b655429220da051db3352779c277bb2dcb441 -size 270622 +oid sha256:fc2c4df81110029eeaf07419c6a5a823faba757aacf776241ad3171370f70338 +size 271393 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_WernerPaletteQuantizer_ErrorDither.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_WernerPaletteQuantizer_ErrorDither.png index 11939c16c..c6f8957ee 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_WernerPaletteQuantizer_ErrorDither.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_WernerPaletteQuantizer_ErrorDither.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6ac7cdcc2fbee0572a369bed29c51e1c9056a4f09c4e0750ecb34d65daf403d4 -size 287741 +oid sha256:4812a323ca35208e55faf476d342202b320a302df9475f40f8d96161e09ae7ea +size 284361 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_WernerPaletteQuantizer_NoDither.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_WernerPaletteQuantizer_NoDither.png index 324bd9253..c6f8957ee 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_WernerPaletteQuantizer_NoDither.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_WernerPaletteQuantizer_NoDither.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:752760327cc1416c171a920f1e0e95e34eae6d78bd0c7393a3be427bf3c8e55c -size 284481 +oid sha256:4812a323ca35208e55faf476d342202b320a302df9475f40f8d96161e09ae7ea +size 284361 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_WernerPaletteQuantizer_OrderedDither.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_WernerPaletteQuantizer_OrderedDither.png index 52bf2a163..13974a84c 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_WernerPaletteQuantizer_OrderedDither.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_WernerPaletteQuantizer_OrderedDither.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:293459538454e07bc9ea1e9df1fa5b0eb986fde7de42f6c25b43e4c8859bd28a -size 285370 +oid sha256:5022fb7d43ec6885efcd6e7513ad92019f28febb91e7e2b61c421bf96cfbb76c +size 285270 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_WuQuantizer_ErrorDither.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_WuQuantizer_ErrorDither.png index 9702a635d..7d07b09e1 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_WuQuantizer_ErrorDither.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_WuQuantizer_ErrorDither.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:be7812accadc6347d6df43c308f7293596f92d0d90cf0b6a8e48fac1f4144fc0 -size 320157 +oid sha256:eb153e0e3c50cc038f8ab215646dd2a1739e748fa4a7a04ef34ea2f6d8e0c22a +size 318803 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_WuQuantizer_NoDither.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_WuQuantizer_NoDither.png index d94d57759..9342757f6 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_WuQuantizer_NoDither.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_WuQuantizer_NoDither.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ff094e6bafe81e818bcbac69018dcfe29366389dfca0d63d8e05ef42896ffe1d -size 317309 +oid sha256:0e09d63aaf5365a1f4ac44beef9f203c4217a7e158703d44b2b1007c413b1f0c +size 318179 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_WuQuantizer_OrderedDither.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_WuQuantizer_OrderedDither.png index e016e3de6..5a9662e03 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_WuQuantizer_OrderedDither.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_WuQuantizer_OrderedDither.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ee0778aac671365dd0afae06cdcf8f36243bd9815f684b975f83e297bb694e63 -size 323979 +oid sha256:563e9bf52b4ac7be22bc15e6aaf8b39e508eb7e31cb90986fe559ac88ba40829 +size 324538 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_OctreeQuantizer_ErrorDither_0.25.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_OctreeQuantizer_ErrorDither_0.25.png index 4b1e9fed2..dae4b4e14 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_OctreeQuantizer_ErrorDither_0.25.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_OctreeQuantizer_ErrorDither_0.25.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:033bb9e0ce89ffe4abda4d409af5741958d4035f9c9824c28d7598d72c4db96f -size 13818 +oid sha256:d96528a2950fb583ff1a1c00e7e13861fc582bd62b72deccaafb73f2cdd15b10 +size 20936 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_OctreeQuantizer_ErrorDither_0.5.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_OctreeQuantizer_ErrorDither_0.5.png index cdf5f1a96..dae4b4e14 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_OctreeQuantizer_ErrorDither_0.5.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_OctreeQuantizer_ErrorDither_0.5.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:9c6617ebf10f2fe1608fbc2a3c75f1a86ff4e3835b5d3fd7fcc2e5d0a4e5bbb1 -size 14380 +oid sha256:d96528a2950fb583ff1a1c00e7e13861fc582bd62b72deccaafb73f2cdd15b10 +size 20936 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_OctreeQuantizer_ErrorDither_0.75.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_OctreeQuantizer_ErrorDither_0.75.png index 2ad42755c..dae4b4e14 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_OctreeQuantizer_ErrorDither_0.75.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_OctreeQuantizer_ErrorDither_0.75.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:5bb52f047f410e2b0bdcd8d186043f0d3b03835f39007775608fb05365ac9a20 -size 14616 +oid sha256:d96528a2950fb583ff1a1c00e7e13861fc582bd62b72deccaafb73f2cdd15b10 +size 20936 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_OctreeQuantizer_ErrorDither_0.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_OctreeQuantizer_ErrorDither_0.png index 4b1e9fed2..dae4b4e14 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_OctreeQuantizer_ErrorDither_0.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_OctreeQuantizer_ErrorDither_0.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:033bb9e0ce89ffe4abda4d409af5741958d4035f9c9824c28d7598d72c4db96f -size 13818 +oid sha256:d96528a2950fb583ff1a1c00e7e13861fc582bd62b72deccaafb73f2cdd15b10 +size 20936 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_OctreeQuantizer_ErrorDither_1.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_OctreeQuantizer_ErrorDither_1.png index aabb936bd..dae4b4e14 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_OctreeQuantizer_ErrorDither_1.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_OctreeQuantizer_ErrorDither_1.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:47e0924ddbf191dca8932eb46b9c533d0983b9fbce956026b392d5fe589fb90a -size 14630 +oid sha256:d96528a2950fb583ff1a1c00e7e13861fc582bd62b72deccaafb73f2cdd15b10 +size 20936 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_OctreeQuantizer_OrderedDither_0.25.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_OctreeQuantizer_OrderedDither_0.25.png index 0fe9cd867..3abeb0316 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_OctreeQuantizer_OrderedDither_0.25.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_OctreeQuantizer_OrderedDither_0.25.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:4ede7ffc5d07a09c7c5706e8e2554897d29acaabf71701191b0f689f2c22ae71 -size 17826 +oid sha256:14dfcd85d0331448c1e3ff447e7e789ad4fc24618b6a9c5d05468bdd6a2f2209 +size 20804 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_OctreeQuantizer_OrderedDither_0.5.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_OctreeQuantizer_OrderedDither_0.5.png index 1e8cd88ff..3102144a1 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_OctreeQuantizer_OrderedDither_0.5.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_OctreeQuantizer_OrderedDither_0.5.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f91c7f1f687c73dd909b629e40f703740270042507c47aa3834ccd38bf289dc3 -size 19394 +oid sha256:8e7df6174aad752e0f3846eeef5efc4bfc70b80e3622a54eba59f6dcd4673d87 +size 21802 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_OctreeQuantizer_OrderedDither_0.75.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_OctreeQuantizer_OrderedDither_0.75.png index a9f7d76f8..f5c07ac7a 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_OctreeQuantizer_OrderedDither_0.75.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_OctreeQuantizer_OrderedDither_0.75.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:2b836ab4e0f1c95fe5d553bf5bca37eac402ba431268d25e4641e86c952f5fdd -size 19802 +oid sha256:95c739dd75b7be9dea10c12dd69ea60095ac617de491a2ba8063c86cd1bd9aa3 +size 22060 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_OctreeQuantizer_OrderedDither_0.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_OctreeQuantizer_OrderedDither_0.png index 4b1e9fed2..dae4b4e14 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_OctreeQuantizer_OrderedDither_0.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_OctreeQuantizer_OrderedDither_0.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:033bb9e0ce89ffe4abda4d409af5741958d4035f9c9824c28d7598d72c4db96f -size 13818 +oid sha256:d96528a2950fb583ff1a1c00e7e13861fc582bd62b72deccaafb73f2cdd15b10 +size 20936 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_OctreeQuantizer_OrderedDither_1.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_OctreeQuantizer_OrderedDither_1.png index 958eee6ad..e1bc766fb 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_OctreeQuantizer_OrderedDither_1.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_OctreeQuantizer_OrderedDither_1.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:00bcd2ac107f45cb31efee44de275dd597eae6d2d4fda71c397416ff5f2f0914 -size 20175 +oid sha256:f49ea178ca76bd1b2165b0b700a558433d27e22b07b3b35be3fdf3a1868d7ad6 +size 22371 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WebSafePaletteQuantizer_ErrorDither_0.25.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WebSafePaletteQuantizer_ErrorDither_0.25.png index 881ee4e58..dc3d45fa1 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WebSafePaletteQuantizer_ErrorDither_0.25.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WebSafePaletteQuantizer_ErrorDither_0.25.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b04b71b0a4718e5b3f91c33c23ef792fc81a67c9fef7b9e4d80bdb9dce3539dd -size 9107 +oid sha256:7bf316a54f0add7d92287fddc9cc3744d4be1a7ba7c6881d83d3f45356d2c53e +size 8627 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WebSafePaletteQuantizer_ErrorDither_0.5.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WebSafePaletteQuantizer_ErrorDither_0.5.png index 680445481..6f49f2715 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WebSafePaletteQuantizer_ErrorDither_0.5.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WebSafePaletteQuantizer_ErrorDither_0.5.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e0125454d2c3859d8457202856e21591ab96f61e2a29c3f017af29bf03961c48 -size 8883 +oid sha256:97c36988a937893cf106b7b7986fff32d0d90c882ed292010fc1172b1a149d22 +size 9388 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WebSafePaletteQuantizer_ErrorDither_0.75.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WebSafePaletteQuantizer_ErrorDither_0.75.png index 52ba79e98..3498290b8 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WebSafePaletteQuantizer_ErrorDither_0.75.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WebSafePaletteQuantizer_ErrorDither_0.75.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:213ebd9fc7a190ad7226b487387edb9452284193cee4c4720448d7e19ef38e76 -size 11149 +oid sha256:5e167ecea77962fa0b8376f1217cbba64c90c77e67213b229fba1c6122d2bc06 +size 11011 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WebSafePaletteQuantizer_ErrorDither_0.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WebSafePaletteQuantizer_ErrorDither_0.png index fc26d1b76..0f00a00f8 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WebSafePaletteQuantizer_ErrorDither_0.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WebSafePaletteQuantizer_ErrorDither_0.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:43b21e948795abb52ebbbf94e785542e55488cc7f17996e2b92404ca8ad1a7cb -size 7844 +oid sha256:b7fd78e11b8de40f646218597ec1ebccddbca7a6d901e5c87848ae1d9e0de361 +size 8390 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WebSafePaletteQuantizer_ErrorDither_1.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WebSafePaletteQuantizer_ErrorDither_1.png index 6f67736dd..93f1ca234 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WebSafePaletteQuantizer_ErrorDither_1.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WebSafePaletteQuantizer_ErrorDither_1.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0bb8f730a43a8f58e4269905014461bab8dc8b47387ec86a84c1064b1cdabe14 -size 11813 +oid sha256:45f3bf5ae0e8061811eebd3328f1d8f7d54dc7b213f6324148c36292509a8ee5 +size 12761 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WebSafePaletteQuantizer_OrderedDither_0.25.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WebSafePaletteQuantizer_OrderedDither_0.25.png index 22ce841b4..2b611ace6 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WebSafePaletteQuantizer_OrderedDither_0.25.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WebSafePaletteQuantizer_OrderedDither_0.25.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:7500be583beb22759dec7e5bbb2a8d2230054366bfbd0e39bf10fdc8af63eb58 -size 8925 +oid sha256:36b1193c16c585918655bae2d98e4105b92cfab66ad903995f2af6b316e8b4a4 +size 9060 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WebSafePaletteQuantizer_OrderedDither_0.5.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WebSafePaletteQuantizer_OrderedDither_0.5.png index 2457c12e9..44f717124 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WebSafePaletteQuantizer_OrderedDither_0.5.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WebSafePaletteQuantizer_OrderedDither_0.5.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c48da7aad9c0aad576293d7085f9ddb2ea90a76d53bea54cbe0cb7aef71c7bb4 -size 9136 +oid sha256:71d5c7b4addc49a13e0adb60dc53b463845957ea5df490cd476f011b6770373e +size 9633 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WebSafePaletteQuantizer_OrderedDither_0.75.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WebSafePaletteQuantizer_OrderedDither_0.75.png index 33ec41713..67fcc5bf6 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WebSafePaletteQuantizer_OrderedDither_0.75.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WebSafePaletteQuantizer_OrderedDither_0.75.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:72e6c327e98d9929a4d850905b1471fcd1191088feb38b028e4240e9f93b4996 -size 9827 +oid sha256:8f0e458b747377aad809e48fdf6d7af69b1e523e1728d2367615bb7ce90c2680 +size 9733 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WebSafePaletteQuantizer_OrderedDither_0.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WebSafePaletteQuantizer_OrderedDither_0.png index fc26d1b76..0d4f8c0f9 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WebSafePaletteQuantizer_OrderedDither_0.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WebSafePaletteQuantizer_OrderedDither_0.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:43b21e948795abb52ebbbf94e785542e55488cc7f17996e2b92404ca8ad1a7cb -size 7844 +oid sha256:08e2b6d31c842366175508fbe74c8ba546d0fa88f366ebb27279e38939b3bfa8 +size 8375 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WebSafePaletteQuantizer_OrderedDither_1.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WebSafePaletteQuantizer_OrderedDither_1.png index ea35c328c..7e124931c 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WebSafePaletteQuantizer_OrderedDither_1.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WebSafePaletteQuantizer_OrderedDither_1.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:70f8fc84d6e578822582ebee5888514dcd70c8d0da1a920f35e63a1617d1e92c -size 9841 +oid sha256:a7052b23b8d91947ba1f869f13ff99136096805e889558a741ce8e5eac3ece63 +size 9965 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WernerPaletteQuantizer_ErrorDither_0.25.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WernerPaletteQuantizer_ErrorDither_0.25.png index e2b742ca4..a54b4088b 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WernerPaletteQuantizer_ErrorDither_0.25.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WernerPaletteQuantizer_ErrorDither_0.25.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ff4d6e3a7c8e69db0d5a88b7f1c91621692962bdbcedd6b8af1311506243bcde -size 11259 +oid sha256:5d5d5023535689657840dbf63d6343f4d1070cf5ec853586447eb8465da9640a +size 11444 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WernerPaletteQuantizer_ErrorDither_0.5.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WernerPaletteQuantizer_ErrorDither_0.5.png index 61a28a9cf..7b0ab347d 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WernerPaletteQuantizer_ErrorDither_0.5.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WernerPaletteQuantizer_ErrorDither_0.5.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:520910b75cae834b9844ce896ad9ab6d0b6c33c44c7bd822ce63752aaa2a8c5a -size 12122 +oid sha256:0f9ffc543dd312b3ca9d00f5444be214edfd78dbfb1a740556da3872b33623bf +size 12062 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WernerPaletteQuantizer_ErrorDither_0.75.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WernerPaletteQuantizer_ErrorDither_0.75.png index bfaf9e30a..1cf8978bc 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WernerPaletteQuantizer_ErrorDither_0.75.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WernerPaletteQuantizer_ErrorDither_0.75.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8e1fb4bb5d1591ac6bb8a4fb2504e2e836578c9c42d8286061cf4f57f0b7f97d -size 12876 +oid sha256:038d07eb02ed4fdfabfd97a4c7f4e1f1be25ae7ea874bac0ab192a18ea6be397 +size 12760 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WernerPaletteQuantizer_ErrorDither_0.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WernerPaletteQuantizer_ErrorDither_0.png index 78576c467..b5926ee62 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WernerPaletteQuantizer_ErrorDither_0.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WernerPaletteQuantizer_ErrorDither_0.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:3f5babf5f6ae746a9b9f131ecf990f037c8e8bdb257bdbb7f6127460c9b8f98d -size 10507 +oid sha256:cc402424e236ec35f0b510451533b1566dd9c4d4dea45af93aab8279f5f5abbe +size 10880 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WernerPaletteQuantizer_ErrorDither_1.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WernerPaletteQuantizer_ErrorDither_1.png index 71d771fe3..3a24b61af 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WernerPaletteQuantizer_ErrorDither_1.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WernerPaletteQuantizer_ErrorDither_1.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:56a9088fdca14b82fa3ac3ecfb67b6b8e97e8d69b89907c8408e17861e8609e0 -size 14195 +oid sha256:92c44ac71bf6822f12c8e90e1727da8db6e802c293f7945e5e6f6a9c311faccf +size 14115 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WernerPaletteQuantizer_OrderedDither_0.25.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WernerPaletteQuantizer_OrderedDither_0.25.png index a68c99d4d..476a346c6 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WernerPaletteQuantizer_OrderedDither_0.25.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WernerPaletteQuantizer_OrderedDither_0.25.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:7d4ef129d8846e6802997c0ace90abdc7412d232e9a31fc53f25bb9f32de08f2 -size 12665 +oid sha256:fcc5ba9e73231485a4cfc3d38a6692762b079aa4057c7b32177c982a64b786f2 +size 12632 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WernerPaletteQuantizer_OrderedDither_0.5.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WernerPaletteQuantizer_OrderedDither_0.5.png index 992ae0425..e00a684ee 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WernerPaletteQuantizer_OrderedDither_0.5.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WernerPaletteQuantizer_OrderedDither_0.5.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:69b5771a313aa777341d1460db94a7b3cc74b69c44f6c9d1a1b5b3734936e795 -size 12791 +oid sha256:36f5aea2b06ca8d8f04beac2abd75a3620913e16f4ae55173b3b5cb371924d3f +size 13256 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WernerPaletteQuantizer_OrderedDither_0.75.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WernerPaletteQuantizer_OrderedDither_0.75.png index 7865b70d7..6d8df6c27 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WernerPaletteQuantizer_OrderedDither_0.75.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WernerPaletteQuantizer_OrderedDither_0.75.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a56f72c5a12e6d216a32404c996e72721b4cf350d82071a43ebe44190adef94a -size 12895 +oid sha256:d77d1c95fcc3dbe43c34bc94733eb6e8b3d007d023883f9bf18c9d9373444882 +size 13287 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WernerPaletteQuantizer_OrderedDither_0.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WernerPaletteQuantizer_OrderedDither_0.png index 78576c467..b5926ee62 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WernerPaletteQuantizer_OrderedDither_0.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WernerPaletteQuantizer_OrderedDither_0.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:3f5babf5f6ae746a9b9f131ecf990f037c8e8bdb257bdbb7f6127460c9b8f98d -size 10507 +oid sha256:cc402424e236ec35f0b510451533b1566dd9c4d4dea45af93aab8279f5f5abbe +size 10880 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WernerPaletteQuantizer_OrderedDither_1.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WernerPaletteQuantizer_OrderedDither_1.png index 140af0ed8..9ecfcb1ff 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WernerPaletteQuantizer_OrderedDither_1.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WernerPaletteQuantizer_OrderedDither_1.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:39f85cb564a257381656fd969acaeda04016e09380133f772eb029e332aeaa95 -size 13482 +oid sha256:30f8c8275afb6c61803facee006f3a181151e4d3d09a398eba60704a8e799df9 +size 13834 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WuQuantizer_ErrorDither_0.25.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WuQuantizer_ErrorDither_0.25.png index 1a53f430c..fbe6fedca 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WuQuantizer_ErrorDither_0.25.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WuQuantizer_ErrorDither_0.25.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:9d528ea896a2e56e0b8967d5ce3486078cbd7bf29e5f0d43a58ef21100147915 -size 13818 +oid sha256:b09ecc73b2366eb5ab50ac07eab5bba0643d4d26a71e4d8af6235da2ac35631c +size 15368 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WuQuantizer_ErrorDither_0.5.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WuQuantizer_ErrorDither_0.5.png index 90823958c..566e1befe 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WuQuantizer_ErrorDither_0.5.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WuQuantizer_ErrorDither_0.5.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:4e96967a2a6362f42026ab2e2cdd82437e573c16a52c5631f7495cb30615441d -size 13841 +oid sha256:20f45d86bdedce746fcd807d4abd6eda645c10064233d84ecf12613bcdcfc131 +size 15372 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WuQuantizer_ErrorDither_0.75.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WuQuantizer_ErrorDither_0.75.png index f0270cd0a..40ede8647 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WuQuantizer_ErrorDither_0.75.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WuQuantizer_ErrorDither_0.75.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d5f9e9e4f7f48681d5b240d6df6ae282c6b9e896eacc1ad73f2a097975fa8d29 -size 14060 +oid sha256:70638fa2140e8400683ef7b2914cf877cbfe81a44847035239165e81357d9f55 +size 15707 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WuQuantizer_ErrorDither_0.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WuQuantizer_ErrorDither_0.png index 1a53f430c..fbe6fedca 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WuQuantizer_ErrorDither_0.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WuQuantizer_ErrorDither_0.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:9d528ea896a2e56e0b8967d5ce3486078cbd7bf29e5f0d43a58ef21100147915 -size 13818 +oid sha256:b09ecc73b2366eb5ab50ac07eab5bba0643d4d26a71e4d8af6235da2ac35631c +size 15368 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WuQuantizer_ErrorDither_1.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WuQuantizer_ErrorDither_1.png index 636f8299a..be922bfe5 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WuQuantizer_ErrorDither_1.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WuQuantizer_ErrorDither_1.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:97d6923c3342d600a9a2ab3fa713136b2649b74364d6e90a905de607838e0cb7 -size 14319 +oid sha256:a33e1510a59a7abd38ba4e97d18428aa76bc6bdad9016f2d279b996a8f0ea0a3 +size 16040 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WuQuantizer_OrderedDither_0.25.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WuQuantizer_OrderedDither_0.25.png index f287f604e..2debe15ab 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WuQuantizer_OrderedDither_0.25.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WuQuantizer_OrderedDither_0.25.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:66399a4ba262bdacdc5966fa9fc6f8f6bfa3f8c70db889363e9bbd5778dc1ecf -size 16081 +oid sha256:9a209b60feda34e2078fe0609e8e32a67901d1ec0d99fe500e4d7968031f62df +size 17253 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WuQuantizer_OrderedDither_0.5.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WuQuantizer_OrderedDither_0.5.png index f6a36e203..447ffb0ee 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WuQuantizer_OrderedDither_0.5.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WuQuantizer_OrderedDither_0.5.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:5953a12a2fa67f9fe17485c62d156bb7f80a4bcf4124c123db4440c2559e0e69 -size 17002 +oid sha256:5383256fd20787dcce51bf8d13af94172ea47e2bda8ae1285768d422c3d08f61 +size 17531 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WuQuantizer_OrderedDither_0.75.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WuQuantizer_OrderedDither_0.75.png index 03519e032..b991ad659 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WuQuantizer_OrderedDither_0.75.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WuQuantizer_OrderedDither_0.75.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:efc08c6969344de404692c2b367130e3f736442c0722067a9105f036a9e4511e -size 17616 +oid sha256:b754d74a036eff2a56ebea8fce81d952db54f05c6ec247cfe5bb76eacbf0c33f +size 17949 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WuQuantizer_OrderedDither_0.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WuQuantizer_OrderedDither_0.png index 1a53f430c..fbe6fedca 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WuQuantizer_OrderedDither_0.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WuQuantizer_OrderedDither_0.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:9d528ea896a2e56e0b8967d5ce3486078cbd7bf29e5f0d43a58ef21100147915 -size 13818 +oid sha256:b09ecc73b2366eb5ab50ac07eab5bba0643d4d26a71e4d8af6235da2ac35631c +size 15368 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WuQuantizer_OrderedDither_1.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WuQuantizer_OrderedDither_1.png index c4c8d7aac..cd3527df3 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WuQuantizer_OrderedDither_1.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WuQuantizer_OrderedDither_1.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:455488369cd943f3c3a0b2c402dbb17d6fc9c384b5f26dbc049634ad4bdab73f -size 18259 +oid sha256:fdf77c8222a40afcfdda5edeb87b7921c71b1368ac7f91cc560e8d73676fdb33 +size 18115 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_OctreeQuantizer_ErrorDither.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_OctreeQuantizer_ErrorDither.png index ce6858e2d..4013073c1 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_OctreeQuantizer_ErrorDither.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_OctreeQuantizer_ErrorDither.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:2a65ffa4c6b8488f52892306052337e17a4e58d28fa43f18009e6d1f997962de -size 83060 +oid sha256:374a09cf2f904bc512bafb1d280f5de484bf101bb7d24d4dfef0f5fe22f2ed39 +size 82110 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_OctreeQuantizer_NoDither.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_OctreeQuantizer_NoDither.png index 651ed382f..f8c459512 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_OctreeQuantizer_NoDither.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_OctreeQuantizer_NoDither.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1f69909cdc4b65548834e30caf44a31b7e5d41be1db05cb43bd8277613f327ed -size 55263 +oid sha256:2a4564e26e53e6562ca4dfa44e671150903e022db4f6ec76038627bca130bbe9 +size 54177 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_OctreeQuantizer_OrderedDither.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_OctreeQuantizer_OrderedDither.png index 501074f23..834c5ffe6 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_OctreeQuantizer_OrderedDither.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_OctreeQuantizer_OrderedDither.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ff7d35f1ce04d8f2b6cee41c951487bd24e18f923dc84c77a7944ec3aab61540 -size 80844 +oid sha256:00ede0da073e60deba1f0eb7cc5f3da0902b44dfcea71cbf33a56fc74955823b +size 79449 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_WebSafePaletteQuantizer_ErrorDither.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_WebSafePaletteQuantizer_ErrorDither.png index 08ddd2def..7ceb6114a 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_WebSafePaletteQuantizer_ErrorDither.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_WebSafePaletteQuantizer_ErrorDither.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:48ac9eea396e010b1c110413a6861b1708f3928243271ea78f390e64dfe11737 -size 62249 +oid sha256:7ce4b978b800820275635ae8da59212661c74a1b6ba9050ebf052c87315aedef +size 62107 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_WebSafePaletteQuantizer_NoDither.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_WebSafePaletteQuantizer_NoDither.png index da68bff3c..570db6a3a 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_WebSafePaletteQuantizer_NoDither.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_WebSafePaletteQuantizer_NoDither.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a4786eb2c04be00302996f3ad65987f54fe5d80ded438fdcccf7b9bfe9520dbb -size 33930 +oid sha256:602181e876201b10f75650eb07961fa499c05009067c7d298d4826e1503b93ca +size 33091 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_WebSafePaletteQuantizer_OrderedDither.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_WebSafePaletteQuantizer_OrderedDither.png index 5f9b599b7..9710298a3 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_WebSafePaletteQuantizer_OrderedDither.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_WebSafePaletteQuantizer_OrderedDither.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ccdf5937c30999e3b09071200de2e1db63b606ad9cbf6f7677a7499fb0b52963 -size 44252 +oid sha256:a2a534bc0862e0d7325e9d6bbdf1c73ddc356bd621dfdc64a9c0c04399a93e00 +size 43807 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_WernerPaletteQuantizer_ErrorDither.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_WernerPaletteQuantizer_ErrorDither.png index 60e5f18dc..062113ae6 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_WernerPaletteQuantizer_ErrorDither.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_WernerPaletteQuantizer_ErrorDither.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:898c8524d6fcad8498a22dd97956265302458d43f8d3f93846c2268d7b47fb73 -size 35351 +oid sha256:8906591aa17712c67939d40ada949e10c8de0cdaac5186a88db1199ac1819bd3 +size 33835 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_WernerPaletteQuantizer_NoDither.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_WernerPaletteQuantizer_NoDither.png index cf7cba181..8fd221575 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_WernerPaletteQuantizer_NoDither.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_WernerPaletteQuantizer_NoDither.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:5f37abc44b703b6feac3a2a950df7f40a8a67fe64fa25c19af59862e5272f0ff -size 34311 +oid sha256:545a01286eff5b5835e79b595fe482f2f5e571972dfdbff651eb5db2944d7889 +size 33045 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_WernerPaletteQuantizer_OrderedDither.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_WernerPaletteQuantizer_OrderedDither.png index 8ddd2cf00..877ed8778 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_WernerPaletteQuantizer_OrderedDither.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_WernerPaletteQuantizer_OrderedDither.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1cb4518f6b9e1ed1409b0f4e87c17ae990cff2725c650d69941cc76ba90b2f7b -size 44672 +oid sha256:2f34ecadac596bccfb2fcc5cc7f070ea98917a30d036bee558775c01737c72d2 +size 43016 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_WuQuantizer_ErrorDither.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_WuQuantizer_ErrorDither.png index e2fc05dd9..7869d6f13 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_WuQuantizer_ErrorDither.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_WuQuantizer_ErrorDither.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:16a592718ba5aa15f79ef4864cba75fae5a7cc5e13ca56eeab5fd13a0e5347de -size 102049 +oid sha256:d713b040dbe51d9034cd7c3b1055b64a21c9146e6417c7c35f1012f4093b872c +size 104792 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_WuQuantizer_NoDither.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_WuQuantizer_NoDither.png index fb44b61f9..d6f69a5bd 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_WuQuantizer_NoDither.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_WuQuantizer_NoDither.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:86ebb09209d4bfbe266c633df1c7062755cd413f47d0a96a05bcf003a02cb12b -size 84428 +oid sha256:0773406e402e8c30bb345de5e4e20f2546a3ad692b9ad0b29dfe3df8c659cf4b +size 83576 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_WuQuantizer_OrderedDither.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_WuQuantizer_OrderedDither.png index b4ff7af8d..c8d7908ef 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_WuQuantizer_OrderedDither.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_WuQuantizer_OrderedDither.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:779521e132b4ee6b3ac7a9186fca3250c6d2ace7e55f4f6c9601fe5393d9eb10 -size 99561 +oid sha256:6b75b9c856570b0b2bfd8826eb8771bf15b74f309ee70aae72a607e96040e254 +size 99413 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_OctreeQuantizer_ErrorDither.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_OctreeQuantizer_ErrorDither.png index 8b5956b1a..b56b714e8 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_OctreeQuantizer_ErrorDither.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_OctreeQuantizer_ErrorDither.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a6d0b12e19b980fd558a4920ec18620a9bdb00bd1379a20719ff0ee92c6887a1 -size 96007 +oid sha256:7a609150bfad93dc56bf9e1c3214ea3eabec48dd73939945840656de544bd423 +size 94976 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_OctreeQuantizer_NoDither.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_OctreeQuantizer_NoDither.png index e1122365e..bd2913174 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_OctreeQuantizer_NoDither.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_OctreeQuantizer_NoDither.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:82c34b115c081a1f3723540b9a273724353930c53006dd6daea576a227271e01 -size 78599 +oid sha256:b06aa4d0f3caca205cb5adcdcf26bf6ec6157febdd9266a3fcda9179deaf0840 +size 77036 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_OctreeQuantizer_OrderedDither.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_OctreeQuantizer_OrderedDither.png index 30cfcf2de..ab2a2e3f6 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_OctreeQuantizer_OrderedDither.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_OctreeQuantizer_OrderedDither.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6fbe71277a196e5c57fba7caba5c0a1b2bff2da9eadbd7bab1ae1853fab2dd93 -size 92581 +oid sha256:66c47408a07b7e32d7cabc64289aa8f16b0c96e5ca4c478ebae48729ff55b229 +size 91645 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_WebSafePaletteQuantizer_ErrorDither.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_WebSafePaletteQuantizer_ErrorDither.png index d79f324a7..036448778 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_WebSafePaletteQuantizer_ErrorDither.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_WebSafePaletteQuantizer_ErrorDither.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b1061a363744661febdd051ef2f6839bfe288eb83f7ebf281fb06717fbe6703a -size 60739 +oid sha256:ded9cb4986e620f5fc11486ccdf81934dff7b3cea7db7e292929d339ecc21714 +size 60116 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_WebSafePaletteQuantizer_NoDither.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_WebSafePaletteQuantizer_NoDither.png index 03baa702a..a798459cb 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_WebSafePaletteQuantizer_NoDither.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_WebSafePaletteQuantizer_NoDither.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:cacd80b681c086310d3be7d90575da357e6ab0a62e24227a1e7e3ae4cab1de2d -size 46905 +oid sha256:5da4f2f3024fe3c8ae7f0ce6113f78c74d699eb4efe41f1d9df875af05d09547 +size 46806 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_WebSafePaletteQuantizer_OrderedDither.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_WebSafePaletteQuantizer_OrderedDither.png index 35ab5b75b..687cd1dbc 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_WebSafePaletteQuantizer_OrderedDither.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_WebSafePaletteQuantizer_OrderedDither.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b58144146585f50960dfd6ac5dc3f52238160287ae5f9b18c6796962cc3d2fd2 -size 51550 +oid sha256:e2d47f277bc904ed8c3b3bbfe636b647884511de39661bfd34cdcb2abe9f6a13 +size 50818 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_WernerPaletteQuantizer_ErrorDither.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_WernerPaletteQuantizer_ErrorDither.png index dfcc0603d..8e0f6002c 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_WernerPaletteQuantizer_ErrorDither.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_WernerPaletteQuantizer_ErrorDither.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d54a2b762a16c76ad52919708d0126ae63958e9d900e3870cced695540e16192 -size 68366 +oid sha256:4324dc77448aeb0ee4ebe39c449ddedff81bf6f0243e0c4713ae89a18b13f0b8 +size 67273 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_WernerPaletteQuantizer_NoDither.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_WernerPaletteQuantizer_NoDither.png index c1c66f840..33c9d4324 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_WernerPaletteQuantizer_NoDither.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_WernerPaletteQuantizer_NoDither.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:87450152fdccfaebb55e22040206cb246bc59b61461fd8b6ca6e099256fa0f1d -size 63839 +oid sha256:eb0a2e6d50625ae9a67f9d36d3296f65e41bb6d5a7d3e8163dba89333e3ac6a3 +size 63442 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_WernerPaletteQuantizer_OrderedDither.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_WernerPaletteQuantizer_OrderedDither.png index f79a145a6..f3900e5f6 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_WernerPaletteQuantizer_OrderedDither.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_WernerPaletteQuantizer_OrderedDither.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:7136f92f3fd1927e98204b1140ca36a901dcd9b67c17f7080413588c2e2dcc28 -size 69579 +oid sha256:6551261b6b8c4cf4f69ba2860e163fe10fc9f2660cbb43c1423a9b5dd9456040 +size 68497 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_WuQuantizer_ErrorDither.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_WuQuantizer_ErrorDither.png index 79cec59e5..6203feb92 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_WuQuantizer_ErrorDither.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_WuQuantizer_ErrorDither.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ab5972eaa6b008b9768f3af4c61957db3d6da41cdf52696c05ecd3f2efaf3d5f -size 113964 +oid sha256:5f5cf2a5917f8edf77cfc78b8d923bda672435da095876390bd14212bfd87d75 +size 112571 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_WuQuantizer_NoDither.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_WuQuantizer_NoDither.png index 159e284c5..1625e1686 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_WuQuantizer_NoDither.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_WuQuantizer_NoDither.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:46c6b9eb83c6d4ffafc2163b0e0ccd5aa24ac56bb65e8cf5f02b80319cf29e4b -size 108931 +oid sha256:abd3ad65543bf9bdfb9924652cc107b23c80db3e210d2b994fd575ea73475aa7 +size 108014 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_WuQuantizer_OrderedDither.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_WuQuantizer_OrderedDither.png index c11f7ac79..19a672d80 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_WuQuantizer_OrderedDither.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_WuQuantizer_OrderedDither.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f54b00e54e8786c9c9ff8e8cf7e42111e06e5dc5d2e69ee2c8c8be2353030e43 -size 114680 +oid sha256:a27a605acbd026ba475fae9110c69c6002a399ef3747ce7ca5c060a6df5a078b +size 114379 From 4f8ea7f677d2b857aedd87b0644c93dfe8e3592b Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Fri, 24 Nov 2023 23:11:40 +1000 Subject: [PATCH 08/24] Tweak bounds clamping --- src/ImageSharp/Formats/AnimationUtilities.cs | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/ImageSharp/Formats/AnimationUtilities.cs b/src/ImageSharp/Formats/AnimationUtilities.cs index b66efd7f5..a3a5292a2 100644 --- a/src/ImageSharp/Formats/AnimationUtilities.cs +++ b/src/ImageSharp/Formats/AnimationUtilities.cs @@ -148,11 +148,12 @@ internal static class AnimationUtilities PixelOperations.Instance.FromVector4Destructive(configuration, result, resultFrame.DangerousGetPixelRowMemory(y).Span, PixelConversionModifiers.Scale); } - Rectangle bounds = Rectangle.FromLTRB( - left = Numerics.Clamp(left, 0, resultFrame.Width - 1), - top = Numerics.Clamp(top, 0, resultFrame.Height - 1), - Numerics.Clamp(right, left + 1, resultFrame.Width), - Numerics.Clamp(bottom, top + 1, resultFrame.Height)); + left = Math.Max(0, Math.Min(left, resultFrame.Width - 1)); + top = Math.Max(0, Math.Min(top, resultFrame.Height - 1)); + right = Math.Max(left + 1, Math.Min(right, resultFrame.Width)); + bottom = Math.Max(top + 1, Math.Min(bottom, resultFrame.Height)); + + Rectangle bounds = Rectangle.FromLTRB(left, top, right, bottom); // Webp requires even bounds if (clampingMode == ClampingMode.Even) From c306c56ce5616fc4ed12d469bc0f29bfb7a0b15e Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Sat, 25 Nov 2023 16:50:32 +1000 Subject: [PATCH 09/24] Use correct buffer dimensions --- src/ImageSharp/Formats/AnimationUtilities.cs | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/src/ImageSharp/Formats/AnimationUtilities.cs b/src/ImageSharp/Formats/AnimationUtilities.cs index a3a5292a2..82003d4b6 100644 --- a/src/ImageSharp/Formats/AnimationUtilities.cs +++ b/src/ImageSharp/Formats/AnimationUtilities.cs @@ -148,12 +148,11 @@ internal static class AnimationUtilities PixelOperations.Instance.FromVector4Destructive(configuration, result, resultFrame.DangerousGetPixelRowMemory(y).Span, PixelConversionModifiers.Scale); } - left = Math.Max(0, Math.Min(left, resultFrame.Width - 1)); - top = Math.Max(0, Math.Min(top, resultFrame.Height - 1)); - right = Math.Max(left + 1, Math.Min(right, resultFrame.Width)); - bottom = Math.Max(top + 1, Math.Min(bottom, resultFrame.Height)); - - Rectangle bounds = Rectangle.FromLTRB(left, top, right, bottom); + Rectangle bounds = Rectangle.FromLTRB( + left = Numerics.Clamp(left, 0, resultFrame.Width - 1), + top = Numerics.Clamp(top, 0, resultFrame.Height - 1), + Numerics.Clamp(right, left + 1, resultFrame.Width), + Numerics.Clamp(bottom, top + 1, resultFrame.Height)); // Webp requires even bounds if (clampingMode == ClampingMode.Even) @@ -172,7 +171,7 @@ internal static class AnimationUtilities { Buffer2DRegion sourceBuffer = source.PixelBuffer.GetRegion(bounds); Buffer2DRegion destBuffer = destination.PixelBuffer.GetRegion(bounds); - for (int y = 0; y < destination.Height; y++) + for (int y = 0; y < destBuffer.Height; y++) { Span sourceRow = sourceBuffer.DangerousGetRowSpan(y); Span destRow = destBuffer.DangerousGetRowSpan(y); From 6cda7b03c66b8146c1fd7d73e32c578cc1247b3c Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Sun, 26 Nov 2023 22:13:40 +1000 Subject: [PATCH 10/24] Fix scanline lengths --- src/ImageSharp/Formats/Gif/GifEncoderCore.cs | 2 +- src/ImageSharp/Formats/Png/PngDecoderCore.cs | 18 ++++++++++-------- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/src/ImageSharp/Formats/Gif/GifEncoderCore.cs b/src/ImageSharp/Formats/Gif/GifEncoderCore.cs index 24bb3c00e..37e31f32d 100644 --- a/src/ImageSharp/Formats/Gif/GifEncoderCore.cs +++ b/src/ImageSharp/Formats/Gif/GifEncoderCore.cs @@ -176,7 +176,7 @@ internal sealed class GifEncoderCore : IImageEncoderInternals { if (image.Metadata.TryGetGifMetadata(out GifMetadata? gif)) { - return gif; + return (GifMetadata)gif.DeepClone(); } if (image.Metadata.TryGetPngMetadata(out PngMetadata? png)) diff --git a/src/ImageSharp/Formats/Png/PngDecoderCore.cs b/src/ImageSharp/Formats/Png/PngDecoderCore.cs index cb4f599eb..a1be79759 100644 --- a/src/ImageSharp/Formats/Png/PngDecoderCore.cs +++ b/src/ImageSharp/Formats/Png/PngDecoderCore.cs @@ -764,10 +764,12 @@ internal sealed class PngDecoderCore : IImageDecoderInternals { cancellationToken.ThrowIfCancellationRequested(); int bytesPerFrameScanline = this.CalculateScanlineLength((int)frameControl.Width) + 1; - Span scanlineSpan = this.scanline.GetSpan()[..bytesPerFrameScanline]; + Span scanSpan = this.scanline.GetSpan()[..bytesPerFrameScanline]; + Span prevSpan = this.scanline.GetSpan()[..bytesPerFrameScanline]; + while (currentRowBytesRead < bytesPerFrameScanline) { - int bytesRead = compressedStream.Read(scanlineSpan, currentRowBytesRead, bytesPerFrameScanline - currentRowBytesRead); + int bytesRead = compressedStream.Read(scanSpan, currentRowBytesRead, bytesPerFrameScanline - currentRowBytesRead); if (bytesRead <= 0) { return; @@ -778,25 +780,25 @@ internal sealed class PngDecoderCore : IImageDecoderInternals currentRowBytesRead = 0; - switch ((FilterType)scanlineSpan[0]) + switch ((FilterType)scanSpan[0]) { case FilterType.None: break; case FilterType.Sub: - SubFilter.Decode(scanlineSpan, this.bytesPerPixel); + SubFilter.Decode(scanSpan, this.bytesPerPixel); break; case FilterType.Up: - UpFilter.Decode(scanlineSpan, this.previousScanline.GetSpan()); + UpFilter.Decode(scanSpan, prevSpan); break; case FilterType.Average: - AverageFilter.Decode(scanlineSpan, this.previousScanline.GetSpan(), this.bytesPerPixel); + AverageFilter.Decode(scanSpan, prevSpan, this.bytesPerPixel); break; case FilterType.Paeth: - PaethFilter.Decode(scanlineSpan, this.previousScanline.GetSpan(), this.bytesPerPixel); + PaethFilter.Decode(scanSpan, prevSpan, this.bytesPerPixel); break; default: @@ -804,7 +806,7 @@ internal sealed class PngDecoderCore : IImageDecoderInternals break; } - this.ProcessDefilteredScanline(frameControl, currentRow, scanlineSpan, imageFrame, pngMetadata, blendRowBuffer); + this.ProcessDefilteredScanline(frameControl, currentRow, scanSpan, imageFrame, pngMetadata, blendRowBuffer); this.SwapScanlineBuffers(); currentRow++; } From 90fa817135295b1c2a89a98f8ebbb0f63108285e Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Sun, 26 Nov 2023 22:40:10 +1000 Subject: [PATCH 11/24] Update PngDecoderCore.cs --- src/ImageSharp/Formats/Png/PngDecoderCore.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ImageSharp/Formats/Png/PngDecoderCore.cs b/src/ImageSharp/Formats/Png/PngDecoderCore.cs index a1be79759..5bbfc39e3 100644 --- a/src/ImageSharp/Formats/Png/PngDecoderCore.cs +++ b/src/ImageSharp/Formats/Png/PngDecoderCore.cs @@ -765,7 +765,7 @@ internal sealed class PngDecoderCore : IImageDecoderInternals cancellationToken.ThrowIfCancellationRequested(); int bytesPerFrameScanline = this.CalculateScanlineLength((int)frameControl.Width) + 1; Span scanSpan = this.scanline.GetSpan()[..bytesPerFrameScanline]; - Span prevSpan = this.scanline.GetSpan()[..bytesPerFrameScanline]; + Span prevSpan = this.previousScanline.GetSpan()[..bytesPerFrameScanline]; while (currentRowBytesRead < bytesPerFrameScanline) { From 4029b15bd670f220e32167d50bc53c0c10e7bf31 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Sun, 26 Nov 2023 22:41:20 +1000 Subject: [PATCH 12/24] Remove duplicate condition check --- src/ImageSharp/Formats/AnimationUtilities.cs | 60 ++++++++++---------- 1 file changed, 29 insertions(+), 31 deletions(-) diff --git a/src/ImageSharp/Formats/AnimationUtilities.cs b/src/ImageSharp/Formats/AnimationUtilities.cs index 82003d4b6..486b4027a 100644 --- a/src/ImageSharp/Formats/AnimationUtilities.cs +++ b/src/ImageSharp/Formats/AnimationUtilities.cs @@ -74,40 +74,38 @@ internal static class AnimationUtilities uint x = 0; int length = current.Length; int remaining = current.Length; - if (Avx2.IsSupported && remaining >= 2) + + while (Avx2.IsSupported && remaining >= 2) { - while (remaining >= 2) + Vector256 p = Unsafe.Add(ref previousBase, x); + Vector256 c = Unsafe.Add(ref currentBase, x); + + // Compare the previous and current pixels + Vector256 mask = Avx2.CompareEqual(p.AsInt32(), c.AsInt32()); + mask = Avx2.CompareEqual(mask.AsInt64(), Vector256.AllBitsSet).AsInt32(); + mask = Avx2.And(mask, Avx2.Shuffle(mask, 0b_01_00_11_10)).AsInt32(); + + Vector256 neq = Avx2.Xor(mask.AsInt64(), Vector256.AllBitsSet).AsInt32(); + int m = Avx2.MoveMask(neq.AsByte()); + if (m != 0) { - Vector256 p = Unsafe.Add(ref previousBase, x); - Vector256 c = Unsafe.Add(ref currentBase, x); - - // Compare the previous and current pixels - Vector256 mask = Avx2.CompareEqual(p.AsInt32(), c.AsInt32()); - mask = Avx2.CompareEqual(mask.AsInt64(), Vector256.AllBitsSet).AsInt32(); - mask = Avx2.And(mask, Avx2.Shuffle(mask, 0b_01_00_11_10)).AsInt32(); - - Vector256 neq = Avx2.Xor(mask.AsInt64(), Vector256.AllBitsSet).AsInt32(); - int m = Avx2.MoveMask(neq.AsByte()); - if (m != 0) - { - // If is diff is found, the left side is marked by the min of previously found left side and the start position. - // The right is the max of the previously found right side and the end position. - int start = i + (BitOperations.TrailingZeroCount(m) / size); - int end = i + (2 - (BitOperations.LeadingZeroCount((uint)m) / size)); - left = Math.Min(left, start); - right = Math.Max(right, end); - hasRowDiff = true; - hasDiff = true; - } - - // Replace the pixel value with the replacement if the full pixel is matched. - Vector256 r = Avx.BlendVariable(c, replacement256, mask.AsSingle()); - Unsafe.Add(ref resultBase, x) = r; - - x++; - i += 2; - remaining -= 2; + // If is diff is found, the left side is marked by the min of previously found left side and the start position. + // The right is the max of the previously found right side and the end position. + int start = i + (BitOperations.TrailingZeroCount(m) / size); + int end = i + (2 - (BitOperations.LeadingZeroCount((uint)m) / size)); + left = Math.Min(left, start); + right = Math.Max(right, end); + hasRowDiff = true; + hasDiff = true; } + + // Replace the pixel value with the replacement if the full pixel is matched. + Vector256 r = Avx.BlendVariable(c, replacement256, mask.AsSingle()); + Unsafe.Add(ref resultBase, x) = r; + + x++; + i += 2; + remaining -= 2; } for (i = remaining; i > 0; i--) From 44b031132e69a60f317b0361c43d1998420d83ef Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Sun, 26 Nov 2023 22:43:25 +1000 Subject: [PATCH 13/24] Remove "new" --- src/ImageSharp/Formats/AnimationUtilities.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ImageSharp/Formats/AnimationUtilities.cs b/src/ImageSharp/Formats/AnimationUtilities.cs index 486b4027a..4e322d2a2 100644 --- a/src/ImageSharp/Formats/AnimationUtilities.cs +++ b/src/ImageSharp/Formats/AnimationUtilities.cs @@ -161,7 +161,7 @@ internal static class AnimationUtilities bounds.Y = Math.Max(0, bounds.Y - (bounds.Y & 1)); } - return new(hasDiff, bounds); + return (hasDiff, bounds); } public static void CopySource(ImageFrame source, ImageFrame destination, Rectangle bounds) From 55e69c7bd24c10b48c744ffcda6db8c164bd483d Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Sun, 26 Nov 2023 23:00:38 +1000 Subject: [PATCH 14/24] Try disabling new high memory tests on failing platforms. --- tests/ImageSharp.Tests/Formats/Gif/GifEncoderTests.cs | 10 ++++++++++ tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs | 10 ++++++++++ .../ImageSharp.Tests/Formats/WebP/WebpEncoderTests.cs | 10 ++++++++++ 3 files changed, 30 insertions(+) diff --git a/tests/ImageSharp.Tests/Formats/Gif/GifEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Gif/GifEncoderTests.cs index d6d63baab..a7e16f773 100644 --- a/tests/ImageSharp.Tests/Formats/Gif/GifEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Gif/GifEncoderTests.cs @@ -275,6 +275,11 @@ public class GifEncoderTests public void Encode_AnimatedFormatTransform_FromPng(TestImageProvider provider) where TPixel : unmanaged, IPixel { + if (TestEnvironment.RunsOnCI && !TestEnvironment.IsWindows) + { + return; + } + using Image image = provider.GetImage(PngDecoder.Instance); using MemoryStream memStream = new(); @@ -318,6 +323,11 @@ public class GifEncoderTests public void Encode_AnimatedFormatTransform_FromWebp(TestImageProvider provider) where TPixel : unmanaged, IPixel { + if (TestEnvironment.RunsOnCI && !TestEnvironment.IsWindows) + { + return; + } + using Image image = provider.GetImage(WebpDecoder.Instance); using MemoryStream memStream = new(); diff --git a/tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs index f8fc774b7..6679a765f 100644 --- a/tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs @@ -483,6 +483,11 @@ public partial class PngEncoderTests public void Encode_AnimatedFormatTransform_FromGif(TestImageProvider provider) where TPixel : unmanaged, IPixel { + if (TestEnvironment.RunsOnCI && !TestEnvironment.IsWindows) + { + return; + } + using Image image = provider.GetImage(GifDecoder.Instance); using MemoryStream memStream = new(); @@ -530,6 +535,11 @@ public partial class PngEncoderTests public void Encode_AnimatedFormatTransform_FromWebp(TestImageProvider provider) where TPixel : unmanaged, IPixel { + if (TestEnvironment.RunsOnCI && !TestEnvironment.IsWindows) + { + return; + } + using Image image = provider.GetImage(WebpDecoder.Instance); using MemoryStream memStream = new(); diff --git a/tests/ImageSharp.Tests/Formats/WebP/WebpEncoderTests.cs b/tests/ImageSharp.Tests/Formats/WebP/WebpEncoderTests.cs index 6baacb38c..acca49dcf 100644 --- a/tests/ImageSharp.Tests/Formats/WebP/WebpEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/WebP/WebpEncoderTests.cs @@ -67,6 +67,11 @@ public class WebpEncoderTests public void Encode_AnimatedFormatTransform_FromGif(TestImageProvider provider) where TPixel : unmanaged, IPixel { + if (TestEnvironment.RunsOnCI && !TestEnvironment.IsWindows) + { + return; + } + using Image image = provider.GetImage(GifDecoder.Instance); using MemoryStream memStream = new(); @@ -109,6 +114,11 @@ public class WebpEncoderTests public void Encode_AnimatedFormatTransform_FromPng(TestImageProvider provider) where TPixel : unmanaged, IPixel { + if (TestEnvironment.RunsOnCI && !TestEnvironment.IsWindows) + { + return; + } + using Image image = provider.GetImage(PngDecoder.Instance); using MemoryStream memStream = new(); From a6f96f775e5724ea1fa99cddb2db7b3f5da7aee7 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Tue, 28 Nov 2023 22:39:09 +1000 Subject: [PATCH 15/24] Optimize and fix deduper --- src/ImageSharp/Formats/AnimationUtilities.cs | 142 +++++++++--------- src/ImageSharp/Formats/Gif/GifEncoderCore.cs | 14 +- src/ImageSharp/Formats/Png/PngEncoderCore.cs | 28 ++-- .../Formats/Webp/WebpEncoderCore.cs | 58 +++---- tests/ImageSharp.Tests/TestImages.cs | 2 + tests/Images/Input/Gif/m4nb.gif | 3 + 6 files changed, 137 insertions(+), 110 deletions(-) create mode 100644 tests/Images/Input/Gif/m4nb.gif diff --git a/src/ImageSharp/Formats/AnimationUtilities.cs b/src/ImageSharp/Formats/AnimationUtilities.cs index 4e322d2a2..23fc40cdf 100644 --- a/src/ImageSharp/Formats/AnimationUtilities.cs +++ b/src/ImageSharp/Formats/AnimationUtilities.cs @@ -25,26 +25,31 @@ internal static class AnimationUtilities /// The configuration. /// The previous frame if present. /// The current frame. + /// The next frame if present. /// The resultant output. /// The value to use when replacing duplicate pixels. + /// Whether the resultant frame represents an animation blend. /// The clamping bound to apply when calculating difference bounds. /// The representing the operation result. public static (bool Difference, Rectangle Bounds) DeDuplicatePixels( Configuration configuration, ImageFrame? previousFrame, ImageFrame currentFrame, + ImageFrame? nextFrame, ImageFrame resultFrame, - Vector4 replacement, + Color replacement, + bool blend, ClampingMode clampingMode = ClampingMode.None) where TPixel : unmanaged, IPixel { - // TODO: This would be faster (but more complicated to find diff bounds) if we operated on Rgba32. - // If someone wants to do that, they have my unlimited thanks. MemoryAllocator memoryAllocator = configuration.MemoryAllocator; - IMemoryOwner buffers = memoryAllocator.Allocate(currentFrame.Width * 3, AllocationOptions.Clean); - Span previous = buffers.GetSpan()[..currentFrame.Width]; - Span current = buffers.GetSpan().Slice(currentFrame.Width, currentFrame.Width); - Span result = buffers.GetSpan()[(currentFrame.Width * 2)..]; + IMemoryOwner buffers = memoryAllocator.Allocate(currentFrame.Width * 4, AllocationOptions.Clean); + Span previous = buffers.GetSpan()[..currentFrame.Width]; + Span current = buffers.GetSpan().Slice(currentFrame.Width, currentFrame.Width); + Span next = buffers.GetSpan().Slice(currentFrame.Width * 2, currentFrame.Width); + Span result = buffers.GetSpan()[(currentFrame.Width * 3)..]; + + Rgba32 bg = replacement; int top = int.MinValue; int bottom = int.MaxValue; @@ -56,70 +61,90 @@ internal static class AnimationUtilities { if (previousFrame != null) { - PixelOperations.Instance.ToVector4(configuration, previousFrame.DangerousGetPixelRowMemory(y).Span, previous, PixelConversionModifiers.Scale); + PixelOperations.Instance.ToRgba32(configuration, previousFrame.DangerousGetPixelRowMemory(y).Span, previous); } - PixelOperations.Instance.ToVector4(configuration, currentFrame.DangerousGetPixelRowMemory(y).Span, current, PixelConversionModifiers.Scale); - - ref Vector256 previousBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(previous)); - ref Vector256 currentBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(current)); - ref Vector256 resultBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(result)); + PixelOperations.Instance.ToRgba32(configuration, currentFrame.DangerousGetPixelRowMemory(y).Span, current); - Vector256 replacement256 = Vector256.Create(replacement.X, replacement.Y, replacement.Z, replacement.W, replacement.X, replacement.Y, replacement.Z, replacement.W); + if (nextFrame != null) + { + PixelOperations.Instance.ToRgba32(configuration, nextFrame.DangerousGetPixelRowMemory(y).Span, next); + } - int size = Unsafe.SizeOf(); + ref Vector256 previousBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(previous)); + ref Vector256 currentBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(current)); + ref Vector256 nextBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(next)); + ref Vector256 resultBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(result)); - bool hasRowDiff = false; int i = 0; uint x = 0; + bool hasRowDiff = false; int length = current.Length; int remaining = current.Length; - while (Avx2.IsSupported && remaining >= 2) + if (Avx2.IsSupported && remaining >= 8) { - Vector256 p = Unsafe.Add(ref previousBase, x); - Vector256 c = Unsafe.Add(ref currentBase, x); - - // Compare the previous and current pixels - Vector256 mask = Avx2.CompareEqual(p.AsInt32(), c.AsInt32()); - mask = Avx2.CompareEqual(mask.AsInt64(), Vector256.AllBitsSet).AsInt32(); - mask = Avx2.And(mask, Avx2.Shuffle(mask, 0b_01_00_11_10)).AsInt32(); - - Vector256 neq = Avx2.Xor(mask.AsInt64(), Vector256.AllBitsSet).AsInt32(); - int m = Avx2.MoveMask(neq.AsByte()); - if (m != 0) + Vector256 r256 = previousFrame != null ? Vector256.Create(bg.PackedValue) : Vector256.Zero; + Vector256 vmb256 = Vector256.Zero; + if (blend) { - // If is diff is found, the left side is marked by the min of previously found left side and the start position. - // The right is the max of the previously found right side and the end position. - int start = i + (BitOperations.TrailingZeroCount(m) / size); - int end = i + (2 - (BitOperations.LeadingZeroCount((uint)m) / size)); - left = Math.Min(left, start); - right = Math.Max(right, end); - hasRowDiff = true; - hasDiff = true; + vmb256 = Avx2.CompareEqual(vmb256, vmb256); } - // Replace the pixel value with the replacement if the full pixel is matched. - Vector256 r = Avx.BlendVariable(c, replacement256, mask.AsSingle()); - Unsafe.Add(ref resultBase, x) = r; - - x++; - i += 2; - remaining -= 2; + while (remaining >= 8) + { + Vector256 p = Unsafe.Add(ref previousBase, x).AsUInt32(); + Vector256 c = Unsafe.Add(ref currentBase, x).AsUInt32(); + + Vector256 eq = Avx2.CompareEqual(p, c); + Vector256 r = Avx2.BlendVariable(c, r256, Avx2.And(eq, vmb256)); + + if (nextFrame != null) + { + Vector256 n = Avx2.ShiftRightLogical(Unsafe.Add(ref nextBase, x).AsUInt32(), 24).AsInt32(); + eq = Avx2.AndNot(Avx2.CompareGreaterThan(Avx2.ShiftRightLogical(c, 24).AsInt32(), n).AsUInt32(), eq); + } + + Unsafe.Add(ref resultBase, x) = r.AsByte(); + + uint msk = (uint)Avx2.MoveMask(eq.AsByte()); + msk = ~msk; + + if (msk != 0) + { + // If is diff is found, the left side is marked by the min of previously found left side and the start position. + // The right is the max of the previously found right side and the end position. + int start = i + (BitOperations.TrailingZeroCount(msk) / sizeof(uint)); + int end = i + (8 - (BitOperations.LeadingZeroCount(msk) / sizeof(uint))); + left = Math.Min(left, start); + right = Math.Max(right, end); + hasRowDiff = true; + hasDiff = true; + } + + x++; + i += 8; + remaining -= 8; + } } for (i = remaining; i > 0; i--) { x = (uint)(length - i); - Vector4 p = Unsafe.Add(ref Unsafe.As, Vector4>(ref previousBase), x); - Vector4 c = Unsafe.Add(ref Unsafe.As, Vector4>(ref currentBase), x); - ref Vector4 r = ref Unsafe.Add(ref Unsafe.As, Vector4>(ref resultBase), x); + Rgba32 p = Unsafe.Add(ref MemoryMarshal.GetReference(previous), x); + Rgba32 c = Unsafe.Add(ref MemoryMarshal.GetReference(current), x); + Rgba32 n = Unsafe.Add(ref MemoryMarshal.GetReference(next), x); + ref Rgba32 r = ref Unsafe.Add(ref MemoryMarshal.GetReference(result), x); - if (p != c) - { - r = c; + bool peq = c.Rgba == (previousFrame != null ? p.Rgba : bg.Rgba); + Rgba32 val = (blend & peq) ? replacement : c; + + peq &= nextFrame == null || (n.Rgba >> 24 >= c.Rgba >> 24); + r = val; + if (!peq) + { // If is diff is found, the left side is marked by the min of previously found left side and the diff position. // The right is the max of the previously found right side and the diff position + 1. left = Math.Min(left, (int)x); @@ -127,10 +152,6 @@ internal static class AnimationUtilities hasRowDiff = true; hasDiff = true; } - else - { - r = replacement; - } } if (hasRowDiff) @@ -143,7 +164,7 @@ internal static class AnimationUtilities bottom = y + 1; } - PixelOperations.Instance.FromVector4Destructive(configuration, result, resultFrame.DangerousGetPixelRowMemory(y).Span, PixelConversionModifiers.Scale); + PixelOperations.Instance.FromRgba32(configuration, result, resultFrame.DangerousGetPixelRowMemory(y).Span); } Rectangle bounds = Rectangle.FromLTRB( @@ -163,19 +184,6 @@ internal static class AnimationUtilities return (hasDiff, bounds); } - - public static void CopySource(ImageFrame source, ImageFrame destination, Rectangle bounds) - where TPixel : unmanaged, IPixel - { - Buffer2DRegion sourceBuffer = source.PixelBuffer.GetRegion(bounds); - Buffer2DRegion destBuffer = destination.PixelBuffer.GetRegion(bounds); - for (int y = 0; y < destBuffer.Height; y++) - { - Span sourceRow = sourceBuffer.DangerousGetRowSpan(y); - Span destRow = destBuffer.DangerousGetRowSpan(y); - sourceRow.CopyTo(destRow); - } - } } #pragma warning disable SA1201 // Elements should appear in the correct order diff --git a/src/ImageSharp/Formats/Gif/GifEncoderCore.cs b/src/ImageSharp/Formats/Gif/GifEncoderCore.cs index 37e31f32d..2186cc2e4 100644 --- a/src/ImageSharp/Formats/Gif/GifEncoderCore.cs +++ b/src/ImageSharp/Formats/Gif/GifEncoderCore.cs @@ -264,10 +264,13 @@ internal sealed class GifEncoderCore : IImageEncoderInternals hasPaletteQuantizer = true; } + ImageFrame? nextFrame = i < image.Frames.Count - 1 ? image.Frames[i + 1] : null; + this.EncodeAdditionalFrame( stream, previousFrame, currentFrame, + nextFrame, encodingFrame, useLocal, gifMetadata, @@ -311,6 +314,7 @@ internal sealed class GifEncoderCore : IImageEncoderInternals Stream stream, ImageFrame previousFrame, ImageFrame currentFrame, + ImageFrame? nextFrame, ImageFrame encodingFrame, bool useLocal, GifFrameMetadata metadata, @@ -325,7 +329,15 @@ internal sealed class GifEncoderCore : IImageEncoderInternals ImageFrame? previous = previousDisposal == GifDisposalMethod.RestoreToBackground ? null : previousFrame; // Deduplicate and quantize the frame capturing only required parts. - (bool difference, Rectangle bounds) = AnimationUtilities.DeDuplicatePixels(this.configuration, previous, currentFrame, encodingFrame, Vector4.Zero); + (bool difference, Rectangle bounds) = + AnimationUtilities.DeDuplicatePixels( + this.configuration, + previous, + currentFrame, + nextFrame, + encodingFrame, + Color.Transparent, + true); using IndexedImageFrame quantized = this.QuantizeAdditionalFrameAndUpdateMetadata( encodingFrame, diff --git a/src/ImageSharp/Formats/Png/PngEncoderCore.cs b/src/ImageSharp/Formats/Png/PngEncoderCore.cs index 7908109e8..932916dec 100644 --- a/src/ImageSharp/Formats/Png/PngEncoderCore.cs +++ b/src/ImageSharp/Formats/Png/PngEncoderCore.cs @@ -3,7 +3,6 @@ using System.Buffers; using System.Buffers.Binary; -using System.Numerics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using SixLabors.ImageSharp.Common.Helpers; @@ -208,21 +207,22 @@ internal sealed class PngEncoderCore : IImageEncoderInternals, IDisposable for (int i = 1; i < image.Frames.Count; i++) { - currentFrame = image.Frames[i]; - frameMetadata = GetPngFrameMetadata(currentFrame); - ImageFrame? prev = previousDisposal == PngDisposalMethod.RestoreToBackground ? null : previousFrame; - (bool difference, Rectangle bounds) = AnimationUtilities.DeDuplicatePixels(image.Configuration, prev, currentFrame, encodingFrame, Vector4.Zero); + currentFrame = image.Frames[i]; + ImageFrame? nextFrame = i < image.Frames.Count - 1 ? image.Frames[i + 1] : null; - if (difference && previousDisposal != PngDisposalMethod.RestoreToBackground) - { - if (frameMetadata.BlendMethod == PngBlendMethod.Source) - { - // We've potentially introduced transparency within our area of interest - // so we need to overwrite the changed area with the full data. - AnimationUtilities.CopySource(currentFrame, encodingFrame, bounds); - } - } + frameMetadata = GetPngFrameMetadata(currentFrame); + bool blend = frameMetadata.BlendMethod == PngBlendMethod.Over; + + (bool difference, Rectangle bounds) = + AnimationUtilities.DeDuplicatePixels( + image.Configuration, + prev, + currentFrame, + nextFrame, + encodingFrame, + Color.Transparent, + blend); if (clearTransparency) { diff --git a/src/ImageSharp/Formats/Webp/WebpEncoderCore.cs b/src/ImageSharp/Formats/Webp/WebpEncoderCore.cs index db12d7c67..e37c1d179 100644 --- a/src/ImageSharp/Formats/Webp/WebpEncoderCore.cs +++ b/src/ImageSharp/Formats/Webp/WebpEncoderCore.cs @@ -1,7 +1,6 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System.Numerics; using SixLabors.ImageSharp.Formats.Webp.Lossless; using SixLabors.ImageSharp.Formats.Webp.Lossy; using SixLabors.ImageSharp.Memory; @@ -161,22 +160,23 @@ internal sealed class WebpEncoderCore : IImageEncoderInternals for (int i = 1; i < image.Frames.Count; i++) { - ImageFrame currentFrame = image.Frames[i]; - frameMetadata = WebpCommonUtils.GetWebpFrameMetadata(currentFrame); - ImageFrame? prev = previousDisposal == WebpDisposalMethod.RestoreToBackground ? null : previousFrame; + ImageFrame currentFrame = image.Frames[i]; + ImageFrame? nextFrame = i < image.Frames.Count - 1 ? image.Frames[i + 1] : null; - (bool difference, Rectangle bounds) = AnimationUtilities.DeDuplicatePixels(image.Configuration, prev, currentFrame, encodingFrame, Vector4.Zero, ClampingMode.Even); - - if (difference && previousDisposal != WebpDisposalMethod.RestoreToBackground) - { - if (frameMetadata.BlendMethod == WebpBlendMethod.Source) - { - // We've potentially introduced transparency within our area of interest - // so we need to overwrite the changed area with the full data. - AnimationUtilities.CopySource(currentFrame, encodingFrame, bounds); - } - } + frameMetadata = WebpCommonUtils.GetWebpFrameMetadata(currentFrame); + bool blend = frameMetadata.BlendMethod == WebpBlendMethod.Over; + + (bool difference, Rectangle bounds) = + AnimationUtilities.DeDuplicatePixels( + image.Configuration, + prev, + currentFrame, + nextFrame, + encodingFrame, + Color.Transparent, + blend, + ClampingMode.Even); using Vp8LEncoder animatedEncoder = new( this.memoryAllocator, @@ -232,21 +232,23 @@ internal sealed class WebpEncoderCore : IImageEncoderInternals for (int i = 1; i < image.Frames.Count; i++) { + ImageFrame? prev = previousDisposal == WebpDisposalMethod.RestoreToBackground ? null : previousFrame; ImageFrame currentFrame = image.Frames[i]; - frameMetadata = WebpCommonUtils.GetWebpFrameMetadata(currentFrame); + ImageFrame? nextFrame = i < image.Frames.Count - 1 ? image.Frames[i + 1] : null; - ImageFrame? prev = previousDisposal == WebpDisposalMethod.RestoreToBackground ? null : previousFrame; - (bool difference, Rectangle bounds) = AnimationUtilities.DeDuplicatePixels(image.Configuration, prev, currentFrame, encodingFrame, Vector4.Zero, ClampingMode.Even); - - if (difference && previousDisposal != WebpDisposalMethod.RestoreToBackground) - { - if (frameMetadata.BlendMethod == WebpBlendMethod.Source) - { - // We've potentially introduced transparency within our area of interest - // so we need to overwrite the changed area with the full data. - AnimationUtilities.CopySource(currentFrame, encodingFrame, bounds); - } - } + frameMetadata = WebpCommonUtils.GetWebpFrameMetadata(currentFrame); + bool blend = frameMetadata.BlendMethod == WebpBlendMethod.Over; + + (bool difference, Rectangle bounds) = + AnimationUtilities.DeDuplicatePixels( + image.Configuration, + prev, + currentFrame, + nextFrame, + encodingFrame, + Color.Transparent, + blend, + ClampingMode.Even); using Vp8Encoder animatedEncoder = new( this.memoryAllocator, diff --git a/tests/ImageSharp.Tests/TestImages.cs b/tests/ImageSharp.Tests/TestImages.cs index 5430494a8..851c79217 100644 --- a/tests/ImageSharp.Tests/TestImages.cs +++ b/tests/ImageSharp.Tests/TestImages.cs @@ -480,6 +480,7 @@ public static class TestImages public const string LargeComment = "Gif/large_comment.gif"; public const string GlobalQuantizationTest = "Gif/GlobalQuantizationTest.gif"; public const string MixedDisposal = "Gif/mixed-disposal.gif"; + public const string M4nb = "Gif/m4nb.gif"; // Test images from https://github.com/robert-ancell/pygif/tree/master/test-suite public const string ZeroSize = "Gif/image-zero-size.gif"; @@ -511,6 +512,7 @@ public static class TestImages public static readonly string[] Animated = { + M4nb, Giphy, Cheers, Kumin, diff --git a/tests/Images/Input/Gif/m4nb.gif b/tests/Images/Input/Gif/m4nb.gif new file mode 100644 index 000000000..0c921b2af --- /dev/null +++ b/tests/Images/Input/Gif/m4nb.gif @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b5b495caf1eb1f1cf7b15a1998faa33a6f4a49999e5edd435d4ff91265ff1ce5 +size 2100 From 6bfabe9d547dbda90d633155d460b6044d464e9b Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Tue, 28 Nov 2023 22:59:38 +1000 Subject: [PATCH 16/24] Reduce memory pressure on gif decoder tests --- tests/ImageSharp.Tests/Formats/Gif/GifDecoderTests.cs | 4 ++-- .../Decode_VerifyRootFrameAndFrameCount_Rgba32_cheers.png | 3 --- ...tFrameAndFrameCount_Rgba32_issue403_baddescriptorwidth.png | 3 --- .../Decode_VerifyRootFrameAndFrameCount_Rgba32_m4nb.png | 3 +++ ...ode_VerifyRootFrameAndFrameCount_Rgba32_mixed-disposal.png | 3 +++ 5 files changed, 8 insertions(+), 8 deletions(-) delete mode 100644 tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_VerifyRootFrameAndFrameCount_Rgba32_cheers.png delete mode 100644 tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_VerifyRootFrameAndFrameCount_Rgba32_issue403_baddescriptorwidth.png create mode 100644 tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_VerifyRootFrameAndFrameCount_Rgba32_m4nb.png create mode 100644 tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_VerifyRootFrameAndFrameCount_Rgba32_mixed-disposal.png diff --git a/tests/ImageSharp.Tests/Formats/Gif/GifDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Gif/GifDecoderTests.cs index 8b2392741..a99d3c65b 100644 --- a/tests/ImageSharp.Tests/Formats/Gif/GifDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Gif/GifDecoderTests.cs @@ -101,9 +101,9 @@ public class GifDecoderTests } [Theory] - [WithFile(TestImages.Gif.Cheers, PixelTypes.Rgba32, 93)] + [WithFile(TestImages.Gif.M4nb, PixelTypes.Rgba32, 5)] [WithFile(TestImages.Gif.Rings, PixelTypes.Rgba32, 1)] - [WithFile(TestImages.Gif.Issues.BadDescriptorWidth, PixelTypes.Rgba32, 36)] + [WithFile(TestImages.Gif.MixedDisposal, PixelTypes.Rgba32, 11)] public void Decode_VerifyRootFrameAndFrameCount(TestImageProvider provider, int expectedFrameCount) where TPixel : unmanaged, IPixel { diff --git a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_VerifyRootFrameAndFrameCount_Rgba32_cheers.png b/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_VerifyRootFrameAndFrameCount_Rgba32_cheers.png deleted file mode 100644 index d5be7b0b2..000000000 --- a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_VerifyRootFrameAndFrameCount_Rgba32_cheers.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:b199a36f9e682a54c5d7c67f3403bba174b37e1a7a8412481f66c6d5eb0349e9 -size 27679 diff --git a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_VerifyRootFrameAndFrameCount_Rgba32_issue403_baddescriptorwidth.png b/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_VerifyRootFrameAndFrameCount_Rgba32_issue403_baddescriptorwidth.png deleted file mode 100644 index 5bd551cb1..000000000 --- a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_VerifyRootFrameAndFrameCount_Rgba32_issue403_baddescriptorwidth.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:4bedcf1a0ca0281dd5531d96c74e19c5d5fd379d6e2acb899077299917215705 -size 1013 diff --git a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_VerifyRootFrameAndFrameCount_Rgba32_m4nb.png b/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_VerifyRootFrameAndFrameCount_Rgba32_m4nb.png new file mode 100644 index 000000000..abe2828ec --- /dev/null +++ b/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_VerifyRootFrameAndFrameCount_Rgba32_m4nb.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:04239ee46f0276ba52566bcb2407f4a6fcee35b3f51a6182394f851e2d8df3fc +size 277 diff --git a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_VerifyRootFrameAndFrameCount_Rgba32_mixed-disposal.png b/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_VerifyRootFrameAndFrameCount_Rgba32_mixed-disposal.png new file mode 100644 index 000000000..c30d822c1 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_VerifyRootFrameAndFrameCount_Rgba32_mixed-disposal.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0b654f948a26d256ff9e28ada399465bd6a4205aedaf93ea7cdffb70483535ef +size 2216 From 9677be02ade3194003b4542708f4f9a57c37d42f Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Tue, 28 Nov 2023 23:14:31 +1000 Subject: [PATCH 17/24] More memory pressure reductions --- tests/ImageSharp.Tests/Formats/Gif/GifDecoderTests.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/ImageSharp.Tests/Formats/Gif/GifDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Gif/GifDecoderTests.cs index a99d3c65b..5aec1f814 100644 --- a/tests/ImageSharp.Tests/Formats/Gif/GifDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Gif/GifDecoderTests.cs @@ -212,7 +212,7 @@ public class GifDecoderTests public void Issue405_BadApplicationExtensionBlockLength(TestImageProvider provider) where TPixel : unmanaged, IPixel { - using Image image = provider.GetImage(); + using Image image = provider.GetImage(GifDecoder.Instance, new() { MaxFrames = 1 }); image.DebugSave(provider); image.CompareFirstFrameToReferenceOutput(ImageComparer.Exact, provider); @@ -224,7 +224,7 @@ public class GifDecoderTests public void Issue1668_InvalidColorIndex(TestImageProvider provider) where TPixel : unmanaged, IPixel { - using Image image = provider.GetImage(); + using Image image = provider.GetImage(GifDecoder.Instance, new() { MaxFrames = 1 }); image.DebugSave(provider); image.CompareFirstFrameToReferenceOutput(ImageComparer.Exact, provider); @@ -273,7 +273,7 @@ public class GifDecoderTests public void Issue1962(TestImageProvider provider) where TPixel : unmanaged, IPixel { - using Image image = provider.GetImage(); + using Image image = provider.GetImage(GifDecoder.Instance, new() { MaxFrames = 1 }); image.DebugSave(provider); image.CompareFirstFrameToReferenceOutput(ImageComparer.Exact, provider); @@ -285,7 +285,7 @@ public class GifDecoderTests public void Issue2012EmptyXmp(TestImageProvider provider) where TPixel : unmanaged, IPixel { - using Image image = provider.GetImage(); + using Image image = provider.GetImage(GifDecoder.Instance, new() { MaxFrames = 1 }); image.DebugSave(provider); image.CompareFirstFrameToReferenceOutput(ImageComparer.Exact, provider); From 35d55b5da350a31b40866f8b14c58080a000d3b4 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Tue, 28 Nov 2023 23:28:24 +1000 Subject: [PATCH 18/24] More memory pressure reduction --- tests/ImageSharp.Tests/Formats/Gif/GifDecoderTests.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tests/ImageSharp.Tests/Formats/Gif/GifDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Gif/GifDecoderTests.cs index 5aec1f814..6399e1193 100644 --- a/tests/ImageSharp.Tests/Formats/Gif/GifDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Gif/GifDecoderTests.cs @@ -133,7 +133,6 @@ public class GifDecoderTests } [Theory] - [InlineData(TestImages.Gif.Cheers, 8)] [InlineData(TestImages.Gif.Giphy, 8)] [InlineData(TestImages.Gif.Rings, 8)] [InlineData(TestImages.Gif.Trans, 8)] @@ -194,7 +193,7 @@ public class GifDecoderTests } } - // https://github.com/SixLabors/ImageSharp/issues/1503 + // https://github.com/SixLabors/ImageSharp/issues/1530 [Theory] [WithFile(TestImages.Gif.Issues.Issue1530, PixelTypes.Rgba32)] public void Issue1530_BadDescriptorDimensions(TestImageProvider provider) From e35e9a8d89d78067fe855e98c0b0bb5763435615 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Wed, 29 Nov 2023 10:00:57 +1000 Subject: [PATCH 19/24] Reduce memory usage in pixel map --- .../Processors/Quantization/EuclideanPixelMap{TPixel}.cs | 4 ++-- tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs | 2 +- .../Encode_8BitColor_WithOctreeQuantizer_rgb32.bmp | 2 +- .../Encode_8BitColor_WithWuQuantizer_rgb32.bmp | 2 +- .../ApplyDiffusionFilterInBox_Rgba32_CalliphoraPartial.png | 4 ++-- .../ApplyDitherFilterInBox_Rgba32_CalliphoraPartial.png | 4 ++-- ...Filter_ShouldNotDependOnSinglePixelType_Bgra32_filter0.png | 4 ++-- ...nFilter_ShouldNotDependOnSinglePixelType_Rgb24_filter0.png | 4 ++-- ...Filter_ShouldNotDependOnSinglePixelType_Rgba32_filter0.png | 4 ++-- ...er_ShouldNotDependOnSinglePixelType_RgbaVector_filter0.png | 4 ++-- ...ffusionFilter_WorksWithAllErrorDiffusers_Bike_Atkinson.png | 4 ++-- .../DiffusionFilter_WorksWithAllErrorDiffusers_Bike_Burks.png | 4 ++-- ...nFilter_WorksWithAllErrorDiffusers_Bike_FloydSteinberg.png | 4 ++-- ...lter_WorksWithAllErrorDiffusers_Bike_JarvisJudiceNinke.png | 4 ++-- ...iffusionFilter_WorksWithAllErrorDiffusers_Bike_Sierra2.png | 4 ++-- ...iffusionFilter_WorksWithAllErrorDiffusers_Bike_Sierra3.png | 4 ++-- ...usionFilter_WorksWithAllErrorDiffusers_Bike_SierraLite.png | 4 ++-- ...onFilter_WorksWithAllErrorDiffusers_Bike_StevensonArce.png | 4 ++-- ...DiffusionFilter_WorksWithAllErrorDiffusers_Bike_Stucki.png | 4 ++-- ..._WorksWithAllErrorDiffusers_CalliphoraPartial_Atkinson.png | 4 ++-- ...ter_WorksWithAllErrorDiffusers_CalliphoraPartial_Burks.png | 4 ++-- ...WithAllErrorDiffusers_CalliphoraPartial_FloydSteinberg.png | 4 ++-- ...hAllErrorDiffusers_CalliphoraPartial_JarvisJudiceNinke.png | 4 ++-- ...r_WorksWithAllErrorDiffusers_CalliphoraPartial_Sierra2.png | 4 ++-- ...r_WorksWithAllErrorDiffusers_CalliphoraPartial_Sierra3.png | 4 ++-- ...orksWithAllErrorDiffusers_CalliphoraPartial_SierraLite.png | 4 ++-- ...sWithAllErrorDiffusers_CalliphoraPartial_StevensonArce.png | 4 ++-- ...er_WorksWithAllErrorDiffusers_CalliphoraPartial_Stucki.png | 4 ++-- .../DitherFilter_WorksWithAllDitherers_Bike_Bayer16x16.png | 4 ++-- .../DitherFilter_WorksWithAllDitherers_Bike_Bayer2x2.png | 4 ++-- .../DitherFilter_WorksWithAllDitherers_Bike_Bayer4x4.png | 4 ++-- .../DitherFilter_WorksWithAllDitherers_Bike_Bayer8x8.png | 4 ++-- .../DitherFilter_WorksWithAllDitherers_Bike_Ordered3x3.png | 4 ++-- ...ter_WorksWithAllDitherers_CalliphoraPartial_Bayer16x16.png | 4 ++-- ...ilter_WorksWithAllDitherers_CalliphoraPartial_Bayer2x2.png | 4 ++-- ...ilter_WorksWithAllDitherers_CalliphoraPartial_Bayer4x4.png | 4 ++-- ...ilter_WorksWithAllDitherers_CalliphoraPartial_Bayer8x8.png | 4 ++-- ...ter_WorksWithAllDitherers_CalliphoraPartial_Ordered3x3.png | 4 ++-- ...Issue2469_Quantized_Encode_Artifacts_Rgba32_issue_2469.png | 4 ++-- ...pplyQuantizationInBox_Bike_OctreeQuantizer_ErrorDither.png | 4 ++-- .../ApplyQuantizationInBox_Bike_OctreeQuantizer_NoDither.png | 4 ++-- ...lyQuantizationInBox_Bike_OctreeQuantizer_OrderedDither.png | 4 ++-- ...tizationInBox_Bike_WebSafePaletteQuantizer_ErrorDither.png | 4 ++-- ...uantizationInBox_Bike_WebSafePaletteQuantizer_NoDither.png | 4 ++-- ...zationInBox_Bike_WebSafePaletteQuantizer_OrderedDither.png | 4 ++-- ...ntizationInBox_Bike_WernerPaletteQuantizer_ErrorDither.png | 4 ++-- ...QuantizationInBox_Bike_WernerPaletteQuantizer_NoDither.png | 4 ++-- ...izationInBox_Bike_WernerPaletteQuantizer_OrderedDither.png | 4 ++-- .../ApplyQuantizationInBox_Bike_WuQuantizer_ErrorDither.png | 4 ++-- .../ApplyQuantizationInBox_Bike_WuQuantizer_NoDither.png | 4 ++-- .../ApplyQuantizationInBox_Bike_WuQuantizer_OrderedDither.png | 4 ++-- ...ionInBox_CalliphoraPartial_OctreeQuantizer_ErrorDither.png | 4 ++-- ...zationInBox_CalliphoraPartial_OctreeQuantizer_NoDither.png | 4 ++-- ...nInBox_CalliphoraPartial_OctreeQuantizer_OrderedDither.png | 4 ++-- ..._CalliphoraPartial_WebSafePaletteQuantizer_ErrorDither.png | 4 ++-- ...Box_CalliphoraPartial_WebSafePaletteQuantizer_NoDither.png | 4 ++-- ...alliphoraPartial_WebSafePaletteQuantizer_OrderedDither.png | 4 ++-- ...x_CalliphoraPartial_WernerPaletteQuantizer_ErrorDither.png | 4 ++-- ...nBox_CalliphoraPartial_WernerPaletteQuantizer_NoDither.png | 4 ++-- ...CalliphoraPartial_WernerPaletteQuantizer_OrderedDither.png | 4 ++-- ...izationInBox_CalliphoraPartial_WuQuantizer_ErrorDither.png | 4 ++-- ...antizationInBox_CalliphoraPartial_WuQuantizer_NoDither.png | 4 ++-- ...ationInBox_CalliphoraPartial_WuQuantizer_OrderedDither.png | 4 ++-- ...hDitheringScale_david_OctreeQuantizer_ErrorDither_0.25.png | 4 ++-- ...thDitheringScale_david_OctreeQuantizer_ErrorDither_0.5.png | 4 ++-- ...hDitheringScale_david_OctreeQuantizer_ErrorDither_0.75.png | 4 ++-- ...WithDitheringScale_david_OctreeQuantizer_ErrorDither_0.png | 4 ++-- ...WithDitheringScale_david_OctreeQuantizer_ErrorDither_1.png | 4 ++-- ...itheringScale_david_OctreeQuantizer_OrderedDither_0.25.png | 4 ++-- ...DitheringScale_david_OctreeQuantizer_OrderedDither_0.5.png | 4 ++-- ...itheringScale_david_OctreeQuantizer_OrderedDither_0.75.png | 4 ++-- ...thDitheringScale_david_OctreeQuantizer_OrderedDither_0.png | 4 ++-- ...thDitheringScale_david_OctreeQuantizer_OrderedDither_1.png | 4 ++-- ...ngScale_david_WebSafePaletteQuantizer_ErrorDither_0.25.png | 4 ++-- ...ingScale_david_WebSafePaletteQuantizer_ErrorDither_0.5.png | 4 ++-- ...ngScale_david_WebSafePaletteQuantizer_ErrorDither_0.75.png | 4 ++-- ...eringScale_david_WebSafePaletteQuantizer_ErrorDither_0.png | 4 ++-- ...eringScale_david_WebSafePaletteQuantizer_ErrorDither_1.png | 4 ++-- ...Scale_david_WebSafePaletteQuantizer_OrderedDither_0.25.png | 4 ++-- ...gScale_david_WebSafePaletteQuantizer_OrderedDither_0.5.png | 4 ++-- ...Scale_david_WebSafePaletteQuantizer_OrderedDither_0.75.png | 4 ++-- ...ingScale_david_WebSafePaletteQuantizer_OrderedDither_0.png | 4 ++-- ...ingScale_david_WebSafePaletteQuantizer_OrderedDither_1.png | 4 ++-- ...ingScale_david_WernerPaletteQuantizer_ErrorDither_0.25.png | 4 ++-- ...ringScale_david_WernerPaletteQuantizer_ErrorDither_0.5.png | 4 ++-- ...ingScale_david_WernerPaletteQuantizer_ErrorDither_0.75.png | 4 ++-- ...heringScale_david_WernerPaletteQuantizer_ErrorDither_0.png | 4 ++-- ...heringScale_david_WernerPaletteQuantizer_ErrorDither_1.png | 4 ++-- ...gScale_david_WernerPaletteQuantizer_OrderedDither_0.25.png | 4 ++-- ...ngScale_david_WernerPaletteQuantizer_OrderedDither_0.5.png | 4 ++-- ...gScale_david_WernerPaletteQuantizer_OrderedDither_0.75.png | 4 ++-- ...ringScale_david_WernerPaletteQuantizer_OrderedDither_0.png | 4 ++-- ...ringScale_david_WernerPaletteQuantizer_OrderedDither_1.png | 4 ++-- ...nWithDitheringScale_david_WuQuantizer_ErrorDither_0.25.png | 4 ++-- ...onWithDitheringScale_david_WuQuantizer_ErrorDither_0.5.png | 4 ++-- ...nWithDitheringScale_david_WuQuantizer_ErrorDither_0.75.png | 4 ++-- ...tionWithDitheringScale_david_WuQuantizer_ErrorDither_0.png | 4 ++-- ...tionWithDitheringScale_david_WuQuantizer_ErrorDither_1.png | 4 ++-- ...ithDitheringScale_david_WuQuantizer_OrderedDither_0.25.png | 4 ++-- ...WithDitheringScale_david_WuQuantizer_OrderedDither_0.5.png | 4 ++-- ...ithDitheringScale_david_WuQuantizer_OrderedDither_0.75.png | 4 ++-- ...onWithDitheringScale_david_WuQuantizer_OrderedDither_0.png | 4 ++-- ...onWithDitheringScale_david_WuQuantizer_OrderedDither_1.png | 4 ++-- .../ApplyQuantization_Bike_OctreeQuantizer_ErrorDither.png | 4 ++-- .../ApplyQuantization_Bike_OctreeQuantizer_NoDither.png | 4 ++-- .../ApplyQuantization_Bike_OctreeQuantizer_OrderedDither.png | 4 ++-- ...yQuantization_Bike_WebSafePaletteQuantizer_ErrorDither.png | 4 ++-- ...pplyQuantization_Bike_WebSafePaletteQuantizer_NoDither.png | 4 ++-- ...uantization_Bike_WebSafePaletteQuantizer_OrderedDither.png | 4 ++-- ...lyQuantization_Bike_WernerPaletteQuantizer_ErrorDither.png | 4 ++-- ...ApplyQuantization_Bike_WernerPaletteQuantizer_NoDither.png | 4 ++-- ...Quantization_Bike_WernerPaletteQuantizer_OrderedDither.png | 4 ++-- .../ApplyQuantization_Bike_WuQuantizer_ErrorDither.png | 4 ++-- .../ApplyQuantization_Bike_WuQuantizer_NoDither.png | 4 ++-- .../ApplyQuantization_Bike_WuQuantizer_OrderedDither.png | 4 ++-- ...tization_CalliphoraPartial_OctreeQuantizer_ErrorDither.png | 4 ++-- ...uantization_CalliphoraPartial_OctreeQuantizer_NoDither.png | 4 ++-- ...zation_CalliphoraPartial_OctreeQuantizer_OrderedDither.png | 4 ++-- ..._CalliphoraPartial_WebSafePaletteQuantizer_ErrorDither.png | 4 ++-- ...ion_CalliphoraPartial_WebSafePaletteQuantizer_NoDither.png | 4 ++-- ...alliphoraPartial_WebSafePaletteQuantizer_OrderedDither.png | 4 ++-- ...n_CalliphoraPartial_WernerPaletteQuantizer_ErrorDither.png | 4 ++-- ...tion_CalliphoraPartial_WernerPaletteQuantizer_NoDither.png | 4 ++-- ...CalliphoraPartial_WernerPaletteQuantizer_OrderedDither.png | 4 ++-- ...Quantization_CalliphoraPartial_WuQuantizer_ErrorDither.png | 4 ++-- ...plyQuantization_CalliphoraPartial_WuQuantizer_NoDither.png | 4 ++-- ...antization_CalliphoraPartial_WuQuantizer_OrderedDither.png | 4 ++-- 127 files changed, 251 insertions(+), 251 deletions(-) diff --git a/src/ImageSharp/Processing/Processors/Quantization/EuclideanPixelMap{TPixel}.cs b/src/ImageSharp/Processing/Processors/Quantization/EuclideanPixelMap{TPixel}.cs index 3c7d57670..72148374a 100644 --- a/src/ImageSharp/Processing/Processors/Quantization/EuclideanPixelMap{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Quantization/EuclideanPixelMap{TPixel}.cs @@ -184,14 +184,14 @@ internal sealed class EuclideanPixelMap : IDisposable /// The granularity of the cache has been determined based upon the current /// suite of test images and provides the lowest possible memory usage while /// providing enough match accuracy. - /// Entry count is currently limited to 4601025 entries (8MB). + /// Entry count is currently limited to 2335905 entries (4MB). /// /// private unsafe struct ColorDistanceCache : IDisposable { private const int IndexRBits = 5; private const int IndexGBits = 5; - private const int IndexBBits = 6; + private const int IndexBBits = 5; private const int IndexABits = 6; private const int IndexRCount = (1 << IndexRBits) + 1; private const int IndexGCount = (1 << IndexGBits) + 1; diff --git a/tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs index 6679a765f..c81b7eb6c 100644 --- a/tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs @@ -499,7 +499,7 @@ public partial class PngEncoderTests // TODO: Find a better way to compare. // The image has been visually checked but the quantization pattern used in the png encoder // means we cannot use an exact comparison nor replicate using the quantizing processor. - ImageComparer.TolerantPercentage(0.12f).VerifySimilarity(output, image); + ImageComparer.TolerantPercentage(0.46f).VerifySimilarity(output, image); GifMetadata gif = image.Metadata.GetGifMetadata(); PngMetadata png = output.Metadata.GetPngMetadata(); diff --git a/tests/Images/External/ReferenceOutput/BmpEncoderTests/Encode_8BitColor_WithOctreeQuantizer_rgb32.bmp b/tests/Images/External/ReferenceOutput/BmpEncoderTests/Encode_8BitColor_WithOctreeQuantizer_rgb32.bmp index d484eace0..2b8e05b07 100644 --- a/tests/Images/External/ReferenceOutput/BmpEncoderTests/Encode_8BitColor_WithOctreeQuantizer_rgb32.bmp +++ b/tests/Images/External/ReferenceOutput/BmpEncoderTests/Encode_8BitColor_WithOctreeQuantizer_rgb32.bmp @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:23a9d9233314ec08bd3e464f245e69d96566cbb12d2dba36c69bba483d6ba6b8 +oid sha256:11375b15df083d98335f4a4baf0717e7fdd6b21ab2132a6815cadc787ac17e7d size 9270 diff --git a/tests/Images/External/ReferenceOutput/BmpEncoderTests/Encode_8BitColor_WithWuQuantizer_rgb32.bmp b/tests/Images/External/ReferenceOutput/BmpEncoderTests/Encode_8BitColor_WithWuQuantizer_rgb32.bmp index 6896c4fa6..f7eb06c55 100644 --- a/tests/Images/External/ReferenceOutput/BmpEncoderTests/Encode_8BitColor_WithWuQuantizer_rgb32.bmp +++ b/tests/Images/External/ReferenceOutput/BmpEncoderTests/Encode_8BitColor_WithWuQuantizer_rgb32.bmp @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:3052831cb18fecc26f54e29dfe19b538d2e0d3c104ddd7ec5bc8e0adcf56693c +oid sha256:e063e97cd8a000de6830adcc3961a7dc41785d40cd4d83af10ca38d96e071362 size 9270 diff --git a/tests/Images/External/ReferenceOutput/DitherTests/ApplyDiffusionFilterInBox_Rgba32_CalliphoraPartial.png b/tests/Images/External/ReferenceOutput/DitherTests/ApplyDiffusionFilterInBox_Rgba32_CalliphoraPartial.png index 39bb7e52b..de42d1bfc 100644 --- a/tests/Images/External/ReferenceOutput/DitherTests/ApplyDiffusionFilterInBox_Rgba32_CalliphoraPartial.png +++ b/tests/Images/External/ReferenceOutput/DitherTests/ApplyDiffusionFilterInBox_Rgba32_CalliphoraPartial.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e89597ab9aa006d026a560d1482350739bd93604d9c6726f6730d782c017a0a3 -size 273049 +oid sha256:681b0e36298cb702683fb9ffb2a82f7dfd9080b268db19a03f413809f69d0e07 +size 273269 diff --git a/tests/Images/External/ReferenceOutput/DitherTests/ApplyDitherFilterInBox_Rgba32_CalliphoraPartial.png b/tests/Images/External/ReferenceOutput/DitherTests/ApplyDitherFilterInBox_Rgba32_CalliphoraPartial.png index 133112867..43e414da6 100644 --- a/tests/Images/External/ReferenceOutput/DitherTests/ApplyDitherFilterInBox_Rgba32_CalliphoraPartial.png +++ b/tests/Images/External/ReferenceOutput/DitherTests/ApplyDitherFilterInBox_Rgba32_CalliphoraPartial.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:59d331efd9e6d926eaf90bed1f76b3ba55b2a42d2f83fc512a985cdea97781ec -size 271152 +oid sha256:a899a84c6af24bfad89f9fde75957c7a979d65bcf096ab667cb976efd71cb560 +size 271171 diff --git a/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_ShouldNotDependOnSinglePixelType_Bgra32_filter0.png b/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_ShouldNotDependOnSinglePixelType_Bgra32_filter0.png index cac3b9c42..d8ececb4c 100644 --- a/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_ShouldNotDependOnSinglePixelType_Bgra32_filter0.png +++ b/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_ShouldNotDependOnSinglePixelType_Bgra32_filter0.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:3cee43ebaefd94bcd993b8548f734a0a44b948a532263b8d2ee41b0cd42ab7a9 -size 727 +oid sha256:f7c19df70d24948e1a36299705bb030715cf0d01b453d390989d472c0999d46a +size 728 diff --git a/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_ShouldNotDependOnSinglePixelType_Rgb24_filter0.png b/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_ShouldNotDependOnSinglePixelType_Rgb24_filter0.png index cac3b9c42..d8ececb4c 100644 --- a/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_ShouldNotDependOnSinglePixelType_Rgb24_filter0.png +++ b/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_ShouldNotDependOnSinglePixelType_Rgb24_filter0.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:3cee43ebaefd94bcd993b8548f734a0a44b948a532263b8d2ee41b0cd42ab7a9 -size 727 +oid sha256:f7c19df70d24948e1a36299705bb030715cf0d01b453d390989d472c0999d46a +size 728 diff --git a/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_ShouldNotDependOnSinglePixelType_Rgba32_filter0.png b/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_ShouldNotDependOnSinglePixelType_Rgba32_filter0.png index cac3b9c42..d8ececb4c 100644 --- a/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_ShouldNotDependOnSinglePixelType_Rgba32_filter0.png +++ b/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_ShouldNotDependOnSinglePixelType_Rgba32_filter0.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:3cee43ebaefd94bcd993b8548f734a0a44b948a532263b8d2ee41b0cd42ab7a9 -size 727 +oid sha256:f7c19df70d24948e1a36299705bb030715cf0d01b453d390989d472c0999d46a +size 728 diff --git a/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_ShouldNotDependOnSinglePixelType_RgbaVector_filter0.png b/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_ShouldNotDependOnSinglePixelType_RgbaVector_filter0.png index 4a3a1e874..5da96d59d 100644 --- a/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_ShouldNotDependOnSinglePixelType_RgbaVector_filter0.png +++ b/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_ShouldNotDependOnSinglePixelType_RgbaVector_filter0.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:2f6df6c76c0f3795c439147fd49d658e52fae5c24a071ee0f4fed7aa096d87d1 -size 719 +oid sha256:0e7ece9d70c4fe0771abd43e4dbb33fb95f474ca56633dcb821022ee44e746d4 +size 728 diff --git a/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_Bike_Atkinson.png b/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_Bike_Atkinson.png index 5db7bd5ef..1656b2e9c 100644 --- a/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_Bike_Atkinson.png +++ b/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_Bike_Atkinson.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c419d6b6cb589f95bff06514e3a5da37d7b5704aa3f9e6f4365fe5e00c6d81d1 -size 50670 +oid sha256:38597c6144d61960d25c74d7a465b1cdf69b7c0804a6dec68128a6c953258313 +size 52688 diff --git a/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_Bike_Burks.png b/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_Bike_Burks.png index b2c857801..c6016ae35 100644 --- a/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_Bike_Burks.png +++ b/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_Bike_Burks.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:90c337c38076fb597ecde87e0e8b5b95c369a90a99e64c5add3b31fc778cb52d -size 61178 +oid sha256:5f9191c71eea1f73aa4c55397ca26f240615c9c4a7fff9a05e6f2e046b5e4d8b +size 62323 diff --git a/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_Bike_FloydSteinberg.png b/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_Bike_FloydSteinberg.png index 7ceb6114a..40243937d 100644 --- a/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_Bike_FloydSteinberg.png +++ b/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_Bike_FloydSteinberg.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:7ce4b978b800820275635ae8da59212661c74a1b6ba9050ebf052c87315aedef -size 62107 +oid sha256:b63810145832db459bb7a6b37a028a7b778f6b6b4e6eae00e50e6e21c5a06086 +size 62199 diff --git a/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_Bike_JarvisJudiceNinke.png b/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_Bike_JarvisJudiceNinke.png index 9f2a2482a..83f9e067d 100644 --- a/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_Bike_JarvisJudiceNinke.png +++ b/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_Bike_JarvisJudiceNinke.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e44a87274d4f4fc5f1a0a849f27108ff8b98410f5d1e5e328236a9c79b9af51b -size 56175 +oid sha256:a67c14ef99a943706f050ff1ea0ef101429292d52bc14ed4610f8338736ff87e +size 56800 diff --git a/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_Bike_Sierra2.png b/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_Bike_Sierra2.png index a8a0eda5e..22e4f4b6d 100644 --- a/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_Bike_Sierra2.png +++ b/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_Bike_Sierra2.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:542285d1c2a375f64680173ca2736399d061009413e8aacf62f785425357f868 -size 58538 +oid sha256:623dd82d372ba517b0d3357d06cffaf105d407a9090cbcbc6a76ae944ab33d67 +size 59468 diff --git a/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_Bike_Sierra3.png b/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_Bike_Sierra3.png index 58a5909c0..838863c15 100644 --- a/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_Bike_Sierra3.png +++ b/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_Bike_Sierra3.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b4aa18018421bda728ec9adab4eff235d4f4c683a0bb5328dab386833685ab35 -size 57616 +oid sha256:8edceef8e12c4f3d194523437045c5cf4e80c7bb95ff75f38c1f38a21872e3d0 +size 59376 diff --git a/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_Bike_SierraLite.png b/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_Bike_SierraLite.png index 847aab49c..60513e199 100644 --- a/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_Bike_SierraLite.png +++ b/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_Bike_SierraLite.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ec9d5fe07a99a995e889870ac4f6d264360adf1dbc5cb656d5657e096fe7f393 -size 61838 +oid sha256:b1d7019e8cb170ae67496f8250446c4f6b6217378658408c3d51a95c49a4c3bc +size 63287 diff --git a/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_Bike_StevensonArce.png b/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_Bike_StevensonArce.png index d8cb185c9..0d1b34d8c 100644 --- a/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_Bike_StevensonArce.png +++ b/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_Bike_StevensonArce.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b2dd1df6e5fb97b5fdeab6450713432ee1e0a318953b11ab25f4618bde369792 -size 54658 +oid sha256:d7c03ede7ab3bd4e57e6a63e53e2e8c771e938fdc7d5dfe5c9339a2c9907c9cf +size 55550 diff --git a/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_Bike_Stucki.png b/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_Bike_Stucki.png index 6df5a227b..f8c998ecb 100644 --- a/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_Bike_Stucki.png +++ b/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_Bike_Stucki.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ba66ef358ebcec36932e6d2874827846b31585fcbc0683e26c548fc3706e27e3 -size 57294 +oid sha256:79b690b91223d1fe7ddf1b8826b4474b89644822bc8aa9adee3cf819bc095b4c +size 60979 diff --git a/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_CalliphoraPartial_Atkinson.png b/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_CalliphoraPartial_Atkinson.png index c4a20b45a..cc2327b23 100644 --- a/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_CalliphoraPartial_Atkinson.png +++ b/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_CalliphoraPartial_Atkinson.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:9f777e6d61a882da066a00b9d268be5fdbf49bdbccb568eb5bb3d959fe9f4fb0 -size 57454 +oid sha256:7e22401dddf6552cd91517c1cdd142d3b9a66a7ad5c80d2e52ae07a7f583708e +size 57657 diff --git a/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_CalliphoraPartial_Burks.png b/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_CalliphoraPartial_Burks.png index 0c5249484..e3ae6508e 100644 --- a/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_CalliphoraPartial_Burks.png +++ b/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_CalliphoraPartial_Burks.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:2b00d60902d54f911a9b4ab9be225ab589073b9f0efe06019895a75e487eadec -size 59415 +oid sha256:819a0ce38e27e2adfa454d8c5ad5b24e818bf8954c9f2406f608dcecf506c2c4 +size 59838 diff --git a/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_CalliphoraPartial_FloydSteinberg.png b/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_CalliphoraPartial_FloydSteinberg.png index 036448778..2b897a5d6 100644 --- a/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_CalliphoraPartial_FloydSteinberg.png +++ b/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_CalliphoraPartial_FloydSteinberg.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ded9cb4986e620f5fc11486ccdf81934dff7b3cea7db7e292929d339ecc21714 -size 60116 +oid sha256:007ac609ec61b39c7bdd04bc87a698f5cdc76eadd834c1457f41eb9c135c3f7b +size 60688 diff --git a/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_CalliphoraPartial_JarvisJudiceNinke.png b/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_CalliphoraPartial_JarvisJudiceNinke.png index 587929f1f..10ba90ae8 100644 --- a/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_CalliphoraPartial_JarvisJudiceNinke.png +++ b/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_CalliphoraPartial_JarvisJudiceNinke.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1518a2c1840a8d88767a45c7ba08b904ea471c7422fbb5f2b623f1f219992f83 -size 58479 +oid sha256:46892c07e9a93f1df71f0e38b331a437fb9b7c52d8f40cf62780cb6bd35d3b13 +size 58963 diff --git a/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_CalliphoraPartial_Sierra2.png b/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_CalliphoraPartial_Sierra2.png index 0cccb2c77..9608289e8 100644 --- a/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_CalliphoraPartial_Sierra2.png +++ b/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_CalliphoraPartial_Sierra2.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:01c6000407f5302a5dd27a88053f795cdb101c791f92e983e6859792ef8a0935 -size 58879 +oid sha256:1b83345ca3de8d1fc0fbb5d8e68329b94ad79fc29b9f10a1392a97ffe9a0733e +size 58985 diff --git a/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_CalliphoraPartial_Sierra3.png b/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_CalliphoraPartial_Sierra3.png index 7150bcc1f..79d2c5eb1 100644 --- a/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_CalliphoraPartial_Sierra3.png +++ b/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_CalliphoraPartial_Sierra3.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:11438d8eef23ae6ff5b7d37f0ccc9b6bfe744abb2dc95785a5769c306e51d11b -size 58908 +oid sha256:c775a5b19ba09e1b335389e0dc12cb0c3feaff6072e904da750a676fcd6b07dc +size 59202 diff --git a/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_CalliphoraPartial_SierraLite.png b/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_CalliphoraPartial_SierraLite.png index d1919cbd1..8d3cf1a56 100644 --- a/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_CalliphoraPartial_SierraLite.png +++ b/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_CalliphoraPartial_SierraLite.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:57584582f914c0c059899f9241de006526a14081093b647972c363b00de62a57 -size 60492 +oid sha256:6c88740c0553829eaa42ca751b34cc456623a84ccdff4020949a06ef4b4802d1 +size 61137 diff --git a/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_CalliphoraPartial_StevensonArce.png b/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_CalliphoraPartial_StevensonArce.png index 6778314d5..a146f8f66 100644 --- a/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_CalliphoraPartial_StevensonArce.png +++ b/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_CalliphoraPartial_StevensonArce.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:adfc7192b03c29addbc9c19e9eefc5c122d35d3d0fae7dfa65d3390ee5f60502 -size 57887 +oid sha256:0a4a404b0767faac952435f768867cf7bf053848e1e3ef121624f136658a107c +size 58386 diff --git a/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_CalliphoraPartial_Stucki.png b/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_CalliphoraPartial_Stucki.png index 5c8c2f3cc..edec46a92 100644 --- a/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_CalliphoraPartial_Stucki.png +++ b/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_CalliphoraPartial_Stucki.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:9735370ed168043d1fd176a258f08fcd705dcefc1d5e6841d012e843ec34d7a9 -size 58485 +oid sha256:8cc216ed952216d203836dc559234216614f1ed059651677cc0ea714010bd932 +size 58855 diff --git a/tests/Images/External/ReferenceOutput/DitherTests/DitherFilter_WorksWithAllDitherers_Bike_Bayer16x16.png b/tests/Images/External/ReferenceOutput/DitherTests/DitherFilter_WorksWithAllDitherers_Bike_Bayer16x16.png index cb4ce6138..e899ffb42 100644 --- a/tests/Images/External/ReferenceOutput/DitherTests/DitherFilter_WorksWithAllDitherers_Bike_Bayer16x16.png +++ b/tests/Images/External/ReferenceOutput/DitherTests/DitherFilter_WorksWithAllDitherers_Bike_Bayer16x16.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:5f844bf243df10178cdc8bd0d49fa8e7722476373c5b2cbc494bdc53eaa7a116 -size 44133 +oid sha256:ca70bb0200776efd00c4ef7596d4e1f2f5fbc68e447b395b25ef2b3c732e5156 +size 44189 diff --git a/tests/Images/External/ReferenceOutput/DitherTests/DitherFilter_WorksWithAllDitherers_Bike_Bayer2x2.png b/tests/Images/External/ReferenceOutput/DitherTests/DitherFilter_WorksWithAllDitherers_Bike_Bayer2x2.png index 7669d9686..543640c2e 100644 --- a/tests/Images/External/ReferenceOutput/DitherTests/DitherFilter_WorksWithAllDitherers_Bike_Bayer2x2.png +++ b/tests/Images/External/ReferenceOutput/DitherTests/DitherFilter_WorksWithAllDitherers_Bike_Bayer2x2.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b80161aa7723ac8804a2679fbb1fc8327961166475e1a3567ee7bf1f61b43307 -size 41530 +oid sha256:8474b847b7d4a8f3e5c9793ca257ce46efcf49c473c731a9ca9c759851410b94 +size 43066 diff --git a/tests/Images/External/ReferenceOutput/DitherTests/DitherFilter_WorksWithAllDitherers_Bike_Bayer4x4.png b/tests/Images/External/ReferenceOutput/DitherTests/DitherFilter_WorksWithAllDitherers_Bike_Bayer4x4.png index cc825e03f..fec3c9b2b 100644 --- a/tests/Images/External/ReferenceOutput/DitherTests/DitherFilter_WorksWithAllDitherers_Bike_Bayer4x4.png +++ b/tests/Images/External/ReferenceOutput/DitherTests/DitherFilter_WorksWithAllDitherers_Bike_Bayer4x4.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:69e745d55bb37c75c5651713648fb9d2d761b8790b86b67e9a553a7ae6328f0a -size 43625 +oid sha256:20e80e7d9e68fd85bfbc63c61953327354b0634000ec142e01a42618995fd14c +size 44391 diff --git a/tests/Images/External/ReferenceOutput/DitherTests/DitherFilter_WorksWithAllDitherers_Bike_Bayer8x8.png b/tests/Images/External/ReferenceOutput/DitherTests/DitherFilter_WorksWithAllDitherers_Bike_Bayer8x8.png index 9710298a3..68a95a054 100644 --- a/tests/Images/External/ReferenceOutput/DitherTests/DitherFilter_WorksWithAllDitherers_Bike_Bayer8x8.png +++ b/tests/Images/External/ReferenceOutput/DitherTests/DitherFilter_WorksWithAllDitherers_Bike_Bayer8x8.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a2a534bc0862e0d7325e9d6bbdf1c73ddc356bd621dfdc64a9c0c04399a93e00 -size 43807 +oid sha256:8af98bfcc5edef3f3ff33ee8f76f33ce2906a6677167e2b29e1dbe63b00a78d8 +size 44202 diff --git a/tests/Images/External/ReferenceOutput/DitherTests/DitherFilter_WorksWithAllDitherers_Bike_Ordered3x3.png b/tests/Images/External/ReferenceOutput/DitherTests/DitherFilter_WorksWithAllDitherers_Bike_Ordered3x3.png index e6fc9aa0f..d67f02dca 100644 --- a/tests/Images/External/ReferenceOutput/DitherTests/DitherFilter_WorksWithAllDitherers_Bike_Ordered3x3.png +++ b/tests/Images/External/ReferenceOutput/DitherTests/DitherFilter_WorksWithAllDitherers_Bike_Ordered3x3.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:4ba2d0740f6d614725125c393ed8cf19e0dc648a82b114de5b8531a97e828351 -size 43868 +oid sha256:b149ebbd550808ae46ff05b5ddcdb1fc0eb6ae0eacbe048e9a1ff24368d8f64d +size 45003 diff --git a/tests/Images/External/ReferenceOutput/DitherTests/DitherFilter_WorksWithAllDitherers_CalliphoraPartial_Bayer16x16.png b/tests/Images/External/ReferenceOutput/DitherTests/DitherFilter_WorksWithAllDitherers_CalliphoraPartial_Bayer16x16.png index 3251f0466..4175cf40b 100644 --- a/tests/Images/External/ReferenceOutput/DitherTests/DitherFilter_WorksWithAllDitherers_CalliphoraPartial_Bayer16x16.png +++ b/tests/Images/External/ReferenceOutput/DitherTests/DitherFilter_WorksWithAllDitherers_CalliphoraPartial_Bayer16x16.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a980061a57ac57351b4ccedb34f915319373759753cf2d28fb604a968b87fb2f -size 50640 +oid sha256:9316cbbcb137ae6ff31646f6a5ba1d0aec100db4512509f7684187e74d16a111 +size 51074 diff --git a/tests/Images/External/ReferenceOutput/DitherTests/DitherFilter_WorksWithAllDitherers_CalliphoraPartial_Bayer2x2.png b/tests/Images/External/ReferenceOutput/DitherTests/DitherFilter_WorksWithAllDitherers_CalliphoraPartial_Bayer2x2.png index 7bf58cd3e..11d916bdc 100644 --- a/tests/Images/External/ReferenceOutput/DitherTests/DitherFilter_WorksWithAllDitherers_CalliphoraPartial_Bayer2x2.png +++ b/tests/Images/External/ReferenceOutput/DitherTests/DitherFilter_WorksWithAllDitherers_CalliphoraPartial_Bayer2x2.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:4b64630a74a435fde316a2d6be140ef54b04fecbf5fa76e31014d92c9cd925db -size 52298 +oid sha256:08c39a43993deadebab21f1d3504027b5910a52adc437c167d77d62e5f5db46e +size 52762 diff --git a/tests/Images/External/ReferenceOutput/DitherTests/DitherFilter_WorksWithAllDitherers_CalliphoraPartial_Bayer4x4.png b/tests/Images/External/ReferenceOutput/DitherTests/DitherFilter_WorksWithAllDitherers_CalliphoraPartial_Bayer4x4.png index c7e3a0589..a4f91b330 100644 --- a/tests/Images/External/ReferenceOutput/DitherTests/DitherFilter_WorksWithAllDitherers_CalliphoraPartial_Bayer4x4.png +++ b/tests/Images/External/ReferenceOutput/DitherTests/DitherFilter_WorksWithAllDitherers_CalliphoraPartial_Bayer4x4.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:3535fa1a240cc7c11e9bb233333894f8782ec67ac36708c8ee3baf905c2d3f65 -size 51268 +oid sha256:0c9c47fa755d603f8c148011511ee91f32444e0d94367f9db57593e3bf30f2e0 +size 51808 diff --git a/tests/Images/External/ReferenceOutput/DitherTests/DitherFilter_WorksWithAllDitherers_CalliphoraPartial_Bayer8x8.png b/tests/Images/External/ReferenceOutput/DitherTests/DitherFilter_WorksWithAllDitherers_CalliphoraPartial_Bayer8x8.png index 687cd1dbc..ac56fa923 100644 --- a/tests/Images/External/ReferenceOutput/DitherTests/DitherFilter_WorksWithAllDitherers_CalliphoraPartial_Bayer8x8.png +++ b/tests/Images/External/ReferenceOutput/DitherTests/DitherFilter_WorksWithAllDitherers_CalliphoraPartial_Bayer8x8.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e2d47f277bc904ed8c3b3bbfe636b647884511de39661bfd34cdcb2abe9f6a13 -size 50818 +oid sha256:6d2289ed4fa0c679f0f120d260fec8ab40b1599043cc0a1fbebc6b67e238ff87 +size 51428 diff --git a/tests/Images/External/ReferenceOutput/DitherTests/DitherFilter_WorksWithAllDitherers_CalliphoraPartial_Ordered3x3.png b/tests/Images/External/ReferenceOutput/DitherTests/DitherFilter_WorksWithAllDitherers_CalliphoraPartial_Ordered3x3.png index dd21dc76d..9a7c7b461 100644 --- a/tests/Images/External/ReferenceOutput/DitherTests/DitherFilter_WorksWithAllDitherers_CalliphoraPartial_Ordered3x3.png +++ b/tests/Images/External/ReferenceOutput/DitherTests/DitherFilter_WorksWithAllDitherers_CalliphoraPartial_Ordered3x3.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ed86ea3844a13de5247bcb409c70ca4a8051cb18b6a3c298a5665f4573f89490 -size 51756 +oid sha256:366e84ab8587735455798651096d2af5f965fc325f4852dc68356e94600598b1 +size 52176 diff --git a/tests/Images/External/ReferenceOutput/PngEncoderTests/Issue2469_Quantized_Encode_Artifacts_Rgba32_issue_2469.png b/tests/Images/External/ReferenceOutput/PngEncoderTests/Issue2469_Quantized_Encode_Artifacts_Rgba32_issue_2469.png index 6ea363a34..4c7830375 100644 --- a/tests/Images/External/ReferenceOutput/PngEncoderTests/Issue2469_Quantized_Encode_Artifacts_Rgba32_issue_2469.png +++ b/tests/Images/External/ReferenceOutput/PngEncoderTests/Issue2469_Quantized_Encode_Artifacts_Rgba32_issue_2469.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:968a3cfdec62a89823e711f7ed15e3c456a8e44b8f9d46268dd312693e04be00 -size 1028911 +oid sha256:1af50619f835b4470afac4553445176c121c3c9fa838dff937dcc56ae37941c3 +size 945821 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_OctreeQuantizer_ErrorDither.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_OctreeQuantizer_ErrorDither.png index 939897ba9..4948c7ade 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_OctreeQuantizer_ErrorDither.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_OctreeQuantizer_ErrorDither.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:62b08eae3bbcc99c2dace7156dae8e37786416e1e428455417b3efc7cdcbd56a -size 240453 +oid sha256:a51d04953c1c82d99884af62912d2271108c6bc62f18d4b32d0b5290c01fa7f7 +size 247462 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_OctreeQuantizer_NoDither.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_OctreeQuantizer_NoDither.png index 5afed5704..d993923d4 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_OctreeQuantizer_NoDither.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_OctreeQuantizer_NoDither.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f240153a429da26fbe2ffcbce71ceeae47225c261002c242c34b2747823d0f9a -size 231151 +oid sha256:9f165908729d723818b6c5843bd75298d987448e2cd4278dfe3f388a62025add +size 238396 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_OctreeQuantizer_OrderedDither.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_OctreeQuantizer_OrderedDither.png index 0a026b217..223d3bc01 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_OctreeQuantizer_OrderedDither.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_OctreeQuantizer_OrderedDither.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:7700134ca070ccef3f027b70534a811e4a388d05b421ca91d9176d84310ddfc0 -size 237380 +oid sha256:34eaa0696da00838e591b2c48e7797641521f7f3feb01abbd774591c4dd6f200 +size 265546 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_WebSafePaletteQuantizer_ErrorDither.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_WebSafePaletteQuantizer_ErrorDither.png index 977181666..922c2bf9b 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_WebSafePaletteQuantizer_ErrorDither.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_WebSafePaletteQuantizer_ErrorDither.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:4e46eddb5225c37cfe20a4d94af0eabd1def78eb379a17d9cdae5fae0866d2bb -size 208360 +oid sha256:4f1462733e02d499b0d8c61ab835a27c7fee560fdc7fc521d20ec09bb4ccc80f +size 216030 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_WebSafePaletteQuantizer_NoDither.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_WebSafePaletteQuantizer_NoDither.png index 977181666..922c2bf9b 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_WebSafePaletteQuantizer_NoDither.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_WebSafePaletteQuantizer_NoDither.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:4e46eddb5225c37cfe20a4d94af0eabd1def78eb379a17d9cdae5fae0866d2bb -size 208360 +oid sha256:4f1462733e02d499b0d8c61ab835a27c7fee560fdc7fc521d20ec09bb4ccc80f +size 216030 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_WebSafePaletteQuantizer_OrderedDither.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_WebSafePaletteQuantizer_OrderedDither.png index cce645585..29c93d14e 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_WebSafePaletteQuantizer_OrderedDither.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_WebSafePaletteQuantizer_OrderedDither.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:344efea18d09c8c9b23d7c06a8e865c271dc6acdc9330b589d9ebe15b48c285f -size 215670 +oid sha256:7e6d91a3ec4f974af675dc360fd5fd623ec8773cdbc88c0a3a6506880838718a +size 226727 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_WernerPaletteQuantizer_ErrorDither.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_WernerPaletteQuantizer_ErrorDither.png index a5433fa6e..dbfab2b50 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_WernerPaletteQuantizer_ErrorDither.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_WernerPaletteQuantizer_ErrorDither.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c32da9b861ac38306d1bfcae6384e4cdf449cfdb9de1fee3fdb6975e4096aea6 -size 212659 +oid sha256:c68eba122814b5470e5f2e03e34190ff79e84e4b431ad8227355ce7ffcd4a6a7 +size 220192 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_WernerPaletteQuantizer_NoDither.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_WernerPaletteQuantizer_NoDither.png index a5433fa6e..dbfab2b50 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_WernerPaletteQuantizer_NoDither.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_WernerPaletteQuantizer_NoDither.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c32da9b861ac38306d1bfcae6384e4cdf449cfdb9de1fee3fdb6975e4096aea6 -size 212659 +oid sha256:c68eba122814b5470e5f2e03e34190ff79e84e4b431ad8227355ce7ffcd4a6a7 +size 220192 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_WernerPaletteQuantizer_OrderedDither.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_WernerPaletteQuantizer_OrderedDither.png index 284b8ad04..86655af42 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_WernerPaletteQuantizer_OrderedDither.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_WernerPaletteQuantizer_OrderedDither.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:60fcd8cf74e595b9f5add7ef6924e4ed801f3835b7d2873bbc79e0b61b98bb0f -size 218566 +oid sha256:6dbd3189b559941f91dd6e0aa15b34a3e5081477400678c2396c6a66d398876f +size 230883 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_WuQuantizer_ErrorDither.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_WuQuantizer_ErrorDither.png index 430f98d00..82d5e5d59 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_WuQuantizer_ErrorDither.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_WuQuantizer_ErrorDither.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:3a650e180ee2f073f5d06ef343f5d14cde568777a21d8f419dae6c23f36bfb66 -size 255237 +oid sha256:f4df5b1bc2c291ec1cf599580d198b447278412576ab998e099cc21110e82b3d +size 263152 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_WuQuantizer_NoDither.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_WuQuantizer_NoDither.png index f75e87831..d8a1178ad 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_WuQuantizer_NoDither.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_WuQuantizer_NoDither.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:934c4a8fd38d2d360dd1b69627043895975ee33ed63e2b1e514b8c6f8451c3f0 -size 253854 +oid sha256:df63a3d12e2998d5242b64169ac86e3df7ab4be585a80daddc3e3888dfcb7095 +size 262298 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_WuQuantizer_OrderedDither.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_WuQuantizer_OrderedDither.png index 558f8a545..76946ee06 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_WuQuantizer_OrderedDither.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_WuQuantizer_OrderedDither.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:38a6c8caeef772c51508954483810ecf4aa13168a8ec33af266cde028ee19f75 -size 249584 +oid sha256:457a0b4e27a09440ff4e13792b68fb5a9da82b7ce6129ea15a5ea8dcd99bd522 +size 274300 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_OctreeQuantizer_ErrorDither.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_OctreeQuantizer_ErrorDither.png index fb9b5513c..ebb9ff6b0 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_OctreeQuantizer_ErrorDither.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_OctreeQuantizer_ErrorDither.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:649e8a71ba3328af6a48a0afe8747433bce459bed50ba5f4108343265f07cdf2 -size 314905 +oid sha256:f414473561bfa792c2e6342ff5e5dddffbdec5286932781b11a093803593b52a +size 313787 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_OctreeQuantizer_NoDither.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_OctreeQuantizer_NoDither.png index 77d1cc115..7e3080562 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_OctreeQuantizer_NoDither.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_OctreeQuantizer_NoDither.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d484a8d20c288de3f30b6c8ea2cecb6893d618d96e8a9079c0003ee49c798967 -size 303608 +oid sha256:0203ecb9e4665e7c3992b7da4777c6d35b539790506fc9ca2acbcbc2bdb5db18 +size 303979 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_OctreeQuantizer_OrderedDither.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_OctreeQuantizer_OrderedDither.png index 34ba72d10..5626fa1b8 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_OctreeQuantizer_OrderedDither.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_OctreeQuantizer_OrderedDither.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6b935deeff7a6f9be835f1975513b0063090d52e5d4178da1ce8575909729164 -size 320831 +oid sha256:62cdce27fc46a38a16995df8ed1501f65091d69315288479b1d613b1d87c8239 +size 321123 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_WebSafePaletteQuantizer_ErrorDither.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_WebSafePaletteQuantizer_ErrorDither.png index c575778e0..020562673 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_WebSafePaletteQuantizer_ErrorDither.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_WebSafePaletteQuantizer_ErrorDither.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c14e3f64a9fa9487c4828bc931338525663177828d65f31928b1acf6f5c1d3a7 -size 269542 +oid sha256:3a2aae04edebcaca9b95f30963201794887fa0eac954b64c68bfe529b14fa9be +size 269397 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_WebSafePaletteQuantizer_NoDither.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_WebSafePaletteQuantizer_NoDither.png index c575778e0..020562673 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_WebSafePaletteQuantizer_NoDither.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_WebSafePaletteQuantizer_NoDither.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c14e3f64a9fa9487c4828bc931338525663177828d65f31928b1acf6f5c1d3a7 -size 269542 +oid sha256:3a2aae04edebcaca9b95f30963201794887fa0eac954b64c68bfe529b14fa9be +size 269397 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_WebSafePaletteQuantizer_OrderedDither.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_WebSafePaletteQuantizer_OrderedDither.png index 02f91294f..68d91fc43 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_WebSafePaletteQuantizer_OrderedDither.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_WebSafePaletteQuantizer_OrderedDither.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:fc2c4df81110029eeaf07419c6a5a823faba757aacf776241ad3171370f70338 -size 271393 +oid sha256:2f3e9a338a5ae37c88ce0c348e0b655429220da051db3352779c277bb2dcb441 +size 270622 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_WernerPaletteQuantizer_ErrorDither.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_WernerPaletteQuantizer_ErrorDither.png index c6f8957ee..324bd9253 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_WernerPaletteQuantizer_ErrorDither.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_WernerPaletteQuantizer_ErrorDither.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:4812a323ca35208e55faf476d342202b320a302df9475f40f8d96161e09ae7ea -size 284361 +oid sha256:752760327cc1416c171a920f1e0e95e34eae6d78bd0c7393a3be427bf3c8e55c +size 284481 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_WernerPaletteQuantizer_NoDither.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_WernerPaletteQuantizer_NoDither.png index c6f8957ee..324bd9253 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_WernerPaletteQuantizer_NoDither.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_WernerPaletteQuantizer_NoDither.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:4812a323ca35208e55faf476d342202b320a302df9475f40f8d96161e09ae7ea -size 284361 +oid sha256:752760327cc1416c171a920f1e0e95e34eae6d78bd0c7393a3be427bf3c8e55c +size 284481 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_WernerPaletteQuantizer_OrderedDither.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_WernerPaletteQuantizer_OrderedDither.png index 13974a84c..52bf2a163 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_WernerPaletteQuantizer_OrderedDither.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_WernerPaletteQuantizer_OrderedDither.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:5022fb7d43ec6885efcd6e7513ad92019f28febb91e7e2b61c421bf96cfbb76c -size 285270 +oid sha256:293459538454e07bc9ea1e9df1fa5b0eb986fde7de42f6c25b43e4c8859bd28a +size 285370 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_WuQuantizer_ErrorDither.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_WuQuantizer_ErrorDither.png index 7d07b09e1..05be1395a 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_WuQuantizer_ErrorDither.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_WuQuantizer_ErrorDither.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:eb153e0e3c50cc038f8ab215646dd2a1739e748fa4a7a04ef34ea2f6d8e0c22a -size 318803 +oid sha256:90a2b7b3872c6eb1f1f039558d9f6ace92891c86951c801da01ad55b055fd670 +size 316544 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_WuQuantizer_NoDither.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_WuQuantizer_NoDither.png index 9342757f6..d94d57759 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_WuQuantizer_NoDither.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_WuQuantizer_NoDither.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0e09d63aaf5365a1f4ac44beef9f203c4217a7e158703d44b2b1007c413b1f0c -size 318179 +oid sha256:ff094e6bafe81e818bcbac69018dcfe29366389dfca0d63d8e05ef42896ffe1d +size 317309 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_WuQuantizer_OrderedDither.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_WuQuantizer_OrderedDither.png index 5a9662e03..e016e3de6 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_WuQuantizer_OrderedDither.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_WuQuantizer_OrderedDither.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:563e9bf52b4ac7be22bc15e6aaf8b39e508eb7e31cb90986fe559ac88ba40829 -size 324538 +oid sha256:ee0778aac671365dd0afae06cdcf8f36243bd9815f684b975f83e297bb694e63 +size 323979 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_OctreeQuantizer_ErrorDither_0.25.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_OctreeQuantizer_ErrorDither_0.25.png index dae4b4e14..82b965123 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_OctreeQuantizer_ErrorDither_0.25.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_OctreeQuantizer_ErrorDither_0.25.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d96528a2950fb583ff1a1c00e7e13861fc582bd62b72deccaafb73f2cdd15b10 -size 20936 +oid sha256:4bed69d43856ebd4b1af4055f8d3aacabd50c361a4e1e1f9cad080d799d6b744 +size 13853 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_OctreeQuantizer_ErrorDither_0.5.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_OctreeQuantizer_ErrorDither_0.5.png index dae4b4e14..571b0db4b 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_OctreeQuantizer_ErrorDither_0.5.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_OctreeQuantizer_ErrorDither_0.5.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d96528a2950fb583ff1a1c00e7e13861fc582bd62b72deccaafb73f2cdd15b10 -size 20936 +oid sha256:4c8c8393708002f06f9d8ed1ff8979db820035585c08b66ae463d94724fa64d3 +size 14330 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_OctreeQuantizer_ErrorDither_0.75.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_OctreeQuantizer_ErrorDither_0.75.png index dae4b4e14..a1b3da681 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_OctreeQuantizer_ErrorDither_0.75.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_OctreeQuantizer_ErrorDither_0.75.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d96528a2950fb583ff1a1c00e7e13861fc582bd62b72deccaafb73f2cdd15b10 -size 20936 +oid sha256:fda13875f4c762a95001426487cc04c9add39821eb793168fdbe5cc18e705643 +size 14566 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_OctreeQuantizer_ErrorDither_0.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_OctreeQuantizer_ErrorDither_0.png index dae4b4e14..82b965123 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_OctreeQuantizer_ErrorDither_0.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_OctreeQuantizer_ErrorDither_0.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d96528a2950fb583ff1a1c00e7e13861fc582bd62b72deccaafb73f2cdd15b10 -size 20936 +oid sha256:4bed69d43856ebd4b1af4055f8d3aacabd50c361a4e1e1f9cad080d799d6b744 +size 13853 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_OctreeQuantizer_ErrorDither_1.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_OctreeQuantizer_ErrorDither_1.png index dae4b4e14..e0fc79202 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_OctreeQuantizer_ErrorDither_1.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_OctreeQuantizer_ErrorDither_1.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d96528a2950fb583ff1a1c00e7e13861fc582bd62b72deccaafb73f2cdd15b10 -size 20936 +oid sha256:cb826afb127fe4175e6e47253b8a8313b9d10aee193c316731f34e5d327a2591 +size 14580 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_OctreeQuantizer_OrderedDither_0.25.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_OctreeQuantizer_OrderedDither_0.25.png index 3abeb0316..491847e49 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_OctreeQuantizer_OrderedDither_0.25.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_OctreeQuantizer_OrderedDither_0.25.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:14dfcd85d0331448c1e3ff447e7e789ad4fc24618b6a9c5d05468bdd6a2f2209 -size 20804 +oid sha256:37018ecc499651833208d846a0f446db94cc11eae002ab6e7ce45b3e7c09e86c +size 17734 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_OctreeQuantizer_OrderedDither_0.5.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_OctreeQuantizer_OrderedDither_0.5.png index 3102144a1..013bb4a3b 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_OctreeQuantizer_OrderedDither_0.5.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_OctreeQuantizer_OrderedDither_0.5.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8e7df6174aad752e0f3846eeef5efc4bfc70b80e3622a54eba59f6dcd4673d87 -size 21802 +oid sha256:c2f9ed902882f58704b22460bc64a7b27bc6f47fc2c822ee09f52345cc0d6ebf +size 19255 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_OctreeQuantizer_OrderedDither_0.75.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_OctreeQuantizer_OrderedDither_0.75.png index f5c07ac7a..31fd7a544 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_OctreeQuantizer_OrderedDither_0.75.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_OctreeQuantizer_OrderedDither_0.75.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:95c739dd75b7be9dea10c12dd69ea60095ac617de491a2ba8063c86cd1bd9aa3 -size 22060 +oid sha256:aad3f26f2939f3679afa2b6165db29885fff40bbb1d171d5ffecc7861b5fac31 +size 19654 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_OctreeQuantizer_OrderedDither_0.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_OctreeQuantizer_OrderedDither_0.png index dae4b4e14..82b965123 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_OctreeQuantizer_OrderedDither_0.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_OctreeQuantizer_OrderedDither_0.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d96528a2950fb583ff1a1c00e7e13861fc582bd62b72deccaafb73f2cdd15b10 -size 20936 +oid sha256:4bed69d43856ebd4b1af4055f8d3aacabd50c361a4e1e1f9cad080d799d6b744 +size 13853 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_OctreeQuantizer_OrderedDither_1.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_OctreeQuantizer_OrderedDither_1.png index e1bc766fb..e2a05b9bd 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_OctreeQuantizer_OrderedDither_1.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_OctreeQuantizer_OrderedDither_1.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f49ea178ca76bd1b2165b0b700a558433d27e22b07b3b35be3fdf3a1868d7ad6 -size 22371 +oid sha256:6d21029fa22dbe72cdc60b90c758cb9becd9fce03a33580d9466c1aedd323c1c +size 20000 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WebSafePaletteQuantizer_ErrorDither_0.25.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WebSafePaletteQuantizer_ErrorDither_0.25.png index dc3d45fa1..9850675be 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WebSafePaletteQuantizer_ErrorDither_0.25.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WebSafePaletteQuantizer_ErrorDither_0.25.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:7bf316a54f0add7d92287fddc9cc3744d4be1a7ba7c6881d83d3f45356d2c53e -size 8627 +oid sha256:ea836214840a5da2b89dad3cd9e916413d3f9e21f9b855dc8161faa3544edcfc +size 9266 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WebSafePaletteQuantizer_ErrorDither_0.5.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WebSafePaletteQuantizer_ErrorDither_0.5.png index 6f49f2715..f3278c3d2 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WebSafePaletteQuantizer_ErrorDither_0.5.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WebSafePaletteQuantizer_ErrorDither_0.5.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:97c36988a937893cf106b7b7986fff32d0d90c882ed292010fc1172b1a149d22 -size 9388 +oid sha256:346c9e4239d917614525a99f7ae58ed0c0a22dc09d639f3a54dad1975e75ec44 +size 8833 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WebSafePaletteQuantizer_ErrorDither_0.75.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WebSafePaletteQuantizer_ErrorDither_0.75.png index 3498290b8..77821255b 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WebSafePaletteQuantizer_ErrorDither_0.75.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WebSafePaletteQuantizer_ErrorDither_0.75.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:5e167ecea77962fa0b8376f1217cbba64c90c77e67213b229fba1c6122d2bc06 -size 11011 +oid sha256:717fe46156f3d144f31cfce066dd13532ee8721d7d3a7b8c8425c646f411e8a5 +size 11099 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WebSafePaletteQuantizer_ErrorDither_0.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WebSafePaletteQuantizer_ErrorDither_0.png index 0f00a00f8..0615793d5 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WebSafePaletteQuantizer_ErrorDither_0.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WebSafePaletteQuantizer_ErrorDither_0.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b7fd78e11b8de40f646218597ec1ebccddbca7a6d901e5c87848ae1d9e0de361 -size 8390 +oid sha256:9e01c7276f1c4e905b1d8f4c84259f1047c0949f7a6a81f43a790bd1bd3201e3 +size 7932 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WebSafePaletteQuantizer_ErrorDither_1.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WebSafePaletteQuantizer_ErrorDither_1.png index 93f1ca234..c43b5836e 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WebSafePaletteQuantizer_ErrorDither_1.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WebSafePaletteQuantizer_ErrorDither_1.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:45f3bf5ae0e8061811eebd3328f1d8f7d54dc7b213f6324148c36292509a8ee5 -size 12761 +oid sha256:ae18d22edc011d576d6a1e9545bc52084ca0bed55a6ce19d391d2a5f97b1843c +size 11763 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WebSafePaletteQuantizer_OrderedDither_0.25.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WebSafePaletteQuantizer_OrderedDither_0.25.png index 2b611ace6..e54740610 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WebSafePaletteQuantizer_OrderedDither_0.25.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WebSafePaletteQuantizer_OrderedDither_0.25.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:36b1193c16c585918655bae2d98e4105b92cfab66ad903995f2af6b316e8b4a4 -size 9060 +oid sha256:74b3f36e3fbac940d1f3bf90089b6b40234aa2ce3570b094534a4448c1d98aec +size 8875 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WebSafePaletteQuantizer_OrderedDither_0.5.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WebSafePaletteQuantizer_OrderedDither_0.5.png index 44f717124..b08ba5be1 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WebSafePaletteQuantizer_OrderedDither_0.5.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WebSafePaletteQuantizer_OrderedDither_0.5.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:71d5c7b4addc49a13e0adb60dc53b463845957ea5df490cd476f011b6770373e -size 9633 +oid sha256:80e60c42fa11e973e1c865ed93448d3af0503e32d7b119bfe7162738efe691db +size 9086 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WebSafePaletteQuantizer_OrderedDither_0.75.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WebSafePaletteQuantizer_OrderedDither_0.75.png index 67fcc5bf6..692c119e4 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WebSafePaletteQuantizer_OrderedDither_0.75.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WebSafePaletteQuantizer_OrderedDither_0.75.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8f0e458b747377aad809e48fdf6d7af69b1e523e1728d2367615bb7ce90c2680 -size 9733 +oid sha256:fd5a9c76ee332603877624e219d84f85fe159389e7f9e72d1fb6177289dd1fb7 +size 9777 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WebSafePaletteQuantizer_OrderedDither_0.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WebSafePaletteQuantizer_OrderedDither_0.png index 0d4f8c0f9..0615793d5 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WebSafePaletteQuantizer_OrderedDither_0.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WebSafePaletteQuantizer_OrderedDither_0.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:08e2b6d31c842366175508fbe74c8ba546d0fa88f366ebb27279e38939b3bfa8 -size 8375 +oid sha256:9e01c7276f1c4e905b1d8f4c84259f1047c0949f7a6a81f43a790bd1bd3201e3 +size 7932 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WebSafePaletteQuantizer_OrderedDither_1.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WebSafePaletteQuantizer_OrderedDither_1.png index 7e124931c..17a810448 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WebSafePaletteQuantizer_OrderedDither_1.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WebSafePaletteQuantizer_OrderedDither_1.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a7052b23b8d91947ba1f869f13ff99136096805e889558a741ce8e5eac3ece63 -size 9965 +oid sha256:240743d5f742b872c0f66f4033ad065402372605a76cda23f4c506d254a9d127 +size 9791 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WernerPaletteQuantizer_ErrorDither_0.25.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WernerPaletteQuantizer_ErrorDither_0.25.png index a54b4088b..10b511a1a 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WernerPaletteQuantizer_ErrorDither_0.25.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WernerPaletteQuantizer_ErrorDither_0.25.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:5d5d5023535689657840dbf63d6343f4d1070cf5ec853586447eb8465da9640a -size 11444 +oid sha256:074842dcbdf60690f41da31e12c290045d05ab6dc587f3f5ba29c9496871391c +size 11209 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WernerPaletteQuantizer_ErrorDither_0.5.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WernerPaletteQuantizer_ErrorDither_0.5.png index 7b0ab347d..1ed81c0d0 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WernerPaletteQuantizer_ErrorDither_0.5.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WernerPaletteQuantizer_ErrorDither_0.5.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0f9ffc543dd312b3ca9d00f5444be214edfd78dbfb1a740556da3872b33623bf -size 12062 +oid sha256:29e1ff6d454efca61852a88946e25dcf29708230bfc47c2625c4d1b2407070c6 +size 12072 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WernerPaletteQuantizer_ErrorDither_0.75.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WernerPaletteQuantizer_ErrorDither_0.75.png index 1cf8978bc..30f75826e 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WernerPaletteQuantizer_ErrorDither_0.75.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WernerPaletteQuantizer_ErrorDither_0.75.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:038d07eb02ed4fdfabfd97a4c7f4e1f1be25ae7ea874bac0ab192a18ea6be397 -size 12760 +oid sha256:f7838c37c32134f325960312095ed8e1decbb0dd7e14a84e82637258c7ea117e +size 12826 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WernerPaletteQuantizer_ErrorDither_0.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WernerPaletteQuantizer_ErrorDither_0.png index b5926ee62..af9954116 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WernerPaletteQuantizer_ErrorDither_0.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WernerPaletteQuantizer_ErrorDither_0.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:cc402424e236ec35f0b510451533b1566dd9c4d4dea45af93aab8279f5f5abbe -size 10880 +oid sha256:0166ad5236ecdcc943d839fad092fe3899dcd4e418703846c492edb7700e4726 +size 10682 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WernerPaletteQuantizer_ErrorDither_1.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WernerPaletteQuantizer_ErrorDither_1.png index 3a24b61af..5b8c5127c 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WernerPaletteQuantizer_ErrorDither_1.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WernerPaletteQuantizer_ErrorDither_1.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:92c44ac71bf6822f12c8e90e1727da8db6e802c293f7945e5e6f6a9c311faccf -size 14115 +oid sha256:aaeee39c61b86d9ce569ca2288f998b8461a3f2169dac23cf2f750dd475d8b81 +size 14145 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WernerPaletteQuantizer_OrderedDither_0.25.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WernerPaletteQuantizer_OrderedDither_0.25.png index 476a346c6..93fa5c1de 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WernerPaletteQuantizer_OrderedDither_0.25.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WernerPaletteQuantizer_OrderedDither_0.25.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:fcc5ba9e73231485a4cfc3d38a6692762b079aa4057c7b32177c982a64b786f2 -size 12632 +oid sha256:5908ff88ddaa6eb3faea6174d87b0182e4407b11812ad70ddcd39c6619b6a5c5 +size 12615 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WernerPaletteQuantizer_OrderedDither_0.5.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WernerPaletteQuantizer_OrderedDither_0.5.png index e00a684ee..af2345fe5 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WernerPaletteQuantizer_OrderedDither_0.5.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WernerPaletteQuantizer_OrderedDither_0.5.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:36f5aea2b06ca8d8f04beac2abd75a3620913e16f4ae55173b3b5cb371924d3f -size 13256 +oid sha256:b6852daae665638e38c0b7ff58b2a0de1d5df9dd771c5cbccbbb83ff78e6a1d7 +size 12741 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WernerPaletteQuantizer_OrderedDither_0.75.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WernerPaletteQuantizer_OrderedDither_0.75.png index 6d8df6c27..3f91a9259 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WernerPaletteQuantizer_OrderedDither_0.75.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WernerPaletteQuantizer_OrderedDither_0.75.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d77d1c95fcc3dbe43c34bc94733eb6e8b3d007d023883f9bf18c9d9373444882 -size 13287 +oid sha256:6d86473ff1024fc53373b1dba49fc14283b8a323d6b85ba3e16f41ebff8288d0 +size 12845 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WernerPaletteQuantizer_OrderedDither_0.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WernerPaletteQuantizer_OrderedDither_0.png index b5926ee62..af9954116 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WernerPaletteQuantizer_OrderedDither_0.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WernerPaletteQuantizer_OrderedDither_0.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:cc402424e236ec35f0b510451533b1566dd9c4d4dea45af93aab8279f5f5abbe -size 10880 +oid sha256:0166ad5236ecdcc943d839fad092fe3899dcd4e418703846c492edb7700e4726 +size 10682 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WernerPaletteQuantizer_OrderedDither_1.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WernerPaletteQuantizer_OrderedDither_1.png index 9ecfcb1ff..878a36a47 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WernerPaletteQuantizer_OrderedDither_1.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WernerPaletteQuantizer_OrderedDither_1.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:30f8c8275afb6c61803facee006f3a181151e4d3d09a398eba60704a8e799df9 -size 13834 +oid sha256:b2bd11fa19fab712b5cd6c2b36d673c7dce904b5032b860d257b00e095e4aadf +size 13432 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WuQuantizer_ErrorDither_0.25.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WuQuantizer_ErrorDither_0.25.png index fbe6fedca..dba923209 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WuQuantizer_ErrorDither_0.25.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WuQuantizer_ErrorDither_0.25.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b09ecc73b2366eb5ab50ac07eab5bba0643d4d26a71e4d8af6235da2ac35631c -size 15368 +oid sha256:faa91657288e6a6797d8459d41d5fecca3c0a2e8e63317ebaf47df28688d13d7 +size 13853 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WuQuantizer_ErrorDither_0.5.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WuQuantizer_ErrorDither_0.5.png index 566e1befe..ea062d5be 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WuQuantizer_ErrorDither_0.5.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WuQuantizer_ErrorDither_0.5.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:20f45d86bdedce746fcd807d4abd6eda645c10064233d84ecf12613bcdcfc131 -size 15372 +oid sha256:79e48506430f3a9b25f484ef191fd820819c438392a4e588c2ecafb6db9a2210 +size 13775 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WuQuantizer_ErrorDither_0.75.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WuQuantizer_ErrorDither_0.75.png index 40ede8647..ae90ea9b5 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WuQuantizer_ErrorDither_0.75.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WuQuantizer_ErrorDither_0.75.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:70638fa2140e8400683ef7b2914cf877cbfe81a44847035239165e81357d9f55 -size 15707 +oid sha256:f56c884a0e4666cd662d36ec3a0d4e751c899c0122595378154507fffc69fda4 +size 14010 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WuQuantizer_ErrorDither_0.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WuQuantizer_ErrorDither_0.png index fbe6fedca..dba923209 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WuQuantizer_ErrorDither_0.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WuQuantizer_ErrorDither_0.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b09ecc73b2366eb5ab50ac07eab5bba0643d4d26a71e4d8af6235da2ac35631c -size 15368 +oid sha256:faa91657288e6a6797d8459d41d5fecca3c0a2e8e63317ebaf47df28688d13d7 +size 13853 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WuQuantizer_ErrorDither_1.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WuQuantizer_ErrorDither_1.png index be922bfe5..1e1795063 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WuQuantizer_ErrorDither_1.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WuQuantizer_ErrorDither_1.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a33e1510a59a7abd38ba4e97d18428aa76bc6bdad9016f2d279b996a8f0ea0a3 -size 16040 +oid sha256:4095927693b3cd49df58c0c1d7c5430255350c9ae595408a52ad83b1a65614ac +size 14269 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WuQuantizer_OrderedDither_0.25.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WuQuantizer_OrderedDither_0.25.png index 2debe15ab..29a3ed7ff 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WuQuantizer_OrderedDither_0.25.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WuQuantizer_OrderedDither_0.25.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:9a209b60feda34e2078fe0609e8e32a67901d1ec0d99fe500e4d7968031f62df -size 17253 +oid sha256:d062d4b79ee01942776ae13467e9bcbb529a7eeb5ad7c28ff3d0ccd3d88dcde6 +size 15962 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WuQuantizer_OrderedDither_0.5.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WuQuantizer_OrderedDither_0.5.png index 447ffb0ee..50fa46d16 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WuQuantizer_OrderedDither_0.5.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WuQuantizer_OrderedDither_0.5.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:5383256fd20787dcce51bf8d13af94172ea47e2bda8ae1285768d422c3d08f61 -size 17531 +oid sha256:47b2265af41ba042904cab387bf1de4715bd4d8a318bc6c1f69bfdbff5eabe2c +size 16928 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WuQuantizer_OrderedDither_0.75.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WuQuantizer_OrderedDither_0.75.png index b991ad659..5d1030e6b 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WuQuantizer_OrderedDither_0.75.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WuQuantizer_OrderedDither_0.75.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b754d74a036eff2a56ebea8fce81d952db54f05c6ec247cfe5bb76eacbf0c33f -size 17949 +oid sha256:6679d6d6f7c8b44461956b54654cea71180a2b0d43712d3775e60cbedd90cc82 +size 17520 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WuQuantizer_OrderedDither_0.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WuQuantizer_OrderedDither_0.png index fbe6fedca..dba923209 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WuQuantizer_OrderedDither_0.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WuQuantizer_OrderedDither_0.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b09ecc73b2366eb5ab50ac07eab5bba0643d4d26a71e4d8af6235da2ac35631c -size 15368 +oid sha256:faa91657288e6a6797d8459d41d5fecca3c0a2e8e63317ebaf47df28688d13d7 +size 13853 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WuQuantizer_OrderedDither_1.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WuQuantizer_OrderedDither_1.png index cd3527df3..567e5d6a3 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WuQuantizer_OrderedDither_1.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WuQuantizer_OrderedDither_1.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:fdf77c8222a40afcfdda5edeb87b7921c71b1368ac7f91cc560e8d73676fdb33 -size 18115 +oid sha256:5af5d16f875172d73f8426928fc8edaa4a6cab321a968b6c29fca32d0fba0df5 +size 18182 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_OctreeQuantizer_ErrorDither.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_OctreeQuantizer_ErrorDither.png index 4013073c1..09c471914 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_OctreeQuantizer_ErrorDither.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_OctreeQuantizer_ErrorDither.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:374a09cf2f904bc512bafb1d280f5de484bf101bb7d24d4dfef0f5fe22f2ed39 -size 82110 +oid sha256:a40b319d264f046159722cb57599eda51de9ba3795272b3785901cdc51053fab +size 83010 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_OctreeQuantizer_NoDither.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_OctreeQuantizer_NoDither.png index f8c459512..3bd7cbabb 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_OctreeQuantizer_NoDither.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_OctreeQuantizer_NoDither.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:2a4564e26e53e6562ca4dfa44e671150903e022db4f6ec76038627bca130bbe9 -size 54177 +oid sha256:3bc93509a983e20986614f4937f66d5d979bbb433a30a7736150934cf14b452a +size 55213 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_OctreeQuantizer_OrderedDither.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_OctreeQuantizer_OrderedDither.png index 834c5ffe6..34490e602 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_OctreeQuantizer_OrderedDither.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_OctreeQuantizer_OrderedDither.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:00ede0da073e60deba1f0eb7cc5f3da0902b44dfcea71cbf33a56fc74955823b -size 79449 +oid sha256:b92f3320120d53444cefc79b4684933cfe2b933dc79c2414496785743b5c8f18 +size 80808 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_WebSafePaletteQuantizer_ErrorDither.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_WebSafePaletteQuantizer_ErrorDither.png index 7ceb6114a..40243937d 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_WebSafePaletteQuantizer_ErrorDither.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_WebSafePaletteQuantizer_ErrorDither.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:7ce4b978b800820275635ae8da59212661c74a1b6ba9050ebf052c87315aedef -size 62107 +oid sha256:b63810145832db459bb7a6b37a028a7b778f6b6b4e6eae00e50e6e21c5a06086 +size 62199 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_WebSafePaletteQuantizer_NoDither.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_WebSafePaletteQuantizer_NoDither.png index 570db6a3a..5e9fa1233 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_WebSafePaletteQuantizer_NoDither.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_WebSafePaletteQuantizer_NoDither.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:602181e876201b10f75650eb07961fa499c05009067c7d298d4826e1503b93ca -size 33091 +oid sha256:f90db3ce2153cc9ba4d1d79e5749dc4d49e916dff8a0e121ebce9b00702cfcc8 +size 33880 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_WebSafePaletteQuantizer_OrderedDither.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_WebSafePaletteQuantizer_OrderedDither.png index 9710298a3..68a95a054 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_WebSafePaletteQuantizer_OrderedDither.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_WebSafePaletteQuantizer_OrderedDither.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a2a534bc0862e0d7325e9d6bbdf1c73ddc356bd621dfdc64a9c0c04399a93e00 -size 43807 +oid sha256:8af98bfcc5edef3f3ff33ee8f76f33ce2906a6677167e2b29e1dbe63b00a78d8 +size 44202 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_WernerPaletteQuantizer_ErrorDither.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_WernerPaletteQuantizer_ErrorDither.png index 062113ae6..96c66aad7 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_WernerPaletteQuantizer_ErrorDither.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_WernerPaletteQuantizer_ErrorDither.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8906591aa17712c67939d40ada949e10c8de0cdaac5186a88db1199ac1819bd3 -size 33835 +oid sha256:828b082a1892f0200ef84254637b340b1276e1bee44e01c6b715de8838e4818f +size 35301 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_WernerPaletteQuantizer_NoDither.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_WernerPaletteQuantizer_NoDither.png index 8fd221575..3ff151f6d 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_WernerPaletteQuantizer_NoDither.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_WernerPaletteQuantizer_NoDither.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:545a01286eff5b5835e79b595fe482f2f5e571972dfdbff651eb5db2944d7889 -size 33045 +oid sha256:f70d1aa2f985dfb7227ea5fd7b4b98effc1a31c89fd05bbee9cfa8f003b9cb4e +size 34261 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_WernerPaletteQuantizer_OrderedDither.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_WernerPaletteQuantizer_OrderedDither.png index 877ed8778..10daff76b 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_WernerPaletteQuantizer_OrderedDither.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_WernerPaletteQuantizer_OrderedDither.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:2f34ecadac596bccfb2fcc5cc7f070ea98917a30d036bee558775c01737c72d2 -size 43016 +oid sha256:d8ba00e2948337f77d935d98349958c6a520958671e9ec714ff1bfadfb130e72 +size 44622 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_WuQuantizer_ErrorDither.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_WuQuantizer_ErrorDither.png index 7869d6f13..747ca70c1 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_WuQuantizer_ErrorDither.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_WuQuantizer_ErrorDither.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d713b040dbe51d9034cd7c3b1055b64a21c9146e6417c7c35f1012f4093b872c -size 104792 +oid sha256:d58c425ce5b1ca56450095a66dea24b379935b0087aec7b4102f15a99f95a017 +size 101999 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_WuQuantizer_NoDither.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_WuQuantizer_NoDither.png index d6f69a5bd..de464b94c 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_WuQuantizer_NoDither.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_WuQuantizer_NoDither.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0773406e402e8c30bb345de5e4e20f2546a3ad692b9ad0b29dfe3df8c659cf4b -size 83576 +oid sha256:93a4822e39babba059a88536a965e4f3207e4402d2b92d7d18485fec5e9e69da +size 84378 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_WuQuantizer_OrderedDither.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_WuQuantizer_OrderedDither.png index c8d7908ef..ce5454827 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_WuQuantizer_OrderedDither.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_WuQuantizer_OrderedDither.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6b75b9c856570b0b2bfd8826eb8771bf15b74f309ee70aae72a607e96040e254 -size 99413 +oid sha256:35969c8dc96de4dacc3048ae760a0681278a2011993a0edbceaacc93d6fc3a67 +size 102713 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_OctreeQuantizer_ErrorDither.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_OctreeQuantizer_ErrorDither.png index b56b714e8..5efcaedc9 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_OctreeQuantizer_ErrorDither.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_OctreeQuantizer_ErrorDither.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:7a609150bfad93dc56bf9e1c3214ea3eabec48dd73939945840656de544bd423 -size 94976 +oid sha256:40d012f4ecb4e36c94d086f8ec7bc199fbfd9fb30a9427a07b35df1b1e430a71 +size 95601 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_OctreeQuantizer_NoDither.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_OctreeQuantizer_NoDither.png index bd2913174..916dc3756 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_OctreeQuantizer_NoDither.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_OctreeQuantizer_NoDither.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b06aa4d0f3caca205cb5adcdcf26bf6ec6157febdd9266a3fcda9179deaf0840 -size 77036 +oid sha256:fa64863f73dfd1c5daef645c54e9275136f66513a87750bee0ec8e13ac357da5 +size 79649 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_OctreeQuantizer_OrderedDither.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_OctreeQuantizer_OrderedDither.png index ab2a2e3f6..f039dd222 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_OctreeQuantizer_OrderedDither.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_OctreeQuantizer_OrderedDither.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:66c47408a07b7e32d7cabc64289aa8f16b0c96e5ca4c478ebae48729ff55b229 -size 91645 +oid sha256:8f5138589c606de20ba193d4279f049ee1ecb3f1801b949d3436995bbf242cbe +size 92683 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_WebSafePaletteQuantizer_ErrorDither.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_WebSafePaletteQuantizer_ErrorDither.png index 036448778..2b897a5d6 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_WebSafePaletteQuantizer_ErrorDither.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_WebSafePaletteQuantizer_ErrorDither.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ded9cb4986e620f5fc11486ccdf81934dff7b3cea7db7e292929d339ecc21714 -size 60116 +oid sha256:007ac609ec61b39c7bdd04bc87a698f5cdc76eadd834c1457f41eb9c135c3f7b +size 60688 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_WebSafePaletteQuantizer_NoDither.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_WebSafePaletteQuantizer_NoDither.png index a798459cb..e40a91cbc 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_WebSafePaletteQuantizer_NoDither.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_WebSafePaletteQuantizer_NoDither.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:5da4f2f3024fe3c8ae7f0ce6113f78c74d699eb4efe41f1d9df875af05d09547 -size 46806 +oid sha256:6fc2f82bdbf4b204ad78f3bb54bfdea7452a2d1430814f45262fd309225f2fc0 +size 46727 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_WebSafePaletteQuantizer_OrderedDither.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_WebSafePaletteQuantizer_OrderedDither.png index 687cd1dbc..ac56fa923 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_WebSafePaletteQuantizer_OrderedDither.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_WebSafePaletteQuantizer_OrderedDither.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e2d47f277bc904ed8c3b3bbfe636b647884511de39661bfd34cdcb2abe9f6a13 -size 50818 +oid sha256:6d2289ed4fa0c679f0f120d260fec8ab40b1599043cc0a1fbebc6b67e238ff87 +size 51428 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_WernerPaletteQuantizer_ErrorDither.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_WernerPaletteQuantizer_ErrorDither.png index 8e0f6002c..8b79a19e0 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_WernerPaletteQuantizer_ErrorDither.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_WernerPaletteQuantizer_ErrorDither.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:4324dc77448aeb0ee4ebe39c449ddedff81bf6f0243e0c4713ae89a18b13f0b8 -size 67273 +oid sha256:ef2b6073b75a2de97a78d47d3b3e40c264687c5756f153d3d85bc5b2714cf85a +size 68226 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_WernerPaletteQuantizer_NoDither.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_WernerPaletteQuantizer_NoDither.png index 33c9d4324..8d0d2b60d 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_WernerPaletteQuantizer_NoDither.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_WernerPaletteQuantizer_NoDither.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:eb0a2e6d50625ae9a67f9d36d3296f65e41bb6d5a7d3e8163dba89333e3ac6a3 -size 63442 +oid sha256:ac1424c6c4c18feb42106e14da6b161ce3f48276d0aa6603ca60ad5caa0a5338 +size 63764 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_WernerPaletteQuantizer_OrderedDither.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_WernerPaletteQuantizer_OrderedDither.png index f3900e5f6..88cf83a30 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_WernerPaletteQuantizer_OrderedDither.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_WernerPaletteQuantizer_OrderedDither.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6551261b6b8c4cf4f69ba2860e163fe10fc9f2660cbb43c1423a9b5dd9456040 -size 68497 +oid sha256:513844ed95c2b50e792d3346398256846b8b280dbadf7ef3f4e11d58c1e679c0 +size 69529 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_WuQuantizer_ErrorDither.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_WuQuantizer_ErrorDither.png index 6203feb92..a3eefcba2 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_WuQuantizer_ErrorDither.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_WuQuantizer_ErrorDither.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:5f5cf2a5917f8edf77cfc78b8d923bda672435da095876390bd14212bfd87d75 -size 112571 +oid sha256:32b269d62d4eebe555d5d9f12b9958b41206848504bb985dcd1ff9c81a5003c6 +size 117073 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_WuQuantizer_NoDither.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_WuQuantizer_NoDither.png index 1625e1686..3b0c46ac3 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_WuQuantizer_NoDither.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_WuQuantizer_NoDither.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:abd3ad65543bf9bdfb9924652cc107b23c80db3e210d2b994fd575ea73475aa7 -size 108014 +oid sha256:12f58b00a16913cd85ffa18fcea580a59550dcc201295b060d55a870230f37f7 +size 113995 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_WuQuantizer_OrderedDither.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_WuQuantizer_OrderedDither.png index 19a672d80..328f86330 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_WuQuantizer_OrderedDither.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_WuQuantizer_OrderedDither.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a27a605acbd026ba475fae9110c69c6002a399ef3747ce7ca5c060a6df5a078b -size 114379 +oid sha256:867d7b727de278cbc01b7d2b8e968f1fc0d0a81a3e4af636ce4a6598a8709be6 +size 114630 From 5da17f3992b142399615fa49db28d0979257a1a0 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Wed, 29 Nov 2023 12:03:14 +1000 Subject: [PATCH 20/24] Enable Sse2, simplify --- .../Common/Helpers/SimdUtils.HwIntrinsics.cs | 31 +++++++++++++ src/ImageSharp/Formats/AnimationUtilities.cs | 46 +++++++++++++++++++ src/ImageSharp/Formats/Gif/GifEncoderCore.cs | 5 +- src/ImageSharp/Formats/Png/PngMetadata.cs | 17 +++---- .../TestUtilities/TestEnvironment.Formats.cs | 1 - 5 files changed, 86 insertions(+), 14 deletions(-) diff --git a/src/ImageSharp/Common/Helpers/SimdUtils.HwIntrinsics.cs b/src/ImageSharp/Common/Helpers/SimdUtils.HwIntrinsics.cs index 7caaa5868..fc58ef344 100644 --- a/src/ImageSharp/Common/Helpers/SimdUtils.HwIntrinsics.cs +++ b/src/ImageSharp/Common/Helpers/SimdUtils.HwIntrinsics.cs @@ -1,6 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. +using System.Numerics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.Intrinsics; @@ -656,6 +657,36 @@ internal static partial class SimdUtils return AdvSimd.BitwiseSelect(signedMask, right.AsInt16(), left.AsInt16()).AsByte(); } + /// + /// Blend packed 32-bit unsigned integers from and using . + /// The high bit of each corresponding byte determines the selection. + /// If the high bit is set the element of is selected. + /// The element of is selected otherwise. + /// + /// The left vector. + /// The right vector. + /// The mask vector. + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector128 BlendVariable(Vector128 left, Vector128 right, Vector128 mask) + => BlendVariable(left.AsByte(), right.AsByte(), mask.AsByte()).AsUInt32(); + + /// + /// Count the number of leading zero bits in a mask. + /// Similar in behavior to the x86 instruction LZCNT. + /// + /// The value. + public static ushort LeadingZeroCount(ushort value) + => (ushort)(BitOperations.LeadingZeroCount(value) - 16); + + /// + /// Count the number of trailing zero bits in an integer value. + /// Similar in behavior to the x86 instruction TZCNT. + /// + /// The value. + public static ushort TrailingZeroCount(ushort value) + => (ushort)(BitOperations.TrailingZeroCount(value << 16) - 16); + /// /// as many elements as possible, slicing them down (keeping the remainder). /// diff --git a/src/ImageSharp/Formats/AnimationUtilities.cs b/src/ImageSharp/Formats/AnimationUtilities.cs index 23fc40cdf..3c731422a 100644 --- a/src/ImageSharp/Formats/AnimationUtilities.cs +++ b/src/ImageSharp/Formats/AnimationUtilities.cs @@ -128,6 +128,52 @@ internal static class AnimationUtilities } } + if (Sse2.IsSupported && remaining >= 4) + { + Vector128 r128 = previousFrame != null ? Vector128.Create(bg.PackedValue) : Vector128.Zero; + Vector128 vmb128 = Vector128.Zero; + if (blend) + { + vmb128 = Sse2.CompareEqual(vmb128, vmb128); + } + + while (remaining >= 4) + { + Vector128 p = Unsafe.Add(ref Unsafe.As, Vector128>(ref previousBase), x); + Vector128 c = Unsafe.Add(ref Unsafe.As, Vector128>(ref currentBase), x); + + Vector128 eq = Sse2.CompareEqual(p, c); + Vector128 r = SimdUtils.HwIntrinsics.BlendVariable(c, r128, Sse2.And(eq, vmb128)); + + if (nextFrame != null) + { + Vector128 n = Sse2.ShiftRightLogical(Unsafe.Add(ref Unsafe.As, Vector128>(ref nextBase), x), 24).AsInt32(); + eq = Sse2.AndNot(Sse2.CompareGreaterThan(Sse2.ShiftRightLogical(c, 24).AsInt32(), n).AsUInt32(), eq); + } + + Unsafe.Add(ref Unsafe.As, Vector128>(ref resultBase), x) = r.AsByte(); + + ushort msk = (ushort)(uint)Sse2.MoveMask(eq.AsByte()); + msk = (ushort)~msk; + if (msk != 0) + { + // If is diff is found, the left side is marked by the min of previously found left side and the start position. + // The right is the max of the previously found right side and the end position. + int start = i + (SimdUtils.HwIntrinsics.TrailingZeroCount(msk) / sizeof(uint)); + int end = i + (4 - (SimdUtils.HwIntrinsics.LeadingZeroCount(msk) / sizeof(uint))); + left = Math.Min(left, start); + right = Math.Max(right, end); + hasRowDiff = true; + hasDiff = true; + } + + x++; + i += 4; + remaining -= 4; + } + } + + // TODO: AdvSimd ?? for (i = remaining; i > 0; i--) { x = (uint)(length - i); diff --git a/src/ImageSharp/Formats/Gif/GifEncoderCore.cs b/src/ImageSharp/Formats/Gif/GifEncoderCore.cs index 2186cc2e4..f0e1aafd7 100644 --- a/src/ImageSharp/Formats/Gif/GifEncoderCore.cs +++ b/src/ImageSharp/Formats/Gif/GifEncoderCore.cs @@ -168,7 +168,7 @@ internal sealed class GifEncoderCore : IImageEncoderInternals stream.WriteByte(GifConstants.EndIntroducer); - quantized.Dispose(); + quantized?.Dispose(); } private static GifMetadata GetGifMetadata(Image image) @@ -251,6 +251,7 @@ internal sealed class GifEncoderCore : IImageEncoderInternals { // Gather the metadata for this frame. ImageFrame currentFrame = image.Frames[i]; + ImageFrame? nextFrame = i < image.Frames.Count - 1 ? image.Frames[i + 1] : null; GifFrameMetadata gifMetadata = GetGifFrameMetadata(currentFrame, globalTransparencyIndex); bool useLocal = this.colorTableMode == GifColorTableMode.Local || (gifMetadata.ColorTableMode == GifColorTableMode.Local); @@ -264,8 +265,6 @@ internal sealed class GifEncoderCore : IImageEncoderInternals hasPaletteQuantizer = true; } - ImageFrame? nextFrame = i < image.Frames.Count - 1 ? image.Frames[i + 1] : null; - this.EncodeAdditionalFrame( stream, previousFrame, diff --git a/src/ImageSharp/Formats/Png/PngMetadata.cs b/src/ImageSharp/Formats/Png/PngMetadata.cs index 128237684..93ddcf263 100644 --- a/src/ImageSharp/Formats/Png/PngMetadata.cs +++ b/src/ImageSharp/Formats/Png/PngMetadata.cs @@ -90,13 +90,12 @@ public class PngMetadata : IDeepCloneable { // Should the conversion be from a format that uses a 24bit palette entries (gif) // we need to clone and adjust the color table to allow for transparency. - ReadOnlyMemory? colorTable = metadata.ColorTable; - if (metadata.ColorTable.HasValue) + Color[]? colorTable = metadata.ColorTable.HasValue ? metadata.ColorTable.Value.ToArray() : null; + if (colorTable != null) { - Color[] clone = metadata.ColorTable.Value.ToArray(); - for (int i = 0; i < clone.Length; i++) + for (int i = 0; i < colorTable.Length; i++) { - ref Color c = ref clone[i]; + ref Color c = ref colorTable[i]; if (c == metadata.BackgroundColor) { // Png treats background as fully empty @@ -104,15 +103,13 @@ public class PngMetadata : IDeepCloneable break; } } - - colorTable = clone; } return new() { - ColorType = colorTable.HasValue ? PngColorType.Palette : null, - BitDepth = colorTable.HasValue - ? (PngBitDepth)Numerics.Clamp(ColorNumerics.GetBitsNeededForColorDepth(colorTable.Value.Length), 1, 8) + ColorType = colorTable != null ? PngColorType.Palette : null, + BitDepth = colorTable != null + ? (PngBitDepth)Numerics.Clamp(ColorNumerics.GetBitsNeededForColorDepth(colorTable.Length), 1, 8) : null, ColorTable = colorTable, RepeatCount = metadata.RepeatCount, diff --git a/tests/ImageSharp.Tests/TestUtilities/TestEnvironment.Formats.cs b/tests/ImageSharp.Tests/TestUtilities/TestEnvironment.Formats.cs index 6c6f300d0..9508de246 100644 --- a/tests/ImageSharp.Tests/TestUtilities/TestEnvironment.Formats.cs +++ b/tests/ImageSharp.Tests/TestUtilities/TestEnvironment.Formats.cs @@ -1,7 +1,6 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System.Diagnostics.CodeAnalysis; using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Formats.Bmp; using SixLabors.ImageSharp.Formats.Gif; From 10cd3d514af39825d0b517ec11e7ba4023af9a96 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Wed, 29 Nov 2023 12:19:13 +1000 Subject: [PATCH 21/24] Update AnimationUtilities.cs --- src/ImageSharp/Formats/AnimationUtilities.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ImageSharp/Formats/AnimationUtilities.cs b/src/ImageSharp/Formats/AnimationUtilities.cs index 3c731422a..727364227 100644 --- a/src/ImageSharp/Formats/AnimationUtilities.cs +++ b/src/ImageSharp/Formats/AnimationUtilities.cs @@ -43,7 +43,7 @@ internal static class AnimationUtilities where TPixel : unmanaged, IPixel { MemoryAllocator memoryAllocator = configuration.MemoryAllocator; - IMemoryOwner buffers = memoryAllocator.Allocate(currentFrame.Width * 4, AllocationOptions.Clean); + using IMemoryOwner buffers = memoryAllocator.Allocate(currentFrame.Width * 4, AllocationOptions.Clean); Span previous = buffers.GetSpan()[..currentFrame.Width]; Span current = buffers.GetSpan().Slice(currentFrame.Width, currentFrame.Width); Span next = buffers.GetSpan().Slice(currentFrame.Width * 2, currentFrame.Width); From f4dc0fc4f2d9db47c4100e1985dbdb98eee3b04e Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Wed, 29 Nov 2023 12:56:43 +1000 Subject: [PATCH 22/24] Disable Sse2 for now. --- src/ImageSharp/Formats/AnimationUtilities.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/ImageSharp/Formats/AnimationUtilities.cs b/src/ImageSharp/Formats/AnimationUtilities.cs index 727364227..5e576c983 100644 --- a/src/ImageSharp/Formats/AnimationUtilities.cs +++ b/src/ImageSharp/Formats/AnimationUtilities.cs @@ -128,7 +128,8 @@ internal static class AnimationUtilities } } - if (Sse2.IsSupported && remaining >= 4) + // TODO: There's a bug here. See WebpEncoderTests.Encode_AnimatedLossless + if (Sse2.IsSupported && remaining >= 4 && false) { Vector128 r128 = previousFrame != null ? Vector128.Create(bg.PackedValue) : Vector128.Zero; Vector128 vmb128 = Vector128.Zero; @@ -151,7 +152,7 @@ internal static class AnimationUtilities eq = Sse2.AndNot(Sse2.CompareGreaterThan(Sse2.ShiftRightLogical(c, 24).AsInt32(), n).AsUInt32(), eq); } - Unsafe.Add(ref Unsafe.As, Vector128>(ref resultBase), x) = r.AsByte(); + Unsafe.Add(ref Unsafe.As, Vector128>(ref resultBase), x) = r; ushort msk = (ushort)(uint)Sse2.MoveMask(eq.AsByte()); msk = (ushort)~msk; From 43c86145305e48fb6d3e28300dc0a8be5e8a4cb1 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Wed, 29 Nov 2023 13:31:38 +1000 Subject: [PATCH 23/24] Fix Sse2 offset --- src/ImageSharp/Formats/AnimationUtilities.cs | 29 ++++++++++---------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/src/ImageSharp/Formats/AnimationUtilities.cs b/src/ImageSharp/Formats/AnimationUtilities.cs index 5e576c983..814e48b63 100644 --- a/src/ImageSharp/Formats/AnimationUtilities.cs +++ b/src/ImageSharp/Formats/AnimationUtilities.cs @@ -71,10 +71,10 @@ internal static class AnimationUtilities PixelOperations.Instance.ToRgba32(configuration, nextFrame.DangerousGetPixelRowMemory(y).Span, next); } - ref Vector256 previousBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(previous)); - ref Vector256 currentBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(current)); - ref Vector256 nextBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(next)); - ref Vector256 resultBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(result)); + ref Vector256 previousBase256 = ref Unsafe.As>(ref MemoryMarshal.GetReference(previous)); + ref Vector256 currentBase256 = ref Unsafe.As>(ref MemoryMarshal.GetReference(current)); + ref Vector256 nextBase256 = ref Unsafe.As>(ref MemoryMarshal.GetReference(next)); + ref Vector256 resultBase256 = ref Unsafe.As>(ref MemoryMarshal.GetReference(result)); int i = 0; uint x = 0; @@ -93,19 +93,19 @@ internal static class AnimationUtilities while (remaining >= 8) { - Vector256 p = Unsafe.Add(ref previousBase, x).AsUInt32(); - Vector256 c = Unsafe.Add(ref currentBase, x).AsUInt32(); + Vector256 p = Unsafe.Add(ref previousBase256, x).AsUInt32(); + Vector256 c = Unsafe.Add(ref currentBase256, x).AsUInt32(); Vector256 eq = Avx2.CompareEqual(p, c); Vector256 r = Avx2.BlendVariable(c, r256, Avx2.And(eq, vmb256)); if (nextFrame != null) { - Vector256 n = Avx2.ShiftRightLogical(Unsafe.Add(ref nextBase, x).AsUInt32(), 24).AsInt32(); + Vector256 n = Avx2.ShiftRightLogical(Unsafe.Add(ref nextBase256, x).AsUInt32(), 24).AsInt32(); eq = Avx2.AndNot(Avx2.CompareGreaterThan(Avx2.ShiftRightLogical(c, 24).AsInt32(), n).AsUInt32(), eq); } - Unsafe.Add(ref resultBase, x) = r.AsByte(); + Unsafe.Add(ref resultBase256, x) = r.AsByte(); uint msk = (uint)Avx2.MoveMask(eq.AsByte()); msk = ~msk; @@ -128,9 +128,10 @@ internal static class AnimationUtilities } } - // TODO: There's a bug here. See WebpEncoderTests.Encode_AnimatedLossless - if (Sse2.IsSupported && remaining >= 4 && false) + if (Sse2.IsSupported && remaining >= 4) { + // Update offset since we may be operating on the remainder previously incremented by pixel steps of 8. + x *= 2; Vector128 r128 = previousFrame != null ? Vector128.Create(bg.PackedValue) : Vector128.Zero; Vector128 vmb128 = Vector128.Zero; if (blend) @@ -140,19 +141,19 @@ internal static class AnimationUtilities while (remaining >= 4) { - Vector128 p = Unsafe.Add(ref Unsafe.As, Vector128>(ref previousBase), x); - Vector128 c = Unsafe.Add(ref Unsafe.As, Vector128>(ref currentBase), x); + Vector128 p = Unsafe.Add(ref Unsafe.As, Vector128>(ref previousBase256), x); + Vector128 c = Unsafe.Add(ref Unsafe.As, Vector128>(ref currentBase256), x); Vector128 eq = Sse2.CompareEqual(p, c); Vector128 r = SimdUtils.HwIntrinsics.BlendVariable(c, r128, Sse2.And(eq, vmb128)); if (nextFrame != null) { - Vector128 n = Sse2.ShiftRightLogical(Unsafe.Add(ref Unsafe.As, Vector128>(ref nextBase), x), 24).AsInt32(); + Vector128 n = Sse2.ShiftRightLogical(Unsafe.Add(ref Unsafe.As, Vector128>(ref nextBase256), x), 24).AsInt32(); eq = Sse2.AndNot(Sse2.CompareGreaterThan(Sse2.ShiftRightLogical(c, 24).AsInt32(), n).AsUInt32(), eq); } - Unsafe.Add(ref Unsafe.As, Vector128>(ref resultBase), x) = r; + Unsafe.Add(ref Unsafe.As, Vector128>(ref resultBase256), x) = r; ushort msk = (ushort)(uint)Sse2.MoveMask(eq.AsByte()); msk = (ushort)~msk; From cf4106b78c1b85ae7e5384abd28506c34b15ffd6 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Wed, 29 Nov 2023 16:50:39 +1000 Subject: [PATCH 24/24] Add note --- src/ImageSharp/Formats/AnimationUtilities.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ImageSharp/Formats/AnimationUtilities.cs b/src/ImageSharp/Formats/AnimationUtilities.cs index 814e48b63..67ee72e95 100644 --- a/src/ImageSharp/Formats/AnimationUtilities.cs +++ b/src/ImageSharp/Formats/AnimationUtilities.cs @@ -175,7 +175,7 @@ internal static class AnimationUtilities } } - // TODO: AdvSimd ?? + // TODO: v4 AdvSimd when we can use .NET 8 for (i = remaining; i > 0; i--) { x = (uint)(length - i);