Browse Source

Decode TIFF image resolution

pull/119/head
Andrew Wilkinson 9 years ago
parent
commit
143a73e655
  1. 2
      src/ImageSharp/Formats/Tiff/Constants/TiffResolutionUnit.cs
  2. 20
      src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs
  3. 24
      src/ImageSharp/Formats/Tiff/TiffIfd/TiffIfd.cs
  4. 76
      tests/ImageSharp.Formats.Tiff.Tests/Formats/Tiff/TiffDecoderImageTests.cs
  5. 35
      tests/ImageSharp.Formats.Tiff.Tests/Formats/Tiff/TiffIfd/TiffIfdTests.cs
  6. 30
      tests/ImageSharp.Formats.Tiff.Tests/TestUtilities/Tiff/TiffGenEntry.cs
  7. 15
      tests/ImageSharp.Formats.Tiff.Tests/TestUtilities/Tiff/TiffGenIfdExtensions.cs

2
src/ImageSharp/Formats/Tiff/Constants/TiffResolutionUnit.cs

@ -23,6 +23,6 @@ namespace ImageSharp.Formats
/// <summary>
/// Centimeter.
/// </summary>
Centimeter = 2
Centimeter = 3
}
}

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

@ -170,6 +170,26 @@ namespace ImageSharp.Formats
int height = (int)this.ReadUnsignedInteger(ref imageLengthEntry);
image.InitPixels(width, height);
TiffResolutionUnit resolutionUnit = TiffResolutionUnit.Inch;
if (ifd.TryGetIfdEntry(TiffTags.ResolutionUnit, out TiffIfdEntry resolutionUnitEntry))
{
resolutionUnit = (TiffResolutionUnit)this.ReadUnsignedInteger(ref resolutionUnitEntry);
}
double resolutionUnitFactor = resolutionUnit == TiffResolutionUnit.Centimeter ? 1.0 / 2.54 : 1.0;
if (ifd.TryGetIfdEntry(TiffTags.XResolution, out TiffIfdEntry xResolutionEntry))
{
Rational xResolution = this.ReadUnsignedRational(ref xResolutionEntry);
image.MetaData.HorizontalResolution = xResolution.ToDouble();
}
if (ifd.TryGetIfdEntry(TiffTags.YResolution, out TiffIfdEntry yResolutionEntry))
{
Rational yResolution = this.ReadUnsignedRational(ref yResolutionEntry);
image.MetaData.VerticalResolution = yResolution.ToDouble();
}
}
/// <summary>

24
src/ImageSharp/Formats/Tiff/TiffIfd/TiffIfd.cs

@ -35,21 +35,31 @@ namespace ImageSharp.Formats
/// Gets the child <see cref="TiffIfdEntry"/> with the specified tag ID.
/// </summary>
/// <param name="tag">The tag ID to search for.</param>
/// <param name="entry">The resulting <see cref="TiffIfdEntry"/>, if it exists.</param>
/// <returns>A flag indicating whether the requested entry exists</returns>
public bool TryGetIfdEntry(ushort tag, out TiffIfdEntry entry)
/// <returns>The resulting <see cref="TiffIfdEntry"/>, or null if it does not exists.</returns>
public TiffIfdEntry? GetIfdEntry(ushort tag)
{
for (int i = 0; i < this.Entries.Length; i++)
{
if (this.Entries[i].Tag == tag)
{
entry = this.Entries[i];
return true;
return this.Entries[i];
}
}
entry = default(TiffIfdEntry);
return false;
return null;
}
/// <summary>
/// Gets the child <see cref="TiffIfdEntry"/> with the specified tag ID.
/// </summary>
/// <param name="tag">The tag ID to search for.</param>
/// <param name="entry">The resulting <see cref="TiffIfdEntry"/>, if it exists.</param>
/// <returns>A flag indicating whether the requested entry exists.</returns>
public bool TryGetIfdEntry(ushort tag, out TiffIfdEntry entry)
{
TiffIfdEntry? nullableEntry = this.GetIfdEntry(tag);
entry = nullableEntry ?? default(TiffIfdEntry);
return nullableEntry.HasValue;
}
}
}

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

