Browse Source

finish decoder

pull/2899/head
Poker 1 year ago
parent
commit
e58010abea
No known key found for this signature in database GPG Key ID: C65A6AD457D5C8F8
  1. 38
      src/ImageSharp/Formats/Ani/AniChunk.cs
  2. 65
      src/ImageSharp/Formats/Ani/AniChunkType.cs
  3. 18
      src/ImageSharp/Formats/Ani/AniConfigurationModule.cs
  4. 25
      src/ImageSharp/Formats/Ani/AniConstants.cs
  5. 427
      src/ImageSharp/Formats/Ani/AniDecoderCore.cs
  6. 19
      src/ImageSharp/Formats/Ani/AniFormat.cs
  7. 70
      src/ImageSharp/Formats/Ani/AniFrameMetadata.cs
  8. 18
      src/ImageSharp/Formats/Ani/AniHeader.cs
  9. 30
      src/ImageSharp/Formats/Ani/AniImageFormatDetector.cs
  10. 47
      src/ImageSharp/Formats/Ani/AniMetadata.cs
  11. 2
      src/ImageSharp/Formats/Bmp/BmpRenderingIntent.cs
  12. 16
      src/ImageSharp/Formats/Icon/IconDir.cs
  13. 9
      src/ImageSharp/Formats/Icon/IconDirEntry.cs
  14. 20
      src/ImageSharp/Formats/Icon/IconEncoderCore.cs
  15. 2
      src/ImageSharp/Formats/Icon/IconFileType.cs
  16. 6
      src/ImageSharp/Formats/Webp/BitWriter/BitWriterBase.cs
  17. 2
      src/ImageSharp/Formats/Webp/Chunks/WebpVp8X.cs
  18. 2
      src/ImageSharp/Formats/Webp/Lossless/Vp8LEncoder.cs
  19. 2
      src/ImageSharp/Formats/Webp/Lossy/Vp8Encoder.cs
  20. 154
      src/ImageSharp/Formats/Webp/RiffHelper.cs
  21. 29
      src/ImageSharp/Formats/Webp/WebpConstants.cs
  22. 24
      src/ImageSharp/Formats/Webp/WebpImageFormatDetector.cs
  23. 20
      src/ImageSharp/Formats/_Generated/ImageMetadataExtensions.cs
  24. 57
      src/ImageSharp/IO/BufferedReadStream.cs
  25. 7
      tests/ImageSharp.Tests/Formats/Ani/AniDecoderTests.cs
  26. 3
      tests/ImageSharp.Tests/TestImages.cs
  27. 3
      tests/Images/Input/Ani/Help.ani
  28. 3
      tests/Images/Input/Ani/aero_busy.ani

38
src/ImageSharp/Formats/Ani/AniChunk.cs

@ -1,38 +0,0 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
#nullable disable
using System.Buffers;
using SixLabors.ImageSharp.Formats.Png;
namespace SixLabors.ImageSharp.Formats.Ani;
internal readonly struct AniChunk
{
public AniChunk(int length, AniChunkType type, IMemoryOwner<byte> data = null)
{
this.Length = length;
this.Type = type;
this.Data = data;
}
/// <summary>
/// Gets the length.
/// An unsigned integer giving the number of bytes in the chunk's
/// data field. The length counts only the data field, not itself,
/// the chunk type code, or the CRC. Zero is a valid length
/// </summary>
public int Length { get; }
/// <summary>
/// Gets the chunk type.
/// The value is the equal to the UInt32BigEndian encoding of its 4 ASCII characters.
/// </summary>
public AniChunkType Type { get; }
/// <summary>
/// Gets the data bytes appropriate to the chunk type, if any.
/// This field can be of zero length or null.
/// </summary>
public IMemoryOwner<byte> Data { get; }
}

65
src/ImageSharp/Formats/Ani/AniChunkType.cs

@ -5,7 +5,66 @@ namespace SixLabors.ImageSharp.Formats.Ani;
internal enum AniChunkType : uint
{
Seq = 0x73_65_71_20,
Rate = 0x72_61_74_65,
List = 0x4C_49_53_54
/// <summary>
/// "anih"
/// </summary>
AniH = 0x68_69_6E_61,
/// <summary>
/// "seq "
/// </summary>
Seq = 0x20_71_65_73,
/// <summary>
/// "rate"
/// </summary>
Rate = 0x65_74_61_72,
/// <summary>
/// "LIST"
/// </summary>
List = 0x54_53_49_4C
}
/// <summary>
/// ListType
/// </summary>
internal enum AniListType : uint
{
/// <summary>
/// "INFO" (ListType)
/// </summary>
Info = 0x4F_46_4E_49,
/// <summary>
/// "fram"
/// </summary>
Fram = 0x6D_61_72_66
}
/// <summary>
/// in "INFO"
/// </summary>
internal enum AniListInfoType : uint
{
/// <summary>
/// "INAM"
/// </summary>
INam = 0x4D_41_4E_49,
/// <summary>
/// "IART"
/// </summary>
IArt = 0x54_52_41_49
}
/// <summary>
/// in "Fram"
/// </summary>
internal enum AniListFrameType : uint
{
/// <summary>
/// "icon"
/// </summary>
Icon = 0x6E_6F_63_69
}

18
src/ImageSharp/Formats/Ani/AniConfigurationModule.cs

@ -0,0 +1,18 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
namespace SixLabors.ImageSharp.Formats.Ani;
/// <summary>
/// Registers the image encoders, decoders and mime type detectors for the Ico format.
/// </summary>
public sealed class AniConfigurationModule : IImageFormatConfigurationModule
{
/// <inheritdoc/>
public void Configure(Configuration configuration)
{
// configuration.ImageFormatsManager.SetEncoder(AniFormat.Instance, new AniEncoder());
configuration.ImageFormatsManager.SetDecoder(AniFormat.Instance, AniDecoder.Instance);
configuration.ImageFormatsManager.AddImageFormatDetector(new AniImageFormatDetector());
}
}

25
src/ImageSharp/Formats/Ani/AniConstants.cs

@ -8,14 +8,31 @@ internal static class AniConstants
/// <summary>
/// The list of mime types that equate to an ani.
/// </summary>
/// <remarks>
/// See <see href="https://en.wikipedia.org/wiki/ICO_(file_format)#MIME_type"/>
/// </remarks>
public static readonly IEnumerable<string> MimeTypes = [];
public static readonly IEnumerable<string> MimeTypes = ["application/x-navi-animation"];
/// <summary>
/// The list of file extensions that equate to an ani.
/// </summary>
public static readonly IEnumerable<string> FileExtensions = ["ani"];
/// <summary>
/// Gets the header bytes identifying an ani.
/// </summary>
public static ReadOnlySpan<byte> AniFormTypeFourCc => "ACON"u8;
/// <summary>
/// Gets the header bytes identifying an ani.
/// </summary>
public const uint AniFourCc = 0x41_43_4F_4E;
public static class ChunkFourCcs
{
public static ReadOnlySpan<byte> AniHeader => "anih"u8;
public static ReadOnlySpan<byte> Seq => "seq "u8;
public static ReadOnlySpan<byte> Rate => "rate"u8;
public static ReadOnlySpan<byte> Icon => "icon"u8;
}
}

