From dfaa4cf835125bceedca34eecc30609bb9620088 Mon Sep 17 00:00:00 2001 From: Andrew Wilkinson Date: Mon, 19 Jun 2017 21:46:02 +0100 Subject: [PATCH] Add TIFF IFD encoding --- .../Formats/Tiff/Constants/TiffConstants.cs | 5 - .../Formats/Tiff/TiffEncoderCore.cs | 88 +++++- .../Formats/Tiff/Utils/TiffWriter.cs | 109 +++++++ .../Formats/Tiff/TiffEncoderHeaderTests.cs | 33 +- .../Formats/Tiff/TiffEncoderIfdTests.cs | 299 ++++++++++++++++++ .../Formats/Tiff/Utils/TiffWriterTests.cs | 135 ++++++++ 6 files changed, 629 insertions(+), 40 deletions(-) create mode 100644 src/ImageSharp/Formats/Tiff/Utils/TiffWriter.cs create mode 100644 tests/ImageSharp.Formats.Tiff.Tests/Formats/Tiff/TiffEncoderIfdTests.cs create mode 100644 tests/ImageSharp.Formats.Tiff.Tests/Formats/Tiff/Utils/TiffWriterTests.cs diff --git a/src/ImageSharp/Formats/Tiff/Constants/TiffConstants.cs b/src/ImageSharp/Formats/Tiff/Constants/TiffConstants.cs index 10a3478c0d..5c03d33b0c 100644 --- a/src/ImageSharp/Formats/Tiff/Constants/TiffConstants.cs +++ b/src/ImageSharp/Formats/Tiff/Constants/TiffConstants.cs @@ -69,10 +69,5 @@ namespace ImageSharp.Formats.Tiff /// Size (in bytes) of the Double data type /// public const int SizeOfDouble = 8; - - /// - /// Size (in bytes) of the word boundary to allign data to when required - /// - public const int SizeOfWordBoundary = 4; } } diff --git a/src/ImageSharp/Formats/Tiff/TiffEncoderCore.cs b/src/ImageSharp/Formats/Tiff/TiffEncoderCore.cs index 74e8338c20..d32e34c433 100644 --- a/src/ImageSharp/Formats/Tiff/TiffEncoderCore.cs +++ b/src/ImageSharp/Formats/Tiff/TiffEncoderCore.cs @@ -7,6 +7,7 @@ namespace ImageSharp.Formats { using System; using System.Buffers; + using System.Collections.Generic; using System.IO; using System.Linq; using System.Runtime.CompilerServices; @@ -50,30 +51,95 @@ namespace ImageSharp.Formats Guard.NotNull(image, nameof(image)); Guard.NotNull(stream, nameof(stream)); - using (BinaryWriter writer = new BinaryWriter(stream, Encoding.UTF8, true)) + using (TiffWriter writer = new TiffWriter(stream)) { - this.WriteHeader(writer, 0); + long firstIfdMarker = this.WriteHeader(writer); + long nextIfdMarker = this.WriteImage(writer, image, firstIfdMarker); } } /// /// Writes the TIFF file header. /// - /// The to write data to. - /// The byte offset to the first IFD in the file. - public void WriteHeader(BinaryWriter writer, uint firstIfdOffset) + /// The to write data to. + /// The marker to write the first IFD offset. + public long WriteHeader(TiffWriter writer) { - if (firstIfdOffset == 0 || firstIfdOffset % TiffConstants.SizeOfWordBoundary != 0) - { - throw new ArgumentException("IFD offsets must be non-zero and on a word boundary.", nameof(firstIfdOffset)); - } - ushort byteOrderMarker = BitConverter.IsLittleEndian ? TiffConstants.ByteOrderLittleEndianShort : TiffConstants.ByteOrderBigEndianShort; writer.Write(byteOrderMarker); writer.Write((ushort)42); - writer.Write(firstIfdOffset); + long firstIfdMarker = writer.PlaceMarker(); + + return firstIfdMarker; + } + + /// + /// Writes a TIFF IFD block. + /// + /// The to write data to. + /// The IFD entries to write to the file. + /// The marker to write the next IFD offset (if present). + public long WriteIfd(TiffWriter writer, List entries) + { + if (entries.Count == 0) + { + throw new ArgumentException("There must be at least one entry per IFD.", nameof(entries)); + } + + uint dataOffset = (uint)writer.Position + (uint)(6 + (entries.Count * 12)); + List largeDataBlocks = new List(); + + entries.Sort((a, b) => a.Tag - b.Tag); + + writer.Write((ushort)entries.Count); + + foreach (TiffIfdEntry entry in entries) + { + writer.Write(entry.Tag); + writer.Write((ushort)entry.Type); + writer.Write(entry.Count); + + if (entry.Value.Length <= 4) + { + writer.WritePadded(entry.Value); + } + else + { + largeDataBlocks.Add(entry.Value); + writer.Write(dataOffset); + dataOffset += (uint)(entry.Value.Length + (entry.Value.Length % 2)); + } + } + + long nextIfdMarker = writer.PlaceMarker(); + + foreach (byte[] dataBlock in largeDataBlocks) + { + writer.Write(dataBlock); + + if (dataBlock.Length % 2 == 1) + { + writer.Write((byte)0); + } + } + + return nextIfdMarker; + } + + /// + /// Writes all data required to define an image + /// + /// The pixel format. + /// The to write data to. + /// The to encode from. + /// The marker to write this IFD offset. + /// The marker to write the next IFD offset (if present). + public long WriteImage(TiffWriter writer, Image image, long ifdOffset) + where TPixel : struct, IPixel + { + throw new NotImplementedException(); } } } \ No newline at end of file diff --git a/src/ImageSharp/Formats/Tiff/Utils/TiffWriter.cs b/src/ImageSharp/Formats/Tiff/Utils/TiffWriter.cs new file mode 100644 index 0000000000..201e7b4da3 --- /dev/null +++ b/src/ImageSharp/Formats/Tiff/Utils/TiffWriter.cs @@ -0,0 +1,109 @@ +// +// 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.IO; + + /// + /// Utility class for writing TIFF data to a . + /// + internal class TiffWriter : IDisposable + { + private readonly Stream output; + + private readonly byte[] paddingBytes = new byte[4]; + + private readonly List references = new List(); + + /// Initializes a new instance of the class. + /// The output stream. + public TiffWriter(Stream output) + { + this.output = output; + } + + /// + /// Gets a flag indicating whether the architecture is little-endian. + /// + public bool IsLittleEndian => BitConverter.IsLittleEndian; + + /// + /// Returns the current position within the stream. + /// + public long Position => this.output.Position; + + /// Writes an empty four bytes to the stream, returning the offset to be written later. + /// The offset to be written later + public long PlaceMarker() + { + long offset = this.output.Position; + this.Write(0u); + return offset; + } + + /// Writes an array of bytes to the current stream. + /// The bytes to write. + public void Write(byte[] value) + { + this.output.Write(value, 0, value.Length); + } + + /// Writes a byte to the current stream. + /// The byte to write. + public void Write(byte value) + { + this.output.Write(new byte[] { value }, 0, 1); + } + + /// Writes a two-byte unsigned integer to the current stream. + /// The two-byte unsigned integer to write. + public void Write(ushort value) + { + byte[] bytes = BitConverter.GetBytes(value); + this.output.Write(bytes, 0, 2); + } + + /// Writes a four-byte unsigned integer to the current stream. + /// The four-byte unsigned integer to write. + public void Write(uint value) + { + byte[] bytes = BitConverter.GetBytes(value); + this.output.Write(bytes, 0, 4); + } + + /// Writes an array of bytes to the current stream, padded to four-bytes. + /// The bytes to write. + public void WritePadded(byte[] value) + { + this.output.Write(value, 0, value.Length); + + if (value.Length < 4) + { + this.output.Write(this.paddingBytes, 0, 4 - value.Length); + } + } + + /// Writes a four-byte unsigned integer to the specified marker in the stream. + /// The offset returned when placing the marker + /// The four-byte unsigned integer to write. + public void WriteMarker(long offset, uint value) + { + long currentOffset = this.output.Position; + this.output.Seek(offset, SeekOrigin.Begin); + this.Write(value); + this.output.Seek(currentOffset, SeekOrigin.Begin); + } + + /// + /// Disposes instance, ensuring any unwritten data is flushed. + /// + public void Dispose() + { + this.output.Flush(); + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Formats.Tiff.Tests/Formats/Tiff/TiffEncoderHeaderTests.cs b/tests/ImageSharp.Formats.Tiff.Tests/Formats/Tiff/TiffEncoderHeaderTests.cs index d5c21f5948..76d15f6a10 100644 --- a/tests/ImageSharp.Formats.Tiff.Tests/Formats/Tiff/TiffEncoderHeaderTests.cs +++ b/tests/ImageSharp.Formats.Tiff.Tests/Formats/Tiff/TiffEncoderHeaderTests.cs @@ -7,9 +7,11 @@ namespace ImageSharp.Tests { using System; using System.IO; + using System.Linq; using Xunit; using ImageSharp.Formats; + using ImageSharp.Formats.Tiff; using System.Text; public class TiffEncoderHeaderTests @@ -20,41 +22,24 @@ namespace ImageSharp.Tests MemoryStream stream = new MemoryStream(); TiffEncoderCore encoder = new TiffEncoderCore(null); - using (BinaryWriter writer = new BinaryWriter(stream, Encoding.UTF8, true)) + using (TiffWriter writer = new TiffWriter(stream)) { - encoder.WriteHeader(writer, 1232); + long firstIfdMarker = encoder.WriteHeader(writer); } - stream.Position = 0; - Assert.Equal(8, stream.Length); - Assert.Equal(new byte[] { 0x49, 0x49, 42, 0, 0xD0, 0x04, 0x00, 0x00 }, stream.ToArray()); + Assert.Equal(new byte[] { 0x49, 0x49, 42, 0, 0x00, 0x00, 0x00, 0x00 }, stream.ToArray()); } [Fact] - public void WriteHeader_ThrowsExceptionIfFirstIfdOffsetIsZero() + public void WriteHeader_ReturnsFirstIfdMarker() { MemoryStream stream = new MemoryStream(); TiffEncoderCore encoder = new TiffEncoderCore(null); - using (BinaryWriter writer = new BinaryWriter(stream, Encoding.UTF8, true)) + using (TiffWriter writer = new TiffWriter(stream)) { - ArgumentException e = Assert.Throws(() => { encoder.WriteHeader(writer, 0); }); - Assert.Equal("IFD offsets must be non-zero and on a word boundary.\r\nParameter name: firstIfdOffset", e.Message); - Assert.Equal("firstIfdOffset", e.ParamName); - } - } - - [Fact] - public void WriteHeader_ThrowsExceptionIfIfdOffsetIsNotOnAWordBoundary() - { - MemoryStream stream = new MemoryStream(); - TiffEncoderCore encoder = new TiffEncoderCore(null); - - using (BinaryWriter writer = new BinaryWriter(stream, Encoding.UTF8, true)) - { - ArgumentException e = Assert.Throws(() => { encoder.WriteHeader(writer, 1234); }); - Assert.Equal("IFD offsets must be non-zero and on a word boundary.\r\nParameter name: firstIfdOffset", e.Message); - Assert.Equal("firstIfdOffset", e.ParamName); + long firstIfdMarker = encoder.WriteHeader(writer); + Assert.Equal(4, firstIfdMarker); } } } diff --git a/tests/ImageSharp.Formats.Tiff.Tests/Formats/Tiff/TiffEncoderIfdTests.cs b/tests/ImageSharp.Formats.Tiff.Tests/Formats/Tiff/TiffEncoderIfdTests.cs new file mode 100644 index 0000000000..c4c4fb84bf --- /dev/null +++ b/tests/ImageSharp.Formats.Tiff.Tests/Formats/Tiff/TiffEncoderIfdTests.cs @@ -0,0 +1,299 @@ +// +// 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.Text; + using System.Collections.Generic; + + public class TiffEncoderIfdTests + { + [Fact] + public void WriteIfd_DataIsCorrectLength() + { + MemoryStream stream = new MemoryStream(); + TiffEncoderCore encoder = new TiffEncoderCore(null); + + List entries = new List() + { + new TiffIfdEntry(TiffTags.ImageWidth, TiffType.Long, 1, new byte[] { 1, 2, 3, 4 }), + new TiffIfdEntry(TiffTags.ImageLength, TiffType.Long, 1, new byte[] { 5, 6, 7, 8 }), + new TiffIfdEntry(TiffTags.Compression, TiffType.Long, 1, new byte[] { 9, 10, 11, 12 }) + }; + + using (TiffWriter writer = new TiffWriter(stream)) + { + long nextIfdMarker = encoder.WriteIfd(writer, entries); + } + + Assert.Equal(2 + 12 * 3 + 4, stream.Length); + } + + [Fact] + public void WriteIfd_WritesNumberOfEntries() + { + MemoryStream stream = new MemoryStream(); + TiffEncoderCore encoder = new TiffEncoderCore(null); + + List entries = new List() + { + new TiffIfdEntry(TiffTags.ImageWidth, TiffType.Long, 1, new byte[] { 1, 2, 3, 4 }), + new TiffIfdEntry(TiffTags.ImageLength, TiffType.Long, 1, new byte[] { 5, 6, 7, 8 }), + new TiffIfdEntry(TiffTags.Compression, TiffType.Long, 1, new byte[] { 9, 10, 11, 12 }) + }; + + using (TiffWriter writer = new TiffWriter(stream)) + { + long nextIfdMarker = encoder.WriteIfd(writer, entries); + } + + var ifdEntryBytes = stream.ToArray().Take(2).ToArray(); + Assert.Equal(new byte[] { 3, 0 }, ifdEntryBytes); + } + + [Fact] + public void WriteIfd_ReturnsNextIfdMarker() + { + MemoryStream stream = new MemoryStream(); + TiffEncoderCore encoder = new TiffEncoderCore(null); + + List entries = new List() + { + new TiffIfdEntry(TiffTags.ImageWidth, TiffType.Long, 1, new byte[] { 1, 2, 3, 4 }), + new TiffIfdEntry(TiffTags.ImageLength, TiffType.Long, 1, new byte[] { 5, 6, 7, 8 }), + new TiffIfdEntry(TiffTags.Compression, TiffType.Long, 1, new byte[] { 9, 10, 11, 12 }) + }; + + using (TiffWriter writer = new TiffWriter(stream)) + { + long nextIfdMarker = encoder.WriteIfd(writer, entries); + Assert.Equal(2 + 12 * 3, nextIfdMarker); + } + } + + [Fact] + public void WriteIfd_WritesTagIdForEachEntry() + { + MemoryStream stream = new MemoryStream(); + TiffEncoderCore encoder = new TiffEncoderCore(null); + + List entries = new List() + { + new TiffIfdEntry(10, TiffType.Long, 1, new byte[] { 1, 2, 3, 4 }), + new TiffIfdEntry(20, TiffType.Long, 1, new byte[] { 5, 6, 7, 8 }), + new TiffIfdEntry(30, TiffType.Long, 1, new byte[] { 9, 10, 11, 12 }) + }; + + using (TiffWriter writer = new TiffWriter(stream)) + { + long nextIfdMarker = encoder.WriteIfd(writer, entries); + } + + var ifdEntry1Bytes = stream.ToArray().Skip(2 + 12 * 0).Take(2).ToArray(); + var ifdEntry2Bytes = stream.ToArray().Skip(2 + 12 * 1).Take(2).ToArray(); + var ifdEntry3Bytes = stream.ToArray().Skip(2 + 12 * 2).Take(2).ToArray(); + + Assert.Equal(new byte[] { 10, 0 }, ifdEntry1Bytes); + Assert.Equal(new byte[] { 20, 0 }, ifdEntry2Bytes); + Assert.Equal(new byte[] { 30, 0 }, ifdEntry3Bytes); + } + + [Fact] + public void WriteIfd_WritesTypeForEachEntry() + { + MemoryStream stream = new MemoryStream(); + TiffEncoderCore encoder = new TiffEncoderCore(null); + + List entries = new List() + { + new TiffIfdEntry(TiffTags.ImageWidth, TiffType.Long, 1, new byte[] { 1, 2, 3, 4 }), + new TiffIfdEntry(TiffTags.ImageLength, TiffType.Short, 2, new byte[] { 5, 6, 7, 8 }), + new TiffIfdEntry(TiffTags.Compression, TiffType.Ascii, 4, new byte[] { (byte)'A', (byte)'B', (byte)'C', 0 }) + }; + + using (TiffWriter writer = new TiffWriter(stream)) + { + long nextIfdMarker = encoder.WriteIfd(writer, entries); + } + + var ifdEntry1Bytes = stream.ToArray().Skip(4 + 12 * 0).Take(2).ToArray(); + var ifdEntry2Bytes = stream.ToArray().Skip(4 + 12 * 1).Take(2).ToArray(); + var ifdEntry3Bytes = stream.ToArray().Skip(4 + 12 * 2).Take(2).ToArray(); + + Assert.Equal(new byte[] { 4, 0 }, ifdEntry1Bytes); + Assert.Equal(new byte[] { 3, 0 }, ifdEntry2Bytes); + Assert.Equal(new byte[] { 2, 0 }, ifdEntry3Bytes); + } + + [Fact] + public void WriteIfd_WritesCountForEachEntry() + { + MemoryStream stream = new MemoryStream(); + TiffEncoderCore encoder = new TiffEncoderCore(null); + + List entries = new List() + { + new TiffIfdEntry(TiffTags.ImageWidth, TiffType.Long, 1, new byte[] { 1, 2, 3, 4 }), + new TiffIfdEntry(TiffTags.ImageLength, TiffType.Short, 2, new byte[] { 5, 6, 7, 8 }), + new TiffIfdEntry(TiffTags.Compression, TiffType.Ascii, 4, new byte[] { (byte)'A', (byte)'B', (byte)'C', 0 }) + }; + + using (TiffWriter writer = new TiffWriter(stream)) + { + long nextIfdMarker = encoder.WriteIfd(writer, entries); + } + + var ifdEntry1Bytes = stream.ToArray().Skip(6 + 12 * 0).Take(4).ToArray(); + var ifdEntry2Bytes = stream.ToArray().Skip(6 + 12 * 1).Take(4).ToArray(); + var ifdEntry3Bytes = stream.ToArray().Skip(6 + 12 * 2).Take(4).ToArray(); + + Assert.Equal(new byte[] { 1, 0, 0, 0 }, ifdEntry1Bytes); + Assert.Equal(new byte[] { 2, 0, 0, 0 }, ifdEntry2Bytes); + Assert.Equal(new byte[] { 4, 0, 0, 0 }, ifdEntry3Bytes); + } + + [Fact] + public void WriteIfd_WritesDataInline() + { + MemoryStream stream = new MemoryStream(); + TiffEncoderCore encoder = new TiffEncoderCore(null); + + List entries = new List() + { + new TiffIfdEntry(TiffTags.ImageWidth, TiffType.Long, 1, new byte[] { 1, 2, 3, 4 }), + new TiffIfdEntry(TiffTags.ImageLength, TiffType.Short, 2, new byte[] { 5, 6, 7, 8 }), + new TiffIfdEntry(TiffTags.Compression, TiffType.Ascii, 3, new byte[] { (byte)'A', (byte)'B', 0 }) + }; + + using (TiffWriter writer = new TiffWriter(stream)) + { + long nextIfdMarker = encoder.WriteIfd(writer, entries); + } + + var ifdEntry1Bytes = stream.ToArray().Skip(10 + 12 * 0).Take(4).ToArray(); + var ifdEntry2Bytes = stream.ToArray().Skip(10 + 12 * 1).Take(4).ToArray(); + var ifdEntry3Bytes = stream.ToArray().Skip(10 + 12 * 2).Take(4).ToArray(); + + Assert.Equal(new byte[] { 1, 2, 3, 4 }, ifdEntry1Bytes); + Assert.Equal(new byte[] { 5, 6, 7, 8 }, ifdEntry2Bytes); + Assert.Equal(new byte[] { (byte)'A', (byte)'B', 0, 0 }, ifdEntry3Bytes); + } + + [Fact] + public void WriteIfd_WritesDataByReference() + { + MemoryStream stream = new MemoryStream(); + TiffEncoderCore encoder = new TiffEncoderCore(null); + + List entries = new List() + { + new TiffIfdEntry(TiffTags.ImageWidth, TiffType.Byte, 8, new byte[] { 1, 2, 3, 4, 4, 3, 2, 1 }), + new TiffIfdEntry(TiffTags.ImageLength, TiffType.Short, 4, new byte[] { 5, 6, 7, 8, 9, 10, 11, 12 }), + new TiffIfdEntry(TiffTags.Compression, TiffType.Ascii, 3, new byte[] { (byte)'A', (byte)'B', 0 }) + }; + + using (TiffWriter writer = new TiffWriter(stream)) + { + writer.Write(new byte[] { 1, 2, 3, 4 }); + long nextIfdMarker = encoder.WriteIfd(writer, entries); + } + + var ifdEntry1Bytes = stream.ToArray().Skip(14 + 12 * 0).Take(4).ToArray(); + var ifdEntry1Data = stream.ToArray().Skip(46).Take(8).ToArray(); + var ifdEntry2Bytes = stream.ToArray().Skip(14 + 12 * 1).Take(4).ToArray(); + var ifdEntry2Data = stream.ToArray().Skip(54).Take(8).ToArray(); + var ifdEntry3Bytes = stream.ToArray().Skip(14 + 12 * 2).Take(4).ToArray(); + + Assert.Equal(new byte[] { 46, 0, 0, 0 }, ifdEntry1Bytes); + Assert.Equal(new byte[] { 1, 2, 3, 4, 4, 3, 2, 1 }, ifdEntry1Data); + Assert.Equal(new byte[] { 54, 0, 0, 0 }, ifdEntry2Bytes); + Assert.Equal(new byte[] { 5, 6, 7, 8, 9, 10, 11, 12 }, ifdEntry2Data); + Assert.Equal(new byte[] { (byte)'A', (byte)'B', 0, 0 }, ifdEntry3Bytes); + } + + [Fact] + public void WriteIfd_WritesDataByReferenceOnWordBoundary() + { + MemoryStream stream = new MemoryStream(); + TiffEncoderCore encoder = new TiffEncoderCore(null); + + List entries = new List() + { + new TiffIfdEntry(TiffTags.ImageWidth, TiffType.Byte, 8, new byte[] { 1, 2, 3, 4, 5 }), + new TiffIfdEntry(TiffTags.ImageLength, TiffType.Short, 4, new byte[] { 5, 6, 7, 8, 9, 10, 11, 12 }), + new TiffIfdEntry(TiffTags.Compression, TiffType.Ascii, 3, new byte[] { (byte)'A', (byte)'B', 0 }) + }; + + using (TiffWriter writer = new TiffWriter(stream)) + { + writer.Write(new byte[] { 1, 2, 3, 4 }); + long nextIfdMarker = encoder.WriteIfd(writer, entries); + } + + var ifdEntry1Bytes = stream.ToArray().Skip(14 + 12 * 0).Take(4).ToArray(); + var ifdEntry1Data = stream.ToArray().Skip(46).Take(5).ToArray(); + var ifdEntry2Bytes = stream.ToArray().Skip(14 + 12 * 1).Take(4).ToArray(); + var ifdEntry2Data = stream.ToArray().Skip(52).Take(8).ToArray(); + var ifdEntry3Bytes = stream.ToArray().Skip(14 + 12 * 2).Take(4).ToArray(); + + Assert.Equal(new byte[] { 46, 0, 0, 0 }, ifdEntry1Bytes); + Assert.Equal(new byte[] { 1, 2, 3, 4, 5 }, ifdEntry1Data); + Assert.Equal(new byte[] { 52, 0, 0, 0 }, ifdEntry2Bytes); + Assert.Equal(new byte[] { 5, 6, 7, 8, 9, 10, 11, 12 }, ifdEntry2Data); + Assert.Equal(new byte[] { (byte)'A', (byte)'B', 0, 0 }, ifdEntry3Bytes); + } + + [Fact] + public void WriteIfd_WritesEntriesInCorrectOrder() + { + MemoryStream stream = new MemoryStream(); + TiffEncoderCore encoder = new TiffEncoderCore(null); + + List entries = new List() + { + new TiffIfdEntry(10, TiffType.Long, 1, new byte[] { 1, 2, 3, 4 }), + new TiffIfdEntry(30, TiffType.Long, 1, new byte[] { 5, 6, 7, 8 }), + new TiffIfdEntry(20, TiffType.Long, 1, new byte[] { 9, 10, 11, 12 }) + }; + + using (TiffWriter writer = new TiffWriter(stream)) + { + long nextIfdMarker = encoder.WriteIfd(writer, entries); + } + + var ifdEntry1Bytes = stream.ToArray().Skip(2 + 12 * 0).Take(2).ToArray(); + var ifdEntry2Bytes = stream.ToArray().Skip(2 + 12 * 1).Take(2).ToArray(); + var ifdEntry3Bytes = stream.ToArray().Skip(2 + 12 * 2).Take(2).ToArray(); + + Assert.Equal(new byte[] { 10, 0 }, ifdEntry1Bytes); + Assert.Equal(new byte[] { 20, 0 }, ifdEntry2Bytes); + Assert.Equal(new byte[] { 30, 0 }, ifdEntry3Bytes); + } + + [Fact] + public void WriteIfd_ThrowsException_IfNoEntriesArePresent() + { + MemoryStream stream = new MemoryStream(); + TiffEncoderCore encoder = new TiffEncoderCore(null); + + List entries = new List(); + + using (TiffWriter writer = new TiffWriter(stream)) + { + ArgumentException e = Assert.Throws(() => { encoder.WriteIfd(writer, entries); }); + + Assert.Equal("There must be at least one entry per IFD.\r\nParameter name: entries", e.Message); + Assert.Equal("entries", e.ParamName); + } + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Formats.Tiff.Tests/Formats/Tiff/Utils/TiffWriterTests.cs b/tests/ImageSharp.Formats.Tiff.Tests/Formats/Tiff/Utils/TiffWriterTests.cs new file mode 100644 index 0000000000..31582fb6d8 --- /dev/null +++ b/tests/ImageSharp.Formats.Tiff.Tests/Formats/Tiff/Utils/TiffWriterTests.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; + using System.IO; + using Xunit; + + using ImageSharp.Formats.Tiff; + + public class TiffWriterTests + { + [Fact] + public void IsLittleEndian_IsTrueOnWindows() + { + MemoryStream stream = new MemoryStream(); + + using (TiffWriter writer = new TiffWriter(stream)) + { + Assert.True(writer.IsLittleEndian); + } + } + + [Theory] + [InlineData(new byte[] {}, 0)] + [InlineData(new byte[] { 42 }, 1)] + [InlineData(new byte[] { 1, 2, 3, 4, 5 }, 5)] + public void Position_EqualsTheStreamPosition(byte[] data, long expectedResult) + { + MemoryStream stream = new MemoryStream(); + + using (TiffWriter writer = new TiffWriter(stream)) + { + writer.Write(data); + Assert.Equal(writer.Position, expectedResult); + } + } + + [Fact] + public void Write_WritesByte() + { + MemoryStream stream = new MemoryStream(); + + using (TiffWriter writer = new TiffWriter(stream)) + { + writer.Write((byte)42); + } + + Assert.Equal(new byte[] { 42 }, stream.ToArray()); + } + + [Fact] + public void Write_WritesByteArray() + { + MemoryStream stream = new MemoryStream(); + + using (TiffWriter writer = new TiffWriter(stream)) + { + writer.Write(new byte[] { 2, 4, 6, 8 }); + } + + Assert.Equal(new byte[] { 2, 4, 6, 8 }, stream.ToArray()); + } + + [Fact] + public void Write_WritesUInt16() + { + MemoryStream stream = new MemoryStream(); + + using (TiffWriter writer = new TiffWriter(stream)) + { + writer.Write((ushort)1234); + } + + Assert.Equal(new byte[] { 0xD2, 0x04 }, stream.ToArray()); + } + + [Fact] + public void Write_WritesUInt32() + { + MemoryStream stream = new MemoryStream(); + + using (TiffWriter writer = new TiffWriter(stream)) + { + writer.Write((uint)12345678); + } + + Assert.Equal(new byte[] { 0x4E, 0x61, 0xBC, 0x00 }, stream.ToArray()); + } + + [Theory] + [InlineData(new byte[] { }, new byte[] { 0, 0, 0, 0 })] + [InlineData(new byte[] { 2 }, new byte[] { 2, 0, 0, 0 })] + [InlineData(new byte[] { 2, 4 }, new byte[] { 2, 4, 0, 0 })] + [InlineData(new byte[] { 2, 4, 6 }, new byte[] { 2, 4, 6, 0 })] + [InlineData(new byte[] { 2, 4, 6, 8 }, new byte[] { 2, 4, 6, 8 })] + [InlineData(new byte[] { 2, 4, 6, 8, 10, 12 }, new byte[] { 2, 4, 6, 8, 10, 12 })] + public void WritePadded_WritesByteArray(byte[] bytes, byte[] expectedResult) + { + MemoryStream stream = new MemoryStream(); + + using (TiffWriter writer = new TiffWriter(stream)) + { + writer.WritePadded(bytes); + } + + Assert.Equal(expectedResult, stream.ToArray()); + } + + [Fact] + public void WriteMarker_WritesToPlacedPosition() + { + MemoryStream stream = new MemoryStream(); + + using (TiffWriter writer = new TiffWriter(stream)) + { + writer.Write((uint)0x11111111); + long marker = writer.PlaceMarker(); + writer.Write((uint)0x33333333); + + writer.WriteMarker(marker, 0x12345678); + + writer.Write((uint)0x44444444); + } + + Assert.Equal(new byte[] { 0x11, 0x11, 0x11, 0x11, + 0x78, 0x56, 0x34, 0x12, + 0x33, 0x33, 0x33, 0x33, + 0x44, 0x44, 0x44, 0x44 }, stream.ToArray()); + } + } +} \ No newline at end of file