Browse Source

Merge pull request #3124 from SixLabors/bp/decodePxr24

Add support for decoding PXR24 compressed Exr images
pull/3127/head
James Jackson-South 2 months ago
committed by GitHub
parent
commit
936a65bdbf
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 6
      src/ImageSharp/Formats/Exr/Compression/Compressors/NoneExrCompressor.cs
  2. 6
      src/ImageSharp/Formats/Exr/Compression/Compressors/ZipExrCompressor.cs
  3. 49
      src/ImageSharp/Formats/Exr/Compression/Decompressors/B44ExrCompression.cs
  4. 6
      src/ImageSharp/Formats/Exr/Compression/Decompressors/NoneExrCompression.cs
  5. 153
      src/ImageSharp/Formats/Exr/Compression/Decompressors/Pxr24Compression.cs
  6. 8
      src/ImageSharp/Formats/Exr/Compression/Decompressors/RunLengthExrCompression.cs
  7. 37
      src/ImageSharp/Formats/Exr/Compression/Decompressors/ZipExrCompression.cs
  8. 11
      src/ImageSharp/Formats/Exr/Compression/ExrBaseCompression.cs
  9. 49
      src/ImageSharp/Formats/Exr/Compression/ExrBaseDecompressor.cs
  10. 10
      src/ImageSharp/Formats/Exr/Compression/ExrCompressorFactory.cs
  11. 13
      src/ImageSharp/Formats/Exr/Compression/ExrDecompressorFactory.cs
  12. 6
      src/ImageSharp/Formats/Exr/ExrBaseCompressor.cs
  13. 22
      src/ImageSharp/Formats/Exr/ExrDecoderCore.cs
  14. 4
      src/ImageSharp/Formats/Exr/ExrEncoderCore.cs
  15. 26
      tests/ImageSharp.Tests/Formats/Exr/ExrDecoderTests.cs
  16. 5
      tests/ImageSharp.Tests/TestImages.cs
  17. 3
      tests/Images/External/ReferenceOutput/ExrDecoderTests/ExrDecoder_CanDecode_Pxr24Compressed_ExrPixelType_Uint_Rgba32_Calliphora_uint_pxr24.png
  18. 3
      tests/Images/Input/Exr/Calliphora_float_pxr24.exr
  19. 3
      tests/Images/Input/Exr/Calliphora_float_uncompressed.exr
  20. 3
      tests/Images/Input/Exr/Calliphora_half_pxr24.exr
  21. 3
      tests/Images/Input/Exr/Calliphora_uint_pxr24.exr
  22. 3
      tests/Images/Input/Exr/rgb_float32_uncompressed.exr

6
src/ImageSharp/Formats/Exr/Compression/Compressors/NoneExrCompressor.cs

@ -17,8 +17,10 @@ internal class NoneExrCompressor : ExrBaseCompressor
/// <param name="allocator">The memory allocator.</param>
/// <param name="bytesPerBlock">Bytes per row block.</param>
/// <param name="bytesPerRow">Bytes per pixel row.</param>
public NoneExrCompressor(Stream output, MemoryAllocator allocator, uint bytesPerBlock, uint bytesPerRow)
: base(output, allocator, bytesPerBlock, bytesPerRow)
/// <param name="rowsPerBlock">The pixel rows per block.</param>
/// <param name="width">The witdh of one row in pixels.</param>
public NoneExrCompressor(Stream output, MemoryAllocator allocator, uint bytesPerBlock, uint bytesPerRow, uint rowsPerBlock, int width)
: base(output, allocator, bytesPerBlock, bytesPerRow, rowsPerBlock, width)
{
}

6
src/ImageSharp/Formats/Exr/Compression/Compressors/ZipExrCompressor.cs

