From f9df91108a8600696e0b1fa700fbe355c4a5a09f Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Mon, 23 Mar 2026 18:14:00 +0100 Subject: [PATCH] Add support for writing exr with zip compression with 16 rows per block --- .../Compressors/ZipExrCompressor.cs | 6 +- src/ImageSharp/Formats/Exr/ExrDecoderCore.cs | 4 +- src/ImageSharp/Formats/Exr/ExrEncoderCore.cs | 78 +++++++++++-------- 3 files changed, 50 insertions(+), 38 deletions(-) diff --git a/src/ImageSharp/Formats/Exr/Compression/Compressors/ZipExrCompressor.cs b/src/ImageSharp/Formats/Exr/Compression/Compressors/ZipExrCompressor.cs index 6b82b70777..8a7b784601 100644 --- a/src/ImageSharp/Formats/Exr/Compression/Compressors/ZipExrCompressor.cs +++ b/src/ImageSharp/Formats/Exr/Compression/Compressors/ZipExrCompressor.cs @@ -29,8 +29,8 @@ internal class ZipExrCompressor : ExrBaseCompressor public override uint CompressRowBlock(Span rows, int rowCount) { // Re-oder pixel values. - int n = rows.Length; - Span reordered = this.buffer.GetSpan(); + Span reordered = this.buffer.GetSpan()[..(int)(rowCount * this.BytesPerRow)]; + int n = reordered.Length; int t1 = 0; int t2 = (n + 1) >> 1; for (int i = 0; i < n; i++) @@ -42,7 +42,7 @@ internal class ZipExrCompressor : ExrBaseCompressor // Predictor. Span predicted = reordered; byte p = predicted[0]; - for (int i = 1; i < rows.Length; i++) + for (int i = 1; i < predicted.Length; i++) { int d = (predicted[i] - p + 128 + 256) & 255; p = predicted[i]; diff --git a/src/ImageSharp/Formats/Exr/ExrDecoderCore.cs b/src/ImageSharp/Formats/Exr/ExrDecoderCore.cs index dbc6b7bf71..f8eb17f4e8 100644 --- a/src/ImageSharp/Formats/Exr/ExrDecoderCore.cs +++ b/src/ImageSharp/Formats/Exr/ExrDecoderCore.cs @@ -142,7 +142,7 @@ internal sealed class ExrDecoderCore : ImageDecoderCore using IMemoryOwner rowBuffer = this.memoryAllocator.Allocate(width * 4); using IMemoryOwner decompressedPixelDataBuffer = this.memoryAllocator.Allocate((int)bytesPerBlock); Span decompressedPixelData = decompressedPixelDataBuffer.GetSpan(); - Span redPixelData = rowBuffer.GetSpan().Slice(0, width); + Span redPixelData = rowBuffer.GetSpan()[..width]; Span greenPixelData = rowBuffer.GetSpan().Slice(width, width); Span bluePixelData = rowBuffer.GetSpan().Slice(width * 2, width); Span alphaPixelData = rowBuffer.GetSpan().Slice(width * 3, width); @@ -195,7 +195,7 @@ internal sealed class ExrDecoderCore : ImageDecoderCore using IMemoryOwner rowBuffer = this.memoryAllocator.Allocate(width * 4); using IMemoryOwner decompressedPixelDataBuffer = this.memoryAllocator.Allocate((int)bytesPerBlock); Span decompressedPixelData = decompressedPixelDataBuffer.GetSpan(); - Span redPixelData = rowBuffer.GetSpan().Slice(0, width); + Span redPixelData = rowBuffer.GetSpan()[..width]; Span greenPixelData = rowBuffer.GetSpan().Slice(width, width); Span bluePixelData = rowBuffer.GetSpan().Slice(width * 2, width); Span alphaPixelData = rowBuffer.GetSpan().Slice(width * 3, width); diff --git a/src/ImageSharp/Formats/Exr/ExrEncoderCore.cs b/src/ImageSharp/Formats/Exr/ExrEncoderCore.cs index a7b37eda28..a89caf07eb 100644 --- a/src/ImageSharp/Formats/Exr/ExrEncoderCore.cs +++ b/src/ImageSharp/Formats/Exr/ExrEncoderCore.cs @@ -172,41 +172,47 @@ internal sealed class ExrEncoderCore using ExrBaseCompressor compressor = ExrCompressorFactory.Create(compression, this.memoryAllocator, stream, bytesPerBlock, bytesPerRow); ulong[] rowOffsets = new ulong[height]; - for (int y = 0; y < height; y++) + for (uint y = 0; y < height; y += rowsPerBlock) { rowOffsets[y] = (ulong)stream.Position; // Write row index. - BinaryPrimitives.WriteUInt32LittleEndian(this.buffer, (uint)y); + BinaryPrimitives.WriteUInt32LittleEndian(this.buffer, y); stream.Write(this.buffer.AsSpan(0, 4)); // At this point, it is not yet known how much bytes the compressed data will take up, keep stream position. long pixelDataSizePos = stream.Position; - Span pixelRowSpan = pixels.DangerousGetRowSpan(y); stream.Position = pixelDataSizePos + 4; - for (int x = 0; x < width; x++) + uint rowsInBlockCount = 0; + for (uint rowIndex = y; rowIndex < y + rowsPerBlock && rowIndex < height; rowIndex++) { - Vector4 vector4 = pixelRowSpan[x].ToVector4(); - redBuffer[x] = vector4.X; - greenBuffer[x] = vector4.Y; - blueBuffer[x] = vector4.Z; - } + Span pixelRowSpan = pixels.DangerousGetRowSpan((int)rowIndex); + for (int x = 0; x < width; x++) + { + Vector4 vector4 = pixelRowSpan[x].ToVector4(); + redBuffer[x] = vector4.X; + greenBuffer[x] = vector4.Y; + blueBuffer[x] = vector4.Z; + } - // Write pixel data to buffer. - Span rowBlockSpan = rowBlockBuffer.GetSpan(); - switch (this.pixelType) - { - case ExrPixelType.Float: - this.WriteSingleRow(rowBlockSpan, width, blueBuffer, greenBuffer, redBuffer); - break; - case ExrPixelType.Half: - this.WriteHalfSingleRow(rowBlockSpan, width, blueBuffer, greenBuffer, redBuffer); - break; + // Write pixel data to row block buffer. + Span rowBlockSpan = rowBlockBuffer.GetSpan().Slice((int)(rowsInBlockCount * bytesPerRow), (int)bytesPerRow); + switch (this.pixelType) + { + case ExrPixelType.Float: + this.WriteSingleRow(rowBlockSpan, width, blueBuffer, greenBuffer, redBuffer); + break; + case ExrPixelType.Half: + this.WriteHalfSingleRow(rowBlockSpan, width, blueBuffer, greenBuffer, redBuffer); + break; + } + + rowsInBlockCount++; } // Write compressed pixel row data to the stream. - uint compressedBytes = compressor.CompressRowBlock(rowBlockSpan, (int)rowsPerBlock); + uint compressedBytes = compressor.CompressRowBlock(rowBlockBuffer.GetSpan(), (int)rowsInBlockCount); long positionAfterPixelData = stream.Position; // Write pixel row data size. @@ -243,34 +249,40 @@ internal sealed class ExrEncoderCore Rgb96 rgb = default; ulong[] rowOffsets = new ulong[height]; - for (int y = 0; y < height; y++) + for (uint y = 0; y < height; y += rowsPerBlock) { rowOffsets[y] = (ulong)stream.Position; // Write row index. - BinaryPrimitives.WriteUInt32LittleEndian(this.buffer, (uint)y); + BinaryPrimitives.WriteUInt32LittleEndian(this.buffer, y); stream.Write(this.buffer.AsSpan(0, 4)); // At this point, it is not yet known how much bytes the compressed data will take up, keep stream position. long pixelDataSizePos = stream.Position; - Span pixelRowSpan = pixels.DangerousGetRowSpan(y); stream.Position = pixelDataSizePos + 4; - for (int x = 0; x < width; x++) + uint rowsInBlockCount = 0; + for (uint rowIndex = y; rowIndex < y + rowsPerBlock && rowIndex < height; rowIndex++) { - Vector4 vector4 = pixelRowSpan[x].ToVector4(); - Rgb96.FromVector4(vector4); + Span pixelRowSpan = pixels.DangerousGetRowSpan((int)rowIndex); + for (int x = 0; x < width; x++) + { + Vector4 vector4 = pixelRowSpan[x].ToVector4(); + Rgb96.FromVector4(vector4); - redBuffer[x] = rgb.R; - greenBuffer[x] = rgb.G; - blueBuffer[x] = rgb.B; - } + redBuffer[x] = rgb.R; + greenBuffer[x] = rgb.G; + blueBuffer[x] = rgb.B; + } - // Write row data to buffer. - this.WriteUnsignedIntRow(rowBlockBuffer.GetSpan(), width, blueBuffer, greenBuffer, redBuffer); + // Write row data to row block buffer. + Span rowBlockSpan = rowBlockBuffer.GetSpan().Slice((int)(rowsInBlockCount * bytesPerRow), (int)bytesPerRow); + this.WriteUnsignedIntRow(rowBlockSpan, width, blueBuffer, greenBuffer, redBuffer); + rowsInBlockCount++; + } // Write pixel row data compressed to the stream. - uint compressedBytes = compressor.CompressRowBlock(rowBlockBuffer.GetSpan(), 1); + uint compressedBytes = compressor.CompressRowBlock(rowBlockBuffer.GetSpan(), (int)rowsInBlockCount); long positionAfterPixelData = stream.Position; // Write pixel row data size.