Browse Source

Handle partial EXR scanline blocks

pull/3187/head
James Jackson-South 4 weeks ago
parent
commit
6ed2a27521
  1. 2
      src/ImageSharp/Formats/Exr/Compression/Decompressors/B44ExrCompression.cs
  2. 6
      src/ImageSharp/Formats/Exr/Compression/Decompressors/NoneExrCompression.cs
  3. 11
      src/ImageSharp/Formats/Exr/Compression/Decompressors/Pxr24Compression.cs
  4. 2
      src/ImageSharp/Formats/Exr/Compression/Decompressors/RunLengthExrCompression.cs
  5. 9
      src/ImageSharp/Formats/Exr/Compression/Decompressors/ZipExrCompression.cs
  6. 7
      src/ImageSharp/Formats/Exr/Compression/ExrBaseDecompressor.cs
  7. 18
      src/ImageSharp/Formats/Exr/ExrDecoderCore.cs
  8. 2
      tests/ImageSharp.Tests/Formats/Exr/ExrZipDecoderTests.cs

2
src/ImageSharp/Formats/Exr/Compression/Decompressors/B44ExrCompression.cs

@ -39,7 +39,7 @@ internal class B44ExrCompression : ExrBaseDecompressor
}
/// <inheritdoc/>
public override void Decompress(BufferedReadStream stream, uint compressedBytes, Span<byte> buffer)
public override void Decompress(BufferedReadStream stream, uint compressedBytes, uint uncompressedBytes, Span<byte> buffer)
{
Span<ushort> outputBuffer = MemoryMarshal.Cast<byte, ushort>(buffer);
Span<ushort> decompressed = this.tmpBuffer.GetSpan();

6
src/ImageSharp/Formats/Exr/Compression/Decompressors/NoneExrCompression.cs

@ -25,10 +25,10 @@ internal class NoneExrCompression : ExrBaseDecompressor
}
/// <inheritdoc/>
public override void Decompress(BufferedReadStream stream, uint compressedBytes, Span<byte> buffer)
public override void Decompress(BufferedReadStream stream, uint compressedBytes, uint uncompressedBytes, Span<byte> buffer)
{
int bytesRead = stream.Read(buffer, 0, Math.Min(buffer.Length, (int)this.BytesPerBlock));
if (bytesRead != (int)this.BytesPerBlock)
int bytesRead = stream.Read(buffer[..(int)uncompressedBytes]);
if (bytesRead != uncompressedBytes)
{
ExrThrowHelper.ThrowInvalidImageContentException("Could not read enough pixel data from the stream!");
}

11
src/ImageSharp/Formats/Exr/Compression/Decompressors/Pxr24Compression.cs

@ -39,19 +39,20 @@ internal class Pxr24Compression : ExrBaseDecompressor
}
/// <inheritdoc/>
public override void Decompress(BufferedReadStream stream, uint compressedBytes, Span<byte> buffer)
public override void Decompress(BufferedReadStream stream, uint compressedBytes, uint uncompressedBytes, Span<byte> buffer)
{
Span<byte> uncompressed = this.tmpBuffer.GetSpan();
uint rowCount = uncompressedBytes / this.BytesPerRow;
uint packedBytes = this.pixelType == ExrPixelType.Float ? (uncompressedBytes / 4) * 3 : uncompressedBytes;
Span<byte> uncompressed = this.tmpBuffer.GetSpan()[..(int)packedBytes];
Span<ushort> outputBufferHalf = MemoryMarshal.Cast<byte, ushort>(buffer);
Span<uint> outputBufferFloat = MemoryMarshal.Cast<byte, uint>(buffer);
Span<uint> outputBufferUint = MemoryMarshal.Cast<byte, uint>(buffer);
uint uncompressedBytes = this.BytesPerBlock;
UndoZipCompression(stream, compressedBytes, uncompressed, uncompressedBytes);
UndoZipCompression(stream, compressedBytes, uncompressed, packedBytes);
int lastIn = 0;
int outputOffset = 0;
for (int y = 0; y < this.RowsPerBlock; y++)
for (uint y = 0; y < rowCount; y++)
{
for (int c = 0; c < this.channelCount; c++)
{

2
src/ImageSharp/Formats/Exr/Compression/Decompressors/RunLengthExrCompression.cs

@ -26,7 +26,7 @@ internal class RunLengthExrCompression : ExrBaseDecompressor
: base(allocator, bytesPerBlock, bytesPerRow, rowsPerBlock, width) => this.tmpBuffer = allocator.Allocate<byte>((int)bytesPerBlock);
/// <inheritdoc/>
public override void Decompress(BufferedReadStream stream, uint compressedBytes, Span<byte> buffer)
public override void Decompress(BufferedReadStream stream, uint compressedBytes, uint uncompressedBytes, Span<byte> buffer)
{
Span<byte> uncompressed = this.tmpBuffer.GetSpan();
int maxLength = (int)this.BytesPerBlock;

9
src/ImageSharp/Formats/Exr/Compression/Decompressors/ZipExrCompression.cs

@ -26,15 +26,14 @@ internal class ZipExrCompression : ExrBaseDecompressor
: base(allocator, bytesPerBlock, bytesPerRow, rowsPerBlock, width) => this.tmpBuffer = allocator.Allocate<byte>((int)bytesPerBlock);
/// <inheritdoc/>
public override void Decompress(BufferedReadStream stream, uint compressedBytes, Span<byte> buffer)
public override void Decompress(BufferedReadStream stream, uint compressedBytes, uint uncompressedBytes, Span<byte> buffer)
{
Span<byte> uncompressed = this.tmpBuffer.GetSpan();
Span<byte> uncompressed = this.tmpBuffer.GetSpan()[..(int)uncompressedBytes];
uint uncompressedBytes = (uint)buffer.Length;
int totalRead = UndoZipCompression(stream, compressedBytes, uncompressed, uncompressedBytes);
Reconstruct(uncompressed, (uint)totalRead);
Interleave(uncompressed, (uint)totalRead, buffer);
Reconstruct(uncompressed, uncompressedBytes);
Interleave(uncompressed, uncompressedBytes, buffer);
}
/// <inheritdoc/>

7
src/ImageSharp/Formats/Exr/Compression/ExrBaseDecompressor.cs

@ -31,8 +31,9 @@ internal abstract class ExrBaseDecompressor : ExrBaseCompression
/// </summary>
/// <param name="stream">The buffered stream to decompress.</param>
/// <param name="compressedBytes">The compressed bytes.</param>
/// <param name="uncompressedBytes">The expected byte count for the current block.</param>
/// <param name="buffer">The buffer to write the decompressed data to.</param>
public abstract void Decompress(BufferedReadStream stream, uint compressedBytes, Span<byte> buffer);
public abstract void Decompress(BufferedReadStream stream, uint compressedBytes, uint uncompressedBytes, Span<byte> buffer);
/// <summary>
/// Decompresses zip compressed data.
@ -67,9 +68,9 @@ internal abstract class ExrBaseDecompressor : ExrBaseCompression
totalRead += bytesRead;
}
if (totalRead != uncompressedBytes)
if (totalRead != uncompressedBytes || dataStream.ReadByte() != -1)
{
ExrThrowHelper.ThrowInvalidImageContentException("Could not read enough data for zip compressed EXR image data!");
ExrThrowHelper.ThrowInvalidImageContentException("ZIP compressed EXR block has an invalid decompressed length.");
}
return totalRead;

18
src/ImageSharp/Formats/Exr/ExrDecoderCore.cs

@ -193,9 +193,16 @@ internal sealed class ExrDecoderCore : ImageDecoderCore
this.ValidateChunkOffset(rowOffset, stream);
stream.Position = (long)rowOffset;
uint rowStartIndex = this.ReadUnsignedInteger(stream);
if (rowStartIndex >= height)
{
ExrThrowHelper.ThrowInvalidImageContentException("EXR chunk row index is outside the data window.");
}
uint compressedBytesCount = this.ReadUnsignedInteger(stream);
decompressor.Decompress(stream, compressedBytesCount, decompressedPixelData);
uint rowsInBlock = Math.Min(rowsPerBlock, (uint)height - rowStartIndex);
uint uncompressedBytesCount = (uint)(bytesPerRow * rowsInBlock);
decompressor.Decompress(stream, compressedBytesCount, uncompressedBytesCount, decompressedPixelData);
int offset = 0;
for (uint rowIndex = rowStartIndex; rowIndex < rowStartIndex + rowsPerBlock && rowIndex < height; rowIndex++)
@ -274,9 +281,16 @@ internal sealed class ExrDecoderCore : ImageDecoderCore
this.ValidateChunkOffset(rowOffset, stream);
stream.Position = (long)rowOffset;
uint rowStartIndex = this.ReadUnsignedInteger(stream);
if (rowStartIndex >= height)
{
ExrThrowHelper.ThrowInvalidImageContentException("EXR chunk row index is outside the data window.");
}
uint compressedBytesCount = this.ReadUnsignedInteger(stream);
decompressor.Decompress(stream, compressedBytesCount, decompressedPixelData);
uint rowsInBlock = Math.Min(rowsPerBlock, (uint)height - rowStartIndex);
uint uncompressedBytesCount = (uint)(bytesPerRow * rowsInBlock);
decompressor.Decompress(stream, compressedBytesCount, uncompressedBytesCount, decompressedPixelData);
int offset = 0;
for (uint rowIndex = rowStartIndex; rowIndex < rowStartIndex + rowsPerBlock && rowIndex < height; rowIndex++)

2
tests/ImageSharp.Tests/Formats/Exr/ExrZipDecoderTests.cs

@ -33,7 +33,7 @@ public class ExrZipDecoderTests
private static byte[] BuildExr(byte[] compressed)
{
const int width = 64;
const int width = 256;
const int height = 1;
using MemoryStream output = new();

Loading…
Cancel
Save