diff --git a/src/ImageSharp.Formats.Tiff/TiffConstants.cs b/src/ImageSharp.Formats.Tiff/TiffConstants.cs index 0f9145208..73508b34a 100644 --- a/src/ImageSharp.Formats.Tiff/TiffConstants.cs +++ b/src/ImageSharp.Formats.Tiff/TiffConstants.cs @@ -26,5 +26,15 @@ namespace ImageSharp.Formats /// Magic number used within the image file header to identify a TIFF format file. /// public const ushort HeaderMagicNumber = 42; + + /// + /// Size (in bytes) of the TIFF file header. + /// + public const int SizeOfTiffHeader = 8; + + /// + /// Size (in bytes) of each individual TIFF IFD entry + /// + public const int SizeOfIfdEntry = 12; } } diff --git a/src/ImageSharp.Formats.Tiff/TiffDecoderCore.cs b/src/ImageSharp.Formats.Tiff/TiffDecoderCore.cs index aee57b1ea..e8c0db788 100644 --- a/src/ImageSharp.Formats.Tiff/TiffDecoderCore.cs +++ b/src/ImageSharp.Formats.Tiff/TiffDecoderCore.cs @@ -59,6 +59,7 @@ namespace ImageSharp.Formats this.InputStream = stream; uint firstIfdOffset = ReadHeader(); + TiffIfd firstIfd = ReadIfd(firstIfdOffset); } /// @@ -70,8 +71,8 @@ namespace ImageSharp.Formats public uint ReadHeader() { - byte[] headerBytes = new byte[8]; - ReadBytes(headerBytes, 8); + byte[] headerBytes = new byte[TiffConstants.SizeOfTiffHeader]; + ReadBytes(headerBytes, TiffConstants.SizeOfTiffHeader); if (headerBytes[0] == TiffConstants.ByteOrderLittleEndian && headerBytes[1] == TiffConstants.ByteOrderLittleEndian) IsLittleEndian = true; @@ -88,7 +89,35 @@ namespace ImageSharp.Formats return firstIfdOffset; } - private byte[] ReadBytes(byte[] buffer, int count) + public TiffIfd ReadIfd(uint offset) + { + InputStream.Seek(offset, SeekOrigin.Begin); + + byte[] buffer = new byte[TiffConstants.SizeOfIfdEntry]; + + ReadBytes(buffer, 2); + ushort entryCount = ToUInt16(buffer, 0); + + TiffIfdEntry[] entries = new TiffIfdEntry[entryCount]; + for (int i = 0 ; i +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Formats +{ + /// + /// Data structure for holding details of each TIFF IFD + /// + internal struct TiffIfd + { + public TiffIfdEntry[] Entries; + public uint NextIfdOffset; + + public TiffIfd(TiffIfdEntry[] entries, uint nextIfdOffset) + { + this.Entries = entries; + this.NextIfdOffset = nextIfdOffset; + } + } +} diff --git a/src/ImageSharp.Formats.Tiff/TiffIfd/TiffIfdEntry.cs b/src/ImageSharp.Formats.Tiff/TiffIfd/TiffIfdEntry.cs new file mode 100644 index 000000000..04686a4da --- /dev/null +++ b/src/ImageSharp.Formats.Tiff/TiffIfd/TiffIfdEntry.cs @@ -0,0 +1,26 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Formats +{ + /// + /// Data structure for holding details of each TIFF IFD entry + /// + internal struct TiffIfdEntry + { + public ushort Tag; + public TiffType Type; + public uint Count; + public byte[] Value; + + public TiffIfdEntry(ushort tag, TiffType type, uint count, byte[] value) + { + this.Tag = tag; + this.Type = type; + this.Count = count; + this.Value = value; + } + } +} diff --git a/tests/ImageSharp.Formats.Tiff.Tests/Formats/Tiff/TiffDecoderIfdTests.cs b/tests/ImageSharp.Formats.Tiff.Tests/Formats/Tiff/TiffDecoderIfdTests.cs new file mode 100644 index 000000000..af0e0b93f --- /dev/null +++ b/tests/ImageSharp.Formats.Tiff.Tests/Formats/Tiff/TiffDecoderIfdTests.cs @@ -0,0 +1,111 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Tests +{ + using System.IO; + using System.Linq; + using Xunit; + + using ImageSharp.Formats; + + public class TiffDecoderIfdTests + { + public static object[][] IsLittleEndianValues = new[] { new object[] { false }, + new object[] { true } }; + + [Theory] + [MemberData(nameof(IsLittleEndianValues))] + public void ReadIfd_ReadsNextIfdOffset_IfPresent(bool isLittleEndian) + { + Stream stream = new TiffGenIfd() + { + Entries = + { + TiffGenEntry.Integer(TiffTags.ImageWidth, TiffType.Long, 150) + }, + NextIfd = new TiffGenIfd() + } + .ToStream(isLittleEndian); + + TiffDecoderCore decoder = new TiffDecoderCore(stream, isLittleEndian, null); + TiffIfd ifd = decoder.ReadIfd(0); + + Assert.Equal(18u, ifd.NextIfdOffset); + } + + [Theory] + [MemberData(nameof(IsLittleEndianValues))] + public void ReadIfd_ReadsNextIfdOffset_ZeroIfLastIfd(bool isLittleEndian) + { + Stream stream = new TiffGenIfd() + { + Entries = + { + TiffGenEntry.Integer(TiffTags.ImageWidth, TiffType.Long, 150) + } + } + .ToStream(isLittleEndian); + + TiffDecoderCore decoder = new TiffDecoderCore(stream, isLittleEndian, null); + TiffIfd ifd = decoder.ReadIfd(0); + + Assert.Equal(0u, ifd.NextIfdOffset); + } + + [Theory] + [MemberData(nameof(IsLittleEndianValues))] + public void ReadIfd_ReturnsCorrectNumberOfEntries(bool isLittleEndian) + { + Stream stream = new TiffGenIfd() + { + Entries = + { + TiffGenEntry.Integer(TiffTags.ImageWidth, TiffType.Long, 150), + TiffGenEntry.Integer(TiffTags.ImageLength, TiffType.Long, 210), + TiffGenEntry.Integer(TiffTags.Orientation, TiffType.Short, 1), + TiffGenEntry.Ascii(TiffTags.Artist, "Image Artist Name"), + TiffGenEntry.Ascii(TiffTags.HostComputer, "Host Computer Name") + }, + NextIfd = new TiffGenIfd() + } + .ToStream(isLittleEndian); + + TiffDecoderCore decoder = new TiffDecoderCore(stream, isLittleEndian, null); + TiffIfd ifd = decoder.ReadIfd(0); + + Assert.NotNull(ifd.Entries); + Assert.Equal(5, ifd.Entries.Length); + } + + [Theory] + [MemberData(nameof(IsLittleEndianValues))] + public void ReadIfd_ReadsRawTiffEntryData(bool isLittleEndian) + { + Stream stream = new TiffGenIfd() + { + Entries = + { + TiffGenEntry.Integer(TiffTags.ImageWidth, TiffType.Long, 150), + TiffGenEntry.Integer(TiffTags.ImageLength, TiffType.Long, 210), + TiffGenEntry.Integer(TiffTags.Orientation, TiffType.Short, 1) + }, + NextIfd = new TiffGenIfd() + } + .ToStream(isLittleEndian); + + TiffDecoderCore decoder = new TiffDecoderCore(stream, isLittleEndian, null); + TiffIfd ifd = decoder.ReadIfd(0); + TiffIfdEntry entry = ifd.Entries[1]; + + byte[] expectedData = isLittleEndian ? new byte[] {210,0,0,0} : new byte[] {0,0,0,210}; + Assert.NotNull(entry); + Assert.Equal(TiffTags.ImageLength, entry.Tag); + Assert.Equal(TiffType.Long, entry.Type); + Assert.Equal(4u, entry.Count); + Assert.Equal(expectedData, entry.Value); + } + } +} \ No newline at end of file