// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System; using System.Collections.Generic; using System.IO; using Microsoft.DotNet.RemoteExecutor; using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Formats.Gif; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Metadata; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Tests.TestUtilities; using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; using Xunit; // ReSharper disable InconsistentNaming namespace SixLabors.ImageSharp.Tests.Formats.Gif { public class GifDecoderTests { private const PixelTypes TestPixelTypes = PixelTypes.Rgba32 | PixelTypes.RgbaVector | PixelTypes.Argb32; private static GifDecoder GifDecoder => new GifDecoder(); public static readonly string[] MultiFrameTestFiles = { TestImages.Gif.Giphy, TestImages.Gif.Kumin }; public static readonly string[] BasicVerificationFiles = { TestImages.Gif.Cheers, TestImages.Gif.Rings, // previously DecodeBadApplicationExtensionLength: TestImages.Gif.Issues.BadAppExtLength, TestImages.Gif.Issues.BadAppExtLength_2, // previously DecodeBadDescriptorDimensionsLength: TestImages.Gif.Issues.BadDescriptorWidth }; private static readonly Dictionary BasicVerificationFrameCount = new Dictionary { [TestImages.Gif.Cheers] = 93, [TestImages.Gif.Issues.BadDescriptorWidth] = 36, }; public static readonly string[] BadAppExtFiles = { TestImages.Gif.Issues.BadAppExtLength, TestImages.Gif.Issues.BadAppExtLength_2 }; [Theory] [WithFileCollection(nameof(MultiFrameTestFiles), PixelTypes.Rgba32)] public void Decode_VerifyAllFrames(TestImageProvider provider) where TPixel : unmanaged, IPixel { using (Image image = provider.GetImage()) { image.DebugSaveMultiFrame(provider); image.CompareToReferenceOutputMultiFrame(provider, ImageComparer.Exact); } } [Fact] public unsafe void Decode_NonTerminatedFinalFrame() { var testFile = TestFile.Create(TestImages.Gif.Rings); int length = testFile.Bytes.Length - 2; fixed (byte* data = testFile.Bytes.AsSpan(0, length)) { using (var stream = new UnmanagedMemoryStream(data, length)) { using (Image image = GifDecoder.Decode(Configuration.Default, stream)) { Assert.Equal((200, 200), (image.Width, image.Height)); } } } } [Theory] [WithFile(TestImages.Gif.Trans, TestPixelTypes)] public void GifDecoder_IsNotBoundToSinglePixelType(TestImageProvider provider) where TPixel : unmanaged, IPixel { using (Image image = provider.GetImage()) { image.DebugSave(provider); image.CompareFirstFrameToReferenceOutput(ImageComparer.Exact, provider); } } [Theory] [WithFileCollection(nameof(BasicVerificationFiles), PixelTypes.Rgba32)] public void Decode_VerifyRootFrameAndFrameCount(TestImageProvider provider) where TPixel : unmanaged, IPixel { if (!BasicVerificationFrameCount.TryGetValue(provider.SourceFileOrDescription, out int expectedFrameCount)) { expectedFrameCount = 1; } using (Image image = provider.GetImage()) { Assert.Equal(expectedFrameCount, image.Frames.Count); image.DebugSave(provider); image.CompareFirstFrameToReferenceOutput(ImageComparer.Exact, provider); } } [Theory] [WithFile(TestImages.Gif.Giphy, PixelTypes.Rgba32)] public void CanDecodeJustOneFrame(TestImageProvider provider) where TPixel : unmanaged, IPixel { using (Image image = provider.GetImage(new GifDecoder { DecodingMode = FrameDecodingMode.First })) { Assert.Equal(1, image.Frames.Count); } } [Theory] [WithFile(TestImages.Gif.Giphy, PixelTypes.Rgba32)] public void CanDecodeAllFrames(TestImageProvider provider) where TPixel : unmanaged, IPixel { using (Image image = provider.GetImage(new GifDecoder { DecodingMode = FrameDecodingMode.All })) { Assert.True(image.Frames.Count > 1); } } [Theory] [InlineData(TestImages.Gif.Cheers, 8)] [InlineData(TestImages.Gif.Giphy, 8)] [InlineData(TestImages.Gif.Rings, 8)] [InlineData(TestImages.Gif.Trans, 8)] public void DetectPixelSize(string imagePath, int expectedPixelSize) { var testFile = TestFile.Create(imagePath); using (var stream = new MemoryStream(testFile.Bytes, false)) { Assert.Equal(expectedPixelSize, Image.Identify(stream)?.PixelType?.BitsPerPixel); } } [Fact] public void CanDecodeIntermingledImages() { using (var kumin1 = Image.Load(TestFile.Create(TestImages.Gif.Kumin).Bytes)) using (Image.Load(TestFile.Create(TestImages.Png.Icon).Bytes)) using (var kumin2 = Image.Load(TestFile.Create(TestImages.Gif.Kumin).Bytes)) { for (int i = 0; i < kumin1.Frames.Count; i++) { ImageFrame first = kumin1.Frames[i]; ImageFrame second = kumin2.Frames[i]; Assert.True(second.TryGetSinglePixelSpan(out Span secondSpan)); first.ComparePixelBufferTo(secondSpan); } } } [Theory] [WithFile(TestImages.Gif.Giphy, PixelTypes.Rgba32)] [WithFile(TestImages.Gif.Kumin, PixelTypes.Rgba32)] public void GifDecoder_DegenerateMemoryRequest_ShouldTranslateTo_ImageFormatException(TestImageProvider provider) where TPixel : unmanaged, IPixel { provider.LimitAllocatorBufferCapacity().InPixelsSqrt(10); ImageFormatException ex = Assert.Throws(() => provider.GetImage(GifDecoder)); Assert.IsType(ex.InnerException); } [Theory] [WithFile(TestImages.Gif.Giphy, PixelTypes.Rgba32)] [WithFile(TestImages.Gif.Kumin, PixelTypes.Rgba32)] public void GifDecoder_CanDecode_WithLimitedAllocatorBufferCapacity(TestImageProvider provider) where TPixel : unmanaged, IPixel { static void RunTest(string providerDump, string nonContiguousBuffersStr) { TestImageProvider provider = BasicSerializer.Deserialize>(providerDump); provider.LimitAllocatorBufferCapacity().InPixelsSqrt(100); using Image image = provider.GetImage(GifDecoder); image.DebugSave(provider); image.CompareToOriginal(provider); } string providerDump = BasicSerializer.Serialize(provider); RemoteExecutor.Invoke( RunTest, providerDump, "Disco") .Dispose(); } } }