Browse Source

Implement PngFrameMetadata

pull/2751/head
James Jackson-South 2 years ago
parent
commit
2127b46a04
  1. 2
      src/ImageSharp/Formats/Gif/GifDecoderCore.cs
  2. 10
      src/ImageSharp/Formats/Gif/GifEncoderCore.cs
  3. 13
      src/ImageSharp/Formats/Gif/GifFrameMetadata.cs
  4. 8
      src/ImageSharp/Formats/Gif/GifMetadata.cs
  5. 4
      src/ImageSharp/Formats/Gif/MetadataExtensions.cs
  6. 6
      src/ImageSharp/Formats/IImageFormat.cs
  7. 97
      src/ImageSharp/Formats/Pbm/PbmMetadata.cs
  8. 20
      src/ImageSharp/Formats/Png/Chunks/FrameControl.cs
  9. 14
      src/ImageSharp/Formats/Png/MetadataExtensions.cs
  10. 22
      src/ImageSharp/Formats/Png/PngBlendMethod.cs
  11. 41
      src/ImageSharp/Formats/Png/PngDecoderCore.cs
  12. 25
      src/ImageSharp/Formats/Png/PngDisposalMethod.cs
  13. 17
      src/ImageSharp/Formats/Png/PngEncoderCore.cs
  14. 64
      src/ImageSharp/Formats/Png/PngFrameMetadata.cs
  15. 163
      src/ImageSharp/Formats/Png/PngMetadata.cs
  16. 2
      src/ImageSharp/Metadata/ImageMetadata.cs
  17. 31
      src/ImageSharp/PixelFormats/PixelColorType.cs
  18. 14
      tests/ImageSharp.Tests/Formats/Gif/GifEncoderTests.cs
  19. 6
      tests/ImageSharp.Tests/Formats/Gif/GifFrameMetadataTests.cs
  20. 2
      tests/ImageSharp.Tests/Formats/Gif/GifMetadataTests.cs
  21. 20
      tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs
  22. 19
      tests/ImageSharp.Tests/Formats/Png/PngFrameMetadataTests.cs
  23. 14
      tests/ImageSharp.Tests/Formats/WebP/WebpEncoderTests.cs
  24. 2
      tests/ImageSharp.Tests/Image/ImageFrameCollectionTests.NonGeneric.cs
  25. 4
      tests/ImageSharp.Tests/Metadata/ImageFrameMetadataTests.cs
  26. 39
      tests/ImageSharp.Tests/PixelFormats/PixelColorTypeTests.cs

2
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;
}
}

10
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(

13
src/ImageSharp/Formats/Gif/GifFrameMetadata.cs

@ -26,7 +26,7 @@ public class GifFrameMetadata : IFormatFrameMetadata<GifFrameMetadata>
{
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<GifFrameMetadata>
/// Primarily used in Gif animation, this field indicates the way in which the graphic is to
/// be treated after being displayed.
/// </summary>
public FrameDisposalMode DisposalMethod { get; set; }
public FrameDisposalMode DisposalMode { get; set; }
/// <inheritdoc />
public static GifFrameMetadata FromFormatConnectingFrameMetadata(FormatConnectingFrameMetadata metadata)
@ -100,7 +100,7 @@ public class GifFrameMetadata : IFormatFrameMetadata<GifFrameMetadata>
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<GifFrameMetadata>
/// <inheritdoc />
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<GifFrameMetadata>
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<GifFrameMetadata>
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,
};

8
src/ImageSharp/Formats/Gif/GifMetadata.cs

@ -129,16 +129,20 @@ public class GifMetadata : IFormatMetadata<GifMetadata>
? 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,
};

4
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,
};
}

6
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; }
/// <summary>
/// Gets the default mimetype that the image format uses
/// Gets the default mime type that the image format uses
/// </summary>
string DefaultMimeType { get; }
/// <summary>
/// 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.
/// </summary>
IEnumerable<string> MimeTypes { get; }

