From c582ecf1f6f3aaf47a18297a4318856f51f225d3 Mon Sep 17 00:00:00 2001 From: Andrew Wilkinson Date: Mon, 20 Mar 2017 15:51:25 +0000 Subject: [PATCH] Decode TIFF image dimensions. --- .../Formats/Tiff/TiffDecoderCore.cs | 22 +++++ .../Formats/Tiff/TiffIfd/TiffIfd.cs | 21 +++++ .../Formats/Tiff/TiffDecoderImageTests.cs | 84 +++++++++++++++++++ .../Formats/Tiff/TiffIfd/TiffIfdEntryTests.cs | 26 ++++++ .../Formats/Tiff/TiffIfd/TiffIfdTests.cs | 61 ++++++++++++++ .../Tiff/TiffGenIfdExtensions.cs | 24 ++++++ 6 files changed, 238 insertions(+) create mode 100644 tests/ImageSharp.Formats.Tiff.Tests/Formats/Tiff/TiffDecoderImageTests.cs create mode 100644 tests/ImageSharp.Formats.Tiff.Tests/Formats/Tiff/TiffIfd/TiffIfdEntryTests.cs create mode 100644 tests/ImageSharp.Formats.Tiff.Tests/Formats/Tiff/TiffIfd/TiffIfdTests.cs create mode 100644 tests/ImageSharp.Formats.Tiff.Tests/TestUtilities/Tiff/TiffGenIfdExtensions.cs diff --git a/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs b/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs index e24a1aa39e..eb4c6b1c89 100644 --- a/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs +++ b/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs @@ -73,6 +73,7 @@ namespace ImageSharp.Formats uint firstIfdOffset = this.ReadHeader(); TiffIfd firstIfd = this.ReadIfd(firstIfdOffset); + this.DecodeImage(firstIfd, image); } /// @@ -150,6 +151,27 @@ namespace ImageSharp.Formats return new TiffIfd(entries, nextIfdOffset); } + /// + /// Decodes the image data from a specified IFD. + /// + /// The pixel format. + /// The IFD to read the image from. + /// The image, where the data should be set to. + public void DecodeImage(TiffIfd ifd, Image image) + where TColor : struct, IPixel + { + if (!ifd.TryGetIfdEntry(TiffTags.ImageLength, out TiffIfdEntry imageLengthEntry) + || !ifd.TryGetIfdEntry(TiffTags.ImageWidth, out TiffIfdEntry imageWidthEntry)) + { + throw new ImageFormatException("The TIFF IFD does not specify the image dimensions."); + } + + int width = (int)this.ReadUnsignedInteger(ref imageWidthEntry); + int height = (int)this.ReadUnsignedInteger(ref imageLengthEntry); + + image.InitPixels(width, height); + } + /// /// Reads the data from a as an array of bytes. /// diff --git a/src/ImageSharp/Formats/Tiff/TiffIfd/TiffIfd.cs b/src/ImageSharp/Formats/Tiff/TiffIfd/TiffIfd.cs index 477182c1e1..2206e97f39 100644 --- a/src/ImageSharp/Formats/Tiff/TiffIfd/TiffIfd.cs +++ b/src/ImageSharp/Formats/Tiff/TiffIfd/TiffIfd.cs @@ -30,5 +30,26 @@ namespace ImageSharp.Formats this.Entries = entries; this.NextIfdOffset = nextIfdOffset; } + + /// + /// Gets the child with the specified tag ID. + /// + /// The tag ID to search for. + /// The resulting , if it exists. + /// A flag indicating whether the requested entry exists + public bool TryGetIfdEntry(ushort tag, out TiffIfdEntry entry) + { + for (int i = 0; i < this.Entries.Length; i++) + { + if (this.Entries[i].Tag == tag) + { + entry = this.Entries[i]; + return true; + } + } + + entry = default(TiffIfdEntry); + return false; + } } } diff --git a/tests/ImageSharp.Formats.Tiff.Tests/Formats/Tiff/TiffDecoderImageTests.cs b/tests/ImageSharp.Formats.Tiff.Tests/Formats/Tiff/TiffDecoderImageTests.cs new file mode 100644 index 0000000000..824bbc3b5b --- /dev/null +++ b/tests/ImageSharp.Formats.Tiff.Tests/Formats/Tiff/TiffDecoderImageTests.cs @@ -0,0 +1,84 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Tests +{ + using System.IO; + using Xunit; + + using ImageSharp.Formats; + + public class TiffDecoderImageTests + { + public const int ImageWidth = 200; + public const int ImageHeight = 150; + + public static object[][] IsLittleEndianValues = new[] { new object[] { false }, + new object[] { true } }; + + [Theory] + [MemberData(nameof(IsLittleEndianValues))] + public void DecodeImage_SetsImageDimensions(bool isLittleEndian) + { + Stream stream = CreateTiffGenIfd() + .ToStream(isLittleEndian); + + TiffDecoderCore decoder = new TiffDecoderCore(stream, isLittleEndian, null); + TiffIfd ifd = decoder.ReadIfd(0); + Image image = new Image(1,1); + + decoder.DecodeImage(ifd, image); + + Assert.Equal(ImageWidth, image.Width); + Assert.Equal(ImageHeight, image.Height); + } + + [Theory] + [MemberData(nameof(IsLittleEndianValues))] + public void DecodeImage_ThrowsException_WithMissingImageWidth(bool isLittleEndian) + { + Stream stream = CreateTiffGenIfd() + .WithoutEntry(TiffTags.ImageWidth) + .ToStream(isLittleEndian); + + TiffDecoderCore decoder = new TiffDecoderCore(stream, isLittleEndian, null); + TiffIfd ifd = decoder.ReadIfd(0); + Image image = new Image(1,1); + + var e = Assert.Throws(() => decoder.DecodeImage(ifd, image)); + + Assert.Equal("The TIFF IFD does not specify the image dimensions.", e.Message); + } + + [Theory] + [MemberData(nameof(IsLittleEndianValues))] + public void DecodeImage_ThrowsException_WithMissingImageLength(bool isLittleEndian) + { + Stream stream = CreateTiffGenIfd() + .WithoutEntry(TiffTags.ImageLength) + .ToStream(isLittleEndian); + + TiffDecoderCore decoder = new TiffDecoderCore(stream, isLittleEndian, null); + TiffIfd ifd = decoder.ReadIfd(0); + Image image = new Image(1,1); + + var e = Assert.Throws(() => decoder.DecodeImage(ifd, image)); + + Assert.Equal("The TIFF IFD does not specify the image dimensions.", e.Message); + } + + private TiffGenIfd CreateTiffGenIfd() + { + return new TiffGenIfd() + { + Entries = + { + TiffGenEntry.Integer(TiffTags.ImageWidth, TiffType.Long, ImageWidth), + TiffGenEntry.Integer(TiffTags.ImageLength, TiffType.Long, ImageHeight), + } + }; + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Formats.Tiff.Tests/Formats/Tiff/TiffIfd/TiffIfdEntryTests.cs b/tests/ImageSharp.Formats.Tiff.Tests/Formats/Tiff/TiffIfd/TiffIfdEntryTests.cs new file mode 100644 index 0000000000..08b7dc8eba --- /dev/null +++ b/tests/ImageSharp.Formats.Tiff.Tests/Formats/Tiff/TiffIfd/TiffIfdEntryTests.cs @@ -0,0 +1,26 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Tests +{ + using System.IO; + using Xunit; + + using ImageSharp.Formats; + + public class TiffIfdEntryTests + { + [Fact] + public void Constructor_SetsProperties() + { + var entry = new TiffIfdEntry((ushort)10u, TiffType.Short, 20u, new byte[] { 2, 4, 6, 8 }); + + Assert.Equal(10u, entry.Tag); + Assert.Equal(TiffType.Short, entry.Type); + Assert.Equal(20u, entry.Count); + Assert.Equal(new byte[] { 2, 4, 6, 8 }, entry.Value); + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Formats.Tiff.Tests/Formats/Tiff/TiffIfd/TiffIfdTests.cs b/tests/ImageSharp.Formats.Tiff.Tests/Formats/Tiff/TiffIfd/TiffIfdTests.cs new file mode 100644 index 0000000000..d9f8425cb6 --- /dev/null +++ b/tests/ImageSharp.Formats.Tiff.Tests/Formats/Tiff/TiffIfd/TiffIfdTests.cs @@ -0,0 +1,61 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Tests +{ + using System.IO; + using Xunit; + + using ImageSharp.Formats; + + public class TiffIfdTests + { + [Fact] + public void Constructor_SetsProperties() + { + var entries = new TiffIfdEntry[10]; + var ifd = new TiffIfd(entries, 1234u); + + Assert.Equal(entries, ifd.Entries); + Assert.Equal(1234u, ifd.NextIfdOffset); + } + + [Fact] + public void TryGetIfdEntry_ReturnsIfdIfExists() + { + var entries = new[] + { + new TiffIfdEntry(10, TiffType.Short, 20, new byte[4]), + new TiffIfdEntry(20, TiffType.Short, 20, new byte[4]), + new TiffIfdEntry(30, TiffType.Short, 20, new byte[4]), + new TiffIfdEntry(40, TiffType.Short, 20, new byte[4]) + }; + var ifd = new TiffIfd(entries, 1234u); + + bool success = ifd.TryGetIfdEntry(30, out var entry); + + Assert.Equal(true, success); + Assert.Equal(30, entry.Tag); + } + + [Fact] + public void TryGetIfdEntry_ReturnsFalseOtherwise() + { + var entries = new[] + { + new TiffIfdEntry(10, TiffType.Short, 20, new byte[4]), + new TiffIfdEntry(20, TiffType.Short, 20, new byte[4]), + new TiffIfdEntry(30, TiffType.Short, 20, new byte[4]), + new TiffIfdEntry(40, TiffType.Short, 20, new byte[4]) + }; + var ifd = new TiffIfd(entries, 1234u); + + bool success = ifd.TryGetIfdEntry(25, out var entry); + + Assert.Equal(false, success); + Assert.Equal(0, entry.Tag); + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Formats.Tiff.Tests/TestUtilities/Tiff/TiffGenIfdExtensions.cs b/tests/ImageSharp.Formats.Tiff.Tests/TestUtilities/Tiff/TiffGenIfdExtensions.cs new file mode 100644 index 0000000000..84ea0f1acd --- /dev/null +++ b/tests/ImageSharp.Formats.Tiff.Tests/TestUtilities/Tiff/TiffGenIfdExtensions.cs @@ -0,0 +1,24 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Tests +{ + using System; + using System.IO; + using System.Linq; + + /// + /// A utility class for manipulating in-memory Tiff files for use in unit tests. + /// + internal static class TiffGenIfdExtensions + { + public static TiffGenIfd WithoutEntry(this TiffGenIfd ifd, ushort tag) + { + TiffGenEntry entry = ifd.Entries.First(e => e.Tag == tag); + ifd.Entries.Remove(entry); + return ifd; + } + } +} \ No newline at end of file