427
src/ImageSharp/Formats/Ani/AniDecoderCore.cs

@ -2,23 +2,36 @@
// Licensed under the Six Labors Split License.
using System.Buffers;
using System.Buffers.Binary;
using System.Collections;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Text;
using SixLabors.ImageSharp.Formats.Bmp;
using SixLabors.ImageSharp.Formats.Cur;
using SixLabors.ImageSharp.Formats.Ico;
using SixLabors.ImageSharp.Formats.Icon;
using SixLabors.ImageSharp.Formats.Png;
using SixLabors.ImageSharp.Formats.Webp;
using SixLabors.ImageSharp.IO;
using SixLabors.ImageSharp.Memory.Internals;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.Memory.Internals;
using SixLabors.ImageSharp.Metadata;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Formats.Ani;
internal class AniDecoderCore : ImageDecoderCore
internal class AniDecoderCore(DecoderOptions options) : ImageDecoderCore(options)
{
private enum ListIconChunkType
{
Ico = 1,
Cur = 2,
Bmp = 3
}
/// <summary>
/// The general decoder options.
/// </summary>
private readonly Configuration configuration;
private readonly Configuration configuration = options.Configuration;
/// <summary>
/// The stream to decode from.
@ -27,157 +40,354 @@ internal class AniDecoderCore : ImageDecoderCore
private AniHeader header;
public AniDecoderCore(DecoderOptions options)
: base(options) => this.configuration = options.Configuration;
protected override Image<TPixel> Decode<TPixel>(BufferedReadStream stream, CancellationToken cancellationToken)
{
this.currentStream = stream;
this.ReadHeader();
Guard.IsTrue(this.currentStream.TryReadUnmanaged(out RiffOrListChunkHeader riffHeader), nameof(riffHeader), "Invalid RIFF header.");
long dataSize = riffHeader.Size;
long dataStartPosition = this.currentStream.Position;
ImageMetadata metadata = new();
AniMetadata aniMetadata = metadata.GetAniMetadata();
Image<TPixel>? image = null;
AniMetadata aniMetadata = this.ReadHeader(dataStartPosition, dataSize, metadata);
List<(ListIconChunkType Type, Image<TPixel> Image)> frames = [];
this.HandleRiffChunk(out Span<int> sequence, out Span<uint> rate, dataStartPosition, dataSize, aniMetadata, frames, DecodeFrameChunk);
Span<byte> buffer = stackalloc byte[20];
List<ImageFrame<TPixel>> list = [];
try
foreach (int i in sequence)
{
while (this.TryReadChunk(buffer, out AniChunk chunk))
(ListIconChunkType type, Image<TPixel>? img) = frames[i];
byte? encodingWidth = null;
byte? encodingHeight = null;
bool isRootFrame = true;
list.AddRange(img.Frames.Select(source =>
{
try
ImageFrame<TPixel> target = new(this.Options.Configuration, this.Dimensions);
for (int y = 0; y < source.Height; y++)
{
switch (chunk.Type)
{
case AniChunkType.Seq:
source.PixelBuffer.DangerousGetRowSpan(y).CopyTo(target.PixelBuffer.DangerousGetRowSpan(y));
}
break;
case AniChunkType.Rate:
switch (type)
{
case ListIconChunkType.Ico:
IcoFrameMetadata icoFrameMetadata = source.Metadata.GetIcoMetadata();
target.Metadata.SetFormatMetadata(IcoFormat.Instance, icoFrameMetadata);
if (isRootFrame)
{
encodingWidth ??= icoFrameMetadata.EncodingWidth;
encodingHeight ??= icoFrameMetadata.EncodingHeight;
}
break;
case ListIconChunkType.Cur:
CurFrameMetadata curFrameMetadata = source.Metadata.GetCurMetadata();
target.Metadata.SetFormatMetadata(CurFormat.Instance, curFrameMetadata);
if (isRootFrame)
{
encodingWidth ??= curFrameMetadata.EncodingWidth;
encodingHeight ??= curFrameMetadata.EncodingHeight;
}
break;
case ListIconChunkType.Bmp:
if (isRootFrame)
{
encodingWidth = Narrow(source.Width);
encodingHeight = Narrow(source.Height);
}
break;
default:
break;
}
break;
case AniChunkType.List:
isRootFrame = false;
break;
default:
break;
return target;
}));
ImageFrameMetadata rootFrameMetadata = img.Frames.RootFrame.Metadata;
AniFrameMetadata aniFrameMetadata = rootFrameMetadata.GetAniMetadata();
aniFrameMetadata.Rate = rate == default ? aniMetadata.DisplayRate : rate[i];
aniFrameMetadata.FrameCount = img.Frames.Count;
aniFrameMetadata.EncodingWidth = encodingWidth;
aniFrameMetadata.EncodingHeight = encodingHeight;
aniFrameMetadata.SubImageMetadata = img.Metadata;
aniMetadata.IconFrames.Add(rootFrameMetadata);
}
foreach ((ListIconChunkType _, Image<TPixel> img) in frames)
{
img.Dispose();
}
Image<TPixel> image = new(this.Options.Configuration, metadata, list);
return image;
void DecodeFrameChunk()
{
while (this.TryReadChunk(dataStartPosition, dataSize, out RiffChunkHeader chunk))
{
if ((AniListFrameType)chunk.FourCc is not AniListFrameType.Icon)
{
continue;
}
long endPosition = this.currentStream.Position + chunk.Size;
Image<TPixel>? frame = null;
ListIconChunkType type = default;
if (aniMetadata.Flags.HasFlag(AniHeaderFlags.IsIcon))
{
if (this.currentStream.TryReadUnmanaged(out IconDir dir))
{
this.currentStream.Position -= Unsafe.SizeOf<IconDir>();
switch (dir.Type)
{
case IconFileType.CUR:
frame = CurDecoder.Instance.Decode<TPixel>(this.Options, this.currentStream);
type = ListIconChunkType.Cur;
break;
case IconFileType.ICO:
frame = IcoDecoder.Instance.Decode<TPixel>(this.Options, this.currentStream);
type = ListIconChunkType.Ico;
break;
}
}
}
finally
else
{
chunk.Data?.Dispose();
frame = BmpDecoder.Instance.Decode<TPixel>(this.Options, this.currentStream);
type = ListIconChunkType.Bmp;
}
}
}
catch
{
image?.Dispose();
throw;
}
if (frame is not null)
{
frames.Add((type, frame));
this.Dimensions = new(Math.Max(this.Dimensions.Width, frame.Size.Width), Math.Max(this.Dimensions.Height, frame.Size.Height));
}
throw new NotImplementedException();
this.currentStream.Position = endPosition;
}
}
}
private void ReadSeq()
protected override ImageInfo Identify(BufferedReadStream stream, CancellationToken cancellationToken)
{
this.currentStream = stream;
}
Guard.IsTrue(this.currentStream.TryReadUnmanaged(out RiffOrListChunkHeader riffHeader), nameof(riffHeader), "Invalid RIFF header.");
long dataSize = riffHeader.Size;
long dataStartPosition = this.currentStream.Position;
private bool TryReadChunk(Span<byte> buffer, out AniChunk chunk)
{
if (!this.TryReadChunkLength(buffer, out int length))
{
// IEND
chunk = default;
return false;
}
ImageMetadata metadata = new();
AniMetadata aniMetadata = this.ReadHeader(dataStartPosition, dataSize, metadata);
List<(ListIconChunkType Type, ImageInfo Info)> infoList = [];
this.HandleRiffChunk(out Span<int> sequence, out Span<uint> rate, dataStartPosition, dataSize, aniMetadata, infoList, IdentifyFrameChunk);
ImageInfo imageInfo = new(this.Dimensions, metadata, (IReadOnlyList<ImageFrameMetadata>)aniMetadata.IconFrames);
while (length < 0)
foreach (int i in sequence)
{
// Not a valid chunk so try again until we reach a known chunk.
if (!this.TryReadChunkLength(buffer, out length))
(ListIconChunkType type, ImageInfo info) = infoList[i];
ImageFrameMetadata rootFrameMetadata = imageInfo.FrameMetadataCollection is [var first, ..] ? first : new();
AniFrameMetadata aniFrameMetadata = rootFrameMetadata.GetAniMetadata();
aniFrameMetadata.Rate = rate == default ? aniMetadata.DisplayRate : rate[i];
aniFrameMetadata.FrameCount = info.FrameMetadataCollection.Count;
aniFrameMetadata.EncodingWidth = type switch
{
// IEND
chunk = default;
return false;
}
ListIconChunkType.Bmp => Narrow(info.Width),
ListIconChunkType.Cur => rootFrameMetadata.GetCurMetadata().EncodingWidth,
ListIconChunkType.Ico => rootFrameMetadata.GetIcoMetadata().EncodingWidth,
_ => null
};
aniFrameMetadata.EncodingHeight = type switch
{
ListIconChunkType.Bmp => Narrow(info.Height),
ListIconChunkType.Cur => rootFrameMetadata.GetCurMetadata().EncodingHeight,
ListIconChunkType.Ico => rootFrameMetadata.GetIcoMetadata().EncodingHeight,
_ => null
};
aniFrameMetadata.SubImageMetadata = info.Metadata;
aniMetadata.IconFrames.Add(rootFrameMetadata);
}
AniChunkType type = this.ReadChunkType(buffer);
return imageInfo;
// A chunk might report a length that exceeds the length of the stream.
// Take the minimum of the two values to ensure we don't read past the end of the stream.
long position = this.currentStream.Position;
chunk = new AniChunk(
length: (int)Math.Min(length, this.currentStream.Length - position),
type: type,
data: this.ReadChunkData(length));
void IdentifyFrameChunk()
{
while (this.TryReadChunk(dataStartPosition, dataSize, out RiffChunkHeader chunk))
{
if ((AniListFrameType)chunk.FourCc is not AniListFrameType.Icon)
{
continue;
}
return true;
}
long endPosition = this.currentStream.Position + chunk.Size;
ImageInfo? info = null;
ListIconChunkType type = default;
if (aniMetadata.Flags.HasFlag(AniHeaderFlags.IsIcon))
{
if (this.currentStream.TryReadUnmanaged(out IconDir dir))
{
this.currentStream.Position -= Unsafe.SizeOf<IconDir>();
switch (dir.Type)
{
case IconFileType.CUR:
info = CurDecoder.Instance.Identify(this.Options, this.currentStream);
type = ListIconChunkType.Cur;
break;
case IconFileType.ICO:
info = IcoDecoder.Instance.Identify(this.Options, this.currentStream);
type = ListIconChunkType.Ico;
break;
}
}
}
else
{
info = BmpDecoder.Instance.Identify(this.Options, this.currentStream);
type = ListIconChunkType.Bmp;
}
if (info is not null)
{
infoList.Add((type, info));
this.Dimensions = new(Math.Max(this.Dimensions.Width, info.Size.Width), Math.Max(this.Dimensions.Height, info.Size.Height));
}
protected override ImageInfo Identify(BufferedReadStream stream, CancellationToken cancellationToken)
{
throw new NotImplementedException();
this.currentStream.Position = endPosition;
}
}
}
private void ReadHeader()
private AniMetadata ReadHeader(long dataStartPosition, long dataSize, ImageMetadata metadata)
{
// Skip the identifier
this.currentStream.Skip(12);
Span<byte> buffer = stackalloc byte[36];
_ = this.currentStream.Read(buffer);
this.header = AniHeader.Parse(buffer);
}
if (!this.TryReadChunk(dataStartPosition, dataSize, out RiffChunkHeader riffChunkHeader) ||
(AniChunkType)riffChunkHeader.FourCc is not AniChunkType.AniH)
{
Guard.IsTrue(false, nameof(riffChunkHeader), "Missing ANIH chunk.");
}
private void ReadSeq(Stream stream)
{
Span<byte> buffer = stackalloc byte[4];
int length = BinaryPrimitives.ReadInt32BigEndian(buffer);
AniMetadata aniMetadata = metadata.GetAniMetadata();
if (this.currentStream.TryReadUnmanaged(out AniHeader result))
{
this.header = result;
aniMetadata.Width = result.Width;
aniMetadata.Height = result.Height;
aniMetadata.BitCount = result.BitCount;
aniMetadata.Planes = result.Planes;
aniMetadata.DisplayRate = result.DisplayRate;
aniMetadata.Flags = result.Flags;
}
return aniMetadata;
}
/// <summary>
/// Attempts to read the length of the next chunk.
/// Call <see cref="HandleRiffChunk"/> <br/>
/// -> Call <see cref="HandleListChunk"/> <br/>
/// -> Call <paramref name="handleFrameChunk"/>
/// </summary>
/// <param name="buffer">Temporary buffer.</param>
/// <param name="result">The result length. If the return type is <see langword="false"/> this parameter is passed uninitialized.</param>
/// <returns>
/// Whether the length was read.
/// </returns>
[MethodImpl(InliningOptions.ShortMethod)]
private bool TryReadChunkLength(Span<byte> buffer, out int result)
private void HandleRiffChunk(out Span<int> sequence, out Span<uint> rate, long dataStartPosition, long dataSize, AniMetadata aniMetadata, ICollection totalFrameCount, Action handleFrameChunk)
{
if (this.currentStream.Read(buffer, 0, 4) == 4)
sequence = default;
rate = default;
while (this.TryReadChunk(dataStartPosition, dataSize, out RiffChunkHeader chunk))
{
result = BinaryPrimitives.ReadInt32BigEndian(buffer);
switch ((AniChunkType)chunk.FourCc)
{
case AniChunkType.Seq:
{
using IMemoryOwner<byte> data = this.ReadChunkData(chunk.Size);
sequence = MemoryMarshal.Cast<byte, int>(data.Memory.Span);
break;
}
return true;
case AniChunkType.Rate:
{
using IMemoryOwner<byte> data = this.ReadChunkData(chunk.Size);
rate = MemoryMarshal.Cast<byte, uint>(data.Memory.Span);
break;
}
case AniChunkType.List:
this.HandleListChunk(dataStartPosition, dataSize, aniMetadata, handleFrameChunk);
break;
default:
break;
}
}
result = 0;
return false;
if (sequence == default)
{
sequence = Enumerable.Range(0, totalFrameCount.Count).ToArray();
}
}
/// <summary>
/// Identifies the chunk type from the chunk.
/// </summary>
/// <param name="buffer">Temporary buffer.</param>
/// <exception cref="ImageFormatException">
/// Thrown if the input stream is not valid.
/// </exception>
[MethodImpl(InliningOptions.ShortMethod)]
private AniChunkType ReadChunkType(Span<byte> buffer)
private void HandleListChunk(long dataStartPosition, long dataSize, AniMetadata aniMetadata, Action handleFrameChunk)
{
if (this.currentStream.Read(buffer, 0, 4) == 4)
if (!this.currentStream.TryReadUnmanaged(out uint listType))
{
return;
}
switch ((AniListType)listType)
{
return (AniChunkType)BinaryPrimitives.ReadUInt32BigEndian(buffer);
case AniListType.Fram:
{
handleFrameChunk();
break;
}
case AniListType.Info:
{
while (this.TryReadChunk(dataStartPosition, dataSize, out RiffChunkHeader chunk))
{
switch ((AniListInfoType)chunk.FourCc)
{
case AniListInfoType.INam:
{
using IMemoryOwner<byte> data = this.ReadChunkData(chunk.Size);
aniMetadata.Name = Encoding.ASCII.GetString(data.Memory.Span).TrimEnd('\0');
break;
}
case AniListInfoType.IArt:
{
using IMemoryOwner<byte> data = this.ReadChunkData(chunk.Size);
aniMetadata.Artist = Encoding.ASCII.GetString(data.Memory.Span).TrimEnd('\0');
break;
}
default:
break;
}
}
break;
}
}
}
PngThrowHelper.ThrowInvalidChunkType();
private bool TryReadChunk(long startPosition, long size, out RiffChunkHeader chunk)
{
if (this.currentStream.Position - startPosition >= size)
{
chunk = default;
return false;
}
// The IDE cannot detect the throw here.
return default;
return this.currentStream.TryReadUnmanaged(out chunk);
}
/// <summary>
@ -185,9 +395,9 @@ internal class AniDecoderCore : ImageDecoderCore
/// </summary>
/// <param name="length">The length of the chunk data to read.</param>
[MethodImpl(InliningOptions.ShortMethod)]
private IMemoryOwner<byte> ReadChunkData(int length)
private IMemoryOwner<byte> ReadChunkData(uint length)
{
if (length == 0)
if (length is 0)
{
return new BasicArrayBuffer<byte>([]);
}
@ -195,12 +405,13 @@ internal class AniDecoderCore : ImageDecoderCore
// We rent the buffer here to return it afterwards in Decode()
// We don't want to throw a degenerated memory exception here as we want to allow partial decoding
// so limit the length.
length = (int)Math.Min(length, this.currentStream.Length - this.currentStream.Position);
IMemoryOwner<byte> buffer = this.configuration.MemoryAllocator.Allocate<byte>(length, AllocationOptions.Clean);
int len = (int)Math.Min(length, this.currentStream.Length - this.currentStream.Position);
IMemoryOwner<byte> buffer = this.configuration.MemoryAllocator.Allocate<byte>(len, AllocationOptions.Clean);
this.currentStream.Read(buffer.GetSpan(), 0, length);
this.currentStream.Read(buffer.GetSpan(), 0, len);
return buffer;
}
private static byte Narrow(int value) => value > byte.MaxValue ? (byte)0 : (byte)value;
}

