Browse Source

Implement image decoding for the most basic TIFF image format.

pull/1570/head
Andrew Wilkinson 9 years ago
parent
commit
738f62bf9c
  1. 28
      src/ImageSharp/Formats/Tiff/Compression/NoneTiffCompression.cs
  2. 18
      src/ImageSharp/Formats/Tiff/Compression/TiffCompressionType.cs
  3. 18
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/TiffColorType.cs
  4. 45
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero8TiffColor.cs
  5. 226
      src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs
  6. 38
      src/ImageSharp/Formats/Tiff/Utils/TiffUtils.cs
  7. 28
      tests/ImageSharp.Formats.Tiff.Tests/Formats/Tiff/Compression/NoneTiffCompressionTests.cs
  8. 59
      tests/ImageSharp.Formats.Tiff.Tests/Formats/Tiff/PhotometricInterpretation/PhotometricInterpretationTestBase.cs
  9. 51
      tests/ImageSharp.Formats.Tiff.Tests/Formats/Tiff/PhotometricInterpretation/WhiteIsZero8TiffColorTests.cs
  10. 190
      tests/ImageSharp.Formats.Tiff.Tests/Formats/Tiff/TiffDecoderImageTests.cs
  11. 2
      tests/ImageSharp.Formats.Tiff.Tests/TestUtilities/Tiff/TiffGenIfdExtensions.cs

28
src/ImageSharp/Formats/Tiff/Compression/NoneTiffCompression.cs

@ -0,0 +1,28 @@
// <copyright file="NoneTiffCompression.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp.Formats
{
using System.IO;
using System.Runtime.CompilerServices;
/// <summary>
/// Class to handle cases where TIFF image data is not compressed.
/// </summary>
internal static class NoneTiffCompression
{
/// <summary>
/// Decompresses image data into the supplied buffer.
/// </summary>
/// <param name="stream">The <see cref="Stream"/> to read image data from.</param>
/// <param name="byteCount">The number of bytes to read from the input stream.</param>
/// <param name="buffer">The output buffer for uncompressed data.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Decompress(Stream stream, int byteCount, byte[] buffer)
{
stream.ReadFull(buffer, byteCount);
}
}
}

18
src/ImageSharp/Formats/Tiff/Compression/TiffCompressionType.cs

@ -0,0 +1,18 @@
// <copyright file="TiffCompressionType.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp.Formats
{
/// <summary>
/// Provides enumeration of the various TIFF compression types.
/// </summary>
internal enum TiffCompressionType
{
/// <summary>
/// Image data is stored uncompressed in the TIFF file.
/// </summary>
None = 0
}
}

18
src/ImageSharp/Formats/Tiff/PhotometricInterpretation/TiffColorType.cs

@ -0,0 +1,18 @@
// <copyright file="TiffColorType.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp.Formats
{
/// <summary>
/// Provides enumeration of the various TIFF photometric interpretation implementation types.
/// </summary>
internal enum TiffColorType
{
/// <summary>
/// Grayscale: 0 is imaged as white. The maximum value is imaged as black. Optimised implementation for 8-bit images.
/// </summary>
WhiteIsZero8
}
}

45
src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero8TiffColor.cs

@ -0,0 +1,45 @@
// <copyright file="WhiteIsZero8TiffColor.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp.Formats
{
using System.Runtime.CompilerServices;
using ImageSharp;
/// <summary>
/// Implements the 'WhiteIsZero' photometric interpretation (optimised for 8-bit grayscale images).
/// </summary>
internal static class WhiteIsZero8TiffColor
{
/// <summary>
/// Decodes pixel data using the current photometric interpretation.
/// </summary>
/// <typeparam name="TColor">The pixel format.</typeparam>
/// <param name="data">The buffer to read image data from.</param>
/// <param name="pixels">The image buffer to write pixels to.</param>
/// <param name="left">The x-coordinate of the left-hand side of the image block.</param>
/// <param name="top">The y-coordinate of the top of the image block.</param>
/// <param name="width">The width of the image block.</param>
/// <param name="height">The height of the image block.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Decode<TColor>(byte[] data, PixelAccessor<TColor> pixels, int left, int top, int width, int height)
where TColor : struct, IPixel<TColor>
{
TColor color = default(TColor);
uint offset = 0;
for (int y = top; y < top + height; y++)
{
for (int x = left; x < left + width; x++)
{
byte intensity = (byte)(255 - data[offset++]);
color.PackFromBytes(intensity, intensity, intensity, 255);
pixels[x, y] = color;
}
}
}
}
}

