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; + } + } }