diff --git a/src/ImageSharp/Formats/Tiff/Ifd/DirectoryReader.cs b/src/ImageSharp/Formats/Tiff/Ifd/DirectoryReader.cs index b961656538..3e8f138b74 100644 --- a/src/ImageSharp/Formats/Tiff/Ifd/DirectoryReader.cs +++ b/src/ImageSharp/Formats/Tiff/Ifd/DirectoryReader.cs @@ -1,9 +1,10 @@ // Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. +using System; using System.Collections.Generic; using System.IO; - +using SixLabors.ImageSharp.Formats.Experimental.Tiff.Constants; using SixLabors.ImageSharp.Metadata.Profiles.Exif; namespace SixLabors.ImageSharp.Formats.Experimental.Tiff @@ -13,60 +14,82 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff /// internal class DirectoryReader { - private readonly ByteOrder byteOrder; - private readonly Stream stream; private uint nextIfdOffset; - public DirectoryReader(ByteOrder byteOrder, Stream stream) - { - this.byteOrder = byteOrder; - this.stream = stream; - } + // used for sequential read big values (actual for multiframe big files) + // todo: different tags can link to the same data (stream offset) - investigate + private readonly SortedList lazyLoaders = new SortedList(new DuplicateKeyComparer()); + + public DirectoryReader(Stream stream) => this.stream = stream; + + public ByteOrder ByteOrder { get; private set; } public IEnumerable Read() { - this.nextIfdOffset = new HeaderReader(this.byteOrder, this.stream).ReadFileHeader(); - - IEnumerable> ifdList = this.ReadIfds(); + this.ByteOrder = ReadByteOrder(this.stream); + this.nextIfdOffset = new HeaderReader(this.stream, this.ByteOrder).ReadFileHeader(); + return this.ReadIfds(); + } - var list = new List(); - foreach (List ifd in ifdList) + private static ByteOrder ReadByteOrder(Stream stream) + { + var headerBytes = new byte[2]; + stream.Read(headerBytes, 0, 2); + if (headerBytes[0] == TiffConstants.ByteOrderLittleEndian && headerBytes[1] == TiffConstants.ByteOrderLittleEndian) { - var profile = new ExifProfile(); - profile.InitializeInternal(ifd); - list.Add(profile); + return ByteOrder.LittleEndian; + } + else if (headerBytes[0] == TiffConstants.ByteOrderBigEndian && headerBytes[1] == TiffConstants.ByteOrderBigEndian) + { + return ByteOrder.BigEndian; } - return list; + throw TiffThrowHelper.InvalidHeader(); } - private IEnumerable> ReadIfds() + private IEnumerable ReadIfds() { - var valuesList = new List>(); - var readersList = new SortedList(); - while (this.nextIfdOffset != 0) + var readers = new List(); + while (this.nextIfdOffset != 0 && this.nextIfdOffset < this.stream.Length) { - var reader = new EntryReader(this.byteOrder, this.stream, this.nextIfdOffset); - List values = reader.ReadValues(); - valuesList.Add(values); + var reader = new EntryReader(this.stream, this.ByteOrder, this.nextIfdOffset, this.lazyLoaders); + reader.ReadTags(); this.nextIfdOffset = reader.NextIfdOffset; - if (reader.BigValuesOffset.HasValue) - { - readersList.Add(reader.BigValuesOffset.Value, reader); - } + readers.Add(reader); } // sequential reading big values - foreach (EntryReader reader in readersList.Values) + foreach (Action loader in this.lazyLoaders.Values) { - reader.LoadBigValues(); + loader(); } - return valuesList; + var list = new List(); + foreach (EntryReader reader in readers) + { + var profile = new ExifProfile(reader.Values, reader.InvalidTags); + list.Add(profile); + } + + return list; + } + + /// used for possiblity add a duplicate offsets (but tags don't duplicate). + /// The type of the key. + private class DuplicateKeyComparer : IComparer + where TKey : IComparable + { + public int Compare(TKey x, TKey y) + { + int result = x.CompareTo(y); + + // Handle equality as beeing greater + return (result == 0) ? 1 : result; + } } } } diff --git a/src/ImageSharp/Formats/Tiff/Ifd/EntryReader.cs b/src/ImageSharp/Formats/Tiff/Ifd/EntryReader.cs index 53ac093234..c81474a9d4 100644 --- a/src/ImageSharp/Formats/Tiff/Ifd/EntryReader.cs +++ b/src/ImageSharp/Formats/Tiff/Ifd/EntryReader.cs @@ -1,6 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. +using System; using System.Collections.Generic; using System.IO; @@ -9,38 +10,41 @@ using SixLabors.ImageSharp.Metadata.Profiles.Exif; namespace SixLabors.ImageSharp.Formats.Experimental.Tiff { - internal class EntryReader : ExifReader + internal class EntryReader : BaseExifReader { private readonly uint startOffset; - public EntryReader(ByteOrder byteOrder, Stream stream, uint ifdOffset) - : base(byteOrder == ByteOrder.BigEndian, stream) => + private readonly SortedList lazyLoaders; + + public EntryReader(Stream stream, ByteOrder byteOrder, uint ifdOffset, SortedList lazyLoaders) + : base(stream) + { + this.IsBigEndian = byteOrder == ByteOrder.BigEndian; this.startOffset = ifdOffset; + this.lazyLoaders = lazyLoaders; + } - public uint? BigValuesOffset => this.LazyStartOffset; + public List Values { get; } = new List(); public uint NextIfdOffset { get; private set; } - public override List ReadValues() + public void ReadTags() { - var values = new List(); - this.AddValues(values, this.startOffset); - + this.ReadValues(this.Values, this.startOffset); this.NextIfdOffset = this.ReadUInt32(); - this.AddSubIfdValues(values); - return values; + this.ReadSubIfd(this.Values); } - public void LoadBigValues() => this.LazyLoad(); + protected override void RegisterExtLoader(uint offset, Action reader) => + this.lazyLoaders.Add(offset, reader); } - internal class HeaderReader : ExifReader + internal class HeaderReader : BaseExifReader { - public HeaderReader(ByteOrder byteOrder, Stream stream) - : base(byteOrder == ByteOrder.BigEndian, stream) - { - } + public HeaderReader(Stream stream, ByteOrder byteOrder) + : base(stream) => + this.IsBigEndian = byteOrder == ByteOrder.BigEndian; public uint FirstIfdOffset { get; private set; } @@ -55,5 +59,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff this.FirstIfdOffset = this.ReadUInt32(); return this.FirstIfdOffset; } + + protected override void RegisterExtLoader(uint offset, Action reader) => throw new NotImplementedException(); } } diff --git a/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs b/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs index ee1befe237..8cd88b963b 100644 --- a/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs +++ b/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs @@ -2,7 +2,6 @@ // Licensed under the Apache License, Version 2.0. using System.Collections.Generic; -using System.IO; using System.Threading; using SixLabors.ImageSharp.Formats.Experimental.Tiff.Compression; @@ -102,8 +101,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff where TPixel : unmanaged, IPixel { this.inputStream = stream; - ByteOrder byteOrder = ReadByteOrder(stream); - var reader = new DirectoryReader(byteOrder, stream); + var reader = new DirectoryReader(stream); IEnumerable directories = reader.Read(); @@ -116,7 +114,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff framesMetadata.Add(frameMetadata); } - ImageMetadata metadata = TiffDecoderMetadataCreator.Create(framesMetadata, this.ignoreMetadata, byteOrder); + ImageMetadata metadata = TiffDecoderMetadataCreator.Create(framesMetadata, this.ignoreMetadata, reader.ByteOrder); // todo: tiff frames can have different sizes { @@ -140,40 +138,23 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff public IImageInfo Identify(BufferedReadStream stream, CancellationToken cancellationToken) { this.inputStream = stream; - ByteOrder byteOrder = ReadByteOrder(stream); - var reader = new DirectoryReader(byteOrder, stream); + var reader = new DirectoryReader(stream); IEnumerable directories = reader.Read(); var framesMetadata = new List(); foreach (ExifProfile ifd in directories) { - var meta = new TiffFrameMetadata() { FrameTags = ifd }; + var meta = new TiffFrameMetadata() { ExifProfile = ifd }; framesMetadata.Add(meta); } - ImageMetadata metadata = TiffDecoderMetadataCreator.Create(framesMetadata, this.ignoreMetadata, byteOrder); + ImageMetadata metadata = TiffDecoderMetadataCreator.Create(framesMetadata, this.ignoreMetadata, reader.ByteOrder); TiffFrameMetadata root = framesMetadata[0]; return new ImageInfo(new PixelTypeInfo(root.BitsPerPixel), (int)root.Width, (int)root.Height, metadata); } - private static ByteOrder ReadByteOrder(Stream stream) - { - var headerBytes = new byte[2]; - stream.Read(headerBytes, 0, 2); - if (headerBytes[0] == TiffConstants.ByteOrderLittleEndian && headerBytes[1] == TiffConstants.ByteOrderLittleEndian) - { - return ByteOrder.LittleEndian; - } - else if (headerBytes[0] == TiffConstants.ByteOrderBigEndian && headerBytes[1] == TiffConstants.ByteOrderBigEndian) - { - return ByteOrder.BigEndian; - } - - throw TiffThrowHelper.InvalidHeader(); - } - /// /// Decodes the image data from a specified IFD. /// @@ -188,7 +169,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff { var coreMetadata = new ImageFrameMetadata(); frameMetaData = coreMetadata.GetTiffMetadata(); - frameMetaData.FrameTags = tags; + frameMetaData.ExifProfile = tags; this.VerifyAndParse(frameMetaData); diff --git a/src/ImageSharp/Formats/Tiff/TiffEncoderEntriesCollector.cs b/src/ImageSharp/Formats/Tiff/TiffEncoderEntriesCollector.cs index e440ae3dc7..f0e41c63bb 100644 --- a/src/ImageSharp/Formats/Tiff/TiffEncoderEntriesCollector.cs +++ b/src/ImageSharp/Formats/Tiff/TiffEncoderEntriesCollector.cs @@ -134,7 +134,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff private void ProcessMetadata(TiffFrameMetadata frameMetadata) { - foreach (IExifValue entry in frameMetadata.FrameTags.Values) + foreach (IExifValue entry in frameMetadata.ExifProfile.Values) { // todo: skip subIfd if (entry.DataType == ExifDataType.Ifd) diff --git a/src/ImageSharp/Formats/Tiff/TiffFrameMetadata.cs b/src/ImageSharp/Formats/Tiff/TiffFrameMetadata.cs index a290301b0e..a099e1e193 100644 --- a/src/ImageSharp/Formats/Tiff/TiffFrameMetadata.cs +++ b/src/ImageSharp/Formats/Tiff/TiffFrameMetadata.cs @@ -33,7 +33,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff /// /// Gets the Tiff directory tags. /// - public ExifProfile FrameTags + public ExifProfile ExifProfile { get => this.frameTags ??= new ExifProfile(); internal set => this.frameTags = value; @@ -116,7 +116,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff public string ImageDescription { get => this.GetString(ExifTag.ImageDescription); - set => this.SetString(ExifTag.ImageDescription, value); + set => this.Set(ExifTag.ImageDescription, value); } /// @@ -125,7 +125,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff public string Make { get => this.GetString(ExifTag.Make); - set => this.SetString(ExifTag.Make, value); + set => this.Set(ExifTag.Make, value); } /// @@ -134,7 +134,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff public string Model { get => this.GetString(ExifTag.Model); - set => this.SetString(ExifTag.Model, value); + set => this.Set(ExifTag.Model, value); } /// Gets for each strip, the byte offset of that strip.. @@ -181,7 +181,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff public string Software { get => this.GetString(ExifTag.Software); - set => this.SetString(ExifTag.Software, value); + set => this.Set(ExifTag.Software, value); } /// @@ -190,7 +190,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff public string DateTime { get => this.GetString(ExifTag.DateTime); - set => this.SetString(ExifTag.DateTime, value); + set => this.Set(ExifTag.DateTime, value); } /// @@ -199,7 +199,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff public string Artist { get => this.GetString(ExifTag.Artist); - set => this.SetString(ExifTag.Artist, value); + set => this.Set(ExifTag.Artist, value); } /// @@ -208,7 +208,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff public string HostComputer { get => this.GetString(ExifTag.HostComputer); - set => this.SetString(ExifTag.HostComputer, value); + set => this.Set(ExifTag.HostComputer, value); } /// @@ -227,7 +227,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff public string Copyright { get => this.GetString(ExifTag.Copyright); - set => this.SetString(ExifTag.Copyright, value); + set => this.Set(ExifTag.Copyright, value); } /// @@ -247,7 +247,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff public void ClearMetadata() { var tags = new List(); - foreach (IExifValue entry in this.FrameTags.Values) + foreach (IExifValue entry in this.ExifProfile.Values) { switch ((ExifTagValue)(ushort)entry.Tag) { @@ -267,12 +267,10 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff } } - var profile = new ExifProfile(); - profile.InitializeInternal(tags); - this.FrameTags = profile; + this.ExifProfile = new ExifProfile(tags, this.ExifProfile.InvalidTags); } /// - public IDeepCloneable DeepClone() => new TiffFrameMetadata() { FrameTags = this.FrameTags.DeepClone() }; + public IDeepCloneable DeepClone() => new TiffFrameMetadata() { ExifProfile = this.ExifProfile.DeepClone() }; } } diff --git a/src/ImageSharp/Formats/Tiff/TiffFrameMetadataExtensions.cs b/src/ImageSharp/Formats/Tiff/TiffFrameMetadataExtensions.cs index 7229011f57..7521e93343 100644 --- a/src/ImageSharp/Formats/Tiff/TiffFrameMetadataExtensions.cs +++ b/src/ImageSharp/Formats/Tiff/TiffFrameMetadataExtensions.cs @@ -31,15 +31,13 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff public static bool TryGetArray(this TiffFrameMetadata meta, ExifTag tag, out T[] result) where T : struct { - foreach (IExifValue entry in meta.FrameTags.Values) + IExifValue obj = meta.ExifProfile.GetValueInternal(tag); + if (obj != null) { - if (entry.Tag == tag) - { - DebugGuard.IsTrue(entry.IsArray, "Expected array entry"); - - result = (T[])entry.GetValue(); - return true; - } + DebugGuard.IsTrue(obj.IsArray, "Expected array entry"); + object value = obj.GetValue(); + result = (T[])value; + return true; } result = null; @@ -65,23 +63,20 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff public static string GetString(this TiffFrameMetadata meta, ExifTag tag) { - foreach (IExifValue entry in meta.FrameTags.Values) + IExifValue obj = meta.ExifProfile.GetValueInternal(tag); + if (obj != null) { - if (entry.Tag == tag) - { - DebugGuard.IsTrue(entry.DataType == ExifDataType.Ascii, "Expected string entry"); - object value = entry.GetValue(); - DebugGuard.IsTrue(value is string, "Expected string entry"); - - return (string)value; - } + DebugGuard.IsTrue(obj.DataType == ExifDataType.Ascii, "Expected string entry"); + object value = obj.GetValue(); + DebugGuard.IsTrue(value is string, "Expected string entry"); + return (string)value; } return null; } - public static void SetString(this TiffFrameMetadata meta, ExifTag tag, string value) => - meta.FrameTags.SetValueInternal(tag, value); + public static void Set(this TiffFrameMetadata meta, ExifTag tag, object value) => + meta.ExifProfile.SetValueInternal(tag, value); public static TEnum? GetSingleEnumNullable(this TiffFrameMetadata meta, ExifTag tag) where TEnum : struct @@ -100,11 +95,6 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff where TTagValue : struct => meta.GetSingleEnumNullable(tag) ?? (defaultValue != null ? defaultValue.Value : throw TiffThrowHelper.TagNotFound(nameof(tag))); - public static void SetSingleEnum(this TiffFrameMetadata meta, ExifTag tag, TEnum value) - where TEnum : struct - where TTagValue : struct - => meta.FrameTags.SetValueInternal(tag, value); - public static T GetSingle(this TiffFrameMetadata meta, ExifTag tag) where T : struct { @@ -119,42 +109,25 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff public static bool TryGetSingle(this TiffFrameMetadata meta, ExifTag tag, out T result) where T : struct { - foreach (IExifValue entry in meta.FrameTags.Values) + IExifValue obj = meta.ExifProfile.GetValueInternal(tag); + if (obj != null) { - if (entry.Tag == tag) - { - DebugGuard.IsTrue(!entry.IsArray, "Expected non array entry"); - - object value = entry.GetValue(); - - result = (T)value; - return true; - } + DebugGuard.IsTrue(!obj.IsArray, "Expected non array entry"); + object value = obj.GetValue(); + result = (T)value; + return true; } result = default; return false; } - public static void SetSingle(this TiffFrameMetadata meta, ExifTag tag, T value) - where T : struct - => meta.FrameTags.SetValueInternal(tag, value); - public static bool Remove(this TiffFrameMetadata meta, ExifTag tag) { - IExifValue obj = null; - foreach (IExifValue entry in meta.FrameTags.Values) - { - if (entry.Tag == tag) - { - obj = entry; - break; - } - } - + IExifValue obj = meta.ExifProfile.GetValueInternal(tag); if (obj != null) { - return meta.FrameTags.RemoveValue(obj.Tag); + return meta.ExifProfile.RemoveValue(obj.Tag); } return false; diff --git a/src/ImageSharp/Formats/Tiff/TiffFrameMetadataResolutionExtensions.cs b/src/ImageSharp/Formats/Tiff/TiffFrameMetadataResolutionExtensions.cs index f17e9dddc2..e45420c917 100644 --- a/src/ImageSharp/Formats/Tiff/TiffFrameMetadataResolutionExtensions.cs +++ b/src/ImageSharp/Formats/Tiff/TiffFrameMetadataResolutionExtensions.cs @@ -30,7 +30,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff break; } - meta.SetSingle(ExifTag.ResolutionUnit, (ushort)unit + 1); + meta.Set(ExifTag.ResolutionUnit, (ushort)unit + 1); meta.SetResolution(ExifTag.XResolution, horizontal); meta.SetResolution(ExifTag.YResolution, vertical); } @@ -94,7 +94,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff break; } - meta.SetSingle(tag, new Rational(res)); + meta.Set(tag, new Rational(res)); } } } diff --git a/src/ImageSharp/Metadata/Profiles/Exif/ExifProfile.cs b/src/ImageSharp/Metadata/Profiles/Exif/ExifProfile.cs index 753b5d29c4..39c8c22936 100644 --- a/src/ImageSharp/Metadata/Profiles/Exif/ExifProfile.cs +++ b/src/ImageSharp/Metadata/Profiles/Exif/ExifProfile.cs @@ -53,6 +53,18 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif this.InvalidTags = Array.Empty(); } + /// + /// Initializes a new instance of the class. + /// + /// The values. + /// The invalid tags. + internal ExifProfile(List values, IReadOnlyList invalidTags) + { + this.Parts = ExifParts.All; + this.values = values; + this.InvalidTags = invalidTags; + } + /// /// Initializes a new instance of the class /// by making a copy from another EXIF profile. @@ -259,9 +271,6 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif this.SyncResolution(ExifTag.YResolution, metadata.VerticalResolution); } - internal void InitializeInternal(List values) => - this.values = values; - private void SyncResolution(ExifTag tag, double resolution) { IExifValue value = this.GetValue(tag); diff --git a/src/ImageSharp/Metadata/Profiles/Exif/ExifReader.cs b/src/ImageSharp/Metadata/Profiles/Exif/ExifReader.cs index dd4fada78b..57a9a8cc6e 100644 --- a/src/ImageSharp/Metadata/Profiles/Exif/ExifReader.cs +++ b/src/ImageSharp/Metadata/Profiles/Exif/ExifReader.cs @@ -12,70 +12,13 @@ using System.Text; namespace SixLabors.ImageSharp.Metadata.Profiles.Exif { - /// - /// Reads and parses EXIF data from a stream. - /// - internal class ExifReader + internal class ExifReader : BaseExifReader { - private readonly Stream data; - - private readonly byte[] offsetBuffer = new byte[4]; - private readonly byte[] buf4 = new byte[4]; - private readonly byte[] buf2 = new byte[2]; - - // used for sequential read big values (actual for multiframe big files) - // todo: different tags can link to the same data (stream offset) - investigate - private readonly SortedList lazyLoaders = new SortedList(new DuplicateKeyComparer()); + private readonly List loaders = new List(); - private bool isBigEndian; - - private List invalidTags; - - private uint exifOffset = 0; - - private uint gpsOffset = 0; - - public ExifReader(bool isBigEndian, Stream stream) + public ExifReader(byte[] exifData) + : base(new MemoryStream(exifData ?? throw new ArgumentNullException(nameof(exifData)))) { - this.isBigEndian = isBigEndian; - this.data = stream ?? throw new ArgumentNullException(nameof(stream)); - } - - public ExifReader(byte[] exifData) => - this.data = new MemoryStream(exifData ?? throw new ArgumentNullException(nameof(exifData))); - - private delegate TDataType ConverterMethod(ReadOnlySpan data); - - /// - /// Gets the invalid tags. - /// - public IReadOnlyList InvalidTags => this.invalidTags ?? (IReadOnlyList)Array.Empty(); - - /// - /// Gets the thumbnail length in the byte stream. - /// - public uint ThumbnailLength { get; private set; } - - /// - /// Gets the thumbnail offset position in the byte stream. - /// - public uint ThumbnailOffset { get; private set; } - - protected uint? LazyStartOffset => this.lazyLoaders.Count > 0 ? this.lazyLoaders.Keys[0] : (uint?)null; - - private uint Length => (uint)this.data.Length; - - private int RemainingLength - { - get - { - if (this.data.Position >= this.data.Length) - { - return 0; - } - - return (int)(this.data.Length - this.data.Position); - } } /// @@ -84,12 +27,12 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif /// /// The . /// - public virtual List ReadValues() + public List ReadValues() { var values = new List(); - // Exif header: II == 0x4949 - this.isBigEndian = this.ReadUInt16() != 0x4949; + // II == 0x4949 + this.IsBigEndian = this.ReadUInt16() != 0x4949; if (this.ReadUInt16() != 0x002A) { @@ -97,38 +40,107 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif } uint ifdOffset = this.ReadUInt32(); - this.AddValues(values, ifdOffset); + this.ReadValues(values, ifdOffset); uint thumbnailOffset = this.ReadUInt32(); this.GetThumbnail(thumbnailOffset); - this.AddSubIfdValues(values); - this.LazyLoad(); + this.ReadSubIfd(values); + + foreach (Action loader in this.loaders) + { + loader(); + } return values; } - protected void LazyLoad() + protected override void RegisterExtLoader(uint offset, Action loader) => this.loaders.Add(loader); + + private void GetThumbnail(uint offset) { - foreach (Action act in this.lazyLoaders.Values) + if (offset == 0) + { + return; + } + + var values = new List(); + this.ReadValues(values, offset); + + foreach (ExifValue value in values) { - act(); + if (value == ExifTag.JPEGInterchangeFormat) + { + this.ThumbnailOffset = ((ExifLong)value).Value; + } + else if (value == ExifTag.JPEGInterchangeFormatLength) + { + this.ThumbnailLength = ((ExifLong)value).Value; + } } } + } + + /// + /// Reads and parses EXIF data from a stream. + /// + internal abstract class BaseExifReader + { + private readonly byte[] offsetBuffer = new byte[4]; + private readonly byte[] buf4 = new byte[4]; + private readonly byte[] buf2 = new byte[2]; + + private readonly Stream data; + + private bool isBigEndian; + + private List invalidTags; + + private uint exifOffset; + + private uint gpsOffset; + + protected BaseExifReader(Stream stream) => + this.data = stream ?? throw new ArgumentNullException(nameof(stream)); + + private delegate TDataType ConverterMethod(ReadOnlySpan data); + + /// + /// Gets the invalid tags. + /// + public IReadOnlyList InvalidTags => this.invalidTags ?? (IReadOnlyList)Array.Empty(); + + /// + /// Gets or sets the thumbnail length in the byte stream. + /// + public uint ThumbnailLength { get; protected set; } + + /// + /// Gets or sets the thumbnail offset position in the byte stream. + /// + public uint ThumbnailOffset { get; protected set; } + + public bool IsBigEndian + { + get => this.isBigEndian; + protected set => this.isBigEndian = value; + } + + protected abstract void RegisterExtLoader(uint offset, Action loader); /// - /// Adds the collection of EXIF values to the reader. + /// Reads the values to the values collection. /// /// The values. - /// The index. - protected void AddValues(List values, uint index) + /// The IFD offset. + protected void ReadValues(List values, uint offset) { - if (index > this.Length) + if (offset > this.data.Length) { return; } - this.Seek(index); + this.Seek(offset); int count = this.ReadUInt16(); for (int i = 0; i < count; i++) @@ -137,16 +149,16 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif } } - protected void AddSubIfdValues(List values) + protected void ReadSubIfd(List values) { if (this.exifOffset != 0) { - this.AddValues(values, this.exifOffset); + this.ReadValues(values, this.exifOffset); } if (this.gpsOffset != 0) { - this.AddValues(values, this.gpsOffset); + this.ReadValues(values, this.gpsOffset); } } @@ -280,7 +292,7 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif { // 2 | 2 | 4 | 4 // tag | type | count | value offset - if (this.RemainingLength < 12) + if ((this.data.Length - this.data.Position) < 12) { return; } @@ -316,28 +328,22 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif uint size = numberOfComponents * ExifDataTypes.GetSize(dataType); if (size > 4) { - uint newIndex = this.ConvertToUInt32(this.offsetBuffer); + uint newOffset = this.ConvertToUInt32(this.offsetBuffer); // Ensure that the new index does not overrun the data - if (newIndex > int.MaxValue || newIndex + size > this.Length) + if (newOffset > int.MaxValue || (newOffset + size) > this.data.Length) { this.AddInvalidTag(new UnkownExifTag(tag)); return; } - if (this.lazyLoaders.ContainsKey(newIndex)) - { - Debug.WriteLine($"Duplicate offset: tag={tag}, size={size}, offset={newIndex}"); - } - - this.lazyLoaders.Add(newIndex, () => + this.RegisterExtLoader(newOffset, () => { var dataBuffer = new byte[size]; - this.Seek(newIndex); + this.Seek(newOffset); if (this.TryReadSpan(dataBuffer)) { object value = this.ConvertValue(dataType, dataBuffer, numberOfComponents); - this.Add(values, exifValue, value); } }); @@ -391,7 +397,7 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif private bool TryReadSpan(Span span) { int length = span.Length; - if (this.RemainingLength < length) + if ((this.data.Length - this.data.Position) < length) { span = default; return false; @@ -411,29 +417,6 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif ? this.ConvertToShort(this.buf2) : default; - private void GetThumbnail(uint offset) - { - if (offset == 0) - { - return; - } - - var values = new List(); - this.AddValues(values, offset); - - foreach (ExifValue value in values) - { - if (value == ExifTag.JPEGInterchangeFormat) - { - this.ThumbnailOffset = ((ExifLong)value).Value; - } - else if (value == ExifTag.JPEGInterchangeFormatLength) - { - this.ThumbnailLength = ((ExifLong)value).Value; - } - } - } - private double ConvertToDouble(ReadOnlySpan buffer) { if (buffer.Length < 8) @@ -538,19 +521,5 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif ? BinaryPrimitives.ReadInt16BigEndian(buffer) : BinaryPrimitives.ReadInt16LittleEndian(buffer); } - - /// used for possiblity add a duplicate offsets (but tags don't duplicate). - /// The type of the key. - public class DuplicateKeyComparer : IComparer - where TKey : IComparable - { - public int Compare(TKey x, TKey y) - { - int result = x.CompareTo(y); - - // Handle equality as beeing greater - return (result == 0) ? 1 : result; - } - } } } diff --git a/tests/ImageSharp.Tests/Formats/Tiff/TiffMetadataTests.cs b/tests/ImageSharp.Tests/Formats/Tiff/TiffMetadataTests.cs index ad2d7e291b..75538a3f76 100644 --- a/tests/ImageSharp.Tests/Formats/Tiff/TiffMetadataTests.cs +++ b/tests/ImageSharp.Tests/Formats/Tiff/TiffMetadataTests.cs @@ -146,7 +146,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff Assert.Equal(10, image.Metadata.VerticalResolution); TiffFrameMetadata frame = image.Frames.RootFrame.Metadata.GetTiffMetadata(); - Assert.Equal(30, frame.FrameTags.Values.Count); + Assert.Equal(30, frame.ExifProfile.Values.Count); Assert.Equal(32u, frame.Width); Assert.Equal(32u, frame.Height); @@ -156,10 +156,10 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff Assert.Equal("This is Название", frame.ImageDescription); Assert.Equal("This is Изготовитель камеры", frame.Make); Assert.Equal("This is Модель камеры", frame.Model); - TiffTestUtils.Compare(new Number[] { 8 }, frame.StripOffsets); + Assert.Equal(new Number[] { 8u }, frame.StripOffsets, new NumberComparer()); Assert.Equal(1, frame.SamplesPerPixel); Assert.Equal(32u, frame.RowsPerStrip); - TiffTestUtils.Compare(new Number[] { 297 }, frame.StripByteCounts); + Assert.Equal(new Number[] { 297u }, frame.StripByteCounts, new NumberComparer()); Assert.Equal(10, frame.HorizontalResolution); Assert.Equal(10, frame.VerticalResolution); Assert.Equal(TiffPlanarConfiguration.Chunky, frame.PlanarConfiguration); @@ -178,8 +178,8 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff Assert.Equal(TiffPredictor.None, frame.Predictor); Assert.Null(frame.SampleFormat); Assert.Equal("This is Авторские права", frame.Copyright); - Assert.Equal(4, frame.FrameTags.GetValue(ExifTag.Rating).Value); - Assert.Equal(75, frame.FrameTags.GetValue(ExifTag.RatingPercent).Value); + Assert.Equal(4, frame.ExifProfile.GetValue(ExifTag.Rating).Value); + Assert.Equal(75, frame.ExifProfile.GetValue(ExifTag.RatingPercent).Value); } } diff --git a/tests/ImageSharp.Tests/Formats/Tiff/TiffTestUtils.cs b/tests/ImageSharp.Tests/Formats/Tiff/TiffTestUtils.cs index c679b3164d..5ac2f475ed 100644 --- a/tests/ImageSharp.Tests/Formats/Tiff/TiffTestUtils.cs +++ b/tests/ImageSharp.Tests/Formats/Tiff/TiffTestUtils.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. using System; +using System.Collections.Generic; using System.IO; using ImageMagick; @@ -54,20 +55,12 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff return result; } + } - public static void Compare(Number[] a1, Number[] a2) - { - Assert.True(a1 == null ^ a2 != null); - if (a1 == null /*&& a2 == null*/) - { - return; - } + internal class NumberComparer : IEqualityComparer + { + public bool Equals(Number x, Number y) => x.Equals(y); - Assert.Equal(a1.Length, a2.Length); - for (int i = 0; i < a1.Length; i++) - { - Assert.Equal((int)a1[i], (int)a2[i]); - } - } + public int GetHashCode(Number obj) => obj.GetHashCode(); } } diff --git a/tests/ImageSharp.Tests/TestImages.cs b/tests/ImageSharp.Tests/TestImages.cs index 92d5f1bcf0..b972c18d0a 100644 --- a/tests/ImageSharp.Tests/TestImages.cs +++ b/tests/ImageSharp.Tests/TestImages.cs @@ -567,13 +567,15 @@ namespace SixLabors.ImageSharp.Tests public const string FillOrder2 = "Tiff/b0350_fillorder2.tiff"; public const string LittleEndianByteOrder = "Tiff/little_endian.tiff"; + public const string Fax4_Motorola = "Tiff/moy.tiff"; + public const string SampleMetadata = "Tiff/metadata_sample.tiff"; public static readonly string[] Multiframes = { MultiframeDeflateWithPreview, MultiframeLzwPredictor /*, MultiFrameDifferentSize, MultiframeDifferentSizeTiled, MultiFrameDifferentVariants,*/ }; public static readonly string[] Metadata = { SampleMetadata }; - public static readonly string[] NotSupported = { Calliphora_RgbJpeg, RgbJpeg, RgbUncompressedTiled, MultiframeDifferentSize, MultiframeDifferentVariants, FillOrder2 }; + public static readonly string[] NotSupported = { Calliphora_RgbJpeg, RgbJpeg, RgbUncompressedTiled, MultiframeDifferentSize, MultiframeDifferentVariants, FillOrder2, Calliphora_Fax4Compressed, Fax4_Motorola }; } } } diff --git a/tests/Images/Input/Tiff/moy.tiff b/tests/Images/Input/Tiff/moy.tiff new file mode 100644 index 0000000000..197aade491 --- /dev/null +++ b/tests/Images/Input/Tiff/moy.tiff @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:026bb9372882d8fc15540b4f94e23138e75aacb0ebf2f5940b056fc66819ec46 +size 1968862