@ -24,9 +24,11 @@ internal class ZipExrCompressor : ExrBaseCompressor
/// <param name="allocator">The memory allocator.</param>
/// <param name="bytesPerBlock">The bytes per block.</param>
/// <param name="bytesPerRow">The bytes per row.</param>
/// <param name="rowsPerBlock">The pixel rows per block.</param>
/// <param name="width">The witdh of one row in pixels.</param>
/// <param name="compressionLevel">The compression level for deflate compression.</param>
public ZipExrCompressor(Stream output, MemoryAllocator allocator, uint bytesPerBlock, uint bytesPerRow, DeflateCompressionLevel compressionLevel)
: base(output, allocator, bytesPerBlock, bytesPerRow)
public ZipExrCompressor(Stream output, MemoryAllocator allocator, uint bytesPerBlock, uint bytesPerRow, uint rowsPerBlock, int width, DeflateCompressionLevel compressionLevel)
: base(output, allocator, bytesPerBlock, bytesPerRow, rowsPerBlock, width)
{
this.compressionLevel = compressionLevel;
this.buffer = allocator.Allocate<byte>((int)bytesPerBlock);

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

@ -13,10 +13,6 @@ namespace SixLabors.ImageSharp.Formats.Exr.Compression.Decompressors;
/// </summary>
internal class B44ExrCompression : ExrBaseDecompressor
{
private readonly int width;
private readonly uint rowsPerBlock;
private readonly int channelCount;
private readonly byte[] scratch = new byte[14];
@ -31,14 +27,12 @@ internal class B44ExrCompression : ExrBaseDecompressor
/// <param name="allocator">The memory allocator.</param>
/// <param name="bytesPerBlock">The bytes per pixel row block.</param>
/// <param name="bytesPerRow">The bytes per row.</param>
/// <param name="rowsPerBlock">The rows per block.</param>
/// <param name="rowsPerBlock">The pixel rows per block.</param>
/// <param name="width">The width of a pixel row in pixels.</param>
/// <param name="channelCount">The number of channels of the image.</param>
public B44ExrCompression(MemoryAllocator allocator, uint bytesPerBlock, uint bytesPerRow, uint rowsPerBlock, int width, int channelCount)
: base(allocator, bytesPerBlock, bytesPerRow)
: base(allocator, bytesPerBlock, bytesPerRow, rowsPerBlock, width)
{
this.width = width;
this.rowsPerBlock = rowsPerBlock;
this.channelCount = channelCount;
this.tmpBuffer = allocator.Allocate<ushort>((int)(width * rowsPerBlock * channelCount));
}
@ -52,19 +46,19 @@ internal class B44ExrCompression : ExrBaseDecompressor
int bytesLeft = (int)compressedBytes;
for (int i = 0; i < this.channelCount && bytesLeft > 0; i++)
{
for (int y = 0; y < this.rowsPerBlock; y += 4)
for (int y = 0; y < this.RowsPerBlock; y += 4)
{
Span<ushort> row0 = decompressed.Slice(outputOffset, this.width);
outputOffset += this.width;
Span<ushort> row1 = decompressed.Slice(outputOffset, this.width);
outputOffset += this.width;
Span<ushort> row2 = decompressed.Slice(outputOffset, this.width);
outputOffset += this.width;
Span<ushort> row3 = decompressed.Slice(outputOffset, this.width);
outputOffset += this.width;
Span<ushort> row0 = decompressed.Slice(outputOffset, this.Width);
outputOffset += this.Width;
Span<ushort> row1 = decompressed.Slice(outputOffset, this.Width);
outputOffset += this.Width;
Span<ushort> row2 = decompressed.Slice(outputOffset, this.Width);
outputOffset += this.Width;
Span<ushort> row3 = decompressed.Slice(outputOffset, this.Width);
outputOffset += this.Width;
int rowOffset = 0;
for (int x = 0; x < this.width && bytesLeft > 0; x += 4)
for (int x = 0; x < this.Width && bytesLeft > 0; x += 4)
{
int bytesRead = stream.Read(this.scratch, 0, 3);
if (bytesRead == 0)
@ -72,6 +66,7 @@ internal class B44ExrCompression : ExrBaseDecompressor
ExrThrowHelper.ThrowInvalidImageContentException("Could not read enough data from the stream!");
}
// Check if 3-byte encoded flat field.
if (this.scratch[2] >= 13 << 2)
{
Unpack3(this.scratch, this.s);
@ -89,8 +84,8 @@ internal class B44ExrCompression : ExrBaseDecompressor
bytesLeft -= 14;
}
int n = x + 3 < this.width ? 4 : this.width - x;
if (y + 3 < this.rowsPerBlock)
int n = x + 3 < this.Width ? 4 : this.Width - x;
if (y + 3 < this.RowsPerBlock)
{
this.s.AsSpan(0, n).CopyTo(row0[rowOffset..]);
this.s.AsSpan(4, n).CopyTo(row1[rowOffset..]);
@ -100,12 +95,12 @@ internal class B44ExrCompression : ExrBaseDecompressor
else
{
this.s.AsSpan(0, n).CopyTo(row0[rowOffset..]);
if (y + 1 < this.rowsPerBlock)
if (y + 1 < this.RowsPerBlock)
{
this.s.AsSpan(4, n).CopyTo(row1[rowOffset..]);
}
if (y + 2 < this.rowsPerBlock)
if (y + 2 < this.RowsPerBlock)
{
this.s.AsSpan(8, n).CopyTo(row2[rowOffset..]);
}
@ -124,16 +119,16 @@ internal class B44ExrCompression : ExrBaseDecompressor
// Rearrange the decompressed data such that the data for each scan line form a contiguous block.
int offsetDecompressed = 0;
int offsetOutput = 0;
int blockSize = (int)(this.width * this.rowsPerBlock);
for (int y = 0; y < this.rowsPerBlock; y++)
int blockSize = (int)(this.Width * this.RowsPerBlock);
for (int y = 0; y < this.RowsPerBlock; y++)
{
for (int i = 0; i < this.channelCount; i++)
{
decompressed.Slice(offsetDecompressed + (i * blockSize), this.width).CopyTo(outputBuffer[offsetOutput..]);
offsetOutput += this.width;
decompressed.Slice(offsetDecompressed + (i * blockSize), this.Width).CopyTo(outputBuffer[offsetOutput..]);
offsetOutput += this.Width;
}
offsetDecompressed += this.width;
offsetDecompressed += this.Width;
}
}

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

@ -17,8 +17,10 @@ internal class NoneExrCompression : ExrBaseDecompressor
/// <param name="allocator">The memory allocator.</param>
/// <param name="bytesPerBlock">The bytes per pixel row block.</param>
/// <param name="bytesPerRow">The bytes per pixel row.</param>
public NoneExrCompression(MemoryAllocator allocator, uint bytesPerBlock, uint bytesPerRow)
: base(allocator, bytesPerBlock, bytesPerRow)
/// <param name="rowsPerBlock">The pixel rows per block.</param>
/// <param name="width">The number of pixels per row.</param>
public NoneExrCompression(MemoryAllocator allocator, uint bytesPerBlock, uint bytesPerRow, uint rowsPerBlock, int width)
: base(allocator, bytesPerBlock, bytesPerRow, rowsPerBlock, width)
{
}

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

@ -0,0 +1,153 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System.Buffers;
using System.Runtime.InteropServices;
using SixLabors.ImageSharp.Formats.Exr.Constants;
using SixLabors.ImageSharp.IO;
using SixLabors.ImageSharp.Memory;
namespace SixLabors.ImageSharp.Formats.Exr.Compression.Decompressors;
/// <summary>
/// Implementation of PXR24 decompressor for EXR image data.
/// </summary>
internal class Pxr24Compression : ExrBaseDecompressor
{
private readonly IMemoryOwner<byte> tmpBuffer;
private readonly int channelCount;
private readonly ExrPixelType pixelType;
/// <summary>
/// Initializes a new instance of the <see cref="Pxr24Compression" /> class.
/// </summary>
/// <param name="allocator">The memory allocator.</param>
/// <param name="bytesPerBlock">The bytes per pixel row block.</param>
/// <param name="bytesPerRow">The bytes per pixel row.</param>
/// <param name="rowsPerBlock">The pixel rows per block.</param>
/// <param name="width">The witdh of one row in pixels.</param>
/// <param name="channelCount">The number of channels for a pixel.</param>
/// <param name="pixelType">The pixel type.</param>
public Pxr24Compression(MemoryAllocator allocator, uint bytesPerBlock, uint bytesPerRow, uint rowsPerBlock, int width, int channelCount, ExrPixelType pixelType)
: base(allocator, bytesPerBlock, bytesPerRow, rowsPerBlock, width)
{
this.tmpBuffer = allocator.Allocate<byte>((int)bytesPerBlock);
this.channelCount = channelCount;
this.pixelType = pixelType;
}
/// <inheritdoc/>
public override void Decompress(BufferedReadStream stream, uint compressedBytes, Span<byte> buffer)
{
Span<byte> uncompressed = this.tmpBuffer.GetSpan();
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);
int lastIn = 0;
int outputOffset = 0;
for (int y = 0; y < this.RowsPerBlock; y++)
{
for (int c = 0; c < this.channelCount; c++)
{
switch (this.pixelType)
{
case ExrPixelType.UnsignedInt:
{
int offsetT0 = lastIn;
lastIn += this.Width;
int offsetT1 = lastIn;
lastIn += this.Width;
int offsetT2 = lastIn;
lastIn += this.Width;
int offsetT3 = lastIn;
lastIn += this.Width;
uint pixel = 0;
for (int x = 0; x < this.Width; x++)
{
uint t0 = uncompressed[offsetT0];
uint t1 = uncompressed[offsetT1];
uint t2 = uncompressed[offsetT2];
uint t3 = uncompressed[offsetT3];
uint diff = (t0 << 24) | (t1 << 16) | (t2 << 8) | t3;
pixel += diff;
outputBufferUint[outputOffset] = pixel;
offsetT0++;
offsetT1++;
offsetT2++;
offsetT3++;
outputOffset++;
}
break;
}
case ExrPixelType.Half:
{
int offsetT0 = lastIn;
lastIn += this.Width;
int offsetT1 = lastIn;
lastIn += this.Width;
uint pixel = 0;
for (int x = 0; x < this.Width; x++)
{
uint t0 = uncompressed[offsetT0];
uint t1 = uncompressed[offsetT1];
uint diff = (t0 << 8) | t1;
pixel += diff;
outputBufferHalf[outputOffset] = (ushort)pixel;
offsetT0++;
offsetT1++;
outputOffset++;
}
break;
}
case ExrPixelType.Float:
{
int offsetT0 = lastIn;
lastIn += this.Width;
int offsetT1 = lastIn;
lastIn += this.Width;
int offsetT2 = lastIn;
lastIn += this.Width;
uint pixel = 0;
for (int x = 0; x < this.Width; x++)
{
uint t0 = uncompressed[offsetT0];
uint t1 = uncompressed[offsetT1];
uint t2 = uncompressed[offsetT2];
uint diff = (t0 << 24) | (t1 << 16) | (t2 << 8);
pixel += diff;
outputBufferFloat[outputOffset] = pixel;
offsetT0++;
offsetT1++;
offsetT2++;
outputOffset++;
}
break;
}
}
}
}
}
/// <inheritdoc/>
protected override void Dispose(bool disposing) => this.tmpBuffer.Dispose();
}

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

@ -14,16 +14,16 @@ internal class RunLengthExrCompression : ExrBaseDecompressor
{
private readonly IMemoryOwner<byte> tmpBuffer;
private readonly ushort[] s = new ushort[16];
/// <summary>
/// Initializes a new instance of the <see cref="RunLengthExrCompression" /> class.
/// </summary>
/// <param name="allocator">The memory allocator.</param>
/// <param name="bytesPerBlock">The bytes per pixel row block.</param>
/// <param name="bytesPerRow">The bytes per row.</param>
public RunLengthExrCompression(MemoryAllocator allocator, uint bytesPerBlock, uint bytesPerRow)
: base(allocator, bytesPerBlock, bytesPerRow) => this.tmpBuffer = allocator.Allocate<byte>((int)bytesPerBlock);
/// <param name="rowsPerBlock">The pixel rows per block.</param>
/// <param name="width">The witdh of one row in pixels.</param>
public RunLengthExrCompression(MemoryAllocator allocator, uint bytesPerBlock, uint bytesPerRow, uint rowsPerBlock, int width)
: 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)

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

@ -2,8 +2,6 @@
// Licensed under the Six Labors Split License.
using System.Buffers;
using System.IO.Compression;
using SixLabors.ImageSharp.Compression.Zlib;
using SixLabors.ImageSharp.IO;
using SixLabors.ImageSharp.Memory;
@ -22,41 +20,18 @@ internal class ZipExrCompression : ExrBaseDecompressor
/// <param name="allocator">The memory allocator.</param>
/// <param name="bytesPerBlock">The bytes per pixel row block.</param>
/// <param name="bytesPerRow">The bytes per pixel row.</param>
public ZipExrCompression(MemoryAllocator allocator, uint bytesPerBlock, uint bytesPerRow)
: base(allocator, bytesPerBlock, bytesPerRow) => this.tmpBuffer = allocator.Allocate<byte>((int)bytesPerBlock);
/// <param name="rowsPerBlock">The pixel rows per block.</param>
/// <param name="width">The witdh of one row in pixels.</param>
public ZipExrCompression(MemoryAllocator allocator, uint bytesPerBlock, uint bytesPerRow, uint rowsPerBlock, int width)
: 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)
{
Span<byte> uncompressed = this.tmpBuffer.GetSpan();
long pos = stream.Position;
using ZlibInflateStream inflateStream = new(
stream,
() =>
{
int left = (int)(compressedBytes - (stream.Position - pos));
return left > 0 ? left : 0;
});
inflateStream.AllocateNewBytes((int)this.BytesPerBlock, true);
using DeflateStream dataStream = inflateStream.CompressedStream!;
int totalRead = 0;
while (totalRead < buffer.Length)
{
int bytesRead = dataStream.Read(uncompressed, totalRead, buffer.Length - totalRead);
if (bytesRead <= 0)
{
break;
}
totalRead += bytesRead;
}
if (totalRead == 0)
{
ExrThrowHelper.ThrowInvalidImageContentException("Could not read enough data for zip compressed image data!");
}
uint uncompressedBytes = (uint)buffer.Length;
int totalRead = UndoZipCompression(stream, compressedBytes, uncompressed, uncompressedBytes);
Reconstruct(uncompressed, (uint)totalRead);
Interleave(uncompressed, (uint)totalRead, buffer);

11
src/ImageSharp/Formats/Exr/Compression/ExrBaseCompression.cs

@ -18,11 +18,15 @@ internal abstract class ExrBaseCompression : IDisposable
/// <param name="allocator">The memory allocator.</param>
/// <param name="bytesPerBlock">The bytes per block.</param>
/// <param name="bytesPerRow">The bytes per row.</param>
protected ExrBaseCompression(MemoryAllocator allocator, uint bytesPerBlock, uint bytesPerRow)
/// <param name="rowsPerBlock">The number of pixel rows per block.</param>
/// <param name="width">The number of pixels of a row.</param>
protected ExrBaseCompression(MemoryAllocator allocator, uint bytesPerBlock, uint bytesPerRow, uint rowsPerBlock, int width)
{
this.Allocator = allocator;
this.BytesPerBlock = bytesPerBlock;
this.BytesPerRow = bytesPerRow;
this.RowsPerBlock = rowsPerBlock;
this.Width = width;
}
/// <summary>
@ -45,6 +49,11 @@ internal abstract class ExrBaseCompression : IDisposable
/// </summary>
public uint BytesPerBlock { get; }
/// <summary>
/// Gets the number of pixel rows per block.
/// </summary>
public uint RowsPerBlock { get; }
/// <summary>
/// Gets the image width.
/// </summary>

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

@ -1,6 +1,8 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System.IO.Compression;
using SixLabors.ImageSharp.Compression.Zlib;
using SixLabors.ImageSharp.IO;
using SixLabors.ImageSharp.Memory;
@ -17,8 +19,10 @@ internal abstract class ExrBaseDecompressor : ExrBaseCompression
/// <param name="allocator">The memory allocator.</param>
/// <param name="bytesPerBlock">The bytes per row block.</param>
/// <param name="bytesPerRow">The bytes per row.</param>
protected ExrBaseDecompressor(MemoryAllocator allocator, uint bytesPerBlock, uint bytesPerRow)
: base(allocator, bytesPerBlock, bytesPerRow)
/// <param name="rowsPerBlock">The pixel rows per block.</param>
/// <param name="width">The number of pixels per row.</param>
protected ExrBaseDecompressor(MemoryAllocator allocator, uint bytesPerBlock, uint bytesPerRow, uint rowsPerBlock, int width)
: base(allocator, bytesPerBlock, bytesPerRow, rowsPerBlock, width)
{
}
@ -30,6 +34,47 @@ internal abstract class ExrBaseDecompressor : ExrBaseCompression
/// <param name="buffer">The buffer to write the decompressed data to.</param>
public abstract void Decompress(BufferedReadStream stream, uint compressedBytes, Span<byte> buffer);
/// <summary>
/// Decompresses zip compressed data.
/// </summary>
/// <param name="stream">The buffered stream to decompress.</param>
/// <param name="compressedBytes">The compressed bytes.</param>
/// <param name="uncompressed">The buffer to write the uncompressed data to.</param>
/// <param name="uncompressedBytes">The uncompressed bytes.</param>
/// <returns>The total bytes read from the stream.</returns>
protected static int UndoZipCompression(BufferedReadStream stream, uint compressedBytes, Span<byte> uncompressed, uint uncompressedBytes)
{
long pos = stream.Position;
using ZlibInflateStream inflateStream = new(
stream,
() =>
{
int left = (int)(compressedBytes - (stream.Position - pos));
return left > 0 ? left : 0;
});
inflateStream.AllocateNewBytes((int)compressedBytes, true);
using DeflateStream dataStream = inflateStream.CompressedStream!;
int totalRead = 0;
while (totalRead < uncompressedBytes)
{
int bytesRead = dataStream.Read(uncompressed, totalRead, (int)uncompressedBytes - totalRead);
if (bytesRead <= 0)
{
break;
}
totalRead += bytesRead;
}
if (totalRead == 0)
{
ExrThrowHelper.ThrowInvalidImageContentException("Could not read enough data for zip compressed EXR image data!");
}
return totalRead;
}
/// <summary>
/// Integrate over all differences to the previous value in order to
/// reconstruct sample values.

10
src/ImageSharp/Formats/Exr/Compression/ExrCompressorFactory.cs

@ -21,6 +21,8 @@ internal static class ExrCompressorFactory
/// <param name="output">The output stream.</param>
/// <param name="bytesPerBlock">The bytes per block.</param>
/// <param name="bytesPerRow">The bytes per row.</param>
/// <param name="rowsPerBlock">The pixel rows per block.</param>
/// <param name="width">The witdh of one row in pixels.</param>
/// <param name="compressionLevel">The deflate compression level.</param>
/// <returns>A compressor for EXR image data.</returns>
public static ExrBaseCompressor Create(
@ -29,11 +31,13 @@ internal static class ExrCompressorFactory
Stream output,
uint bytesPerBlock,
uint bytesPerRow,
uint rowsPerBlock,
int width,
DeflateCompressionLevel compressionLevel = DeflateCompressionLevel.DefaultCompression) => method switch
{
ExrCompression.None => new NoneExrCompressor(output, allocator, bytesPerBlock, bytesPerRow),
ExrCompression.Zips => new ZipExrCompressor(output, allocator, bytesPerBlock, bytesPerRow, compressionLevel),
ExrCompression.Zip => new ZipExrCompressor(output, allocator, bytesPerBlock, bytesPerRow, compressionLevel),
ExrCompression.None => new NoneExrCompressor(output, allocator, bytesPerBlock, bytesPerRow, rowsPerBlock, width),
ExrCompression.Zips => new ZipExrCompressor(output, allocator, bytesPerBlock, bytesPerRow, rowsPerBlock, width, compressionLevel),
ExrCompression.Zip => new ZipExrCompressor(output, allocator, bytesPerBlock, bytesPerRow, rowsPerBlock, width, compressionLevel),
_ => throw ExrThrowHelper.NotSupportedCompressor(method.ToString()),
};
}

13
src/ImageSharp/Formats/Exr/Compression/ExrDecompressorFactory.cs

@ -22,6 +22,7 @@ internal static class ExrDecompressorFactory
/// <param name="bytesPerRow">The bytes per row.</param>
/// <param name="rowsPerBlock">The rows per block.</param>
/// <param name="channelCount">The number of image channels.</param>
/// <param name="pixelType">The pixel type.</param>
/// <returns>Decompressor for EXR image data.</returns>
public static ExrBaseDecompressor Create(
ExrCompression method,
@ -30,13 +31,15 @@ internal static class ExrDecompressorFactory
uint bytesPerBlock,
uint bytesPerRow,
uint rowsPerBlock,
int channelCount) => method switch
int channelCount,
ExrPixelType pixelType) => method switch
{
ExrCompression.None => new NoneExrCompression(memoryAllocator, bytesPerBlock, bytesPerRow),
ExrCompression.Zips => new ZipExrCompression(memoryAllocator, bytesPerBlock, bytesPerRow),
ExrCompression.Zip => new ZipExrCompression(memoryAllocator, bytesPerBlock, bytesPerRow),
ExrCompression.RunLengthEncoded => new RunLengthExrCompression(memoryAllocator, bytesPerBlock, bytesPerRow),
ExrCompression.None => new NoneExrCompression(memoryAllocator, bytesPerBlock, bytesPerRow, rowsPerBlock, width),
ExrCompression.Zips => new ZipExrCompression(memoryAllocator, bytesPerBlock, bytesPerRow, rowsPerBlock, width),
ExrCompression.Zip => new ZipExrCompression(memoryAllocator, bytesPerBlock, bytesPerRow, rowsPerBlock, width),
ExrCompression.RunLengthEncoded => new RunLengthExrCompression(memoryAllocator, bytesPerBlock, bytesPerRow, rowsPerBlock, width),
ExrCompression.B44 => new B44ExrCompression(memoryAllocator, bytesPerBlock, bytesPerRow, rowsPerBlock, width, channelCount),
ExrCompression.Pxr24 => new Pxr24Compression(memoryAllocator, bytesPerBlock, bytesPerRow, rowsPerBlock, width, channelCount, pixelType),
_ => throw ExrThrowHelper.NotSupportedDecompressor(nameof(method)),
};
}

6
src/ImageSharp/Formats/Exr/ExrBaseCompressor.cs

@ -14,8 +14,10 @@ internal abstract class ExrBaseCompressor : ExrBaseCompression
/// <param name="allocator">The memory allocator.</param>
/// <param name="bytesPerBlock">Bytes per row block.</param>
/// <param name="bytesPerRow">Bytes per pixel row.</param>
protected ExrBaseCompressor(Stream output, MemoryAllocator allocator, uint bytesPerBlock, uint bytesPerRow)
: base(allocator, bytesPerBlock, bytesPerRow)
/// <param name="rowsPerBlock">The pixel rows per block.</param>
/// <param name="width">The number of pixels per row.</param>
protected ExrBaseCompressor(Stream output, MemoryAllocator allocator, uint bytesPerBlock, uint bytesPerRow, uint rowsPerBlock, int width)
: base(allocator, bytesPerBlock, bytesPerRow, rowsPerBlock, width)
=> this.Output = output;
/// <summary>

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

@ -154,7 +154,15 @@ internal sealed class ExrDecoderCore : ImageDecoderCore
Span<float> bluePixelData = rowBuffer.GetSpan().Slice(width * 2, width);
Span<float> alphaPixelData = rowBuffer.GetSpan().Slice(width * 3, width);
using ExrBaseDecompressor decompressor = ExrDecompressorFactory.Create(this.Compression, this.memoryAllocator, width, bytesPerBlock, bytesPerRow, rowsPerBlock, channelCount);
using ExrBaseDecompressor decompressor = ExrDecompressorFactory.Create(
this.Compression,
this.memoryAllocator,
width,
bytesPerBlock,
bytesPerRow,
rowsPerBlock,
channelCount,
this.PixelType);
int decodedRows = 0;
while (decodedRows < height)
@ -219,7 +227,15 @@ internal sealed class ExrDecoderCore : ImageDecoderCore
Span<uint> bluePixelData = rowBuffer.GetSpan().Slice(width * 2, width);
Span<uint> alphaPixelData = rowBuffer.GetSpan().Slice(width * 3, width);
using ExrBaseDecompressor decompressor = ExrDecompressorFactory.Create(this.Compression, this.memoryAllocator, width, bytesPerBlock, bytesPerRow, rowsPerBlock, channelCount);
using ExrBaseDecompressor decompressor = ExrDecompressorFactory.Create(
this.Compression,
this.memoryAllocator,
width,
bytesPerBlock,
bytesPerRow,
rowsPerBlock,
channelCount,
this.PixelType);
int decodedRows = 0;
while (decodedRows < height)
@ -846,7 +862,7 @@ internal sealed class ExrDecoderCore : ImageDecoderCore
/// <returns> True if the compression is supported; otherwise, false>. </returns>
private bool IsSupportedCompression() => this.Compression switch
{
ExrCompression.None or ExrCompression.Zip or ExrCompression.Zips or ExrCompression.RunLengthEncoded or ExrCompression.B44 => true,
ExrCompression.None or ExrCompression.Zip or ExrCompression.Zips or ExrCompression.RunLengthEncoded or ExrCompression.B44 or ExrCompression.Pxr24 => true,
_ => false,
};

4
src/ImageSharp/Formats/Exr/ExrEncoderCore.cs

@ -181,7 +181,7 @@ internal sealed class ExrEncoderCore
Span<float> blueBuffer = rgbBuffer.GetSpan().Slice(width * 2, width);
Span<float> alphaBuffer = rgbBuffer.GetSpan().Slice(width * 3, width);
using ExrBaseCompressor compressor = ExrCompressorFactory.Create(compression, this.memoryAllocator, stream, bytesPerBlock, bytesPerRow);
using ExrBaseCompressor compressor = ExrCompressorFactory.Create(compression, this.memoryAllocator, stream, bytesPerBlock, bytesPerRow, rowsPerBlock, width);
ulong[] rowOffsets = new ulong[height];
for (uint y = 0; y < height; y += rowsPerBlock)
@ -273,7 +273,7 @@ internal sealed class ExrEncoderCore
Span<uint> blueBuffer = rgbBuffer.GetSpan().Slice(width * 2, width);
Span<uint> alphaBuffer = rgbBuffer.GetSpan().Slice(width * 3, width);
using ExrBaseCompressor compressor = ExrCompressorFactory.Create(compression, this.memoryAllocator, stream, bytesPerBlock, bytesPerRow);
using ExrBaseCompressor compressor = ExrCompressorFactory.Create(compression, this.memoryAllocator, stream, bytesPerBlock, bytesPerRow, rowsPerBlock, width);
Rgba128 rgb = default;
ulong[] rowOffsets = new ulong[height];

26
tests/ImageSharp.Tests/Formats/Exr/ExrDecoderTests.cs

@ -36,8 +36,7 @@ public class ExrDecoderTests
ExrMetadata exrMetaData = image.Metadata.GetExrMetadata();
image.DebugSave(provider);
// There is a 0,0059% difference to the Reference decoder.
image.CompareToOriginal(provider, ImageComparer.Tolerant(0.0005f), ReferenceDecoder);
image.CompareToOriginal(provider, ImageComparer.Exact, ReferenceDecoder);
Assert.Equal(ExrPixelType.Float, exrMetaData.PixelType);
}
@ -119,6 +118,29 @@ public class ExrDecoderTests
image.CompareToOriginal(provider, ImageComparer.Exact, ReferenceDecoder);
}
[Theory]
[WithFile(TestImages.Exr.Pxr24Half, PixelTypes.Rgba32)]
[WithFile(TestImages.Exr.Pxr24Float, PixelTypes.Rgba32)]
public void ExrDecoder_CanDecode_Pxr24Compressed<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel>
{
using Image<TPixel> image = provider.GetImage(ExrDecoder.Instance);
image.DebugSave(provider);
image.CompareToOriginal(provider, ImageComparer.Exact, ReferenceDecoder);
}
[Theory]
[WithFile(TestImages.Exr.Pxr24Uint, PixelTypes.Rgba32)]
public void ExrDecoder_CanDecode_Pxr24Compressed_ExrPixelType_Uint<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel>
{
using Image<TPixel> image = provider.GetImage(ExrDecoder.Instance);
image.DebugSave(provider);
// Compare to Reference here instead using the reference decoder, since uint pixel type is not supported by the Reference decoder.
image.CompareToReferenceOutput(provider);
}
[Theory]
[WithFile(TestImages.Exr.B44, PixelTypes.Rgba32)]
public void ExrDecoder_CanDecode_B44Compressed<TPixel>(TestImageProvider<TPixel> provider)

