diff --git a/src/ImageSharp/Formats/Exr/Compression/Compressors/NoneExrCompressor.cs b/src/ImageSharp/Formats/Exr/Compression/Compressors/NoneExrCompressor.cs
index 58768e990f..8aca0da65e 100644
--- a/src/ImageSharp/Formats/Exr/Compression/Compressors/NoneExrCompressor.cs
+++ b/src/ImageSharp/Formats/Exr/Compression/Compressors/NoneExrCompressor.cs
@@ -17,8 +17,10 @@ internal class NoneExrCompressor : ExrBaseCompressor
/// The memory allocator.
/// Bytes per row block.
/// Bytes per pixel row.
- public NoneExrCompressor(Stream output, MemoryAllocator allocator, uint bytesPerBlock, uint bytesPerRow)
- : base(output, allocator, bytesPerBlock, bytesPerRow)
+ /// The pixel rows per block.
+ /// The witdh of one row in pixels.
+ public NoneExrCompressor(Stream output, MemoryAllocator allocator, uint bytesPerBlock, uint bytesPerRow, uint rowsPerBlock, int width)
+ : base(output, allocator, bytesPerBlock, bytesPerRow, rowsPerBlock, width)
{
}
diff --git a/src/ImageSharp/Formats/Exr/Compression/Compressors/ZipExrCompressor.cs b/src/ImageSharp/Formats/Exr/Compression/Compressors/ZipExrCompressor.cs
index ef7285da0c..01248cc395 100644
--- a/src/ImageSharp/Formats/Exr/Compression/Compressors/ZipExrCompressor.cs
+++ b/src/ImageSharp/Formats/Exr/Compression/Compressors/ZipExrCompressor.cs
@@ -24,9 +24,11 @@ internal class ZipExrCompressor : ExrBaseCompressor
/// The memory allocator.
/// The bytes per block.
/// The bytes per row.
+ /// The pixel rows per block.
+ /// The witdh of one row in pixels.
/// The compression level for deflate compression.
- 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((int)bytesPerBlock);
diff --git a/src/ImageSharp/Formats/Exr/Compression/Decompressors/B44ExrCompression.cs b/src/ImageSharp/Formats/Exr/Compression/Decompressors/B44ExrCompression.cs
index aa4944649c..06df7b6a25 100644
--- a/src/ImageSharp/Formats/Exr/Compression/Decompressors/B44ExrCompression.cs
+++ b/src/ImageSharp/Formats/Exr/Compression/Decompressors/B44ExrCompression.cs
@@ -13,10 +13,6 @@ namespace SixLabors.ImageSharp.Formats.Exr.Compression.Decompressors;
///
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
/// The memory allocator.
/// The bytes per pixel row block.
/// The bytes per row.
- /// The rows per block.
+ /// The pixel rows per block.
/// The width of a pixel row in pixels.
/// The number of channels of the image.
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((int)(width * rowsPerBlock * channelCount));
}
@@ -52,19 +46,19 @@ public override void Decompress(BufferedReadStream stream, uint compressedBytes,
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 row0 = decompressed.Slice(outputOffset, this.width);
- outputOffset += this.width;
- Span row1 = decompressed.Slice(outputOffset, this.width);
- outputOffset += this.width;
- Span row2 = decompressed.Slice(outputOffset, this.width);
- outputOffset += this.width;
- Span row3 = decompressed.Slice(outputOffset, this.width);
- outputOffset += this.width;
+ Span row0 = decompressed.Slice(outputOffset, this.Width);
+ outputOffset += this.Width;
+ Span row1 = decompressed.Slice(outputOffset, this.Width);
+ outputOffset += this.Width;
+ Span row2 = decompressed.Slice(outputOffset, this.Width);
+ outputOffset += this.Width;
+ Span 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 @@ public override void Decompress(BufferedReadStream stream, uint compressedBytes,
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 @@ public override void Decompress(BufferedReadStream stream, uint compressedBytes,
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 @@ public override void Decompress(BufferedReadStream stream, uint compressedBytes,
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 @@ public override void Decompress(BufferedReadStream stream, uint compressedBytes,
// 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;
}
}
diff --git a/src/ImageSharp/Formats/Exr/Compression/Decompressors/NoneExrCompression.cs b/src/ImageSharp/Formats/Exr/Compression/Decompressors/NoneExrCompression.cs
index c15ffbe325..19edb31afe 100644
--- a/src/ImageSharp/Formats/Exr/Compression/Decompressors/NoneExrCompression.cs
+++ b/src/ImageSharp/Formats/Exr/Compression/Decompressors/NoneExrCompression.cs
@@ -17,8 +17,10 @@ internal class NoneExrCompression : ExrBaseDecompressor
/// The memory allocator.
/// The bytes per pixel row block.
/// The bytes per pixel row.
- public NoneExrCompression(MemoryAllocator allocator, uint bytesPerBlock, uint bytesPerRow)
- : base(allocator, bytesPerBlock, bytesPerRow)
+ /// The pixel rows per block.
+ /// The number of pixels per row.
+ public NoneExrCompression(MemoryAllocator allocator, uint bytesPerBlock, uint bytesPerRow, uint rowsPerBlock, int width)
+ : base(allocator, bytesPerBlock, bytesPerRow, rowsPerBlock, width)
{
}
diff --git a/src/ImageSharp/Formats/Exr/Compression/Decompressors/Pxr24Compression.cs b/src/ImageSharp/Formats/Exr/Compression/Decompressors/Pxr24Compression.cs
new file mode 100644
index 0000000000..f45b660e7d
--- /dev/null
+++ b/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;
+
+///
+/// Implementation of PXR24 decompressor for EXR image data.
+///
+internal class Pxr24Compression : ExrBaseDecompressor
+{
+ private readonly IMemoryOwner tmpBuffer;
+
+ private readonly int channelCount;
+
+ private readonly ExrPixelType pixelType;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The memory allocator.
+ /// The bytes per pixel row block.
+ /// The bytes per pixel row.
+ /// The pixel rows per block.
+ /// The witdh of one row in pixels.
+ /// The number of channels for a pixel.
+ /// The pixel type.
+ 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((int)bytesPerBlock);
+ this.channelCount = channelCount;
+ this.pixelType = pixelType;
+ }
+
+ ///
+ public override void Decompress(BufferedReadStream stream, uint compressedBytes, Span buffer)
+ {
+ Span uncompressed = this.tmpBuffer.GetSpan();
+ Span outputBufferHalf = MemoryMarshal.Cast(buffer);
+ Span outputBufferFloat = MemoryMarshal.Cast(buffer);
+ Span outputBufferUint = MemoryMarshal.Cast(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;
+ }
+ }
+ }
+ }
+ }
+
+ ///
+ protected override void Dispose(bool disposing) => this.tmpBuffer.Dispose();
+}
diff --git a/src/ImageSharp/Formats/Exr/Compression/Decompressors/RunLengthExrCompression.cs b/src/ImageSharp/Formats/Exr/Compression/Decompressors/RunLengthExrCompression.cs
index 489493d82f..f548a81810 100644
--- a/src/ImageSharp/Formats/Exr/Compression/Decompressors/RunLengthExrCompression.cs
+++ b/src/ImageSharp/Formats/Exr/Compression/Decompressors/RunLengthExrCompression.cs
@@ -14,16 +14,16 @@ internal class RunLengthExrCompression : ExrBaseDecompressor
{
private readonly IMemoryOwner tmpBuffer;
- private readonly ushort[] s = new ushort[16];
-
///
/// Initializes a new instance of the class.
///
/// The memory allocator.
/// The bytes per pixel row block.
/// The bytes per row.
- public RunLengthExrCompression(MemoryAllocator allocator, uint bytesPerBlock, uint bytesPerRow)
- : base(allocator, bytesPerBlock, bytesPerRow) => this.tmpBuffer = allocator.Allocate((int)bytesPerBlock);
+ /// The pixel rows per block.
+ /// The witdh of one row in pixels.
+ public RunLengthExrCompression(MemoryAllocator allocator, uint bytesPerBlock, uint bytesPerRow, uint rowsPerBlock, int width)
+ : base(allocator, bytesPerBlock, bytesPerRow, rowsPerBlock, width) => this.tmpBuffer = allocator.Allocate((int)bytesPerBlock);
///
public override void Decompress(BufferedReadStream stream, uint compressedBytes, Span buffer)
diff --git a/src/ImageSharp/Formats/Exr/Compression/Decompressors/ZipExrCompression.cs b/src/ImageSharp/Formats/Exr/Compression/Decompressors/ZipExrCompression.cs
index dafe3d8321..8bab76f402 100644
--- a/src/ImageSharp/Formats/Exr/Compression/Decompressors/ZipExrCompression.cs
+++ b/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
/// The memory allocator.
/// The bytes per pixel row block.
/// The bytes per pixel row.
- public ZipExrCompression(MemoryAllocator allocator, uint bytesPerBlock, uint bytesPerRow)
- : base(allocator, bytesPerBlock, bytesPerRow) => this.tmpBuffer = allocator.Allocate((int)bytesPerBlock);
+ /// The pixel rows per block.
+ /// The witdh of one row in pixels.
+ public ZipExrCompression(MemoryAllocator allocator, uint bytesPerBlock, uint bytesPerRow, uint rowsPerBlock, int width)
+ : base(allocator, bytesPerBlock, bytesPerRow, rowsPerBlock, width) => this.tmpBuffer = allocator.Allocate((int)bytesPerBlock);
///
public override void Decompress(BufferedReadStream stream, uint compressedBytes, Span buffer)
{
Span 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);
diff --git a/src/ImageSharp/Formats/Exr/Compression/ExrBaseCompression.cs b/src/ImageSharp/Formats/Exr/Compression/ExrBaseCompression.cs
index b116dffc65..72d5be980b 100644
--- a/src/ImageSharp/Formats/Exr/Compression/ExrBaseCompression.cs
+++ b/src/ImageSharp/Formats/Exr/Compression/ExrBaseCompression.cs
@@ -18,11 +18,15 @@ internal abstract class ExrBaseCompression : IDisposable
/// The memory allocator.
/// The bytes per block.
/// The bytes per row.
- protected ExrBaseCompression(MemoryAllocator allocator, uint bytesPerBlock, uint bytesPerRow)
+ /// The number of pixel rows per block.
+ /// The number of pixels of a row.
+ 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;
}
///
@@ -45,6 +49,11 @@ protected ExrBaseCompression(MemoryAllocator allocator, uint bytesPerBlock, uint
///
public uint BytesPerBlock { get; }
+ ///
+ /// Gets the number of pixel rows per block.
+ ///
+ public uint RowsPerBlock { get; }
+
///
/// Gets the image width.
///
diff --git a/src/ImageSharp/Formats/Exr/Compression/ExrBaseDecompressor.cs b/src/ImageSharp/Formats/Exr/Compression/ExrBaseDecompressor.cs
index efe3c7877f..5dfbdf148f 100644
--- a/src/ImageSharp/Formats/Exr/Compression/ExrBaseDecompressor.cs
+++ b/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
/// The memory allocator.
/// The bytes per row block.
/// The bytes per row.
- protected ExrBaseDecompressor(MemoryAllocator allocator, uint bytesPerBlock, uint bytesPerRow)
- : base(allocator, bytesPerBlock, bytesPerRow)
+ /// The pixel rows per block.
+ /// The number of pixels per row.
+ protected ExrBaseDecompressor(MemoryAllocator allocator, uint bytesPerBlock, uint bytesPerRow, uint rowsPerBlock, int width)
+ : base(allocator, bytesPerBlock, bytesPerRow, rowsPerBlock, width)
{
}
@@ -30,6 +34,47 @@ protected ExrBaseDecompressor(MemoryAllocator allocator, uint bytesPerBlock, uin
/// The buffer to write the decompressed data to.
public abstract void Decompress(BufferedReadStream stream, uint compressedBytes, Span buffer);
+ ///
+ /// Decompresses zip compressed data.
+ ///
+ /// The buffered stream to decompress.
+ /// The compressed bytes.
+ /// The buffer to write the uncompressed data to.
+ /// The uncompressed bytes.
+ /// The total bytes read from the stream.
+ protected static int UndoZipCompression(BufferedReadStream stream, uint compressedBytes, Span 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)uncompressedBytes, 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 image data!");
+ }
+
+ return totalRead;
+ }
+
///
/// Integrate over all differences to the previous value in order to
/// reconstruct sample values.
diff --git a/src/ImageSharp/Formats/Exr/Compression/ExrCompressorFactory.cs b/src/ImageSharp/Formats/Exr/Compression/ExrCompressorFactory.cs
index b1b7d2e8e6..0a7d4157f2 100644
--- a/src/ImageSharp/Formats/Exr/Compression/ExrCompressorFactory.cs
+++ b/src/ImageSharp/Formats/Exr/Compression/ExrCompressorFactory.cs
@@ -21,6 +21,8 @@ internal static class ExrCompressorFactory
/// The output stream.
/// The bytes per block.
/// The bytes per row.
+ /// The pixel rows per block.
+ /// The witdh of one row in pixels.
/// The deflate compression level.
/// A compressor for EXR image data.
public static ExrBaseCompressor Create(
@@ -29,11 +31,13 @@ public static ExrBaseCompressor Create(
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()),
};
}
diff --git a/src/ImageSharp/Formats/Exr/Compression/ExrDecompressorFactory.cs b/src/ImageSharp/Formats/Exr/Compression/ExrDecompressorFactory.cs
index 62519666b5..353d1af117 100644
--- a/src/ImageSharp/Formats/Exr/Compression/ExrDecompressorFactory.cs
+++ b/src/ImageSharp/Formats/Exr/Compression/ExrDecompressorFactory.cs
@@ -22,6 +22,7 @@ internal static class ExrDecompressorFactory
/// The bytes per row.
/// The rows per block.
/// The number of image channels.
+ /// The pixel type.
/// Decompressor for EXR image data.
public static ExrBaseDecompressor Create(
ExrCompression method,
@@ -30,13 +31,15 @@ public static ExrBaseDecompressor Create(
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)),
};
}
diff --git a/src/ImageSharp/Formats/Exr/ExrBaseCompressor.cs b/src/ImageSharp/Formats/Exr/ExrBaseCompressor.cs
index 1dcdec5be0..9e3fe8ec65 100644
--- a/src/ImageSharp/Formats/Exr/ExrBaseCompressor.cs
+++ b/src/ImageSharp/Formats/Exr/ExrBaseCompressor.cs
@@ -14,8 +14,10 @@ internal abstract class ExrBaseCompressor : ExrBaseCompression
/// The memory allocator.
/// Bytes per row block.
/// Bytes per pixel row.
- protected ExrBaseCompressor(Stream output, MemoryAllocator allocator, uint bytesPerBlock, uint bytesPerRow)
- : base(allocator, bytesPerBlock, bytesPerRow)
+ /// The pixel rows per block.
+ /// The number of pixels per row.
+ protected ExrBaseCompressor(Stream output, MemoryAllocator allocator, uint bytesPerBlock, uint bytesPerRow, uint rowsPerBlock, int width)
+ : base(allocator, bytesPerBlock, bytesPerRow, rowsPerBlock, width)
=> this.Output = output;
///
diff --git a/src/ImageSharp/Formats/Exr/ExrDecoderCore.cs b/src/ImageSharp/Formats/Exr/ExrDecoderCore.cs
index 0b75154c2c..2e60f3a5b9 100644
--- a/src/ImageSharp/Formats/Exr/ExrDecoderCore.cs
+++ b/src/ImageSharp/Formats/Exr/ExrDecoderCore.cs
@@ -154,7 +154,15 @@ private void DecodeFloatingPointPixelData(BufferedReadStream stream, Buf
Span bluePixelData = rowBuffer.GetSpan().Slice(width * 2, width);
Span 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 @@ private void DecodeUnsignedIntPixelData(BufferedReadStream stream, Buffe
Span bluePixelData = rowBuffer.GetSpan().Slice(width * 2, width);
Span 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 @@ private static string ReadString(BufferedReadStream stream)
/// True if the compression is supported; otherwise, false>.
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,
};
diff --git a/src/ImageSharp/Formats/Exr/ExrEncoderCore.cs b/src/ImageSharp/Formats/Exr/ExrEncoderCore.cs
index 04ca5e40c8..e2750d8972 100644
--- a/src/ImageSharp/Formats/Exr/ExrEncoderCore.cs
+++ b/src/ImageSharp/Formats/Exr/ExrEncoderCore.cs
@@ -181,7 +181,7 @@ private ulong[] EncodeFloatingPointPixelData(
Span blueBuffer = rgbBuffer.GetSpan().Slice(width * 2, width);
Span 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 @@ private ulong[] EncodeUnsignedIntPixelData(
Span blueBuffer = rgbBuffer.GetSpan().Slice(width * 2, width);
Span 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];
diff --git a/tests/ImageSharp.Tests/Formats/Exr/ExrDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Exr/ExrDecoderTests.cs
index b81dba7555..1ee0cc582b 100644
--- a/tests/ImageSharp.Tests/Formats/Exr/ExrDecoderTests.cs
+++ b/tests/ImageSharp.Tests/Formats/Exr/ExrDecoderTests.cs
@@ -36,8 +36,7 @@ public void ExrDecoder_CanDecode_Uncompressed_Rgb_ExrPixelType_Float(Tes
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 void ExrDecoder_CanDecode_RunLengthCompressed(TestImageProvider(TestImageProvider provider)
+ where TPixel : unmanaged, IPixel
+ {
+ using Image 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(TestImageProvider provider)
+ where TPixel : unmanaged, IPixel
+ {
+ using Image 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(TestImageProvider provider)
diff --git a/tests/ImageSharp.Tests/TestImages.cs b/tests/ImageSharp.Tests/TestImages.cs
index 1a8cf948e8..2624d1cdad 100644
--- a/tests/ImageSharp.Tests/TestImages.cs
+++ b/tests/ImageSharp.Tests/TestImages.cs
@@ -1388,13 +1388,16 @@ public static class Exr
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";
}
diff --git a/tests/Images/External/ReferenceOutput/ExrDecoderTests/ExrDecoder_CanDecode_Pxr24Compressed_ExrPixelType_Uint_Rgba32_Calliphora_uint_pxr24.png b/tests/Images/External/ReferenceOutput/ExrDecoderTests/ExrDecoder_CanDecode_Pxr24Compressed_ExrPixelType_Uint_Rgba32_Calliphora_uint_pxr24.png
new file mode 100644
index 0000000000..8f31f0d8d0
--- /dev/null
+++ b/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
diff --git a/tests/Images/Input/Exr/Calliphora_float_pxr24.exr b/tests/Images/Input/Exr/Calliphora_float_pxr24.exr
new file mode 100644
index 0000000000..3c5d61e535
--- /dev/null
+++ b/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
diff --git a/tests/Images/Input/Exr/Calliphora_float_uncompressed.exr b/tests/Images/Input/Exr/Calliphora_float_uncompressed.exr
new file mode 100644
index 0000000000..04a5c035dd
--- /dev/null
+++ b/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
diff --git a/tests/Images/Input/Exr/Calliphora_half_pxr24.exr b/tests/Images/Input/Exr/Calliphora_half_pxr24.exr
new file mode 100644
index 0000000000..17a90699b0
--- /dev/null
+++ b/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
diff --git a/tests/Images/Input/Exr/Calliphora_uint_pxr24.exr b/tests/Images/Input/Exr/Calliphora_uint_pxr24.exr
new file mode 100644
index 0000000000..fd858abb04
--- /dev/null
+++ b/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
diff --git a/tests/Images/Input/Exr/rgb_float32_uncompressed.exr b/tests/Images/Input/Exr/rgb_float32_uncompressed.exr
deleted file mode 100644
index 489758bb04..0000000000
--- a/tests/Images/Input/Exr/rgb_float32_uncompressed.exr
+++ /dev/null
@@ -1,3 +0,0 @@
-version https://git-lfs.github.com/spec/v1
-oid sha256:5ab8b71824ca384fc2cde1f74a6f34c38169fa328ccc67f17d08333e9de42f55
-size 243558