226
src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs

@ -6,6 +6,7 @@
namespace ImageSharp.Formats
{
using System;
using System.Buffers;
using System.IO;
using System.Text;
@ -41,6 +42,16 @@ namespace ImageSharp.Formats
this.IsLittleEndian = isLittleEndian;
}
/// <summary>
/// Gets or sets the photometric interpretation implementation to use when decoding the image.
/// </summary>
public TiffColorType ColorType { get; set; }
/// <summary>
/// Gets or sets the compression implementation to use when decoding the image.
/// </summary>
public TiffCompressionType CompressionType { get; set; }
/// <summary>
/// Gets the input stream.
/// </summary>
@ -93,7 +104,7 @@ namespace ImageSharp.Formats
public uint ReadHeader()
{
byte[] headerBytes = new byte[TiffConstants.SizeOfTiffHeader];
this.ReadBytes(headerBytes, TiffConstants.SizeOfTiffHeader);
this.InputStream.ReadFull(headerBytes, TiffConstants.SizeOfTiffHeader);
if (headerBytes[0] == TiffConstants.ByteOrderLittleEndian && headerBytes[1] == TiffConstants.ByteOrderLittleEndian)
{
@ -129,13 +140,13 @@ namespace ImageSharp.Formats
byte[] buffer = new byte[TiffConstants.SizeOfIfdEntry];
this.ReadBytes(buffer, 2);
this.InputStream.ReadFull(buffer, 2);
ushort entryCount = this.ToUInt16(buffer, 0);
TiffIfdEntry[] entries = new TiffIfdEntry[entryCount];
for (int i = 0; i < entryCount; i++)
{
this.ReadBytes(buffer, TiffConstants.SizeOfIfdEntry);
this.InputStream.ReadFull(buffer, TiffConstants.SizeOfIfdEntry);
ushort tag = this.ToUInt16(buffer, 0);
TiffType type = (TiffType)this.ToUInt16(buffer, 2);
@ -145,7 +156,7 @@ namespace ImageSharp.Formats
entries[i] = new TiffIfdEntry(tag, type, count, value);
}
this.ReadBytes(buffer, 4);
this.InputStream.ReadFull(buffer, 4);
uint nextIfdOffset = this.ToUInt32(buffer, 0);
return new TiffIfd(entries, nextIfdOffset);
@ -177,18 +188,184 @@ namespace ImageSharp.Formats
resolutionUnit = (TiffResolutionUnit)this.ReadUnsignedInteger(ref resolutionUnitEntry);
}
double resolutionUnitFactor = resolutionUnit == TiffResolutionUnit.Centimeter ? 1.0 / 2.54 : 1.0;
if (resolutionUnit != TiffResolutionUnit.None)
{
double resolutionUnitFactor = resolutionUnit == TiffResolutionUnit.Centimeter ? 2.54 : 1.0;
if (ifd.TryGetIfdEntry(TiffTags.XResolution, out TiffIfdEntry xResolutionEntry))
{
Rational xResolution = this.ReadUnsignedRational(ref xResolutionEntry);
image.MetaData.HorizontalResolution = xResolution.ToDouble() * resolutionUnitFactor;
}
if (ifd.TryGetIfdEntry(TiffTags.YResolution, out TiffIfdEntry yResolutionEntry))
{
Rational yResolution = this.ReadUnsignedRational(ref yResolutionEntry);
image.MetaData.VerticalResolution = yResolution.ToDouble() * resolutionUnitFactor;
}
}
this.ReadImageFormat(ifd);
if (ifd.TryGetIfdEntry(TiffTags.RowsPerStrip, out TiffIfdEntry rowsPerStripEntry)
&& ifd.TryGetIfdEntry(TiffTags.StripOffsets, out TiffIfdEntry stripOffsetsEntry)
&& ifd.TryGetIfdEntry(TiffTags.StripByteCounts, out TiffIfdEntry stripByteCountsEntry))
{
int rowsPerStrip = (int)this.ReadUnsignedInteger(ref rowsPerStripEntry);
uint[] stripOffsets = this.ReadUnsignedIntegerArray(ref stripOffsetsEntry);
uint[] stripByteCounts = this.ReadUnsignedIntegerArray(ref stripByteCountsEntry);
int uncompressedStripSize = this.CalculateImageBufferSize(width, rowsPerStrip);
using (PixelAccessor<TColor> pixels = image.Lock())
{
byte[] stripBytes = ArrayPool<byte>.Shared.Rent(uncompressedStripSize);
try
{
this.DecompressImageBlock(stripOffsets[0], stripByteCounts[0], stripBytes);
this.ProcessImageBlock(stripBytes, pixels, 0, 0, width, rowsPerStrip);
}
finally
{
ArrayPool<byte>.Shared.Return(stripBytes);
}
}
}
}
/// <summary>
/// Determines the TIFF compression and color types, and reads any associated parameters.
/// </summary>
/// <param name="ifd">The IFD to read the image format information for.</param>
public void ReadImageFormat(TiffIfd ifd)
{
TiffCompression compression = TiffCompression.None;
if (ifd.TryGetIfdEntry(TiffTags.XResolution, out TiffIfdEntry xResolutionEntry))
if (ifd.TryGetIfdEntry(TiffTags.Compression, out TiffIfdEntry compressionEntry))
{
Rational xResolution = this.ReadUnsignedRational(ref xResolutionEntry);
image.MetaData.HorizontalResolution = xResolution.ToDouble();
compression = (TiffCompression)this.ReadUnsignedInteger(ref compressionEntry);
}
if (ifd.TryGetIfdEntry(TiffTags.YResolution, out TiffIfdEntry yResolutionEntry))
switch (compression)
{
Rational yResolution = this.ReadUnsignedRational(ref yResolutionEntry);
image.MetaData.VerticalResolution = yResolution.ToDouble();
case TiffCompression.None:
{
this.CompressionType = TiffCompressionType.None;
break;
}
default:
{
throw new NotSupportedException("The specified TIFF compression format is not supported.");
}
}
TiffPhotometricInterpretation photometricInterpretation;
if (ifd.TryGetIfdEntry(TiffTags.PhotometricInterpretation, out TiffIfdEntry photometricInterpretationEntry))
{
photometricInterpretation = (TiffPhotometricInterpretation)this.ReadUnsignedInteger(ref photometricInterpretationEntry);
}
else
{
if (compression == TiffCompression.Ccitt1D)
{
photometricInterpretation = TiffPhotometricInterpretation.WhiteIsZero;
}
else
{
throw new ImageFormatException("The TIFF photometric interpretation entry is missing.");
}
}
switch (photometricInterpretation)
{
case TiffPhotometricInterpretation.WhiteIsZero:
{
if (ifd.TryGetIfdEntry(TiffTags.BitsPerSample, out TiffIfdEntry bitsPerSampleEntry))
{
uint[] bitsPerSample = this.ReadUnsignedIntegerArray(ref bitsPerSampleEntry);
if (bitsPerSample.Length == 1 && bitsPerSample[0] == 8)
{
this.ColorType = TiffColorType.WhiteIsZero8;
}
else
{
throw new NotSupportedException("The specified TIFF bit-depth is not supported.");
}
}
else
{
throw new NotSupportedException("TIFF bilevel images are not supported.");
}
break;
}
default:
throw new NotSupportedException("The specified TIFF photometric interpretation is not supported.");
}
}
/// <summary>
/// Calculates the size (in bytes) for a pixel buffer using the determined color format.
/// </summary>
/// <param name="width">The width for the desired pixel buffer.</param>
/// <param name="height">The height for the desired pixel buffer.</param>
/// <returns>The size (in bytes) of the required pixel buffer.</returns>
public int CalculateImageBufferSize(int width, int height)
{
switch (this.ColorType)
{
case TiffColorType.WhiteIsZero8:
return width * height;
default:
throw new InvalidOperationException();
}
}
/// <summary>
/// Decompresses an image block from the input stream into the specified buffer.
/// </summary>
/// <param name="offset">The offset within the file of the image block.</param>
/// <param name="byteCount">The size (in bytes) of the compressed data.</param>
/// <param name="buffer">The buffer to write the uncompressed data.</param>
public void DecompressImageBlock(uint offset, uint byteCount, byte[] buffer)
{
this.InputStream.Seek(offset, SeekOrigin.Begin);
switch (this.CompressionType)
{
case TiffCompressionType.None:
NoneTiffCompression.Decompress(this.InputStream, (int)byteCount, buffer);
break;
default:
throw new InvalidOperationException();
}
}
/// <summary>
/// Decodes pixel data using the current photometric interpretation.
/// </summary>
/// <typeparam name="TColor">The pixel format.</typeparam>
/// <param name="data">The buffer to read image data from.</param>
/// <param name="pixels">The image buffer to write pixels to.</param>
/// <param name="left">The x-coordinate of the left-hand side of the image block.</param>
/// <param name="top">The y-coordinate of the top of the image block.</param>
/// <param name="width">The width of the image block.</param>
/// <param name="height">The height of the image block.</param>
public void ProcessImageBlock<TColor>(byte[] data, PixelAccessor<TColor> pixels, int left, int top, int width, int height)
where TColor : struct, IPixel<TColor>
{
switch (this.ColorType)
{
case TiffColorType.WhiteIsZero8:
WhiteIsZero8TiffColor.Decode(data, pixels, left, top, width, height);
break;
default:
throw new InvalidOperationException();
}
}
@ -207,7 +384,7 @@ namespace ImageSharp.Formats
this.InputStream.Seek(offset, SeekOrigin.Begin);
byte[] data = new byte[byteLength];
this.ReadBytes(data, (int)byteLength);
this.InputStream.ReadFull(data, (int)byteLength);
entry.Value = data;
}
@ -621,29 +798,6 @@ namespace ImageSharp.Formats
}
}
/// <summary>
/// Reads a sequence of bytes from the input stream into a buffer.
/// </summary>
/// <param name="buffer">A buffer to store the retrieved data.</param>
/// <param name="count">The number of bytes to read.</param>
private void ReadBytes(byte[] buffer, int count)
{
int offset = 0;
while (count > 0)
{
int bytesRead = this.InputStream.Read(buffer, offset, count);
if (bytesRead == 0)
{
break;
}
offset += bytesRead;
count -= bytesRead;
}
}
/// <summary>
/// Converts buffer data into an <see cref="sbyte"/> using the correct endianness.
/// </summary>