5
tests/ImageSharp.Tests/TestImages.cs

@ -1388,13 +1388,16 @@ public static class TestImages
public const string Benchmark = "Exr/Calliphora_benchmark.exr";
public const string Uncompressed = "Exr/Calliphora_uncompressed.exr";
public const string UncompressedRgba = "Exr/Calliphora_uncompressed_rgba.exr";
public const string UncompressedFloatRgb = "Exr/rgb_float32_uncompressed.exr";
public const string UncompressedFloatRgb = "Exr/Calliphora_float_uncompressed.exr";
public const string UncompressedUintRgb = "Exr/Calliphora_uint32_uncompressed.exr";
public const string UintRgba = "Exr/rgba_uint_uncompressed.exr";
public const string Zip = "Exr/Calliphora_zip.exr";
public const string Zips = "Exr/Calliphora_zips.exr";
public const string Rle = "Exr/Calliphora_rle.exr";
public const string B44 = "Exr/Calliphora_b44.exr";
public const string Pxr24Half = "Exr/Calliphora_half_pxr24.exr";
public const string Pxr24Float = "Exr/Calliphora_float_pxr24.exr";
public const string Pxr24Uint = "Exr/Calliphora_uint_pxr24.exr";
public const string Rgb = "Exr/Calliphora_rgb.exr";
public const string Gray = "Exr/Calliphora_gray.exr";
}

