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