19
src/ImageSharp/Formats/Ani/AniFormat.cs

@ -1,16 +1,12 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using SixLabors.ImageSharp.Formats.Ico;
namespace SixLabors.ImageSharp.Formats.Ani;
/// <summary>
/// Registers the image encoders, decoders and mime type detectors for the bmp format.
/// </summary>
public sealed class AniFormat : IImageFormat<AniMetadata>
public sealed class AniFormat : IImageFormat<AniMetadata, AniFrameMetadata>
{
/// <summary>
/// Gets the shared instance.
@ -18,17 +14,20 @@ public sealed class AniFormat : IImageFormat<AniMetadata>
public static AniFormat Instance { get; } = new();
/// <inheritdoc/>
public AniMetadata CreateDefaultFormatMetadata() => throw new NotImplementedException();
public string Name => "ANI";
/// <inheritdoc/>
public string Name => "ANI";
public string DefaultMimeType => "application/x-navi-animation";
/// <inheritdoc/>
public IEnumerable<string> MimeTypes => AniConstants.MimeTypes;
/// <inheritdoc/>
public string DefaultMimeType { get; }
public IEnumerable<string> FileExtensions => AniConstants.FileExtensions;
/// <inheritdoc/>
public IEnumerable<string> MimeTypes { get; }
public AniMetadata CreateDefaultFormatMetadata() => new();
/// <inheritdoc/>
public IEnumerable<string> FileExtensions { get; }
public AniFrameMetadata CreateDefaultFormatFrameMetadata() => new();
}

