diff --git a/src/ImageSharp/Formats/Tiff/Compression/TiffBaseDecompressor.cs b/src/ImageSharp/Formats/Tiff/Compression/TiffBaseDecompressor.cs
index 03cd639ad3..86b5c19d21 100644
--- a/src/ImageSharp/Formats/Tiff/Compression/TiffBaseDecompressor.cs
+++ b/src/ImageSharp/Formats/Tiff/Compression/TiffBaseDecompressor.cs
@@ -28,20 +28,20 @@ protected TiffBaseDecompressor(MemoryAllocator memoryAllocator, int width, int b
/// Decompresses image data into the supplied buffer.
///
/// The to read image data from.
- /// The strip offset of stream.
- /// The number of bytes to read from the input stream.
+ /// The data offset within the stream.
+ /// The number of bytes to read from the input stream.
/// The height of the strip.
/// The output buffer for uncompressed data.
/// The token to monitor cancellation.
- public void Decompress(BufferedReadStream stream, ulong stripOffset, ulong stripByteCount, int stripHeight, Span buffer, CancellationToken cancellationToken)
+ public void Decompress(BufferedReadStream stream, ulong offset, ulong count, int stripHeight, Span buffer, CancellationToken cancellationToken)
{
- DebugGuard.MustBeLessThanOrEqualTo(stripOffset, (ulong)long.MaxValue, nameof(stripOffset));
- DebugGuard.MustBeLessThanOrEqualTo(stripByteCount, (ulong)long.MaxValue, nameof(stripByteCount));
+ DebugGuard.MustBeLessThanOrEqualTo(offset, (ulong)long.MaxValue, nameof(offset));
+ DebugGuard.MustBeLessThanOrEqualTo(count, (ulong)int.MaxValue, nameof(count));
- stream.Seek((long)stripOffset, SeekOrigin.Begin);
- this.Decompress(stream, (int)stripByteCount, stripHeight, buffer, cancellationToken);
+ stream.Seek((long)offset, SeekOrigin.Begin);
+ this.Decompress(stream, (int)count, stripHeight, buffer, cancellationToken);
- if ((long)stripOffset + (long)stripByteCount < stream.Position)
+ if ((long)offset + (long)count < stream.Position)
{
TiffThrowHelper.ThrowImageFormatException("Out of range when reading a strip.");
}
diff --git a/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs b/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs
index e66f0f3765..134f8e2aa0 100644
--- a/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs
+++ b/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs
@@ -5,6 +5,7 @@
using System.Buffers;
using System.Runtime.CompilerServices;
using SixLabors.ImageSharp.Formats.Tiff.Compression;
+using SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors;
using SixLabors.ImageSharp.Formats.Tiff.Constants;
using SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation;
using SixLabors.ImageSharp.IO;
@@ -403,8 +404,14 @@ private void DecodeStripsPlanar(ImageFrame frame, int rowsPerStr
{
for (int stripIndex = 0; stripIndex < stripBuffers.Length; stripIndex++)
{
- int uncompressedStripSize = this.CalculateStripBufferSize(frame.Width, rowsPerStrip, stripIndex);
- stripBuffers[stripIndex] = this.memoryAllocator.Allocate(uncompressedStripSize);
+ ulong uncompressedStripSize = this.CalculateStripBufferSize(frame.Width, rowsPerStrip, stripIndex);
+
+ if (uncompressedStripSize > int.MaxValue)
+ {
+ TiffThrowHelper.ThrowNotSupported("Strips larger than Int32.MaxValue bytes are not supported for compressed images.");
+ }
+
+ stripBuffers[stripIndex] = this.memoryAllocator.Allocate((int)uncompressedStripSize);
}
using TiffBaseDecompressor decompressor = this.CreateDecompressor(frame.Width, bitsPerPixel);
@@ -460,26 +467,97 @@ private void DecodeStripsChunky(ImageFrame frame, int rowsPerStr
rowsPerStrip = frame.Height;
}
- int uncompressedStripSize = this.CalculateStripBufferSize(frame.Width, rowsPerStrip);
+ ulong uncompressedStripSize = this.CalculateStripBufferSize(frame.Width, rowsPerStrip);
int bitsPerPixel = this.BitsPerPixel;
-
- using IMemoryOwner stripBuffer = this.memoryAllocator.Allocate(uncompressedStripSize, AllocationOptions.Clean);
- Span stripBufferSpan = stripBuffer.GetSpan();
Buffer2D pixels = frame.PixelBuffer;
- using TiffBaseDecompressor decompressor = this.CreateDecompressor(frame.Width, bitsPerPixel);
+ int width = frame.Width;
+ int height = frame.Height;
+
+ using TiffBaseDecompressor decompressor = this.CreateDecompressor(width, bitsPerPixel);
TiffBaseColorDecoder colorDecoder = this.CreateChunkyColorDecoder();
+ // There exists in this world TIFF files with uncompressed strips larger than Int32.MaxValue.
+ // We can read them, but we cannot allocate a buffer that large to hold the uncompressed data.
+ // In this scenario we fall back to reading and decoding one row at a time.
+ //
+ // The NoneTiffCompression decompressor can be used to read individual rows since we have
+ // a guarantee that each row required the same number of bytes.
+ if (decompressor is NoneTiffCompression none && uncompressedStripSize > int.MaxValue)
+ {
+ ulong bytesPerRowU = this.CalculateStripBufferSize(frame.Width, 1);
+
+ // This should never happen, but we check just to be sure.
+ if (bytesPerRowU > int.MaxValue)
+ {
+ TiffThrowHelper.ThrowNotSupported("Strips larger than Int32.MaxValue bytes are not supported for compressed images.");
+ }
+
+ int bytesPerRow = (int)bytesPerRowU;
+ using IMemoryOwner rowBufferOwner = this.memoryAllocator.Allocate(bytesPerRow, AllocationOptions.Clean);
+ Span rowBuffer = rowBufferOwner.GetSpan();
+ for (int stripIndex = 0; stripIndex < stripOffsets.Length; stripIndex++)
+ {
+ cancellationToken.ThrowIfCancellationRequested();
+
+ int stripHeight = stripIndex < stripOffsets.Length - 1 || height % rowsPerStrip == 0
+ ? rowsPerStrip
+ : height % rowsPerStrip;
+
+ int top = rowsPerStrip * stripIndex;
+ if (top + stripHeight > height)
+ {
+ break;
+ }
+
+ ulong baseOffset = stripOffsets[stripIndex];
+ ulong available = stripByteCounts[stripIndex];
+ ulong required = (ulong)bytesPerRow * (ulong)stripHeight;
+ if (available < required)
+ {
+ break;
+ }
+
+ for (int r = 0; r < stripHeight; r++)
+ {
+ cancellationToken.ThrowIfCancellationRequested();
+
+ ulong rowOffset = baseOffset + ((ulong)r * (ulong)bytesPerRow);
+
+ // Use the NoneTiffCompression decompressor to read exactly one row.
+ none.Decompress(
+ this.inputStream,
+ rowOffset,
+ (ulong)bytesPerRow,
+ 1,
+ rowBuffer,
+ cancellationToken);
+
+ colorDecoder.Decode(rowBuffer, pixels, 0, top + r, width, 1);
+ }
+ }
+
+ return;
+ }
+
+ if (uncompressedStripSize > int.MaxValue)
+ {
+ TiffThrowHelper.ThrowNotSupported("Strips larger than Int32.MaxValue bytes are not supported for compressed images.");
+ }
+
+ using IMemoryOwner stripBuffer = this.memoryAllocator.Allocate((int)uncompressedStripSize, AllocationOptions.Clean);
+ Span stripBufferSpan = stripBuffer.GetSpan();
+
for (int stripIndex = 0; stripIndex < stripOffsets.Length; stripIndex++)
{
cancellationToken.ThrowIfCancellationRequested();
- int stripHeight = stripIndex < stripOffsets.Length - 1 || frame.Height % rowsPerStrip == 0
+ int stripHeight = stripIndex < stripOffsets.Length - 1 || height % rowsPerStrip == 0
? rowsPerStrip
- : frame.Height % rowsPerStrip;
+ : height % rowsPerStrip;
int top = rowsPerStrip * stripIndex;
- if (top + stripHeight > frame.Height)
+ if (top + stripHeight > height)
{
// Make sure we ignore any strips that are not needed for the image (if too many are present).
break;
@@ -493,7 +571,7 @@ private void DecodeStripsChunky(ImageFrame frame, int rowsPerStr
stripBufferSpan,
cancellationToken);
- colorDecoder.Decode(stripBufferSpan, pixels, 0, top, frame.Width, stripHeight);
+ colorDecoder.Decode(stripBufferSpan, pixels, 0, top, width, stripHeight);
}
}
@@ -753,7 +831,7 @@ private IMemoryOwner ConvertNumbers(Array array, out Span span)
/// The height for the desired pixel buffer.
/// The index of the plane for planar image configuration (or zero for chunky).
/// The size (in bytes) of the required pixel buffer.
- private int CalculateStripBufferSize(int width, int height, int plane = -1)
+ private ulong CalculateStripBufferSize(int width, int height, int plane = -1)
{
DebugGuard.MustBeLessThanOrEqualTo(plane, 3, nameof(plane));
@@ -786,8 +864,8 @@ private int CalculateStripBufferSize(int width, int height, int plane = -1)
}
}
- int bytesPerRow = ((width * bitsPerPixel) + 7) / 8;
- return bytesPerRow * height;
+ ulong bytesPerRow = (((ulong)width * (ulong)bitsPerPixel) + 7) / 8;
+ return bytesPerRow * (ulong)height;
}
///