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 d5387166c..3e8f138b7 100644
--- a/src/ImageSharp/Formats/Tiff/Ifd/DirectoryReader.cs
+++ b/src/ImageSharp/Formats/Tiff/Ifd/DirectoryReader.cs
@@ -1,10 +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.Formats.Experimental.Tiff.Streams;
using SixLabors.ImageSharp.Metadata.Profiles.Exif;
namespace SixLabors.ImageSharp.Formats.Experimental.Tiff
@@ -14,92 +14,82 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff
///
internal class DirectoryReader
{
- private readonly TiffStream stream;
-
- private readonly EntryReader tagReader;
+ private readonly Stream stream;
private uint nextIfdOffset;
- public DirectoryReader(TiffStream 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.stream = stream;
- this.tagReader = new EntryReader(stream);
+ this.ByteOrder = ReadByteOrder(this.stream);
+ this.nextIfdOffset = new HeaderReader(this.stream, this.ByteOrder).ReadFileHeader();
+ return this.ReadIfds();
}
- public IEnumerable Read()
+ private static ByteOrder ReadByteOrder(Stream stream)
{
- if (this.ReadHeader())
+ var headerBytes = new byte[2];
+ stream.Read(headerBytes, 0, 2);
+ if (headerBytes[0] == TiffConstants.ByteOrderLittleEndian && headerBytes[1] == TiffConstants.ByteOrderLittleEndian)
{
- return this.ReadIfds();
+ return ByteOrder.LittleEndian;
+ }
+ else if (headerBytes[0] == TiffConstants.ByteOrderBigEndian && headerBytes[1] == TiffConstants.ByteOrderBigEndian)
+ {
+ return ByteOrder.BigEndian;
}
- return null;
+ throw TiffThrowHelper.InvalidHeader();
}
- private bool ReadHeader()
+ private IEnumerable ReadIfds()
{
- ushort magic = this.stream.ReadUInt16();
- if (magic != TiffConstants.HeaderMagicNumber)
+ var readers = new List();
+ while (this.nextIfdOffset != 0 && this.nextIfdOffset < this.stream.Length)
{
- TiffThrowHelper.ThrowInvalidHeader();
+ var reader = new EntryReader(this.stream, this.ByteOrder, this.nextIfdOffset, this.lazyLoaders);
+ reader.ReadTags();
+
+ this.nextIfdOffset = reader.NextIfdOffset;
+
+ readers.Add(reader);
}
- uint firstIfdOffset = this.stream.ReadUInt32();
- if (firstIfdOffset == 0)
+ // sequential reading big values
+ foreach (Action loader in this.lazyLoaders.Values)
{
- TiffThrowHelper.ThrowInvalidHeader();
+ loader();
}
- this.nextIfdOffset = firstIfdOffset;
-
- return true;
- }
-
- private IEnumerable ReadIfds()
- {
- var list = new List();
- while (this.nextIfdOffset != 0)
+ var list = new List();
+ foreach (EntryReader reader in readers)
{
- this.stream.Seek(this.nextIfdOffset);
- IExifValue[] ifd = this.ReadIfd();
- list.Add(ifd);
+ var profile = new ExifProfile(reader.Values, reader.InvalidTags);
+ list.Add(profile);
}
- this.tagReader.LoadExtendedData();
-
return list;
}
- private IExifValue[] ReadIfd()
+ /// used for possiblity add a duplicate offsets (but tags don't duplicate).
+ /// The type of the key.
+ private class DuplicateKeyComparer : IComparer
+ where TKey : IComparable
{
- long pos = this.stream.Position;
-
- ushort entryCount = this.stream.ReadUInt16();
- var entries = new List(entryCount);
- for (int i = 0; i < entryCount; i++)
+ public int Compare(TKey x, TKey y)
{
- IExifValue tag = this.tagReader.ReadNext();
- if (tag != null)
- {
- entries.Add(tag);
- }
- }
+ int result = x.CompareTo(y);
- 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);
+ // Handle equality as beeing greater
+ return (result == 0) ? 1 : result;
}
- else if (leftBytes < 0)
- {
- TiffThrowHelper.ThrowOutOfRange("IFD");
- }
-
- return entries.ToArray();
}
}
}
diff --git a/src/ImageSharp/Formats/Tiff/Ifd/EntryReader.cs b/src/ImageSharp/Formats/Tiff/Ifd/EntryReader.cs
index b51997a7e..df45e5434 100644
--- a/src/ImageSharp/Formats/Tiff/Ifd/EntryReader.cs
+++ b/src/ImageSharp/Formats/Tiff/Ifd/EntryReader.cs
@@ -3,317 +3,63 @@
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 : BaseExifReader
{
- private readonly TiffStream stream;
+ private readonly uint startOffset;
- private readonly SortedDictionary extValueLoaders = new SortedDictionary();
+ private readonly SortedList lazyLoaders;
- ///
- /// Initializes a new instance of the class.
- ///
- /// The stream.
- public EntryReader(TiffStream stream)
+ public EntryReader(Stream stream, ByteOrder byteOrder, uint ifdOffset, SortedList lazyLoaders)
+ : base(stream)
{
- this.stream = stream;
+ this.IsBigEndian = byteOrder == ByteOrder.BigEndian;
+ this.startOffset = ifdOffset;
+ this.lazyLoaders = lazyLoaders;
}
- 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);
-
- 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;
- }
-
- return null; // new UnkownExifTag(tagId);
- }
+ public List Values { get; } = new List();
- public void LoadExtendedData()
- {
- foreach (Action action in this.extValueLoaders.Values)
- {
- action();
- }
- }
+ public uint NextIfdOffset { get; private set; }
- private static bool HasExtData(ExifValue tag, uint count) => ExifDataTypes.GetSize(tag.DataType) * count > 4;
-
- private static bool SetValue(ExifValue entry, object value)
+ public void ReadTags()
{
- 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);
- }
+ this.ReadValues(this.Values, this.startOffset);
+ this.NextIfdOffset = this.ReadUInt32();
- return entry.TrySetValue(value);
+ this.ReadSubIfd(this.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;
- }
- }
+ protected override void RegisterExtLoader(uint offset, Action reader) =>
+ this.lazyLoaders.Add(offset, reader);
+ }
- 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;
+ internal class HeaderReader : BaseExifReader
+ {
+ public HeaderReader(Stream stream, ByteOrder byteOrder)
+ : base(stream) =>
+ this.IsBigEndian = byteOrder == ByteOrder.BigEndian;
- default:
- return count > 1;
- }
- }
+ public uint FirstIfdOffset { get; private set; }
- private bool ReadValueOrOffset(ExifValue entry, ExifDataType rawDataType, uint count)
+ public uint ReadFileHeader()
{
- if (entry.Tag == ExifTag.SubIFDOffset || entry.Tag == ExifTag.GPSIFDOffset /*|| entry.Tag == ExifTagValue.SubIFDs*/)
+ ushort magic = this.ReadUInt16();
+ if (magic != TiffConstants.HeaderMagicNumber)
{
- // todo: ignore subIfds (exif, gps)
- this.stream.Skip(4);
- return false;
+ TiffThrowHelper.ThrowInvalidHeader();
}
- 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);
+ this.FirstIfdOffset = this.ReadUInt32();
+ return this.FirstIfdOffset;
}
- 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);
-
- 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)
- {
- switch (rawDataType)
- {
- 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;
- }
- }
+ protected override void RegisterExtLoader(uint offset, Action reader) => throw new NotSupportedException();
}
}
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 c6d3f42ba..80d5108dd 100644
--- a/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs
+++ b/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs
@@ -2,12 +2,10 @@
// 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;
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;
@@ -50,9 +48,9 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff
{
options ??= new TiffDecoder();
- this.configuration = configuration ?? Configuration.Default;
+ this.Configuration = configuration ?? Configuration.Default;
this.ignoreMetadata = options.IgnoreMetadata;
- this.memoryAllocator = this.configuration.MemoryAllocator;
+ this.memoryAllocator = this.Configuration.MemoryAllocator;
}
///
@@ -101,7 +99,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff
public TiffPredictor Predictor { get; set; }
///
- public Configuration Configuration => this.configuration;
+ public Configuration Configuration { get; }
///
public Size Dimensions { get; private set; }
@@ -111,21 +109,20 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff
where TPixel : unmanaged, IPixel
{
this.inputStream = stream;
- TiffStream tiffStream = CreateStream(stream);
- var reader = new DirectoryReader(tiffStream);
+ var reader = new DirectoryReader(stream);
- IEnumerable directories = reader.Read();
+ IEnumerable directories = reader.Read();
var frames = new List>();
var framesMetadata = new List();
- foreach (IExifValue[] ifd in directories)
+ foreach (ExifProfile ifd in directories)
{
ImageFrame frame = this.DecodeFrame(ifd, out TiffFrameMetadata frameMetadata);
frames.Add(frame);
framesMetadata.Add(frameMetadata);
}
- ImageMetadata metadata = TiffDecoderMetadataCreator.Create(framesMetadata, this.ignoreMetadata, tiffStream.ByteOrder);
+ ImageMetadata metadata = TiffDecoderMetadataCreator.Create(framesMetadata, this.ignoreMetadata, reader.ByteOrder);
// todo: tiff frames can have different sizes
{
@@ -140,7 +137,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff
}
}
- var image = new Image(this.configuration, metadata, frames);
+ var image = new Image(this.Configuration, metadata, frames);
return image;
}
@@ -149,56 +146,23 @@ 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);
+ var reader = new DirectoryReader(stream);
- IEnumerable directories = reader.Read();
+ IEnumerable directories = reader.Read();
var framesMetadata = new List();
- foreach (IExifValue[] ifd in directories)
+ foreach (ExifProfile ifd in directories)
{
- var meta = new TiffFrameMetadata();
- meta.FrameTags.AddRange(ifd);
+ var meta = new TiffFrameMetadata() { ExifProfile = ifd };
framesMetadata.Add(meta);
}
- ImageMetadata metadata = TiffDecoderMetadataCreator.Create(framesMetadata, this.ignoreMetadata, tiffStream.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 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];
- 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.
///
@@ -208,22 +172,22 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff
///
/// The tiff frame.
///
- private ImageFrame DecodeFrame(IExifValue[] tags, out TiffFrameMetadata frameMetaData)
+ private ImageFrame DecodeFrame(ExifProfile tags, out TiffFrameMetadata frameMetaData)
where TPixel : unmanaged, IPixel
{
var coreMetadata = new ImageFrameMetadata();
frameMetaData = coreMetadata.GetTiffMetadata();
- frameMetaData.FrameTags.AddRange(tags);
+ frameMetaData.ExifProfile = tags;
this.VerifyAndParse(frameMetaData);
int width = (int)frameMetaData.Width;
int height = (int)frameMetaData.Height;
- var frame = new ImageFrame(this.configuration, width, height, coreMetadata);
+ 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)
{
@@ -273,7 +237,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;
@@ -304,7 +268,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);
@@ -319,7 +283,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);
@@ -337,7 +301,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..865c1eeba 100644
--- a/src/ImageSharp/Formats/Tiff/TiffDecoderMetadataCreator.cs
+++ b/src/ImageSharp/Formats/Tiff/TiffDecoderMetadataCreator.cs
@@ -47,37 +47,28 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff
{
if (tiffMetadata.XmpProfile == null)
{
- byte[] buf = frame.GetArray(ExifTag.XMP, true);
- if (buf != null)
+ IExifValue val = frame.ExifProfile.GetValue(ExifTag.XMP);
+ if (val != null)
{
- tiffMetadata.XmpProfile = buf;
- }
- }
-
- if (coreMetadata.ExifProfile == null)
- {
- byte[] buf = frame.GetArray(ExifTag.SubIFDOffset, true);
- if (buf != null)
- {
- coreMetadata.ExifProfile = new ExifProfile(buf);
+ tiffMetadata.XmpProfile = val.Value;
}
}
if (coreMetadata.IptcProfile == null)
{
- byte[] buf = frame.GetArray(ExifTag.IPTC, true);
- if (buf != null)
+ IExifValue val = frame.ExifProfile.GetValue(ExifTag.IPTC);
+ if (val != null)
{
- coreMetadata.IptcProfile = new IptcProfile(buf);
+ coreMetadata.IptcProfile = new IptcProfile(val.Value);
}
}
if (coreMetadata.IccProfile == null)
{
- byte[] buf = frame.GetArray(ExifTag.IccProfile, true);
- if (buf != null)
+ IExifValue val = frame.ExifProfile.GetValue(ExifTag.IccProfile);
+ if (val != null)
{
- coreMetadata.IccProfile = new IccProfile(buf);
+ coreMetadata.IccProfile = new IccProfile(val.Value);
}
}
}
diff --git a/src/ImageSharp/Formats/Tiff/TiffDecoderOptionsParser.cs b/src/ImageSharp/Formats/Tiff/TiffDecoderOptionsParser.cs
index 5e1851018..e4c653209 100644
--- a/src/ImageSharp/Formats/Tiff/TiffDecoderOptionsParser.cs
+++ b/src/ImageSharp/Formats/Tiff/TiffDecoderOptionsParser.cs
@@ -1,7 +1,6 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
-using System.Runtime.CompilerServices;
using SixLabors.ImageSharp.Formats.Experimental.Tiff.Compression;
using SixLabors.ImageSharp.Formats.Experimental.Tiff.Constants;
using SixLabors.ImageSharp.Metadata.Profiles.Exif;
@@ -30,7 +29,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff
TiffThrowHelper.ThrowNotSupported("The lower-order bits of the byte FillOrder is not supported.");
}
- if (entries.GetArray(ExifTag.TileOffsets, true) != null)
+ if (entries.ExifProfile.GetValue(ExifTag.TileOffsets) != null)
{
TiffThrowHelper.ThrowNotSupported("The Tile images is not supported.");
}
@@ -242,7 +241,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff
case TiffCompression.CcittGroup3Fax:
{
options.CompressionType = TiffDecoderCompressionType.T4;
- IExifValue t4options = entries.FrameTags.Find(tag => tag.Tag == ExifTag.T4Options);
+ IExifValue t4options = entries.ExifProfile.GetValue(ExifTag.T4Options);
if (t4options != null)
{
var t4OptionValue = (FaxCompressionOptions)t4options.GetValue();
diff --git a/src/ImageSharp/Formats/Tiff/TiffEncoderEntriesCollector.cs b/src/ImageSharp/Formats/Tiff/TiffEncoderEntriesCollector.cs
index 5b26a8551..9cdce21e2 100644
--- a/src/ImageSharp/Formats/Tiff/TiffEncoderEntriesCollector.cs
+++ b/src/ImageSharp/Formats/Tiff/TiffEncoderEntriesCollector.cs
@@ -114,17 +114,17 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff
var xResolution = new ExifRational(ExifTagValue.XResolution)
{
- Value = frameMetadata.GetSingle(ExifTag.XResolution)
+ Value = frameMetadata.ExifProfile.GetValue(ExifTag.XResolution).Value
};
var yResolution = new ExifRational(ExifTagValue.YResolution)
{
- Value = frameMetadata.GetSingle(ExifTag.YResolution)
+ Value = frameMetadata.ExifProfile.GetValue(ExifTag.YResolution).Value
};
var resolutionUnit = new ExifShort(ExifTagValue.ResolutionUnit)
{
- Value = frameMetadata.GetSingle(ExifTag.ResolutionUnit)
+ Value = frameMetadata.ExifProfile.GetValue(ExifTag.ResolutionUnit).Value
};
this.collector.AddInternal(xResolution);
@@ -134,7 +134,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff
private void ProcessMetadata(TiffFrameMetadata frameMetadata)
{
- foreach (IExifValue entry in frameMetadata.FrameTags)
+ foreach (IExifValue entry in frameMetadata.ExifProfile.Values)
{
// todo: skip subIfd
if (entry.DataType == ExifDataType.Ifd)
@@ -183,7 +183,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff
}
else
{
- tiffFrameMetadata.Remove(ExifTag.SubIFDOffset);
+ tiffFrameMetadata.ExifProfile.RemoveValue(ExifTag.SubIFDOffset);
}
if (imageMetadata.IptcProfile != null)
@@ -198,7 +198,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff
}
else
{
- tiffFrameMetadata.Remove(ExifTag.IPTC);
+ tiffFrameMetadata.ExifProfile.RemoveValue(ExifTag.IPTC);
}
if (imageMetadata.IccProfile != null)
@@ -212,7 +212,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff
}
else
{
- tiffFrameMetadata.Remove(ExifTag.IccProfile);
+ tiffFrameMetadata.ExifProfile.RemoveValue(ExifTag.IccProfile);
}
TiffMetadata tiffMetadata = imageMetadata.GetTiffMetadata();
@@ -227,7 +227,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff
}
else
{
- tiffFrameMetadata.Remove(ExifTag.XMP);
+ tiffFrameMetadata.ExifProfile.RemoveValue(ExifTag.XMP);
}
}
}
diff --git a/src/ImageSharp/Formats/Tiff/TiffFrameMetadata.cs b/src/ImageSharp/Formats/Tiff/TiffFrameMetadata.cs
index c9bab385a..e89e337b7 100644
--- a/src/ImageSharp/Formats/Tiff/TiffFrameMetadata.cs
+++ b/src/ImageSharp/Formats/Tiff/TiffFrameMetadata.cs
@@ -2,7 +2,7 @@
// Licensed under the Apache License, Version 2.0.
using System.Collections.Generic;
-
+using System.Linq;
using SixLabors.ImageSharp.Formats.Experimental.Tiff.Constants;
using SixLabors.ImageSharp.Metadata;
using SixLabors.ImageSharp.Metadata.Profiles.Exif;
@@ -21,6 +21,8 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff
private const TiffPredictor DefaultPredictor = TiffPredictor.None;
+ private ExifProfile frameTags;
+
///
/// Initializes a new instance of the class.
///
@@ -29,27 +31,31 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff
}
///
- /// Gets the Tiff directory tags list.
+ /// Gets the Tiff directory tags.
///
- public List FrameTags { get; internal set; } = new List();
+ public ExifProfile ExifProfile
+ {
+ get => this.frameTags ??= new ExifProfile();
+ internal set => this.frameTags = value;
+ }
/// 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 => (TiffNewSubfileType?)this.ExifProfile.GetValue(ExifTag.SubfileType)?.Value ?? 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 => (TiffSubfileType?)this.ExifProfile.GetValue(ExifTag.OldSubfileType)?.Value;
///
/// 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.ExifProfile.GetValue(ExifTag.ImageWidth).Value;
///
/// Gets the number of rows of pixels in the image.
///
- public uint Height => this.GetSingle(ExifTag.ImageLength);
+ public Number Height => this.ExifProfile.GetValue(ExifTag.ImageLength).Value;
///
/// Gets the number of bits per component.
@@ -58,7 +64,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff
{
get
{
- var bits = this.GetArray(ExifTag.BitsPerSample, true);
+ var bits = this.ExifProfile.GetValue(ExifTag.BitsPerSample)?.Value;
if (bits == null)
{
if (this.PhotometricInterpretation == TiffPhotometricInterpretation.WhiteIsZero
@@ -92,25 +98,25 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff
/// Gets the compression scheme used on the image data.
/// The compression scheme used on the image data.
- public TiffCompression Compression => this.GetSingleEnum(ExifTag.Compression);
+ public TiffCompression Compression => (TiffCompression)this.ExifProfile.GetValue(ExifTag.Compression).Value;
///
/// Gets the color space of the image data.
///
- public TiffPhotometricInterpretation PhotometricInterpretation => this.GetSingleEnum(ExifTag.PhotometricInterpretation);
+ public TiffPhotometricInterpretation PhotometricInterpretation => (TiffPhotometricInterpretation)this.ExifProfile.GetValue(ExifTag.PhotometricInterpretation).Value;
///
/// Gets the logical order of bits within a byte.
///
- internal TiffFillOrder FillOrder => this.GetSingleEnum(ExifTag.FillOrder, TiffFillOrder.MostSignificantBitFirst);
+ internal TiffFillOrder FillOrder => (TiffFillOrder?)this.ExifProfile.GetValue(ExifTag.FillOrder)?.Value ?? TiffFillOrder.MostSignificantBitFirst;
///
/// Gets or sets the a string that describes the subject of the image.
///
public string ImageDescription
{
- get => this.GetString(ExifTag.ImageDescription);
- set => this.SetString(ExifTag.ImageDescription, value);
+ get => this.ExifProfile.GetValue(ExifTag.ImageDescription)?.Value;
+ set => this.ExifProfile.SetValue(ExifTag.ImageDescription, value);
}
///
@@ -118,8 +124,8 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff
///
public string Make
{
- get => this.GetString(ExifTag.Make);
- set => this.SetString(ExifTag.Make, value);
+ get => this.ExifProfile.GetValue(ExifTag.Make)?.Value;
+ set => this.ExifProfile.SetValue(ExifTag.Make, value);
}
///
@@ -127,27 +133,27 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff
///
public string Model
{
- get => this.GetString(ExifTag.Model);
- set => this.SetString(ExifTag.Model, value);
+ get => this.ExifProfile.GetValue(ExifTag.Model)?.Value;
+ set => this.ExifProfile.SetValue(ExifTag.Model, value);
}
/// Gets for each strip, the byte offset of that strip..
- public uint[] StripOffsets => this.GetArray(ExifTag.StripOffsets);
+ public Number[] StripOffsets => this.ExifProfile.GetValue(ExifTag.StripOffsets).Value;
///
/// Gets the number of components per pixel.
///
- public ushort SamplesPerPixel => this.GetSingle(ExifTag.SamplesPerPixel);
+ public ushort SamplesPerPixel => this.ExifProfile.GetValue(ExifTag.SamplesPerPixel).Value;
///
/// Gets the number of rows per strip.
///
- public uint RowsPerStrip => this.GetSingle(ExifTag.RowsPerStrip);
+ public Number RowsPerStrip => this.ExifProfile.GetValue(ExifTag.RowsPerStrip).Value;
///
/// Gets for each strip, the number of bytes in the strip after compression.
///
- public uint[] StripByteCounts => this.GetArray(ExifTag.StripByteCounts);
+ public Number[] StripByteCounts => this.ExifProfile.GetValue(ExifTag.StripByteCounts).Value;
/// Gets the resolution of the image in x- direction.
/// The density of the image in x- direction.
@@ -162,7 +168,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff
///
/// Gets how the components of each pixel are stored.
///
- public TiffPlanarConfiguration PlanarConfiguration => this.GetSingleEnum(ExifTag.PlanarConfiguration, DefaultPlanarConfiguration);
+ public TiffPlanarConfiguration PlanarConfiguration => (TiffPlanarConfiguration?)this.ExifProfile.GetValue(ExifTag.PlanarConfiguration)?.Value ?? DefaultPlanarConfiguration;
///
/// Gets the unit of measurement for XResolution and YResolution.
@@ -174,8 +180,8 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff
///
public string Software
{
- get => this.GetString(ExifTag.Software);
- set => this.SetString(ExifTag.Software, value);
+ get => this.ExifProfile.GetValue(ExifTag.Software)?.Value;
+ set => this.ExifProfile.SetValue(ExifTag.Software, value);
}
///
@@ -183,8 +189,8 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff
///
public string DateTime
{
- get => this.GetString(ExifTag.DateTime);
- set => this.SetString(ExifTag.DateTime, value);
+ get => this.ExifProfile.GetValue(ExifTag.DateTime)?.Value;
+ set => this.ExifProfile.SetValue(ExifTag.DateTime, value);
}
///
@@ -192,8 +198,8 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff
///
public string Artist
{
- get => this.GetString(ExifTag.Artist);
- set => this.SetString(ExifTag.Artist, value);
+ get => this.ExifProfile.GetValue(ExifTag.Artist)?.Value;
+ set => this.ExifProfile.SetValue(ExifTag.Artist, value);
}
///
@@ -201,39 +207,39 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff
///
public string HostComputer
{
- get => this.GetString(ExifTag.HostComputer);
- set => this.SetString(ExifTag.HostComputer, value);
+ get => this.ExifProfile.GetValue(ExifTag.HostComputer)?.Value;
+ set => this.ExifProfile.SetValue(ExifTag.HostComputer, value);
}
///
/// Gets a color map for palette color images.
///
- public ushort[] ColorMap => this.GetArray(ExifTag.ColorMap, true);
+ public ushort[] ColorMap => this.ExifProfile.GetValue(ExifTag.ColorMap)?.Value;
///
/// Gets the description of extra components.
///
- public ushort[] ExtraSamples => this.GetArray(ExifTag.ExtraSamples, true);
+ public ushort[] ExtraSamples => this.ExifProfile.GetValue(ExifTag.ExtraSamples)?.Value;
///
/// Gets or sets the copyright notice.
///
public string Copyright
{
- get => this.GetString(ExifTag.Copyright);
- set => this.SetString(ExifTag.Copyright, value);
+ get => this.ExifProfile.GetValue(ExifTag.Copyright)?.Value;
+ set => this.ExifProfile.SetValue(ExifTag.Copyright, value);
}
///
/// Gets a mathematical operator that is applied to the image data before an encoding scheme is applied.
///
- public TiffPredictor Predictor => this.GetSingleEnum(ExifTag.Predictor, DefaultPredictor);
+ public TiffPredictor Predictor => (TiffPredictor?)this.ExifProfile.GetValue(ExifTag.Predictor)?.Value ?? DefaultPredictor;
///
/// Gets the specifies how to interpret each data sample in a pixel.
///
///
- public TiffSampleFormat[] SampleFormat => this.GetEnumArray(ExifTag.SampleFormat, true);
+ public TiffSampleFormat[] SampleFormat => this.ExifProfile.GetValue(ExifTag.SampleFormat)?.Value?.Select(a => (TiffSampleFormat)a).ToArray();
///
/// Clears the metadata.
@@ -241,7 +247,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff
public void ClearMetadata()
{
var tags = new List();
- foreach (IExifValue entry in this.FrameTags)
+ foreach (IExifValue entry in this.ExifProfile.Values)
{
switch ((ExifTagValue)(ushort)entry.Tag)
{
@@ -261,19 +267,10 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff
}
}
- this.FrameTags = tags;
+ this.ExifProfile = new ExifProfile(tags, this.ExifProfile.InvalidTags);
}
///
- public IDeepCloneable DeepClone()
- {
- var tags = new List();
- foreach (IExifValue entry in this.FrameTags)
- {
- tags.Add(entry.DeepClone());
- }
-
- return new TiffFrameMetadata() { FrameTags = tags };
- }
+ 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
deleted file mode 100644
index aea12dbe5..000000000
--- a/src/ImageSharp/Formats/Tiff/TiffFrameMetadataExtensions.cs
+++ /dev/null
@@ -1,200 +0,0 @@
-// Copyright (c) Six Labors.
-// Licensed under the Apache License, Version 2.0.
-
-using System.Linq;
-
-using SixLabors.ImageSharp.Metadata.Profiles.Exif;
-
-namespace SixLabors.ImageSharp.Formats.Experimental.Tiff
-{
- ///
- /// The tiff metadata extensions
- ///
- internal static class TiffFrameMetadataExtensions
- {
- public static T[] GetArray(this TiffFrameMetadata meta, ExifTag tag, bool optional = false)
- where T : struct
- {
- if (meta.TryGetArray(tag, out T[] result))
- {
- return result;
- }
-
- if (!optional)
- {
- TiffThrowHelper.ThrowTagNotFound(nameof(tag));
- }
-
- return null;
- }
-
- public static bool TryGetArray(this TiffFrameMetadata meta, ExifTag tag, out T[] result)
- where T : struct
- {
- foreach (IExifValue entry in meta.FrameTags)
- {
- if (entry.Tag == tag)
- {
- DebugGuard.IsTrue(entry.IsArray, "Expected array entry");
-
- result = (T[])entry.GetValue();
- return true;
- }
- }
-
- result = null;
- return false;
- }
-
- public static TEnum[] GetEnumArray(this TiffFrameMetadata meta, ExifTag tag, bool optional = false)
- where TEnum : struct
- where TTagValue : struct
- {
- if (meta.TryGetArray(tag, out TTagValue[] result))
- {
- return result.Select(a => (TEnum)(object)a).ToArray();
- }
-
- if (!optional)
- {
- TiffThrowHelper.ThrowTagNotFound(nameof(tag));
- }
-
- return null;
- }
-
- public static string GetString(this TiffFrameMetadata meta, ExifTag tag)
- {
- foreach (IExifValue entry in meta.FrameTags)
- {
- 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;
- }
- }
-
- return null;
- }
-
- public static bool SetString(this TiffFrameMetadata meta, ExifTag tag, string value)
- {
- IExifValue obj = FindOrCreate(meta, tag);
- DebugGuard.IsTrue(obj.DataType == ExifDataType.Ascii, "Expected string entry");
-
- return obj.TrySetValue(value);
- }
-
- public static TEnum? GetSingleEnumNullable(this TiffFrameMetadata meta, ExifTag tag)
- where TEnum : struct
- where TTagValue : struct
- {
- if (!meta.TryGetSingle(tag, out TTagValue value))
- {
- return null;
- }
-
- return (TEnum)(object)value;
- }
-
- public static TEnum GetSingleEnum(this TiffFrameMetadata meta, ExifTag tag, TEnum? defaultValue = null)
- where TEnum : struct
- where TTagValue : struct
- => meta.GetSingleEnumNullable(tag) ?? (defaultValue != null ? defaultValue.Value : throw TiffThrowHelper.TagNotFound(nameof(tag)));
-
- public static bool SetSingleEnum(this TiffFrameMetadata meta, ExifTag tag, TEnum value)
- where TEnum : struct
- where TTagValue : struct
- {
- IExifValue obj = FindOrCreate(meta, tag);
-
- object val = (TTagValue)(object)value;
- return obj.TrySetValue(val);
- }
-
- public static T GetSingle(this TiffFrameMetadata meta, ExifTag tag)
- where T : struct
- {
- if (meta.TryGetSingle(tag, out T result))
- {
- return result;
- }
-
- throw TiffThrowHelper.TagNotFound(nameof(tag));
- }
-
- public static bool TryGetSingle(this TiffFrameMetadata meta, ExifTag tag, out T result)
- where T : struct
- {
- foreach (IExifValue entry in meta.FrameTags)
- {
- if (entry.Tag == tag)
- {
- DebugGuard.IsTrue(!entry.IsArray, "Expected non array entry");
-
- object value = entry.GetValue();
-
- result = (T)value;
- return true;
- }
- }
-
- result = default;
- return false;
- }
-
- public static bool SetSingle(this TiffFrameMetadata meta, ExifTag tag, T value)
- where T : struct
- {
- IExifValue obj = FindOrCreate(meta, tag);
- DebugGuard.IsTrue(!obj.IsArray, "Expected non array entry");
-
- object val = value;
- return obj.TrySetValue(val);
- }
-
- public static bool Remove(this TiffFrameMetadata meta, ExifTag tag)
- {
- IExifValue obj = null;
- foreach (IExifValue entry in meta.FrameTags)
- {
- if (entry.Tag == tag)
- {
- obj = entry;
- break;
- }
- }
-
- if (obj != null)
- {
- return meta.FrameTags.Remove(obj);
- }
-
- return false;
- }
-
- private static IExifValue FindOrCreate(TiffFrameMetadata meta, ExifTag tag)
- {
- IExifValue obj = null;
- foreach (IExifValue entry in meta.FrameTags)
- {
- if (entry.Tag == tag)
- {
- obj = entry;
- break;
- }
- }
-
- if (obj == null)
- {
- obj = ExifValues.Create(tag);
- meta.FrameTags.Add(obj);
- }
-
- return obj;
- }
- }
-}
diff --git a/src/ImageSharp/Formats/Tiff/TiffFrameMetadataResolutionExtensions.cs b/src/ImageSharp/Formats/Tiff/TiffFrameMetadataResolutionExtensions.cs
index f17e9dddc..5a7ad1ac9 100644
--- a/src/ImageSharp/Formats/Tiff/TiffFrameMetadataResolutionExtensions.cs
+++ b/src/ImageSharp/Formats/Tiff/TiffFrameMetadataResolutionExtensions.cs
@@ -19,9 +19,9 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff
break;
case PixelResolutionUnit.PixelsPerMeter:
{
- unit = PixelResolutionUnit.PixelsPerCentimeter;
- horizontal = UnitConverter.MeterToCm(horizontal);
- vertical = UnitConverter.MeterToCm(vertical);
+ unit = PixelResolutionUnit.PixelsPerCentimeter;
+ horizontal = UnitConverter.MeterToCm(horizontal);
+ vertical = UnitConverter.MeterToCm(vertical);
}
break;
@@ -30,29 +30,27 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff
break;
}
- meta.SetSingle(ExifTag.ResolutionUnit, (ushort)unit + 1);
+ meta.ExifProfile.SetValue(ExifTag.ResolutionUnit, (ushort)(unit + 1));
meta.SetResolution(ExifTag.XResolution, horizontal);
meta.SetResolution(ExifTag.YResolution, vertical);
}
public static PixelResolutionUnit GetResolutionUnit(this TiffFrameMetadata meta)
{
- if (!meta.TryGetSingle(ExifTag.ResolutionUnit, out ushort res))
- {
- res = TiffFrameMetadata.DefaultResolutionUnit;
- }
+ ushort res = meta.ExifProfile.GetValue(ExifTag.ResolutionUnit)?.Value ?? TiffFrameMetadata.DefaultResolutionUnit;
return (PixelResolutionUnit)(res - 1);
}
- public static double? GetResolution(this TiffFrameMetadata meta, ExifTag tag)
+ public static double? GetResolution(this TiffFrameMetadata meta, ExifTag tag)
{
- if (!meta.TryGetSingle(tag, out Rational resolution))
+ IExifValue resolution = meta.ExifProfile.GetValue(tag);
+ if (resolution == null)
{
return null;
}
- double res = resolution.ToDouble();
+ double res = resolution.Value.ToDouble();
switch (meta.ResolutionUnit)
{
case PixelResolutionUnit.AspectRatio:
@@ -68,11 +66,11 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff
}
}
- private static void SetResolution(this TiffFrameMetadata meta, ExifTag tag, double? value)
+ private static void SetResolution(this TiffFrameMetadata meta, ExifTag tag, double? value)
{
if (value == null)
{
- meta.Remove(tag);
+ meta.ExifProfile.RemoveValue(tag);
return;
}
else
@@ -94,7 +92,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff
break;
}
- meta.SetSingle(tag, new Rational(res));
+ meta.ExifProfile.SetValue(tag, new Rational(res));
}
}
}
diff --git a/src/ImageSharp/Metadata/Profiles/Exif/ExifProfile.cs b/src/ImageSharp/Metadata/Profiles/Exif/ExifProfile.cs
index 55af45fb4..39c8c2293 100644
--- a/src/ImageSharp/Metadata/Profiles/Exif/ExifProfile.cs
+++ b/src/ImageSharp/Metadata/Profiles/Exif/ExifProfile.cs
@@ -4,6 +4,7 @@
using System;
using System.Collections.Generic;
using System.IO;
+
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Metadata.Profiles.Exif
@@ -52,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.
diff --git a/src/ImageSharp/Metadata/Profiles/Exif/ExifReader.cs b/src/ImageSharp/Metadata/Profiles/Exif/ExifReader.cs
index 749c69186..57a9a8cc6 100644
--- a/src/ImageSharp/Metadata/Profiles/Exif/ExifReader.cs
+++ b/src/ImageSharp/Metadata/Profiles/Exif/ExifReader.cs
@@ -5,27 +5,103 @@ using System;
using System.Buffers.Binary;
using System.Collections.Generic;
using System.Collections.ObjectModel;
+using System.Diagnostics;
+using System.IO;
using System.Runtime.CompilerServices;
using System.Text;
namespace SixLabors.ImageSharp.Metadata.Profiles.Exif
{
+ internal class ExifReader : BaseExifReader
+ {
+ private readonly List loaders = new List();
+
+ public ExifReader(byte[] exifData)
+ : base(new MemoryStream(exifData ?? throw new ArgumentNullException(nameof(exifData))))
+ {
+ }
+
+ ///
+ /// Reads and returns the collection of EXIF values.
+ ///
+ ///
+ /// The .
+ ///
+ public List ReadValues()
+ {
+ var values = new List();
+
+ // II == 0x4949
+ this.IsBigEndian = this.ReadUInt16() != 0x4949;
+
+ if (this.ReadUInt16() != 0x002A)
+ {
+ return values;
+ }
+
+ uint ifdOffset = this.ReadUInt32();
+ this.ReadValues(values, ifdOffset);
+
+ uint thumbnailOffset = this.ReadUInt32();
+ this.GetThumbnail(thumbnailOffset);
+
+ this.ReadSubIfd(values);
+
+ foreach (Action loader in this.loaders)
+ {
+ loader();
+ }
+
+ return values;
+ }
+
+ protected override void RegisterExtLoader(uint offset, Action loader) => this.loaders.Add(loader);
+
+ private void GetThumbnail(uint offset)
+ {
+ if (offset == 0)
+ {
+ return;
+ }
+
+ var values = new List();
+ this.ReadValues(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;
+ }
+ }
+ }
+ }
+
///
- /// Reads and parses EXIF data from a byte array.
+ /// Reads and parses EXIF data from a stream.
///
- internal sealed class ExifReader
+ internal abstract class BaseExifReader
{
- private List invalidTags;
- private readonly byte[] exifData;
- private int position;
+ 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;
- public ExifReader(byte[] exifData)
- {
- this.exifData = exifData ?? throw new ArgumentNullException(nameof(exifData));
- }
+ protected BaseExifReader(Stream stream) =>
+ this.data = stream ?? throw new ArgumentNullException(nameof(stream));
private delegate TDataType ConverterMethod(ReadOnlySpan data);
@@ -35,66 +111,55 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif
public IReadOnlyList InvalidTags => this.invalidTags ?? (IReadOnlyList)Array.Empty();
///
- /// Gets the thumbnail length in the byte stream.
+ /// Gets or sets the thumbnail length in the byte stream.
///
- public uint ThumbnailLength { get; private set; }
+ public uint ThumbnailLength { get; protected set; }
///
- /// Gets the thumbnail offset position in the byte stream.
+ /// Gets or sets the thumbnail offset position in the byte stream.
///
- public uint ThumbnailOffset { get; private set; }
+ public uint ThumbnailOffset { get; protected set; }
- ///
- /// Gets the remaining length.
- ///
- private int RemainingLength
+ public bool IsBigEndian
{
- get
- {
- if (this.position >= this.exifData.Length)
- {
- return 0;
- }
-
- return this.exifData.Length - this.position;
- }
+ get => this.isBigEndian;
+ protected set => this.isBigEndian = value;
}
+ protected abstract void RegisterExtLoader(uint offset, Action loader);
+
///
- /// Reads and returns the collection of EXIF values.
+ /// Reads the values to the values collection.
///
- ///
- /// The .
- ///
- public List ReadValues()
+ /// The values.
+ /// The IFD offset.
+ protected void ReadValues(List values, uint offset)
{
- var values = new List();
-
- // II == 0x4949
- this.isBigEndian = this.ReadUInt16() != 0x4949;
-
- if (this.ReadUInt16() != 0x002A)
+ if (offset > this.data.Length)
{
- return values;
+ return;
}
- uint ifdOffset = this.ReadUInt32();
- this.AddValues(values, ifdOffset);
+ this.Seek(offset);
+ int count = this.ReadUInt16();
- uint thumbnailOffset = this.ReadUInt32();
- this.GetThumbnail(thumbnailOffset);
+ for (int i = 0; i < count; i++)
+ {
+ this.ReadValue(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);
}
-
- return values;
}
private static TDataType[] ToArray(ExifDataType dataType, ReadOnlySpan data, ConverterMethod converter)
@@ -128,58 +193,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 +288,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)
+ if ((this.data.Length - this.data.Position) < 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,110 +317,105 @@ 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);
if (size > 4)
{
- int oldIndex = this.position;
- uint newIndex = this.ConvertToUInt32(offsetBuffer);
+ uint newOffset = this.ConvertToUInt32(this.offsetBuffer);
// Ensure that the new index does not overrun the data
- if (newIndex > int.MaxValue)
+ if (newOffset > int.MaxValue || (newOffset + size) > this.data.Length)
{
this.AddInvalidTag(new UnkownExifTag(tag));
- return false;
+ return;
}
- this.position = (int)newIndex;
-
- if (this.RemainingLength < size)
+ this.RegisterExtLoader(newOffset, () =>
{
- this.AddInvalidTag(new UnkownExifTag(tag));
-
- this.position = oldIndex;
- return false;
- }
-
- this.TryReadSpan((int)size, out ReadOnlySpan dataBuffer);
-
- value = this.ConvertValue(dataType, dataBuffer, numberOfComponents);
- this.position = oldIndex;
+ var dataBuffer = new byte[size];
+ this.Seek(newOffset);
+ if (this.TryReadSpan(dataBuffer))
+ {
+ object value = this.ConvertValue(dataType, dataBuffer, numberOfComponents);
+ this.Add(values, exifValue, value);
+ }
+ });
}
else
{
- value = this.ConvertValue(dataType, offsetBuffer, numberOfComponents);
- }
+ object value = this.ConvertValue(dataType, this.offsetBuffer, numberOfComponents);
- exifValue = ExifValues.Create(tag) ?? ExifValues.Create(tag, dataType, numberOfComponents);
+ this.Add(values, exifValue, value);
+ }
+ }
- if (exifValue is null)
+ private void Add(IList values, IExifValue exif, object value)
+ {
+ if (!exif.TrySetValue(value))
{
- this.AddInvalidTag(new UnkownExifTag(tag));
- return false;
+ return;
}
- if (!exifValue.TrySetValue(value))
+ foreach (IExifValue val in values)
{
- return false;
+ // sometimes duplicates appear,
+ // can compare val.Tag == exif.Tag
+ if (val == exif)
+ {
+ Debug.WriteLine($"Duplicate Exif tag: tag={exif.Tag}, dataType={exif.DataType}");
+ return;
+ }
}
- return true;
+ if (exif.Tag == ExifTag.SubIFDOffset)
+ {
+ this.exifOffset = (uint)value;
+ }
+ else if (exif.Tag == ExifTag.GPSIFDOffset)
+ {
+ this.gpsOffset = (uint)value;
+ }
+ else
+ {
+ values.Add(exif);
+ }
}
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)
{
- if (this.RemainingLength < length)
+ int length = span.Length;
+ if ((this.data.Length - this.data.Position) < length)
{
span = default;
-
return false;
}
- span = new ReadOnlySpan(this.exifData, this.position, length);
-
- this.position += length;
-
- return true;
+ int readed = this.data.Read(span);
+ 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)
- {
- 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)
{
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..75538a3f7 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;
@@ -145,6 +146,8 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff
Assert.Equal(10, image.Metadata.VerticalResolution);
TiffFrameMetadata frame = image.Frames.RootFrame.Metadata.GetTiffMetadata();
+ Assert.Equal(30, frame.ExifProfile.Values.Count);
+
Assert.Equal(32u, frame.Width);
Assert.Equal(32u, frame.Height);
Assert.Equal(new ushort[] { 4 }, frame.BitsPerSample);
@@ -153,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);
- Assert.Equal(new uint[] { 8 }, frame.StripOffsets);
+ Assert.Equal(new Number[] { 8u }, frame.StripOffsets, new NumberComparer());
Assert.Equal(1, frame.SamplesPerPixel);
Assert.Equal(32u, frame.RowsPerStrip);
- Assert.Equal(new uint[] { 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);
@@ -175,6 +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.ExifProfile.GetValue(ExifTag.Rating).Value);
+ Assert.Equal(75, frame.ExifProfile.GetValue(ExifTag.RatingPercent).Value);
}
}
@@ -191,14 +196,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..5ac2f475e 100644
--- a/tests/ImageSharp.Tests/Formats/Tiff/TiffTestUtils.cs
+++ b/tests/ImageSharp.Tests/Formats/Tiff/TiffTestUtils.cs
@@ -2,9 +2,11 @@
// Licensed under the Apache License, Version 2.0.
using System;
+using System.Collections.Generic;
using System.IO;
using ImageMagick;
+
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison;
@@ -54,4 +56,11 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff
return result;
}
}
+
+ internal class NumberComparer : IEqualityComparer
+ {
+ public bool Equals(Number x, Number y) => x.Equals(y);
+
+ public int GetHashCode(Number obj) => obj.GetHashCode();
+ }
}
diff --git a/tests/ImageSharp.Tests/Metadata/Profiles/Exif/ExifProfileTests.cs b/tests/ImageSharp.Tests/Metadata/Profiles/Exif/ExifProfileTests.cs
index 466568bfe..10f6ff9bf 100644
--- a/tests/ImageSharp.Tests/Metadata/Profiles/Exif/ExifProfileTests.cs
+++ b/tests/ImageSharp.Tests/Metadata/Profiles/Exif/ExifProfileTests.cs
@@ -6,6 +6,7 @@ using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
+
using SixLabors.ImageSharp.Metadata;
using SixLabors.ImageSharp.Metadata.Profiles.Exif;
using SixLabors.ImageSharp.PixelFormats;
@@ -14,6 +15,7 @@ using Xunit;
namespace SixLabors.ImageSharp.Tests
{
+ [Trait("Profile", "Exif")]
public class ExifProfileTests
{
public enum TestImageWriteFormat
@@ -201,10 +203,14 @@ namespace SixLabors.ImageSharp.Tests
IExifValue latitude = image.Metadata.ExifProfile.GetValue(ExifTag.GPSLatitude);
Assert.Equal(expectedLatitude, latitude.Value);
+ // todo: duplicate tags
+ Assert.Equal(2, image.Metadata.ExifProfile.Values.Count(v => (ushort)v.Tag == 59932));
+
int profileCount = image.Metadata.ExifProfile.Values.Count;
image = WriteAndRead(image, imageFormat);
Assert.NotNull(image.Metadata.ExifProfile);
+ Assert.Equal(0, image.Metadata.ExifProfile.Values.Count(v => (ushort)v.Tag == 59932));
// Should be 3 less.
// 1 x due to setting of null "ReferenceBlackWhite" value.
@@ -363,8 +369,14 @@ namespace SixLabors.ImageSharp.Tests
// Force parsing of the profile.
Assert.Equal(25, profile.Values.Count);
+ // todo: duplicate tags (from root container and subIfd)
+ Assert.Equal(2, profile.Values.Count(v => (ExifTagValue)(ushort)v.Tag == ExifTagValue.DateTime));
+
byte[] bytes = profile.ToByteArray();
Assert.Equal(525, bytes.Length);
+
+ var profile2 = new ExifProfile(bytes);
+ Assert.Equal(25, profile2.Values.Count);
}
[Theory]
@@ -477,6 +489,9 @@ namespace SixLabors.ImageSharp.Tests
{
Assert.NotNull(profile);
+ // todo: duplicate tags
+ Assert.Equal(2, profile.Values.Count(v => (ushort)v.Tag == 59932));
+
Assert.Equal(16, profile.Values.Count);
foreach (IExifValue value in profile.Values)
diff --git a/tests/ImageSharp.Tests/Metadata/Profiles/Exif/ExifReaderTests.cs b/tests/ImageSharp.Tests/Metadata/Profiles/Exif/ExifReaderTests.cs
index 401546e5c..4ff37eb6b 100644
--- a/tests/ImageSharp.Tests/Metadata/Profiles/Exif/ExifReaderTests.cs
+++ b/tests/ImageSharp.Tests/Metadata/Profiles/Exif/ExifReaderTests.cs
@@ -8,6 +8,7 @@ using Xunit;
namespace SixLabors.ImageSharp.Tests
{
+ [Trait("Profile", "Exif")]
public class ExifReaderTests
{
[Fact]
diff --git a/tests/ImageSharp.Tests/Metadata/Profiles/Exif/ExifTagDescriptionAttributeTests.cs b/tests/ImageSharp.Tests/Metadata/Profiles/Exif/ExifTagDescriptionAttributeTests.cs
index 2b00cc5b4..42a3b72a6 100644
--- a/tests/ImageSharp.Tests/Metadata/Profiles/Exif/ExifTagDescriptionAttributeTests.cs
+++ b/tests/ImageSharp.Tests/Metadata/Profiles/Exif/ExifTagDescriptionAttributeTests.cs
@@ -6,6 +6,7 @@ using Xunit;
namespace SixLabors.ImageSharp.Tests
{
+ [Trait("Profile", "Exif")]
public class ExifTagDescriptionAttributeTests
{
[Fact]
diff --git a/tests/ImageSharp.Tests/Metadata/Profiles/Exif/ExifValueTests.cs b/tests/ImageSharp.Tests/Metadata/Profiles/Exif/ExifValueTests.cs
index 5fe1b51ba..a2c01ea61 100644
--- a/tests/ImageSharp.Tests/Metadata/Profiles/Exif/ExifValueTests.cs
+++ b/tests/ImageSharp.Tests/Metadata/Profiles/Exif/ExifValueTests.cs
@@ -7,6 +7,7 @@ using Xunit;
namespace SixLabors.ImageSharp.Tests
{
+ [Trait("Profile", "Exif")]
public class ExifValueTests
{
private ExifProfile profile;
diff --git a/tests/ImageSharp.Tests/Metadata/Profiles/Exif/Values/ExifValuesTests.cs b/tests/ImageSharp.Tests/Metadata/Profiles/Exif/Values/ExifValuesTests.cs
index 898c69356..3358b1f97 100644
--- a/tests/ImageSharp.Tests/Metadata/Profiles/Exif/Values/ExifValuesTests.cs
+++ b/tests/ImageSharp.Tests/Metadata/Profiles/Exif/Values/ExifValuesTests.cs
@@ -6,6 +6,7 @@ using Xunit;
namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.Exif.Values
{
+ [Trait("Profile", "Exif")]
public class ExifValuesTests
{
public static TheoryData ByteTags => new TheoryData
@@ -94,6 +95,7 @@ namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.Exif.Values
public static TheoryData NumberArrayTags => new TheoryData
{
{ ExifTag.StripOffsets },
+ { ExifTag.StripByteCounts },
{ ExifTag.TileByteCounts },
{ ExifTag.ImageLayer }
};
@@ -160,6 +162,7 @@ namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.Exif.Values
{ ExifTag.Orientation },
{ ExifTag.SamplesPerPixel },
{ ExifTag.PlanarConfiguration },
+ { ExifTag.Predictor },
{ ExifTag.GrayResponseUnit },
{ ExifTag.ResolutionUnit },
{ ExifTag.CleanFaxData },
@@ -208,7 +211,6 @@ namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.Exif.Values
{ ExifTag.ExtraSamples },
{ ExifTag.PageNumber },
{ ExifTag.TransferFunction },
- { ExifTag.Predictor },
{ ExifTag.HalftoneHints },
{ ExifTag.SampleFormat },
{ ExifTag.TransferRange },
diff --git a/tests/ImageSharp.Tests/TestImages.cs b/tests/ImageSharp.Tests/TestImages.cs
index 4edfdc925..1c86289c5 100644
--- a/tests/ImageSharp.Tests/TestImages.cs
+++ b/tests/ImageSharp.Tests/TestImages.cs
@@ -569,13 +569,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 000000000..197aade49
--- /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