diff --git a/src/ImageSharp/Formats/Bmp/BmpMetadata.cs b/src/ImageSharp/Formats/Bmp/BmpMetadata.cs index 2f03b9c6f3..00c5910d4b 100644 --- a/src/ImageSharp/Formats/Bmp/BmpMetadata.cs +++ b/src/ImageSharp/Formats/Bmp/BmpMetadata.cs @@ -138,8 +138,8 @@ public class BmpMetadata : IFormatMetadata, IFormatFrameMetadata new(); /// - public IDeepCloneable DeepClone() => ((IDeepCloneable)this).DeepClone(); + IDeepCloneable IDeepCloneable.DeepClone() => this.DeepClone(); /// - BmpMetadata IDeepCloneable.DeepClone() => new(this); + public BmpMetadata DeepClone() => new(this); } diff --git a/src/ImageSharp/Formats/Gif/GifDecoderCore.cs b/src/ImageSharp/Formats/Gif/GifDecoderCore.cs index a67ee8f982..e0fe4973dd 100644 --- a/src/ImageSharp/Formats/Gif/GifDecoderCore.cs +++ b/src/ImageSharp/Formats/Gif/GifDecoderCore.cs @@ -517,7 +517,7 @@ internal sealed class GifDecoderCore : IImageDecoderInternals } else { - if (this.graphicsControlExtension.DisposalMethod == GifDisposalMethod.RestoreToPrevious) + if (this.graphicsControlExtension.DisposalMethod == FrameDisposalMode.RestoreToPrevious) { prevFrame = previousFrame; } @@ -624,7 +624,7 @@ internal sealed class GifDecoderCore : IImageDecoderInternals previousFrame = currentFrame ?? image.Frames.RootFrame; - if (this.graphicsControlExtension.DisposalMethod == GifDisposalMethod.RestoreToBackground) + if (this.graphicsControlExtension.DisposalMethod == FrameDisposalMode.RestoreToBackground) { this.restoreArea = new Rectangle(descriptor.Left, descriptor.Top, descriptor.Width, descriptor.Height); } diff --git a/src/ImageSharp/Formats/Gif/GifDisposalMethod.cs b/src/ImageSharp/Formats/Gif/GifDisposalMethod.cs deleted file mode 100644 index 12b4239c4f..0000000000 --- a/src/ImageSharp/Formats/Gif/GifDisposalMethod.cs +++ /dev/null @@ -1,37 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Formats.Gif; - -/// -/// Provides enumeration for instructing the decoder what to do with the last image -/// in an animation sequence. -/// section 23 -/// -public enum GifDisposalMethod -{ - /// - /// No disposal specified. - /// The decoder is not required to take any action. - /// - Unspecified = 0, - - /// - /// Do not dispose. - /// The graphic is to be left in place. - /// - NotDispose = 1, - - /// - /// Restore to background color. - /// The area used by the graphic must be restored to the background color. - /// - RestoreToBackground = 2, - - /// - /// Restore to previous. - /// The decoder is required to restore the area overwritten by the - /// graphic with what was there prior to rendering the graphic. - /// - RestoreToPrevious = 3 -} diff --git a/src/ImageSharp/Formats/Gif/GifEncoderCore.cs b/src/ImageSharp/Formats/Gif/GifEncoderCore.cs index 70afe12e10..f4cc166fec 100644 --- a/src/ImageSharp/Formats/Gif/GifEncoderCore.cs +++ b/src/ImageSharp/Formats/Gif/GifEncoderCore.cs @@ -235,7 +235,7 @@ internal sealed class GifEncoderCore : IImageEncoderInternals Image image, ReadOnlyMemory globalPalette, int globalTransparencyIndex, - GifDisposalMethod previousDisposalMethod) + FrameDisposalMode previousDisposalMode) where TPixel : unmanaged, IPixel { if (image.Frames.Count == 1) @@ -279,10 +279,10 @@ internal sealed class GifEncoderCore : IImageEncoderInternals useLocal, gifMetadata, paletteQuantizer, - previousDisposalMethod); + previousDisposalMode); previousFrame = currentFrame; - previousDisposalMethod = gifMetadata.DisposalMethod; + previousDisposalMode = gifMetadata.DisposalMethod; } if (hasPaletteQuantizer) @@ -323,14 +323,14 @@ internal sealed class GifEncoderCore : IImageEncoderInternals bool useLocal, GifFrameMetadata metadata, PaletteQuantizer globalPaletteQuantizer, - GifDisposalMethod previousDisposal) + FrameDisposalMode previousDisposalMode) where TPixel : unmanaged, IPixel { // Capture any explicit transparency index from the metadata. // We use it to determine the value to use to replace duplicate pixels. int transparencyIndex = metadata.HasTransparency ? metadata.TransparencyIndex : -1; - ImageFrame? previous = previousDisposal == GifDisposalMethod.RestoreToBackground ? null : previousFrame; + ImageFrame? previous = previousDisposalMode == FrameDisposalMode.RestoreToBackground ? null : previousFrame; // Deduplicate and quantize the frame capturing only required parts. (bool difference, Rectangle bounds) = @@ -664,7 +664,7 @@ internal sealed class GifEncoderCore : IImageEncoderInternals bool hasTransparency = metadata.HasTransparency; byte packedValue = GifGraphicControlExtension.GetPackedValue( - disposalMethod: metadata.DisposalMethod, + disposalMode: metadata.DisposalMethod, transparencyFlag: hasTransparency); GifGraphicControlExtension extension = new( diff --git a/src/ImageSharp/Formats/Gif/GifFrameMetadata.cs b/src/ImageSharp/Formats/Gif/GifFrameMetadata.cs index 3f8563706d..46bc415ce1 100644 --- a/src/ImageSharp/Formats/Gif/GifFrameMetadata.cs +++ b/src/ImageSharp/Formats/Gif/GifFrameMetadata.cs @@ -9,7 +9,7 @@ namespace SixLabors.ImageSharp.Formats.Gif; /// /// Provides Gif specific metadata information for the image frame. /// -public class GifFrameMetadata : IDeepCloneable +public class GifFrameMetadata : IFormatFrameMetadata { /// /// Initializes a new instance of the class. @@ -73,10 +73,65 @@ public class GifFrameMetadata : IDeepCloneable /// Primarily used in Gif animation, this field indicates the way in which the graphic is to /// be treated after being displayed. /// - public GifDisposalMethod DisposalMethod { get; set; } + public FrameDisposalMode DisposalMethod { get; set; } + + /// + public static GifFrameMetadata FromFormatConnectingFrameMetadata(FormatConnectingFrameMetadata metadata) + { + int index = -1; + const float background = 1f; + if (metadata.ColorTable.HasValue) + { + ReadOnlySpan colorTable = metadata.ColorTable.Value.Span; + for (int i = 0; i < colorTable.Length; i++) + { + Vector4 vector = colorTable[i].ToScaledVector4(); + if (vector.W < background) + { + index = i; + } + } + } + + bool hasTransparency = index >= 0; + + return new() + { + LocalColorTable = metadata.ColorTable, + ColorTableMode = metadata.ColorTableMode, + FrameDelay = (int)Math.Round(metadata.Duration.TotalMilliseconds / 10), + DisposalMethod = metadata.DisposalMode, + HasTransparency = hasTransparency, + TransparencyIndex = hasTransparency ? unchecked((byte)index) : byte.MinValue, + }; + } + + /// + public FormatConnectingFrameMetadata ToFormatConnectingFrameMetadata() + { + // throw new NotImplementedException(); + // For most scenarios we would consider the blend method to be 'Over' however if a frame has a disposal method of 'RestoreToBackground' or + // has a local palette with 256 colors and is not transparent we should use 'Source'. + bool blendSource = this.DisposalMethod == FrameDisposalMode.RestoreToBackground || (this.LocalColorTable?.Length == 256 && !this.HasTransparency); + + // If the color table is global and frame has no transparency. Consider it 'Source' also. + blendSource |= this.ColorTableMode == FrameColorTableMode.Global && !this.HasTransparency; + + return new() + { + ColorTable = this.LocalColorTable, + ColorTableMode = this.ColorTableMode, + Duration = TimeSpan.FromMilliseconds(this.FrameDelay * 10), + DisposalMode = this.DisposalMethod, + BlendMode = blendSource ? FrameBlendMode.Source : FrameBlendMode.Over, + }; + } + + /// + IDeepCloneable IDeepCloneable.DeepClone() => this.DeepClone(); /// - public IDeepCloneable DeepClone() => new GifFrameMetadata(this); + public GifFrameMetadata DeepClone() => new(this); internal static GifFrameMetadata FromAnimatedMetadata(AnimatedImageFrameMetadata metadata) { @@ -103,17 +158,9 @@ public class GifFrameMetadata : IDeepCloneable LocalColorTable = metadata.ColorTable, ColorTableMode = metadata.ColorTableMode, FrameDelay = (int)Math.Round(metadata.Duration.TotalMilliseconds / 10), - DisposalMethod = GetMode(metadata.DisposalMode), + DisposalMethod = metadata.DisposalMode, HasTransparency = hasTransparency, TransparencyIndex = hasTransparency ? unchecked((byte)index) : byte.MinValue, }; } - - private static GifDisposalMethod GetMode(FrameDisposalMode mode) => mode switch - { - FrameDisposalMode.DoNotDispose => GifDisposalMethod.NotDispose, - FrameDisposalMode.RestoreToBackground => GifDisposalMethod.RestoreToBackground, - FrameDisposalMode.RestoreToPrevious => GifDisposalMethod.RestoreToPrevious, - _ => GifDisposalMethod.Unspecified, - }; } diff --git a/src/ImageSharp/Formats/Gif/GifMetadata.cs b/src/ImageSharp/Formats/Gif/GifMetadata.cs index 43935504b1..4ebe046ba3 100644 --- a/src/ImageSharp/Formats/Gif/GifMetadata.cs +++ b/src/ImageSharp/Formats/Gif/GifMetadata.cs @@ -145,8 +145,8 @@ public class GifMetadata : IFormatMetadata } /// - public IDeepCloneable DeepClone() => ((IDeepCloneable)this).DeepClone(); + IDeepCloneable IDeepCloneable.DeepClone() => this.DeepClone(); /// - GifMetadata IDeepCloneable.DeepClone() => new(this); + public GifMetadata DeepClone() => new(this); } diff --git a/src/ImageSharp/Formats/Gif/MetadataExtensions.cs b/src/ImageSharp/Formats/Gif/MetadataExtensions.cs index d4650403cb..5fd2d5c1e9 100644 --- a/src/ImageSharp/Formats/Gif/MetadataExtensions.cs +++ b/src/ImageSharp/Formats/Gif/MetadataExtensions.cs @@ -80,7 +80,7 @@ public static partial class MetadataExtensions { // For most scenarios we would consider the blend method to be 'Over' however if a frame has a disposal method of 'RestoreToBackground' or // 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); + bool blendSource = source.DisposalMethod == FrameDisposalMode.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 == FrameColorTableMode.Global && !source.HasTransparency; @@ -90,16 +90,8 @@ public static partial class MetadataExtensions ColorTable = source.LocalColorTable, ColorTableMode = source.ColorTableMode, Duration = TimeSpan.FromMilliseconds(source.FrameDelay * 10), - DisposalMode = GetMode(source.DisposalMethod), + DisposalMode = source.DisposalMethod, BlendMode = blendSource ? FrameBlendMode.Source : FrameBlendMode.Over, }; } - - private static FrameDisposalMode GetMode(GifDisposalMethod method) => method switch - { - GifDisposalMethod.NotDispose => FrameDisposalMode.DoNotDispose, - GifDisposalMethod.RestoreToBackground => FrameDisposalMode.RestoreToBackground, - GifDisposalMethod.RestoreToPrevious => FrameDisposalMode.RestoreToPrevious, - _ => FrameDisposalMode.Unspecified, - }; } diff --git a/src/ImageSharp/Formats/Gif/Sections/GifGraphicControlExtension.cs b/src/ImageSharp/Formats/Gif/Sections/GifGraphicControlExtension.cs index 52052021fc..ad99ac0f42 100644 --- a/src/ImageSharp/Formats/Gif/Sections/GifGraphicControlExtension.cs +++ b/src/ImageSharp/Formats/Gif/Sections/GifGraphicControlExtension.cs @@ -52,7 +52,7 @@ internal readonly struct GifGraphicControlExtension : IGifExtension, IEquatable< /// Gets the disposal method which indicates the way in which the /// graphic is to be treated after being displayed. /// - public GifDisposalMethod DisposalMethod => (GifDisposalMethod)((this.Packed & 0x1C) >> 2); + public FrameDisposalMode DisposalMethod => (FrameDisposalMode)((this.Packed & 0x1C) >> 2); /// /// Gets a value indicating whether transparency flag is to be set. @@ -80,7 +80,7 @@ internal readonly struct GifGraphicControlExtension : IGifExtension, IEquatable< public static GifGraphicControlExtension Parse(ReadOnlySpan buffer) => MemoryMarshal.Cast(buffer)[0]; - public static byte GetPackedValue(GifDisposalMethod disposalMethod, bool userInputFlag = false, bool transparencyFlag = false) + public static byte GetPackedValue(FrameDisposalMode disposalMode, bool userInputFlag = false, bool transparencyFlag = false) { /* Reserved | 3 Bits @@ -91,7 +91,7 @@ internal readonly struct GifGraphicControlExtension : IGifExtension, IEquatable< byte value = 0; - value |= (byte)((int)disposalMethod << 2); + value |= (byte)((int)disposalMode << 2); if (userInputFlag) { diff --git a/tests/ImageSharp.Tests/Formats/Gif/GifEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Gif/GifEncoderTests.cs index 767141f56a..0b6615b8ea 100644 --- a/tests/ImageSharp.Tests/Formats/Gif/GifEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Gif/GifEncoderTests.cs @@ -309,11 +309,11 @@ public class GifEncoderTests switch (pngF.DisposalMethod) { case PngDisposalMethod.RestoreToBackground: - Assert.Equal(GifDisposalMethod.RestoreToBackground, gifF.DisposalMethod); + Assert.Equal(FrameDisposalMode.RestoreToBackground, gifF.DisposalMethod); break; case PngDisposalMethod.DoNotDispose: default: - Assert.Equal(GifDisposalMethod.NotDispose, gifF.DisposalMethod); + Assert.Equal(FrameDisposalMode.DoNotDispose, gifF.DisposalMethod); break; } } @@ -359,11 +359,11 @@ public class GifEncoderTests switch (webpF.DisposalMethod) { case WebpDisposalMethod.RestoreToBackground: - Assert.Equal(GifDisposalMethod.RestoreToBackground, gifF.DisposalMethod); + Assert.Equal(FrameDisposalMode.RestoreToBackground, gifF.DisposalMethod); break; case WebpDisposalMethod.DoNotDispose: default: - Assert.Equal(GifDisposalMethod.NotDispose, gifF.DisposalMethod); + Assert.Equal(FrameDisposalMode.DoNotDispose, gifF.DisposalMethod); break; } } diff --git a/tests/ImageSharp.Tests/Formats/Gif/GifFrameMetadataTests.cs b/tests/ImageSharp.Tests/Formats/Gif/GifFrameMetadataTests.cs index 774638311d..f92896b7ec 100644 --- a/tests/ImageSharp.Tests/Formats/Gif/GifFrameMetadataTests.cs +++ b/tests/ImageSharp.Tests/Formats/Gif/GifFrameMetadataTests.cs @@ -1,6 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. +using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Formats.Gif; namespace SixLabors.ImageSharp.Tests.Formats.Gif; @@ -14,14 +15,14 @@ public class GifFrameMetadataTests GifFrameMetadata meta = new() { FrameDelay = 1, - DisposalMethod = GifDisposalMethod.RestoreToBackground, + DisposalMethod = FrameDisposalMode.RestoreToBackground, LocalColorTable = new[] { Color.Black, Color.White } }; GifFrameMetadata clone = (GifFrameMetadata)meta.DeepClone(); clone.FrameDelay = 2; - clone.DisposalMethod = GifDisposalMethod.RestoreToPrevious; + clone.DisposalMethod = FrameDisposalMode.RestoreToPrevious; clone.LocalColorTable = new[] { Color.Black }; Assert.False(meta.FrameDelay.Equals(clone.FrameDelay)); diff --git a/tests/ImageSharp.Tests/Formats/Gif/GifMetadataTests.cs b/tests/ImageSharp.Tests/Formats/Gif/GifMetadataTests.cs index c7babe9322..afaa827bfb 100644 --- a/tests/ImageSharp.Tests/Formats/Gif/GifMetadataTests.cs +++ b/tests/ImageSharp.Tests/Formats/Gif/GifMetadataTests.cs @@ -183,14 +183,14 @@ public class GifMetadataTests } [Theory] - [InlineData(TestImages.Gif.Cheers, 93, FrameColorTableMode.Global, 256, 4, GifDisposalMethod.NotDispose)] + [InlineData(TestImages.Gif.Cheers, 93, FrameColorTableMode.Global, 256, 4, FrameDisposalMode.DoNotDispose)] public void Identify_Frames( string imagePath, int framesCount, FrameColorTableMode colorTableMode, int globalColorTableLength, int frameDelay, - GifDisposalMethod disposalMethod) + FrameDisposalMode disposalMethod) { TestFile testFile = TestFile.Create(imagePath); using MemoryStream stream = new(testFile.Bytes, false); diff --git a/tests/ImageSharp.Tests/Formats/Gif/Sections/GifGraphicControlExtensionTests.cs b/tests/ImageSharp.Tests/Formats/Gif/Sections/GifGraphicControlExtensionTests.cs index c602bc91bb..05dc5bc52a 100644 --- a/tests/ImageSharp.Tests/Formats/Gif/Sections/GifGraphicControlExtensionTests.cs +++ b/tests/ImageSharp.Tests/Formats/Gif/Sections/GifGraphicControlExtensionTests.cs @@ -1,6 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. +using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Formats.Gif; namespace SixLabors.ImageSharp.Tests.Formats.Gif.Sections; @@ -10,9 +11,9 @@ public class GifGraphicControlExtensionTests [Fact] public void TestPackedValue() { - Assert.Equal(0, GifGraphicControlExtension.GetPackedValue(GifDisposalMethod.Unspecified, false, false)); - Assert.Equal(11, GifGraphicControlExtension.GetPackedValue(GifDisposalMethod.RestoreToBackground, true, true)); - Assert.Equal(4, GifGraphicControlExtension.GetPackedValue(GifDisposalMethod.NotDispose, false, false)); - Assert.Equal(14, GifGraphicControlExtension.GetPackedValue(GifDisposalMethod.RestoreToPrevious, true, false)); + Assert.Equal(0, GifGraphicControlExtension.GetPackedValue(FrameDisposalMode.Unspecified, false, false)); + Assert.Equal(11, GifGraphicControlExtension.GetPackedValue(FrameDisposalMode.RestoreToBackground, true, true)); + Assert.Equal(4, GifGraphicControlExtension.GetPackedValue(FrameDisposalMode.DoNotDispose, false, false)); + Assert.Equal(14, GifGraphicControlExtension.GetPackedValue(FrameDisposalMode.RestoreToPrevious, true, false)); } } diff --git a/tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs index 35c446c704..1e4472e8a2 100644 --- a/tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs @@ -521,14 +521,14 @@ public partial class PngEncoderTests switch (gifF.DisposalMethod) { - case GifDisposalMethod.RestoreToBackground: + case FrameDisposalMode.RestoreToBackground: Assert.Equal(PngDisposalMethod.RestoreToBackground, pngF.DisposalMethod); break; - case GifDisposalMethod.RestoreToPrevious: + case FrameDisposalMode.RestoreToPrevious: Assert.Equal(PngDisposalMethod.RestoreToPrevious, pngF.DisposalMethod); break; - case GifDisposalMethod.Unspecified: - case GifDisposalMethod.NotDispose: + case FrameDisposalMode.Unspecified: + case FrameDisposalMode.DoNotDispose: default: Assert.Equal(PngDisposalMethod.DoNotDispose, pngF.DisposalMethod); break; diff --git a/tests/ImageSharp.Tests/Formats/WebP/WebpEncoderTests.cs b/tests/ImageSharp.Tests/Formats/WebP/WebpEncoderTests.cs index acca49dcf4..49c47adfd4 100644 --- a/tests/ImageSharp.Tests/Formats/WebP/WebpEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/WebP/WebpEncoderTests.cs @@ -2,6 +2,7 @@ // Licensed under the Six Labors Split License. using System.Runtime.InteropServices; +using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Formats.Gif; using SixLabors.ImageSharp.Formats.Png; using SixLabors.ImageSharp.Formats.Webp; @@ -96,12 +97,12 @@ public class WebpEncoderTests switch (gifF.DisposalMethod) { - case GifDisposalMethod.RestoreToBackground: + case FrameDisposalMode.RestoreToBackground: Assert.Equal(WebpDisposalMethod.RestoreToBackground, webpF.DisposalMethod); break; - case GifDisposalMethod.RestoreToPrevious: - case GifDisposalMethod.Unspecified: - case GifDisposalMethod.NotDispose: + case FrameDisposalMode.RestoreToPrevious: + case FrameDisposalMode.Unspecified: + case FrameDisposalMode.DoNotDispose: default: Assert.Equal(WebpDisposalMethod.DoNotDispose, webpF.DisposalMethod); break; diff --git a/tests/ImageSharp.Tests/Metadata/ImageFrameMetadataTests.cs b/tests/ImageSharp.Tests/Metadata/ImageFrameMetadataTests.cs index bcc9675404..9cb7137fe0 100644 --- a/tests/ImageSharp.Tests/Metadata/ImageFrameMetadataTests.cs +++ b/tests/ImageSharp.Tests/Metadata/ImageFrameMetadataTests.cs @@ -1,6 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. +using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Formats.Gif; using SixLabors.ImageSharp.Metadata; using SixLabors.ImageSharp.Metadata.Profiles.Icc; @@ -21,7 +22,7 @@ public class ImageFrameMetadataTests { const int frameDelay = 42; const int colorTableLength = 128; - const GifDisposalMethod disposalMethod = GifDisposalMethod.RestoreToBackground; + const FrameDisposalMode disposalMethod = FrameDisposalMode.RestoreToBackground; ImageFrameMetadata metaData = new(); GifFrameMetadata gifFrameMetadata = metaData.GetGifMetadata();