38
src/ImageSharp/Formats/Tiff/Utils/TiffUtils.cs

@ -0,0 +1,38 @@
// <copyright file="TiffUtils.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp.Formats
{
using System.IO;
/// <summary>
/// TIFF specific utilities and extension methods.
/// </summary>
internal static class TiffUtils
{
/// <summary>
/// Reads a sequence of bytes from the input stream into a buffer.
/// </summary>
/// <param name="stream">The stream to read from.</param>
/// <param name="buffer">A buffer to store the retrieved data.</param>
/// <param name="count">The number of bytes to read.</param>
public static void ReadFull(this Stream stream, byte[] buffer, int count)
{
int offset = 0;
while (count > 0)
{
int bytesRead = stream.Read(buffer, offset, count);
if (bytesRead == 0)
{
break;
}
offset += bytesRead;
count -= bytesRead;
}
}
}
}

28
tests/ImageSharp.Formats.Tiff.Tests/Formats/Tiff/Compression/NoneTiffCompressionTests.cs

@ -0,0 +1,28 @@
// <copyright file="NoneTiffCompressionTests.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp.Tests
{
using System.IO;
using Xunit;
using ImageSharp.Formats;
public class NoneTiffCompressionTests
{
[Theory]
[InlineData(new byte[] { 10, 15, 20, 25, 30, 35, 40, 45 }, 8, new byte[] { 10, 15, 20, 25, 30, 35, 40, 45 })]
[InlineData(new byte[] { 10, 15, 20, 25, 30, 35, 40, 45 }, 5, new byte[] { 10, 15, 20, 25, 30 })]
public void Decompress_ReadsData(byte[] inputData, int byteCount, byte[] expectedResult)
{
Stream stream = new MemoryStream(inputData);
byte[] buffer = new byte[expectedResult.Length];
NoneTiffCompression.Decompress(stream, byteCount, buffer);
Assert.Equal(expectedResult, buffer);
}
}
}

