Browse Source

Implement support BigTiff format decoding

pull/1760/head
Ildar Khayrutdinov 5 years ago
parent
commit
4b3ae005a6
  1. 10
      src/ImageSharp/Formats/Tiff/Compression/TiffBaseDecompressor.cs
  2. 10
      src/ImageSharp/Formats/Tiff/Constants/TiffConstants.cs
  3. 24
      src/ImageSharp/Formats/Tiff/Ifd/DirectoryReader.cs
  4. 58
      src/ImageSharp/Formats/Tiff/Ifd/EntryReader.cs
  5. 5
      src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs
  6. 13
      src/ImageSharp/Formats/Tiff/TiffDecoderOptionsParser.cs

10
src/ImageSharp/Formats/Tiff/Compression/TiffBaseDecompressor.cs

@ -35,17 +35,17 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression
/// <param name="stripByteCount">The number of bytes to read from the input stream.</param> /// <param name="stripByteCount">The number of bytes to read from the input stream.</param>
/// <param name="stripHeight">The height of the strip.</param> /// <param name="stripHeight">The height of the strip.</param>
/// <param name="buffer">The output buffer for uncompressed data.</param> /// <param name="buffer">The output buffer for uncompressed data.</param>
public void Decompress(BufferedReadStream stream, uint stripOffset, uint stripByteCount, int stripHeight, Span<byte> buffer) public void Decompress(BufferedReadStream stream, ulong stripOffset, ulong stripByteCount, int stripHeight, Span<byte> buffer)
{ {
if (stripByteCount > int.MaxValue) if (stripOffset > long.MaxValue || stripByteCount > long.MaxValue)
{ {
TiffThrowHelper.ThrowImageFormatException("The StripByteCount value is too big."); TiffThrowHelper.ThrowImageFormatException("The StripOffset or StripByteCount value is too big.");
} }
stream.Seek(stripOffset, SeekOrigin.Begin); stream.Seek((long)stripOffset, SeekOrigin.Begin);
this.Decompress(stream, (int)stripByteCount, stripHeight, buffer); this.Decompress(stream, (int)stripByteCount, stripHeight, buffer);
if (stripOffset + stripByteCount < stream.Position) if ((long)stripOffset + (long)stripByteCount < stream.Position)
{ {
TiffThrowHelper.ThrowImageFormatException("Out of range when reading a strip."); TiffThrowHelper.ThrowImageFormatException("Out of range when reading a strip.");
} }

10
src/ImageSharp/Formats/Tiff/Constants/TiffConstants.cs

@ -35,6 +35,16 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Constants
/// </summary> /// </summary>
public const ushort HeaderMagicNumber = 42; public const ushort HeaderMagicNumber = 42;
/// <summary>
/// The big tiff header magic number
/// </summary>
public const ushort BigTiffHeaderMagicNumber = 43;
/// <summary>
/// The big tiff bytesize of offsets value.
/// </summary>
public const ushort BigTiffBytesize = 8;
/// <summary> /// <summary>
/// RowsPerStrip default value, which is effectively infinity. /// RowsPerStrip default value, which is effectively infinity.
/// </summary> /// </summary>

24
src/ImageSharp/Formats/Tiff/Ifd/DirectoryReader.cs

@ -16,11 +16,11 @@ namespace SixLabors.ImageSharp.Formats.Tiff
{ {
private readonly Stream stream; private readonly Stream stream;
private uint nextIfdOffset; private ulong nextIfdOffset;
// used for sequential read big values (actual for multiframe big files) // used for sequential read big values (actual for multiframe big files)
// todo: different tags can link to the same data (stream offset) - investigate // todo: different tags can link to the same data (stream offset) - investigate
private readonly SortedList<uint, Action> lazyLoaders = new SortedList<uint, Action>(new DuplicateKeyComparer<uint>()); private readonly SortedList<ulong, Action> lazyLoaders = new SortedList<ulong, Action>(new DuplicateKeyComparer<ulong>());
public DirectoryReader(Stream stream) => this.stream = stream; public DirectoryReader(Stream stream) => this.stream = stream;
@ -36,13 +36,17 @@ namespace SixLabors.ImageSharp.Formats.Tiff
public IEnumerable<ExifProfile> Read() public IEnumerable<ExifProfile> Read()
{ {
this.ByteOrder = ReadByteOrder(this.stream); this.ByteOrder = ReadByteOrder(this.stream);
this.nextIfdOffset = new HeaderReader(this.stream, this.ByteOrder).ReadFileHeader(); var headerReader = new HeaderReader(this.stream, this.ByteOrder);
return this.ReadIfds(); headerReader.ReadFileHeader();
this.nextIfdOffset = headerReader.FirstIfdOffset;
return this.ReadIfds(headerReader.IsBigTiff);
} }
private static ByteOrder ReadByteOrder(Stream stream) private static ByteOrder ReadByteOrder(Stream stream)
{ {
var headerBytes = new byte[2]; byte[] headerBytes = new byte[2];
stream.Read(headerBytes, 0, 2); stream.Read(headerBytes, 0, 2);
if (headerBytes[0] == TiffConstants.ByteOrderLittleEndian && headerBytes[1] == TiffConstants.ByteOrderLittleEndian) if (headerBytes[0] == TiffConstants.ByteOrderLittleEndian && headerBytes[1] == TiffConstants.ByteOrderLittleEndian)
{ {
@ -56,13 +60,13 @@ namespace SixLabors.ImageSharp.Formats.Tiff
throw TiffThrowHelper.ThrowInvalidHeader(); throw TiffThrowHelper.ThrowInvalidHeader();
} }
private IEnumerable<ExifProfile> ReadIfds() private IEnumerable<ExifProfile> ReadIfds(bool isBigTiff)
{ {
var readers = new List<EntryReader>(); var readers = new List<EntryReader>();
while (this.nextIfdOffset != 0 && this.nextIfdOffset < this.stream.Length) while (this.nextIfdOffset != 0 && this.nextIfdOffset < (ulong)this.stream.Length)
{ {
var reader = new EntryReader(this.stream, this.ByteOrder, this.nextIfdOffset, this.lazyLoaders); var reader = new EntryReader(this.stream, this.ByteOrder, this.lazyLoaders);
reader.ReadTags(); reader.ReadTags(isBigTiff, this.nextIfdOffset);
this.nextIfdOffset = reader.NextIfdOffset; this.nextIfdOffset = reader.NextIfdOffset;
@ -75,7 +79,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff
loader(); loader();
} }
var list = new List<ExifProfile>(); var list = new List<ExifProfile>(readers.Count);
foreach (EntryReader reader in readers) foreach (EntryReader reader in readers)
{ {
var profile = new ExifProfile(reader.Values, reader.InvalidTags); var profile = new ExifProfile(reader.Values, reader.InvalidTags);

58
src/ImageSharp/Formats/Tiff/Ifd/EntryReader.cs

@ -12,31 +12,38 @@ namespace SixLabors.ImageSharp.Formats.Tiff
{ {
internal class EntryReader : BaseExifReader internal class EntryReader : BaseExifReader
{ {
private readonly uint startOffset; private readonly SortedList<ulong, Action> lazyLoaders;
private readonly SortedList<uint, Action> lazyLoaders; public EntryReader(Stream stream, ByteOrder byteOrder, SortedList<ulong, Action> lazyLoaders)
public EntryReader(Stream stream, ByteOrder byteOrder, uint ifdOffset, SortedList<uint, Action> lazyLoaders)
: base(stream) : base(stream)
{ {
this.IsBigEndian = byteOrder == ByteOrder.BigEndian; this.IsBigEndian = byteOrder == ByteOrder.BigEndian;
this.startOffset = ifdOffset;
this.lazyLoaders = lazyLoaders; this.lazyLoaders = lazyLoaders;
} }
public List<IExifValue> Values { get; } = new List<IExifValue>(); public List<IExifValue> Values { get; } = new List<IExifValue>();
public uint NextIfdOffset { get; private set; } public ulong NextIfdOffset { get; private set; }
public void ReadTags() public void ReadTags(bool isBigTiff, ulong ifdOffset)
{ {
this.ReadValues(this.Values, this.startOffset); if (!isBigTiff)
this.NextIfdOffset = this.ReadUInt32(); {
this.ReadValues(this.Values, (uint)ifdOffset);
this.NextIfdOffset = this.ReadUInt32();
this.ReadSubIfd(this.Values);
}
else
{
this.ReadValues64(this.Values, ifdOffset);
this.NextIfdOffset = this.ReadUInt64();
this.ReadSubIfd(this.Values); this.ReadSubIfd64(this.Values);
}
} }
protected override void RegisterExtLoader(uint offset, Action reader) => protected override void RegisterExtLoader(ulong offset, Action reader) =>
this.lazyLoaders.Add(offset, reader); this.lazyLoaders.Add(offset, reader);
} }
@ -46,20 +53,35 @@ namespace SixLabors.ImageSharp.Formats.Tiff
: base(stream) => : base(stream) =>
this.IsBigEndian = byteOrder == ByteOrder.BigEndian; this.IsBigEndian = byteOrder == ByteOrder.BigEndian;
public uint FirstIfdOffset { get; private set; } public bool IsBigTiff { get; private set; }
public ulong FirstIfdOffset { get; private set; }
public uint ReadFileHeader() public void ReadFileHeader()
{ {
ushort magic = this.ReadUInt16(); ushort magic = this.ReadUInt16();
if (magic != TiffConstants.HeaderMagicNumber) if (magic == TiffConstants.HeaderMagicNumber)
{ {
TiffThrowHelper.ThrowInvalidHeader(); this.IsBigTiff = false;
this.FirstIfdOffset = this.ReadUInt32();
return;
}
else if (magic == TiffConstants.BigTiffHeaderMagicNumber)
{
this.IsBigTiff = true;
ushort bytesize = this.ReadUInt16();
ushort reserve = this.ReadUInt16();
if (bytesize == TiffConstants.BigTiffBytesize && reserve == 0)
{
this.FirstIfdOffset = this.ReadUInt64();
return;
}
} }
this.FirstIfdOffset = this.ReadUInt32(); TiffThrowHelper.ThrowInvalidHeader();
return this.FirstIfdOffset;
} }
protected override void RegisterExtLoader(uint offset, Action reader) => throw new NotSupportedException(); protected override void RegisterExtLoader(ulong offset, Action reader) => throw new NotSupportedException();
} }
} }

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

@ -432,6 +432,11 @@ namespace SixLabors.ImageSharp.Formats.Tiff
TiffThrowHelper.ThrowImageFormatException("The TIFF image frame is missing the ImageWidth"); TiffThrowHelper.ThrowImageFormatException("The TIFF image frame is missing the ImageWidth");
} }
if (((ulong)width.Value) > int.MaxValue)
{
TiffThrowHelper.ThrowImageFormatException("Too big ImageWidth value");
}
return (int)width.Value; return (int)width.Value;
} }

13
src/ImageSharp/Formats/Tiff/TiffDecoderOptionsParser.cs

@ -24,12 +24,12 @@ namespace SixLabors.ImageSharp.Formats.Tiff
/// <param name="frameMetadata">The IFD entries container to read the image format information for current frame.</param> /// <param name="frameMetadata">The IFD entries container to read the image format information for current frame.</param>
public static void VerifyAndParse(this TiffDecoderCore options, ExifProfile exifProfile, TiffFrameMetadata frameMetadata) public static void VerifyAndParse(this TiffDecoderCore options, ExifProfile exifProfile, TiffFrameMetadata frameMetadata)
{ {
if (exifProfile.GetValue(ExifTag.TileOffsets)?.Value != null) if (exifProfile.GetValueInternal(ExifTag.TileOffsets) is not null || exifProfile.GetValueInternal(ExifTag.TileByteCounts) is not null)
{ {
TiffThrowHelper.ThrowNotSupported("Tiled images are not supported."); TiffThrowHelper.ThrowNotSupported("Tiled images are not supported.");
} }
if (exifProfile.GetValue(ExifTag.ExtraSamples)?.Value != null) if (exifProfile.GetValueInternal(ExifTag.ExtraSamples) is not null)
{ {
TiffThrowHelper.ThrowNotSupported("ExtraSamples is not supported."); TiffThrowHelper.ThrowNotSupported("ExtraSamples is not supported.");
} }
@ -95,12 +95,12 @@ namespace SixLabors.ImageSharp.Formats.Tiff
private static void VerifyRequiredFieldsArePresent(ExifProfile exifProfile, TiffFrameMetadata frameMetadata) private static void VerifyRequiredFieldsArePresent(ExifProfile exifProfile, TiffFrameMetadata frameMetadata)
{ {
if (exifProfile.GetValue(ExifTag.StripOffsets) == null) if (exifProfile.GetValueInternal(ExifTag.StripOffsets) is null)
{ {
TiffThrowHelper.ThrowImageFormatException("StripOffsets are missing and are required for decoding the TIFF image!"); TiffThrowHelper.ThrowImageFormatException("StripOffsets are missing and are required for decoding the TIFF image!");
} }
if (exifProfile.GetValue(ExifTag.StripByteCounts) == null) if (exifProfile.GetValueInternal(ExifTag.StripByteCounts) is null)
{ {
TiffThrowHelper.ThrowImageFormatException("StripByteCounts are missing and are required for decoding the TIFF image!"); TiffThrowHelper.ThrowImageFormatException("StripByteCounts are missing and are required for decoding the TIFF image!");
} }
@ -384,7 +384,8 @@ namespace SixLabors.ImageSharp.Formats.Tiff
private static void ParseCompression(this TiffDecoderCore options, TiffCompression? compression, ExifProfile exifProfile) private static void ParseCompression(this TiffDecoderCore options, TiffCompression? compression, ExifProfile exifProfile)
{ {
switch (compression) // Default 1 (No compression) https://www.awaresystems.be/imaging/tiff/tifftags/compression.html
switch (compression ?? TiffCompression.None)
{ {
case TiffCompression.None: case TiffCompression.None:
{ {
@ -441,7 +442,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff
default: default:
{ {
TiffThrowHelper.ThrowNotSupported($"The specified TIFF compression format {compression} is not supported"); TiffThrowHelper.ThrowNotSupported($"The specified TIFF compression format '{compression}' is not supported");
break; break;
} }
} }

Loading…
Cancel
Save