70
src/ImageSharp/Formats/Ani/AniFrameMetadata.cs

@ -0,0 +1,70 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using SixLabors.ImageSharp.Metadata;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Formats.Ani;
/// <summary>
/// Provides Ani specific metadata information for the image.
/// </summary>
public class AniFrameMetadata : IFormatFrameMetadata<AniFrameMetadata>
{
/// <summary>
/// Initializes a new instance of the <see cref="AniFrameMetadata"/> class.
/// </summary>
public AniFrameMetadata()
{
}
/// <summary>
/// Gets or sets the display time for this frame (in 1/60 seconds)
/// </summary>
public uint Rate { get; set; }
/// <summary>
/// Gets or sets the frames count of **one** "icon" chunk.
/// </summary>
public int FrameCount { get; set; } = 1;
/// <summary>
/// Gets or sets the encoding width. <br />
/// Can be any number between 0 and 255. Value 0 means a frame height of 256 pixels or greater.
/// </summary>
public byte? EncodingWidth { get; set; }
/// <summary>
/// Gets or sets the encoding height. <br />
/// Can be any number between 0 and 255. Value 0 means a frame height of 256 pixels or greater.
/// </summary>
public byte? EncodingHeight { get; set; }
/// <summary>
/// Gets or sets the <see cref="ImageMetadata"/> of one "icon" chunk.
/// </summary>
public ImageMetadata? SubImageMetadata { get; set; }
/// <inheritdoc/>
public static AniFrameMetadata FromFormatConnectingFrameMetadata(FormatConnectingFrameMetadata metadata) =>
new()
{
Rate = (uint)metadata.Duration.TotalSeconds * 60
};
/// <inheritdoc/>
IDeepCloneable IDeepCloneable.DeepClone() => new AniFrameMetadata { Rate = this.Rate };
/// <inheritdoc/>
public FormatConnectingFrameMetadata ToFormatConnectingFrameMetadata() => new FormatConnectingFrameMetadata() { Duration = TimeSpan.FromSeconds(this.Rate / 60d) };
/// <inheritdoc/>
public void AfterFrameApply<TPixel>(ImageFrame<TPixel> source, ImageFrame<TPixel> destination)
where TPixel : unmanaged, IPixel<TPixel>
{
throw new NotImplementedException();
}
/// <inheritdoc/>
public AniFrameMetadata DeepClone() => new() { Rate = this.Rate };
}

