Browse Source

Use exif reader for tiff tags reading, minor fixes for exif values.

pull/1570/head
Ildar Khayrutdinov 5 years ago
parent
commit
14d261fbd2
  1. 2
      src/ImageSharp/Formats/Tiff/Constants/TiffSubfileType.cs
  2. 87
      src/ImageSharp/Formats/Tiff/Ifd/DirectoryReader.cs
  3. 316
      src/ImageSharp/Formats/Tiff/Ifd/EntryReader.cs
  4. 88
      src/ImageSharp/Formats/Tiff/Streams/TiffBigEndianStream.cs
  5. 88
      src/ImageSharp/Formats/Tiff/Streams/TiffLittleEndianStream.cs
  6. 94
      src/ImageSharp/Formats/Tiff/Streams/TiffStream.cs
  7. 40
      src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs
  8. 9
      src/ImageSharp/Formats/Tiff/TiffDecoderMetadataCreator.cs
  9. 14
      src/ImageSharp/Formats/Tiff/TiffFrameMetadata.cs
  10. 273
      src/ImageSharp/Metadata/Profiles/Exif/ExifReader.cs
  11. 5
      src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.Long.cs
  12. 5
      src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.LongArray.cs
  13. 5
      src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.Number.cs
  14. 5
      src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.NumberArray.cs
  15. 5
      src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.Short.cs
  16. 5
      src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.ShortArray.cs
  17. 50
      src/ImageSharp/Metadata/Profiles/Exif/Values/ExifNumberArray.cs
  18. 4
      src/ImageSharp/Metadata/Profiles/Exif/Values/ExifValues.cs
  19. 13
      tests/ImageSharp.Tests/Formats/Tiff/TiffMetadataTests.cs
  20. 16
      tests/ImageSharp.Tests/Formats/Tiff/TiffTestUtils.cs

2
src/ImageSharp/Formats/Tiff/Constants/TiffSubfileType.cs

@ -6,7 +6,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff.Constants
/// <summary>
/// Enumeration representing the sub-file types defined by the Tiff file-format.
/// </summary>
public enum TiffSubfileType : uint
public enum TiffSubfileType : ushort
{
/// <summary>
/// Full-resolution image data.

87
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
/// </summary>
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<ExifProfile> Read()
{
if (this.ReadHeader())
{
return this.ReadIfds();
}
this.nextIfdOffset = new HeaderReader(this.byteOrder, this.stream).ReadFileHeader();
return null;
}
IEnumerable<List<IExifValue>> 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<ExifProfile>();
foreach (List<IExifValue> ifd in ifdList)
{
TiffThrowHelper.ThrowInvalidHeader();
var profile = new ExifProfile();
profile.InitializeInternal(ifd);
list.Add(profile);
}
this.nextIfdOffset = firstIfdOffset;
return true;
return list;
}
private IEnumerable<ExifProfile> ReadIfds()
private IEnumerable<List<IExifValue>> ReadIfds()
{
var list = new List<ExifProfile>();
var valuesList = new List<List<IExifValue>>();
var readersList = new SortedList<uint, EntryReader>();
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<IExifValue> 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<IExifValue>(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;
}
}
}

316
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<uint, Action> extValueLoaders = new SortedDictionary<uint, Action>();
public EntryReader(ByteOrder byteOrder, Stream stream, uint ifdOffset)
: base(byteOrder == ByteOrder.BigEndian, stream) =>
this.startOffset = ifdOffset;
/// <summary>
/// Initializes a new instance of the <see cref="EntryReader" /> class.
/// </summary>
/// <param name="stream">The stream.</param>
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<IExifValue> ReadValues()
{
foreach (Action action in this.extValueLoaders.Values)
{
action();
}
}
var values = new List<IExifValue>();
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;
}
}
}

88
src/ImageSharp/Formats/Tiff/Streams/TiffBigEndianStream.cs

