Skip to content

Commit 91045ad

Browse files
Fix #2959
1 parent 8931ae5 commit 91045ad

File tree

2 files changed

+100
-22
lines changed

2 files changed

+100
-22
lines changed

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

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -28,20 +28,20 @@ protected TiffBaseDecompressor(MemoryAllocator memoryAllocator, int width, int b
2828
/// Decompresses image data into the supplied buffer.
2929
/// </summary>
3030
/// <param name="stream">The <see cref="Stream" /> to read image data from.</param>
31-
/// <param name="stripOffset">The strip offset of stream.</param>
32-
/// <param name="stripByteCount">The number of bytes to read from the input stream.</param>
31+
/// <param name="offset">The data offset within the stream.</param>
32+
/// <param name="count">The number of bytes to read from the input stream.</param>
3333
/// <param name="stripHeight">The height of the strip.</param>
3434
/// <param name="buffer">The output buffer for uncompressed data.</param>
3535
/// <param name="cancellationToken">The token to monitor cancellation.</param>
36-
public void Decompress(BufferedReadStream stream, ulong stripOffset, ulong stripByteCount, int stripHeight, Span<byte> buffer, CancellationToken cancellationToken)
36+
public void Decompress(BufferedReadStream stream, ulong offset, ulong count, int stripHeight, Span<byte> buffer, CancellationToken cancellationToken)
3737
{
38-
DebugGuard.MustBeLessThanOrEqualTo(stripOffset, (ulong)long.MaxValue, nameof(stripOffset));
39-
DebugGuard.MustBeLessThanOrEqualTo(stripByteCount, (ulong)long.MaxValue, nameof(stripByteCount));
38+
DebugGuard.MustBeLessThanOrEqualTo(offset, (ulong)long.MaxValue, nameof(offset));
39+
DebugGuard.MustBeLessThanOrEqualTo(count, (ulong)int.MaxValue, nameof(count));
4040

41-
stream.Seek((long)stripOffset, SeekOrigin.Begin);
42-
this.Decompress(stream, (int)stripByteCount, stripHeight, buffer, cancellationToken);
41+
stream.Seek((long)offset, SeekOrigin.Begin);
42+
this.Decompress(stream, (int)count, stripHeight, buffer, cancellationToken);
4343

44-
if ((long)stripOffset + (long)stripByteCount < stream.Position)
44+
if ((long)offset + (long)count < stream.Position)
4545
{
4646
TiffThrowHelper.ThrowImageFormatException("Out of range when reading a strip.");
4747
}

src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs

Lines changed: 92 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
using System.Buffers;
66
using System.Runtime.CompilerServices;
77
using SixLabors.ImageSharp.Formats.Tiff.Compression;
8+
using SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors;
89
using SixLabors.ImageSharp.Formats.Tiff.Constants;
910
using SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation;
1011
using SixLabors.ImageSharp.IO;
@@ -403,8 +404,14 @@ private void DecodeStripsPlanar<TPixel>(ImageFrame<TPixel> frame, int rowsPerStr
403404
{
404405
for (int stripIndex = 0; stripIndex < stripBuffers.Length; stripIndex++)
405406
{
406-
int uncompressedStripSize = this.CalculateStripBufferSize(frame.Width, rowsPerStrip, stripIndex);
407-
stripBuffers[stripIndex] = this.memoryAllocator.Allocate<byte>(uncompressedStripSize);
407+
ulong uncompressedStripSize = this.CalculateStripBufferSize(frame.Width, rowsPerStrip, stripIndex);
408+
409+
if (uncompressedStripSize > int.MaxValue)
410+
{
411+
TiffThrowHelper.ThrowNotSupported("Strips larger than Int32.MaxValue bytes are not supported for compressed images.");
412+
}
413+
414+
stripBuffers[stripIndex] = this.memoryAllocator.Allocate<byte>((int)uncompressedStripSize);
408415
}
409416

410417
using TiffBaseDecompressor decompressor = this.CreateDecompressor<TPixel>(frame.Width, bitsPerPixel);
@@ -460,26 +467,97 @@ private void DecodeStripsChunky<TPixel>(ImageFrame<TPixel> frame, int rowsPerStr
460467
rowsPerStrip = frame.Height;
461468
}
462469

463-
int uncompressedStripSize = this.CalculateStripBufferSize(frame.Width, rowsPerStrip);
470+
ulong uncompressedStripSize = this.CalculateStripBufferSize(frame.Width, rowsPerStrip);
464471
int bitsPerPixel = this.BitsPerPixel;
465-
466-
using IMemoryOwner<byte> stripBuffer = this.memoryAllocator.Allocate<byte>(uncompressedStripSize, AllocationOptions.Clean);
467-
Span<byte> stripBufferSpan = stripBuffer.GetSpan();
468472
Buffer2D<TPixel> pixels = frame.PixelBuffer;
469473

470-
using TiffBaseDecompressor decompressor = this.CreateDecompressor<TPixel>(frame.Width, bitsPerPixel);
474+
int width = frame.Width;
475+
int height = frame.Height;
476+
477+
using TiffBaseDecompressor decompressor = this.CreateDecompressor<TPixel>(width, bitsPerPixel);
471478
TiffBaseColorDecoder<TPixel> colorDecoder = this.CreateChunkyColorDecoder<TPixel>();
472479

480+
// There exists in this world TIFF files with uncompressed strips larger than Int32.MaxValue.
481+
// We can read them, but we cannot allocate a buffer that large to hold the uncompressed data.
482+
// In this scenario we fall back to reading and decoding one row at a time.
483+
//
484+
// The NoneTiffCompression decompressor can be used to read individual rows since we have
485+
// a guarantee that each row required the same number of bytes.
486+
if (decompressor is NoneTiffCompression none && uncompressedStripSize > int.MaxValue)
487+
{
488+
ulong bytesPerRowU = this.CalculateStripBufferSize(frame.Width, 1);
489+
490+
// This should never happen, but we check just to be sure.
491+
if (bytesPerRowU > int.MaxValue)
492+
{
493+
TiffThrowHelper.ThrowNotSupported("Strips larger than Int32.MaxValue bytes are not supported for compressed images.");
494+
}
495+
496+
int bytesPerRow = (int)bytesPerRowU;
497+
using IMemoryOwner<byte> rowBufferOwner = this.memoryAllocator.Allocate<byte>(bytesPerRow, AllocationOptions.Clean);
498+
Span<byte> rowBuffer = rowBufferOwner.GetSpan();
499+
for (int stripIndex = 0; stripIndex < stripOffsets.Length; stripIndex++)
500+
{
501+
cancellationToken.ThrowIfCancellationRequested();
502+
503+
int stripHeight = stripIndex < stripOffsets.Length - 1 || height % rowsPerStrip == 0
504+
? rowsPerStrip
505+
: height % rowsPerStrip;
506+
507+
int top = rowsPerStrip * stripIndex;
508+
if (top + stripHeight > height)
509+
{
510+
break;
511+
}
512+
513+
ulong baseOffset = stripOffsets[stripIndex];
514+
ulong available = stripByteCounts[stripIndex];
515+
ulong required = (ulong)bytesPerRow * (ulong)stripHeight;
516+
if (available < required)
517+
{
518+
break;
519+
}
520+
521+
for (int r = 0; r < stripHeight; r++)
522+
{
523+
cancellationToken.ThrowIfCancellationRequested();
524+
525+
ulong rowOffset = baseOffset + ((ulong)r * (ulong)bytesPerRow);
526+
527+
// Use the NoneTiffCompression decompressor to read exactly one row.
528+
none.Decompress(
529+
this.inputStream,
530+
rowOffset,
531+
(ulong)bytesPerRow,
532+
1,
533+
rowBuffer,
534+
cancellationToken);
535+
536+
colorDecoder.Decode(rowBuffer, pixels, 0, top + r, width, 1);
537+
}
538+
}
539+
540+
return;
541+
}
542+
543+
if (uncompressedStripSize > int.MaxValue)
544+
{
545+
TiffThrowHelper.ThrowNotSupported("Strips larger than Int32.MaxValue bytes are not supported for compressed images.");
546+
}
547+
548+
using IMemoryOwner<byte> stripBuffer = this.memoryAllocator.Allocate<byte>((int)uncompressedStripSize, AllocationOptions.Clean);
549+
Span<byte> stripBufferSpan = stripBuffer.GetSpan();
550+
473551
for (int stripIndex = 0; stripIndex < stripOffsets.Length; stripIndex++)
474552
{
475553
cancellationToken.ThrowIfCancellationRequested();
476554

477-
int stripHeight = stripIndex < stripOffsets.Length - 1 || frame.Height % rowsPerStrip == 0
555+
int stripHeight = stripIndex < stripOffsets.Length - 1 || height % rowsPerStrip == 0
478556
? rowsPerStrip
479-
: frame.Height % rowsPerStrip;
557+
: height % rowsPerStrip;
480558

481559
int top = rowsPerStrip * stripIndex;
482-
if (top + stripHeight > frame.Height)
560+
if (top + stripHeight > height)
483561
{
484562
// Make sure we ignore any strips that are not needed for the image (if too many are present).
485563
break;
@@ -493,7 +571,7 @@ private void DecodeStripsChunky<TPixel>(ImageFrame<TPixel> frame, int rowsPerStr
493571
stripBufferSpan,
494572
cancellationToken);
495573

496-
colorDecoder.Decode(stripBufferSpan, pixels, 0, top, frame.Width, stripHeight);
574+
colorDecoder.Decode(stripBufferSpan, pixels, 0, top, width, stripHeight);
497575
}
498576
}
499577

@@ -753,7 +831,7 @@ private IMemoryOwner<ulong> ConvertNumbers(Array array, out Span<ulong> span)
753831
/// <param name="height">The height for the desired pixel buffer.</param>
754832
/// <param name="plane">The index of the plane for planar image configuration (or zero for chunky).</param>
755833
/// <returns>The size (in bytes) of the required pixel buffer.</returns>
756-
private int CalculateStripBufferSize(int width, int height, int plane = -1)
834+
private ulong CalculateStripBufferSize(int width, int height, int plane = -1)
757835
{
758836
DebugGuard.MustBeLessThanOrEqualTo(plane, 3, nameof(plane));
759837

@@ -786,8 +864,8 @@ private int CalculateStripBufferSize(int width, int height, int plane = -1)
786864
}
787865
}
788866

789-
int bytesPerRow = ((width * bitsPerPixel) + 7) / 8;
790-
return bytesPerRow * height;
867+
ulong bytesPerRow = (((ulong)width * (ulong)bitsPerPixel) + 7) / 8;
868+
return bytesPerRow * (ulong)height;
791869
}
792870

793871
/// <summary>

0 commit comments

Comments
 (0)