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