diff --git a/src/ImageSharp/Configuration.cs b/src/ImageSharp/Configuration.cs index 5d1f66a22e..21ecd2cb2b 100644 --- a/src/ImageSharp/Configuration.cs +++ b/src/ImageSharp/Configuration.cs @@ -5,8 +5,8 @@ using System.Collections.Concurrent; 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.Jpeg; using SixLabors.ImageSharp.Formats.Pbm; using SixLabors.ImageSharp.Formats.Png; using SixLabors.ImageSharp.Formats.Qoi; diff --git a/src/ImageSharp/Formats/Heic/FourCharacterCode.cs b/src/ImageSharp/Formats/Heic/FourCharacterCode.cs index ad4cad7443..fc9ac748dc 100644 --- a/src/ImageSharp/Formats/Heic/FourCharacterCode.cs +++ b/src/ImageSharp/Formats/Heic/FourCharacterCode.cs @@ -1,11 +1,16 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. +using System.Buffers.Binary; +using System.CodeDom.Compiler; +using System.Text; + namespace SixLabors.ImageSharp.Formats.Heic; /// /// Provides constants for 4 Character codes used in HEIC images. /// +[GeneratedCode("T4", null)] public static class FourCharacterCode { // TODO: Create T4 template for this file @@ -13,197 +18,202 @@ public static class FourCharacterCode /// /// File Type /// - public const uint ftyp = 0x66747970U, + public const uint ftyp = 0x66747970U; /// /// Metadata container /// - public const uint meta = 0x6D657461U, + public const uint meta = 0x6D657461U; /// /// Media Data /// - public const uint mdat = 0x6D646174U, + public const uint mdat = 0x6D646174U; /// /// Item Information Entry /// - public const uint infe = 0x696E6665U, + public const uint infe = 0x696E6665U; /// /// Item Data /// - public const uint idat = 0x69646174U, + public const uint idat = 0x69646174U; /// /// Item Location /// - public const uint iloc = 0x696C6F63U, + public const uint iloc = 0x696C6F63U; /// /// EXIF metadata /// - public const uint Exif = 0x45786966U, + public const uint Exif = 0x45786966U; /// /// Data Reference /// - public const uint dref = 0x64726566U, + public const uint dref = 0x64726566U; /// /// Primary Item /// - public const uint pitm = 0x7069746DU, + public const uint pitm = 0x7069746DU; /// /// Item Spatial Extent /// - public const uint ispe = 0x69737064U, + public const uint ispe = 0x69737064U; /// /// Alternative text /// - public const uint altt = 0, // 'altt' + public const uint altt = 0; // 'altt' /// /// Colour information /// - public const uint colr = 0, // 'colr' + public const uint colr = 0; // 'colr' /// /// HVC configuration /// - public const uint hvcC = 0, // 'hvcC' + public const uint hvcC = 0; // 'hvcC' /// /// Image Mirror /// - public const uint imir = 0, // 'imir' + public const uint imir = 0; // 'imir' /// /// Image Rotation /// - public const uint irot = 0, // 'irot' + public const uint irot = 0; // 'irot' /// /// Image Scaling /// - public const uint iscl = 0, // 'iscl' + public const uint iscl = 0; // 'iscl' /// /// Pixel Aspect Ration /// - public const uint pasp = 0, // 'pasp' + public const uint pasp = 0; // 'pasp' /// /// Pixel Information /// - public const uint pixi = 0x70697869U, + public const uint pixi = 0x70697869U; /// /// Reference Location /// - public const uint rloc = 0, // 'rloc + public const uint rloc = 0; // 'rloc /// /// User Description /// - public const uint udes = 0, // 'udes' + public const uint udes = 0; // 'udes' /// /// Item Property Container /// - public const uint ipco = 0, + public const uint ipco = 0; /// /// Item Property Association /// - public const uint ipma = 0, + public const uint ipma = 0; /// /// High Efficient Image Coding /// - public const uint heic = 0, + public const uint heic = 0; /// /// High Efficiency Coding tile /// - public const uint hvc1 = 0, + public const uint hvc1 = 0; /// /// Data Information /// - public const uint dinf = 0, + public const uint dinf = 0; /// /// Group list /// - public const uint grpl = 0, + public const uint grpl = 0; /// /// Handler /// - public const uint hdlr = 0, - - /// - /// Item Data - /// - public const uint idat = 0, // 'idat' + public const uint hdlr = 0; /// /// Item Information /// - public const uint iinf = 0, // 'iinf' + public const uint iinf = 0; // 'iinf' /// /// Item Property /// - public const uint iprp = 0, // 'iprp' + public const uint iprp = 0; // 'iprp' /// /// Item Protection /// - public const uint ipro = 0, // 'ipro' + public const uint ipro = 0; // 'ipro' /// /// Item Reference /// - public const uint iref = 0, // 'iref' + public const uint iref = 0; // 'iref' /// /// Grid /// - public const uint grid = 0, // 'grid' + public const uint grid = 0; // 'grid' /// /// Derived Image /// - public const uint dimg = 0, // 'dimg' + public const uint dimg = 0; // 'dimg' /// /// Thumbnail /// - public const uint thmb = 0, // 'thmb' + public const uint thmb = 0; // 'thmb' /// /// Content Description /// - public const uint cdsc = 0, // 'cdsc' + public const uint cdsc = 0; // 'cdsc' + + /// + /// MIME type + /// + public const uint mime = 0; // 'mime' + + /// + /// URI + /// + public const uint uri = 0; // 'uri ' public static uint Parse(string code) { if (code.Length != 4) { - throw new ImageFormatException(); + throw new ImageFormatException("Unbale to parse FourCC code of more than 4 characters."); } Span span = Encoding.UTF8.GetBytes(code); - return BinaryPrimitives.ReadUInt32BigEndian(buffer); + return BinaryPrimitives.ReadUInt32BigEndian(span); } public static string ToString(uint fourcc) { - Span span = stackalloc new byte[4]; - BinaryPrimitives.WriteUInt32BigEndian(buffer, fourcc); + Span span = stackalloc byte[4]; + BinaryPrimitives.WriteUInt32BigEndian(span, fourcc); return Encoding.UTF8.GetString(span); } } diff --git a/src/ImageSharp/Formats/Heic/HeicBoxType.cs b/src/ImageSharp/Formats/Heic/HeicBoxType.cs deleted file mode 100644 index 45cecacb68..0000000000 --- a/src/ImageSharp/Formats/Heic/HeicBoxType.cs +++ /dev/null @@ -1,26 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Formats.Heic; - -/// -/// Provides enumeration of supported ISO Base Format Box Types for HEIC. -/// -public enum HeicBoxType : uint -{ - FileType = FourCharacterCode.ftyp, - Meta = FourCharacterCode.meta, - MediaData = FourCharacterCode.mdat, - ItemInfo = FourCharacterCode.infe - ItemData = FourCharacterCode.idat, - ItemLocation = FourCharacterCode.iloc, - Exif = FourCharacterCode.Exif, - ItemPropertyAssociation = FourCharacterCode.ipma, - DataReference = FourCharacterCode.dref, - PrimaryItemReference = FourCharacterCode.pitm, - ImageSpatialExtentsProperty = FourCharacterCode.ispe, - - // Possible box types outside of HEIC images: - Movie = FourCharacterCode.moov, - Track = FourCharacterCode.trak, -} diff --git a/src/ImageSharp/Formats/Heic/HeicDecoderCore.cs b/src/ImageSharp/Formats/Heic/HeicDecoderCore.cs index 551696e912..a9e7c4d056 100644 --- a/src/ImageSharp/Formats/Heic/HeicDecoderCore.cs +++ b/src/ImageSharp/Formats/Heic/HeicDecoderCore.cs @@ -1,12 +1,12 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System.Diagnostics.CodeAnalysis; +using System.Buffers.Binary; +using System.Text; 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; @@ -25,6 +25,8 @@ internal sealed class HeicDecoderCore : IImageDecoderInternals /// private ImageMetadata? metadata; + private uint primaryItem; + private List items; private List itemLinks; @@ -37,6 +39,9 @@ internal sealed class HeicDecoderCore : IImageDecoderInternals { this.Options = options; this.configuration = options.Configuration; + this.metadata = new ImageMetadata(); + this.items = new List(); + this.itemLinks = new List(); } /// @@ -51,19 +56,19 @@ internal sealed class HeicDecoderCore : IImageDecoderInternals { if (!this.CheckFileTypeBox(stream)) { - throw new InvalidImageFormatException(); + throw new ImageFormatException("Not an HEIC image."); } - while (!stream.Eof) + while (stream.EofHitCount == 0) { - var length = ReadBoxHeader(stream, out var boxType); + long length = this.ReadBoxHeader(stream, out var boxType); switch (boxType) { - case HeicBoxType.Meta: - ParseMetadata(stream, length); + case FourCharacterCode.meta: + this.ParseMetadata(stream, length); break; - case HeicBoxType.MediaData: - ParseMediaData(stream, length); + case FourCharacterCode.mdat: + this.ParseMediaData(stream, length); break; default: throw new ImageFormatException($"Unknown box type of '{FourCharacterCode.ToString(boxType)}'"); @@ -82,63 +87,64 @@ internal sealed class HeicDecoderCore : IImageDecoderInternals { this.CheckFileTypeBox(stream); - while (!stream.Eof) + while (stream.EofHitCount == 0) { - var length = ReadBoxHeader(stream, out var boxType); - var buffer = new byte[length]; - stream.Read(buffer); + long length = this.ReadBoxHeader(stream, out uint boxType); switch (boxType) { - case HeicBoxType.Metadata: - ParseMetadata(buffer); + case FourCharacterCode.meta: + this.ParseMetadata(stream, length); break; default: // Silently skip all other box types. break; } } + return new ImageInfo(new PixelTypeInfo(bitsPerPixel), new(this.pixelSize.Width, this.pixelSize.Height), this.metadata); } private bool CheckFileTypeBox(BufferedReadStream stream) { - var boxLength = ReadBoxHeader(stream, out var boxType); - Span buffer = stackalloc new byte[boxLength]; + var boxLength = this.ReadBoxHeader(stream, out var boxType); + Span buffer = stackalloc byte[(int)boxLength]; stream.Read(buffer); - var majorBrand = BinaryPrimitives.ReadUInt32BigEndian(buf); + var majorBrand = BinaryPrimitives.ReadUInt32BigEndian(buffer); + // TODO: Interpret minorVersion and compatible brands. - return boxTypepe == HeicBoxType.FileType && majorBrand == HeicConstants.HeicBrand; + return boxType == FourCharacterCode.ftyp && majorBrand == FourCharacterCode.heic; } - private ulong ReadBoxHeader(BufferedReadStream stream, out uint boxType) + private long ReadBoxHeader(BufferedReadStream stream, out uint boxType) { // Read 4 bytes of length of box - Span buf = stackalloc new byte[8]; + Span buf = stackalloc byte[8]; stream.Read(buf); - ulong boxSize = BinaryPrimitives.ReadUInt32BigEndian(buf); - ulong headerSize = 8; + long boxSize = BinaryPrimitives.ReadUInt32BigEndian(buf); + long headerSize = 8; + // Read 4 bytes of box type - boxType = BinaryPrimitives.ReadUInt32BigEndian(buf.Slice(4)); + boxType = BinaryPrimitives.ReadUInt32BigEndian(buf[4..]); if (boxSize == 1) { stream.Read(buf); - boxSize = BinaryPrimitives.ReadUInt64BigEndian(buf); - headerSize += 8UL; + boxSize = (long)BinaryPrimitives.ReadUInt64BigEndian(buf); + headerSize += 8; } return boxSize - headerSize; } - private uint ParseBoxHeader(Span buffer, out ulong length, out uint boxType) + private static int ParseBoxHeader(Span buffer, out long length, out uint boxType) { - ulong boxSize = BinaryPrimitives.ReadUInt32BigEndian(buffer); - ulong bytesRead = 4; - boxType = BinaryPrimitives.ReadUInt32BigEndian(buffer.Slice(bytesRead)); + long boxSize = BinaryPrimitives.ReadUInt32BigEndian(buffer); + int bytesRead = 4; + boxType = BinaryPrimitives.ReadUInt32BigEndian(buffer[bytesRead..]); bytesRead += 4; if (boxSize == 1) { - boxSize = BinaryPrimitives.ReadUInt64BigEndian(buffer.Slice(bytesRead)); + boxSize = (long)BinaryPrimitives.ReadUInt64BigEndian(buffer[bytesRead..]); bytesRead += 8; } @@ -146,30 +152,32 @@ internal sealed class HeicDecoderCore : IImageDecoderInternals return bytesRead; } - private void ParseMetadata(BufferedReadStream stream, uint boxLength) + private void ParseMetadata(BufferedReadStream stream, long boxLength) { - var endPosition = stream.Position + boxLength; + long endPosition = stream.Position + boxLength; while (stream.Position < endPosition) { - var length = ReadBoxHeader(stream, out var boxType); + long length = this.ReadBoxHeader(stream, out uint boxType); switch (boxType) { - case HeicMetaSubBoxType.ItemProperty: - ParseItemPropertyContainer(stream, length); + case FourCharacterCode.iprp: + this.ParseItemPropertyContainer(stream, length); break; - case HeicMetaSubBoxType.ItemInfo: - ParseItemInfo(stream, length); + case FourCharacterCode.iinf: + this.ParseItemInfo(stream, length); break; - case HeicMetaSubBoxType.ItemReference: - ParseItemReference(stream, length); + case FourCharacterCode.iref: + this.ParseItemReference(stream, length); break; - case HeicMetaSubBoxType.DataInformation: - case HeicMetaSubBoxType.GroupsList: - case HeicMetaSubBoxType.Handler: - case HeicMetaSubBoxType.ItemData: - case HeicMetaSubBoxType.ItemLocation: - case HeicMetaSubBoxType.ItemProtection: - case HeicMetaSubBoxType.PrimaryItem: + case FourCharacterCode.pitm: + this.ParsePrimaryItem(stream, length); + break; + case FourCharacterCode.dinf: + case FourCharacterCode.grpl: + case FourCharacterCode.hdlr: + case FourCharacterCode.idat: + case FourCharacterCode.iloc: + case FourCharacterCode.ipro: // TODO: Implement break; default: @@ -178,63 +186,65 @@ internal sealed class HeicDecoderCore : IImageDecoderInternals } } - private void ParseItemInfo(BufferedReadStream stream, uint boxLength) + private void ParseItemInfo(BufferedReadStream stream, long boxLength) { - Span buffer = new byte[length]; + Span buffer = stackalloc byte[(int)boxLength]; stream.Read(buffer); uint entryCount; int bytesRead = 0; if (buffer[bytesRead] == 0) { bytesRead += 4; - entryCount = BinaryPrimitives.ReadUInt16BigEndian(buffer.Slice(bytesRead)); + entryCount = BinaryPrimitives.ReadUInt16BigEndian(buffer[bytesRead..]); bytesRead += 2; } else { bytesRead += 4; - entryCount = BinaryPrimitives.ReadUInt32BigEndian(buffer.Slice(bytesRead)); + entryCount = BinaryPrimitives.ReadUInt32BigEndian(buffer[bytesRead..]); bytesRead += 4; } - for(uint i = 0; i < entryCount; i++) + for (uint i = 0; i < entryCount; i++) { - bytesRead += ParseBoxHeader(out var subBoxLength, out var boxType); - ParseItemInfoEntry(buffer.Slice(bytesRead, subBoxLength)); - bytesRead += subBoxLength; + bytesRead += this.ParseItemInfoEntry(buffer[bytesRead..]); } } - private void ParseItemInfoEntry(Span buffer, uint boxLength) + private int ParseItemInfoEntry(Span buffer) { - int bytesRead = 0; - var version = buffer[bytesRead]; + int bytesRead = ParseBoxHeader(buffer, out long boxLength, out uint boxType); + byte version = buffer[bytesRead]; bytesRead += 4; - var item = new HeicItem(); - if (version == 0 || version == 1) + HeicItem? item = null; + if (version is 0 or 1) { - item.Id = BinaryPrimitives.ReadUInt16BigEndian(buffer.Slice(bytesRead)); + uint itemId = BinaryPrimitives.ReadUInt16BigEndian(buffer[bytesRead..]); bytesRead += 2; - item.ProtectionIndex = BinaryPrimitives.ReadUInt16BigEndian(buffer.Slice(bytesRead)); + item = new HeicItem(boxType, itemId); + + // Skip Protection Index, not sure what that means... bytesRead += 2; - item.Name = ReadNullTerminatedString(buffer.Slice(bytesRead)); + item.Name = ReadNullTerminatedString(buffer[bytesRead..]); bytesRead += item.Name.Length + 1; - item.ContentType = ReadNullTerminatedString(buffer.Slice(bytesRead)); + item.ContentType = ReadNullTerminatedString(buffer[bytesRead..]); bytesRead += item.ContentType.Length + 1; + // Optional field. if (bytesRead < boxLength) { - item.ContentEncoding = ReadNullTerminatedString(buffer.Slice(bytesRead)); + item.ContentEncoding = ReadNullTerminatedString(buffer[bytesRead..]); bytesRead += item.ContentEncoding.Length + 1; } } if (version == 1) { + // Optional fields. if (bytesRead < boxLength) { - item.ExtensionType = BinaryPrimitives.ReadUInt32BigEndian(buffer.Slice(bytesRead)); + item!.ExtensionType = BinaryPrimitives.ReadUInt32BigEndian(buffer[bytesRead..]); bytesRead += 4; } @@ -246,132 +256,162 @@ internal sealed class HeicDecoderCore : IImageDecoderInternals if (version >= 2) { - if (getVersion() == 2) + uint itemId = 0U; + if (version == 2) { - item.Id = BinaryPrimitives.ReadUInt16BigEndian(buffer.Slice(bytesRead)); + itemId = BinaryPrimitives.ReadUInt16BigEndian(buffer[bytesRead..]); bytesRead += 2; } - else if (getVersion() == 3) + else if (version == 3) { - item.Id = BinaryPrimitives.ReadUInt32BigEndian(buffer.Slice(bytesRead)); + itemId = BinaryPrimitives.ReadUInt32BigEndian(buffer[bytesRead..]); bytesRead += 4; } - item.ProtectionIndex = BinaryPrimitives.ReadUInt16BigEndian(buffer.Slice(bytesRead)); + // Skip Protection Index, not sure what that means... bytesRead += 2; - item.Type = BinaryPrimitives.ReadUInt32BigEndian(buffer.Slice(bytesRead)); + uint itemType = BinaryPrimitives.ReadUInt32BigEndian(buffer[bytesRead..]); bytesRead += 4; - item.Name = ReadNullTerminatedString(buffer.Slice(bytesRead)); + item = new HeicItem(itemId, itemType); + item.Name = ReadNullTerminatedString(buffer[bytesRead..]); bytesRead += item.Name.Length + 1; - if (item.Type == "mime") + if (item.Type == FourCharacterCode.mime) { - item.ContentType = ReadNullTerminatedString(buffer.Slice(bytesRead)); + item.ContentType = ReadNullTerminatedString(buffer[bytesRead..]); bytesRead += item.ContentType.Length + 1; + // Optional field. if (bytesRead < boxLength) { - item.ContentEncoding = ReadNullTerminatedString(buffer.Slice(bytesRead)); + item.ContentEncoding = ReadNullTerminatedString(buffer[bytesRead..]); bytesRead += item.ContentEncoding.Length + 1; } } - else if (item.Type == "uri ") + else if (item.Type == FourCharacterCode.uri) { - item.UriType = ReadNullTerminatedString(buffer.Slice(bytesRead)); - bytesRead += item.ContentEncoding.Length + 1; + item.UriType = ReadNullTerminatedString(buffer[bytesRead..]); + bytesRead += item.UriType.Length + 1; } } + + return bytesRead; } - private void ParseItemReference(BufferedReadStream stream, uint boxLength) + private void ParseItemReference(BufferedReadStream stream, long boxLength) { - Span buffer = new byte[length]; + Span buffer = new byte[boxLength]; stream.Read(buffer); int bytesRead = 0; bool largeIds = buffer[bytesRead] != 0; bytesRead += 4; - while(bytesRead < boxLength) + while (bytesRead < boxLength) { - ParseBoxHeader(buffer.Slice(bytesRead), out var subBoxLength, out var linkType); - var link = new HeicItemLink(); - link.Type = linkType; + ParseBoxHeader(buffer[bytesRead..], out long subBoxLength, out uint linkType); + uint sourceId; if (largeIds) { - link.Source = BinaryPrimitives.ReadUInt32BigEndian(buffer.Slice(bytesRead)); + sourceId = BinaryPrimitives.ReadUInt32BigEndian(buffer[bytesRead..]); bytesRead += 4; } else { - link.Source = BinaryPrimitives.ReadUInt16BigEndian(buffer.Slice(bytesRead)); + sourceId = BinaryPrimitives.ReadUInt16BigEndian(buffer[bytesRead..]); bytesRead += 2; } - var count = BinaryPrimitives.ReadUInt16BigEndian(buffer.Slice(bytesRead)); + HeicItemLink link = new(linkType, sourceId); + + int count = BinaryPrimitives.ReadUInt16BigEndian(buffer[bytesRead..]); bytesRead += 2; - for(uint i = 0; i < count; i++) + for (uint i = 0; i < count; i++) { uint destId; if (largeIds) { - destId = BinaryPrimitives.ReadUInt32BigEndian(buffer.Slice(bytesRead)); + destId = BinaryPrimitives.ReadUInt32BigEndian(buffer[bytesRead..]); bytesRead += 4; } else { - destId = BinaryPrimitives.ReadUInt16BigEndian(buffer.Slice(bytesRead)); + destId = BinaryPrimitives.ReadUInt16BigEndian(buffer[bytesRead..]); bytesRead += 2; } - link.Destinations.Add(destId); + + link.DestinationIds.Add(destId); } - itemLinks.Add(link); + this.itemLinks!.Add(link); } } - private void ParseItemPropertyContainer(BufferedReadStream stream, uint boxLength) + private void ParsePrimaryItem(BufferedReadStream stream, long boxLength) { - var containerLength = ReadBoxHeader(stream, out var containerType); + // BoxLength should be 6 or 8. + Span buffer = stackalloc byte[(int)boxLength]; + stream.Read(buffer); + byte version = buffer[0]; + if (version == 0) + { + this.primaryItem = BinaryPrimitives.ReadUInt16BigEndian(buffer[4..]); + } + else + { + this.primaryItem = BinaryPrimitives.ReadUInt32BigEndian(buffer[4..]); + } + } + + private void ParseItemPropertyContainer(BufferedReadStream stream, long boxLength) + { + // Cannot use Dictionary here, Properties can have multiple instances with the same key. + List> properties = new(); + long containerLength = this.ReadBoxHeader(stream, out uint containerType); if (containerType == FourCharacterCode.ipco) { // Parse Item Property Container, which is just an array of preperty boxes. - var endPosition = stream.Position + containerLength; + long endPosition = stream.Position + containerLength; while (stream.Position < endPosition) { - var length = ReadBoxHeader(stream, out var boxType); + int length = (int)this.ReadBoxHeader(stream, out uint boxType); + Span buffer = stackalloc byte[length]; switch (boxType) { - case HeicItemPropertyType.ImageSpatialExtents: + case FourCharacterCode.ispe: // Length should be 12. - Span buffer = stackalloc new byte[length]; stream.Read(buffer); + // Skip over version (8 bits) and flags (24 bits). - var width = BinaryPrimitives.ReadUInt32BigEndian(buffer.Slice(4)); - var height = BinaryPrimitives.ReadUInt32BigEndian(buffer.Slice(8)); + uint width = BinaryPrimitives.ReadUInt32BigEndian(buffer[4..]); + uint height = BinaryPrimitives.ReadUInt32BigEndian(buffer[8..]); + properties.Add(new KeyValuePair(FourCharacterCode.ispe, new uint[] { width, height })); break; - case HeicItemPropertyType.PixelAspectRatio: + case FourCharacterCode.pasp: // Length should be 8. - Span buffer = stackalloc new byte[length]; stream.Read(buffer); - var horizontalSpacing = BinaryPrimitives.ReadUInt32BigEndian(buffer); - var verticalSpacing = BinaryPrimitives.ReadUInt32BigEndian(buffer.Slice(4)); + uint horizontalSpacing = BinaryPrimitives.ReadUInt32BigEndian(buffer); + uint verticalSpacing = BinaryPrimitives.ReadUInt32BigEndian(buffer[4..]); + properties.Add(new KeyValuePair(FourCharacterCode.pasp, new uint[] { horizontalSpacing, verticalSpacing })); break; - case HeicItemPropertyType.PixelInformation: - Span buffer = stackalloc new byte[length]; + case FourCharacterCode.pixi: stream.Read(buffer); + // Skip over version (8 bits) and flags (24 bits). - var channelCount = buffer[4]; + int channelCount = buffer[4]; int offset = 5; int bitsPerPixel = 0; for (int i = 0; i < channelCount; i++) { - bitsPerPixel += buffer[i]; + bitsPerPixel += buffer[offset + i]; } + + properties.Add(new KeyValuePair(FourCharacterCode.pixi, new int[] { channelCount, bitsPerPixel })); + break; - case HeicItemPropertyType.AcessibilityText: - case HeicItemPropertyType.ImageMirror: - case HeicItemPropertyType.ImageRotation: - case HeicItemPropertyType.ImageScaling: - case HeicItemPropertyType.RelativeLocation: - case HeicItemPropertyType.UserDescription; + case FourCharacterCode.altt: + case FourCharacterCode.imir: + case FourCharacterCode.irot: + case FourCharacterCode.iscl: + case FourCharacterCode.rloc: + case FourCharacterCode.udes: // TODO: Implement break; default: @@ -382,42 +422,49 @@ internal sealed class HeicDecoderCore : IImageDecoderInternals else if (containerType == FourCharacterCode.ipma) { // Parse Item Property Association + Span buffer = stackalloc byte[(int)boxLength]; + byte version = buffer[0]; + byte flags = buffer[3]; + int itemId; + int bytesRead = 4; + if (version < 1) + { + itemId = BinaryPrimitives.ReadUInt16BigEndian(buffer[bytesRead..]); + bytesRead += 2; + } + else + { + itemId = (int)BinaryPrimitives.ReadUInt32BigEndian(buffer[bytesRead..]); + bytesRead += 4; + } + + int associationCount = buffer[bytesRead++]; + for (int i = 0; i < associationCount; i++) + { + uint propId; + if (flags == 1) + { + propId = BinaryPrimitives.ReadUInt16BigEndian(buffer[bytesRead..]) & 0x4FFFU; + bytesRead += 2; + } + else + { + propId = buffer[bytesRead++] & 0x4FU; + } + + this.items![itemId].SetProperty(properties[(int)propId]); + } } } - private void ParseMediaData(BufferedReadStream stream, uint boxLength) + private void ParseMediaData(BufferedReadStream stream, long boxLength) { // TODO: Implement } - /// - /// Forwards the stream to just past the Start of NAL marker. - /// - private void FindStartOfNal(BufferedReadStream stream) + private static string ReadNullTerminatedString(Span span) { - uint i = stream.Position; - uint length = 0; - var dataLength = stream.Length; - - while (i < streamLength) - { - var current = stream.ReadByte(); - if (current == 0) - { - length++; - } - else if (length > 1 && current == 1) - { - // Found the marker ! - //length++; - break; - } - else - { - // False alarm, resetting... - length = 0; - } - i++; - } + Span bytes = span[..span.IndexOf((byte)0)]; + return Encoding.UTF8.GetString(bytes); } } diff --git a/src/ImageSharp/Formats/Heic/HeicEncoderCore.cs b/src/ImageSharp/Formats/Heic/HeicEncoderCore.cs index 291ece9d52..55f892a8ce 100644 --- a/src/ImageSharp/Formats/Heic/HeicEncoderCore.cs +++ b/src/ImageSharp/Formats/Heic/HeicEncoderCore.cs @@ -47,7 +47,6 @@ internal sealed class HeicEncoderCore : IImageEncoderInternals Guard.NotNull(stream, nameof(stream)); // TODO: Implement - stream.Flush(); } } diff --git a/src/ImageSharp/Formats/Heic/HeicImageFormatDetector.cs b/src/ImageSharp/Formats/Heic/HeicImageFormatDetector.cs index 583d9f4f54..eeda941092 100644 --- a/src/ImageSharp/Formats/Heic/HeicImageFormatDetector.cs +++ b/src/ImageSharp/Formats/Heic/HeicImageFormatDetector.cs @@ -1,6 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. +using System.Buffers.Binary; using System.Diagnostics.CodeAnalysis; namespace SixLabors.ImageSharp.Formats.Heic; @@ -11,8 +12,7 @@ namespace SixLabors.ImageSharp.Formats.Heic; public sealed class HeicImageFormatDetector : IImageFormatDetector { /// - int HeaderSize => 12; - + public int HeaderSize => 12; /// public bool TryDetectFormat(ReadOnlySpan header, [NotNullWhen(true)] out IImageFormat? format) @@ -21,10 +21,7 @@ public sealed class HeicImageFormatDetector : IImageFormatDetector return format != null; } - private static bool IsSupportedFileFormat(ReadOnlySpan header) - { - return - BinaryPrimitives.ReadUInt32BigEndian(header.Slice(4)) == FourCharacterCode.ftyp && - BinaryPrimitives.ReadUInt32BigEndian(header.Slice(8)) == FourCharacterCode.heic - } + private static bool IsSupportedFileFormat(ReadOnlySpan header) => + BinaryPrimitives.ReadUInt32BigEndian(header.Slice(4)) == FourCharacterCode.ftyp && + BinaryPrimitives.ReadUInt32BigEndian(header.Slice(8)) == FourCharacterCode.heic; } diff --git a/src/ImageSharp/Formats/Heic/HeicItem.cs b/src/ImageSharp/Formats/Heic/HeicItem.cs index 0dfc442d47..4349a65858 100644 --- a/src/ImageSharp/Formats/Heic/HeicItem.cs +++ b/src/ImageSharp/Formats/Heic/HeicItem.cs @@ -3,30 +3,62 @@ namespace SixLabors.ImageSharp.Formats.Heic; -public enum HeicItemType -{ - Hvc1, - Grid, - Exif -} - -public class HeicItemLink -{ - public uint Type; - public HeicItem Source; - public List Destinations = new List(); -} - /// /// Provides definition for a HEIC Item. /// -public class HeicItem +public class HeicItem(uint type, uint id) { - public uint Id; - public HeicItemType type; - public string Name; - public string ContentType; - public string ContentEncoding; - public uint ExtensionType; - public string UriType; + /// + /// Gets the ID of this Item. + /// + public uint Id { get; } = id; + + /// + /// Gets the type of this Item. + /// + public uint Type { get; } = type; + + /// + /// Gets or sets the name of this item. + /// + public string? Name { get; set; } + + /// + /// Gets or sets the Content Type of this item. + /// + public string? ContentType { get; set; } + + /// + /// Gets or sets the Content Encoding of this item. + /// + public string? ContentEncoding { get; set; } + + /// + /// Gets or sets the type of extension of this item. + /// + public uint ExtensionType { get; set; } + + /// + /// Gets or sets the URI of this item. + /// + public string? UriType { get; set; } + + /// + /// Sets a property on this item. + /// + public void SetProperty(KeyValuePair pair) + { + switch (pair.Key) + { + case FourCharacterCode.ispe: + // Set image extents + break; + case FourCharacterCode.pasp: + // Set pixel aspact ratio + break; + case FourCharacterCode.pixi: + // Set pixel information + break; + } + } } diff --git a/src/ImageSharp/Formats/Heic/HeicItemLink.cs b/src/ImageSharp/Formats/Heic/HeicItemLink.cs new file mode 100644 index 0000000000..a93946c2c4 --- /dev/null +++ b/src/ImageSharp/Formats/Heic/HeicItemLink.cs @@ -0,0 +1,25 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Formats.Heic; + +/// +/// Link between instances within the same HEIC file. +/// +public class HeicItemLink(uint type, uint sourceId) +{ + /// + /// Gets the type of link. + /// + public uint Type { get; } = type; + + /// + /// Gets the ID of the source item of this link. + /// + public uint SourceId { get; } = sourceId; + + /// + /// Gets the destination item IDs of this link. + /// + public List DestinationIds { get; } = new List(); +} diff --git a/src/ImageSharp/Formats/Heic/HeicItemPropertyType.cs b/src/ImageSharp/Formats/Heic/HeicItemPropertyType.cs deleted file mode 100644 index 157860f184..0000000000 --- a/src/ImageSharp/Formats/Heic/HeicItemPropertyType.cs +++ /dev/null @@ -1,23 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Formats.Heic; - -/// -/// Provides enumeration of supported Item Property Types for HEIC. -/// -public enum HeicItemPropertyType : uint -{ - Invalid = 0, - AcessibilityText = FourCharacterCode.altt, - Colour = FourCharacterCode.colr, - HvcConfiguration = FourCharacterCode.hvcC, - ImageMirror = FourCharacterCode.imir, - ImageRotation = FourCharacterCode.irot, - ImageScaling = FourCharacterCode.iscl, - ImageSpatialExtents = FourCharacterCode.ispe, - PixelAspectRatio = FourCharacterCode.pasp, - PixelInformation = FourCharacterCode.pixi, - RelativeLocation = FourCharacterCode.rloc, - UserDescription = FourCharacterCode.udes, -} diff --git a/src/ImageSharp/Formats/Heic/HeicMetaSubBoxType.cs b/src/ImageSharp/Formats/Heic/HeicMetaSubBoxType.cs deleted file mode 100644 index 46d29878c3..0000000000 --- a/src/ImageSharp/Formats/Heic/HeicMetaSubBoxType.cs +++ /dev/null @@ -1,22 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Formats.Heic; - -/// -/// Provides enumeration of supported sub type boxes within the 'meta' box for HEIC. -/// -public enum HeicMetaSubBoxType : uint -{ - Invalid = 0, - DataInformation = 0, // 'dinf' - GroupsList = 0, // 'grpl' - Handler = 0, // 'hdlr' - ItemData = 0, // 'idat' - ItemInfo = 0, // 'iinf' - ItemLocation = 0, // 'iloc' - ItemProperty = 0, // 'iprp' - ItemProtection = 0, // 'ipro' - ItemReference = 0, // 'iref' - PrimaryItem = 0, // 'pitm' -}