97
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;
/// <summary>
/// Provides PBM specific metadata information for the image.
/// </summary>
public class PbmMetadata : IDeepCloneable
public class PbmMetadata : IFormatMetadata<PbmMetadata>
{
/// <summary>
/// Initializes a new instance of the <see cref="PbmMetadata"/> class.
@ -38,8 +40,97 @@ public class PbmMetadata : IDeepCloneable
/// <summary>
/// Gets or sets the data type of the pixel components.
/// </summary>
public PbmComponentType ComponentType { get; set; }
public PbmComponentType ComponentType { get; set; } = PbmComponentType.Byte;
/// <inheritdoc/>
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
};
}
/// <inheritdoc/>
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,
},
};
}
/// <inheritdoc/>
IDeepCloneable IDeepCloneable.DeepClone() => this.DeepClone();
/// <inheritdoc/>
public IDeepCloneable DeepClone() => new PbmMetadata(this);
public PbmMetadata DeepClone() => new(this);
}

20
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;
}
/// <summary>
@ -84,12 +84,12 @@ internal readonly struct FrameControl
/// <summary>
/// Gets the type of frame area disposal to be done after rendering this frame
/// </summary>
public PngDisposalMethod DisposeOperation { get; }
public FrameDisposalMode DisposalMode { get; }
/// <summary>
/// Gets the type of frame area rendering for this frame
/// </summary>
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;
}
/// <summary>
@ -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]);
}

14
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,
};
}

22
src/ImageSharp/Formats/Png/PngBlendMethod.cs

@ -1,22 +0,0 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
namespace SixLabors.ImageSharp.Formats.Png;
/// <summary>
/// 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.
/// </summary>
public enum PngBlendMethod
{
/// <summary>
/// All color components of the frame, including alpha, overwrite the current contents of the frame's output buffer region.
/// </summary>
Source,
/// <summary>
/// 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].
/// </summary>
Over
}

