From 2a89db1a067630ca00f5aa3f42de9b3353247ff5 Mon Sep 17 00:00:00 2001 From: Andrew Wilkinson Date: Mon, 6 Mar 2017 22:17:02 +0000 Subject: [PATCH] Read raw bytes for IFD entries --- .../TiffDecoderCore.cs | 45 ++++++ .../Formats/Tiff/TiffDecoderIfdEntryTests.cs | 135 ++++++++++++++++++ .../Formats/Tiff/TiffDecoderIfdTests.cs | 4 +- .../ImageSharp.Formats.Tiff.Tests.csproj | 1 + .../TestUtilities/Tiff/TiffGenEntry.cs | 35 ++++- .../TestUtilities/Tiff/TiffGenIfd.cs | 13 +- 6 files changed, 220 insertions(+), 13 deletions(-) create mode 100644 tests/ImageSharp.Formats.Tiff.Tests/Formats/Tiff/TiffDecoderIfdEntryTests.cs diff --git a/src/ImageSharp.Formats.Tiff/TiffDecoderCore.cs b/src/ImageSharp.Formats.Tiff/TiffDecoderCore.cs index e8c0db788e..37aad73200 100644 --- a/src/ImageSharp.Formats.Tiff/TiffDecoderCore.cs +++ b/src/ImageSharp.Formats.Tiff/TiffDecoderCore.cs @@ -133,6 +133,23 @@ namespace ImageSharp.Formats } } + public byte[] ReadBytes(ref TiffIfdEntry entry) + { + uint byteLength = GetSizeOfData(entry); + + if (entry.Value.Length < byteLength) + { + uint offset = ToUInt32(entry.Value, 0); + InputStream.Seek(offset, SeekOrigin.Begin); + + byte[] data = new byte[byteLength]; + ReadBytes(data, (int)byteLength); + entry.Value = data; + } + + return entry.Value; + } + private Int16 ToInt16(byte[] bytes, int offset) { if (IsLittleEndian) @@ -158,5 +175,33 @@ namespace ImageSharp.Formats { return (ushort)ToInt16(bytes, offset); } + + public static uint GetSizeOfData(TiffIfdEntry entry) => SizeOfDataType(entry.Type) * entry.Count; + + private static uint SizeOfDataType(TiffType type) + { + switch (type) + { + case TiffType.Byte: + case TiffType.Ascii: + case TiffType.SByte: + case TiffType.Undefined: + return 1u; + case TiffType.Short: + case TiffType.SShort: + return 2u; + case TiffType.Long: + case TiffType.SLong: + case TiffType.Float: + case TiffType.Ifd: + return 4u; + case TiffType.Rational: + case TiffType.SRational: + case TiffType.Double: + return 8u; + default: + return 0u; + } + } } } diff --git a/tests/ImageSharp.Formats.Tiff.Tests/Formats/Tiff/TiffDecoderIfdEntryTests.cs b/tests/ImageSharp.Formats.Tiff.Tests/Formats/Tiff/TiffDecoderIfdEntryTests.cs new file mode 100644 index 0000000000..1d987de9af --- /dev/null +++ b/tests/ImageSharp.Formats.Tiff.Tests/Formats/Tiff/TiffDecoderIfdEntryTests.cs @@ -0,0 +1,135 @@ +// +// 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 TiffDecoderIfdEntryTests + { + [Theory] + [InlineDataAttribute(TiffType.Byte, 1u, 1u)] + [InlineDataAttribute(TiffType.Ascii, 1u, 1u)] + [InlineDataAttribute(TiffType.Short, 1u, 2u)] + [InlineDataAttribute(TiffType.Long, 1u, 4u)] + [InlineDataAttribute(TiffType.Rational, 1u, 8u)] + [InlineDataAttribute(TiffType.SByte, 1u, 1u)] + [InlineDataAttribute(TiffType.Undefined, 1u, 1u)] + [InlineDataAttribute(TiffType.SShort, 1u, 2u)] + [InlineDataAttribute(TiffType.SLong, 1u, 4u)] + [InlineDataAttribute(TiffType.SRational, 1u, 8u)] + [InlineDataAttribute(TiffType.Float, 1u, 4u)] + [InlineDataAttribute(TiffType.Double, 1u, 8u)] + [InlineDataAttribute(TiffType.Ifd, 1u, 4u)] + [InlineDataAttribute((TiffType)999, 1u, 0u)] + public void GetSizeOfData_SingleItem_ReturnsCorrectSize(ushort type, uint count, uint expectedSize) + { + TiffIfdEntry entry = new TiffIfdEntry(TiffTags.ImageWidth, (TiffType)type, count, new byte[4]); + uint size = TiffDecoderCore.GetSizeOfData(entry); + Assert.Equal(expectedSize, size); + } + + [Theory] + [InlineDataAttribute(TiffType.Byte, 15u, 15u)] + [InlineDataAttribute(TiffType.Ascii, 20u, 20u)] + [InlineDataAttribute(TiffType.Short, 18u, 36u)] + [InlineDataAttribute(TiffType.Long, 4u, 16u)] + [InlineDataAttribute(TiffType.Rational, 9u, 72u)] + [InlineDataAttribute(TiffType.SByte, 5u, 5u)] + [InlineDataAttribute(TiffType.Undefined, 136u, 136u)] + [InlineDataAttribute(TiffType.SShort, 12u, 24u)] + [InlineDataAttribute(TiffType.SLong, 15u, 60u)] + [InlineDataAttribute(TiffType.SRational, 10u, 80u)] + [InlineDataAttribute(TiffType.Float, 2u, 8u)] + [InlineDataAttribute(TiffType.Double, 2u, 16u)] + [InlineDataAttribute(TiffType.Ifd, 10u, 40u)] + [InlineDataAttribute((TiffType)999, 1050u, 0u)] + public void GetSizeOfData_Array_ReturnsCorrectSize(ushort type, uint count, uint expectedSize) + { + TiffIfdEntry entry = new TiffIfdEntry(TiffTags.ImageWidth, (TiffType)type, count, new byte[4]); + uint size = TiffDecoderCore.GetSizeOfData(entry); + Assert.Equal(expectedSize, size); + } + + [Theory] + [InlineDataAttribute(TiffType.Byte, 1u, new byte[] { 17 }, false)] + [InlineDataAttribute(TiffType.Byte, 1u, new byte[] { 17 }, true)] + [InlineDataAttribute(TiffType.Byte, 2u, new byte[] { 17, 28 }, false)] + [InlineDataAttribute(TiffType.Byte, 2u, new byte[] { 17, 28 }, true)] + [InlineDataAttribute(TiffType.Byte, 4u, new byte[] { 17, 28, 2, 9 }, false)] + [InlineDataAttribute(TiffType.Byte, 4u, new byte[] { 17, 28, 2, 9 }, true)] + [InlineDataAttribute(TiffType.Byte, 5u, new byte[] { 17, 28, 2, 9, 13 }, false)] + [InlineDataAttribute(TiffType.Byte, 5u, new byte[] { 17, 28, 2, 9, 13 }, true)] + [InlineDataAttribute(TiffType.Byte, 10u, new byte[] { 17, 28, 2, 9, 13, 37, 18, 2, 127, 86 }, false)] + [InlineDataAttribute(TiffType.Byte, 10u, new byte[] { 17, 28, 2, 9, 13, 37, 18, 2, 127, 86 }, true)] + [InlineDataAttribute(TiffType.Short, 1u, new byte[] { 17, 28 }, false)] + [InlineDataAttribute(TiffType.Short, 1u, new byte[] { 17, 28 }, true)] + [InlineDataAttribute(TiffType.Short, 2u, new byte[] { 17, 28, 2, 9 }, false)] + [InlineDataAttribute(TiffType.Short, 2u, new byte[] { 17, 28, 2, 9 }, true)] + [InlineDataAttribute(TiffType.Short, 3u, new byte[] { 17, 28, 2, 9, 13, 37 }, false)] + [InlineDataAttribute(TiffType.Short, 3u, new byte[] { 17, 28, 2, 9, 13, 37 }, true)] + [InlineDataAttribute(TiffType.Long, 1u, new byte[] { 17, 28, 2, 9 }, false)] + [InlineDataAttribute(TiffType.Long, 1u, new byte[] { 17, 28, 2, 9 }, true)] + [InlineDataAttribute(TiffType.Long, 2u, new byte[] { 17, 28, 2, 9, 13, 37, 18, 2 }, false)] + [InlineDataAttribute(TiffType.Long, 2u, new byte[] { 17, 28, 2, 9, 13, 37, 18, 2 }, true)] + [InlineDataAttribute(TiffType.Rational, 1u, new byte[] { 17, 28, 2, 9, 13, 37, 18, 2 }, false)] + [InlineDataAttribute(TiffType.Rational, 1u, new byte[] { 17, 28, 2, 9, 13, 37, 18, 2 }, true)] + public void ReadBytes_ReturnsExpectedData(ushort type, uint count, byte[] bytes, bool isLittleEndian) + { + (TiffDecoderCore decoder, TiffIfdEntry entry) = GenerateTestIfdEntry(TiffGenEntry.Bytes(TiffTags.ImageWidth, (TiffType)type, count, bytes), isLittleEndian); + + byte[] result = decoder.ReadBytes(ref entry); + + if (bytes.Length < 4) + result = result.Take(bytes.Length).ToArray(); + + Assert.Equal(bytes, result); + } + + [Theory] + [InlineDataAttribute(TiffType.Byte, 5u, new byte[] { 17, 28, 2, 9, 13 }, false)] + [InlineDataAttribute(TiffType.Byte, 5u, new byte[] { 17, 28, 2, 9, 13 }, true)] + [InlineDataAttribute(TiffType.Byte, 10u, new byte[] { 17, 28, 2, 9, 13, 37, 18, 2, 127, 86 }, false)] + [InlineDataAttribute(TiffType.Byte, 10u, new byte[] { 17, 28, 2, 9, 13, 37, 18, 2, 127, 86 }, true)] + [InlineDataAttribute(TiffType.Short, 3u, new byte[] { 17, 28, 2, 9, 13, 37 }, false)] + [InlineDataAttribute(TiffType.Short, 3u, new byte[] { 17, 28, 2, 9, 13, 37 }, true)] + [InlineDataAttribute(TiffType.Long, 2u, new byte[] { 17, 28, 2, 9, 13, 37, 18, 2 }, false)] + [InlineDataAttribute(TiffType.Long, 2u, new byte[] { 17, 28, 2, 9, 13, 37, 18, 2 }, true)] + [InlineDataAttribute(TiffType.Rational, 1u, new byte[] { 17, 28, 2, 9, 13, 37, 18, 2 }, false)] + [InlineDataAttribute(TiffType.Rational, 1u, new byte[] { 17, 28, 2, 9, 13, 37, 18, 2 }, true)] + public void ReadBytes_CachesDataLongerThanFourBytes(ushort type, uint count, byte[] bytes, bool isLittleEndian) + { + (TiffDecoderCore decoder, TiffIfdEntry entry) = GenerateTestIfdEntry(TiffGenEntry.Bytes(TiffTags.ImageWidth, (TiffType)type, count, bytes), isLittleEndian); + + Assert.Equal(4, entry.Value.Length); + + byte[] result = decoder.ReadBytes(ref entry); + + Assert.Equal(bytes.Length, entry.Value.Length); + Assert.Equal(bytes, entry.Value); + } + + private (TiffDecoderCore, TiffIfdEntry) GenerateTestIfdEntry(TiffGenEntry entry, bool isLittleEndian) + { + Stream stream = new TiffGenIfd() + { + Entries = + { + entry + } + } + .ToStream(isLittleEndian); + + TiffDecoderCore decoder = new TiffDecoderCore(stream, isLittleEndian, null); + TiffIfdEntry ifdEntry = decoder.ReadIfd(0).Entries[0]; + + return (decoder, ifdEntry); + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Formats.Tiff.Tests/Formats/Tiff/TiffDecoderIfdTests.cs b/tests/ImageSharp.Formats.Tiff.Tests/Formats/Tiff/TiffDecoderIfdTests.cs index af0e0b93f0..606e024a14 100644 --- a/tests/ImageSharp.Formats.Tiff.Tests/Formats/Tiff/TiffDecoderIfdTests.cs +++ b/tests/ImageSharp.Formats.Tiff.Tests/Formats/Tiff/TiffDecoderIfdTests.cs @@ -80,7 +80,7 @@ namespace ImageSharp.Tests Assert.Equal(5, ifd.Entries.Length); } - [Theory] + [Theory] [MemberData(nameof(IsLittleEndianValues))] public void ReadIfd_ReadsRawTiffEntryData(bool isLittleEndian) { @@ -104,7 +104,7 @@ namespace ImageSharp.Tests Assert.NotNull(entry); Assert.Equal(TiffTags.ImageLength, entry.Tag); Assert.Equal(TiffType.Long, entry.Type); - Assert.Equal(4u, entry.Count); + Assert.Equal(1u, entry.Count); Assert.Equal(expectedData, entry.Value); } } diff --git a/tests/ImageSharp.Formats.Tiff.Tests/ImageSharp.Formats.Tiff.Tests.csproj b/tests/ImageSharp.Formats.Tiff.Tests/ImageSharp.Formats.Tiff.Tests.csproj index a20f1fd99f..30ec6b75f5 100644 --- a/tests/ImageSharp.Formats.Tiff.Tests/ImageSharp.Formats.Tiff.Tests.csproj +++ b/tests/ImageSharp.Formats.Tiff.Tests/ImageSharp.Formats.Tiff.Tests.csproj @@ -11,6 +11,7 @@ + diff --git a/tests/ImageSharp.Formats.Tiff.Tests/TestUtilities/Tiff/TiffGenEntry.cs b/tests/ImageSharp.Formats.Tiff.Tests/TestUtilities/Tiff/TiffGenEntry.cs index df61b4d8a6..757da63b4d 100644 --- a/tests/ImageSharp.Formats.Tiff.Tests/TestUtilities/Tiff/TiffGenEntry.cs +++ b/tests/ImageSharp.Formats.Tiff.Tests/TestUtilities/Tiff/TiffGenEntry.cs @@ -16,12 +16,14 @@ namespace ImageSharp.Tests /// internal abstract class TiffGenEntry : ITiffGenDataSource { - private TiffGenEntry(ushort tag, TiffType type) + private TiffGenEntry(ushort tag, TiffType type, uint count) { this.Tag = tag; this.Type = type; + this.Count = count; } + public uint Count { get; } public ushort Tag { get; } public TiffType Type { get; } @@ -32,6 +34,11 @@ namespace ImageSharp.Tests return new TiffGenEntryAscii(tag, value); } + public static TiffGenEntry Bytes(ushort tag, TiffType type, uint count, byte[] value) + { + return new TiffGenEntryBytes(tag, type, count, value); + } + public static TiffGenEntry Integer(ushort tag, TiffType type, int value) { return TiffGenEntry.Integer(tag, type, new int[] {value}); @@ -48,7 +55,7 @@ namespace ImageSharp.Tests private class TiffGenEntryAscii : TiffGenEntry { - public TiffGenEntryAscii(ushort tag, string value) : base(tag, TiffType.Ascii) + public TiffGenEntryAscii(ushort tag, string value) : base(tag, TiffType.Ascii, (uint)GetBytes(value).Length) { this.Value = value; } @@ -57,14 +64,34 @@ namespace ImageSharp.Tests public override IEnumerable GetData(bool isLittleEndian) { - byte[] bytes = Encoding.ASCII.GetBytes($"{Value}\0"); + byte[] bytes = GetBytes(Value); return new[] { new TiffGenDataBlock(bytes) }; } + + private static byte[] GetBytes(string value) + { + return Encoding.ASCII.GetBytes($"{value}\0"); + } + } + + private class TiffGenEntryBytes : TiffGenEntry + { + public TiffGenEntryBytes(ushort tag, TiffType type, uint count, byte[] value) : base(tag, type, count) + { + this.Value = value; + } + + public byte[] Value { get; } + + public override IEnumerable GetData(bool isLittleEndian) + { + return new[] { new TiffGenDataBlock(Value) }; + } } private class TiffGenEntryInteger : TiffGenEntry { - public TiffGenEntryInteger(ushort tag, TiffType type, int[] value) : base(tag, type) + public TiffGenEntryInteger(ushort tag, TiffType type, int[] value) : base(tag, type, (uint)value.Length) { this.Value = value; } diff --git a/tests/ImageSharp.Formats.Tiff.Tests/TestUtilities/Tiff/TiffGenIfd.cs b/tests/ImageSharp.Formats.Tiff.Tests/TestUtilities/Tiff/TiffGenIfd.cs index c26a0f1997..ee560b18fe 100644 --- a/tests/ImageSharp.Formats.Tiff.Tests/TestUtilities/Tiff/TiffGenIfd.cs +++ b/tests/ImageSharp.Formats.Tiff.Tests/TestUtilities/Tiff/TiffGenIfd.cs @@ -40,18 +40,17 @@ namespace ImageSharp.Tests { var entryData = entry.GetData(isLittleEndian); var entryBytes = entryData.First().Bytes; - var entryCount = entryBytes.Length; bytes.AddUInt16(entry.Tag); bytes.AddUInt16((ushort)entry.Type); - bytes.AddUInt32((uint)entryCount); + bytes.AddUInt32(entry.Count); - if (entryCount <=4) + if (entryBytes.Length <=4) { - bytes.AddByte(entryCount > 0 ? entryBytes[0] : (byte)0); - bytes.AddByte(entryCount > 1 ? entryBytes[1] : (byte)0); - bytes.AddByte(entryCount > 2 ? entryBytes[2] : (byte)0); - bytes.AddByte(entryCount > 3 ? entryBytes[3] : (byte)0); + bytes.AddByte(entryBytes.Length > 0 ? entryBytes[0] : (byte)0); + bytes.AddByte(entryBytes.Length > 1 ? entryBytes[1] : (byte)0); + bytes.AddByte(entryBytes.Length > 2 ? entryBytes[2] : (byte)0); + bytes.AddByte(entryBytes.Length > 3 ? entryBytes[3] : (byte)0); dataBlocks.AddRange(entryData.Skip(1)); }