18
src/ImageSharp/Formats/Ani/AniHeader.cs

@ -1,11 +1,11 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace SixLabors.ImageSharp.Formats.Ani;
[StructLayout(LayoutKind.Sequential, Pack = 1, Size = 36)]
internal readonly struct AniHeader
{
public uint Size { get; }
@ -26,16 +26,24 @@ internal readonly struct AniHeader
public AniHeaderFlags Flags { get; }
public static AniHeader Parse(in ReadOnlySpan<byte> data)
=> MemoryMarshal.Cast<byte, AniHeader>(data)[0];
public static ref AniHeader Parse(ReadOnlySpan<byte> data) => ref Unsafe.As<byte, AniHeader>(ref MemoryMarshal.GetReference(data));
public readonly unsafe void WriteTo(in Stream stream)
=> stream.Write(MemoryMarshal.Cast<AniHeader, byte>([this]));
public void WriteTo(in Stream stream) => stream.Write(MemoryMarshal.AsBytes(MemoryMarshal.CreateReadOnlySpan(in this, 1)));
}
/// <summary>
/// Flags for the ANI header.
/// </summary>
[Flags]
public enum AniHeaderFlags : uint
{
/// <summary>
/// If set, the ANI file's "icon" chunk contains an ICO or CUR file, otherwise it contains a BMP file.
/// </summary>
IsIcon = 1,
/// <summary>
/// If set, the ANI file contains a "seq " chunk.
/// </summary>
ContainsSeq = 2
}

30
src/ImageSharp/Formats/Ani/AniImageFormatDetector.cs

@ -0,0 +1,30 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System.Diagnostics.CodeAnalysis;
using SixLabors.ImageSharp.Formats.Webp;
namespace SixLabors.ImageSharp.Formats.Ani;
/// <summary>
/// Detects ico file headers.
/// </summary>
public class AniImageFormatDetector : IImageFormatDetector
{
/// <inheritdoc/>
public int HeaderSize => RiffOrListChunkHeader.HeaderSize;
/// <inheritdoc/>
public bool TryDetectFormat(ReadOnlySpan<byte> header, [NotNullWhen(true)] out IImageFormat? format)
{
format = this.IsSupportedFileFormat(header) ? AniFormat.Instance : null;
return format is not null;
}
private bool IsSupportedFileFormat(ReadOnlySpan<byte> header)
=> header.Length >= this.HeaderSize && RiffOrListChunkHeader.Parse(header) is
{
IsRiff: true,
FormType: AniConstants.AniFourCc
};
}

47
src/ImageSharp/Formats/Ani/AniMetadata.cs

@ -1,7 +1,7 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using SixLabors.ImageSharp.Formats.Ico;
using SixLabors.ImageSharp.Metadata;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Formats.Ani;
@ -11,6 +11,51 @@ namespace SixLabors.ImageSharp.Formats.Ani;
/// </summary>
public class AniMetadata : IFormatMetadata<AniMetadata>
{
/// <summary>
/// Gets or sets the width of frames in the animation.
/// </summary>
public uint Width { get; set; }
/// <summary>
/// Gets or sets the height of frames in the animation.
/// </summary>
public uint Height { get; set; }
/// <summary>
/// Gets or sets the number of bits per pixel.
/// </summary>
public uint BitCount { get; set; }
/// <summary>
/// Gets or sets the number of frames in the animation.
/// </summary>
public uint Planes { get; set; }
/// <summary>
/// Gets or sets the default display rate of frames in the animation.
/// </summary>
public uint DisplayRate { get; set; }
/// <summary>
/// Gets or sets the flags for the ANI header.
/// </summary>
public AniHeaderFlags Flags { get; set; }
/// <summary>
/// Gets or sets the name of the ANI file.
/// </summary>
public string? Name { get; set; }
/// <summary>
/// Gets or sets the artist of the ANI file.
/// </summary>
public string? Artist { get; set; }
/// <summary>
/// Gets or sets the <see cref="ImageFrameMetadata"/> each "icon" chunk in ANI file.
/// </summary>
public IList<ImageFrameMetadata> IconFrames { get; set; } = [];
/// <summary>
/// Initializes a new instance of the <see cref="AniMetadata"/> class.
/// </summary>

2
src/ImageSharp/Formats/Bmp/BmpRenderingIntent.cs

@ -17,7 +17,7 @@ internal enum BmpRenderingIntent
/// <summary>
/// Maintains saturation. Used for business charts and other situations in which undithered colors are required.
/// </summary>
LCS_GM_BUSINESS = 1,
LCS_GM_BUSINESS = 1,
/// <summary>
/// Maintains colorimetric match. Used for graphic designs and named colors.

16
src/ImageSharp/Formats/Icon/IconDir.cs

@ -1,6 +1,7 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace SixLabors.ImageSharp.Formats.Icon;
@ -25,19 +26,14 @@ internal struct IconDir(ushort reserved, IconFileType type, ushort count)
/// </summary>
public ushort Count = count;
public IconDir(IconFileType type)
: this(type, 0)
{
}
public IconDir(IconFileType type, ushort count)
public IconDir(IconFileType type, ushort count = 0)
: this(0, type, count)
{
}
public static IconDir Parse(ReadOnlySpan<byte> data)
=> MemoryMarshal.Cast<byte, IconDir>(data)[0];
public static ref IconDir Parse(ReadOnlySpan<byte> data)
=> ref Unsafe.As<byte, IconDir>(ref MemoryMarshal.GetReference(data));
public readonly unsafe void WriteTo(Stream stream)
=> stream.Write(MemoryMarshal.Cast<IconDir, byte>([this]));
public readonly void WriteTo(Stream stream)
=> stream.Write(MemoryMarshal.AsBytes(MemoryMarshal.CreateReadOnlySpan(in this, 1)));
}

