From 4e21188efeca88bac1c326e4ae9726a0732e5ef6 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Wed, 26 Feb 2025 14:35:54 +1000 Subject: [PATCH 01/18] Fix all known quantizing issues --- src/ImageSharp/Common/InlineArray.cs | 29 + src/ImageSharp/Common/InlineArray.tt | 38 + src/ImageSharp/Formats/Bmp/BmpMetadata.cs | 3 +- .../Formats/Cur/CurFrameMetadata.cs | 1 + src/ImageSharp/Formats/Cur/CurMetadata.cs | 3 +- src/ImageSharp/Formats/EncodingUtilities.cs | 39 +- src/ImageSharp/Formats/Gif/GifDecoderCore.cs | 141 +++- src/ImageSharp/Formats/Gif/GifEncoderCore.cs | 93 ++- .../Formats/Gif/GifFrameMetadata.cs | 3 +- src/ImageSharp/Formats/Gif/GifMetadata.cs | 12 +- .../Formats/IFormatFrameMetadata.cs | 6 +- src/ImageSharp/Formats/IFormatMetadata.cs | 8 +- .../Formats/IQuantizingImageEncoder.cs | 4 +- .../Formats/Ico/IcoFrameMetadata.cs | 1 + src/ImageSharp/Formats/Ico/IcoMetadata.cs | 3 +- src/ImageSharp/Formats/Png/PngEncoder.cs | 12 - src/ImageSharp/Formats/Png/PngEncoderCore.cs | 246 +++--- src/ImageSharp/Formats/Png/PngMetadata.cs | 3 +- .../Formats/TransparentColorMode.cs | 2 +- .../Formats/Webp/BitReader/BitReaderBase.cs | 2 +- .../Formats/Webp/Lossy/Vp8Decoder.cs | 14 +- .../Formats/Webp/WebpAnimationDecoder.cs | 110 ++- .../Formats/Webp/WebpCommonUtils.cs | 2 +- src/ImageSharp/ImageSharp.csproj | 14 + src/ImageSharp/IndexedImageFrame{TPixel}.cs | 4 +- .../CloningImageProcessor{TPixel}.cs | 6 +- .../Processors/Dithering/IDither.cs | 4 +- .../IPaletteDitherImageProcessor{TPixel}.cs | 8 +- .../Processors/ImageProcessor{TPixel}.cs | 7 +- .../Quantization/EuclideanPixelMap{TPixel}.cs | 521 +++++++++--- .../Quantization/IQuantizer{TPixel}.cs | 12 +- .../Quantization/OctreeQuantizer{TPixel}.cs | 766 +++++++++++++++--- .../Quantization/PaletteQuantizer.cs | 23 +- .../Quantization/PaletteQuantizer{TPixel}.cs | 55 +- .../Quantization/QuantizerConstants.cs | 19 +- .../Quantization/QuantizerOptions.cs | 11 + .../Quantization/QuantizerUtilities.cs | 38 +- .../Quantization/WuQuantizer{TPixel}.cs | 71 +- .../Transforms/TransformProcessor{TPixel}.cs | 15 +- .../Formats/Gif/GifEncoderTests.cs | 11 + .../Formats/Png/PngEncoderTests.cs | 46 +- .../Formats/WebP/WebpCommonUtilsTests.cs | 4 +- .../Formats/WebP/WebpDecoderTests.cs | 16 + .../ImageSharp.Tests/Image/ImageFrameTests.cs | 2 +- tests/ImageSharp.Tests/Image/ImageTests.cs | 2 +- .../Quantization/WuQuantizerTests.cs | 4 +- tests/ImageSharp.Tests/TestImages.cs | 2 + .../ReferenceCodecs/MagickReferenceDecoder.cs | 36 +- ...de_8BitColor_WithOctreeQuantizer_rgb32.bmp | 2 +- ...Encode_8BitColor_WithWuQuantizer_rgb32.bmp | 2 +- ...onFilterInBox_Rgba32_CalliphoraPartial.png | 4 +- ...erFilterInBox_Rgba32_CalliphoraPartial.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 +- ...e1962_Rgba32_issue1962_tiniest_gif_1st.png | 4 +- ...2012BadMinCode_Rgba32_issue2012_drona1.png | 4 +- ...zed_Encode_Artifacts_Rgba32_issue_2469.png | 4 +- ...antized_Encode_Alpha_Rgba32_Issue_2668.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 +- ...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 +- ...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 +- tests/Images/Input/Gif/issues/issue_2866.gif | 3 + .../Images/Input/Png/issues/issue_2469-i.png | 3 + tests/Images/Input/Webp/issues/Issue2866.webp | 3 + 177 files changed, 2120 insertions(+), 861 deletions(-) create mode 100644 src/ImageSharp/Common/InlineArray.cs create mode 100644 src/ImageSharp/Common/InlineArray.tt create mode 100644 tests/Images/Input/Gif/issues/issue_2866.gif create mode 100644 tests/Images/Input/Png/issues/issue_2469-i.png create mode 100644 tests/Images/Input/Webp/issues/Issue2866.webp diff --git a/src/ImageSharp/Common/InlineArray.cs b/src/ImageSharp/Common/InlineArray.cs new file mode 100644 index 0000000000..358121d0a4 --- /dev/null +++ b/src/ImageSharp/Common/InlineArray.cs @@ -0,0 +1,29 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +// + +using System; +using System.Runtime.CompilerServices; + +namespace SixLabors.ImageSharp; + +/// +/// Represents a safe, fixed sized buffer of 4 elements. +/// +[InlineArray(4)] +internal struct InlineArray4 +{ + private T t; +} + +/// +/// Represents a safe, fixed sized buffer of 16 elements. +/// +[InlineArray(16)] +internal struct InlineArray16 +{ + private T t; +} + + diff --git a/src/ImageSharp/Common/InlineArray.tt b/src/ImageSharp/Common/InlineArray.tt new file mode 100644 index 0000000000..fae6ab2279 --- /dev/null +++ b/src/ImageSharp/Common/InlineArray.tt @@ -0,0 +1,38 @@ +<#@ template debug="false" hostspecific="false" language="C#" #> +<#@ assembly name="System.Core" #> +<#@ import namespace="System.Linq" #> +<#@ import namespace="System.Text" #> +<#@ import namespace="System.Collections.Generic" #> +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +// + +using System; +using System.Runtime.CompilerServices; + +namespace SixLabors.ImageSharp; + +<#GenerateInlineArrays();#> + +<#+ +private static int[] Lengths = new int[] {4, 16 }; + +void GenerateInlineArrays() +{ + foreach (int length in Lengths) + { +#> +/// +/// Represents a safe, fixed sized buffer of <#=length#> elements. +/// +[InlineArray(<#=length#>)] +internal struct InlineArray<#=length#> +{ + private T t; +} + +<#+ + } +} +#> diff --git a/src/ImageSharp/Formats/Bmp/BmpMetadata.cs b/src/ImageSharp/Formats/Bmp/BmpMetadata.cs index d0c60421c4..1dac74ba3a 100644 --- a/src/ImageSharp/Formats/Bmp/BmpMetadata.cs +++ b/src/ImageSharp/Formats/Bmp/BmpMetadata.cs @@ -158,6 +158,5 @@ public FormatConnectingMetadata ToFormatConnectingMetadata() /// public void AfterImageApply(Image destination) where TPixel : unmanaged, IPixel - { - } + => this.ColorTable = null; } diff --git a/src/ImageSharp/Formats/Cur/CurFrameMetadata.cs b/src/ImageSharp/Formats/Cur/CurFrameMetadata.cs index 01b7fbce08..f1ebec72f5 100644 --- a/src/ImageSharp/Formats/Cur/CurFrameMetadata.cs +++ b/src/ImageSharp/Formats/Cur/CurFrameMetadata.cs @@ -126,6 +126,7 @@ public void AfterFrameApply(ImageFrame source, ImageFrame diff --git a/src/ImageSharp/Formats/Cur/CurMetadata.cs b/src/ImageSharp/Formats/Cur/CurMetadata.cs index 19de7f434d..5c725e2916 100644 --- a/src/ImageSharp/Formats/Cur/CurMetadata.cs +++ b/src/ImageSharp/Formats/Cur/CurMetadata.cs @@ -152,8 +152,7 @@ public FormatConnectingMetadata ToFormatConnectingMetadata() /// public void AfterImageApply(Image destination) where TPixel : unmanaged, IPixel - { - } + => this.ColorTable = null; /// IDeepCloneable IDeepCloneable.DeepClone() => this.DeepClone(); diff --git a/src/ImageSharp/Formats/EncodingUtilities.cs b/src/ImageSharp/Formats/EncodingUtilities.cs index a979fdf6fa..db951b1c33 100644 --- a/src/ImageSharp/Formats/EncodingUtilities.cs +++ b/src/ImageSharp/Formats/EncodingUtilities.cs @@ -3,6 +3,7 @@ using System.Buffers; using System.Numerics; +using System.Runtime.CompilerServices; using System.Runtime.Intrinsics; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; @@ -24,13 +25,29 @@ public static bool ShouldClearTransparentPixels(TransparentColorMode mod /// to better compression in some cases. /// /// The type of the pixel. - /// The cloned where the transparent pixels will be changed. + /// The where the transparent pixels will be changed. /// The color to replace transparent pixels with. - public static void ClearTransparentPixels(ImageFrame clone, Color color) + public static void ClearTransparentPixels(ImageFrame frame, Color color) + where TPixel : unmanaged, IPixel + => ClearTransparentPixels(frame.Configuration, frame.PixelBuffer, color); + + /// + /// Convert transparent pixels, to pixels represented by , which can yield + /// to better compression in some cases. + /// + /// The type of the pixel. + /// The configuration. + /// The where the transparent pixels will be changed. + /// The color to replace transparent pixels with. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void ClearTransparentPixels( + Configuration configuration, + Buffer2D buffer, + Color color) where TPixel : unmanaged, IPixel { - Buffer2DRegion buffer = clone.PixelBuffer.GetRegion(); - ClearTransparentPixels(clone.Configuration, ref buffer, color); + Buffer2DRegion region = buffer.GetRegion(); + ClearTransparentPixels(configuration, in region, color); } /// @@ -39,29 +56,27 @@ public static void ClearTransparentPixels(ImageFrame clone, Colo /// /// The type of the pixel. /// The configuration. - /// The cloned where the transparent pixels will be changed. + /// The where the transparent pixels will be changed. /// The color to replace transparent pixels with. public static void ClearTransparentPixels( Configuration configuration, - ref Buffer2DRegion clone, + in Buffer2DRegion region, Color color) where TPixel : unmanaged, IPixel { - using IMemoryOwner vectors = configuration.MemoryAllocator.Allocate(clone.Width); + using IMemoryOwner vectors = configuration.MemoryAllocator.Allocate(region.Width); Span vectorsSpan = vectors.GetSpan(); Vector4 replacement = color.ToScaledVector4(); - for (int y = 0; y < clone.Height; y++) + for (int y = 0; y < region.Height; y++) { - Span span = clone.DangerousGetRowSpan(y); + Span span = region.DangerousGetRowSpan(y); PixelOperations.Instance.ToVector4(configuration, span, vectorsSpan, PixelConversionModifiers.Scale); ClearTransparentPixelRow(vectorsSpan, replacement); PixelOperations.Instance.FromVector4Destructive(configuration, vectorsSpan, span, PixelConversionModifiers.Scale); } } - private static void ClearTransparentPixelRow( - Span vectorsSpan, - Vector4 replacement) + private static void ClearTransparentPixelRow(Span vectorsSpan, Vector4 replacement) { if (Vector128.IsHardwareAccelerated) { diff --git a/src/ImageSharp/Formats/Gif/GifDecoderCore.cs b/src/ImageSharp/Formats/Gif/GifDecoderCore.cs index e18166c4b8..e9012436e9 100644 --- a/src/ImageSharp/Formats/Gif/GifDecoderCore.cs +++ b/src/ImageSharp/Formats/Gif/GifDecoderCore.cs @@ -89,6 +89,11 @@ internal sealed class GifDecoderCore : ImageDecoderCore /// private GifMetadata? gifMetadata; + /// + /// The background color used to fill the frame. + /// + private Color backgroundColor; + /// /// Initializes a new instance of the class. /// @@ -108,9 +113,13 @@ protected override Image Decode(BufferedReadStream stream, Cance uint frameCount = 0; Image? image = null; ImageFrame? previousFrame = null; + FrameDisposalMode? previousDisposalMode = null; + bool globalColorTableUsed = false; + try { this.ReadLogicalScreenDescriptorAndGlobalColorTable(stream); + TPixel backgroundPixel = this.backgroundColor.ToPixel(); // Loop though the respective gif parts and read the data. int nextFlag = stream.ReadByte(); @@ -123,7 +132,7 @@ protected override Image Decode(BufferedReadStream stream, Cance break; } - this.ReadFrame(stream, ref image, ref previousFrame); + globalColorTableUsed |= this.ReadFrame(stream, ref image, ref previousFrame, ref previousDisposalMode, backgroundPixel); // Reset per-frame state. this.imageDescriptor = default; @@ -158,6 +167,13 @@ protected override Image Decode(BufferedReadStream stream, Cance break; } } + + // We cannot always trust the global GIF palette has actually been used. + // https://github.com/SixLabors/ImageSharp/issues/2866 + if (!globalColorTableUsed) + { + this.gifMetadata.ColorTableMode = FrameColorTableMode.Local; + } } finally { @@ -179,6 +195,8 @@ protected override ImageInfo Identify(BufferedReadStream stream, CancellationTok uint frameCount = 0; ImageFrameMetadata? previousFrame = null; List framesMetadata = []; + bool globalColorTableUsed = false; + try { this.ReadLogicalScreenDescriptorAndGlobalColorTable(stream); @@ -194,7 +212,7 @@ protected override ImageInfo Identify(BufferedReadStream stream, CancellationTok break; } - this.ReadFrameMetadata(stream, framesMetadata, ref previousFrame); + globalColorTableUsed |= this.ReadFrameMetadata(stream, framesMetadata, ref previousFrame); // Reset per-frame state. this.imageDescriptor = default; @@ -229,6 +247,13 @@ protected override ImageInfo Identify(BufferedReadStream stream, CancellationTok break; } } + + // We cannot always trust the global GIF palette has actually been used. + // https://github.com/SixLabors/ImageSharp/issues/2866 + if (!globalColorTableUsed) + { + this.gifMetadata.ColorTableMode = FrameColorTableMode.Local; + } } finally { @@ -416,7 +441,15 @@ private void ReadComments(BufferedReadStream stream) /// The containing image data. /// The image to decode the information to. /// The previous frame. - private void ReadFrame(BufferedReadStream stream, ref Image? image, ref ImageFrame? previousFrame) + /// The previous frame disposal mode. + /// The background color pixel. + /// Whether the frame has a global color table. + private bool ReadFrame( + BufferedReadStream stream, + ref Image? image, + ref ImageFrame? previousFrame, + ref FrameDisposalMode? previousDisposalMode, + TPixel backgroundPixel) where TPixel : unmanaged, IPixel { this.ReadImageDescriptor(stream); @@ -438,10 +471,12 @@ private void ReadFrame(BufferedReadStream stream, ref Image? ima } ReadOnlySpan colorTable = MemoryMarshal.Cast(rawColorTable); - this.ReadFrameColors(stream, ref image, ref previousFrame, colorTable); + this.ReadFrameColors(stream, ref image, ref previousFrame, ref previousDisposalMode, colorTable, backgroundPixel); // Skip any remaining blocks SkipBlock(stream); + + return !hasLocalColorTable; } /// @@ -451,46 +486,36 @@ private void ReadFrame(BufferedReadStream stream, ref Image? ima /// The containing image data. /// The image to decode the information to. /// The previous frame. + /// The previous frame disposal mode. /// The color table containing the available colors. + /// The background color pixel. private void ReadFrameColors( BufferedReadStream stream, ref Image? image, ref ImageFrame? previousFrame, - ReadOnlySpan colorTable) + ref FrameDisposalMode? previousDisposalMode, + ReadOnlySpan colorTable, + TPixel backgroundPixel) where TPixel : unmanaged, IPixel { GifImageDescriptor descriptor = this.imageDescriptor; int imageWidth = this.logicalScreenDescriptor.Width; int imageHeight = this.logicalScreenDescriptor.Height; bool transFlag = this.graphicsControlExtension.TransparencyFlag; - - ImageFrame? prevFrame = null; - ImageFrame? currentFrame = null; - ImageFrame imageFrame; + FrameDisposalMode disposalMethod = this.graphicsControlExtension.DisposalMethod; + ImageFrame currentFrame; if (previousFrame is null) { - if (!transFlag) - { - image = new Image(this.configuration, imageWidth, imageHeight, Color.Black.ToPixel(), this.metadata); - } - else - { - // This initializes the image to become fully transparent because the alpha channel is zero. - image = new Image(this.configuration, imageWidth, imageHeight, this.metadata); - } + image = transFlag + ? new Image(this.configuration, imageWidth, imageHeight, this.metadata) + : new Image(this.configuration, imageWidth, imageHeight, backgroundPixel, this.metadata); this.SetFrameMetadata(image.Frames.RootFrame.Metadata); - - imageFrame = image.Frames.RootFrame; + currentFrame = image.Frames.RootFrame; } else { - if (this.graphicsControlExtension.DisposalMethod == FrameDisposalMode.RestoreToPrevious) - { - prevFrame = previousFrame; - } - // We create a clone of the frame and add it. // We will overpaint the difference of pixels on the current frame to create a complete image. // This ensures that we have enough pixel data to process without distortion. #2450 @@ -498,9 +523,19 @@ private void ReadFrameColors( this.SetFrameMetadata(currentFrame.Metadata); - imageFrame = currentFrame; + if (previousDisposalMode == FrameDisposalMode.RestoreToBackground) + { + this.RestoreToBackground(currentFrame, backgroundPixel, transFlag); + } + } + + Rectangle interest = Rectangle.Intersect(image.Bounds, new(descriptor.Left, descriptor.Top, descriptor.Width, descriptor.Height)); + previousFrame = currentFrame; + previousDisposalMode = disposalMethod; - this.RestoreToBackground(imageFrame); + if (disposalMethod == FrameDisposalMode.RestoreToBackground) + { + this.restoreArea = interest; } if (colorTable.Length == 0) @@ -568,7 +603,7 @@ private void ReadFrameColors( // #403 The left + width value can be larger than the image width int maxX = Math.Min(descriptorRight, imageWidth); - Span row = imageFrame.PixelBuffer.DangerousGetRowSpan(writeY); + Span row = currentFrame.PixelBuffer.DangerousGetRowSpan(writeY); // Take the descriptorLeft..maxX slice of the row, so the loop can be simplified. row = row[descriptorLeft..maxX]; @@ -599,19 +634,6 @@ private void ReadFrameColors( } } } - - if (prevFrame != null) - { - previousFrame = prevFrame; - return; - } - - previousFrame = currentFrame ?? image.Frames.RootFrame; - - if (this.graphicsControlExtension.DisposalMethod == FrameDisposalMode.RestoreToBackground) - { - this.restoreArea = new Rectangle(descriptor.Left, descriptor.Top, descriptor.Width, descriptor.Height); - } } /// @@ -620,7 +642,8 @@ private void ReadFrameColors( /// The containing image data. /// The collection of frame metadata. /// The previous frame metadata. - private void ReadFrameMetadata(BufferedReadStream stream, List frameMetadata, ref ImageFrameMetadata? previousFrame) + /// Whether the frame has a global color table. + private bool ReadFrameMetadata(BufferedReadStream stream, List frameMetadata, ref ImageFrameMetadata? previousFrame) { this.ReadImageDescriptor(stream); @@ -632,6 +655,11 @@ private void ReadFrameMetadata(BufferedReadStream stream, List(768, AllocationOptions.Clean); stream.Read(this.currentLocalColorTable.GetSpan()[..length]); } + else + { + this.currentLocalColorTable = null; + this.currentLocalColorTableSize = 0; + } // Skip the frame indices. Pixels length + mincode size. // The gif format does not tell us the length of the compressed data beforehand. @@ -649,6 +677,8 @@ private void ReadFrameMetadata(BufferedReadStream stream, List @@ -656,7 +686,9 @@ private void ReadFrameMetadata(BufferedReadStream stream, List /// The pixel format. /// The frame. - private void RestoreToBackground(ImageFrame frame) + /// The background color. + /// Whether the background is transparent. + private void RestoreToBackground(ImageFrame frame, TPixel background, bool transparent) where TPixel : unmanaged, IPixel { if (this.restoreArea is null) @@ -666,7 +698,14 @@ private void RestoreToBackground(ImageFrame frame) Rectangle interest = Rectangle.Intersect(frame.Bounds, this.restoreArea.Value); Buffer2DRegion pixelRegion = frame.PixelBuffer.GetRegion(interest); - pixelRegion.Clear(); + if (transparent) + { + pixelRegion.Clear(); + } + else + { + pixelRegion.Fill(background); + } this.restoreArea = null; } @@ -775,7 +814,19 @@ private void ReadLogicalScreenDescriptorAndGlobalColorTable(BufferedReadStream s } } - this.gifMetadata.BackgroundColorIndex = this.logicalScreenDescriptor.BackgroundColorIndex; + // If the global color table is present, we can set the background color + // otherwise we default to transparent to match browser behavior. + ReadOnlyMemory? table = this.gifMetadata.GlobalColorTable; + byte index = this.logicalScreenDescriptor.BackgroundColorIndex; + if (table is not null && index < table.Value.Length) + { + this.backgroundColor = table.Value.Span[index]; + this.gifMetadata.BackgroundColorIndex = index; + } + else + { + this.backgroundColor = Color.Transparent; + } } private unsafe struct ScratchBuffer diff --git a/src/ImageSharp/Formats/Gif/GifEncoderCore.cs b/src/ImageSharp/Formats/Gif/GifEncoderCore.cs index 797e825dc4..a4830d7793 100644 --- a/src/ImageSharp/Formats/Gif/GifEncoderCore.cs +++ b/src/ImageSharp/Formats/Gif/GifEncoderCore.cs @@ -39,6 +39,11 @@ internal sealed class GifEncoderCore /// private IQuantizer? quantizer; + /// + /// The fallback quantizer to use when no quantizer is provided. + /// + private static readonly IQuantizer FallbackQuantizer = KnownQuantizers.Octree; + /// /// Whether the quantizer was supplied via options. /// @@ -67,6 +72,9 @@ internal sealed class GifEncoderCore /// private readonly ushort? repeatCount; + /// + /// The transparent color mode. + /// private readonly TransparentColorMode transparentColorMode; /// @@ -104,14 +112,18 @@ public void Encode(Image image, Stream stream, CancellationToken GifMetadata gifMetadata = image.Metadata.CloneGifMetadata(); this.colorTableMode ??= gifMetadata.ColorTableMode; bool useGlobalTable = this.colorTableMode == FrameColorTableMode.Global; - - // Quantize the first image frame returning a palette. - IndexedImageFrame? quantized = null; + bool useGlobalTableForFirstFrame = useGlobalTable; // 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); + if (frameMetadata.ColorTableMode == FrameColorTableMode.Local) + { + useGlobalTableForFirstFrame = false; + } + // Quantize the first image frame returning a palette. + IndexedImageFrame? quantized = null; if (this.quantizer is null) { // Is this a gif with color information. If so use that, otherwise use octree. @@ -121,21 +133,22 @@ public void Encode(Image image, Stream stream, CancellationToken int transparencyIndex = GetTransparentIndex(quantized, frameMetadata); if (transparencyIndex >= 0 || gifMetadata.GlobalColorTable.Value.Length < 256) { - this.quantizer = new PaletteQuantizer(gifMetadata.GlobalColorTable.Value, new() { Dither = null }, transparencyIndex); + this.quantizer = new PaletteQuantizer(gifMetadata.GlobalColorTable.Value, new() { Dither = null }); } else { - this.quantizer = KnownQuantizers.Octree; + this.quantizer = FallbackQuantizer; } } else { - this.quantizer = KnownQuantizers.Octree; + this.quantizer = FallbackQuantizer; } } // Quantize the first frame. Checking to see whether we can clear the transparent pixels // to allow for a smaller color palette and encoded result. + Color background = Color.Transparent; using (IQuantizer frameQuantizer = this.quantizer.CreatePixelSpecificQuantizer(this.configuration)) { ImageFrame? clonedFrame = null; @@ -147,24 +160,40 @@ public void Encode(Image image, Stream stream, CancellationToken clonedFrame = image.Frames.RootFrame.Clone(); GifFrameMetadata frameMeta = clonedFrame.Metadata.GetGifMetadata(); - Color background = frameMeta.DisposalMode == FrameDisposalMode.RestoreToBackground - ? this.backgroundColor ?? Color.Transparent - : Color.Transparent; + if (frameMeta.DisposalMode == FrameDisposalMode.RestoreToBackground) + { + background = this.backgroundColor ?? Color.Transparent; + } EncodingUtilities.ClearTransparentPixels(clonedFrame, background); } ImageFrame encodingFrame = clonedFrame ?? image.Frames.RootFrame; - if (useGlobalTable) + if (useGlobalTableForFirstFrame) { - frameQuantizer.BuildPalette(configuration, mode, strategy, image); - quantized = frameQuantizer.QuantizeFrame(encodingFrame, image.Bounds); + if (useGlobalTable) + { + frameQuantizer.BuildPalette(configuration, mode, strategy, image, background); + quantized = frameQuantizer.QuantizeFrame(encodingFrame, image.Bounds); + } + else + { + frameQuantizer.BuildPalette(configuration, mode, strategy, encodingFrame, background); + quantized = frameQuantizer.QuantizeFrame(encodingFrame, encodingFrame.Bounds); + } } else { - frameQuantizer.BuildPalette(configuration, mode, strategy, encodingFrame); - quantized = frameQuantizer.QuantizeFrame(encodingFrame, image.Bounds); + quantized = this.QuantizeAdditionalFrameAndUpdateMetadata( + encodingFrame, + encodingFrame.Bounds, + frameMetadata, + true, + default, + false, + frameMetadata.HasTransparency ? frameMetadata.TransparencyIndex : -1, + background); } clonedFrame?.Dispose(); @@ -259,8 +288,8 @@ private void EncodeAdditionalFrames( return; } - PaletteQuantizer paletteQuantizer = default; - bool hasPaletteQuantizer = false; + PaletteQuantizer globalPaletteQuantizer = default; + bool hasGlobalPaletteQuantizer = false; // Store the first frame as a reference for de-duplication comparison. ImageFrame previousFrame = image.Frames.RootFrame; @@ -280,14 +309,13 @@ private void EncodeAdditionalFrames( GifFrameMetadata gifMetadata = GetGifFrameMetadata(currentFrame, globalTransparencyIndex); bool useLocal = this.colorTableMode == FrameColorTableMode.Local || (gifMetadata.ColorTableMode == FrameColorTableMode.Local); - if (!useLocal && !hasPaletteQuantizer && i > 0) + if (!useLocal && !hasGlobalPaletteQuantizer && 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 ? gifMetadata.TransparencyIndex : -1; - paletteQuantizer = new(this.configuration, this.quantizer!.Options, globalPalette, transparencyIndex); - hasPaletteQuantizer = true; + globalPaletteQuantizer = new(this.configuration, this.quantizer!.Options, globalPalette); + hasGlobalPaletteQuantizer = true; } this.EncodeAdditionalFrame( @@ -298,7 +326,7 @@ private void EncodeAdditionalFrames( encodingFrame, useLocal, gifMetadata, - paletteQuantizer, + globalPaletteQuantizer, previousDisposalMode); previousFrame = currentFrame; @@ -307,9 +335,9 @@ private void EncodeAdditionalFrames( } finally { - if (hasPaletteQuantizer) + if (hasGlobalPaletteQuantizer) { - paletteQuantizer.Dispose(); + globalPaletteQuantizer.Dispose(); } } } @@ -387,7 +415,8 @@ private void EncodeAdditionalFrame( useLocal, globalPaletteQuantizer, difference, - transparencyIndex); + transparencyIndex, + background); this.WriteGraphicalControlExtension(metadata, stream); @@ -410,7 +439,8 @@ private IndexedImageFrame QuantizeAdditionalFrameAndUpdateMetadata globalPaletteQuantizer, bool hasDuplicates, - int transparencyIndex) + int transparencyIndex, + Color transparentColor) where TPixel : unmanaged, IPixel { IndexedImageFrame quantized; @@ -434,14 +464,14 @@ private IndexedImageFrame QuantizeAdditionalFrameAndUpdateMetadata frameQuantizer = quantizer.CreatePixelSpecificQuantizer(this.configuration, quantizer.Options); quantized = frameQuantizer.BuildPaletteAndQuantizeFrame(encodingFrame, bounds); } else { // We must quantize the frame to generate a local color table. - IQuantizer quantizer = this.hasQuantizer ? this.quantizer! : KnownQuantizers.Octree; + IQuantizer quantizer = this.hasQuantizer ? this.quantizer! : FallbackQuantizer; using IQuantizer frameQuantizer = quantizer.CreatePixelSpecificQuantizer(this.configuration, quantizer.Options); quantized = frameQuantizer.BuildPaletteAndQuantizeFrame(encodingFrame, bounds); @@ -454,7 +484,7 @@ private IndexedImageFrame QuantizeAdditionalFrameAndUpdateMetadata frameQuantizer = quantizer.CreatePixelSpecificQuantizer(this.configuration, quantizer.Options); quantized = frameQuantizer.BuildPaletteAndQuantizeFrame(encodingFrame, bounds); } @@ -462,7 +492,7 @@ private IndexedImageFrame QuantizeAdditionalFrameAndUpdateMetadata frameQuantizer = quantizer.CreatePixelSpecificQuantizer(this.configuration, quantizer.Options); quantized = frameQuantizer.BuildPaletteAndQuantizeFrame(encodingFrame, bounds); @@ -486,7 +516,8 @@ private IndexedImageFrame QuantizeAdditionalFrameAndUpdateMetadata QuantizeAdditionalFrameAndUpdateMetadata()); quantized = globalPaletteQuantizer.QuantizeFrame(encodingFrame, bounds); } diff --git a/src/ImageSharp/Formats/Gif/GifFrameMetadata.cs b/src/ImageSharp/Formats/Gif/GifFrameMetadata.cs index 5fe892c656..92bd114e8f 100644 --- a/src/ImageSharp/Formats/Gif/GifFrameMetadata.cs +++ b/src/ImageSharp/Formats/Gif/GifFrameMetadata.cs @@ -129,8 +129,7 @@ public FormatConnectingFrameMetadata ToFormatConnectingFrameMetadata() /// public void AfterFrameApply(ImageFrame source, ImageFrame destination) where TPixel : unmanaged, IPixel - { - } + => this.LocalColorTable = null; /// IDeepCloneable IDeepCloneable.DeepClone() => this.DeepClone(); diff --git a/src/ImageSharp/Formats/Gif/GifMetadata.cs b/src/ImageSharp/Formats/Gif/GifMetadata.cs index 517609af45..fc6c9ab9a4 100644 --- a/src/ImageSharp/Formats/Gif/GifMetadata.cs +++ b/src/ImageSharp/Formats/Gif/GifMetadata.cs @@ -101,7 +101,7 @@ public static GifMetadata FromFormatConnectingMetadata(FormatConnectingMetadata /// public PixelTypeInfo GetPixelTypeInfo() { - int bpp = this.GlobalColorTable.HasValue + int bpp = this.ColorTableMode == FrameColorTableMode.Global && this.GlobalColorTable.HasValue ? Numerics.Clamp(ColorNumerics.GetBitsNeededForColorDepth(this.GlobalColorTable.Value.Length), 1, 8) : 8; @@ -115,15 +115,16 @@ public PixelTypeInfo GetPixelTypeInfo() /// public FormatConnectingMetadata ToFormatConnectingMetadata() { - Color color = this.GlobalColorTable.HasValue && this.GlobalColorTable.Value.Span.Length > this.BackgroundColorIndex + bool global = this.ColorTableMode == FrameColorTableMode.Global; + Color color = global && this.GlobalColorTable.HasValue && this.GlobalColorTable.Value.Span.Length > this.BackgroundColorIndex ? this.GlobalColorTable.Value.Span[this.BackgroundColorIndex] : Color.Transparent; - return new() + return new FormatConnectingMetadata() { AnimateRootFrame = true, + ColorTable = global ? this.GlobalColorTable : null, BackgroundColor = color, - ColorTable = this.GlobalColorTable, ColorTableMode = this.ColorTableMode, PixelTypeInfo = this.GetPixelTypeInfo(), RepeatCount = this.RepeatCount, @@ -133,8 +134,7 @@ public FormatConnectingMetadata ToFormatConnectingMetadata() /// public void AfterImageApply(Image destination) where TPixel : unmanaged, IPixel - { - } + => this.GlobalColorTable = null; /// IDeepCloneable IDeepCloneable.DeepClone() => this.DeepClone(); diff --git a/src/ImageSharp/Formats/IFormatFrameMetadata.cs b/src/ImageSharp/Formats/IFormatFrameMetadata.cs index 20f27d050c..261cc12639 100644 --- a/src/ImageSharp/Formats/IFormatFrameMetadata.cs +++ b/src/ImageSharp/Formats/IFormatFrameMetadata.cs @@ -14,7 +14,7 @@ public interface IFormatFrameMetadata : IDeepCloneable /// Converts the metadata to a instance. /// /// The . - FormatConnectingFrameMetadata ToFormatConnectingFrameMetadata(); + public FormatConnectingFrameMetadata ToFormatConnectingFrameMetadata(); /// /// This method is called after a process has been applied to the image frame. @@ -22,7 +22,7 @@ public interface IFormatFrameMetadata : IDeepCloneable /// The type of pixel format. /// The source image frame. /// The destination image frame. - void AfterFrameApply(ImageFrame source, ImageFrame destination) + public void AfterFrameApply(ImageFrame source, ImageFrame destination) where TPixel : unmanaged, IPixel; } @@ -39,6 +39,6 @@ public interface IFormatFrameMetadata : IFormatFrameMetadata, IDeepClonea /// The . /// The . #pragma warning disable CA1000 // Do not declare static members on generic types - static abstract TSelf FromFormatConnectingFrameMetadata(FormatConnectingFrameMetadata metadata); + public static abstract TSelf FromFormatConnectingFrameMetadata(FormatConnectingFrameMetadata metadata); #pragma warning restore CA1000 // Do not declare static members on generic types } diff --git a/src/ImageSharp/Formats/IFormatMetadata.cs b/src/ImageSharp/Formats/IFormatMetadata.cs index a351431c94..3142b465cb 100644 --- a/src/ImageSharp/Formats/IFormatMetadata.cs +++ b/src/ImageSharp/Formats/IFormatMetadata.cs @@ -14,20 +14,20 @@ public interface IFormatMetadata : IDeepCloneable /// Converts the metadata to a instance. /// /// The pixel type info. - PixelTypeInfo GetPixelTypeInfo(); + public PixelTypeInfo GetPixelTypeInfo(); /// /// Converts the metadata to a instance. /// /// The . - FormatConnectingMetadata ToFormatConnectingMetadata(); + public FormatConnectingMetadata ToFormatConnectingMetadata(); /// /// This method is called after a process has been applied to the image. /// /// The type of pixel format. /// The destination image . - void AfterImageApply(Image destination) + public void AfterImageApply(Image destination) where TPixel : unmanaged, IPixel; } @@ -44,6 +44,6 @@ public interface IFormatMetadata : IFormatMetadata, IDeepCloneable /// The . /// The . #pragma warning disable CA1000 // Do not declare static members on generic types - static abstract TSelf FromFormatConnectingMetadata(FormatConnectingMetadata metadata); + public static abstract TSelf FromFormatConnectingMetadata(FormatConnectingMetadata metadata); #pragma warning restore CA1000 // Do not declare static members on generic types } diff --git a/src/ImageSharp/Formats/IQuantizingImageEncoder.cs b/src/ImageSharp/Formats/IQuantizingImageEncoder.cs index 5edf6e40e9..1ce2aa0918 100644 --- a/src/ImageSharp/Formats/IQuantizingImageEncoder.cs +++ b/src/ImageSharp/Formats/IQuantizingImageEncoder.cs @@ -13,12 +13,12 @@ public interface IQuantizingImageEncoder /// /// Gets the quantizer used to generate the color palette. /// - IQuantizer? Quantizer { get; } + public IQuantizer? Quantizer { get; } /// /// Gets the used for quantization when building color palettes. /// - IPixelSamplingStrategy PixelSamplingStrategy { get; } + public IPixelSamplingStrategy PixelSamplingStrategy { get; } } /// diff --git a/src/ImageSharp/Formats/Ico/IcoFrameMetadata.cs b/src/ImageSharp/Formats/Ico/IcoFrameMetadata.cs index 62aa705cbe..77096d5241 100644 --- a/src/ImageSharp/Formats/Ico/IcoFrameMetadata.cs +++ b/src/ImageSharp/Formats/Ico/IcoFrameMetadata.cs @@ -119,6 +119,7 @@ public void AfterFrameApply(ImageFrame source, ImageFrame diff --git a/src/ImageSharp/Formats/Ico/IcoMetadata.cs b/src/ImageSharp/Formats/Ico/IcoMetadata.cs index a6c2704b31..09c1da1b1a 100644 --- a/src/ImageSharp/Formats/Ico/IcoMetadata.cs +++ b/src/ImageSharp/Formats/Ico/IcoMetadata.cs @@ -152,8 +152,7 @@ public FormatConnectingMetadata ToFormatConnectingMetadata() /// public void AfterImageApply(Image destination) where TPixel : unmanaged, IPixel - { - } + => this.ColorTable = null; /// IDeepCloneable IDeepCloneable.DeepClone() => this.DeepClone(); diff --git a/src/ImageSharp/Formats/Png/PngEncoder.cs b/src/ImageSharp/Formats/Png/PngEncoder.cs index 63e675b505..1032f88529 100644 --- a/src/ImageSharp/Formats/Png/PngEncoder.cs +++ b/src/ImageSharp/Formats/Png/PngEncoder.cs @@ -1,8 +1,6 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using SixLabors.ImageSharp.Processing.Processors.Quantization; - namespace SixLabors.ImageSharp.Formats.Png; /// @@ -10,16 +8,6 @@ namespace SixLabors.ImageSharp.Formats.Png; /// public class PngEncoder : QuantizingAnimatedImageEncoder { - /// - /// Initializes a new instance of the class. - /// - public PngEncoder() - - // Hack. TODO: Investigate means to fix/optimize the Wu quantizer. - // The Wu quantizer does not handle the default sampling strategy well for some larger images. - // It's expensive and the results are not better than the extensive strategy. - => this.PixelSamplingStrategy = new ExtensivePixelSamplingStrategy(); - /// /// Gets the number of bits per sample or per palette index (not per pixel). /// Not all values are allowed for all values. diff --git a/src/ImageSharp/Formats/Png/PngEncoderCore.cs b/src/ImageSharp/Formats/Png/PngEncoderCore.cs index ea36d9fe1e..45d7d1270b 100644 --- a/src/ImageSharp/Formats/Png/PngEncoderCore.cs +++ b/src/ImageSharp/Formats/Png/PngEncoderCore.cs @@ -3,8 +3,8 @@ using System.Buffers; using System.Buffers.Binary; +using System.Diagnostics.CodeAnalysis; using System.IO.Hashing; -using System.Numerics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.Intrinsics; @@ -119,18 +119,13 @@ internal sealed class PngEncoderCore : IDisposable /// private IQuantizer? quantizer; - /// - /// Any explicit quantized transparent index provided by the background color. - /// - private int derivedTransparencyIndex = -1; - /// /// The default background color of the canvas when animating. /// This color may be used to fill the unused space on the canvas around the frames, /// as well as the transparent pixels of the first frame. /// The background color is also used when a frame disposal mode is . /// - private readonly Color? backgroundColor; + private Color? backgroundColor; /// /// The number of times any animation is repeated. @@ -158,7 +153,6 @@ public PngEncoderCore(Configuration configuration, PngEncoder encoder) this.memoryAllocator = configuration.MemoryAllocator; this.encoder = encoder; this.quantizer = encoder.Quantizer; - this.backgroundColor = encoder.BackgroundColor; this.repeatCount = encoder.RepeatCount; this.animateRootFrame = encoder.AnimateRootFrame; } @@ -187,74 +181,92 @@ public void Encode(Image image, Stream stream, CancellationToken ImageFrame? clonedFrame = null; ImageFrame currentFrame = image.Frames.RootFrame; - int currentFrameIndex = 0; + IndexedImageFrame? quantized = null; + PaletteQuantizer? paletteQuantizer = null; + Buffer2DRegion currentFrameRegion = currentFrame.PixelBuffer.GetRegion(); - bool clearTransparency = EncodingUtilities.ShouldClearTransparentPixels(this.encoder.TransparentColorMode); - if (clearTransparency) + try { - currentFrame = clonedFrame = currentFrame.Clone(); - EncodingUtilities.ClearTransparentPixels(currentFrame, Color.Transparent); - } + int currentFrameIndex = 0; - // Do not move this. We require an accurate bit depth for the header chunk. - IndexedImageFrame? quantized = this.CreateQuantizedImageAndUpdateBitDepth( - pngMetadata, - currentFrame, - currentFrame.Bounds, - null); - - this.WriteHeaderChunk(stream); - this.WriteGammaChunk(stream); - this.WriteCicpChunk(stream, metadata); - this.WriteColorProfileChunk(stream, metadata); - this.WritePaletteChunk(stream, quantized); - this.WriteTransparencyChunk(stream, pngMetadata); - this.WritePhysicalChunk(stream, metadata); - this.WriteExifChunk(stream, metadata); - this.WriteXmpChunk(stream, metadata); - this.WriteTextChunks(stream, pngMetadata); + bool clearTransparency = EncodingUtilities.ShouldClearTransparentPixels(this.encoder.TransparentColorMode); + if (clearTransparency) + { + currentFrame = clonedFrame = currentFrame.Clone(); + currentFrameRegion = currentFrame.PixelBuffer.GetRegion(); + EncodingUtilities.ClearTransparentPixels(this.configuration, in currentFrameRegion, this.backgroundColor.Value); + } - if (image.Frames.Count > 1) - { - this.WriteAnimationControlChunk( - stream, - (uint)(image.Frames.Count - (pngMetadata.AnimateRootFrame ? 0 : 1)), - this.repeatCount ?? pngMetadata.RepeatCount); - } + // Do not move this. We require an accurate bit depth for the header chunk. + quantized = this.CreateQuantizedImageAndUpdateBitDepth( + pngMetadata, + image, + currentFrame, + currentFrame.Bounds, + null); + + this.WriteHeaderChunk(stream); + this.WriteGammaChunk(stream); + this.WriteCicpChunk(stream, metadata); + this.WriteColorProfileChunk(stream, metadata); + this.WritePaletteChunk(stream, quantized); + this.WriteTransparencyChunk(stream, pngMetadata); + this.WritePhysicalChunk(stream, metadata); + this.WriteExifChunk(stream, metadata); + this.WriteXmpChunk(stream, metadata); + this.WriteTextChunks(stream, pngMetadata); - // If the first frame isn't animated, write it as usual and skip it when writing animated frames - bool userAnimateRootFrame = this.animateRootFrame == true; - if ((!userAnimateRootFrame && !pngMetadata.AnimateRootFrame) || image.Frames.Count == 1) - { - cancellationToken.ThrowIfCancellationRequested(); - FrameControl frameControl = new((uint)this.width, (uint)this.height); - this.WriteDataChunks(frameControl, currentFrame.PixelBuffer.GetRegion(), quantized, stream, false); - currentFrameIndex++; - } + if (image.Frames.Count > 1) + { + this.WriteAnimationControlChunk( + stream, + (uint)(image.Frames.Count - (pngMetadata.AnimateRootFrame ? 0 : 1)), + this.repeatCount ?? pngMetadata.RepeatCount); + } + + // If the first frame isn't animated, write it as usual and skip it when writing animated frames + bool userAnimateRootFrame = this.animateRootFrame == true; + if ((!userAnimateRootFrame && !pngMetadata.AnimateRootFrame) || image.Frames.Count == 1) + { + cancellationToken.ThrowIfCancellationRequested(); + FrameControl frameControl = new((uint)this.width, (uint)this.height); + this.WriteDataChunks(in frameControl, in currentFrameRegion, quantized, stream, false); + currentFrameIndex++; + } - try - { if (image.Frames.Count > 1) { // Write the first animated frame. currentFrame = image.Frames[currentFrameIndex]; + currentFrameRegion = currentFrame.PixelBuffer.GetRegion(); + PngFrameMetadata frameMetadata = currentFrame.Metadata.GetPngMetadata(); FrameDisposalMode previousDisposal = frameMetadata.DisposalMode; FrameControl frameControl = this.WriteFrameControlChunk(stream, frameMetadata, currentFrame.Bounds, 0); uint sequenceNumber = 1; if (pngMetadata.AnimateRootFrame) { - this.WriteDataChunks(frameControl, currentFrame.PixelBuffer.GetRegion(), quantized, stream, false); + this.WriteDataChunks(in frameControl, in currentFrameRegion, quantized, stream, false); } else { - sequenceNumber += this.WriteDataChunks(frameControl, currentFrame.PixelBuffer.GetRegion(), quantized, stream, true); + sequenceNumber += this.WriteDataChunks(in frameControl, in currentFrameRegion, quantized, stream, true); } currentFrameIndex++; // Capture the global palette for reuse on subsequent frames. - ReadOnlyMemory? previousPalette = quantized?.Palette.ToArray(); + ReadOnlyMemory previousPalette = quantized?.Palette.ToArray(); + + if (!previousPalette.IsEmpty) + { + // Use the previously derived global palette and a shared quantizer to + // quantize the subsequent frames. This allows us to cache the color matching resolution. + paletteQuantizer ??= new( + this.configuration, + this.quantizer!.Options, + previousPalette); + } // Write following frames. ImageFrame previousFrame = image.Frames.RootFrame; @@ -267,13 +279,16 @@ public void Encode(Image image, Stream stream, CancellationToken cancellationToken.ThrowIfCancellationRequested(); ImageFrame? prev = previousDisposal == FrameDisposalMode.RestoreToBackground ? null : previousFrame; + currentFrame = image.Frames[currentFrameIndex]; + currentFrameRegion = currentFrame.PixelBuffer.GetRegion(); + ImageFrame? nextFrame = currentFrameIndex < image.Frames.Count - 1 ? image.Frames[currentFrameIndex + 1] : null; frameMetadata = currentFrame.Metadata.GetPngMetadata(); bool blend = frameMetadata.BlendMode == FrameBlendMode.Over; Color background = frameMetadata.DisposalMode == FrameDisposalMode.RestoreToBackground - ? this.backgroundColor ?? Color.Transparent + ? this.backgroundColor.Value : Color.Transparent; (bool difference, Rectangle bounds) = @@ -296,8 +311,20 @@ public void Encode(Image image, Stream stream, CancellationToken // Dispose of previous quantized frame and reassign. quantized?.Dispose(); - quantized = this.CreateQuantizedImageAndUpdateBitDepth(pngMetadata, encodingFrame, bounds, previousPalette); - sequenceNumber += this.WriteDataChunks(frameControl, encodingFrame.PixelBuffer.GetRegion(bounds), quantized, stream, true) + 1; + + quantized = this.CreateQuantizedFrame( + this.encoder, + this.colorType, + this.bitDepth, + pngMetadata, + image, + encodingFrame, + bounds, + paletteQuantizer, + default); + + Buffer2DRegion encodingFrameRegion = encodingFrame.PixelBuffer.GetRegion(bounds); + sequenceNumber += this.WriteDataChunks(in frameControl, in encodingFrameRegion, quantized, stream, true) + 1; previousFrame = currentFrame; previousDisposal = frameMetadata.DisposalMode; @@ -313,6 +340,7 @@ public void Encode(Image image, Stream stream, CancellationToken // Dispose of allocations from final frame. clonedFrame?.Dispose(); quantized?.Dispose(); + paletteQuantizer?.Dispose(); } } @@ -328,18 +356,35 @@ public void Dispose() /// /// The type of the pixel. /// The image metadata. - /// The frame to quantize. + /// The image. + /// The current image frame. /// The area of interest within the frame. - /// Any previously derived palette. + /// The quantizer containing any previously derived palette. /// The quantized image. private IndexedImageFrame? CreateQuantizedImageAndUpdateBitDepth( PngMetadata metadata, + Image image, ImageFrame frame, Rectangle bounds, - ReadOnlyMemory? previousPalette) + PaletteQuantizer? paletteQuantizer) where TPixel : unmanaged, IPixel { - IndexedImageFrame? quantized = this.CreateQuantizedFrame(this.encoder, this.colorType, this.bitDepth, metadata, frame, bounds, previousPalette); + PngFrameMetadata frameMetadata = frame.Metadata.GetPngMetadata(); + Color background = frameMetadata.DisposalMode == FrameDisposalMode.RestoreToBackground + ? this.backgroundColor ?? Color.Transparent + : Color.Transparent; + + IndexedImageFrame? quantized = this.CreateQuantizedFrame( + this.encoder, + this.colorType, + this.bitDepth, + metadata, + image, + frame, + bounds, + paletteQuantizer, + background); + this.bitDepth = CalculateBitDepth(this.colorType, this.bitDepth, quantized); return quantized; } @@ -1105,7 +1150,7 @@ private FrameControl WriteFrameControlChunk(Stream stream, PngFrameMetadata fram /// The quantized pixel data. Can be null. /// The stream. /// Is writing fdAT or IDAT. - private uint WriteDataChunks(FrameControl frameControl, Buffer2DRegion frame, IndexedImageFrame? quantized, Stream stream, bool isFrame) + private uint WriteDataChunks(in FrameControl frameControl, in Buffer2DRegion frame, IndexedImageFrame? quantized, Stream stream, bool isFrame) where TPixel : unmanaged, IPixel { byte[] buffer; @@ -1123,12 +1168,12 @@ private uint WriteDataChunks(FrameControl frameControl, Buffer2DRegionThe image frame pixel buffer. /// The quantized pixels. /// The deflate stream. - private void EncodePixels(Buffer2DRegion pixels, IndexedImageFrame? quantized, ZlibDeflateStream deflateStream) + private void EncodePixels(in Buffer2DRegion pixels, IndexedImageFrame? quantized, ZlibDeflateStream deflateStream) where TPixel : unmanaged, IPixel { int bytesPerScanline = this.CalculateScanlineLength(pixels.Width); @@ -1222,7 +1267,7 @@ private void EncodePixels(Buffer2DRegion pixels, IndexedImageFra /// The type of the pixel. /// The image frame pixel buffer. /// The deflate stream. - private void EncodeAdam7Pixels(Buffer2DRegion pixels, ZlibDeflateStream deflateStream) + private void EncodeAdam7Pixels(in Buffer2DRegion pixels, ZlibDeflateStream deflateStream) where TPixel : unmanaged, IPixel { for (int pass = 0; pass < 7; pass++) @@ -1258,7 +1303,7 @@ private void EncodeAdam7Pixels(Buffer2DRegion pixels, ZlibDeflat // Encode data // Note: quantized parameter not used // Note: row parameter not used - this.CollectAndFilterPixelRow(block, ref filter, ref attempt, null, -1); + this.CollectAndFilterPixelRow(block, ref filter, ref attempt, null, -1); deflateStream.Write(filter); this.SwapScanlineBuffers(); @@ -1432,6 +1477,7 @@ private void SwapScanlineBuffers() /// The PNG metadata. /// if set to true [use16 bit]. /// The bytes per pixel. + [MemberNotNull(nameof(backgroundColor))] private void SanitizeAndSetEncoderOptions( PngEncoder encoder, PngMetadata pngMetadata, @@ -1473,6 +1519,7 @@ private void SanitizeAndSetEncoderOptions( this.interlaceMode = encoder.InterlaceMethod ?? pngMetadata.InterlaceMethod; this.chunkFilter = encoder.SkipMetadata ? PngChunkFilter.ExcludeAll : encoder.ChunkFilter ?? PngChunkFilter.None; + this.backgroundColor = encoder.BackgroundColor ?? pngMetadata.TransparentColor ?? Color.Transparent; } /// @@ -1483,17 +1530,21 @@ private void SanitizeAndSetEncoderOptions( /// The color type. /// The bits per component. /// The image metadata. - /// The frame to quantize. + /// The image. + /// The current image frame. /// The frame area of interest. - /// Any previously derived palette. + /// The quantizer containing any previously derived palette. + /// The background color. private IndexedImageFrame? CreateQuantizedFrame( QuantizingImageEncoder encoder, PngColorType colorType, byte bitDepth, PngMetadata metadata, + Image image, ImageFrame frame, Rectangle bounds, - ReadOnlyMemory? previousPalette) + PaletteQuantizer? paletteQuantizer, + Color backgroundColor) where TPixel : unmanaged, IPixel { if (colorType is not PngColorType.Palette) @@ -1501,55 +1552,52 @@ private void SanitizeAndSetEncoderOptions( return null; } - if (previousPalette is not null) + if (paletteQuantizer.HasValue) { - // 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, - this.derivedTransparencyIndex); - paletteQuantizer.BuildPalette(encoder.PixelSamplingStrategy, frame); - return paletteQuantizer.QuantizeFrame(frame, bounds); + return paletteQuantizer.Value.QuantizeFrame(frame, bounds); } // Use the metadata to determine what quantization depth to use if no quantizer has been set. if (this.quantizer is null) { - if (metadata.ColorTable is not null) + if (metadata.ColorTable?.Length > 0) { // We can use the color data from the decoded metadata here. // We avoid dithering by default to preserve the original colors. - ReadOnlySpan palette = metadata.ColorTable.Value.Span; - - // Certain operations perform alpha premultiplication, which can cause the color to change so we - // must search for the transparency index in the palette. - // Transparent pixels are much more likely to be found at the end of a palette. - int index = -1; - for (int i = palette.Length - 1; i >= 0; i--) - { - Vector4 instance = palette[i].ToScaledVector4(); - if (instance.W == 0f) - { - index = i; - break; - } - } - - this.derivedTransparencyIndex = index; - - this.quantizer = new PaletteQuantizer(metadata.ColorTable.Value, new() { Dither = null }, this.derivedTransparencyIndex); + this.quantizer = new PaletteQuantizer(metadata.ColorTable.Value, new() { Dither = null }); } else { - this.quantizer = new WuQuantizer(new QuantizerOptions { MaxColors = ColorNumerics.GetColorCountForBitDepth(bitDepth) }); + // Don't use transparency threshold for quantization PNG can handle multiple transparent colors. + this.quantizer = new WuQuantizer(new QuantizerOptions { TransparencyThreshold = 0, MaxColors = ColorNumerics.GetColorCountForBitDepth(bitDepth) }); } } // Create quantized frame returning the palette and set the bit depth. using IQuantizer frameQuantizer = this.quantizer.CreatePixelSpecificQuantizer(frame.Configuration); - frameQuantizer.BuildPalette(encoder.PixelSamplingStrategy, frame); + if (image.Frames.Count > 1) + { + // Encoding animated frames with a global palette requires a transparent pixel in the palette + // since we only encode the delta between frames. To ensure that we have a transparent pixel + // we create a fake frame with a containing only transparent pixels and add it to the palette. + using Buffer2D px = image.Configuration.MemoryAllocator.Allocate2D(Math.Min(256, image.Width), Math.Min(256, image.Height)); + TPixel backGroundPixel = backgroundColor.ToPixel(); + for (int i = 0; i < px.Height; i++) + { + px.DangerousGetRowSpan(i).Fill(backGroundPixel); + } + + frameQuantizer.AddPaletteColors(px.GetRegion()); + } + + frameQuantizer.BuildPalette( + this.configuration, + encoder.TransparentColorMode, + encoder.PixelSamplingStrategy, + image, + backgroundColor); + return frameQuantizer.QuantizeFrame(frame, bounds); } diff --git a/src/ImageSharp/Formats/Png/PngMetadata.cs b/src/ImageSharp/Formats/Png/PngMetadata.cs index 00cba088cb..bb80438ba0 100644 --- a/src/ImageSharp/Formats/Png/PngMetadata.cs +++ b/src/ImageSharp/Formats/Png/PngMetadata.cs @@ -250,8 +250,7 @@ public FormatConnectingMetadata ToFormatConnectingMetadata() /// public void AfterImageApply(Image destination) where TPixel : unmanaged, IPixel - { - } + => this.ColorTable = null; /// IDeepCloneable IDeepCloneable.DeepClone() => this.DeepClone(); diff --git a/src/ImageSharp/Formats/TransparentColorMode.cs b/src/ImageSharp/Formats/TransparentColorMode.cs index 39986b5024..5b52e5fa76 100644 --- a/src/ImageSharp/Formats/TransparentColorMode.cs +++ b/src/ImageSharp/Formats/TransparentColorMode.cs @@ -18,5 +18,5 @@ public enum TransparentColorMode /// to fully transparent pixels (all components set to zero), /// which may improve compression. /// - Clear = 1, + Clear = 1 } diff --git a/src/ImageSharp/Formats/Webp/BitReader/BitReaderBase.cs b/src/ImageSharp/Formats/Webp/BitReader/BitReaderBase.cs index 83f9e797ab..2b843cc8f6 100644 --- a/src/ImageSharp/Formats/Webp/BitReader/BitReaderBase.cs +++ b/src/ImageSharp/Formats/Webp/BitReader/BitReaderBase.cs @@ -32,7 +32,7 @@ protected BitReaderBase(Stream inputStream, int imageDataSize, MemoryAllocator m /// Used for allocating memory during reading data from the stream. protected static IMemoryOwner ReadImageDataFromStream(Stream input, int bytesToRead, MemoryAllocator memoryAllocator) { - IMemoryOwner data = memoryAllocator.Allocate(bytesToRead); + IMemoryOwner data = memoryAllocator.Allocate(bytesToRead, AllocationOptions.Clean); Span dataSpan = data.Memory.Span; input.Read(dataSpan[..bytesToRead], 0, bytesToRead); diff --git a/src/ImageSharp/Formats/Webp/Lossy/Vp8Decoder.cs b/src/ImageSharp/Formats/Webp/Lossy/Vp8Decoder.cs index b3c5bfaf41..eb4a517511 100644 --- a/src/ImageSharp/Formats/Webp/Lossy/Vp8Decoder.cs +++ b/src/ImageSharp/Formats/Webp/Lossy/Vp8Decoder.cs @@ -67,14 +67,14 @@ public Vp8Decoder(Vp8FrameHeader frameHeader, Vp8PictureHeader pictureHeader, Vp int extraY = extraRows * this.CacheYStride; int extraUv = extraRows / 2 * this.CacheUvStride; this.YuvBuffer = memoryAllocator.Allocate((WebpConstants.Bps * 17) + (WebpConstants.Bps * 9) + extraY); - this.CacheY = memoryAllocator.Allocate((16 * this.CacheYStride) + extraY); + this.CacheY = memoryAllocator.Allocate((16 * this.CacheYStride) + extraY, AllocationOptions.Clean); int cacheUvSize = (16 * this.CacheUvStride) + extraUv; - this.CacheU = memoryAllocator.Allocate(cacheUvSize); - this.CacheV = memoryAllocator.Allocate(cacheUvSize); - this.TmpYBuffer = memoryAllocator.Allocate((int)width); - this.TmpUBuffer = memoryAllocator.Allocate((int)width); - this.TmpVBuffer = memoryAllocator.Allocate((int)width); - this.Pixels = memoryAllocator.Allocate((int)(width * height * 4)); + this.CacheU = memoryAllocator.Allocate(cacheUvSize, AllocationOptions.Clean); + this.CacheV = memoryAllocator.Allocate(cacheUvSize, AllocationOptions.Clean); + this.TmpYBuffer = memoryAllocator.Allocate((int)width, AllocationOptions.Clean); + this.TmpUBuffer = memoryAllocator.Allocate((int)width, AllocationOptions.Clean); + this.TmpVBuffer = memoryAllocator.Allocate((int)width, AllocationOptions.Clean); + this.Pixels = memoryAllocator.Allocate((int)(width * height * 4), AllocationOptions.Clean); #if DEBUG // Filling those buffers with 205, is only useful for debugging, diff --git a/src/ImageSharp/Formats/Webp/WebpAnimationDecoder.cs b/src/ImageSharp/Formats/Webp/WebpAnimationDecoder.cs index b74337ef37..173d9436dd 100644 --- a/src/ImageSharp/Formats/Webp/WebpAnimationDecoder.cs +++ b/src/ImageSharp/Formats/Webp/WebpAnimationDecoder.cs @@ -81,16 +81,29 @@ public WebpAnimationDecoder(MemoryAllocator memoryAllocator, Configuration confi /// The width of the image. /// The height of the image. /// The size of the image data in bytes. - public Image Decode(BufferedReadStream stream, WebpFeatures features, uint width, uint height, uint completeDataSize) + public Image Decode( + BufferedReadStream stream, + WebpFeatures features, + uint width, + uint height, + uint completeDataSize) where TPixel : unmanaged, IPixel { Image? image = null; ImageFrame? previousFrame = null; + WebpFrameData? prevFrameData = null; this.metadata = new ImageMetadata(); this.webpMetadata = this.metadata.GetWebpMetadata(); this.webpMetadata.RepeatCount = features.AnimationLoopCount; + Color backgroundColor = this.backgroundColorHandling == BackgroundColorHandling.Ignore + ? Color.Transparent + : features.AnimationBackgroundColor!.Value; + + this.webpMetadata.BackgroundColor = backgroundColor; + TPixel backgroundPixel = backgroundColor.ToPixel(); + Span buffer = stackalloc byte[4]; uint frameCount = 0; int remainingBytes = (int)completeDataSize; @@ -101,10 +114,16 @@ public Image Decode(BufferedReadStream stream, WebpFeatures feat switch (chunkType) { case WebpChunkType.FrameData: - Color backgroundColor = this.backgroundColorHandling == BackgroundColorHandling.Ignore - ? Color.FromPixel(new Bgra32(0, 0, 0, 0)) - : features.AnimationBackgroundColor!.Value; - uint dataSize = this.ReadFrame(stream, ref image, ref previousFrame, width, height, backgroundColor); + + uint dataSize = this.ReadFrame( + stream, + ref image, + ref previousFrame, + ref prevFrameData, + width, + height, + backgroundPixel); + remainingBytes -= (int)dataSize; break; case WebpChunkType.Xmp: @@ -132,10 +151,18 @@ public Image Decode(BufferedReadStream stream, WebpFeatures feat /// The stream, where the image should be decoded from. Cannot be null. /// The image to decode the information to. /// The previous frame. + /// The previous frame data. /// The width of the image. /// The height of the image. /// The default background color of the canvas in. - private uint ReadFrame(BufferedReadStream stream, ref Image? image, ref ImageFrame? previousFrame, uint width, uint height, Color backgroundColor) + private uint ReadFrame( + BufferedReadStream stream, + ref Image? image, + ref ImageFrame? previousFrame, + ref WebpFrameData? prevFrameData, + uint width, + uint height, + TPixel backgroundColor) where TPixel : unmanaged, IPixel { WebpFrameData frameData = WebpFrameData.Parse(stream); @@ -174,40 +201,51 @@ private uint ReadFrame(BufferedReadStream stream, ref Image? ima break; } - ImageFrame? currentFrame = null; - ImageFrame imageFrame; + ImageFrame currentFrame; if (previousFrame is null) { - image = new Image(this.configuration, (int)width, (int)height, backgroundColor.ToPixel(), this.metadata); - - SetFrameMetadata(image.Frames.RootFrame.Metadata, frameData); + image = new Image(this.configuration, (int)width, (int)height, backgroundColor, this.metadata); - imageFrame = image.Frames.RootFrame; + currentFrame = image.Frames.RootFrame; + SetFrameMetadata(currentFrame.Metadata, frameData); } else { - currentFrame = image!.Frames.AddFrame(previousFrame); // This clones the frame and adds it the collection. + // If the frame is a key frame we do not need to clone the frame or clear it. + bool isKeyFrame = prevFrameData?.DisposalMethod is FrameDisposalMode.RestoreToBackground + && this.restoreArea == image!.Bounds; - SetFrameMetadata(currentFrame.Metadata, frameData); + if (isKeyFrame) + { + currentFrame = image!.Frames.CreateFrame(backgroundColor); + } + else + { + // This clones the frame and adds it the collection. + currentFrame = image!.Frames.AddFrame(previousFrame); + if (prevFrameData?.DisposalMethod is FrameDisposalMode.RestoreToBackground) + { + this.RestoreToBackground(currentFrame, backgroundColor); + } + } - imageFrame = currentFrame; + SetFrameMetadata(currentFrame.Metadata, frameData); } - Rectangle regionRectangle = frameData.Bounds; + Rectangle interest = frameData.Bounds; + bool blend = previousFrame != null && frameData.BlendingMethod == FrameBlendMode.Over; + using Buffer2D pixelData = this.DecodeImageFrameData(frameData, webpInfo); + DrawDecodedImageFrameOnCanvas(pixelData, currentFrame, interest, blend); + + webpInfo?.Dispose(); + previousFrame = currentFrame; + prevFrameData = frameData; if (frameData.DisposalMethod is FrameDisposalMode.RestoreToBackground) { - this.RestoreToBackground(imageFrame, backgroundColor); + this.restoreArea = interest; } - using Buffer2D decodedImageFrame = this.DecodeImageFrameData(frameData, webpInfo); - - bool blend = previousFrame != null && frameData.BlendingMethod == FrameBlendMode.Over; - DrawDecodedImageFrameOnCanvas(decodedImageFrame, imageFrame, regionRectangle, blend); - - previousFrame = currentFrame ?? image.Frames.RootFrame; - this.restoreArea = regionRectangle; - return (uint)(stream.Position - streamStartPosition); } @@ -257,31 +295,26 @@ private Buffer2D DecodeImageFrameData(WebpFrameData frameData, W try { - Buffer2D pixelBufferDecoded = decodedFrame.PixelBuffer; + Buffer2D decodeBuffer = decodedFrame.PixelBuffer; if (webpInfo.IsLossless) { - WebpLosslessDecoder losslessDecoder = - new(webpInfo.Vp8LBitReader, this.memoryAllocator, this.configuration); - losslessDecoder.Decode(pixelBufferDecoded, (int)webpInfo.Width, (int)webpInfo.Height); + WebpLosslessDecoder losslessDecoder = new(webpInfo.Vp8LBitReader, this.memoryAllocator, this.configuration); + losslessDecoder.Decode(decodeBuffer, (int)webpInfo.Width, (int)webpInfo.Height); } else { WebpLossyDecoder lossyDecoder = new(webpInfo.Vp8BitReader, this.memoryAllocator, this.configuration); - lossyDecoder.Decode(pixelBufferDecoded, (int)webpInfo.Width, (int)webpInfo.Height, webpInfo, this.alphaData); + lossyDecoder.Decode(decodeBuffer, (int)webpInfo.Width, (int)webpInfo.Height, webpInfo, this.alphaData); } - return pixelBufferDecoded; + return decodeBuffer; } catch { decodedFrame?.Dispose(); throw; } - finally - { - webpInfo.Dispose(); - } } /// @@ -335,7 +368,7 @@ private static void DrawDecodedImageFrameOnCanvas( /// The pixel format. /// The image frame. /// Color of the background. - private void RestoreToBackground(ImageFrame imageFrame, Color backgroundColor) + private void RestoreToBackground(ImageFrame imageFrame, TPixel backgroundColor) where TPixel : unmanaged, IPixel { if (!this.restoreArea.HasValue) @@ -345,8 +378,9 @@ private void RestoreToBackground(ImageFrame imageFrame, Color ba Rectangle interest = Rectangle.Intersect(imageFrame.Bounds, this.restoreArea.Value); Buffer2DRegion pixelRegion = imageFrame.PixelBuffer.GetRegion(interest); - TPixel backgroundPixel = backgroundColor.ToPixel(); - pixelRegion.Fill(backgroundPixel); + pixelRegion.Fill(backgroundColor); + + this.restoreArea = null; } /// diff --git a/src/ImageSharp/Formats/Webp/WebpCommonUtils.cs b/src/ImageSharp/Formats/Webp/WebpCommonUtils.cs index a1e9821c09..1ca409f9a4 100644 --- a/src/ImageSharp/Formats/Webp/WebpCommonUtils.cs +++ b/src/ImageSharp/Formats/Webp/WebpCommonUtils.cs @@ -18,7 +18,7 @@ internal static class WebpCommonUtils /// /// The row to check. /// Returns true if alpha has non-0xff values. - public static unsafe bool CheckNonOpaque(Span row) + public static unsafe bool CheckNonOpaque(ReadOnlySpan row) { if (Avx2.IsSupported) { diff --git a/src/ImageSharp/ImageSharp.csproj b/src/ImageSharp/ImageSharp.csproj index 0d36340bf8..36c9375bd6 100644 --- a/src/ImageSharp/ImageSharp.csproj +++ b/src/ImageSharp/ImageSharp.csproj @@ -44,6 +44,11 @@ + + True + True + InlineArray.tt + @@ -51,6 +56,11 @@ + + True + True + InlineArray.tt + True True @@ -154,6 +164,10 @@ + + TextTemplatingFileGenerator + InlineArray.cs + ImageMetadataExtensions.cs TextTemplatingFileGenerator diff --git a/src/ImageSharp/IndexedImageFrame{TPixel}.cs b/src/ImageSharp/IndexedImageFrame{TPixel}.cs index 6807e77ad2..49c9e33eb1 100644 --- a/src/ImageSharp/IndexedImageFrame{TPixel}.cs +++ b/src/ImageSharp/IndexedImageFrame{TPixel}.cs @@ -30,7 +30,7 @@ public sealed class IndexedImageFrame : IPixelSource, IDisposable /// The frame width. /// The frame height. /// The color palette. - internal IndexedImageFrame(Configuration configuration, int width, int height, ReadOnlyMemory palette) + public IndexedImageFrame(Configuration configuration, int width, int height, ReadOnlyMemory palette) { Guard.NotNull(configuration, nameof(configuration)); Guard.MustBeLessThanOrEqualTo(palette.Length, QuantizerConstants.MaxColors, nameof(palette)); @@ -42,7 +42,7 @@ internal IndexedImageFrame(Configuration configuration, int width, int height, R this.Height = height; this.pixelBuffer = configuration.MemoryAllocator.Allocate2D(width, height); - // Copy the palette over. We want the lifetime of this frame to be independant of any palette source. + // Copy the palette over. We want the lifetime of this frame to be independent of any palette source. this.paletteOwner = configuration.MemoryAllocator.Allocate(palette.Length); palette.Span.CopyTo(this.paletteOwner.GetSpan()); this.Palette = this.paletteOwner.Memory[..palette.Length]; diff --git a/src/ImageSharp/Processing/Processors/CloningImageProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/CloningImageProcessor{TPixel}.cs index abe32e3882..bc34f759a0 100644 --- a/src/ImageSharp/Processing/Processors/CloningImageProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/CloningImageProcessor{TPixel}.cs @@ -132,16 +132,14 @@ protected virtual void BeforeFrameApply(ImageFrame source, ImageFrameThe source image. Cannot be null. /// The cloned/destination image. Cannot be null. protected virtual void AfterFrameApply(ImageFrame source, ImageFrame destination) - { - } + => destination.Metadata.AfterFrameApply(source, destination); /// /// This method is called after the process is applied to prepare the processor. /// /// The cloned/destination image. Cannot be null. protected virtual void AfterImageApply(Image destination) - { - } + => destination.Metadata.AfterImageApply(destination); /// /// Disposes the object and frees resources for the Garbage Collector. diff --git a/src/ImageSharp/Processing/Processors/Dithering/IDither.cs b/src/ImageSharp/Processing/Processors/Dithering/IDither.cs index ac2921b98d..3217601270 100644 --- a/src/ImageSharp/Processing/Processors/Dithering/IDither.cs +++ b/src/ImageSharp/Processing/Processors/Dithering/IDither.cs @@ -21,7 +21,7 @@ public interface IDither /// The source image. /// The destination quantized frame. /// The region of interest bounds. - void ApplyQuantizationDither( + public void ApplyQuantizationDither( ref TFrameQuantizer quantizer, ImageFrame source, IndexedImageFrame destination, @@ -38,7 +38,7 @@ void ApplyQuantizationDither( /// The palette dithering processor. /// The source image. /// The region of interest bounds. - void ApplyPaletteDither( + public void ApplyPaletteDither( in TPaletteDitherImageProcessor processor, ImageFrame source, Rectangle bounds) diff --git a/src/ImageSharp/Processing/Processors/Dithering/IPaletteDitherImageProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Dithering/IPaletteDitherImageProcessor{TPixel}.cs index e406d82c69..347e2f0ef6 100644 --- a/src/ImageSharp/Processing/Processors/Dithering/IPaletteDitherImageProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Dithering/IPaletteDitherImageProcessor{TPixel}.cs @@ -15,22 +15,22 @@ public interface IPaletteDitherImageProcessor /// /// Gets the configuration instance to use when performing operations. /// - Configuration Configuration { get; } + public Configuration Configuration { get; } /// /// Gets the dithering palette. /// - ReadOnlyMemory Palette { get; } + public ReadOnlyMemory Palette { get; } /// /// Gets the dithering scale used to adjust the amount of dither. Range 0..1. /// - float DitherScale { get; } + public float DitherScale { get; } /// /// Returns the color from the dithering palette corresponding to the given color. /// /// The color to match. /// The match. - TPixel GetPaletteColor(TPixel color); + public TPixel GetPaletteColor(TPixel color); } diff --git a/src/ImageSharp/Processing/Processors/ImageProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/ImageProcessor{TPixel}.cs index 2fa79220e5..e1f7d1fffb 100644 --- a/src/ImageSharp/Processing/Processors/ImageProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/ImageProcessor{TPixel}.cs @@ -95,7 +95,7 @@ protected virtual void BeforeFrameApply(ImageFrame source) protected abstract void OnFrameApply(ImageFrame source); /// - /// This method is called after the process is applied to prepare the processor. + /// This method is called after the process is applied to each frame. /// /// The source image. Cannot be null. protected virtual void AfterFrameApply(ImageFrame source) @@ -103,11 +103,10 @@ protected virtual void AfterFrameApply(ImageFrame source) } /// - /// This method is called after the process is applied to prepare the processor. + /// This method is called after the process is applied to the complete image. /// protected virtual void AfterImageApply() - { - } + => this.Source.Metadata.AfterImageApply(this.Source); /// /// Disposes the object and frees resources for the Garbage Collector. diff --git a/src/ImageSharp/Processing/Processors/Quantization/EuclideanPixelMap{TPixel}.cs b/src/ImageSharp/Processing/Processors/Quantization/EuclideanPixelMap{TPixel}.cs index 4fd37d479d..d11376e3ba 100644 --- a/src/ImageSharp/Processing/Processors/Quantization/EuclideanPixelMap{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Quantization/EuclideanPixelMap{TPixel}.cs @@ -2,6 +2,7 @@ // 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; @@ -21,13 +22,7 @@ internal sealed class EuclideanPixelMap : IDisposable where TPixel : unmanaged, IPixel { 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. - /// - private ColorDistanceCache cache; + private readonly HybridColorDistanceCache cache; private readonly Configuration configuration; /// @@ -36,26 +31,12 @@ internal sealed class EuclideanPixelMap : IDisposable /// The configuration. /// The color palette to map from. public EuclideanPixelMap(Configuration configuration, ReadOnlyMemory palette) - : this(configuration, palette, -1) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// The configuration. - /// The color palette to map from. - /// An explicit index at which to match transparent pixels. - public EuclideanPixelMap(Configuration configuration, ReadOnlyMemory palette, int transparentIndex = -1) { this.configuration = configuration; this.Palette = palette; this.rgbaPalette = new Rgba32[palette.Length]; - this.cache = new ColorDistanceCache(configuration.MemoryAllocator); + this.cache = new HybridColorDistanceCache(configuration.MemoryAllocator); PixelOperations.Instance.ToRgba32(configuration, this.Palette.Span, this.rgbaPalette); - - this.transparentIndex = transparentIndex; - this.transparentMatch = TPixel.FromRgba32(default); } /// @@ -70,21 +51,27 @@ public EuclideanPixelMap(Configuration configuration, ReadOnlyMemory pal /// /// The color to match. /// The matched color. + /// The transparency threshold. /// The index. - [MethodImpl(InliningOptions.ShortMethod)] - public int GetClosestColor(TPixel color, out TPixel match) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public int GetClosestColor(TPixel color, out TPixel match, short transparencyThreshold = -1) { ref TPixel paletteRef = ref MemoryMarshal.GetReference(this.Palette.Span); Rgba32 rgba = color.ToRgba32(); + if (transparencyThreshold > -1 && rgba.A < transparencyThreshold) + { + rgba = default; + } + // Check if the color is in the lookup table - if (!this.cache.TryGetValue(rgba, out short index)) + if (this.cache.TryGetValue(rgba, out short index)) { - return this.GetClosestColorSlow(rgba, ref paletteRef, out match); + match = Unsafe.Add(ref paletteRef, (ushort)index); + return index; } - match = Unsafe.Add(ref paletteRef, (ushort)index); - return index; + return this.GetClosestColorSlow(rgba, ref paletteRef, out match); } /// @@ -96,46 +83,25 @@ public void Clear(ReadOnlyMemory palette) 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. - /// - /// An explicit index at which to match transparent pixels. - public void SetTransparentIndex(int index) - { - if (index != this.transparentIndex) - { - this.cache.Clear(); - } - - this.transparentIndex = index; - } - - [MethodImpl(InliningOptions.ShortMethod)] + [MethodImpl(MethodImplOptions.NoInlining)] private int GetClosestColorSlow(Rgba32 rgba, ref TPixel paletteRef, out TPixel match) { // Loop through the palette and find the nearest match. int index = 0; - - if (this.transparentIndex >= 0 && rgba == default) - { - // We have explicit instructions. No need to search. - index = this.transparentIndex; - this.cache.Add(rgba, (byte)index); - match = this.transparentMatch; - return index; - } - float leastDistance = float.MaxValue; for (int i = 0; i < this.rgbaPalette.Length; i++) { Rgba32 candidate = this.rgbaPalette[i]; - float distance = DistanceSquared(rgba, candidate); + if (candidate.PackedValue == rgba.PackedValue) + { + index = i; + break; + } - // If it's an exact match, exit the loop + float distance = DistanceSquared(rgba, candidate); if (distance == 0) { index = i; @@ -144,7 +110,6 @@ private int GetClosestColorSlow(Rgba32 rgba, ref TPixel paletteRef, out TPixel m if (distance < leastDistance) { - // Less than... assign. index = i; leastDistance = distance; } @@ -153,6 +118,7 @@ private int GetClosestColorSlow(Rgba32 rgba, ref TPixel paletteRef, out TPixel m // Now I have the index, pop it into the cache for next time this.cache.Add(rgba, (byte)index); match = Unsafe.Add(ref paletteRef, (uint)index); + return index; } @@ -162,96 +128,415 @@ private int GetClosestColorSlow(Rgba32 rgba, ref TPixel paletteRef, out TPixel m /// The first point. /// The second point. /// The distance squared. - [MethodImpl(InliningOptions.ShortMethod)] + [MethodImpl(MethodImplOptions.AggressiveInlining)] private static float DistanceSquared(Rgba32 a, Rgba32 b) { - float deltaR = a.R - b.R; - float deltaG = a.G - b.G; - float deltaB = a.B - b.B; - float deltaA = a.A - b.A; - return (deltaR * deltaR) + (deltaG * deltaG) + (deltaB * deltaB) + (deltaA * deltaA); + Vector4 va = new(a.R, a.G, a.B, a.A); + Vector4 vb = new(b.R, b.G, b.B, b.A); + return Vector4.DistanceSquared(va, vb); } public void Dispose() => this.cache.Dispose(); /// - /// A cache for storing color distance matching results. + /// A hybrid color distance cache that combines a small, fixed-capacity exact-match dictionary + /// (ExactCache, ~4–5 KB for up to 512 entries) with a coarse lookup table (CoarseCache) for 5,5,5,6 precision. /// /// - /// - /// 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 (4MB). - /// + /// ExactCache provides O(1) lookup for common cases using a simple 256-entry hash-based dictionary, while CoarseCache + /// quantizes RGB channels to 5 bits (yielding 32^3 buckets) and alpha to 6 bits, storing up to 4 alpha entries per bucket + /// (a design chosen based on probability theory to capture most real-world variations) for a total memory footprint of + /// roughly 576 KB. Lookups and insertions are performed in constant time, making the overall design both fast and memory-predictable. /// - private unsafe struct ColorDistanceCache : IDisposable +#pragma warning disable CA1001 // Types that own disposable fields should be disposable + // https://github.com/dotnet/roslyn-analyzers/issues/6151 + private readonly unsafe struct HybridColorDistanceCache : IDisposable +#pragma warning restore CA1001 // Types that own disposable fields should be disposable { - private const int IndexRBits = 5; - private const int IndexGBits = 5; - private const int IndexBBits = 5; - 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; - - public ColorDistanceCache(MemoryAllocator allocator) + private readonly CoarseCache coarseCache; + private readonly ExactCache exactCache; + + public HybridColorDistanceCache(MemoryAllocator allocator) + { + this.exactCache = new ExactCache(allocator); + this.coarseCache = new CoarseCache(allocator); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly void Add(Rgba32 color, short index) { - this.table = allocator.Allocate(Entries); - this.table.GetSpan().Fill(-1); - this.tableHandle = this.table.Memory.Pin(); - this.tablePointer = (short*)this.tableHandle.Pointer; + if (this.exactCache.TryAdd(color.PackedValue, index)) + { + return; + } + + this.coarseCache.Add(color, index); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly bool TryGetValue(Rgba32 color, out short match) + { + if (this.exactCache.TryGetValue(color.PackedValue, out match)) + { + return true; // Exact match found + } + + if (this.coarseCache.TryGetValue(color, out match)) + { + return true; // Coarse match found + } + + match = -1; + return false; + } + + public readonly void Clear() + { + this.exactCache.Clear(); + this.coarseCache.Clear(); + } + + public void Dispose() + { + this.exactCache.Dispose(); + this.coarseCache.Dispose(); + } + } + + /// + /// A fixed-capacity dictionary with exactly 512 entries mapping a key + /// to a value. + /// + /// + /// The dictionary is implemented using a fixed array of 512 buckets and an entries array + /// of the same size. The bucket for a key is computed as (key & 0x1FF), and collisions are + /// resolved through a linked chain stored in the field. + /// The overall memory usage is approximately 4–5 KB. Both lookup and insertion operations are, + /// on average, O(1) since the bucket is determined via a simple bitmask and collision chains are + /// typically very short; in the worst-case, the number of iterations is bounded by 256. + /// This guarantees highly efficient and predictable performance for small, fixed-size color palettes. + /// + internal sealed unsafe class ExactCache : IDisposable + { + // Buckets array: each bucket holds the index (0-based) into the entries array + // of the first entry in the chain, or -1 if empty. + private readonly IMemoryOwner bucketsOwner; + private MemoryHandle bucketsHandle; + private short* buckets; + + // Entries array: stores up to 256 entries. + private readonly IMemoryOwner entriesOwner; + private MemoryHandle entriesHandle; + private Entry* entries; + + public const int Capacity = 512; + + public ExactCache(MemoryAllocator allocator) + { + this.Count = 0; + + // Allocate exactly 512 ints for buckets. + this.bucketsOwner = allocator.Allocate(Capacity, AllocationOptions.Clean); + Span bucketSpan = this.bucketsOwner.GetSpan(); + bucketSpan.Fill(-1); + this.bucketsHandle = this.bucketsOwner.Memory.Pin(); + this.buckets = (short*)this.bucketsHandle.Pointer; + + // Allocate exactly 512 entries. + this.entriesOwner = allocator.Allocate(Capacity, AllocationOptions.Clean); + this.entriesHandle = this.entriesOwner.Memory.Pin(); + this.entries = (Entry*)this.entriesHandle.Pointer; } - [MethodImpl(InliningOptions.ShortMethod)] - public readonly void Add(Rgba32 rgba, byte index) + public int Count { get; private set; } + + /// + /// Adds a key/value pair to the dictionary. + /// If the key already exists, the dictionary is left unchanged. + /// + /// The key to add. + /// The value to add. + /// if the key was added; otherwise, . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool TryAdd(uint key, short value) { - int idx = GetPaletteIndex(rgba); - this.tablePointer[idx] = index; + if (this.Count == Capacity) + { + return false; // Dictionary is full. + } + + // The key is a 32-bit unsigned integer representing an RGBA color, where the bytes are laid out as R|G|B|A + // (with R in the most significant byte and A in the least significant). + // To compute the bucket index: + // 1. (key >> 16) extracts the top 16 bits, effectively giving us the R and G channels. + // 2. (key >> 8) shifts the key right by 8 bits, bringing R, G, and B into the lower 24 bits (dropping A). + // 3. XORing these two values with the original key mixes bits from all four channels (R, G, B, and A), + // which helps to counteract situations where one or more channels have a limited range. + // 4. Finally, we apply a bitmask of 0x1FF to keep only the lowest 9 bits, ensuring the result is between 0 and 511, + // which corresponds to our fixed bucket count of 512. + int bucket = (int)(((key >> 16) ^ (key >> 8) ^ key) & 0x1FF); + int i = this.buckets[bucket]; + + // Traverse the collision chain. + Entry* entries = this.entries; + while (i != -1) + { + Entry e = entries[i]; + if (e.Key == key) + { + // Key already exists; do not overwrite. + return false; + } + + i = e.Next; + } + + short index = (short)this.Count; + this.Count++; + + // Insert the new entry: + entries[index].Key = key; + entries[index].Value = value; + + // Link this new entry into the bucket chain. + entries[index].Next = this.buckets[bucket]; + this.buckets[bucket] = index; + return true; } - [MethodImpl(InliningOptions.ShortMethod)] - public readonly bool TryGetValue(Rgba32 rgba, out short match) + /// + /// Tries to retrieve the value associated with the specified key. + /// Returns true if the key is found; otherwise, returns false. + /// + /// The key to search for. + /// The value associated with the key, if found. + /// if the key is found; otherwise, . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool TryGetValue(uint key, out short value) { - int idx = GetPaletteIndex(rgba); - match = this.tablePointer[idx]; - return match > -1; + int bucket = (int)(((key >> 16) ^ (key >> 8) ^ key) & 0x1FF); + int i = this.buckets[bucket]; + + // If the bucket is empty, return immediately. + if (i == -1) + { + value = -1; + return false; + } + + // Traverse the chain. + Entry* entries = this.entries; + do + { + Entry e = entries[i]; + if (e.Key == key) + { + value = e.Value; + return true; + } + + i = e.Next; + } + while (i != -1); + + value = -1; + return false; } /// - /// Clears the cache resetting each entry to empty. + /// Clears the dictionary. /// - [MethodImpl(InliningOptions.ShortMethod)] - public readonly void Clear() => this.table.GetSpan().Fill(-1); + public void Clear() + { + Span bucketSpan = this.bucketsOwner.GetSpan(); + bucketSpan.Fill(-1); + this.Count = 0; + } - [MethodImpl(InliningOptions.ShortMethod)] - private static int GetPaletteIndex(Rgba32 rgba) + public void Dispose() { - 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; + this.bucketsHandle.Dispose(); + this.bucketsOwner.Dispose(); + this.entriesHandle.Dispose(); + this.entriesOwner.Dispose(); + this.buckets = null; + this.entries = null; + } + + private struct Entry + { + public uint Key; // The key (packed RGBA) + public short Value; // The value; -1 means unused. + public short Next; // Index of the next entry in the chain, or -1 if none. + } + } + + /// + /// + /// CoarseCache is a fast, low-memory lookup structure for caching palette indices associated with RGBA values, + /// using a quantized representation of 5,5,5,6 (RGB: 5 bits each, Alpha: 6 bits). + /// + /// + /// The cache quantizes the RGB channels to 5 bits each, resulting in 32 levels per channel and a total of 32³ = 32,768 buckets. + /// Each bucket is represented by an , which holds a small, inline array of alpha entries. + /// Each alpha entry stores the alpha value quantized to 6 bits (0–63) along with a palette index (a 16-bit value). + /// + /// + /// Performance Characteristics: + /// - Lookup: O(1) for computing the bucket index from the RGB channels, plus a small constant time (up to 4 iterations) + /// to search through the alpha entries in the bucket. + /// - Insertion: O(1) for bucket index computation and a quick linear search over a very small (fixed) number of entries. + /// + /// + /// Memory Characteristics: + /// - The cache consists of 32,768 buckets. + /// - Each is implemented using an inline array with a capacity of 4 entries. + /// - Each bucket occupies approximately 18 bytes. + /// - Overall, the buckets occupy roughly 32,768 × 18 = 589,824 bytes (576 KB). + /// + /// + /// This design provides nearly constant-time lookup and insertion with minimal memory usage, + /// making it ideal for applications such as color distance caching in images with a limited palette (up to 256 entries). + /// + /// + internal sealed unsafe class CoarseCache : IDisposable + { + // Use 5 bits per channel for R, G, and B: 32 levels each. + // Total buckets = 32^3 = 32768. + private const int RgbBits = 5; + private const int BucketCount = 1 << (RgbBits * 3); // 32768 + private readonly IMemoryOwner bucketsOwner; + private readonly AlphaBucket* buckets; + private MemoryHandle bucketHandle; + + public CoarseCache(MemoryAllocator allocator) + { + this.bucketsOwner = allocator.Allocate(BucketCount, AllocationOptions.Clean); + this.bucketHandle = this.bucketsOwner.Memory.Pin(); + this.buckets = (AlphaBucket*)this.bucketHandle.Pointer; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static int GetBucketIndex(byte r, byte g, byte b) + { + int qr = r >> (8 - RgbBits); + int qg = g >> (8 - RgbBits); + int qb = b >> (8 - RgbBits); + + // Combine the quantized channels into a single index. + return (qr << (RgbBits * 2)) | (qg << RgbBits) | qb; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static byte QuantizeAlpha(byte a) + + // Quantize to 6 bits: shift right by (8 - 6) = 2 bits. + => (byte)(a >> 2); + + public void Add(Rgba32 color, short paletteIndex) + { + int bucketIndex = GetBucketIndex(color.R, color.G, color.B); + byte quantAlpha = QuantizeAlpha(color.A); + this.buckets[bucketIndex].Add(quantAlpha, paletteIndex); } public void Dispose() { - if (this.table != null) + this.bucketHandle.Dispose(); + this.bucketsOwner.Dispose(); + } + + public bool TryGetValue(Rgba32 color, out short paletteIndex) + { + int bucketIndex = GetBucketIndex(color.R, color.G, color.B); + byte quantAlpha = QuantizeAlpha(color.A); + return this.buckets[bucketIndex].TryGetValue(quantAlpha, out paletteIndex); + } + + public void Clear() + { + Span bucketsSpan = this.bucketsOwner.GetSpan(); + bucketsSpan.Clear(); + } + + public struct AlphaEntry + { + // Store the alpha value quantized to 6 bits (0..63) + public byte QuantizedAlpha; + public short PaletteIndex; + } + + public struct AlphaBucket + { + // Fixed capacity for alpha entries in this bucket. + // We choose a capacity of 4 for several reasons: + // + // 1. The alpha channel is quantized to 6 bits, so there are 64 possible distinct values. + // In the worst-case, a given RGB bucket might encounter up to 64 different alpha values. + // + // 2. However, in practice (based on probability theory and typical image data), + // the number of unique alpha values that actually occur for a given quantized RGB + // bucket is usually very small. If you randomly sample 4 values out of 64, + // the probability that these 4 samples are all unique is high if the distribution + // of alpha values is skewed or if only a few alpha values are used. + // + // 3. Statistically, for many real-world images, most RGB buckets will have only a couple + // of unique alpha values. Allocating 4 slots per bucket provides a good trade-off: + // it captures the common-case scenario while keeping overall memory usage low. + // + // 4. Even if more than 4 unique alpha values occur in a bucket, + // our design overwrites the first entry. This behavior gives us some "wriggle room" + // while preserving the most frequently encountered or most recent values. + public const int Capacity = 4; + public byte Count; + private InlineArray4 entries; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool TryGetValue(byte quantizedAlpha, out short paletteIndex) { - this.tableHandle.Dispose(); - this.table.Dispose(); + for (int i = 0; i < this.Count; i++) + { + ref AlphaEntry entry = ref this.entries[i]; + if (entry.QuantizedAlpha == quantizedAlpha) + { + paletteIndex = entry.PaletteIndex; + return true; + } + } + + paletteIndex = -1; + return false; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Add(byte quantizedAlpha, short paletteIndex) + { + // Check for an existing entry with the same quantized alpha. + for (int i = 0; i < this.Count; i++) + { + ref AlphaEntry entry = ref this.entries[i]; + if (entry.QuantizedAlpha == quantizedAlpha) + { + // Update palette index if found. + entry.PaletteIndex = paletteIndex; + return; + } + } + + // If there's room, add a new entry. + if (this.Count < Capacity) + { + ref AlphaEntry newEntry = ref this.entries[this.Count]; + newEntry.QuantizedAlpha = quantizedAlpha; + newEntry.PaletteIndex = paletteIndex; + this.Count++; + } + else + { + // Bucket is full. Overwrite the first entry to give us some wriggle room. + this.entries[0].QuantizedAlpha = quantizedAlpha; + this.entries[0].PaletteIndex = paletteIndex; + } } } } diff --git a/src/ImageSharp/Processing/Processors/Quantization/IQuantizer{TPixel}.cs b/src/ImageSharp/Processing/Processors/Quantization/IQuantizer{TPixel}.cs index 35bbb1289e..dc5bdbd627 100644 --- a/src/ImageSharp/Processing/Processors/Quantization/IQuantizer{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Quantization/IQuantizer{TPixel}.cs @@ -16,12 +16,12 @@ public interface IQuantizer : IDisposable /// /// Gets the configuration. /// - Configuration Configuration { get; } + public Configuration Configuration { get; } /// /// Gets the quantizer options defining quantization rules. /// - QuantizerOptions Options { get; } + public QuantizerOptions Options { get; } /// /// Gets the quantized color palette. @@ -29,13 +29,13 @@ public interface IQuantizer : IDisposable /// /// The palette has not been built via . /// - ReadOnlyMemory Palette { get; } + public ReadOnlyMemory Palette { get; } /// /// Adds colors to the quantized palette from the given pixel source. /// /// The of source pixels to register. - void AddPaletteColors(Buffer2DRegion pixelRegion); + public void AddPaletteColors(in Buffer2DRegion pixelRegion); /// /// Quantizes an image frame and return the resulting output pixels. @@ -49,7 +49,7 @@ public interface IQuantizer : IDisposable /// Only executes the second (quantization) step. The palette has to be built by calling . /// To run both steps, use . /// - IndexedImageFrame QuantizeFrame(ImageFrame source, Rectangle bounds); + public IndexedImageFrame QuantizeFrame(ImageFrame source, Rectangle bounds); /// /// Returns the index and color from the quantized palette corresponding to the given color. @@ -57,7 +57,7 @@ public interface IQuantizer : IDisposable /// The color to match. /// The matched color. /// The index. - byte GetQuantizedColor(TPixel color, out TPixel match); + public byte GetQuantizedColor(TPixel color, out TPixel match); // TODO: Enable bulk operations. // void GetQuantizedColors(ReadOnlySpan colors, ReadOnlySpan palette, Span indices, Span matches); diff --git a/src/ImageSharp/Processing/Processors/Quantization/OctreeQuantizer{TPixel}.cs b/src/ImageSharp/Processing/Processors/Quantization/OctreeQuantizer{TPixel}.cs index 8b39b74579..be00bc433a 100644 --- a/src/ImageSharp/Processing/Processors/Quantization/OctreeQuantizer{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Quantization/OctreeQuantizer{TPixel}.cs @@ -30,6 +30,7 @@ public struct OctreeQuantizer : IQuantizer private ReadOnlyMemory palette; private EuclideanPixelMap? pixelMap; private readonly bool isDithering; + private readonly short transparencyThreshold; private bool isDisposed; /// @@ -44,8 +45,9 @@ public OctreeQuantizer(Configuration configuration, QuantizerOptions options) this.Options = options; this.maxColors = this.Options.MaxColors; + this.transparencyThreshold = (short)(this.Options.TransparencyThreshold * 255); this.bitDepth = Numerics.Clamp(ColorNumerics.GetBitsNeededForColorDepth(this.maxColors), 1, 8); - this.octree = new Octree(this.bitDepth); + this.octree = new Octree(this.bitDepth, this.maxColors, this.transparencyThreshold, configuration.MemoryAllocator); this.paletteOwner = configuration.MemoryAllocator.Allocate(this.maxColors, AllocationOptions.Clean); this.pixelMap = default; this.palette = default; @@ -60,65 +62,54 @@ public OctreeQuantizer(Configuration configuration, QuantizerOptions options) public QuantizerOptions Options { get; } /// - public readonly ReadOnlyMemory Palette + public ReadOnlyMemory Palette { get { - QuantizerUtilities.CheckPaletteState(in this.palette); + if (this.palette.IsEmpty) + { + this.ResolvePalette(); + QuantizerUtilities.CheckPaletteState(in this.palette); + } + return this.palette; } } /// - public void AddPaletteColors(Buffer2DRegion pixelRegion) + public readonly void AddPaletteColors(in Buffer2DRegion pixelRegion) { - using (IMemoryOwner buffer = this.Configuration.MemoryAllocator.Allocate(pixelRegion.Width)) + using IMemoryOwner buffer = this.Configuration.MemoryAllocator.Allocate(pixelRegion.Width); + Span bufferSpan = buffer.GetSpan(); + + // Loop through each row + for (int y = 0; y < pixelRegion.Height; y++) { - Span bufferSpan = buffer.GetSpan(); + Span row = pixelRegion.DangerousGetRowSpan(y); + PixelOperations.Instance.ToRgba32(this.Configuration, row, bufferSpan); - // Loop through each row - for (int y = 0; y < pixelRegion.Height; y++) + Octree octree = this.octree; + int transparencyThreshold = this.transparencyThreshold; + for (int x = 0; x < bufferSpan.Length; x++) { - Span row = pixelRegion.DangerousGetRowSpan(y); - PixelOperations.Instance.ToRgba32(this.Configuration, row, bufferSpan); - - for (int x = 0; x < bufferSpan.Length; x++) - { - Rgba32 rgba = bufferSpan[x]; - - // Add the color to the Octree - this.octree.AddColor(rgba); - } + // Add the color to the Octree + octree.AddColor(bufferSpan[x]); } } + } - int paletteIndex = 0; + private void ResolvePalette() + { + short paletteIndex = 0; Span paletteSpan = this.paletteOwner.GetSpan(); - // On very rare occasions, (blur.png), the quantizer does not preserve a - // transparent entry when palletizing the captured colors. - // To workaround this we ensure the palette ends with the default color - // for higher bit depths. Lower bit depths will correctly reduce the palette. - // TODO: Investigate more evenly reduced palette reduction. - int max = this.maxColors; - if (this.bitDepth >= 4) - { - max--; - } - - this.octree.Palletize(paletteSpan, max, ref paletteIndex); + this.octree.Palettize(paletteSpan, ref paletteIndex); ReadOnlyMemory result = this.paletteOwner.Memory[..paletteSpan.Length]; - // When called multiple times by QuantizerUtilities.BuildPalette - // this prevents memory churn caused by reallocation. - if (this.pixelMap is null) + if (this.isDithering) { this.pixelMap = new EuclideanPixelMap(this.Configuration, result); } - else - { - this.pixelMap.Clear(result); - } this.palette = result; } @@ -132,18 +123,19 @@ public readonly IndexedImageFrame QuantizeFrame(ImageFrame sourc [MethodImpl(InliningOptions.ShortMethod)] public readonly byte GetQuantizedColor(TPixel color, out TPixel match) { - // Octree only maps the RGB component of a color - // so cannot tell the difference between a fully transparent - // pixel and a black one. - if (this.isDithering || color.Equals(default)) + // Due to the addition of new colors by dithering that are not part of the original histogram, + // the octree nodes might not match the correct color. + // In this case, we must use the pixel map to get the closest color. + if (this.isDithering) { - return (byte)this.pixelMap!.GetClosestColor(color, out match); + return (byte)this.pixelMap!.GetClosestColor(color, out match, this.transparencyThreshold); } ref TPixel paletteRef = ref MemoryMarshal.GetReference(this.palette.Span); - byte index = (byte)this.octree.GetPaletteIndex(color); - match = Unsafe.Add(ref paletteRef, index); - return index; + + int index = this.octree.GetPaletteIndex(color); + match = Unsafe.Add(ref paletteRef, (nuint)index); + return (byte)index; } /// @@ -155,16 +147,521 @@ public void Dispose() this.paletteOwner.Dispose(); this.pixelMap?.Dispose(); this.pixelMap = null; + this.octree.Dispose(); + } + } + + /// + /// A hexadecatree-based color quantization structure used for fast color distance lookups and palette generation. + /// This tree maintains a fixed pool of nodes (capacity 4096) where each node can have up to 16 children, stores + /// color accumulation data, and supports dynamic node allocation and reduction. It offers near-constant-time insertions + /// and lookups while consuming roughly 240 KB for the node pool. + /// + internal sealed class Octree : IDisposable + { + // Pooled buffer for OctreeNodes. + private readonly IMemoryOwner nodesOwner; + + // Reducible nodes: one per level; we use an integer index; -1 means “no node.” + private readonly short[] reducibleNodes; + + // Maximum number of allowable colors. + private readonly int maxColors; + + // Maximum significant bits. + private readonly int maxColorBits; + + // The threshold for transparent colors. + private readonly short transparencyThreshold; + + // Instead of a reference to the root, we store the index of the root node. + // Index 0 is reserved for the root. + private readonly short rootIndex; + + // Running index for node allocation. Start at 1 so that index 0 is reserved for the root. + private short nextNode = 1; + + // Previously quantized node (index; -1 if none) and its color. + private int previousNode; + private Rgba32 previousColor; + + // Free list for reclaimed node indices. + private readonly Stack freeIndices = new(); + + /// + /// Initializes a new instance of the class. + /// + /// The maximum number of significant bits in the image. + /// The maximum number of colors to allow in the palette. + /// The threshold for transparent colors. + /// The memory allocator. + public Octree(int maxColorBits, int maxColors, short transparencyThreshold, MemoryAllocator allocator) + { + this.maxColorBits = maxColorBits; + this.maxColors = maxColors; + this.transparencyThreshold = transparencyThreshold; + this.Leaves = 0; + this.previousNode = -1; + this.previousColor = default; + + // Allocate a conservative buffer for nodes. + const int capacity = 4096; + this.nodesOwner = allocator.Allocate(capacity, AllocationOptions.Clean); + + // Create the reducible nodes array (one per level 0 .. maxColorBits-1). + this.reducibleNodes = new short[this.maxColorBits]; + this.reducibleNodes.AsSpan().Fill(-1); + + // Reserve index 0 for the root. + this.rootIndex = 0; + ref OctreeNode root = ref this.Nodes[this.rootIndex]; + root.Initialize(0, this.maxColorBits, this, this.rootIndex); + } + + /// + /// Gets or sets the number of leaves in the tree. + /// + public int Leaves { get; set; } + + /// + /// Gets the full collection of nodes as a span. + /// + internal Span Nodes => this.nodesOwner.Memory.Span; + + /// + /// Add a color to the Octree. + /// + /// The color to add. + public void AddColor(Rgba32 color) + { + // Ensure that the tree is not already full. + if (this.nextNode >= this.Nodes.Length && this.freeIndices.Count == 0) + { + while (this.Leaves > this.maxColors) + { + this.Reduce(); + } + } + + if (color.A < this.transparencyThreshold) + { + color = default; + } + + // If the color is the same as the previous color, increment the node. + // Otherwise, add a new node. + if (this.previousColor.Equals(color)) + { + if (this.previousNode == -1) + { + this.previousColor = color; + OctreeNode.AddColor(this.rootIndex, color, this.maxColorBits, 0, this); + } + else + { + OctreeNode.Increment(this.previousNode, color, this); + } + } + else + { + this.previousColor = color; + OctreeNode.AddColor(this.rootIndex, color, this.maxColorBits, 0, this); + } + } + + /// + /// Construct the palette from the octree. + /// + /// The palette to construct. + /// The current palette index. + public void Palettize(Span palette, ref short paletteIndex) + { + while (this.Leaves > this.maxColors) + { + this.Reduce(); + } + + this.Nodes[this.rootIndex].ConstructPalette(this, palette, ref paletteIndex); + } + + /// + /// Get the palette index for the passed color. + /// + /// The color to get the palette index for. + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public int GetPaletteIndex(TPixel color) + => this.Nodes[this.rootIndex].GetPaletteIndex(color.ToRgba32(), 0, this); + + /// + /// Track the previous node and color. + /// + /// The node index. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void TrackPrevious(int nodeIndex) + => this.previousNode = nodeIndex; + + /// + /// Reduce the depth of the tree. + /// + private void Reduce() + { + // Find the deepest level containing at least one reducible node + int index = this.maxColorBits - 1; + while ((index > 0) && (this.reducibleNodes[index] == -1)) + { + index--; + } + + // Reduce the node most recently added to the list at level 'index' + ref OctreeNode node = ref this.Nodes[this.reducibleNodes[index]]; + this.reducibleNodes[index] = node.NextReducibleIndex; + + // Decrement the leaf count after reducing the node + node.Reduce(this); + + // And just in case I've reduced the last color to be added, and the next color to + // be added is the same, invalidate the previousNode... + this.previousNode = -1; + } + + // Allocate a new OctreeNode from the pooled buffer. + // First check the freeIndices stack. + internal short AllocateNode() + { + if (this.freeIndices.Count > 0) + { + return this.freeIndices.Pop(); + } + + if (this.nextNode >= this.Nodes.Length) + { + return -1; + } + + short newIndex = this.nextNode; + this.nextNode++; + return newIndex; + } + + /// + /// Free a node index, making it available for re-allocation. + /// + /// The index to free. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal void FreeNode(short index) + { + this.freeIndices.Push(index); + this.Leaves--; + } + + /// + public void Dispose() => this.nodesOwner.Dispose(); + + [StructLayout(LayoutKind.Sequential)] + internal unsafe struct OctreeNode + { + public bool Leaf; + public int PixelCount; + public int Red; + public int Green; + public int Blue; + public int Alpha; + public short PaletteIndex; + public short NextReducibleIndex; + private InlineArray16 children; + + [UnscopedRef] + public Span Children => this.children; + + /// + /// Initialize the . + /// + /// The level of the node. + /// The number of significant color bits in the image. + /// The parent octree. + /// The index of the node. + public void Initialize(int level, int colorBits, Octree octree, short index) + { + // Construct the new node. + this.Leaf = level == colorBits; + this.Red = 0; + this.Green = 0; + this.Blue = 0; + this.Alpha = 0; + this.PixelCount = 0; + this.PaletteIndex = 0; + this.NextReducibleIndex = -1; + + // Always clear the Children array. + this.Children.Fill(-1); + + if (this.Leaf) + { + octree.Leaves++; + } + else + { + // Add this node to the reducible nodes list for its level. + this.NextReducibleIndex = octree.reducibleNodes[level]; + octree.reducibleNodes[level] = index; + } + } + + /// + /// Add a color to the Octree. + /// + /// The node index. + /// The color to add. + /// The number of significant color bits in the image. + /// The level of the node. + /// The parent octree. + public static void AddColor(int nodeIndex, Rgba32 color, int colorBits, int level, Octree octree) + { + ref OctreeNode node = ref octree.Nodes[nodeIndex]; + if (node.Leaf) + { + Increment(nodeIndex, color, octree); + octree.TrackPrevious(nodeIndex); + } + else + { + int index = GetColorIndex(color, level); + short childIndex; + + Span children = node.Children; + childIndex = children[index]; + + if (childIndex == -1) + { + childIndex = octree.AllocateNode(); + + if (childIndex == -1) + { + // No room in the tree, so increment the count and return. + Increment(nodeIndex, color, octree); + octree.TrackPrevious(nodeIndex); + return; + } + + ref OctreeNode child = ref octree.Nodes[childIndex]; + child.Initialize(level + 1, colorBits, octree, childIndex); + children[index] = childIndex; + } + + AddColor(childIndex, color, colorBits, level + 1, octree); + } + } + + /// + /// Increment the color components of this node. + /// + /// The node index. + /// The color to increment by. + /// The parent octree. + public static void Increment(int nodeIndex, Rgba32 color, Octree octree) + { + ref OctreeNode node = ref octree.Nodes[nodeIndex]; + node.PixelCount++; + node.Red += color.R; + node.Green += color.G; + node.Blue += color.B; + node.Alpha += color.A; + } + + /// + /// Reduce this node by ensuring its children are all reduced (i.e. leaves) and then merging their data. + /// + /// The parent octree. + public void Reduce(Octree octree) + { + // If already a leaf, do nothing. + if (this.Leaf) + { + return; + } + + // Now merge the (presumably reduced) children. + int pixelCount = 0; + int sumRed = 0, sumGreen = 0, sumBlue = 0, sumAlpha = 0; + Span children = this.Children; + for (int i = 0; i < children.Length; i++) + { + short childIndex = children[i]; + if (childIndex != -1) + { + ref OctreeNode child = ref octree.Nodes[childIndex]; + int pixels = child.PixelCount; + + sumRed += child.Red; + sumGreen += child.Green; + sumBlue += child.Blue; + sumAlpha += child.Alpha; + pixelCount += pixels; + + // Free the child immediately. + children[i] = -1; + octree.FreeNode(childIndex); + } + } + + if (pixelCount > 0) + { + this.Red = sumRed; + this.Green = sumGreen; + this.Blue = sumBlue; + this.Alpha = sumAlpha; + this.PixelCount = pixelCount; + } + else + { + this.Red = this.Green = this.Blue = this.Alpha = 0; + this.PixelCount = 0; + } + + this.Leaf = true; + octree.Leaves++; + } + + /// + /// Traverse the tree to construct the palette. + /// + /// The parent octree. + /// The palette to construct. + /// The current palette index. + public void ConstructPalette(Octree octree, Span palette, ref short paletteIndex) + { + if (this.Leaf) + { + Vector4 sum = new(this.Red, this.Green, this.Blue, this.Alpha); + Vector4 offset = new(this.PixelCount >> 1); + Vector4 vector = Vector4.Clamp( + (sum + offset) / this.PixelCount, + Vector4.Zero, + new Vector4(255)); + + if (vector.W < octree.transparencyThreshold) + { + vector = default; + } + + palette[paletteIndex] = TPixel.FromRgba32(new Rgba32((byte)vector.X, (byte)vector.Y, (byte)vector.Z, (byte)vector.W)); + + this.PaletteIndex = paletteIndex++; + } + else + { + Span children = this.Children; + for (int i = 0; i < children.Length; i++) + { + int childIndex = children[i]; + if (childIndex != -1) + { + octree.Nodes[childIndex].ConstructPalette(octree, palette, ref paletteIndex); + } + } + } + } + + /// + /// Get the palette index for the passed color. + /// + /// The color to get the palette index for. + /// The level of the node. + /// The parent octree. + public int GetPaletteIndex(Rgba32 color, int level, Octree octree) + { + if (color.A < octree.transparencyThreshold) + { + color = default; + } + + if (this.Leaf) + { + return this.PaletteIndex; + } + + int colorIndex = GetColorIndex(color, level); + Span children = this.Children; + int childIndex = children[colorIndex]; + if (childIndex != -1) + { + return octree.Nodes[childIndex].GetPaletteIndex(color, level + 1, octree); + } + + for (int i = 0; i < children.Length; i++) + { + childIndex = children[i]; + if (childIndex != -1) + { + int childPaletteIndex = octree.Nodes[childIndex].GetPaletteIndex(color, level + 1, octree); + if (childPaletteIndex != -1) + { + return childPaletteIndex; + } + } + } + + return -1; + } + + /// + /// Gets the color index at the given level. + /// + /// The color to get the index for. + /// The level to get the index at. + public static int GetColorIndex(Rgba32 color, int level) + { + // Determine how many bits to shift based on the current tree level. + // At level 0, shift = 7; as level increases, the shift decreases. + int shift = 7 - level; + byte mask = (byte)(1 << shift); + + // Compute the luminance of the RGB components using the BT.709 standard. + // This gives a measure of brightness for the color. + int luminance = ColorNumerics.Get8BitBT709Luminance(color.R, color.G, color.B); + + // Define thresholds for determining when to include the alpha bit in the index. + // The thresholds are scaled according to the current level. + // 128 is the midpoint of the 8-bit range (0–255), so shifting it right by 'level' + // produces a threshold that scales with the color cube subdivision. + int darkThreshold = 128 >> level; + + // The light threshold is set symmetrically: 255 minus the scaled midpoint. + int lightThreshold = 255 - (128 >> level); + + // If the pixel is fully opaque and its brightness falls between the dark and light thresholds, + // ignore the alpha channel to maximize RGB resolution. + // Otherwise (if the pixel is dark, light, or semi-transparent), include the alpha bit + // to preserve any gradient that may be present. + if (color.A == 255 && luminance > darkThreshold && luminance < lightThreshold) + { + // Extract one bit each from R, G, and B channels and combine them into a 3-bit index. + int rBits = ((color.R & mask) >> shift) << 2; + int gBits = ((color.G & mask) >> shift) << 1; + int bBits = (color.B & mask) >> shift; + return rBits | gBits | bBits; + } + else + { + // Extract one bit from each channel including alpha (alpha becomes the most significant bit). + int aBits = ((color.A & mask) >> shift) << 3; + int rBits = ((color.R & mask) >> shift) << 2; + int gBits = ((color.G & mask) >> shift) << 1; + int bBits = (color.B & mask) >> shift; + return aBits | rBits | gBits | bBits; + } + } } } /// /// Class which does the actual quantization. /// - private sealed class Octree + private sealed class Octree2 { /// - /// The root of the Octree + /// The root of the Octree2 /// private readonly OctreeNode root; @@ -173,6 +670,11 @@ private sealed class Octree /// private readonly int maxColorBits; + /// + /// The threshold for transparent colors. + /// + private readonly int transparencyThreshold; + /// /// Store the last node quantized /// @@ -184,14 +686,16 @@ private sealed class Octree private Rgba32 previousColor; /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// /// The maximum number of significant bits in the image /// - public Octree(int maxColorBits) + /// The threshold for transparent colors. + public Octree2(int maxColorBits, int transparencyThreshold) { this.maxColorBits = maxColorBits; + this.transparencyThreshold = transparencyThreshold; this.Leaves = 0; this.ReducibleNodes = new OctreeNode[9]; this.root = new OctreeNode(0, this.maxColorBits, this); @@ -221,43 +725,46 @@ private OctreeNode?[] ReducibleNodes } /// - /// Add a given color value to the Octree + /// Add a given color value to the Octree2 /// /// The color to add. public void AddColor(Rgba32 color) { + if (color.A < this.transparencyThreshold) + { + color.A = 0; + } + // Check if this request is for the same color as the last if (this.previousColor.Equals(color)) { // If so, check if I have a previous node setup. - // This will only occur if the first color in the image - // happens to be black, with an alpha component of zero. if (this.previousNode is null) { this.previousColor = color; - this.root.AddColor(ref color, this.maxColorBits, 0, this); + this.root.AddColor(color, this.maxColorBits, 0, this); } else { // Just update the previous node - this.previousNode.Increment(ref color); + this.previousNode.Increment(color, this); } } else { this.previousColor = color; - this.root.AddColor(ref color, this.maxColorBits, 0, this); + this.root.AddColor(color, this.maxColorBits, 0, this); } } /// - /// Convert the nodes in the Octree to a palette with a maximum of colorCount colors + /// Convert the nodes in the Octree2 to a palette with a maximum of colorCount colors /// /// The palette to fill. /// The maximum number of colors /// The palette index, used to calculate the final size of the palette. [MethodImpl(InliningOptions.ShortMethod)] - public void Palletize(Span palette, int colorCount, ref int paletteIndex) + public void Palettize(Span palette, int colorCount, ref int paletteIndex) { while (this.Leaves > colorCount) { @@ -276,10 +783,7 @@ public void Palletize(Span palette, int colorCount, ref int paletteIndex /// [MethodImpl(InliningOptions.ShortMethod)] public int GetPaletteIndex(TPixel color) - { - Rgba32 rgba = color.ToRgba32(); - return this.root.GetPaletteIndex(ref rgba, 0); - } + => this.root.GetPaletteIndex(color.ToRgba32(), 0); /// /// Keep track of the previous node that was quantized @@ -307,7 +811,7 @@ private void Reduce() this.ReducibleNodes[index] = node.NextReducible; // Decrement the leaf count after reducing the node - this.Leaves -= node.Reduce(); + this.Leaves -= node.Reduce(this); // And just in case I've reduced the last color to be added, and the next color to // be added is the same, invalidate the previousNode... @@ -349,6 +853,11 @@ public sealed class OctreeNode /// private int blue; + /// + /// Alpha component + /// + private int alpha; + /// /// The index of this node in the palette /// @@ -360,12 +869,12 @@ public sealed class OctreeNode /// The level in the tree = 0 - 7. /// The number of significant color bits in the image. /// The tree to which this node belongs. - public OctreeNode(int level, int colorBits, Octree octree) + public OctreeNode(int level, int colorBits, Octree2 octree) { // Construct the new node this.leaf = level == colorBits; - this.red = this.green = this.blue = 0; + this.red = this.green = this.blue = this.alpha = 0; this.pixelCount = 0; // If a leaf, increment the leaf count @@ -380,7 +889,7 @@ public OctreeNode(int level, int colorBits, Octree octree) // Otherwise add this to the reducible nodes this.NextReducible = octree.ReducibleNodes[level]; octree.ReducibleNodes[level] = this; - this.children = new OctreeNode[8]; + this.children = new OctreeNode[16]; } } @@ -400,12 +909,12 @@ public OctreeNode? NextReducible /// The number of significant color bits. /// The level in the tree. /// The tree to which this node belongs. - public void AddColor(ref Rgba32 color, int colorBits, int level, Octree octree) + public void AddColor(Rgba32 color, int colorBits, int level, Octree2 octree) { // Update the color information if this is a leaf if (this.leaf) { - this.Increment(ref color); + this.Increment(color, octree); // Setup the previous node octree.TrackPrevious(this); @@ -413,7 +922,7 @@ public void AddColor(ref Rgba32 color, int colorBits, int level, Octree octree) else { // Go to the next level down in the tree - int index = GetColorIndex(ref color, level); + int index = GetColorIndex(color, level); OctreeNode? child = this.children![index]; if (child is null) @@ -424,38 +933,63 @@ public void AddColor(ref Rgba32 color, int colorBits, int level, Octree octree) } // Add the color to the child node - child.AddColor(ref color, colorBits, level + 1, octree); + child.AddColor(color, colorBits, level + 1, octree); } } /// - /// Reduce this node by removing all of its children + /// Reduce this node by removing all of its children. /// /// The number of leaves removed - public int Reduce() + /// The tree to which this node belongs. + public int Reduce(Octree2 octree) { - this.red = this.green = this.blue = 0; + if (this.leaf) + { + return 1; + } + int childNodes = 0; + int sumRed = 0, sumGreen = 0, sumBlue = 0, sumAlpha = 0, pixelCount = 0; - // Loop through all children and add their information to this node - for (int index = 0; index < 8; index++) + // Loop through all children. + for (int index = 0; index < this.children!.Length; index++) { - OctreeNode? child = this.children![index]; + OctreeNode? child = this.children[index]; if (child != null) { - this.red += child.red; - this.green += child.green; - this.blue += child.blue; - this.pixelCount += child.pixelCount; - ++childNodes; + childNodes++; + + sumRed += child.red; + sumGreen += child.green; + sumBlue += child.blue; + sumAlpha += child.alpha; + pixelCount += child.pixelCount; + + // Remove the child reference. this.children[index] = null; } } - // Now change this to a leaf node + if (pixelCount > 0) + { + int offset = pixelCount >> 1; + this.red = sumRed; + this.green = sumGreen; + this.blue = sumBlue; + this.alpha = ((sumAlpha + offset) / pixelCount < octree.transparencyThreshold) ? 0 : sumAlpha; + this.pixelCount = pixelCount; + } + else + { + this.red = this.green = this.blue = this.alpha = 0; + this.pixelCount = 0; + } + + // Convert this node into a leaf. this.leaf = true; - // Return the number of nodes to decrement the leaf count by + // Return the number of nodes merged (for decrementing the leaf count). return childNodes - 1; } @@ -470,12 +1004,15 @@ public void ConstructPalette(Span palette, ref int index) if (this.leaf) { // Set the color of the palette entry - Vector3 vector = Vector3.Clamp( - new Vector3(this.red, this.green, this.blue) / this.pixelCount, - Vector3.Zero, - new Vector3(255)); + Vector4 sum = new(this.red, this.green, this.blue, this.alpha); + Vector4 offset = new(this.pixelCount >> 1); + + Vector4 vector = Vector4.Clamp( + (sum + offset) / this.pixelCount, + Vector4.Zero, + new Vector4(255)); - palette[index] = TPixel.FromRgba32(new Rgba32((byte)vector.X, (byte)vector.Y, (byte)vector.Z)); + palette[index] = TPixel.FromRgba32(new Rgba32((byte)vector.X, (byte)vector.Y, (byte)vector.Z, (byte)vector.W)); // Consume the next palette index this.paletteIndex = index++; @@ -483,9 +1020,9 @@ public void ConstructPalette(Span palette, ref int index) else { // Loop through children looking for leaves - for (int i = 0; i < 8; i++) + for (int i = 0; i < this.children!.Length; i++) { - this.children![i]?.ConstructPalette(palette, ref index); + this.children[i]?.ConstructPalette(palette, ref index); } } } @@ -499,20 +1036,20 @@ public void ConstructPalette(Span palette, ref int index) /// The representing the index of the pixel in the palette. /// [MethodImpl(InliningOptions.ColdPath)] - public int GetPaletteIndex(ref Rgba32 pixel, int level) + public int GetPaletteIndex(Rgba32 pixel, int level) { if (this.leaf) { return this.paletteIndex; } - int colorIndex = GetColorIndex(ref pixel, level); + int colorIndex = GetColorIndex(pixel, level); OctreeNode? child = this.children![colorIndex]; - int index = 0; + int index = -1; if (child != null) { - index = child.GetPaletteIndex(ref pixel, level + 1); + index = child.GetPaletteIndex(pixel, level + 1); } else { @@ -522,8 +1059,8 @@ public int GetPaletteIndex(ref Rgba32 pixel, int level) child = this.children[i]; if (child != null) { - int childIndex = child.GetPaletteIndex(ref pixel, level + 1); - if (childIndex != 0) + int childIndex = child.GetPaletteIndex(pixel, level + 1); + if (childIndex != -1) { return childIndex; } @@ -541,27 +1078,54 @@ public int GetPaletteIndex(ref Rgba32 pixel, int level) /// The node level. /// The index. [MethodImpl(InliningOptions.ShortMethod)] - private static int GetColorIndex(ref Rgba32 color, int level) + private static int GetColorIndex(Rgba32 color, int level) { int shift = 7 - level; byte mask = (byte)(1 << shift); - return ((color.R & mask) >> shift) - | (((color.G & mask) >> shift) << 1) - | (((color.B & mask) >> shift) << 2); + // Compute luminance of the RGB channels. + int luminance = ColorNumerics.Get8BitBT709Luminance(color.R, color.G, color.B); + + // Shift the threshold (arbitrary) right by the current level. + // This allows us to partition the RGB space into smaller and smaller cubes + // with increasing accuracy for the alpha component. + int darkThreshold = 24 >> level; + + // For fully opaque and bright pixels, ignore the alpha bit to achieve finer RGB partitioning. + // For dark pixels, include the alpha bit so that dark drop shadows remain distinct. + if (color.A == 255 && luminance > darkThreshold) + { + int rBits = ((color.R & mask) >> shift) << 2; + int gBits = ((color.G & mask) >> shift) << 1; + int bBits = (color.B & mask) >> shift; + return rBits | gBits | bBits; + } + else + { + int aBits = ((color.A & mask) >> shift) << 3; + int rBits = ((color.R & mask) >> shift) << 2; + int gBits = ((color.G & mask) >> shift) << 1; + int bBits = (color.B & mask) >> shift; + return aBits | rBits | gBits | bBits; + } } /// /// Increment the color count and add to the color information /// /// The pixel to add. + /// The parent octree. [MethodImpl(InliningOptions.ShortMethod)] - public void Increment(ref Rgba32 color) + public void Increment(Rgba32 color, Octree2 octree) { this.pixelCount++; this.red += color.R; this.green += color.G; this.blue += color.B; + + int sumAlpha = this.alpha + color.A; + int offset = this.pixelCount >> 1; + this.alpha = ((sumAlpha + offset) / this.pixelCount < octree.transparencyThreshold) ? 0 : sumAlpha; } } } diff --git a/src/ImageSharp/Processing/Processors/Quantization/PaletteQuantizer.cs b/src/ImageSharp/Processing/Processors/Quantization/PaletteQuantizer.cs index 13a59a26de..a49691515a 100644 --- a/src/ImageSharp/Processing/Processors/Quantization/PaletteQuantizer.cs +++ b/src/ImageSharp/Processing/Processors/Quantization/PaletteQuantizer.cs @@ -11,7 +11,8 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization; public class PaletteQuantizer : IQuantizer { private readonly ReadOnlyMemory colorPalette; - private readonly int transparentIndex; + private readonly int transparencyIndex; + private readonly Color transparentColor; /// /// Initializes a new instance of the class. @@ -25,27 +26,33 @@ public PaletteQuantizer(ReadOnlyMemory palette) /// /// Initializes a new instance of the class. /// - /// The color palette. + /// The color palette to use. /// The quantizer options defining quantization rules. public PaletteQuantizer(ReadOnlyMemory palette, QuantizerOptions options) - : this(palette, options, -1) + : this(palette, options, -1, default) { } /// /// Initializes a new instance of the class. /// - /// The color palette. + /// The color palette to use. /// The quantizer options defining quantization rules. - /// An explicit index at which to match transparent pixels. - internal PaletteQuantizer(ReadOnlyMemory palette, QuantizerOptions options, int transparentIndex) + /// The index of the color in the palette that should be considered as transparent. + /// The color that should be considered as transparent. + internal PaletteQuantizer( + ReadOnlyMemory palette, + QuantizerOptions options, + int transparencyIndex, + Color transparentColor) { Guard.MustBeGreaterThan(palette.Length, 0, nameof(palette)); Guard.NotNull(options, nameof(options)); this.colorPalette = palette; this.Options = options; - this.transparentIndex = transparentIndex; + this.transparencyIndex = transparencyIndex; + this.transparentColor = transparentColor; } /// @@ -66,6 +73,6 @@ public IQuantizer CreatePixelSpecificQuantizer(Configuration con // treat the buffer as FILO. TPixel[] palette = new TPixel[Math.Min(options.MaxColors, this.colorPalette.Length)]; Color.ToPixel(this.colorPalette.Span[..palette.Length], palette.AsSpan()); - return new PaletteQuantizer(configuration, options, palette, this.transparentIndex); + return new PaletteQuantizer(configuration, options, palette, this.transparencyIndex, this.transparentColor.ToPixel()); } } diff --git a/src/ImageSharp/Processing/Processors/Quantization/PaletteQuantizer{TPixel}.cs b/src/ImageSharp/Processing/Processors/Quantization/PaletteQuantizer{TPixel}.cs index 092975d28a..d734b36c30 100644 --- a/src/ImageSharp/Processing/Processors/Quantization/PaletteQuantizer{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Quantization/PaletteQuantizer{TPixel}.cs @@ -17,10 +17,12 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization; "Design", "CA1001:Types that own disposable fields should be disposable", Justification = "https://github.com/dotnet/roslyn-analyzers/issues/6151")] -internal readonly struct PaletteQuantizer : IQuantizer +internal struct PaletteQuantizer : IQuantizer where TPixel : unmanaged, IPixel { private readonly EuclideanPixelMap pixelMap; + private int transparencyIndex; + private TPixel transparentColor; /// /// Initializes a new instance of the struct. @@ -28,20 +30,37 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization; /// The configuration which allows altering default behavior or extending the library. /// The quantizer options defining quantization rules. /// The palette to use. - /// An explicit index at which to match transparent pixels. [MethodImpl(InliningOptions.ShortMethod)] + public PaletteQuantizer(Configuration configuration, QuantizerOptions options, ReadOnlyMemory palette) + : this(configuration, options, palette, -1, default) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.NotNull(options, nameof(options)); + } + + /// + /// Initializes a new instance of the struct. + /// + /// The configuration which allows altering default behavior or extending the library. + /// The quantizer options defining quantization rules. + /// The palette to use. + /// The index of the color in the palette that should be considered as transparent. + /// The color that should be considered as transparent. public PaletteQuantizer( Configuration configuration, QuantizerOptions options, ReadOnlyMemory palette, - int transparentIndex) + int transparencyIndex, + TPixel transparentColor) { Guard.NotNull(configuration, nameof(configuration)); Guard.NotNull(options, nameof(options)); this.Configuration = configuration; this.Options = options; - this.pixelMap = new EuclideanPixelMap(configuration, palette, transparentIndex); + this.pixelMap = new EuclideanPixelMap(configuration, palette); + this.transparencyIndex = transparencyIndex; + this.transparentColor = transparentColor; } /// @@ -51,7 +70,7 @@ public PaletteQuantizer( public QuantizerOptions Options { get; } /// - public ReadOnlyMemory Palette => this.pixelMap.Palette; + public readonly ReadOnlyMemory Palette => this.pixelMap.Palette; /// [MethodImpl(InliningOptions.ShortMethod)] @@ -60,21 +79,29 @@ public readonly IndexedImageFrame QuantizeFrame(ImageFrame sourc /// [MethodImpl(InliningOptions.ShortMethod)] - public void AddPaletteColors(Buffer2DRegion pixelRegion) + public readonly void AddPaletteColors(in Buffer2DRegion pixelRegion) { } - /// - /// Allows setting the transparent index after construction. - /// - /// An explicit index at which to match transparent pixels. - public void SetTransparentIndex(int index) => this.pixelMap.SetTransparentIndex(index); - /// [MethodImpl(InliningOptions.ShortMethod)] public readonly byte GetQuantizedColor(TPixel color, out TPixel match) - => (byte)this.pixelMap.GetClosestColor(color, out match); + { + if (this.transparencyIndex >= 0 && color.Equals(this.transparentColor)) + { + match = this.transparentColor; + return (byte)this.transparencyIndex; + } + + return (byte)this.pixelMap.GetClosestColor(color, out match); + } + + public void SetTransparencyIndex(int transparencyIndex, TPixel transparentColor) + { + this.transparencyIndex = transparencyIndex; + this.transparentColor = transparentColor; + } /// - public void Dispose() => this.pixelMap.Dispose(); + public readonly void Dispose() => this.pixelMap.Dispose(); } diff --git a/src/ImageSharp/Processing/Processors/Quantization/QuantizerConstants.cs b/src/ImageSharp/Processing/Processors/Quantization/QuantizerConstants.cs index 2bf4c6d56d..3b515e372d 100644 --- a/src/ImageSharp/Processing/Processors/Quantization/QuantizerConstants.cs +++ b/src/ImageSharp/Processing/Processors/Quantization/QuantizerConstants.cs @@ -21,15 +21,30 @@ public static class QuantizerConstants public const int MaxColors = 256; /// - /// The minumim dithering scale used to adjust the amount of dither. + /// The minimum dithering scale used to adjust the amount of dither. /// public const float MinDitherScale = 0; /// - /// The max dithering scale used to adjust the amount of dither. + /// The maximum dithering scale used to adjust the amount of dither. /// public const float MaxDitherScale = 1F; + /// + /// The default threshold at which to consider a pixel transparent. + /// + public const float DefaultTransparencyThreshold = 64 / 255F; + + /// + /// The minimum threshold at which to consider a pixel transparent. + /// + public const float MinTransparencyThreshold = 0F; + + /// + /// The maximum threshold at which to consider a pixel transparent. + /// + public const float MaxTransparencyThreshold = 1F; + /// /// Gets the default dithering algorithm to use. /// diff --git a/src/ImageSharp/Processing/Processors/Quantization/QuantizerOptions.cs b/src/ImageSharp/Processing/Processors/Quantization/QuantizerOptions.cs index a6bb265a81..4f4104a8a3 100644 --- a/src/ImageSharp/Processing/Processors/Quantization/QuantizerOptions.cs +++ b/src/ImageSharp/Processing/Processors/Quantization/QuantizerOptions.cs @@ -12,6 +12,7 @@ public class QuantizerOptions { private float ditherScale = QuantizerConstants.MaxDitherScale; private int maxColors = QuantizerConstants.MaxColors; + private float threshold = QuantizerConstants.DefaultTransparencyThreshold; /// /// Gets or sets the algorithm to apply to the output image. @@ -38,4 +39,14 @@ public int MaxColors get => this.maxColors; set => this.maxColors = Numerics.Clamp(value, QuantizerConstants.MinColors, QuantizerConstants.MaxColors); } + + /// + /// Gets or sets the threshold at which to consider a pixel transparent. Range 0..1. + /// Defaults to . + /// + public float TransparencyThreshold + { + get => this.threshold; + set => this.threshold = Numerics.Clamp(value, QuantizerConstants.MinTransparencyThreshold, QuantizerConstants.MaxTransparencyThreshold); + } } diff --git a/src/ImageSharp/Processing/Processors/Quantization/QuantizerUtilities.cs b/src/ImageSharp/Processing/Processors/Quantization/QuantizerUtilities.cs index 6d2200b8a7..7669f0d3c6 100644 --- a/src/ImageSharp/Processing/Processors/Quantization/QuantizerUtilities.cs +++ b/src/ImageSharp/Processing/Processors/Quantization/QuantizerUtilities.cs @@ -112,7 +112,12 @@ public static void BuildPalette( IPixelSamplingStrategy pixelSamplingStrategy, Image source) where TPixel : unmanaged, IPixel - => quantizer.BuildPalette(source.Configuration, TransparentColorMode.Preserve, pixelSamplingStrategy, source); + => quantizer.BuildPalette( + source.Configuration, + TransparentColorMode.Preserve, + pixelSamplingStrategy, + source, + Color.Transparent); /// /// Adds colors to the quantized palette from the given pixel regions. @@ -123,27 +128,33 @@ public static void BuildPalette( /// The transparent color mode. /// The pixel sampling strategy. /// The source image to sample from. + /// The background color to use when clearing transparent pixels. public static void BuildPalette( this IQuantizer quantizer, Configuration configuration, TransparentColorMode mode, IPixelSamplingStrategy pixelSamplingStrategy, - Image source) + Image source, + Color backgroundColor) where TPixel : unmanaged, IPixel { if (EncodingUtilities.ShouldClearTransparentPixels(mode)) { foreach (Buffer2DRegion region in pixelSamplingStrategy.EnumeratePixelRegions(source)) { + // We need to clone the region to ensure we don't alter the original image. using Buffer2D clone = region.Buffer.CloneRegion(configuration, region.Rectangle); - quantizer.AddPaletteColors(clone.GetRegion()); + Buffer2DRegion clonedRegion = clone.GetRegion(); + + EncodingUtilities.ClearTransparentPixels(configuration, in clonedRegion, backgroundColor); + quantizer.AddPaletteColors(in clonedRegion); } } else { foreach (Buffer2DRegion region in pixelSamplingStrategy.EnumeratePixelRegions(source)) { - quantizer.AddPaletteColors(region); + quantizer.AddPaletteColors(in region); } } } @@ -160,7 +171,12 @@ public static void BuildPalette( IPixelSamplingStrategy pixelSamplingStrategy, ImageFrame source) where TPixel : unmanaged, IPixel - => quantizer.BuildPalette(source.Configuration, TransparentColorMode.Preserve, pixelSamplingStrategy, source); + => quantizer.BuildPalette( + source.Configuration, + TransparentColorMode.Preserve, + pixelSamplingStrategy, + source, + Color.Transparent); /// /// Adds colors to the quantized palette from the given pixel regions. @@ -171,27 +187,33 @@ public static void BuildPalette( /// The transparent color mode. /// The pixel sampling strategy. /// The source image frame to sample from. + /// The background color to use when clearing transparent pixels. public static void BuildPalette( this IQuantizer quantizer, Configuration configuration, TransparentColorMode mode, IPixelSamplingStrategy pixelSamplingStrategy, - ImageFrame source) + ImageFrame source, + Color backgroundColor) where TPixel : unmanaged, IPixel { if (EncodingUtilities.ShouldClearTransparentPixels(mode)) { + // We need to clone the region to ensure we don't alter the original image. foreach (Buffer2DRegion region in pixelSamplingStrategy.EnumeratePixelRegions(source)) { using Buffer2D clone = region.Buffer.CloneRegion(configuration, region.Rectangle); - quantizer.AddPaletteColors(clone.GetRegion()); + Buffer2DRegion clonedRegion = clone.GetRegion(); + + EncodingUtilities.ClearTransparentPixels(configuration, in clonedRegion, backgroundColor); + quantizer.AddPaletteColors(in clonedRegion); } } else { foreach (Buffer2DRegion region in pixelSamplingStrategy.EnumeratePixelRegions(source)) { - quantizer.AddPaletteColors(region); + quantizer.AddPaletteColors(in region); } } } diff --git a/src/ImageSharp/Processing/Processors/Quantization/WuQuantizer{TPixel}.cs b/src/ImageSharp/Processing/Processors/Quantization/WuQuantizer{TPixel}.cs index ba2ab825ad..e637c2cf04 100644 --- a/src/ImageSharp/Processing/Processors/Quantization/WuQuantizer{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Quantization/WuQuantizer{TPixel}.cs @@ -74,6 +74,7 @@ internal struct WuQuantizer : IQuantizer private readonly IMemoryOwner paletteOwner; private ReadOnlyMemory palette; private int maxColors; + private short transparencyThreshold; private readonly Box[] colorCube; private EuclideanPixelMap? pixelMap; private readonly bool isDithering; @@ -102,6 +103,7 @@ public WuQuantizer(Configuration configuration, QuantizerOptions options) this.pixelMap = default; this.palette = default; this.isDithering = this.isDithering = this.Options.Dither is not null; + this.transparencyThreshold = (short)(this.Options.TransparencyThreshold * 255); } /// @@ -111,57 +113,57 @@ public WuQuantizer(Configuration configuration, QuantizerOptions options) public QuantizerOptions Options { get; } /// - public readonly ReadOnlyMemory Palette + public ReadOnlyMemory Palette { get { - QuantizerUtilities.CheckPaletteState(in this.palette); + if (this.palette.IsEmpty) + { + this.ResolvePalette(); + QuantizerUtilities.CheckPaletteState(in this.palette); + } + return this.palette; } } /// - public void AddPaletteColors(Buffer2DRegion pixelRegion) + public readonly void AddPaletteColors(in Buffer2DRegion pixelRegion) + => this.Build3DHistogram(pixelRegion); + + /// + /// Once all histogram data has been accumulated, this method computes the moments, + /// splits the color cube, and resolves the final palette from the accumulated histogram. + /// + private void ResolvePalette() { - // TODO: Something is destroying the existing palette when adding new colors. - // When the QuantizingImageEncoder.PixelSamplingStrategy is DefaultPixelSamplingStrategy - // this leads to performance issues + the palette is not preserved. - // https://github.com/SixLabors/ImageSharp/issues/2498 - this.Build3DHistogram(pixelRegion); + // Calculate the cumulative moments from the accumulated histogram. this.Get3DMoments(this.memoryAllocator); + + // Partition the histogram into color cubes. this.BuildCube(); - // Slice again since maxColors has been updated since the buffer was created. + // Compute the palette colors from the resolved cubes. Span paletteSpan = this.paletteOwner.GetSpan()[..this.maxColors]; ReadOnlySpan momentsSpan = this.momentsOwner.GetSpan(); for (int k = 0; k < paletteSpan.Length; k++) { this.Mark(ref this.colorCube[k], (byte)k); - Moment moment = Volume(ref this.colorCube[k], momentsSpan); - if (moment.Weight > 0) { paletteSpan[k] = TPixel.FromScaledVector4(moment.Normalize()); } } - ReadOnlyMemory result = this.paletteOwner.Memory[..paletteSpan.Length]; - if (this.isDithering) + // Update the palette to the new computed colors. + this.palette = this.paletteOwner.Memory[..paletteSpan.Length]; + + // Create the pixel map if dithering is enabled. + if (this.isDithering && this.pixelMap is null) { - // When called multiple times by QuantizerUtilities.BuildPalette - // this prevents memory churn caused by reallocation. - if (this.pixelMap is null) - { - this.pixelMap = new EuclideanPixelMap(this.Configuration, result); - } - else - { - this.pixelMap.Clear(result); - } + this.pixelMap = new EuclideanPixelMap(this.Configuration, this.palette); } - - this.palette = result; } /// @@ -172,12 +174,19 @@ public readonly IndexedImageFrame QuantizeFrame(ImageFrame sourc /// public readonly byte GetQuantizedColor(TPixel color, out TPixel match) { + // Due to the addition of new colors by dithering that are not part of the original histogram, + // the color cube might not match the correct color. + // In this case, we must use the pixel map to get the closest color. if (this.isDithering) { - return (byte)this.pixelMap!.GetClosestColor(color, out match); + return (byte)this.pixelMap!.GetClosestColor(color, out match, this.transparencyThreshold); } Rgba32 rgba = color.ToRgba32(); + if (rgba.A < this.transparencyThreshold) + { + rgba = default; + } const int shift = 8 - IndexBits; int r = rgba.R >> shift; @@ -188,7 +197,7 @@ public readonly byte GetQuantizedColor(TPixel color, out TPixel match) ReadOnlySpan tagSpan = this.tagsOwner.GetSpan(); byte index = tagSpan[GetPaletteIndex(r + 1, g + 1, b + 1, a + 1)]; ref TPixel paletteRef = ref MemoryMarshal.GetReference(this.palette.Span); - match = Unsafe.Add(ref paletteRef, index); + match = Unsafe.Add(ref paletteRef, (nuint)index); return index; } @@ -360,7 +369,7 @@ private static Moment Top(ref Box cube, int direction, int position, ReadOnlySpa /// Builds a 3-D color histogram of counts, r/g/b, c^2. /// /// The source pixel data. - private readonly void Build3DHistogram(Buffer2DRegion source) + private readonly void Build3DHistogram(in Buffer2DRegion source) { Span momentSpan = this.momentsOwner.GetSpan(); @@ -368,6 +377,8 @@ private readonly void Build3DHistogram(Buffer2DRegion source) using IMemoryOwner buffer = this.memoryAllocator.Allocate(source.Width); Span bufferSpan = buffer.GetSpan(); + float transparencyThreshold = this.Options.TransparencyThreshold * 255; + for (int y = 0; y < source.Height; y++) { Span row = source.DangerousGetRowSpan(y); @@ -376,6 +387,10 @@ private readonly void Build3DHistogram(Buffer2DRegion source) for (int x = 0; x < bufferSpan.Length; x++) { Rgba32 rgba = bufferSpan[x]; + if (rgba.A < transparencyThreshold) + { + rgba = default; + } int r = (rgba.R >> (8 - IndexBits)) + 1; int g = (rgba.G >> (8 - IndexBits)) + 1; diff --git a/src/ImageSharp/Processing/Processors/Transforms/TransformProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Transforms/TransformProcessor{TPixel}.cs index bdfac00366..a8455a06ea 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/TransformProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/TransformProcessor{TPixel}.cs @@ -3,6 +3,7 @@ using SixLabors.ImageSharp.PixelFormats; +// TODO: DO we need this class? namespace SixLabors.ImageSharp.Processing.Processors.Transforms; /// @@ -22,18 +23,4 @@ protected TransformProcessor(Configuration configuration, Image source, : base(configuration, source, sourceRectangle) { } - - /// - protected override void AfterFrameApply(ImageFrame source, ImageFrame destination) - { - base.AfterFrameApply(source, destination); - destination.Metadata.AfterFrameApply(source, destination); - } - - /// - protected override void AfterImageApply(Image destination) - { - base.AfterImageApply(destination); - destination.Metadata.AfterImageApply(destination); - } } diff --git a/tests/ImageSharp.Tests/Formats/Gif/GifEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Gif/GifEncoderTests.cs index f12f66186e..d219f551b2 100644 --- a/tests/ImageSharp.Tests/Formats/Gif/GifEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Gif/GifEncoderTests.cs @@ -419,4 +419,15 @@ public void Encode_WithTransparentColorBehaviorClear_Works() } }); } + + [Theory] + [WithFile(TestImages.Gif.Issues.Issue2866, PixelTypes.Rgba32)] + public void GifEncoder_CanDecode_Issue2866(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using Image image = provider.GetImage(); + + // image.DebugSaveMultiFrame(provider); + 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 b4995d77b6..298d5b788e 100644 --- a/tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs @@ -442,11 +442,12 @@ public void Encode_APng(TestImageProvider provider) } [Theory] - [WithFile(TestImages.Gif.Leo, PixelTypes.Rgba32)] - public void Encode_AnimatedFormatTransform_FromGif(TestImageProvider provider) + [WithFile(TestImages.Gif.Leo, PixelTypes.Rgba32, 0.613F)] + [WithFile(TestImages.Gif.Issues.Issue2866, PixelTypes.Rgba32, 1.06F)] + public void Encode_AnimatedFormatTransform_FromGif(TestImageProvider provider, float percentage) where TPixel : unmanaged, IPixel { - if (TestEnvironment.RunsOnCI && !TestEnvironment.IsWindows) + if (TestEnvironment.RunsOnCI) { return; } @@ -457,12 +458,14 @@ public void Encode_AnimatedFormatTransform_FromGif(TestImageProvider 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.613f).VerifySimilarity(output, image); + // The image has been visually checked but the coarse cache used by the palette quantizer + // can lead to minor differences between frames. + ImageComparer.TolerantPercentage(percentage).VerifySimilarity(output, image); GifMetadata gif = image.Metadata.GetGifMetadata(); PngMetadata png = output.Metadata.GetPngMetadata(); @@ -641,7 +644,7 @@ public void Issue2469_Quantized_Encode_Artifacts(TestImageProvider(TestImageProvider provider) @@ -657,6 +660,35 @@ public void Issue2668_Quantized_Encode_Alpha(TestImageProvider p encoded.CompareToReferenceOutput(ImageComparer.Exact, provider); } + [Fact] + public void Issue_2862() + { + // Create a grayscale palette (or any other palette with colors that are very close to each other): + Rgba32[] palette = [.. Enumerable.Range(0, 256).Select(i => new Rgba32((byte)i, (byte)i, (byte)i))]; + + using Image image = new(254, 4); + for (int y = 0; y < image.Height; y++) + { + for (int x = 0; x < image.Width; x++) + { + image[x, y] = palette[x]; + } + } + + using MemoryStream ms = new(); + image.Save(ms, new PngEncoder + { + ColorType = PngColorType.Palette, + BitDepth = PngBitDepth.Bit8, + Quantizer = new PaletteQuantizer(palette.Select(Color.FromPixel).ToArray()) + }); + + ms.Position = 0; + + using Image encoded = Image.Load(ms); + ImageComparer.Exact.VerifySimilarity(image, encoded); + } + private static void TestPngEncoderCore( TestImageProvider provider, PngColorType pngColorType, diff --git a/tests/ImageSharp.Tests/Formats/WebP/WebpCommonUtilsTests.cs b/tests/ImageSharp.Tests/Formats/WebP/WebpCommonUtilsTests.cs index a3fe028db5..1491cd13cf 100644 --- a/tests/ImageSharp.Tests/Formats/WebP/WebpCommonUtilsTests.cs +++ b/tests/ImageSharp.Tests/Formats/WebP/WebpCommonUtilsTests.cs @@ -106,7 +106,7 @@ private static void RunCheckNoneOpaqueWithNoneOpaquePixelsTest() 174, 183, 189, 255, 148, 158, 158, 255, }; - Span row = MemoryMarshal.Cast(rowBytes); + ReadOnlySpan row = MemoryMarshal.Cast(rowBytes); bool noneOpaque; for (int length = 8; length < row.Length; length += 8) @@ -188,7 +188,7 @@ private static void RunCheckNoneOpaqueWithOpaquePixelsTest() 174, 183, 189, 255, 148, 158, 158, 255, }; - Span row = MemoryMarshal.Cast(rowBytes); + ReadOnlySpan row = MemoryMarshal.Cast(rowBytes); bool noneOpaque; for (int length = 8; length < row.Length; length += 8) diff --git a/tests/ImageSharp.Tests/Formats/WebP/WebpDecoderTests.cs b/tests/ImageSharp.Tests/Formats/WebP/WebpDecoderTests.cs index 657ab25546..adabb727d8 100644 --- a/tests/ImageSharp.Tests/Formats/WebP/WebpDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/WebP/WebpDecoderTests.cs @@ -450,6 +450,22 @@ public void WebpDecoder_CanDecode_Issue2670(TestImageProvider pr image.CompareToOriginal(provider, ReferenceDecoder); } + // https://github.com/SixLabors/ImageSharp/issues/2866 + [Theory] + [WithFile(Lossy.Issue2866, PixelTypes.Rgba32)] + public void WebpDecoder_CanDecode_Issue2866(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + // Web + using Image image = provider.GetImage( + WebpDecoder.Instance, + new WebpDecoderOptions() { BackgroundColorHandling = BackgroundColorHandling.Ignore }); + + // We can't use the reference decoder here. + // It creates frames of different size without blending the frames. + image.DebugSave(provider, extension: "webp", encoder: new WebpEncoder()); + } + [Theory] [WithFile(Lossless.LossLessCorruptImage3, PixelTypes.Rgba32)] public void WebpDecoder_ThrowImageFormatException_OnInvalidImages(TestImageProvider provider) diff --git a/tests/ImageSharp.Tests/Image/ImageFrameTests.cs b/tests/ImageSharp.Tests/Image/ImageFrameTests.cs index e09ef487a8..58cf326001 100644 --- a/tests/ImageSharp.Tests/Image/ImageFrameTests.cs +++ b/tests/ImageSharp.Tests/Image/ImageFrameTests.cs @@ -119,7 +119,7 @@ public void CopyPixelDataTo_Success(bool disco, bool byteSpan) } else { - Span destination = MemoryMarshal.Cast(actual); + Span destination = MemoryMarshal.Cast(actual.AsSpan()); image.Frames.RootFrame.CopyPixelDataTo(destination); } diff --git a/tests/ImageSharp.Tests/Image/ImageTests.cs b/tests/ImageSharp.Tests/Image/ImageTests.cs index ac91ea948e..291e523816 100644 --- a/tests/ImageSharp.Tests/Image/ImageTests.cs +++ b/tests/ImageSharp.Tests/Image/ImageTests.cs @@ -197,7 +197,7 @@ public void CopyPixelDataTo_Success(bool disco, bool byteSpan) } else { - Span destination = MemoryMarshal.Cast(actual); + Span destination = MemoryMarshal.Cast(actual.AsSpan()); image.CopyPixelDataTo(destination); } diff --git a/tests/ImageSharp.Tests/Quantization/WuQuantizerTests.cs b/tests/ImageSharp.Tests/Quantization/WuQuantizerTests.cs index 74f2fc3b42..28a7c49e51 100644 --- a/tests/ImageSharp.Tests/Quantization/WuQuantizerTests.cs +++ b/tests/ImageSharp.Tests/Quantization/WuQuantizerTests.cs @@ -79,7 +79,7 @@ public void Palette256() } Configuration config = Configuration.Default; - WuQuantizer quantizer = new(new QuantizerOptions { Dither = null }); + WuQuantizer quantizer = new(new QuantizerOptions { Dither = null, TransparencyThreshold = 0 }); ImageFrame frame = image.Frames.RootFrame; @@ -152,7 +152,7 @@ private static void TestScale(Func pixelBuilder) } Configuration config = Configuration.Default; - WuQuantizer quantizer = new(new QuantizerOptions { Dither = null }); + WuQuantizer quantizer = new(new QuantizerOptions { Dither = null, TransparencyThreshold = 0 }); ImageFrame frame = image.Frames.RootFrame; using (IQuantizer frameQuantizer = quantizer.CreatePixelSpecificQuantizer(config)) diff --git a/tests/ImageSharp.Tests/TestImages.cs b/tests/ImageSharp.Tests/TestImages.cs index fafa1d2429..462d54154e 100644 --- a/tests/ImageSharp.Tests/TestImages.cs +++ b/tests/ImageSharp.Tests/TestImages.cs @@ -536,6 +536,7 @@ public static class Issues public const string Issue2450_B = "Gif/issues/issue_2450_2.gif"; public const string Issue2198 = "Gif/issues/issue_2198.gif"; public const string Issue2758 = "Gif/issues/issue_2758.gif"; + public const string Issue2866 = "Gif/issues/issue_2866.gif"; public const string Issue2859_A = "Gif/issues/issue_2859_A.gif"; public const string Issue2859_B = "Gif/issues/issue_2859_B.gif"; } @@ -830,6 +831,7 @@ public static class Lossy public const string Issue2670 = "Webp/issues/Issue2670.webp"; public const string Issue2763 = "Webp/issues/Issue2763.png"; public const string Issue2801 = "Webp/issues/Issue2801.webp"; + public const string Issue2866 = "Webp/issues/Issue2866.webp"; } } diff --git a/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/MagickReferenceDecoder.cs b/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/MagickReferenceDecoder.cs index 74015a4eff..34c8170c3c 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/MagickReferenceDecoder.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/MagickReferenceDecoder.cs @@ -64,20 +64,27 @@ protected override Image Decode(DecoderOptions options, Stream s settings.SetDefines(pngReadDefines); using MagickImageCollection magickImageCollection = new(stream, settings); + int imageWidth = magickImageCollection.Max(x => x.Width); + int imageHeight = magickImageCollection.Max(x => x.Height); + List> framesList = []; foreach (IMagickImage magicFrame in magickImageCollection) { - ImageFrame frame = new(configuration, (int)magicFrame.Width, (int)magicFrame.Height); + ImageFrame frame = new(configuration, imageWidth, imageHeight); framesList.Add(frame); - MemoryGroup framePixels = frame.PixelBuffer.FastMemoryGroup; + Buffer2DRegion buffer = frame.PixelBuffer.GetRegion( + imageWidth - magicFrame.Width, + imageHeight - magicFrame.Height, + magicFrame.Width, + magicFrame.Height); using IUnsafePixelCollection pixels = magicFrame.GetPixelsUnsafe(); if (magicFrame.Depth is 12 or 10 or 8 or 6 or 5 or 4 or 3 or 2 or 1) { byte[] data = pixels.ToByteArray(PixelMapping.RGBA); - FromRgba32Bytes(configuration, data, framePixels); + FromRgba32Bytes(configuration, data, buffer); } else if (magicFrame.Depth is 16 or 14) { @@ -88,7 +95,7 @@ protected override Image Decode(DecoderOptions options, Stream s ushort[] data = pixels.ToShortArray(PixelMapping.RGBA); Span bytes = MemoryMarshal.Cast(data.AsSpan()); - FromRgba64Bytes(configuration, bytes, framePixels); + FromRgba64Bytes(configuration, bytes, buffer); } else { @@ -111,33 +118,40 @@ protected override ImageInfo Identify(DecoderOptions options, Stream stream, Can PixelType = metadata.GetDecodedPixelTypeInfo() }; } - - private static void FromRgba32Bytes(Configuration configuration, Span rgbaBytes, IMemoryGroup destinationGroup) + private static void FromRgba32Bytes( + Configuration configuration, + Span rgbaBytes, + Buffer2DRegion destinationGroup) where TPixel : unmanaged, ImageSharp.PixelFormats.IPixel { Span sourcePixels = MemoryMarshal.Cast(rgbaBytes); - foreach (Memory m in destinationGroup) + for (int y = 0; y < destinationGroup.Height; y++) { - Span destBuffer = m.Span; + Span destBuffer = destinationGroup.DangerousGetRowSpan(y); PixelOperations.Instance.FromRgba32( configuration, sourcePixels[..destBuffer.Length], destBuffer); + sourcePixels = sourcePixels[destBuffer.Length..]; } } - private static void FromRgba64Bytes(Configuration configuration, Span rgbaBytes, IMemoryGroup destinationGroup) + private static void FromRgba64Bytes( + Configuration configuration, + Span rgbaBytes, + Buffer2DRegion destinationGroup) where TPixel : unmanaged, ImageSharp.PixelFormats.IPixel { - foreach (Memory m in destinationGroup) + for (int y = 0; y < destinationGroup.Height; y++) { - Span destBuffer = m.Span; + Span destBuffer = destinationGroup.DangerousGetRowSpan(y); PixelOperations.Instance.FromRgba64Bytes( configuration, rgbaBytes, destBuffer, destBuffer.Length); + rgbaBytes = rgbaBytes[(destBuffer.Length * 8)..]; } } 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 2b8e05b070..291739dfa9 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:9d7441b03c24acb887b2d9a6e2346bb23e2d38293c3df3ff489d48593f87b29a 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 f7eb06c558..c69ed3130c 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:5d9f2745de2b6e7fc3b1403fe651f3bbba835c67a6fb410fc8a9d91a15b44328 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 de42d1bfc2..401ceaa36c 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:681b0e36298cb702683fb9ffb2a82f7dfd9080b268db19a03f413809f69d0e07 -size 273269 +oid sha256:596472e74050d968479b672c1d2436b179e41a7b99fcefb53286ad47e5a4fe13 +size 273115 diff --git a/tests/Images/External/ReferenceOutput/DitherTests/ApplyDitherFilterInBox_Rgba32_CalliphoraPartial.png b/tests/Images/External/ReferenceOutput/DitherTests/ApplyDitherFilterInBox_Rgba32_CalliphoraPartial.png index 43e414da6d..35ed6dee46 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:a899a84c6af24bfad89f9fde75957c7a979d65bcf096ab667cb976efd71cb560 -size 271171 +oid sha256:a025a094c285cea8510430b0e3657bc59231b38132781e298d60a74238879912 +size 270699 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 1656b2e9cb..0b03e4e185 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:38597c6144d61960d25c74d7a465b1cdf69b7c0804a6dec68128a6c953258313 -size 52688 +oid sha256:2465dde9a5d6202194f7af3924ca24ab3151948d551549a711977d3302dbc0a3 +size 51158 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 c6016ae358..1d27fab771 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:5f9191c71eea1f73aa4c55397ca26f240615c9c4a7fff9a05e6f2e046b5e4d8b -size 62323 +oid sha256:82dcdd4f28a9ffafd36a21d06aee8adb49017df2d4abeee4205d65b1ae3df35e +size 59875 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 40243937d3..51c93894de 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:b63810145832db459bb7a6b37a028a7b778f6b6b4e6eae00e50e6e21c5a06086 -size 62199 +oid sha256:a836c8efd7aa9818cf807cf56412e78399a6568798be23d0f3f6b89552856ff1 +size 62172 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 83f9e067db..de9ef47037 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:a67c14ef99a943706f050ff1ea0ef101429292d52bc14ed4610f8338736ff87e -size 56800 +oid sha256:78900d779181140a02a2b9fb9fa922ca854d9905c1dc7e006592a3fdc00f8dee +size 58107 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 22e4f4b6d6..bb62475c97 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:623dd82d372ba517b0d3357d06cffaf105d407a9090cbcbc6a76ae944ab33d67 -size 59468 +oid sha256:76f10d4280258d2941d85e795cf788977ca1e85bdc1b75b5a482b5bbdaa49d32 +size 57900 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 838863c158..2d99410117 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:8edceef8e12c4f3d194523437045c5cf4e80c7bb95ff75f38c1f38a21872e3d0 -size 59376 +oid sha256:aba9172bb4d117ba1b0c5f32b46251d473cc06b3f697e5729da0c5768a70b5d2 +size 59104 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 60513e1992..bd3295e424 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:b1d7019e8cb170ae67496f8250446c4f6b6217378658408c3d51a95c49a4c3bc -size 63287 +oid sha256:e7d6ea824ba19632afa940b3062632d305bf3521b1795d46f3fea90abc1f0ed8 +size 64431 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 0d1b34d8ce..6bea03c0be 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:d7c03ede7ab3bd4e57e6a63e53e2e8c771e938fdc7d5dfe5c9339a2c9907c9cf -size 55550 +oid sha256:3efcf6f924d3d07cad9dbf9dddb6104c3748ac4354298acf5afde66c2321e819 +size 55358 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 f8c998ecbd..b535b14155 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:79b690b91223d1fe7ddf1b8826b4474b89644822bc8aa9adee3cf819bc095b4c -size 60979 +oid sha256:2b9f295f6b539fbeeae3c473907fa450f9b8c94017abad4bf915a8a4a2e7b612 +size 56982 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 cc2327b23f..e0334be2d5 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:7e22401dddf6552cd91517c1cdd142d3b9a66a7ad5c80d2e52ae07a7f583708e -size 57657 +oid sha256:c4c45632b6cd387c929a9e0982f3943a7c3f64f27862c0b539bbf71228561f39 +size 57886 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 e3ae6508e1..db22e17585 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:819a0ce38e27e2adfa454d8c5ad5b24e818bf8954c9f2406f608dcecf506c2c4 -size 59838 +oid sha256:bf9e8bd50b62ba62ab04a5ab2af207414183a015567080fa7cdd827016694369 +size 60458 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 2b897a5d6d..2d6226d096 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:007ac609ec61b39c7bdd04bc87a698f5cdc76eadd834c1457f41eb9c135c3f7b -size 60688 +oid sha256:89864a77216b51cc5b9415453ade7f7ec64c1c112546aa47ee6b4b89f9b258a3 +size 60543 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 10ba90ae86..4d9b22139e 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:46892c07e9a93f1df71f0e38b331a437fb9b7c52d8f40cf62780cb6bd35d3b13 -size 58963 +oid sha256:0c03c3dc0b3da69ef4f55b5ad6d162da94ad46f4e426e318695bedc7e5bb3dfd +size 58725 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 9608289e84..3b7c78182a 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:1b83345ca3de8d1fc0fbb5d8e68329b94ad79fc29b9f10a1392a97ffe9a0733e -size 58985 +oid sha256:af86b108639f833972958fd2cc7d00221982069c40cab67b5bc6b8ce1a7e826d +size 59137 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 79d2c5eb14..b56e3ccfd6 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:c775a5b19ba09e1b335389e0dc12cb0c3feaff6072e904da750a676fcd6b07dc -size 59202 +oid sha256:959b49f5498e4018bfb8a5fac8a688c51b06161dc0c6559547293c613ddca760 +size 59248 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 8d3cf1a564..78078ac90d 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:6c88740c0553829eaa42ca751b34cc456623a84ccdff4020949a06ef4b4802d1 -size 61137 +oid sha256:fdc28c281666e381c7ba2483d033f73c88111f13eec10cc406e07730eb5fa709 +size 60804 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 a146f8f668..523a87b7a5 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:0a4a404b0767faac952435f768867cf7bf053848e1e3ef121624f136658a107c -size 58386 +oid sha256:c80f215d4a839fb1ca722d03923b587bac6326d54d2d7a3656667e46464b4307 +size 58011 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 edec46a92a..741ac096a3 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:8cc216ed952216d203836dc559234216614f1ed059651677cc0ea714010bd932 -size 58855 +oid sha256:38fbfc201e8ef31b879e863f7f49ac1e731c4d7dfca58a80e1e45890565af979 +size 58742 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 e2e4147f68..91ff81d5df 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:a3253003b088c9975725cf321c2fc827547a5feb199f2d1aa515c69bde59deb7 -size 871 +oid sha256:a9b0209e8bae05da6de72a4249d2fe43ef08388c7296556921c17b11bdb8bdcc +size 875 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 aa0e9a4824..91ff81d5df 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:bb3e3b9b3001e76505fb0e2db7ad200cad2a016c06f1993c60c3cab42c134863 -size 867 +oid sha256:a9b0209e8bae05da6de72a4249d2fe43ef08388c7296556921c17b11bdb8bdcc +size 875 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 e2e4147f68..91ff81d5df 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:a3253003b088c9975725cf321c2fc827547a5feb199f2d1aa515c69bde59deb7 -size 871 +oid sha256:a9b0209e8bae05da6de72a4249d2fe43ef08388c7296556921c17b11bdb8bdcc +size 875 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 e2e4147f68..91ff81d5df 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:a3253003b088c9975725cf321c2fc827547a5feb199f2d1aa515c69bde59deb7 -size 871 +oid sha256:a9b0209e8bae05da6de72a4249d2fe43ef08388c7296556921c17b11bdb8bdcc +size 875 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 e899ffb42a..6cacba7877 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:ca70bb0200776efd00c4ef7596d4e1f2f5fbc68e447b395b25ef2b3c732e5156 -size 44189 +oid sha256:1de82d05feed0b3bd9d6d7d16507ff5dc06744843abaaf77fd4207edd5205488 +size 44246 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 543640c2e8..f623d52ac7 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:8474b847b7d4a8f3e5c9793ca257ce46efcf49c473c731a9ca9c759851410b94 -size 43066 +oid sha256:ea34b188ce71a8fbd76fddf052fc1322fff62ba0acc218582b996d9b00c81671 +size 42667 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 fec3c9b2b3..668d562a73 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:20e80e7d9e68fd85bfbc63c61953327354b0634000ec142e01a42618995fd14c -size 44391 +oid sha256:c179834a368c8fa4bb3e1a1fb2e12b567d7034c5a8e52741bc33ffa30ea73c8a +size 44251 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 68a95a0540..c9ad188dc1 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:8af98bfcc5edef3f3ff33ee8f76f33ce2906a6677167e2b29e1dbe63b00a78d8 -size 44202 +oid sha256:a049a50155bf56c53a1b74e919806cbb83716842b5c0a233c44c87b3630115e0 +size 44394 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 d67f02dca5..0e7e6cd659 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:b149ebbd550808ae46ff05b5ddcdb1fc0eb6ae0eacbe048e9a1ff24368d8f64d -size 45003 +oid sha256:286314ca90912de65427d51269a3263ea58b3c32f2839f797f2689b7dac0c6ff +size 44953 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 4175cf40b7..b77d857dea 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:9316cbbcb137ae6ff31646f6a5ba1d0aec100db4512509f7684187e74d16a111 -size 51074 +oid sha256:76a3abb7c908e365abd8fc5b1fdc7536a71645a5fd59be61e200707e208fb341 +size 51241 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 11d916bdc3..84176ad9b3 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:08c39a43993deadebab21f1d3504027b5910a52adc437c167d77d62e5f5db46e -size 52762 +oid sha256:968bba323acfabd9b1b02001e5b37047f6ab7fb7dae8c781eed2f84771beb9c9 +size 52812 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 a4f91b3301..278552e34c 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:0c9c47fa755d603f8c148011511ee91f32444e0d94367f9db57593e3bf30f2e0 -size 51808 +oid sha256:146039cba79c21408296e77e2aef33ccc3bc952283011ee4b441451512b2a634 +size 51680 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 ac56fa9236..bf0604356f 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:6d2289ed4fa0c679f0f120d260fec8ab40b1599043cc0a1fbebc6b67e238ff87 -size 51428 +oid sha256:6937822a02885fc236f5520e947081883d1ccbdb3da04821d0da133e1f98d98e +size 51009 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 9a7c7b4611..f5fc62eeed 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:366e84ab8587735455798651096d2af5f965fc325f4852dc68356e94600598b1 -size 52176 +oid sha256:8c3f249cb608697afabe92a91f571a1a990424a212b92a9c2241e7ef9a173734 +size 52022 diff --git a/tests/Images/External/ReferenceOutput/GifDecoderTests/Issue1962_Rgba32_issue1962_tiniest_gif_1st.png b/tests/Images/External/ReferenceOutput/GifDecoderTests/Issue1962_Rgba32_issue1962_tiniest_gif_1st.png index 24f5e9c0cd..a52b27708a 100644 --- a/tests/Images/External/ReferenceOutput/GifDecoderTests/Issue1962_Rgba32_issue1962_tiniest_gif_1st.png +++ b/tests/Images/External/ReferenceOutput/GifDecoderTests/Issue1962_Rgba32_issue1962_tiniest_gif_1st.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:4f8c6d416f09671777934e57bc67fb52ccc97145dc6f1869e628d9ffd7d8f6e7 -size 119 +oid sha256:9ab8374e77865606a2426e3d22628f717914472431de1d9d8ee9690d319850a0 +size 118 diff --git a/tests/Images/External/ReferenceOutput/GifDecoderTests/Issue2012BadMinCode_Rgba32_issue2012_drona1.png b/tests/Images/External/ReferenceOutput/GifDecoderTests/Issue2012BadMinCode_Rgba32_issue2012_drona1.png index b07e806620..6f2fc842f8 100644 --- a/tests/Images/External/ReferenceOutput/GifDecoderTests/Issue2012BadMinCode_Rgba32_issue2012_drona1.png +++ b/tests/Images/External/ReferenceOutput/GifDecoderTests/Issue2012BadMinCode_Rgba32_issue2012_drona1.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:588d055a93c7b4fdb62e8b77f3ae08753a9e8990151cb0523f5e761996189b70 -size 142244 +oid sha256:ff67035f78690321c29a4e15c8de7c55bcb3260d667dbd9bced15de6b626fca1 +size 148499 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 4c78303750..94eee7f07d 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:1af50619f835b4470afac4553445176c121c3c9fa838dff937dcc56ae37941c3 -size 945821 +oid sha256:c5953aaa4569e97d1bf690e4429ae6684a4131521347cb8bf1f607d773018ee6 +size 939085 diff --git a/tests/Images/External/ReferenceOutput/PngEncoderTests/Issue2668_Quantized_Encode_Alpha_Rgba32_Issue_2668.png b/tests/Images/External/ReferenceOutput/PngEncoderTests/Issue2668_Quantized_Encode_Alpha_Rgba32_Issue_2668.png index 7af5391f70..f156cf1d64 100644 --- a/tests/Images/External/ReferenceOutput/PngEncoderTests/Issue2668_Quantized_Encode_Alpha_Rgba32_Issue_2668.png +++ b/tests/Images/External/ReferenceOutput/PngEncoderTests/Issue2668_Quantized_Encode_Alpha_Rgba32_Issue_2668.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f934af128b85b9e8f557d71ac8b1f1473a0922d0754fc0c4ece0d0e3d8d94c39 -size 7702 +oid sha256:215b86efdfb603ad851a7f4b5830e0ff82fb49e7729fcfd0853a6c066b21507e +size 8235 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 4948c7adee..28232733f6 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:a51d04953c1c82d99884af62912d2271108c6bc62f18d4b32d0b5290c01fa7f7 -size 247462 +oid sha256:33f86d176382805fe60cc7cf8057583a8451f802982ebcd337bda8dbf69efd6a +size 248753 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 d993923d48..3e0be536e3 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:85ee8479984aa52f837badbc49085c5448597fbfd987438fe25b58bad475e85f +size 239498 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 223d3bc012..e1bc23807a 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:24f738baad4417c2eedddf36064974ccd5ff9e1d1ac23e4f6c859c4fa789a447 +size 266819 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 922c2bf9b2..0f3d653f43 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:4f1462733e02d499b0d8c61ab835a27c7fee560fdc7fc521d20ec09bb4ccc80f -size 216030 +oid sha256:684cdf0f3f9d074e986b8b85b2c6c65da1f6f486c0eab727cc8a1c92b651fc9e +size 216246 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 922c2bf9b2..0f3d653f43 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:684cdf0f3f9d074e986b8b85b2c6c65da1f6f486c0eab727cc8a1c92b651fc9e +size 216246 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 29c93d14e2..c406e71c30 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:7efb8263a067de2f4368a43416049d13619a69761584867ec89867ed8b366c5e +size 226887 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 dbfab2b508..ec95754094 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:c68eba122814b5470e5f2e03e34190ff79e84e4b431ad8227355ce7ffcd4a6a7 -size 220192 +oid sha256:7aa18d1a444a12c30003c533b411b018c83684dbe48fce07293f83401c44b853 +size 220689 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 dbfab2b508..ec95754094 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:7aa18d1a444a12c30003c533b411b018c83684dbe48fce07293f83401c44b853 +size 220689 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 86655af42b..7b099d1346 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:96fceb13a0ec386959e5bdad17e3e2896f43dc86c02abf0b88f882c898523563 +size 230800 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 82d5e5d592..52a297cdf6 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:f4df5b1bc2c291ec1cf599580d198b447278412576ab998e099cc21110e82b3d -size 263152 +oid sha256:14b8be6579cea0742be6ab1d8a44b7fc7f7acc26698692dbe445435f1fa2e48a +size 262707 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 76946ee06f..268f787beb 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:0b4ffa39ea41480b02ac183dcb28617278a46bcaef0a30af62fe17167f009bbd +size 274683 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 ebb9ff6b00..36317b7bbe 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:f414473561bfa792c2e6342ff5e5dddffbdec5286932781b11a093803593b52a -size 313787 +oid sha256:d10d2efb6d711bbff03a803785f7269ddc9f5ba9417597e60804f2476ad72af2 +size 315621 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 7e3080562c..284c3a2702 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:2bfc23a95df8a88ac6e2777d67f381e800d23647c162a9a97131a101bbb97143 +size 306703 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 5626fa1b83..a45e842833 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:d85d4e8da5754786c6e632d4e7ce811ab4ce026664ca34c7a0e5cb01d6cce847 +size 322349 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 0205626738..f33e66693b 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:3a2aae04edebcaca9b95f30963201794887fa0eac954b64c68bfe529b14fa9be -size 269397 +oid sha256:09d4a269df15a78b84c94a22788b0cdbe187852979ad59afff886a98a66282c4 +size 269323 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 0205626738..f33e66693b 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:09d4a269df15a78b84c94a22788b0cdbe187852979ad59afff886a98a66282c4 +size 269323 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 68d91fc437..11d5e4ec37 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:992b1f5b3e8b342d4fbe19259a2dd88bed1a0b60fe78b7c4b3027472e141ca90 +size 271448 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 324bd92539..9df2f13dbb 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:752760327cc1416c171a920f1e0e95e34eae6d78bd0c7393a3be427bf3c8e55c -size 284481 +oid sha256:ae0a724189324a60c9f68014f4c178add91aecf7dbe49a1ca2c01c10816927a4 +size 284288 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 324bd92539..9df2f13dbb 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:ae0a724189324a60c9f68014f4c178add91aecf7dbe49a1ca2c01c10816927a4 +size 284288 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 52bf2a163f..8d7a39e7af 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:00962e89673a11aa5379fe8a1513012fa192bc1fe9caffedf26652ce14b681d1 +size 284714 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 05be1395ab..64560574a6 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:90a2b7b3872c6eb1f1f039558d9f6ace92891c86951c801da01ad55b055fd670 -size 316544 +oid sha256:a446d9dba2cdcfd336847e6a475872a1e1c99f204a9aba7aed0e4da282e0e9dd +size 317073 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 e016e3de69..46689cc8d7 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:993480e1b245bcae568bc4ac99f356509a932ab796bad17f12b051d12e08481d +size 323571 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 82b965123d..8b9efe3364 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:4bed69d43856ebd4b1af4055f8d3aacabd50c361a4e1e1f9cad080d799d6b744 -size 13853 +oid sha256:88be683fabadd0d6812c208b878def4a64612712332c748bbbf05e793d9a65e1 +size 17790 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 571b0db4b9..8b9efe3364 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:4c8c8393708002f06f9d8ed1ff8979db820035585c08b66ae463d94724fa64d3 -size 14330 +oid sha256:88be683fabadd0d6812c208b878def4a64612712332c748bbbf05e793d9a65e1 +size 17790 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 a1b3da6816..8b9efe3364 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:fda13875f4c762a95001426487cc04c9add39821eb793168fdbe5cc18e705643 -size 14566 +oid sha256:88be683fabadd0d6812c208b878def4a64612712332c748bbbf05e793d9a65e1 +size 17790 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 82b965123d..8b9efe3364 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:4bed69d43856ebd4b1af4055f8d3aacabd50c361a4e1e1f9cad080d799d6b744 -size 13853 +oid sha256:88be683fabadd0d6812c208b878def4a64612712332c748bbbf05e793d9a65e1 +size 17790 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 e0fc792026..8b9efe3364 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:cb826afb127fe4175e6e47253b8a8313b9d10aee193c316731f34e5d327a2591 -size 14580 +oid sha256:88be683fabadd0d6812c208b878def4a64612712332c748bbbf05e793d9a65e1 +size 17790 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 491847e491..1bf46ad350 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:37018ecc499651833208d846a0f446db94cc11eae002ab6e7ce45b3e7c09e86c -size 17734 +oid sha256:61d389cf13cd8ed2a4692dd812848675973669758fef2b88a03e77248b7804dd +size 19142 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 013bb4a3b7..dfb0ba32d6 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:c2f9ed902882f58704b22460bc64a7b27bc6f47fc2c822ee09f52345cc0d6ebf -size 19255 +oid sha256:215dd3bf4bfb82bbd42751440115e1983760ca2a792e07b929203f33712bb29e +size 20372 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 31fd7a5445..c09f3957ab 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:aad3f26f2939f3679afa2b6165db29885fff40bbb1d171d5ffecc7861b5fac31 -size 19654 +oid sha256:99a22bdcc34c31cab34ceb314b3114231ccdf1ad9159ea2f6f73d65179b28225 +size 21285 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 82b965123d..8b9efe3364 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:4bed69d43856ebd4b1af4055f8d3aacabd50c361a4e1e1f9cad080d799d6b744 -size 13853 +oid sha256:88be683fabadd0d6812c208b878def4a64612712332c748bbbf05e793d9a65e1 +size 17790 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 e2a05b9bd5..04e2846027 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:6d21029fa22dbe72cdc60b90c758cb9becd9fce03a33580d9466c1aedd323c1c -size 20000 +oid sha256:64f863b7acbf08125e894093b7c844fdbc7cb635f72e6eaf3f1f2d2f4f10f880 +size 22179 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 9850675bed..93ebc8ac7e 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:ea836214840a5da2b89dad3cd9e916413d3f9e21f9b855dc8161faa3544edcfc -size 9266 +oid sha256:4f95a03b6167e1e5174b94cbd2d4c0df6fbcf74402abcf027fb51c048d6040ca +size 9236 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 f3278c3d2f..9ff1175dfa 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:346c9e4239d917614525a99f7ae58ed0c0a22dc09d639f3a54dad1975e75ec44 -size 8833 +oid sha256:3f30a9cabe172856d45e058b41adba1580e4aac2c5455738a0fe1222e0fdb313 +size 10128 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 77821255bb..34257a5236 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:717fe46156f3d144f31cfce066dd13532ee8721d7d3a7b8c8425c646f411e8a5 -size 11099 +oid sha256:28f444d388662a516e261dd7b5d28fc142b6089061ca9450a9990053884eee6d +size 10596 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 0615793d57..170dd9482e 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:9e01c7276f1c4e905b1d8f4c84259f1047c0949f7a6a81f43a790bd1bd3201e3 -size 7932 +oid sha256:2e51a7ab6a24ad8466eb1c07bbd221a9b99cff2515304cd7d8b5577d7924c333 +size 8600 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 c43b5836ec..0523babb39 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:ae18d22edc011d576d6a1e9545bc52084ca0bed55a6ce19d391d2a5f97b1843c -size 11763 +oid sha256:1eb4268feb96c5fe5e53cf9a70dc50ff54a4d229f41a9ec7a005232f707ae395 +size 12835 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 e54740610c..5d6ec0c0fa 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:74b3f36e3fbac940d1f3bf90089b6b40234aa2ce3570b094534a4448c1d98aec -size 8875 +oid sha256:db9c3dee98f961011829fd5d1733c990016d8518684dea43bdc0ed8b46dc065b +size 9461 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 b08ba5be19..a13f473352 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:80e60c42fa11e973e1c865ed93448d3af0503e32d7b119bfe7162738efe691db -size 9086 +oid sha256:c49ae3b99cac4592c3aba6b1bd2613b7c2887419df84135facd9caab1c67c4f2 +size 9527 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 692c119e4a..6a2400a99d 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:fd5a9c76ee332603877624e219d84f85fe159389e7f9e72d1fb6177289dd1fb7 -size 9777 +oid sha256:7d2646e749b40122224662cc505dbf047703c39d100c9564c880bda7b3b31f9b +size 9648 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 0615793d57..170dd9482e 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:9e01c7276f1c4e905b1d8f4c84259f1047c0949f7a6a81f43a790bd1bd3201e3 -size 7932 +oid sha256:2e51a7ab6a24ad8466eb1c07bbd221a9b99cff2515304cd7d8b5577d7924c333 +size 8600 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 17a810448e..f29dae3423 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:240743d5f742b872c0f66f4033ad065402372605a76cda23f4c506d254a9d127 -size 9791 +oid sha256:26c1b8c9390950c7224a39ffdb4829db57b0bf55e05a041f1bc47f5d2218893c +size 9726 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 10b511a1a3..80c5b82ce0 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:074842dcbdf60690f41da31e12c290045d05ab6dc587f3f5ba29c9496871391c -size 11209 +oid sha256:88881839126c275a3d71278e4873cd05f4fe26eb7e1d1c2f5b29826d5bc60ef2 +size 11601 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 1ed81c0d0a..dde458b3a9 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:29e1ff6d454efca61852a88946e25dcf29708230bfc47c2625c4d1b2407070c6 -size 12072 +oid sha256:bcd2b1732cfd93dc307c783fd71df4736a3e99ff318a32197e472b6202118582 +size 11457 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 30f75826eb..395b0db882 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:f7838c37c32134f325960312095ed8e1decbb0dd7e14a84e82637258c7ea117e -size 12826 +oid sha256:3d9f342735dd97d58952ac3f4561cdc71c0c0c9059886d5a9cc7fe87c3f5dd3f +size 12955 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 af9954116a..d50b18799f 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:0166ad5236ecdcc943d839fad092fe3899dcd4e418703846c492edb7700e4726 -size 10682 +oid sha256:b3c5370823f6f2a80a843967d541afdbb38779e2e56d629e5e011c3b35c7060d +size 10928 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 5b8c5127c0..2cc4e0207d 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:aaeee39c61b86d9ce569ca2288f998b8461a3f2169dac23cf2f750dd475d8b81 -size 14145 +oid sha256:603b127cbb2c4134a2f7cde964dcdc37ab7f8ba4e7eef01df09ac1d8aba02346 +size 14262 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 93fa5c1de3..1188593ceb 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:5908ff88ddaa6eb3faea6174d87b0182e4407b11812ad70ddcd39c6619b6a5c5 -size 12615 +oid sha256:05796c5a4b91edf69d39ec63160651b86d296d67d7a1424059741e93d5da8307 +size 12904 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 af2345fe52..d5218089a1 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:b6852daae665638e38c0b7ff58b2a0de1d5df9dd771c5cbccbbb83ff78e6a1d7 -size 12741 +oid sha256:dc3087514a1a569e206280f5855af412301a432176c7731ca77ff7af725a18ed +size 13349 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 3f91a9259c..b954c7ee7e 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:6d86473ff1024fc53373b1dba49fc14283b8a323d6b85ba3e16f41ebff8288d0 -size 12845 +oid sha256:353472eefdd16b56ef623cdc0ad95b8741496b2a52484fecc89a2b7e53e33d54 +size 13619 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 af9954116a..d50b18799f 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:0166ad5236ecdcc943d839fad092fe3899dcd4e418703846c492edb7700e4726 -size 10682 +oid sha256:b3c5370823f6f2a80a843967d541afdbb38779e2e56d629e5e011c3b35c7060d +size 10928 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 878a36a477..3a45bec62b 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:b2bd11fa19fab712b5cd6c2b36d673c7dce904b5032b860d257b00e095e4aadf -size 13432 +oid sha256:ec6a6dfa0ae752d5e13439fe78365b7c84b4deb6c5d85fc151a61c69df01a48d +size 13824 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 dba9232097..eaf7e8241d 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:faa91657288e6a6797d8459d41d5fecca3c0a2e8e63317ebaf47df28688d13d7 -size 13853 +oid sha256:4baf0e7bc4ae8b8a911d87f3a7af2bf3ef0235f77f3f509251f2d2f26cfb639d +size 13158 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 ea062d5be6..1c131b0017 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:79e48506430f3a9b25f484ef191fd820819c438392a4e588c2ecafb6db9a2210 -size 13775 +oid sha256:bac98b38fa3d0029341d9ad1a4325b1957c353734dec225605a07fb64662802a +size 13219 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 ae90ea9b5f..ae6bfc9fa7 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:f56c884a0e4666cd662d36ec3a0d4e751c899c0122595378154507fffc69fda4 -size 14010 +oid sha256:28881d29b70b83facf59e4dd6d4c396412f65b322503af57ee2fbbbee8031e53 +size 13474 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 dba9232097..eaf7e8241d 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:faa91657288e6a6797d8459d41d5fecca3c0a2e8e63317ebaf47df28688d13d7 -size 13853 +oid sha256:4baf0e7bc4ae8b8a911d87f3a7af2bf3ef0235f77f3f509251f2d2f26cfb639d +size 13158 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 1e1795063b..0d85aeab5d 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:4095927693b3cd49df58c0c1d7c5430255350c9ae595408a52ad83b1a65614ac -size 14269 +oid sha256:d18dda730014d92b7e5718762c457d013a5c0b1086ef076ed398d656697607ba +size 13849 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 29a3ed7ffd..cd728c99bc 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:d062d4b79ee01942776ae13467e9bcbb529a7eeb5ad7c28ff3d0ccd3d88dcde6 -size 15962 +oid sha256:83208a455e42ef4d097c3db84535a5e83f1b84d26b0294d9f374b20c4987d1df +size 15810 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 50fa46d169..faf1a0d92d 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:47b2265af41ba042904cab387bf1de4715bd4d8a318bc6c1f69bfdbff5eabe2c -size 16928 +oid sha256:3b0b886fa479a9d57f21d39d8e72db0cc075207fadfdcb2d293c4daea206e848 +size 17167 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 5d1030e6b8..c81658d0b5 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:6679d6d6f7c8b44461956b54654cea71180a2b0d43712d3775e60cbedd90cc82 -size 17520 +oid sha256:4cb5b4d98d13b0212c0e52547c8e402889c43b48f1a7368b5d54e96d25f61ed2 +size 18026 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 dba9232097..eaf7e8241d 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:faa91657288e6a6797d8459d41d5fecca3c0a2e8e63317ebaf47df28688d13d7 -size 13853 +oid sha256:4baf0e7bc4ae8b8a911d87f3a7af2bf3ef0235f77f3f509251f2d2f26cfb639d +size 13158 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 567e5d6a3b..b2126744f3 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:5af5d16f875172d73f8426928fc8edaa4a6cab321a968b6c29fca32d0fba0df5 -size 18182 +oid sha256:a24c69dc99d0a35afb91b17f256df55e774149e53df81e9102ba57725c5f7791 +size 18097 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 09c471914a..624d5cc670 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:a40b319d264f046159722cb57599eda51de9ba3795272b3785901cdc51053fab -size 83010 +oid sha256:33e5d119018fcb0ff3ad4bdf0efa947ec9ef8a0a92a3dba64fe5c65b7a046737 +size 84080 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 3bd7cbabbb..5e1556dc45 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:3bc93509a983e20986614f4937f66d5d979bbb433a30a7736150934cf14b452a -size 55213 +oid sha256:af9e6c3b9e9e90186fb66be188bad9f3f0738d558aab915b3c8dd78652010674 +size 55419 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 34490e602d..6b174dd34a 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:b92f3320120d53444cefc79b4684933cfe2b933dc79c2414496785743b5c8f18 -size 80808 +oid sha256:d35750b85b062eae398db3788e416e9b6e229ce53f9f2413b244497012fd9931 +size 85369 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 40243937d3..51c93894de 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:b63810145832db459bb7a6b37a028a7b778f6b6b4e6eae00e50e6e21c5a06086 -size 62199 +oid sha256:a836c8efd7aa9818cf807cf56412e78399a6568798be23d0f3f6b89552856ff1 +size 62172 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 5e9fa12332..c48268b955 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:f90db3ce2153cc9ba4d1d79e5749dc4d49e916dff8a0e121ebce9b00702cfcc8 -size 33880 +oid sha256:fdf1ebcbf951a82b4899c12c1e24eb28c84694dac52d390b54b6db8a06949ddf +size 33901 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 68a95a0540..c9ad188dc1 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:8af98bfcc5edef3f3ff33ee8f76f33ce2906a6677167e2b29e1dbe63b00a78d8 -size 44202 +oid sha256:a049a50155bf56c53a1b74e919806cbb83716842b5c0a233c44c87b3630115e0 +size 44394 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 96c66aad72..7cfe595a16 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:828b082a1892f0200ef84254637b340b1276e1bee44e01c6b715de8838e4818f -size 35301 +oid sha256:1739c75759f8714526bbb6bff1df02aa1e327f2e22b50e3514903ec1fc672aa7 +size 35332 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 3ff151f6d0..ff91ab181f 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:f70d1aa2f985dfb7227ea5fd7b4b98effc1a31c89fd05bbee9cfa8f003b9cb4e -size 34261 +oid sha256:7c1d2789714f291746e254815df3001655c4bae2c9c02a08b9a913e87bec5036 +size 34335 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 10daff76b2..987f01d0a3 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:d8ba00e2948337f77d935d98349958c6a520958671e9ec714ff1bfadfb130e72 -size 44622 +oid sha256:ec42f78f2ce5d3afc68988646d8ddd1059797c6cebf327997709831470db62c5 +size 44446 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 747ca70c1d..5860c0eacc 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:d58c425ce5b1ca56450095a66dea24b379935b0087aec7b4102f15a99f95a017 -size 101999 +oid sha256:b979180d0e4c4c644bc39d87f17d5d5b7a7dac12cb830622f3db2cd6ef2dfdc0 +size 106317 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 de464b94cc..e72ea4b246 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:93a4822e39babba059a88536a965e4f3207e4402d2b92d7d18485fec5e9e69da -size 84378 +oid sha256:bf2021eba9edbb2295924f8394472ac0bb237f0c462c39aa32a2074ef15f9acc +size 81771 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 ce54548279..deda6d6db8 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:35969c8dc96de4dacc3048ae760a0681278a2011993a0edbceaacc93d6fc3a67 -size 102713 +oid sha256:d470b839a21cab2e528d8f00086dc4f289d2e74cfc1f4b37abc1dabb42e6d92b +size 102771 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 5efcaedc94..34a6c57902 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:40d012f4ecb4e36c94d086f8ec7bc199fbfd9fb30a9427a07b35df1b1e430a71 -size 95601 +oid sha256:4c0fd89358099a05ca78b3656c6d0dc7c670b373021899e9e1bf9d40c2a63000 +size 98386 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 916dc37566..5293046724 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:fa64863f73dfd1c5daef645c54e9275136f66513a87750bee0ec8e13ac357da5 -size 79649 +oid sha256:c4b59097d1507236af2556ae5f2638360b223b7752cd4c8f760bc14673d811d0 +size 81709 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 f039dd222e..0f56d5156c 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:8f5138589c606de20ba193d4279f049ee1ecb3f1801b949d3436995bbf242cbe -size 92683 +oid sha256:4b3fc79c254c1decea8c947ebe959f256baa18534084f9b37099369870021a6e +size 94870 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 2b897a5d6d..2d6226d096 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:007ac609ec61b39c7bdd04bc87a698f5cdc76eadd834c1457f41eb9c135c3f7b -size 60688 +oid sha256:89864a77216b51cc5b9415453ade7f7ec64c1c112546aa47ee6b4b89f9b258a3 +size 60543 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 e40a91cbc4..7b842e1f78 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:6fc2f82bdbf4b204ad78f3bb54bfdea7452a2d1430814f45262fd309225f2fc0 -size 46727 +oid sha256:e2982181e3ca61a0e6e5e27a3909d7003dc784d6bf51800159dc274ce058eb49 +size 47214 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 ac56fa9236..bf0604356f 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:6d2289ed4fa0c679f0f120d260fec8ab40b1599043cc0a1fbebc6b67e238ff87 -size 51428 +oid sha256:6937822a02885fc236f5520e947081883d1ccbdb3da04821d0da133e1f98d98e +size 51009 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 8b79a19e05..fd27271129 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:ef2b6073b75a2de97a78d47d3b3e40c264687c5756f153d3d85bc5b2714cf85a -size 68226 +oid sha256:96abf20f6b75757da529a661a2567a74527ffe8391615e34ae8f27ceaf381dba +size 67973 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 8d0d2b60db..2bad1f1f25 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:ac1424c6c4c18feb42106e14da6b161ce3f48276d0aa6603ca60ad5caa0a5338 -size 63764 +oid sha256:be280b1be360e42b2eb2f8270d900a7f268e75a9ba1828fe0985925a7add6192 +size 64155 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 88cf83a306..f8962d9907 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:513844ed95c2b50e792d3346398256846b8b280dbadf7ef3f4e11d58c1e679c0 -size 69529 +oid sha256:d4f9bd6f79db363966fcf99ed12064500efe7a91ec6a87ef8720a7958ef7b06e +size 69758 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 a3eefcba20..d8a04a055d 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:32b269d62d4eebe555d5d9f12b9958b41206848504bb985dcd1ff9c81a5003c6 -size 117073 +oid sha256:156319efa8874050ed646bf3a8b7dfd2aa7eef56c9d31fad3c408b418608ff69 +size 111255 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 3b0c46ac38..691623fc88 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:12f58b00a16913cd85ffa18fcea580a59550dcc201295b060d55a870230f37f7 -size 113995 +oid sha256:58a61c1d9a1d05acd484948c3e5c0496dbc74c0060f5de71741de39eae04ffa8 +size 103875 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 328f863307..6b11fd3ead 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:867d7b727de278cbc01b7d2b8e968f1fc0d0a81a3e4af636ce4a6598a8709be6 -size 114630 +oid sha256:31132b4f8d744bd63a1395ac97e2efcad924c564b382de759b504b9d8a977e5b +size 110214 diff --git a/tests/Images/Input/Gif/issues/issue_2866.gif b/tests/Images/Input/Gif/issues/issue_2866.gif new file mode 100644 index 0000000000..0ead86bf89 --- /dev/null +++ b/tests/Images/Input/Gif/issues/issue_2866.gif @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0b2a9e3728c41e1b45d6f865e4692eadbed28dcaec65806e6bda22a9a16f930f +size 7526725 diff --git a/tests/Images/Input/Png/issues/issue_2469-i.png b/tests/Images/Input/Png/issues/issue_2469-i.png new file mode 100644 index 0000000000..bd651a3f2d --- /dev/null +++ b/tests/Images/Input/Png/issues/issue_2469-i.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d4e0da0601ca5f479684359633c4dbd82881a35631d63477c01e8fd180e31482 +size 2521324 diff --git a/tests/Images/Input/Webp/issues/Issue2866.webp b/tests/Images/Input/Webp/issues/Issue2866.webp new file mode 100644 index 0000000000..845569624d --- /dev/null +++ b/tests/Images/Input/Webp/issues/Issue2866.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:15e8a52a6d528fe071e73b037543b682bf62da7bab6d98ab690f25dd97f7298e +size 248688 From 19ea81575f8d4fe8d797646e13e256b125ee5351 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Wed, 26 Feb 2025 22:54:43 +1000 Subject: [PATCH 02/18] Use explicit ReadOnlySpan to fix CI build with preview lang --- src/ImageSharp/Formats/Png/PngEncoderCore.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/ImageSharp/Formats/Png/PngEncoderCore.cs b/src/ImageSharp/Formats/Png/PngEncoderCore.cs index 45d7d1270b..3f7f1e5623 100644 --- a/src/ImageSharp/Formats/Png/PngEncoderCore.cs +++ b/src/ImageSharp/Formats/Png/PngEncoderCore.cs @@ -1255,7 +1255,8 @@ private void EncodePixels(in Buffer2DRegion pixels, IndexedImage Span attempt = attemptBuffer.GetSpan(); for (int y = 0; y < pixels.Height; y++) { - this.CollectAndFilterPixelRow(pixels.DangerousGetRowSpan(y), ref filter, ref attempt, quantized, y); + ReadOnlySpan rowSpan = pixels.DangerousGetRowSpan(y); + this.CollectAndFilterPixelRow(rowSpan, ref filter, ref attempt, quantized, y); deflateStream.Write(filter); this.SwapScanlineBuffers(); } @@ -1303,7 +1304,8 @@ private void EncodeAdam7Pixels(in Buffer2DRegion pixels, ZlibDef // Encode data // Note: quantized parameter not used // Note: row parameter not used - this.CollectAndFilterPixelRow(block, ref filter, ref attempt, null, -1); + ReadOnlySpan blockSpan = block; + this.CollectAndFilterPixelRow(blockSpan, ref filter, ref attempt, null, -1); deflateStream.Write(filter); this.SwapScanlineBuffers(); From e73349562822cb57d5c7fbc64b767d25aad4b08f Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Wed, 26 Feb 2025 23:03:45 +1000 Subject: [PATCH 03/18] remove old unused type --- .../Quantization/OctreeQuantizer{TPixel}.cs | 475 ------------------ 1 file changed, 475 deletions(-) diff --git a/src/ImageSharp/Processing/Processors/Quantization/OctreeQuantizer{TPixel}.cs b/src/ImageSharp/Processing/Processors/Quantization/OctreeQuantizer{TPixel}.cs index be00bc433a..65e814e620 100644 --- a/src/ImageSharp/Processing/Processors/Quantization/OctreeQuantizer{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Quantization/OctreeQuantizer{TPixel}.cs @@ -654,479 +654,4 @@ public static int GetColorIndex(Rgba32 color, int level) } } } - - /// - /// Class which does the actual quantization. - /// - private sealed class Octree2 - { - /// - /// The root of the Octree2 - /// - private readonly OctreeNode root; - - /// - /// Maximum number of significant bits in the image - /// - private readonly int maxColorBits; - - /// - /// The threshold for transparent colors. - /// - private readonly int transparencyThreshold; - - /// - /// Store the last node quantized - /// - private OctreeNode? previousNode; - - /// - /// Cache the previous color quantized - /// - private Rgba32 previousColor; - - /// - /// Initializes a new instance of the class. - /// - /// - /// The maximum number of significant bits in the image - /// - /// The threshold for transparent colors. - public Octree2(int maxColorBits, int transparencyThreshold) - { - this.maxColorBits = maxColorBits; - this.transparencyThreshold = transparencyThreshold; - this.Leaves = 0; - this.ReducibleNodes = new OctreeNode[9]; - this.root = new OctreeNode(0, this.maxColorBits, this); - this.previousColor = default; - this.previousNode = null; - } - - /// - /// Gets or sets the number of leaves in the tree - /// - public int Leaves - { - [MethodImpl(InliningOptions.ShortMethod)] - get; - - [MethodImpl(InliningOptions.ShortMethod)] - set; - } - - /// - /// Gets the array of reducible nodes - /// - private OctreeNode?[] ReducibleNodes - { - [MethodImpl(InliningOptions.ShortMethod)] - get; - } - - /// - /// Add a given color value to the Octree2 - /// - /// The color to add. - public void AddColor(Rgba32 color) - { - if (color.A < this.transparencyThreshold) - { - color.A = 0; - } - - // Check if this request is for the same color as the last - if (this.previousColor.Equals(color)) - { - // If so, check if I have a previous node setup. - if (this.previousNode is null) - { - this.previousColor = color; - this.root.AddColor(color, this.maxColorBits, 0, this); - } - else - { - // Just update the previous node - this.previousNode.Increment(color, this); - } - } - else - { - this.previousColor = color; - this.root.AddColor(color, this.maxColorBits, 0, this); - } - } - - /// - /// Convert the nodes in the Octree2 to a palette with a maximum of colorCount colors - /// - /// The palette to fill. - /// The maximum number of colors - /// The palette index, used to calculate the final size of the palette. - [MethodImpl(InliningOptions.ShortMethod)] - public void Palettize(Span palette, int colorCount, ref int paletteIndex) - { - while (this.Leaves > colorCount) - { - this.Reduce(); - } - - this.root.ConstructPalette(palette, ref paletteIndex); - } - - /// - /// Get the palette index for the passed color - /// - /// The color to match. - /// - /// The index. - /// - [MethodImpl(InliningOptions.ShortMethod)] - public int GetPaletteIndex(TPixel color) - => this.root.GetPaletteIndex(color.ToRgba32(), 0); - - /// - /// Keep track of the previous node that was quantized - /// - /// - /// The node last quantized - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void TrackPrevious(OctreeNode node) => this.previousNode = node; - - /// - /// Reduce the depth of the tree - /// - private void Reduce() - { - // Find the deepest level containing at least one reducible node - int index = this.maxColorBits - 1; - while ((index > 0) && (this.ReducibleNodes[index] is null)) - { - index--; - } - - // Reduce the node most recently added to the list at level 'index' - OctreeNode node = this.ReducibleNodes[index]!; - this.ReducibleNodes[index] = node.NextReducible; - - // Decrement the leaf count after reducing the node - this.Leaves -= node.Reduce(this); - - // And just in case I've reduced the last color to be added, and the next color to - // be added is the same, invalidate the previousNode... - this.previousNode = null; - } - - /// - /// Class which encapsulates each node in the tree - /// - public sealed class OctreeNode - { - /// - /// Pointers to any child nodes - /// - private readonly OctreeNode?[]? children; - - /// - /// Flag indicating that this is a leaf node - /// - private bool leaf; - - /// - /// Number of pixels in this node - /// - private int pixelCount; - - /// - /// Red component - /// - private int red; - - /// - /// Green Component - /// - private int green; - - /// - /// Blue component - /// - private int blue; - - /// - /// Alpha component - /// - private int alpha; - - /// - /// The index of this node in the palette - /// - private int paletteIndex; - - /// - /// Initializes a new instance of the class. - /// - /// The level in the tree = 0 - 7. - /// The number of significant color bits in the image. - /// The tree to which this node belongs. - public OctreeNode(int level, int colorBits, Octree2 octree) - { - // Construct the new node - this.leaf = level == colorBits; - - this.red = this.green = this.blue = this.alpha = 0; - this.pixelCount = 0; - - // If a leaf, increment the leaf count - if (this.leaf) - { - octree.Leaves++; - this.NextReducible = null; - this.children = null; - } - else - { - // Otherwise add this to the reducible nodes - this.NextReducible = octree.ReducibleNodes[level]; - octree.ReducibleNodes[level] = this; - this.children = new OctreeNode[16]; - } - } - - /// - /// Gets the next reducible node - /// - public OctreeNode? NextReducible - { - [MethodImpl(InliningOptions.ShortMethod)] - get; - } - - /// - /// Add a color into the tree - /// - /// The color to add. - /// The number of significant color bits. - /// The level in the tree. - /// The tree to which this node belongs. - public void AddColor(Rgba32 color, int colorBits, int level, Octree2 octree) - { - // Update the color information if this is a leaf - if (this.leaf) - { - this.Increment(color, octree); - - // Setup the previous node - octree.TrackPrevious(this); - } - else - { - // Go to the next level down in the tree - int index = GetColorIndex(color, level); - - OctreeNode? child = this.children![index]; - if (child is null) - { - // Create a new child node and store it in the array - child = new OctreeNode(level + 1, colorBits, octree); - this.children[index] = child; - } - - // Add the color to the child node - child.AddColor(color, colorBits, level + 1, octree); - } - } - - /// - /// Reduce this node by removing all of its children. - /// - /// The number of leaves removed - /// The tree to which this node belongs. - public int Reduce(Octree2 octree) - { - if (this.leaf) - { - return 1; - } - - int childNodes = 0; - int sumRed = 0, sumGreen = 0, sumBlue = 0, sumAlpha = 0, pixelCount = 0; - - // Loop through all children. - for (int index = 0; index < this.children!.Length; index++) - { - OctreeNode? child = this.children[index]; - if (child != null) - { - childNodes++; - - sumRed += child.red; - sumGreen += child.green; - sumBlue += child.blue; - sumAlpha += child.alpha; - pixelCount += child.pixelCount; - - // Remove the child reference. - this.children[index] = null; - } - } - - if (pixelCount > 0) - { - int offset = pixelCount >> 1; - this.red = sumRed; - this.green = sumGreen; - this.blue = sumBlue; - this.alpha = ((sumAlpha + offset) / pixelCount < octree.transparencyThreshold) ? 0 : sumAlpha; - this.pixelCount = pixelCount; - } - else - { - this.red = this.green = this.blue = this.alpha = 0; - this.pixelCount = 0; - } - - // Convert this node into a leaf. - this.leaf = true; - - // Return the number of nodes merged (for decrementing the leaf count). - return childNodes - 1; - } - - /// - /// Traverse the tree, building up the color palette - /// - /// The palette - /// The current palette index - [MethodImpl(InliningOptions.ColdPath)] - public void ConstructPalette(Span palette, ref int index) - { - if (this.leaf) - { - // Set the color of the palette entry - Vector4 sum = new(this.red, this.green, this.blue, this.alpha); - Vector4 offset = new(this.pixelCount >> 1); - - Vector4 vector = Vector4.Clamp( - (sum + offset) / this.pixelCount, - Vector4.Zero, - new Vector4(255)); - - palette[index] = TPixel.FromRgba32(new Rgba32((byte)vector.X, (byte)vector.Y, (byte)vector.Z, (byte)vector.W)); - - // Consume the next palette index - this.paletteIndex = index++; - } - else - { - // Loop through children looking for leaves - for (int i = 0; i < this.children!.Length; i++) - { - this.children[i]?.ConstructPalette(palette, ref index); - } - } - } - - /// - /// Return the palette index for the passed color - /// - /// The pixel data. - /// The level. - /// - /// The representing the index of the pixel in the palette. - /// - [MethodImpl(InliningOptions.ColdPath)] - public int GetPaletteIndex(Rgba32 pixel, int level) - { - if (this.leaf) - { - return this.paletteIndex; - } - - int colorIndex = GetColorIndex(pixel, level); - OctreeNode? child = this.children![colorIndex]; - - int index = -1; - if (child != null) - { - index = child.GetPaletteIndex(pixel, level + 1); - } - else - { - // Check other children. - for (int i = 0; i < this.children.Length; i++) - { - child = this.children[i]; - if (child != null) - { - int childIndex = child.GetPaletteIndex(pixel, level + 1); - if (childIndex != -1) - { - return childIndex; - } - } - } - } - - return index; - } - - /// - /// Gets the color index at the given level. - /// - /// The color. - /// The node level. - /// The index. - [MethodImpl(InliningOptions.ShortMethod)] - private static int GetColorIndex(Rgba32 color, int level) - { - int shift = 7 - level; - byte mask = (byte)(1 << shift); - - // Compute luminance of the RGB channels. - int luminance = ColorNumerics.Get8BitBT709Luminance(color.R, color.G, color.B); - - // Shift the threshold (arbitrary) right by the current level. - // This allows us to partition the RGB space into smaller and smaller cubes - // with increasing accuracy for the alpha component. - int darkThreshold = 24 >> level; - - // For fully opaque and bright pixels, ignore the alpha bit to achieve finer RGB partitioning. - // For dark pixels, include the alpha bit so that dark drop shadows remain distinct. - if (color.A == 255 && luminance > darkThreshold) - { - int rBits = ((color.R & mask) >> shift) << 2; - int gBits = ((color.G & mask) >> shift) << 1; - int bBits = (color.B & mask) >> shift; - return rBits | gBits | bBits; - } - else - { - int aBits = ((color.A & mask) >> shift) << 3; - int rBits = ((color.R & mask) >> shift) << 2; - int gBits = ((color.G & mask) >> shift) << 1; - int bBits = (color.B & mask) >> shift; - return aBits | rBits | gBits | bBits; - } - } - - /// - /// Increment the color count and add to the color information - /// - /// The pixel to add. - /// The parent octree. - [MethodImpl(InliningOptions.ShortMethod)] - public void Increment(Rgba32 color, Octree2 octree) - { - this.pixelCount++; - this.red += color.R; - this.green += color.G; - this.blue += color.B; - - int sumAlpha = this.alpha + color.A; - int offset = this.pixelCount >> 1; - this.alpha = ((sumAlpha + offset) / this.pixelCount < octree.transparencyThreshold) ? 0 : sumAlpha; - } - } - } } From 05c552524b1862c4b64c40ab14c8e7001932c1de Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Thu, 27 Feb 2025 20:48:31 +1000 Subject: [PATCH 04/18] Threshold on the way out also. --- .../Quantization/WuQuantizer{TPixel}.cs | 20 +++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/src/ImageSharp/Processing/Processors/Quantization/WuQuantizer{TPixel}.cs b/src/ImageSharp/Processing/Processors/Quantization/WuQuantizer{TPixel}.cs index e637c2cf04..db6490259f 100644 --- a/src/ImageSharp/Processing/Processors/Quantization/WuQuantizer{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Quantization/WuQuantizer{TPixel}.cs @@ -74,7 +74,8 @@ internal struct WuQuantizer : IQuantizer private readonly IMemoryOwner paletteOwner; private ReadOnlyMemory palette; private int maxColors; - private short transparencyThreshold; + private readonly float transparencyThreshold; + private readonly short transparencyThreshold255; private readonly Box[] colorCube; private EuclideanPixelMap? pixelMap; private readonly bool isDithering; @@ -102,8 +103,9 @@ public WuQuantizer(Configuration configuration, QuantizerOptions options) this.isDisposed = false; this.pixelMap = default; this.palette = default; - this.isDithering = this.isDithering = this.Options.Dither is not null; - this.transparencyThreshold = (short)(this.Options.TransparencyThreshold * 255); + this.isDithering = this.Options.Dither is not null; + this.transparencyThreshold = this.Options.TransparencyThreshold; + this.transparencyThreshold255 = (short)(this.Options.TransparencyThreshold * 255); } /// @@ -152,7 +154,13 @@ private void ResolvePalette() Moment moment = Volume(ref this.colorCube[k], momentsSpan); if (moment.Weight > 0) { - paletteSpan[k] = TPixel.FromScaledVector4(moment.Normalize()); + Vector4 normalized = moment.Normalize(); + if (normalized.W < this.transparencyThreshold) + { + normalized = Vector4.Zero; + } + + paletteSpan[k] = TPixel.FromScaledVector4(normalized); } } @@ -179,11 +187,11 @@ public readonly byte GetQuantizedColor(TPixel color, out TPixel match) // In this case, we must use the pixel map to get the closest color. if (this.isDithering) { - return (byte)this.pixelMap!.GetClosestColor(color, out match, this.transparencyThreshold); + return (byte)this.pixelMap!.GetClosestColor(color, out match, this.transparencyThreshold255); } Rgba32 rgba = color.ToRgba32(); - if (rgba.A < this.transparencyThreshold) + if (rgba.A < this.transparencyThreshold255) { rgba = default; } From c573228b0900ea160103e5383a9bdd2802de3323 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Mon, 17 Mar 2025 16:52:45 +1000 Subject: [PATCH 05/18] Upstream fixes. --- .../Formats/Cur/CurFrameMetadata.cs | 2 - src/ImageSharp/Formats/Cur/CurMetadata.cs | 6 +- .../Formats/FormatConnectingFrameMetadata.cs | 5 - .../Formats/FormatConnectingMetadata.cs | 5 - src/ImageSharp/Formats/Gif/GifDecoderCore.cs | 101 +++++++++++++----- .../Formats/Gif/GifFrameMetadata.cs | 26 +---- src/ImageSharp/Formats/Gif/GifMetadata.cs | 37 +------ .../Formats/Ico/IcoFrameMetadata.cs | 4 +- src/ImageSharp/Formats/Ico/IcoMetadata.cs | 6 +- .../Formats/Icon/IconEncoderCore.cs | 16 ++- src/ImageSharp/Formats/Png/PngEncoderCore.cs | 3 +- src/ImageSharp/Formats/Png/PngMetadata.cs | 21 ---- .../Formats/Webp/Lossy/Vp8Decoder.cs | 10 +- .../Quantization/EuclideanPixelMap{TPixel}.cs | 10 +- .../Formats/Gif/GifDecoderTests.cs | 35 ++++++ .../Formats/Gif/GifEncoderTests.cs | 12 ++- .../Formats/Icon/Cur/CurEncoderTests.cs | 5 +- .../Formats/Png/PngEncoderTests.cs | 32 +++--- .../Formats/WebP/WebpEncoderTests.cs | 59 ++++++++++ tests/ImageSharp.Tests/TestImages.cs | 30 ++++++ .../TestUtilities/TestImageExtensions.cs | 91 ++++++++++++++++ .../00.png | 3 + .../01.png | 3 + .../02.png | 3 + .../03.png | 3 + .../00.png | 3 + .../01.png | 3 + .../02.png | 3 + .../03.png | 3 + .../00.png | 3 + .../01.png | 3 + .../02.png | 3 + .../03.png | 3 + .../00.png | 3 + .../01.png | 3 + .../02.png | 3 + .../03.png | 3 + .../00.png | 3 + .../01.png | 3 + .../02.png | 3 + .../03.png | 3 + .../00.png | 3 + .../01.png | 3 + .../02.png | 3 + .../03.png | 3 + ...Animation_Rgba32_static_nontransparent.png | 3 + ...No_Animation_Rgba32_static_transparent.png | 3 + ...2012BadMinCode_Rgba32_issue2012_drona1.png | 4 +- .../00.png | 4 +- .../01.png | 4 +- .../02.png | 4 +- .../03.png | 4 +- .../04.png | 4 +- .../05.png | 4 +- .../06.png | 4 +- .../07.png | 4 +- .../00.gif | 3 + .../08.gif | 3 + .../104.gif | 3 + .../112.gif | 3 + .../16.gif | 3 + .../24.gif | 3 + .../32.gif | 3 + .../40.gif | 3 + .../48.gif | 3 + .../56.gif | 3 + .../64.gif | 3 + .../72.gif | 3 + .../80.gif | 3 + .../88.gif | 3 + .../96.gif | 3 + .../00.png | 3 + .../08.png | 3 + .../104.png | 3 + .../112.png | 3 + .../16.png | 3 + .../24.png | 3 + .../32.png | 3 + .../40.png | 3 + .../48.png | 3 + .../56.png | 3 + .../64.png | 3 + .../72.png | 3 + .../80.png | 3 + .../88.png | 3 + .../96.png | 3 + .../00.png | 3 + .../08.png | 3 + tests/Images/Input/Gif/animated_loop.gif | 3 + .../Input/Gif/animated_loop_interlaced.gif | 3 + ...transparent_firstframerestoreprev_loop.gif | 3 + ...mated_transparent_frame_norestore_loop.gif | 3 + ...ansparent_frame_restorebackground_loop.gif | 3 + ...ted_transparent_frame_restoreprev_loop.gif | 3 + .../Input/Gif/animated_transparent_loop.gif | 3 + .../animated_transparent_restoreprev_loop.gif | 3 + .../Input/Gif/static_nontransparent.gif | 3 + tests/Images/Input/Gif/static_transparent.gif | 3 + tests/Images/Input/Webp/alpha-blend-2.webp | 3 + tests/Images/Input/Webp/alpha-blend-3.webp | 3 + tests/Images/Input/Webp/alpha-blend-4.webp | 3 + tests/Images/Input/Webp/alpha-blend.webp | 3 + 102 files changed, 587 insertions(+), 181 deletions(-) create mode 100644 tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Animated_Rgba32_animated_loop.gif/00.png create mode 100644 tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Animated_Rgba32_animated_loop.gif/01.png create mode 100644 tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Animated_Rgba32_animated_loop.gif/02.png create mode 100644 tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Animated_Rgba32_animated_loop.gif/03.png create mode 100644 tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Animated_Rgba32_animated_loop_interlaced.gif/00.png create mode 100644 tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Animated_Rgba32_animated_loop_interlaced.gif/01.png create mode 100644 tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Animated_Rgba32_animated_loop_interlaced.gif/02.png create mode 100644 tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Animated_Rgba32_animated_loop_interlaced.gif/03.png create mode 100644 tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Animated_WithTransparency_Rgba32_animated_transparent_firstframerestoreprev_loop.gif/00.png create mode 100644 tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Animated_WithTransparency_Rgba32_animated_transparent_firstframerestoreprev_loop.gif/01.png create mode 100644 tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Animated_WithTransparency_Rgba32_animated_transparent_firstframerestoreprev_loop.gif/02.png create mode 100644 tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Animated_WithTransparency_Rgba32_animated_transparent_firstframerestoreprev_loop.gif/03.png create mode 100644 tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Animated_WithTransparency_Rgba32_animated_transparent_frame_norestore_loop.gif/00.png create mode 100644 tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Animated_WithTransparency_Rgba32_animated_transparent_frame_norestore_loop.gif/01.png create mode 100644 tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Animated_WithTransparency_Rgba32_animated_transparent_frame_norestore_loop.gif/02.png create mode 100644 tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Animated_WithTransparency_Rgba32_animated_transparent_frame_norestore_loop.gif/03.png create mode 100644 tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Animated_WithTransparency_Rgba32_animated_transparent_frame_restoreprev_loop.gif/00.png create mode 100644 tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Animated_WithTransparency_Rgba32_animated_transparent_frame_restoreprev_loop.gif/01.png create mode 100644 tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Animated_WithTransparency_Rgba32_animated_transparent_frame_restoreprev_loop.gif/02.png create mode 100644 tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Animated_WithTransparency_Rgba32_animated_transparent_frame_restoreprev_loop.gif/03.png create mode 100644 tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Animated_WithTransparency_Rgba32_animated_transparent_loop.gif/00.png create mode 100644 tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Animated_WithTransparency_Rgba32_animated_transparent_loop.gif/01.png create mode 100644 tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Animated_WithTransparency_Rgba32_animated_transparent_loop.gif/02.png create mode 100644 tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Animated_WithTransparency_Rgba32_animated_transparent_loop.gif/03.png create mode 100644 tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Static_No_Animation_Rgba32_static_nontransparent.png create mode 100644 tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Static_No_Animation_Rgba32_static_transparent.png create mode 100644 tests/Images/External/ReferenceOutput/GifEncoderTests/GifEncoder_CanDecode_AndEncode_Issue2866_Rgba32_issue_2866.gif/00.gif create mode 100644 tests/Images/External/ReferenceOutput/GifEncoderTests/GifEncoder_CanDecode_AndEncode_Issue2866_Rgba32_issue_2866.gif/08.gif create mode 100644 tests/Images/External/ReferenceOutput/GifEncoderTests/GifEncoder_CanDecode_AndEncode_Issue2866_Rgba32_issue_2866.gif/104.gif create mode 100644 tests/Images/External/ReferenceOutput/GifEncoderTests/GifEncoder_CanDecode_AndEncode_Issue2866_Rgba32_issue_2866.gif/112.gif create mode 100644 tests/Images/External/ReferenceOutput/GifEncoderTests/GifEncoder_CanDecode_AndEncode_Issue2866_Rgba32_issue_2866.gif/16.gif create mode 100644 tests/Images/External/ReferenceOutput/GifEncoderTests/GifEncoder_CanDecode_AndEncode_Issue2866_Rgba32_issue_2866.gif/24.gif create mode 100644 tests/Images/External/ReferenceOutput/GifEncoderTests/GifEncoder_CanDecode_AndEncode_Issue2866_Rgba32_issue_2866.gif/32.gif create mode 100644 tests/Images/External/ReferenceOutput/GifEncoderTests/GifEncoder_CanDecode_AndEncode_Issue2866_Rgba32_issue_2866.gif/40.gif create mode 100644 tests/Images/External/ReferenceOutput/GifEncoderTests/GifEncoder_CanDecode_AndEncode_Issue2866_Rgba32_issue_2866.gif/48.gif create mode 100644 tests/Images/External/ReferenceOutput/GifEncoderTests/GifEncoder_CanDecode_AndEncode_Issue2866_Rgba32_issue_2866.gif/56.gif create mode 100644 tests/Images/External/ReferenceOutput/GifEncoderTests/GifEncoder_CanDecode_AndEncode_Issue2866_Rgba32_issue_2866.gif/64.gif create mode 100644 tests/Images/External/ReferenceOutput/GifEncoderTests/GifEncoder_CanDecode_AndEncode_Issue2866_Rgba32_issue_2866.gif/72.gif create mode 100644 tests/Images/External/ReferenceOutput/GifEncoderTests/GifEncoder_CanDecode_AndEncode_Issue2866_Rgba32_issue_2866.gif/80.gif create mode 100644 tests/Images/External/ReferenceOutput/GifEncoderTests/GifEncoder_CanDecode_AndEncode_Issue2866_Rgba32_issue_2866.gif/88.gif create mode 100644 tests/Images/External/ReferenceOutput/GifEncoderTests/GifEncoder_CanDecode_AndEncode_Issue2866_Rgba32_issue_2866.gif/96.gif create mode 100644 tests/Images/External/ReferenceOutput/PngEncoderTests/Encode_AnimatedFormatTransform_FromGif_Rgba32_issue_2866.gif/00.png create mode 100644 tests/Images/External/ReferenceOutput/PngEncoderTests/Encode_AnimatedFormatTransform_FromGif_Rgba32_issue_2866.gif/08.png create mode 100644 tests/Images/External/ReferenceOutput/PngEncoderTests/Encode_AnimatedFormatTransform_FromGif_Rgba32_issue_2866.gif/104.png create mode 100644 tests/Images/External/ReferenceOutput/PngEncoderTests/Encode_AnimatedFormatTransform_FromGif_Rgba32_issue_2866.gif/112.png create mode 100644 tests/Images/External/ReferenceOutput/PngEncoderTests/Encode_AnimatedFormatTransform_FromGif_Rgba32_issue_2866.gif/16.png create mode 100644 tests/Images/External/ReferenceOutput/PngEncoderTests/Encode_AnimatedFormatTransform_FromGif_Rgba32_issue_2866.gif/24.png create mode 100644 tests/Images/External/ReferenceOutput/PngEncoderTests/Encode_AnimatedFormatTransform_FromGif_Rgba32_issue_2866.gif/32.png create mode 100644 tests/Images/External/ReferenceOutput/PngEncoderTests/Encode_AnimatedFormatTransform_FromGif_Rgba32_issue_2866.gif/40.png create mode 100644 tests/Images/External/ReferenceOutput/PngEncoderTests/Encode_AnimatedFormatTransform_FromGif_Rgba32_issue_2866.gif/48.png create mode 100644 tests/Images/External/ReferenceOutput/PngEncoderTests/Encode_AnimatedFormatTransform_FromGif_Rgba32_issue_2866.gif/56.png create mode 100644 tests/Images/External/ReferenceOutput/PngEncoderTests/Encode_AnimatedFormatTransform_FromGif_Rgba32_issue_2866.gif/64.png create mode 100644 tests/Images/External/ReferenceOutput/PngEncoderTests/Encode_AnimatedFormatTransform_FromGif_Rgba32_issue_2866.gif/72.png create mode 100644 tests/Images/External/ReferenceOutput/PngEncoderTests/Encode_AnimatedFormatTransform_FromGif_Rgba32_issue_2866.gif/80.png create mode 100644 tests/Images/External/ReferenceOutput/PngEncoderTests/Encode_AnimatedFormatTransform_FromGif_Rgba32_issue_2866.gif/88.png create mode 100644 tests/Images/External/ReferenceOutput/PngEncoderTests/Encode_AnimatedFormatTransform_FromGif_Rgba32_issue_2866.gif/96.png create mode 100644 tests/Images/External/ReferenceOutput/PngEncoderTests/Encode_AnimatedFormatTransform_FromGif_Rgba32_leo.gif/00.png create mode 100644 tests/Images/External/ReferenceOutput/PngEncoderTests/Encode_AnimatedFormatTransform_FromGif_Rgba32_leo.gif/08.png create mode 100644 tests/Images/Input/Gif/animated_loop.gif create mode 100644 tests/Images/Input/Gif/animated_loop_interlaced.gif create mode 100644 tests/Images/Input/Gif/animated_transparent_firstframerestoreprev_loop.gif create mode 100644 tests/Images/Input/Gif/animated_transparent_frame_norestore_loop.gif create mode 100644 tests/Images/Input/Gif/animated_transparent_frame_restorebackground_loop.gif create mode 100644 tests/Images/Input/Gif/animated_transparent_frame_restoreprev_loop.gif create mode 100644 tests/Images/Input/Gif/animated_transparent_loop.gif create mode 100644 tests/Images/Input/Gif/animated_transparent_restoreprev_loop.gif create mode 100644 tests/Images/Input/Gif/static_nontransparent.gif create mode 100644 tests/Images/Input/Gif/static_transparent.gif create mode 100644 tests/Images/Input/Webp/alpha-blend-2.webp create mode 100644 tests/Images/Input/Webp/alpha-blend-3.webp create mode 100644 tests/Images/Input/Webp/alpha-blend-4.webp create mode 100644 tests/Images/Input/Webp/alpha-blend.webp diff --git a/src/ImageSharp/Formats/Cur/CurFrameMetadata.cs b/src/ImageSharp/Formats/Cur/CurFrameMetadata.cs index f1ebec72f5..9854854aad 100644 --- a/src/ImageSharp/Formats/Cur/CurFrameMetadata.cs +++ b/src/ImageSharp/Formats/Cur/CurFrameMetadata.cs @@ -104,7 +104,6 @@ public static CurFrameMetadata FromFormatConnectingFrameMetadata(FormatConnectin Compression = compression, EncodingWidth = ClampEncodingDimension(metadata.EncodingWidth), EncodingHeight = ClampEncodingDimension(metadata.EncodingHeight), - ColorTable = compression == IconFrameCompression.Bmp ? metadata.ColorTable : null }; } @@ -113,7 +112,6 @@ public FormatConnectingFrameMetadata ToFormatConnectingFrameMetadata() => new() { PixelTypeInfo = this.GetPixelTypeInfo(), - ColorTable = this.ColorTable, EncodingWidth = this.EncodingWidth, EncodingHeight = this.EncodingHeight }; diff --git a/src/ImageSharp/Formats/Cur/CurMetadata.cs b/src/ImageSharp/Formats/Cur/CurMetadata.cs index 5c725e2916..d8fdb32902 100644 --- a/src/ImageSharp/Formats/Cur/CurMetadata.cs +++ b/src/ImageSharp/Formats/Cur/CurMetadata.cs @@ -71,8 +71,7 @@ public static CurMetadata FromFormatConnectingMetadata(FormatConnectingMetadata return new CurMetadata { BmpBitsPerPixel = bbpp, - Compression = compression, - ColorTable = compression == IconFrameCompression.Bmp ? metadata.ColorTable : null + Compression = compression }; } @@ -145,8 +144,7 @@ public FormatConnectingMetadata ToFormatConnectingMetadata() EncodingType = this.Compression == IconFrameCompression.Bmp && this.BmpBitsPerPixel <= BmpBitsPerPixel.Bit8 ? EncodingType.Lossy : EncodingType.Lossless, - PixelTypeInfo = this.GetPixelTypeInfo(), - ColorTable = this.ColorTable + PixelTypeInfo = this.GetPixelTypeInfo() }; /// diff --git a/src/ImageSharp/Formats/FormatConnectingFrameMetadata.cs b/src/ImageSharp/Formats/FormatConnectingFrameMetadata.cs index ded220c9ad..15a28c301a 100644 --- a/src/ImageSharp/Formats/FormatConnectingFrameMetadata.cs +++ b/src/ImageSharp/Formats/FormatConnectingFrameMetadata.cs @@ -15,11 +15,6 @@ public class FormatConnectingFrameMetadata /// public PixelTypeInfo? PixelTypeInfo { get; init; } - /// - /// Gets the frame color table if any. - /// - public ReadOnlyMemory? ColorTable { get; init; } - /// /// Gets the frame color table mode. /// diff --git a/src/ImageSharp/Formats/FormatConnectingMetadata.cs b/src/ImageSharp/Formats/FormatConnectingMetadata.cs index 9cfe40f385..efa7acdc86 100644 --- a/src/ImageSharp/Formats/FormatConnectingMetadata.cs +++ b/src/ImageSharp/Formats/FormatConnectingMetadata.cs @@ -28,11 +28,6 @@ public class FormatConnectingMetadata /// public PixelTypeInfo PixelTypeInfo { get; init; } - /// - /// Gets the shared color table if any. - /// - public ReadOnlyMemory? ColorTable { get; init; } - /// /// Gets the shared color table mode. /// diff --git a/src/ImageSharp/Formats/Gif/GifDecoderCore.cs b/src/ImageSharp/Formats/Gif/GifDecoderCore.cs index e9012436e9..f6e3643d58 100644 --- a/src/ImageSharp/Formats/Gif/GifDecoderCore.cs +++ b/src/ImageSharp/Formats/Gif/GifDecoderCore.cs @@ -90,9 +90,9 @@ internal sealed class GifDecoderCore : ImageDecoderCore private GifMetadata? gifMetadata; /// - /// The background color used to fill the frame. + /// The background color index. /// - private Color backgroundColor; + private byte backgroundColorIndex; /// /// Initializes a new instance of the class. @@ -115,11 +115,11 @@ protected override Image Decode(BufferedReadStream stream, Cance ImageFrame? previousFrame = null; FrameDisposalMode? previousDisposalMode = null; bool globalColorTableUsed = false; + Color backgroundColor = Color.Transparent; try { this.ReadLogicalScreenDescriptorAndGlobalColorTable(stream); - TPixel backgroundPixel = this.backgroundColor.ToPixel(); // Loop though the respective gif parts and read the data. int nextFlag = stream.ReadByte(); @@ -132,7 +132,7 @@ protected override Image Decode(BufferedReadStream stream, Cance break; } - globalColorTableUsed |= this.ReadFrame(stream, ref image, ref previousFrame, ref previousDisposalMode, backgroundPixel); + globalColorTableUsed |= this.ReadFrame(stream, ref image, ref previousFrame, ref previousDisposalMode, ref backgroundColor); // Reset per-frame state. this.imageDescriptor = default; @@ -442,14 +442,14 @@ private void ReadComments(BufferedReadStream stream) /// The image to decode the information to. /// The previous frame. /// The previous frame disposal mode. - /// The background color pixel. + /// The background color. /// Whether the frame has a global color table. private bool ReadFrame( BufferedReadStream stream, ref Image? image, ref ImageFrame? previousFrame, ref FrameDisposalMode? previousDisposalMode, - TPixel backgroundPixel) + ref Color backgroundColor) where TPixel : unmanaged, IPixel { this.ReadImageDescriptor(stream); @@ -471,7 +471,47 @@ private bool ReadFrame( } ReadOnlySpan colorTable = MemoryMarshal.Cast(rawColorTable); - this.ReadFrameColors(stream, ref image, ref previousFrame, ref previousDisposalMode, colorTable, backgroundPixel); + + // First frame + if (image is null) + { + if (this.backgroundColorIndex < colorTable.Length) + { + backgroundColor = Color.FromPixel(colorTable[this.backgroundColorIndex]); + } + else + { + backgroundColor = Color.Transparent; + } + + if (this.graphicsControlExtension.TransparencyFlag) + { + backgroundColor = backgroundColor.WithAlpha(0); + } + } + + this.ReadFrameColors(stream, ref image, ref previousFrame, ref previousDisposalMode, colorTable, backgroundColor.ToPixel()); + + // Update from newly decoded frame. + if (this.graphicsControlExtension.DisposalMethod != FrameDisposalMode.RestoreToPrevious) + { + if (this.backgroundColorIndex < colorTable.Length) + { + backgroundColor = Color.FromPixel(colorTable[this.backgroundColorIndex]); + } + else + { + backgroundColor = Color.Transparent; + } + + // TODO: I don't understand why this is always set to alpha of zero. + // This should be dependent on the transparency flag of the graphics + // control extension. ImageMagick does the same. + // if (this.graphicsControlExtension.TransparencyFlag) + { + backgroundColor = backgroundColor.WithAlpha(0); + } + } // Skip any remaining blocks SkipBlock(stream); @@ -504,8 +544,9 @@ private void ReadFrameColors( bool transFlag = this.graphicsControlExtension.TransparencyFlag; FrameDisposalMode disposalMethod = this.graphicsControlExtension.DisposalMethod; ImageFrame currentFrame; + ImageFrame? restoreFrame = null; - if (previousFrame is null) + if (previousFrame is null && previousDisposalMode is null) { image = transFlag ? new Image(this.configuration, imageWidth, imageHeight, this.metadata) @@ -516,26 +557,42 @@ private void ReadFrameColors( } else { - // We create a clone of the frame and add it. - // We will overpaint the difference of pixels on the current frame to create a complete image. - // This ensures that we have enough pixel data to process without distortion. #2450 - currentFrame = image!.Frames.AddFrame(previousFrame); + if (previousFrame != null) + { + currentFrame = image!.Frames.AddFrame(previousFrame); + } + else + { + currentFrame = image!.Frames.CreateFrame(backgroundPixel); + } this.SetFrameMetadata(currentFrame.Metadata); + if (this.graphicsControlExtension.DisposalMethod == FrameDisposalMode.RestoreToPrevious) + { + restoreFrame = previousFrame; + } + if (previousDisposalMode == FrameDisposalMode.RestoreToBackground) { this.RestoreToBackground(currentFrame, backgroundPixel, transFlag); } } - Rectangle interest = Rectangle.Intersect(image.Bounds, new(descriptor.Left, descriptor.Top, descriptor.Width, descriptor.Height)); - previousFrame = currentFrame; + if (this.graphicsControlExtension.DisposalMethod == FrameDisposalMode.RestoreToPrevious) + { + previousFrame = restoreFrame; + } + else + { + previousFrame = currentFrame; + } + previousDisposalMode = disposalMethod; if (disposalMethod == FrameDisposalMode.RestoreToBackground) { - this.restoreArea = interest; + this.restoreArea = Rectangle.Intersect(image.Bounds, new(descriptor.Left, descriptor.Top, descriptor.Width, descriptor.Height)); } if (colorTable.Length == 0) @@ -814,19 +871,9 @@ private void ReadLogicalScreenDescriptorAndGlobalColorTable(BufferedReadStream s } } - // If the global color table is present, we can set the background color - // otherwise we default to transparent to match browser behavior. - ReadOnlyMemory? table = this.gifMetadata.GlobalColorTable; byte index = this.logicalScreenDescriptor.BackgroundColorIndex; - if (table is not null && index < table.Value.Length) - { - this.backgroundColor = table.Value.Span[index]; - this.gifMetadata.BackgroundColorIndex = index; - } - else - { - this.backgroundColor = Color.Transparent; - } + this.backgroundColorIndex = index; + this.gifMetadata.BackgroundColorIndex = index; } private unsafe struct ScratchBuffer diff --git a/src/ImageSharp/Formats/Gif/GifFrameMetadata.cs b/src/ImageSharp/Formats/Gif/GifFrameMetadata.cs index 92bd114e8f..e1b3354ad2 100644 --- a/src/ImageSharp/Formats/Gif/GifFrameMetadata.cs +++ b/src/ImageSharp/Formats/Gif/GifFrameMetadata.cs @@ -1,7 +1,6 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System.Numerics; using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Formats.Gif; @@ -77,34 +76,12 @@ private GifFrameMetadata(GifFrameMetadata other) /// public static GifFrameMetadata FromFormatConnectingFrameMetadata(FormatConnectingFrameMetadata metadata) - { - int index = -1; - const float background = 1f; - if (metadata.ColorTable.HasValue) + => new() { - ReadOnlySpan colorTable = metadata.ColorTable.Value.Span; - for (int i = 0; i < colorTable.Length; i++) - { - Vector4 vector = colorTable[i].ToScaledVector4(); - if (vector.W < background) - { - index = i; - } - } - } - - bool hasTransparency = index >= 0; - - return new() - { - LocalColorTable = metadata.ColorTable, ColorTableMode = metadata.ColorTableMode, FrameDelay = (int)Math.Round(metadata.Duration.TotalMilliseconds / 10), DisposalMode = metadata.DisposalMode, - HasTransparency = hasTransparency, - TransparencyIndex = hasTransparency ? unchecked((byte)index) : byte.MinValue, }; - } /// public FormatConnectingFrameMetadata ToFormatConnectingFrameMetadata() @@ -118,7 +95,6 @@ public FormatConnectingFrameMetadata ToFormatConnectingFrameMetadata() return new() { - ColorTable = this.LocalColorTable, ColorTableMode = this.ColorTableMode, Duration = TimeSpan.FromMilliseconds(this.FrameDelay * 10), DisposalMode = this.DisposalMode, diff --git a/src/ImageSharp/Formats/Gif/GifMetadata.cs b/src/ImageSharp/Formats/Gif/GifMetadata.cs index fc6c9ab9a4..77f600633b 100644 --- a/src/ImageSharp/Formats/Gif/GifMetadata.cs +++ b/src/ImageSharp/Formats/Gif/GifMetadata.cs @@ -71,32 +71,14 @@ private GifMetadata(GifMetadata other) /// public static GifMetadata FromFormatConnectingMetadata(FormatConnectingMetadata 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]) - { - continue; - } - - index = i; - break; - } - } - - return new() + => new() { - GlobalColorTable = metadata.ColorTable, + // Do not copy the color table or bit depth. + // This will lead to a mismatch when the image is comprised of frames + // extracted individually from a multi-frame image. ColorTableMode = metadata.ColorTableMode, RepeatCount = metadata.RepeatCount, - BackgroundColorIndex = (byte)Numerics.Clamp(index, 0, 255), }; - } /// public PixelTypeInfo GetPixelTypeInfo() @@ -114,22 +96,13 @@ public PixelTypeInfo GetPixelTypeInfo() /// public FormatConnectingMetadata ToFormatConnectingMetadata() - { - bool global = this.ColorTableMode == FrameColorTableMode.Global; - Color color = global && this.GlobalColorTable.HasValue && this.GlobalColorTable.Value.Span.Length > this.BackgroundColorIndex - ? this.GlobalColorTable.Value.Span[this.BackgroundColorIndex] - : Color.Transparent; - - return new FormatConnectingMetadata() + => new() { AnimateRootFrame = true, - ColorTable = global ? this.GlobalColorTable : null, - BackgroundColor = color, ColorTableMode = this.ColorTableMode, PixelTypeInfo = this.GetPixelTypeInfo(), RepeatCount = this.RepeatCount, }; - } /// public void AfterImageApply(Image destination) diff --git a/src/ImageSharp/Formats/Ico/IcoFrameMetadata.cs b/src/ImageSharp/Formats/Ico/IcoFrameMetadata.cs index 77096d5241..31f65133e6 100644 --- a/src/ImageSharp/Formats/Ico/IcoFrameMetadata.cs +++ b/src/ImageSharp/Formats/Ico/IcoFrameMetadata.cs @@ -96,8 +96,7 @@ public static IcoFrameMetadata FromFormatConnectingFrameMetadata(FormatConnectin BmpBitsPerPixel = bbpp, Compression = compression, EncodingWidth = ClampEncodingDimension(metadata.EncodingWidth), - EncodingHeight = ClampEncodingDimension(metadata.EncodingHeight), - ColorTable = compression == IconFrameCompression.Bmp ? metadata.ColorTable : null + EncodingHeight = ClampEncodingDimension(metadata.EncodingHeight) }; } @@ -106,7 +105,6 @@ public FormatConnectingFrameMetadata ToFormatConnectingFrameMetadata() => new() { PixelTypeInfo = this.GetPixelTypeInfo(), - ColorTable = this.ColorTable, EncodingWidth = this.EncodingWidth, EncodingHeight = this.EncodingHeight }; diff --git a/src/ImageSharp/Formats/Ico/IcoMetadata.cs b/src/ImageSharp/Formats/Ico/IcoMetadata.cs index 09c1da1b1a..f8c2ff40f2 100644 --- a/src/ImageSharp/Formats/Ico/IcoMetadata.cs +++ b/src/ImageSharp/Formats/Ico/IcoMetadata.cs @@ -71,8 +71,7 @@ public static IcoMetadata FromFormatConnectingMetadata(FormatConnectingMetadata return new IcoMetadata { BmpBitsPerPixel = bbpp, - Compression = compression, - ColorTable = compression == IconFrameCompression.Bmp ? metadata.ColorTable : null + Compression = compression }; } @@ -145,8 +144,7 @@ public FormatConnectingMetadata ToFormatConnectingMetadata() EncodingType = this.Compression == IconFrameCompression.Bmp && this.BmpBitsPerPixel <= BmpBitsPerPixel.Bit8 ? EncodingType.Lossy : EncodingType.Lossless, - PixelTypeInfo = this.GetPixelTypeInfo(), - ColorTable = this.ColorTable + PixelTypeInfo = this.GetPixelTypeInfo() }; /// diff --git a/src/ImageSharp/Formats/Icon/IconEncoderCore.cs b/src/ImageSharp/Formats/Icon/IconEncoderCore.cs index 03e01f912f..76b14832ae 100644 --- a/src/ImageSharp/Formats/Icon/IconEncoderCore.cs +++ b/src/ImageSharp/Formats/Icon/IconEncoderCore.cs @@ -120,17 +120,17 @@ private void InitHeader(Image image) this.entries = this.iconFileType switch { IconFileType.ICO => - image.Frames.Select(i => + [.. image.Frames.Select(i => { IcoFrameMetadata metadata = i.Metadata.GetIcoMetadata(); return new EncodingFrameMetadata(metadata.Compression, metadata.BmpBitsPerPixel, metadata.ColorTable, metadata.ToIconDirEntry(i.Size)); - }).ToArray(), + })], IconFileType.CUR => - image.Frames.Select(i => + [.. image.Frames.Select(i => { CurFrameMetadata metadata = i.Metadata.GetCurMetadata(); return new EncodingFrameMetadata(metadata.Compression, metadata.BmpBitsPerPixel, metadata.ColorTable, metadata.ToIconDirEntry(i.Size)); - }).ToArray(), + })], _ => throw new NotSupportedException(), }; } @@ -149,9 +149,15 @@ private void InitHeader(Image image) if (metadata.ColorTable is null) { + int count = metadata.Entry.ColorCount; + if (count == 0) + { + count = 256; + } + return new WuQuantizer(new() { - MaxColors = metadata.Entry.ColorCount + MaxColors = count }); } diff --git a/src/ImageSharp/Formats/Png/PngEncoderCore.cs b/src/ImageSharp/Formats/Png/PngEncoderCore.cs index 3f7f1e5623..b90a9975d3 100644 --- a/src/ImageSharp/Formats/Png/PngEncoderCore.cs +++ b/src/ImageSharp/Formats/Png/PngEncoderCore.cs @@ -1570,7 +1570,8 @@ private void SanitizeAndSetEncoderOptions( } else { - // Don't use transparency threshold for quantization PNG can handle multiple transparent colors. + // Don't use the default transparency threshold for quantization as PNG can handle multiple transparent colors. + // We choose a value that is close to zero so that edge cases causes by lower bit depths for the alpha channel are handled correctly. this.quantizer = new WuQuantizer(new QuantizerOptions { TransparencyThreshold = 0, MaxColors = ColorNumerics.GetColorCountForBitDepth(bitDepth) }); } } diff --git a/src/ImageSharp/Formats/Png/PngMetadata.cs b/src/ImageSharp/Formats/Png/PngMetadata.cs index bb80438ba0..59ca3b17a0 100644 --- a/src/ImageSharp/Formats/Png/PngMetadata.cs +++ b/src/ImageSharp/Formats/Png/PngMetadata.cs @@ -93,25 +93,6 @@ private PngMetadata(PngMetadata other) /// public static PngMetadata FromFormatConnectingMetadata(FormatConnectingMetadata 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. - Color[]? colorTable = metadata.ColorTable?.ToArray(); - if (colorTable != null) - { - for (int i = 0; i < colorTable.Length; i++) - { - ref Color c = ref colorTable[i]; - if (c != metadata.BackgroundColor) - { - continue; - } - - // Png treats background as fully empty - c = Color.Transparent; - break; - } - } - PngColorType color; PixelColorType colorType = metadata.PixelTypeInfo.ColorType; @@ -152,7 +133,6 @@ public static PngMetadata FromFormatConnectingMetadata(FormatConnectingMetadata { ColorType = color, BitDepth = bitDepth, - ColorTable = colorTable, RepeatCount = metadata.RepeatCount, }; } @@ -241,7 +221,6 @@ public PixelTypeInfo GetPixelTypeInfo() public FormatConnectingMetadata ToFormatConnectingMetadata() => new() { - ColorTable = this.ColorTable, ColorTableMode = FrameColorTableMode.Global, PixelTypeInfo = this.GetPixelTypeInfo(), RepeatCount = (ushort)Numerics.Clamp(this.RepeatCount, 0, ushort.MaxValue), diff --git a/src/ImageSharp/Formats/Webp/Lossy/Vp8Decoder.cs b/src/ImageSharp/Formats/Webp/Lossy/Vp8Decoder.cs index eb4a517511..3c8bafa1b2 100644 --- a/src/ImageSharp/Formats/Webp/Lossy/Vp8Decoder.cs +++ b/src/ImageSharp/Formats/Webp/Lossy/Vp8Decoder.cs @@ -69,11 +69,11 @@ public Vp8Decoder(Vp8FrameHeader frameHeader, Vp8PictureHeader pictureHeader, Vp this.YuvBuffer = memoryAllocator.Allocate((WebpConstants.Bps * 17) + (WebpConstants.Bps * 9) + extraY); this.CacheY = memoryAllocator.Allocate((16 * this.CacheYStride) + extraY, AllocationOptions.Clean); int cacheUvSize = (16 * this.CacheUvStride) + extraUv; - this.CacheU = memoryAllocator.Allocate(cacheUvSize, AllocationOptions.Clean); - this.CacheV = memoryAllocator.Allocate(cacheUvSize, AllocationOptions.Clean); - this.TmpYBuffer = memoryAllocator.Allocate((int)width, AllocationOptions.Clean); - this.TmpUBuffer = memoryAllocator.Allocate((int)width, AllocationOptions.Clean); - this.TmpVBuffer = memoryAllocator.Allocate((int)width, AllocationOptions.Clean); + this.CacheU = memoryAllocator.Allocate(cacheUvSize); + this.CacheV = memoryAllocator.Allocate(cacheUvSize); + this.TmpYBuffer = memoryAllocator.Allocate((int)width); + this.TmpUBuffer = memoryAllocator.Allocate((int)width); + this.TmpVBuffer = memoryAllocator.Allocate((int)width); this.Pixels = memoryAllocator.Allocate((int)(width * height * 4), AllocationOptions.Clean); #if DEBUG diff --git a/src/ImageSharp/Processing/Processors/Quantization/EuclideanPixelMap{TPixel}.cs b/src/ImageSharp/Processing/Processors/Quantization/EuclideanPixelMap{TPixel}.cs index d11376e3ba..250122e683 100644 --- a/src/ImageSharp/Processing/Processors/Quantization/EuclideanPixelMap{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Quantization/EuclideanPixelMap{TPixel}.cs @@ -116,7 +116,7 @@ private int GetClosestColorSlow(Rgba32 rgba, ref TPixel paletteRef, out TPixel m } // Now I have the index, pop it into the cache for next time - this.cache.Add(rgba, (byte)index); + this.cache.Add(rgba, (short)index); match = Unsafe.Add(ref paletteRef, (uint)index); return index; @@ -131,9 +131,11 @@ private int GetClosestColorSlow(Rgba32 rgba, ref TPixel paletteRef, out TPixel m [MethodImpl(MethodImplOptions.AggressiveInlining)] private static float DistanceSquared(Rgba32 a, Rgba32 b) { - Vector4 va = new(a.R, a.G, a.B, a.A); - Vector4 vb = new(b.R, b.G, b.B, b.A); - return Vector4.DistanceSquared(va, vb); + float deltaR = a.R - b.R; + float deltaG = a.G - b.G; + float deltaB = a.B - b.B; + float deltaA = a.A - b.A; + return (deltaR * deltaR) + (deltaG * deltaG) + (deltaB * deltaB) + (deltaA * deltaA); } public void Dispose() => this.cache.Dispose(); diff --git a/tests/ImageSharp.Tests/Formats/Gif/GifDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Gif/GifDecoderTests.cs index bc6eeedcbe..6593b8df70 100644 --- a/tests/ImageSharp.Tests/Formats/Gif/GifDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Gif/GifDecoderTests.cs @@ -34,6 +34,41 @@ public void Decode_VerifyAllFrames(TestImageProvider provider) image.CompareToReferenceOutputMultiFrame(provider, ImageComparer.Exact); } + [Theory] + [WithFile(TestImages.Gif.AnimatedLoop, PixelTypes.Rgba32)] + [WithFile(TestImages.Gif.AnimatedLoopInterlaced, PixelTypes.Rgba32)] + public void Decode_Animated(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using Image image = provider.GetImage(); + image.DebugSaveMultiFrame(provider); + image.CompareToReferenceOutputMultiFrame(provider, ImageComparer.Exact); + } + + [Theory] + [WithFile(TestImages.Gif.AnimatedTransparentNoRestore, PixelTypes.Rgba32)] + [WithFile(TestImages.Gif.AnimatedTransparentRestorePrevious, PixelTypes.Rgba32)] + [WithFile(TestImages.Gif.AnimatedTransparentLoop, PixelTypes.Rgba32)] + [WithFile(TestImages.Gif.AnimatedTransparentFirstFrameRestorePrev, PixelTypes.Rgba32)] + public void Decode_Animated_WithTransparency(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using Image image = provider.GetImage(); + image.DebugSaveMultiFrame(provider); + image.CompareToReferenceOutputMultiFrame(provider, ImageComparer.Exact); + } + + [Theory] + [WithFile(TestImages.Gif.StaticNontransparent, PixelTypes.Rgba32)] + [WithFile(TestImages.Gif.StaticTransparent, PixelTypes.Rgba32)] + public void Decode_Static_No_Animation(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using Image image = provider.GetImage(); + image.DebugSave(provider); + image.CompareFirstFrameToReferenceOutput(ImageComparer.Exact, provider); + } + [Theory] [WithFile(TestImages.Gif.Issues.Issue2450_A, PixelTypes.Rgba32)] [WithFile(TestImages.Gif.Issues.Issue2450_B, PixelTypes.Rgba32)] diff --git a/tests/ImageSharp.Tests/Formats/Gif/GifEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Gif/GifEncoderTests.cs index d219f551b2..748f505cce 100644 --- a/tests/ImageSharp.Tests/Formats/Gif/GifEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Gif/GifEncoderTests.cs @@ -422,12 +422,18 @@ public void Encode_WithTransparentColorBehaviorClear_Works() [Theory] [WithFile(TestImages.Gif.Issues.Issue2866, PixelTypes.Rgba32)] - public void GifEncoder_CanDecode_Issue2866(TestImageProvider provider) - where TPixel : unmanaged, IPixel + public void GifEncoder_CanDecode_AndEncode_Issue2866(TestImageProvider provider) + where TPixel : unmanaged, IPixel { using Image image = provider.GetImage(); - // image.DebugSaveMultiFrame(provider); + // Save the image for visual inspection. provider.Utility.SaveTestOutputFile(image, "gif", new GifEncoder(), "animated"); + + // Now compare the debug output with the reference output. + // We do this because the gif encoding is lossy and encoding will lead to differences in the 10s of percent. + // From the unencoded image, we can see that the image is visually the same. + static bool Predicate(int i, int _) => i % 8 == 0; // Image has many frames, only compare a selection of them. + image.CompareDebugOutputToReferenceOutputMultiFrame(provider, ImageComparer.Exact, extension: "gif", predicate: Predicate); } } diff --git a/tests/ImageSharp.Tests/Formats/Icon/Cur/CurEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Icon/Cur/CurEncoderTests.cs index bf94e1d489..69c6317a75 100644 --- a/tests/ImageSharp.Tests/Formats/Icon/Cur/CurEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Icon/Cur/CurEncoderTests.cs @@ -4,6 +4,7 @@ using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Formats.Cur; using SixLabors.ImageSharp.Formats.Ico; +using SixLabors.ImageSharp.Formats.Png; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; using static SixLabors.ImageSharp.Tests.TestImages.Cur; @@ -49,8 +50,8 @@ public void CanConvertFromIco(TestImageProvider provider) using Image encoded = Image.Load(memStream); encoded.DebugSaveMultiFrame(provider); - // Despite preservation of the palette. The process can still be lossy - encoded.CompareToOriginalMultiFrame(provider, ImageComparer.TolerantPercentage(.23f), IcoDecoder.Instance); + // Color palettes are not preserved when transcoding. + encoded.CompareToOriginalMultiFrame(provider, ImageComparer.TolerantPercentage(.05F), IcoDecoder.Instance); for (int i = 0; i < image.Frames.Count; i++) { diff --git a/tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs index 298d5b788e..2a53e4de79 100644 --- a/tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs @@ -442,40 +442,42 @@ public void Encode_APng(TestImageProvider provider) } [Theory] - [WithFile(TestImages.Gif.Leo, PixelTypes.Rgba32, 0.613F)] - [WithFile(TestImages.Gif.Issues.Issue2866, PixelTypes.Rgba32, 1.06F)] - public void Encode_AnimatedFormatTransform_FromGif(TestImageProvider provider, float percentage) + [WithFile(TestImages.Gif.Leo, PixelTypes.Rgba32)] + [WithFile(TestImages.Gif.Issues.Issue2866, PixelTypes.Rgba32)] + public void Encode_AnimatedFormatTransform_FromGif(TestImageProvider provider) where TPixel : unmanaged, IPixel { - if (TestEnvironment.RunsOnCI) + if (TestEnvironment.RunsOnCI && !TestEnvironment.IsWindows) { return; } using Image image = provider.GetImage(GifDecoder.Instance); + // Save the image for visual inspection. + provider.Utility.SaveTestOutputFile(image, "png", PngEncoder, "animated"); + + // Now compare the debug output with the reference output. + // We do this because the transcoding encoding is lossy and encoding will lead to differences. + // From the unencoded image, we can see that the image is visually the same. + static bool Predicate(int i, int _) => i % 8 == 0; // Image has many frames, only compare a selection of them. + image.CompareDebugOutputToReferenceOutputMultiFrame(provider, ImageComparer.Exact, extension: "png", encoder: PngEncoder, predicate: Predicate); + + // Now save the image and load it again to compare the metadata. using MemoryStream memStream = new(); image.Save(memStream, PngEncoder); memStream.Position = 0; - image.DebugSave(provider: provider, extension: "png", encoder: PngEncoder); - - using Image output = Image.Load(memStream); - - // TODO: Find a better way to compare. - // The image has been visually checked but the coarse cache used by the palette quantizer - // can lead to minor differences between frames. - ImageComparer.TolerantPercentage(percentage).VerifySimilarity(output, image); - + using Image encoded = Image.Load(memStream); GifMetadata gif = image.Metadata.GetGifMetadata(); - PngMetadata png = output.Metadata.GetPngMetadata(); + PngMetadata png = encoded.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(); + PngFrameMetadata pngF = encoded.Frames[i].Metadata.GetPngMetadata(); Assert.Equal(gifF.FrameDelay, (int)(pngF.FrameDelay.ToDouble() * 100)); diff --git a/tests/ImageSharp.Tests/Formats/WebP/WebpEncoderTests.cs b/tests/ImageSharp.Tests/Formats/WebP/WebpEncoderTests.cs index f82fa65df8..af6f7eea17 100644 --- a/tests/ImageSharp.Tests/Formats/WebP/WebpEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/WebP/WebpEncoderTests.cs @@ -8,6 +8,8 @@ using SixLabors.ImageSharp.Formats.Webp; using SixLabors.ImageSharp.Metadata; using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing; +using SixLabors.ImageSharp.Processing.Processors.Quantization; using SixLabors.ImageSharp.Tests.TestUtilities; using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; using SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs; @@ -110,6 +112,63 @@ public void Encode_AnimatedFormatTransform_FromGif(TestImageProvider(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using Image image = provider.GetImage(); + WebpEncoder encoder = new() + { + FileFormat = WebpFileFormatType.Lossless + }; + + QuantizerOptions options = new() + { + TransparencyThreshold = 128 / 255F + }; + + // First save as gif to gif using different quantizers with default options. + // Alpha thresholding is 64/255F. + GifEncoder gifEncoder = new() + { + Quantizer = new OctreeQuantizer(options) + }; + provider.Utility.SaveTestOutputFile(image, "gif", gifEncoder, "octree"); + + gifEncoder = new GifEncoder() + { + Quantizer = new WuQuantizer(options) + }; + provider.Utility.SaveTestOutputFile(image, "gif", gifEncoder, "wu"); + + // Now clone and quantize the image using the same quantizers without alpha thresholding and save as webp. + options = new() + { + TransparencyThreshold = 0 + }; + + using Image cloned1 = image.Clone(); + cloned1.Mutate(c => c.Quantize(new OctreeQuantizer(options))); + provider.Utility.SaveTestOutputFile(cloned1, "webp", encoder, "octree"); + + using Image cloned2 = image.Clone(); + cloned2.Mutate(c => c.Quantize(new WuQuantizer(options))); + provider.Utility.SaveTestOutputFile(cloned2, "webp", encoder, "wu"); + + // Now blend the images with a blue background and save as webp. + using Image background1 = new(image.Width, image.Height, Color.White.ToPixel()); + background1.Mutate(c => c.DrawImage(cloned1, 1)); + provider.Utility.SaveTestOutputFile(background1, "webp", encoder, "octree-blended"); + + using Image background2 = new(image.Width, image.Height, Color.White.ToPixel()); + background2.Mutate(c => c.DrawImage(cloned2, 1)); + provider.Utility.SaveTestOutputFile(background2, "webp", encoder, "wu-blended"); + } + [Theory] [WithFile(TestImages.Png.APng, PixelTypes.Rgba32)] public void Encode_AnimatedFormatTransform_FromPng(TestImageProvider provider) diff --git a/tests/ImageSharp.Tests/TestImages.cs b/tests/ImageSharp.Tests/TestImages.cs index 462d54154e..abaec4997c 100644 --- a/tests/ImageSharp.Tests/TestImages.cs +++ b/tests/ImageSharp.Tests/TestImages.cs @@ -508,6 +508,31 @@ public static class Gif public const string Bit18RGBCube = "Gif/18-bit_RGB_Cube.gif"; public const string Global256NoTrans = "Gif/global-256-no-trans.gif"; + // Test images from: https://github.com/peterdn/gif-test-suite.git + // Animated gif with 4 frames, looping forever, no transparency. + public const string AnimatedLoop = "Gif/animated_loop.gif"; + + // Animated gif with 4 frames, interlaced, looping forever, no transparency. + public const string AnimatedLoopInterlaced = "Gif/animated_loop_interlaced.gif"; + + // Transparent gif with 4 frames, loops forever. + public const string AnimatedTransparentLoop = "Gif/animated_transparent_loop.gif"; + + // Transparent gif with 4 frames, loops forever, first frame restore previous. + public const string AnimatedTransparentFirstFrameRestorePrev = "Gif/animated_transparent_firstframerestoreprev_loop.gif"; + + // Transparent gif with 4 transparent frames, loops forever, no dispose + public const string AnimatedTransparentNoRestore = "Gif/animated_transparent_frame_norestore_loop.gif"; + + // Transparent gif with 4 transparent frames, loops forever, restore previous. + public const string AnimatedTransparentRestorePrevious = "Gif/animated_transparent_frame_restoreprev_loop.gif"; + + // Static gif with no animation, no transparency. + public const string StaticNontransparent = "Gif/static_nontransparent.gif"; + + // Static transparent gif with no animation. + public const string StaticTransparent = "Gif/static_transparent.gif"; + // Test images from https://github.com/robert-ancell/pygif/tree/master/test-suite public const string ZeroSize = "Gif/image-zero-size.gif"; public const string ZeroHeight = "Gif/image-zero-height.gif"; @@ -833,6 +858,11 @@ public static class Lossy public const string Issue2801 = "Webp/issues/Issue2801.webp"; public const string Issue2866 = "Webp/issues/Issue2866.webp"; } + + public const string AlphaBlend = "Webp/alpha-blend.webp"; + public const string AlphaBlend2 = "Webp/alpha-blend-2.webp"; + public const string AlphaBlend3 = "Webp/alpha-blend-3.webp"; + public const string AlphaBlend4 = "Webp/alpha-blend-4.webp"; } public static class Tiff diff --git a/tests/ImageSharp.Tests/TestUtilities/TestImageExtensions.cs b/tests/ImageSharp.Tests/TestUtilities/TestImageExtensions.cs index 05abedbd8e..263df8f3a6 100644 --- a/tests/ImageSharp.Tests/TestUtilities/TestImageExtensions.cs +++ b/tests/ImageSharp.Tests/TestUtilities/TestImageExtensions.cs @@ -107,6 +107,7 @@ public static Image DebugSaveMultiFrame( ITestImageProvider provider, object testOutputDetails = null, string extension = "png", + IImageEncoder encoder = null, bool appendPixelTypeToFileName = true, Func predicate = null) where TPixel : unmanaged, IPixel @@ -119,6 +120,7 @@ public static Image DebugSaveMultiFrame( provider.Utility.SaveTestOutputFileMultiFrame( image, extension, + encoder: encoder, testOutputDetails: testOutputDetails, appendPixelTypeToFileName: appendPixelTypeToFileName, predicate: predicate); @@ -277,6 +279,47 @@ public static Image CompareFirstFrameToReferenceOutput( return image; } + public static Image CompareDebugOutputToReferenceOutputMultiFrame( + this Image image, + ITestImageProvider provider, + ImageComparer comparer, + object testOutputDetails = null, + string extension = "png", + IImageEncoder encoder = null, + bool appendPixelTypeToFileName = true, + Func predicate = null) + where TPixel : unmanaged, IPixel + { + image.DebugSaveMultiFrame( + provider, + testOutputDetails, + extension, + encoder, + appendPixelTypeToFileName, + predicate: predicate); + + using (Image debugImage = GetDebugOutputImageMultiFrame( + provider, + image.Frames.Count, + testOutputDetails, + extension, + appendPixelTypeToFileName, + predicate: predicate)) + + using (Image referenceImage = GetReferenceOutputImageMultiFrame( + provider, + image.Frames.Count, + testOutputDetails, + extension, + appendPixelTypeToFileName, + predicate: predicate)) + { + comparer.VerifySimilarity(referenceImage, debugImage); + } + + return image; + } + public static Image CompareToReferenceOutputMultiFrame( this Image image, ITestImageProvider provider, @@ -375,6 +418,54 @@ public static Image GetReferenceOutputImageMultiFrame( return result; } + public static Image GetDebugOutputImageMultiFrame( + this ITestImageProvider provider, + int frameCount, + object testOutputDetails = null, + string extension = "png", + bool appendPixelTypeToFileName = true, + Func predicate = null) + where TPixel : unmanaged, IPixel + { + (int Index, string FileName)[] frameFiles = [.. provider.Utility.GetTestOutputFileNamesMultiFrame( + frameCount, + extension, + testOutputDetails, + appendPixelTypeToFileName, + predicate: predicate)]; + + List> temporaryFrameImages = []; + + IImageDecoder decoder = TestEnvironment.GetReferenceDecoder(frameFiles[0].FileName); + + for (int i = 0; i < frameFiles.Length; i++) + { + string path = frameFiles[i].FileName; + if (!File.Exists(path)) + { + throw new FileNotFoundException("Reference output file missing: " + path); + } + + using FileStream stream = File.OpenRead(path); + Image tempImage = decoder.Decode(DecoderOptions.Default, stream); + temporaryFrameImages.Add(tempImage); + } + + Image firstTemp = temporaryFrameImages[0]; + + Image result = new(firstTemp.Width, firstTemp.Height); + + foreach (Image fi in temporaryFrameImages) + { + result.Frames.AddFrame(fi.Frames.RootFrame); + fi.Dispose(); + } + + // Remove the initial empty frame: + result.Frames.RemoveFrame(0); + return result; + } + public static IEnumerable GetReferenceOutputSimilarityReports( this Image image, ITestImageProvider provider, diff --git a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Animated_Rgba32_animated_loop.gif/00.png b/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Animated_Rgba32_animated_loop.gif/00.png new file mode 100644 index 0000000000..65b2c6ff72 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Animated_Rgba32_animated_loop.gif/00.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0b980fb1927a70f88bd26b039c54e4ea20a6a1ad68aacd6f1a68a46eb1997a29 +size 1180 diff --git a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Animated_Rgba32_animated_loop.gif/01.png b/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Animated_Rgba32_animated_loop.gif/01.png new file mode 100644 index 0000000000..8c9e125ade --- /dev/null +++ b/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Animated_Rgba32_animated_loop.gif/01.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4006374b88ff4c4ed665333608a19e693fc083ae72beb71850d0e39ad45c9943 +size 1144 diff --git a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Animated_Rgba32_animated_loop.gif/02.png b/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Animated_Rgba32_animated_loop.gif/02.png new file mode 100644 index 0000000000..a2fc2ab3bc --- /dev/null +++ b/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Animated_Rgba32_animated_loop.gif/02.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1f5054e1e464c9e9fc999eec00b9949a6dc256ee062e9910b5718b6d4658661a +size 1303 diff --git a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Animated_Rgba32_animated_loop.gif/03.png b/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Animated_Rgba32_animated_loop.gif/03.png new file mode 100644 index 0000000000..9d5b54c718 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Animated_Rgba32_animated_loop.gif/03.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:49e8bcbcc5dc63fbd555f90a52b4e111cfc058f3adba2ca9c52dec966dbbae8f +size 1371 diff --git a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Animated_Rgba32_animated_loop_interlaced.gif/00.png b/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Animated_Rgba32_animated_loop_interlaced.gif/00.png new file mode 100644 index 0000000000..65b2c6ff72 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Animated_Rgba32_animated_loop_interlaced.gif/00.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0b980fb1927a70f88bd26b039c54e4ea20a6a1ad68aacd6f1a68a46eb1997a29 +size 1180 diff --git a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Animated_Rgba32_animated_loop_interlaced.gif/01.png b/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Animated_Rgba32_animated_loop_interlaced.gif/01.png new file mode 100644 index 0000000000..8c9e125ade --- /dev/null +++ b/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Animated_Rgba32_animated_loop_interlaced.gif/01.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4006374b88ff4c4ed665333608a19e693fc083ae72beb71850d0e39ad45c9943 +size 1144 diff --git a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Animated_Rgba32_animated_loop_interlaced.gif/02.png b/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Animated_Rgba32_animated_loop_interlaced.gif/02.png new file mode 100644 index 0000000000..a2fc2ab3bc --- /dev/null +++ b/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Animated_Rgba32_animated_loop_interlaced.gif/02.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1f5054e1e464c9e9fc999eec00b9949a6dc256ee062e9910b5718b6d4658661a +size 1303 diff --git a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Animated_Rgba32_animated_loop_interlaced.gif/03.png b/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Animated_Rgba32_animated_loop_interlaced.gif/03.png new file mode 100644 index 0000000000..9d5b54c718 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Animated_Rgba32_animated_loop_interlaced.gif/03.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:49e8bcbcc5dc63fbd555f90a52b4e111cfc058f3adba2ca9c52dec966dbbae8f +size 1371 diff --git a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Animated_WithTransparency_Rgba32_animated_transparent_firstframerestoreprev_loop.gif/00.png b/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Animated_WithTransparency_Rgba32_animated_transparent_firstframerestoreprev_loop.gif/00.png new file mode 100644 index 0000000000..193cde24d9 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Animated_WithTransparency_Rgba32_animated_transparent_firstframerestoreprev_loop.gif/00.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:18b60d2066cb53d41988da37b8c521ddcb5355b995320a8413b95522a0492140 +size 687 diff --git a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Animated_WithTransparency_Rgba32_animated_transparent_firstframerestoreprev_loop.gif/01.png b/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Animated_WithTransparency_Rgba32_animated_transparent_firstframerestoreprev_loop.gif/01.png new file mode 100644 index 0000000000..4d2d255108 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Animated_WithTransparency_Rgba32_animated_transparent_firstframerestoreprev_loop.gif/01.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:30ff7708250c5f02dc02d74238d398b319d8fc6c071178f32f82a17e3b637afd +size 542 diff --git a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Animated_WithTransparency_Rgba32_animated_transparent_firstframerestoreprev_loop.gif/02.png b/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Animated_WithTransparency_Rgba32_animated_transparent_firstframerestoreprev_loop.gif/02.png new file mode 100644 index 0000000000..0654e49d45 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Animated_WithTransparency_Rgba32_animated_transparent_firstframerestoreprev_loop.gif/02.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d21f4576486692122b6ee719d75883849f65ddb07f632ea1c62b42651c289688 +size 591 diff --git a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Animated_WithTransparency_Rgba32_animated_transparent_firstframerestoreprev_loop.gif/03.png b/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Animated_WithTransparency_Rgba32_animated_transparent_firstframerestoreprev_loop.gif/03.png new file mode 100644 index 0000000000..0c1090f662 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Animated_WithTransparency_Rgba32_animated_transparent_firstframerestoreprev_loop.gif/03.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:88db68f2d59301b8ff9326143455a03c94cb616220f6e8e3832f13effe0c09bc +size 545 diff --git a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Animated_WithTransparency_Rgba32_animated_transparent_frame_norestore_loop.gif/00.png b/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Animated_WithTransparency_Rgba32_animated_transparent_frame_norestore_loop.gif/00.png new file mode 100644 index 0000000000..193cde24d9 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Animated_WithTransparency_Rgba32_animated_transparent_frame_norestore_loop.gif/00.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:18b60d2066cb53d41988da37b8c521ddcb5355b995320a8413b95522a0492140 +size 687 diff --git a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Animated_WithTransparency_Rgba32_animated_transparent_frame_norestore_loop.gif/01.png b/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Animated_WithTransparency_Rgba32_animated_transparent_frame_norestore_loop.gif/01.png new file mode 100644 index 0000000000..f289fdca31 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Animated_WithTransparency_Rgba32_animated_transparent_frame_norestore_loop.gif/01.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:034b0b6b94c13fbef8c44d650daa07362f113aae6600d63230a3f96e29b16dec +size 790 diff --git a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Animated_WithTransparency_Rgba32_animated_transparent_frame_norestore_loop.gif/02.png b/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Animated_WithTransparency_Rgba32_animated_transparent_frame_norestore_loop.gif/02.png new file mode 100644 index 0000000000..07537b9df0 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Animated_WithTransparency_Rgba32_animated_transparent_frame_norestore_loop.gif/02.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f4b3684db6e3df52a9eb520d562b51b54632e897e9e39bff5ce904ae00799f2f +size 924 diff --git a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Animated_WithTransparency_Rgba32_animated_transparent_frame_norestore_loop.gif/03.png b/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Animated_WithTransparency_Rgba32_animated_transparent_frame_norestore_loop.gif/03.png new file mode 100644 index 0000000000..e376be6890 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Animated_WithTransparency_Rgba32_animated_transparent_frame_norestore_loop.gif/03.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6e33c564f55b653a693105612949401002014821abaecaf654c96d0f2b5d59b4 +size 962 diff --git a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Animated_WithTransparency_Rgba32_animated_transparent_frame_restoreprev_loop.gif/00.png b/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Animated_WithTransparency_Rgba32_animated_transparent_frame_restoreprev_loop.gif/00.png new file mode 100644 index 0000000000..193cde24d9 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Animated_WithTransparency_Rgba32_animated_transparent_frame_restoreprev_loop.gif/00.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:18b60d2066cb53d41988da37b8c521ddcb5355b995320a8413b95522a0492140 +size 687 diff --git a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Animated_WithTransparency_Rgba32_animated_transparent_frame_restoreprev_loop.gif/01.png b/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Animated_WithTransparency_Rgba32_animated_transparent_frame_restoreprev_loop.gif/01.png new file mode 100644 index 0000000000..f289fdca31 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Animated_WithTransparency_Rgba32_animated_transparent_frame_restoreprev_loop.gif/01.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:034b0b6b94c13fbef8c44d650daa07362f113aae6600d63230a3f96e29b16dec +size 790 diff --git a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Animated_WithTransparency_Rgba32_animated_transparent_frame_restoreprev_loop.gif/02.png b/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Animated_WithTransparency_Rgba32_animated_transparent_frame_restoreprev_loop.gif/02.png new file mode 100644 index 0000000000..27f29acbb0 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Animated_WithTransparency_Rgba32_animated_transparent_frame_restoreprev_loop.gif/02.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a4f4eb12da36cd43c620aa5ad1c793bb6eb8431c61d2cc1b77c1118f35a741cc +size 876 diff --git a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Animated_WithTransparency_Rgba32_animated_transparent_frame_restoreprev_loop.gif/03.png b/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Animated_WithTransparency_Rgba32_animated_transparent_frame_restoreprev_loop.gif/03.png new file mode 100644 index 0000000000..684c2fa389 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Animated_WithTransparency_Rgba32_animated_transparent_frame_restoreprev_loop.gif/03.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:15bf02e06c6819d74a0a79cbfc5c86913c248a8812ff0ec613c0e747a000241b +size 789 diff --git a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Animated_WithTransparency_Rgba32_animated_transparent_loop.gif/00.png b/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Animated_WithTransparency_Rgba32_animated_transparent_loop.gif/00.png new file mode 100644 index 0000000000..193cde24d9 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Animated_WithTransparency_Rgba32_animated_transparent_loop.gif/00.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:18b60d2066cb53d41988da37b8c521ddcb5355b995320a8413b95522a0492140 +size 687 diff --git a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Animated_WithTransparency_Rgba32_animated_transparent_loop.gif/01.png b/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Animated_WithTransparency_Rgba32_animated_transparent_loop.gif/01.png new file mode 100644 index 0000000000..7818cf380f --- /dev/null +++ b/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Animated_WithTransparency_Rgba32_animated_transparent_loop.gif/01.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:01f389a2e93023f3132927a9565c04c8c1f827e36111ebe682177adecc3a27ee +size 774 diff --git a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Animated_WithTransparency_Rgba32_animated_transparent_loop.gif/02.png b/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Animated_WithTransparency_Rgba32_animated_transparent_loop.gif/02.png new file mode 100644 index 0000000000..18bc408639 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Animated_WithTransparency_Rgba32_animated_transparent_loop.gif/02.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:00ef57db2fef89112ac7d1808afb6803612c6a20fc589166be6d6b7007c46400 +size 946 diff --git a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Animated_WithTransparency_Rgba32_animated_transparent_loop.gif/03.png b/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Animated_WithTransparency_Rgba32_animated_transparent_loop.gif/03.png new file mode 100644 index 0000000000..bd2ea67aed --- /dev/null +++ b/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Animated_WithTransparency_Rgba32_animated_transparent_loop.gif/03.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:deafa4d4f8fd84489c061ca1042c2ad9e655fff3b6419248cfb35fa4ea40d9e6 +size 1000 diff --git a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Static_No_Animation_Rgba32_static_nontransparent.png b/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Static_No_Animation_Rgba32_static_nontransparent.png new file mode 100644 index 0000000000..65b2c6ff72 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Static_No_Animation_Rgba32_static_nontransparent.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0b980fb1927a70f88bd26b039c54e4ea20a6a1ad68aacd6f1a68a46eb1997a29 +size 1180 diff --git a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Static_No_Animation_Rgba32_static_transparent.png b/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Static_No_Animation_Rgba32_static_transparent.png new file mode 100644 index 0000000000..193cde24d9 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Static_No_Animation_Rgba32_static_transparent.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:18b60d2066cb53d41988da37b8c521ddcb5355b995320a8413b95522a0492140 +size 687 diff --git a/tests/Images/External/ReferenceOutput/GifDecoderTests/Issue2012BadMinCode_Rgba32_issue2012_drona1.png b/tests/Images/External/ReferenceOutput/GifDecoderTests/Issue2012BadMinCode_Rgba32_issue2012_drona1.png index 6f2fc842f8..5d443b52aa 100644 --- a/tests/Images/External/ReferenceOutput/GifDecoderTests/Issue2012BadMinCode_Rgba32_issue2012_drona1.png +++ b/tests/Images/External/ReferenceOutput/GifDecoderTests/Issue2012BadMinCode_Rgba32_issue2012_drona1.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ff67035f78690321c29a4e15c8de7c55bcb3260d667dbd9bced15de6b626fca1 -size 148499 +oid sha256:a0e1677baade797de1eaec390f2e475865d24de8bd344edddbb3fce200d6bcb0 +size 135418 diff --git a/tests/Images/External/ReferenceOutput/GifDecoderTests/IssueTooLargeLzwBits_Rgba32_issue_2743.gif/00.png b/tests/Images/External/ReferenceOutput/GifDecoderTests/IssueTooLargeLzwBits_Rgba32_issue_2743.gif/00.png index 923fbc1225..52f14e0f72 100644 --- a/tests/Images/External/ReferenceOutput/GifDecoderTests/IssueTooLargeLzwBits_Rgba32_issue_2743.gif/00.png +++ b/tests/Images/External/ReferenceOutput/GifDecoderTests/IssueTooLargeLzwBits_Rgba32_issue_2743.gif/00.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:800d1ec2d7c7c99d449db1f49ef202cf18214016eae65ebc4216d6f4b1f4d328 -size 537 +oid sha256:473c5629d7a9f8b3d6c809e8ede40f8fd38e90beddf71851b352c726fc0570d6 +size 534 diff --git a/tests/Images/External/ReferenceOutput/GifDecoderTests/IssueTooLargeLzwBits_Rgba32_issue_2743.gif/01.png b/tests/Images/External/ReferenceOutput/GifDecoderTests/IssueTooLargeLzwBits_Rgba32_issue_2743.gif/01.png index 6c2134d8b8..b47f34ba04 100644 --- a/tests/Images/External/ReferenceOutput/GifDecoderTests/IssueTooLargeLzwBits_Rgba32_issue_2743.gif/01.png +++ b/tests/Images/External/ReferenceOutput/GifDecoderTests/IssueTooLargeLzwBits_Rgba32_issue_2743.gif/01.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:94dcd97831b16165f3331e429d72d7ef546e04038cab754c7918f9cf535ff30a -size 542 +oid sha256:4b9b9f856c0347b460f824d6b027b343c65c67a29360793181c9a29a76f9002b +size 538 diff --git a/tests/Images/External/ReferenceOutput/GifDecoderTests/IssueTooLargeLzwBits_Rgba32_issue_2743.gif/02.png b/tests/Images/External/ReferenceOutput/GifDecoderTests/IssueTooLargeLzwBits_Rgba32_issue_2743.gif/02.png index 6f50397ea4..64869ca3c6 100644 --- a/tests/Images/External/ReferenceOutput/GifDecoderTests/IssueTooLargeLzwBits_Rgba32_issue_2743.gif/02.png +++ b/tests/Images/External/ReferenceOutput/GifDecoderTests/IssueTooLargeLzwBits_Rgba32_issue_2743.gif/02.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ec1a589a8fae1b17a82b70a9583ea2ee012a476b1fa8fdba27fee2b7ce0403b2 -size 540 +oid sha256:74b8015c60d215808b1d663ae4af956d1454414206ba21326ad35b8952b0cab6 +size 534 diff --git a/tests/Images/External/ReferenceOutput/GifDecoderTests/IssueTooLargeLzwBits_Rgba32_issue_2743.gif/03.png b/tests/Images/External/ReferenceOutput/GifDecoderTests/IssueTooLargeLzwBits_Rgba32_issue_2743.gif/03.png index 82061ba0aa..ab52225f8b 100644 --- a/tests/Images/External/ReferenceOutput/GifDecoderTests/IssueTooLargeLzwBits_Rgba32_issue_2743.gif/03.png +++ b/tests/Images/External/ReferenceOutput/GifDecoderTests/IssueTooLargeLzwBits_Rgba32_issue_2743.gif/03.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0c8751f4fafd5c56066dbb8d64a3890fc420a3bd66881a55e309ba274b6d14e4 -size 542 +oid sha256:8dacb6a468d3cdc94613d56264ddf34d3649846edf33619fc13b9522fcf982d6 +size 539 diff --git a/tests/Images/External/ReferenceOutput/GifDecoderTests/IssueTooLargeLzwBits_Rgba32_issue_2743.gif/04.png b/tests/Images/External/ReferenceOutput/GifDecoderTests/IssueTooLargeLzwBits_Rgba32_issue_2743.gif/04.png index 8902eb824a..78988aa60a 100644 --- a/tests/Images/External/ReferenceOutput/GifDecoderTests/IssueTooLargeLzwBits_Rgba32_issue_2743.gif/04.png +++ b/tests/Images/External/ReferenceOutput/GifDecoderTests/IssueTooLargeLzwBits_Rgba32_issue_2743.gif/04.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b78516c9874cb15de4c4b98ed307e8105d962fc6bfa7aa3490b2c7e13b455a2d -size 544 +oid sha256:8af74db6e01928ad54444fa122e4b87929741052c85abe9c0ffc998adffcbdfc +size 542 diff --git a/tests/Images/External/ReferenceOutput/GifDecoderTests/IssueTooLargeLzwBits_Rgba32_issue_2743.gif/05.png b/tests/Images/External/ReferenceOutput/GifDecoderTests/IssueTooLargeLzwBits_Rgba32_issue_2743.gif/05.png index 82061ba0aa..ab52225f8b 100644 --- a/tests/Images/External/ReferenceOutput/GifDecoderTests/IssueTooLargeLzwBits_Rgba32_issue_2743.gif/05.png +++ b/tests/Images/External/ReferenceOutput/GifDecoderTests/IssueTooLargeLzwBits_Rgba32_issue_2743.gif/05.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0c8751f4fafd5c56066dbb8d64a3890fc420a3bd66881a55e309ba274b6d14e4 -size 542 +oid sha256:8dacb6a468d3cdc94613d56264ddf34d3649846edf33619fc13b9522fcf982d6 +size 539 diff --git a/tests/Images/External/ReferenceOutput/GifDecoderTests/IssueTooLargeLzwBits_Rgba32_issue_2743.gif/06.png b/tests/Images/External/ReferenceOutput/GifDecoderTests/IssueTooLargeLzwBits_Rgba32_issue_2743.gif/06.png index 6f50397ea4..64869ca3c6 100644 --- a/tests/Images/External/ReferenceOutput/GifDecoderTests/IssueTooLargeLzwBits_Rgba32_issue_2743.gif/06.png +++ b/tests/Images/External/ReferenceOutput/GifDecoderTests/IssueTooLargeLzwBits_Rgba32_issue_2743.gif/06.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ec1a589a8fae1b17a82b70a9583ea2ee012a476b1fa8fdba27fee2b7ce0403b2 -size 540 +oid sha256:74b8015c60d215808b1d663ae4af956d1454414206ba21326ad35b8952b0cab6 +size 534 diff --git a/tests/Images/External/ReferenceOutput/GifDecoderTests/IssueTooLargeLzwBits_Rgba32_issue_2743.gif/07.png b/tests/Images/External/ReferenceOutput/GifDecoderTests/IssueTooLargeLzwBits_Rgba32_issue_2743.gif/07.png index efba40c99d..97610dbc00 100644 --- a/tests/Images/External/ReferenceOutput/GifDecoderTests/IssueTooLargeLzwBits_Rgba32_issue_2743.gif/07.png +++ b/tests/Images/External/ReferenceOutput/GifDecoderTests/IssueTooLargeLzwBits_Rgba32_issue_2743.gif/07.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:5016a323018f09e292165ad5392d82dcbad5e79c2b6b93aff3322dffff80b309 -size 126 +oid sha256:c816ca1e58d14361b84ba47454e4cbf4d3e4d29dfb7827756eb52ef2604f297c +size 161 diff --git a/tests/Images/External/ReferenceOutput/GifEncoderTests/GifEncoder_CanDecode_AndEncode_Issue2866_Rgba32_issue_2866.gif/00.gif b/tests/Images/External/ReferenceOutput/GifEncoderTests/GifEncoder_CanDecode_AndEncode_Issue2866_Rgba32_issue_2866.gif/00.gif new file mode 100644 index 0000000000..8668f7102a --- /dev/null +++ b/tests/Images/External/ReferenceOutput/GifEncoderTests/GifEncoder_CanDecode_AndEncode_Issue2866_Rgba32_issue_2866.gif/00.gif @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e738470c04121376b42937f0a72eed3f068bc32bdc788cc0d5877bc7f705f419 +size 67611 diff --git a/tests/Images/External/ReferenceOutput/GifEncoderTests/GifEncoder_CanDecode_AndEncode_Issue2866_Rgba32_issue_2866.gif/08.gif b/tests/Images/External/ReferenceOutput/GifEncoderTests/GifEncoder_CanDecode_AndEncode_Issue2866_Rgba32_issue_2866.gif/08.gif new file mode 100644 index 0000000000..8fd66d1206 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/GifEncoderTests/GifEncoder_CanDecode_AndEncode_Issue2866_Rgba32_issue_2866.gif/08.gif @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:33b9244a09ed3b7bb17dfcbb694cbacc4f7de8ce6ffd7d7f7c35d424a6e8b2c5 +size 58696 diff --git a/tests/Images/External/ReferenceOutput/GifEncoderTests/GifEncoder_CanDecode_AndEncode_Issue2866_Rgba32_issue_2866.gif/104.gif b/tests/Images/External/ReferenceOutput/GifEncoderTests/GifEncoder_CanDecode_AndEncode_Issue2866_Rgba32_issue_2866.gif/104.gif new file mode 100644 index 0000000000..90b8a081ce --- /dev/null +++ b/tests/Images/External/ReferenceOutput/GifEncoderTests/GifEncoder_CanDecode_AndEncode_Issue2866_Rgba32_issue_2866.gif/104.gif @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:380f86015751a8445eb706d99001efe96633c62dd183c22303e732d036175dff +size 86095 diff --git a/tests/Images/External/ReferenceOutput/GifEncoderTests/GifEncoder_CanDecode_AndEncode_Issue2866_Rgba32_issue_2866.gif/112.gif b/tests/Images/External/ReferenceOutput/GifEncoderTests/GifEncoder_CanDecode_AndEncode_Issue2866_Rgba32_issue_2866.gif/112.gif new file mode 100644 index 0000000000..180c1e730b --- /dev/null +++ b/tests/Images/External/ReferenceOutput/GifEncoderTests/GifEncoder_CanDecode_AndEncode_Issue2866_Rgba32_issue_2866.gif/112.gif @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ae0f80bbc03fc35be4622df85f13f8fe7aec2d981424d9ca6dce88bd23ff2dad +size 59015 diff --git a/tests/Images/External/ReferenceOutput/GifEncoderTests/GifEncoder_CanDecode_AndEncode_Issue2866_Rgba32_issue_2866.gif/16.gif b/tests/Images/External/ReferenceOutput/GifEncoderTests/GifEncoder_CanDecode_AndEncode_Issue2866_Rgba32_issue_2866.gif/16.gif new file mode 100644 index 0000000000..6eceee3979 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/GifEncoderTests/GifEncoder_CanDecode_AndEncode_Issue2866_Rgba32_issue_2866.gif/16.gif @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:696ef2d76fe8ae8a32f0299b668b0cf537c1ffe5ac3ca4b418499f03ca134d89 +size 70345 diff --git a/tests/Images/External/ReferenceOutput/GifEncoderTests/GifEncoder_CanDecode_AndEncode_Issue2866_Rgba32_issue_2866.gif/24.gif b/tests/Images/External/ReferenceOutput/GifEncoderTests/GifEncoder_CanDecode_AndEncode_Issue2866_Rgba32_issue_2866.gif/24.gif new file mode 100644 index 0000000000..51ef668112 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/GifEncoderTests/GifEncoder_CanDecode_AndEncode_Issue2866_Rgba32_issue_2866.gif/24.gif @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1dbd382f6f7b71f4cdffe61c01f0ab04523423576bad6195f8950ff7758792a2 +size 67153 diff --git a/tests/Images/External/ReferenceOutput/GifEncoderTests/GifEncoder_CanDecode_AndEncode_Issue2866_Rgba32_issue_2866.gif/32.gif b/tests/Images/External/ReferenceOutput/GifEncoderTests/GifEncoder_CanDecode_AndEncode_Issue2866_Rgba32_issue_2866.gif/32.gif new file mode 100644 index 0000000000..26818c6a8f --- /dev/null +++ b/tests/Images/External/ReferenceOutput/GifEncoderTests/GifEncoder_CanDecode_AndEncode_Issue2866_Rgba32_issue_2866.gif/32.gif @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a2942af218c05b29b4f5a8eb9117c8dea4dcf61b33e1ade3cfa6ba47a0d71f6c +size 68007 diff --git a/tests/Images/External/ReferenceOutput/GifEncoderTests/GifEncoder_CanDecode_AndEncode_Issue2866_Rgba32_issue_2866.gif/40.gif b/tests/Images/External/ReferenceOutput/GifEncoderTests/GifEncoder_CanDecode_AndEncode_Issue2866_Rgba32_issue_2866.gif/40.gif new file mode 100644 index 0000000000..92f5c1fb95 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/GifEncoderTests/GifEncoder_CanDecode_AndEncode_Issue2866_Rgba32_issue_2866.gif/40.gif @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:45872e2361fc445d956abd5de454ad42bcdb8ade69742b032c865978e41cb477 +size 73531 diff --git a/tests/Images/External/ReferenceOutput/GifEncoderTests/GifEncoder_CanDecode_AndEncode_Issue2866_Rgba32_issue_2866.gif/48.gif b/tests/Images/External/ReferenceOutput/GifEncoderTests/GifEncoder_CanDecode_AndEncode_Issue2866_Rgba32_issue_2866.gif/48.gif new file mode 100644 index 0000000000..5524ce8a41 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/GifEncoderTests/GifEncoder_CanDecode_AndEncode_Issue2866_Rgba32_issue_2866.gif/48.gif @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:432af0fd8df68724f2faa3fc90dc751ff15a7e45604e20f3737d1902c64a913e +size 72024 diff --git a/tests/Images/External/ReferenceOutput/GifEncoderTests/GifEncoder_CanDecode_AndEncode_Issue2866_Rgba32_issue_2866.gif/56.gif b/tests/Images/External/ReferenceOutput/GifEncoderTests/GifEncoder_CanDecode_AndEncode_Issue2866_Rgba32_issue_2866.gif/56.gif new file mode 100644 index 0000000000..bfd22ffef5 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/GifEncoderTests/GifEncoder_CanDecode_AndEncode_Issue2866_Rgba32_issue_2866.gif/56.gif @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:aab6a8c7892d8449ad8915f414689019684f99d3f011ddb82f722037b35a70c0 +size 77189 diff --git a/tests/Images/External/ReferenceOutput/GifEncoderTests/GifEncoder_CanDecode_AndEncode_Issue2866_Rgba32_issue_2866.gif/64.gif b/tests/Images/External/ReferenceOutput/GifEncoderTests/GifEncoder_CanDecode_AndEncode_Issue2866_Rgba32_issue_2866.gif/64.gif new file mode 100644 index 0000000000..e3f17772eb --- /dev/null +++ b/tests/Images/External/ReferenceOutput/GifEncoderTests/GifEncoder_CanDecode_AndEncode_Issue2866_Rgba32_issue_2866.gif/64.gif @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b93da4e1ab6ed01c561d5b565c078ff8752f7b16b78c733b658237977b1e4689 +size 78172 diff --git a/tests/Images/External/ReferenceOutput/GifEncoderTests/GifEncoder_CanDecode_AndEncode_Issue2866_Rgba32_issue_2866.gif/72.gif b/tests/Images/External/ReferenceOutput/GifEncoderTests/GifEncoder_CanDecode_AndEncode_Issue2866_Rgba32_issue_2866.gif/72.gif new file mode 100644 index 0000000000..cfe4a2eefe --- /dev/null +++ b/tests/Images/External/ReferenceOutput/GifEncoderTests/GifEncoder_CanDecode_AndEncode_Issue2866_Rgba32_issue_2866.gif/72.gif @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a8b426203cd6b6b5f4b97a5213a8a04da66056e3b238be3407751a43b365e11b +size 71198 diff --git a/tests/Images/External/ReferenceOutput/GifEncoderTests/GifEncoder_CanDecode_AndEncode_Issue2866_Rgba32_issue_2866.gif/80.gif b/tests/Images/External/ReferenceOutput/GifEncoderTests/GifEncoder_CanDecode_AndEncode_Issue2866_Rgba32_issue_2866.gif/80.gif new file mode 100644 index 0000000000..6c713186f4 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/GifEncoderTests/GifEncoder_CanDecode_AndEncode_Issue2866_Rgba32_issue_2866.gif/80.gif @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d47400e426792c550afb2797b68e41e3ce04409dde627e0f74c39f216a951f22 +size 83675 diff --git a/tests/Images/External/ReferenceOutput/GifEncoderTests/GifEncoder_CanDecode_AndEncode_Issue2866_Rgba32_issue_2866.gif/88.gif b/tests/Images/External/ReferenceOutput/GifEncoderTests/GifEncoder_CanDecode_AndEncode_Issue2866_Rgba32_issue_2866.gif/88.gif new file mode 100644 index 0000000000..89a8e3880a --- /dev/null +++ b/tests/Images/External/ReferenceOutput/GifEncoderTests/GifEncoder_CanDecode_AndEncode_Issue2866_Rgba32_issue_2866.gif/88.gif @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d11ae15a30005e335d2f84f2749573fc59d88d2f1c415cfea2126b47e3dac60b +size 91647 diff --git a/tests/Images/External/ReferenceOutput/GifEncoderTests/GifEncoder_CanDecode_AndEncode_Issue2866_Rgba32_issue_2866.gif/96.gif b/tests/Images/External/ReferenceOutput/GifEncoderTests/GifEncoder_CanDecode_AndEncode_Issue2866_Rgba32_issue_2866.gif/96.gif new file mode 100644 index 0000000000..f7ef065348 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/GifEncoderTests/GifEncoder_CanDecode_AndEncode_Issue2866_Rgba32_issue_2866.gif/96.gif @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6410a7792823ad1c5d5f0fcaed388b362498f1bbd57175b70165e0ce3542662c +size 99183 diff --git a/tests/Images/External/ReferenceOutput/PngEncoderTests/Encode_AnimatedFormatTransform_FromGif_Rgba32_issue_2866.gif/00.png b/tests/Images/External/ReferenceOutput/PngEncoderTests/Encode_AnimatedFormatTransform_FromGif_Rgba32_issue_2866.gif/00.png new file mode 100644 index 0000000000..3658168255 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/PngEncoderTests/Encode_AnimatedFormatTransform_FromGif_Rgba32_issue_2866.gif/00.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0840e31b83f0f9621e2c98cb784881f88c882f7b558aef889eb2511a893af585 +size 63955 diff --git a/tests/Images/External/ReferenceOutput/PngEncoderTests/Encode_AnimatedFormatTransform_FromGif_Rgba32_issue_2866.gif/08.png b/tests/Images/External/ReferenceOutput/PngEncoderTests/Encode_AnimatedFormatTransform_FromGif_Rgba32_issue_2866.gif/08.png new file mode 100644 index 0000000000..eb52b14732 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/PngEncoderTests/Encode_AnimatedFormatTransform_FromGif_Rgba32_issue_2866.gif/08.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c674fe70b76d6763215620680f7f24bc3113c3ee98eeb9f9e9572ccd8825c61f +size 68053 diff --git a/tests/Images/External/ReferenceOutput/PngEncoderTests/Encode_AnimatedFormatTransform_FromGif_Rgba32_issue_2866.gif/104.png b/tests/Images/External/ReferenceOutput/PngEncoderTests/Encode_AnimatedFormatTransform_FromGif_Rgba32_issue_2866.gif/104.png new file mode 100644 index 0000000000..ad4232e3c0 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/PngEncoderTests/Encode_AnimatedFormatTransform_FromGif_Rgba32_issue_2866.gif/104.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e46346d886acf62b3d742d6df9ddd9226afd2a4184be045e00724cf0ab4c25b4 +size 77343 diff --git a/tests/Images/External/ReferenceOutput/PngEncoderTests/Encode_AnimatedFormatTransform_FromGif_Rgba32_issue_2866.gif/112.png b/tests/Images/External/ReferenceOutput/PngEncoderTests/Encode_AnimatedFormatTransform_FromGif_Rgba32_issue_2866.gif/112.png new file mode 100644 index 0000000000..5402b021f6 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/PngEncoderTests/Encode_AnimatedFormatTransform_FromGif_Rgba32_issue_2866.gif/112.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9a647f174ac31570a3e42c472fa888dadcd5117e6f8300ef35b21ed2cff5bd6c +size 65959 diff --git a/tests/Images/External/ReferenceOutput/PngEncoderTests/Encode_AnimatedFormatTransform_FromGif_Rgba32_issue_2866.gif/16.png b/tests/Images/External/ReferenceOutput/PngEncoderTests/Encode_AnimatedFormatTransform_FromGif_Rgba32_issue_2866.gif/16.png new file mode 100644 index 0000000000..0f7643659f --- /dev/null +++ b/tests/Images/External/ReferenceOutput/PngEncoderTests/Encode_AnimatedFormatTransform_FromGif_Rgba32_issue_2866.gif/16.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:951a034bae41acdcbd202d1d8624609bf4a47edbcd94806e4993b0a610fb767e +size 74231 diff --git a/tests/Images/External/ReferenceOutput/PngEncoderTests/Encode_AnimatedFormatTransform_FromGif_Rgba32_issue_2866.gif/24.png b/tests/Images/External/ReferenceOutput/PngEncoderTests/Encode_AnimatedFormatTransform_FromGif_Rgba32_issue_2866.gif/24.png new file mode 100644 index 0000000000..0d680bb4c9 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/PngEncoderTests/Encode_AnimatedFormatTransform_FromGif_Rgba32_issue_2866.gif/24.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ce790490566b7d908e6372746a264b0d207e3b8f906fddf020e68e0b5effd757 +size 75693 diff --git a/tests/Images/External/ReferenceOutput/PngEncoderTests/Encode_AnimatedFormatTransform_FromGif_Rgba32_issue_2866.gif/32.png b/tests/Images/External/ReferenceOutput/PngEncoderTests/Encode_AnimatedFormatTransform_FromGif_Rgba32_issue_2866.gif/32.png new file mode 100644 index 0000000000..0c724b5e96 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/PngEncoderTests/Encode_AnimatedFormatTransform_FromGif_Rgba32_issue_2866.gif/32.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5aea5c9671a91101081287426e57c07fb56c1f072f582bd2f135705c114239f8 +size 79474 diff --git a/tests/Images/External/ReferenceOutput/PngEncoderTests/Encode_AnimatedFormatTransform_FromGif_Rgba32_issue_2866.gif/40.png b/tests/Images/External/ReferenceOutput/PngEncoderTests/Encode_AnimatedFormatTransform_FromGif_Rgba32_issue_2866.gif/40.png new file mode 100644 index 0000000000..587107445e --- /dev/null +++ b/tests/Images/External/ReferenceOutput/PngEncoderTests/Encode_AnimatedFormatTransform_FromGif_Rgba32_issue_2866.gif/40.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:100d85d20cf39ed4b8f70a7c3a23c5ef857899e3830a7869437eb38819dbe309 +size 77200 diff --git a/tests/Images/External/ReferenceOutput/PngEncoderTests/Encode_AnimatedFormatTransform_FromGif_Rgba32_issue_2866.gif/48.png b/tests/Images/External/ReferenceOutput/PngEncoderTests/Encode_AnimatedFormatTransform_FromGif_Rgba32_issue_2866.gif/48.png new file mode 100644 index 0000000000..ea2bd82071 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/PngEncoderTests/Encode_AnimatedFormatTransform_FromGif_Rgba32_issue_2866.gif/48.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c48b5e76c80649d42c58043d6afaebf872b69031e12af0554847afc167379d9e +size 77420 diff --git a/tests/Images/External/ReferenceOutput/PngEncoderTests/Encode_AnimatedFormatTransform_FromGif_Rgba32_issue_2866.gif/56.png b/tests/Images/External/ReferenceOutput/PngEncoderTests/Encode_AnimatedFormatTransform_FromGif_Rgba32_issue_2866.gif/56.png new file mode 100644 index 0000000000..206ddc164e --- /dev/null +++ b/tests/Images/External/ReferenceOutput/PngEncoderTests/Encode_AnimatedFormatTransform_FromGif_Rgba32_issue_2866.gif/56.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e98364b6e9e913a56e95aace2ac07890e1d4f8a31e44b5195cac0779e9ed75bb +size 75433 diff --git a/tests/Images/External/ReferenceOutput/PngEncoderTests/Encode_AnimatedFormatTransform_FromGif_Rgba32_issue_2866.gif/64.png b/tests/Images/External/ReferenceOutput/PngEncoderTests/Encode_AnimatedFormatTransform_FromGif_Rgba32_issue_2866.gif/64.png new file mode 100644 index 0000000000..b92f711df6 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/PngEncoderTests/Encode_AnimatedFormatTransform_FromGif_Rgba32_issue_2866.gif/64.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:53a1c8c9653da218d6f49837a7d5b8623568eb629670d0026e056fb1f13ca52e +size 77402 diff --git a/tests/Images/External/ReferenceOutput/PngEncoderTests/Encode_AnimatedFormatTransform_FromGif_Rgba32_issue_2866.gif/72.png b/tests/Images/External/ReferenceOutput/PngEncoderTests/Encode_AnimatedFormatTransform_FromGif_Rgba32_issue_2866.gif/72.png new file mode 100644 index 0000000000..9e78a1f28a --- /dev/null +++ b/tests/Images/External/ReferenceOutput/PngEncoderTests/Encode_AnimatedFormatTransform_FromGif_Rgba32_issue_2866.gif/72.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:06f1d36cede878377ef7731ac2530d3cd489fdb09308794315da7b62a48ec667 +size 73840 diff --git a/tests/Images/External/ReferenceOutput/PngEncoderTests/Encode_AnimatedFormatTransform_FromGif_Rgba32_issue_2866.gif/80.png b/tests/Images/External/ReferenceOutput/PngEncoderTests/Encode_AnimatedFormatTransform_FromGif_Rgba32_issue_2866.gif/80.png new file mode 100644 index 0000000000..0855f16ed3 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/PngEncoderTests/Encode_AnimatedFormatTransform_FromGif_Rgba32_issue_2866.gif/80.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3ace97f772679993cb35dc0630d4431019a7e885cd397db98d2b0d91a6c984db +size 81863 diff --git a/tests/Images/External/ReferenceOutput/PngEncoderTests/Encode_AnimatedFormatTransform_FromGif_Rgba32_issue_2866.gif/88.png b/tests/Images/External/ReferenceOutput/PngEncoderTests/Encode_AnimatedFormatTransform_FromGif_Rgba32_issue_2866.gif/88.png new file mode 100644 index 0000000000..5564c32923 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/PngEncoderTests/Encode_AnimatedFormatTransform_FromGif_Rgba32_issue_2866.gif/88.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a1fa4b08ec93e17cfa9906ca6f447da651f469aad3615cc5ae08f4080784c096 +size 86317 diff --git a/tests/Images/External/ReferenceOutput/PngEncoderTests/Encode_AnimatedFormatTransform_FromGif_Rgba32_issue_2866.gif/96.png b/tests/Images/External/ReferenceOutput/PngEncoderTests/Encode_AnimatedFormatTransform_FromGif_Rgba32_issue_2866.gif/96.png new file mode 100644 index 0000000000..41b121a15f --- /dev/null +++ b/tests/Images/External/ReferenceOutput/PngEncoderTests/Encode_AnimatedFormatTransform_FromGif_Rgba32_issue_2866.gif/96.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f9e6fd785ab0ef4e75adbf4697d7cb04873a95d01a5fb5197090e87061aa3430 +size 88729 diff --git a/tests/Images/External/ReferenceOutput/PngEncoderTests/Encode_AnimatedFormatTransform_FromGif_Rgba32_leo.gif/00.png b/tests/Images/External/ReferenceOutput/PngEncoderTests/Encode_AnimatedFormatTransform_FromGif_Rgba32_leo.gif/00.png new file mode 100644 index 0000000000..a0b3e0a5d3 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/PngEncoderTests/Encode_AnimatedFormatTransform_FromGif_Rgba32_leo.gif/00.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:bf499248f2cdb443d12998c45cad8fcfee04e0febb21965980815aa910bc34ab +size 23945 diff --git a/tests/Images/External/ReferenceOutput/PngEncoderTests/Encode_AnimatedFormatTransform_FromGif_Rgba32_leo.gif/08.png b/tests/Images/External/ReferenceOutput/PngEncoderTests/Encode_AnimatedFormatTransform_FromGif_Rgba32_leo.gif/08.png new file mode 100644 index 0000000000..bbe754da3d --- /dev/null +++ b/tests/Images/External/ReferenceOutput/PngEncoderTests/Encode_AnimatedFormatTransform_FromGif_Rgba32_leo.gif/08.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0ecf0666ca8b0ef16cf0e6147aea4b76af76f62777a62ea02edcf5cf5a309d8b +size 29721 diff --git a/tests/Images/Input/Gif/animated_loop.gif b/tests/Images/Input/Gif/animated_loop.gif new file mode 100644 index 0000000000..5fad702a10 --- /dev/null +++ b/tests/Images/Input/Gif/animated_loop.gif @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a8750149c953e9e910472684158c07a2cb551c1f7e95744ab48db1a67f63f342 +size 873 diff --git a/tests/Images/Input/Gif/animated_loop_interlaced.gif b/tests/Images/Input/Gif/animated_loop_interlaced.gif new file mode 100644 index 0000000000..9577a84658 --- /dev/null +++ b/tests/Images/Input/Gif/animated_loop_interlaced.gif @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c2bc2f895f03092b1c26381a32b5dd5838aacd3331e07f7e4dae55d5cbb4e149 +size 878 diff --git a/tests/Images/Input/Gif/animated_transparent_firstframerestoreprev_loop.gif b/tests/Images/Input/Gif/animated_transparent_firstframerestoreprev_loop.gif new file mode 100644 index 0000000000..5012324caf --- /dev/null +++ b/tests/Images/Input/Gif/animated_transparent_firstframerestoreprev_loop.gif @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6b0cf18d386dc979fcf853d6a9adac673a2709a8751d31a94930199dededa25f +size 536 diff --git a/tests/Images/Input/Gif/animated_transparent_frame_norestore_loop.gif b/tests/Images/Input/Gif/animated_transparent_frame_norestore_loop.gif new file mode 100644 index 0000000000..712f334aba --- /dev/null +++ b/tests/Images/Input/Gif/animated_transparent_frame_norestore_loop.gif @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ad7359801fa6ed89fb041de1e88faea856b1028d9f477fdc4eda774df6e5f1ce +size 685 diff --git a/tests/Images/Input/Gif/animated_transparent_frame_restorebackground_loop.gif b/tests/Images/Input/Gif/animated_transparent_frame_restorebackground_loop.gif new file mode 100644 index 0000000000..b6f675dcaf --- /dev/null +++ b/tests/Images/Input/Gif/animated_transparent_frame_restorebackground_loop.gif @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:12254f22eeee9eac6babbfbfb34b6ae9302342454fd6677e8c7c9937656cc127 +size 685 diff --git a/tests/Images/Input/Gif/animated_transparent_frame_restoreprev_loop.gif b/tests/Images/Input/Gif/animated_transparent_frame_restoreprev_loop.gif new file mode 100644 index 0000000000..be7fdf85d8 --- /dev/null +++ b/tests/Images/Input/Gif/animated_transparent_frame_restoreprev_loop.gif @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0cdfba6efea653bb94ede6edd0577ba6af1f7c130307b94903dd94f0f8bbc4f9 +size 685 diff --git a/tests/Images/Input/Gif/animated_transparent_loop.gif b/tests/Images/Input/Gif/animated_transparent_loop.gif new file mode 100644 index 0000000000..cb001ece8f --- /dev/null +++ b/tests/Images/Input/Gif/animated_transparent_loop.gif @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3297987894ba27c2acc6a5c447c3d3a52cc169447b451409535decccc1743e55 +size 536 diff --git a/tests/Images/Input/Gif/animated_transparent_restoreprev_loop.gif b/tests/Images/Input/Gif/animated_transparent_restoreprev_loop.gif new file mode 100644 index 0000000000..f51d02433e --- /dev/null +++ b/tests/Images/Input/Gif/animated_transparent_restoreprev_loop.gif @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9418561ef2f2307456bb068ecc1a9d5aa02da5e314e7aadd722985e27503926b +size 536 diff --git a/tests/Images/Input/Gif/static_nontransparent.gif b/tests/Images/Input/Gif/static_nontransparent.gif new file mode 100644 index 0000000000..17ab1e2ec7 --- /dev/null +++ b/tests/Images/Input/Gif/static_nontransparent.gif @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d0a5e1b2f0c5c1763eb950a1d92c5317f048875e04e88dca7f1a966552c2774c +size 678 diff --git a/tests/Images/Input/Gif/static_transparent.gif b/tests/Images/Input/Gif/static_transparent.gif new file mode 100644 index 0000000000..89039a732a --- /dev/null +++ b/tests/Images/Input/Gif/static_transparent.gif @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f73f56bbe2206bd1cd8a4625b6a4d61506214b37b61ff3e8194e2030b28abca5 +size 341 diff --git a/tests/Images/Input/Webp/alpha-blend-2.webp b/tests/Images/Input/Webp/alpha-blend-2.webp new file mode 100644 index 0000000000..3ca4c77cff --- /dev/null +++ b/tests/Images/Input/Webp/alpha-blend-2.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b07a49ab8b3af82fa123faf897ec537cd26d57175b1d6301b617372c06432899 +size 1580484 diff --git a/tests/Images/Input/Webp/alpha-blend-3.webp b/tests/Images/Input/Webp/alpha-blend-3.webp new file mode 100644 index 0000000000..1922f561d5 --- /dev/null +++ b/tests/Images/Input/Webp/alpha-blend-3.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b7244a2cfb42285a196fc7846c49da65fac47e5b85f735bc07b131707c8a2d46 +size 948 diff --git a/tests/Images/Input/Webp/alpha-blend-4.webp b/tests/Images/Input/Webp/alpha-blend-4.webp new file mode 100644 index 0000000000..6f4db231f6 --- /dev/null +++ b/tests/Images/Input/Webp/alpha-blend-4.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8c8b98f188d006715bd5bc60593ff2a379078f28a7fc14a51d28ae1cfb279aac +size 2185502 diff --git a/tests/Images/Input/Webp/alpha-blend.webp b/tests/Images/Input/Webp/alpha-blend.webp new file mode 100644 index 0000000000..110d2a594c --- /dev/null +++ b/tests/Images/Input/Webp/alpha-blend.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5dc2762d1c1030fb951e1af57eaa7e66035a7b5e63bd9dc9f9bd50f0ff5c4c3a +size 1297692 From 4aae44fdd94a81f8dd1dd95e224288cbc0a55dbc Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Tue, 18 Mar 2025 20:09:41 +1000 Subject: [PATCH 06/18] Pass readonly region --- src/ImageSharp/Formats/Png/PngEncoderCore.cs | 9 +++++---- .../Processors/Quantization/QuantizerUtilities.cs | 2 +- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/ImageSharp/Formats/Png/PngEncoderCore.cs b/src/ImageSharp/Formats/Png/PngEncoderCore.cs index b90a9975d3..fea9e801c6 100644 --- a/src/ImageSharp/Formats/Png/PngEncoderCore.cs +++ b/src/ImageSharp/Formats/Png/PngEncoderCore.cs @@ -1584,14 +1584,15 @@ private void SanitizeAndSetEncoderOptions( // Encoding animated frames with a global palette requires a transparent pixel in the palette // since we only encode the delta between frames. To ensure that we have a transparent pixel // we create a fake frame with a containing only transparent pixels and add it to the palette. - using Buffer2D px = image.Configuration.MemoryAllocator.Allocate2D(Math.Min(256, image.Width), Math.Min(256, image.Height)); + using Buffer2D fake = image.Configuration.MemoryAllocator.Allocate2D(Math.Min(256, image.Width), Math.Min(256, image.Height)); TPixel backGroundPixel = backgroundColor.ToPixel(); - for (int i = 0; i < px.Height; i++) + for (int i = 0; i < fake.Height; i++) { - px.DangerousGetRowSpan(i).Fill(backGroundPixel); + fake.DangerousGetRowSpan(i).Fill(backGroundPixel); } - frameQuantizer.AddPaletteColors(px.GetRegion()); + Buffer2DRegion fakeRegion = fake.GetRegion(); + frameQuantizer.AddPaletteColors(in fakeRegion); } frameQuantizer.BuildPalette( diff --git a/src/ImageSharp/Processing/Processors/Quantization/QuantizerUtilities.cs b/src/ImageSharp/Processing/Processors/Quantization/QuantizerUtilities.cs index 7669f0d3c6..49eaa79abb 100644 --- a/src/ImageSharp/Processing/Processors/Quantization/QuantizerUtilities.cs +++ b/src/ImageSharp/Processing/Processors/Quantization/QuantizerUtilities.cs @@ -55,7 +55,7 @@ public static IndexedImageFrame BuildPaletteAndQuantizeFrame( Buffer2DRegion region = source.PixelBuffer.GetRegion(interest); // Collect the palette. Required before the second pass runs. - quantizer.AddPaletteColors(region); + quantizer.AddPaletteColors(in region); return quantizer.QuantizeFrame(source, bounds); } From 6322bcb7d5b7e0563bffd20424e6f43d44a05d81 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Thu, 27 Mar 2025 09:38:40 +1000 Subject: [PATCH 07/18] Use different cache types and begin to make quantizers own clearing pixels. --- src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs | 7 +- src/ImageSharp/Formats/EncodingUtilities.cs | 119 +++- src/ImageSharp/Formats/Gif/GifEncoderCore.cs | 28 +- .../Formats/IAnimatedImageEncoder.cs | 6 +- .../Formats/ISpecializedDecoderOptions.cs | 2 +- src/ImageSharp/Formats/Png/PngEncoder.cs | 5 - src/ImageSharp/Formats/Png/PngEncoderCore.cs | 23 +- src/ImageSharp/Formats/Qoi/QoiEncoderCore.cs | 4 +- src/ImageSharp/Formats/Tga/TgaEncoderCore.cs | 4 +- .../Formats/Tiff/TiffEncoderCore.cs | 4 +- src/ImageSharp/IndexedImageFrame{TPixel}.cs | 4 +- ...nownTypes.cs => ErrorDither.KnownTypes.cs} | 0 .../PaletteDitherProcessor{TPixel}.cs | 4 +- .../Quantization/ColorMatchingMode.cs | 28 + .../EuclideanPixelMap{TPixel,TCache}.cs | 184 ++++++ .../Quantization/EuclideanPixelMap{TPixel}.cs | 545 ------------------ .../Quantization/IColorIndexCache.cs | 491 ++++++++++++++++ .../Processors/Quantization/IQuantizer.cs | 6 +- .../Quantization/IQuantizer{TPixel}.cs | 35 +- .../IQuantizingPixelRowDelegate{TPixel}.cs | 27 + .../Quantization/OctreeQuantizer{TPixel}.cs | 121 ++-- .../Quantization/PaletteQuantizer{TPixel}.cs | 21 +- .../Quantization/QuantizerOptions.cs | 11 + .../Quantization/QuantizerUtilities.cs | 365 ++++++++++-- .../Quantization/WuQuantizer{TPixel}.cs | 118 ++-- .../Formats/Gif/GifEncoderTests.cs | 2 +- .../Formats/Png/PngEncoderTests.cs | 4 +- 27 files changed, 1363 insertions(+), 805 deletions(-) rename src/ImageSharp/Processing/Processors/Dithering/{ErroDither.KnownTypes.cs => ErrorDither.KnownTypes.cs} (100%) create mode 100644 src/ImageSharp/Processing/Processors/Quantization/ColorMatchingMode.cs create mode 100644 src/ImageSharp/Processing/Processors/Quantization/EuclideanPixelMap{TPixel,TCache}.cs delete mode 100644 src/ImageSharp/Processing/Processors/Quantization/EuclideanPixelMap{TPixel}.cs create mode 100644 src/ImageSharp/Processing/Processors/Quantization/IColorIndexCache.cs create mode 100644 src/ImageSharp/Processing/Processors/Quantization/IQuantizingPixelRowDelegate{TPixel}.cs diff --git a/src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs b/src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs index 321a559b1e..6a88f080d9 100644 --- a/src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs +++ b/src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs @@ -362,10 +362,13 @@ private void WriteImage( ImageFrame? clonedFrame = null; try { - if (EncodingUtilities.ShouldClearTransparentPixels(this.transparentColorMode)) + // No need to clone when quantizing. The quantizer will do it for us. + // TODO: We should really try to avoid the clone entirely. + int bpp = this.bitsPerPixel != null ? (int)this.bitsPerPixel : 32; + if (bpp > 8 && EncodingUtilities.ShouldReplaceTransparentPixels(this.transparentColorMode)) { clonedFrame = image.Frames.RootFrame.Clone(); - EncodingUtilities.ClearTransparentPixels(clonedFrame, Color.Transparent); + EncodingUtilities.ReplaceTransparentPixels(clonedFrame, Color.Transparent); } ImageFrame encodingFrame = clonedFrame ?? image.Frames.RootFrame; diff --git a/src/ImageSharp/Formats/EncodingUtilities.cs b/src/ImageSharp/Formats/EncodingUtilities.cs index db951b1c33..130dd567eb 100644 --- a/src/ImageSharp/Formats/EncodingUtilities.cs +++ b/src/ImageSharp/Formats/EncodingUtilities.cs @@ -4,6 +4,7 @@ using System.Buffers; using System.Numerics; using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; using System.Runtime.Intrinsics; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; @@ -15,50 +16,52 @@ namespace SixLabors.ImageSharp.Formats; /// internal static class EncodingUtilities { - public static bool ShouldClearTransparentPixels(TransparentColorMode mode) + /// + /// Determines if transparent pixels can be replaced based on the specified color mode and pixel type. + /// + /// The type of the pixel. + /// Indicates the color mode used to assess the ability to replace transparent pixels. + /// Returns true if transparent pixels can be replaced; otherwise, false. + public static bool ShouldReplaceTransparentPixels(TransparentColorMode mode) where TPixel : unmanaged, IPixel - => mode == TransparentColorMode.Clear && - TPixel.GetPixelTypeInfo().AlphaRepresentation == PixelAlphaRepresentation.Unassociated; + => mode == TransparentColorMode.Clear && TPixel.GetPixelTypeInfo().AlphaRepresentation == PixelAlphaRepresentation.Unassociated; /// - /// Convert transparent pixels, to pixels represented by , which can yield - /// to better compression in some cases. + /// Replaces transparent pixels with pixels represented by . /// /// The type of the pixel. /// The where the transparent pixels will be changed. /// The color to replace transparent pixels with. - public static void ClearTransparentPixels(ImageFrame frame, Color color) + public static void ReplaceTransparentPixels(ImageFrame frame, Color color) where TPixel : unmanaged, IPixel - => ClearTransparentPixels(frame.Configuration, frame.PixelBuffer, color); + => ReplaceTransparentPixels(frame.Configuration, frame.PixelBuffer, color); /// - /// Convert transparent pixels, to pixels represented by , which can yield - /// to better compression in some cases. + /// Replaces transparent pixels with pixels represented by . /// /// The type of the pixel. /// The configuration. /// The where the transparent pixels will be changed. /// The color to replace transparent pixels with. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void ClearTransparentPixels( + public static void ReplaceTransparentPixels( Configuration configuration, Buffer2D buffer, Color color) where TPixel : unmanaged, IPixel { Buffer2DRegion region = buffer.GetRegion(); - ClearTransparentPixels(configuration, in region, color); + ReplaceTransparentPixels(configuration, in region, color); } /// - /// Convert transparent pixels, to pixels represented by , which can yield - /// to better compression in some cases. + /// Replaces transparent pixels with pixels represented by . /// /// The type of the pixel. /// The configuration. /// The where the transparent pixels will be changed. /// The color to replace transparent pixels with. - public static void ClearTransparentPixels( + public static void ReplaceTransparentPixels( Configuration configuration, in Buffer2DRegion region, Color color) @@ -71,20 +74,92 @@ public static void ClearTransparentPixels( { Span span = region.DangerousGetRowSpan(y); PixelOperations.Instance.ToVector4(configuration, span, vectorsSpan, PixelConversionModifiers.Scale); - ClearTransparentPixelRow(vectorsSpan, replacement); + ReplaceTransparentPixels(vectorsSpan, replacement); PixelOperations.Instance.FromVector4Destructive(configuration, vectorsSpan, span, PixelConversionModifiers.Scale); } } - private static void ClearTransparentPixelRow(Span vectorsSpan, Vector4 replacement) + /// + /// Replaces transparent pixels with pixels represented by . + /// + /// A span of color vectors that will be checked for transparency and potentially modified. + /// A color vector that will replace transparent pixels when the alpha value is below the specified threshold. + public static void ReplaceTransparentPixels(Span source, Vector4 replacement) { - if (Vector128.IsHardwareAccelerated) + if (Vector512.IsHardwareAccelerated && source.Length >= 4) + { + Vector128 replacement128 = replacement.AsVector128(); + Vector256 replacement256 = Vector256.Create(replacement128, replacement128); + Vector512 replacement512 = Vector512.Create(replacement256, replacement256); + + Span> source512 = MemoryMarshal.Cast>(source); + for (int i = 0; i < source512.Length; i++) + { + ref Vector512 v = ref source512[i]; + + // Do `vector < threshold` + Vector512 mask = Vector512.Equals(v, Vector512.Zero); + + // Replicate the result for W to all elements (is AllBitsSet if the W was 0 and Zero otherwise) + mask = Vector512.Shuffle(mask, Vector512.Create(3, 3, 3, 3, 7, 7, 7, 7, 11, 11, 11, 11, 15, 15, 15, 15)); + + // Use the mask to select the replacement vector + // (replacement & mask) | (v512 & ~mask) + v = Vector512.ConditionalSelect(mask, replacement512, v); + } + + int m = Numerics.Modulo4(source.Length); + if (m != 0) + { + for (int i = source.Length - m; i < source.Length; i++) + { + if (source[i].W == 0) + { + source[i] = replacement; + } + } + } + } + else if (Vector256.IsHardwareAccelerated && source.Length >= 2) + { + Vector128 replacement128 = replacement.AsVector128(); + Vector256 replacement256 = Vector256.Create(replacement128, replacement128); + + Span> source256 = MemoryMarshal.Cast>(source); + for (int i = 0; i < source256.Length; i++) + { + ref Vector256 v = ref source256[i]; + + // Do `vector < threshold` + Vector256 mask = Vector256.Equals(v, Vector256.Zero); + + // Replicate the result for W to all elements (is AllBitsSet if the W was 0 and Zero otherwise) + mask = Vector256.Shuffle(mask, Vector256.Create(3, 3, 3, 3, 7, 7, 7, 7)); + + // Use the mask to select the replacement vector + // (replacement & mask) | (v256 & ~mask) + v = Vector256.ConditionalSelect(mask, replacement256, v); + } + + int m = Numerics.Modulo2(source.Length); + if (m != 0) + { + for (int i = source.Length - m; i < source.Length; i++) + { + if (source[i].W == 0) + { + source[i] = replacement; + } + } + } + } + else if (Vector128.IsHardwareAccelerated) { Vector128 replacement128 = replacement.AsVector128(); - for (int i = 0; i < vectorsSpan.Length; i++) + for (int i = 0; i < source.Length; i++) { - ref Vector4 v = ref vectorsSpan[i]; + ref Vector4 v = ref source[i]; Vector128 v128 = v.AsVector128(); // Do `vector == 0` @@ -100,11 +175,11 @@ private static void ClearTransparentPixelRow(Span vectorsSpan, Vector4 } else { - for (int i = 0; i < vectorsSpan.Length; i++) + for (int i = 0; i < source.Length; i++) { - if (vectorsSpan[i].W == 0F) + if (source[i].W == 0F) { - vectorsSpan[i] = replacement; + source[i] = replacement; } } } diff --git a/src/ImageSharp/Formats/Gif/GifEncoderCore.cs b/src/ImageSharp/Formats/Gif/GifEncoderCore.cs index a4830d7793..eff33f2d1c 100644 --- a/src/ImageSharp/Formats/Gif/GifEncoderCore.cs +++ b/src/ImageSharp/Formats/Gif/GifEncoderCore.cs @@ -151,35 +151,20 @@ public void Encode(Image image, Stream stream, CancellationToken Color background = Color.Transparent; using (IQuantizer frameQuantizer = this.quantizer.CreatePixelSpecificQuantizer(this.configuration)) { - ImageFrame? clonedFrame = null; - Configuration configuration = this.configuration; TransparentColorMode mode = this.transparentColorMode; IPixelSamplingStrategy strategy = this.pixelSamplingStrategy; - if (EncodingUtilities.ShouldClearTransparentPixels(mode)) - { - clonedFrame = image.Frames.RootFrame.Clone(); - - GifFrameMetadata frameMeta = clonedFrame.Metadata.GetGifMetadata(); - if (frameMeta.DisposalMode == FrameDisposalMode.RestoreToBackground) - { - background = this.backgroundColor ?? Color.Transparent; - } - - EncodingUtilities.ClearTransparentPixels(clonedFrame, background); - } - - ImageFrame encodingFrame = clonedFrame ?? image.Frames.RootFrame; + ImageFrame encodingFrame = image.Frames.RootFrame; if (useGlobalTableForFirstFrame) { if (useGlobalTable) { - frameQuantizer.BuildPalette(configuration, mode, strategy, image, background); + frameQuantizer.BuildPalette(mode, strategy, image); quantized = frameQuantizer.QuantizeFrame(encodingFrame, image.Bounds); } else { - frameQuantizer.BuildPalette(configuration, mode, strategy, encodingFrame, background); + frameQuantizer.BuildPalette(mode, strategy, encodingFrame); quantized = frameQuantizer.QuantizeFrame(encodingFrame, encodingFrame.Bounds); } } @@ -195,8 +180,6 @@ public void Encode(Image image, Stream stream, CancellationToken frameMetadata.HasTransparency ? frameMetadata.TransparencyIndex : -1, background); } - - clonedFrame?.Dispose(); } // Write the header. @@ -403,11 +386,6 @@ private void EncodeAdditionalFrame( background, true); - if (EncodingUtilities.ShouldClearTransparentPixels(this.transparentColorMode)) - { - EncodingUtilities.ClearTransparentPixels(encodingFrame, background); - } - using IndexedImageFrame quantized = this.QuantizeAdditionalFrameAndUpdateMetadata( encodingFrame, bounds, diff --git a/src/ImageSharp/Formats/IAnimatedImageEncoder.cs b/src/ImageSharp/Formats/IAnimatedImageEncoder.cs index d2c3ad6907..26f2114df2 100644 --- a/src/ImageSharp/Formats/IAnimatedImageEncoder.cs +++ b/src/ImageSharp/Formats/IAnimatedImageEncoder.cs @@ -14,17 +14,17 @@ public interface IAnimatedImageEncoder /// as well as the transparent pixels of the first frame. /// The background color is also used when a frame disposal mode is . /// - Color? BackgroundColor { get; } + public Color? BackgroundColor { get; } /// /// Gets the number of times any animation is repeated in supported encoders. /// - ushort? RepeatCount { get; } + public ushort? RepeatCount { get; } /// /// Gets a value indicating whether the root frame is shown as part of the animated sequence in supported encoders. /// - bool? AnimateRootFrame { get; } + public bool? AnimateRootFrame { get; } } /// diff --git a/src/ImageSharp/Formats/ISpecializedDecoderOptions.cs b/src/ImageSharp/Formats/ISpecializedDecoderOptions.cs index e0a4c9b62c..881b5bcd44 100644 --- a/src/ImageSharp/Formats/ISpecializedDecoderOptions.cs +++ b/src/ImageSharp/Formats/ISpecializedDecoderOptions.cs @@ -11,5 +11,5 @@ public interface ISpecializedDecoderOptions /// /// Gets the general decoder options. /// - DecoderOptions GeneralOptions { get; init; } + public DecoderOptions GeneralOptions { get; init; } } diff --git a/src/ImageSharp/Formats/Png/PngEncoder.cs b/src/ImageSharp/Formats/Png/PngEncoder.cs index 1032f88529..b6031c1640 100644 --- a/src/ImageSharp/Formats/Png/PngEncoder.cs +++ b/src/ImageSharp/Formats/Png/PngEncoder.cs @@ -41,11 +41,6 @@ public class PngEncoder : QuantizingAnimatedImageEncoder /// The gamma value of the image. public float? Gamma { get; init; } - /// - /// Gets the transparency threshold. - /// - public byte Threshold { get; init; } = byte.MaxValue; - /// /// Gets a value indicating whether this instance should write an Adam7 interlaced image. /// diff --git a/src/ImageSharp/Formats/Png/PngEncoderCore.cs b/src/ImageSharp/Formats/Png/PngEncoderCore.cs index fea9e801c6..c05c7e8289 100644 --- a/src/ImageSharp/Formats/Png/PngEncoderCore.cs +++ b/src/ImageSharp/Formats/Png/PngEncoderCore.cs @@ -189,12 +189,15 @@ public void Encode(Image image, Stream stream, CancellationToken { int currentFrameIndex = 0; - bool clearTransparency = EncodingUtilities.ShouldClearTransparentPixels(this.encoder.TransparentColorMode); - if (clearTransparency) + bool clearTransparency = EncodingUtilities.ShouldReplaceTransparentPixels(this.encoder.TransparentColorMode); + + // No need to clone when quantizing. The quantizer will do it for us. + // TODO: We should really try to avoid the clone entirely. + if (clearTransparency && this.colorType is not PngColorType.Palette) { currentFrame = clonedFrame = currentFrame.Clone(); currentFrameRegion = currentFrame.PixelBuffer.GetRegion(); - EncodingUtilities.ClearTransparentPixels(this.configuration, in currentFrameRegion, this.backgroundColor.Value); + EncodingUtilities.ReplaceTransparentPixels(this.configuration, in currentFrameRegion, this.backgroundColor.Value); } // Do not move this. We require an accurate bit depth for the header chunk. @@ -286,6 +289,7 @@ public void Encode(Image image, Stream stream, CancellationToken ImageFrame? nextFrame = currentFrameIndex < image.Frames.Count - 1 ? image.Frames[currentFrameIndex + 1] : null; frameMetadata = currentFrame.Metadata.GetPngMetadata(); + bool blend = frameMetadata.BlendMode == FrameBlendMode.Over; Color background = frameMetadata.DisposalMode == FrameDisposalMode.RestoreToBackground ? this.backgroundColor.Value @@ -301,9 +305,9 @@ public void Encode(Image image, Stream stream, CancellationToken background, blend); - if (clearTransparency) + if (clearTransparency && this.colorType is not PngColorType.Palette) { - EncodingUtilities.ClearTransparentPixels(encodingFrame, background); + EncodingUtilities.ReplaceTransparentPixels(encodingFrame, background); } // Each frame control sequence number must be incremented by the number of frame data chunks that follow. @@ -779,11 +783,6 @@ private void WritePaletteChunk(Stream stream, IndexedImageFrame? byte alpha = rgba.A; Unsafe.Add(ref colorTableRef, (uint)i) = rgba.Rgb; - if (alpha > this.encoder.Threshold) - { - alpha = byte.MaxValue; - } - hasAlpha = hasAlpha || alpha < byte.MaxValue; Unsafe.Add(ref alphaTableRef, (uint)i) = alpha; } @@ -1596,11 +1595,9 @@ private void SanitizeAndSetEncoderOptions( } frameQuantizer.BuildPalette( - this.configuration, encoder.TransparentColorMode, encoder.PixelSamplingStrategy, - image, - backgroundColor); + image); return frameQuantizer.QuantizeFrame(frame, bounds); } diff --git a/src/ImageSharp/Formats/Qoi/QoiEncoderCore.cs b/src/ImageSharp/Formats/Qoi/QoiEncoderCore.cs index 872cec3fd0..916830273a 100644 --- a/src/ImageSharp/Formats/Qoi/QoiEncoderCore.cs +++ b/src/ImageSharp/Formats/Qoi/QoiEncoderCore.cs @@ -90,10 +90,10 @@ private void WritePixels(Image image, Stream stream, Cancellatio ImageFrame? clonedFrame = null; try { - if (EncodingUtilities.ShouldClearTransparentPixels(this.encoder.TransparentColorMode)) + if (EncodingUtilities.ShouldReplaceTransparentPixels(this.encoder.TransparentColorMode)) { clonedFrame = image.Frames.RootFrame.Clone(); - EncodingUtilities.ClearTransparentPixels(clonedFrame, Color.Transparent); + EncodingUtilities.ReplaceTransparentPixels(clonedFrame, Color.Transparent); } ImageFrame encodingFrame = clonedFrame ?? image.Frames.RootFrame; diff --git a/src/ImageSharp/Formats/Tga/TgaEncoderCore.cs b/src/ImageSharp/Formats/Tga/TgaEncoderCore.cs index e2ea9c4fe7..17259629ae 100644 --- a/src/ImageSharp/Formats/Tga/TgaEncoderCore.cs +++ b/src/ImageSharp/Formats/Tga/TgaEncoderCore.cs @@ -110,10 +110,10 @@ public void Encode(Image image, Stream stream, CancellationToken ImageFrame? clonedFrame = null; try { - if (EncodingUtilities.ShouldClearTransparentPixels(this.transparentColorMode)) + if (EncodingUtilities.ShouldReplaceTransparentPixels(this.transparentColorMode)) { clonedFrame = image.Frames.RootFrame.Clone(); - EncodingUtilities.ClearTransparentPixels(clonedFrame, Color.Transparent); + EncodingUtilities.ReplaceTransparentPixels(clonedFrame, Color.Transparent); } ImageFrame encodingFrame = clonedFrame ?? image.Frames.RootFrame; diff --git a/src/ImageSharp/Formats/Tiff/TiffEncoderCore.cs b/src/ImageSharp/Formats/Tiff/TiffEncoderCore.cs index da55ef9f9b..1e12781a99 100644 --- a/src/ImageSharp/Formats/Tiff/TiffEncoderCore.cs +++ b/src/ImageSharp/Formats/Tiff/TiffEncoderCore.cs @@ -146,10 +146,10 @@ public void Encode(Image image, Stream stream, CancellationToken { cancellationToken.ThrowIfCancellationRequested(); - if (EncodingUtilities.ShouldClearTransparentPixels(this.transparentColorMode)) + if (EncodingUtilities.ShouldReplaceTransparentPixels(this.transparentColorMode)) { clonedFrame = frame.Clone(); - EncodingUtilities.ClearTransparentPixels(clonedFrame, Color.Transparent); + EncodingUtilities.ReplaceTransparentPixels(clonedFrame, Color.Transparent); } ImageFrame encodingFrame = clonedFrame ?? frame; diff --git a/src/ImageSharp/IndexedImageFrame{TPixel}.cs b/src/ImageSharp/IndexedImageFrame{TPixel}.cs index 49c9e33eb1..a88cdb524e 100644 --- a/src/ImageSharp/IndexedImageFrame{TPixel}.cs +++ b/src/ImageSharp/IndexedImageFrame{TPixel}.cs @@ -25,7 +25,7 @@ public sealed class IndexedImageFrame : IPixelSource, IDisposable /// Initializes a new instance of the class. /// /// - /// The configuration which allows altering default behaviour or extending the library. + /// The configuration which allows altering default behavior or extending the library. /// /// The frame width. /// The frame height. @@ -49,7 +49,7 @@ public IndexedImageFrame(Configuration configuration, int width, int height, Rea } /// - /// Gets the configuration which allows altering default behaviour or extending the library. + /// Gets the configuration which allows altering default behavior or extending the library. /// public Configuration Configuration { get; } diff --git a/src/ImageSharp/Processing/Processors/Dithering/ErroDither.KnownTypes.cs b/src/ImageSharp/Processing/Processors/Dithering/ErrorDither.KnownTypes.cs similarity index 100% rename from src/ImageSharp/Processing/Processors/Dithering/ErroDither.KnownTypes.cs rename to src/ImageSharp/Processing/Processors/Dithering/ErrorDither.KnownTypes.cs diff --git a/src/ImageSharp/Processing/Processors/Dithering/PaletteDitherProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Dithering/PaletteDitherProcessor{TPixel}.cs index 7e672393c7..bb9b4d8c8f 100644 --- a/src/ImageSharp/Processing/Processors/Dithering/PaletteDitherProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Dithering/PaletteDitherProcessor{TPixel}.cs @@ -80,7 +80,7 @@ protected override void Dispose(bool disposing) Justification = "https://github.com/dotnet/roslyn-analyzers/issues/6151")] internal readonly struct DitherProcessor : IPaletteDitherImageProcessor, IDisposable { - private readonly EuclideanPixelMap pixelMap; + private readonly PixelMap pixelMap; [MethodImpl(InliningOptions.ShortMethod)] public DitherProcessor( @@ -89,7 +89,7 @@ public DitherProcessor( float ditherScale) { this.Configuration = configuration; - this.pixelMap = new EuclideanPixelMap(configuration, palette); + this.pixelMap = PixelMapFactory.Create(configuration, palette, ColorMatchingMode.Hybrid); this.Palette = palette; this.DitherScale = ditherScale; } diff --git a/src/ImageSharp/Processing/Processors/Quantization/ColorMatchingMode.cs b/src/ImageSharp/Processing/Processors/Quantization/ColorMatchingMode.cs new file mode 100644 index 0000000000..26fd7d5d76 --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Quantization/ColorMatchingMode.cs @@ -0,0 +1,28 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Processing.Processors.Quantization; + +/// +/// Defines the precision level used when matching colors during quantization. +/// +public enum ColorMatchingMode +{ + /// + /// Uses a coarse caching strategy optimized for performance at the expense of exact matches. + /// This provides the fastest matching but may yield approximate results. + /// + Coarse, + + /// + /// Enables an exact color match cache for the first 512 unique colors encountered, + /// falling back to coarse matching thereafter. + /// + Hybrid, + + /// + /// Performs exact color matching without any caching optimizations. + /// This is the slowest but most accurate matching strategy. + /// + Exact +} diff --git a/src/ImageSharp/Processing/Processors/Quantization/EuclideanPixelMap{TPixel,TCache}.cs b/src/ImageSharp/Processing/Processors/Quantization/EuclideanPixelMap{TPixel,TCache}.cs new file mode 100644 index 0000000000..5b0c7252cb --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Quantization/EuclideanPixelMap{TPixel,TCache}.cs @@ -0,0 +1,184 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using SixLabors.ImageSharp.PixelFormats; + +namespace SixLabors.ImageSharp.Processing.Processors.Quantization; + +/// +/// Gets the closest color to the supplied color based upon the Euclidean distance. +/// +/// The pixel format. +/// The cache type. +/// +/// This class is not thread safe and should not be accessed in parallel. +/// Doing so will result in non-idempotent results. +/// +internal sealed class EuclideanPixelMap : PixelMap + where TPixel : unmanaged, IPixel + where TCache : struct, IColorIndexCache +{ + private Rgba32[] rgbaPalette; + + // Do not make readonly. It's a mutable struct. +#pragma warning disable IDE0044 // Add readonly modifier + private TCache cache; +#pragma warning restore IDE0044 // Add readonly modifier + + private readonly Configuration configuration; + + /// + /// Initializes a new instance of the class. + /// + /// Specifies the settings and resources for the pixel map's operations. + /// Defines the color palette used for pixel mapping. + public EuclideanPixelMap(Configuration configuration, ReadOnlyMemory palette) + { + this.configuration = configuration; + this.Palette = palette; + this.rgbaPalette = new Rgba32[palette.Length]; + this.cache = TCache.Create(configuration.MemoryAllocator); + PixelOperations.Instance.ToRgba32(configuration, this.Palette.Span, this.rgbaPalette); + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public override int GetClosestColor(TPixel color, out TPixel match) + { + ref TPixel paletteRef = ref MemoryMarshal.GetReference(this.Palette.Span); + Rgba32 rgba = color.ToRgba32(); + + if (this.cache.TryGetValue(rgba, out short index)) + { + match = Unsafe.Add(ref paletteRef, (ushort)index); + return index; + } + + return this.GetClosestColorSlow(rgba, ref paletteRef, out match); + } + + /// + public override void Clear(ReadOnlyMemory palette) + { + this.Palette = palette; + this.rgbaPalette = new Rgba32[palette.Length]; + PixelOperations.Instance.ToRgba32(this.configuration, this.Palette.Span, this.rgbaPalette); + this.cache.Clear(); + } + + [MethodImpl(InliningOptions.ColdPath)] + 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; + for (int i = 0; i < this.rgbaPalette.Length; i++) + { + Rgba32 candidate = this.rgbaPalette[i]; + if (candidate.PackedValue == rgba.PackedValue) + { + index = i; + break; + } + + float distance = DistanceSquared(rgba, candidate); + if (distance == 0) + { + index = i; + break; + } + + if (distance < leastDistance) + { + index = i; + leastDistance = distance; + } + } + + // Now I have the index, pop it into the cache for next time + _ = this.cache.TryAdd(rgba, (short)index); + match = Unsafe.Add(ref paletteRef, (uint)index); + + return index; + } + + /// + /// Returns the Euclidean distance squared between two specified points. + /// + /// The first point. + /// The second point. + /// The distance squared. + [MethodImpl(InliningOptions.ShortMethod)] + private static float DistanceSquared(Rgba32 a, Rgba32 b) + { + float deltaR = a.R - b.R; + float deltaG = a.G - b.G; + float deltaB = a.B - b.B; + float deltaA = a.A - b.A; + return (deltaR * deltaR) + (deltaG * deltaG) + (deltaB * deltaB) + (deltaA * deltaA); + } + + /// + public override void Dispose() => this.cache.Dispose(); +} + +/// +/// Represents a map of colors to indices. +/// +/// The pixel format. +internal abstract class PixelMap : IDisposable + where TPixel : unmanaged, IPixel +{ + /// + /// Gets the color palette of this . + /// + public ReadOnlyMemory Palette { get; private protected set; } + + /// + /// Returns the closest color in the palette and the index of that pixel. + /// + /// The color to match. + /// The matched color. + /// + /// The index. + /// + public abstract int GetClosestColor(TPixel color, out TPixel match); + + /// + /// Clears the map, resetting it to use the given palette. + /// + /// The color palette to map from. + public abstract void Clear(ReadOnlyMemory palette); + + /// + public abstract void Dispose(); +} + +/// +/// A factory for creating instances. +/// +internal static class PixelMapFactory +{ + /// + /// Creates a new instance. + /// + /// The pixel format. + /// The configuration. + /// The color palette to map from. + /// The color matching mode. + /// + /// The . + /// + public static PixelMap Create( + Configuration configuration, + ReadOnlyMemory palette, + ColorMatchingMode colorMatchingMode) + where TPixel : unmanaged, IPixel => colorMatchingMode switch + { + ColorMatchingMode.Hybrid => new EuclideanPixelMap(configuration, palette), + ColorMatchingMode.Exact => new EuclideanPixelMap(configuration, palette), + _ => new EuclideanPixelMap(configuration, palette), + }; +} diff --git a/src/ImageSharp/Processing/Processors/Quantization/EuclideanPixelMap{TPixel}.cs b/src/ImageSharp/Processing/Processors/Quantization/EuclideanPixelMap{TPixel}.cs deleted file mode 100644 index 250122e683..0000000000 --- a/src/ImageSharp/Processing/Processors/Quantization/EuclideanPixelMap{TPixel}.cs +++ /dev/null @@ -1,545 +0,0 @@ -// 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 SixLabors.ImageSharp.Memory; -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Processing.Processors.Quantization; - -/// -/// Gets the closest color to the supplied color based upon the Euclidean distance. -/// -/// The pixel format. -/// -/// This class is not thread safe and should not be accessed in parallel. -/// Doing so will result in non-idempotent results. -/// -internal sealed class EuclideanPixelMap : IDisposable - where TPixel : unmanaged, IPixel -{ - private Rgba32[] rgbaPalette; - private readonly HybridColorDistanceCache cache; - private readonly Configuration configuration; - - /// - /// Initializes a new instance of the class. - /// - /// The configuration. - /// The color palette to map from. - public EuclideanPixelMap(Configuration configuration, ReadOnlyMemory palette) - { - this.configuration = configuration; - this.Palette = palette; - this.rgbaPalette = new Rgba32[palette.Length]; - this.cache = new HybridColorDistanceCache(configuration.MemoryAllocator); - PixelOperations.Instance.ToRgba32(configuration, this.Palette.Span, this.rgbaPalette); - } - - /// - /// Gets the color palette of this . - /// The palette memory is owned by the palette source that created it. - /// - public ReadOnlyMemory Palette { get; private set; } - - /// - /// Returns the closest color in the palette and the index of that pixel. - /// The palette contents must match the one used in the constructor. - /// - /// The color to match. - /// The matched color. - /// The transparency threshold. - /// The index. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public int GetClosestColor(TPixel color, out TPixel match, short transparencyThreshold = -1) - { - ref TPixel paletteRef = ref MemoryMarshal.GetReference(this.Palette.Span); - Rgba32 rgba = color.ToRgba32(); - - if (transparencyThreshold > -1 && rgba.A < transparencyThreshold) - { - rgba = default; - } - - // Check if the color is in the lookup table - if (this.cache.TryGetValue(rgba, out short index)) - { - match = Unsafe.Add(ref paletteRef, (ushort)index); - return index; - } - - return this.GetClosestColorSlow(rgba, ref paletteRef, out match); - } - - /// - /// Clears the map, resetting it to use the given palette. - /// - /// The color palette to map from. - public void Clear(ReadOnlyMemory palette) - { - this.Palette = palette; - this.rgbaPalette = new Rgba32[palette.Length]; - PixelOperations.Instance.ToRgba32(this.configuration, this.Palette.Span, this.rgbaPalette); - this.cache.Clear(); - } - - [MethodImpl(MethodImplOptions.NoInlining)] - 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; - for (int i = 0; i < this.rgbaPalette.Length; i++) - { - Rgba32 candidate = this.rgbaPalette[i]; - if (candidate.PackedValue == rgba.PackedValue) - { - index = i; - break; - } - - float distance = DistanceSquared(rgba, candidate); - if (distance == 0) - { - index = i; - break; - } - - if (distance < leastDistance) - { - index = i; - leastDistance = distance; - } - } - - // Now I have the index, pop it into the cache for next time - this.cache.Add(rgba, (short)index); - match = Unsafe.Add(ref paletteRef, (uint)index); - - return index; - } - - /// - /// Returns the Euclidean distance squared between two specified points. - /// - /// The first point. - /// The second point. - /// The distance squared. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static float DistanceSquared(Rgba32 a, Rgba32 b) - { - float deltaR = a.R - b.R; - float deltaG = a.G - b.G; - float deltaB = a.B - b.B; - float deltaA = a.A - b.A; - return (deltaR * deltaR) + (deltaG * deltaG) + (deltaB * deltaB) + (deltaA * deltaA); - } - - public void Dispose() => this.cache.Dispose(); - - /// - /// A hybrid color distance cache that combines a small, fixed-capacity exact-match dictionary - /// (ExactCache, ~4–5 KB for up to 512 entries) with a coarse lookup table (CoarseCache) for 5,5,5,6 precision. - /// - /// - /// ExactCache provides O(1) lookup for common cases using a simple 256-entry hash-based dictionary, while CoarseCache - /// quantizes RGB channels to 5 bits (yielding 32^3 buckets) and alpha to 6 bits, storing up to 4 alpha entries per bucket - /// (a design chosen based on probability theory to capture most real-world variations) for a total memory footprint of - /// roughly 576 KB. Lookups and insertions are performed in constant time, making the overall design both fast and memory-predictable. - /// -#pragma warning disable CA1001 // Types that own disposable fields should be disposable - // https://github.com/dotnet/roslyn-analyzers/issues/6151 - private readonly unsafe struct HybridColorDistanceCache : IDisposable -#pragma warning restore CA1001 // Types that own disposable fields should be disposable - { - private readonly CoarseCache coarseCache; - private readonly ExactCache exactCache; - - public HybridColorDistanceCache(MemoryAllocator allocator) - { - this.exactCache = new ExactCache(allocator); - this.coarseCache = new CoarseCache(allocator); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public readonly void Add(Rgba32 color, short index) - { - if (this.exactCache.TryAdd(color.PackedValue, index)) - { - return; - } - - this.coarseCache.Add(color, index); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public readonly bool TryGetValue(Rgba32 color, out short match) - { - if (this.exactCache.TryGetValue(color.PackedValue, out match)) - { - return true; // Exact match found - } - - if (this.coarseCache.TryGetValue(color, out match)) - { - return true; // Coarse match found - } - - match = -1; - return false; - } - - public readonly void Clear() - { - this.exactCache.Clear(); - this.coarseCache.Clear(); - } - - public void Dispose() - { - this.exactCache.Dispose(); - this.coarseCache.Dispose(); - } - } - - /// - /// A fixed-capacity dictionary with exactly 512 entries mapping a key - /// to a value. - /// - /// - /// The dictionary is implemented using a fixed array of 512 buckets and an entries array - /// of the same size. The bucket for a key is computed as (key & 0x1FF), and collisions are - /// resolved through a linked chain stored in the field. - /// The overall memory usage is approximately 4–5 KB. Both lookup and insertion operations are, - /// on average, O(1) since the bucket is determined via a simple bitmask and collision chains are - /// typically very short; in the worst-case, the number of iterations is bounded by 256. - /// This guarantees highly efficient and predictable performance for small, fixed-size color palettes. - /// - internal sealed unsafe class ExactCache : IDisposable - { - // Buckets array: each bucket holds the index (0-based) into the entries array - // of the first entry in the chain, or -1 if empty. - private readonly IMemoryOwner bucketsOwner; - private MemoryHandle bucketsHandle; - private short* buckets; - - // Entries array: stores up to 256 entries. - private readonly IMemoryOwner entriesOwner; - private MemoryHandle entriesHandle; - private Entry* entries; - - public const int Capacity = 512; - - public ExactCache(MemoryAllocator allocator) - { - this.Count = 0; - - // Allocate exactly 512 ints for buckets. - this.bucketsOwner = allocator.Allocate(Capacity, AllocationOptions.Clean); - Span bucketSpan = this.bucketsOwner.GetSpan(); - bucketSpan.Fill(-1); - this.bucketsHandle = this.bucketsOwner.Memory.Pin(); - this.buckets = (short*)this.bucketsHandle.Pointer; - - // Allocate exactly 512 entries. - this.entriesOwner = allocator.Allocate(Capacity, AllocationOptions.Clean); - this.entriesHandle = this.entriesOwner.Memory.Pin(); - this.entries = (Entry*)this.entriesHandle.Pointer; - } - - public int Count { get; private set; } - - /// - /// Adds a key/value pair to the dictionary. - /// If the key already exists, the dictionary is left unchanged. - /// - /// The key to add. - /// The value to add. - /// if the key was added; otherwise, . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool TryAdd(uint key, short value) - { - if (this.Count == Capacity) - { - return false; // Dictionary is full. - } - - // The key is a 32-bit unsigned integer representing an RGBA color, where the bytes are laid out as R|G|B|A - // (with R in the most significant byte and A in the least significant). - // To compute the bucket index: - // 1. (key >> 16) extracts the top 16 bits, effectively giving us the R and G channels. - // 2. (key >> 8) shifts the key right by 8 bits, bringing R, G, and B into the lower 24 bits (dropping A). - // 3. XORing these two values with the original key mixes bits from all four channels (R, G, B, and A), - // which helps to counteract situations where one or more channels have a limited range. - // 4. Finally, we apply a bitmask of 0x1FF to keep only the lowest 9 bits, ensuring the result is between 0 and 511, - // which corresponds to our fixed bucket count of 512. - int bucket = (int)(((key >> 16) ^ (key >> 8) ^ key) & 0x1FF); - int i = this.buckets[bucket]; - - // Traverse the collision chain. - Entry* entries = this.entries; - while (i != -1) - { - Entry e = entries[i]; - if (e.Key == key) - { - // Key already exists; do not overwrite. - return false; - } - - i = e.Next; - } - - short index = (short)this.Count; - this.Count++; - - // Insert the new entry: - entries[index].Key = key; - entries[index].Value = value; - - // Link this new entry into the bucket chain. - entries[index].Next = this.buckets[bucket]; - this.buckets[bucket] = index; - return true; - } - - /// - /// Tries to retrieve the value associated with the specified key. - /// Returns true if the key is found; otherwise, returns false. - /// - /// The key to search for. - /// The value associated with the key, if found. - /// if the key is found; otherwise, . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool TryGetValue(uint key, out short value) - { - int bucket = (int)(((key >> 16) ^ (key >> 8) ^ key) & 0x1FF); - int i = this.buckets[bucket]; - - // If the bucket is empty, return immediately. - if (i == -1) - { - value = -1; - return false; - } - - // Traverse the chain. - Entry* entries = this.entries; - do - { - Entry e = entries[i]; - if (e.Key == key) - { - value = e.Value; - return true; - } - - i = e.Next; - } - while (i != -1); - - value = -1; - return false; - } - - /// - /// Clears the dictionary. - /// - public void Clear() - { - Span bucketSpan = this.bucketsOwner.GetSpan(); - bucketSpan.Fill(-1); - this.Count = 0; - } - - public void Dispose() - { - this.bucketsHandle.Dispose(); - this.bucketsOwner.Dispose(); - this.entriesHandle.Dispose(); - this.entriesOwner.Dispose(); - this.buckets = null; - this.entries = null; - } - - private struct Entry - { - public uint Key; // The key (packed RGBA) - public short Value; // The value; -1 means unused. - public short Next; // Index of the next entry in the chain, or -1 if none. - } - } - - /// - /// - /// CoarseCache is a fast, low-memory lookup structure for caching palette indices associated with RGBA values, - /// using a quantized representation of 5,5,5,6 (RGB: 5 bits each, Alpha: 6 bits). - /// - /// - /// The cache quantizes the RGB channels to 5 bits each, resulting in 32 levels per channel and a total of 32³ = 32,768 buckets. - /// Each bucket is represented by an , which holds a small, inline array of alpha entries. - /// Each alpha entry stores the alpha value quantized to 6 bits (0–63) along with a palette index (a 16-bit value). - /// - /// - /// Performance Characteristics: - /// - Lookup: O(1) for computing the bucket index from the RGB channels, plus a small constant time (up to 4 iterations) - /// to search through the alpha entries in the bucket. - /// - Insertion: O(1) for bucket index computation and a quick linear search over a very small (fixed) number of entries. - /// - /// - /// Memory Characteristics: - /// - The cache consists of 32,768 buckets. - /// - Each is implemented using an inline array with a capacity of 4 entries. - /// - Each bucket occupies approximately 18 bytes. - /// - Overall, the buckets occupy roughly 32,768 × 18 = 589,824 bytes (576 KB). - /// - /// - /// This design provides nearly constant-time lookup and insertion with minimal memory usage, - /// making it ideal for applications such as color distance caching in images with a limited palette (up to 256 entries). - /// - /// - internal sealed unsafe class CoarseCache : IDisposable - { - // Use 5 bits per channel for R, G, and B: 32 levels each. - // Total buckets = 32^3 = 32768. - private const int RgbBits = 5; - private const int BucketCount = 1 << (RgbBits * 3); // 32768 - private readonly IMemoryOwner bucketsOwner; - private readonly AlphaBucket* buckets; - private MemoryHandle bucketHandle; - - public CoarseCache(MemoryAllocator allocator) - { - this.bucketsOwner = allocator.Allocate(BucketCount, AllocationOptions.Clean); - this.bucketHandle = this.bucketsOwner.Memory.Pin(); - this.buckets = (AlphaBucket*)this.bucketHandle.Pointer; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static int GetBucketIndex(byte r, byte g, byte b) - { - int qr = r >> (8 - RgbBits); - int qg = g >> (8 - RgbBits); - int qb = b >> (8 - RgbBits); - - // Combine the quantized channels into a single index. - return (qr << (RgbBits * 2)) | (qg << RgbBits) | qb; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static byte QuantizeAlpha(byte a) - - // Quantize to 6 bits: shift right by (8 - 6) = 2 bits. - => (byte)(a >> 2); - - public void Add(Rgba32 color, short paletteIndex) - { - int bucketIndex = GetBucketIndex(color.R, color.G, color.B); - byte quantAlpha = QuantizeAlpha(color.A); - this.buckets[bucketIndex].Add(quantAlpha, paletteIndex); - } - - public void Dispose() - { - this.bucketHandle.Dispose(); - this.bucketsOwner.Dispose(); - } - - public bool TryGetValue(Rgba32 color, out short paletteIndex) - { - int bucketIndex = GetBucketIndex(color.R, color.G, color.B); - byte quantAlpha = QuantizeAlpha(color.A); - return this.buckets[bucketIndex].TryGetValue(quantAlpha, out paletteIndex); - } - - public void Clear() - { - Span bucketsSpan = this.bucketsOwner.GetSpan(); - bucketsSpan.Clear(); - } - - public struct AlphaEntry - { - // Store the alpha value quantized to 6 bits (0..63) - public byte QuantizedAlpha; - public short PaletteIndex; - } - - public struct AlphaBucket - { - // Fixed capacity for alpha entries in this bucket. - // We choose a capacity of 4 for several reasons: - // - // 1. The alpha channel is quantized to 6 bits, so there are 64 possible distinct values. - // In the worst-case, a given RGB bucket might encounter up to 64 different alpha values. - // - // 2. However, in practice (based on probability theory and typical image data), - // the number of unique alpha values that actually occur for a given quantized RGB - // bucket is usually very small. If you randomly sample 4 values out of 64, - // the probability that these 4 samples are all unique is high if the distribution - // of alpha values is skewed or if only a few alpha values are used. - // - // 3. Statistically, for many real-world images, most RGB buckets will have only a couple - // of unique alpha values. Allocating 4 slots per bucket provides a good trade-off: - // it captures the common-case scenario while keeping overall memory usage low. - // - // 4. Even if more than 4 unique alpha values occur in a bucket, - // our design overwrites the first entry. This behavior gives us some "wriggle room" - // while preserving the most frequently encountered or most recent values. - public const int Capacity = 4; - public byte Count; - private InlineArray4 entries; - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool TryGetValue(byte quantizedAlpha, out short paletteIndex) - { - for (int i = 0; i < this.Count; i++) - { - ref AlphaEntry entry = ref this.entries[i]; - if (entry.QuantizedAlpha == quantizedAlpha) - { - paletteIndex = entry.PaletteIndex; - return true; - } - } - - paletteIndex = -1; - return false; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Add(byte quantizedAlpha, short paletteIndex) - { - // Check for an existing entry with the same quantized alpha. - for (int i = 0; i < this.Count; i++) - { - ref AlphaEntry entry = ref this.entries[i]; - if (entry.QuantizedAlpha == quantizedAlpha) - { - // Update palette index if found. - entry.PaletteIndex = paletteIndex; - return; - } - } - - // If there's room, add a new entry. - if (this.Count < Capacity) - { - ref AlphaEntry newEntry = ref this.entries[this.Count]; - newEntry.QuantizedAlpha = quantizedAlpha; - newEntry.PaletteIndex = paletteIndex; - this.Count++; - } - else - { - // Bucket is full. Overwrite the first entry to give us some wriggle room. - this.entries[0].QuantizedAlpha = quantizedAlpha; - this.entries[0].PaletteIndex = paletteIndex; - } - } - } - } -} diff --git a/src/ImageSharp/Processing/Processors/Quantization/IColorIndexCache.cs b/src/ImageSharp/Processing/Processors/Quantization/IColorIndexCache.cs new file mode 100644 index 0000000000..52efc62b7e --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Quantization/IColorIndexCache.cs @@ -0,0 +1,491 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Buffers; +using System.Runtime.CompilerServices; +using SixLabors.ImageSharp.Memory; +using SixLabors.ImageSharp.PixelFormats; + +namespace SixLabors.ImageSharp.Processing.Processors.Quantization; + +/// +/// Represents a cache used for efficiently retrieving palette indices for colors. +/// +internal interface IColorIndexCache : IDisposable +{ + /// + /// Adds a color to the cache. + /// + /// The color to add. + /// The index of the color in the palette. + /// + /// if the color was added; otherwise, . + /// + public bool TryAdd(Rgba32 color, short value); + + /// + /// Gets the index of the color in the palette. + /// + /// The color to get the index for. + /// The index of the color in the palette. + /// + /// if the color is in the palette; otherwise, . + /// + public bool TryGetValue(Rgba32 color, out short value); + + /// + /// Clears the cache. + /// + public void Clear(); +} + +/// +/// Represents a cache used for efficiently retrieving palette indices for colors. +/// +/// The type of the cache. +internal interface IColorIndexCache : IColorIndexCache + where T : struct, IColorIndexCache +{ + /// + /// Creates a new instance of the cache. + /// + /// The memory allocator to use. + /// + /// The new instance of the cache. + /// + public static abstract T Create(MemoryAllocator allocator); +} + +/// +/// A hybrid color distance cache that combines a small, fixed-capacity exact-match dictionary +/// (ExactCache, ~4–5 KB for up to 512 entries) with a coarse lookup table (CoarseCache) for 5,5,5,6 precision. +/// +/// +/// ExactCache provides O(1) lookup for common cases using a simple 256-entry hash-based dictionary, while CoarseCache +/// quantizes RGB channels to 5 bits (yielding 32^3 buckets) and alpha to 6 bits, storing up to 4 alpha entries per bucket +/// (a design chosen based on probability theory to capture most real-world variations) for a total memory footprint of +/// roughly 576 KB. Lookups and insertions are performed in constant time, making the overall design both fast and memory-predictable. +/// +internal unsafe struct HybridCache : IColorIndexCache +{ + private CoarseCache coarseCache; + private ExactCache exactCache; + + public HybridCache(MemoryAllocator allocator) + { + this.exactCache = ExactCache.Create(allocator); + this.coarseCache = CoarseCache.Create(allocator); + } + + /// + public static HybridCache Create(MemoryAllocator allocator) => new(allocator); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public bool TryAdd(Rgba32 color, short index) + { + if (this.exactCache.TryAdd(color, index)) + { + return true; + } + + return this.coarseCache.TryAdd(color, index); + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public readonly bool TryGetValue(Rgba32 color, out short value) + { + if (this.exactCache.TryGetValue(color, out value)) + { + return true; + } + + return this.coarseCache.TryGetValue(color, out value); + } + + /// + public readonly void Clear() + { + this.exactCache.Clear(); + this.coarseCache.Clear(); + } + + /// + public void Dispose() + { + this.exactCache.Dispose(); + this.coarseCache.Dispose(); + } +} + +/// +/// +/// CoarseCache is a fast, low-memory lookup structure for caching palette indices associated with RGBA values, +/// using a quantized representation of 5,5,5,6 (RGB: 5 bits each, Alpha: 6 bits). +/// +/// +/// The cache quantizes the RGB channels to 5 bits each, resulting in 32 levels per channel and a total of 32³ = 32,768 buckets. +/// Each bucket is represented by an , which holds a small, inline array of alpha entries. +/// Each alpha entry stores the alpha value quantized to 6 bits (0–63) along with a palette index (a 16-bit value). +/// +/// +/// Performance Characteristics: +/// - Lookup: O(1) for computing the bucket index from the RGB channels, plus a small constant time (up to 4 iterations) +/// to search through the alpha entries in the bucket. +/// - Insertion: O(1) for bucket index computation and a quick linear search over a very small (fixed) number of entries. +/// +/// +/// Memory Characteristics: +/// - The cache consists of 32,768 buckets. +/// - Each is implemented using an inline array with a capacity of 4 entries. +/// - Each bucket occupies approximately 18 bytes. +/// - Overall, the buckets occupy roughly 32,768 × 18 = 589,824 bytes (576 KB). +/// +/// +/// This design provides nearly constant-time lookup and insertion with minimal memory usage, +/// making it ideal for applications such as color distance caching in images with a limited palette (up to 256 entries). +/// +/// +internal unsafe struct CoarseCache : IColorIndexCache +{ + // Use 5 bits per channel for R, G, and B: 32 levels each. + // Total buckets = 32^3 = 32768. + private const int RgbBits = 5; + private const int RgbShift = 8 - RgbBits; // 3 + private const int BucketCount = 1 << (RgbBits * 3); // 32768 + private readonly IMemoryOwner bucketsOwner; + private readonly AlphaBucket* buckets; + private MemoryHandle bucketHandle; + + private CoarseCache(MemoryAllocator allocator) + { + this.bucketsOwner = allocator.Allocate(BucketCount, AllocationOptions.Clean); + this.bucketHandle = this.bucketsOwner.Memory.Pin(); + this.buckets = (AlphaBucket*)this.bucketHandle.Pointer; + } + + /// + public static CoarseCache Create(MemoryAllocator allocator) => new(allocator); + + /// + public readonly bool TryAdd(Rgba32 color, short paletteIndex) + { + int bucketIndex = GetBucketIndex(color.R, color.G, color.B); + byte quantAlpha = QuantizeAlpha(color.A); + this.buckets[bucketIndex].Add(quantAlpha, paletteIndex); + return true; + } + + /// + public readonly bool TryGetValue(Rgba32 color, out short paletteIndex) + { + int bucketIndex = GetBucketIndex(color.R, color.G, color.B); + byte quantAlpha = QuantizeAlpha(color.A); + return this.buckets[bucketIndex].TryGetValue(quantAlpha, out paletteIndex); + } + + /// + public readonly void Clear() + { + Span bucketsSpan = this.bucketsOwner.GetSpan(); + bucketsSpan.Clear(); + } + + /// + public void Dispose() + { + this.bucketHandle.Dispose(); + this.bucketsOwner.Dispose(); + } + + [MethodImpl(InliningOptions.ShortMethod)] + private static int GetBucketIndex(byte r, byte g, byte b) + { + int qr = r >> RgbShift; + int qg = g >> RgbShift; + int qb = b >> RgbShift; + + // Combine the quantized channels into a single index. + return (qr << (RgbBits << 1)) | (qg << RgbBits) | qb; + } + + [MethodImpl(InliningOptions.ShortMethod)] + private static byte QuantizeAlpha(byte a) + + // Quantize to 6 bits: shift right by (8 - 6) = 2 bits. + => (byte)(a >> 2); + + public struct AlphaEntry + { + // Store the alpha value quantized to 6 bits (0..63) + public byte QuantizedAlpha; + public short PaletteIndex; + } + + public struct AlphaBucket + { + // Fixed capacity for alpha entries in this bucket. + // We choose a capacity of 4 for several reasons: + // + // 1. The alpha channel is quantized to 6 bits, so there are 64 possible distinct values. + // In the worst-case, a given RGB bucket might encounter up to 64 different alpha values. + // + // 2. However, in practice (based on probability theory and typical image data), + // the number of unique alpha values that actually occur for a given quantized RGB + // bucket is usually very small. If you randomly sample 4 values out of 64, + // the probability that these 4 samples are all unique is high if the distribution + // of alpha values is skewed or if only a few alpha values are used. + // + // 3. Statistically, for many real-world images, most RGB buckets will have only a couple + // of unique alpha values. Allocating 4 slots per bucket provides a good trade-off: + // it captures the common-case scenario while keeping overall memory usage low. + // + // 4. Even if more than 4 unique alpha values occur in a bucket, + // our design overwrites the first entry. This behavior gives us some "wriggle room" + // while preserving the most frequently encountered or most recent values. + public const int Capacity = 4; + public byte Count; + private InlineArray4 entries; + + [MethodImpl(InliningOptions.ShortMethod)] + public bool TryGetValue(byte quantizedAlpha, out short paletteIndex) + { + for (int i = 0; i < this.Count; i++) + { + ref AlphaEntry entry = ref this.entries[i]; + if (entry.QuantizedAlpha == quantizedAlpha) + { + paletteIndex = entry.PaletteIndex; + return true; + } + } + + paletteIndex = -1; + return false; + } + + [MethodImpl(InliningOptions.ShortMethod)] + public void Add(byte quantizedAlpha, short paletteIndex) + { + // Check for an existing entry with the same quantized alpha. + for (int i = 0; i < this.Count; i++) + { + ref AlphaEntry entry = ref this.entries[i]; + if (entry.QuantizedAlpha == quantizedAlpha) + { + // Update palette index if found. + entry.PaletteIndex = paletteIndex; + return; + } + } + + // If there's room, add a new entry. + if (this.Count < Capacity) + { + ref AlphaEntry newEntry = ref this.entries[this.Count]; + newEntry.QuantizedAlpha = quantizedAlpha; + newEntry.PaletteIndex = paletteIndex; + this.Count++; + } + else + { + // Bucket is full. Overwrite the first entry to give us some wriggle room. + this.entries[0].QuantizedAlpha = quantizedAlpha; + this.entries[0].PaletteIndex = paletteIndex; + } + } + } +} + +/// +/// A fixed-capacity dictionary with exactly 512 entries mapping a key +/// to a value. +/// +/// +/// The dictionary is implemented using a fixed array of 512 buckets and an entries array +/// of the same size. The bucket for a key is computed as (key & 0x1FF), and collisions are +/// resolved through a linked chain stored in the field. +/// The overall memory usage is approximately 4–5 KB. Both lookup and insertion operations are, +/// on average, O(1) since the bucket is determined via a simple bitmask and collision chains are +/// typically very short; in the worst-case, the number of iterations is bounded by 256. +/// This guarantees highly efficient and predictable performance for small, fixed-size color palettes. +/// +internal unsafe struct ExactCache : IColorIndexCache +{ + // Buckets array: each bucket holds the index (0-based) into the entries array + // of the first entry in the chain, or -1 if empty. + private readonly IMemoryOwner bucketsOwner; + private MemoryHandle bucketsHandle; + private short* buckets; + + // Entries array: stores up to 256 entries. + private readonly IMemoryOwner entriesOwner; + private MemoryHandle entriesHandle; + private Entry* entries; + + public const int Capacity = 512; + + private ExactCache(MemoryAllocator allocator) + { + this.Count = 0; + + // Allocate exactly 512 indexes for buckets. + this.bucketsOwner = allocator.Allocate(Capacity, AllocationOptions.Clean); + Span bucketSpan = this.bucketsOwner.GetSpan(); + bucketSpan.Fill(-1); + this.bucketsHandle = this.bucketsOwner.Memory.Pin(); + this.buckets = (short*)this.bucketsHandle.Pointer; + + // Allocate exactly 512 entries. + this.entriesOwner = allocator.Allocate(Capacity, AllocationOptions.Clean); + this.entriesHandle = this.entriesOwner.Memory.Pin(); + this.entries = (Entry*)this.entriesHandle.Pointer; + } + + public int Count { get; private set; } + + /// + public static ExactCache Create(MemoryAllocator allocator) => new(allocator); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public bool TryAdd(Rgba32 color, short value) + { + if (this.Count == Capacity) + { + return false; // Dictionary is full. + } + + uint key = color.PackedValue; + + // The key is a 32-bit unsigned integer representing an RGBA color, where the bytes are laid out as R|G|B|A + // (with R in the most significant byte and A in the least significant). + // To compute the bucket index: + // 1. (key >> 16) extracts the top 16 bits, effectively giving us the R and G channels. + // 2. (key >> 8) shifts the key right by 8 bits, bringing R, G, and B into the lower 24 bits (dropping A). + // 3. XORing these two values with the original key mixes bits from all four channels (R, G, B, and A), + // which helps to counteract situations where one or more channels have a limited range. + // 4. Finally, we apply a bitmask of 0x1FF to keep only the lowest 9 bits, ensuring the result is between 0 and 511, + // which corresponds to our fixed bucket count of 512. + int bucket = (int)(((key >> 16) ^ (key >> 8) ^ key) & 0x1FF); + int i = this.buckets[bucket]; + + // Traverse the collision chain. + Entry* entries = this.entries; + while (i != -1) + { + Entry e = entries[i]; + if (e.Key == key) + { + // Key already exists; do not overwrite. + return false; + } + + i = e.Next; + } + + short index = (short)this.Count; + this.Count++; + + // Insert the new entry: + entries[index].Key = key; + entries[index].Value = value; + + // Link this new entry into the bucket chain. + entries[index].Next = this.buckets[bucket]; + this.buckets[bucket] = index; + return true; + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public bool TryGetValue(Rgba32 color, out short value) + { + uint key = color.PackedValue; + int bucket = (int)(((key >> 16) ^ (key >> 8) ^ key) & 0x1FF); + int i = this.buckets[bucket]; + + // If the bucket is empty, return immediately. + if (i == -1) + { + value = -1; + return false; + } + + // Traverse the chain. + Entry* entries = this.entries; + do + { + Entry e = entries[i]; + if (e.Key == key) + { + value = e.Value; + return true; + } + + i = e.Next; + } + while (i != -1); + + value = -1; + return false; + } + + /// + /// Clears the dictionary. + /// + public void Clear() + { + Span bucketSpan = this.bucketsOwner.GetSpan(); + bucketSpan.Fill(-1); + this.Count = 0; + } + + public void Dispose() + { + this.bucketsHandle.Dispose(); + this.bucketsOwner.Dispose(); + this.entriesHandle.Dispose(); + this.entriesOwner.Dispose(); + this.buckets = null; + this.entries = null; + } + + private struct Entry + { + public uint Key; // The key (packed RGBA) + public short Value; // The value; -1 means unused. + public short Next; // Index of the next entry in the chain, or -1 if none. + } +} + +/// +/// Represents a cache that does not store any values. +/// It allows adding colors, but always returns false when trying to retrieve them. +/// +internal readonly struct NullCache : IColorIndexCache +{ + /// + public static NullCache Create(MemoryAllocator allocator) => default; + + /// + public bool TryAdd(Rgba32 color, short value) => true; + + /// + public bool TryGetValue(Rgba32 color, out short value) + { + value = -1; + return false; + } + + /// + public void Clear() + { + } + + /// + public void Dispose() + { + } +} diff --git a/src/ImageSharp/Processing/Processors/Quantization/IQuantizer.cs b/src/ImageSharp/Processing/Processors/Quantization/IQuantizer.cs index 9d5b606040..02dce8ca48 100644 --- a/src/ImageSharp/Processing/Processors/Quantization/IQuantizer.cs +++ b/src/ImageSharp/Processing/Processors/Quantization/IQuantizer.cs @@ -13,7 +13,7 @@ public interface IQuantizer /// /// Gets the quantizer options defining quantization rules. /// - QuantizerOptions Options { get; } + public QuantizerOptions Options { get; } /// /// Creates the generic frame quantizer. @@ -21,7 +21,7 @@ public interface IQuantizer /// The to configure internal operations. /// The pixel format. /// The . - IQuantizer CreatePixelSpecificQuantizer(Configuration configuration) + public IQuantizer CreatePixelSpecificQuantizer(Configuration configuration) where TPixel : unmanaged, IPixel; /// @@ -31,6 +31,6 @@ IQuantizer CreatePixelSpecificQuantizer(Configuration configurat /// The to configure internal operations. /// The options to create the quantizer with. /// The . - IQuantizer CreatePixelSpecificQuantizer(Configuration configuration, QuantizerOptions options) + public IQuantizer CreatePixelSpecificQuantizer(Configuration configuration, QuantizerOptions options) where TPixel : unmanaged, IPixel; } diff --git a/src/ImageSharp/Processing/Processors/Quantization/IQuantizer{TPixel}.cs b/src/ImageSharp/Processing/Processors/Quantization/IQuantizer{TPixel}.cs index dc5bdbd627..02c2052fef 100644 --- a/src/ImageSharp/Processing/Processors/Quantization/IQuantizer{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Quantization/IQuantizer{TPixel}.cs @@ -1,6 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. +using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; @@ -27,7 +28,7 @@ public interface IQuantizer : IDisposable /// Gets the quantized color palette. /// /// - /// The palette has not been built via . + /// The palette has not been built via . /// public ReadOnlyMemory Palette { get; } @@ -35,21 +36,45 @@ public interface IQuantizer : IDisposable /// Adds colors to the quantized palette from the given pixel source. /// /// The of source pixels to register. - public void AddPaletteColors(in Buffer2DRegion pixelRegion); + public void AddPaletteColors(in Buffer2DRegion pixelRegion) + => this.AddPaletteColors(pixelRegion, TransparentColorMode.Preserve); + + /// + /// Adds colors to the quantized palette from the given pixel source. + /// + /// The of source pixels to register. + /// The to use when adding colors to the palette. + public void AddPaletteColors(in Buffer2DRegion pixelRegion, TransparentColorMode mode); + + /// + /// Quantizes an image frame and return the resulting output pixels. + /// + /// The source image frame to quantize. + /// The bounds within the frame to quantize. + /// + /// A representing a quantized version of the source frame pixels. + /// + /// + /// Only executes the second (quantization) step. The palette has to be built by calling . + /// To run both steps, use . + /// + public IndexedImageFrame QuantizeFrame(ImageFrame source, Rectangle bounds) + => this.QuantizeFrame(source, bounds, TransparentColorMode.Preserve); /// /// Quantizes an image frame and return the resulting output pixels. /// /// The source image frame to quantize. /// The bounds within the frame to quantize. + /// The to use when quantizing the frame. /// /// A representing a quantized version of the source frame pixels. /// /// - /// Only executes the second (quantization) step. The palette has to be built by calling . - /// To run both steps, use . + /// Only executes the second (quantization) step. The palette has to be built by calling . + /// To run both steps, use . /// - public IndexedImageFrame QuantizeFrame(ImageFrame source, Rectangle bounds); + public IndexedImageFrame QuantizeFrame(ImageFrame source, Rectangle bounds, TransparentColorMode mode); /// /// Returns the index and color from the quantized palette corresponding to the given color. diff --git a/src/ImageSharp/Processing/Processors/Quantization/IQuantizingPixelRowDelegate{TPixel}.cs b/src/ImageSharp/Processing/Processors/Quantization/IQuantizingPixelRowDelegate{TPixel}.cs new file mode 100644 index 0000000000..ce06adf455 --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Quantization/IQuantizingPixelRowDelegate{TPixel}.cs @@ -0,0 +1,27 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.ImageSharp.Formats; +using SixLabors.ImageSharp.PixelFormats; + +namespace SixLabors.ImageSharp.Processing.Processors.Quantization; + +/// +/// Defines a delegate for processing a row of pixels in an image for quantization. +/// +/// Represents a pixel type that can be processed in a quantizing operation. +internal interface IQuantizingPixelRowDelegate + where TPixel : unmanaged, IPixel +{ + /// + /// Gets the transparent color mode to use when adding colors to the palette. + /// + public TransparentColorMode TransparentColorMode { get; } + + /// + /// Processes a row of pixels for quantization. + /// + /// The row of pixels to process. + /// The index of the row being processed. + public void Invoke(ReadOnlySpan row, int rowIndex); +} diff --git a/src/ImageSharp/Processing/Processors/Quantization/OctreeQuantizer{TPixel}.cs b/src/ImageSharp/Processing/Processors/Quantization/OctreeQuantizer{TPixel}.cs index 65e814e620..e094b18964 100644 --- a/src/ImageSharp/Processing/Processors/Quantization/OctreeQuantizer{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Quantization/OctreeQuantizer{TPixel}.cs @@ -6,6 +6,7 @@ using System.Numerics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; +using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; @@ -16,11 +17,10 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization; /// /// /// The pixel format. -[SuppressMessage( - "Design", - "CA1001:Types that own disposable fields should be disposable", - Justification = "https://github.com/dotnet/roslyn-analyzers/issues/6151")] +#pragma warning disable CA1001 // Types that own disposable fields should be disposable +// See https://github.com/dotnet/roslyn-analyzers/issues/6151 public struct OctreeQuantizer : IQuantizer +#pragma warning restore CA1001 // Types that own disposable fields should be disposable where TPixel : unmanaged, IPixel { private readonly int maxColors; @@ -28,15 +28,14 @@ public struct OctreeQuantizer : IQuantizer private readonly Octree octree; private readonly IMemoryOwner paletteOwner; private ReadOnlyMemory palette; - private EuclideanPixelMap? pixelMap; + private PixelMap? pixelMap; private readonly bool isDithering; - private readonly short transparencyThreshold; private bool isDisposed; /// /// Initializes a new instance of the struct. /// - /// The configuration which allows altering default behaviour or extending the library. + /// The configuration which allows altering default behavior or extending the library. /// The quantizer options defining quantization rules. [MethodImpl(InliningOptions.ShortMethod)] public OctreeQuantizer(Configuration configuration, QuantizerOptions options) @@ -45,9 +44,8 @@ public OctreeQuantizer(Configuration configuration, QuantizerOptions options) this.Options = options; this.maxColors = this.Options.MaxColors; - this.transparencyThreshold = (short)(this.Options.TransparencyThreshold * 255); this.bitDepth = Numerics.Clamp(ColorNumerics.GetBitsNeededForColorDepth(this.maxColors), 1, 8); - this.octree = new Octree(this.bitDepth, this.maxColors, this.transparencyThreshold, configuration.MemoryAllocator); + this.octree = new Octree(configuration, this.bitDepth, this.maxColors, this.Options.TransparencyThreshold, this.Options.ThresholdReplacementColor); this.paletteOwner = configuration.MemoryAllocator.Allocate(this.maxColors, AllocationOptions.Clean); this.pixelMap = default; this.palette = default; @@ -77,25 +75,13 @@ public ReadOnlyMemory Palette } /// - public readonly void AddPaletteColors(in Buffer2DRegion pixelRegion) + public readonly void AddPaletteColors(in Buffer2DRegion pixelRegion, TransparentColorMode mode) { - using IMemoryOwner buffer = this.Configuration.MemoryAllocator.Allocate(pixelRegion.Width); - Span bufferSpan = buffer.GetSpan(); - - // Loop through each row - for (int y = 0; y < pixelRegion.Height; y++) - { - Span row = pixelRegion.DangerousGetRowSpan(y); - PixelOperations.Instance.ToRgba32(this.Configuration, row, bufferSpan); - - Octree octree = this.octree; - int transparencyThreshold = this.transparencyThreshold; - for (int x = 0; x < bufferSpan.Length; x++) - { - // Add the color to the Octree - octree.AddColor(bufferSpan[x]); - } - } + PixelRowDelegate pixelRowDelegate = new(this.octree, mode); + QuantizerUtilities.AddPaletteColors, TPixel, Rgba32, PixelRowDelegate>( + ref Unsafe.AsRef(in this), + in pixelRegion, + in pixelRowDelegate); } private void ResolvePalette() @@ -108,7 +94,7 @@ private void ResolvePalette() if (this.isDithering) { - this.pixelMap = new EuclideanPixelMap(this.Configuration, result); + this.pixelMap = PixelMapFactory.Create(this.Configuration, result, this.Options.ColorMatchingMode); } this.palette = result; @@ -117,7 +103,12 @@ private void ResolvePalette() /// [MethodImpl(InliningOptions.ShortMethod)] public readonly IndexedImageFrame QuantizeFrame(ImageFrame source, Rectangle bounds) - => QuantizerUtilities.QuantizeFrame(ref Unsafe.AsRef(in this), source, bounds); + => this.QuantizeFrame(source, bounds, TransparentColorMode.Preserve); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public readonly IndexedImageFrame QuantizeFrame(ImageFrame source, Rectangle bounds, TransparentColorMode mode) + => QuantizerUtilities.QuantizeFrame(ref Unsafe.AsRef(in this), source, bounds, mode); /// [MethodImpl(InliningOptions.ShortMethod)] @@ -128,7 +119,7 @@ public readonly byte GetQuantizedColor(TPixel color, out TPixel match) // In this case, we must use the pixel map to get the closest color. if (this.isDithering) { - return (byte)this.pixelMap!.GetClosestColor(color, out match, this.transparencyThreshold); + return (byte)this.pixelMap!.GetClosestColor(color, out match); } ref TPixel paletteRef = ref MemoryMarshal.GetReference(this.palette.Span); @@ -151,6 +142,21 @@ public void Dispose() } } + private readonly struct PixelRowDelegate : IQuantizingPixelRowDelegate + { + private readonly Octree octree; + + public PixelRowDelegate(Octree octree, TransparentColorMode mode) + { + this.octree = octree; + this.TransparentColorMode = mode; + } + + public TransparentColorMode TransparentColorMode { get; } + + public void Invoke(ReadOnlySpan row, int rowIndex) => this.octree.AddColors(row); + } + /// /// A hexadecatree-based color quantization structure used for fast color distance lookups and palette generation. /// This tree maintains a fixed pool of nodes (capacity 4096) where each node can have up to 16 children, stores @@ -159,6 +165,9 @@ public void Dispose() /// internal sealed class Octree : IDisposable { + // The memory allocator. + private readonly MemoryAllocator allocator; + // Pooled buffer for OctreeNodes. private readonly IMemoryOwner nodesOwner; @@ -172,7 +181,7 @@ internal sealed class Octree : IDisposable private readonly int maxColorBits; // The threshold for transparent colors. - private readonly short transparencyThreshold; + private readonly int transparencyThreshold255; // Instead of a reference to the root, we store the index of the root node. // Index 0 is reserved for the root. @@ -185,28 +194,43 @@ internal sealed class Octree : IDisposable private int previousNode; private Rgba32 previousColor; + // The color to use for pixels below the transparency threshold. + private Vector4 thresholdReplacementColor; + private Vector4 thresholdReplacementColorV4; + private readonly Rgba32 thresholdReplacementColorRgba; + // Free list for reclaimed node indices. private readonly Stack freeIndices = new(); /// /// Initializes a new instance of the class. /// + /// The configuration which allows altering default behavior or extending the library. /// The maximum number of significant bits in the image. /// The maximum number of colors to allow in the palette. /// The threshold for transparent colors. - /// The memory allocator. - public Octree(int maxColorBits, int maxColors, short transparencyThreshold, MemoryAllocator allocator) + /// The color to use for pixels below the transparency threshold. + public Octree( + Configuration configuration, + int maxColorBits, + int maxColors, + float transparencyThreshold, + Color thresholdReplacementColor) { this.maxColorBits = maxColorBits; this.maxColors = maxColors; - this.transparencyThreshold = transparencyThreshold; + this.transparencyThreshold255 = (int)(transparencyThreshold * 255F); + this.thresholdReplacementColor = thresholdReplacementColor.ToScaledVector4(); + this.thresholdReplacementColorV4 = this.thresholdReplacementColor * 255F; + this.thresholdReplacementColorRgba = thresholdReplacementColor.ToPixel(); this.Leaves = 0; this.previousNode = -1; this.previousColor = default; // Allocate a conservative buffer for nodes. const int capacity = 4096; - this.nodesOwner = allocator.Allocate(capacity, AllocationOptions.Clean); + this.allocator = configuration.MemoryAllocator; + this.nodesOwner = this.allocator.Allocate(capacity, AllocationOptions.Clean); // Create the reducible nodes array (one per level 0 .. maxColorBits-1). this.reducibleNodes = new short[this.maxColorBits]; @@ -228,11 +252,23 @@ public Octree(int maxColorBits, int maxColors, short transparencyThreshold, Memo /// internal Span Nodes => this.nodesOwner.Memory.Span; + /// + /// Adds a span of colors to the octree. + /// + /// A span of color values to be added. + public void AddColors(ReadOnlySpan row) + { + for (int x = 0; x < row.Length; x++) + { + this.AddColor(row[x]); + } + } + /// /// Add a color to the Octree. /// /// The color to add. - public void AddColor(Rgba32 color) + private void AddColor(Rgba32 color) { // Ensure that the tree is not already full. if (this.nextNode >= this.Nodes.Length && this.freeIndices.Count == 0) @@ -243,11 +279,6 @@ public void AddColor(Rgba32 color) } } - if (color.A < this.transparencyThreshold) - { - color = default; - } - // If the color is the same as the previous color, increment the node. // Otherwise, add a new node. if (this.previousColor.Equals(color)) @@ -540,9 +571,9 @@ public void ConstructPalette(Octree octree, Span palette, ref short pale Vector4.Zero, new Vector4(255)); - if (vector.W < octree.transparencyThreshold) + if (vector.W < octree.transparencyThreshold255) { - vector = default; + vector = octree.thresholdReplacementColorV4; } palette[paletteIndex] = TPixel.FromRgba32(new Rgba32((byte)vector.X, (byte)vector.Y, (byte)vector.Z, (byte)vector.W)); @@ -571,9 +602,9 @@ public void ConstructPalette(Octree octree, Span palette, ref short pale /// The parent octree. public int GetPaletteIndex(Rgba32 color, int level, Octree octree) { - if (color.A < octree.transparencyThreshold) + if (color.A < octree.transparencyThreshold255) { - color = default; + color = octree.thresholdReplacementColorRgba; } if (this.Leaf) diff --git a/src/ImageSharp/Processing/Processors/Quantization/PaletteQuantizer{TPixel}.cs b/src/ImageSharp/Processing/Processors/Quantization/PaletteQuantizer{TPixel}.cs index d734b36c30..712204bbfe 100644 --- a/src/ImageSharp/Processing/Processors/Quantization/PaletteQuantizer{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Quantization/PaletteQuantizer{TPixel}.cs @@ -3,6 +3,7 @@ using System.Diagnostics.CodeAnalysis; using System.Runtime.CompilerServices; +using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; @@ -20,7 +21,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization; internal struct PaletteQuantizer : IQuantizer where TPixel : unmanaged, IPixel { - private readonly EuclideanPixelMap pixelMap; + private readonly PixelMap pixelMap; private int transparencyIndex; private TPixel transparentColor; @@ -58,7 +59,7 @@ public PaletteQuantizer( this.Configuration = configuration; this.Options = options; - this.pixelMap = new EuclideanPixelMap(configuration, palette); + this.pixelMap = PixelMapFactory.Create(this.Configuration, palette, options.ColorMatchingMode); this.transparencyIndex = transparencyIndex; this.transparentColor = transparentColor; } @@ -74,15 +75,25 @@ public PaletteQuantizer( /// [MethodImpl(InliningOptions.ShortMethod)] - public readonly IndexedImageFrame QuantizeFrame(ImageFrame source, Rectangle bounds) - => QuantizerUtilities.QuantizeFrame(ref Unsafe.AsRef(in this), source, bounds); + public readonly void AddPaletteColors(in Buffer2DRegion pixelRegion) + => this.AddPaletteColors(in pixelRegion, TransparentColorMode.Preserve); /// [MethodImpl(InliningOptions.ShortMethod)] - public readonly void AddPaletteColors(in Buffer2DRegion pixelRegion) + public readonly void AddPaletteColors(in Buffer2DRegion pixelRegion, TransparentColorMode mode) { } + /// + [MethodImpl(InliningOptions.ShortMethod)] + public readonly IndexedImageFrame QuantizeFrame(ImageFrame source, Rectangle bounds) + => this.QuantizeFrame(source, bounds, TransparentColorMode.Preserve); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public readonly IndexedImageFrame QuantizeFrame(ImageFrame source, Rectangle bounds, TransparentColorMode mode) + => QuantizerUtilities.QuantizeFrame(ref Unsafe.AsRef(in this), source, bounds, mode); + /// [MethodImpl(InliningOptions.ShortMethod)] public readonly byte GetQuantizedColor(TPixel color, out TPixel match) diff --git a/src/ImageSharp/Processing/Processors/Quantization/QuantizerOptions.cs b/src/ImageSharp/Processing/Processors/Quantization/QuantizerOptions.cs index 4f4104a8a3..7a66204c64 100644 --- a/src/ImageSharp/Processing/Processors/Quantization/QuantizerOptions.cs +++ b/src/ImageSharp/Processing/Processors/Quantization/QuantizerOptions.cs @@ -49,4 +49,15 @@ public float TransparencyThreshold get => this.threshold; set => this.threshold = Numerics.Clamp(value, QuantizerConstants.MinTransparencyThreshold, QuantizerConstants.MaxTransparencyThreshold); } + + /// + /// Gets or sets the color used for replacing colors with an alpha component below the threshold. + /// Defaults to . + /// + public Color ThresholdReplacementColor { get; set; } = Color.Transparent; + + /// + /// Gets or sets the color matching mode used for matching pixel values to palette colors. + /// + public ColorMatchingMode ColorMatchingMode { get; set; } = ColorMatchingMode.Hybrid; } diff --git a/src/ImageSharp/Processing/Processors/Quantization/QuantizerUtilities.cs b/src/ImageSharp/Processing/Processors/Quantization/QuantizerUtilities.cs index 49eaa79abb..d2d9d68b4f 100644 --- a/src/ImageSharp/Processing/Processors/Quantization/QuantizerUtilities.cs +++ b/src/ImageSharp/Processing/Processors/Quantization/QuantizerUtilities.cs @@ -1,7 +1,11 @@ // 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 SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; @@ -14,6 +18,126 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization; /// public static class QuantizerUtilities { + /// + /// Determines if transparent pixels can be replaced based on the specified color mode and pixel type. + /// + /// The type of the pixel. + /// The alpha threshold used to determine if a pixel is transparent. + /// Returns true if transparent pixels can be replaced; otherwise, false. + public static bool ShouldReplacePixelsByAlphaThreshold(float threshold) + where TPixel : unmanaged, IPixel + => threshold > 0 && TPixel.GetPixelTypeInfo().AlphaRepresentation == PixelAlphaRepresentation.Unassociated; + + /// + /// Replaces transparent pixels in a span with a specified color based on an alpha threshold. + /// + /// A span of color vectors that will be checked for transparency and potentially modified. + /// A color vector that will replace transparent pixels when the alpha value is below the specified threshold. + /// The alpha threshold used to determine if a pixel is transparent. + public static void ReplacePixelsByAlphaThreshold(Span source, Vector4 replacement, float threshold) + { + if (Vector512.IsHardwareAccelerated && source.Length >= 4) + { + Vector128 replacement128 = replacement.AsVector128(); + Vector256 replacement256 = Vector256.Create(replacement128, replacement128); + Vector512 replacement512 = Vector512.Create(replacement256, replacement256); + Vector512 threshold512 = Vector512.Create(threshold); + + Span> source512 = MemoryMarshal.Cast>(source); + for (int i = 0; i < source512.Length; i++) + { + ref Vector512 v = ref source512[i]; + + // Do `vector < threshold` + Vector512 mask = Vector512.LessThan(v, threshold512); + + // Replicate the result for W to all elements (is AllBitsSet if the W was less than threshold and Zero otherwise) + mask = Vector512.Shuffle(mask, Vector512.Create(3, 3, 3, 3, 7, 7, 7, 7, 11, 11, 11, 11, 15, 15, 15, 15)); + + // Use the mask to select the replacement vector + // (replacement & mask) | (v512 & ~mask) + v = Vector512.ConditionalSelect(mask, replacement512, v); + } + + int m = Numerics.Modulo4(source.Length); + if (m != 0) + { + for (int i = source.Length - m; i < source.Length; i++) + { + if (source[i].W < threshold) + { + source[i] = replacement; + } + } + } + } + else if (Vector256.IsHardwareAccelerated && source.Length >= 2) + { + Vector128 replacement128 = replacement.AsVector128(); + Vector256 replacement256 = Vector256.Create(replacement128, replacement128); + Vector256 threshold256 = Vector256.Create(threshold); + + Span> source256 = MemoryMarshal.Cast>(source); + for (int i = 0; i < source256.Length; i++) + { + ref Vector256 v = ref source256[i]; + + // Do `vector < threshold` + Vector256 mask = Vector256.LessThan(v, threshold256); + + // Replicate the result for W to all elements (is AllBitsSet if the W was less than threshold and Zero otherwise) + mask = Vector256.Shuffle(mask, Vector256.Create(3, 3, 3, 3, 7, 7, 7, 7)); + + // Use the mask to select the replacement vector + // (replacement & mask) | (v256 & ~mask) + v = Vector256.ConditionalSelect(mask, replacement256, v); + } + + int m = Numerics.Modulo2(source.Length); + if (m != 0) + { + for (int i = source.Length - m; i < source.Length; i++) + { + if (source[i].W < threshold) + { + source[i] = replacement; + } + } + } + } + else if (Vector128.IsHardwareAccelerated) + { + Vector128 replacement128 = replacement.AsVector128(); + Vector128 threshold128 = Vector128.Create(threshold); + + for (int i = 0; i < source.Length; i++) + { + ref Vector4 v = ref source[i]; + Vector128 v128 = v.AsVector128(); + + // Do `vector < threshold` + Vector128 mask = Vector128.LessThan(v128, threshold128); + + // Replicate the result for W to all elements (is AllBitsSet if the W was less than threshold and Zero otherwise) + mask = Vector128.Shuffle(mask, Vector128.Create(3, 3, 3, 3)); + + // Use the mask to select the replacement vector + // (replacement & mask) | (v128 & ~mask) + v = Vector128.ConditionalSelect(mask, replacement128, v128).AsVector4(); + } + } + else + { + for (int i = 0; i < source.Length; i++) + { + if (source[i].W < threshold) + { + source[i] = replacement; + } + } + } + } + /// /// Helper method for throwing an exception when a frame quantizer palette has /// been requested but not built yet. @@ -21,12 +145,13 @@ public static class QuantizerUtilities /// The pixel format. /// The frame quantizer palette. /// - /// The palette has not been built via + /// The palette has not been built via /// + [MethodImpl(InliningOptions.ColdPath)] public static void CheckPaletteState(in ReadOnlyMemory palette) where TPixel : unmanaged, IPixel { - if (palette.Equals(default)) + if (palette.IsEmpty) { throw new InvalidOperationException("Frame Quantizer palette has not been built."); } @@ -47,6 +172,29 @@ public static IndexedImageFrame BuildPaletteAndQuantizeFrame( ImageFrame source, Rectangle bounds) where TPixel : unmanaged, IPixel + => BuildPaletteAndQuantizeFrame( + quantizer, + source, + bounds, + TransparentColorMode.Preserve); + + /// + /// Execute both steps of the quantization. + /// + /// The pixel specific quantizer. + /// The source image frame to quantize. + /// The bounds within the frame to quantize. + /// The transparent color mode. + /// The pixel type. + /// + /// A representing a quantized version of the source frame pixels. + /// + public static IndexedImageFrame BuildPaletteAndQuantizeFrame( + this IQuantizer quantizer, + ImageFrame source, + Rectangle bounds, + TransparentColorMode mode) + where TPixel : unmanaged, IPixel { Guard.NotNull(quantizer, nameof(quantizer)); Guard.NotNull(source, nameof(source)); @@ -54,8 +202,7 @@ public static IndexedImageFrame BuildPaletteAndQuantizeFrame( Rectangle interest = Rectangle.Intersect(source.Bounds, bounds); Buffer2DRegion region = source.PixelBuffer.GetRegion(interest); - // Collect the palette. Required before the second pass runs. - quantizer.AddPaletteColors(in region); + quantizer.AddPaletteColors(in region, mode); return quantizer.QuantizeFrame(source, bounds); } @@ -67,13 +214,15 @@ public static IndexedImageFrame BuildPaletteAndQuantizeFrame( /// The pixel specific quantizer. /// The source image frame to quantize. /// The bounds within the frame to quantize. + /// The transparent color mode. /// /// A representing a quantized version of the source frame pixels. /// public static IndexedImageFrame QuantizeFrame( ref TFrameQuantizer quantizer, ImageFrame source, - Rectangle bounds) + Rectangle bounds, + TransparentColorMode mode) where TFrameQuantizer : struct, IQuantizer where TPixel : unmanaged, IPixel { @@ -88,13 +237,13 @@ public static IndexedImageFrame QuantizeFrame( if (quantizer.Options.Dither is null) { - SecondPass(ref quantizer, source, destination, interest); + SecondPass(ref quantizer, source, destination, interest, mode); } else { // We clone the image as we don't want to alter the original via error diffusion based dithering. using ImageFrame clone = source.Clone(); - SecondPass(ref quantizer, clone, destination, interest); + SecondPass(ref quantizer, clone, destination, interest, mode); } return destination; @@ -113,49 +262,28 @@ public static void BuildPalette( Image source) where TPixel : unmanaged, IPixel => quantizer.BuildPalette( - source.Configuration, TransparentColorMode.Preserve, pixelSamplingStrategy, - source, - Color.Transparent); + source); /// /// Adds colors to the quantized palette from the given pixel regions. /// /// The pixel format. /// The pixel specific quantizer. - /// The configuration. /// The transparent color mode. /// The pixel sampling strategy. /// The source image to sample from. - /// The background color to use when clearing transparent pixels. public static void BuildPalette( this IQuantizer quantizer, - Configuration configuration, TransparentColorMode mode, IPixelSamplingStrategy pixelSamplingStrategy, - Image source, - Color backgroundColor) + Image source) where TPixel : unmanaged, IPixel { - if (EncodingUtilities.ShouldClearTransparentPixels(mode)) + foreach (Buffer2DRegion region in pixelSamplingStrategy.EnumeratePixelRegions(source)) { - foreach (Buffer2DRegion region in pixelSamplingStrategy.EnumeratePixelRegions(source)) - { - // We need to clone the region to ensure we don't alter the original image. - using Buffer2D clone = region.Buffer.CloneRegion(configuration, region.Rectangle); - Buffer2DRegion clonedRegion = clone.GetRegion(); - - EncodingUtilities.ClearTransparentPixels(configuration, in clonedRegion, backgroundColor); - quantizer.AddPaletteColors(in clonedRegion); - } - } - else - { - foreach (Buffer2DRegion region in pixelSamplingStrategy.EnumeratePixelRegions(source)) - { - quantizer.AddPaletteColors(in region); - } + quantizer.AddPaletteColors(in region, mode); } } @@ -172,83 +300,208 @@ public static void BuildPalette( ImageFrame source) where TPixel : unmanaged, IPixel => quantizer.BuildPalette( - source.Configuration, TransparentColorMode.Preserve, pixelSamplingStrategy, - source, - Color.Transparent); + source); /// /// Adds colors to the quantized palette from the given pixel regions. /// /// The pixel format. /// The pixel specific quantizer. - /// The configuration. /// The transparent color mode. /// The pixel sampling strategy. /// The source image frame to sample from. - /// The background color to use when clearing transparent pixels. public static void BuildPalette( this IQuantizer quantizer, - Configuration configuration, TransparentColorMode mode, IPixelSamplingStrategy pixelSamplingStrategy, - ImageFrame source, - Color backgroundColor) + ImageFrame source) + where TPixel : unmanaged, IPixel + { + foreach (Buffer2DRegion region in pixelSamplingStrategy.EnumeratePixelRegions(source)) + { + quantizer.AddPaletteColors(in region, mode); + } + } + + internal static void AddPaletteColors( + ref TFrameQuantizer quantizer, + in Buffer2DRegion source, + in TDelegate rowDelegate) + where TFrameQuantizer : struct, IQuantizer where TPixel : unmanaged, IPixel + where TPixel2 : unmanaged, IPixel + where TDelegate : struct, IQuantizingPixelRowDelegate { - if (EncodingUtilities.ShouldClearTransparentPixels(mode)) + Configuration configuration = quantizer.Configuration; + float threshold = quantizer.Options.TransparencyThreshold; + Color replacement = quantizer.Options.ThresholdReplacementColor; + + using IMemoryOwner delegateRowOwner = configuration.MemoryAllocator.Allocate(source.Width); + Span delegateRow = delegateRowOwner.Memory.Span; + + bool replaceByThreshold = ShouldReplacePixelsByAlphaThreshold(threshold); + bool replaceTransparent = EncodingUtilities.ShouldReplaceTransparentPixels(rowDelegate.TransparentColorMode); + + if (replaceByThreshold || replaceTransparent) { - // We need to clone the region to ensure we don't alter the original image. - foreach (Buffer2DRegion region in pixelSamplingStrategy.EnumeratePixelRegions(source)) + using IMemoryOwner vectorRowOwner = configuration.MemoryAllocator.Allocate(source.Width); + Span vectorRow = vectorRowOwner.Memory.Span; + Vector4 replacementV4 = replacement.ToScaledVector4(); + + if (replaceByThreshold) { - using Buffer2D clone = region.Buffer.CloneRegion(configuration, region.Rectangle); - Buffer2DRegion clonedRegion = clone.GetRegion(); + for (int y = 0; y < source.Height; y++) + { + Span sourceRow = source.DangerousGetRowSpan(y); + PixelOperations.Instance.ToVector4(configuration, sourceRow, vectorRow, PixelConversionModifiers.Scale); + + ReplacePixelsByAlphaThreshold(vectorRow, replacementV4, threshold); - EncodingUtilities.ClearTransparentPixels(configuration, in clonedRegion, backgroundColor); - quantizer.AddPaletteColors(in clonedRegion); + PixelOperations.Instance.FromVector4Destructive(configuration, vectorRow, delegateRow, PixelConversionModifiers.Scale); + rowDelegate.Invoke(delegateRow, y); + } + } + else + { + for (int y = 0; y < source.Height; y++) + { + Span sourceRow = source.DangerousGetRowSpan(y); + PixelOperations.Instance.ToVector4(configuration, sourceRow, vectorRow, PixelConversionModifiers.Scale); + + EncodingUtilities.ReplaceTransparentPixels(vectorRow, replacementV4); + + PixelOperations.Instance.FromVector4Destructive(configuration, vectorRow, delegateRow, PixelConversionModifiers.Scale); + rowDelegate.Invoke(delegateRow, y); + } } } else { - foreach (Buffer2DRegion region in pixelSamplingStrategy.EnumeratePixelRegions(source)) + for (int y = 0; y < source.Height; y++) { - quantizer.AddPaletteColors(in region); + Span sourceRow = source.DangerousGetRowSpan(y); + PixelOperations.Instance.To(configuration, sourceRow, delegateRow); + rowDelegate.Invoke(delegateRow, y); } } } - [MethodImpl(InliningOptions.ShortMethod)] private static void SecondPass( ref TFrameQuantizer quantizer, ImageFrame source, IndexedImageFrame destination, - Rectangle bounds) + Rectangle bounds, + TransparentColorMode mode) where TFrameQuantizer : struct, IQuantizer where TPixel : unmanaged, IPixel { + float threshold = quantizer.Options.TransparencyThreshold; + Color replacement = quantizer.Options.ThresholdReplacementColor; + bool replaceByThreshold = ShouldReplacePixelsByAlphaThreshold(threshold); + bool replaceTransparent = EncodingUtilities.ShouldReplaceTransparentPixels(mode); + Vector4 replacementV4 = replacement.ToScaledVector4(); + IDither? dither = quantizer.Options.Dither; Buffer2D sourceBuffer = source.PixelBuffer; + Buffer2DRegion region = sourceBuffer.GetRegion(bounds); + + Configuration configuration = quantizer.Configuration; + using IMemoryOwner vectorOwner = configuration.MemoryAllocator.Allocate(region.Width); + Span vectorRow = vectorOwner.Memory.Span; if (dither is null) { - int offsetY = bounds.Top; - int offsetX = bounds.Left; + using IMemoryOwner quantizingRowOwner = configuration.MemoryAllocator.Allocate(region.Width); + Span quantizingRow = quantizingRowOwner.Memory.Span; - for (int y = 0; y < destination.Height; y++) + // This is NOT a clone so we DO NOT write back to the source. + if (replaceByThreshold || replaceTransparent) { - ReadOnlySpan sourceRow = sourceBuffer.DangerousGetRowSpan(y + offsetY); + if (replaceByThreshold) + { + for (int y = 0; y < region.Height; y++) + { + Span sourceRow = region.DangerousGetRowSpan(y); + PixelOperations.Instance.ToVector4(configuration, sourceRow, vectorRow, PixelConversionModifiers.Scale); + + ReplacePixelsByAlphaThreshold(vectorRow, replacementV4, threshold); + + PixelOperations.Instance.FromVector4Destructive(configuration, vectorRow, quantizingRow, PixelConversionModifiers.Scale); + + Span destinationRow = destination.GetWritablePixelRowSpanUnsafe(y); + for (int x = 0; x < destinationRow.Length; x++) + { + destinationRow[x] = quantizer.GetQuantizedColor(quantizingRow[x], out TPixel _); + } + } + } + else + { + for (int y = 0; y < region.Height; y++) + { + Span sourceRow = region.DangerousGetRowSpan(y); + PixelOperations.Instance.ToVector4(configuration, sourceRow, vectorRow, PixelConversionModifiers.Scale); + + EncodingUtilities.ReplaceTransparentPixels(vectorRow, replacementV4); + + PixelOperations.Instance.FromVector4Destructive(configuration, vectorRow, quantizingRow, PixelConversionModifiers.Scale); + + Span destinationRow = destination.GetWritablePixelRowSpanUnsafe(y); + for (int x = 0; x < destinationRow.Length; x++) + { + destinationRow[x] = quantizer.GetQuantizedColor(quantizingRow[x], out TPixel _); + } + } + } + + return; + } + + for (int y = 0; y < region.Height; y++) + { + ReadOnlySpan sourceRow = region.DangerousGetRowSpan(y); Span destinationRow = destination.GetWritablePixelRowSpanUnsafe(y); for (int x = 0; x < destinationRow.Length; x++) { - destinationRow[x] = Unsafe.AsRef(in quantizer).GetQuantizedColor(sourceRow[x + offsetX], out TPixel _); + destinationRow[x] = quantizer.GetQuantizedColor(sourceRow[x], out TPixel _); } } return; } + // This is a clone so we write back to the source. + if (replaceByThreshold || replaceTransparent) + { + if (replaceByThreshold) + { + for (int y = 0; y < region.Height; y++) + { + Span sourceRow = region.DangerousGetRowSpan(y); + PixelOperations.Instance.ToVector4(configuration, sourceRow, vectorRow, PixelConversionModifiers.Scale); + + ReplacePixelsByAlphaThreshold(vectorRow, replacementV4, threshold); + + PixelOperations.Instance.FromVector4Destructive(configuration, vectorRow, sourceRow, PixelConversionModifiers.Scale); + } + } + else + { + for (int y = 0; y < region.Height; y++) + { + Span sourceRow = region.DangerousGetRowSpan(y); + PixelOperations.Instance.ToVector4(configuration, sourceRow, vectorRow, PixelConversionModifiers.Scale); + + EncodingUtilities.ReplaceTransparentPixels(vectorRow, replacementV4); + + PixelOperations.Instance.FromVector4Destructive(configuration, vectorRow, sourceRow, PixelConversionModifiers.Scale); + } + } + } + dither.ApplyQuantizationDither(ref quantizer, source, destination, bounds); } } diff --git a/src/ImageSharp/Processing/Processors/Quantization/WuQuantizer{TPixel}.cs b/src/ImageSharp/Processing/Processors/Quantization/WuQuantizer{TPixel}.cs index db6490259f..a132aa2813 100644 --- a/src/ImageSharp/Processing/Processors/Quantization/WuQuantizer{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Quantization/WuQuantizer{TPixel}.cs @@ -6,6 +6,7 @@ using System.Numerics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; +using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; @@ -43,30 +44,10 @@ internal struct WuQuantizer : IQuantizer // The following two variables determine the amount of bits to preserve when calculating the histogram. // Reducing the value of these numbers the granularity of the color maps produced, making it much faster // and using much less memory but potentially less accurate. Current results are very good though! - - /// - /// The index bits. 6 in original code. - /// private const int IndexBits = 5; - - /// - /// The index alpha bits. 3 in original code. - /// private const int IndexAlphaBits = 5; - - /// - /// The index count. - /// private const int IndexCount = (1 << IndexBits) + 1; - - /// - /// The index alpha count. - /// private const int IndexAlphaCount = (1 << IndexAlphaBits) + 1; - - /// - /// The table length. Now 1185921. originally 2471625. - /// private const int TableLength = IndexCount * IndexCount * IndexCount * IndexAlphaCount; private readonly IMemoryOwner momentsOwner; @@ -74,17 +55,18 @@ internal struct WuQuantizer : IQuantizer private readonly IMemoryOwner paletteOwner; private ReadOnlyMemory palette; private int maxColors; - private readonly float transparencyThreshold; - private readonly short transparencyThreshold255; private readonly Box[] colorCube; - private EuclideanPixelMap? pixelMap; + private PixelMap? pixelMap; private readonly bool isDithering; + private readonly int transparencyThreshold255; + private Vector4 thresholdReplacementColorV4; + private readonly Rgba32 thresholdReplacementColorRgba; private bool isDisposed; /// /// Initializes a new instance of the struct. /// - /// The configuration which allows altering default behaviour or extending the library. + /// The configuration which allows altering default behavior or extending the library. /// The quantizer options defining quantization rules. [MethodImpl(InliningOptions.ShortMethod)] public WuQuantizer(Configuration configuration, QuantizerOptions options) @@ -104,8 +86,9 @@ public WuQuantizer(Configuration configuration, QuantizerOptions options) this.pixelMap = default; this.palette = default; this.isDithering = this.Options.Dither is not null; - this.transparencyThreshold = this.Options.TransparencyThreshold; - this.transparencyThreshold255 = (short)(this.Options.TransparencyThreshold * 255); + this.transparencyThreshold255 = (int)(this.Options.TransparencyThreshold * 255F); + this.thresholdReplacementColorV4 = this.Options.ThresholdReplacementColor.ToScaledVector4(); + this.thresholdReplacementColorRgba = this.Options.ThresholdReplacementColor.ToPixel(); } /// @@ -130,8 +113,14 @@ public ReadOnlyMemory Palette } /// - public readonly void AddPaletteColors(in Buffer2DRegion pixelRegion) - => this.Build3DHistogram(pixelRegion); + public readonly void AddPaletteColors(in Buffer2DRegion pixelRegion, TransparentColorMode mode) + { + PixelRowDelegate pixelRowDelegate = new(ref Unsafe.AsRef(in this), mode); + QuantizerUtilities.AddPaletteColors, TPixel, Rgba32, PixelRowDelegate>( + ref Unsafe.AsRef(in this), + in pixelRegion, + in pixelRowDelegate); + } /// /// Once all histogram data has been accumulated, this method computes the moments, @@ -148,6 +137,9 @@ private void ResolvePalette() // Compute the palette colors from the resolved cubes. Span paletteSpan = this.paletteOwner.GetSpan()[..this.maxColors]; ReadOnlySpan momentsSpan = this.momentsOwner.GetSpan(); + + float transparencyThreshold = this.Options.TransparencyThreshold; + Vector4 thresholdReplacementColor = this.thresholdReplacementColorV4; for (int k = 0; k < paletteSpan.Length; k++) { this.Mark(ref this.colorCube[k], (byte)k); @@ -155,9 +147,9 @@ private void ResolvePalette() if (moment.Weight > 0) { Vector4 normalized = moment.Normalize(); - if (normalized.W < this.transparencyThreshold) + if (normalized.W < transparencyThreshold) { - normalized = Vector4.Zero; + normalized = thresholdReplacementColor; } paletteSpan[k] = TPixel.FromScaledVector4(normalized); @@ -170,14 +162,19 @@ private void ResolvePalette() // Create the pixel map if dithering is enabled. if (this.isDithering && this.pixelMap is null) { - this.pixelMap = new EuclideanPixelMap(this.Configuration, this.palette); + this.pixelMap = PixelMapFactory.Create(this.Configuration, this.palette, this.Options.ColorMatchingMode); } } /// [MethodImpl(InliningOptions.ShortMethod)] public readonly IndexedImageFrame QuantizeFrame(ImageFrame source, Rectangle bounds) - => QuantizerUtilities.QuantizeFrame(ref Unsafe.AsRef(in this), source, bounds); + => this.QuantizeFrame(source, bounds, TransparentColorMode.Preserve); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public readonly IndexedImageFrame QuantizeFrame(ImageFrame source, Rectangle bounds, TransparentColorMode mode) + => QuantizerUtilities.QuantizeFrame(ref Unsafe.AsRef(in this), source, bounds, mode); /// public readonly byte GetQuantizedColor(TPixel color, out TPixel match) @@ -187,13 +184,13 @@ public readonly byte GetQuantizedColor(TPixel color, out TPixel match) // In this case, we must use the pixel map to get the closest color. if (this.isDithering) { - return (byte)this.pixelMap!.GetClosestColor(color, out match, this.transparencyThreshold255); + return (byte)this.pixelMap!.GetClosestColor(color, out match); } Rgba32 rgba = color.ToRgba32(); if (rgba.A < this.transparencyThreshold255) { - rgba = default; + rgba = this.thresholdReplacementColorRgba; } const int shift = 8 - IndexBits; @@ -376,37 +373,19 @@ private static Moment Top(ref Box cube, int direction, int position, ReadOnlySpa /// /// Builds a 3-D color histogram of counts, r/g/b, c^2. /// - /// The source pixel data. - private readonly void Build3DHistogram(in Buffer2DRegion source) + /// The source pixel data. + private readonly void Build3DHistogram(ReadOnlySpan pixels) { - Span momentSpan = this.momentsOwner.GetSpan(); - - // Build up the 3-D color histogram - using IMemoryOwner buffer = this.memoryAllocator.Allocate(source.Width); - Span bufferSpan = buffer.GetSpan(); - - float transparencyThreshold = this.Options.TransparencyThreshold * 255; - - for (int y = 0; y < source.Height; y++) + Span moments = this.momentsOwner.GetSpan(); + for (int x = 0; x < pixels.Length; x++) { - Span row = source.DangerousGetRowSpan(y); - PixelOperations.Instance.ToRgba32(this.Configuration, row, bufferSpan); - - for (int x = 0; x < bufferSpan.Length; x++) - { - Rgba32 rgba = bufferSpan[x]; - if (rgba.A < transparencyThreshold) - { - rgba = default; - } - - int r = (rgba.R >> (8 - IndexBits)) + 1; - int g = (rgba.G >> (8 - IndexBits)) + 1; - int b = (rgba.B >> (8 - IndexBits)) + 1; - int a = (rgba.A >> (8 - IndexAlphaBits)) + 1; + Rgba32 rgba = pixels[x]; + int r = (rgba.R >> (8 - IndexBits)) + 1; + int g = (rgba.G >> (8 - IndexBits)) + 1; + int b = (rgba.B >> (8 - IndexBits)) + 1; + int a = (rgba.A >> (8 - IndexAlphaBits)) + 1; - momentSpan[GetPaletteIndex(r, g, b, a)] += rgba; - } + moments[GetPaletteIndex(r, g, b, a)] += rgba; } } @@ -918,4 +897,19 @@ public override readonly int GetHashCode() return hash.ToHashCode(); } } + + private readonly struct PixelRowDelegate : IQuantizingPixelRowDelegate + { + private readonly WuQuantizer quantizer; + + public PixelRowDelegate(ref WuQuantizer quantizer, TransparentColorMode mode) + { + this.quantizer = quantizer; + this.TransparentColorMode = mode; + } + + public TransparentColorMode TransparentColorMode { get; } + + public void Invoke(ReadOnlySpan row, int rowIndex) => this.quantizer.Build3DHistogram(row); + } } diff --git a/tests/ImageSharp.Tests/Formats/Gif/GifEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Gif/GifEncoderTests.cs index 748f505cce..44ed5e38dd 100644 --- a/tests/ImageSharp.Tests/Formats/Gif/GifEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Gif/GifEncoderTests.cs @@ -56,7 +56,7 @@ public void EncodeGeneratedPatterns(TestImageProvider provider, { // Use the palette quantizer without dithering to ensure results // are consistent - Quantizer = new WebSafePaletteQuantizer(new QuantizerOptions { Dither = null }) + Quantizer = new WebSafePaletteQuantizer(new QuantizerOptions { Dither = null, TransparencyThreshold = 0 }) }; // Always save as we need to compare the encoded output. diff --git a/tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs index 2a53e4de79..6836b98500 100644 --- a/tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs @@ -419,8 +419,8 @@ public void Encode_APng(TestImageProvider provider) using Image output = Image.Load(memStream); - // some loss from original, due to compositing - ImageComparer.TolerantPercentage(0.01f).VerifySimilarity(output, image); + // Some loss from original, due to palette matching accuracy. + ImageComparer.TolerantPercentage(0.172F).VerifySimilarity(output, image); Assert.Equal(image.Frames.Count, output.Frames.Count); From 86b99170491f2fea53fdef65fb0046f0421d266e Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Thu, 27 Mar 2025 10:00:36 +1000 Subject: [PATCH 08/18] Upstream V3 changes. --- src/ImageSharp/Advanced/AotCompilerTools.cs | 17 +++- src/ImageSharp/Formats/Png/PngDecoderCore.cs | 78 ++++++++++++++++++- src/ImageSharp/Formats/Png/PngEncoderCore.cs | 11 ++- .../Quantization/IColorIndexCache.cs | 18 ++--- .../Formats/Png/PngEncoderTests.cs | 1 + tests/ImageSharp.Tests/TestImages.cs | 1 + tests/Images/Input/Png/issues/Issue_2882.png | 3 + 7 files changed, 114 insertions(+), 15 deletions(-) create mode 100644 tests/Images/Input/Png/issues/Issue_2882.png diff --git a/src/ImageSharp/Advanced/AotCompilerTools.cs b/src/ImageSharp/Advanced/AotCompilerTools.cs index 0d5faabe18..23bf85cf3d 100644 --- a/src/ImageSharp/Advanced/AotCompilerTools.cs +++ b/src/ImageSharp/Advanced/AotCompilerTools.cs @@ -138,10 +138,11 @@ private static void Seed() AotCompileResamplers(); AotCompileQuantizers(); AotCompilePixelSamplingStrategys(); + AotCompilePixelMaps(); AotCompileDithers(); AotCompileMemoryManagers(); - Unsafe.SizeOf(); + _ = Unsafe.SizeOf(); // TODO: Do the discovery work to figure out what works and what doesn't. } @@ -514,6 +515,20 @@ private static void AotCompilePixelSamplingStrategys() default(ExtensivePixelSamplingStrategy).EnumeratePixelRegions(default(ImageFrame)); } + /// + /// This method pre-seeds the all in the AoT compiler. + /// + /// The pixel format. + [Preserve] + private static void AotCompilePixelMaps() + where TPixel : unmanaged, IPixel + { + default(EuclideanPixelMap).GetClosestColor(default, out _); + default(EuclideanPixelMap).GetClosestColor(default, out _); + default(EuclideanPixelMap).GetClosestColor(default, out _); + default(EuclideanPixelMap).GetClosestColor(default, out _); + } + /// /// This method pre-seeds the all in the AoT compiler. /// diff --git a/src/ImageSharp/Formats/Png/PngDecoderCore.cs b/src/ImageSharp/Formats/Png/PngDecoderCore.cs index 484241d52f..9df6f14bf7 100644 --- a/src/ImageSharp/Formats/Png/PngDecoderCore.cs +++ b/src/ImageSharp/Formats/Png/PngDecoderCore.cs @@ -1086,7 +1086,7 @@ private void ProcessDefilteredScanline( { PixelBlender blender = PixelOperations.Instance.GetPixelBlender(PixelColorBlendingMode.Normal, PixelAlphaCompositionMode.SrcOver); - blender.Blend(this.configuration, destination, destination, rowSpan, 1f); + blender.Blend(this.configuration, destination, destination, rowSpan, 1f); } } finally @@ -1208,7 +1208,7 @@ private void ProcessInterlacedDefilteredScanline( { PixelBlender blender = PixelOperations.Instance.GetPixelBlender(PixelColorBlendingMode.Normal, PixelAlphaCompositionMode.SrcOver); - blender.Blend(this.configuration, destination, destination, rowSpan, 1f); + blender.Blend(this.configuration, destination, destination, rowSpan, 1F); } } finally @@ -1866,6 +1866,9 @@ private bool TryReadChunk(Span buffer, out PngChunk chunk) return false; } + // Capture the current position so we can revert back to it if we fail to read a valid chunk. + long position = this.currentStream.Position; + if (!this.TryReadChunkLength(buffer, out int length)) { // IEND @@ -1884,7 +1887,48 @@ private bool TryReadChunk(Span buffer, out PngChunk chunk) } } - PngChunkType type = this.ReadChunkType(buffer); + PngChunkType type; + + // Loop until we get a chunk type that is valid. + while (true) + { + type = this.ReadChunkType(buffer); + if (!IsValidChunkType(type)) + { + // The chunk type is invalid. + // Revert back to the next byte past the previous position and try again. + this.currentStream.Position = ++position; + + // If we are now at the end of the stream, we're done. + if (this.currentStream.Position >= this.currentStream.Length) + { + chunk = default; + return false; + } + + // Read the next chunk’s length. + if (!this.TryReadChunkLength(buffer, out length)) + { + chunk = default; + return false; + } + + while (length < 0) + { + if (!this.TryReadChunkLength(buffer, out length)) + { + chunk = default; + return false; + } + } + + // Continue to try reading the next chunk. + continue; + } + + // We have a valid chunk type. + break; + } // If we're reading color metadata only we're only interested in the IHDR and tRNS chunks. // We can skip most other chunk data in the stream for better performance. @@ -1901,7 +1945,7 @@ private bool TryReadChunk(Span buffer, out PngChunk chunk) // A chunk might report a length that exceeds the length of the stream. // Take the minimum of the two values to ensure we don't read past the end of the stream. - long position = this.currentStream.Position; + position = this.currentStream.Position; chunk = new PngChunk( length: (int)Math.Min(length, this.currentStream.Length - position), type: type, @@ -1919,6 +1963,32 @@ private bool TryReadChunk(Span buffer, out PngChunk chunk) return true; } + /// + /// Determines whether the 4-byte chunk type is valid (all ASCII letters). + /// + /// The chunk type. + [MethodImpl(InliningOptions.ShortMethod)] + private static bool IsValidChunkType(PngChunkType type) + { + uint value = (uint)type; + byte b0 = (byte)(value >> 24); + byte b1 = (byte)(value >> 16); + byte b2 = (byte)(value >> 8); + byte b3 = (byte)value; + return IsAsciiLetter(b0) && IsAsciiLetter(b1) && IsAsciiLetter(b2) && IsAsciiLetter(b3); + } + + /// + /// Returns a value indicating whether the given byte is an ASCII letter. + /// + /// The byte to check. + /// + /// if the byte is an ASCII letter; otherwise, . + /// + [MethodImpl(InliningOptions.ShortMethod)] + private static bool IsAsciiLetter(byte b) + => (b >= (byte)'A' && b <= (byte)'Z') || (b >= (byte)'a' && b <= (byte)'z'); + /// /// Validates the png chunk. /// diff --git a/src/ImageSharp/Formats/Png/PngEncoderCore.cs b/src/ImageSharp/Formats/Png/PngEncoderCore.cs index c05c7e8289..6eecff8eda 100644 --- a/src/ImageSharp/Formats/Png/PngEncoderCore.cs +++ b/src/ImageSharp/Formats/Png/PngEncoderCore.cs @@ -290,7 +290,16 @@ public void Encode(Image image, Stream stream, CancellationToken frameMetadata = currentFrame.Metadata.GetPngMetadata(); - bool blend = frameMetadata.BlendMode == FrameBlendMode.Over; + // Determine whether to blend the current frame over the existing canvas. + // Blending is applied only when the blend method is 'Over' (source-over blending) + // and when the frame's disposal method is not 'RestoreToPrevious', which indicates that + // the frame should not permanently alter the canvas. + bool blend = frameMetadata.BlendMode == FrameBlendMode.Over + && frameMetadata.DisposalMode != FrameDisposalMode.RestoreToPrevious; + + // Establish the background color for the current frame. + // If the disposal method is 'RestoreToBackground', use the predefined background color; + // otherwise, use transparent, as no explicit background restoration is needed. Color background = frameMetadata.DisposalMode == FrameDisposalMode.RestoreToBackground ? this.backgroundColor.Value : Color.Transparent; diff --git a/src/ImageSharp/Processing/Processors/Quantization/IColorIndexCache.cs b/src/ImageSharp/Processing/Processors/Quantization/IColorIndexCache.cs index 52efc62b7e..4e4fa5f0ac 100644 --- a/src/ImageSharp/Processing/Processors/Quantization/IColorIndexCache.cs +++ b/src/ImageSharp/Processing/Processors/Quantization/IColorIndexCache.cs @@ -69,11 +69,11 @@ internal interface IColorIndexCache : IColorIndexCache internal unsafe struct HybridCache : IColorIndexCache { private CoarseCache coarseCache; - private ExactCache exactCache; + private AccurateCache accurateCache; public HybridCache(MemoryAllocator allocator) { - this.exactCache = ExactCache.Create(allocator); + this.accurateCache = AccurateCache.Create(allocator); this.coarseCache = CoarseCache.Create(allocator); } @@ -84,7 +84,7 @@ public HybridCache(MemoryAllocator allocator) [MethodImpl(InliningOptions.ShortMethod)] public bool TryAdd(Rgba32 color, short index) { - if (this.exactCache.TryAdd(color, index)) + if (this.accurateCache.TryAdd(color, index)) { return true; } @@ -96,7 +96,7 @@ public bool TryAdd(Rgba32 color, short index) [MethodImpl(InliningOptions.ShortMethod)] public readonly bool TryGetValue(Rgba32 color, out short value) { - if (this.exactCache.TryGetValue(color, out value)) + if (this.accurateCache.TryGetValue(color, out value)) { return true; } @@ -107,14 +107,14 @@ public readonly bool TryGetValue(Rgba32 color, out short value) /// public readonly void Clear() { - this.exactCache.Clear(); + this.accurateCache.Clear(); this.coarseCache.Clear(); } /// public void Dispose() { - this.exactCache.Dispose(); + this.accurateCache.Dispose(); this.coarseCache.Dispose(); } } @@ -311,7 +311,7 @@ public void Add(byte quantizedAlpha, short paletteIndex) /// typically very short; in the worst-case, the number of iterations is bounded by 256. /// This guarantees highly efficient and predictable performance for small, fixed-size color palettes. /// -internal unsafe struct ExactCache : IColorIndexCache +internal unsafe struct AccurateCache : IColorIndexCache { // Buckets array: each bucket holds the index (0-based) into the entries array // of the first entry in the chain, or -1 if empty. @@ -326,7 +326,7 @@ internal unsafe struct ExactCache : IColorIndexCache public const int Capacity = 512; - private ExactCache(MemoryAllocator allocator) + private AccurateCache(MemoryAllocator allocator) { this.Count = 0; @@ -346,7 +346,7 @@ private ExactCache(MemoryAllocator allocator) public int Count { get; private set; } /// - public static ExactCache Create(MemoryAllocator allocator) => new(allocator); + public static AccurateCache Create(MemoryAllocator allocator) => new(allocator); /// [MethodImpl(InliningOptions.ShortMethod)] diff --git a/tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs index 6836b98500..9d29b3bad5 100644 --- a/tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs @@ -407,6 +407,7 @@ public void Encode_WithTransparentColorBehaviorClear_Works(PngColorType colorTyp [WithFile(TestImages.Png.APng, PixelTypes.Rgba32)] [WithFile(TestImages.Png.DefaultNotAnimated, PixelTypes.Rgba32)] [WithFile(TestImages.Png.FrameOffset, PixelTypes.Rgba32)] + [WithFile(TestImages.Png.Issue2882, PixelTypes.Rgba32)] public void Encode_APng(TestImageProvider provider) where TPixel : unmanaged, IPixel { diff --git a/tests/ImageSharp.Tests/TestImages.cs b/tests/ImageSharp.Tests/TestImages.cs index abaec4997c..71dfe0480e 100644 --- a/tests/ImageSharp.Tests/TestImages.cs +++ b/tests/ImageSharp.Tests/TestImages.cs @@ -76,6 +76,7 @@ public static class Png public const string FrameOffset = "Png/animated/frame-offset.png"; public const string DefaultNotAnimated = "Png/animated/default-not-animated.png"; public const string Issue2666 = "Png/issues/Issue_2666.png"; + public const string Issue2882 = "Png/issues/Issue_2882.png"; // Filtered test images from http://www.schaik.com/pngsuite/pngsuite_fil_png.html public const string Filter0 = "Png/filter0.png"; diff --git a/tests/Images/Input/Png/issues/Issue_2882.png b/tests/Images/Input/Png/issues/Issue_2882.png new file mode 100644 index 0000000000..2d7a51dacb --- /dev/null +++ b/tests/Images/Input/Png/issues/Issue_2882.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:cebc98e62bcfe31df73ae7b6980382f4b56bdf7e7e6e9037946f5a84cb51c7d2 +size 1117 From a84160be9f1993bc43d84faa5ec14c567e432212 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Thu, 27 Mar 2025 13:07:08 +1000 Subject: [PATCH 09/18] Add new benchmarks --- .../Quantization/QuantizerOptions.cs | 5 +- .../Codecs/Gif/DecodeEncodeGif.cs | 59 +++++++++++++++++++ .../Codecs/Gif/EncodeGif.cs | 26 +++++--- 3 files changed, 80 insertions(+), 10 deletions(-) create mode 100644 tests/ImageSharp.Benchmarks/Codecs/Gif/DecodeEncodeGif.cs diff --git a/src/ImageSharp/Processing/Processors/Quantization/QuantizerOptions.cs b/src/ImageSharp/Processing/Processors/Quantization/QuantizerOptions.cs index 7a66204c64..85db2b7c4e 100644 --- a/src/ImageSharp/Processing/Processors/Quantization/QuantizerOptions.cs +++ b/src/ImageSharp/Processing/Processors/Quantization/QuantizerOptions.cs @@ -10,9 +10,11 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization; /// public class QuantizerOptions { +#pragma warning disable IDE0032 // Use auto property private float ditherScale = QuantizerConstants.MaxDitherScale; private int maxColors = QuantizerConstants.MaxColors; private float threshold = QuantizerConstants.DefaultTransparencyThreshold; +#pragma warning restore IDE0032 // Use auto property /// /// Gets or sets the algorithm to apply to the output image. @@ -58,6 +60,7 @@ public float TransparencyThreshold /// /// Gets or sets the color matching mode used for matching pixel values to palette colors. + /// Defaults to . /// - public ColorMatchingMode ColorMatchingMode { get; set; } = ColorMatchingMode.Hybrid; + public ColorMatchingMode ColorMatchingMode { get; set; } = ColorMatchingMode.Coarse; } diff --git a/tests/ImageSharp.Benchmarks/Codecs/Gif/DecodeEncodeGif.cs b/tests/ImageSharp.Benchmarks/Codecs/Gif/DecodeEncodeGif.cs new file mode 100644 index 0000000000..3238e8dac2 --- /dev/null +++ b/tests/ImageSharp.Benchmarks/Codecs/Gif/DecodeEncodeGif.cs @@ -0,0 +1,59 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Drawing.Imaging; +using BenchmarkDotNet.Attributes; +using SixLabors.ImageSharp.Formats.Gif; +using SixLabors.ImageSharp.Processing; +using SixLabors.ImageSharp.Processing.Processors.Quantization; +using SixLabors.ImageSharp.Tests; +using SDImage = System.Drawing.Image; + +namespace SixLabors.ImageSharp.Benchmarks.Codecs; + +public abstract class DecodeEncodeGif +{ + private MemoryStream outputStream; + + protected abstract GifEncoder Encoder { get; } + + [Params(TestImages.Gif.Leo, TestImages.Gif.Cheers)] + public string TestImage { get; set; } + + private string TestImageFullPath => Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, this.TestImage); + + [GlobalSetup] + public void Setup() => this.outputStream = new MemoryStream(); + + [GlobalCleanup] + public void Cleanup() => this.outputStream.Close(); + + [Benchmark(Baseline = true)] + public void SystemDrawing() + { + this.outputStream.Position = 0; + using SDImage image = SDImage.FromFile(this.TestImageFullPath); + image.Save(this.outputStream, ImageFormat.Gif); + } + + [Benchmark] + public void ImageSharp() + { + this.outputStream.Position = 0; + using Image image = Image.Load(this.TestImageFullPath); + image.SaveAsGif(this.outputStream, this.Encoder); + } +} + +public class DecodeEncodeGif_DefaultEncoder : DecodeEncodeGif +{ + protected override GifEncoder Encoder => new(); +} + +public class DecodeEncodeGif_CoarsePaletteEncoder : DecodeEncodeGif +{ + protected override GifEncoder Encoder => new() + { + Quantizer = new WebSafePaletteQuantizer(new QuantizerOptions { Dither = KnownDitherings.Bayer4x4, ColorMatchingMode = ColorMatchingMode.Coarse }) + }; +} diff --git a/tests/ImageSharp.Benchmarks/Codecs/Gif/EncodeGif.cs b/tests/ImageSharp.Benchmarks/Codecs/Gif/EncodeGif.cs index beedbbe07c..2b2428210a 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/Gif/EncodeGif.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/Gif/EncodeGif.cs @@ -12,21 +12,16 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs; -[Config(typeof(Config.Short))] -public class EncodeGif +public abstract class EncodeGif { // System.Drawing needs this. private FileStream bmpStream; private SDImage bmpDrawing; private Image bmpCore; - // Try to get as close to System.Drawing's output as possible - private readonly GifEncoder encoder = new() - { - Quantizer = new WebSafePaletteQuantizer(new QuantizerOptions { Dither = KnownDitherings.Bayer4x4 }) - }; + protected abstract GifEncoder Encoder { get; } - [Params(TestImages.Bmp.Car, TestImages.Png.Rgb48Bpp)] + [Params(TestImages.Gif.Leo, TestImages.Gif.Cheers)] public string TestImage { get; set; } [GlobalSetup] @@ -61,6 +56,19 @@ public void GifSystemDrawing() public void GifImageSharp() { using MemoryStream memoryStream = new(); - this.bmpCore.SaveAsGif(memoryStream, this.encoder); + this.bmpCore.SaveAsGif(memoryStream, this.Encoder); } } + +public class EncodeGif_DefaultEncoder : EncodeGif +{ + protected override GifEncoder Encoder => new(); +} + +public class EncodeGif_CoarsePaletteEncoder : EncodeGif +{ + protected override GifEncoder Encoder => new() + { + Quantizer = new WebSafePaletteQuantizer(new QuantizerOptions { Dither = KnownDitherings.Bayer4x4, ColorMatchingMode = ColorMatchingMode.Coarse }) + }; +} From dca98af177aa9da5e96a5c131360e53a3a7de141 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Thu, 27 Mar 2025 14:14:31 +1000 Subject: [PATCH 10/18] Update test ref images to match caching --- .../Processors/Dithering/PaletteDitherProcessor{TPixel}.cs | 2 +- tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs | 6 +++++- .../Encode_8BitColor_WithOctreeQuantizer_rgb32.bmp | 2 +- .../Encode_8BitColor_WithWuQuantizer_rgb32.bmp | 2 +- .../ApplyDiffusionFilterInBox_Rgba32_CalliphoraPartial.png | 4 ++-- ...usionFilter_WorksWithAllErrorDiffusers_Bike_Atkinson.png | 4 ++-- ...iffusionFilter_WorksWithAllErrorDiffusers_Bike_Burks.png | 4 ++-- ...ilter_WorksWithAllErrorDiffusers_Bike_FloydSteinberg.png | 4 ++-- ...er_WorksWithAllErrorDiffusers_Bike_JarvisJudiceNinke.png | 4 ++-- ...fusionFilter_WorksWithAllErrorDiffusers_Bike_Sierra2.png | 4 ++-- ...fusionFilter_WorksWithAllErrorDiffusers_Bike_Sierra3.png | 4 ++-- ...ionFilter_WorksWithAllErrorDiffusers_Bike_SierraLite.png | 4 ++-- ...Filter_WorksWithAllErrorDiffusers_Bike_StevensonArce.png | 4 ++-- ...ffusionFilter_WorksWithAllErrorDiffusers_Bike_Stucki.png | 4 ++-- ...orksWithAllErrorDiffusers_CalliphoraPartial_Atkinson.png | 4 ++-- ...r_WorksWithAllErrorDiffusers_CalliphoraPartial_Burks.png | 4 ++-- ...thAllErrorDiffusers_CalliphoraPartial_FloydSteinberg.png | 4 ++-- ...llErrorDiffusers_CalliphoraPartial_JarvisJudiceNinke.png | 4 ++-- ...WorksWithAllErrorDiffusers_CalliphoraPartial_Sierra2.png | 4 ++-- ...WorksWithAllErrorDiffusers_CalliphoraPartial_Sierra3.png | 4 ++-- ...ksWithAllErrorDiffusers_CalliphoraPartial_SierraLite.png | 4 ++-- ...ithAllErrorDiffusers_CalliphoraPartial_StevensonArce.png | 4 ++-- ..._WorksWithAllErrorDiffusers_CalliphoraPartial_Stucki.png | 4 ++-- ...lter_ShouldNotDependOnSinglePixelType_Bgra32_filter0.png | 4 ++-- ...ilter_ShouldNotDependOnSinglePixelType_Rgb24_filter0.png | 4 ++-- ...lter_ShouldNotDependOnSinglePixelType_Rgba32_filter0.png | 4 ++-- ..._ShouldNotDependOnSinglePixelType_RgbaVector_filter0.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 ++-- ...r_WorksWithAllDitherers_CalliphoraPartial_Bayer16x16.png | 4 ++-- ...ter_WorksWithAllDitherers_CalliphoraPartial_Bayer2x2.png | 4 ++-- ...ter_WorksWithAllDitherers_CalliphoraPartial_Bayer4x4.png | 4 ++-- ...ter_WorksWithAllDitherers_CalliphoraPartial_Bayer8x8.png | 4 ++-- ...r_WorksWithAllDitherers_CalliphoraPartial_Ordered3x3.png | 4 ++-- .../00.gif | 4 ++-- .../08.gif | 4 ++-- .../104.gif | 4 ++-- .../112.gif | 4 ++-- .../16.gif | 4 ++-- .../24.gif | 4 ++-- .../32.gif | 4 ++-- .../40.gif | 4 ++-- .../48.gif | 4 ++-- .../56.gif | 4 ++-- .../64.gif | 4 ++-- .../72.gif | 4 ++-- .../80.gif | 4 ++-- .../88.gif | 4 ++-- .../96.gif | 4 ++-- .../00.png | 4 ++-- .../08.png | 4 ++-- .../104.png | 4 ++-- .../112.png | 4 ++-- .../16.png | 4 ++-- .../24.png | 4 ++-- .../32.png | 4 ++-- .../40.png | 4 ++-- .../48.png | 4 ++-- .../56.png | 4 ++-- .../64.png | 4 ++-- .../72.png | 4 ++-- .../80.png | 4 ++-- .../88.png | 4 ++-- .../96.png | 4 ++-- .../00.png | 4 ++-- .../08.png | 4 ++-- ...sue2469_Quantized_Encode_Artifacts_Rgba32_issue_2469.png | 4 ++-- .../Issue2668_Quantized_Encode_Alpha_Rgba32_Issue_2668.png | 4 ++-- ...lyQuantizationInBox_Bike_OctreeQuantizer_ErrorDither.png | 4 ++-- ...QuantizationInBox_Bike_OctreeQuantizer_OrderedDither.png | 4 ++-- ...zationInBox_Bike_WebSafePaletteQuantizer_ErrorDither.png | 4 ++-- ...ntizationInBox_Bike_WebSafePaletteQuantizer_NoDither.png | 4 ++-- ...tionInBox_Bike_WebSafePaletteQuantizer_OrderedDither.png | 4 ++-- ...izationInBox_Bike_WernerPaletteQuantizer_ErrorDither.png | 4 ++-- ...antizationInBox_Bike_WernerPaletteQuantizer_NoDither.png | 4 ++-- ...ationInBox_Bike_WernerPaletteQuantizer_OrderedDither.png | 4 ++-- .../ApplyQuantizationInBox_Bike_WuQuantizer_ErrorDither.png | 4 ++-- ...pplyQuantizationInBox_Bike_WuQuantizer_OrderedDither.png | 4 ++-- ...nInBox_CalliphoraPartial_OctreeQuantizer_ErrorDither.png | 4 ++-- ...nBox_CalliphoraPartial_OctreeQuantizer_OrderedDither.png | 4 ++-- ...alliphoraPartial_WebSafePaletteQuantizer_ErrorDither.png | 4 ++-- ...x_CalliphoraPartial_WebSafePaletteQuantizer_NoDither.png | 4 ++-- ...liphoraPartial_WebSafePaletteQuantizer_OrderedDither.png | 4 ++-- ...CalliphoraPartial_WernerPaletteQuantizer_ErrorDither.png | 4 ++-- ...ox_CalliphoraPartial_WernerPaletteQuantizer_NoDither.png | 4 ++-- ...lliphoraPartial_WernerPaletteQuantizer_OrderedDither.png | 4 ++-- ...ationInBox_CalliphoraPartial_WuQuantizer_ErrorDither.png | 4 ++-- ...ionInBox_CalliphoraPartial_WuQuantizer_OrderedDither.png | 4 ++-- ...itheringScale_david_OctreeQuantizer_ErrorDither_0.25.png | 4 ++-- ...DitheringScale_david_OctreeQuantizer_ErrorDither_0.5.png | 4 ++-- ...itheringScale_david_OctreeQuantizer_ErrorDither_0.75.png | 4 ++-- ...thDitheringScale_david_OctreeQuantizer_ErrorDither_0.png | 4 ++-- ...thDitheringScale_david_OctreeQuantizer_ErrorDither_1.png | 4 ++-- ...heringScale_david_OctreeQuantizer_OrderedDither_0.25.png | 4 ++-- ...theringScale_david_OctreeQuantizer_OrderedDither_0.5.png | 4 ++-- ...heringScale_david_OctreeQuantizer_OrderedDither_0.75.png | 4 ++-- ...DitheringScale_david_OctreeQuantizer_OrderedDither_0.png | 4 ++-- ...DitheringScale_david_OctreeQuantizer_OrderedDither_1.png | 4 ++-- ...Scale_david_WebSafePaletteQuantizer_ErrorDither_0.25.png | 4 ++-- ...gScale_david_WebSafePaletteQuantizer_ErrorDither_0.5.png | 4 ++-- ...Scale_david_WebSafePaletteQuantizer_ErrorDither_0.75.png | 4 ++-- ...ingScale_david_WebSafePaletteQuantizer_ErrorDither_0.png | 4 ++-- ...ingScale_david_WebSafePaletteQuantizer_ErrorDither_1.png | 4 ++-- ...ale_david_WebSafePaletteQuantizer_OrderedDither_0.25.png | 4 ++-- ...cale_david_WebSafePaletteQuantizer_OrderedDither_0.5.png | 4 ++-- ...ale_david_WebSafePaletteQuantizer_OrderedDither_0.75.png | 4 ++-- ...gScale_david_WebSafePaletteQuantizer_OrderedDither_0.png | 4 ++-- ...gScale_david_WebSafePaletteQuantizer_OrderedDither_1.png | 4 ++-- ...gScale_david_WernerPaletteQuantizer_ErrorDither_0.25.png | 4 ++-- ...ngScale_david_WernerPaletteQuantizer_ErrorDither_0.5.png | 4 ++-- ...gScale_david_WernerPaletteQuantizer_ErrorDither_0.75.png | 4 ++-- ...ringScale_david_WernerPaletteQuantizer_ErrorDither_0.png | 4 ++-- ...ringScale_david_WernerPaletteQuantizer_ErrorDither_1.png | 4 ++-- ...cale_david_WernerPaletteQuantizer_OrderedDither_0.25.png | 4 ++-- ...Scale_david_WernerPaletteQuantizer_OrderedDither_0.5.png | 4 ++-- ...cale_david_WernerPaletteQuantizer_OrderedDither_0.75.png | 4 ++-- ...ngScale_david_WernerPaletteQuantizer_OrderedDither_0.png | 4 ++-- ...ngScale_david_WernerPaletteQuantizer_OrderedDither_1.png | 4 ++-- ...WithDitheringScale_david_WuQuantizer_ErrorDither_0.5.png | 4 ++-- ...ithDitheringScale_david_WuQuantizer_ErrorDither_0.75.png | 4 ++-- ...onWithDitheringScale_david_WuQuantizer_ErrorDither_1.png | 4 ++-- ...hDitheringScale_david_WuQuantizer_OrderedDither_0.25.png | 4 ++-- ...thDitheringScale_david_WuQuantizer_OrderedDither_0.5.png | 4 ++-- ...hDitheringScale_david_WuQuantizer_OrderedDither_0.75.png | 4 ++-- ...WithDitheringScale_david_WuQuantizer_OrderedDither_1.png | 4 ++-- .../ApplyQuantization_Bike_OctreeQuantizer_ErrorDither.png | 4 ++-- ...ApplyQuantization_Bike_OctreeQuantizer_OrderedDither.png | 4 ++-- ...uantization_Bike_WebSafePaletteQuantizer_ErrorDither.png | 4 ++-- ...lyQuantization_Bike_WebSafePaletteQuantizer_NoDither.png | 4 ++-- ...ntization_Bike_WebSafePaletteQuantizer_OrderedDither.png | 4 ++-- ...Quantization_Bike_WernerPaletteQuantizer_ErrorDither.png | 4 ++-- ...plyQuantization_Bike_WernerPaletteQuantizer_NoDither.png | 4 ++-- ...antization_Bike_WernerPaletteQuantizer_OrderedDither.png | 4 ++-- .../ApplyQuantization_Bike_WuQuantizer_ErrorDither.png | 4 ++-- .../ApplyQuantization_Bike_WuQuantizer_OrderedDither.png | 4 ++-- ...zation_CalliphoraPartial_OctreeQuantizer_ErrorDither.png | 4 ++-- ...tion_CalliphoraPartial_OctreeQuantizer_OrderedDither.png | 4 ++-- ...alliphoraPartial_WebSafePaletteQuantizer_ErrorDither.png | 4 ++-- ...n_CalliphoraPartial_WebSafePaletteQuantizer_NoDither.png | 4 ++-- ...liphoraPartial_WebSafePaletteQuantizer_OrderedDither.png | 4 ++-- ...CalliphoraPartial_WernerPaletteQuantizer_ErrorDither.png | 4 ++-- ...on_CalliphoraPartial_WernerPaletteQuantizer_NoDither.png | 4 ++-- ...lliphoraPartial_WernerPaletteQuantizer_OrderedDither.png | 4 ++-- ...antization_CalliphoraPartial_WuQuantizer_ErrorDither.png | 4 ++-- ...tization_CalliphoraPartial_WuQuantizer_OrderedDither.png | 4 ++-- 148 files changed, 296 insertions(+), 292 deletions(-) diff --git a/src/ImageSharp/Processing/Processors/Dithering/PaletteDitherProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Dithering/PaletteDitherProcessor{TPixel}.cs index bb9b4d8c8f..0d4680e21f 100644 --- a/src/ImageSharp/Processing/Processors/Dithering/PaletteDitherProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Dithering/PaletteDitherProcessor{TPixel}.cs @@ -89,7 +89,7 @@ public DitherProcessor( float ditherScale) { this.Configuration = configuration; - this.pixelMap = PixelMapFactory.Create(configuration, palette, ColorMatchingMode.Hybrid); + this.pixelMap = PixelMapFactory.Create(configuration, palette, ColorMatchingMode.Coarse); this.Palette = palette; this.DitherScale = ditherScale; } diff --git a/tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs index 9d29b3bad5..b0d0563ccb 100644 --- a/tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs @@ -678,12 +678,16 @@ public void Issue_2862() } } + PaletteQuantizer quantizer = new( + palette.Select(Color.FromPixel).ToArray(), + new QuantizerOptions() { ColorMatchingMode = ColorMatchingMode.Hybrid }); + using MemoryStream ms = new(); image.Save(ms, new PngEncoder { ColorType = PngColorType.Palette, BitDepth = PngBitDepth.Bit8, - Quantizer = new PaletteQuantizer(palette.Select(Color.FromPixel).ToArray()) + Quantizer = quantizer }); ms.Position = 0; 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 291739dfa9..f4ae3b9b68 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:9d7441b03c24acb887b2d9a6e2346bb23e2d38293c3df3ff489d48593f87b29a +oid sha256:a98b1ec707af066f77fad7d1a64b858d460986beb6d27682717dd5e221310fd4 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 c69ed3130c..f7eb06c558 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:5d9f2745de2b6e7fc3b1403fe651f3bbba835c67a6fb410fc8a9d91a15b44328 +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 401ceaa36c..de42d1bfc2 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:596472e74050d968479b672c1d2436b179e41a7b99fcefb53286ad47e5a4fe13 -size 273115 +oid sha256:681b0e36298cb702683fb9ffb2a82f7dfd9080b268db19a03f413809f69d0e07 +size 273269 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 0b03e4e185..1656b2e9cb 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:2465dde9a5d6202194f7af3924ca24ab3151948d551549a711977d3302dbc0a3 -size 51158 +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 1d27fab771..c6016ae358 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:82dcdd4f28a9ffafd36a21d06aee8adb49017df2d4abeee4205d65b1ae3df35e -size 59875 +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 51c93894de..40243937d3 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:a836c8efd7aa9818cf807cf56412e78399a6568798be23d0f3f6b89552856ff1 -size 62172 +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 de9ef47037..83f9e067db 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:78900d779181140a02a2b9fb9fa922ca854d9905c1dc7e006592a3fdc00f8dee -size 58107 +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 bb62475c97..22e4f4b6d6 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:76f10d4280258d2941d85e795cf788977ca1e85bdc1b75b5a482b5bbdaa49d32 -size 57900 +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 2d99410117..838863c158 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:aba9172bb4d117ba1b0c5f32b46251d473cc06b3f697e5729da0c5768a70b5d2 -size 59104 +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 bd3295e424..60513e1992 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:e7d6ea824ba19632afa940b3062632d305bf3521b1795d46f3fea90abc1f0ed8 -size 64431 +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 6bea03c0be..0d1b34d8ce 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:3efcf6f924d3d07cad9dbf9dddb6104c3748ac4354298acf5afde66c2321e819 -size 55358 +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 b535b14155..f8c998ecbd 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:2b9f295f6b539fbeeae3c473907fa450f9b8c94017abad4bf915a8a4a2e7b612 -size 56982 +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 e0334be2d5..70acb3f32e 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:c4c45632b6cd387c929a9e0982f3943a7c3f64f27862c0b539bbf71228561f39 -size 57886 +oid sha256:e44c49a8f2ab1280c38e6ba71da29a93803b2aa4cf117e1e919909521b0373e6 +size 57636 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 db22e17585..af35177491 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:bf9e8bd50b62ba62ab04a5ab2af207414183a015567080fa7cdd827016694369 -size 60458 +oid sha256:359a44bb957481c85d5acd65559b43ffc0acf806d4f4e57d6a791ca65b28295b +size 59839 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 2d6226d096..a14c2cb1f6 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:89864a77216b51cc5b9415453ade7f7ec64c1c112546aa47ee6b4b89f9b258a3 -size 60543 +oid sha256:7fb3743098a8147fd24294d933d93a61ec0155d754f52544650f6589719905be +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 4d9b22139e..683f59ea1e 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:0c03c3dc0b3da69ef4f55b5ad6d162da94ad46f4e426e318695bedc7e5bb3dfd -size 58725 +oid sha256:41fa7d92a10db450f3b3729ab9e36074224baaefeda21cffd0466e37a111e138 +size 59113 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 3b7c78182a..813289a26d 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:af86b108639f833972958fd2cc7d00221982069c40cab67b5bc6b8ce1a7e826d -size 59137 +oid sha256:bebf3b3762b339874891e3d434511e5f2557be90d66d6d7fe827b50334ede6c2 +size 58976 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 b56e3ccfd6..d4da100376 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:959b49f5498e4018bfb8a5fac8a688c51b06161dc0c6559547293c613ddca760 -size 59248 +oid sha256:fd4358826739db2c22064e8aa90597f8b6403b9d7e2866ec280e743c51d2f41f +size 59203 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 78078ac90d..8d3cf1a564 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:fdc28c281666e381c7ba2483d033f73c88111f13eec10cc406e07730eb5fa709 -size 60804 +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 523a87b7a5..a146f8f668 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:c80f215d4a839fb1ca722d03923b587bac6326d54d2d7a3656667e46464b4307 -size 58011 +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 741ac096a3..fa8eea57a9 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:38fbfc201e8ef31b879e863f7f49ac1e731c4d7dfca58a80e1e45890565af979 -size 58742 +oid sha256:174ee39c08eb9a174b48b19dc618d043bf6b71eee68ab7127407eb713e164e61 +size 58934 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 91ff81d5df..2c67b3bf23 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:a9b0209e8bae05da6de72a4249d2fe43ef08388c7296556921c17b11bdb8bdcc -size 875 +oid sha256:1110b46ec3296a1631420e0bb915f6fdc3d1cead4b0fc5a63a7a280fbf841ea2 +size 870 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 91ff81d5df..1305c5ede9 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:a9b0209e8bae05da6de72a4249d2fe43ef08388c7296556921c17b11bdb8bdcc -size 875 +oid sha256:e51abcab66201997deda99637de604330ef977fd2d1dbebaa0416c621d03b8f9 +size 869 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 91ff81d5df..2c67b3bf23 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:a9b0209e8bae05da6de72a4249d2fe43ef08388c7296556921c17b11bdb8bdcc -size 875 +oid sha256:1110b46ec3296a1631420e0bb915f6fdc3d1cead4b0fc5a63a7a280fbf841ea2 +size 870 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 91ff81d5df..2c67b3bf23 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:a9b0209e8bae05da6de72a4249d2fe43ef08388c7296556921c17b11bdb8bdcc -size 875 +oid sha256:1110b46ec3296a1631420e0bb915f6fdc3d1cead4b0fc5a63a7a280fbf841ea2 +size 870 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 6cacba7877..53b8e40263 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:1de82d05feed0b3bd9d6d7d16507ff5dc06744843abaaf77fd4207edd5205488 -size 44246 +oid sha256:abdb8fd09b6925a8922f091ee441eaa0f7f81debba9e303c38b7d40bd9fd8479 +size 44173 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 f623d52ac7..543640c2e8 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:ea34b188ce71a8fbd76fddf052fc1322fff62ba0acc218582b996d9b00c81671 -size 42667 +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 668d562a73..d3a8eddd84 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:c179834a368c8fa4bb3e1a1fb2e12b567d7034c5a8e52741bc33ffa30ea73c8a -size 44251 +oid sha256:22725614ba3367b155ed7745034e84c624ed0d4c9b86a4eaa8bf650cc3e5e68f +size 44345 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 c9ad188dc1..2a3e95c169 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:a049a50155bf56c53a1b74e919806cbb83716842b5c0a233c44c87b3630115e0 -size 44394 +oid sha256:05a0c206e13e5ad41410b1ac86720784f13594491b70b15967f761ad1feeb56e +size 44360 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 0e7e6cd659..a09c2faec8 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:286314ca90912de65427d51269a3263ea58b3c32f2839f797f2689b7dac0c6ff -size 44953 +oid sha256:87069e28d286edc6edd6b53af8c3c8bc81bdbd6a16e67487c3ea45170fc29805 +size 45060 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 b77d857dea..60f092bdda 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:76a3abb7c908e365abd8fc5b1fdc7536a71645a5fd59be61e200707e208fb341 -size 51241 +oid sha256:366bf54d2d900c763a4ea52f624ac875c346d1c319ab7d3e8e0019d00c3b0077 +size 51182 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 84176ad9b3..11d916bdc3 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:968bba323acfabd9b1b02001e5b37047f6ab7fb7dae8c781eed2f84771beb9c9 -size 52812 +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 278552e34c..80612ba8aa 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:146039cba79c21408296e77e2aef33ccc3bc952283011ee4b441451512b2a634 -size 51680 +oid sha256:9dea64d46e3b174d525b782a39ea0fa98affebdd88c5d6b62b80785003e7138d +size 51545 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 bf0604356f..84d641f271 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:6937822a02885fc236f5520e947081883d1ccbdb3da04821d0da133e1f98d98e -size 51009 +oid sha256:9b4724b3461a0e76ef44a5932fdce76cb859a7dc4976f4b3f744117f38730c41 +size 50995 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 f5fc62eeed..4160e9bfdd 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:8c3f249cb608697afabe92a91f571a1a990424a212b92a9c2241e7ef9a173734 -size 52022 +oid sha256:96bb4aa54b2bcee34ec6a2ba58b1e5918a0e0a9fb6cc729abc97d84579731f8b +size 52265 diff --git a/tests/Images/External/ReferenceOutput/GifEncoderTests/GifEncoder_CanDecode_AndEncode_Issue2866_Rgba32_issue_2866.gif/00.gif b/tests/Images/External/ReferenceOutput/GifEncoderTests/GifEncoder_CanDecode_AndEncode_Issue2866_Rgba32_issue_2866.gif/00.gif index 8668f7102a..b219975ade 100644 --- a/tests/Images/External/ReferenceOutput/GifEncoderTests/GifEncoder_CanDecode_AndEncode_Issue2866_Rgba32_issue_2866.gif/00.gif +++ b/tests/Images/External/ReferenceOutput/GifEncoderTests/GifEncoder_CanDecode_AndEncode_Issue2866_Rgba32_issue_2866.gif/00.gif @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e738470c04121376b42937f0a72eed3f068bc32bdc788cc0d5877bc7f705f419 -size 67611 +oid sha256:cb4bbef09dc6618380e34c5dcf8612fa5a51ba81a09edc5500be9191f0554d9c +size 49665 diff --git a/tests/Images/External/ReferenceOutput/GifEncoderTests/GifEncoder_CanDecode_AndEncode_Issue2866_Rgba32_issue_2866.gif/08.gif b/tests/Images/External/ReferenceOutput/GifEncoderTests/GifEncoder_CanDecode_AndEncode_Issue2866_Rgba32_issue_2866.gif/08.gif index 8fd66d1206..2d50761636 100644 --- a/tests/Images/External/ReferenceOutput/GifEncoderTests/GifEncoder_CanDecode_AndEncode_Issue2866_Rgba32_issue_2866.gif/08.gif +++ b/tests/Images/External/ReferenceOutput/GifEncoderTests/GifEncoder_CanDecode_AndEncode_Issue2866_Rgba32_issue_2866.gif/08.gif @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:33b9244a09ed3b7bb17dfcbb694cbacc4f7de8ce6ffd7d7f7c35d424a6e8b2c5 -size 58696 +oid sha256:81a0d629326bb39cfced1a261542e5f94b423527f95bc45422670091b91583b4 +size 50730 diff --git a/tests/Images/External/ReferenceOutput/GifEncoderTests/GifEncoder_CanDecode_AndEncode_Issue2866_Rgba32_issue_2866.gif/104.gif b/tests/Images/External/ReferenceOutput/GifEncoderTests/GifEncoder_CanDecode_AndEncode_Issue2866_Rgba32_issue_2866.gif/104.gif index 90b8a081ce..b1b7781a21 100644 --- a/tests/Images/External/ReferenceOutput/GifEncoderTests/GifEncoder_CanDecode_AndEncode_Issue2866_Rgba32_issue_2866.gif/104.gif +++ b/tests/Images/External/ReferenceOutput/GifEncoderTests/GifEncoder_CanDecode_AndEncode_Issue2866_Rgba32_issue_2866.gif/104.gif @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:380f86015751a8445eb706d99001efe96633c62dd183c22303e732d036175dff -size 86095 +oid sha256:e64d9f2f7a8346f62c9b41a14b3e6b71f76a48e07fa42ac9e0d4a5b146a8a9da +size 58856 diff --git a/tests/Images/External/ReferenceOutput/GifEncoderTests/GifEncoder_CanDecode_AndEncode_Issue2866_Rgba32_issue_2866.gif/112.gif b/tests/Images/External/ReferenceOutput/GifEncoderTests/GifEncoder_CanDecode_AndEncode_Issue2866_Rgba32_issue_2866.gif/112.gif index 180c1e730b..f058764b4f 100644 --- a/tests/Images/External/ReferenceOutput/GifEncoderTests/GifEncoder_CanDecode_AndEncode_Issue2866_Rgba32_issue_2866.gif/112.gif +++ b/tests/Images/External/ReferenceOutput/GifEncoderTests/GifEncoder_CanDecode_AndEncode_Issue2866_Rgba32_issue_2866.gif/112.gif @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ae0f80bbc03fc35be4622df85f13f8fe7aec2d981424d9ca6dce88bd23ff2dad -size 59015 +oid sha256:b01517c53b19f6b151a76cc75142ba3a8a45da8c6e94416447703cbd54ce1a8a +size 48282 diff --git a/tests/Images/External/ReferenceOutput/GifEncoderTests/GifEncoder_CanDecode_AndEncode_Issue2866_Rgba32_issue_2866.gif/16.gif b/tests/Images/External/ReferenceOutput/GifEncoderTests/GifEncoder_CanDecode_AndEncode_Issue2866_Rgba32_issue_2866.gif/16.gif index 6eceee3979..b9f1e2d099 100644 --- a/tests/Images/External/ReferenceOutput/GifEncoderTests/GifEncoder_CanDecode_AndEncode_Issue2866_Rgba32_issue_2866.gif/16.gif +++ b/tests/Images/External/ReferenceOutput/GifEncoderTests/GifEncoder_CanDecode_AndEncode_Issue2866_Rgba32_issue_2866.gif/16.gif @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:696ef2d76fe8ae8a32f0299b668b0cf537c1ffe5ac3ca4b418499f03ca134d89 -size 70345 +oid sha256:1db212159613778c962883de9067852da3bea5f3483dd9f967c0aabbcdc1b2f6 +size 64655 diff --git a/tests/Images/External/ReferenceOutput/GifEncoderTests/GifEncoder_CanDecode_AndEncode_Issue2866_Rgba32_issue_2866.gif/24.gif b/tests/Images/External/ReferenceOutput/GifEncoderTests/GifEncoder_CanDecode_AndEncode_Issue2866_Rgba32_issue_2866.gif/24.gif index 51ef668112..c7a1368ce1 100644 --- a/tests/Images/External/ReferenceOutput/GifEncoderTests/GifEncoder_CanDecode_AndEncode_Issue2866_Rgba32_issue_2866.gif/24.gif +++ b/tests/Images/External/ReferenceOutput/GifEncoderTests/GifEncoder_CanDecode_AndEncode_Issue2866_Rgba32_issue_2866.gif/24.gif @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1dbd382f6f7b71f4cdffe61c01f0ab04523423576bad6195f8950ff7758792a2 -size 67153 +oid sha256:9d09ddfff1f26ed7842df5bf4b8938373700658322c85b154a878dd5e3a90dc1 +size 64432 diff --git a/tests/Images/External/ReferenceOutput/GifEncoderTests/GifEncoder_CanDecode_AndEncode_Issue2866_Rgba32_issue_2866.gif/32.gif b/tests/Images/External/ReferenceOutput/GifEncoderTests/GifEncoder_CanDecode_AndEncode_Issue2866_Rgba32_issue_2866.gif/32.gif index 26818c6a8f..ffd61e5123 100644 --- a/tests/Images/External/ReferenceOutput/GifEncoderTests/GifEncoder_CanDecode_AndEncode_Issue2866_Rgba32_issue_2866.gif/32.gif +++ b/tests/Images/External/ReferenceOutput/GifEncoderTests/GifEncoder_CanDecode_AndEncode_Issue2866_Rgba32_issue_2866.gif/32.gif @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a2942af218c05b29b4f5a8eb9117c8dea4dcf61b33e1ade3cfa6ba47a0d71f6c -size 68007 +oid sha256:18ca31ff631ecc33fe33a893e94e23af8b086a78c3684461e449c02800fffb2b +size 66510 diff --git a/tests/Images/External/ReferenceOutput/GifEncoderTests/GifEncoder_CanDecode_AndEncode_Issue2866_Rgba32_issue_2866.gif/40.gif b/tests/Images/External/ReferenceOutput/GifEncoderTests/GifEncoder_CanDecode_AndEncode_Issue2866_Rgba32_issue_2866.gif/40.gif index 92f5c1fb95..eb93ea4d4a 100644 --- a/tests/Images/External/ReferenceOutput/GifEncoderTests/GifEncoder_CanDecode_AndEncode_Issue2866_Rgba32_issue_2866.gif/40.gif +++ b/tests/Images/External/ReferenceOutput/GifEncoderTests/GifEncoder_CanDecode_AndEncode_Issue2866_Rgba32_issue_2866.gif/40.gif @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:45872e2361fc445d956abd5de454ad42bcdb8ade69742b032c865978e41cb477 -size 73531 +oid sha256:823358342cbc25a9f7ae34abc2669096acd7c0e0c93a8a0b371e548822ed0897 +size 66912 diff --git a/tests/Images/External/ReferenceOutput/GifEncoderTests/GifEncoder_CanDecode_AndEncode_Issue2866_Rgba32_issue_2866.gif/48.gif b/tests/Images/External/ReferenceOutput/GifEncoderTests/GifEncoder_CanDecode_AndEncode_Issue2866_Rgba32_issue_2866.gif/48.gif index 5524ce8a41..99f0e64dc6 100644 --- a/tests/Images/External/ReferenceOutput/GifEncoderTests/GifEncoder_CanDecode_AndEncode_Issue2866_Rgba32_issue_2866.gif/48.gif +++ b/tests/Images/External/ReferenceOutput/GifEncoderTests/GifEncoder_CanDecode_AndEncode_Issue2866_Rgba32_issue_2866.gif/48.gif @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:432af0fd8df68724f2faa3fc90dc751ff15a7e45604e20f3737d1902c64a913e -size 72024 +oid sha256:90558311a7b7127d9f970a17ae0630d81507be246f511f1cc3b10c6ee953a25c +size 61986 diff --git a/tests/Images/External/ReferenceOutput/GifEncoderTests/GifEncoder_CanDecode_AndEncode_Issue2866_Rgba32_issue_2866.gif/56.gif b/tests/Images/External/ReferenceOutput/GifEncoderTests/GifEncoder_CanDecode_AndEncode_Issue2866_Rgba32_issue_2866.gif/56.gif index bfd22ffef5..8e6410f9bf 100644 --- a/tests/Images/External/ReferenceOutput/GifEncoderTests/GifEncoder_CanDecode_AndEncode_Issue2866_Rgba32_issue_2866.gif/56.gif +++ b/tests/Images/External/ReferenceOutput/GifEncoderTests/GifEncoder_CanDecode_AndEncode_Issue2866_Rgba32_issue_2866.gif/56.gif @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:aab6a8c7892d8449ad8915f414689019684f99d3f011ddb82f722037b35a70c0 -size 77189 +oid sha256:9514b736d946d4e93ba3f59b586d2c29e0c031155f7824756ecf468ef87ea8e6 +size 61367 diff --git a/tests/Images/External/ReferenceOutput/GifEncoderTests/GifEncoder_CanDecode_AndEncode_Issue2866_Rgba32_issue_2866.gif/64.gif b/tests/Images/External/ReferenceOutput/GifEncoderTests/GifEncoder_CanDecode_AndEncode_Issue2866_Rgba32_issue_2866.gif/64.gif index e3f17772eb..2257625c41 100644 --- a/tests/Images/External/ReferenceOutput/GifEncoderTests/GifEncoder_CanDecode_AndEncode_Issue2866_Rgba32_issue_2866.gif/64.gif +++ b/tests/Images/External/ReferenceOutput/GifEncoderTests/GifEncoder_CanDecode_AndEncode_Issue2866_Rgba32_issue_2866.gif/64.gif @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b93da4e1ab6ed01c561d5b565c078ff8752f7b16b78c733b658237977b1e4689 -size 78172 +oid sha256:0334c551b9efcaa9f5c16c4599884b4aabe5129e3f023222be3214cf8623242e +size 60825 diff --git a/tests/Images/External/ReferenceOutput/GifEncoderTests/GifEncoder_CanDecode_AndEncode_Issue2866_Rgba32_issue_2866.gif/72.gif b/tests/Images/External/ReferenceOutput/GifEncoderTests/GifEncoder_CanDecode_AndEncode_Issue2866_Rgba32_issue_2866.gif/72.gif index cfe4a2eefe..efc9569f4d 100644 --- a/tests/Images/External/ReferenceOutput/GifEncoderTests/GifEncoder_CanDecode_AndEncode_Issue2866_Rgba32_issue_2866.gif/72.gif +++ b/tests/Images/External/ReferenceOutput/GifEncoderTests/GifEncoder_CanDecode_AndEncode_Issue2866_Rgba32_issue_2866.gif/72.gif @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a8b426203cd6b6b5f4b97a5213a8a04da66056e3b238be3407751a43b365e11b -size 71198 +oid sha256:32a9fecdad6508c1c6beae839717d1854cca1f7b247bff36a00a93cc953f608c +size 57370 diff --git a/tests/Images/External/ReferenceOutput/GifEncoderTests/GifEncoder_CanDecode_AndEncode_Issue2866_Rgba32_issue_2866.gif/80.gif b/tests/Images/External/ReferenceOutput/GifEncoderTests/GifEncoder_CanDecode_AndEncode_Issue2866_Rgba32_issue_2866.gif/80.gif index 6c713186f4..9f7ae53fb0 100644 --- a/tests/Images/External/ReferenceOutput/GifEncoderTests/GifEncoder_CanDecode_AndEncode_Issue2866_Rgba32_issue_2866.gif/80.gif +++ b/tests/Images/External/ReferenceOutput/GifEncoderTests/GifEncoder_CanDecode_AndEncode_Issue2866_Rgba32_issue_2866.gif/80.gif @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d47400e426792c550afb2797b68e41e3ce04409dde627e0f74c39f216a951f22 -size 83675 +oid sha256:5d50d4ccba947ef95b9e8a2c4acd08f57c414f4e38a0d03d65b5fee093e4481a +size 67784 diff --git a/tests/Images/External/ReferenceOutput/GifEncoderTests/GifEncoder_CanDecode_AndEncode_Issue2866_Rgba32_issue_2866.gif/88.gif b/tests/Images/External/ReferenceOutput/GifEncoderTests/GifEncoder_CanDecode_AndEncode_Issue2866_Rgba32_issue_2866.gif/88.gif index 89a8e3880a..22dc30784c 100644 --- a/tests/Images/External/ReferenceOutput/GifEncoderTests/GifEncoder_CanDecode_AndEncode_Issue2866_Rgba32_issue_2866.gif/88.gif +++ b/tests/Images/External/ReferenceOutput/GifEncoderTests/GifEncoder_CanDecode_AndEncode_Issue2866_Rgba32_issue_2866.gif/88.gif @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d11ae15a30005e335d2f84f2749573fc59d88d2f1c415cfea2126b47e3dac60b -size 91647 +oid sha256:840780f2916cb9d010a95802d9c123c3051bcee5dde7b173a50854e3b5f3636a +size 72552 diff --git a/tests/Images/External/ReferenceOutput/GifEncoderTests/GifEncoder_CanDecode_AndEncode_Issue2866_Rgba32_issue_2866.gif/96.gif b/tests/Images/External/ReferenceOutput/GifEncoderTests/GifEncoder_CanDecode_AndEncode_Issue2866_Rgba32_issue_2866.gif/96.gif index f7ef065348..53e1a35cbf 100644 --- a/tests/Images/External/ReferenceOutput/GifEncoderTests/GifEncoder_CanDecode_AndEncode_Issue2866_Rgba32_issue_2866.gif/96.gif +++ b/tests/Images/External/ReferenceOutput/GifEncoderTests/GifEncoder_CanDecode_AndEncode_Issue2866_Rgba32_issue_2866.gif/96.gif @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6410a7792823ad1c5d5f0fcaed388b362498f1bbd57175b70165e0ce3542662c -size 99183 +oid sha256:41db6ded3de84d43dec1175c1481f75a045c5ad126369e4e82ae29ec4bad0bc4 +size 76868 diff --git a/tests/Images/External/ReferenceOutput/PngEncoderTests/Encode_AnimatedFormatTransform_FromGif_Rgba32_issue_2866.gif/00.png b/tests/Images/External/ReferenceOutput/PngEncoderTests/Encode_AnimatedFormatTransform_FromGif_Rgba32_issue_2866.gif/00.png index 3658168255..17c8e35c6f 100644 --- a/tests/Images/External/ReferenceOutput/PngEncoderTests/Encode_AnimatedFormatTransform_FromGif_Rgba32_issue_2866.gif/00.png +++ b/tests/Images/External/ReferenceOutput/PngEncoderTests/Encode_AnimatedFormatTransform_FromGif_Rgba32_issue_2866.gif/00.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0840e31b83f0f9621e2c98cb784881f88c882f7b558aef889eb2511a893af585 -size 63955 +oid sha256:4aa1dea5da7a94fddd2259cd86e7171a57cb5cd2198decedd86149778b9aa20a +size 64030 diff --git a/tests/Images/External/ReferenceOutput/PngEncoderTests/Encode_AnimatedFormatTransform_FromGif_Rgba32_issue_2866.gif/08.png b/tests/Images/External/ReferenceOutput/PngEncoderTests/Encode_AnimatedFormatTransform_FromGif_Rgba32_issue_2866.gif/08.png index eb52b14732..f2ec385a88 100644 --- a/tests/Images/External/ReferenceOutput/PngEncoderTests/Encode_AnimatedFormatTransform_FromGif_Rgba32_issue_2866.gif/08.png +++ b/tests/Images/External/ReferenceOutput/PngEncoderTests/Encode_AnimatedFormatTransform_FromGif_Rgba32_issue_2866.gif/08.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c674fe70b76d6763215620680f7f24bc3113c3ee98eeb9f9e9572ccd8825c61f -size 68053 +oid sha256:16d4159e492162372a16b08fb3a4fdf36908ce759c6a0ad72a0da4bbeb477b72 +size 68619 diff --git a/tests/Images/External/ReferenceOutput/PngEncoderTests/Encode_AnimatedFormatTransform_FromGif_Rgba32_issue_2866.gif/104.png b/tests/Images/External/ReferenceOutput/PngEncoderTests/Encode_AnimatedFormatTransform_FromGif_Rgba32_issue_2866.gif/104.png index ad4232e3c0..dc57ed717f 100644 --- a/tests/Images/External/ReferenceOutput/PngEncoderTests/Encode_AnimatedFormatTransform_FromGif_Rgba32_issue_2866.gif/104.png +++ b/tests/Images/External/ReferenceOutput/PngEncoderTests/Encode_AnimatedFormatTransform_FromGif_Rgba32_issue_2866.gif/104.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e46346d886acf62b3d742d6df9ddd9226afd2a4184be045e00724cf0ab4c25b4 -size 77343 +oid sha256:1095bc829b8fab3c5fda66b90cdd751b81439ad80e9fc7d564a5212c75155b2c +size 76100 diff --git a/tests/Images/External/ReferenceOutput/PngEncoderTests/Encode_AnimatedFormatTransform_FromGif_Rgba32_issue_2866.gif/112.png b/tests/Images/External/ReferenceOutput/PngEncoderTests/Encode_AnimatedFormatTransform_FromGif_Rgba32_issue_2866.gif/112.png index 5402b021f6..0158b28407 100644 --- a/tests/Images/External/ReferenceOutput/PngEncoderTests/Encode_AnimatedFormatTransform_FromGif_Rgba32_issue_2866.gif/112.png +++ b/tests/Images/External/ReferenceOutput/PngEncoderTests/Encode_AnimatedFormatTransform_FromGif_Rgba32_issue_2866.gif/112.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:9a647f174ac31570a3e42c472fa888dadcd5117e6f8300ef35b21ed2cff5bd6c -size 65959 +oid sha256:0212cd29319c0d90c18a5f90716156a888c9925e4a36a6d9bbbe8d67a706db8f +size 66282 diff --git a/tests/Images/External/ReferenceOutput/PngEncoderTests/Encode_AnimatedFormatTransform_FromGif_Rgba32_issue_2866.gif/16.png b/tests/Images/External/ReferenceOutput/PngEncoderTests/Encode_AnimatedFormatTransform_FromGif_Rgba32_issue_2866.gif/16.png index 0f7643659f..52867f468e 100644 --- a/tests/Images/External/ReferenceOutput/PngEncoderTests/Encode_AnimatedFormatTransform_FromGif_Rgba32_issue_2866.gif/16.png +++ b/tests/Images/External/ReferenceOutput/PngEncoderTests/Encode_AnimatedFormatTransform_FromGif_Rgba32_issue_2866.gif/16.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:951a034bae41acdcbd202d1d8624609bf4a47edbcd94806e4993b0a610fb767e -size 74231 +oid sha256:620ea89afc2e41b6199c47b2e4777f5ef9afe139e22bfabca9ec094b0adcbdc2 +size 73862 diff --git a/tests/Images/External/ReferenceOutput/PngEncoderTests/Encode_AnimatedFormatTransform_FromGif_Rgba32_issue_2866.gif/24.png b/tests/Images/External/ReferenceOutput/PngEncoderTests/Encode_AnimatedFormatTransform_FromGif_Rgba32_issue_2866.gif/24.png index 0d680bb4c9..26ae147489 100644 --- a/tests/Images/External/ReferenceOutput/PngEncoderTests/Encode_AnimatedFormatTransform_FromGif_Rgba32_issue_2866.gif/24.png +++ b/tests/Images/External/ReferenceOutput/PngEncoderTests/Encode_AnimatedFormatTransform_FromGif_Rgba32_issue_2866.gif/24.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ce790490566b7d908e6372746a264b0d207e3b8f906fddf020e68e0b5effd757 -size 75693 +oid sha256:d39997f18af5b97ed32ea30c15da879f70906b4f5cf12439ea6ff2d229e40199 +size 75734 diff --git a/tests/Images/External/ReferenceOutput/PngEncoderTests/Encode_AnimatedFormatTransform_FromGif_Rgba32_issue_2866.gif/32.png b/tests/Images/External/ReferenceOutput/PngEncoderTests/Encode_AnimatedFormatTransform_FromGif_Rgba32_issue_2866.gif/32.png index 0c724b5e96..6cd5074ecf 100644 --- a/tests/Images/External/ReferenceOutput/PngEncoderTests/Encode_AnimatedFormatTransform_FromGif_Rgba32_issue_2866.gif/32.png +++ b/tests/Images/External/ReferenceOutput/PngEncoderTests/Encode_AnimatedFormatTransform_FromGif_Rgba32_issue_2866.gif/32.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:5aea5c9671a91101081287426e57c07fb56c1f072f582bd2f135705c114239f8 -size 79474 +oid sha256:4b459edf763ffff8a8a016c1b7d9e13b7e99f6752e4452b52539d2eb0f7f31be +size 79505 diff --git a/tests/Images/External/ReferenceOutput/PngEncoderTests/Encode_AnimatedFormatTransform_FromGif_Rgba32_issue_2866.gif/40.png b/tests/Images/External/ReferenceOutput/PngEncoderTests/Encode_AnimatedFormatTransform_FromGif_Rgba32_issue_2866.gif/40.png index 587107445e..8b9ea7c239 100644 --- a/tests/Images/External/ReferenceOutput/PngEncoderTests/Encode_AnimatedFormatTransform_FromGif_Rgba32_issue_2866.gif/40.png +++ b/tests/Images/External/ReferenceOutput/PngEncoderTests/Encode_AnimatedFormatTransform_FromGif_Rgba32_issue_2866.gif/40.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:100d85d20cf39ed4b8f70a7c3a23c5ef857899e3830a7869437eb38819dbe309 -size 77200 +oid sha256:f1957c82709b22abbaa354ecd679159eeddf135aabb301c266518c23ff4918f9 +size 77588 diff --git a/tests/Images/External/ReferenceOutput/PngEncoderTests/Encode_AnimatedFormatTransform_FromGif_Rgba32_issue_2866.gif/48.png b/tests/Images/External/ReferenceOutput/PngEncoderTests/Encode_AnimatedFormatTransform_FromGif_Rgba32_issue_2866.gif/48.png index ea2bd82071..4430290427 100644 --- a/tests/Images/External/ReferenceOutput/PngEncoderTests/Encode_AnimatedFormatTransform_FromGif_Rgba32_issue_2866.gif/48.png +++ b/tests/Images/External/ReferenceOutput/PngEncoderTests/Encode_AnimatedFormatTransform_FromGif_Rgba32_issue_2866.gif/48.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c48b5e76c80649d42c58043d6afaebf872b69031e12af0554847afc167379d9e -size 77420 +oid sha256:ae33b303bd1feae301cf645ee6596c499ad4eaa9164c52a37fca760169f9f363 +size 80317 diff --git a/tests/Images/External/ReferenceOutput/PngEncoderTests/Encode_AnimatedFormatTransform_FromGif_Rgba32_issue_2866.gif/56.png b/tests/Images/External/ReferenceOutput/PngEncoderTests/Encode_AnimatedFormatTransform_FromGif_Rgba32_issue_2866.gif/56.png index 206ddc164e..8feae54c6a 100644 --- a/tests/Images/External/ReferenceOutput/PngEncoderTests/Encode_AnimatedFormatTransform_FromGif_Rgba32_issue_2866.gif/56.png +++ b/tests/Images/External/ReferenceOutput/PngEncoderTests/Encode_AnimatedFormatTransform_FromGif_Rgba32_issue_2866.gif/56.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e98364b6e9e913a56e95aace2ac07890e1d4f8a31e44b5195cac0779e9ed75bb -size 75433 +oid sha256:d86360bd43de392a9cf4b671a4cf3cd7ba9f61952a6d0400a5baab7dbb5eeb6d +size 78578 diff --git a/tests/Images/External/ReferenceOutput/PngEncoderTests/Encode_AnimatedFormatTransform_FromGif_Rgba32_issue_2866.gif/64.png b/tests/Images/External/ReferenceOutput/PngEncoderTests/Encode_AnimatedFormatTransform_FromGif_Rgba32_issue_2866.gif/64.png index b92f711df6..284ad99233 100644 --- a/tests/Images/External/ReferenceOutput/PngEncoderTests/Encode_AnimatedFormatTransform_FromGif_Rgba32_issue_2866.gif/64.png +++ b/tests/Images/External/ReferenceOutput/PngEncoderTests/Encode_AnimatedFormatTransform_FromGif_Rgba32_issue_2866.gif/64.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:53a1c8c9653da218d6f49837a7d5b8623568eb629670d0026e056fb1f13ca52e -size 77402 +oid sha256:60e1694b121d0d3fa17d62f4477de349376aa6c2a98b83f9a4525da9ea471662 +size 75204 diff --git a/tests/Images/External/ReferenceOutput/PngEncoderTests/Encode_AnimatedFormatTransform_FromGif_Rgba32_issue_2866.gif/72.png b/tests/Images/External/ReferenceOutput/PngEncoderTests/Encode_AnimatedFormatTransform_FromGif_Rgba32_issue_2866.gif/72.png index 9e78a1f28a..d09b77afca 100644 --- a/tests/Images/External/ReferenceOutput/PngEncoderTests/Encode_AnimatedFormatTransform_FromGif_Rgba32_issue_2866.gif/72.png +++ b/tests/Images/External/ReferenceOutput/PngEncoderTests/Encode_AnimatedFormatTransform_FromGif_Rgba32_issue_2866.gif/72.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:06f1d36cede878377ef7731ac2530d3cd489fdb09308794315da7b62a48ec667 -size 73840 +oid sha256:9348ef330a239bea501f41a3256107fc10a6bb323642118b1fcbe5227b742c88 +size 79048 diff --git a/tests/Images/External/ReferenceOutput/PngEncoderTests/Encode_AnimatedFormatTransform_FromGif_Rgba32_issue_2866.gif/80.png b/tests/Images/External/ReferenceOutput/PngEncoderTests/Encode_AnimatedFormatTransform_FromGif_Rgba32_issue_2866.gif/80.png index 0855f16ed3..d31fd5a808 100644 --- a/tests/Images/External/ReferenceOutput/PngEncoderTests/Encode_AnimatedFormatTransform_FromGif_Rgba32_issue_2866.gif/80.png +++ b/tests/Images/External/ReferenceOutput/PngEncoderTests/Encode_AnimatedFormatTransform_FromGif_Rgba32_issue_2866.gif/80.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:3ace97f772679993cb35dc0630d4431019a7e885cd397db98d2b0d91a6c984db -size 81863 +oid sha256:452dd2339b6ad3e35528601c289d88eafcc7e9a6e717f25da2bbf4267b85be97 +size 81553 diff --git a/tests/Images/External/ReferenceOutput/PngEncoderTests/Encode_AnimatedFormatTransform_FromGif_Rgba32_issue_2866.gif/88.png b/tests/Images/External/ReferenceOutput/PngEncoderTests/Encode_AnimatedFormatTransform_FromGif_Rgba32_issue_2866.gif/88.png index 5564c32923..669dfb8600 100644 --- a/tests/Images/External/ReferenceOutput/PngEncoderTests/Encode_AnimatedFormatTransform_FromGif_Rgba32_issue_2866.gif/88.png +++ b/tests/Images/External/ReferenceOutput/PngEncoderTests/Encode_AnimatedFormatTransform_FromGif_Rgba32_issue_2866.gif/88.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a1fa4b08ec93e17cfa9906ca6f447da651f469aad3615cc5ae08f4080784c096 -size 86317 +oid sha256:b67e3b57b62f9e1c924de6fbd2699f93a6594812ad8c4cafd6160323547b1a88 +size 86402 diff --git a/tests/Images/External/ReferenceOutput/PngEncoderTests/Encode_AnimatedFormatTransform_FromGif_Rgba32_issue_2866.gif/96.png b/tests/Images/External/ReferenceOutput/PngEncoderTests/Encode_AnimatedFormatTransform_FromGif_Rgba32_issue_2866.gif/96.png index 41b121a15f..f9a5aee191 100644 --- a/tests/Images/External/ReferenceOutput/PngEncoderTests/Encode_AnimatedFormatTransform_FromGif_Rgba32_issue_2866.gif/96.png +++ b/tests/Images/External/ReferenceOutput/PngEncoderTests/Encode_AnimatedFormatTransform_FromGif_Rgba32_issue_2866.gif/96.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f9e6fd785ab0ef4e75adbf4697d7cb04873a95d01a5fb5197090e87061aa3430 -size 88729 +oid sha256:887e94402f93a491820d3d25d641c5901cf796c1275a2feb9e1c072a6a63ce28 +size 87335 diff --git a/tests/Images/External/ReferenceOutput/PngEncoderTests/Encode_AnimatedFormatTransform_FromGif_Rgba32_leo.gif/00.png b/tests/Images/External/ReferenceOutput/PngEncoderTests/Encode_AnimatedFormatTransform_FromGif_Rgba32_leo.gif/00.png index a0b3e0a5d3..961e205f9f 100644 --- a/tests/Images/External/ReferenceOutput/PngEncoderTests/Encode_AnimatedFormatTransform_FromGif_Rgba32_leo.gif/00.png +++ b/tests/Images/External/ReferenceOutput/PngEncoderTests/Encode_AnimatedFormatTransform_FromGif_Rgba32_leo.gif/00.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:bf499248f2cdb443d12998c45cad8fcfee04e0febb21965980815aa910bc34ab -size 23945 +oid sha256:a6baac5db47ef2ee3ba72c7e8d5e04fc888c5e1460d4d7e348f63b597e1b5ff7 +size 23660 diff --git a/tests/Images/External/ReferenceOutput/PngEncoderTests/Encode_AnimatedFormatTransform_FromGif_Rgba32_leo.gif/08.png b/tests/Images/External/ReferenceOutput/PngEncoderTests/Encode_AnimatedFormatTransform_FromGif_Rgba32_leo.gif/08.png index bbe754da3d..82cb6168e8 100644 --- a/tests/Images/External/ReferenceOutput/PngEncoderTests/Encode_AnimatedFormatTransform_FromGif_Rgba32_leo.gif/08.png +++ b/tests/Images/External/ReferenceOutput/PngEncoderTests/Encode_AnimatedFormatTransform_FromGif_Rgba32_leo.gif/08.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0ecf0666ca8b0ef16cf0e6147aea4b76af76f62777a62ea02edcf5cf5a309d8b -size 29721 +oid sha256:7fad45a0682a70a1fb84eec76d4a80ee054e85df59e6729508f3698d39999e31 +size 29509 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 94eee7f07d..f0f8ef57f8 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:c5953aaa4569e97d1bf690e4429ae6684a4131521347cb8bf1f607d773018ee6 -size 939085 +oid sha256:9779d9221f198ce26cd57482a5257266959b2b2f009a838ca1c52535d82d9305 +size 1013666 diff --git a/tests/Images/External/ReferenceOutput/PngEncoderTests/Issue2668_Quantized_Encode_Alpha_Rgba32_Issue_2668.png b/tests/Images/External/ReferenceOutput/PngEncoderTests/Issue2668_Quantized_Encode_Alpha_Rgba32_Issue_2668.png index f156cf1d64..5a4eecdf51 100644 --- a/tests/Images/External/ReferenceOutput/PngEncoderTests/Issue2668_Quantized_Encode_Alpha_Rgba32_Issue_2668.png +++ b/tests/Images/External/ReferenceOutput/PngEncoderTests/Issue2668_Quantized_Encode_Alpha_Rgba32_Issue_2668.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:215b86efdfb603ad851a7f4b5830e0ff82fb49e7729fcfd0853a6c066b21507e -size 8235 +oid sha256:09b75a0eaaa14516a0b86f7254819e4ccded4afe31260fe87658685e8ff0e97c +size 8240 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 28232733f6..327366f5b6 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:33f86d176382805fe60cc7cf8057583a8451f802982ebcd337bda8dbf69efd6a -size 248753 +oid sha256:0086044f12a7c58e49733f203af29a8aff2826ea654730274720eada15669254 +size 249163 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 e1bc23807a..046a7129a2 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:24f738baad4417c2eedddf36064974ccd5ff9e1d1ac23e4f6c859c4fa789a447 -size 266819 +oid sha256:a8ac5998ff0f30874083c5c91639a091157762e5b3d4737f36575771d2d84112 +size 266733 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 0f3d653f43..922c2bf9b2 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:684cdf0f3f9d074e986b8b85b2c6c65da1f6f486c0eab727cc8a1c92b651fc9e -size 216246 +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 0f3d653f43..922c2bf9b2 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:684cdf0f3f9d074e986b8b85b2c6c65da1f6f486c0eab727cc8a1c92b651fc9e -size 216246 +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 c406e71c30..9876d24345 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:7efb8263a067de2f4368a43416049d13619a69761584867ec89867ed8b366c5e -size 226887 +oid sha256:996b07c1bd04c7307045c4c784f01151ae6e34e17ee578fc37a4d98797e3e312 +size 227290 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 ec95754094..dbfab2b508 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:7aa18d1a444a12c30003c533b411b018c83684dbe48fce07293f83401c44b853 -size 220689 +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 ec95754094..dbfab2b508 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:7aa18d1a444a12c30003c533b411b018c83684dbe48fce07293f83401c44b853 -size 220689 +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 7b099d1346..bb9bc84b07 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:96fceb13a0ec386959e5bdad17e3e2896f43dc86c02abf0b88f882c898523563 -size 230800 +oid sha256:459ee778721b80a14f19063aeb5c9efc3217e7c56617cebc42a2718c918f0eb7 +size 231125 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 52a297cdf6..82d5e5d592 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:14b8be6579cea0742be6ab1d8a44b7fc7f7acc26698692dbe445435f1fa2e48a -size 262707 +oid sha256:f4df5b1bc2c291ec1cf599580d198b447278412576ab998e099cc21110e82b3d +size 263152 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 268f787beb..a3239701bb 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:0b4ffa39ea41480b02ac183dcb28617278a46bcaef0a30af62fe17167f009bbd -size 274683 +oid sha256:7b5e6685b88a6c1aa2dc4c5ad40251468fe3261696b5c870b2044eb3ba1ea3fc +size 274351 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 36317b7bbe..f29db004f5 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:d10d2efb6d711bbff03a803785f7269ddc9f5ba9417597e60804f2476ad72af2 -size 315621 +oid sha256:ce381c2d261b9b1ca61d8f6e2ff07b992283c327dc6b7cf53c7e5c9317abb7d3 +size 316443 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 a45e842833..6613f0b955 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:d85d4e8da5754786c6e632d4e7ce811ab4ce026664ca34c7a0e5cb01d6cce847 -size 322349 +oid sha256:ad005d9206d10cecbc66fddbf69d7710ed39ae2625be834d79790b031625a8e9 +size 322032 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 f33e66693b..0205626738 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:09d4a269df15a78b84c94a22788b0cdbe187852979ad59afff886a98a66282c4 -size 269323 +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 f33e66693b..0205626738 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:09d4a269df15a78b84c94a22788b0cdbe187852979ad59afff886a98a66282c4 -size 269323 +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 11d5e4ec37..698af27f84 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:992b1f5b3e8b342d4fbe19259a2dd88bed1a0b60fe78b7c4b3027472e141ca90 -size 271448 +oid sha256:177cdc502d4b34fd576023de3fff6900c0ec197bd8e8543aee5876b1d5968a1b +size 270708 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 9df2f13dbb..324bd92539 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:ae0a724189324a60c9f68014f4c178add91aecf7dbe49a1ca2c01c10816927a4 -size 284288 +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 9df2f13dbb..324bd92539 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:ae0a724189324a60c9f68014f4c178add91aecf7dbe49a1ca2c01c10816927a4 -size 284288 +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 8d7a39e7af..ab66e92848 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:00962e89673a11aa5379fe8a1513012fa192bc1fe9caffedf26652ce14b681d1 -size 284714 +oid sha256:85f8d506191ef7712c8a47ae86598f825d9ed50214a130137e81b5b6dd0d490e +size 284865 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 64560574a6..05be1395ab 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:a446d9dba2cdcfd336847e6a475872a1e1c99f204a9aba7aed0e4da282e0e9dd -size 317073 +oid sha256:90a2b7b3872c6eb1f1f039558d9f6ace92891c86951c801da01ad55b055fd670 +size 316544 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 46689cc8d7..1aa0b8fa61 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:993480e1b245bcae568bc4ac99f356509a932ab796bad17f12b051d12e08481d -size 323571 +oid sha256:4797cc9eac13224649675472069d21b24607584887771350694408aa22fa88c5 +size 323238 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 8b9efe3364..a2fb2a6760 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:88be683fabadd0d6812c208b878def4a64612712332c748bbbf05e793d9a65e1 -size 17790 +oid sha256:18a47a6fa0f7949daef6969a847d8bc04deeb16bb482211ec3a958bc63f23f89 +size 13158 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 8b9efe3364..8d99eb49b2 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:88be683fabadd0d6812c208b878def4a64612712332c748bbbf05e793d9a65e1 -size 17790 +oid sha256:abfdd1e40c2c1d7fde419bda1da6e534ed989598e790b8ae4de35152a83f77a0 +size 13686 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 8b9efe3364..bf93c39ff8 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:88be683fabadd0d6812c208b878def4a64612712332c748bbbf05e793d9a65e1 -size 17790 +oid sha256:60c28eb1dc3c0416b20cec230917c0e4a70dd2929467bbab796ecbb04fe5a178 +size 13886 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 8b9efe3364..a2fb2a6760 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:88be683fabadd0d6812c208b878def4a64612712332c748bbbf05e793d9a65e1 -size 17790 +oid sha256:18a47a6fa0f7949daef6969a847d8bc04deeb16bb482211ec3a958bc63f23f89 +size 13158 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 8b9efe3364..457298b544 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:88be683fabadd0d6812c208b878def4a64612712332c748bbbf05e793d9a65e1 -size 17790 +oid sha256:a523f097bf3b155f3823c5e400190b5d5e0d4470db7136576472c3257db76600 +size 13909 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 1bf46ad350..5431ffdefd 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:61d389cf13cd8ed2a4692dd812848675973669758fef2b88a03e77248b7804dd -size 19142 +oid sha256:dd8b648b89f9420a0004a5f95dd54dc3769d1f78816b6708c5c6e1c14e8533a1 +size 17802 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 dfb0ba32d6..02ade5b868 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:215dd3bf4bfb82bbd42751440115e1983760ca2a792e07b929203f33712bb29e -size 20372 +oid sha256:7e559263bd8c293797d59166e723fdaf8b1b6ff9ae20fabac0efc86d8306123e +size 19266 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 c09f3957ab..a7404c33ff 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:99a22bdcc34c31cab34ceb314b3114231ccdf1ad9159ea2f6f73d65179b28225 -size 21285 +oid sha256:26c8d680e1f490f3f17d4c54363eed512149e7a2a3f463d357ce8d0a3e03d564 +size 21076 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 8b9efe3364..a2fb2a6760 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:88be683fabadd0d6812c208b878def4a64612712332c748bbbf05e793d9a65e1 -size 17790 +oid sha256:18a47a6fa0f7949daef6969a847d8bc04deeb16bb482211ec3a958bc63f23f89 +size 13158 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 04e2846027..fb5b71e968 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:64f863b7acbf08125e894093b7c844fdbc7cb635f72e6eaf3f1f2d2f4f10f880 -size 22179 +oid sha256:bb75dc75b79b1f51f47779cf491bdd3b73810f6d6b16941c12c0f6e1727e075c +size 21823 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 93ebc8ac7e..9850675bed 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:4f95a03b6167e1e5174b94cbd2d4c0df6fbcf74402abcf027fb51c048d6040ca -size 9236 +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 9ff1175dfa..f3278c3d2f 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:3f30a9cabe172856d45e058b41adba1580e4aac2c5455738a0fe1222e0fdb313 -size 10128 +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 34257a5236..77821255bb 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:28f444d388662a516e261dd7b5d28fc142b6089061ca9450a9990053884eee6d -size 10596 +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 170dd9482e..0615793d57 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:2e51a7ab6a24ad8466eb1c07bbd221a9b99cff2515304cd7d8b5577d7924c333 -size 8600 +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 0523babb39..c43b5836ec 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:1eb4268feb96c5fe5e53cf9a70dc50ff54a4d229f41a9ec7a005232f707ae395 -size 12835 +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 5d6ec0c0fa..e54740610c 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:db9c3dee98f961011829fd5d1733c990016d8518684dea43bdc0ed8b46dc065b -size 9461 +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 a13f473352..b08ba5be19 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:c49ae3b99cac4592c3aba6b1bd2613b7c2887419df84135facd9caab1c67c4f2 -size 9527 +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 6a2400a99d..ffea1266b4 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:7d2646e749b40122224662cc505dbf047703c39d100c9564c880bda7b3b31f9b -size 9648 +oid sha256:65d1f41f344615534551821c3ad68cdf8b1c78cc0c3585d351d697aeeafaac83 +size 9741 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 170dd9482e..0615793d57 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:2e51a7ab6a24ad8466eb1c07bbd221a9b99cff2515304cd7d8b5577d7924c333 -size 8600 +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 f29dae3423..68e78ef5f9 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:26c1b8c9390950c7224a39ffdb4829db57b0bf55e05a041f1bc47f5d2218893c -size 9726 +oid sha256:a4e1276e62f276c1e728afd407a918be96cf21eaa11716d0006e65b8c18bfc3a +size 9823 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 80c5b82ce0..10b511a1a3 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:88881839126c275a3d71278e4873cd05f4fe26eb7e1d1c2f5b29826d5bc60ef2 -size 11601 +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 dde458b3a9..1ed81c0d0a 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:bcd2b1732cfd93dc307c783fd71df4736a3e99ff318a32197e472b6202118582 -size 11457 +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 395b0db882..30f75826eb 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:3d9f342735dd97d58952ac3f4561cdc71c0c0c9059886d5a9cc7fe87c3f5dd3f -size 12955 +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 d50b18799f..af9954116a 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:b3c5370823f6f2a80a843967d541afdbb38779e2e56d629e5e011c3b35c7060d -size 10928 +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 2cc4e0207d..5b8c5127c0 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:603b127cbb2c4134a2f7cde964dcdc37ab7f8ba4e7eef01df09ac1d8aba02346 -size 14262 +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 1188593ceb..93fa5c1de3 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:05796c5a4b91edf69d39ec63160651b86d296d67d7a1424059741e93d5da8307 -size 12904 +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 d5218089a1..6c9199a329 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:dc3087514a1a569e206280f5855af412301a432176c7731ca77ff7af725a18ed -size 13349 +oid sha256:d189df03a886dbc2f8231afc69fda4894f8a7354843601a1a489550e86e058d2 +size 13166 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 b954c7ee7e..49558c6301 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:353472eefdd16b56ef623cdc0ad95b8741496b2a52484fecc89a2b7e53e33d54 -size 13619 +oid sha256:e6e94a38860960ca2f74e4fc71661187b8b2bc7edbbb66dfc99a89ec864d2038 +size 13414 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 d50b18799f..af9954116a 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:b3c5370823f6f2a80a843967d541afdbb38779e2e56d629e5e011c3b35c7060d -size 10928 +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 3a45bec62b..2abaadc655 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:ec6a6dfa0ae752d5e13439fe78365b7c84b4deb6c5d85fc151a61c69df01a48d -size 13824 +oid sha256:011f6b4e6047419ccb0fb7b0c5e9f9b15863f982dbb76ec7c358c6792c8c12a8 +size 13734 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 1c131b0017..02879b7a38 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:bac98b38fa3d0029341d9ad1a4325b1957c353734dec225605a07fb64662802a -size 13219 +oid sha256:c4ac8b88b317281738d833fc71f52348d9f4f45ea5a1303dd91fdb8b42be4267 +size 13186 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 ae6bfc9fa7..ba05094800 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:28881d29b70b83facf59e4dd6d4c396412f65b322503af57ee2fbbbee8031e53 -size 13474 +oid sha256:1305d54f2139d4577490317051d6ce94a7fc8dd45b902d87a30fb04098dd4594 +size 13407 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 0d85aeab5d..b16a5a5c7b 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:d18dda730014d92b7e5718762c457d013a5c0b1086ef076ed398d656697607ba -size 13849 +oid sha256:a3fc3a7ace123c330ea06072eb36dd5d65ed9154d4d0f55a828fc542c8a422c1 +size 13472 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 cd728c99bc..6adac16cf5 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:83208a455e42ef4d097c3db84535a5e83f1b84d26b0294d9f374b20c4987d1df -size 15810 +oid sha256:35757f2e0831cae2fbd3cc11ffaaae855e853ebaa9a1a5564b6568a5e1c442e9 +size 16031 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 faf1a0d92d..156306a599 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:3b0b886fa479a9d57f21d39d8e72db0cc075207fadfdcb2d293c4daea206e848 -size 17167 +oid sha256:1109450bca447e244f8138939258d70e1771fed3ab035b6bb8e5f9752e66c0f3 +size 17176 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 c81658d0b5..79eb0bc56b 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:4cb5b4d98d13b0212c0e52547c8e402889c43b48f1a7368b5d54e96d25f61ed2 -size 18026 +oid sha256:0c301ddcd557583254398d5d1e6cbb1da573355935c6ab07a4f3afe1532ac7be +size 17767 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 b2126744f3..17565808ee 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:a24c69dc99d0a35afb91b17f256df55e774149e53df81e9102ba57725c5f7791 -size 18097 +oid sha256:76bf7bffa123083cee0922edca9c0e738dc8d69c649283cf12ad44e7c6295a71 +size 18175 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 624d5cc670..e9782bc076 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:33e5d119018fcb0ff3ad4bdf0efa947ec9ef8a0a92a3dba64fe5c65b7a046737 -size 84080 +oid sha256:b380eda5646fe97ee217ef711103001e54ee023fb8d95f7f3bbad19d886130da +size 83702 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 6b174dd34a..ce271fa145 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:d35750b85b062eae398db3788e416e9b6e229ce53f9f2413b244497012fd9931 -size 85369 +oid sha256:98e188e6e9f4348988519e1485274ec2cda34c40bc8aa81fe7640897fee04baa +size 84174 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 51c93894de..40243937d3 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:a836c8efd7aa9818cf807cf56412e78399a6568798be23d0f3f6b89552856ff1 -size 62172 +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 c48268b955..5e9fa12332 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:fdf1ebcbf951a82b4899c12c1e24eb28c84694dac52d390b54b6db8a06949ddf -size 33901 +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 c9ad188dc1..2a3e95c169 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:a049a50155bf56c53a1b74e919806cbb83716842b5c0a233c44c87b3630115e0 -size 44394 +oid sha256:05a0c206e13e5ad41410b1ac86720784f13594491b70b15967f761ad1feeb56e +size 44360 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 7cfe595a16..96c66aad72 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:1739c75759f8714526bbb6bff1df02aa1e327f2e22b50e3514903ec1fc672aa7 -size 35332 +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 ff91ab181f..3ff151f6d0 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:7c1d2789714f291746e254815df3001655c4bae2c9c02a08b9a913e87bec5036 -size 34335 +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 987f01d0a3..ddb32d1f1f 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:ec42f78f2ce5d3afc68988646d8ddd1059797c6cebf327997709831470db62c5 -size 44446 +oid sha256:2ebab963708a3083fe01a2bf9774c2035c95d9a713dde333f938755a30f8ee50 +size 44359 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 5860c0eacc..37e5035d86 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:b979180d0e4c4c644bc39d87f17d5d5b7a7dac12cb830622f3db2cd6ef2dfdc0 -size 106317 +oid sha256:3802cfe67638a24869d6cc9ace1d94460b4c0c26f2c91b12b95fa8f979de64bb +size 101579 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 deda6d6db8..6004ca95f0 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:d470b839a21cab2e528d8f00086dc4f289d2e74cfc1f4b37abc1dabb42e6d92b -size 102771 +oid sha256:5e989ba6545170e57cae855bb4f059cf3cf736ca9fab445bb07d5e9ee9cd7169 +size 102178 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 34a6c57902..314a056060 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:4c0fd89358099a05ca78b3656c6d0dc7c670b373021899e9e1bf9d40c2a63000 -size 98386 +oid sha256:2236e81d33fcfb50afb9d5fd1a38c5ddf5d33fbb52de1c3204a4a9892fd334ce +size 99084 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 0f56d5156c..1ba333247c 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:4b3fc79c254c1decea8c947ebe959f256baa18534084f9b37099369870021a6e -size 94870 +oid sha256:d95000bf1d438429069307396b8183c85174c392a903271fb8e3d2a014661dc6 +size 95037 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 2d6226d096..a14c2cb1f6 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:89864a77216b51cc5b9415453ade7f7ec64c1c112546aa47ee6b4b89f9b258a3 -size 60543 +oid sha256:7fb3743098a8147fd24294d933d93a61ec0155d754f52544650f6589719905be +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 7b842e1f78..e40a91cbc4 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:e2982181e3ca61a0e6e5e27a3909d7003dc784d6bf51800159dc274ce058eb49 -size 47214 +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 bf0604356f..84d641f271 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:6937822a02885fc236f5520e947081883d1ccbdb3da04821d0da133e1f98d98e -size 51009 +oid sha256:9b4724b3461a0e76ef44a5932fdce76cb859a7dc4976f4b3f744117f38730c41 +size 50995 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 fd27271129..0d548ca79d 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:96abf20f6b75757da529a661a2567a74527ffe8391615e34ae8f27ceaf381dba -size 67973 +oid sha256:e2f4f4e2237925403fd0228344f9fce9be96c0f26e3465775763aca775779763 +size 68222 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 2bad1f1f25..8d0d2b60db 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:be280b1be360e42b2eb2f8270d900a7f268e75a9ba1828fe0985925a7add6192 -size 64155 +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 f8962d9907..53699affb6 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:d4f9bd6f79db363966fcf99ed12064500efe7a91ec6a87ef8720a7958ef7b06e -size 69758 +oid sha256:b8ef82c4e91baeb157fad3308d04b9407d73310e926d1f7e6f9221ee041f9f21 +size 69431 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 d8a04a055d..7204abff47 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:156319efa8874050ed646bf3a8b7dfd2aa7eef56c9d31fad3c408b418608ff69 -size 111255 +oid sha256:4474b94e2d563938e10ec0526e7d94ba06b440db51b910604e752f7f9e814d66 +size 110757 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 6b11fd3ead..c1056c0ce8 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:31132b4f8d744bd63a1395ac97e2efcad924c564b382de759b504b9d8a977e5b -size 110214 +oid sha256:9f6fdfbcb68e53f9ec33f830ed603c8936fbf51e614e46b81b612d1e8ab3fa1b +size 110005 From 32da5cdd8263566ed7139213032e43fa6868e7b9 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Thu, 27 Mar 2025 23:04:24 +1000 Subject: [PATCH 11/18] Fix build, simplify utilities --- Directory.Build.props | 5 +- src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs | 2 +- src/ImageSharp/Formats/EncodingUtilities.cs | 52 ++++++------------- src/ImageSharp/Formats/Gif/GifEncoderCore.cs | 8 ++- src/ImageSharp/Formats/Png/PngDecoderCore.cs | 4 +- src/ImageSharp/Formats/Png/PngEncoderCore.cs | 4 +- src/ImageSharp/Formats/Qoi/QoiEncoderCore.cs | 4 +- src/ImageSharp/Formats/Tga/TgaEncoderCore.cs | 4 +- .../Formats/Tiff/TiffEncoderCore.cs | 4 +- .../Formats/TransparentColorMode.cs | 2 +- src/ImageSharp/IDeepCloneable.cs | 6 +-- .../Quantization/OctreeQuantizer{TPixel}.cs | 21 ++------ .../Quantization/PaletteQuantizer{TPixel}.cs | 1 + .../Quantization/QuantizerOptions.cs | 43 +++++++++++---- .../Quantization/QuantizerUtilities.cs | 49 ++++++++--------- .../Quantization/WuQuantizer{TPixel}.cs | 13 +---- 16 files changed, 104 insertions(+), 118 deletions(-) diff --git a/Directory.Build.props b/Directory.Build.props index 26b3cc5afc..1191e848ef 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -21,9 +21,8 @@ - - - preview + + 13.0