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.
|
// Copyright (c) Six Labors.
|
||||
// Licensed under the Six Labors Split License.
|
// 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.Icon; |
||||
|
using SixLabors.ImageSharp.Formats.Png; |
||||
using SixLabors.ImageSharp.IO; |
using SixLabors.ImageSharp.IO; |
||||
|
using SixLabors.ImageSharp.Memory.Internals; |
||||
|
using SixLabors.ImageSharp.Memory; |
||||
|
using SixLabors.ImageSharp.Metadata; |
||||
|
|
||||
namespace SixLabors.ImageSharp.Formats.Ani; |
namespace SixLabors.ImageSharp.Formats.Ani; |
||||
|
|
||||
internal class AniDecoderCore : ImageDecoderCore |
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) |
public AniDecoderCore(DecoderOptions options) |
||||
: base(options) |
: base(options) => this.configuration = options.Configuration; |
||||
{ |
|
||||
} |
|
||||
|
|
||||
protected override Image<TPixel> Decode<TPixel>(BufferedReadStream stream, CancellationToken cancellationToken) |
protected override Image<TPixel> Decode<TPixel>(BufferedReadStream stream, CancellationToken cancellationToken) |
||||
{ |
{ |
||||
this.ReadHeader(stream); |
this.currentStream = stream; |
||||
Span<byte> buffer = stackalloc byte[4]; |
this.ReadHeader(); |
||||
_ = stream.Read(buffer); |
ImageMetadata metadata = new(); |
||||
uint type = BitConverter.ToUInt32(buffer); |
AniMetadata aniMetadata = metadata.GetAniMetadata(); |
||||
switch (type) |
Image<TPixel>? image = null; |
||||
|
|
||||
|
Span<byte> buffer = stackalloc byte[20]; |
||||
|
|
||||
|
try |
||||
{ |
{ |
||||
case 0x73_65_71_20: // seq
|
while (this.TryReadChunk(buffer, out AniChunk chunk)) |
||||
break; |
{ |
||||
case 0x72_61_74_65: // rate
|
try |
||||
break; |
{ |
||||
case 0x4C_49_53_54: // list
|
switch (chunk.Type) |
||||
break; |
{ |
||||
default: |
case AniChunkType.Seq: |
||||
break; |
|
||||
|
break; |
||||
|
case AniChunkType.Rate: |
||||
|
|
||||
|
break; |
||||
|
case AniChunkType.List: |
||||
|
|
||||
|
break; |
||||
|
default: |
||||
|
break; |
||||
|
} |
||||
|
} |
||||
|
finally |
||||
|
{ |
||||
|
chunk.Data?.Dispose(); |
||||
|
} |
||||
|
} |
||||
} |
} |
||||
|
catch |
||||
|
{ |
||||
|
image?.Dispose(); |
||||
|
throw; |
||||
|
} |
||||
|
|
||||
|
|
||||
throw new NotImplementedException(); |
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) |
protected override ImageInfo Identify(BufferedReadStream stream, CancellationToken cancellationToken) |
||||
{ |
{ |
||||
throw new NotImplementedException(); |
throw new NotImplementedException(); |
||||
} |
} |
||||
|
|
||||
private void ReadHeader(Stream stream) |
private void ReadHeader() |
||||
{ |
{ |
||||
// Skip the identifier
|
// Skip the identifier
|
||||
stream.Skip(12); |
this.currentStream.Skip(12); |
||||
Span<byte> buffer = stackalloc byte[36]; |
Span<byte> buffer = stackalloc byte[36]; |
||||
_ = stream.Read(buffer); |
_ = this.currentStream.Read(buffer); |
||||
AniHeader header = AniHeader.Parse(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.
|
// Copyright (c) Six Labors.
|
||||
// Licensed under the Six Labors Split License.
|
// Licensed under the Six Labors Split License.
|
||||
|
|
||||
|
using SixLabors.ImageSharp.Formats.Ico; |
||||
using SixLabors.ImageSharp.PixelFormats; |
using SixLabors.ImageSharp.PixelFormats; |
||||
|
|
||||
namespace SixLabors.ImageSharp.Formats.Ani; |
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(); |
public static AniMetadata FromFormatConnectingMetadata(FormatConnectingMetadata metadata) => throw new NotImplementedException(); |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
public void AfterImageApply<TPixel>(Image<TPixel> destination) |
public void AfterImageApply<TPixel>(Image<TPixel> destination) |
||||
where TPixel : unmanaged, IPixel<TPixel> => throw new NotImplementedException(); |
where TPixel : unmanaged, IPixel<TPixel> => throw new NotImplementedException(); |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
public IDeepCloneable DeepClone() => throw new NotImplementedException(); |
public IDeepCloneable DeepClone() => throw new NotImplementedException(); |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
public PixelTypeInfo GetPixelTypeInfo() => throw new NotImplementedException(); |
public PixelTypeInfo GetPixelTypeInfo() => throw new NotImplementedException(); |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
public FormatConnectingMetadata ToFormatConnectingMetadata() => throw new NotImplementedException(); |
public FormatConnectingMetadata ToFormatConnectingMetadata() => throw new NotImplementedException(); |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
AniMetadata IDeepCloneable<AniMetadata>.DeepClone() => throw new NotImplementedException(); |
AniMetadata IDeepCloneable<AniMetadata>.DeepClone() => throw new NotImplementedException(); |
||||
} |
} |
||||
|
|||||
Loading…
Reference in new issue