@ -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;
/// <summary>
/// Converts buffer data into an <see cref="short"/> using the correct endianness.
/// </summary>
/// <returns>The converted value.</returns>
public override short ReadInt16()
{
byte[] bytes = this.ReadBytes(2);
return (short)((bytes[0] << 8) | bytes[1]);
}
/// <summary>
/// Converts buffer data into an <see cref="int"/> using the correct endianness.
/// </summary>
/// <returns>The converted value.</returns>
public override int ReadInt32()
{
byte[] bytes = this.ReadBytes(4);
return (bytes[0] << 24) | (bytes[1] << 16) | (bytes[2] << 8) | bytes[3];
}
/// <summary>
/// Converts buffer data into a <see cref="uint"/> using the correct endianness.
/// </summary>
/// <returns>The converted value.</returns>
public override uint ReadUInt32()
{
return (uint)this.ReadInt32();
}
/// <summary>
/// Converts buffer data into a <see cref="ushort"/> using the correct endianness.
/// </summary>
/// <returns>The converted value.</returns>
public override ushort ReadUInt16()
{
return (ushort)this.ReadInt16();
}
/// <summary>
/// Converts buffer data into a <see cref="float"/> using the correct endianness.
/// </summary>
/// <returns>The converted value.</returns>
public override float ReadSingle()
{
byte[] bytes = this.ReadBytes(4);
if (BitConverter.IsLittleEndian)
{
Array.Reverse(bytes);
}
return BitConverter.ToSingle(bytes, 0);
}
/// <summary>
/// Converts buffer data into a <see cref="double"/> using the correct endianness.
/// </summary>
/// <returns>The converted value.</returns>
public override double ReadDouble()
{
byte[] bytes = this.ReadBytes(8);
if (BitConverter.IsLittleEndian)
{
Array.Reverse(bytes);
}
return BitConverter.ToDouble(bytes, 0);
}
}
}

88
src/ImageSharp/Formats/Tiff/Streams/TiffLittleEndianStream.cs

@ -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;
/// <summary>
/// Converts buffer data into an <see cref="short"/> using the correct endianness.
/// </summary>
/// <returns>The converted value.</returns>
public override short ReadInt16()
{
byte[] bytes = this.ReadBytes(2);
return (short)(bytes[0] | (bytes[1] << 8));
}
/// <summary>
/// Converts buffer data into an <see cref="int"/> using the correct endianness.
/// </summary>
/// <returns>The converted value.</returns>
public override int ReadInt32()
{
byte[] bytes = this.ReadBytes(4);
return bytes[0] | (bytes[1] << 8) | (bytes[2] << 16) | (bytes[3] << 24);
}
/// <summary>
/// Converts buffer data into a <see cref="uint"/> using the correct endianness.
/// </summary>
/// <returns>The converted value.</returns>
public override uint ReadUInt32()
{
return (uint)this.ReadInt32();
}
/// <summary>
/// Converts buffer data into a <see cref="ushort"/> using the correct endianness.
/// </summary>
/// <returns>The converted value.</returns>
public override ushort ReadUInt16()
{
return (ushort)this.ReadInt16();
}
/// <summary>
/// Converts buffer data into a <see cref="float"/> using the correct endianness.
/// </summary>
/// <returns>The converted value.</returns>
public override float ReadSingle()
{
byte[] bytes = this.ReadBytes(4);
if (!BitConverter.IsLittleEndian)
{
Array.Reverse(bytes);
}
return BitConverter.ToSingle(bytes, 0);
}
/// <summary>
/// Converts buffer data into a <see cref="double"/> using the correct endianness.
/// </summary>
/// <returns>The converted value.</returns>
public override double ReadDouble()
{
byte[] bytes = this.ReadBytes(8);
if (!BitConverter.IsLittleEndian)
{
Array.Reverse(bytes);
}
return BitConverter.ToDouble(bytes, 0);
}
}
}

94
src/ImageSharp/Formats/Tiff/Streams/TiffStream.cs

