diff --git a/src/ImageSharp/Formats/Exr/Compression/Compressors/NoneExrCompressor.cs b/src/ImageSharp/Formats/Exr/Compression/Compressors/NoneExrCompressor.cs
index e8f3a0686f..40ee81cf03 100644
--- a/src/ImageSharp/Formats/Exr/Compression/Compressors/NoneExrCompressor.cs
+++ b/src/ImageSharp/Formats/Exr/Compression/Compressors/NoneExrCompressor.cs
@@ -22,7 +22,11 @@ internal class NoneExrCompressor : ExrBaseCompressor
}
///
- public override void CompressStrip(Span rows, int height) => this.Output.Write(rows);
+ public override uint CompressRowBlock(Span rows, int height)
+ {
+ this.Output.Write(rows);
+ return (uint)rows.Length;
+ }
///
protected override void Dispose(bool disposing)
diff --git a/src/ImageSharp/Formats/Exr/Compression/Compressors/ZipExrCompressor.cs b/src/ImageSharp/Formats/Exr/Compression/Compressors/ZipExrCompressor.cs
index af93664f33..e7b695df0c 100644
--- a/src/ImageSharp/Formats/Exr/Compression/Compressors/ZipExrCompressor.cs
+++ b/src/ImageSharp/Formats/Exr/Compression/Compressors/ZipExrCompressor.cs
@@ -26,7 +26,7 @@ internal class ZipExrCompressor : ExrBaseCompressor
}
///
- public override void CompressStrip(Span rows, int height)
+ public override uint CompressRowBlock(Span rows, int height)
{
this.memoryStream.Seek(0, SeekOrigin.Begin);
using (ZlibDeflateStream stream = new(this.Allocator, this.memoryStream, this.compressionLevel))
@@ -38,6 +38,7 @@ internal class ZipExrCompressor : ExrBaseCompressor
int size = (int)this.memoryStream.Position;
byte[] buffer = this.memoryStream.GetBuffer();
this.Output.Write(buffer, 0, size);
+ return (uint)buffer.Length;
}
///
diff --git a/src/ImageSharp/Formats/Exr/Compression/ExrCompressorFactory.cs b/src/ImageSharp/Formats/Exr/Compression/ExrCompressorFactory.cs
index 4482ca7633..e8e7af4712 100644
--- a/src/ImageSharp/Formats/Exr/Compression/ExrCompressorFactory.cs
+++ b/src/ImageSharp/Formats/Exr/Compression/ExrCompressorFactory.cs
@@ -25,6 +25,8 @@ internal static class ExrCompressorFactory
{
case ExrCompression.None:
return new NoneExrCompressor(output, allocator, bytesPerBlock);
+ case ExrCompression.Zip:
+ return new ZipExrCompressor(output, allocator, bytesPerBlock, compressionLevel);
default:
throw ExrThrowHelper.NotSupportedCompressor(method.ToString());
diff --git a/src/ImageSharp/Formats/Exr/ExrBaseCompressor.cs b/src/ImageSharp/Formats/Exr/ExrBaseCompressor.cs
index ee21acb703..cf547eddb9 100644
--- a/src/ImageSharp/Formats/Exr/ExrBaseCompressor.cs
+++ b/src/ImageSharp/Formats/Exr/ExrBaseCompressor.cs
@@ -35,9 +35,10 @@ internal abstract class ExrBaseCompressor : ExrBaseCompression
public abstract void Initialize(int rowsPerBlock);
///
- /// Compresses a strip of the image.
+ /// Compresses a block of rows of the image.
///
/// Image rows to compress.
/// Image height.
- public abstract void CompressStrip(Span rows, int height);
+ /// Number of bytes of of the compressed data.
+ public abstract uint CompressRowBlock(Span rows, int height);
}
diff --git a/src/ImageSharp/Formats/Exr/ExrDecoderCore.cs b/src/ImageSharp/Formats/Exr/ExrDecoderCore.cs
index c7c05a9c54..7165720d30 100644
--- a/src/ImageSharp/Formats/Exr/ExrDecoderCore.cs
+++ b/src/ImageSharp/Formats/Exr/ExrDecoderCore.cs
@@ -53,7 +53,7 @@ internal sealed class ExrDecoderCore : ImageDecoderCore
: base(options.GeneralOptions)
{
this.configuration = options.GeneralOptions.Configuration;
- this.memoryAllocator = this.configuration.MemoryAllocator;
+ this.memoryAllocator = this.configuration.MemoryAllocator;
}
///
diff --git a/src/ImageSharp/Formats/Exr/ExrEncoderCore.cs b/src/ImageSharp/Formats/Exr/ExrEncoderCore.cs
index 7151a642ab..9e63413445 100644
--- a/src/ImageSharp/Formats/Exr/ExrEncoderCore.cs
+++ b/src/ImageSharp/Formats/Exr/ExrEncoderCore.cs
@@ -5,6 +5,7 @@ using System.Buffers;
using System.Buffers.Binary;
using System.Numerics;
using System.Runtime.CompilerServices;
+using System.Threading.Channels;
using SixLabors.ImageSharp.Formats.Exr.Compression;
using SixLabors.ImageSharp.Formats.Exr.Constants;
using SixLabors.ImageSharp.Memory;
@@ -54,8 +55,14 @@ internal sealed class ExrEncoderCore
this.configuration = configuration;
this.encoder = encoder;
this.memoryAllocator = memoryAllocator;
+ this.Compression = encoder.Compression ?? ExrCompression.None;
}
+ ///
+ /// Gets or sets the compression implementation to use when encoding the image.
+ ///
+ internal ExrCompression Compression { get; set; }
+
///
/// Encodes the image to the specified stream from the .
///
@@ -76,7 +83,6 @@ internal sealed class ExrEncoderCore
this.pixelType ??= exrMetadata.PixelType;
int width = image.Width;
int height = image.Height;
- ExrCompression compression = ExrCompression.None;
float aspectRatio = 1.0f;
ExrBox2i dataWindow = new(0, 0, width - 1, height - 1);
ExrBox2i displayWindow = new(0, 0, width - 1, height - 1);
@@ -91,7 +97,7 @@ internal sealed class ExrEncoderCore
];
ExrHeaderAttributes header = new(
channels,
- compression,
+ this.Compression,
dataWindow,
displayWindow,
lineOrder,
@@ -115,31 +121,40 @@ internal sealed class ExrEncoderCore
// Write EXR header.
this.WriteHeader(stream, header);
- // Write offsets to each pixel row.
+ // Next is offsets table to each pixel row, which will be written after the pixel data was written.
int bytesPerChannel = this.pixelType == ExrPixelType.Half ? 2 : 4;
int numberOfChannels = 3;
uint rowSizeBytes = (uint)(width * numberOfChannels * bytesPerChannel);
- this.WriteRowOffsets(stream, height, rowSizeBytes);
+ ulong startOfRowOffsetData = (ulong)stream.Position;
+ stream.Position += 8 * height;
// Write pixel data.
switch (this.pixelType)
{
case ExrPixelType.Half:
case ExrPixelType.Float:
- this.EncodeFloatingPointPixelData(stream, pixels, width, height, rowSizeBytes, channels, compression);
+ {
+ ulong[] rowOffsets = this.EncodeFloatingPointPixelData(stream, pixels, width, height, channels, this.Compression);
+ stream.Position = (long)startOfRowOffsetData;
+ this.WriteRowOffsets(stream, height, rowOffsets);
break;
+ }
+
case ExrPixelType.UnsignedInt:
- this.EncodeUnsignedIntPixelData(stream, pixels, width, height, rowSizeBytes);
+ {
+ ulong[] rowOffsets = this.EncodeUnsignedIntPixelData(stream, pixels, width, height, channels, this.Compression);
+ stream.Position = (long)startOfRowOffsetData;
+ this.WriteRowOffsets(stream, height, rowOffsets);
break;
+ }
}
}
- private void EncodeFloatingPointPixelData(
+ private ulong[] EncodeFloatingPointPixelData(
Stream stream,
Buffer2D pixels,
int width,
int height,
- uint rowSizeBytes,
List channels,
ExrCompression compression)
where TPixel : unmanaged, IPixel
@@ -150,24 +165,26 @@ internal sealed class ExrEncoderCore
int channelCount = channels.Count;
using IMemoryOwner rgbBuffer = this.memoryAllocator.Allocate(width * 3, AllocationOptions.Clean);
- using IMemoryOwner blockBuffer = this.memoryAllocator.Allocate((int)bytesPerBlock, AllocationOptions.Clean);
+ using IMemoryOwner rowBlockBuffer = this.memoryAllocator.Allocate((int)bytesPerBlock, AllocationOptions.Clean);
Span redBuffer = rgbBuffer.GetSpan().Slice(0, width);
Span greenBuffer = rgbBuffer.GetSpan().Slice(width, width);
Span blueBuffer = rgbBuffer.GetSpan().Slice(width * 2, width);
using ExrBaseCompressor compressor = ExrCompressorFactory.Create(compression, this.memoryAllocator, stream, width, height, bytesPerBlock, rowsPerBlock, channelCount);
+ ulong[] rowOffsets = new ulong[height];
for (int y = 0; y < height; y++)
{
+ rowOffsets[y] = (ulong)stream.Position;
+
// Write row index.
BinaryPrimitives.WriteUInt32LittleEndian(this.buffer, (uint)y);
stream.Write(this.buffer.AsSpan(0, 4));
- // Write pixel row data size.
- BinaryPrimitives.WriteUInt32LittleEndian(this.buffer, rowSizeBytes);
- stream.Write(this.buffer.AsSpan(0, 4));
-
+ // At this point, it is not yet known how mcuh 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++)
{
@@ -177,32 +194,67 @@ internal sealed class ExrEncoderCore
blueBuffer[x] = vector4.Z;
}
+ // Write pixel data to buffer.
switch (this.pixelType)
{
case ExrPixelType.Float:
- this.WriteSingleRow(blockBuffer.GetSpan(), width, blueBuffer, greenBuffer, redBuffer);
+ this.WriteSingleRow(rowBlockBuffer.GetSpan(), width, blueBuffer, greenBuffer, redBuffer);
break;
case ExrPixelType.Half:
- this.WriteHalfSingleRow(blockBuffer.GetSpan(), width, blueBuffer, greenBuffer, redBuffer);
+ this.WriteHalfSingleRow(rowBlockBuffer.GetSpan(), width, blueBuffer, greenBuffer, redBuffer);
break;
}
- compressor.CompressStrip(blockBuffer.GetSpan(), 1);
+ // Write compressed pixel row data to stream.
+ uint compressedBytes = compressor.CompressRowBlock(rowBlockBuffer.GetSpan(), 1);
+ long positionAfterPixelData = stream.Position;
+
+ // Write pixel row data size.
+ BinaryPrimitives.WriteUInt32LittleEndian(this.buffer, compressedBytes);
+ stream.Position = pixelDataSizePos;
+ stream.Write(this.buffer.AsSpan(0, 4));
+ stream.Position = positionAfterPixelData;
}
+
+ return rowOffsets;
}
- private void EncodeUnsignedIntPixelData(Stream stream, Buffer2D pixels, int width, int height, uint rowSizeBytes)
+ private ulong[] EncodeUnsignedIntPixelData(
+ Stream stream,
+ Buffer2D pixels,
+ int width,
+ int height,
+ List channels,
+ ExrCompression compression)
where TPixel : unmanaged, IPixel
{
- using IMemoryOwner rgbBuffer = this.memoryAllocator.Allocate(width * 3);
+ uint bytesPerRow = this.CalculateBytesPerRow(channels, (uint)width);
+ uint rowsPerBlock = this.RowsPerBlock(compression);
+ uint bytesPerBlock = bytesPerRow * rowsPerBlock;
+ int channelCount = channels.Count;
+
+ using IMemoryOwner rgbBuffer = this.memoryAllocator.Allocate(width * 3, AllocationOptions.Clean);
+ using IMemoryOwner rowBlockBuffer = this.memoryAllocator.Allocate((int)bytesPerBlock, AllocationOptions.Clean);
Span redBuffer = rgbBuffer.GetSpan().Slice(0, width);
Span greenBuffer = rgbBuffer.GetSpan().Slice(width, width);
Span blueBuffer = rgbBuffer.GetSpan().Slice(width * 2, width);
+ using ExrBaseCompressor compressor = ExrCompressorFactory.Create(compression, this.memoryAllocator, stream, width, height, bytesPerBlock, rowsPerBlock, channelCount);
+
Rgb96 rgb = default;
+ ulong[] rowOffsets = new ulong[height];
for (int y = 0; y < height; y++)
{
+ rowOffsets[y] = (ulong)stream.Position;
+
+ // Write row index.
+ BinaryPrimitives.WriteUInt32LittleEndian(this.buffer, (uint)y);
+ stream.Write(this.buffer.AsSpan(0, 4));
+
+ // At this point, it is not yet known how mcuh 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++)
{
@@ -214,16 +266,21 @@ internal sealed class ExrEncoderCore
blueBuffer[x] = rgb.B;
}
- // Write row index.
- BinaryPrimitives.WriteUInt32LittleEndian(this.buffer, (uint)y);
- stream.Write(this.buffer.AsSpan(0, 4));
+ // Write row data to buffer.
+ this.WriteUnsignedIntRow(rowBlockBuffer.GetSpan(), width, blueBuffer, greenBuffer, redBuffer);
+
+ // Write pixel row data compressed to the stream.
+ uint compressedBytes = compressor.CompressRowBlock(rowBlockBuffer.GetSpan(), 1);
+ long positionAfterPixelData = stream.Position;
// Write pixel row data size.
- BinaryPrimitives.WriteUInt32LittleEndian(this.buffer, rowSizeBytes);
+ BinaryPrimitives.WriteUInt32LittleEndian(this.buffer, compressedBytes);
+ stream.Position = pixelDataSizePos;
stream.Write(this.buffer.AsSpan(0, 4));
-
- this.WriteUnsignedIntRow(stream, width, blueBuffer, greenBuffer, redBuffer);
+ stream.Position = positionAfterPixelData;
}
+
+ return rowOffsets;
}
private void WriteHeader(Stream stream, ExrHeaderAttributes header)
@@ -239,24 +296,6 @@ internal sealed class ExrEncoderCore
stream.WriteByte(0);
}
- private void WriteSingleRow(Stream stream, int width, Span blueBuffer, Span greenBuffer, Span redBuffer)
- {
- for (int x = 0; x < width; x++)
- {
- this.WriteSingle(stream, blueBuffer[x]);
- }
-
- for (int x = 0; x < width; x++)
- {
- this.WriteSingle(stream, greenBuffer[x]);
- }
-
- for (int x = 0; x < width; x++)
- {
- this.WriteSingle(stream, redBuffer[x]);
- }
- }
-
private void WriteSingleRow(Span buffer, int width, Span blueBuffer, Span greenBuffer, Span redBuffer)
{
int offset = 0;
@@ -301,51 +340,36 @@ internal sealed class ExrEncoderCore
}
}
- private void WriteHalfSingleRow(Stream stream, int width, Span blueBuffer, Span greenBuffer, Span redBuffer)
- {
- for (int x = 0; x < width; x++)
- {
- this.WriteHalfSingle(stream, blueBuffer[x]);
- }
-
- for (int x = 0; x < width; x++)
- {
- this.WriteHalfSingle(stream, greenBuffer[x]);
- }
-
- for (int x = 0; x < width; x++)
- {
- this.WriteHalfSingle(stream, redBuffer[x]);
- }
- }
-
- private void WriteUnsignedIntRow(Stream stream, int width, Span blueBuffer, Span greenBuffer, Span redBuffer)
+ private void WriteUnsignedIntRow(Span buffer, int width, Span blueBuffer, Span greenBuffer, Span redBuffer)
{
+ int offset = 0;
for (int x = 0; x < width; x++)
{
- this.WriteUnsignedInt(stream, blueBuffer[x]);
+ this.WriteUnsignedInt(buffer.Slice(offset), blueBuffer[x]);
+ offset += 4;
}
for (int x = 0; x < width; x++)
{
- this.WriteUnsignedInt(stream, greenBuffer[x]);
+ this.WriteUnsignedInt(buffer.Slice(offset), greenBuffer[x]);
+ offset += 4;
}
for (int x = 0; x < width; x++)
{
- this.WriteUnsignedInt(stream, redBuffer[x]);
+ this.WriteUnsignedInt(buffer.Slice(offset), redBuffer[x]);
+ offset += 4;
}
}
- private void WriteRowOffsets(Stream stream, int height, uint rowSizeBytes)
+ private void WriteRowOffsets(Stream stream, int height, ulong[] rowOffsets)
{
- ulong startOfPixelData = (ulong)stream.Position + (8 * (ulong)height);
- ulong offset = startOfPixelData;
+ ulong startOfRowOffsetData = (ulong)stream.Position;
+ ulong offset = startOfRowOffsetData;
for (int i = 0; i < height; i++)
{
- BinaryPrimitives.WriteUInt64LittleEndian(this.buffer, offset);
+ BinaryPrimitives.WriteUInt64LittleEndian(this.buffer, rowOffsets[i]);
stream.Write(this.buffer);
- offset += 4 + 4 + rowSizeBytes;
}
}
@@ -482,19 +506,7 @@ internal sealed class ExrEncoderCore
}
[MethodImpl(InliningOptions.ShortMethod)]
- private unsafe void WriteSingle(Span buffer, float value)
- {
- BinaryPrimitives.WriteInt32LittleEndian(buffer, *(int*)&value);
- }
-
- [MethodImpl(InliningOptions.ShortMethod)]
- private void WriteHalfSingle(Stream stream, float value)
- {
- ushort valueAsShort = HalfTypeHelper.Pack(value);
- BinaryPrimitives.WriteUInt16LittleEndian(this.buffer, valueAsShort);
- stream.Write(this.buffer.AsSpan(0, 2));
- }
-
+ private unsafe void WriteSingle(Span buffer, float value) => BinaryPrimitives.WriteInt32LittleEndian(buffer, *(int*)&value);
[MethodImpl(InliningOptions.ShortMethod)]
private void WriteHalfSingle(Span buffer, float value)
@@ -510,6 +522,9 @@ internal sealed class ExrEncoderCore
stream.Write(this.buffer.AsSpan(0, 4));
}
+ [MethodImpl(InliningOptions.ShortMethod)]
+ private void WriteUnsignedInt(Span buffer, uint value) => BinaryPrimitives.WriteUInt32LittleEndian(buffer, value);
+
// TODO: avoid code duplication: This code is duplicate in the decoder.
private uint CalculateBytesPerRow(List channels, uint width)
{