From 596a738daaa31eca5aeffa373a31b127740f6a25 Mon Sep 17 00:00:00 2001 From: Andrew Wilkinson Date: Thu, 22 Jun 2017 12:53:36 +0100 Subject: [PATCH] Write metadata to a TIFF file --- .../Formats/Tiff/TiffEncoderCore.cs | 98 +++++ .../Tiff/TiffIfd/TiffIfdEntryCreator.cs | 354 +++++++++++++++ .../Formats/Tiff/TiffEncoderMetadataTests.cs | 58 +++ .../Tiff/TiffIfd/TiffIfdEntryCreatorTests.cs | 410 ++++++++++++++++++ .../TestUtilities/Tiff/TiffIfdParser.cs | 77 ++++ 5 files changed, 997 insertions(+) create mode 100644 src/ImageSharp/Formats/Tiff/TiffIfd/TiffIfdEntryCreator.cs create mode 100644 tests/ImageSharp.Formats.Tiff.Tests/Formats/Tiff/TiffEncoderMetadataTests.cs create mode 100644 tests/ImageSharp.Formats.Tiff.Tests/Formats/Tiff/TiffIfd/TiffIfdEntryCreatorTests.cs create mode 100644 tests/ImageSharp.Formats.Tiff.Tests/TestUtilities/Tiff/TiffIfdParser.cs diff --git a/src/ImageSharp/Formats/Tiff/TiffEncoderCore.cs b/src/ImageSharp/Formats/Tiff/TiffEncoderCore.cs index d32e34c433..5f1148adea 100644 --- a/src/ImageSharp/Formats/Tiff/TiffEncoderCore.cs +++ b/src/ImageSharp/Formats/Tiff/TiffEncoderCore.cs @@ -39,6 +39,16 @@ namespace ImageSharp.Formats this.options = options ?? new TiffEncoderOptions(); } + /// + /// Gets or sets the photometric interpretation implementation to use when encoding the image. + /// + public TiffColorType ColorType { get; set; } + + /// + /// Gets or sets the compression implementation to use when encoding the image. + /// + public TiffCompressionType CompressionType { get; set; } + /// /// Encodes the image to the specified stream from the . /// @@ -138,6 +148,94 @@ namespace ImageSharp.Formats /// The marker to write the next IFD offset (if present). public long WriteImage(TiffWriter writer, Image image, long ifdOffset) where TPixel : struct, IPixel + { + List ifdEntries = new List(); + + this.AddImageFormat(image, ifdEntries); + this.AddMetadata(image, ifdEntries); + + writer.WriteMarker(ifdOffset, (uint)writer.Position); + long nextIfdMarker = this.WriteIfd(writer, ifdEntries); + + return nextIfdMarker; + } + + /// + /// Adds image metadata to the specified IFD. + /// + /// The pixel format. + /// The to encode from. + /// The metadata entries to add to the IFD. + public void AddMetadata(Image image, List ifdEntries) + where TPixel : struct, IPixel + { + ifdEntries.AddUnsignedRational(TiffTags.XResolution, new Rational(image.MetaData.HorizontalResolution)); + ifdEntries.AddUnsignedRational(TiffTags.YResolution, new Rational(image.MetaData.VerticalResolution)); + ifdEntries.AddUnsignedShort(TiffTags.ResolutionUnit, (uint)TiffResolutionUnit.Inch); + + foreach (ImageProperty metadata in image.MetaData.Properties) + { + switch (metadata.Name) + { + case TiffMetadataNames.Artist: + { + ifdEntries.AddAscii(TiffTags.Artist, metadata.Value); + break; + } + + case TiffMetadataNames.Copyright: + { + ifdEntries.AddAscii(TiffTags.Copyright, metadata.Value); + break; + } + + case TiffMetadataNames.DateTime: + { + ifdEntries.AddAscii(TiffTags.DateTime, metadata.Value); + break; + } + + case TiffMetadataNames.HostComputer: + { + ifdEntries.AddAscii(TiffTags.HostComputer, metadata.Value); + break; + } + + case TiffMetadataNames.ImageDescription: + { + ifdEntries.AddAscii(TiffTags.ImageDescription, metadata.Value); + break; + } + + case TiffMetadataNames.Make: + { + ifdEntries.AddAscii(TiffTags.Make, metadata.Value); + break; + } + + case TiffMetadataNames.Model: + { + ifdEntries.AddAscii(TiffTags.Model, metadata.Value); + break; + } + + case TiffMetadataNames.Software: + { + ifdEntries.AddAscii(TiffTags.Software, metadata.Value); + break; + } + } + } + } + + /// + /// Adds image format information to the specified IFD. + /// + /// The pixel format. + /// The to encode from. + /// The image format entries to add to the IFD. + public void AddImageFormat(Image image, List ifdEntries) + where TPixel : struct, IPixel { throw new NotImplementedException(); } diff --git a/src/ImageSharp/Formats/Tiff/TiffIfd/TiffIfdEntryCreator.cs b/src/ImageSharp/Formats/Tiff/TiffIfd/TiffIfdEntryCreator.cs new file mode 100644 index 0000000000..c30ce5c8a4 --- /dev/null +++ b/src/ImageSharp/Formats/Tiff/TiffIfd/TiffIfdEntryCreator.cs @@ -0,0 +1,354 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Formats.Tiff +{ + using System; + using System.Collections.Generic; + using System.Text; + + /// + /// Utility class for generating TIFF IFD entries. + /// + internal static class TiffIfdEntryCreator + { + /// + /// Adds a new of type 'Byte' from a unsigned integer. + /// + /// The list of to add the new entry to. + /// The tag for the resulting entry. + /// The value for the resulting entry. + public static void AddUnsignedByte(this List entries, ushort tag, uint value) + { + TiffIfdEntryCreator.AddUnsignedByte(entries, tag, new[] { value }); + } + + /// + /// Adds a new of type 'Byte' from an array of unsigned integers. + /// + /// The list of to add the new entry to. + /// The tag for the resulting entry. + /// The value for the resulting entry. + public static void AddUnsignedByte(this List entries, ushort tag, uint[] value) + { + byte[] bytes = new byte[value.Length]; + + for (int i = 0; i < value.Length; i++) + { + bytes[i] = (byte)value[i]; + } + + entries.Add(new TiffIfdEntry(tag, TiffType.Byte, (uint)value.Length, bytes)); + } + + /// + /// Adds a new of type 'Short' from a unsigned integer. + /// + /// The list of to add the new entry to. + /// The tag for the resulting entry. + /// The value for the resulting entry. + public static void AddUnsignedShort(this List entries, ushort tag, uint value) + { + TiffIfdEntryCreator.AddUnsignedShort(entries, tag, new[] { value }); + } + + /// + /// Adds a new of type 'Short' from an array of unsigned integers. + /// + /// The list of to add the new entry to. + /// The tag for the resulting entry. + /// The value for the resulting entry. + public static void AddUnsignedShort(this List entries, ushort tag, uint[] value) + { + byte[] bytes = new byte[value.Length * TiffConstants.SizeOfShort]; + + for (int i = 0; i < value.Length; i++) + { + ToBytes((ushort)value[i], bytes, i * TiffConstants.SizeOfShort); + } + + entries.Add(new TiffIfdEntry(tag, TiffType.Short, (uint)value.Length, bytes)); + } + + /// + /// Adds a new of type 'Long' from a unsigned integer. + /// + /// The list of to add the new entry to. + /// The tag for the resulting entry. + /// The value for the resulting entry. + public static void AddUnsignedLong(this List entries, ushort tag, uint value) + { + TiffIfdEntryCreator.AddUnsignedLong(entries, tag, new[] { value }); + } + + /// + /// Adds a new of type 'Long' from an array of unsigned integers. + /// + /// The list of to add the new entry to. + /// The tag for the resulting entry. + /// The value for the resulting entry. + public static void AddUnsignedLong(this List entries, ushort tag, uint[] value) + { + byte[] bytes = new byte[value.Length * TiffConstants.SizeOfLong]; + + for (int i = 0; i < value.Length; i++) + { + ToBytes(value[i], bytes, i * TiffConstants.SizeOfLong); + } + + entries.Add(new TiffIfdEntry(tag, TiffType.Long, (uint)value.Length, bytes)); + } + + /// + /// Adds a new of type 'SByte' from a signed integer. + /// + /// The list of to add the new entry to. + /// The tag for the resulting entry. + /// The value for the resulting entry. + public static void AddSignedByte(this List entries, ushort tag, int value) + { + TiffIfdEntryCreator.AddSignedByte(entries, tag, new[] { value }); + } + + /// + /// Adds a new of type 'SByte' from an array of signed integers. + /// + /// The list of to add the new entry to. + /// The tag for the resulting entry. + /// The value for the resulting entry. + public static void AddSignedByte(this List entries, ushort tag, int[] value) + { + byte[] bytes = new byte[value.Length]; + + for (int i = 0; i < value.Length; i++) + { + bytes[i] = (byte)((sbyte)value[i]); + } + + entries.Add(new TiffIfdEntry(tag, TiffType.SByte, (uint)value.Length, bytes)); + } + + /// + /// Adds a new of type 'SShort' from a signed integer. + /// + /// The list of to add the new entry to. + /// The tag for the resulting entry. + /// The value for the resulting entry. + public static void AddSignedShort(this List entries, ushort tag, int value) + { + TiffIfdEntryCreator.AddSignedShort(entries, tag, new[] { value }); + } + + /// + /// Adds a new of type 'SShort' from an array of signed integers. + /// + /// The list of to add the new entry to. + /// The tag for the resulting entry. + /// The value for the resulting entry. + public static void AddSignedShort(this List entries, ushort tag, int[] value) + { + byte[] bytes = new byte[value.Length * TiffConstants.SizeOfShort]; + + for (int i = 0; i < value.Length; i++) + { + ToBytes((short)value[i], bytes, i * TiffConstants.SizeOfShort); + } + + entries.Add(new TiffIfdEntry(tag, TiffType.SShort, (uint)value.Length, bytes)); + } + + /// + /// Adds a new of type 'SLong' from a signed integer. + /// + /// The list of to add the new entry to. + /// The tag for the resulting entry. + /// The value for the resulting entry. + public static void AddSignedLong(this List entries, ushort tag, int value) + { + TiffIfdEntryCreator.AddSignedLong(entries, tag, new[] { value }); + } + + /// + /// Adds a new of type 'SLong' from an array of signed integers. + /// + /// The list of to add the new entry to. + /// The tag for the resulting entry. + /// The value for the resulting entry. + public static void AddSignedLong(this List entries, ushort tag, int[] value) + { + byte[] bytes = new byte[value.Length * TiffConstants.SizeOfLong]; + + for (int i = 0; i < value.Length; i++) + { + ToBytes(value[i], bytes, i * TiffConstants.SizeOfLong); + } + + entries.Add(new TiffIfdEntry(tag, TiffType.SLong, (uint)value.Length, bytes)); + } + + /// + /// Adds a new of type 'Ascii' from a string. + /// + /// The list of to add the new entry to. + /// The tag for the resulting entry. + /// The value for the resulting entry. + public static void AddAscii(this List entries, ushort tag, string value) + { + byte[] bytes = Encoding.UTF8.GetBytes(value + "\0"); + + entries.Add(new TiffIfdEntry(tag, TiffType.Ascii, (uint)bytes.Length, bytes)); + } + + /// + /// Adds a new of type 'Rational' from a . + /// + /// The list of to add the new entry to. + /// The tag for the resulting entry. + /// The value for the resulting entry. + public static void AddUnsignedRational(this List entries, ushort tag, Rational value) + { + TiffIfdEntryCreator.AddUnsignedRational(entries, tag, new[] { value }); + } + + /// + /// Adds a new of type 'Rational' from an array of values. + /// + /// The list of to add the new entry to. + /// The tag for the resulting entry. + /// The value for the resulting entry. + public static void AddUnsignedRational(this List entries, ushort tag, Rational[] value) + { + byte[] bytes = new byte[value.Length * TiffConstants.SizeOfRational]; + + for (int i = 0; i < value.Length; i++) + { + int offset = i * TiffConstants.SizeOfRational; + ToBytes(value[i].Numerator, bytes, offset); + ToBytes(value[i].Denominator, bytes, offset + TiffConstants.SizeOfLong); + } + + entries.Add(new TiffIfdEntry(tag, TiffType.Rational, (uint)value.Length, bytes)); + } + + /// + /// Adds a new of type 'SRational' from a . + /// + /// The list of to add the new entry to. + /// The tag for the resulting entry. + /// The value for the resulting entry. + public static void AddSignedRational(this List entries, ushort tag, SignedRational value) + { + TiffIfdEntryCreator.AddSignedRational(entries, tag, new[] { value }); + } + + /// + /// Adds a new of type 'SRational' from an array of values. + /// + /// The list of to add the new entry to. + /// The tag for the resulting entry. + /// The value for the resulting entry. + public static void AddSignedRational(this List entries, ushort tag, SignedRational[] value) + { + byte[] bytes = new byte[value.Length * TiffConstants.SizeOfRational]; + + for (int i = 0; i < value.Length; i++) + { + int offset = i * TiffConstants.SizeOfRational; + ToBytes(value[i].Numerator, bytes, offset); + ToBytes(value[i].Denominator, bytes, offset + TiffConstants.SizeOfLong); + } + + entries.Add(new TiffIfdEntry(tag, TiffType.SRational, (uint)value.Length, bytes)); + } + + /// + /// Adds a new of type 'Float' from a floating-point value. + /// + /// The list of to add the new entry to. + /// The tag for the resulting entry. + /// The value for the resulting entry. + public static void AddFloat(this List entries, ushort tag, float value) + { + TiffIfdEntryCreator.AddFloat(entries, tag, new[] { value }); + } + + /// + /// Adds a new of type 'Float' from an array of floating-point values. + /// + /// The list of to add the new entry to. + /// The tag for the resulting entry. + /// The value for the resulting entry. + public static void AddFloat(this List entries, ushort tag, float[] value) + { + byte[] bytes = new byte[value.Length * TiffConstants.SizeOfFloat]; + + for (int i = 0; i < value.Length; i++) + { + byte[] itemBytes = BitConverter.GetBytes(value[i]); + Array.Copy(itemBytes, 0, bytes, i * TiffConstants.SizeOfFloat, TiffConstants.SizeOfFloat); + } + + entries.Add(new TiffIfdEntry(tag, TiffType.Float, (uint)value.Length, bytes)); + } + + /// + /// Adds a new of type 'Double' from a floating-point value. + /// + /// The list of to add the new entry to. + /// The tag for the resulting entry. + /// The value for the resulting entry. + public static void AddDouble(this List entries, ushort tag, double value) + { + TiffIfdEntryCreator.AddDouble(entries, tag, new[] { value }); + } + + /// + /// Adds a new of type 'Double' from an array of floating-point values. + /// + /// The list of to add the new entry to. + /// The tag for the resulting entry. + /// The value for the resulting entry. + public static void AddDouble(this List entries, ushort tag, double[] value) + { + byte[] bytes = new byte[value.Length * TiffConstants.SizeOfDouble]; + + for (int i = 0; i < value.Length; i++) + { + byte[] itemBytes = BitConverter.GetBytes(value[i]); + Array.Copy(itemBytes, 0, bytes, i * TiffConstants.SizeOfDouble, TiffConstants.SizeOfDouble); + } + + entries.Add(new TiffIfdEntry(tag, TiffType.Double, (uint)value.Length, bytes)); + } + + private static void ToBytes(ushort value, byte[] bytes, int offset) + { + bytes[offset + 0] = (byte)value; + bytes[offset + 1] = (byte)(value >> 8); + } + + private static void ToBytes(uint value, byte[] bytes, int offset) + { + bytes[offset + 0] = (byte)value; + bytes[offset + 1] = (byte)(value >> 8); + bytes[offset + 2] = (byte)(value >> 16); + bytes[offset + 3] = (byte)(value >> 24); + } + + private static void ToBytes(short value, byte[] bytes, int offset) + { + bytes[offset + 0] = (byte)value; + bytes[offset + 1] = (byte)(value >> 8); + } + + private static void ToBytes(int value, byte[] bytes, int offset) + { + bytes[offset + 0] = (byte)value; + bytes[offset + 1] = (byte)(value >> 8); + bytes[offset + 2] = (byte)(value >> 16); + bytes[offset + 3] = (byte)(value >> 24); + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Formats.Tiff.Tests/Formats/Tiff/TiffEncoderMetadataTests.cs b/tests/ImageSharp.Formats.Tiff.Tests/Formats/Tiff/TiffEncoderMetadataTests.cs new file mode 100644 index 0000000000..4dec7630cd --- /dev/null +++ b/tests/ImageSharp.Formats.Tiff.Tests/Formats/Tiff/TiffEncoderMetadataTests.cs @@ -0,0 +1,58 @@ +// +// 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; + using Xunit; + + using ImageSharp.Formats; + using ImageSharp.Formats.Tiff; + using System.Collections.Generic; + + public class TiffEncoderMetadataTests + { + public static object[][] BaselineMetadataValues = new[] { new object[] { TiffTags.Artist, TiffMetadataNames.Artist, "My Artist Name" }, + new object[] { TiffTags.Copyright, TiffMetadataNames.Copyright, "My Copyright Statement" }, + new object[] { TiffTags.DateTime, TiffMetadataNames.DateTime, "My DateTime Value" }, + new object[] { TiffTags.HostComputer, TiffMetadataNames.HostComputer, "My Host Computer Name" }, + new object[] { TiffTags.ImageDescription, TiffMetadataNames.ImageDescription, "My Image Description" }, + new object[] { TiffTags.Make, TiffMetadataNames.Make, "My Camera Make" }, + new object[] { TiffTags.Model, TiffMetadataNames.Model, "My Camera Model" }, + new object[] { TiffTags.Software, TiffMetadataNames.Software, "My Imaging Software" }}; + + [Fact] + public void AddMetadata_SetsImageResolution() + { + Image image = new Image(100, 100); + image.MetaData.HorizontalResolution = 40.0; + image.MetaData.VerticalResolution = 50.5; + TiffEncoderCore encoder = new TiffEncoderCore(null); + + List ifdEntries = new List(); + encoder.AddMetadata(image, ifdEntries); + + Assert.Equal(new Rational(40, 1), ifdEntries.GetUnsignedRational(TiffTags.XResolution)); + Assert.Equal(new Rational(101, 2), ifdEntries.GetUnsignedRational(TiffTags.YResolution)); + Assert.Equal(TiffResolutionUnit.Inch, (TiffResolutionUnit?)ifdEntries.GetInteger(TiffTags.ResolutionUnit)); + } + + [Theory] + [MemberData(nameof(BaselineMetadataValues))] + public void AddMetadata_SetsAsciiMetadata(ushort tag, string metadataName, string metadataValue) + { + Image image = new Image(100, 100); + image.MetaData.Properties.Add(new ImageProperty(metadataName, metadataValue)); + TiffEncoderCore encoder = new TiffEncoderCore(null); + + List ifdEntries = new List(); + encoder.AddMetadata(image, ifdEntries); + + Assert.Equal(metadataValue + "\0", ifdEntries.GetAscii(tag)); + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Formats.Tiff.Tests/Formats/Tiff/TiffIfd/TiffIfdEntryCreatorTests.cs b/tests/ImageSharp.Formats.Tiff.Tests/Formats/Tiff/TiffIfd/TiffIfdEntryCreatorTests.cs new file mode 100644 index 0000000000..036ab46218 --- /dev/null +++ b/tests/ImageSharp.Formats.Tiff.Tests/Formats/Tiff/TiffIfd/TiffIfdEntryCreatorTests.cs @@ -0,0 +1,410 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Tests +{ + using System; + using System.Collections.Generic; + using System.IO; + using System.Linq; + using Xunit; + + using ImageSharp.Formats; + using ImageSharp.Formats.Tiff; + + public class TiffIfdEntryCreatorTests + { + [Theory] + [InlineDataAttribute(new byte[] { 0 }, 0)] + [InlineDataAttribute(new byte[] { 1 }, 1)] + [InlineDataAttribute(new byte[] { 255 }, 255)] + public void AddUnsignedByte_AddsSingleValue(byte[] bytes, uint value) + { + var entries = new List(); + + entries.AddUnsignedByte(TiffTags.ImageWidth, value); + + var entry = entries[0]; + Assert.Equal(TiffTags.ImageWidth, entry.Tag); + Assert.Equal(TiffType.Byte, entry.Type); + Assert.Equal(1u, entry.Count); + Assert.Equal(bytes, entry.Value); + } + + [Theory] + [InlineDataAttribute(new byte[] { 0 }, new uint[] { 0 })] + [InlineDataAttribute(new byte[] { 0, 1, 2 }, new uint[] { 0, 1, 2 })] + [InlineDataAttribute(new byte[] { 0, 1, 2, 3, 4, 5, 6 }, new uint[] { 0, 1, 2, 3, 4, 5, 6 })] + public void AddUnsignedByte_AddsArray(byte[] bytes, uint[] value) + { + var entries = new List(); + + entries.AddUnsignedByte(TiffTags.ImageWidth, value); + + var entry = entries[0]; + Assert.Equal(TiffTags.ImageWidth, entry.Tag); + Assert.Equal(TiffType.Byte, entry.Type); + Assert.Equal((uint)value.Length, entry.Count); + Assert.Equal(bytes, entry.Value); + } + + [Theory] + [InlineDataAttribute(new byte[] { 0, 0 }, 0)] + [InlineDataAttribute(new byte[] { 1, 0 }, 1)] + [InlineDataAttribute(new byte[] { 0, 1 }, 256)] + [InlineDataAttribute(new byte[] { 2, 1 }, 258)] + [InlineDataAttribute(new byte[] { 255, 255 }, UInt16.MaxValue)] + public void AddUnsignedShort_AddsSingleValue(byte[] bytes, uint value) + { + var entries = new List(); + + entries.AddUnsignedShort(TiffTags.ImageWidth, value); + + var entry = entries[0]; + Assert.Equal(TiffTags.ImageWidth, entry.Tag); + Assert.Equal(TiffType.Short, entry.Type); + Assert.Equal(1u, entry.Count); + Assert.Equal(bytes, entry.Value); + } + + [Theory] + [InlineDataAttribute(new byte[] { 1, 0 }, new uint[] { 1 })] + [InlineDataAttribute(new byte[] { 1, 0, 3, 2 }, new uint[] { 1, 515 })] + [InlineDataAttribute(new byte[] { 1, 0, 3, 2, 5, 4 }, new uint[] { 1, 515, 1029 })] + public void AddUnsignedShort_AddsArray(byte[] bytes, uint[] value) + { + var entries = new List(); + + entries.AddUnsignedShort(TiffTags.ImageWidth, value); + + var entry = entries[0]; + Assert.Equal(TiffTags.ImageWidth, entry.Tag); + Assert.Equal(TiffType.Short, entry.Type); + Assert.Equal((uint)value.Length, entry.Count); + Assert.Equal(bytes, entry.Value); + } + + [Theory] + [InlineDataAttribute(new byte[] { 0, 0, 0, 0 }, 0)] + [InlineDataAttribute(new byte[] { 1, 0, 0, 0 }, 1)] + [InlineDataAttribute(new byte[] { 0, 1, 0, 0 }, 256)] + [InlineDataAttribute(new byte[] { 0, 0, 1, 0 }, 256 * 256)] + [InlineDataAttribute(new byte[] { 0, 0, 0, 1 }, 256 * 256 * 256)] + [InlineDataAttribute(new byte[] { 1, 2, 3, 4 }, 67305985)] + [InlineDataAttribute(new byte[] { 255, 255, 255, 255 }, UInt32.MaxValue)] + public void AddUnsignedLong_AddsSingleValue(byte[] bytes, uint value) + { + var entries = new List(); + + entries.AddUnsignedLong(TiffTags.ImageWidth, value); + + var entry = entries[0]; + Assert.Equal(TiffTags.ImageWidth, entry.Tag); + Assert.Equal(TiffType.Long, entry.Type); + Assert.Equal(1u, entry.Count); + Assert.Equal(bytes, entry.Value); + } + + [Theory] + [InlineDataAttribute(new byte[] { 4, 3, 2, 1 }, new uint[] { 0x01020304 })] + [InlineDataAttribute(new byte[] { 4, 3, 2, 1, 6, 5, 4, 3 }, new uint[] { 0x01020304, 0x03040506 })] + public void AddUnsignedLong_AddsArray(byte[] bytes, uint[] value) + { + var entries = new List(); + + entries.AddUnsignedLong(TiffTags.ImageWidth, value); + + var entry = entries[0]; + Assert.Equal(TiffTags.ImageWidth, entry.Tag); + Assert.Equal(TiffType.Long, entry.Type); + Assert.Equal((uint)value.Length, entry.Count); + Assert.Equal(bytes, entry.Value); + } + + [Theory] + [InlineDataAttribute(new byte[] { 0 }, 0)] + [InlineDataAttribute(new byte[] { 1 }, 1)] + [InlineDataAttribute(new byte[] { 255 }, -1)] + public void AddSignedByte_AddsSingleValue(byte[] bytes, int value) + { + var entries = new List(); + + entries.AddSignedByte(TiffTags.ImageWidth, value); + + var entry = entries[0]; + Assert.Equal(TiffTags.ImageWidth, entry.Tag); + Assert.Equal(TiffType.SByte, entry.Type); + Assert.Equal(1u, entry.Count); + Assert.Equal(bytes, entry.Value); + } + + [Theory] + [InlineDataAttribute(new byte[] { 0 }, new int[] { 0 })] + [InlineDataAttribute(new byte[] { 0, 255, 2 }, new int[] { 0, -1, 2 })] + [InlineDataAttribute(new byte[] { 0, 255, 2, 3, 4, 5, 6 }, new int[] { 0, -1, 2, 3, 4, 5, 6 })] + public void AddSignedByte_AddsArray(byte[] bytes, int[] value) + { + var entries = new List(); + + entries.AddSignedByte(TiffTags.ImageWidth, value); + + var entry = entries[0]; + Assert.Equal(TiffTags.ImageWidth, entry.Tag); + Assert.Equal(TiffType.SByte, entry.Type); + Assert.Equal((uint)value.Length, entry.Count); + Assert.Equal(bytes, entry.Value); + } + + [Theory] + [InlineDataAttribute(new byte[] { 0, 0 }, 0)] + [InlineDataAttribute(new byte[] { 1, 0 }, 1)] + [InlineDataAttribute(new byte[] { 0, 1 }, 256)] + [InlineDataAttribute(new byte[] { 2, 1 }, 258)] + [InlineDataAttribute(new byte[] { 255, 255 }, -1)] + public void AddSignedShort_AddsSingleValue(byte[] bytes, int value) + { + var entries = new List(); + + entries.AddSignedShort(TiffTags.ImageWidth, value); + + var entry = entries[0]; + Assert.Equal(TiffTags.ImageWidth, entry.Tag); + Assert.Equal(TiffType.SShort, entry.Type); + Assert.Equal(1u, entry.Count); + Assert.Equal(bytes, entry.Value); + } + + [Theory] + [InlineDataAttribute(new byte[] { 1, 0 }, new int[] { 1 })] + [InlineDataAttribute(new byte[] { 1, 0, 255, 255 }, new int[] { 1, -1 })] + [InlineDataAttribute(new byte[] { 1, 0, 255, 255, 5, 4 }, new int[] { 1, -1, 1029 })] + public void AddSignedShort_AddsArray(byte[] bytes, int[] value) + { + var entries = new List(); + + entries.AddSignedShort(TiffTags.ImageWidth, value); + + var entry = entries[0]; + Assert.Equal(TiffTags.ImageWidth, entry.Tag); + Assert.Equal(TiffType.SShort, entry.Type); + Assert.Equal((uint)value.Length, entry.Count); + Assert.Equal(bytes, entry.Value); + } + + [Theory] + [InlineDataAttribute(new byte[] { 0, 0, 0, 0 }, 0)] + [InlineDataAttribute(new byte[] { 1, 0, 0, 0 }, 1)] + [InlineDataAttribute(new byte[] { 0, 1, 0, 0 }, 256)] + [InlineDataAttribute(new byte[] { 0, 0, 1, 0 }, 256 * 256)] + [InlineDataAttribute(new byte[] { 0, 0, 0, 1 }, 256 * 256 * 256)] + [InlineDataAttribute(new byte[] { 1, 2, 3, 4 }, 67305985)] + [InlineDataAttribute(new byte[] { 255, 255, 255, 255 }, -1)] + public void AddSignedLong_AddsSingleValue(byte[] bytes, int value) + { + var entries = new List(); + + entries.AddSignedLong(TiffTags.ImageWidth, value); + + var entry = entries[0]; + Assert.Equal(TiffTags.ImageWidth, entry.Tag); + Assert.Equal(TiffType.SLong, entry.Type); + Assert.Equal(1u, entry.Count); + Assert.Equal(bytes, entry.Value); + } + + [Theory] + [InlineDataAttribute(new byte[] { 4, 3, 2, 1 }, new int[] { 0x01020304 })] + [InlineDataAttribute(new byte[] { 4, 3, 2, 1, 255, 255, 255, 255 }, new int[] { 0x01020304, -1 })] + public void AddSignedLong_AddsArray(byte[] bytes, int[] value) + { + var entries = new List(); + + entries.AddSignedLong(TiffTags.ImageWidth, value); + + var entry = entries[0]; + Assert.Equal(TiffTags.ImageWidth, entry.Tag); + Assert.Equal(TiffType.SLong, entry.Type); + Assert.Equal((uint)value.Length, entry.Count); + Assert.Equal(bytes, entry.Value); + } + + [Theory] + [InlineDataAttribute(new byte[] { 0 }, "")] + [InlineDataAttribute(new byte[] { (byte)'A', (byte)'B', (byte)'C', 0 }, "ABC")] + [InlineDataAttribute(new byte[] { (byte)'A', (byte)'B', (byte)'C', (byte)'D', (byte)'E', (byte)'F', 0 }, "ABCDEF")] + [InlineDataAttribute(new byte[] { (byte)'A', (byte)'B', (byte)'C', (byte)'D', 0, (byte)'E', (byte)'F', (byte)'G', (byte)'H', 0 }, "ABCD\0EFGH")] + public void AddAscii_AddsEntry(byte[] bytes, string value) + { + var entries = new List(); + + entries.AddAscii(TiffTags.ImageWidth, value); + + var entry = entries[0]; + Assert.Equal(TiffTags.ImageWidth, entry.Tag); + Assert.Equal(TiffType.Ascii, entry.Type); + Assert.Equal((uint)bytes.Length, entry.Count); + Assert.Equal(bytes, entry.Value); + } + + [Theory] + [InlineDataAttribute(new byte[] { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, 0)] + [InlineDataAttribute(new byte[] { 2, 0, 0, 0, 1, 0, 0, 0 }, 2, 1)] + [InlineDataAttribute(new byte[] { 1, 0, 0, 0, 2, 0, 0, 0 }, 1, 2)] + public void AddUnsignedRational_AddsSingleValue(byte[] bytes, uint numerator, uint denominator) + { + var entries = new List(); + + entries.AddUnsignedRational(TiffTags.ImageWidth, new Rational(numerator, denominator)); + + var entry = entries[0]; + Assert.Equal(TiffTags.ImageWidth, entry.Tag); + Assert.Equal(TiffType.Rational, entry.Type); + Assert.Equal(1u, entry.Count); + Assert.Equal(bytes, entry.Value); + } + + [Theory] + [InlineDataAttribute(new byte[] { 0, 0, 0, 0, 0, 0, 0, 0 }, new uint[] { 0 }, new uint[] { 0 })] + [InlineDataAttribute(new byte[] { 1, 0, 0, 0, 2, 0, 0, 0 }, new uint[] { 1 }, new uint[] { 2 })] + [InlineDataAttribute(new byte[] { 1, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 3, 0, 0, 0 }, new uint[] { 1, 2 }, new uint[] { 2, 3 })] + public void AddUnsignedRational_AddsArray(byte[] bytes, uint[] numerators, uint[] denominators) + { + var entries = new List(); + Rational[] value = Enumerable.Range(0, numerators.Length).Select(i => new Rational(numerators[i], denominators[i])).ToArray(); + + entries.AddUnsignedRational(TiffTags.ImageWidth, value); + + var entry = entries[0]; + Assert.Equal(TiffTags.ImageWidth, entry.Tag); + Assert.Equal(TiffType.Rational, entry.Type); + Assert.Equal((uint)numerators.Length, entry.Count); + Assert.Equal(bytes, entry.Value); + } + + [Theory] + [InlineDataAttribute(new byte[] { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, 0)] + [InlineDataAttribute(new byte[] { 2, 0, 0, 0, 1, 0, 0, 0 }, 2, 1)] + [InlineDataAttribute(new byte[] { 1, 0, 0, 0, 2, 0, 0, 0 }, 1, 2)] + [InlineDataAttribute(new byte[] { 255, 255, 255, 255, 2, 0, 0, 0 }, -1, 2)] + public void AddSignedRational_AddsSingleValue(byte[] bytes, int numerator, int denominator) + { + var entries = new List(); + + entries.AddSignedRational(TiffTags.ImageWidth, new SignedRational(numerator, denominator)); + + var entry = entries[0]; + Assert.Equal(TiffTags.ImageWidth, entry.Tag); + Assert.Equal(TiffType.SRational, entry.Type); + Assert.Equal(1u, entry.Count); + Assert.Equal(bytes, entry.Value); + } + + [Theory] + [InlineDataAttribute(new byte[] { 0, 0, 0, 0, 0, 0, 0, 0 }, new int[] { 0 }, new int[] { 0 })] + [InlineDataAttribute(new byte[] { 2, 0, 0, 0, 1, 0, 0, 0 }, new int[] { 2 }, new int[] { 1 })] + [InlineDataAttribute(new byte[] { 1, 0, 0, 0, 2, 0, 0, 0 }, new int[] { 1 }, new int[] { 2 })] + [InlineDataAttribute(new byte[] { 255, 255, 255, 255, 2, 0, 0, 0 }, new int[] { -1 }, new int[] { 2 })] + [InlineDataAttribute(new byte[] { 255, 255, 255, 255, 2, 0, 0, 0, 2, 0, 0, 0, 3, 0, 0, 0 }, new int[] { -1, 2 }, new int[] { 2, 3 })] + public void AddSignedRational_AddsArray(byte[] bytes, int[] numerators, int[] denominators) + { + var entries = new List(); + SignedRational[] value = Enumerable.Range(0, numerators.Length).Select(i => new SignedRational(numerators[i], denominators[i])).ToArray(); + + entries.AddSignedRational(TiffTags.ImageWidth, value); + + var entry = entries[0]; + Assert.Equal(TiffTags.ImageWidth, entry.Tag); + Assert.Equal(TiffType.SRational, entry.Type); + Assert.Equal((uint)numerators.Length, entry.Count); + Assert.Equal(bytes, entry.Value); + } + + [Theory] + [InlineDataAttribute(new byte[] { 0x00, 0x00, 0x00, 0x00 }, 0.0F)] + [InlineDataAttribute(new byte[] { 0x00, 0x00, 0x80, 0x3F }, 1.0F)] + [InlineDataAttribute(new byte[] { 0x00, 0x00, 0x00, 0xC0 }, -2.0F)] + [InlineDataAttribute(new byte[] { 0xFF, 0xFF, 0x7F, 0x7F }, float.MaxValue)] + [InlineDataAttribute(new byte[] { 0x00, 0x00, 0x80, 0x7F }, float.PositiveInfinity)] + [InlineDataAttribute(new byte[] { 0x00, 0x00, 0x80, 0xFF }, float.NegativeInfinity)] + public void AddFloat_AddsSingleValue(byte[] bytes, float value) + { + var entries = new List(); + + entries.AddFloat(TiffTags.ImageWidth, value); + + var entry = entries[0]; + Assert.Equal(TiffTags.ImageWidth, entry.Tag); + Assert.Equal(TiffType.Float, entry.Type); + Assert.Equal(1u, entry.Count); + Assert.Equal(bytes, entry.Value); + } + + [Theory] + [InlineDataAttribute(new byte[] { 0x00, 0x00, 0x00, 0x00 }, new float[] { 0.0F })] + [InlineDataAttribute(new byte[] { 0x00, 0x00, 0x80, 0x3F }, new float[] { 1.0F })] + [InlineDataAttribute(new byte[] { 0x00, 0x00, 0x00, 0xC0 }, new float[] { -2.0F })] + [InlineDataAttribute(new byte[] { 0xFF, 0xFF, 0x7F, 0x7F }, new float[] { float.MaxValue })] + [InlineDataAttribute(new byte[] { 0x00, 0x00, 0x80, 0x7F }, new float[] { float.PositiveInfinity })] + [InlineDataAttribute(new byte[] { 0x00, 0x00, 0x80, 0xFF }, new float[] { float.NegativeInfinity })] + [InlineDataAttribute(new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x3F, 0x00, 0x00, 0x00, 0xC0 }, new float[] { 0.0F, 1.0F, -2.0F })] + public void AddFloat_AddsArray(byte[] bytes, float[] value) + { + var entries = new List(); + + entries.AddFloat(TiffTags.ImageWidth, value); + + var entry = entries[0]; + Assert.Equal(TiffTags.ImageWidth, entry.Tag); + Assert.Equal(TiffType.Float, entry.Type); + Assert.Equal((uint)value.Length, entry.Count); + Assert.Equal(bytes, entry.Value); + } + + [Theory] + [InlineDataAttribute(new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, 0.0)] + [InlineDataAttribute(new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0, 0x3F }, 1.0)] + [InlineDataAttribute(new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40 }, 2.0)] + [InlineDataAttribute(new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC0 }, -2.0)] + [InlineDataAttribute(new byte[] { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xEF, 0x7F }, double.MaxValue)] + [InlineDataAttribute(new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0, 0x7F }, double.PositiveInfinity)] + [InlineDataAttribute(new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0, 0xFF }, double.NegativeInfinity)] + [InlineDataAttribute(new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF8, 0xFF }, double.NaN)] + public void AddDouble_AddsSingleValue(byte[] bytes, double value) + { + var entries = new List(); + + entries.AddDouble(TiffTags.ImageWidth, value); + + var entry = entries[0]; + Assert.Equal(TiffTags.ImageWidth, entry.Tag); + Assert.Equal(TiffType.Double, entry.Type); + Assert.Equal(1u, entry.Count); + Assert.Equal(bytes, entry.Value); + } + + [Theory] + [InlineDataAttribute(new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, new double[] { 0.0 })] + [InlineDataAttribute(new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0, 0x3F }, new double[] { 1.0 })] + [InlineDataAttribute(new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40 }, new double[] { 2.0 })] + [InlineDataAttribute(new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC0 }, new double[] { -2.0 })] + [InlineDataAttribute(new byte[] { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xEF, 0x7F }, new double[] { double.MaxValue })] + [InlineDataAttribute(new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0, 0x7F }, new double[] { double.PositiveInfinity })] + [InlineDataAttribute(new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0, 0xFF }, new double[] { double.NegativeInfinity })] + [InlineDataAttribute(new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF8, 0xFF }, new double[] { double.NaN })] + [InlineDataAttribute(new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0, 0x3F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC0 }, new double[] { 0.0, 1.0, -2.0 })] + public void AddDouble_AddsArray(byte[] bytes, double[] value) + { + var entries = new List(); + + entries.AddDouble(TiffTags.ImageWidth, value); + + var entry = entries[0]; + Assert.Equal(TiffTags.ImageWidth, entry.Tag); + Assert.Equal(TiffType.Double, entry.Type); + Assert.Equal((uint)value.Length, entry.Count); + Assert.Equal(bytes, entry.Value); + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Formats.Tiff.Tests/TestUtilities/Tiff/TiffIfdParser.cs b/tests/ImageSharp.Formats.Tiff.Tests/TestUtilities/Tiff/TiffIfdParser.cs new file mode 100644 index 0000000000..f8d7440374 --- /dev/null +++ b/tests/ImageSharp.Formats.Tiff.Tests/TestUtilities/Tiff/TiffIfdParser.cs @@ -0,0 +1,77 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Tests +{ + using System; + using System.Collections.Generic; + using System.Linq; + using System.Text; + using ImageSharp.Formats.Tiff; + using Xunit; + + /// + /// A utility data structure to decode Tiff IFD entries in unit tests. + /// + internal static class TiffIfdParser + { + public static int? GetInteger(this List entries, ushort tag) + { + TiffIfdEntry entry = entries.FirstOrDefault(e => e.Tag == tag); + + if (entry.Tag == 0) + return null; + + Assert.Equal(1u, entry.Count); + + switch (entry.Type) + { + case TiffType.Byte: + return entry.Value[0]; + case TiffType.SByte: + return (sbyte)entry.Value[0]; + case TiffType.Short: + return BitConverter.ToUInt16(entry.Value, 0); + case TiffType.SShort: + return BitConverter.ToInt16(entry.Value, 0); + case TiffType.Long: + return (int)BitConverter.ToUInt32(entry.Value, 0); + case TiffType.SLong: + return BitConverter.ToInt32(entry.Value, 0); + default: + Assert.True(1 == 1, "TIFF IFD entry is not convertable to an integer."); + return null; + } + } + + public static Rational? GetUnsignedRational(this List entries, ushort tag) + { + TiffIfdEntry entry = entries.FirstOrDefault(e => e.Tag == tag); + + if (entry.Tag == 0) + return null; + + Assert.Equal(TiffType.Rational, entry.Type); + Assert.Equal(1u, entry.Count); + + uint numerator = BitConverter.ToUInt32(entry.Value, 0); + uint denominator = BitConverter.ToUInt32(entry.Value, 4); + + return new Rational(numerator, denominator); + } + + public static string GetAscii(this List entries, ushort tag) + { + TiffIfdEntry entry = entries.FirstOrDefault(e => e.Tag == tag); + + if (entry.Tag == 0) + return null; + + Assert.Equal(TiffType.Ascii, entry.Type); + + return Encoding.UTF8.GetString(entry.Value, 0, (int)entry.Count); + } + } +} \ No newline at end of file