mirror of https://github.com/SixLabors/ImageSharp
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
148 lines
6.3 KiB
148 lines
6.3 KiB
// Copyright (c) Six Labors.
|
|
// Licensed under the Six Labors Split License.
|
|
|
|
using System.Buffers.Binary;
|
|
using SixLabors.ImageSharp.Formats;
|
|
using SixLabors.ImageSharp.Formats.Ani;
|
|
using SixLabors.ImageSharp.PixelFormats;
|
|
using static SixLabors.ImageSharp.Tests.TestImages.Ani;
|
|
|
|
namespace SixLabors.ImageSharp.Tests.Formats.Ani;
|
|
|
|
[Trait("format", "Ani")]
|
|
[ValidateDisposedMemoryAllocations]
|
|
public class AniDecoderTests
|
|
{
|
|
/// <summary>
|
|
/// Verifies that ANI animation steps and embedded CUR resolution variants are flattened with their ANI metadata.
|
|
/// </summary>
|
|
[Theory]
|
|
[WithFile(Work, PixelTypes.Rgba32, 17, 1, 6U, 6U)]
|
|
[WithFile(MultiFramesInEveryIconChunk, PixelTypes.Rgba32, 54, 3, 3U, 3U)]
|
|
[WithFile(Help, PixelTypes.Rgba32, 4, 1, 10U, 12U)]
|
|
public void AniDecoder_Decode(
|
|
TestImageProvider<Rgba32> provider,
|
|
int expectedFrameCount,
|
|
int variantsPerStep,
|
|
uint expectedDisplayRate,
|
|
uint expectedFrameDelay)
|
|
{
|
|
using Image<Rgba32> image = provider.GetImage(AniDecoder.Instance);
|
|
|
|
Assert.Equal(expectedFrameCount, image.Frames.Count);
|
|
Assert.Equal(expectedDisplayRate, image.Metadata.GetAniMetadata().DisplayRate);
|
|
|
|
for (int i = 0; i < image.Frames.Count; i++)
|
|
{
|
|
AniFrameMetadata metadata = image.Frames[i].Metadata.GetAniMetadata();
|
|
|
|
Assert.Equal((i / variantsPerStep) + 1, metadata.SequenceNumber);
|
|
Assert.Equal(expectedFrameDelay, metadata.FrameDelay);
|
|
Assert.Equal(AniFrameFormat.Cur, metadata.FrameFormat);
|
|
Assert.NotEqual(0, (int)metadata.BmpBitsPerPixel);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Verifies that identification exposes the same flattened ANI frame structure without decoding pixels.
|
|
/// </summary>
|
|
[Theory]
|
|
[InlineData(Work, 17)]
|
|
[InlineData(MultiFramesInEveryIconChunk, 54)]
|
|
[InlineData(Help, 4)]
|
|
public void AniDecoder_Identify(string path, int expectedFrameCount)
|
|
{
|
|
TestFile file = TestFile.Create(path);
|
|
using MemoryStream stream = new(file.Bytes, false);
|
|
|
|
ImageInfo info = AniDecoder.Instance.Identify(DecoderOptions.Default, stream);
|
|
|
|
Assert.Equal(expectedFrameCount, info.FrameMetadataCollection.Count);
|
|
|
|
for (int i = 0; i < info.FrameMetadataCollection.Count; i++)
|
|
{
|
|
Assert.True(info.FrameMetadataCollection[i].GetAniMetadata().SequenceNumber > 0);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Verifies that invalid sequence metadata follows the ancillary-segment integrity policy.
|
|
/// </summary>
|
|
[Fact]
|
|
public void AniDecoder_InvalidSequenceReference_FollowsIntegrityHandling()
|
|
{
|
|
byte[] data = TestFile.Create(Help).Bytes.ToArray();
|
|
int sequenceOffset = data.AsSpan().IndexOf("seq "u8);
|
|
Assert.True(sequenceOffset >= 0);
|
|
|
|
BinaryPrimitives.WriteUInt32LittleEndian(data.AsSpan(sequenceOffset + AniConstants.ChunkHeaderSize), uint.MaxValue);
|
|
|
|
using MemoryStream strictStream = new(data, false);
|
|
DecoderOptions strict = new() { SegmentIntegrityHandling = SegmentIntegrityHandling.Strict };
|
|
Assert.Throws<InvalidImageContentException>(() => AniDecoder.Instance.Decode<Rgba32>(strict, strictStream));
|
|
|
|
using MemoryStream ignoreStream = new(data, false);
|
|
DecoderOptions ignore = new() { SegmentIntegrityHandling = SegmentIntegrityHandling.IgnoreAncillary };
|
|
using Image<Rgba32> image = AniDecoder.Instance.Decode<Rgba32>(ignore, ignoreStream);
|
|
|
|
Assert.Equal(3, image.Frames.Count);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Verifies that ignored corrupt resources retain their sequence-table slot.
|
|
/// </summary>
|
|
[Fact]
|
|
public void AniDecoder_UnsupportedResource_FollowsIntegrityHandling()
|
|
{
|
|
byte[] data = TestFile.Create(Work).Bytes.ToArray();
|
|
int resourceOffset = data.AsSpan().IndexOf("icon"u8);
|
|
Assert.True(resourceOffset >= 0);
|
|
|
|
int iconTypeOffset = resourceOffset + AniConstants.ChunkHeaderSize + sizeof(ushort);
|
|
BinaryPrimitives.WriteUInt16LittleEndian(data.AsSpan(iconTypeOffset), ushort.MaxValue);
|
|
|
|
using MemoryStream strictStream = new(data, false);
|
|
DecoderOptions strict = new() { SegmentIntegrityHandling = SegmentIntegrityHandling.Strict };
|
|
Assert.Throws<InvalidImageContentException>(() => AniDecoder.Instance.Decode<Rgba32>(strict, strictStream));
|
|
|
|
using MemoryStream ignoreStream = new(data, false);
|
|
DecoderOptions ignore = new() { SegmentIntegrityHandling = SegmentIntegrityHandling.IgnoreImageData };
|
|
using Image<Rgba32> image = AniDecoder.Instance.Decode<Rgba32>(ignore, ignoreStream);
|
|
|
|
Assert.Equal(16, image.Frames.Count);
|
|
Assert.Equal(2, image.Frames.RootFrame.Metadata.GetAniMetadata().SequenceNumber);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Verifies that a malformed rate chunk follows the ancillary-segment integrity policy.
|
|
/// </summary>
|
|
[Fact]
|
|
public void AniDecoder_InvalidRateChunk_FollowsIntegrityHandling()
|
|
{
|
|
byte[] source = TestFile.Create(Help).Bytes.ToArray();
|
|
int rateOffset = source.AsSpan().IndexOf("rate"u8);
|
|
Assert.True(rateOffset >= 0);
|
|
|
|
int rateSizeOffset = rateOffset + sizeof(uint);
|
|
int rateSize = (int)BinaryPrimitives.ReadUInt32LittleEndian(source.AsSpan(rateSizeOffset));
|
|
int rateEnd = rateOffset + AniConstants.ChunkHeaderSize + rateSize;
|
|
byte[] data = new byte[source.Length + 2];
|
|
source.AsSpan(0, rateEnd).CopyTo(data);
|
|
source.AsSpan(rateEnd).CopyTo(data.AsSpan(rateEnd + 2));
|
|
BinaryPrimitives.WriteUInt32LittleEndian(data.AsSpan(rateSizeOffset), (uint)rateSize + 2);
|
|
BinaryPrimitives.WriteUInt32LittleEndian(data.AsSpan(sizeof(uint)), (uint)data.Length - 8);
|
|
|
|
using MemoryStream defaultStream = new(data, false);
|
|
using Image<Rgba32> image = AniDecoder.Instance.Decode<Rgba32>(DecoderOptions.Default, defaultStream);
|
|
|
|
Assert.Equal(4, image.Frames.Count);
|
|
foreach (ImageFrame<Rgba32> frame in image.Frames)
|
|
{
|
|
Assert.Equal(10U, frame.Metadata.GetAniMetadata().FrameDelay);
|
|
}
|
|
|
|
using MemoryStream strictStream = new(data, false);
|
|
DecoderOptions strict = new() { SegmentIntegrityHandling = SegmentIntegrityHandling.Strict };
|
|
Assert.Throws<InvalidImageContentException>(() => AniDecoder.Instance.Decode<Rgba32>(strict, strictStream));
|
|
}
|
|
}
|
|
|