Browse Source

Use compressor when writing pixel row data with exr encoder

pull/3096/head
Brian Popow 2 months ago
parent
commit
0d515147d0
  1. 6
      src/ImageSharp/Formats/Exr/Compression/Compressors/NoneExrCompressor.cs
  2. 3
      src/ImageSharp/Formats/Exr/Compression/Compressors/ZipExrCompressor.cs
  3. 2
      src/ImageSharp/Formats/Exr/Compression/ExrCompressorFactory.cs
  4. 5
      src/ImageSharp/Formats/Exr/ExrBaseCompressor.cs
  5. 2
      src/ImageSharp/Formats/Exr/ExrDecoderCore.cs
  6. 179
      src/ImageSharp/Formats/Exr/ExrEncoderCore.cs

6
src/ImageSharp/Formats/Exr/Compression/Compressors/NoneExrCompressor.cs

@ -22,7 +22,11 @@ internal class NoneExrCompressor : ExrBaseCompressor
}
/// <inheritdoc/>
public override void CompressStrip(Span<byte> rows, int height) => this.Output.Write(rows);
public override uint CompressRowBlock(Span<byte> rows, int height)
{
this.Output.Write(rows);
return (uint)rows.Length;
}
/// <inheritdoc/>
protected override void Dispose(bool disposing)

3
src/ImageSharp/Formats/Exr/Compression/Compressors/ZipExrCompressor.cs

@ -26,7 +26,7 @@ internal class ZipExrCompressor : ExrBaseCompressor
}
/// <inheritdoc/>
public override void CompressStrip(Span<byte> rows, int height)
public override uint CompressRowBlock(Span<byte> 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;
}
/// <inheritdoc/>

2
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());

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

@ -35,9 +35,10 @@ internal abstract class ExrBaseCompressor : ExrBaseCompression
public abstract void Initialize(int rowsPerBlock);
/// <summary>
/// Compresses a strip of the image.
/// Compresses a block of rows of the image.
/// </summary>
/// <param name="rows">Image rows to compress.</param>
/// <param name="height">Image height.</param>
public abstract void CompressStrip(Span<byte> rows, int height);
/// <returns>Number of bytes of of the compressed data.</returns>
public abstract uint CompressRowBlock(Span<byte> rows, int height);
}

2
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;
}
/// <summary>

179
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;
}
/// <summary>
/// Gets or sets the compression implementation to use when encoding the image.
/// </summary>
internal ExrCompression Compression { get; set; }
/// <summary>
/// Encodes the image to the specified stream from the <see cref="ImageFrame{TPixel}"/>.
/// </summary>
@ -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<TPixel>(
private ulong[] EncodeFloatingPointPixelData<TPixel>(
Stream stream,
Buffer2D<TPixel> pixels,
int width,
int height,
uint rowSizeBytes,
List<ExrChannelInfo> channels,
ExrCompression compression)
where TPixel : unmanaged, IPixel<TPixel>
@ -150,24 +165,26 @@ internal sealed class ExrEncoderCore
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);
using IMemoryOwner<byte> rowBlockBuffer = 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);
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<TPixel> 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<TPixel>(Stream stream, Buffer2D<TPixel> pixels, int width, int height, uint rowSizeBytes)
private ulong[] EncodeUnsignedIntPixelData<TPixel>(
Stream stream,
Buffer2D<TPixel> pixels,
int width,
int height,
List<ExrChannelInfo> channels,
ExrCompression compression)
where TPixel : unmanaged, IPixel<TPixel>
{
using IMemoryOwner<uint> rgbBuffer = this.memoryAllocator.Allocate<uint>(width * 3);
uint bytesPerRow = this.CalculateBytesPerRow(channels, (uint)width);
uint rowsPerBlock = this.RowsPerBlock(compression);
uint bytesPerBlock = bytesPerRow * rowsPerBlock;
int channelCount = channels.Count;
using IMemoryOwner<uint> rgbBuffer = this.memoryAllocator.Allocate<uint>(width * 3, AllocationOptions.Clean);
using IMemoryOwner<byte> rowBlockBuffer = this.memoryAllocator.Allocate<byte>((int)bytesPerBlock, AllocationOptions.Clean);
Span<uint> redBuffer = rgbBuffer.GetSpan().Slice(0, width);
Span<uint> greenBuffer = rgbBuffer.GetSpan().Slice(width, width);
Span<uint> 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<TPixel> 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<float> blueBuffer, Span<float> greenBuffer, Span<float> 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<byte> buffer, int width, Span<float> blueBuffer, Span<float> greenBuffer, Span<float> redBuffer)
{
int offset = 0;
@ -301,51 +340,36 @@ internal sealed class ExrEncoderCore
}
}
private void WriteHalfSingleRow(Stream stream, int width, Span<float> blueBuffer, Span<float> greenBuffer, Span<float> 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<uint> blueBuffer, Span<uint> greenBuffer, Span<uint> redBuffer)
private void WriteUnsignedIntRow(Span<byte> buffer, int width, Span<uint> blueBuffer, Span<uint> greenBuffer, Span<uint> 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<byte> 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<byte> buffer, float value) => BinaryPrimitives.WriteInt32LittleEndian(buffer, *(int*)&value);
[MethodImpl(InliningOptions.ShortMethod)]
private void WriteHalfSingle(Span<byte> 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<byte> buffer, uint value) => BinaryPrimitives.WriteUInt32LittleEndian(buffer, value);
// TODO: avoid code duplication: This code is duplicate in the decoder.
private uint CalculateBytesPerRow(List<ExrChannelInfo> channels, uint width)
{

Loading…
Cancel
Save