9
src/ImageSharp/Formats/Icon/IconDirEntry.cs

@ -1,6 +1,7 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace SixLabors.ImageSharp.Formats.Icon;
@ -52,9 +53,9 @@ internal struct IconDirEntry
/// </summary>
public uint ImageOffset;
public static IconDirEntry Parse(in ReadOnlySpan<byte> data)
=> MemoryMarshal.Cast<byte, IconDirEntry>(data)[0];
public static ref IconDirEntry Parse(in ReadOnlySpan<byte> data)
=> ref Unsafe.As<byte, IconDirEntry>(ref MemoryMarshal.GetReference(data));
public readonly unsafe void WriteTo(in Stream stream)
=> stream.Write(MemoryMarshal.Cast<IconDirEntry, byte>([this]));
public readonly void WriteTo(in Stream stream)
=> stream.Write(MemoryMarshal.AsBytes(MemoryMarshal.CreateReadOnlySpan(in this, 1)));
}

20
src/ImageSharp/Formats/Icon/IconEncoderCore.cs

@ -120,17 +120,17 @@ internal abstract class IconEncoderCore
this.entries = this.iconFileType switch
{
IconFileType.ICO =>
image.Frames.Select(i =>
{
IcoFrameMetadata metadata = i.Metadata.GetIcoMetadata();
return new EncodingFrameMetadata(metadata.Compression, metadata.BmpBitsPerPixel, metadata.ColorTable, metadata.ToIconDirEntry(i.Size));
}).ToArray(),
image.Frames.Select(i =>
{
IcoFrameMetadata metadata = i.Metadata.GetIcoMetadata();
return new EncodingFrameMetadata(metadata.Compression, metadata.BmpBitsPerPixel, metadata.ColorTable, metadata.ToIconDirEntry(i.Size));
}).ToArray(),
IconFileType.CUR =>
image.Frames.Select(i =>
{
CurFrameMetadata metadata = i.Metadata.GetCurMetadata();
return new EncodingFrameMetadata(metadata.Compression, metadata.BmpBitsPerPixel, metadata.ColorTable, metadata.ToIconDirEntry(i.Size));
}).ToArray(),
image.Frames.Select(i =>
{
CurFrameMetadata metadata = i.Metadata.GetCurMetadata();
return new EncodingFrameMetadata(metadata.Compression, metadata.BmpBitsPerPixel, metadata.ColorTable, metadata.ToIconDirEntry(i.Size));
}).ToArray(),
_ => throw new NotSupportedException(),
};
}

2
src/ImageSharp/Formats/Icon/IconFileType.cs

@ -16,5 +16,5 @@ internal enum IconFileType : ushort
/// <summary>
/// CUR file
/// </summary>
CUR = 2,
CUR = 2
}

6
src/ImageSharp/Formats/Webp/BitWriter/BitWriterBase.cs

@ -100,7 +100,7 @@ internal abstract class BitWriterBase
bool hasAnimation)
{
// Write file size later
RiffHelper.BeginWriteRiffFile(stream, WebpConstants.WebpFourCc);
RiffHelper.BeginWriteRiff(stream, WebpConstants.WebpFormTypeFourCc);
// Write VP8X, header if necessary.
WebpVp8X vp8x = default;
@ -151,7 +151,7 @@ internal abstract class BitWriterBase
RiffHelper.WriteChunk(stream, (uint)WebpChunkType.Xmp, xmpProfile.Data);
}
RiffHelper.EndWriteRiffFile(stream, in vp8x, updateVp8x, initialPosition);
RiffHelper.EndWriteVp8X(stream, in vp8x, updateVp8x, initialPosition);
}
/// <summary>
@ -189,7 +189,7 @@ internal abstract class BitWriterBase
stream.WriteByte(flags);
stream.Write(dataBytes);
RiffHelper.EndWriteChunk(stream, pos);
RiffHelper.EndWriteChunk(stream, pos, 2);
}
/// <summary>

2
src/ImageSharp/Formats/Webp/Chunks/WebpVp8X.cs

@ -127,6 +127,6 @@ internal readonly struct WebpVp8X : IEquatable<WebpVp8X>
WebpChunkParsingUtils.WriteUInt24LittleEndian(stream, this.Width - 1);
WebpChunkParsingUtils.WriteUInt24LittleEndian(stream, this.Height - 1);
RiffHelper.EndWriteChunk(stream, pos);
RiffHelper.EndWriteChunk(stream, pos, 2);
}
}

2
src/ImageSharp/Formats/Webp/Lossless/Vp8LEncoder.cs

@ -325,7 +325,7 @@ internal class Vp8LEncoder : IDisposable
if (hasAnimation)
{
RiffHelper.EndWriteChunk(stream, prevPosition);
RiffHelper.EndWriteChunk(stream, prevPosition, 2);
}
return hasAlpha;

2
src/ImageSharp/Formats/Webp/Lossy/Vp8Encoder.cs

@ -517,7 +517,7 @@ internal class Vp8Encoder : IDisposable
if (hasAnimation)
{
RiffHelper.EndWriteChunk(stream, prevPosition);
RiffHelper.EndWriteChunk(stream, prevPosition, 2);
}
}
finally

154
src/ImageSharp/Formats/Webp/RiffHelper.cs

