Browse Source

Implement support BigTiff format decoding

pull/1760/head
Ildar Khayrutdinov 4 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="stripHeight">The height of the strip.</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);
if (stripOffset + stripByteCount < stream.Position)
if ((long)stripOffset + (long)stripByteCount < stream.Position)
{
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>
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>
/// RowsPerStrip default value, which is effectively infinity.
/// </summary>

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

@ -16,11 +16,11 @@ namespace SixLabors.ImageSharp.Formats.Tiff
{
private readonly Stream stream;
private uint nextIfdOffset;
private ulong nextIfdOffset;
// used for sequential read big values (actual for multiframe big files)
// 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;
@ -36,13 +36,17 @@ namespace SixLabors.ImageSharp.Formats.Tiff
public IEnumerable<ExifProfile> Read()
{
this.ByteOrder = ReadByteOrder(this.stream);
this.nextIfdOffset = new HeaderReader(this.stream, this.ByteOrder).ReadFileHeader();
return this.ReadIfds();
var headerReader = new HeaderReader(this.stream, this.ByteOrder);
headerReader.ReadFileHeader();
this.nextIfdOffset = headerReader.FirstIfdOffset;
return this.ReadIfds(headerReader.IsBigTiff);
}
private static ByteOrder ReadByteOrder(Stream stream)
{
var headerBytes = new byte[2];
byte[] headerBytes = new byte[2];
stream.Read(headerBytes, 0, 2);
if (headerBytes[0] == TiffConstants.ByteOrderLittleEndian && headerBytes[1] == TiffConstants.ByteOrderLittleEndian)
{
@ -56,13 +60,13 @@ namespace SixLabors.ImageSharp.Formats.Tiff
throw TiffThrowHelper.ThrowInvalidHeader();
}
private IEnumerable<ExifProfile> ReadIfds()
private IEnumerable<ExifProfile> ReadIfds(bool isBigTiff)
{
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);
reader.ReadTags();
var reader = new EntryReader(this.stream, this.ByteOrder, this.lazyLoaders);
reader.ReadTags(isBigTiff, this.nextIfdOffset);
this.nextIfdOffset = reader.NextIfdOffset;
@ -75,7 +79,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff
loader();
}
var list = new List<ExifProfile>();
var list = new List<ExifProfile>(readers.Count);
foreach (EntryReader reader in readers)
{
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
{
private readonly uint startOffset;
private readonly SortedList<ulong, Action> lazyLoaders;
private readonly SortedList<uint, Action> lazyLoaders;
public EntryReader(Stream stream, ByteOrder byteOrder, uint ifdOffset, SortedList<uint, Action> lazyLoaders)
public EntryReader(Stream stream, ByteOrder byteOrder, SortedList<ulong, Action> lazyLoaders)
: base(stream)
{
this.IsBigEndian = byteOrder == ByteOrder.BigEndian;
this.startOffset = ifdOffset;
this.lazyLoaders = lazyLoaders;
}
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);
this.NextIfdOffset = this.ReadUInt32();
if (!isBigTiff)
{
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);
}
@ -46,20 +53,35 @@ namespace SixLabors.ImageSharp.Formats.Tiff
: base(stream) =>
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();
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();
return this.FirstIfdOffset;
TiffThrowHelper.ThrowInvalidHeader();
}
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");
}
if (((ulong)width.Value) > int.MaxValue)
{
TiffThrowHelper.ThrowImageFormatException("Too big ImageWidth 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>
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.");
}
if (exifProfile.GetValue(ExifTag.ExtraSamples)?.Value != null)
if (exifProfile.GetValueInternal(ExifTag.ExtraSamples) is not null)
{
TiffThrowHelper.ThrowNotSupported("ExtraSamples is not supported.");
}
@ -95,12 +95,12 @@ namespace SixLabors.ImageSharp.Formats.Tiff
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!");
}
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!");
}
@ -384,7 +384,8 @@ namespace SixLabors.ImageSharp.Formats.Tiff
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:
{
@ -441,7 +442,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff
default:
{
TiffThrowHelper.ThrowNotSupported($"The specified TIFF compression format {compression} is not supported");
TiffThrowHelper.ThrowNotSupported($"The specified TIFF compression format '{compression}' is not supported");
break;
}
}

Loading…
Cancel
Save