mirror of https://github.com/SixLabors/ImageSharp
8 changed files with 320 additions and 23 deletions
@ -0,0 +1,38 @@ |
|||
// 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; } |
|||
} |
|||
@ -0,0 +1,11 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Ani; |
|||
|
|||
internal enum AniChunkType : uint |
|||
{ |
|||
Seq = 0x73_65_71_20, |
|||
Rate = 0x72_61_74_65, |
|||
List = 0x4C_49_53_54 |
|||
} |
|||
@ -0,0 +1,21 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Ani; |
|||
|
|||
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 = []; |
|||
|
|||
/// <summary>
|
|||
/// The list of file extensions that equate to an ani.
|
|||
/// </summary>
|
|||
public static readonly IEnumerable<string> FileExtensions = ["ani"]; |
|||
|
|||
} |
|||
@ -1,50 +1,206 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using System.Buffers; |
|||
using System.Buffers.Binary; |
|||
using System.Runtime.CompilerServices; |
|||
using SixLabors.ImageSharp.Formats.Icon; |
|||
using SixLabors.ImageSharp.Formats.Png; |
|||
using SixLabors.ImageSharp.IO; |
|||
using SixLabors.ImageSharp.Memory.Internals; |
|||
using SixLabors.ImageSharp.Memory; |
|||
using SixLabors.ImageSharp.Metadata; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Ani; |
|||
|
|||
internal class AniDecoderCore : ImageDecoderCore |
|||
{ |
|||
/// <summary>
|
|||
/// The general decoder options.
|
|||
/// </summary>
|
|||
private readonly Configuration configuration; |
|||
|
|||
/// <summary>
|
|||
/// The stream to decode from.
|
|||
/// </summary>
|
|||
private BufferedReadStream currentStream = null!; |
|||
|
|||
private AniHeader header; |
|||
|
|||
public AniDecoderCore(DecoderOptions options) |
|||
: base(options) |
|||
{ |
|||
} |
|||
: base(options) => this.configuration = options.Configuration; |
|||
|
|||
protected override Image<TPixel> Decode<TPixel>(BufferedReadStream stream, CancellationToken cancellationToken) |
|||
{ |
|||
this.ReadHeader(stream); |
|||
Span<byte> buffer = stackalloc byte[4]; |
|||
_ = stream.Read(buffer); |
|||
uint type = BitConverter.ToUInt32(buffer); |
|||
switch (type) |
|||
this.currentStream = stream; |
|||
this.ReadHeader(); |
|||
ImageMetadata metadata = new(); |
|||
AniMetadata aniMetadata = metadata.GetAniMetadata(); |
|||
Image<TPixel>? image = null; |
|||
|
|||
Span<byte> buffer = stackalloc byte[20]; |
|||
|
|||
try |
|||
{ |
|||
case 0x73_65_71_20: // seq
|
|||
break; |
|||
case 0x72_61_74_65: // rate
|
|||
break; |
|||
case 0x4C_49_53_54: // list
|
|||
break; |
|||
default: |
|||
break; |
|||
while (this.TryReadChunk(buffer, out AniChunk chunk)) |
|||
{ |
|||
try |
|||
{ |
|||
switch (chunk.Type) |
|||
{ |
|||
case AniChunkType.Seq: |
|||
|
|||
break; |
|||
case AniChunkType.Rate: |
|||
|
|||
break; |
|||
case AniChunkType.List: |
|||
|
|||
break; |
|||
default: |
|||
break; |
|||
} |
|||
} |
|||
finally |
|||
{ |
|||
chunk.Data?.Dispose(); |
|||
} |
|||
} |
|||
} |
|||
catch |
|||
{ |
|||
image?.Dispose(); |
|||
throw; |
|||
} |
|||
|
|||
|
|||
throw new NotImplementedException(); |
|||
} |
|||
|
|||
private void ReadSeq() |
|||
{ |
|||
|
|||
} |
|||
|
|||
private bool TryReadChunk(Span<byte> buffer, out AniChunk chunk) |
|||
{ |
|||
if (!this.TryReadChunkLength(buffer, out int length)) |
|||
{ |
|||
// IEND
|
|||
chunk = default; |
|||
return false; |
|||
} |
|||
|
|||
while (length < 0) |
|||
{ |
|||
// Not a valid chunk so try again until we reach a known chunk.
|
|||
if (!this.TryReadChunkLength(buffer, out length)) |
|||
{ |
|||
// IEND
|
|||
chunk = default; |
|||
return false; |
|||
} |
|||
} |
|||
|
|||
AniChunkType type = this.ReadChunkType(buffer); |
|||
|
|||
// 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)); |
|||
|
|||
return true; |
|||
} |
|||
|
|||
|
|||
protected override ImageInfo Identify(BufferedReadStream stream, CancellationToken cancellationToken) |
|||
{ |
|||
throw new NotImplementedException(); |
|||
} |
|||
|
|||
private void ReadHeader(Stream stream) |
|||
private void ReadHeader() |
|||
{ |
|||
// Skip the identifier
|
|||
stream.Skip(12); |
|||
this.currentStream.Skip(12); |
|||
Span<byte> buffer = stackalloc byte[36]; |
|||
_ = stream.Read(buffer); |
|||
AniHeader header = AniHeader.Parse(buffer); |
|||
_ = this.currentStream.Read(buffer); |
|||
this.header = AniHeader.Parse(buffer); |
|||
} |
|||
|
|||
private void ReadSeq(Stream stream) |
|||
{ |
|||
Span<byte> buffer = stackalloc byte[4]; |
|||
int length = BinaryPrimitives.ReadInt32BigEndian(buffer); |
|||
|
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Attempts to read the length of the next chunk.
|
|||
/// </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) |
|||
{ |
|||
if (this.currentStream.Read(buffer, 0, 4) == 4) |
|||
{ |
|||
result = BinaryPrimitives.ReadInt32BigEndian(buffer); |
|||
|
|||
return true; |
|||
} |
|||
|
|||
result = 0; |
|||
return false; |
|||
} |
|||
|
|||
/// <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) |
|||
{ |
|||
if (this.currentStream.Read(buffer, 0, 4) == 4) |
|||
{ |
|||
return (AniChunkType)BinaryPrimitives.ReadUInt32BigEndian(buffer); |
|||
} |
|||
|
|||
PngThrowHelper.ThrowInvalidChunkType(); |
|||
|
|||
// The IDE cannot detect the throw here.
|
|||
return default; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Reads the chunk data from the stream.
|
|||
/// </summary>
|
|||
/// <param name="length">The length of the chunk data to read.</param>
|
|||
[MethodImpl(InliningOptions.ShortMethod)] |
|||
private IMemoryOwner<byte> ReadChunkData(int length) |
|||
{ |
|||
if (length == 0) |
|||
{ |
|||
return new BasicArrayBuffer<byte>([]); |
|||
} |
|||
|
|||
// 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); |
|||
|
|||
this.currentStream.Read(buffer.GetSpan(), 0, length); |
|||
|
|||
return buffer; |
|||
} |
|||
|
|||
} |
|||
|
|||
@ -0,0 +1,34 @@ |
|||
// 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> |
|||
{ |
|||
/// <summary>
|
|||
/// Gets the shared instance.
|
|||
/// </summary>
|
|||
public static AniFormat Instance { get; } = new(); |
|||
|
|||
/// <inheritdoc/>
|
|||
public AniMetadata CreateDefaultFormatMetadata() => throw new NotImplementedException(); |
|||
|
|||
/// <inheritdoc/>
|
|||
public string Name => "ANI"; |
|||
|
|||
/// <inheritdoc/>
|
|||
public string DefaultMimeType { get; } |
|||
|
|||
/// <inheritdoc/>
|
|||
public IEnumerable<string> MimeTypes { get; } |
|||
|
|||
/// <inheritdoc/>
|
|||
public IEnumerable<string> FileExtensions { get; } |
|||
} |
|||
@ -1,23 +1,39 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using SixLabors.ImageSharp.Formats.Ico; |
|||
using SixLabors.ImageSharp.PixelFormats; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Ani; |
|||
|
|||
internal class AniMetadata : IFormatMetadata<AniMetadata> |
|||
/// <summary>
|
|||
/// Provides Ani specific metadata information for the image.
|
|||
/// </summary>
|
|||
public class AniMetadata : IFormatMetadata<AniMetadata> |
|||
{ |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="AniMetadata"/> class.
|
|||
/// </summary>
|
|||
public AniMetadata() |
|||
{ |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public static AniMetadata FromFormatConnectingMetadata(FormatConnectingMetadata metadata) => throw new NotImplementedException(); |
|||
|
|||
/// <inheritdoc/>
|
|||
public void AfterImageApply<TPixel>(Image<TPixel> destination) |
|||
where TPixel : unmanaged, IPixel<TPixel> => throw new NotImplementedException(); |
|||
|
|||
/// <inheritdoc/>
|
|||
public IDeepCloneable DeepClone() => throw new NotImplementedException(); |
|||
|
|||
/// <inheritdoc/>
|
|||
public PixelTypeInfo GetPixelTypeInfo() => throw new NotImplementedException(); |
|||
|
|||
/// <inheritdoc/>
|
|||
public FormatConnectingMetadata ToFormatConnectingMetadata() => throw new NotImplementedException(); |
|||
|
|||
/// <inheritdoc/>
|
|||
AniMetadata IDeepCloneable<AniMetadata>.DeepClone() => throw new NotImplementedException(); |
|||
} |
|||
|
|||
Loading…
Reference in new issue