From fd0f49f050bec0c9fe151c752628f1f3f237c558 Mon Sep 17 00:00:00 2001 From: Andrew Wilkinson Date: Wed, 7 Jun 2017 11:45:00 +0100 Subject: [PATCH] Add TiffEncoder and write TIFF header --- .../Formats/Tiff/Constants/TiffConstants.cs | 15 ++++ src/ImageSharp/Formats/Tiff/TiffEncoder.cs | 3 +- .../Formats/Tiff/TiffEncoderCore.cs | 79 +++++++++++++++++++ .../Formats/Tiff/TiffEncoderHeaderTests.cs | 61 ++++++++++++++ 4 files changed, 157 insertions(+), 1 deletion(-) create mode 100644 src/ImageSharp/Formats/Tiff/TiffEncoderCore.cs create mode 100644 tests/ImageSharp.Formats.Tiff.Tests/Formats/Tiff/TiffEncoderHeaderTests.cs diff --git a/src/ImageSharp/Formats/Tiff/Constants/TiffConstants.cs b/src/ImageSharp/Formats/Tiff/Constants/TiffConstants.cs index 1858d49b8..10a3478c0 100644 --- a/src/ImageSharp/Formats/Tiff/Constants/TiffConstants.cs +++ b/src/ImageSharp/Formats/Tiff/Constants/TiffConstants.cs @@ -20,6 +20,16 @@ namespace ImageSharp.Formats.Tiff /// public const byte ByteOrderBigEndian = 0x4D; + /// + /// Byte order markers for indicating little endian encoding. + /// + public const ushort ByteOrderLittleEndianShort = 0x4949; + + /// + /// Byte order markers for indicating big endian encoding. + /// + public const ushort ByteOrderBigEndianShort = 0x4D4D; + /// /// Magic number used within the image file header to identify a TIFF format file. /// @@ -59,5 +69,10 @@ 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/TiffEncoder.cs b/src/ImageSharp/Formats/Tiff/TiffEncoder.cs index 7bcb575db..75ff7dcd4 100644 --- a/src/ImageSharp/Formats/Tiff/TiffEncoder.cs +++ b/src/ImageSharp/Formats/Tiff/TiffEncoder.cs @@ -33,7 +33,8 @@ namespace ImageSharp.Formats public void Encode(Image image, Stream stream, ITiffEncoderOptions options) where TPixel : struct, IPixel { - throw new NotImplementedException(); + var encode = new TiffEncoderCore(options); + encode.Encode(image, stream); } } } diff --git a/src/ImageSharp/Formats/Tiff/TiffEncoderCore.cs b/src/ImageSharp/Formats/Tiff/TiffEncoderCore.cs new file mode 100644 index 000000000..74e8338c2 --- /dev/null +++ b/src/ImageSharp/Formats/Tiff/TiffEncoderCore.cs @@ -0,0 +1,79 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Formats +{ + using System; + using System.Buffers; + using System.IO; + using System.Linq; + using System.Runtime.CompilerServices; + using System.Text; + using ImageSharp.Formats.Tiff; + using ImageSharp.Memory; + using ImageSharp.PixelFormats; + + using Quantizers; + + using static ComparableExtensions; + + /// + /// Performs the TIFF encoding operation. + /// + internal sealed class TiffEncoderCore + { + /// + /// The options for the encoder. + /// + private readonly ITiffEncoderOptions options; + + /// + /// Initializes a new instance of the class. + /// + /// The options for the encoder. + public TiffEncoderCore(ITiffEncoderOptions options) + { + this.options = options ?? new TiffEncoderOptions(); + } + + /// + /// Encodes the image to the specified stream from the . + /// + /// The pixel format. + /// The to encode from. + /// The to encode the image data to. + public void Encode(Image image, Stream stream) + where TPixel : struct, IPixel + { + Guard.NotNull(image, nameof(image)); + Guard.NotNull(stream, nameof(stream)); + + using (BinaryWriter writer = new BinaryWriter(stream, Encoding.UTF8, true)) + { + this.WriteHeader(writer, 0); + } + } + + /// + /// 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) + { + 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); + } + } +} \ 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 new file mode 100644 index 000000000..d5c21f594 --- /dev/null +++ b/tests/ImageSharp.Formats.Tiff.Tests/Formats/Tiff/TiffEncoderHeaderTests.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; + using System.IO; + using Xunit; + + using ImageSharp.Formats; + using System.Text; + + public class TiffEncoderHeaderTests + { + [Fact] + public void WriteHeader_WritesValidHeader() + { + MemoryStream stream = new MemoryStream(); + TiffEncoderCore encoder = new TiffEncoderCore(null); + + using (BinaryWriter writer = new BinaryWriter(stream, Encoding.UTF8, true)) + { + encoder.WriteHeader(writer, 1232); + } + + stream.Position = 0; + Assert.Equal(8, stream.Length); + Assert.Equal(new byte[] { 0x49, 0x49, 42, 0, 0xD0, 0x04, 0x00, 0x00 }, stream.ToArray()); + } + + [Fact] + public void WriteHeader_ThrowsExceptionIfFirstIfdOffsetIsZero() + { + 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, 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); + } + } + } +} \ No newline at end of file