41
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<TPixel> 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<TPixel> pixelRegion = frame.PixelBuffer.GetRegion(restoreArea);
@ -794,8 +794,8 @@ internal sealed class PngDecoderCore : IImageDecoderInternals
int height = (int)frameControl.YMax;
IMemoryOwner<TPixel>? blendMemory = null;
Span<TPixel> blendRowBuffer = Span<TPixel>.Empty;
if (frameControl.BlendOperation == PngBlendMethod.Over)
Span<TPixel> blendRowBuffer = [];
if (frameControl.BlendMode == FrameBlendMode.Over)
{
blendMemory = this.memoryAllocator.Allocate<TPixel>(imageFrame.Width, AllocationOptions.Clean);
blendRowBuffer = blendMemory.Memory.Span;
@ -887,8 +887,8 @@ internal sealed class PngDecoderCore : IImageDecoderInternals
Buffer2D<TPixel> imageBuffer = imageFrame.PixelBuffer;
IMemoryOwner<TPixel>? blendMemory = null;
Span<TPixel> blendRowBuffer = Span<TPixel>.Empty;
if (frameControl.BlendOperation == PngBlendMethod.Over)
Span<TPixel> blendRowBuffer = [];
if (frameControl.BlendMode == FrameBlendMode.Over)
{
blendMemory = this.memoryAllocator.Allocate<TPixel>(imageFrame.Width, AllocationOptions.Clean);
blendRowBuffer = blendMemory.Memory.Span;
@ -1013,7 +1013,7 @@ internal sealed class PngDecoderCore : IImageDecoderInternals
{
Span<TPixel> destination = pixels.PixelBuffer.DangerousGetRowSpan(currentRow);
bool blend = frameControl.BlendOperation == PngBlendMethod.Over;
bool blend = frameControl.BlendMode == FrameBlendMode.Over;
Span<TPixel> rowSpan = blend
? blendRowBuffer
: destination;
@ -1126,7 +1126,7 @@ internal sealed class PngDecoderCore : IImageDecoderInternals
int increment = 1)
where TPixel : unmanaged, IPixel<TPixel>
{
bool blend = frameControl.BlendOperation == PngBlendMethod.Over;
bool blend = frameControl.BlendMode == FrameBlendMode.Over;
Span<TPixel> 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<byte> exifHeader = new byte[] { 0x45, 0x78, 0x69, 0x66, 0x00, 0x00 };
ReadOnlySpan<byte> exifHeader = [0x45, 0x78, 0x69, 0x66, 0x00, 0x00];
if (dataLength < exifHeader.Length)
{
@ -1603,7 +1616,7 @@ internal sealed class PngDecoderCore : IImageDecoderInternals
Span<byte> destUncompressedData = destBuffer.GetSpan();
if (!inflateStream.AllocateNewBytes(compressedData.Length, false))
{
uncompressedBytesArray = Array.Empty<byte>();
uncompressedBytesArray = [];
return false;
}
@ -1612,7 +1625,7 @@ internal sealed class PngDecoderCore : IImageDecoderInternals
{
if (memoryStreamOutput.Length > maxLength)
{
uncompressedBytesArray = Array.Empty<byte>();
uncompressedBytesArray = [];
return false;
}
@ -1979,7 +1992,7 @@ internal sealed class PngDecoderCore : IImageDecoderInternals
{
if (length == 0)
{
return new BasicArrayBuffer<byte>(Array.Empty<byte>());
return new BasicArrayBuffer<byte>([]);
}
// We rent the buffer here to return it afterwards in Decode()

25
src/ImageSharp/Formats/Png/PngDisposalMethod.cs

@ -1,25 +0,0 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
namespace SixLabors.ImageSharp.Formats.Png;
/// <summary>
/// Specifies how the output buffer should be changed at the end of the delay (before rendering the next frame).
/// </summary>
public enum PngDisposalMethod
{
/// <summary>
/// No disposal is done on this frame before rendering the next; the contents of the output buffer are left as is.
/// </summary>
DoNotDispose,
/// <summary>
/// The frame's region of the output buffer is to be cleared to fully transparent black before rendering the next frame.
/// </summary>
RestoreToBackground,
/// <summary>
/// The frame's region of the output buffer is to be reverted to the previous contents before rendering the next frame.
/// </summary>
RestoreToPrevious
}

17
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<TPixel>? prev = previousDisposal == PngDisposalMethod.RestoreToBackground ? null : previousFrame;
ImageFrame<TPixel>? prev = previousDisposal == FrameDisposalMode.RestoreToBackground ? null : previousFrame;
currentFrame = image.Frames[currentFrameIndex];
ImageFrame<TPixel>? 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
/// </summary>
/// <param name="stream">The <see cref="Stream"/> containing image data.</param>
/// <param name="metaData">The image meta data.</param>
/// <exception cref="NotSupportedException">CICP matrix coefficients other than Identity are not supported in PNG.</exception>
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);

64
src/ImageSharp/Formats/Png/PngFrameMetadata.cs

@ -8,7 +8,7 @@ namespace SixLabors.ImageSharp.Formats.Png;
/// <summary>
/// Provides APng specific metadata information for the image frame.
/// </summary>
public class PngFrameMetadata : IDeepCloneable
public class PngFrameMetadata : IFormatFrameMetadata<PngFrameMetadata>
{
/// <summary>
/// Initializes a new instance of the <see cref="PngFrameMetadata"/> 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;
}
/// <summary>
@ -39,12 +39,12 @@ public class PngFrameMetadata : IDeepCloneable
/// <summary>
/// Gets or sets the type of frame area disposal to be done after rendering this frame
/// </summary>
public PngDisposalMethod DisposalMethod { get; set; }
public FrameDisposalMode DisposalMode { get; set; }
/// <summary>
/// Gets or sets the type of frame area rendering for this frame
/// </summary>
public PngBlendMethod BlendMethod { get; set; }
public FrameBlendMode BlendMode { get; set; }
/// <summary>
/// Initializes a new instance of the <see cref="PngFrameMetadata"/> 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;
}
/// <inheritdoc/>
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,
};
/// <inheritdoc/>
public static PngFrameMetadata FromFormatConnectingFrameMetadata(FormatConnectingFrameMetadata metadata)
=> new()
{
FrameDelay = new(metadata.Duration.TotalMilliseconds / 1000),
DisposalMode = GetMode(metadata.DisposalMode),
BlendMode = metadata.BlendMode,
};
/// <inheritdoc/>
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,
};
}
/// <inheritdoc/>
IDeepCloneable IDeepCloneable.DeepClone() => this.DeepClone();
/// <inheritdoc/>
public PngFrameMetadata DeepClone() => new(this);
}