@ -2,138 +2,122 @@
// Licensed under the Six Labors Split License.
using System.Buffers.Binary;
using System.Text;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using SixLabors.ImageSharp.Formats.Webp.Chunks;
namespace SixLabors.ImageSharp.Formats.Webp;
internal static class RiffHelper
{
/// <summary>
/// The header bytes identifying RIFF file.
/// </summary>
private const uint RiffFourCc = 0x52_49_46_46;
public static void WriteChunk(Stream stream, uint fourCc, ReadOnlySpan<byte> data)
{
long pos = BeginWriteChunk(stream, fourCc);
stream.Write(data);
EndWriteChunk(stream, pos);
}
public static void WriteRiffFile(Stream stream, string formType, Action<Stream> func) =>
WriteChunk(stream, RiffFourCc, s =>
{
s.Write(Encoding.ASCII.GetBytes(formType));
func(s);
});
public static void WriteChunk<TStruct>(Stream stream, uint fourCc, in TStruct chunk)
where TStruct : unmanaged =>
WriteChunk(stream, fourCc, MemoryMarshal.AsBytes(MemoryMarshal.CreateReadOnlySpan(in chunk, 1)));
public static void WriteChunk(Stream stream, uint fourCc, Action<Stream> func)
public static long BeginWriteChunk(Stream stream, ReadOnlySpan<byte> fourCc)
{
Span<byte> buffer = stackalloc byte[4];
// write the fourCC
BinaryPrimitives.WriteUInt32BigEndian(buffer, fourCc);
stream.Write(buffer);
stream.Write(fourCc);
long sizePosition = stream.Position;
stream.Position += 4;
func(stream);
long position = stream.Position;
uint dataSize = (uint)(position - sizePosition - 4);
// padding
if (dataSize % 2 == 1)
{
stream.WriteByte(0);
position++;
}
// Leaving the place for the size
stream.Position += 4;
BinaryPrimitives.WriteUInt32LittleEndian(buffer, dataSize);
stream.Position = sizePosition;
stream.Write(buffer);
stream.Position = position;
return sizePosition;
}
public static void WriteChunk(Stream stream, uint fourCc, ReadOnlySpan<byte> data)
public static long BeginWriteChunk(Stream stream, uint fourCc)
{
Span<byte> buffer = stackalloc byte[4];
// write the fourCC
BinaryPrimitives.WriteUInt32BigEndian(buffer, fourCc);
stream.Write(buffer);
uint size = (uint)data.Length;
BinaryPrimitives.WriteUInt32LittleEndian(buffer, size);
stream.Write(buffer);
stream.Write(data);
// padding
if (size % 2 is 1)
{
stream.WriteByte(0);
}
return BeginWriteChunk(stream, buffer);
}
public static unsafe void WriteChunk<TStruct>(Stream stream, uint fourCc, in TStruct chunk)
where TStruct : unmanaged
public static long BeginWriteRiff(Stream stream, ReadOnlySpan<byte> formType)
{
fixed (TStruct* ptr = &chunk)
{
WriteChunk(stream, fourCc, new Span<byte>(ptr, sizeof(TStruct)));
}
long sizePosition = BeginWriteChunk(stream, "RIFF"u8);
stream.Write(formType);
return sizePosition;
}
public static long BeginWriteChunk(Stream stream, uint fourCc)
public static long BeginWriteList(Stream stream, ReadOnlySpan<byte> listType)
{
Span<byte> buffer = stackalloc byte[4];
// write the fourCC
BinaryPrimitives.WriteUInt32BigEndian(buffer, fourCc);
stream.Write(buffer);
long sizePosition = stream.Position;
stream.Position += 4;
long sizePosition = BeginWriteChunk(stream, "LIST"u8);
stream.Write(listType);
return sizePosition;
}
public static void EndWriteChunk(Stream stream, long sizePosition)
public static void EndWriteChunk(Stream stream, long sizePosition, int alignment = 1)
{
Span<byte> buffer = stackalloc byte[4];
Guard.MustBeGreaterThan(alignment, 0, nameof(alignment));
long position = stream.Position;
long currentPosition = stream.Position;
uint dataSize = (uint)(position - sizePosition - 4);
uint dataSize = (uint)(currentPosition - sizePosition - 4);
// padding
if (dataSize % 2 is 1)
// Add padding
while (dataSize % alignment is not 0)
{
stream.WriteByte(0);
position++;
dataSize++;
currentPosition++;
}
// Add the size of the encoded file to the Riff header.
BinaryPrimitives.WriteUInt32LittleEndian(buffer, dataSize);
stream.Position = sizePosition;
stream.Write(buffer);
stream.Position = position;
stream.Write(MemoryMarshal.CreateReadOnlySpan(ref Unsafe.As<uint, byte>(ref dataSize), sizeof(uint)));
stream.Position = currentPosition;
}
public static long BeginWriteRiffFile(Stream stream, string formType)
public static void EndWriteVp8X(Stream stream, in WebpVp8X vp8X, bool updateVp8X, long initPosition)
{
long sizePosition = BeginWriteChunk(stream, RiffFourCc);
stream.Write(Encoding.ASCII.GetBytes(formType));
return sizePosition;
}
public static void EndWriteRiffFile(Stream stream, in WebpVp8X vp8x, bool updateVp8x, long sizePosition)
{
EndWriteChunk(stream, sizePosition + 4);
// Jump through "RIFF" fourCC
EndWriteChunk(stream, initPosition + 4, 2);
// Write the VP8X chunk if necessary.
if (updateVp8x)
if (updateVp8X)
{
long position = stream.Position;
stream.Position = sizePosition + 12;
vp8x.WriteTo(stream);
stream.Position = initPosition + 12;
vp8X.WriteTo(stream);
stream.Position = position;
}
}
}
internal readonly struct RiffChunkHeader
{
public readonly uint FourCc;
public ReadOnlySpan<byte> FourCcBytes => MemoryMarshal.CreateReadOnlySpan(ref Unsafe.As<uint, byte>(ref Unsafe.AsRef(in this.FourCc)), sizeof(uint));
public readonly uint Size;
}
internal readonly struct RiffOrListChunkHeader
{
public const int HeaderSize = 12;
public readonly uint FourCc;
public readonly uint Size;
public readonly uint FormType;
public ReadOnlySpan<byte> FourCcBytes => MemoryMarshal.CreateReadOnlySpan(ref Unsafe.As<uint, byte>(ref Unsafe.AsRef(in this.FourCc)), sizeof(uint));
public bool IsRiff => this.FourCc is 0x52_49_46_46; // "RIFF"
public bool IsList => this.FourCc is 0x4C_49_53_54; // "LIST"
public static ref RiffOrListChunkHeader Parse(ReadOnlySpan<byte> data) => ref Unsafe.As<byte, RiffOrListChunkHeader>(ref MemoryMarshal.GetReference(data));
}

29
src/ImageSharp/Formats/Webp/WebpConstants.cs

@ -29,36 +29,19 @@ internal static class WebpConstants
};
/// <summary>
/// Signature byte which identifies a VP8L header.
/// Gets the header bytes identifying a Webp.
/// </summary>
public const byte Vp8LHeaderMagicByte = 0x2F;
public static ReadOnlySpan<byte> WebpFormTypeFourCc => "WEBP"u8;
/// <summary>
/// The header bytes identifying RIFF file.
/// Gets the header bytes identifying a Webp.
/// </summary>
public static readonly byte[] RiffFourCc =
{
0x52, // R
0x49, // I
0x46, // F
0x46 // F
};
public const uint WebpFourCc = 0x57_45_42_50;
/// <summary>
/// The header bytes identifying a Webp.
/// </summary>
public static readonly byte[] WebpHeader =
{
0x57, // W
0x45, // E
0x42, // B
0x50 // P
};
/// <summary>
/// The header bytes identifying a Webp.
/// Signature byte which identifies a VP8L header.
/// </summary>
public const string WebpFourCc = "WEBP";
public const byte Vp8LHeaderMagicByte = 0x2F;
/// <summary>
/// 3 bits reserved for version.

24
src/ImageSharp/Formats/Webp/WebpImageFormatDetector.cs

