From 0829e868f5b3771647e81adf76b7fdf5931036ff Mon Sep 17 00:00:00 2001 From: Ynse Hoornenborg Date: Mon, 25 Dec 2023 17:30:55 +0100 Subject: [PATCH] Scaffolding for HEIC file format --- .gitattributes | 3 + src/ImageSharp/Configuration.cs | 5 +- .../Formats/Heic/HeicConfigurationModule.cs | 18 +++++ src/ImageSharp/Formats/Heic/HeicConstants.cs | 20 ++++++ src/ImageSharp/Formats/Heic/HeicDecoder.cs | 45 +++++++++++++ .../Formats/Heic/HeicDecoderCore.cs | 65 +++++++++++++++++++ src/ImageSharp/Formats/Heic/HeicEncoder.cs | 18 +++++ .../Formats/Heic/HeicEncoderCore.cs | 53 +++++++++++++++ src/ImageSharp/Formats/Heic/HeicFormat.cs | 34 ++++++++++ .../Formats/Heic/HeicImageFormatDetector.cs | 26 ++++++++ src/ImageSharp/Formats/Heic/HeicMetadata.cs | 26 ++++++++ .../Formats/Heic/HeicNalUnitType.cs | 38 +++++++++++ .../Formats/Heic/IHeicEncoderOptions.cs | 12 ++++ .../Formats/Heic/MetadataExtensions.cs | 20 ++++++ .../Formats/ImageFormatManagerTests.cs | 3 + .../TestUtilities/TestEnvironment.Formats.cs | 2 + 16 files changed, 387 insertions(+), 1 deletion(-) create mode 100644 src/ImageSharp/Formats/Heic/HeicConfigurationModule.cs create mode 100644 src/ImageSharp/Formats/Heic/HeicConstants.cs create mode 100644 src/ImageSharp/Formats/Heic/HeicDecoder.cs create mode 100644 src/ImageSharp/Formats/Heic/HeicDecoderCore.cs create mode 100644 src/ImageSharp/Formats/Heic/HeicEncoder.cs create mode 100644 src/ImageSharp/Formats/Heic/HeicEncoderCore.cs create mode 100644 src/ImageSharp/Formats/Heic/HeicFormat.cs create mode 100644 src/ImageSharp/Formats/Heic/HeicImageFormatDetector.cs create mode 100644 src/ImageSharp/Formats/Heic/HeicMetadata.cs create mode 100644 src/ImageSharp/Formats/Heic/HeicNalUnitType.cs create mode 100644 src/ImageSharp/Formats/Heic/IHeicEncoderOptions.cs create mode 100644 src/ImageSharp/Formats/Heic/MetadataExtensions.cs diff --git a/.gitattributes b/.gitattributes index b5f742ab47..b1cf4dbd0a 100644 --- a/.gitattributes +++ b/.gitattributes @@ -136,3 +136,6 @@ *.ico filter=lfs diff=lfs merge=lfs -text *.cur filter=lfs diff=lfs merge=lfs -text *.ani filter=lfs diff=lfs merge=lfs -text +*.heic filter=lfs diff=lfs merge=lfs -text +*.heif filter=lfs diff=lfs merge=lfs -text +*.avif filter=lfs diff=lfs merge=lfs -text diff --git a/src/ImageSharp/Configuration.cs b/src/ImageSharp/Configuration.cs index 1ca5d0a46b..5d1f66a22e 100644 --- a/src/ImageSharp/Configuration.cs +++ b/src/ImageSharp/Configuration.cs @@ -6,6 +6,7 @@ using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Formats.Bmp; using SixLabors.ImageSharp.Formats.Gif; using SixLabors.ImageSharp.Formats.Jpeg; +using SixLabors.ImageSharp.Formats.Heic; using SixLabors.ImageSharp.Formats.Pbm; using SixLabors.ImageSharp.Formats.Png; using SixLabors.ImageSharp.Formats.Qoi; @@ -211,6 +212,7 @@ public sealed class Configuration /// . /// . /// . + /// . /// /// The default configuration of . internal static Configuration CreateDefaultInstance() => new( @@ -222,5 +224,6 @@ public sealed class Configuration new TgaConfigurationModule(), new TiffConfigurationModule(), new WebpConfigurationModule(), - new QoiConfigurationModule()); + new QoiConfigurationModule(), + new HeicConfigurationModule()); } diff --git a/src/ImageSharp/Formats/Heic/HeicConfigurationModule.cs b/src/ImageSharp/Formats/Heic/HeicConfigurationModule.cs new file mode 100644 index 0000000000..0ba7ceef67 --- /dev/null +++ b/src/ImageSharp/Formats/Heic/HeicConfigurationModule.cs @@ -0,0 +1,18 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Formats.Heic; + +/// +/// Registers the image encoders, decoders and mime type detectors for the HEIC format. +/// +public sealed class HeicConfigurationModule : IImageFormatConfigurationModule +{ + /// + public void Configure(Configuration configuration) + { + configuration.ImageFormatsManager.SetEncoder(HeicFormat.Instance, new HeicEncoder()); + configuration.ImageFormatsManager.SetDecoder(HeicFormat.Instance, HeicDecoder.Instance); + configuration.ImageFormatsManager.AddImageFormatDetector(new HeicImageFormatDetector()); + } +} diff --git a/src/ImageSharp/Formats/Heic/HeicConstants.cs b/src/ImageSharp/Formats/Heic/HeicConstants.cs new file mode 100644 index 0000000000..99c9b8da66 --- /dev/null +++ b/src/ImageSharp/Formats/Heic/HeicConstants.cs @@ -0,0 +1,20 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Formats.Heic; + +/// +/// Contains HEIC (and H.265) constant values defined in the specification. +/// +internal static class HeicConstants +{ + /// + /// The list of mimetypes that equate to a HEIC. + /// + public static readonly IEnumerable MimeTypes = new[] { "image/heif", "image/heif-sequence", "image/heic", "image/heic-sequence", "image/avif" }; + + /// + /// The list of file extensions that equate to a HEIC. + /// + public static readonly IEnumerable FileExtensions = new[] { "heic", "heif", "avif" }; +} diff --git a/src/ImageSharp/Formats/Heic/HeicDecoder.cs b/src/ImageSharp/Formats/Heic/HeicDecoder.cs new file mode 100644 index 0000000000..71c8bdd363 --- /dev/null +++ b/src/ImageSharp/Formats/Heic/HeicDecoder.cs @@ -0,0 +1,45 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.ImageSharp.PixelFormats; + +namespace SixLabors.ImageSharp.Formats.Heic; + +/// +/// Image decoder for reading HEIC images from a stream. +public sealed class HeicDecoder : ImageDecoder +{ + private HeicDecoder() + { + } + + /// + /// Gets the shared instance. + /// + public static HeicDecoder Instance { get; } = new(); + + /// + protected override ImageInfo Identify(DecoderOptions options, Stream stream, CancellationToken cancellationToken) + { + Guard.NotNull(options, nameof(options)); + Guard.NotNull(stream, nameof(stream)); + + return new HeicDecoderCore(options).Identify(options.Configuration, stream, cancellationToken); + } + + /// + protected override Image Decode(DecoderOptions options, Stream stream, CancellationToken cancellationToken) + { + Guard.NotNull(options, nameof(options)); + Guard.NotNull(stream, nameof(stream)); + + HeicDecoderCore decoder = new(options); + Image image = decoder.Decode(options.Configuration, stream, cancellationToken); + + return image; + } + + /// + protected override Image Decode(DecoderOptions options, Stream stream, CancellationToken cancellationToken) + => this.Decode(options, stream, cancellationToken); +} diff --git a/src/ImageSharp/Formats/Heic/HeicDecoderCore.cs b/src/ImageSharp/Formats/Heic/HeicDecoderCore.cs new file mode 100644 index 0000000000..c935af778d --- /dev/null +++ b/src/ImageSharp/Formats/Heic/HeicDecoderCore.cs @@ -0,0 +1,65 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Diagnostics.CodeAnalysis; +using SixLabors.ImageSharp.IO; +using SixLabors.ImageSharp.Memory; +using SixLabors.ImageSharp.Metadata; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing; + +namespace SixLabors.ImageSharp.Formats.Heic; + +/// +/// Performs the PBM decoding operation. +/// +internal sealed class HeicDecoderCore : IImageDecoderInternals +{ + /// + /// The general configuration. + /// + private readonly Configuration configuration; + + /// + /// The decoded by this decoder instance. + /// + private ImageMetadata? metadata; + + /// + /// Initializes a new instance of the class. + /// + /// The decoder options. + public HeicDecoderCore(DecoderOptions options) + { + this.Options = options; + this.configuration = options.Configuration; + } + + /// + public DecoderOptions Options { get; } + + /// + public Image Decode(BufferedReadStream stream, CancellationToken cancellationToken) + where TPixel : unmanaged, IPixel + { + this.ProcessHeader(stream); + + var image = new Image(this.configuration, this.pixelSize.Width, this.pixelSize.Height, this.metadata); + + Buffer2D pixels = image.GetRootFramePixelBuffer(); + + // TODO: Implement + + return image; + } + + /// + public ImageInfo Identify(BufferedReadStream stream, CancellationToken cancellationToken) + { + this.ProcessHeader(stream); + + // TODO: Implement + return new ImageInfo(new PixelTypeInfo(bitsPerPixel), new(this.pixelSize.Width, this.pixelSize.Height), this.metadata); + } + +} diff --git a/src/ImageSharp/Formats/Heic/HeicEncoder.cs b/src/ImageSharp/Formats/Heic/HeicEncoder.cs new file mode 100644 index 0000000000..90ecc754a6 --- /dev/null +++ b/src/ImageSharp/Formats/Heic/HeicEncoder.cs @@ -0,0 +1,18 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.ImageSharp.Advanced; + +namespace SixLabors.ImageSharp.Formats.Heic; + +/// +/// Image encoder for writing an image to a stream as HEIC images. +public sealed class HeicEncoder : ImageEncoder +{ + /// + protected override void Encode(Image image, Stream stream, CancellationToken cancellationToken) + { + HeicEncoderCore encoder = new(image.Configuration, this); + encoder.Encode(image, stream, cancellationToken); + } +} diff --git a/src/ImageSharp/Formats/Heic/HeicEncoderCore.cs b/src/ImageSharp/Formats/Heic/HeicEncoderCore.cs new file mode 100644 index 0000000000..291ece9d52 --- /dev/null +++ b/src/ImageSharp/Formats/Heic/HeicEncoderCore.cs @@ -0,0 +1,53 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Buffers.Text; +using SixLabors.ImageSharp.Advanced; +using SixLabors.ImageSharp.PixelFormats; + +namespace SixLabors.ImageSharp.Formats.Heic; + +/// +/// Image encoder for writing an image to a stream as a HEIC image. +/// +internal sealed class HeicEncoderCore : IImageEncoderInternals +{ + /// + /// The global configuration. + /// + private Configuration configuration; + + /// + /// The encoder with options. + /// + private readonly HeicEncoder encoder; + + /// + /// Initializes a new instance of the class. + /// + /// The configuration. + /// The encoder with options. + public HeicEncoderCore(Configuration configuration, HeicEncoder encoder) + { + this.configuration = configuration; + this.encoder = encoder; + } + + /// + /// Encodes the image to the specified stream from the . + /// + /// The pixel format. + /// The to encode from. + /// The to encode the image data to. + /// The token to request cancellation. + public void Encode(Image image, Stream stream, CancellationToken cancellationToken) + where TPixel : unmanaged, IPixel + { + Guard.NotNull(image, nameof(image)); + Guard.NotNull(stream, nameof(stream)); + + // TODO: Implement + + stream.Flush(); + } +} diff --git a/src/ImageSharp/Formats/Heic/HeicFormat.cs b/src/ImageSharp/Formats/Heic/HeicFormat.cs new file mode 100644 index 0000000000..be47afce00 --- /dev/null +++ b/src/ImageSharp/Formats/Heic/HeicFormat.cs @@ -0,0 +1,34 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Formats.Heic; + +/// +/// Registers the image encoders, decoders and mime type detectors for the HEIC format. +/// +public sealed class HeicFormat : IImageFormat +{ + private HeicFormat() + { + } + + /// + /// Gets the shared instance. + /// + public static HeicFormat Instance { get; } = new(); + + /// + public string Name => "HEIC"; + + /// + public string DefaultMimeType => "image/heif"; + + /// + public IEnumerable MimeTypes => HeicConstants.MimeTypes; + + /// + public IEnumerable FileExtensions => HeicConstants.FileExtensions; + + /// + public HeicMetadata CreateDefaultFormatMetadata() => new(); +} diff --git a/src/ImageSharp/Formats/Heic/HeicImageFormatDetector.cs b/src/ImageSharp/Formats/Heic/HeicImageFormatDetector.cs new file mode 100644 index 0000000000..a220320f35 --- /dev/null +++ b/src/ImageSharp/Formats/Heic/HeicImageFormatDetector.cs @@ -0,0 +1,26 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Diagnostics.CodeAnalysis; + +namespace SixLabors.ImageSharp.Formats.Heic; + +/// +/// Detects HEIC file headers. +/// +public sealed class HeicImageFormatDetector : IImageFormatDetector +{ + /// + public bool TryDetectFormat(ReadOnlySpan header, [NotNullWhen(true)] out IImageFormat? format) + { + format = IsSupportedFileFormat(header) ? HeicFormat.Instance : null; + return format != null; + } + + private static bool IsSupportedFileFormat(ReadOnlySpan header) + { + // TODO: Implement + + return false; + } +} diff --git a/src/ImageSharp/Formats/Heic/HeicMetadata.cs b/src/ImageSharp/Formats/Heic/HeicMetadata.cs new file mode 100644 index 0000000000..ddad88cc46 --- /dev/null +++ b/src/ImageSharp/Formats/Heic/HeicMetadata.cs @@ -0,0 +1,26 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Formats.Heic; + +/// +/// Provides HEIC specific metadata information for the image. +/// +public class HeicMetadata : IDeepCloneable +{ + /// + /// Initializes a new instance of the class. + /// + public HeicMetadata() => + + /// + /// Initializes a new instance of the class. + /// + /// The metadata to create an instance from. + private HeicMetadata(HeicMetadata other) + { + } + + /// + public IDeepCloneable DeepClone() => new HeicMetadata(this); +} diff --git a/src/ImageSharp/Formats/Heic/HeicNalUnitType.cs b/src/ImageSharp/Formats/Heic/HeicNalUnitType.cs new file mode 100644 index 0000000000..127a8dcdca --- /dev/null +++ b/src/ImageSharp/Formats/Heic/HeicNalUnitType.cs @@ -0,0 +1,38 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Formats.Heic; + +/// +/// Provides enumeration of supported x265's LAN Unit Types. +/// +public enum HeicNalUnitType : uint +{ + NAL_UNIT_CODED_SLICE_TRAIL_N = 0, + NAL_UNIT_CODED_SLICE_TRAIL_R, + NAL_UNIT_CODED_SLICE_TSA_N, + NAL_UNIT_CODED_SLICE_TSA_R, + NAL_UNIT_CODED_SLICE_STSA_N, + NAL_UNIT_CODED_SLICE_STSA_R, + NAL_UNIT_CODED_SLICE_RADL_N, + NAL_UNIT_CODED_SLICE_RADL_R, + NAL_UNIT_CODED_SLICE_RASL_N, + NAL_UNIT_CODED_SLICE_RASL_R, + NAL_UNIT_CODED_SLICE_BLA_W_LP = 16, + NAL_UNIT_CODED_SLICE_BLA_W_RADL, + NAL_UNIT_CODED_SLICE_BLA_N_LP, + NAL_UNIT_CODED_SLICE_IDR_W_RADL, + NAL_UNIT_CODED_SLICE_IDR_N_LP, + NAL_UNIT_CODED_SLICE_CRA, + NAL_UNIT_VPS = 32, + NAL_UNIT_SPS, + NAL_UNIT_PPS, + NAL_UNIT_ACCESS_UNIT_DELIMITER, + NAL_UNIT_EOS, + NAL_UNIT_EOB, + NAL_UNIT_FILLER_DATA, + NAL_UNIT_PREFIX_SEI, + NAL_UNIT_SUFFIX_SEI, + Unspecified = 62, + Invalid = 64, +} diff --git a/src/ImageSharp/Formats/Heic/IHeicEncoderOptions.cs b/src/ImageSharp/Formats/Heic/IHeicEncoderOptions.cs new file mode 100644 index 0000000000..fb73ae3ab2 --- /dev/null +++ b/src/ImageSharp/Formats/Heic/IHeicEncoderOptions.cs @@ -0,0 +1,12 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Formats.Heic; + +/// +/// Configuration options for use during HEIC encoding. +/// +internal interface IHeicEncoderOptions +{ + // None defined yet. +} diff --git a/src/ImageSharp/Formats/Heic/MetadataExtensions.cs b/src/ImageSharp/Formats/Heic/MetadataExtensions.cs new file mode 100644 index 0000000000..b708bfc308 --- /dev/null +++ b/src/ImageSharp/Formats/Heic/MetadataExtensions.cs @@ -0,0 +1,20 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.ImageSharp.Formats.Heic; +using SixLabors.ImageSharp.Metadata; + +namespace SixLabors.ImageSharp; + +/// +/// Extension methods for the type. +/// +public static partial class MetadataExtensions +{ + /// + /// Gets the pbm format specific metadata for the image. + /// + /// The metadata this method extends. + /// The . + public static HeicMetadata GetHeicMetadata(this ImageMetadata metadata) => metadata.GetFormatMetadata(HeicFormat.Instance); +} diff --git a/tests/ImageSharp.Tests/Formats/ImageFormatManagerTests.cs b/tests/ImageSharp.Tests/Formats/ImageFormatManagerTests.cs index 324bd4783a..7d21de6dee 100644 --- a/tests/ImageSharp.Tests/Formats/ImageFormatManagerTests.cs +++ b/tests/ImageSharp.Tests/Formats/ImageFormatManagerTests.cs @@ -6,6 +6,7 @@ using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Formats.Bmp; using SixLabors.ImageSharp.Formats.Gif; using SixLabors.ImageSharp.Formats.Jpeg; +using SixLabors.ImageSharp.Formats.Heic; using SixLabors.ImageSharp.Formats.Pbm; using SixLabors.ImageSharp.Formats.Png; using SixLabors.ImageSharp.Formats.Tga; @@ -35,6 +36,7 @@ public class ImageFormatManagerTests Assert.Equal(1, this.DefaultFormatsManager.ImageEncoders.Select(item => item.Value).OfType().Count()); Assert.Equal(1, this.DefaultFormatsManager.ImageEncoders.Select(item => item.Value).OfType().Count()); Assert.Equal(1, this.DefaultFormatsManager.ImageEncoders.Select(item => item.Value).OfType().Count()); + Assert.Equal(1, this.DefaultFormatsManager.ImageEncoders.Select(item => item.Value).OfType().Count()); Assert.Equal(1, this.DefaultFormatsManager.ImageEncoders.Select(item => item.Value).OfType().Count()); Assert.Equal(1, this.DefaultFormatsManager.ImageEncoders.Select(item => item.Value).OfType().Count()); Assert.Equal(1, this.DefaultFormatsManager.ImageEncoders.Select(item => item.Value).OfType().Count()); @@ -44,6 +46,7 @@ public class ImageFormatManagerTests Assert.Equal(1, this.DefaultFormatsManager.ImageDecoders.Select(item => item.Value).OfType().Count()); Assert.Equal(1, this.DefaultFormatsManager.ImageDecoders.Select(item => item.Value).OfType().Count()); Assert.Equal(1, this.DefaultFormatsManager.ImageDecoders.Select(item => item.Value).OfType().Count()); + Assert.Equal(1, this.DefaultFormatsManager.ImageDecoders.Select(item => item.Value).OfType().Count()); Assert.Equal(1, this.DefaultFormatsManager.ImageDecoders.Select(item => item.Value).OfType().Count()); Assert.Equal(1, this.DefaultFormatsManager.ImageDecoders.Select(item => item.Value).OfType().Count()); Assert.Equal(1, this.DefaultFormatsManager.ImageDecoders.Select(item => item.Value).OfType().Count()); diff --git a/tests/ImageSharp.Tests/TestUtilities/TestEnvironment.Formats.cs b/tests/ImageSharp.Tests/TestUtilities/TestEnvironment.Formats.cs index 9508de2469..be3de2a4f8 100644 --- a/tests/ImageSharp.Tests/TestUtilities/TestEnvironment.Formats.cs +++ b/tests/ImageSharp.Tests/TestUtilities/TestEnvironment.Formats.cs @@ -5,6 +5,7 @@ using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Formats.Bmp; using SixLabors.ImageSharp.Formats.Gif; using SixLabors.ImageSharp.Formats.Jpeg; +using SixLabors.ImageSharp.Formats.Heic; using SixLabors.ImageSharp.Formats.Pbm; using SixLabors.ImageSharp.Formats.Png; using SixLabors.ImageSharp.Formats.Qoi; @@ -59,6 +60,7 @@ public static partial class TestEnvironment Configuration cfg = new( new JpegConfigurationModule(), new GifConfigurationModule(), + new HeicConfigurationModule(), new PbmConfigurationModule(), new TgaConfigurationModule(), new WebpConfigurationModule(),