163
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;
/// <summary>
/// Provides Png specific metadata information for the image.
/// </summary>
public class PngMetadata : IDeepCloneable
public class PngMetadata : IFormatMetadata<PngMetadata>
{
/// <summary>
/// Initializes a new instance of the <see cref="PngMetadata"/> class.
@ -89,9 +90,6 @@ public class PngMetadata : IDeepCloneable
/// </summary>
public bool AnimateRootFrame { get; set; } = true;
/// <inheritdoc/>
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,
};
}
/// <inheritdoc/>
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,
};
}
/// <inheritdoc/>
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),
};
}
/// <inheritdoc/>
IDeepCloneable IDeepCloneable.DeepClone() => this.DeepClone();
/// <inheritdoc/>
public PngMetadata DeepClone() => new(this);
}

2
src/ImageSharp/Metadata/ImageMetadata.cs

@ -33,7 +33,7 @@ public sealed class ImageMetadata : IDeepCloneable<ImageMetadata>
/// </summary>
public const PixelResolutionUnit DefaultPixelResolutionUnits = PixelResolutionUnit.PixelsPerInch;
private readonly Dictionary<IImageFormat, IDeepCloneable> formatMetadata = new();
private readonly Dictionary<IImageFormat, IDeepCloneable> formatMetadata = [];
private double horizontalResolution;
private double verticalResolution;

31
src/ImageSharp/PixelFormats/PixelColorType.cs

@ -44,55 +44,60 @@ public enum PixelColorType
/// </summary>
Luminance = 1 << 5,
/// <summary>
/// Indicates that the color is in binary (black and white) format.
/// </summary>
Binary = 1 << 6,
/// <summary>
/// Indicates that the color is indexed using a palette.
/// </summary>
Indexed = 1 << 6,
Indexed = 1 << 7,
/// <summary>
/// Indicates that the color is in RGB (Red, Green, Blue) format.
/// </summary>
RGB = Red | Green | Blue | (1 << 7),
RGB = Red | Green | Blue | (1 << 8),
/// <summary>
/// Indicates that the color is in BGR (Blue, Green, Red) format.
/// </summary>
BGR = Blue | Green | Red | (1 << 8),
BGR = Blue | Green | Red | (1 << 9),
/// <summary>
/// Represents the Chrominance Blue component in YCbCr.
/// Represents the Chrominance Blue component.
/// </summary>
ChrominanceBlue = 1 << 9,
ChrominanceBlue = 1 << 10,
/// <summary>
/// Represents the Chrominance Red component in YCbCr.
/// Represents the Chrominance Red component.
/// </summary>
ChrominanceRed = 1 << 10,
ChrominanceRed = 1 << 11,
/// <summary>
/// Indicates that the color is in YCbCr (Luminance, Chrominance Blue, Chrominance Red) format.
/// </summary>
YCbCr = Luminance | ChrominanceBlue | ChrominanceRed,
YCbCr = Luminance | ChrominanceBlue | ChrominanceRed | (1 << 12),
/// <summary>
/// Represents the Cyan component in CMYK.
/// </summary>
Cyan = 1 << 11,
Cyan = 1 << 13,
/// <summary>
/// Represents the Magenta component in CMYK.
/// </summary>
Magenta = 1 << 12,
Magenta = 1 << 14,
/// <summary>
/// Represents the Yellow component in CMYK.
/// </summary>
Yellow = 1 << 13,
Yellow = 1 << 15,
/// <summary>
/// Represents the Key (black) component in CMYK and YCCK.
/// </summary>
Key = 1 << 14,
Key = 1 << 16,
/// <summary>
/// Indicates that the color is in CMYK (Cyan, Magenta, Yellow, Key) format.
@ -107,5 +112,5 @@ public enum PixelColorType
/// <summary>
/// Indicates that the color is of a type not specified in this enum.
/// </summary>
Other = 1 << 16
Other = 1 << 17
}

14
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;
}
}

6
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);
}

2
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);
}
}

20
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;
}
}

19
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));
}
}

14
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;

2
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)

4
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]

39
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));

Loading…
Cancel
Save