@ -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
{
/// <summary>
/// The tiff data stream base class.
/// </summary>
internal abstract class TiffStream
{
/// <summary>
/// The input stream.
/// </summary>
private readonly Stream stream;
/// <summary>
/// Initializes a new instance of the <see cref="TiffStream"/> class.
/// </summary>
/// <param name="stream">The stream.</param>
protected TiffStream(Stream stream)
{
this.stream = stream;
}
/// <summary>
/// Gets a value indicating whether the file is encoded in little-endian or big-endian format.
/// </summary>
public abstract ByteOrder ByteOrder { get; }
/// <summary>
/// Gets the input stream.
/// </summary>
public Stream InputStream => this.stream;
/// <summary>
/// Gets the stream position.
/// </summary>
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);
}
/// <summary>
/// Converts buffer data into a <see cref="byte"/> using the correct endianness.
/// </summary>
/// <returns>The converted value.</returns>
public byte ReadByte()
{
return (byte)this.stream.ReadByte();
}
/// <summary>
/// Converts buffer data into an <see cref="sbyte"/> using the correct endianness.
/// </summary>
/// <returns>The converted value.</returns>
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();
}
}

40
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<TPixel>
{
this.inputStream = stream;
TiffStream tiffStream = CreateStream(stream);
var reader = new DirectoryReader(tiffStream);
ByteOrder byteOrder = ReadByteOrder(stream);
var reader = new DirectoryReader(byteOrder, stream);
IEnumerable<ExifProfile> 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<ExifProfile> 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<TPixel>(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
/// <param name="stripOffsets">An array of byte offsets to each strip in the image.</param>
/// <param name="stripByteCounts">An array of the size of each strip (in bytes).</param>
/// <param name="width">The image width.</param>
private void DecodeStripsPlanar<TPixel>(ImageFrame<TPixel> frame, int rowsPerStrip, uint[] stripOffsets, uint[] stripByteCounts, int width)
private void DecodeStripsPlanar<TPixel>(ImageFrame<TPixel> frame, int rowsPerStrip, Number[] stripOffsets, Number[] stripByteCounts, int width)
where TPixel : unmanaged, IPixel<TPixel>
{
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<TPixel>(ImageFrame<TPixel> frame, int rowsPerStrip, uint[] stripOffsets, uint[] stripByteCounts, int width)
private void DecodeStripsChunky<TPixel>(ImageFrame<TPixel> frame, int rowsPerStrip, Number[] stripOffsets, Number[] stripByteCounts, int width)
where TPixel : unmanaged, IPixel<TPixel>
{
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);
}

9
src/ImageSharp/Formats/Tiff/TiffDecoderMetadataCreator.cs

@ -54,15 +54,6 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff
}
}
if (coreMetadata.ExifProfile == null)
{
byte[] buf = frame.GetArray<byte>(ExifTag.SubIFDOffset, true);
if (buf != null)
{
coreMetadata.ExifProfile = new ExifProfile(buf);
}
}
if (coreMetadata.IptcProfile == null)
{
byte[] buf = frame.GetArray<byte>(ExifTag.IPTC, true);

14
src/ImageSharp/Formats/Tiff/TiffFrameMetadata.cs

@ -41,21 +41,21 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff
/// <summary>Gets a general indication of the kind of data contained in this subfile.</summary>
/// <value>A general indication of the kind of data contained in this subfile.</value>
public TiffNewSubfileType NewSubfileType => this.GetSingleEnum<TiffNewSubfileType, uint>(ExifTag.SubfileType, TiffNewSubfileType.FullImage);
public TiffNewSubfileType SubfileType => this.GetSingleEnum<TiffNewSubfileType, uint>(ExifTag.SubfileType, TiffNewSubfileType.FullImage);
/// <summary>Gets a general indication of the kind of data contained in this subfile.</summary>
/// <value>A general indication of the kind of data contained in this subfile.</value>
public TiffSubfileType? SubfileType => this.GetSingleEnumNullable<TiffSubfileType, uint>(ExifTag.OldSubfileType);
public TiffSubfileType? OldSubfileType => this.GetSingleEnumNullable<TiffSubfileType, ushort>(ExifTag.OldSubfileType);
/// <summary>
/// Gets the number of columns in the image, i.e., the number of pixels per row.
/// </summary>
public uint Width => this.GetSingle<uint>(ExifTag.ImageWidth);
public Number Width => this.GetSingle<Number>(ExifTag.ImageWidth);
/// <summary>
/// Gets the number of rows of pixels in the image.
/// </summary>
public uint Height => this.GetSingle<uint>(ExifTag.ImageLength);
public Number Height => this.GetSingle<Number>(ExifTag.ImageLength);
/// <summary>
/// Gets the number of bits per component.
@ -138,7 +138,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff
}
/// <summary>Gets for each strip, the byte offset of that strip..</summary>
public uint[] StripOffsets => this.GetArray<uint>(ExifTag.StripOffsets);
public Number[] StripOffsets => this.GetArray<Number>(ExifTag.StripOffsets);
/// <summary>
/// Gets the number of components per pixel.
@ -148,12 +148,12 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff
/// <summary>
/// Gets the number of rows per strip.
/// </summary>
public uint RowsPerStrip => this.GetSingle<uint>(ExifTag.RowsPerStrip);
public Number RowsPerStrip => this.GetSingle<Number>(ExifTag.RowsPerStrip);
/// <summary>
/// Gets for each strip, the number of bytes in the strip after compression.
/// </summary>
public uint[] StripByteCounts => this.GetArray<uint>(ExifTag.StripByteCounts);
public Number[] StripByteCounts => this.GetArray<Number>(ExifTag.StripByteCounts);
/// <summary>Gets the resolution of the image in x- direction.</summary>
/// <value>The density of the image in x- direction.</value>