@ -14,6 +14,8 @@ namespace ImageSharp.Tests
{
public const int ImageWidth = 200;
public const int ImageHeight = 150;
public const int XResolution = 100;
public const int YResolution = 200;
public static object[][] IsLittleEndianValues = new[] { new object[] { false },
new object[] { true } };
@ -27,14 +29,67 @@ namespace ImageSharp.Tests
TiffDecoderCore decoder = new TiffDecoderCore(stream, isLittleEndian, null);
TiffIfd ifd = decoder.ReadIfd(0);
Image<Color> image = new Image<Color>(1,1);
Image<Color> image = new Image<Color>(1, 1);
decoder.DecodeImage(ifd, image);
Assert.Equal(ImageWidth, image.Width);
Assert.Equal(ImageHeight, image.Height);
}
[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, 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)]
[InlineData(false, null, null, null, null, null /* Inch */, 96.0, 96.0)]
[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, 1u /* None */, 96.0, 96.0)]
[InlineData(false, 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)]
[InlineData(true, null, null, 200u, 1u, 2u /* Inch */, 96.0, 200.0)]
public void DecodeImage_SetsImageResolution(bool isLittleEndian, uint? xResolutionNumerator, uint? xResolutionDenominator,
uint? yResolutionNumerator, uint? yResolutionDenominator, uint? resolutionUnit,
double expectedHorizonalResolution, double expectedVerticalResolution)
{
TiffGenIfd ifdGen = CreateTiffGenIfd()
.WithoutEntry(TiffTags.XResolution)
.WithoutEntry(TiffTags.YResolution)
.WithoutEntry(TiffTags.ResolutionUnit);
if (xResolutionNumerator != null)
{
ifdGen.WithEntry(TiffGenEntry.Rational(TiffTags.XResolution, xResolutionNumerator.Value, xResolutionDenominator.Value));
}
if (yResolutionNumerator != null)
{
ifdGen.WithEntry(TiffGenEntry.Rational(TiffTags.YResolution, yResolutionNumerator.Value, yResolutionDenominator.Value));
}
if (resolutionUnit != null)
{
ifdGen.WithEntry(TiffGenEntry.Integer(TiffTags.ResolutionUnit, TiffType.Short, resolutionUnit.Value));
}
Stream stream = ifdGen.ToStream(isLittleEndian);
TiffDecoderCore decoder = new TiffDecoderCore(stream, isLittleEndian, null);
TiffIfd ifd = decoder.ReadIfd(0);
Image<Color> image = new Image<Color>(1, 1);
decoder.DecodeImage(ifd, image);
Assert.Equal(expectedHorizonalResolution, image.MetaData.HorizontalResolution);
Assert.Equal(expectedVerticalResolution, image.MetaData.VerticalResolution);
}
[Theory]
[MemberData(nameof(IsLittleEndianValues))]
public void DecodeImage_ThrowsException_WithMissingImageWidth(bool isLittleEndian)
@ -45,8 +100,8 @@ namespace ImageSharp.Tests
TiffDecoderCore decoder = new TiffDecoderCore(stream, isLittleEndian, null);
TiffIfd ifd = decoder.ReadIfd(0);
Image<Color> image = new Image<Color>(1,1);
Image<Color> image = new Image<Color>(1, 1);
var e = Assert.Throws<ImageFormatException>(() => decoder.DecodeImage(ifd, image));
Assert.Equal("The TIFF IFD does not specify the image dimensions.", e.Message);
@ -62,8 +117,8 @@ namespace ImageSharp.Tests
TiffDecoderCore decoder = new TiffDecoderCore(stream, isLittleEndian, null);
TiffIfd ifd = decoder.ReadIfd(0);
Image<Color> image = new Image<Color>(1,1);
Image<Color> image = new Image<Color>(1, 1);
var e = Assert.Throws<ImageFormatException>(() => decoder.DecodeImage(ifd, image));
Assert.Equal("The TIFF IFD does not specify the image dimensions.", e.Message);
@ -72,13 +127,16 @@ namespace ImageSharp.Tests
private TiffGenIfd CreateTiffGenIfd()
{
return new TiffGenIfd()
{
Entries =
{
Entries =
{
TiffGenEntry.Integer(TiffTags.ImageWidth, TiffType.Long, ImageWidth),
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)
}
};
};
}
}
}

35
tests/ImageSharp.Formats.Tiff.Tests/Formats/Tiff/TiffIfd/TiffIfdTests.cs

