Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 8 additions & 8 deletions src/ImageSharp/Formats/Tiff/Compression/TiffBaseDecompressor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,20 +28,20 @@ protected TiffBaseDecompressor(MemoryAllocator memoryAllocator, int width, int b
/// Decompresses image data into the supplied buffer.
/// </summary>
/// <param name="stream">The <see cref="Stream" /> to read image data from.</param>
/// <param name="stripOffset">The strip offset of stream.</param>
/// <param name="stripByteCount">The number of bytes to read from the input stream.</param>
/// <param name="offset">The data offset within the stream.</param>
/// <param name="count">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>
/// <param name="cancellationToken">The token to monitor cancellation.</param>
public void Decompress(BufferedReadStream stream, ulong stripOffset, ulong stripByteCount, int stripHeight, Span<byte> buffer, CancellationToken cancellationToken)
public void Decompress(BufferedReadStream stream, ulong offset, ulong count, int stripHeight, Span<byte> 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.");
}
Expand Down
95 changes: 85 additions & 10 deletions src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -441,8 +442,14 @@ private void DecodeStripsPlanar<TPixel>(
{
for (int stripIndex = 0; stripIndex < stripBuffers.Length; stripIndex++)
{
int uncompressedStripSize = this.CalculateStripBufferSize(width, rowsPerStrip, stripIndex);
stripBuffers[stripIndex] = this.memoryAllocator.Allocate<byte>(uncompressedStripSize);
ulong uncompressedStripSize = this.CalculateStripBufferSize(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<byte>((int)uncompressedStripSize);
}

using TiffBaseDecompressor decompressor = this.CreateDecompressor<TPixel>(width, bitsPerPixel, frame.Metadata);
Expand Down Expand Up @@ -507,15 +514,83 @@ private void DecodeStripsChunky<TPixel>(
rowsPerStrip = height;
}

int uncompressedStripSize = this.CalculateStripBufferSize(width, rowsPerStrip);
ulong uncompressedStripSize = this.CalculateStripBufferSize(width, rowsPerStrip);
int bitsPerPixel = this.BitsPerPixel;

using IMemoryOwner<byte> stripBuffer = this.memoryAllocator.Allocate<byte>(uncompressedStripSize, AllocationOptions.Clean);
Span<byte> stripBufferSpan = stripBuffer.GetSpan();
Buffer2D<TPixel> pixels = frame.PixelBuffer;

using TiffBaseDecompressor decompressor = this.CreateDecompressor<TPixel>(width, bitsPerPixel, frame.Metadata);
TiffBaseColorDecoder<TPixel> colorDecoder = this.CreateChunkyColorDecoder<TPixel>();
Buffer2D<TPixel> pixels = frame.PixelBuffer;

// 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(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.");
Copy link

Copilot AI Oct 24, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The error message states 'compressed images' but this check is within a code path specifically handling uncompressed images (NoneTiffCompression). The message should say 'uncompressed images' or 'rows' to accurately reflect the context.

Suggested change
TiffThrowHelper.ThrowNotSupported("Strips larger than Int32.MaxValue bytes are not supported for compressed images.");
TiffThrowHelper.ThrowNotSupported("Strips larger than Int32.MaxValue bytes are not supported for uncompressed images.");

Copilot uses AI. Check for mistakes.
}

int bytesPerRow = (int)bytesPerRowU;
using IMemoryOwner<byte> rowBufferOwner = this.memoryAllocator.Allocate<byte>(bytesPerRow, AllocationOptions.Clean);
Span<byte> 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;
Comment on lines +547 to +549
Copy link

Copilot AI Oct 24, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nitpick] This strip height calculation logic is duplicated from the standard path below (around line 603). Consider extracting this into a helper method to reduce code duplication and improve maintainability.

Copilot uses AI. Check for mistakes.

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<byte> stripBuffer = this.memoryAllocator.Allocate<byte>((int)uncompressedStripSize, AllocationOptions.Clean);
Span<byte> stripBufferSpan = stripBuffer.GetSpan();

for (int stripIndex = 0; stripIndex < stripOffsets.Length; stripIndex++)
{
Expand Down Expand Up @@ -808,7 +883,7 @@ private IMemoryOwner<ulong> ConvertNumbers(Array array, out Span<ulong> span)
/// <param name="height">The height for the desired pixel buffer.</param>
/// <param name="plane">The index of the plane for planar image configuration (or zero for chunky).</param>
/// <returns>The size (in bytes) of the required pixel buffer.</returns>
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));

Expand Down Expand Up @@ -841,8 +916,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;
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
Expand Down
Loading