From 39b13a46c69e495966109dc09a9ebd652635560a Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Thu, 23 Apr 2026 20:04:36 +0200 Subject: [PATCH 01/11] Initial version decode Pxr24 compression --- .../Decompressors/B44ExrCompression.cs | 1 + .../Decompressors/Pxr24Compression.cs | 102 ++++++++++++++++++ .../Exr/Compression/ExrDecompressorFactory.cs | 1 + src/ImageSharp/Formats/Exr/ExrDecoderCore.cs | 2 +- 4 files changed, 105 insertions(+), 1 deletion(-) create mode 100644 src/ImageSharp/Formats/Exr/Compression/Decompressors/Pxr24Compression.cs diff --git a/src/ImageSharp/Formats/Exr/Compression/Decompressors/B44ExrCompression.cs b/src/ImageSharp/Formats/Exr/Compression/Decompressors/B44ExrCompression.cs index aa4944649c..3c4e6b1483 100644 --- a/src/ImageSharp/Formats/Exr/Compression/Decompressors/B44ExrCompression.cs +++ b/src/ImageSharp/Formats/Exr/Compression/Decompressors/B44ExrCompression.cs @@ -72,6 +72,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); 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..9e97fd0ad5 --- /dev/null +++ b/src/ImageSharp/Formats/Exr/Compression/Decompressors/Pxr24Compression.cs @@ -0,0 +1,102 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Buffers; +using System.IO.Compression; +using System.Runtime.InteropServices; +using SixLabors.ImageSharp.Compression.Zlib; +using SixLabors.ImageSharp.IO; +using SixLabors.ImageSharp.Memory; + +namespace SixLabors.ImageSharp.Formats.Exr.Compression.Decompressors; + +internal class Pxr24Compression : ExrBaseDecompressor +{ + private readonly IMemoryOwner tmpBuffer; + + private readonly uint rowsPerBlock; + + private readonly int channelCount; + + private readonly int width; + + /// + /// Initializes a new instance of the class. + /// + /// The memory allocator. + /// The bytes per pixel row block. + /// The bytes per pixel row. + public Pxr24Compression(MemoryAllocator allocator, uint bytesPerBlock, uint bytesPerRow, uint rowsPerBlock, int width, int channelCount) + : base(allocator, bytesPerBlock, bytesPerRow) + { + this.tmpBuffer = allocator.Allocate((int)bytesPerBlock); + this.rowsPerBlock = rowsPerBlock; + this.width = width; + this.channelCount = channelCount; + } + + /// + public override void Decompress(BufferedReadStream stream, uint compressedBytes, Span buffer) + { + Span uncompressed = this.tmpBuffer.GetSpan(); + Span outputBuffer = MemoryMarshal.Cast(buffer); + + 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!"); + } + + int lastIn = 0; + int outputOffset = 0; + for (int y = 0; y < this.rowsPerBlock; y++) + { + for (int c = 0; c < this.channelCount; c++) + { + int offsetT1 = lastIn; + lastIn += this.width; + int offsetT2 = lastIn; + lastIn += this.width; + uint pixel = 0; + for (int x = 0; x < this.width; x++) + { + uint t1 = uncompressed[offsetT1]; + uint t2 = uncompressed[offsetT2]; + uint diff = (t1 << 8) | t2; + + pixel += diff; + outputBuffer[outputOffset] = (ushort)pixel; + + offsetT1++; + offsetT2++; + outputOffset++; + } + } + } + } + + /// + protected override void Dispose(bool disposing) => this.tmpBuffer.Dispose(); +} diff --git a/src/ImageSharp/Formats/Exr/Compression/ExrDecompressorFactory.cs b/src/ImageSharp/Formats/Exr/Compression/ExrDecompressorFactory.cs index 62519666b5..bd8df9230b 100644 --- a/src/ImageSharp/Formats/Exr/Compression/ExrDecompressorFactory.cs +++ b/src/ImageSharp/Formats/Exr/Compression/ExrDecompressorFactory.cs @@ -37,6 +37,7 @@ public static ExrBaseDecompressor Create( ExrCompression.Zip => new ZipExrCompression(memoryAllocator, bytesPerBlock, bytesPerRow), ExrCompression.RunLengthEncoded => new RunLengthExrCompression(memoryAllocator, bytesPerBlock, bytesPerRow), ExrCompression.B44 => new B44ExrCompression(memoryAllocator, bytesPerBlock, bytesPerRow, rowsPerBlock, width, channelCount), + ExrCompression.Pxr24 => new Pxr24Compression(memoryAllocator, bytesPerBlock, bytesPerRow, rowsPerBlock, width, channelCount), _ => throw ExrThrowHelper.NotSupportedDecompressor(nameof(method)), }; } diff --git a/src/ImageSharp/Formats/Exr/ExrDecoderCore.cs b/src/ImageSharp/Formats/Exr/ExrDecoderCore.cs index 0b75154c2c..ddfbcdc186 100644 --- a/src/ImageSharp/Formats/Exr/ExrDecoderCore.cs +++ b/src/ImageSharp/Formats/Exr/ExrDecoderCore.cs @@ -846,7 +846,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, }; From d805b9b50c96705cacc3d006715cbc7b9d927531 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Fri, 24 Apr 2026 11:25:28 +0200 Subject: [PATCH 02/11] Add test case for pxr24 compressed image --- tests/ImageSharp.Tests/Formats/Exr/ExrDecoderTests.cs | 10 ++++++++++ tests/ImageSharp.Tests/TestImages.cs | 1 + tests/Images/Input/Exr/Calliphora_half_pxr24.exr | 3 +++ 3 files changed, 14 insertions(+) create mode 100644 tests/Images/Input/Exr/Calliphora_half_pxr24.exr diff --git a/tests/ImageSharp.Tests/Formats/Exr/ExrDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Exr/ExrDecoderTests.cs index b81dba7555..dd958e87ab 100644 --- a/tests/ImageSharp.Tests/Formats/Exr/ExrDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Exr/ExrDecoderTests.cs @@ -119,6 +119,16 @@ 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.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..90adae34cc 100644 --- a/tests/ImageSharp.Tests/TestImages.cs +++ b/tests/ImageSharp.Tests/TestImages.cs @@ -1395,6 +1395,7 @@ public static class 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 Rgb = "Exr/Calliphora_rgb.exr"; public const string Gray = "Exr/Calliphora_gray.exr"; } 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 From 344d49d86fb172409430c31b03cc3a3b1798ba2c Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Fri, 24 Apr 2026 13:26:23 +0200 Subject: [PATCH 03/11] Use Width Property from ExrBaseDecompressor --- .../Compressors/NoneExrCompressor.cs | 5 +-- .../Compressors/ZipExrCompressor.cs | 5 +-- .../Decompressors/B44ExrCompression.cs | 33 +++++++++---------- .../Decompressors/NoneExrCompression.cs | 5 +-- .../Decompressors/Pxr24Compression.cs | 17 ++++++---- .../Decompressors/RunLengthExrCompression.cs | 5 +-- .../Decompressors/ZipExrCompression.cs | 5 +-- .../Exr/Compression/ExrBaseCompression.cs | 4 ++- .../Exr/Compression/ExrBaseDecompressor.cs | 5 +-- .../Exr/Compression/ExrCompressorFactory.cs | 8 +++-- .../Exr/Compression/ExrDecompressorFactory.cs | 8 ++--- .../Formats/Exr/ExrBaseCompressor.cs | 5 +-- src/ImageSharp/Formats/Exr/ExrEncoderCore.cs | 4 +-- 13 files changed, 60 insertions(+), 49 deletions(-) diff --git a/src/ImageSharp/Formats/Exr/Compression/Compressors/NoneExrCompressor.cs b/src/ImageSharp/Formats/Exr/Compression/Compressors/NoneExrCompressor.cs index 58768e990f..c1240667e7 100644 --- a/src/ImageSharp/Formats/Exr/Compression/Compressors/NoneExrCompressor.cs +++ b/src/ImageSharp/Formats/Exr/Compression/Compressors/NoneExrCompressor.cs @@ -17,8 +17,9 @@ 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 witdh of one row in pixels. + public NoneExrCompressor(Stream output, MemoryAllocator allocator, uint bytesPerBlock, uint bytesPerRow, int width) + : base(output, allocator, bytesPerBlock, bytesPerRow, width) { } diff --git a/src/ImageSharp/Formats/Exr/Compression/Compressors/ZipExrCompressor.cs b/src/ImageSharp/Formats/Exr/Compression/Compressors/ZipExrCompressor.cs index ef7285da0c..bf5f078585 100644 --- a/src/ImageSharp/Formats/Exr/Compression/Compressors/ZipExrCompressor.cs +++ b/src/ImageSharp/Formats/Exr/Compression/Compressors/ZipExrCompressor.cs @@ -24,9 +24,10 @@ internal class ZipExrCompressor : ExrBaseCompressor /// The memory allocator. /// The bytes per block. /// The bytes per row. + /// 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, int width, DeflateCompressionLevel compressionLevel) + : base(output, allocator, bytesPerBlock, bytesPerRow, 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 3c4e6b1483..64774537fa 100644 --- a/src/ImageSharp/Formats/Exr/Compression/Decompressors/B44ExrCompression.cs +++ b/src/ImageSharp/Formats/Exr/Compression/Decompressors/B44ExrCompression.cs @@ -13,8 +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; @@ -35,9 +33,8 @@ internal class B44ExrCompression : ExrBaseDecompressor /// 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, width) { - this.width = width; this.rowsPerBlock = rowsPerBlock; this.channelCount = channelCount; this.tmpBuffer = allocator.Allocate((int)(width * rowsPerBlock * channelCount)); @@ -54,17 +51,17 @@ public override void Decompress(BufferedReadStream stream, uint compressedBytes, { 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) @@ -90,7 +87,7 @@ public override void Decompress(BufferedReadStream stream, uint compressedBytes, bytesLeft -= 14; } - int n = x + 3 < this.width ? 4 : this.width - x; + int n = x + 3 < this.Width ? 4 : this.Width - x; if (y + 3 < this.rowsPerBlock) { this.s.AsSpan(0, n).CopyTo(row0[rowOffset..]); @@ -125,16 +122,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); + 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..1b045bdeb7 100644 --- a/src/ImageSharp/Formats/Exr/Compression/Decompressors/NoneExrCompression.cs +++ b/src/ImageSharp/Formats/Exr/Compression/Decompressors/NoneExrCompression.cs @@ -17,8 +17,9 @@ 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 number of pixels per row. + public NoneExrCompression(MemoryAllocator allocator, uint bytesPerBlock, uint bytesPerRow, int width) + : base(allocator, bytesPerBlock, bytesPerRow, width) { } diff --git a/src/ImageSharp/Formats/Exr/Compression/Decompressors/Pxr24Compression.cs b/src/ImageSharp/Formats/Exr/Compression/Decompressors/Pxr24Compression.cs index 9e97fd0ad5..d720c190f9 100644 --- a/src/ImageSharp/Formats/Exr/Compression/Decompressors/Pxr24Compression.cs +++ b/src/ImageSharp/Formats/Exr/Compression/Decompressors/Pxr24Compression.cs @@ -10,6 +10,9 @@ namespace SixLabors.ImageSharp.Formats.Exr.Compression.Decompressors; +/// +/// Implementation of PXR24 decompressor for EXR image data. +/// internal class Pxr24Compression : ExrBaseDecompressor { private readonly IMemoryOwner tmpBuffer; @@ -18,20 +21,20 @@ internal class Pxr24Compression : ExrBaseDecompressor private readonly int channelCount; - private readonly int width; - /// /// 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. public Pxr24Compression(MemoryAllocator allocator, uint bytesPerBlock, uint bytesPerRow, uint rowsPerBlock, int width, int channelCount) - : base(allocator, bytesPerBlock, bytesPerRow) + : base(allocator, bytesPerBlock, bytesPerRow, width) { this.tmpBuffer = allocator.Allocate((int)bytesPerBlock); this.rowsPerBlock = rowsPerBlock; - this.width = width; this.channelCount = channelCount; } @@ -76,11 +79,11 @@ public override void Decompress(BufferedReadStream stream, uint compressedBytes, for (int c = 0; c < this.channelCount; c++) { int offsetT1 = lastIn; - lastIn += this.width; + lastIn += this.Width; int offsetT2 = lastIn; - lastIn += this.width; + lastIn += this.Width; uint pixel = 0; - for (int x = 0; x < this.width; x++) + for (int x = 0; x < this.Width; x++) { uint t1 = uncompressed[offsetT1]; uint t2 = uncompressed[offsetT2]; diff --git a/src/ImageSharp/Formats/Exr/Compression/Decompressors/RunLengthExrCompression.cs b/src/ImageSharp/Formats/Exr/Compression/Decompressors/RunLengthExrCompression.cs index 489493d82f..29782ae03d 100644 --- a/src/ImageSharp/Formats/Exr/Compression/Decompressors/RunLengthExrCompression.cs +++ b/src/ImageSharp/Formats/Exr/Compression/Decompressors/RunLengthExrCompression.cs @@ -22,8 +22,9 @@ internal class RunLengthExrCompression : ExrBaseDecompressor /// 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 witdh of one row in pixels. + public RunLengthExrCompression(MemoryAllocator allocator, uint bytesPerBlock, uint bytesPerRow, int width) + : base(allocator, bytesPerBlock, bytesPerRow, 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..4333f53cb5 100644 --- a/src/ImageSharp/Formats/Exr/Compression/Decompressors/ZipExrCompression.cs +++ b/src/ImageSharp/Formats/Exr/Compression/Decompressors/ZipExrCompression.cs @@ -22,8 +22,9 @@ 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 witdh of one row in pixels. + public ZipExrCompression(MemoryAllocator allocator, uint bytesPerBlock, uint bytesPerRow, int width) + : base(allocator, bytesPerBlock, bytesPerRow, 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/ExrBaseCompression.cs b/src/ImageSharp/Formats/Exr/Compression/ExrBaseCompression.cs index b116dffc65..5912fcdecf 100644 --- a/src/ImageSharp/Formats/Exr/Compression/ExrBaseCompression.cs +++ b/src/ImageSharp/Formats/Exr/Compression/ExrBaseCompression.cs @@ -18,11 +18,13 @@ 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 pixels of a row. + protected ExrBaseCompression(MemoryAllocator allocator, uint bytesPerBlock, uint bytesPerRow, int width) { this.Allocator = allocator; this.BytesPerBlock = bytesPerBlock; this.BytesPerRow = bytesPerRow; + this.Width = width; } /// diff --git a/src/ImageSharp/Formats/Exr/Compression/ExrBaseDecompressor.cs b/src/ImageSharp/Formats/Exr/Compression/ExrBaseDecompressor.cs index efe3c7877f..000e6ecea8 100644 --- a/src/ImageSharp/Formats/Exr/Compression/ExrBaseDecompressor.cs +++ b/src/ImageSharp/Formats/Exr/Compression/ExrBaseDecompressor.cs @@ -17,8 +17,9 @@ 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 number of pixels per row. + protected ExrBaseDecompressor(MemoryAllocator allocator, uint bytesPerBlock, uint bytesPerRow, int width) + : base(allocator, bytesPerBlock, bytesPerRow, width) { } diff --git a/src/ImageSharp/Formats/Exr/Compression/ExrCompressorFactory.cs b/src/ImageSharp/Formats/Exr/Compression/ExrCompressorFactory.cs index b1b7d2e8e6..497bd492aa 100644 --- a/src/ImageSharp/Formats/Exr/Compression/ExrCompressorFactory.cs +++ b/src/ImageSharp/Formats/Exr/Compression/ExrCompressorFactory.cs @@ -21,6 +21,7 @@ internal static class ExrCompressorFactory /// The output stream. /// The bytes per block. /// The bytes per row. + /// The witdh of one row in pixels. /// The deflate compression level. /// A compressor for EXR image data. public static ExrBaseCompressor Create( @@ -29,11 +30,12 @@ public static ExrBaseCompressor Create( Stream output, uint bytesPerBlock, uint bytesPerRow, + 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, width), + ExrCompression.Zips => new ZipExrCompressor(output, allocator, bytesPerBlock, bytesPerRow, width, compressionLevel), + ExrCompression.Zip => new ZipExrCompressor(output, allocator, bytesPerBlock, bytesPerRow, 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 bd8df9230b..8123284585 100644 --- a/src/ImageSharp/Formats/Exr/Compression/ExrDecompressorFactory.cs +++ b/src/ImageSharp/Formats/Exr/Compression/ExrDecompressorFactory.cs @@ -32,10 +32,10 @@ public static ExrBaseDecompressor Create( uint rowsPerBlock, int channelCount) => 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, width), + ExrCompression.Zips => new ZipExrCompression(memoryAllocator, bytesPerBlock, bytesPerRow, width), + ExrCompression.Zip => new ZipExrCompression(memoryAllocator, bytesPerBlock, bytesPerRow, width), + ExrCompression.RunLengthEncoded => new RunLengthExrCompression(memoryAllocator, bytesPerBlock, bytesPerRow, width), ExrCompression.B44 => new B44ExrCompression(memoryAllocator, bytesPerBlock, bytesPerRow, rowsPerBlock, width, channelCount), ExrCompression.Pxr24 => new Pxr24Compression(memoryAllocator, bytesPerBlock, bytesPerRow, rowsPerBlock, width, channelCount), _ => throw ExrThrowHelper.NotSupportedDecompressor(nameof(method)), diff --git a/src/ImageSharp/Formats/Exr/ExrBaseCompressor.cs b/src/ImageSharp/Formats/Exr/ExrBaseCompressor.cs index 1dcdec5be0..83887fd5f1 100644 --- a/src/ImageSharp/Formats/Exr/ExrBaseCompressor.cs +++ b/src/ImageSharp/Formats/Exr/ExrBaseCompressor.cs @@ -14,8 +14,9 @@ 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 number of pixels per row. + protected ExrBaseCompressor(Stream output, MemoryAllocator allocator, uint bytesPerBlock, uint bytesPerRow, int width) + : base(allocator, bytesPerBlock, bytesPerRow, width) => this.Output = output; /// diff --git a/src/ImageSharp/Formats/Exr/ExrEncoderCore.cs b/src/ImageSharp/Formats/Exr/ExrEncoderCore.cs index 04ca5e40c8..c6824c6aad 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, 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, width); Rgba128 rgb = default; ulong[] rowOffsets = new ulong[height]; From 95ee73e241186b779a4ecfe35e3cd3c06759b282 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Sat, 25 Apr 2026 16:10:57 +0200 Subject: [PATCH 04/11] Implement pxr decompression for pixel type uint and float --- .../Decompressors/Pxr24Compression.cs | 106 +++++++++++++++--- .../Exr/Compression/ExrDecompressorFactory.cs | 5 +- src/ImageSharp/Formats/Exr/ExrDecoderCore.cs | 20 +++- 3 files changed, 112 insertions(+), 19 deletions(-) diff --git a/src/ImageSharp/Formats/Exr/Compression/Decompressors/Pxr24Compression.cs b/src/ImageSharp/Formats/Exr/Compression/Decompressors/Pxr24Compression.cs index d720c190f9..d2fdcfcd16 100644 --- a/src/ImageSharp/Formats/Exr/Compression/Decompressors/Pxr24Compression.cs +++ b/src/ImageSharp/Formats/Exr/Compression/Decompressors/Pxr24Compression.cs @@ -5,6 +5,7 @@ using System.IO.Compression; using System.Runtime.InteropServices; using SixLabors.ImageSharp.Compression.Zlib; +using SixLabors.ImageSharp.Formats.Exr.Constants; using SixLabors.ImageSharp.IO; using SixLabors.ImageSharp.Memory; @@ -21,6 +22,8 @@ internal class Pxr24Compression : ExrBaseDecompressor private readonly int channelCount; + private readonly ExrPixelType pixelType; + /// /// Initializes a new instance of the class. /// @@ -30,12 +33,14 @@ internal class Pxr24Compression : ExrBaseDecompressor /// The pixel rows per block. /// The witdh of one row in pixels. /// The number of channels for a pixel. - public Pxr24Compression(MemoryAllocator allocator, uint bytesPerBlock, uint bytesPerRow, uint rowsPerBlock, int width, int channelCount) + /// The pixel type. + public Pxr24Compression(MemoryAllocator allocator, uint bytesPerBlock, uint bytesPerRow, uint rowsPerBlock, int width, int channelCount, ExrPixelType pixelType) : base(allocator, bytesPerBlock, bytesPerRow, width) { this.tmpBuffer = allocator.Allocate((int)bytesPerBlock); this.rowsPerBlock = rowsPerBlock; this.channelCount = channelCount; + this.pixelType = pixelType; } /// @@ -78,23 +83,94 @@ public override void Decompress(BufferedReadStream stream, uint compressedBytes, { for (int c = 0; c < this.channelCount; c++) { - int offsetT1 = lastIn; - lastIn += this.Width; - int offsetT2 = lastIn; - lastIn += this.Width; - uint pixel = 0; - for (int x = 0; x < this.Width; x++) + switch (this.pixelType) { - uint t1 = uncompressed[offsetT1]; - uint t2 = uncompressed[offsetT2]; - uint diff = (t1 << 8) | t2; + 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; + outputBuffer[outputOffset] = (ushort)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; + outputBuffer[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; + outputBuffer[outputOffset] = (ushort)pixel; - pixel += diff; - outputBuffer[outputOffset] = (ushort)pixel; + offsetT0++; + offsetT1++; + offsetT2++; + outputOffset++; + } - offsetT1++; - offsetT2++; - outputOffset++; + break; + } } } } diff --git a/src/ImageSharp/Formats/Exr/Compression/ExrDecompressorFactory.cs b/src/ImageSharp/Formats/Exr/Compression/ExrDecompressorFactory.cs index 8123284585..6f63a932b7 100644 --- a/src/ImageSharp/Formats/Exr/Compression/ExrDecompressorFactory.cs +++ b/src/ImageSharp/Formats/Exr/Compression/ExrDecompressorFactory.cs @@ -30,14 +30,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, width), ExrCompression.Zips => new ZipExrCompression(memoryAllocator, bytesPerBlock, bytesPerRow, width), ExrCompression.Zip => new ZipExrCompression(memoryAllocator, bytesPerBlock, bytesPerRow, width), ExrCompression.RunLengthEncoded => new RunLengthExrCompression(memoryAllocator, bytesPerBlock, bytesPerRow, width), ExrCompression.B44 => new B44ExrCompression(memoryAllocator, bytesPerBlock, bytesPerRow, rowsPerBlock, width, channelCount), - ExrCompression.Pxr24 => new Pxr24Compression(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/ExrDecoderCore.cs b/src/ImageSharp/Formats/Exr/ExrDecoderCore.cs index ddfbcdc186..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) From efa1f8bbae992b0d2c8c239564e0ab394dfadb02 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Sun, 26 Apr 2026 09:57:15 +0200 Subject: [PATCH 05/11] Add test case for pxr compression with pixel type float --- tests/ImageSharp.Tests/Formats/Exr/ExrDecoderTests.cs | 1 + tests/ImageSharp.Tests/TestImages.cs | 1 + tests/Images/Input/Exr/Calliphora_float_pxr24.exr | 3 +++ 3 files changed, 5 insertions(+) create mode 100644 tests/Images/Input/Exr/Calliphora_float_pxr24.exr diff --git a/tests/ImageSharp.Tests/Formats/Exr/ExrDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Exr/ExrDecoderTests.cs index dd958e87ab..ef2a0100be 100644 --- a/tests/ImageSharp.Tests/Formats/Exr/ExrDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Exr/ExrDecoderTests.cs @@ -121,6 +121,7 @@ public void ExrDecoder_CanDecode_RunLengthCompressed(TestImageProvider(TestImageProvider provider) where TPixel : unmanaged, IPixel { diff --git a/tests/ImageSharp.Tests/TestImages.cs b/tests/ImageSharp.Tests/TestImages.cs index 90adae34cc..b08e26dc7d 100644 --- a/tests/ImageSharp.Tests/TestImages.cs +++ b/tests/ImageSharp.Tests/TestImages.cs @@ -1396,6 +1396,7 @@ public static class 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 Rgb = "Exr/Calliphora_rgb.exr"; public const string Gray = "Exr/Calliphora_gray.exr"; } 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 From e8c9d6db9d2c57dec674933c0a66f6866204b396 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Sun, 26 Apr 2026 11:01:36 +0200 Subject: [PATCH 06/11] Add test case for pxr compression with pixel type uint --- .../ImageSharp.Tests/Formats/Exr/ExrDecoderTests.cs | 12 ++++++++++++ tests/ImageSharp.Tests/TestImages.cs | 1 + ...xrPixelType_Uint_Rgba32_Calliphora_uint_pxr24.png | 3 +++ tests/Images/Input/Exr/Calliphora_uint_pxr24.exr | 3 +++ 4 files changed, 19 insertions(+) create mode 100644 tests/Images/External/ReferenceOutput/ExrDecoderTests/ExrDecoder_CanDecode_Pxr24Compressed_ExrPixelType_Uint_Rgba32_Calliphora_uint_pxr24.png create mode 100644 tests/Images/Input/Exr/Calliphora_uint_pxr24.exr diff --git a/tests/ImageSharp.Tests/Formats/Exr/ExrDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Exr/ExrDecoderTests.cs index ef2a0100be..febdc9dae7 100644 --- a/tests/ImageSharp.Tests/Formats/Exr/ExrDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Exr/ExrDecoderTests.cs @@ -130,6 +130,18 @@ public void ExrDecoder_CanDecode_Pxr24Compressed(TestImageProvider(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 b08e26dc7d..4f15650cd2 100644 --- a/tests/ImageSharp.Tests/TestImages.cs +++ b/tests/ImageSharp.Tests/TestImages.cs @@ -1397,6 +1397,7 @@ public static class 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_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 From e917fd317afb08a575380c543da367fc52906e4d Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Sun, 26 Apr 2026 11:30:08 +0200 Subject: [PATCH 07/11] Fix issues with pxr and pixel type uint and float --- .../Exr/Compression/Decompressors/Pxr24Compression.cs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/ImageSharp/Formats/Exr/Compression/Decompressors/Pxr24Compression.cs b/src/ImageSharp/Formats/Exr/Compression/Decompressors/Pxr24Compression.cs index d2fdcfcd16..e15ac4e845 100644 --- a/src/ImageSharp/Formats/Exr/Compression/Decompressors/Pxr24Compression.cs +++ b/src/ImageSharp/Formats/Exr/Compression/Decompressors/Pxr24Compression.cs @@ -47,7 +47,9 @@ public Pxr24Compression(MemoryAllocator allocator, uint bytesPerBlock, uint byte public override void Decompress(BufferedReadStream stream, uint compressedBytes, Span buffer) { Span uncompressed = this.tmpBuffer.GetSpan(); - Span outputBuffer = MemoryMarshal.Cast(buffer); + Span outputBufferHalf = MemoryMarshal.Cast(buffer); + Span outputBufferFloat = MemoryMarshal.Cast(buffer); + Span outputBufferUint = MemoryMarshal.Cast(buffer); long pos = stream.Position; using ZlibInflateStream inflateStream = new( @@ -106,7 +108,7 @@ public override void Decompress(BufferedReadStream stream, uint compressedBytes, uint diff = (t0 << 24) | (t1 << 16) | (t2 << 8) | t3; pixel += diff; - outputBuffer[outputOffset] = (ushort)pixel; + outputBufferUint[outputOffset] = pixel; offsetT0++; offsetT1++; @@ -133,7 +135,7 @@ public override void Decompress(BufferedReadStream stream, uint compressedBytes, uint diff = (t0 << 8) | t1; pixel += diff; - outputBuffer[outputOffset] = (ushort)pixel; + outputBufferHalf[outputOffset] = (ushort)pixel; offsetT0++; offsetT1++; @@ -161,7 +163,7 @@ public override void Decompress(BufferedReadStream stream, uint compressedBytes, uint diff = (t0 << 24) | (t1 << 16) | (t2 << 8); pixel += diff; - outputBuffer[outputOffset] = (ushort)pixel; + outputBufferFloat[outputOffset] = pixel; offsetT0++; offsetT1++; From a85fe63d312b401058b3636ab3c3b50ef3780587 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Sun, 26 Apr 2026 14:50:52 +0200 Subject: [PATCH 08/11] Move undoing zip compression into base class --- .../Decompressors/Pxr24Compression.cs | 29 +------------ .../Decompressors/ZipExrCompression.cs | 29 +------------ .../Exr/Compression/ExrBaseDecompressor.cs | 43 +++++++++++++++++++ 3 files changed, 47 insertions(+), 54 deletions(-) diff --git a/src/ImageSharp/Formats/Exr/Compression/Decompressors/Pxr24Compression.cs b/src/ImageSharp/Formats/Exr/Compression/Decompressors/Pxr24Compression.cs index e15ac4e845..115f455f04 100644 --- a/src/ImageSharp/Formats/Exr/Compression/Decompressors/Pxr24Compression.cs +++ b/src/ImageSharp/Formats/Exr/Compression/Decompressors/Pxr24Compression.cs @@ -51,33 +51,8 @@ public override void Decompress(BufferedReadStream stream, uint compressedBytes, Span outputBufferFloat = MemoryMarshal.Cast(buffer); Span outputBufferUint = MemoryMarshal.Cast(buffer); - 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 = this.BytesPerBlock; + UndoZipCompression(stream, compressedBytes, uncompressed, uncompressedBytes); int lastIn = 0; int outputOffset = 0; diff --git a/src/ImageSharp/Formats/Exr/Compression/Decompressors/ZipExrCompression.cs b/src/ImageSharp/Formats/Exr/Compression/Decompressors/ZipExrCompression.cs index 4333f53cb5..51f8ab4c38 100644 --- a/src/ImageSharp/Formats/Exr/Compression/Decompressors/ZipExrCompression.cs +++ b/src/ImageSharp/Formats/Exr/Compression/Decompressors/ZipExrCompression.cs @@ -31,33 +31,8 @@ public override void Decompress(BufferedReadStream stream, uint compressedBytes, { 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/ExrBaseDecompressor.cs b/src/ImageSharp/Formats/Exr/Compression/ExrBaseDecompressor.cs index 000e6ecea8..b958982cec 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; @@ -31,6 +33,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. From 7d38e50d3f3ccb6e544716a91ba5f7f62ee476d3 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Sun, 26 Apr 2026 14:57:27 +0200 Subject: [PATCH 09/11] Use better test image for float pixel type created with reference encoder --- tests/ImageSharp.Tests/Formats/Exr/ExrDecoderTests.cs | 3 +-- tests/ImageSharp.Tests/TestImages.cs | 2 +- tests/Images/Input/Exr/Calliphora_float_uncompressed.exr | 3 +++ tests/Images/Input/Exr/rgb_float32_uncompressed.exr | 3 --- 4 files changed, 5 insertions(+), 6 deletions(-) create mode 100644 tests/Images/Input/Exr/Calliphora_float_uncompressed.exr delete mode 100644 tests/Images/Input/Exr/rgb_float32_uncompressed.exr diff --git a/tests/ImageSharp.Tests/Formats/Exr/ExrDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Exr/ExrDecoderTests.cs index febdc9dae7..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); } diff --git a/tests/ImageSharp.Tests/TestImages.cs b/tests/ImageSharp.Tests/TestImages.cs index 4f15650cd2..2624d1cdad 100644 --- a/tests/ImageSharp.Tests/TestImages.cs +++ b/tests/ImageSharp.Tests/TestImages.cs @@ -1388,7 +1388,7 @@ 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"; 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/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 From 2bf2e9563853127f1d2f55f2e62ef671741f4924 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Sun, 26 Apr 2026 15:12:55 +0200 Subject: [PATCH 10/11] Move rowsPerBlock to base compression class --- .../Compressors/NoneExrCompressor.cs | 5 +++-- .../Compressors/ZipExrCompressor.cs | 5 +++-- .../Decompressors/B44ExrCompression.cs | 19 ++++++++----------- .../Decompressors/NoneExrCompression.cs | 5 +++-- .../Decompressors/Pxr24Compression.cs | 9 ++------- .../Decompressors/RunLengthExrCompression.cs | 4 ++-- .../Decompressors/ZipExrCompression.cs | 7 +++---- .../Exr/Compression/ExrBaseCompression.cs | 9 ++++++++- .../Exr/Compression/ExrBaseDecompressor.cs | 5 +++-- .../Exr/Compression/ExrCompressorFactory.cs | 8 +++++--- .../Exr/Compression/ExrDecompressorFactory.cs | 9 +++++---- .../Formats/Exr/ExrBaseCompressor.cs | 5 +++-- src/ImageSharp/Formats/Exr/ExrEncoderCore.cs | 4 ++-- 13 files changed, 50 insertions(+), 44 deletions(-) diff --git a/src/ImageSharp/Formats/Exr/Compression/Compressors/NoneExrCompressor.cs b/src/ImageSharp/Formats/Exr/Compression/Compressors/NoneExrCompressor.cs index c1240667e7..8aca0da65e 100644 --- a/src/ImageSharp/Formats/Exr/Compression/Compressors/NoneExrCompressor.cs +++ b/src/ImageSharp/Formats/Exr/Compression/Compressors/NoneExrCompressor.cs @@ -17,9 +17,10 @@ internal class NoneExrCompressor : ExrBaseCompressor /// The memory allocator. /// Bytes per row block. /// Bytes per pixel row. + /// The pixel rows per block. /// The witdh of one row in pixels. - public NoneExrCompressor(Stream output, MemoryAllocator allocator, uint bytesPerBlock, uint bytesPerRow, int width) - : base(output, allocator, bytesPerBlock, bytesPerRow, width) + 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 bf5f078585..01248cc395 100644 --- a/src/ImageSharp/Formats/Exr/Compression/Compressors/ZipExrCompressor.cs +++ b/src/ImageSharp/Formats/Exr/Compression/Compressors/ZipExrCompressor.cs @@ -24,10 +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, int width, DeflateCompressionLevel compressionLevel) - : base(output, allocator, bytesPerBlock, bytesPerRow, width) + 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 64774537fa..06df7b6a25 100644 --- a/src/ImageSharp/Formats/Exr/Compression/Decompressors/B44ExrCompression.cs +++ b/src/ImageSharp/Formats/Exr/Compression/Decompressors/B44ExrCompression.cs @@ -13,8 +13,6 @@ namespace SixLabors.ImageSharp.Formats.Exr.Compression.Decompressors; /// internal class B44ExrCompression : ExrBaseDecompressor { - private readonly uint rowsPerBlock; - private readonly int channelCount; private readonly byte[] scratch = new byte[14]; @@ -29,13 +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, width) + : base(allocator, bytesPerBlock, bytesPerRow, rowsPerBlock, width) { - this.rowsPerBlock = rowsPerBlock; this.channelCount = channelCount; this.tmpBuffer = allocator.Allocate((int)(width * rowsPerBlock * channelCount)); } @@ -49,7 +46,7 @@ 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; @@ -88,7 +85,7 @@ public override void Decompress(BufferedReadStream stream, uint compressedBytes, } int n = x + 3 < this.Width ? 4 : this.Width - x; - if (y + 3 < this.rowsPerBlock) + if (y + 3 < this.RowsPerBlock) { this.s.AsSpan(0, n).CopyTo(row0[rowOffset..]); this.s.AsSpan(4, n).CopyTo(row1[rowOffset..]); @@ -98,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..]); } @@ -122,8 +119,8 @@ 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++) { diff --git a/src/ImageSharp/Formats/Exr/Compression/Decompressors/NoneExrCompression.cs b/src/ImageSharp/Formats/Exr/Compression/Decompressors/NoneExrCompression.cs index 1b045bdeb7..19edb31afe 100644 --- a/src/ImageSharp/Formats/Exr/Compression/Decompressors/NoneExrCompression.cs +++ b/src/ImageSharp/Formats/Exr/Compression/Decompressors/NoneExrCompression.cs @@ -17,9 +17,10 @@ internal class NoneExrCompression : ExrBaseDecompressor /// The memory allocator. /// The bytes per pixel row block. /// The bytes per pixel row. + /// The pixel rows per block. /// The number of pixels per row. - public NoneExrCompression(MemoryAllocator allocator, uint bytesPerBlock, uint bytesPerRow, int width) - : base(allocator, bytesPerBlock, bytesPerRow, width) + 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 index 115f455f04..f45b660e7d 100644 --- a/src/ImageSharp/Formats/Exr/Compression/Decompressors/Pxr24Compression.cs +++ b/src/ImageSharp/Formats/Exr/Compression/Decompressors/Pxr24Compression.cs @@ -2,9 +2,7 @@ // Licensed under the Six Labors Split License. using System.Buffers; -using System.IO.Compression; using System.Runtime.InteropServices; -using SixLabors.ImageSharp.Compression.Zlib; using SixLabors.ImageSharp.Formats.Exr.Constants; using SixLabors.ImageSharp.IO; using SixLabors.ImageSharp.Memory; @@ -18,8 +16,6 @@ internal class Pxr24Compression : ExrBaseDecompressor { private readonly IMemoryOwner tmpBuffer; - private readonly uint rowsPerBlock; - private readonly int channelCount; private readonly ExrPixelType pixelType; @@ -35,10 +31,9 @@ internal class Pxr24Compression : ExrBaseDecompressor /// 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, width) + : base(allocator, bytesPerBlock, bytesPerRow, rowsPerBlock, width) { this.tmpBuffer = allocator.Allocate((int)bytesPerBlock); - this.rowsPerBlock = rowsPerBlock; this.channelCount = channelCount; this.pixelType = pixelType; } @@ -56,7 +51,7 @@ public override void Decompress(BufferedReadStream stream, uint compressedBytes, int lastIn = 0; int outputOffset = 0; - for (int y = 0; y < this.rowsPerBlock; y++) + for (int y = 0; y < this.RowsPerBlock; y++) { for (int c = 0; c < this.channelCount; c++) { diff --git a/src/ImageSharp/Formats/Exr/Compression/Decompressors/RunLengthExrCompression.cs b/src/ImageSharp/Formats/Exr/Compression/Decompressors/RunLengthExrCompression.cs index 29782ae03d..bb3903deb8 100644 --- a/src/ImageSharp/Formats/Exr/Compression/Decompressors/RunLengthExrCompression.cs +++ b/src/ImageSharp/Formats/Exr/Compression/Decompressors/RunLengthExrCompression.cs @@ -23,8 +23,8 @@ internal class RunLengthExrCompression : ExrBaseDecompressor /// The bytes per pixel row block. /// The bytes per row. /// The witdh of one row in pixels. - public RunLengthExrCompression(MemoryAllocator allocator, uint bytesPerBlock, uint bytesPerRow, int width) - : base(allocator, bytesPerBlock, bytesPerRow, width) => this.tmpBuffer = allocator.Allocate((int)bytesPerBlock); + 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 51f8ab4c38..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,9 +20,10 @@ internal class ZipExrCompression : ExrBaseDecompressor /// 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. - public ZipExrCompression(MemoryAllocator allocator, uint bytesPerBlock, uint bytesPerRow, int width) - : base(allocator, bytesPerBlock, bytesPerRow, width) => this.tmpBuffer = allocator.Allocate((int)bytesPerBlock); + 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) diff --git a/src/ImageSharp/Formats/Exr/Compression/ExrBaseCompression.cs b/src/ImageSharp/Formats/Exr/Compression/ExrBaseCompression.cs index 5912fcdecf..72d5be980b 100644 --- a/src/ImageSharp/Formats/Exr/Compression/ExrBaseCompression.cs +++ b/src/ImageSharp/Formats/Exr/Compression/ExrBaseCompression.cs @@ -18,12 +18,14 @@ internal abstract class ExrBaseCompression : IDisposable /// The memory allocator. /// The bytes per block. /// The bytes per row. + /// The number of pixel rows per block. /// The number of pixels of a row. - protected ExrBaseCompression(MemoryAllocator allocator, uint bytesPerBlock, uint bytesPerRow, int width) + 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; } @@ -47,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 b958982cec..5dfbdf148f 100644 --- a/src/ImageSharp/Formats/Exr/Compression/ExrBaseDecompressor.cs +++ b/src/ImageSharp/Formats/Exr/Compression/ExrBaseDecompressor.cs @@ -19,9 +19,10 @@ internal abstract class ExrBaseDecompressor : ExrBaseCompression /// The memory allocator. /// The bytes per row block. /// The bytes per row. + /// The pixel rows per block. /// The number of pixels per row. - protected ExrBaseDecompressor(MemoryAllocator allocator, uint bytesPerBlock, uint bytesPerRow, int width) - : base(allocator, bytesPerBlock, bytesPerRow, width) + protected ExrBaseDecompressor(MemoryAllocator allocator, uint bytesPerBlock, uint bytesPerRow, uint rowsPerBlock, int width) + : base(allocator, bytesPerBlock, bytesPerRow, rowsPerBlock, width) { } diff --git a/src/ImageSharp/Formats/Exr/Compression/ExrCompressorFactory.cs b/src/ImageSharp/Formats/Exr/Compression/ExrCompressorFactory.cs index 497bd492aa..0a7d4157f2 100644 --- a/src/ImageSharp/Formats/Exr/Compression/ExrCompressorFactory.cs +++ b/src/ImageSharp/Formats/Exr/Compression/ExrCompressorFactory.cs @@ -21,6 +21,7 @@ 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. @@ -30,12 +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, width), - ExrCompression.Zips => new ZipExrCompressor(output, allocator, bytesPerBlock, bytesPerRow, width, compressionLevel), - ExrCompression.Zip => new ZipExrCompressor(output, allocator, bytesPerBlock, bytesPerRow, width, 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 6f63a932b7..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, @@ -33,10 +34,10 @@ public static ExrBaseDecompressor Create( int channelCount, ExrPixelType pixelType) => method switch { - ExrCompression.None => new NoneExrCompression(memoryAllocator, bytesPerBlock, bytesPerRow, width), - ExrCompression.Zips => new ZipExrCompression(memoryAllocator, bytesPerBlock, bytesPerRow, width), - ExrCompression.Zip => new ZipExrCompression(memoryAllocator, bytesPerBlock, bytesPerRow, width), - ExrCompression.RunLengthEncoded => new RunLengthExrCompression(memoryAllocator, bytesPerBlock, bytesPerRow, width), + 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 83887fd5f1..9e3fe8ec65 100644 --- a/src/ImageSharp/Formats/Exr/ExrBaseCompressor.cs +++ b/src/ImageSharp/Formats/Exr/ExrBaseCompressor.cs @@ -14,9 +14,10 @@ internal abstract class ExrBaseCompressor : ExrBaseCompression /// The memory allocator. /// Bytes per row block. /// Bytes per pixel row. + /// The pixel rows per block. /// The number of pixels per row. - protected ExrBaseCompressor(Stream output, MemoryAllocator allocator, uint bytesPerBlock, uint bytesPerRow, int width) - : base(allocator, bytesPerBlock, bytesPerRow, width) + 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/ExrEncoderCore.cs b/src/ImageSharp/Formats/Exr/ExrEncoderCore.cs index c6824c6aad..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, width); + 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, width); + using ExrBaseCompressor compressor = ExrCompressorFactory.Create(compression, this.memoryAllocator, stream, bytesPerBlock, bytesPerRow, rowsPerBlock, width); Rgba128 rgb = default; ulong[] rowOffsets = new ulong[height]; From 7ad873aaf36211abc537ecca6ac7bae1f876d339 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Sun, 26 Apr 2026 15:39:18 +0200 Subject: [PATCH 11/11] Fix stylecop warnings --- .../Exr/Compression/Decompressors/RunLengthExrCompression.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/ImageSharp/Formats/Exr/Compression/Decompressors/RunLengthExrCompression.cs b/src/ImageSharp/Formats/Exr/Compression/Decompressors/RunLengthExrCompression.cs index bb3903deb8..f548a81810 100644 --- a/src/ImageSharp/Formats/Exr/Compression/Decompressors/RunLengthExrCompression.cs +++ b/src/ImageSharp/Formats/Exr/Compression/Decompressors/RunLengthExrCompression.cs @@ -14,14 +14,13 @@ 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. + /// 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);