@ -22,6 +22,41 @@ namespace ImageSharp.Tests
Assert.Equal(1234u, ifd.NextIfdOffset);
}
[Fact]
public void GetIfdEntry_ReturnsIfdIfExists()
{
var entries = new[]
{
new TiffIfdEntry(10, TiffType.Short, 20, new byte[4]),
new TiffIfdEntry(20, TiffType.Short, 20, new byte[4]),
new TiffIfdEntry(30, TiffType.Short, 20, new byte[4]),
new TiffIfdEntry(40, TiffType.Short, 20, new byte[4])
};
var ifd = new TiffIfd(entries, 1234u);
TiffIfdEntry? entry = ifd.GetIfdEntry(30);
Assert.Equal(true, entry.HasValue);
Assert.Equal(30, entry.Value.Tag);
}
[Fact]
public void GetIfdEntry_ReturnsNullOtherwise()
{
var entries = new[]
{
new TiffIfdEntry(10, TiffType.Short, 20, new byte[4]),
new TiffIfdEntry(20, TiffType.Short, 20, new byte[4]),
new TiffIfdEntry(30, TiffType.Short, 20, new byte[4]),
new TiffIfdEntry(40, TiffType.Short, 20, new byte[4])
};
var ifd = new TiffIfd(entries, 1234u);
TiffIfdEntry? entry = ifd.GetIfdEntry(25);
Assert.Equal(false, entry.HasValue);
}
[Fact]
public void TryGetIfdEntry_ReturnsIfdIfExists()
{

30
tests/ImageSharp.Formats.Tiff.Tests/TestUtilities/Tiff/TiffGenEntry.cs

@ -41,7 +41,7 @@ namespace ImageSharp.Tests
public static TiffGenEntry Integer(ushort tag, TiffType type, int value)
{
return TiffGenEntry.Integer(tag, type, new int[] {value});
return TiffGenEntry.Integer(tag, type, new int[] { value });
}
public static TiffGenEntry Integer(ushort tag, TiffType type, int[] value)
@ -55,7 +55,7 @@ namespace ImageSharp.Tests
public static TiffGenEntry Integer(ushort tag, TiffType type, uint value)
{
return TiffGenEntry.Integer(tag, type, new uint[] {value});
return TiffGenEntry.Integer(tag, type, new uint[] { value });
}
public static TiffGenEntry Integer(ushort tag, TiffType type, uint[] value)
@ -67,6 +67,11 @@ namespace ImageSharp.Tests
return new TiffGenEntryUnsignedInteger(tag, type, value);
}
public static TiffGenEntry Rational(ushort tag, uint numerator, uint denominator)
{
return new TiffGenEntryRational(tag, numerator, denominator);
}
private class TiffGenEntryAscii : TiffGenEntry
{
public TiffGenEntryAscii(ushort tag, string value) : base(tag, TiffType.Ascii, (uint)GetBytes(value).Length)
@ -176,5 +181,26 @@ namespace ImageSharp.Tests
}
}
}
private class TiffGenEntryRational : TiffGenEntry
{
public TiffGenEntryRational(ushort tag, uint numerator, uint denominator) : base(tag, TiffType.Rational, 1u)
{
this.Numerator = numerator;
this.Denominator = denominator;
}
public uint Numerator { get; }
public uint Denominator { get; }
public override IEnumerable<TiffGenDataBlock> GetData(bool isLittleEndian)
{
byte[] numeratorBytes = BitConverter.GetBytes(Numerator).WithByteOrder(isLittleEndian);
byte[] denominatorBytes = BitConverter.GetBytes(Denominator).WithByteOrder(isLittleEndian);
byte[] bytes = Enumerable.Concat(numeratorBytes, denominatorBytes).ToArray();
return new[] { new TiffGenDataBlock(bytes) };
}
}
}
}

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

@ -5,8 +5,6 @@
namespace ImageSharp.Tests
{
using System;
using System.IO;
using System.Linq;
/// <summary>
@ -17,7 +15,18 @@ namespace ImageSharp.Tests
public static TiffGenIfd WithoutEntry(this TiffGenIfd ifd, ushort tag)
{
TiffGenEntry entry = ifd.Entries.First(e => e.Tag == tag);
ifd.Entries.Remove(entry);
if (entry != null)
{
ifd.Entries.Remove(entry);
}
return ifd;
}
public static TiffGenIfd WithEntry(this TiffGenIfd ifd, TiffGenEntry entry)
{
ifd.WithoutEntry(entry.Tag);
ifd.Entries.Add(entry);
return ifd;
}
}

Loading…
Cancel
Save