59
tests/ImageSharp.Formats.Tiff.Tests/Formats/Tiff/PhotometricInterpretation/PhotometricInterpretationTestBase.cs

@ -0,0 +1,59 @@
// <copyright file="PhotometricInterpretationTestBase.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp.Tests
{
using System;
using Xunit;
public abstract class PhotometricInterpretationTestBase
{
public static Color[][] Offset(Color[][] input, int xOffset, int yOffset, int width, int height)
{
int inputHeight = input.Length;
int inputWidth = input[0].Length;
Color[][] output = new Color[height][];
for (int y = 0; y < output.Length; y++)
{
output[y] = new Color[width];
}
for (int y = 0; y < inputHeight; y++)
{
for (int x = 0; x < inputWidth; x++)
{
output[y + yOffset][x + xOffset] = input[y][x];
}
}
return output;
}
public static void AssertDecode(Color[][] expectedResult, Action<PixelAccessor<Color>> decodeAction)
{
int resultWidth = expectedResult[0].Length;
int resultHeight = expectedResult.Length;
Image image = new Image(resultWidth, resultHeight);
using (PixelAccessor<Color> pixels = image.Lock())
{
decodeAction(pixels);
}
using (PixelAccessor<Color> pixels = image.Lock())
{
for (int y = 0; y < resultHeight; y++)
{
for (int x = 0; x < resultWidth; x++)
{
Assert.Equal(expectedResult[y][x], pixels[x, y]);
}
}
}
}
}
}