273
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
{
/// <summary>
/// Reads and parses EXIF data from a byte array.
/// Reads and parses EXIF data from a stream.
/// </summary>
internal sealed class ExifReader
internal class ExifReader
{
private List<ExifTag> 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<uint, Action> lazyLoaders = new SortedList<uint, Action>();
private bool isBigEndian;
private uint exifOffset;
private uint gpsOffset;
public ExifReader(byte[] exifData)
private List<ExifTag> 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<TDataType>(ReadOnlySpan<byte> data);
/// <summary>
@ -44,19 +55,20 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif
/// </summary>
public uint ThumbnailOffset { get; private set; }
/// <summary>
/// Gets the remaining length.
/// </summary>
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
/// <returns>
/// The <see cref="Collection{ExifValue}"/>.
/// </returns>
public List<IExifValue> ReadValues()
public virtual List<IExifValue> ReadValues()
{
var values = new List<IExifValue>();
// 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();
}
}
/// <summary>
/// Adds the collection of EXIF values to the reader.
/// </summary>
/// <param name="values">The values.</param>
/// <param name="index">The index.</param>
protected void AddValues(List<IExifValue> 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<IExifValue> 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<IExifValue> values, IExifValue value)
{
foreach (IExifValue val in values)
{
if (val == value)
{
return true;
}
}
return false;
}
private static TDataType[] ToArray<TDataType>(ExifDataType dataType, ReadOnlySpan<byte> data, ConverterMethod<TDataType> converter)
@ -128,58 +204,6 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif
return Encoding.UTF8.GetString(buffer);
}
/// <summary>
/// Adds the collection of EXIF values to the reader.
/// </summary>
/// <param name="values">The values.</param>
/// <param name="index">The index.</param>
private void AddValues(List<IExifValue> 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<byte> 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<IExifValue> 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<byte> 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<byte> 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<ExifTag>())).Add(tag);
=> (this.invalidTags ??= new List<ExifTag>()).Add(tag);
private void Seek(long pos)
=> this.data.Seek(pos, SeekOrigin.Begin);
private bool TryReadSpan(int length, out ReadOnlySpan<byte> span)
private bool TryReadSpan(Span<byte> 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<byte>(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<byte> 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<byte> span)
? this.ConvertToShort(span)
protected ushort ReadUInt16() => this.TryReadSpan(this.buf2)
? this.ConvertToShort(this.buf2)
: default;
}
private void GetThumbnail(uint offset)
{

5
src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.Long.cs

@ -11,11 +11,6 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif
/// </summary>
public static ExifTag<uint> SubfileType { get; } = new ExifTag<uint>(ExifTagValue.SubfileType);
/// <summary>
/// Gets the RowsPerStrip exif tag.
/// </summary>
public static ExifTag<uint> RowsPerStrip { get; } = new ExifTag<uint>(ExifTagValue.RowsPerStrip);
/// <summary>
/// Gets the SubIFDOffset exif tag.
/// </summary>

5
src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.LongArray.cs

@ -56,11 +56,6 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif
/// </summary>
public static ExifTag<uint[]> StripRowCounts { get; } = new ExifTag<uint[]>(ExifTagValue.StripRowCounts);
/// <summary>
/// Gets the StripByteCounts exif tag.
/// </summary>
public static ExifTag<uint[]> StripByteCounts { get; } = new ExifTag<uint[]>(ExifTagValue.StripByteCounts);
/// <summary>
/// Gets the IntergraphRegisters exif tag.
/// </summary>

5
src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.Number.cs

@ -16,6 +16,11 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif
/// </summary>
public static ExifTag<Number> ImageLength { get; } = new ExifTag<Number>(ExifTagValue.ImageLength);
/// <summary>
/// Gets the RowsPerStrip exif tag.
/// </summary>
public static ExifTag<Number> RowsPerStrip { get; } = new ExifTag<Number>(ExifTagValue.RowsPerStrip);
/// <summary>
/// Gets the TileWidth exif tag.
/// </summary>

5
src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.NumberArray.cs

@ -11,6 +11,11 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif
/// </summary>
public static ExifTag<Number[]> StripOffsets { get; } = new ExifTag<Number[]>(ExifTagValue.StripOffsets);
/// <summary>
/// Gets the StripByteCounts exif tag.
/// </summary>
public static ExifTag<Number[]> StripByteCounts { get; } = new ExifTag<Number[]>(ExifTagValue.StripByteCounts);
/// <summary>
/// Gets the TileByteCounts exif tag.
/// </summary>

5
src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.Short.cs

@ -56,6 +56,11 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif
/// </summary>
public static ExifTag<ushort> PlanarConfiguration { get; } = new ExifTag<ushort>(ExifTagValue.PlanarConfiguration);
/// <summary>
/// Gets the Predictor exif tag.
/// </summary>
public static ExifTag<ushort> Predictor { get; } = new ExifTag<ushort>(ExifTagValue.Predictor);
/// <summary>
/// Gets the GrayResponseUnit exif tag.
/// </summary>

5
src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.ShortArray.cs

@ -46,11 +46,6 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif
/// </summary>
public static ExifTag<ushort[]> TransferFunction { get; } = new ExifTag<ushort[]>(ExifTagValue.TransferFunction);
/// <summary>
/// Gets the Predictor exif tag.
/// </summary>
public static ExifTag<ushort[]> Predictor { get; } = new ExifTag<ushort[]>(ExifTagValue.Predictor);
/// <summary>
/// Gets the HalftoneHints exif tag.
/// </summary>

50
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<Number>
@ -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>(T value, Func<T, Number> converter)
{
this.Value = new Number[] { converter(value) };
return true;
}
private bool SetArray<T>(T[] values, Func<T, Number> 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;
}
}
}

4
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);

13
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);
}

16
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]);
}
}
}
}

Loading…
Cancel
Save