3
tests/Images/External/ReferenceOutput/ExrDecoderTests/ExrDecoder_CanDecode_Pxr24Compressed_ExrPixelType_Uint_Rgba32_Calliphora_uint_pxr24.png

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:f66bf45b5d80e412314341567b8cf240d56b5872f01ce9f3b0d6034d85e8e942
size 163327

3
tests/Images/Input/Exr/Calliphora_float_pxr24.exr

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:ca6c29eb36395b923298d2bf4ba1f73fd7f081f7b1af2e72b94b92ad2acb638b
size 226997

3
tests/Images/Input/Exr/Calliphora_float_uncompressed.exr

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:926996c647c1c54921db0a623e58e5edeb92b447cb4eceb53b8c0e86fd24f11e
size 720281

3
tests/Images/Input/Exr/Calliphora_half_pxr24.exr

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:927778a73832b5ad68473e46df6be52076d98294271f2cd0ead66691db41b7bf
size 195063

3
tests/Images/Input/Exr/Calliphora_uint_pxr24.exr

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:fc72685224ba818ac77d914f5e4c91162503f88f775e16b5c745baef6bc7b790
size 187914

3
tests/Images/Input/Exr/rgb_float32_uncompressed.exr

@ -1,3 +0,0 @@
version https://git-lfs.github.com/spec/v1
oid sha256:5ab8b71824ca384fc2cde1f74a6f34c38169fa328ccc67f17d08333e9de42f55
size 243558
Loading…
Cancel
Save