diff --git a/src/ImageSharp/Formats/Exr/Compression/Compressors/NoCompressor.cs b/src/ImageSharp/Formats/Exr/Compression/Compressors/NoCompressor.cs
deleted file mode 100644
index 9a9bc8fff6..0000000000
--- a/src/ImageSharp/Formats/Exr/Compression/Compressors/NoCompressor.cs
+++ /dev/null
@@ -1,8 +0,0 @@
-// Copyright (c) Six Labors.
-// Licensed under the Six Labors Split License.
-
-namespace SixLabors.ImageSharp.Formats.Exr.Compression.Compressors;
-
-internal class NoCompressor
-{
-}
diff --git a/src/ImageSharp/Formats/Exr/Compression/Compressors/NoneExrCompressor.cs b/src/ImageSharp/Formats/Exr/Compression/Compressors/NoneExrCompressor.cs
new file mode 100644
index 0000000000..e8f3a0686f
--- /dev/null
+++ b/src/ImageSharp/Formats/Exr/Compression/Compressors/NoneExrCompressor.cs
@@ -0,0 +1,31 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+
+using SixLabors.ImageSharp.Formats.Exr.Constants;
+using SixLabors.ImageSharp.Memory;
+
+namespace SixLabors.ImageSharp.Formats.Exr.Compression.Compressors;
+
+internal class NoneExrCompressor : ExrBaseCompressor
+{
+ public NoneExrCompressor(Stream output, MemoryAllocator allocator, uint bytesPerBlock)
+ : base(output, allocator, bytesPerBlock)
+ {
+ }
+
+ ///
+ public override ExrCompression Method => ExrCompression.Zip;
+
+ ///
+ public override void Initialize(int rowsPerBlock)
+ {
+ }
+
+ ///
+ public override void CompressStrip(Span rows, int height) => this.Output.Write(rows);
+
+ ///
+ protected override void Dispose(bool disposing)
+ {
+ }
+}
diff --git a/src/ImageSharp/Formats/Exr/Compression/Compressors/ZipCompressor.cs b/src/ImageSharp/Formats/Exr/Compression/Compressors/ZipExrCompressor.cs
similarity index 83%
rename from src/ImageSharp/Formats/Exr/Compression/Compressors/ZipCompressor.cs
rename to src/ImageSharp/Formats/Exr/Compression/Compressors/ZipExrCompressor.cs
index d205c2f1a3..af93664f33 100644
--- a/src/ImageSharp/Formats/Exr/Compression/Compressors/ZipCompressor.cs
+++ b/src/ImageSharp/Formats/Exr/Compression/Compressors/ZipExrCompressor.cs
@@ -7,13 +7,13 @@ using SixLabors.ImageSharp.Memory;
namespace SixLabors.ImageSharp.Formats.Exr.Compression.Compressors;
-internal class ZipCompressor : ExrBaseCompressor
+internal class ZipExrCompressor : ExrBaseCompressor
{
private readonly DeflateCompressionLevel compressionLevel;
private readonly MemoryStream memoryStream = new();
- public ZipCompressor(Stream output, MemoryAllocator allocator, uint bytesPerBlock, DeflateCompressionLevel compressionLevel)
+ public ZipExrCompressor(Stream output, MemoryAllocator allocator, uint bytesPerBlock, DeflateCompressionLevel compressionLevel)
: base(output, allocator, bytesPerBlock)
=> this.compressionLevel = compressionLevel;
@@ -21,7 +21,7 @@ internal class ZipCompressor : ExrBaseCompressor
public override ExrCompression Method => ExrCompression.Zip;
///
- public override void Initialize(int rowsPerStrip)
+ public override void Initialize(int rowsPerBlock)
{
}
diff --git a/src/ImageSharp/Formats/Exr/Compression/Decompressors/B44Compression.cs b/src/ImageSharp/Formats/Exr/Compression/Decompressors/B44ExrCompression.cs
similarity index 97%
rename from src/ImageSharp/Formats/Exr/Compression/Decompressors/B44Compression.cs
rename to src/ImageSharp/Formats/Exr/Compression/Decompressors/B44ExrCompression.cs
index d0f9eb6efe..1c6afdfd91 100644
--- a/src/ImageSharp/Formats/Exr/Compression/Decompressors/B44Compression.cs
+++ b/src/ImageSharp/Formats/Exr/Compression/Decompressors/B44ExrCompression.cs
@@ -8,7 +8,7 @@ using SixLabors.ImageSharp.Memory;
namespace SixLabors.ImageSharp.Formats.Exr.Compression.Decompressors;
-internal class B44Compression : ExrBaseDecompressor
+internal class B44ExrCompression : ExrBaseDecompressor
{
private readonly int width;
@@ -24,7 +24,7 @@ internal class B44Compression : ExrBaseDecompressor
private IMemoryOwner tmpBuffer;
- public B44Compression(MemoryAllocator allocator, uint bytesPerBlock, int width, int height, uint rowsPerBlock, int channelCount)
+ public B44ExrCompression(MemoryAllocator allocator, uint bytesPerBlock, int width, int height, uint rowsPerBlock, int channelCount)
: base(allocator, bytesPerBlock)
{
this.width = width;
diff --git a/src/ImageSharp/Formats/Exr/Compression/Decompressors/RunLengthCompression.cs b/src/ImageSharp/Formats/Exr/Compression/Decompressors/RunLengthExrCompression.cs
similarity index 93%
rename from src/ImageSharp/Formats/Exr/Compression/Decompressors/RunLengthCompression.cs
rename to src/ImageSharp/Formats/Exr/Compression/Decompressors/RunLengthExrCompression.cs
index 15538a813e..bc5a9a5579 100644
--- a/src/ImageSharp/Formats/Exr/Compression/Decompressors/RunLengthCompression.cs
+++ b/src/ImageSharp/Formats/Exr/Compression/Decompressors/RunLengthExrCompression.cs
@@ -7,13 +7,13 @@ using SixLabors.ImageSharp.Memory;
namespace SixLabors.ImageSharp.Formats.Exr.Compression.Decompressors;
-internal class RunLengthCompression : ExrBaseDecompressor
+internal class RunLengthExrCompression : ExrBaseDecompressor
{
private readonly IMemoryOwner tmpBuffer;
private readonly ushort[] s = new ushort[16];
- public RunLengthCompression(MemoryAllocator allocator, uint uncompressedBytes)
+ public RunLengthExrCompression(MemoryAllocator allocator, uint uncompressedBytes)
: base(allocator, uncompressedBytes) => this.tmpBuffer = allocator.Allocate((int)uncompressedBytes);
public override void Decompress(BufferedReadStream stream, uint compressedBytes, Span buffer)
diff --git a/src/ImageSharp/Formats/Exr/Compression/ExrCompressorFactory.cs b/src/ImageSharp/Formats/Exr/Compression/ExrCompressorFactory.cs
index fc403be3a0..4482ca7633 100644
--- a/src/ImageSharp/Formats/Exr/Compression/ExrCompressorFactory.cs
+++ b/src/ImageSharp/Formats/Exr/Compression/ExrCompressorFactory.cs
@@ -12,13 +12,20 @@ internal static class ExrCompressorFactory
{
public static ExrBaseCompressor Create(
ExrCompression method,
- Stream output,
MemoryAllocator allocator,
+ Stream output,
int width,
- DeflateCompressionLevel compressionLevel)
+ int height,
+ uint bytesPerBlock,
+ uint rowsPerBlock,
+ int channelCount,
+ DeflateCompressionLevel compressionLevel = DeflateCompressionLevel.DefaultCompression)
{
switch (method)
{
+ case ExrCompression.None:
+ return new NoneExrCompressor(output, allocator, bytesPerBlock);
+
default:
throw ExrThrowHelper.NotSupportedCompressor(method.ToString());
}
diff --git a/src/ImageSharp/Formats/Exr/Compression/ExrDecompressorFactory.cs b/src/ImageSharp/Formats/Exr/Compression/ExrDecompressorFactory.cs
index 6764a2a130..fb620e9854 100644
--- a/src/ImageSharp/Formats/Exr/Compression/ExrDecompressorFactory.cs
+++ b/src/ImageSharp/Formats/Exr/Compression/ExrDecompressorFactory.cs
@@ -9,7 +9,14 @@ namespace SixLabors.ImageSharp.Formats.Exr.Compression;
internal static class ExrDecompressorFactory
{
- public static ExrBaseDecompressor Create(ExrCompression method, MemoryAllocator memoryAllocator, uint bytesPerBlock, int width, int height, uint rowsPerBlock, int channelCount)
+ public static ExrBaseDecompressor Create(
+ ExrCompression method,
+ MemoryAllocator memoryAllocator,
+ int width,
+ int height,
+ uint bytesPerBlock,
+ uint rowsPerBlock,
+ int channelCount)
{
switch (method)
{
@@ -20,9 +27,9 @@ internal static class ExrDecompressorFactory
case ExrCompression.Zip:
return new ZipExrCompression(memoryAllocator, bytesPerBlock);
case ExrCompression.RunLengthEncoded:
- return new RunLengthCompression(memoryAllocator, bytesPerBlock);
+ return new RunLengthExrCompression(memoryAllocator, bytesPerBlock);
case ExrCompression.B44:
- return new B44Compression(memoryAllocator, bytesPerBlock, width, height, rowsPerBlock, channelCount);
+ return new B44ExrCompression(memoryAllocator, bytesPerBlock, width, height, rowsPerBlock, channelCount);
default:
throw ExrThrowHelper.NotSupportedDecompressor(nameof(method));
}
diff --git a/src/ImageSharp/Formats/Exr/ExrBaseCompressor.cs b/src/ImageSharp/Formats/Exr/ExrBaseCompressor.cs
index c2a752c614..ee21acb703 100644
--- a/src/ImageSharp/Formats/Exr/ExrBaseCompressor.cs
+++ b/src/ImageSharp/Formats/Exr/ExrBaseCompressor.cs
@@ -31,8 +31,8 @@ internal abstract class ExrBaseCompressor : ExrBaseCompression
///
/// Does any initialization required for the compression.
///
- /// The number of rows per strip.
- public abstract void Initialize(int rowsPerStrip);
+ /// The number of rows per block.
+ public abstract void Initialize(int rowsPerBlock);
///
/// Compresses a strip of the image.
diff --git a/src/ImageSharp/Formats/Exr/ExrDecoderCore.cs b/src/ImageSharp/Formats/Exr/ExrDecoderCore.cs
index 7f53d8a24b..c7c05a9c54 100644
--- a/src/ImageSharp/Formats/Exr/ExrDecoderCore.cs
+++ b/src/ImageSharp/Formats/Exr/ExrDecoderCore.cs
@@ -147,7 +147,7 @@ internal sealed class ExrDecoderCore : ImageDecoderCore
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, bytesPerBlock, width, height, rowsPerBlock, channelCount);
+ using ExrBaseDecompressor decompressor = ExrDecompressorFactory.Create(this.Compression, this.memoryAllocator, width, height, bytesPerBlock, rowsPerBlock, channelCount);
for (uint y = 0; y < height; y += rowsPerBlock)
{
@@ -200,7 +200,7 @@ internal sealed class ExrDecoderCore : ImageDecoderCore
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, bytesPerBlock, width, height, rowsPerBlock, channelCount);
+ using ExrBaseDecompressor decompressor = ExrDecompressorFactory.Create(this.Compression, this.memoryAllocator, width, height, bytesPerBlock, rowsPerBlock, channelCount);
for (uint y = 0; y < height; y += rowsPerBlock)
{
diff --git a/src/ImageSharp/Formats/Exr/ExrEncoderCore.cs b/src/ImageSharp/Formats/Exr/ExrEncoderCore.cs
index ff5287b752..7151a642ab 100644
--- a/src/ImageSharp/Formats/Exr/ExrEncoderCore.cs
+++ b/src/ImageSharp/Formats/Exr/ExrEncoderCore.cs
@@ -5,7 +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;
using SixLabors.ImageSharp.Metadata;
@@ -47,7 +47,7 @@ internal sealed class ExrEncoderCore
/// Initializes a new instance of the class.
///
/// The encoder with options.
- /// The configuration.
+ /// The configuration.
/// The memory manager.
public ExrEncoderCore(ExrEncoder encoder, Configuration configuration, MemoryAllocator memoryAllocator)
{
@@ -126,7 +126,7 @@ internal sealed class ExrEncoderCore
{
case ExrPixelType.Half:
case ExrPixelType.Float:
- this.EncodeFloatingPointPixelData(stream, pixels, width, height, rowSizeBytes);
+ this.EncodeFloatingPointPixelData(stream, pixels, width, height, rowSizeBytes, channels, compression);
break;
case ExrPixelType.UnsignedInt:
this.EncodeUnsignedIntPixelData(stream, pixels, width, height, rowSizeBytes);
@@ -134,43 +134,60 @@ internal sealed class ExrEncoderCore
}
}
- private void EncodeFloatingPointPixelData(Stream stream, Buffer2D pixels, int width, int height, uint rowSizeBytes)
+ private void EncodeFloatingPointPixelData(
+ Stream stream,
+ Buffer2D pixels,
+ int width,
+ int height,
+ uint rowSizeBytes,
+ 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 blockBuffer = 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);
+
for (int y = 0; y < height; y++)
{
+ // 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));
+
Span pixelRowSpan = pixels.DangerousGetRowSpan(y);
for (int x = 0; x < width; x++)
{
- var vector4 = pixelRowSpan[x].ToVector4();
+ Vector4 vector4 = pixelRowSpan[x].ToVector4();
redBuffer[x] = vector4.X;
greenBuffer[x] = vector4.Y;
blueBuffer[x] = vector4.Z;
}
- // 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));
-
switch (this.pixelType)
{
case ExrPixelType.Float:
- this.WriteSingleRow(stream, width, blueBuffer, greenBuffer, redBuffer);
+ this.WriteSingleRow(blockBuffer.GetSpan(), width, blueBuffer, greenBuffer, redBuffer);
break;
case ExrPixelType.Half:
- this.WriteHalfSingleRow(stream, width, blueBuffer, greenBuffer, redBuffer);
+ this.WriteHalfSingleRow(blockBuffer.GetSpan(), width, blueBuffer, greenBuffer, redBuffer);
break;
}
+
+ compressor.CompressStrip(blockBuffer.GetSpan(), 1);
}
}
@@ -240,6 +257,50 @@ internal sealed class ExrEncoderCore
}
}
+ private void WriteSingleRow(Span buffer, int width, Span blueBuffer, Span greenBuffer, Span redBuffer)
+ {
+ int offset = 0;
+ for (int x = 0; x < width; x++)
+ {
+ this.WriteSingle(buffer.Slice(offset), blueBuffer[x]);
+ offset += 4;
+ }
+
+ for (int x = 0; x < width; x++)
+ {
+ this.WriteSingle(buffer.Slice(offset), greenBuffer[x]);
+ offset += 4;
+ }
+
+ for (int x = 0; x < width; x++)
+ {
+ this.WriteSingle(buffer.Slice(offset), redBuffer[x]);
+ offset += 4;
+ }
+ }
+
+ private void WriteHalfSingleRow(Span buffer, int width, Span blueBuffer, Span greenBuffer, Span redBuffer)
+ {
+ int offset = 0;
+ for (int x = 0; x < width; x++)
+ {
+ this.WriteHalfSingle(buffer.Slice(offset), blueBuffer[x]);
+ offset += 2;
+ }
+
+ for (int x = 0; x < width; x++)
+ {
+ this.WriteHalfSingle(buffer.Slice(offset), greenBuffer[x]);
+ offset += 2;
+ }
+
+ for (int x = 0; x < width; x++)
+ {
+ this.WriteHalfSingle(buffer.Slice(offset), redBuffer[x]);
+ offset += 2;
+ }
+ }
+
private void WriteHalfSingleRow(Stream stream, int width, Span blueBuffer, Span greenBuffer, Span redBuffer)
{
for (int x = 0; x < width; x++)
@@ -420,6 +481,12 @@ internal sealed class ExrEncoderCore
stream.Write(this.buffer.AsSpan(0, 4));
}
+ [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)
{
@@ -428,10 +495,62 @@ internal sealed class ExrEncoderCore
stream.Write(this.buffer.AsSpan(0, 2));
}
+
+ [MethodImpl(InliningOptions.ShortMethod)]
+ private void WriteHalfSingle(Span buffer, float value)
+ {
+ ushort valueAsShort = HalfTypeHelper.Pack(value);
+ BinaryPrimitives.WriteUInt16LittleEndian(this.buffer, valueAsShort);
+ }
+
[MethodImpl(InliningOptions.ShortMethod)]
private void WriteUnsignedInt(Stream stream, uint value)
{
BinaryPrimitives.WriteUInt32LittleEndian(this.buffer, value);
stream.Write(this.buffer.AsSpan(0, 4));
}
+
+ // TODO: avoid code duplication: This code is duplicate in the decoder.
+ private uint CalculateBytesPerRow(List channels, uint width)
+ {
+ uint bytesPerRow = 0;
+ foreach (ExrChannelInfo channelInfo in channels)
+ {
+ if (channelInfo.ChannelName.Equals("A", StringComparison.Ordinal)
+ || channelInfo.ChannelName.Equals("R", StringComparison.Ordinal)
+ || channelInfo.ChannelName.Equals("G", StringComparison.Ordinal)
+ || channelInfo.ChannelName.Equals("B", StringComparison.Ordinal)
+ || channelInfo.ChannelName.Equals("Y", StringComparison.Ordinal))
+ {
+ if (channelInfo.PixelType == ExrPixelType.Half)
+ {
+ bytesPerRow += 2 * width;
+ }
+ else
+ {
+ bytesPerRow += 4 * width;
+ }
+ }
+ }
+
+ return bytesPerRow;
+ }
+
+ // TODO: avoid code duplication: This code is duplicate in the decoder.
+ private uint RowsPerBlock(ExrCompression compression)
+ {
+ switch (compression)
+ {
+ case ExrCompression.Zip:
+ case ExrCompression.Pxr24:
+ return 16;
+ case ExrCompression.B44:
+ case ExrCompression.B44A:
+ case ExrCompression.Piz:
+ return 32;
+
+ default:
+ return 1;
+ }
+ }
}