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
106 changes: 92 additions & 14 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 @@ -403,8 +404,14 @@ private void DecodeStripsPlanar<TPixel>(ImageFrame<TPixel> frame, int rowsPerStr
{
for (int stripIndex = 0; stripIndex < stripBuffers.Length; stripIndex++)
{
int uncompressedStripSize = this.CalculateStripBufferSize(frame.Width, rowsPerStrip, stripIndex);
stripBuffers[stripIndex] = this.memoryAllocator.Allocate<byte>(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<byte>((int)uncompressedStripSize);
}

using TiffBaseDecompressor decompressor = this.CreateDecompressor<TPixel>(frame.Width, bitsPerPixel);
Expand Down Expand Up @@ -460,26 +467,97 @@ private void DecodeStripsChunky<TPixel>(ImageFrame<TPixel> 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<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>(frame.Width, bitsPerPixel);
int width = frame.Width;
int height = frame.Height;

using TiffBaseDecompressor decompressor = this.CreateDecompressor<TPixel>(width, bitsPerPixel);
TiffBaseColorDecoder<TPixel> colorDecoder = this.CreateChunkyColorDecoder<TPixel>();

// 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<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;

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++)
{
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;
Expand All @@ -493,7 +571,7 @@ private void DecodeStripsChunky<TPixel>(ImageFrame<TPixel> frame, int rowsPerStr
stripBufferSpan,
cancellationToken);

colorDecoder.Decode(stripBufferSpan, pixels, 0, top, frame.Width, stripHeight);
colorDecoder.Decode(stripBufferSpan, pixels, 0, top, width, stripHeight);
}
}

Expand Down Expand Up @@ -753,7 +831,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 @@ -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;
}

/// <summary>
Expand Down
Loading