diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index 7c7791347d..75bcb8a256 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -89,14 +89,14 @@ jobs: - name: DotNet Setup if: ${{ matrix.options.sdk-preview != true }} - uses: actions/setup-dotnet@v3 + uses: actions/setup-dotnet@v4 with: dotnet-version: | 8.0.x - name: DotNet Setup Preview if: ${{ matrix.options.sdk-preview == true }} - uses: actions/setup-dotnet@v3 + uses: actions/setup-dotnet@v4 with: dotnet-version: | 8.0.x diff --git a/.github/workflows/code-coverage.yml b/.github/workflows/code-coverage.yml index 7624e86b67..62b6477ee6 100644 --- a/.github/workflows/code-coverage.yml +++ b/.github/workflows/code-coverage.yml @@ -55,7 +55,7 @@ jobs: restore-keys: ${{ runner.os }}-nuget- - name: DotNet Setup - uses: actions/setup-dotnet@v3 + uses: actions/setup-dotnet@v4 with: dotnet-version: | 8.0.x diff --git a/src/ImageSharp/Formats/Gif/GifEncoderCore.cs b/src/ImageSharp/Formats/Gif/GifEncoderCore.cs index 9988848d11..1215768e4b 100644 --- a/src/ImageSharp/Formats/Gif/GifEncoderCore.cs +++ b/src/ImageSharp/Formats/Gif/GifEncoderCore.cs @@ -103,7 +103,14 @@ internal sealed class GifEncoderCore : IImageEncoderInternals { // We avoid dithering by default to preserve the original colors. int transparencyIndex = GetTransparentIndex(quantized, frameMetadata); - this.quantizer = new PaletteQuantizer(gifMetadata.GlobalColorTable.Value, new() { Dither = null }, transparencyIndex); + if (transparencyIndex >= 0 || gifMetadata.GlobalColorTable.Value.Length < 256) + { + this.quantizer = new PaletteQuantizer(gifMetadata.GlobalColorTable.Value, new() { Dither = null }, transparencyIndex); + } + else + { + this.quantizer = KnownQuantizers.Octree; + } } else { @@ -198,19 +205,17 @@ internal sealed class GifEncoderCore : IImageEncoderInternals private static GifFrameMetadata GetGifFrameMetadata(ImageFrame frame, int transparencyIndex) where TPixel : unmanaged, IPixel { + GifFrameMetadata? metadata = null; if (frame.Metadata.TryGetGifMetadata(out GifFrameMetadata? gif)) { - return (GifFrameMetadata)gif.DeepClone(); + metadata = (GifFrameMetadata)gif.DeepClone(); } - - GifFrameMetadata? metadata = null; - if (frame.Metadata.TryGetPngMetadata(out PngFrameMetadata? png)) + else if (frame.Metadata.TryGetPngMetadata(out PngFrameMetadata? png)) { AnimatedImageFrameMetadata ani = png.ToAnimatedImageFrameMetadata(); metadata = GifFrameMetadata.FromAnimatedMetadata(ani); } - - if (frame.Metadata.TryGetWebpFrameMetadata(out WebpFrameMetadata? webp)) + else if (frame.Metadata.TryGetWebpFrameMetadata(out WebpFrameMetadata? webp)) { AnimatedImageFrameMetadata ani = webp.ToAnimatedImageFrameMetadata(); metadata = GifFrameMetadata.FromAnimatedMetadata(ani); diff --git a/src/ImageSharp/Formats/Gif/MetadataExtensions.cs b/src/ImageSharp/Formats/Gif/MetadataExtensions.cs index 42602f2c78..ad06462e77 100644 --- a/src/ImageSharp/Formats/Gif/MetadataExtensions.cs +++ b/src/ImageSharp/Formats/Gif/MetadataExtensions.cs @@ -82,6 +82,9 @@ public static partial class MetadataExtensions // has a local palette with 256 colors and is not transparent we should use 'Source'. bool blendSource = source.DisposalMethod == GifDisposalMethod.RestoreToBackground || (source.LocalColorTable?.Length == 256 && !source.HasTransparency); + // If the color table is global and frame has no transparency. Consider it 'Source' also. + blendSource |= source.ColorTableMode == GifColorTableMode.Global && !source.HasTransparency; + return new() { ColorTable = source.LocalColorTable, diff --git a/tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs index c81b7eb6cd..e70854b082 100644 --- a/tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs @@ -499,7 +499,7 @@ public partial class PngEncoderTests // TODO: Find a better way to compare. // The image has been visually checked but the quantization pattern used in the png encoder // means we cannot use an exact comparison nor replicate using the quantizing processor. - ImageComparer.TolerantPercentage(0.46f).VerifySimilarity(output, image); + ImageComparer.TolerantPercentage(0.613f).VerifySimilarity(output, image); GifMetadata gif = image.Metadata.GetGifMetadata(); PngMetadata png = output.Metadata.GetPngMetadata(); diff --git a/tests/ImageSharp.Tests/TestImages.cs b/tests/ImageSharp.Tests/TestImages.cs index b628529028..8aa95d3496 100644 --- a/tests/ImageSharp.Tests/TestImages.cs +++ b/tests/ImageSharp.Tests/TestImages.cs @@ -488,6 +488,7 @@ public static class TestImages public const string MixedDisposal = "Gif/mixed-disposal.gif"; public const string M4nb = "Gif/m4nb.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/robert-ancell/pygif/tree/master/test-suite public const string ZeroSize = "Gif/image-zero-size.gif"; @@ -535,7 +536,8 @@ public static class TestImages Issues.Issue2450_B, Issues.BadDescriptorWidth, Issues.Issue1530, - Bit18RGBCube + Bit18RGBCube, + Global256NoTrans }; } diff --git a/tests/Images/Input/Gif/global-256-no-trans.gif b/tests/Images/Input/Gif/global-256-no-trans.gif new file mode 100644 index 0000000000..1afa0d21d7 --- /dev/null +++ b/tests/Images/Input/Gif/global-256-no-trans.gif @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ce8ed23b4e21328886f5aa7579079123ff6401efdf65e162e565b056ffddab56 +size 669286