From 2127b46a04a8147c9eb02ff362f3617466265891 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Fri, 24 May 2024 21:02:39 +1000 Subject: [PATCH] Implement PngFrameMetadata --- src/ImageSharp/Formats/Gif/GifDecoderCore.cs | 2 +- src/ImageSharp/Formats/Gif/GifEncoderCore.cs | 10 +- .../Formats/Gif/GifFrameMetadata.cs | 13 +- src/ImageSharp/Formats/Gif/GifMetadata.cs | 8 +- .../Formats/Gif/MetadataExtensions.cs | 4 +- src/ImageSharp/Formats/IImageFormat.cs | 6 +- src/ImageSharp/Formats/Pbm/PbmMetadata.cs | 97 ++++++++++- .../Formats/Png/Chunks/FrameControl.cs | 20 +-- .../Formats/Png/MetadataExtensions.cs | 14 +- src/ImageSharp/Formats/Png/PngBlendMethod.cs | 22 --- src/ImageSharp/Formats/Png/PngDecoderCore.cs | 41 +++-- .../Formats/Png/PngDisposalMethod.cs | 25 --- src/ImageSharp/Formats/Png/PngEncoderCore.cs | 17 +- .../Formats/Png/PngFrameMetadata.cs | 64 +++++-- src/ImageSharp/Formats/Png/PngMetadata.cs | 163 +++++++++++++++++- src/ImageSharp/Metadata/ImageMetadata.cs | 2 +- src/ImageSharp/PixelFormats/PixelColorType.cs | 31 ++-- .../Formats/Gif/GifEncoderTests.cs | 14 +- .../Formats/Gif/GifFrameMetadataTests.cs | 6 +- .../Formats/Gif/GifMetadataTests.cs | 2 +- .../Formats/Png/PngEncoderTests.cs | 20 +-- .../Formats/Png/PngFrameMetadataTests.cs | 19 +- .../Formats/WebP/WebpEncoderTests.cs | 14 +- .../ImageFrameCollectionTests.NonGeneric.cs | 2 +- .../Metadata/ImageFrameMetadataTests.cs | 4 +- .../PixelFormats/PixelColorTypeTests.cs | 39 +++++ 26 files changed, 475 insertions(+), 184 deletions(-) delete mode 100644 src/ImageSharp/Formats/Png/PngBlendMethod.cs delete mode 100644 src/ImageSharp/Formats/Png/PngDisposalMethod.cs diff --git a/src/ImageSharp/Formats/Gif/GifDecoderCore.cs b/src/ImageSharp/Formats/Gif/GifDecoderCore.cs index e0fe4973dd..e110acc30d 100644 --- a/src/ImageSharp/Formats/Gif/GifDecoderCore.cs +++ b/src/ImageSharp/Formats/Gif/GifDecoderCore.cs @@ -719,7 +719,7 @@ internal sealed class GifDecoderCore : IImageDecoderInternals gifMeta.HasTransparency = this.graphicsControlExtension.TransparencyFlag; gifMeta.TransparencyIndex = this.graphicsControlExtension.TransparencyIndex; gifMeta.FrameDelay = this.graphicsControlExtension.DelayTime; - gifMeta.DisposalMethod = this.graphicsControlExtension.DisposalMethod; + gifMeta.DisposalMode = this.graphicsControlExtension.DisposalMethod; } } diff --git a/src/ImageSharp/Formats/Gif/GifEncoderCore.cs b/src/ImageSharp/Formats/Gif/GifEncoderCore.cs index f4cc166fec..5fd20d6ae2 100644 --- a/src/ImageSharp/Formats/Gif/GifEncoderCore.cs +++ b/src/ImageSharp/Formats/Gif/GifEncoderCore.cs @@ -171,7 +171,7 @@ internal sealed class GifEncoderCore : IImageEncoderInternals // Capture the global palette for reuse on subsequent frames and cleanup the quantized frame. TPixel[] globalPalette = image.Frames.Count == 1 ? [] : quantized.Palette.ToArray(); - this.EncodeAdditionalFrames(stream, image, globalPalette, derivedTransparencyIndex, frameMetadata.DisposalMethod); + this.EncodeAdditionalFrames(stream, image, globalPalette, derivedTransparencyIndex, frameMetadata.DisposalMode); stream.WriteByte(GifConstants.EndIntroducer); @@ -183,7 +183,7 @@ internal sealed class GifEncoderCore : IImageEncoderInternals { if (image.Metadata.TryGetGifMetadata(out GifMetadata? gif)) { - return (GifMetadata)gif.DeepClone(); + return gif.DeepClone(); } if (image.Metadata.TryGetPngMetadata(out PngMetadata? png)) @@ -208,7 +208,7 @@ internal sealed class GifEncoderCore : IImageEncoderInternals GifFrameMetadata? metadata = null; if (frame.Metadata.TryGetGifMetadata(out GifFrameMetadata? gif)) { - metadata = (GifFrameMetadata)gif.DeepClone(); + metadata = gif.DeepClone(); } else if (frame.Metadata.TryGetPngMetadata(out PngFrameMetadata? png)) { @@ -282,7 +282,7 @@ internal sealed class GifEncoderCore : IImageEncoderInternals previousDisposalMode); previousFrame = currentFrame; - previousDisposalMode = gifMetadata.DisposalMethod; + previousDisposalMode = gifMetadata.DisposalMode; } if (hasPaletteQuantizer) @@ -664,7 +664,7 @@ internal sealed class GifEncoderCore : IImageEncoderInternals bool hasTransparency = metadata.HasTransparency; byte packedValue = GifGraphicControlExtension.GetPackedValue( - disposalMode: metadata.DisposalMethod, + disposalMode: metadata.DisposalMode, transparencyFlag: hasTransparency); GifGraphicControlExtension extension = new( diff --git a/src/ImageSharp/Formats/Gif/GifFrameMetadata.cs b/src/ImageSharp/Formats/Gif/GifFrameMetadata.cs index 46bc415ce1..f81329e973 100644 --- a/src/ImageSharp/Formats/Gif/GifFrameMetadata.cs +++ b/src/ImageSharp/Formats/Gif/GifFrameMetadata.cs @@ -26,7 +26,7 @@ public class GifFrameMetadata : IFormatFrameMetadata { this.ColorTableMode = other.ColorTableMode; this.FrameDelay = other.FrameDelay; - this.DisposalMethod = other.DisposalMethod; + this.DisposalMode = other.DisposalMode; if (other.LocalColorTable?.Length > 0) { @@ -73,7 +73,7 @@ public class GifFrameMetadata : IFormatFrameMetadata /// Primarily used in Gif animation, this field indicates the way in which the graphic is to /// be treated after being displayed. /// - public FrameDisposalMode DisposalMethod { get; set; } + public FrameDisposalMode DisposalMode { get; set; } /// public static GifFrameMetadata FromFormatConnectingFrameMetadata(FormatConnectingFrameMetadata metadata) @@ -100,7 +100,7 @@ public class GifFrameMetadata : IFormatFrameMetadata LocalColorTable = metadata.ColorTable, ColorTableMode = metadata.ColorTableMode, FrameDelay = (int)Math.Round(metadata.Duration.TotalMilliseconds / 10), - DisposalMethod = metadata.DisposalMode, + DisposalMode = metadata.DisposalMode, HasTransparency = hasTransparency, TransparencyIndex = hasTransparency ? unchecked((byte)index) : byte.MinValue, }; @@ -109,10 +109,9 @@ public class GifFrameMetadata : IFormatFrameMetadata /// 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); + bool blendSource = this.DisposalMode == 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; @@ -122,7 +121,7 @@ public class GifFrameMetadata : IFormatFrameMetadata ColorTable = this.LocalColorTable, ColorTableMode = this.ColorTableMode, Duration = TimeSpan.FromMilliseconds(this.FrameDelay * 10), - DisposalMode = this.DisposalMethod, + DisposalMode = this.DisposalMode, BlendMode = blendSource ? FrameBlendMode.Source : FrameBlendMode.Over, }; } @@ -158,7 +157,7 @@ public class GifFrameMetadata : IFormatFrameMetadata LocalColorTable = metadata.ColorTable, ColorTableMode = metadata.ColorTableMode, FrameDelay = (int)Math.Round(metadata.Duration.TotalMilliseconds / 10), - DisposalMethod = metadata.DisposalMode, + DisposalMode = metadata.DisposalMode, HasTransparency = hasTransparency, TransparencyIndex = hasTransparency ? unchecked((byte)index) : byte.MinValue, }; diff --git a/src/ImageSharp/Formats/Gif/GifMetadata.cs b/src/ImageSharp/Formats/Gif/GifMetadata.cs index 4ebe046ba3..9a234aa9ba 100644 --- a/src/ImageSharp/Formats/Gif/GifMetadata.cs +++ b/src/ImageSharp/Formats/Gif/GifMetadata.cs @@ -129,16 +129,20 @@ public class GifMetadata : IFormatMetadata ? this.GlobalColorTable.Value.Span[this.BackgroundColorIndex] : Color.Transparent; + int bpp = this.GlobalColorTable.HasValue + ? Numerics.Clamp(ColorNumerics.GetBitsNeededForColorDepth(this.GlobalColorTable.Value.Length), 1, 8) + : 8; + return new() { AnimateRootFrame = true, BackgroundColor = color, ColorTable = this.GlobalColorTable, ColorTableMode = this.ColorTableMode, - PixelTypeInfo = new PixelTypeInfo(24) + PixelTypeInfo = new PixelTypeInfo(bpp) { ColorType = PixelColorType.Indexed, - ComponentInfo = PixelComponentInfo.Create(3, 24, 8, 8, 8), + ComponentInfo = PixelComponentInfo.Create(1, bpp, bpp), }, RepeatCount = this.RepeatCount, }; diff --git a/src/ImageSharp/Formats/Gif/MetadataExtensions.cs b/src/ImageSharp/Formats/Gif/MetadataExtensions.cs index 5fd2d5c1e9..0f2d281f16 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 == FrameDisposalMode.RestoreToBackground || (source.LocalColorTable?.Length == 256 && !source.HasTransparency); + bool blendSource = source.DisposalMode == 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,7 +90,7 @@ public static partial class MetadataExtensions ColorTable = source.LocalColorTable, ColorTableMode = source.ColorTableMode, Duration = TimeSpan.FromMilliseconds(source.FrameDelay * 10), - DisposalMode = source.DisposalMethod, + DisposalMode = source.DisposalMode, BlendMode = blendSource ? FrameBlendMode.Source : FrameBlendMode.Over, }; } diff --git a/src/ImageSharp/Formats/IImageFormat.cs b/src/ImageSharp/Formats/IImageFormat.cs index b983219777..5c579eb4f6 100644 --- a/src/ImageSharp/Formats/IImageFormat.cs +++ b/src/ImageSharp/Formats/IImageFormat.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors. +// Copyright (c) Six Labors. // Licensed under the Six Labors Split License. namespace SixLabors.ImageSharp.Formats; @@ -14,12 +14,12 @@ public interface IImageFormat string Name { get; } /// - /// Gets the default mimetype that the image format uses + /// Gets the default mime type that the image format uses /// string DefaultMimeType { get; } /// - /// Gets all the mimetypes that have been used by this image format. + /// Gets all the mime types that have been used by this image format. /// IEnumerable MimeTypes { get; } diff --git a/src/ImageSharp/Formats/Pbm/PbmMetadata.cs b/src/ImageSharp/Formats/Pbm/PbmMetadata.cs index 938e11db16..4e6b6f2650 100644 --- a/src/ImageSharp/Formats/Pbm/PbmMetadata.cs +++ b/src/ImageSharp/Formats/Pbm/PbmMetadata.cs @@ -1,12 +1,14 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. +using SixLabors.ImageSharp.PixelFormats; + namespace SixLabors.ImageSharp.Formats.Pbm; /// /// Provides PBM specific metadata information for the image. /// -public class PbmMetadata : IDeepCloneable +public class PbmMetadata : IFormatMetadata { /// /// Initializes a new instance of the class. @@ -38,8 +40,97 @@ public class PbmMetadata : IDeepCloneable /// /// Gets or sets the data type of the pixel components. /// - public PbmComponentType ComponentType { get; set; } + public PbmComponentType ComponentType { get; set; } = PbmComponentType.Byte; + + /// + public static PbmMetadata FromFormatConnectingMetadata(FormatConnectingMetadata metadata) + { + PbmColorType color; + PixelColorType colorType = metadata.PixelTypeInfo.ColorType ?? PixelColorType.Luminance; + + switch (colorType) + { + case PixelColorType.Binary: + color = PbmColorType.BlackAndWhite; + break; + case PixelColorType.Luminance: + color = PbmColorType.Grayscale; + break; + default: + if (colorType.HasFlag(PixelColorType.RGB) || colorType.HasFlag(PixelColorType.BGR)) + { + color = PbmColorType.Rgb; + } + else + { + color = PbmColorType.Grayscale; + } + + break; + } + + PbmComponentType componentType = PbmComponentType.Short; + int bpp = metadata.PixelTypeInfo.BitsPerPixel; + if (bpp == 1) + { + componentType = PbmComponentType.Bit; + } + else if (bpp <= 8) + { + componentType = PbmComponentType.Byte; + } + + return new PbmMetadata + { + ColorType = color, + ComponentType = componentType + }; + } + + /// + public FormatConnectingMetadata ToFormatConnectingMetadata() + { + int bpp; + PixelColorType colorType; + PixelComponentInfo info; + switch (this.ColorType) + { + case PbmColorType.BlackAndWhite: + bpp = 1; + colorType = PixelColorType.Binary; + info = PixelComponentInfo.Create(1, bpp, 1); + break; + case PbmColorType.Grayscale: + bpp = 8; + colorType = PixelColorType.Luminance; + info = PixelComponentInfo.Create(1, bpp, 8); + break; + case PbmColorType.Rgb: + bpp = 24; + colorType = PixelColorType.RGB; + info = PixelComponentInfo.Create(3, bpp, 8, 8, 8); + break; + default: + bpp = 8; + colorType = PixelColorType.Luminance; + info = PixelComponentInfo.Create(1, bpp, 8); + break; + } + + return new FormatConnectingMetadata + { + PixelTypeInfo = new PixelTypeInfo(bpp) + { + AlphaRepresentation = PixelAlphaRepresentation.None, + ColorType = colorType, + ComponentInfo = info, + }, + }; + } + + /// + IDeepCloneable IDeepCloneable.DeepClone() => this.DeepClone(); /// - public IDeepCloneable DeepClone() => new PbmMetadata(this); + public PbmMetadata DeepClone() => new(this); } diff --git a/src/ImageSharp/Formats/Png/Chunks/FrameControl.cs b/src/ImageSharp/Formats/Png/Chunks/FrameControl.cs index fb2ca473c2..91f79c8154 100644 --- a/src/ImageSharp/Formats/Png/Chunks/FrameControl.cs +++ b/src/ImageSharp/Formats/Png/Chunks/FrameControl.cs @@ -22,8 +22,8 @@ internal readonly struct FrameControl uint yOffset, ushort delayNumerator, ushort delayDenominator, - PngDisposalMethod disposeOperation, - PngBlendMethod blendOperation) + FrameDisposalMode disposalMode, + FrameBlendMode blendMode) { this.SequenceNumber = sequenceNumber; this.Width = width; @@ -32,8 +32,8 @@ internal readonly struct FrameControl this.YOffset = yOffset; this.DelayNumerator = delayNumerator; this.DelayDenominator = delayDenominator; - this.DisposeOperation = disposeOperation; - this.BlendOperation = blendOperation; + this.DisposalMode = disposalMode; + this.BlendMode = blendMode; } /// @@ -84,12 +84,12 @@ internal readonly struct FrameControl /// /// Gets the type of frame area disposal to be done after rendering this frame /// - public PngDisposalMethod DisposeOperation { get; } + public FrameDisposalMode DisposalMode { get; } /// /// Gets the type of frame area rendering for this frame /// - public PngBlendMethod BlendOperation { get; } + public FrameBlendMode BlendMode { get; } public Rectangle Bounds => new((int)this.XOffset, (int)this.YOffset, (int)this.Width, (int)this.Height); @@ -137,8 +137,8 @@ internal readonly struct FrameControl BinaryPrimitives.WriteUInt16BigEndian(buffer[20..22], this.DelayNumerator); BinaryPrimitives.WriteUInt16BigEndian(buffer[22..24], this.DelayDenominator); - buffer[24] = (byte)this.DisposeOperation; - buffer[25] = (byte)this.BlendOperation; + buffer[24] = (byte)(this.DisposalMode - 1); + buffer[25] = (byte)this.BlendMode; } /// @@ -155,6 +155,6 @@ internal readonly struct FrameControl yOffset: BinaryPrimitives.ReadUInt32BigEndian(data[16..20]), delayNumerator: BinaryPrimitives.ReadUInt16BigEndian(data[20..22]), delayDenominator: BinaryPrimitives.ReadUInt16BigEndian(data[22..24]), - disposeOperation: (PngDisposalMethod)data[24], - blendOperation: (PngBlendMethod)data[25]); + disposalMode: (FrameDisposalMode)(data[24] + 1), + blendMode: (FrameBlendMode)data[25]); } diff --git a/src/ImageSharp/Formats/Png/MetadataExtensions.cs b/src/ImageSharp/Formats/Png/MetadataExtensions.cs index b6313bffe0..6da1fb5fdb 100644 --- a/src/ImageSharp/Formats/Png/MetadataExtensions.cs +++ b/src/ImageSharp/Formats/Png/MetadataExtensions.cs @@ -69,16 +69,16 @@ public static partial class MetadataExtensions { ColorTableMode = FrameColorTableMode.Global, Duration = TimeSpan.FromMilliseconds(delay * 1000), - DisposalMode = GetMode(source.DisposalMethod), - BlendMode = source.BlendMethod == PngBlendMethod.Source ? FrameBlendMode.Source : FrameBlendMode.Over, + DisposalMode = GetMode(source.DisposalMode), + BlendMode = source.BlendMode, }; } - private static FrameDisposalMode GetMode(PngDisposalMethod method) => method switch + private static FrameDisposalMode GetMode(FrameDisposalMode method) => method switch { - PngDisposalMethod.DoNotDispose => FrameDisposalMode.DoNotDispose, - PngDisposalMethod.RestoreToBackground => FrameDisposalMode.RestoreToBackground, - PngDisposalMethod.RestoreToPrevious => FrameDisposalMode.RestoreToPrevious, - _ => FrameDisposalMode.Unspecified, + FrameDisposalMode.DoNotDispose => FrameDisposalMode.DoNotDispose, + FrameDisposalMode.RestoreToBackground => FrameDisposalMode.RestoreToBackground, + FrameDisposalMode.RestoreToPrevious => FrameDisposalMode.RestoreToPrevious, + _ => FrameDisposalMode.DoNotDispose, }; } diff --git a/src/ImageSharp/Formats/Png/PngBlendMethod.cs b/src/ImageSharp/Formats/Png/PngBlendMethod.cs deleted file mode 100644 index f71dce8325..0000000000 --- a/src/ImageSharp/Formats/Png/PngBlendMethod.cs +++ /dev/null @@ -1,22 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Formats.Png; - -/// -/// Specifies whether the frame is to be alpha blended into the current output buffer content, -/// or whether it should completely replace its region in the output buffer. -/// -public enum PngBlendMethod -{ - /// - /// All color components of the frame, including alpha, overwrite the current contents of the frame's output buffer region. - /// - Source, - - /// - /// The frame should be composited onto the output buffer based on its alpha, using a simple OVER operation as - /// described in the "Alpha Channel Processing" section of the PNG specification [PNG-1.2]. - /// - Over -} diff --git a/src/ImageSharp/Formats/Png/PngDecoderCore.cs b/src/ImageSharp/Formats/Png/PngDecoderCore.cs index 36a0a8bcbb..a4d13177d8 100644 --- a/src/ImageSharp/Formats/Png/PngDecoderCore.cs +++ b/src/ImageSharp/Formats/Png/PngDecoderCore.cs @@ -246,7 +246,7 @@ internal sealed class PngDecoderCore : IImageDecoderInternals cancellationToken); // if current frame dispose is restore to previous, then from future frame's perspective, it never happened - if (currentFrameControl.Value.DisposeOperation != PngDisposalMethod.RestoreToPrevious) + if (currentFrameControl.Value.DisposalMode != FrameDisposalMode.RestoreToPrevious) { previousFrame = currentFrame; previousFrameControl = currentFrameControl; @@ -659,12 +659,12 @@ internal sealed class PngDecoderCore : IImageDecoderInternals // If the first `fcTL` chunk uses a `dispose_op` of APNG_DISPOSE_OP_PREVIOUS it should be treated as APNG_DISPOSE_OP_BACKGROUND. // So, if restoring to before first frame, clear entire area. Same if first frame (previousFrameControl null). - if (previousFrameControl == null || (previousFrame is null && previousFrameControl.Value.DisposeOperation == PngDisposalMethod.RestoreToPrevious)) + if (previousFrameControl == null || (previousFrame is null && previousFrameControl.Value.DisposalMode == FrameDisposalMode.RestoreToPrevious)) { Buffer2DRegion pixelRegion = frame.PixelBuffer.GetRegion(); pixelRegion.Clear(); } - else if (previousFrameControl.Value.DisposeOperation == PngDisposalMethod.RestoreToBackground) + else if (previousFrameControl.Value.DisposalMode == FrameDisposalMode.RestoreToBackground) { Rectangle restoreArea = previousFrameControl.Value.Bounds; Buffer2DRegion pixelRegion = frame.PixelBuffer.GetRegion(restoreArea); @@ -794,8 +794,8 @@ internal sealed class PngDecoderCore : IImageDecoderInternals int height = (int)frameControl.YMax; IMemoryOwner? blendMemory = null; - Span blendRowBuffer = Span.Empty; - if (frameControl.BlendOperation == PngBlendMethod.Over) + Span blendRowBuffer = []; + if (frameControl.BlendMode == FrameBlendMode.Over) { blendMemory = this.memoryAllocator.Allocate(imageFrame.Width, AllocationOptions.Clean); blendRowBuffer = blendMemory.Memory.Span; @@ -887,8 +887,8 @@ internal sealed class PngDecoderCore : IImageDecoderInternals Buffer2D imageBuffer = imageFrame.PixelBuffer; IMemoryOwner? blendMemory = null; - Span blendRowBuffer = Span.Empty; - if (frameControl.BlendOperation == PngBlendMethod.Over) + Span blendRowBuffer = []; + if (frameControl.BlendMode == FrameBlendMode.Over) { blendMemory = this.memoryAllocator.Allocate(imageFrame.Width, AllocationOptions.Clean); blendRowBuffer = blendMemory.Memory.Span; @@ -1013,7 +1013,7 @@ internal sealed class PngDecoderCore : IImageDecoderInternals { Span destination = pixels.PixelBuffer.DangerousGetRowSpan(currentRow); - bool blend = frameControl.BlendOperation == PngBlendMethod.Over; + bool blend = frameControl.BlendMode == FrameBlendMode.Over; Span rowSpan = blend ? blendRowBuffer : destination; @@ -1126,7 +1126,7 @@ internal sealed class PngDecoderCore : IImageDecoderInternals int increment = 1) where TPixel : unmanaged, IPixel { - bool blend = frameControl.BlendOperation == PngBlendMethod.Over; + bool blend = frameControl.BlendMode == FrameBlendMode.Over; Span rowSpan = blend ? blendRowBuffer : destination; @@ -1460,7 +1460,20 @@ internal sealed class PngDecoderCore : IImageDecoderInternals byte colorPrimaries = data[0]; byte transferFunction = data[1]; byte matrixCoefficients = data[2]; - bool? fullRange = data[3] == 1 ? true : data[3] == 0 ? false : null; + bool? fullRange; + if (data[3] == 1) + { + fullRange = true; + } + else if (data[3] == 0) + { + fullRange = false; + } + else + { + fullRange = null; + } + metadata.CicpProfile = new CicpProfile(colorPrimaries, transferFunction, matrixCoefficients, fullRange); } @@ -1492,7 +1505,7 @@ internal sealed class PngDecoderCore : IImageDecoderInternals // Sequence of bytes for the exif header ("Exif" ASCII and two zero bytes). // This doesn't actually allocate. - ReadOnlySpan exifHeader = new byte[] { 0x45, 0x78, 0x69, 0x66, 0x00, 0x00 }; + ReadOnlySpan exifHeader = [0x45, 0x78, 0x69, 0x66, 0x00, 0x00]; if (dataLength < exifHeader.Length) { @@ -1603,7 +1616,7 @@ internal sealed class PngDecoderCore : IImageDecoderInternals Span destUncompressedData = destBuffer.GetSpan(); if (!inflateStream.AllocateNewBytes(compressedData.Length, false)) { - uncompressedBytesArray = Array.Empty(); + uncompressedBytesArray = []; return false; } @@ -1612,7 +1625,7 @@ internal sealed class PngDecoderCore : IImageDecoderInternals { if (memoryStreamOutput.Length > maxLength) { - uncompressedBytesArray = Array.Empty(); + uncompressedBytesArray = []; return false; } @@ -1979,7 +1992,7 @@ internal sealed class PngDecoderCore : IImageDecoderInternals { if (length == 0) { - return new BasicArrayBuffer(Array.Empty()); + return new BasicArrayBuffer([]); } // We rent the buffer here to return it afterwards in Decode() diff --git a/src/ImageSharp/Formats/Png/PngDisposalMethod.cs b/src/ImageSharp/Formats/Png/PngDisposalMethod.cs deleted file mode 100644 index 1537c5cedf..0000000000 --- a/src/ImageSharp/Formats/Png/PngDisposalMethod.cs +++ /dev/null @@ -1,25 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Formats.Png; - -/// -/// Specifies how the output buffer should be changed at the end of the delay (before rendering the next frame). -/// -public enum PngDisposalMethod -{ - /// - /// No disposal is done on this frame before rendering the next; the contents of the output buffer are left as is. - /// - DoNotDispose, - - /// - /// The frame's region of the output buffer is to be cleared to fully transparent black before rendering the next frame. - /// - RestoreToBackground, - - /// - /// The frame's region of the output buffer is to be reverted to the previous contents before rendering the next frame. - /// - RestoreToPrevious -} diff --git a/src/ImageSharp/Formats/Png/PngEncoderCore.cs b/src/ImageSharp/Formats/Png/PngEncoderCore.cs index 36ce136f47..49157272c9 100644 --- a/src/ImageSharp/Formats/Png/PngEncoderCore.cs +++ b/src/ImageSharp/Formats/Png/PngEncoderCore.cs @@ -212,7 +212,7 @@ internal sealed class PngEncoderCore : IImageEncoderInternals, IDisposable // Write the first animated frame. currentFrame = image.Frames[currentFrameIndex]; PngFrameMetadata frameMetadata = GetPngFrameMetadata(currentFrame); - PngDisposalMethod previousDisposal = frameMetadata.DisposalMethod; + FrameDisposalMode previousDisposal = frameMetadata.DisposalMode; FrameControl frameControl = this.WriteFrameControlChunk(stream, frameMetadata, currentFrame.Bounds(), 0); uint sequenceNumber = 1; if (pngMetadata.AnimateRootFrame) @@ -237,12 +237,12 @@ internal sealed class PngEncoderCore : IImageEncoderInternals, IDisposable for (; currentFrameIndex < image.Frames.Count; currentFrameIndex++) { - ImageFrame? prev = previousDisposal == PngDisposalMethod.RestoreToBackground ? null : previousFrame; + ImageFrame? prev = previousDisposal == FrameDisposalMode.RestoreToBackground ? null : previousFrame; currentFrame = image.Frames[currentFrameIndex]; ImageFrame? nextFrame = currentFrameIndex < image.Frames.Count - 1 ? image.Frames[currentFrameIndex + 1] : null; frameMetadata = GetPngFrameMetadata(currentFrame); - bool blend = frameMetadata.BlendMethod == PngBlendMethod.Over; + bool blend = frameMetadata.BlendMode == FrameBlendMode.Over; (bool difference, Rectangle bounds) = AnimationUtilities.DeDuplicatePixels( @@ -268,7 +268,7 @@ internal sealed class PngEncoderCore : IImageEncoderInternals, IDisposable sequenceNumber += this.WriteDataChunks(frameControl, encodingFrame.PixelBuffer.GetRegion(bounds), quantized, stream, true) + 1; previousFrame = currentFrame; - previousDisposal = frameMetadata.DisposalMethod; + previousDisposal = frameMetadata.DisposalMode; } } @@ -293,7 +293,7 @@ internal sealed class PngEncoderCore : IImageEncoderInternals, IDisposable { if (image.Metadata.TryGetPngMetadata(out PngMetadata? png)) { - return (PngMetadata)png.DeepClone(); + return png.DeepClone(); } if (image.Metadata.TryGetGifMetadata(out GifMetadata? gif)) @@ -317,7 +317,7 @@ internal sealed class PngEncoderCore : IImageEncoderInternals, IDisposable { if (frame.Metadata.TryGetPngMetadata(out PngFrameMetadata? png)) { - return (PngFrameMetadata)png.DeepClone(); + return png.DeepClone(); } if (frame.Metadata.TryGetGifMetadata(out GifFrameMetadata? gif)) @@ -881,6 +881,7 @@ internal sealed class PngEncoderCore : IImageEncoderInternals, IDisposable /// /// The containing image data. /// The image meta data. + /// CICP matrix coefficients other than Identity are not supported in PNG. private void WriteCicpChunk(Stream stream, ImageMetadata metaData) { if (metaData.CicpProfile is null) @@ -1125,8 +1126,8 @@ internal sealed class PngEncoderCore : IImageEncoderInternals, IDisposable yOffset: (uint)bounds.Top, delayNumerator: (ushort)frameMetadata.FrameDelay.Numerator, delayDenominator: (ushort)frameMetadata.FrameDelay.Denominator, - disposeOperation: frameMetadata.DisposalMethod, - blendOperation: frameMetadata.BlendMethod); + disposalMode: frameMetadata.DisposalMode, + blendMode: frameMetadata.BlendMode); fcTL.WriteTo(this.chunkDataBuffer.Span); diff --git a/src/ImageSharp/Formats/Png/PngFrameMetadata.cs b/src/ImageSharp/Formats/Png/PngFrameMetadata.cs index dbda4d73c9..de141909ad 100644 --- a/src/ImageSharp/Formats/Png/PngFrameMetadata.cs +++ b/src/ImageSharp/Formats/Png/PngFrameMetadata.cs @@ -8,7 +8,7 @@ namespace SixLabors.ImageSharp.Formats.Png; /// /// Provides APng specific metadata information for the image frame. /// -public class PngFrameMetadata : IDeepCloneable +public class PngFrameMetadata : IFormatFrameMetadata { /// /// Initializes a new instance of the class. @@ -24,8 +24,8 @@ public class PngFrameMetadata : IDeepCloneable private PngFrameMetadata(PngFrameMetadata other) { this.FrameDelay = other.FrameDelay; - this.DisposalMethod = other.DisposalMethod; - this.BlendMethod = other.BlendMethod; + this.DisposalMode = other.DisposalMode; + this.BlendMode = other.BlendMode; } /// @@ -39,12 +39,12 @@ public class PngFrameMetadata : IDeepCloneable /// /// Gets or sets the type of frame area disposal to be done after rendering this frame /// - public PngDisposalMethod DisposalMethod { get; set; } + public FrameDisposalMode DisposalMode { get; set; } /// /// Gets or sets the type of frame area rendering for this frame /// - public PngBlendMethod BlendMethod { get; set; } + public FrameBlendMode BlendMode { get; set; } /// /// Initializes a new instance of the class. @@ -53,26 +53,56 @@ public class PngFrameMetadata : IDeepCloneable internal void FromChunk(in FrameControl frameControl) { this.FrameDelay = new Rational(frameControl.DelayNumerator, frameControl.DelayDenominator); - this.DisposalMethod = frameControl.DisposeOperation; - this.BlendMethod = frameControl.BlendOperation; + this.DisposalMode = frameControl.DisposalMode; + this.BlendMode = frameControl.BlendMode; } - /// - public IDeepCloneable DeepClone() => new PngFrameMetadata(this); - internal static PngFrameMetadata FromAnimatedMetadata(AnimatedImageFrameMetadata metadata) => new() { FrameDelay = new(metadata.Duration.TotalMilliseconds / 1000), - DisposalMethod = GetMode(metadata.DisposalMode), - BlendMethod = metadata.BlendMode == FrameBlendMode.Source ? PngBlendMethod.Source : PngBlendMethod.Over, + DisposalMode = GetMode(metadata.DisposalMode), + BlendMode = metadata.BlendMode, }; - private static PngDisposalMethod GetMode(FrameDisposalMode mode) => mode switch + private static FrameDisposalMode GetMode(FrameDisposalMode mode) => mode switch { - FrameDisposalMode.RestoreToBackground => PngDisposalMethod.RestoreToBackground, - FrameDisposalMode.RestoreToPrevious => PngDisposalMethod.RestoreToPrevious, - FrameDisposalMode.DoNotDispose => PngDisposalMethod.DoNotDispose, - _ => PngDisposalMethod.DoNotDispose, + FrameDisposalMode.RestoreToBackground => FrameDisposalMode.RestoreToBackground, + FrameDisposalMode.RestoreToPrevious => FrameDisposalMode.RestoreToPrevious, + FrameDisposalMode.DoNotDispose => FrameDisposalMode.DoNotDispose, + _ => FrameDisposalMode.DoNotDispose, }; + + /// + public static PngFrameMetadata FromFormatConnectingFrameMetadata(FormatConnectingFrameMetadata metadata) + => new() + { + FrameDelay = new(metadata.Duration.TotalMilliseconds / 1000), + DisposalMode = GetMode(metadata.DisposalMode), + BlendMode = metadata.BlendMode, + }; + + /// + public FormatConnectingFrameMetadata ToFormatConnectingFrameMetadata() + { + double delay = this.FrameDelay.ToDouble(); + if (double.IsNaN(delay)) + { + delay = 0; + } + + return new() + { + ColorTableMode = FrameColorTableMode.Global, + Duration = TimeSpan.FromMilliseconds(delay * 1000), + DisposalMode = this.DisposalMode, + BlendMode = this.BlendMode, + }; + } + + /// + IDeepCloneable IDeepCloneable.DeepClone() => this.DeepClone(); + + /// + public PngFrameMetadata DeepClone() => new(this); } diff --git a/src/ImageSharp/Formats/Png/PngMetadata.cs b/src/ImageSharp/Formats/Png/PngMetadata.cs index de02e390f0..49799f54b2 100644 --- a/src/ImageSharp/Formats/Png/PngMetadata.cs +++ b/src/ImageSharp/Formats/Png/PngMetadata.cs @@ -2,13 +2,14 @@ // Licensed under the Six Labors Split License. using SixLabors.ImageSharp.Formats.Png.Chunks; +using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Formats.Png; /// /// Provides Png specific metadata information for the image. /// -public class PngMetadata : IDeepCloneable +public class PngMetadata : IFormatMetadata { /// /// Initializes a new instance of the class. @@ -89,9 +90,6 @@ public class PngMetadata : IDeepCloneable /// public bool AnimateRootFrame { get; set; } = true; - /// - public IDeepCloneable DeepClone() => new PngMetadata(this); - internal static PngMetadata FromAnimatedMetadata(AnimatedImageMetadata metadata) { // Should the conversion be from a format that uses a 24bit palette entries (gif) @@ -121,4 +119,161 @@ public class PngMetadata : IDeepCloneable RepeatCount = metadata.RepeatCount, }; } + + /// + 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) + { + // Png treats background as fully empty + c = Color.Transparent; + break; + } + } + } + + PngColorType color; + PixelColorType colorType = + metadata.PixelTypeInfo.ColorType ?? PixelColorType.RGB | PixelColorType.Alpha; + + switch (colorType) + { + case PixelColorType.Binary: + case PixelColorType.Indexed: + color = PngColorType.Palette; + break; + case PixelColorType.Luminance: + color = PngColorType.Grayscale; + break; + case PixelColorType.RGB: + case PixelColorType.BGR: + color = PngColorType.Rgb; + break; + default: + if (colorType.HasFlag(PixelColorType.Luminance)) + { + color = PngColorType.GrayscaleWithAlpha; + break; + } + + color = PngColorType.RgbWithAlpha; + break; + } + + // PNG uses bits per component not per pixel. + int bpc = metadata.PixelTypeInfo.ComponentInfo?.GetMaximumComponentPrecision() ?? 8; + PngBitDepth bitDepth = bpc switch + { + 1 => PngBitDepth.Bit1, + 2 => PngBitDepth.Bit2, + 4 => PngBitDepth.Bit4, + _ => (bpc <= 8) ? PngBitDepth.Bit8 : PngBitDepth.Bit16, + }; + return new() + { + ColorType = color, + BitDepth = bitDepth, + ColorTable = colorTable, + RepeatCount = metadata.RepeatCount, + }; + } + + /// + public FormatConnectingMetadata ToFormatConnectingMetadata() + { + int bpp; + PixelColorType colorType; + PixelAlphaRepresentation alpha = PixelAlphaRepresentation.None; + PixelComponentInfo info; + switch (this.ColorType) + { + case PngColorType.Palette: + bpp = this.ColorTable.HasValue + ? Numerics.Clamp(ColorNumerics.GetBitsNeededForColorDepth(this.ColorTable.Value.Length), 1, 8) + : 8; + + colorType = PixelColorType.Indexed; + info = PixelComponentInfo.Create(1, bpp, bpp); + break; + + case PngColorType.Grayscale: + bpp = 8; + colorType = PixelColorType.Luminance; + info = PixelComponentInfo.Create(1, bpp, 8); + break; + + case PngColorType.GrayscaleWithAlpha: + + alpha = PixelAlphaRepresentation.Unassociated; + if (this.BitDepth == PngBitDepth.Bit16) + { + bpp = 32; + colorType = PixelColorType.Luminance | PixelColorType.Alpha; + info = PixelComponentInfo.Create(2, bpp, 16, 16); + break; + } + + bpp = 16; + colorType = PixelColorType.Luminance | PixelColorType.Alpha; + info = PixelComponentInfo.Create(2, bpp, 8, 8); + break; + + case PngColorType.Rgb: + if (this.BitDepth == PngBitDepth.Bit16) + { + bpp = 24; + colorType = PixelColorType.RGB; + info = PixelComponentInfo.Create(3, bpp, 8, 8, 8); + break; + } + + bpp = 48; + colorType = PixelColorType.RGB; + info = PixelComponentInfo.Create(3, bpp, 16, 16, 16); + break; + + default: + + alpha = PixelAlphaRepresentation.Unassociated; + if (this.BitDepth == PngBitDepth.Bit16) + { + bpp = 64; + colorType = PixelColorType.RGB | PixelColorType.Alpha; + info = PixelComponentInfo.Create(4, bpp, 16, 16, 16, 16); + break; + } + + bpp = 32; + colorType = PixelColorType.RGB | PixelColorType.Alpha; + info = PixelComponentInfo.Create(4, bpp, 8, 8, 8, 8); + break; + } + + return new() + { + ColorTable = this.ColorTable, + ColorTableMode = FrameColorTableMode.Global, + PixelTypeInfo = new PixelTypeInfo(bpp) + { + AlphaRepresentation = alpha, + ColorType = colorType, + ComponentInfo = info, + }, + RepeatCount = (ushort)Numerics.Clamp(this.RepeatCount, 0, ushort.MaxValue), + }; + } + + /// + IDeepCloneable IDeepCloneable.DeepClone() => this.DeepClone(); + + /// + public PngMetadata DeepClone() => new(this); } diff --git a/src/ImageSharp/Metadata/ImageMetadata.cs b/src/ImageSharp/Metadata/ImageMetadata.cs index 6b62be08ff..ceeea42cfc 100644 --- a/src/ImageSharp/Metadata/ImageMetadata.cs +++ b/src/ImageSharp/Metadata/ImageMetadata.cs @@ -33,7 +33,7 @@ public sealed class ImageMetadata : IDeepCloneable /// public const PixelResolutionUnit DefaultPixelResolutionUnits = PixelResolutionUnit.PixelsPerInch; - private readonly Dictionary formatMetadata = new(); + private readonly Dictionary formatMetadata = []; private double horizontalResolution; private double verticalResolution; diff --git a/src/ImageSharp/PixelFormats/PixelColorType.cs b/src/ImageSharp/PixelFormats/PixelColorType.cs index afbe6a4d3e..315c0f6584 100644 --- a/src/ImageSharp/PixelFormats/PixelColorType.cs +++ b/src/ImageSharp/PixelFormats/PixelColorType.cs @@ -44,55 +44,60 @@ public enum PixelColorType /// Luminance = 1 << 5, + /// + /// Indicates that the color is in binary (black and white) format. + /// + Binary = 1 << 6, + /// /// Indicates that the color is indexed using a palette. /// - Indexed = 1 << 6, + Indexed = 1 << 7, /// /// Indicates that the color is in RGB (Red, Green, Blue) format. /// - RGB = Red | Green | Blue | (1 << 7), + RGB = Red | Green | Blue | (1 << 8), /// /// Indicates that the color is in BGR (Blue, Green, Red) format. /// - BGR = Blue | Green | Red | (1 << 8), + BGR = Blue | Green | Red | (1 << 9), /// - /// Represents the Chrominance Blue component in YCbCr. + /// Represents the Chrominance Blue component. /// - ChrominanceBlue = 1 << 9, + ChrominanceBlue = 1 << 10, /// - /// Represents the Chrominance Red component in YCbCr. + /// Represents the Chrominance Red component. /// - ChrominanceRed = 1 << 10, + ChrominanceRed = 1 << 11, /// /// Indicates that the color is in YCbCr (Luminance, Chrominance Blue, Chrominance Red) format. /// - YCbCr = Luminance | ChrominanceBlue | ChrominanceRed, + YCbCr = Luminance | ChrominanceBlue | ChrominanceRed | (1 << 12), /// /// Represents the Cyan component in CMYK. /// - Cyan = 1 << 11, + Cyan = 1 << 13, /// /// Represents the Magenta component in CMYK. /// - Magenta = 1 << 12, + Magenta = 1 << 14, /// /// Represents the Yellow component in CMYK. /// - Yellow = 1 << 13, + Yellow = 1 << 15, /// /// Represents the Key (black) component in CMYK and YCCK. /// - Key = 1 << 14, + Key = 1 << 16, /// /// Indicates that the color is in CMYK (Cyan, Magenta, Yellow, Key) format. @@ -107,5 +112,5 @@ public enum PixelColorType /// /// Indicates that the color is of a type not specified in this enum. /// - Other = 1 << 16 + Other = 1 << 17 } diff --git a/tests/ImageSharp.Tests/Formats/Gif/GifEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Gif/GifEncoderTests.cs index 0b6615b8ea..aeb3bab7bb 100644 --- a/tests/ImageSharp.Tests/Formats/Gif/GifEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Gif/GifEncoderTests.cs @@ -306,14 +306,14 @@ public class GifEncoderTests Assert.Equal((int)(pngF.FrameDelay.ToDouble() * 100), gifF.FrameDelay); - switch (pngF.DisposalMethod) + switch (pngF.DisposalMode) { - case PngDisposalMethod.RestoreToBackground: - Assert.Equal(FrameDisposalMode.RestoreToBackground, gifF.DisposalMethod); + case FrameDisposalMode.RestoreToBackground: + Assert.Equal(FrameDisposalMode.RestoreToBackground, gifF.DisposalMode); break; - case PngDisposalMethod.DoNotDispose: + case FrameDisposalMode.DoNotDispose: default: - Assert.Equal(FrameDisposalMode.DoNotDispose, gifF.DisposalMethod); + Assert.Equal(FrameDisposalMode.DoNotDispose, gifF.DisposalMode); break; } } @@ -359,11 +359,11 @@ public class GifEncoderTests switch (webpF.DisposalMethod) { case WebpDisposalMethod.RestoreToBackground: - Assert.Equal(FrameDisposalMode.RestoreToBackground, gifF.DisposalMethod); + Assert.Equal(FrameDisposalMode.RestoreToBackground, gifF.DisposalMode); break; case WebpDisposalMethod.DoNotDispose: default: - Assert.Equal(FrameDisposalMode.DoNotDispose, gifF.DisposalMethod); + Assert.Equal(FrameDisposalMode.DoNotDispose, gifF.DisposalMode); break; } } diff --git a/tests/ImageSharp.Tests/Formats/Gif/GifFrameMetadataTests.cs b/tests/ImageSharp.Tests/Formats/Gif/GifFrameMetadataTests.cs index f92896b7ec..f12e6e7e50 100644 --- a/tests/ImageSharp.Tests/Formats/Gif/GifFrameMetadataTests.cs +++ b/tests/ImageSharp.Tests/Formats/Gif/GifFrameMetadataTests.cs @@ -15,18 +15,18 @@ public class GifFrameMetadataTests GifFrameMetadata meta = new() { FrameDelay = 1, - DisposalMethod = FrameDisposalMode.RestoreToBackground, + DisposalMode = FrameDisposalMode.RestoreToBackground, LocalColorTable = new[] { Color.Black, Color.White } }; GifFrameMetadata clone = (GifFrameMetadata)meta.DeepClone(); clone.FrameDelay = 2; - clone.DisposalMethod = FrameDisposalMode.RestoreToPrevious; + clone.DisposalMode = FrameDisposalMode.RestoreToPrevious; clone.LocalColorTable = new[] { Color.Black }; Assert.False(meta.FrameDelay.Equals(clone.FrameDelay)); - Assert.False(meta.DisposalMethod.Equals(clone.DisposalMethod)); + Assert.False(meta.DisposalMode.Equals(clone.DisposalMode)); Assert.False(meta.LocalColorTable.Value.Length == clone.LocalColorTable.Value.Length); Assert.Equal(1, clone.LocalColorTable.Value.Length); } diff --git a/tests/ImageSharp.Tests/Formats/Gif/GifMetadataTests.cs b/tests/ImageSharp.Tests/Formats/Gif/GifMetadataTests.cs index afaa827bfb..e61be8235d 100644 --- a/tests/ImageSharp.Tests/Formats/Gif/GifMetadataTests.cs +++ b/tests/ImageSharp.Tests/Formats/Gif/GifMetadataTests.cs @@ -212,6 +212,6 @@ public class GifMetadataTests } Assert.Equal(frameDelay, gifFrameMetadata.FrameDelay); - Assert.Equal(disposalMethod, gifFrameMetadata.DisposalMethod); + Assert.Equal(disposalMethod, gifFrameMetadata.DisposalMode); } } diff --git a/tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs index 1e4472e8a2..e61475b659 100644 --- a/tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs @@ -479,8 +479,8 @@ public partial class PngEncoderTests PngFrameMetadata outputFrameMetadata = output.Frames[i].Metadata.GetPngMetadata(); Assert.Equal(originalFrameMetadata.FrameDelay, outputFrameMetadata.FrameDelay); - Assert.Equal(originalFrameMetadata.BlendMethod, outputFrameMetadata.BlendMethod); - Assert.Equal(originalFrameMetadata.DisposalMethod, outputFrameMetadata.DisposalMethod); + Assert.Equal(originalFrameMetadata.BlendMode, outputFrameMetadata.BlendMode); + Assert.Equal(originalFrameMetadata.DisposalMode, outputFrameMetadata.DisposalMode); } } @@ -519,18 +519,18 @@ public partial class PngEncoderTests Assert.Equal(gifF.FrameDelay, (int)(pngF.FrameDelay.ToDouble() * 100)); - switch (gifF.DisposalMethod) + switch (gifF.DisposalMode) { case FrameDisposalMode.RestoreToBackground: - Assert.Equal(PngDisposalMethod.RestoreToBackground, pngF.DisposalMethod); + Assert.Equal(FrameDisposalMode.RestoreToBackground, pngF.DisposalMode); break; case FrameDisposalMode.RestoreToPrevious: - Assert.Equal(PngDisposalMethod.RestoreToPrevious, pngF.DisposalMethod); + Assert.Equal(FrameDisposalMode.RestoreToPrevious, pngF.DisposalMode); break; case FrameDisposalMode.Unspecified: case FrameDisposalMode.DoNotDispose: default: - Assert.Equal(PngDisposalMethod.DoNotDispose, pngF.DisposalMethod); + Assert.Equal(FrameDisposalMode.DoNotDispose, pngF.DisposalMode); break; } } @@ -570,22 +570,22 @@ public partial class PngEncoderTests switch (webpF.BlendMethod) { case WebpBlendMethod.Source: - Assert.Equal(PngBlendMethod.Source, pngF.BlendMethod); + Assert.Equal(FrameBlendMode.Source, pngF.BlendMode); break; case WebpBlendMethod.Over: default: - Assert.Equal(PngBlendMethod.Over, pngF.BlendMethod); + Assert.Equal(FrameBlendMode.Over, pngF.BlendMode); break; } switch (webpF.DisposalMethod) { case WebpDisposalMethod.RestoreToBackground: - Assert.Equal(PngDisposalMethod.RestoreToBackground, pngF.DisposalMethod); + Assert.Equal(FrameDisposalMode.RestoreToBackground, pngF.DisposalMode); break; case WebpDisposalMethod.DoNotDispose: default: - Assert.Equal(PngDisposalMethod.DoNotDispose, pngF.DisposalMethod); + Assert.Equal(FrameDisposalMode.DoNotDispose, pngF.DisposalMode); break; } } diff --git a/tests/ImageSharp.Tests/Formats/Png/PngFrameMetadataTests.cs b/tests/ImageSharp.Tests/Formats/Png/PngFrameMetadataTests.cs index 9ba2617281..8fde216545 100644 --- a/tests/ImageSharp.Tests/Formats/Png/PngFrameMetadataTests.cs +++ b/tests/ImageSharp.Tests/Formats/Png/PngFrameMetadataTests.cs @@ -1,6 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. +using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Formats.Png; namespace SixLabors.ImageSharp.Tests.Formats.Png; @@ -14,22 +15,22 @@ public class PngFrameMetadataTests PngFrameMetadata meta = new() { FrameDelay = new(1, 0), - DisposalMethod = PngDisposalMethod.RestoreToBackground, - BlendMethod = PngBlendMethod.Over, + DisposalMode = FrameDisposalMode.RestoreToBackground, + BlendMode = FrameBlendMode.Over, }; - PngFrameMetadata clone = (PngFrameMetadata)meta.DeepClone(); + PngFrameMetadata clone = meta.DeepClone(); Assert.True(meta.FrameDelay.Equals(clone.FrameDelay)); - Assert.True(meta.DisposalMethod.Equals(clone.DisposalMethod)); - Assert.True(meta.BlendMethod.Equals(clone.BlendMethod)); + Assert.True(meta.DisposalMode.Equals(clone.DisposalMode)); + Assert.True(meta.BlendMode.Equals(clone.BlendMode)); clone.FrameDelay = new(2, 1); - clone.DisposalMethod = PngDisposalMethod.RestoreToPrevious; - clone.BlendMethod = PngBlendMethod.Source; + clone.DisposalMode = FrameDisposalMode.RestoreToPrevious; + clone.BlendMode = FrameBlendMode.Source; Assert.False(meta.FrameDelay.Equals(clone.FrameDelay)); - Assert.False(meta.DisposalMethod.Equals(clone.DisposalMethod)); - Assert.False(meta.BlendMethod.Equals(clone.BlendMethod)); + Assert.False(meta.DisposalMode.Equals(clone.DisposalMode)); + Assert.False(meta.BlendMode.Equals(clone.BlendMode)); } } diff --git a/tests/ImageSharp.Tests/Formats/WebP/WebpEncoderTests.cs b/tests/ImageSharp.Tests/Formats/WebP/WebpEncoderTests.cs index 49c47adfd4..4d72363075 100644 --- a/tests/ImageSharp.Tests/Formats/WebP/WebpEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/WebP/WebpEncoderTests.cs @@ -95,7 +95,7 @@ public class WebpEncoderTests Assert.Equal(gifF.FrameDelay, (int)(webpF.FrameDelay / 10)); - switch (gifF.DisposalMethod) + switch (gifF.DisposalMode) { case FrameDisposalMode.RestoreToBackground: Assert.Equal(WebpDisposalMethod.RestoreToBackground, webpF.DisposalMethod); @@ -144,23 +144,23 @@ public class WebpEncoderTests Assert.Equal((uint)(pngF.FrameDelay.ToDouble() * 1000), webpF.FrameDelay); - switch (pngF.BlendMethod) + switch (pngF.BlendMode) { - case PngBlendMethod.Source: + case FrameBlendMode.Source: Assert.Equal(WebpBlendMethod.Source, webpF.BlendMethod); break; - case PngBlendMethod.Over: + case FrameBlendMode.Over: default: Assert.Equal(WebpBlendMethod.Over, webpF.BlendMethod); break; } - switch (pngF.DisposalMethod) + switch (pngF.DisposalMode) { - case PngDisposalMethod.RestoreToBackground: + case FrameDisposalMode.RestoreToBackground: Assert.Equal(WebpDisposalMethod.RestoreToBackground, webpF.DisposalMethod); break; - case PngDisposalMethod.DoNotDispose: + case FrameDisposalMode.DoNotDispose: default: Assert.Equal(WebpDisposalMethod.DoNotDispose, webpF.DisposalMethod); break; diff --git a/tests/ImageSharp.Tests/Image/ImageFrameCollectionTests.NonGeneric.cs b/tests/ImageSharp.Tests/Image/ImageFrameCollectionTests.NonGeneric.cs index 572c5b2ebd..f70623f515 100644 --- a/tests/ImageSharp.Tests/Image/ImageFrameCollectionTests.NonGeneric.cs +++ b/tests/ImageSharp.Tests/Image/ImageFrameCollectionTests.NonGeneric.cs @@ -313,7 +313,7 @@ public abstract partial class ImageFrameCollectionTests GifFrameMetadata aData = a.Metadata.GetGifMetadata(); GifFrameMetadata bData = b.Metadata.GetGifMetadata(); - Assert.Equal(aData.DisposalMethod, bData.DisposalMethod); + Assert.Equal(aData.DisposalMode, bData.DisposalMode); Assert.Equal(aData.FrameDelay, bData.FrameDelay); if (aData.ColorTableMode == FrameColorTableMode.Local && bData.ColorTableMode == FrameColorTableMode.Local) diff --git a/tests/ImageSharp.Tests/Metadata/ImageFrameMetadataTests.cs b/tests/ImageSharp.Tests/Metadata/ImageFrameMetadataTests.cs index 9cb7137fe0..cdd6f0cc4f 100644 --- a/tests/ImageSharp.Tests/Metadata/ImageFrameMetadataTests.cs +++ b/tests/ImageSharp.Tests/Metadata/ImageFrameMetadataTests.cs @@ -28,14 +28,14 @@ public class ImageFrameMetadataTests GifFrameMetadata gifFrameMetadata = metaData.GetGifMetadata(); gifFrameMetadata.FrameDelay = frameDelay; gifFrameMetadata.LocalColorTable = Enumerable.Repeat(Color.HotPink, colorTableLength).ToArray(); - gifFrameMetadata.DisposalMethod = disposalMethod; + gifFrameMetadata.DisposalMode = disposalMethod; ImageFrameMetadata clone = new(metaData); GifFrameMetadata cloneGifFrameMetadata = clone.GetGifMetadata(); Assert.Equal(frameDelay, cloneGifFrameMetadata.FrameDelay); Assert.Equal(colorTableLength, cloneGifFrameMetadata.LocalColorTable.Value.Length); - Assert.Equal(disposalMethod, cloneGifFrameMetadata.DisposalMethod); + Assert.Equal(disposalMethod, cloneGifFrameMetadata.DisposalMode); } [Fact] diff --git a/tests/ImageSharp.Tests/PixelFormats/PixelColorTypeTests.cs b/tests/ImageSharp.Tests/PixelFormats/PixelColorTypeTests.cs index c427130054..05fd25cca9 100644 --- a/tests/ImageSharp.Tests/PixelFormats/PixelColorTypeTests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/PixelColorTypeTests.cs @@ -49,6 +49,13 @@ public class PixelColorTypeTests Assert.True(colorType.HasFlag(PixelColorType.Luminance)); } + [Fact] + public void PixelColorType_Binary_ShouldBeSet() + { + const PixelColorType colorType = PixelColorType.Binary; + Assert.True(colorType.HasFlag(PixelColorType.Binary)); + } + [Fact] public void PixelColorType_Indexed_ShouldBeSet() { @@ -168,6 +175,7 @@ public class PixelColorTypeTests Assert.False(colorType.HasFlag(PixelColorType.Alpha)); Assert.False(colorType.HasFlag(PixelColorType.Exponent)); Assert.False(colorType.HasFlag(PixelColorType.Luminance)); + Assert.False(colorType.HasFlag(PixelColorType.Binary)); Assert.False(colorType.HasFlag(PixelColorType.Indexed)); Assert.False(colorType.HasFlag(PixelColorType.ChrominanceBlue)); Assert.False(colorType.HasFlag(PixelColorType.ChrominanceRed)); @@ -185,6 +193,7 @@ public class PixelColorTypeTests Assert.False(colorType.HasFlag(PixelColorType.Alpha)); Assert.False(colorType.HasFlag(PixelColorType.Exponent)); Assert.False(colorType.HasFlag(PixelColorType.Luminance)); + Assert.False(colorType.HasFlag(PixelColorType.Binary)); Assert.False(colorType.HasFlag(PixelColorType.Indexed)); Assert.False(colorType.HasFlag(PixelColorType.ChrominanceBlue)); Assert.False(colorType.HasFlag(PixelColorType.ChrominanceRed)); @@ -204,6 +213,7 @@ public class PixelColorTypeTests Assert.False(colorType.HasFlag(PixelColorType.Blue)); Assert.False(colorType.HasFlag(PixelColorType.Alpha)); Assert.False(colorType.HasFlag(PixelColorType.Exponent)); + Assert.False(colorType.HasFlag(PixelColorType.Binary)); Assert.False(colorType.HasFlag(PixelColorType.Indexed)); Assert.False(colorType.HasFlag(PixelColorType.Cyan)); Assert.False(colorType.HasFlag(PixelColorType.Magenta)); @@ -222,6 +232,7 @@ public class PixelColorTypeTests Assert.False(colorType.HasFlag(PixelColorType.Alpha)); Assert.False(colorType.HasFlag(PixelColorType.Exponent)); Assert.False(colorType.HasFlag(PixelColorType.Luminance)); + Assert.False(colorType.HasFlag(PixelColorType.Binary)); Assert.False(colorType.HasFlag(PixelColorType.Indexed)); Assert.False(colorType.HasFlag(PixelColorType.ChrominanceBlue)); Assert.False(colorType.HasFlag(PixelColorType.ChrominanceRed)); @@ -237,6 +248,7 @@ public class PixelColorTypeTests Assert.False(colorType.HasFlag(PixelColorType.Blue)); Assert.False(colorType.HasFlag(PixelColorType.Alpha)); Assert.False(colorType.HasFlag(PixelColorType.Exponent)); + Assert.False(colorType.HasFlag(PixelColorType.Binary)); Assert.False(colorType.HasFlag(PixelColorType.Indexed)); Assert.False(colorType.HasFlag(PixelColorType.Cyan)); Assert.False(colorType.HasFlag(PixelColorType.Magenta)); @@ -244,6 +256,31 @@ public class PixelColorTypeTests Assert.False(colorType.HasFlag(PixelColorType.Other)); } + [Fact] + public void PixelColorType_Binary_ShouldNotContainOtherFlags() + { + const PixelColorType colorType = PixelColorType.Binary; + Assert.False(colorType.HasFlag(PixelColorType.Red)); + Assert.False(colorType.HasFlag(PixelColorType.Green)); + Assert.False(colorType.HasFlag(PixelColorType.Blue)); + Assert.False(colorType.HasFlag(PixelColorType.Alpha)); + Assert.False(colorType.HasFlag(PixelColorType.Exponent)); + Assert.False(colorType.HasFlag(PixelColorType.Luminance)); + Assert.False(colorType.HasFlag(PixelColorType.Indexed)); + Assert.False(colorType.HasFlag(PixelColorType.RGB)); + Assert.False(colorType.HasFlag(PixelColorType.BGR)); + Assert.False(colorType.HasFlag(PixelColorType.ChrominanceBlue)); + Assert.False(colorType.HasFlag(PixelColorType.ChrominanceRed)); + Assert.False(colorType.HasFlag(PixelColorType.YCbCr)); + Assert.False(colorType.HasFlag(PixelColorType.Cyan)); + Assert.False(colorType.HasFlag(PixelColorType.Magenta)); + Assert.False(colorType.HasFlag(PixelColorType.Yellow)); + Assert.False(colorType.HasFlag(PixelColorType.Key)); + Assert.False(colorType.HasFlag(PixelColorType.CMYK)); + Assert.False(colorType.HasFlag(PixelColorType.YCCK)); + Assert.False(colorType.HasFlag(PixelColorType.Other)); + } + [Fact] public void PixelColorType_Indexed_ShouldNotContainOtherFlags() { @@ -254,6 +291,7 @@ public class PixelColorTypeTests Assert.False(colorType.HasFlag(PixelColorType.Alpha)); Assert.False(colorType.HasFlag(PixelColorType.Exponent)); Assert.False(colorType.HasFlag(PixelColorType.Luminance)); + Assert.False(colorType.HasFlag(PixelColorType.Binary)); Assert.False(colorType.HasFlag(PixelColorType.RGB)); Assert.False(colorType.HasFlag(PixelColorType.BGR)); Assert.False(colorType.HasFlag(PixelColorType.ChrominanceBlue)); @@ -278,6 +316,7 @@ public class PixelColorTypeTests Assert.False(colorType.HasFlag(PixelColorType.Alpha)); Assert.False(colorType.HasFlag(PixelColorType.Exponent)); Assert.False(colorType.HasFlag(PixelColorType.Luminance)); + Assert.False(colorType.HasFlag(PixelColorType.Binary)); Assert.False(colorType.HasFlag(PixelColorType.Indexed)); Assert.False(colorType.HasFlag(PixelColorType.RGB)); Assert.False(colorType.HasFlag(PixelColorType.BGR));