Browse Source

Fix #2959

pull/3003/head
James Jackson-South 4 months ago
parent
commit
c3e990fcfc
  1. 16
      src/ImageSharp/Formats/Tiff/Compression/TiffBaseDecompressor.cs
  2. 95
      src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs

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

@ -28,20 +28,20 @@ internal abstract class TiffBaseDecompressor : TiffBaseCompression
/// 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.");
}

95
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;
@ -441,8 +442,14 @@ internal class TiffDecoderCore : ImageDecoderCore
{
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);
@ -507,15 +514,83 @@ internal class TiffDecoderCore : ImageDecoderCore
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.");
}
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)
{
TiffThrowHelper.ThrowImageFormatException("StripByteCounts is smaller than required for uncompressed data.");
}
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++)
{
@ -808,7 +883,7 @@ internal class TiffDecoderCore : ImageDecoderCore
/// <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));
@ -841,8 +916,8 @@ internal class TiffDecoderCore : ImageDecoderCore
}
}
int bytesPerRow = ((width * bitsPerPixel) + 7) / 8;
return bytesPerRow * height;
ulong bytesPerRow = (((ulong)width * (ulong)bitsPerPixel) + 7) / 8;
return bytesPerRow * (ulong)height;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]

Loading…
Cancel
Save