diff --git a/src/ImageSharp/Formats/Tiff/Constants/TiffSubfileType.cs b/src/ImageSharp/Formats/Tiff/Constants/TiffSubfileType.cs index c8218b77e..f0fb03fd7 100644 --- a/src/ImageSharp/Formats/Tiff/Constants/TiffSubfileType.cs +++ b/src/ImageSharp/Formats/Tiff/Constants/TiffSubfileType.cs @@ -6,7 +6,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff.Constants /// /// Enumeration representing the sub-file types defined by the Tiff file-format. /// - public enum TiffSubfileType : uint + public enum TiffSubfileType : ushort { /// /// Full-resolution image data. diff --git a/src/ImageSharp/Formats/Tiff/Ifd/DirectoryReader.cs b/src/ImageSharp/Formats/Tiff/Ifd/DirectoryReader.cs index 39931c4b6..b96165653 100644 --- a/src/ImageSharp/Formats/Tiff/Ifd/DirectoryReader.cs +++ b/src/ImageSharp/Formats/Tiff/Ifd/DirectoryReader.cs @@ -2,9 +2,8 @@ // Licensed under the Apache License, Version 2.0. using System.Collections.Generic; +using System.IO; -using SixLabors.ImageSharp.Formats.Experimental.Tiff.Constants; -using SixLabors.ImageSharp.Formats.Experimental.Tiff.Streams; using SixLabors.ImageSharp.Metadata.Profiles.Exif; namespace SixLabors.ImageSharp.Formats.Experimental.Tiff @@ -14,94 +13,60 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff /// internal class DirectoryReader { - private readonly TiffStream stream; + private readonly ByteOrder byteOrder; - private readonly EntryReader tagReader; + private readonly Stream stream; private uint nextIfdOffset; - public DirectoryReader(TiffStream stream) + public DirectoryReader(ByteOrder byteOrder, Stream stream) { + this.byteOrder = byteOrder; this.stream = stream; - this.tagReader = new EntryReader(stream); } public IEnumerable Read() { - if (this.ReadHeader()) - { - return this.ReadIfds(); - } + this.nextIfdOffset = new HeaderReader(this.byteOrder, this.stream).ReadFileHeader(); - return null; - } + IEnumerable> ifdList = this.ReadIfds(); - private bool ReadHeader() - { - ushort magic = this.stream.ReadUInt16(); - if (magic != TiffConstants.HeaderMagicNumber) - { - TiffThrowHelper.ThrowInvalidHeader(); - } - - uint firstIfdOffset = this.stream.ReadUInt32(); - if (firstIfdOffset == 0) + var list = new List(); + foreach (List ifd in ifdList) { - TiffThrowHelper.ThrowInvalidHeader(); + var profile = new ExifProfile(); + profile.InitializeInternal(ifd); + list.Add(profile); } - this.nextIfdOffset = firstIfdOffset; - - return true; + return list; } - private IEnumerable ReadIfds() + private IEnumerable> ReadIfds() { - var list = new List(); + var valuesList = new List>(); + var readersList = new SortedList(); while (this.nextIfdOffset != 0) { - this.stream.Seek(this.nextIfdOffset); - ExifProfile ifd = this.ReadIfd(); - list.Add(ifd); - } - - this.tagReader.LoadExtendedData(); - - return list; - } + var reader = new EntryReader(this.byteOrder, this.stream, this.nextIfdOffset); + List values = reader.ReadValues(); + valuesList.Add(values); - private ExifProfile ReadIfd() - { - long pos = this.stream.Position; + this.nextIfdOffset = reader.NextIfdOffset; - ushort entryCount = this.stream.ReadUInt16(); - var entries = new List(entryCount); - for (int i = 0; i < entryCount; i++) - { - IExifValue tag = this.tagReader.ReadNext(); - if (tag != null) + if (reader.BigValuesOffset.HasValue) { - entries.Add(tag); + readersList.Add(reader.BigValuesOffset.Value, reader); } } - this.nextIfdOffset = this.stream.ReadUInt32(); - - int ifdSize = 2 + (entryCount * TiffConstants.SizeOfIfdEntry) + 4; - int readedBytes = (int)(this.stream.Position - pos); - int leftBytes = ifdSize - readedBytes; - if (leftBytes > 0) - { - this.stream.Skip(leftBytes); - } - else if (leftBytes < 0) + // sequential reading big values + foreach (EntryReader reader in readersList.Values) { - TiffThrowHelper.ThrowOutOfRange("IFD"); + reader.LoadBigValues(); } - var profile = new ExifProfile(); - profile.InitializeInternal(entries); - return profile; + return valuesList; } } } diff --git a/src/ImageSharp/Formats/Tiff/Ifd/EntryReader.cs b/src/ImageSharp/Formats/Tiff/Ifd/EntryReader.cs index b51997a7e..53ac09323 100644 --- a/src/ImageSharp/Formats/Tiff/Ifd/EntryReader.cs +++ b/src/ImageSharp/Formats/Tiff/Ifd/EntryReader.cs @@ -1,319 +1,59 @@ // Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. -using System; using System.Collections.Generic; -using System.Text; +using System.IO; using SixLabors.ImageSharp.Formats.Experimental.Tiff.Constants; -using SixLabors.ImageSharp.Formats.Experimental.Tiff.Streams; using SixLabors.ImageSharp.Metadata.Profiles.Exif; namespace SixLabors.ImageSharp.Formats.Experimental.Tiff { - internal class EntryReader + internal class EntryReader : ExifReader { - private readonly TiffStream stream; + private readonly uint startOffset; - private readonly SortedDictionary extValueLoaders = new SortedDictionary(); + public EntryReader(ByteOrder byteOrder, Stream stream, uint ifdOffset) + : base(byteOrder == ByteOrder.BigEndian, stream) => + this.startOffset = ifdOffset; - /// - /// Initializes a new instance of the class. - /// - /// The stream. - public EntryReader(TiffStream stream) - { - this.stream = stream; - } - - public IExifValue ReadNext() - { - var tagId = (ExifTagValue)this.stream.ReadUInt16(); - ExifDataType dataType = EnumUtils.Parse(this.stream.ReadUInt16(), ExifDataType.Unknown); - uint count = this.stream.ReadUInt32(); - - ExifDataType rawDataType = dataType; - dataType = LongOrShortFiltering(tagId, dataType); - bool isArray = GetIsArray(tagId, count); + public uint? BigValuesOffset => this.LazyStartOffset; - ExifValue entry = ExifValues.Create(tagId, dataType, isArray); - if (rawDataType == ExifDataType.Undefined && count == 0) - { - // todo: investgate - count = 4; - } - - if (this.ReadValueOrOffset(entry, rawDataType, count)) - { - return entry; - } + public uint NextIfdOffset { get; private set; } - return null; // new UnkownExifTag(tagId); - } - - public void LoadExtendedData() + public override List ReadValues() { - foreach (Action action in this.extValueLoaders.Values) - { - action(); - } - } + var values = new List(); + this.AddValues(values, this.startOffset); - private static bool HasExtData(ExifValue tag, uint count) => ExifDataTypes.GetSize(tag.DataType) * count > 4; + this.NextIfdOffset = this.ReadUInt32(); - private static bool SetValue(ExifValue entry, object value) - { - if (!entry.IsArray && entry.DataType != ExifDataType.Ascii) - { - DebugGuard.IsTrue(((Array)value).Length == 1, "Expected a length is 1"); - - var single = ((Array)value).GetValue(0); - return entry.TrySetValue(single); - } - - return entry.TrySetValue(value); + this.AddSubIfdValues(values); + return values; } - private static ExifDataType LongOrShortFiltering(ExifTagValue tagId, ExifDataType dataType) - { - switch (tagId) - { - case ExifTagValue.ImageWidth: - case ExifTagValue.ImageLength: - case ExifTagValue.StripOffsets: - case ExifTagValue.RowsPerStrip: - case ExifTagValue.StripByteCounts: - case ExifTagValue.TileWidth: - case ExifTagValue.TileLength: - case ExifTagValue.TileOffsets: - case ExifTagValue.TileByteCounts: - case ExifTagValue.OldSubfileType: // by spec SHORT, but can be LONG - return ExifDataType.Long; - - default: - return dataType; - } - } - - private static bool GetIsArray(ExifTagValue tagId, uint count) - { - switch (tagId) - { - case ExifTagValue.BitsPerSample: - case ExifTagValue.StripOffsets: - case ExifTagValue.StripByteCounts: - case ExifTagValue.TileOffsets: - case ExifTagValue.TileByteCounts: - case ExifTagValue.ColorMap: - case ExifTagValue.ExtraSamples: - case ExifTagValue.SampleFormat: - return true; - - default: - return count > 1; - } - } + public void LoadBigValues() => this.LazyLoad(); + } - private bool ReadValueOrOffset(ExifValue entry, ExifDataType rawDataType, uint count) + internal class HeaderReader : ExifReader + { + public HeaderReader(ByteOrder byteOrder, Stream stream) + : base(byteOrder == ByteOrder.BigEndian, stream) { - if (entry.Tag == ExifTag.SubIFDOffset || entry.Tag == ExifTag.GPSIFDOffset /*|| entry.Tag == ExifTagValue.SubIFDs*/) - { - // todo: ignore subIfds (exif, gps) - this.stream.Skip(4); - return false; - } - - if (HasExtData(entry, count)) - { - uint offset = this.stream.ReadUInt32(); - this.extValueLoaders.Add(offset, () => - { - this.ReadExtValue(entry, rawDataType, offset, count); - }); - - return true; - } - - long pos = this.stream.Position; - object value = this.ReadData(entry.DataType, rawDataType, count); - if (value == null) - { - // read unknown type value - value = this.stream.ReadBytes(4); - } - else - { - int leftBytes = 4 - (int)(this.stream.Position - pos); - if (leftBytes > 0) - { - this.stream.Skip(leftBytes); - } - else if (leftBytes < 0) - { - TiffThrowHelper.ThrowOutOfRange("IFD entry"); - } - } - - return SetValue(entry, value); } - private void ReadExtValue(ExifValue entry, ExifDataType rawDataType, uint offset, uint count) - { - DebugGuard.IsTrue(HasExtData(entry, count), "Excepted extended data"); - DebugGuard.MustBeGreaterThanOrEqualTo(offset, (uint)TiffConstants.SizeOfTiffHeader, nameof(offset)); - - this.stream.Seek(offset); - var value = this.ReadData(entry.DataType, rawDataType, count); + public uint FirstIfdOffset { get; private set; } - SetValue(entry, value); - - DebugGuard.IsTrue(entry.DataType == ExifDataType.Ascii || count > 1 ^ !entry.IsArray, "Invalid tag"); - DebugGuard.IsTrue(entry.GetValue() != null, "Invalid tag"); - } - - private object ReadData(ExifDataType entryDataType, ExifDataType rawDataType, uint count) + public uint ReadFileHeader() { - switch (rawDataType) + ushort magic = this.ReadUInt16(); + if (magic != TiffConstants.HeaderMagicNumber) { - case ExifDataType.Byte: - case ExifDataType.Undefined: - { - return this.stream.ReadBytes(count); - } - - case ExifDataType.SignedByte: - { - sbyte[] res = new sbyte[count]; - byte[] buf = this.stream.ReadBytes(count); - Array.Copy(buf, res, buf.Length); - return res; - } - - case ExifDataType.Short: - { - if (entryDataType == ExifDataType.Long) - { - uint[] buf = new uint[count]; - for (int i = 0; i < buf.Length; i++) - { - buf[i] = this.stream.ReadUInt16(); - } - - return buf; - } - else - { - ushort[] buf = new ushort[count]; - for (int i = 0; i < buf.Length; i++) - { - buf[i] = this.stream.ReadUInt16(); - } - - return buf; - } - } - - case ExifDataType.SignedShort: - { - short[] buf = new short[count]; - for (int i = 0; i < buf.Length; i++) - { - buf[i] = this.stream.ReadInt16(); - } - - return buf; - } - - case ExifDataType.Long: - { - uint[] buf = new uint[count]; - for (int i = 0; i < buf.Length; i++) - { - buf[i] = this.stream.ReadUInt32(); - } - - return buf; - } - - case ExifDataType.SignedLong: - { - int[] buf = new int[count]; - for (int i = 0; i < buf.Length; i++) - { - buf[i] = this.stream.ReadInt32(); - } - - return buf; - } - - case ExifDataType.Ascii: - { - byte[] buf = this.stream.ReadBytes(count); - - if (buf[buf.Length - 1] != 0) - { - TiffThrowHelper.ThrowBadStringEntry(); - } - - return Encoding.UTF8.GetString(buf, 0, buf.Length - 1); - } - - case ExifDataType.SingleFloat: - { - float[] buf = new float[count]; - for (int i = 0; i < buf.Length; i++) - { - buf[i] = this.stream.ReadSingle(); - } - - return buf; - } - - case ExifDataType.DoubleFloat: - { - double[] buf = new double[count]; - for (int i = 0; i < buf.Length; i++) - { - buf[i] = this.stream.ReadDouble(); - } - - return buf; - } - - case ExifDataType.Rational: - { - var buf = new Rational[count]; - for (int i = 0; i < buf.Length; i++) - { - uint numerator = this.stream.ReadUInt32(); - uint denominator = this.stream.ReadUInt32(); - buf[i] = new Rational(numerator, denominator); - } - - return buf; - } - - case ExifDataType.SignedRational: - { - var buf = new SignedRational[count]; - for (int i = 0; i < buf.Length; i++) - { - int numerator = this.stream.ReadInt32(); - int denominator = this.stream.ReadInt32(); - buf[i] = new SignedRational(numerator, denominator); - } - - return buf; - } - - case ExifDataType.Ifd: - { - return this.stream.ReadUInt32(); - } - - default: - return null; + TiffThrowHelper.ThrowInvalidHeader(); } + + this.FirstIfdOffset = this.ReadUInt32(); + return this.FirstIfdOffset; } } } diff --git a/src/ImageSharp/Formats/Tiff/Streams/TiffBigEndianStream.cs b/src/ImageSharp/Formats/Tiff/Streams/TiffBigEndianStream.cs deleted file mode 100644 index 021fb9d51..000000000 --- a/src/ImageSharp/Formats/Tiff/Streams/TiffBigEndianStream.cs +++ /dev/null @@ -1,88 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Apache License, Version 2.0. - -using System; -using System.IO; - -namespace SixLabors.ImageSharp.Formats.Experimental.Tiff.Streams -{ - internal class TiffBigEndianStream : TiffStream - { - public TiffBigEndianStream(Stream stream) - : base(stream) - { - } - - public override ByteOrder ByteOrder => ByteOrder.BigEndian; - - /// - /// Converts buffer data into an using the correct endianness. - /// - /// The converted value. - public override short ReadInt16() - { - byte[] bytes = this.ReadBytes(2); - return (short)((bytes[0] << 8) | bytes[1]); - } - - /// - /// Converts buffer data into an using the correct endianness. - /// - /// The converted value. - public override int ReadInt32() - { - byte[] bytes = this.ReadBytes(4); - return (bytes[0] << 24) | (bytes[1] << 16) | (bytes[2] << 8) | bytes[3]; - } - - /// - /// Converts buffer data into a using the correct endianness. - /// - /// The converted value. - public override uint ReadUInt32() - { - return (uint)this.ReadInt32(); - } - - /// - /// Converts buffer data into a using the correct endianness. - /// - /// The converted value. - public override ushort ReadUInt16() - { - return (ushort)this.ReadInt16(); - } - - /// - /// Converts buffer data into a using the correct endianness. - /// - /// The converted value. - public override float ReadSingle() - { - byte[] bytes = this.ReadBytes(4); - - if (BitConverter.IsLittleEndian) - { - Array.Reverse(bytes); - } - - return BitConverter.ToSingle(bytes, 0); - } - - /// - /// Converts buffer data into a using the correct endianness. - /// - /// The converted value. - public override double ReadDouble() - { - byte[] bytes = this.ReadBytes(8); - - if (BitConverter.IsLittleEndian) - { - Array.Reverse(bytes); - } - - return BitConverter.ToDouble(bytes, 0); - } - } -} diff --git a/src/ImageSharp/Formats/Tiff/Streams/TiffLittleEndianStream.cs b/src/ImageSharp/Formats/Tiff/Streams/TiffLittleEndianStream.cs deleted file mode 100644 index 43c27b98f..000000000 --- a/src/ImageSharp/Formats/Tiff/Streams/TiffLittleEndianStream.cs +++ /dev/null @@ -1,88 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Apache License, Version 2.0. - -using System; -using System.IO; - -namespace SixLabors.ImageSharp.Formats.Experimental.Tiff.Streams -{ - internal class TiffLittleEndianStream : TiffStream - { - public TiffLittleEndianStream(Stream stream) - : base(stream) - { - } - - public override ByteOrder ByteOrder => ByteOrder.LittleEndian; - - /// - /// Converts buffer data into an using the correct endianness. - /// - /// The converted value. - public override short ReadInt16() - { - byte[] bytes = this.ReadBytes(2); - return (short)(bytes[0] | (bytes[1] << 8)); - } - - /// - /// Converts buffer data into an using the correct endianness. - /// - /// The converted value. - public override int ReadInt32() - { - byte[] bytes = this.ReadBytes(4); - return bytes[0] | (bytes[1] << 8) | (bytes[2] << 16) | (bytes[3] << 24); - } - - /// - /// Converts buffer data into a using the correct endianness. - /// - /// The converted value. - public override uint ReadUInt32() - { - return (uint)this.ReadInt32(); - } - - /// - /// Converts buffer data into a using the correct endianness. - /// - /// The converted value. - public override ushort ReadUInt16() - { - return (ushort)this.ReadInt16(); - } - - /// - /// Converts buffer data into a using the correct endianness. - /// - /// The converted value. - public override float ReadSingle() - { - byte[] bytes = this.ReadBytes(4); - - if (!BitConverter.IsLittleEndian) - { - Array.Reverse(bytes); - } - - return BitConverter.ToSingle(bytes, 0); - } - - /// - /// Converts buffer data into a using the correct endianness. - /// - /// The converted value. - public override double ReadDouble() - { - byte[] bytes = this.ReadBytes(8); - - if (!BitConverter.IsLittleEndian) - { - Array.Reverse(bytes); - } - - return BitConverter.ToDouble(bytes, 0); - } - } -} diff --git a/src/ImageSharp/Formats/Tiff/Streams/TiffStream.cs b/src/ImageSharp/Formats/Tiff/Streams/TiffStream.cs deleted file mode 100644 index e178dd179..000000000 --- a/src/ImageSharp/Formats/Tiff/Streams/TiffStream.cs +++ /dev/null @@ -1,94 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Apache License, Version 2.0. - -using System.IO; - -namespace SixLabors.ImageSharp.Formats.Experimental.Tiff.Streams -{ - /// - /// The tiff data stream base class. - /// - internal abstract class TiffStream - { - /// - /// The input stream. - /// - private readonly Stream stream; - - /// - /// Initializes a new instance of the class. - /// - /// The stream. - protected TiffStream(Stream stream) - { - this.stream = stream; - } - - /// - /// Gets a value indicating whether the file is encoded in little-endian or big-endian format. - /// - public abstract ByteOrder ByteOrder { get; } - - /// - /// Gets the input stream. - /// - public Stream InputStream => this.stream; - - /// - /// Gets the stream position. - /// - public long Position => this.stream.Position; - - public void Seek(uint offset) - { - this.stream.Seek(offset, SeekOrigin.Begin); - } - - public void Skip(uint offset) - { - this.stream.Seek(offset, SeekOrigin.Current); - } - - public void Skip(int offset) - { - this.stream.Seek(offset, SeekOrigin.Current); - } - - /// - /// Converts buffer data into a using the correct endianness. - /// - /// The converted value. - public byte ReadByte() - { - return (byte)this.stream.ReadByte(); - } - - /// - /// Converts buffer data into an using the correct endianness. - /// - /// The converted value. - public sbyte ReadSByte() - { - return (sbyte)this.stream.ReadByte(); - } - - public byte[] ReadBytes(uint count) - { - byte[] buf = new byte[count]; - this.stream.Read(buf, 0, buf.Length); - return buf; - } - - public abstract short ReadInt16(); - - public abstract int ReadInt32(); - - public abstract uint ReadUInt32(); - - public abstract ushort ReadUInt16(); - - public abstract float ReadSingle(); - - public abstract double ReadDouble(); - } -} diff --git a/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs b/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs index 94a973b50..ee1befe23 100644 --- a/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs +++ b/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs @@ -7,7 +7,6 @@ using System.Threading; using SixLabors.ImageSharp.Formats.Experimental.Tiff.Compression; using SixLabors.ImageSharp.Formats.Experimental.Tiff.Constants; -using SixLabors.ImageSharp.Formats.Experimental.Tiff.Streams; using SixLabors.ImageSharp.IO; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Metadata; @@ -103,8 +102,8 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff where TPixel : unmanaged, IPixel { this.inputStream = stream; - TiffStream tiffStream = CreateStream(stream); - var reader = new DirectoryReader(tiffStream); + ByteOrder byteOrder = ReadByteOrder(stream); + var reader = new DirectoryReader(byteOrder, stream); IEnumerable directories = reader.Read(); @@ -117,7 +116,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff framesMetadata.Add(frameMetadata); } - ImageMetadata metadata = TiffDecoderMetadataCreator.Create(framesMetadata, this.ignoreMetadata, tiffStream.ByteOrder); + ImageMetadata metadata = TiffDecoderMetadataCreator.Create(framesMetadata, this.ignoreMetadata, byteOrder); // todo: tiff frames can have different sizes { @@ -141,8 +140,8 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff public IImageInfo Identify(BufferedReadStream stream, CancellationToken cancellationToken) { this.inputStream = stream; - TiffStream tiffStream = CreateStream(stream); - var reader = new DirectoryReader(tiffStream); + ByteOrder byteOrder = ReadByteOrder(stream); + var reader = new DirectoryReader(byteOrder, stream); IEnumerable directories = reader.Read(); @@ -153,27 +152,12 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff framesMetadata.Add(meta); } - ImageMetadata metadata = TiffDecoderMetadataCreator.Create(framesMetadata, this.ignoreMetadata, tiffStream.ByteOrder); + ImageMetadata metadata = TiffDecoderMetadataCreator.Create(framesMetadata, this.ignoreMetadata, byteOrder); TiffFrameMetadata root = framesMetadata[0]; return new ImageInfo(new PixelTypeInfo(root.BitsPerPixel), (int)root.Width, (int)root.Height, metadata); } - private static TiffStream CreateStream(Stream stream) - { - ByteOrder byteOrder = ReadByteOrder(stream); - if (byteOrder == ByteOrder.BigEndian) - { - return new TiffBigEndianStream(stream); - } - else if (byteOrder == ByteOrder.LittleEndian) - { - return new TiffLittleEndianStream(stream); - } - - throw TiffThrowHelper.InvalidHeader(); - } - private static ByteOrder ReadByteOrder(Stream stream) { var headerBytes = new byte[2]; @@ -213,8 +197,8 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff var frame = new ImageFrame(this.configuration, width, height, coreMetadata); int rowsPerStrip = (int)frameMetaData.RowsPerStrip; - uint[] stripOffsets = frameMetaData.StripOffsets; - uint[] stripByteCounts = frameMetaData.StripByteCounts; + Number[] stripOffsets = frameMetaData.StripOffsets; + Number[] stripByteCounts = frameMetaData.StripByteCounts; if (this.PlanarConfiguration == TiffPlanarConfiguration.Planar) { @@ -264,7 +248,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff /// An array of byte offsets to each strip in the image. /// An array of the size of each strip (in bytes). /// The image width. - private void DecodeStripsPlanar(ImageFrame frame, int rowsPerStrip, uint[] stripOffsets, uint[] stripByteCounts, int width) + private void DecodeStripsPlanar(ImageFrame frame, int rowsPerStrip, Number[] stripOffsets, Number[] stripByteCounts, int width) where TPixel : unmanaged, IPixel { int stripsPerPixel = this.BitsPerSample.Length; @@ -295,7 +279,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff { int stripIndex = (i * stripsPerPixel) + planeIndex; - decompressor.Decompress(this.inputStream, stripOffsets[stripIndex], stripByteCounts[stripIndex], stripBuffers[planeIndex].GetSpan()); + decompressor.Decompress(this.inputStream, (uint)stripOffsets[stripIndex], (uint)stripByteCounts[stripIndex], stripBuffers[planeIndex].GetSpan()); } colorDecoder.Decode(stripBuffers, pixels, 0, rowsPerStrip * i, frame.Width, stripHeight); @@ -310,7 +294,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff } } - private void DecodeStripsChunky(ImageFrame frame, int rowsPerStrip, uint[] stripOffsets, uint[] stripByteCounts, int width) + private void DecodeStripsChunky(ImageFrame frame, int rowsPerStrip, Number[] stripOffsets, Number[] stripByteCounts, int width) where TPixel : unmanaged, IPixel { int uncompressedStripSize = this.CalculateStripBufferSize(frame.Width, rowsPerStrip); @@ -328,7 +312,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff { int stripHeight = stripIndex < stripOffsets.Length - 1 || frame.Height % rowsPerStrip == 0 ? rowsPerStrip : frame.Height % rowsPerStrip; - decompressor.Decompress(this.inputStream, stripOffsets[stripIndex], stripByteCounts[stripIndex], stripBuffer.GetSpan()); + decompressor.Decompress(this.inputStream, (uint)stripOffsets[stripIndex], (uint)stripByteCounts[stripIndex], stripBuffer.GetSpan()); colorDecoder.Decode(stripBuffer.GetSpan(), pixels, 0, rowsPerStrip * stripIndex, frame.Width, stripHeight); } diff --git a/src/ImageSharp/Formats/Tiff/TiffDecoderMetadataCreator.cs b/src/ImageSharp/Formats/Tiff/TiffDecoderMetadataCreator.cs index 99337a8b2..ff093777a 100644 --- a/src/ImageSharp/Formats/Tiff/TiffDecoderMetadataCreator.cs +++ b/src/ImageSharp/Formats/Tiff/TiffDecoderMetadataCreator.cs @@ -54,15 +54,6 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff } } - if (coreMetadata.ExifProfile == null) - { - byte[] buf = frame.GetArray(ExifTag.SubIFDOffset, true); - if (buf != null) - { - coreMetadata.ExifProfile = new ExifProfile(buf); - } - } - if (coreMetadata.IptcProfile == null) { byte[] buf = frame.GetArray(ExifTag.IPTC, true); diff --git a/src/ImageSharp/Formats/Tiff/TiffFrameMetadata.cs b/src/ImageSharp/Formats/Tiff/TiffFrameMetadata.cs index 6e61aa270..a290301b0 100644 --- a/src/ImageSharp/Formats/Tiff/TiffFrameMetadata.cs +++ b/src/ImageSharp/Formats/Tiff/TiffFrameMetadata.cs @@ -41,21 +41,21 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff /// Gets a general indication of the kind of data contained in this subfile. /// A general indication of the kind of data contained in this subfile. - public TiffNewSubfileType NewSubfileType => this.GetSingleEnum(ExifTag.SubfileType, TiffNewSubfileType.FullImage); + public TiffNewSubfileType SubfileType => this.GetSingleEnum(ExifTag.SubfileType, TiffNewSubfileType.FullImage); /// Gets a general indication of the kind of data contained in this subfile. /// A general indication of the kind of data contained in this subfile. - public TiffSubfileType? SubfileType => this.GetSingleEnumNullable(ExifTag.OldSubfileType); + public TiffSubfileType? OldSubfileType => this.GetSingleEnumNullable(ExifTag.OldSubfileType); /// /// Gets the number of columns in the image, i.e., the number of pixels per row. /// - public uint Width => this.GetSingle(ExifTag.ImageWidth); + public Number Width => this.GetSingle(ExifTag.ImageWidth); /// /// Gets the number of rows of pixels in the image. /// - public uint Height => this.GetSingle(ExifTag.ImageLength); + public Number Height => this.GetSingle(ExifTag.ImageLength); /// /// Gets the number of bits per component. @@ -138,7 +138,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff } /// Gets for each strip, the byte offset of that strip.. - public uint[] StripOffsets => this.GetArray(ExifTag.StripOffsets); + public Number[] StripOffsets => this.GetArray(ExifTag.StripOffsets); /// /// Gets the number of components per pixel. @@ -148,12 +148,12 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff /// /// Gets the number of rows per strip. /// - public uint RowsPerStrip => this.GetSingle(ExifTag.RowsPerStrip); + public Number RowsPerStrip => this.GetSingle(ExifTag.RowsPerStrip); /// /// Gets for each strip, the number of bytes in the strip after compression. /// - public uint[] StripByteCounts => this.GetArray(ExifTag.StripByteCounts); + public Number[] StripByteCounts => this.GetArray(ExifTag.StripByteCounts); /// Gets the resolution of the image in x- direction. /// The density of the image in x- direction. diff --git a/src/ImageSharp/Metadata/Profiles/Exif/ExifReader.cs b/src/ImageSharp/Metadata/Profiles/Exif/ExifReader.cs index 749c69186..65a1eeaee 100644 --- a/src/ImageSharp/Metadata/Profiles/Exif/ExifReader.cs +++ b/src/ImageSharp/Metadata/Profiles/Exif/ExifReader.cs @@ -5,28 +5,39 @@ using System; using System.Buffers.Binary; using System.Collections.Generic; using System.Collections.ObjectModel; +using System.IO; using System.Runtime.CompilerServices; using System.Text; namespace SixLabors.ImageSharp.Metadata.Profiles.Exif { /// - /// Reads and parses EXIF data from a byte array. + /// Reads and parses EXIF data from a stream. /// - internal sealed class ExifReader + internal class ExifReader { - private List invalidTags; - private readonly byte[] exifData; - private int position; + 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) + private readonly SortedList lazyLoaders = new SortedList(); + private bool isBigEndian; - private uint exifOffset; - private uint gpsOffset; - public ExifReader(byte[] exifData) + private List invalidTags; + + public ExifReader(bool isBigEndian, Stream stream) { - this.exifData = 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); /// @@ -44,19 +55,20 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif /// public uint ThumbnailOffset { get; private set; } - /// - /// Gets the remaining length. - /// + 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.position >= this.exifData.Length) + if (this.data.Position >= this.data.Length) { return 0; } - return this.exifData.Length - this.position; + return (int)(this.data.Length - this.data.Position); } } @@ -66,11 +78,11 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif /// /// The . /// - public List ReadValues() + public virtual List ReadValues() { var values = new List(); - // II == 0x4949 + // Exif header: II == 0x4949 this.isBigEndian = this.ReadUInt16() != 0x4949; if (this.ReadUInt16() != 0x002A) @@ -79,22 +91,86 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif } uint ifdOffset = this.ReadUInt32(); + this.AddValues(values, ifdOffset); uint thumbnailOffset = this.ReadUInt32(); this.GetThumbnail(thumbnailOffset); - if (this.exifOffset != 0) + this.AddSubIfdValues(values); + this.LazyLoad(); + + return values; + } + + protected void LazyLoad() + { + foreach (Action act in this.lazyLoaders.Values) { - this.AddValues(values, this.exifOffset); + act(); } + } + + /// + /// Adds the collection of EXIF values to the reader. + /// + /// The values. + /// The index. + protected void AddValues(List values, uint index) + { + if (index > this.Length) + { + return; + } + + this.Seek(index); + int count = this.ReadUInt16(); - if (this.gpsOffset != 0) + for (int i = 0; i < count; i++) { - this.AddValues(values, this.gpsOffset); + this.ReadValue(values); } + } - return values; + protected void AddSubIfdValues(List values) + { + uint exifOffset = 0; + uint gpsOffset = 0; + foreach (IExifValue value in values) + { + if (value.Tag == ExifTag.SubIFDOffset) + { + exifOffset = ((ExifLong)value).Value; + } + + if (value.Tag == ExifTag.GPSIFDOffset) + { + gpsOffset = ((ExifLong)value).Value; + } + } + + if (exifOffset != 0) + { + this.AddValues(values, exifOffset); + } + + if (gpsOffset != 0) + { + this.AddValues(values, gpsOffset); + } + } + + private static bool IsDuplicate(IList values, IExifValue value) + { + foreach (IExifValue val in values) + { + if (val == value) + { + return true; + } + } + + return false; } private static TDataType[] ToArray(ExifDataType dataType, ReadOnlySpan data, ConverterMethod converter) @@ -128,58 +204,6 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif return Encoding.UTF8.GetString(buffer); } - /// - /// Adds the collection of EXIF values to the reader. - /// - /// The values. - /// The index. - private void AddValues(List values, uint index) - { - if (index > (uint)this.exifData.Length) - { - return; - } - - this.position = (int)index; - int count = this.ReadUInt16(); - - for (int i = 0; i < count; i++) - { - if (!this.TryReadValue(out ExifValue value)) - { - continue; - } - - bool duplicate = false; - foreach (IExifValue val in values) - { - if (val == value) - { - duplicate = true; - break; - } - } - - if (duplicate) - { - continue; - } - - if (value == ExifTag.SubIFDOffset) - { - this.exifOffset = ((ExifLong)value).Value; - } - else if (value == ExifTag.GPSIFDOffset) - { - this.gpsOffset = ((ExifLong)value).Value; - } - else - { - values.Add(value); - } - } - } - private object ConvertValue(ExifDataType dataType, ReadOnlySpan buffer, uint numberOfComponents) { if (buffer.Length == 0) @@ -275,28 +299,28 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif } } - private bool TryReadValue(out ExifValue exifValue) + private void ReadValue(List values) { - exifValue = default; - // 2 | 2 | 4 | 4 // tag | type | count | value offset if (this.RemainingLength < 12) { - return false; + return; } var tag = (ExifTagValue)this.ReadUInt16(); ExifDataType dataType = EnumUtils.Parse(this.ReadUInt16(), ExifDataType.Unknown); + uint numberOfComponents = this.ReadUInt32(); + + this.TryReadSpan(this.offsetBuffer); + // Ensure that the data type is valid if (dataType == ExifDataType.Unknown) { - return false; + return; } - uint numberOfComponents = this.ReadUInt32(); - // Issue #132: ExifDataType == Undefined is treated like a byte array. // If numberOfComponents == 0 this value can only be handled as an inline value and must fallback to 4 (bytes) if (dataType == ExifDataType.Undefined && numberOfComponents == 0) @@ -304,64 +328,62 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif numberOfComponents = 4; } - uint size = numberOfComponents * ExifDataTypes.GetSize(dataType); + ExifValue exifValue = ExifValues.Create(tag) ?? ExifValues.Create(tag, dataType, numberOfComponents); - this.TryReadSpan(4, out ReadOnlySpan offsetBuffer); + if (exifValue is null) + { + this.AddInvalidTag(new UnkownExifTag(tag)); + return; + } - object value; + uint size = numberOfComponents * ExifDataTypes.GetSize(dataType); + object value = null; if (size > 4) { - int oldIndex = this.position; - uint newIndex = this.ConvertToUInt32(offsetBuffer); + uint newIndex = this.ConvertToUInt32(this.offsetBuffer); // Ensure that the new index does not overrun the data - if (newIndex > int.MaxValue) + if (newIndex > int.MaxValue || newIndex + size > this.Length) { this.AddInvalidTag(new UnkownExifTag(tag)); - return false; + return; } - this.position = (int)newIndex; - - if (this.RemainingLength < size) + this.lazyLoaders.Add(newIndex, () => { - this.AddInvalidTag(new UnkownExifTag(tag)); - - this.position = oldIndex; - return false; - } - - this.TryReadSpan((int)size, out ReadOnlySpan dataBuffer); + var dataBuffer = new byte[size]; + this.Seek(newIndex); + if (this.TryReadSpan(dataBuffer)) + { + value = this.ConvertValue(dataType, dataBuffer, numberOfComponents); + } - value = this.ConvertValue(dataType, dataBuffer, numberOfComponents); - this.position = oldIndex; + if (exifValue.TrySetValue(value) && !IsDuplicate(values, exifValue)) + { + values.Add(exifValue); + } + }); } else { - value = this.ConvertValue(dataType, offsetBuffer, numberOfComponents); + value = this.ConvertValue(dataType, this.offsetBuffer, numberOfComponents); } - exifValue = ExifValues.Create(tag) ?? ExifValues.Create(tag, dataType, numberOfComponents); - - if (exifValue is null) - { - this.AddInvalidTag(new UnkownExifTag(tag)); - return false; - } - - if (!exifValue.TrySetValue(value)) + if (exifValue.TrySetValue(value) && !IsDuplicate(values, exifValue)) { - return false; + values.Add(exifValue); } - - return true; } private void AddInvalidTag(ExifTag tag) - => (this.invalidTags ?? (this.invalidTags = new List())).Add(tag); + => (this.invalidTags ??= new List()).Add(tag); + + private void Seek(long pos) + => this.data.Seek(pos, SeekOrigin.Begin); - private bool TryReadSpan(int length, out ReadOnlySpan span) + private bool TryReadSpan(Span span) { + int length = span.Length; if (this.RemainingLength < length) { span = default; @@ -369,27 +391,20 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif return false; } - span = new ReadOnlySpan(this.exifData, this.position, length); + int readed = this.data.Read(span); - this.position += length; - - return true; + return readed == length; } - private uint ReadUInt32() - { - // Known as Long in Exif Specification - return this.TryReadSpan(4, out ReadOnlySpan span) - ? this.ConvertToUInt32(span) + // Known as Long in Exif Specification + protected uint ReadUInt32() => + this.TryReadSpan(this.buf4) + ? this.ConvertToUInt32(this.buf4) : default; - } - private ushort ReadUInt16() - { - return this.TryReadSpan(2, out ReadOnlySpan span) - ? this.ConvertToShort(span) + protected ushort ReadUInt16() => this.TryReadSpan(this.buf2) + ? this.ConvertToShort(this.buf2) : default; - } private void GetThumbnail(uint offset) { diff --git a/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.Long.cs b/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.Long.cs index 68156fbb3..8aae08160 100644 --- a/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.Long.cs +++ b/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.Long.cs @@ -11,11 +11,6 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif /// public static ExifTag SubfileType { get; } = new ExifTag(ExifTagValue.SubfileType); - /// - /// Gets the RowsPerStrip exif tag. - /// - public static ExifTag RowsPerStrip { get; } = new ExifTag(ExifTagValue.RowsPerStrip); - /// /// Gets the SubIFDOffset exif tag. /// diff --git a/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.LongArray.cs b/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.LongArray.cs index e08a0b1e7..ac4b0a1bf 100644 --- a/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.LongArray.cs +++ b/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.LongArray.cs @@ -56,11 +56,6 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif /// public static ExifTag StripRowCounts { get; } = new ExifTag(ExifTagValue.StripRowCounts); - /// - /// Gets the StripByteCounts exif tag. - /// - public static ExifTag StripByteCounts { get; } = new ExifTag(ExifTagValue.StripByteCounts); - /// /// Gets the IntergraphRegisters exif tag. /// diff --git a/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.Number.cs b/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.Number.cs index 7e73b75aa..680136b03 100644 --- a/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.Number.cs +++ b/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.Number.cs @@ -16,6 +16,11 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif /// public static ExifTag ImageLength { get; } = new ExifTag(ExifTagValue.ImageLength); + /// + /// Gets the RowsPerStrip exif tag. + /// + public static ExifTag RowsPerStrip { get; } = new ExifTag(ExifTagValue.RowsPerStrip); + /// /// Gets the TileWidth exif tag. /// diff --git a/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.NumberArray.cs b/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.NumberArray.cs index e9440119e..e4ea6d0de 100644 --- a/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.NumberArray.cs +++ b/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.NumberArray.cs @@ -11,6 +11,11 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif /// public static ExifTag StripOffsets { get; } = new ExifTag(ExifTagValue.StripOffsets); + /// + /// Gets the StripByteCounts exif tag. + /// + public static ExifTag StripByteCounts { get; } = new ExifTag(ExifTagValue.StripByteCounts); + /// /// Gets the TileByteCounts exif tag. /// diff --git a/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.Short.cs b/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.Short.cs index f52045531..d27c26ee5 100644 --- a/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.Short.cs +++ b/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.Short.cs @@ -56,6 +56,11 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif /// public static ExifTag PlanarConfiguration { get; } = new ExifTag(ExifTagValue.PlanarConfiguration); + /// + /// Gets the Predictor exif tag. + /// + public static ExifTag Predictor { get; } = new ExifTag(ExifTagValue.Predictor); + /// /// Gets the GrayResponseUnit exif tag. /// diff --git a/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.ShortArray.cs b/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.ShortArray.cs index ce0bb36f0..6ed9131c8 100644 --- a/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.ShortArray.cs +++ b/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.ShortArray.cs @@ -46,11 +46,6 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif /// public static ExifTag TransferFunction { get; } = new ExifTag(ExifTagValue.TransferFunction); - /// - /// Gets the Predictor exif tag. - /// - public static ExifTag Predictor { get; } = new ExifTag(ExifTagValue.Predictor); - /// /// Gets the HalftoneHints exif tag. /// diff --git a/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifNumberArray.cs b/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifNumberArray.cs index 28002f0b7..f447173c7 100644 --- a/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifNumberArray.cs +++ b/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifNumberArray.cs @@ -1,6 +1,8 @@ // Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. +using System; + namespace SixLabors.ImageSharp.Metadata.Profiles.Exif { internal sealed class ExifNumberArray : ExifArrayValue @@ -36,6 +38,54 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif } } + public override bool TrySetValue(object value) + { + if (base.TrySetValue(value)) + { + return true; + } + + switch (value) + { + case int val: + return this.SetSingle(val, v => (Number)v); + case uint val: + return this.SetSingle(val, v => (Number)v); + case short val: + return this.SetSingle(val, v => (Number)v); + case ushort val: + return this.SetSingle(val, v => (Number)v); + case int[] array: + return this.SetArray(array, v => (Number)v); + case uint[] array: + return this.SetArray(array, v => (Number)v); + case short[] array: + return this.SetArray(array, v => (Number)v); + case ushort[] array: + return this.SetArray(array, v => (Number)v); + } + + return false; + } + public override IExifValue DeepClone() => new ExifNumberArray(this); + + private bool SetSingle(T value, Func converter) + { + this.Value = new Number[] { converter(value) }; + return true; + } + + private bool SetArray(T[] values, Func converter) + { + var numbers = new Number[values.Length]; + for (int i = 0; i < values.Length; i++) + { + numbers[i] = converter(values[i]); + } + + this.Value = numbers; + return true; + } } } diff --git a/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifValues.cs b/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifValues.cs index e47d5da25..af1eee2dc 100644 --- a/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifValues.cs +++ b/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifValues.cs @@ -93,6 +93,7 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif case ExifTagValue.ImageWidth: return new ExifNumber(ExifTag.ImageWidth); case ExifTagValue.ImageLength: return new ExifNumber(ExifTag.ImageLength); + case ExifTagValue.RowsPerStrip: return new ExifNumber(ExifTag.RowsPerStrip); case ExifTagValue.TileWidth: return new ExifNumber(ExifTag.TileWidth); case ExifTagValue.TileLength: return new ExifNumber(ExifTag.TileLength); case ExifTagValue.BadFaxLines: return new ExifNumber(ExifTag.BadFaxLines); @@ -100,6 +101,7 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif case ExifTagValue.PixelXDimension: return new ExifNumber(ExifTag.PixelXDimension); case ExifTagValue.PixelYDimension: return new ExifNumber(ExifTag.PixelYDimension); + case ExifTagValue.StripByteCounts: return new ExifNumberArray(ExifTag.StripByteCounts); case ExifTagValue.StripOffsets: return new ExifNumberArray(ExifTag.StripOffsets); case ExifTagValue.TileByteCounts: return new ExifNumberArray(ExifTag.TileByteCounts); case ExifTagValue.ImageLayer: return new ExifNumberArray(ExifTag.ImageLayer); @@ -158,6 +160,7 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif case ExifTagValue.Orientation: return new ExifShort(ExifTag.Orientation); case ExifTagValue.SamplesPerPixel: return new ExifShort(ExifTag.SamplesPerPixel); case ExifTagValue.PlanarConfiguration: return new ExifShort(ExifTag.PlanarConfiguration); + case ExifTagValue.Predictor: return new ExifShort(ExifTag.Predictor); case ExifTagValue.GrayResponseUnit: return new ExifShort(ExifTag.GrayResponseUnit); case ExifTagValue.ResolutionUnit: return new ExifShort(ExifTag.ResolutionUnit); case ExifTagValue.CleanFaxData: return new ExifShort(ExifTag.CleanFaxData); @@ -203,7 +206,6 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif case ExifTagValue.ExtraSamples: return new ExifShortArray(ExifTag.ExtraSamples); case ExifTagValue.PageNumber: return new ExifShortArray(ExifTag.PageNumber); case ExifTagValue.TransferFunction: return new ExifShortArray(ExifTag.TransferFunction); - case ExifTagValue.Predictor: return new ExifShortArray(ExifTag.Predictor); case ExifTagValue.HalftoneHints: return new ExifShortArray(ExifTag.HalftoneHints); case ExifTagValue.SampleFormat: return new ExifShortArray(ExifTag.SampleFormat); case ExifTagValue.TransferRange: return new ExifShortArray(ExifTag.TransferRange); diff --git a/tests/ImageSharp.Tests/Formats/Tiff/TiffMetadataTests.cs b/tests/ImageSharp.Tests/Formats/Tiff/TiffMetadataTests.cs index be9f18907..7e0b472c4 100644 --- a/tests/ImageSharp.Tests/Formats/Tiff/TiffMetadataTests.cs +++ b/tests/ImageSharp.Tests/Formats/Tiff/TiffMetadataTests.cs @@ -8,6 +8,7 @@ using System.Linq; using SixLabors.ImageSharp.Formats.Experimental.Tiff; using SixLabors.ImageSharp.Formats.Experimental.Tiff.Constants; using SixLabors.ImageSharp.Metadata; +using SixLabors.ImageSharp.Metadata.Profiles.Exif; using SixLabors.ImageSharp.Metadata.Profiles.Icc; using SixLabors.ImageSharp.Metadata.Profiles.Iptc; using SixLabors.ImageSharp.PixelFormats; @@ -153,10 +154,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); - Assert.Equal(new uint[] { 8 }, frame.StripOffsets); + TiffTestUtils.Compare(new Number[] { 8 }, frame.StripOffsets); Assert.Equal(1, frame.SamplesPerPixel); Assert.Equal(32u, frame.RowsPerStrip); - Assert.Equal(new uint[] { 297 }, frame.StripByteCounts); + TiffTestUtils.Compare(new Number[] { 297 }, frame.StripByteCounts); Assert.Equal(10, frame.HorizontalResolution); Assert.Equal(10, frame.VerticalResolution); Assert.Equal(TiffPlanarConfiguration.Chunky, frame.PlanarConfiguration); @@ -191,14 +192,14 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff Assert.Equal(2, image.Frames.Count); TiffFrameMetadata frame0 = image.Frames[0].Metadata.GetTiffMetadata(); - Assert.Equal(TiffNewSubfileType.FullImage, frame0.NewSubfileType); - Assert.Null(frame0.SubfileType); + Assert.Equal(TiffNewSubfileType.FullImage, frame0.SubfileType); + Assert.Null(frame0.OldSubfileType); Assert.Equal(255u, frame0.Width); Assert.Equal(255u, frame0.Height); TiffFrameMetadata frame1 = image.Frames[1].Metadata.GetTiffMetadata(); - Assert.Equal(TiffNewSubfileType.Preview, frame1.NewSubfileType); - Assert.Equal(TiffSubfileType.Preview, frame1.SubfileType); + Assert.Equal(TiffNewSubfileType.Preview, frame1.SubfileType); + Assert.Null(frame1.OldSubfileType); Assert.Equal(255u, frame1.Width); Assert.Equal(255u, frame1.Height); } diff --git a/tests/ImageSharp.Tests/Formats/Tiff/TiffTestUtils.cs b/tests/ImageSharp.Tests/Formats/Tiff/TiffTestUtils.cs index 5d81d3b3d..c679b3164 100644 --- a/tests/ImageSharp.Tests/Formats/Tiff/TiffTestUtils.cs +++ b/tests/ImageSharp.Tests/Formats/Tiff/TiffTestUtils.cs @@ -5,6 +5,7 @@ using System; using System.IO; using ImageMagick; + using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; @@ -53,5 +54,20 @@ 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; + } + + Assert.Equal(a1.Length, a2.Length); + for (int i = 0; i < a1.Length; i++) + { + Assert.Equal((int)a1[i], (int)a2[i]); + } + } } }