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 @@ internal class B44ExrCompression : ExrBaseDecompressor ExrThrowHelper.ThrowInvalidImageContentException("Could not read enough data from the stream!"); } + // Check if 3-byte encoded flat field. if (this.scratch[2] >= 13 << 2) { Unpack3(this.scratch, this.s); 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 @@ internal static class ExrDecompressorFactory 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 @@ internal sealed class ExrDecoderCore : ImageDecoderCore /// 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, };