mirror of https://github.com/SixLabors/ImageSharp
Browse Source
- Ico Decoder - Ico Detector - Ico Detector UnitTest - Cur Decoder - Cur Detector - Cur Detector UnitTest Signed-off-by: 舰队的偶像-岛风酱! <frg2089@outlook.com>pull/2579/head
32 changed files with 1015 additions and 2 deletions
@ -0,0 +1,19 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Icon.Cur; |
|||
|
|||
/// <summary>
|
|||
/// Registers the image encoders, decoders and mime type detectors for the Ico format.
|
|||
/// </summary>
|
|||
public sealed class CurConfigurationModule : IImageFormatConfigurationModule |
|||
{ |
|||
/// <inheritdoc/>
|
|||
public void Configure(Configuration configuration) |
|||
{ |
|||
// TODO: CurEncoder
|
|||
// configuration.ImageFormatsManager.SetEncoder(CurFormat.Instance, new CurEncoder());
|
|||
configuration.ImageFormatsManager.SetDecoder(CurFormat.Instance, CurDecoder.Instance); |
|||
configuration.ImageFormatsManager.AddImageFormatDetector(new IconImageFormatDetector()); |
|||
} |
|||
} |
|||
@ -0,0 +1,28 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Icon.Cur; |
|||
|
|||
/// <summary>
|
|||
/// Defines constants relating to ICOs
|
|||
/// </summary>
|
|||
internal static class CurConstants |
|||
{ |
|||
/// <summary>
|
|||
/// The list of mimetypes that equate to a ico.
|
|||
/// </summary>
|
|||
/// <remarks>
|
|||
/// See <see href="https://en.wikipedia.org/wiki/ICO_(file_format)#MIME_type"/>
|
|||
/// </remarks>
|
|||
public static readonly IEnumerable<string> MimeTypes = new[] |
|||
{ |
|||
"application/octet-stream", |
|||
}; |
|||
|
|||
/// <summary>
|
|||
/// The list of file extensions that equate to a ico.
|
|||
/// </summary>
|
|||
public static readonly IEnumerable<string> FileExtensions = new[] { "cur" }; |
|||
|
|||
public const uint FileHeader = 0x00_02_00_00; |
|||
} |
|||
@ -0,0 +1,47 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using SixLabors.ImageSharp.PixelFormats; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Icon.Cur; |
|||
|
|||
/// <summary>
|
|||
/// Decoder for generating an image out of a ico encoded stream.
|
|||
/// </summary>
|
|||
public sealed class CurDecoder : ImageDecoder |
|||
{ |
|||
private CurDecoder() |
|||
{ |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the shared instance.
|
|||
/// </summary>
|
|||
public static CurDecoder Instance { get; } = new(); |
|||
|
|||
/// <inheritdoc/>
|
|||
protected override Image<TPixel> Decode<TPixel>(DecoderOptions options, Stream stream, CancellationToken cancellationToken) |
|||
{ |
|||
Guard.NotNull(options, nameof(options)); |
|||
Guard.NotNull(stream, nameof(stream)); |
|||
|
|||
Image<TPixel> image = new CurDecoderCore(options).Decode<TPixel>(options.Configuration, stream, cancellationToken); |
|||
|
|||
ScaleToTargetSize(options, image); |
|||
|
|||
return image; |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
protected override Image Decode(DecoderOptions options, Stream stream, CancellationToken cancellationToken) |
|||
=> this.Decode<Rgba32>(options, stream, cancellationToken); |
|||
|
|||
/// <inheritdoc/>
|
|||
protected override ImageInfo Identify(DecoderOptions options, Stream stream, CancellationToken cancellationToken) |
|||
{ |
|||
Guard.NotNull(options, nameof(options)); |
|||
Guard.NotNull(stream, nameof(stream)); |
|||
|
|||
return new CurDecoderCore(options).Identify(options.Configuration, stream, cancellationToken); |
|||
} |
|||
} |
|||
@ -0,0 +1,16 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using SixLabors.ImageSharp.Metadata; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Icon.Cur; |
|||
|
|||
internal sealed class CurDecoderCore : IconDecoderCore |
|||
{ |
|||
public CurDecoderCore(DecoderOptions options) |
|||
: base(options) |
|||
{ |
|||
} |
|||
|
|||
protected override IconFrameMetadata GetFrameMetadata(ImageFrameMetadata metadata) => metadata.GetCurMetadata(); |
|||
} |
|||
@ -0,0 +1,37 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Icon.Cur; |
|||
|
|||
/// <summary>
|
|||
/// Registers the image encoders, decoders and mime type detectors for the ICO format.
|
|||
/// </summary>
|
|||
public sealed class CurFormat : IImageFormat<CurMetadata, CurFrameMetadata> |
|||
{ |
|||
private CurFormat() |
|||
{ |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the shared instance.
|
|||
/// </summary>
|
|||
public static CurFormat Instance { get; } = new(); |
|||
|
|||
/// <inheritdoc/>
|
|||
public string Name => "ICO"; |
|||
|
|||
/// <inheritdoc/>
|
|||
public string DefaultMimeType => CurConstants.MimeTypes.First(); |
|||
|
|||
/// <inheritdoc/>
|
|||
public IEnumerable<string> MimeTypes => CurConstants.MimeTypes; |
|||
|
|||
/// <inheritdoc/>
|
|||
public IEnumerable<string> FileExtensions => CurConstants.FileExtensions; |
|||
|
|||
/// <inheritdoc/>
|
|||
public CurMetadata CreateDefaultFormatMetadata() => new(); |
|||
|
|||
/// <inheritdoc/>
|
|||
public CurFrameMetadata CreateDefaultFormatFrameMetadata() => new(); |
|||
} |
|||
@ -0,0 +1,52 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Icon.Cur; |
|||
|
|||
/// <summary>
|
|||
/// IcoFrameMetadata
|
|||
/// </summary>
|
|||
public class CurFrameMetadata : IconFrameMetadata, IDeepCloneable<CurFrameMetadata>, IDeepCloneable |
|||
{ |
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="CurFrameMetadata"/> class.
|
|||
/// </summary>
|
|||
public CurFrameMetadata() |
|||
{ |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="CurFrameMetadata"/> class.
|
|||
/// </summary>
|
|||
/// <param name="metadata">metadata</param>
|
|||
public CurFrameMetadata(IconFrameMetadata metadata) |
|||
: base(metadata) |
|||
{ |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="CurFrameMetadata"/> class.
|
|||
/// </summary>
|
|||
/// <param name="width">width</param>
|
|||
/// <param name="height">height</param>
|
|||
/// <param name="colorCount">colorCount</param>
|
|||
/// <param name="field1">field1</param>
|
|||
/// <param name="field2">field2</param>
|
|||
public CurFrameMetadata(byte width, byte height, byte colorCount, ushort field1, ushort field2) |
|||
: base(width, height, colorCount, field1, field2) |
|||
{ |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets Specifies the horizontal coordinates of the hotspot in number of pixels from the left.
|
|||
/// </summary>
|
|||
public ushort HotspotX { get => this.Field1; set => this.Field1 = value; } |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets Specifies the vertical coordinates of the hotspot in number of pixels from the top.
|
|||
/// </summary>
|
|||
public ushort HotspotY { get => this.Field2; set => this.Field2 = value; } |
|||
|
|||
/// <inheritdoc/>
|
|||
public override CurFrameMetadata DeepClone() => new(this); |
|||
} |
|||
@ -0,0 +1,16 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Icon.Cur; |
|||
|
|||
/// <summary>
|
|||
/// Provides Ico specific metadata information for the image.
|
|||
/// </summary>
|
|||
public class CurMetadata : IDeepCloneable<CurMetadata>, IDeepCloneable |
|||
{ |
|||
/// <inheritdoc/>
|
|||
public CurMetadata DeepClone() => new(); |
|||
|
|||
/// <inheritdoc/>
|
|||
IDeepCloneable IDeepCloneable.DeepClone() => this.DeepClone(); |
|||
} |
|||
@ -0,0 +1,44 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using System.Diagnostics.CodeAnalysis; |
|||
using SixLabors.ImageSharp.Metadata; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Icon.Cur; |
|||
|
|||
/// <summary>
|
|||
/// Extension methods for the <see cref="ImageMetadata"/> type.
|
|||
/// </summary>
|
|||
public static class MetadataExtensions |
|||
{ |
|||
/// <summary>
|
|||
/// Gets the Icon format specific metadata for the image.
|
|||
/// </summary>
|
|||
/// <param name="source">The metadata this method extends.</param>
|
|||
/// <returns>The <see cref="CurMetadata"/>.</returns>
|
|||
public static CurMetadata GetCurMetadata(this ImageMetadata source) |
|||
=> source.GetFormatMetadata(CurFormat.Instance); |
|||
|
|||
/// <summary>
|
|||
/// Gets the Icon format specific metadata for the image frame.
|
|||
/// </summary>
|
|||
/// <param name="source">The metadata this method extends.</param>
|
|||
/// <returns>The <see cref="CurFrameMetadata"/>.</returns>
|
|||
public static CurFrameMetadata GetCurMetadata(this ImageFrameMetadata source) |
|||
=> source.GetFormatMetadata(CurFormat.Instance); |
|||
|
|||
/// <summary>
|
|||
/// Gets the Icon format specific metadata for the image frame.
|
|||
/// </summary>
|
|||
/// <param name="source">The metadata this method extends.</param>
|
|||
/// <param name="metadata">
|
|||
/// When this method returns, contains the metadata associated with the specified frame,
|
|||
/// if found; otherwise, the default value for the type of the metadata parameter.
|
|||
/// This parameter is passed uninitialized.
|
|||
/// </param>
|
|||
/// <returns>
|
|||
/// <see langword="true"/> if the Icon frame metadata exists; otherwise, <see langword="false"/>.
|
|||
/// </returns>
|
|||
public static bool TryGetCurMetadata(this ImageFrameMetadata source, [NotNullWhen(true)] out CurFrameMetadata? metadata) |
|||
=> source.TryGetFormatMetadata(CurFormat.Instance, out metadata); |
|||
} |
|||
@ -0,0 +1,19 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Icon.Ico; |
|||
|
|||
/// <summary>
|
|||
/// Registers the image encoders, decoders and mime type detectors for the Ico format.
|
|||
/// </summary>
|
|||
public sealed class IcoConfigurationModule : IImageFormatConfigurationModule |
|||
{ |
|||
/// <inheritdoc/>
|
|||
public void Configure(Configuration configuration) |
|||
{ |
|||
// TODO: IcoEncoder
|
|||
// configuration.ImageFormatsManager.SetEncoder(IcoFormat.Instance, new IcoEncoder());
|
|||
configuration.ImageFormatsManager.SetDecoder(IcoFormat.Instance, IcoDecoder.Instance); |
|||
configuration.ImageFormatsManager.AddImageFormatDetector(new IconImageFormatDetector()); |
|||
} |
|||
} |
|||
@ -0,0 +1,38 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Icon.Ico; |
|||
|
|||
/// <summary>
|
|||
/// Defines constants relating to ICOs
|
|||
/// </summary>
|
|||
internal static class IcoConstants |
|||
{ |
|||
/// <summary>
|
|||
/// The list of mimetypes that equate to a ico.
|
|||
/// </summary>
|
|||
/// <remarks>
|
|||
/// See <see href="https://en.wikipedia.org/wiki/ICO_(file_format)#MIME_type"/>
|
|||
/// </remarks>
|
|||
public static readonly IEnumerable<string> MimeTypes = new[] |
|||
{ |
|||
// IANA-registered
|
|||
"image/vnd.microsoft.icon", |
|||
|
|||
// ICO & CUR types used by Windows
|
|||
"image/x-icon", |
|||
|
|||
// Erroneous types but have been used
|
|||
"image/ico", |
|||
"image/icon", |
|||
"text/ico", |
|||
"application/ico", |
|||
}; |
|||
|
|||
/// <summary>
|
|||
/// The list of file extensions that equate to a ico.
|
|||
/// </summary>
|
|||
public static readonly IEnumerable<string> FileExtensions = new[] { "ico" }; |
|||
|
|||
public const uint FileHeader = 0x00_01_00_00; |
|||
} |
|||
@ -0,0 +1,47 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using SixLabors.ImageSharp.PixelFormats; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Icon.Ico; |
|||
|
|||
/// <summary>
|
|||
/// Decoder for generating an image out of a ico encoded stream.
|
|||
/// </summary>
|
|||
public sealed class IcoDecoder : ImageDecoder |
|||
{ |
|||
private IcoDecoder() |
|||
{ |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the shared instance.
|
|||
/// </summary>
|
|||
public static IcoDecoder Instance { get; } = new(); |
|||
|
|||
/// <inheritdoc/>
|
|||
protected override Image<TPixel> Decode<TPixel>(DecoderOptions options, Stream stream, CancellationToken cancellationToken) |
|||
{ |
|||
Guard.NotNull(options, nameof(options)); |
|||
Guard.NotNull(stream, nameof(stream)); |
|||
|
|||
Image<TPixel> image = new IcoDecoderCore(options).Decode<TPixel>(options.Configuration, stream, cancellationToken); |
|||
|
|||
ScaleToTargetSize(options, image); |
|||
|
|||
return image; |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
protected override Image Decode(DecoderOptions options, Stream stream, CancellationToken cancellationToken) |
|||
=> this.Decode<Rgba32>(options, stream, cancellationToken); |
|||
|
|||
/// <inheritdoc/>
|
|||
protected override ImageInfo Identify(DecoderOptions options, Stream stream, CancellationToken cancellationToken) |
|||
{ |
|||
Guard.NotNull(options, nameof(options)); |
|||
Guard.NotNull(stream, nameof(stream)); |
|||
|
|||
return new IcoDecoderCore(options).Identify(options.Configuration, stream, cancellationToken); |
|||
} |
|||
} |
|||
@ -0,0 +1,16 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using SixLabors.ImageSharp.Metadata; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Icon.Ico; |
|||
|
|||
internal sealed class IcoDecoderCore : IconDecoderCore |
|||
{ |
|||
public IcoDecoderCore(DecoderOptions options) |
|||
: base(options) |
|||
{ |
|||
} |
|||
|
|||
protected override IconFrameMetadata GetFrameMetadata(ImageFrameMetadata metadata) => metadata.GetIcoMetadata(); |
|||
} |
|||
@ -0,0 +1,37 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Icon.Ico; |
|||
|
|||
/// <summary>
|
|||
/// Registers the image encoders, decoders and mime type detectors for the ICO format.
|
|||
/// </summary>
|
|||
public sealed class IcoFormat : IImageFormat<IcoMetadata, IcoFrameMetadata> |
|||
{ |
|||
private IcoFormat() |
|||
{ |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the shared instance.
|
|||
/// </summary>
|
|||
public static IcoFormat Instance { get; } = new(); |
|||
|
|||
/// <inheritdoc/>
|
|||
public string Name => "ICO"; |
|||
|
|||
/// <inheritdoc/>
|
|||
public string DefaultMimeType => IcoConstants.MimeTypes.First(); |
|||
|
|||
/// <inheritdoc/>
|
|||
public IEnumerable<string> MimeTypes => IcoConstants.MimeTypes; |
|||
|
|||
/// <inheritdoc/>
|
|||
public IEnumerable<string> FileExtensions => IcoConstants.FileExtensions; |
|||
|
|||
/// <inheritdoc/>
|
|||
public IcoMetadata CreateDefaultFormatMetadata() => new(); |
|||
|
|||
/// <inheritdoc/>
|
|||
public IcoFrameMetadata CreateDefaultFormatFrameMetadata() => new(); |
|||
} |
|||
@ -0,0 +1,50 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Icon.Ico; |
|||
|
|||
/// <summary>
|
|||
/// IcoFrameMetadata
|
|||
/// </summary>
|
|||
public class IcoFrameMetadata : IconFrameMetadata, IDeepCloneable<IcoFrameMetadata>, IDeepCloneable |
|||
{ |
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="IcoFrameMetadata"/> class.
|
|||
/// </summary>
|
|||
public IcoFrameMetadata() |
|||
{ |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="IcoFrameMetadata"/> class.
|
|||
/// </summary>
|
|||
/// <param name="metadata">metadata</param>
|
|||
public IcoFrameMetadata(IconFrameMetadata metadata) |
|||
: base(metadata) |
|||
{ |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="IcoFrameMetadata"/> class.
|
|||
/// </summary>
|
|||
/// <param name="width">width</param>
|
|||
/// <param name="height">height</param>
|
|||
/// <param name="colorCount">colorCount</param>
|
|||
/// <param name="field1">field1</param>
|
|||
/// <param name="field2">field2</param>
|
|||
public IcoFrameMetadata(byte width, byte height, byte colorCount, ushort field1, ushort field2) |
|||
: base(width, height, colorCount, field1, field2) |
|||
{ |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets Specifies bits per pixel.
|
|||
/// </summary>
|
|||
/// <remarks>
|
|||
/// It may used by Encoder.
|
|||
/// </remarks>
|
|||
public ushort BitCount { get => this.Field2; set => this.Field2 = value; } |
|||
|
|||
/// <inheritdoc/>
|
|||
public override IcoFrameMetadata DeepClone() => new(this); |
|||
} |
|||
@ -0,0 +1,16 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Icon.Ico; |
|||
|
|||
/// <summary>
|
|||
/// Provides Ico specific metadata information for the image.
|
|||
/// </summary>
|
|||
public class IcoMetadata : IDeepCloneable<IcoMetadata>, IDeepCloneable |
|||
{ |
|||
/// <inheritdoc/>
|
|||
public IcoMetadata DeepClone() => new(); |
|||
|
|||
/// <inheritdoc/>
|
|||
IDeepCloneable IDeepCloneable.DeepClone() => this.DeepClone(); |
|||
} |
|||
@ -0,0 +1,44 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using System.Diagnostics.CodeAnalysis; |
|||
using SixLabors.ImageSharp.Metadata; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Icon.Ico; |
|||
|
|||
/// <summary>
|
|||
/// Extension methods for the <see cref="ImageMetadata"/> type.
|
|||
/// </summary>
|
|||
public static class MetadataExtensions |
|||
{ |
|||
/// <summary>
|
|||
/// Gets the Ico format specific metadata for the image.
|
|||
/// </summary>
|
|||
/// <param name="source">The metadata this method extends.</param>
|
|||
/// <returns>The <see cref="IcoMetadata"/>.</returns>
|
|||
public static IcoMetadata GetIcoMetadata(this ImageMetadata source) |
|||
=> source.GetFormatMetadata(IcoFormat.Instance); |
|||
|
|||
/// <summary>
|
|||
/// Gets the Ico format specific metadata for the image frame.
|
|||
/// </summary>
|
|||
/// <param name="source">The metadata this method extends.</param>
|
|||
/// <returns>The <see cref="IcoFrameMetadata"/>.</returns>
|
|||
public static IcoFrameMetadata GetIcoMetadata(this ImageFrameMetadata source) |
|||
=> source.GetFormatMetadata(IcoFormat.Instance); |
|||
|
|||
/// <summary>
|
|||
/// Gets the Ico format specific metadata for the image frame.
|
|||
/// </summary>
|
|||
/// <param name="source">The metadata this method extends.</param>
|
|||
/// <param name="metadata">
|
|||
/// When this method returns, contains the metadata associated with the specified frame,
|
|||
/// if found; otherwise, the default value for the type of the metadata parameter.
|
|||
/// This parameter is passed uninitialized.
|
|||
/// </param>
|
|||
/// <returns>
|
|||
/// <see langword="true"/> if the Ico frame metadata exists; otherwise, <see langword="false"/>.
|
|||
/// </returns>
|
|||
public static bool TryGetIcoMetadata(this ImageFrameMetadata source, [NotNullWhen(true)] out IcoFrameMetadata? metadata) |
|||
=> source.TryGetFormatMetadata(IcoFormat.Instance, out metadata); |
|||
} |
|||
@ -0,0 +1,43 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Icon; |
|||
|
|||
internal class IconAssert |
|||
{ |
|||
internal static void CanSeek(Stream stream) |
|||
{ |
|||
if (!stream.CanSeek) |
|||
{ |
|||
throw new NotSupportedException("This stream cannot support seek"); |
|||
} |
|||
} |
|||
|
|||
internal static int EndOfStream(int v, int length) |
|||
{ |
|||
if (v != length) |
|||
{ |
|||
throw new EndOfStreamException(); |
|||
} |
|||
|
|||
return v; |
|||
} |
|||
|
|||
internal static long EndOfStream(long v, long length) |
|||
{ |
|||
if (v != length) |
|||
{ |
|||
throw new EndOfStreamException(); |
|||
} |
|||
|
|||
return v; |
|||
} |
|||
|
|||
internal static void NotSquare(in Size size) |
|||
{ |
|||
if (size.Width != size.Height) |
|||
{ |
|||
throw new FormatException("This image is not square."); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,136 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using System.Runtime.InteropServices; |
|||
using SixLabors.ImageSharp; |
|||
using SixLabors.ImageSharp.IO; |
|||
using SixLabors.ImageSharp.Metadata; |
|||
using SixLabors.ImageSharp.PixelFormats; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Icon; |
|||
|
|||
internal abstract class IconDecoderCore : IImageDecoderInternals |
|||
{ |
|||
private IconDir fileHeader; |
|||
|
|||
public IconDecoderCore(DecoderOptions options) => this.Options = options; |
|||
|
|||
public DecoderOptions Options { get; } |
|||
|
|||
public Size Dimensions { get; private set; } |
|||
|
|||
protected IconDir FileHeader { get => this.fileHeader; private set => this.fileHeader = value; } |
|||
|
|||
protected IconDirEntry[] Entries { get; private set; } = Array.Empty<IconDirEntry>(); |
|||
|
|||
public Image<TPixel> Decode<TPixel>(BufferedReadStream stream, CancellationToken cancellationToken) |
|||
where TPixel : unmanaged, IPixel<TPixel> |
|||
{ |
|||
// Stream may not at 0.
|
|||
long basePosition = stream.Position; |
|||
this.ReadHeader(stream); |
|||
|
|||
Span<byte> flag = stackalloc byte[Png.PngConstants.HeaderBytes.Length]; |
|||
|
|||
List<(Image<TPixel> Image, bool IsPng, int Index)> frames = new(this.Entries.Length); |
|||
for (int i = 0; i < this.Entries.Length; i++) |
|||
{ |
|||
_ = IconAssert.EndOfStream(stream.Seek(basePosition + this.Entries[i].ImageOffset, SeekOrigin.Begin), basePosition + this.Entries[i].ImageOffset); |
|||
_ = IconAssert.EndOfStream(stream.Read(flag), Png.PngConstants.HeaderBytes.Length); |
|||
_ = stream.Seek(-Png.PngConstants.HeaderBytes.Length, SeekOrigin.Current); |
|||
|
|||
bool isPng = flag.SequenceEqual(Png.PngConstants.HeaderBytes); |
|||
|
|||
Image<TPixel> img = this.GetDecoder(isPng).Decode<TPixel>(stream, cancellationToken); |
|||
IconAssert.NotSquare(img.Size); |
|||
frames.Add((img, isPng, i)); |
|||
if (isPng && img.Size.Width > this.Dimensions.Width) |
|||
{ |
|||
this.Dimensions = img.Size; |
|||
} |
|||
} |
|||
|
|||
ImageMetadata metadata = new(); |
|||
return new(this.Options.Configuration, metadata, frames.Select(i => |
|||
{ |
|||
ImageFrame<TPixel> target = new(this.Options.Configuration, this.Dimensions); |
|||
ImageFrame<TPixel> source = i.Image.Frames.RootFrameUnsafe; |
|||
for (int h = 0; h < source.Height; h++) |
|||
{ |
|||
source.PixelBuffer.DangerousGetRowSpan(h).CopyTo(target.PixelBuffer.DangerousGetRowSpan(h)); |
|||
} |
|||
|
|||
if (i.IsPng) |
|||
{ |
|||
target.Metadata.UnsafeSetFormatMetadata(Png.PngFormat.Instance, i.Image.Metadata.GetPngMetadata()); |
|||
} |
|||
else |
|||
{ |
|||
target.Metadata.UnsafeSetFormatMetadata(Bmp.BmpFormat.Instance, i.Image.Metadata.GetBmpMetadata()); |
|||
} |
|||
|
|||
this.GetFrameMetadata(target.Metadata).FromIconDirEntry(this.Entries[i.Index]); |
|||
|
|||
i.Image.Dispose(); |
|||
return target; |
|||
}).ToArray()); |
|||
} |
|||
|
|||
public ImageInfo Identify(BufferedReadStream stream, CancellationToken cancellationToken) |
|||
{ |
|||
this.ReadHeader(stream); |
|||
|
|||
ImageMetadata metadata = new(); |
|||
ImageFrameMetadata[] frames = new ImageFrameMetadata[this.FileHeader.Count]; |
|||
for (int i = 0; i < frames.Length; i++) |
|||
{ |
|||
frames[i] = new(); |
|||
IconFrameMetadata icoFrameMetadata = this.GetFrameMetadata(frames[i]); |
|||
icoFrameMetadata.FromIconDirEntry(this.Entries[i]); |
|||
} |
|||
|
|||
return new(new(32), new(0), metadata, frames); |
|||
} |
|||
|
|||
protected abstract IconFrameMetadata GetFrameMetadata(ImageFrameMetadata metadata); |
|||
|
|||
protected void ReadHeader(Stream stream) |
|||
{ |
|||
_ = Read(stream, out this.fileHeader, IconDir.Size); |
|||
this.Entries = new IconDirEntry[this.FileHeader.Count]; |
|||
for (int i = 0; i < this.Entries.Length; i++) |
|||
{ |
|||
_ = Read(stream, out this.Entries[i], IconDirEntry.Size); |
|||
} |
|||
|
|||
this.Dimensions = new( |
|||
this.Entries.Max(i => i.Width), |
|||
this.Entries.Max(i => i.Height)); |
|||
} |
|||
|
|||
private static int Read<T>(Stream stream, out T data, int size) |
|||
where T : unmanaged |
|||
{ |
|||
Span<byte> buffer = stackalloc byte[size]; |
|||
_ = IconAssert.EndOfStream(stream.Read(buffer), size); |
|||
data = MemoryMarshal.Cast<byte, T>(buffer)[0]; |
|||
return size; |
|||
} |
|||
|
|||
private IImageDecoderInternals GetDecoder(bool isPng) |
|||
{ |
|||
if (isPng) |
|||
{ |
|||
return new Png.PngDecoderCore(this.Options); |
|||
} |
|||
else |
|||
{ |
|||
return new Bmp.BmpDecoderCore(new() |
|||
{ |
|||
ProcessedAlphaMask = true, |
|||
SkipFileHeader = true, |
|||
IsDoubleHeight = true, |
|||
}); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,26 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using System.Runtime.InteropServices; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Icon; |
|||
|
|||
[StructLayout(LayoutKind.Sequential, Pack = 1, Size = Size)] |
|||
internal struct IconDir |
|||
{ |
|||
public const int Size = 3 * sizeof(ushort); |
|||
public ushort Reserved; |
|||
public IconFileType Type; |
|||
public ushort Count; |
|||
|
|||
public IconDir(IconFileType type) |
|||
: this(type, 0) |
|||
=> this.Type = type; |
|||
|
|||
public IconDir(IconFileType type, ushort count) |
|||
{ |
|||
this.Reserved = 0; |
|||
this.Type = type; |
|||
this.Count = count; |
|||
} |
|||
} |
|||
@ -0,0 +1,28 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using System.Runtime.InteropServices; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Icon; |
|||
|
|||
[StructLayout(LayoutKind.Sequential, Pack = 1, Size = Size)] |
|||
internal struct IconDirEntry |
|||
{ |
|||
public const int Size = (4 * sizeof(byte)) + (2 * sizeof(ushort)) + (2 * sizeof(uint)); |
|||
|
|||
public byte Width; |
|||
|
|||
public byte Height; |
|||
|
|||
public byte ColorCount; |
|||
|
|||
public byte Reserved; |
|||
|
|||
public ushort Planes; |
|||
|
|||
public ushort BitCount; |
|||
|
|||
public uint BytesInRes; |
|||
|
|||
public uint ImageOffset; |
|||
} |
|||
@ -0,0 +1,20 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Icon; |
|||
|
|||
/// <summary>
|
|||
/// Ico file type
|
|||
/// </summary>
|
|||
internal enum IconFileType : ushort |
|||
{ |
|||
/// <summary>
|
|||
/// ICO file
|
|||
/// </summary>
|
|||
ICO = 1, |
|||
|
|||
/// <summary>
|
|||
/// CUR file
|
|||
/// </summary>
|
|||
CUR = 2, |
|||
} |
|||
@ -0,0 +1,25 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Icon; |
|||
|
|||
/// <summary>
|
|||
/// IconFrameCompression
|
|||
/// </summary>
|
|||
public enum IconFrameCompression |
|||
{ |
|||
/// <summary>
|
|||
/// Unknown
|
|||
/// </summary>
|
|||
Unknown, |
|||
|
|||
/// <summary>
|
|||
/// Bmp
|
|||
/// </summary>
|
|||
Bmp, |
|||
|
|||
/// <summary>
|
|||
/// Png
|
|||
/// </summary>
|
|||
Png |
|||
} |
|||
@ -0,0 +1,103 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Icon; |
|||
|
|||
/// <summary>
|
|||
/// IconFrameMetadata
|
|||
/// </summary>
|
|||
public abstract class IconFrameMetadata : IDeepCloneable |
|||
{ |
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="IconFrameMetadata"/> class.
|
|||
/// </summary>
|
|||
protected IconFrameMetadata() |
|||
{ |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="IconFrameMetadata"/> class.
|
|||
/// </summary>
|
|||
/// <param name="width">width</param>
|
|||
/// <param name="height">height</param>
|
|||
/// <param name="colorCount">colorCount</param>
|
|||
/// <param name="field1">field1</param>
|
|||
/// <param name="field2">field2</param>
|
|||
protected IconFrameMetadata(byte width, byte height, byte colorCount, ushort field1, ushort field2) |
|||
{ |
|||
this.EncodingWidth = width; |
|||
this.EncodingHeight = height; |
|||
this.ColorCount = colorCount; |
|||
this.Field1 = field1; |
|||
this.Field2 = field2; |
|||
} |
|||
|
|||
/// <inheritdoc cref="IconFrameMetadata()"/>
|
|||
protected IconFrameMetadata(IconFrameMetadata metadata) |
|||
{ |
|||
this.EncodingWidth = metadata.EncodingWidth; |
|||
this.EncodingHeight = metadata.EncodingHeight; |
|||
this.ColorCount = metadata.ColorCount; |
|||
this.Field1 = metadata.Field1; |
|||
this.Field2 = metadata.Field2; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets icoFrameCompression.
|
|||
/// </summary>
|
|||
public IconFrameCompression Compression { get; protected set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets ColorCount field. <br />
|
|||
/// Specifies number of colors in the color palette. Should be 0 if the image does not use a color palette.
|
|||
/// </summary>
|
|||
// TODO: BmpMetadata does not supported palette yet.
|
|||
public byte ColorCount { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets Planes field. <br />
|
|||
/// In ICO format: Specifies color planes. Should be 0 or 1. <br />
|
|||
/// In CUR format: Specifies the horizontal coordinates of the hotspot in number of pixels from the left.
|
|||
/// </summary>
|
|||
protected ushort Field1 { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets BitCount field. <br />
|
|||
/// In ICO format: Specifies bits per pixel. <br />
|
|||
/// In CUR format: Specifies the vertical coordinates of the hotspot in number of pixels from the top.
|
|||
/// </summary>
|
|||
protected ushort Field2 { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets Height field. <br />
|
|||
/// Specifies image height in pixels. Can be any number between 0 and 255. Value 0 means image height is 256 pixels.
|
|||
/// </summary>
|
|||
public byte EncodingHeight { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets Width field. <br />
|
|||
/// Specifies image width in pixels. Can be any number between 0 and 255. Value 0 means image width is 256 pixels.
|
|||
/// </summary>
|
|||
public byte EncodingWidth { get; set; } |
|||
|
|||
/// <inheritdoc/>
|
|||
public abstract IDeepCloneable DeepClone(); |
|||
|
|||
internal void FromIconDirEntry(in IconDirEntry metadata) |
|||
{ |
|||
this.EncodingWidth = metadata.Width; |
|||
this.EncodingHeight = metadata.Height; |
|||
this.ColorCount = metadata.ColorCount; |
|||
this.Field1 = metadata.Planes; |
|||
this.Field2 = metadata.BitCount; |
|||
} |
|||
|
|||
internal IconDirEntry ToIconDirEntry() => new() |
|||
{ |
|||
Width = this.EncodingWidth, |
|||
Height = this.EncodingHeight, |
|||
ColorCount = this.ColorCount, |
|||
Planes = this.Field1, |
|||
BitCount = this.Field2, |
|||
}; |
|||
} |
|||
@ -0,0 +1,33 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using System.Diagnostics.CodeAnalysis; |
|||
using System.Runtime.InteropServices; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Icon; |
|||
|
|||
/// <summary>
|
|||
/// Detects ico file headers.
|
|||
/// </summary>
|
|||
public class IconImageFormatDetector : IImageFormatDetector |
|||
{ |
|||
/// <inheritdoc/>
|
|||
public int HeaderSize { get; } = 4; |
|||
|
|||
/// <inheritdoc/>
|
|||
public bool TryDetectFormat(ReadOnlySpan<byte> header, [NotNullWhen(true)] out IImageFormat? format) |
|||
{ |
|||
switch (MemoryMarshal.Cast<byte, uint>(header)[0]) |
|||
{ |
|||
case Ico.IcoConstants.FileHeader: |
|||
format = Ico.IcoFormat.Instance; |
|||
return true; |
|||
case Cur.CurConstants.FileHeader: |
|||
format = Cur.CurFormat.Instance; |
|||
return true; |
|||
default: |
|||
format = default; |
|||
return false; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,23 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using SixLabors.ImageSharp.Formats.Icon.Cur; |
|||
using SixLabors.ImageSharp.Formats.Tiff; |
|||
using SixLabors.ImageSharp.PixelFormats; |
|||
using static SixLabors.ImageSharp.Tests.TestImages.Cur; |
|||
|
|||
namespace SixLabors.ImageSharp.Tests.Formats.Icon.Cur; |
|||
|
|||
[Trait("Format", "Cur")] |
|||
[ValidateDisposedMemoryAllocations] |
|||
public class CurDecoderTests |
|||
{ |
|||
[Theory] |
|||
[WithFile(WindowsMouse, PixelTypes.Rgba32)] |
|||
public void CurDecoder_Decode(TestImageProvider<Rgba32> provider) |
|||
{ |
|||
using Image<Rgba32> image = provider.GetImage(CurDecoder.Instance); |
|||
|
|||
image.DebugSave(provider, extension: "tiff", encoder: new TiffEncoder()); |
|||
} |
|||
} |
|||
@ -0,0 +1,23 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using SixLabors.ImageSharp.Formats.Icon.Ico; |
|||
using SixLabors.ImageSharp.Formats.Tiff; |
|||
using SixLabors.ImageSharp.PixelFormats; |
|||
using static SixLabors.ImageSharp.Tests.TestImages.Ico; |
|||
|
|||
namespace SixLabors.ImageSharp.Tests.Formats.Icon.Ico; |
|||
|
|||
[Trait("Format", "Icon")] |
|||
[ValidateDisposedMemoryAllocations] |
|||
public class IcoDecoderTests |
|||
{ |
|||
[Theory] |
|||
[WithFile(Flutter, PixelTypes.Rgba32)] |
|||
public void IcoDecoder_Decode(TestImageProvider<Rgba32> provider) |
|||
{ |
|||
using Image<Rgba32> image = provider.GetImage(IcoDecoder.Instance); |
|||
|
|||
image.DebugSave(provider, extension: "tiff", encoder: new TiffEncoder()); |
|||
} |
|||
} |
|||
@ -0,0 +1,3 @@ |
|||
version https://git-lfs.github.com/spec/v1 |
|||
oid sha256:06678bbf954f0bece61062633dc63a52a34a6f3c27ac7108f28c0f0d26bb22a7 |
|||
size 136606 |
|||
@ -0,0 +1,3 @@ |
|||
version https://git-lfs.github.com/spec/v1 |
|||
oid sha256:c098d3fc85cacff98b8e69811b48e9f0d852fcee278132d794411d978869cbf8 |
|||
size 33772 |
|||
Loading…
Reference in new issue