51
tests/ImageSharp.Formats.Tiff.Tests/Formats/Tiff/PhotometricInterpretation/WhiteIsZero8TiffColorTests.cs

@ -0,0 +1,51 @@
// <copyright file="WhiteIsZero8TiffColorTests.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp.Tests
{
using System.Collections.Generic;
using Xunit;
using ImageSharp.Formats;
public class WhiteIsZero8TiffColorTests : PhotometricInterpretationTestBase
{
private static Color Gray000 = new Color(255, 255, 255, 255);
private static Color Gray128 = new Color(127, 127, 127, 255);
private static Color Gray255 = new Color(0, 0, 0, 255);
private static byte[] GrayscaleBytes4x4 = new byte[] { 128, 255, 000, 255,
255, 255, 255, 255,
000, 128, 128, 255,
255, 000, 255, 128 };
private static Color[][] GrayscaleResult4x4 = new[] { new[] { Gray128, Gray255, Gray000, Gray255 },
new[] { Gray255, Gray255, Gray255, Gray255 },
new[] { Gray000, Gray128, Gray128, Gray255 },
new[] { Gray255, Gray000, Gray255, Gray128 }};
public static IEnumerable<object[]> DecodeData
{
get
{
yield return new object[] { GrayscaleBytes4x4, 0, 0, 4, 4, GrayscaleResult4x4 };
yield return new object[] { GrayscaleBytes4x4, 0, 0, 4, 4, Offset(GrayscaleResult4x4, 0, 0, 6, 6) };
yield return new object[] { GrayscaleBytes4x4, 1, 0, 4, 4, Offset(GrayscaleResult4x4, 1, 0, 6, 6) };
yield return new object[] { GrayscaleBytes4x4, 0, 1, 4, 4, Offset(GrayscaleResult4x4, 0, 1, 6, 6) };
yield return new object[] { GrayscaleBytes4x4, 1, 1, 4, 4, Offset(GrayscaleResult4x4, 1, 1, 6, 6) };
}
}
[Theory]
[MemberData(nameof(DecodeData))]
public void Decode_WritesPixelData(byte[] inputData, int left, int top, int width, int height, Color[][] expectedResult)
{
AssertDecode(expectedResult, pixels =>
{
WhiteIsZero8TiffColor.Decode(inputData, pixels, left, top, width, height);
});
}
}
}

190
tests/ImageSharp.Formats.Tiff.Tests/Formats/Tiff/TiffDecoderImageTests.cs