@ -11,7 +11,7 @@ namespace SixLabors.ImageSharp.Formats.Webp;
public sealed class WebpImageFormatDetector : IImageFormatDetector
{
/// <inheritdoc />
public int HeaderSize => 12;
public int HeaderSize => RiffOrListChunkHeader.HeaderSize;
/// <inheritdoc />
public bool TryDetectFormat(ReadOnlySpan<byte> header, [NotNullWhen(true)] out IImageFormat? format)
@ -21,21 +21,9 @@ public sealed class WebpImageFormatDetector : IImageFormatDetector
}
private bool IsSupportedFileFormat(ReadOnlySpan<byte> header)
=> header.Length >= this.HeaderSize && IsRiffContainer(header) && IsWebpFile(header);
/// <summary>
/// Checks, if the header starts with a valid RIFF FourCC.
/// </summary>
/// <param name="header">The header bytes.</param>
/// <returns>True, if its a valid RIFF FourCC.</returns>
private static bool IsRiffContainer(ReadOnlySpan<byte> header)
=> header[..4].SequenceEqual(WebpConstants.RiffFourCc);
/// <summary>
/// Checks if 'WEBP' is present in the header.
/// </summary>
/// <param name="header">The header bytes.</param>
/// <returns>True, if its a webp file.</returns>
private static bool IsWebpFile(ReadOnlySpan<byte> header)
=> header.Slice(8, 4).SequenceEqual(WebpConstants.WebpHeader);
=> header.Length >= this.HeaderSize && RiffOrListChunkHeader.Parse(header) is
{
IsRiff: true,
FormType: WebpConstants.WebpFourCc
};
}

20
src/ImageSharp/Formats/_Generated/ImageMetadataExtensions.cs

@ -304,6 +304,26 @@ public static class ImageMetadataExtensions
/// <returns>The new <see cref="IcoFrameMetadata"/></returns>
public static IcoFrameMetadata CloneIcoMetadata(this ImageFrameMetadata source) => source.CloneFormatMetadata(IcoFormat.Instance);
/// <summary>
/// Gets the <see cref="AniFrameMetadata"/> from <paramref name="source"/>.<br/>
/// If none is found, an instance is created either by conversion from the decoded image format metadata
/// or the requested format default constructor.
/// This instance will be added to the metadata for future requests.
/// </summary>
/// <param name="source">The image frame metadata.</param>
/// <returns>
/// The <see cref="IcoFrameMetadata"/>
/// </returns>
public static AniFrameMetadata GetAniMetadata(this ImageFrameMetadata source) => source.GetFormatMetadata(AniFormat.Instance);
/// <summary>
/// Creates a new cloned instance of <see cref="IcoMetadata"/> from the <paramref name="source"/>.
/// The instance is created via <see cref="GetAniMetadata(ImageFrameMetadata)"/>
/// </summary>
/// <param name="source">The image frame metadata.</param>
/// <returns>The new <see cref="IcoFrameMetadata"/></returns>
public static AniFrameMetadata CloneAniMetadata(this ImageFrameMetadata source) => source.CloneFormatMetadata(AniFormat.Instance);
/// <summary>
/// Gets the <see cref="GifFrameMetadata"/> from <paramref name="source"/>.<br/>
/// If none is found, an instance is created either by conversion from the decoded image format metadata

57
src/ImageSharp/IO/BufferedReadStream.cs

@ -3,6 +3,12 @@
using System.Buffers;
using System.Runtime.CompilerServices;
using SixLabors.ImageSharp.Formats.Ani;
using static System.Runtime.InteropServices.JavaScript.JSType;
using System.Runtime.InteropServices;
using System;
using System.Diagnostics.CodeAnalysis;
using SixLabors.ImageSharp.Memory;
namespace SixLabors.ImageSharp.IO;
@ -22,10 +28,14 @@ internal sealed class BufferedReadStream : Stream
private readonly unsafe byte* pinnedReadBuffer;
// Index within our buffer, not reader position.
/// <summary>
/// Index within our buffer, not reader position.
/// </summary>
private int readBufferIndex;
// Matches what the stream position would be without buffering
/// <summary>
/// Matches what the stream position would be without buffering
/// </summary>
private long readerPosition;
private bool isDisposed;
@ -194,6 +204,49 @@ internal sealed class BufferedReadStream : Stream
return this.ReadToBufferViaCopyFast(buffer);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool TryReadUnmanaged<T>(out T result)
where T : unmanaged
{
this.cancellationToken.ThrowIfCancellationRequested();
int size = Unsafe.SizeOf<T>();
if (size > this.BufferSize)
{
Span<byte> span = stackalloc byte[size];
if (this.ReadToBufferDirectSlow(span) != size)
{
result = default;
return false;
}
result = MemoryMarshal.Read<T>(span);
}
else
{
if ((uint)this.readBufferIndex > (uint)(this.BufferSize - size))
{
this.FillReadBuffer();
}
if (this.GetCopyCount(size) != size)
{
this.EofHitCount++;
result = default;
return false;
}
Span<byte> span = this.readBuffer.AsSpan(this.readBufferIndex, size);
this.readerPosition += size;
this.readBufferIndex += size;
result = MemoryMarshal.Read<T>(span);
}
return true;
}
/// <inheritdoc/>
public override void Flush()
{

7
tests/ImageSharp.Tests/Formats/Ani/AniDecoderTests.cs

@ -2,9 +2,6 @@
// Licensed under the Six Labors Split License.
using SixLabors.ImageSharp.Formats.Ani;
using SixLabors.ImageSharp.Formats.Bmp;
using SixLabors.ImageSharp.Formats.Cur;
using SixLabors.ImageSharp.Formats.Icon;
using SixLabors.ImageSharp.PixelFormats;
using static SixLabors.ImageSharp.Tests.TestImages.Ani;
@ -16,7 +13,9 @@ public class AniDecoderTests
{
[Theory]
[WithFile(Work, PixelTypes.Rgba32)]
public void CurDecoder_Decode(TestImageProvider<Rgba32> provider)
[WithFile(MultiFramesInEveryIconChunk, PixelTypes.Rgba32)]
[WithFile(Help, PixelTypes.Rgba32)]
public void AniDecoder_Decode(TestImageProvider<Rgba32> provider)
{
using Image<Rgba32> image = provider.GetImage(AniDecoder.Instance);
}

3
tests/ImageSharp.Tests/TestImages.cs

@ -1260,6 +1260,7 @@ public static class TestImages
public static class Ani
{
public const string Work = "Ani/Work.ani";
public const string MultiFramesInEveryIconChunk = "Ani/aero_busy.ani";
public const string Help = "Ani/Help.ani";
}
}

3
tests/Images/Input/Ani/Help.ani

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:c49cbb1ca0a3f268695a80df93b1ce2b2cba335a80e8244dd3a702863159bd99
size 12998

3
tests/Images/Input/Ani/aero_busy.ani

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:ff38afb523490e1a9f157c0447bc616b19c22df88bdb45c163243d834e9745f8
size 556304
Loading…
Cancel
Save