Browse Source

Refactor exr encoder for compression

pull/3096/head
Brian Popow 3 months ago
parent
commit
3ff9d24422
  1. 8
      src/ImageSharp/Formats/Exr/Compression/Compressors/NoCompressor.cs
  2. 31
      src/ImageSharp/Formats/Exr/Compression/Compressors/NoneExrCompressor.cs
  3. 6
      src/ImageSharp/Formats/Exr/Compression/Compressors/ZipExrCompressor.cs
  4. 4
      src/ImageSharp/Formats/Exr/Compression/Decompressors/B44ExrCompression.cs
  5. 4
      src/ImageSharp/Formats/Exr/Compression/Decompressors/RunLengthExrCompression.cs
  6. 11
      src/ImageSharp/Formats/Exr/Compression/ExrCompressorFactory.cs
  7. 13
      src/ImageSharp/Formats/Exr/Compression/ExrDecompressorFactory.cs
  8. 4
      src/ImageSharp/Formats/Exr/ExrBaseCompressor.cs
  9. 4
      src/ImageSharp/Formats/Exr/ExrDecoderCore.cs
  10. 151
      src/ImageSharp/Formats/Exr/ExrEncoderCore.cs

8
src/ImageSharp/Formats/Exr/Compression/Compressors/NoCompressor.cs

@ -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
{
}

31
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)
{
}
/// <inheritdoc/>
public override ExrCompression Method => ExrCompression.Zip;
/// <inheritdoc/>
public override void Initialize(int rowsPerBlock)
{
}
/// <inheritdoc/>
public override void CompressStrip(Span<byte> rows, int height) => this.Output.Write(rows);
/// <inheritdoc/>
protected override void Dispose(bool disposing)
{
}
}

6
src/ImageSharp/Formats/Exr/Compression/Compressors/ZipCompressor.cs → 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;
/// <inheritdoc/>
public override void Initialize(int rowsPerStrip)
public override void Initialize(int rowsPerBlock)
{
}

4
src/ImageSharp/Formats/Exr/Compression/Decompressors/B44Compression.cs → 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<ushort> 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;

4
src/ImageSharp/Formats/Exr/Compression/Decompressors/RunLengthCompression.cs → 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<byte> 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<byte>((int)uncompressedBytes);
public override void Decompress(BufferedReadStream stream, uint compressedBytes, Span<byte> buffer)

11
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());
}

13
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));
}

4
src/ImageSharp/Formats/Exr/ExrBaseCompressor.cs

@ -31,8 +31,8 @@ internal abstract class ExrBaseCompressor : ExrBaseCompression
/// <summary>
/// Does any initialization required for the compression.
/// </summary>
/// <param name="rowsPerStrip">The number of rows per strip.</param>
public abstract void Initialize(int rowsPerStrip);
/// <param name="rowsPerBlock">The number of rows per block.</param>
public abstract void Initialize(int rowsPerBlock);
/// <summary>
/// Compresses a strip of the image.

4
src/ImageSharp/Formats/Exr/ExrDecoderCore.cs

@ -147,7 +147,7 @@ internal sealed class ExrDecoderCore : ImageDecoderCore
Span<float> bluePixelData = rowBuffer.GetSpan().Slice(width * 2, width);
Span<float> 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<uint> bluePixelData = rowBuffer.GetSpan().Slice(width * 2, width);
Span<uint> 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)
{

151
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 <see cref="ExrEncoderCore"/> class.
/// </summary>
/// <param name="encoder">The encoder with options.</param>
/// <param name="configuration">The configuration.</param>
/// <param name="configuration">The configuration.</param>
/// <param name="memoryAllocator">The memory manager.</param>
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<TPixel>(Stream stream, Buffer2D<TPixel> pixels, int width, int height, uint rowSizeBytes)
private void EncodeFloatingPointPixelData<TPixel>(
Stream stream,
Buffer2D<TPixel> pixels,
int width,
int height,
uint rowSizeBytes,
List<ExrChannelInfo> channels,
ExrCompression compression)
where TPixel : unmanaged, IPixel<TPixel>
{
using IMemoryOwner<float> rgbBuffer = this.memoryAllocator.Allocate<float>(width * 3);
uint bytesPerRow = this.CalculateBytesPerRow(channels, (uint)width);
uint rowsPerBlock = this.RowsPerBlock(compression);
uint bytesPerBlock = bytesPerRow * rowsPerBlock;
int channelCount = channels.Count;
using IMemoryOwner<float> rgbBuffer = this.memoryAllocator.Allocate<float>(width * 3, AllocationOptions.Clean);
using IMemoryOwner<byte> blockBuffer = this.memoryAllocator.Allocate<byte>((int)bytesPerBlock, AllocationOptions.Clean);
Span<float> redBuffer = rgbBuffer.GetSpan().Slice(0, width);
Span<float> greenBuffer = rgbBuffer.GetSpan().Slice(width, width);
Span<float> 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<TPixel> 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<byte> buffer, int width, Span<float> blueBuffer, Span<float> greenBuffer, Span<float> 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<byte> buffer, int width, Span<float> blueBuffer, Span<float> greenBuffer, Span<float> 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<float> blueBuffer, Span<float> greenBuffer, Span<float> 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<byte> 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<byte> 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<ExrChannelInfo> 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;
}
}
}

Loading…
Cancel
Save