@ -5,6 +5,7 @@
namespace ImageSharp.Tests
{
using System;
using System.IO;
using Xunit;
@ -39,7 +40,7 @@ namespace ImageSharp.Tests
[Theory]
[InlineData(false, 150u, 1u, 200u, 1u, 2u /* Inch */, 150.0, 200.0)]
[InlineData(false, 150u, 1u, 200u, 1u, 3u /* Cm */, 150.0 / 2.54, 200.0 / 2.54)]
[InlineData(false, 150u, 1u, 200u, 1u, 3u /* Cm */, 150.0 * 2.54, 200.0 * 2.54)]
[InlineData(false, 150u, 1u, 200u, 1u, 1u /* None */, 96.0, 96.0)]
[InlineData(false, 150u, 1u, 200u, 1u, null /* Inch */, 150.0, 200.0)]
[InlineData(false, 5u, 2u, 9u, 4u, 2u /* Inch */, 2.5, 2.25)]
@ -47,9 +48,9 @@ namespace ImageSharp.Tests
[InlineData(false, 150u, 1u, null, null, 2u /* Inch */, 150.0, 96.0)]
[InlineData(false, null, null, 200u, 1u, 2u /* Inch */, 96.0, 200.0)]
[InlineData(true, 150u, 1u, 200u, 1u, 2u /* Inch */, 150.0, 200.0)]
[InlineData(true, 150u, 1u, 200u, 1u, 3u /* Cm */, 150.0 / 2.54, 200.0 / 2.54)]
[InlineData(true, 150u, 1u, 200u, 1u, 3u /* Cm */, 150.0 * 2.54, 200.0 * 2.54)]
[InlineData(true, 150u, 1u, 200u, 1u, 1u /* None */, 96.0, 96.0)]
[InlineData(false, 5u, 2u, 9u, 4u, 2u /* Inch */, 2.5, 2.25)]
[InlineData(true, 5u, 2u, 9u, 4u, 2u /* Inch */, 2.5, 2.25)]
[InlineData(true, 150u, 1u, 200u, 1u, null /* Inch */, 150.0, 200.0)]
[InlineData(true, null, null, null, null, null /* Inch */, 96.0, 96.0)]
[InlineData(true, 150u, 1u, null, null, 2u /* Inch */, 150.0, 96.0)]
@ -86,8 +87,8 @@ namespace ImageSharp.Tests
decoder.DecodeImage(ifd, image);
Assert.Equal(expectedHorizonalResolution, image.MetaData.HorizontalResolution);
Assert.Equal(expectedVerticalResolution, image.MetaData.VerticalResolution);
Assert.Equal(expectedHorizonalResolution, image.MetaData.HorizontalResolution, 10);
Assert.Equal(expectedVerticalResolution, image.MetaData.VerticalResolution, 10);
}
[Theory]
@ -124,6 +125,180 @@ namespace ImageSharp.Tests
Assert.Equal("The TIFF IFD does not specify the image dimensions.", e.Message);
}
[Theory]
[InlineData(false, TiffCompression.None, TiffCompressionType.None)]
[InlineData(true, TiffCompression.None, TiffCompressionType.None)]
public void ReadImageFormat_DeterminesCorrectCompressionImplementation(bool isLittleEndian, ushort compression, int compressionType)
{
Stream stream = CreateTiffGenIfd()
.WithEntry(TiffGenEntry.Integer(TiffTags.Compression, TiffType.Short, compression))
.ToStream(isLittleEndian);
TiffDecoderCore decoder = new TiffDecoderCore(stream, isLittleEndian, null);
TiffIfd ifd = decoder.ReadIfd(0);
decoder.ReadImageFormat(ifd);
Assert.Equal((TiffCompressionType)compressionType, decoder.CompressionType);
}
[Theory]
[InlineData(false, TiffCompression.Ccitt1D)]
[InlineData(false, TiffCompression.CcittGroup3Fax)]
[InlineData(false, TiffCompression.CcittGroup4Fax)]
[InlineData(false, TiffCompression.Deflate)]
[InlineData(false, TiffCompression.ItuTRecT43)]
[InlineData(false, TiffCompression.ItuTRecT82)]
[InlineData(false, TiffCompression.Jpeg)]
[InlineData(false, TiffCompression.Lzw)]
[InlineData(false, TiffCompression.OldDeflate)]
[InlineData(false, TiffCompression.OldJpeg)]
[InlineData(false, TiffCompression.PackBits)]
[InlineData(false, 999)]
[InlineData(true, TiffCompression.Ccitt1D)]
[InlineData(true, TiffCompression.CcittGroup3Fax)]
[InlineData(true, TiffCompression.CcittGroup4Fax)]
[InlineData(true, TiffCompression.Deflate)]
[InlineData(true, TiffCompression.ItuTRecT43)]
[InlineData(true, TiffCompression.ItuTRecT82)]
[InlineData(true, TiffCompression.Jpeg)]
[InlineData(true, TiffCompression.Lzw)]
[InlineData(true, TiffCompression.OldDeflate)]
[InlineData(true, TiffCompression.OldJpeg)]
[InlineData(true, TiffCompression.PackBits)]
[InlineData(true, 999)]
public void ReadImageFormat_ThrowsExceptionForUnsupportedCompression(bool isLittleEndian, ushort compression)
{
Stream stream = CreateTiffGenIfd()
.WithEntry(TiffGenEntry.Integer(TiffTags.Compression, TiffType.Short, compression))
.ToStream(isLittleEndian);
TiffDecoderCore decoder = new TiffDecoderCore(stream, isLittleEndian, null);
TiffIfd ifd = decoder.ReadIfd(0);
var e = Assert.Throws<NotSupportedException>(() => decoder.ReadImageFormat(ifd));
Assert.Equal("The specified TIFF compression format is not supported.", e.Message);
}
[Theory]
[InlineData(false, TiffPhotometricInterpretation.WhiteIsZero, new[] { 8 }, TiffColorType.WhiteIsZero8)]
[InlineData(true, TiffPhotometricInterpretation.WhiteIsZero, new[] { 8 }, TiffColorType.WhiteIsZero8)]
public void ReadImageFormat_DeterminesCorrectColorImplementation(bool isLittleEndian, ushort photometricInterpretation, int[] bitsPerSample, int colorType)
{
Stream stream = CreateTiffGenIfd()
.WithEntry(TiffGenEntry.Integer(TiffTags.PhotometricInterpretation, TiffType.Short, photometricInterpretation))
.WithEntry(TiffGenEntry.Integer(TiffTags.BitsPerSample, TiffType.Short, bitsPerSample))
.ToStream(isLittleEndian);
TiffDecoderCore decoder = new TiffDecoderCore(stream, isLittleEndian, null);
TiffIfd ifd = decoder.ReadIfd(0);
decoder.ReadImageFormat(ifd);
Assert.Equal((TiffColorType)colorType, decoder.ColorType);
}
// [Theory]
// [InlineData(false, new[] { 8 }, TiffColorType.WhiteIsZero8)]
// [InlineData(true, new[] { 8 }, TiffColorType.WhiteIsZero8)]
// public void ReadImageFormat_UsesDefaultColorImplementationForCcitt1D(bool isLittleEndian, int[] bitsPerSample, int colorType)
// {
// Stream stream = CreateTiffGenIfd()
// .WithEntry(TiffGenEntry.Integer(TiffTags.Compression, TiffType.Short, (int)TiffCompression.Ccitt1D))
// .WithEntry(TiffGenEntry.Integer(TiffTags.BitsPerSample, TiffType.Short, bitsPerSample))
// .WithoutEntry(TiffTags.PhotometricInterpretation)
// .ToStream(isLittleEndian);
// TiffDecoderCore decoder = new TiffDecoderCore(stream, isLittleEndian, null);
// TiffIfd ifd = decoder.ReadIfd(0);
// decoder.ReadImageFormat(ifd);
// Assert.Equal((TiffColorType)colorType, decoder.ColorType);
// }
[Theory]
[MemberData(nameof(IsLittleEndianValues))]
public void ReadImageFormat_ThrowsExceptionForMissingPhotometricInterpretation(bool isLittleEndian)
{
Stream stream = CreateTiffGenIfd()
.WithoutEntry(TiffTags.PhotometricInterpretation)
.ToStream(isLittleEndian);
TiffDecoderCore decoder = new TiffDecoderCore(stream, isLittleEndian, null);
TiffIfd ifd = decoder.ReadIfd(0);
var e = Assert.Throws<ImageFormatException>(() => decoder.ReadImageFormat(ifd));
Assert.Equal("The TIFF photometric interpretation entry is missing.", e.Message);
}
[Theory]
[InlineData(false, TiffPhotometricInterpretation.BlackIsZero)]
[InlineData(false, TiffPhotometricInterpretation.CieLab)]
[InlineData(false, TiffPhotometricInterpretation.ColorFilterArray)]
[InlineData(false, TiffPhotometricInterpretation.IccLab)]
[InlineData(false, TiffPhotometricInterpretation.ItuLab)]
[InlineData(false, TiffPhotometricInterpretation.LinearRaw)]
[InlineData(false, TiffPhotometricInterpretation.PaletteColor)]
[InlineData(false, TiffPhotometricInterpretation.Rgb)]
[InlineData(false, TiffPhotometricInterpretation.Separated)]
[InlineData(false, TiffPhotometricInterpretation.TransparencyMask)]
[InlineData(false, TiffPhotometricInterpretation.YCbCr)]
[InlineData(false, 999)]
[InlineData(true, TiffPhotometricInterpretation.BlackIsZero)]
[InlineData(true, TiffPhotometricInterpretation.CieLab)]
[InlineData(true, TiffPhotometricInterpretation.ColorFilterArray)]
[InlineData(true, TiffPhotometricInterpretation.IccLab)]
[InlineData(true, TiffPhotometricInterpretation.ItuLab)]
[InlineData(true, TiffPhotometricInterpretation.LinearRaw)]
[InlineData(true, TiffPhotometricInterpretation.PaletteColor)]
[InlineData(true, TiffPhotometricInterpretation.Rgb)]
[InlineData(true, TiffPhotometricInterpretation.Separated)]
[InlineData(true, TiffPhotometricInterpretation.TransparencyMask)]
[InlineData(true, TiffPhotometricInterpretation.YCbCr)]
[InlineData(true, 999)]
public void ReadImageFormat_ThrowsExceptionForUnsupportedPhotometricInterpretation(bool isLittleEndian, ushort photometricInterpretation)
{
Stream stream = CreateTiffGenIfd()
.WithEntry(TiffGenEntry.Integer(TiffTags.PhotometricInterpretation, TiffType.Short, photometricInterpretation))
.ToStream(isLittleEndian);
TiffDecoderCore decoder = new TiffDecoderCore(stream, isLittleEndian, null);
TiffIfd ifd = decoder.ReadIfd(0);
var e = Assert.Throws<NotSupportedException>(() => decoder.ReadImageFormat(ifd));
Assert.Equal("The specified TIFF photometric interpretation is not supported.", e.Message);
}
[Theory]
[InlineData(false, TiffPhotometricInterpretation.WhiteIsZero, new[] { 3 })]
[InlineData(true, TiffPhotometricInterpretation.WhiteIsZero, new[] { 3 })]
public void ReadImageFormat_ThrowsExceptionForUnsupportedBitDepth(bool isLittleEndian, ushort photometricInterpretation, int[] bitsPerSample)
{
Stream stream = CreateTiffGenIfd()
.WithEntry(TiffGenEntry.Integer(TiffTags.PhotometricInterpretation, TiffType.Short, photometricInterpretation))
.WithEntry(TiffGenEntry.Integer(TiffTags.BitsPerSample, TiffType.Short, bitsPerSample))
.ToStream(isLittleEndian);
TiffDecoderCore decoder = new TiffDecoderCore(stream, isLittleEndian, null);
TiffIfd ifd = decoder.ReadIfd(0);
var e = Assert.Throws<NotSupportedException>(() => decoder.ReadImageFormat(ifd));
Assert.Equal("The specified TIFF bit-depth is not supported.", e.Message);
}
[Theory]
[InlineData(TiffColorType.WhiteIsZero8, 100, 80, 100 * 80)]
public void CalculateImageBufferSize_ReturnsCorrectSize(ushort colorType, int width, int height, int expectedResult)
{
TiffDecoderCore decoder = new TiffDecoderCore(null);
int bufferSize = decoder.CalculateImageBufferSize(width, height);
Assert.Equal(expectedResult, bufferSize);
}
private TiffGenIfd CreateTiffGenIfd()
{
return new TiffGenIfd()
@ -134,7 +309,10 @@ namespace ImageSharp.Tests
TiffGenEntry.Integer(TiffTags.ImageLength, TiffType.Long, ImageHeight),
TiffGenEntry.Rational(TiffTags.XResolution, XResolution, 1),
TiffGenEntry.Rational(TiffTags.YResolution, YResolution, 1),
TiffGenEntry.Integer(TiffTags.ResolutionUnit, TiffType.Short, 2)
TiffGenEntry.Integer(TiffTags.ResolutionUnit, TiffType.Short, 2),
TiffGenEntry.Integer(TiffTags.PhotometricInterpretation, TiffType.Short, (int)TiffPhotometricInterpretation.WhiteIsZero),
TiffGenEntry.Integer(TiffTags.BitsPerSample, TiffType.Short, new int[] { 8 }),
TiffGenEntry.Integer(TiffTags.Compression, TiffType.Short, (int)TiffCompression.None)
}
};
}

2
tests/ImageSharp.Formats.Tiff.Tests/TestUtilities/Tiff/TiffGenIfdExtensions.cs

@ -14,7 +14,7 @@ namespace ImageSharp.Tests
{
public static TiffGenIfd WithoutEntry(this TiffGenIfd ifd, ushort tag)
{
TiffGenEntry entry = ifd.Entries.First(e => e.Tag == tag);
TiffGenEntry entry = ifd.Entries.FirstOrDefault(e => e.Tag == tag);
if (entry != null)
{
ifd.Entries.Remove(entry);

Loading…
Cancel
Save