Browse Source

Add support for ZIP compressed exr images

pull/3096/head
Brian Popow 5 years ago
parent
commit
69c8cd8a5f
  1. 8
      src/ImageSharp/Formats/OpenExr/Compression/Compressors/ZipExrCompression.cs
  2. 10
      src/ImageSharp/Formats/OpenExr/Compression/ExrDecompressorFactory.cs
  3. 336
      src/ImageSharp/Formats/OpenExr/ExrDecoderCore.cs

8
src/ImageSharp/Formats/OpenExr/Compression/Compressors/ZipsExrCompression.cs → src/ImageSharp/Formats/OpenExr/Compression/Compressors/ZipExrCompression.cs

@ -10,11 +10,11 @@ using SixLabors.ImageSharp.Memory;
namespace SixLabors.ImageSharp.Formats.OpenExr.Compression.Compressors namespace SixLabors.ImageSharp.Formats.OpenExr.Compression.Compressors
{ {
internal class ZipsExrCompression : ExrBaseDecompressor internal class ZipExrCompression : ExrBaseDecompressor
{ {
private readonly IMemoryOwner<byte> tmpBuffer; private readonly IMemoryOwner<byte> tmpBuffer;
public ZipsExrCompression(MemoryAllocator allocator, uint uncompressedBytes) public ZipExrCompression(MemoryAllocator allocator, uint uncompressedBytes)
: base(allocator, uncompressedBytes) => this.tmpBuffer = allocator.Allocate<byte>((int)uncompressedBytes); : base(allocator, uncompressedBytes) => this.tmpBuffer = allocator.Allocate<byte>((int)uncompressedBytes);
public override void Decompress(BufferedReadStream stream, uint compressedBytes, Span<byte> buffer) public override void Decompress(BufferedReadStream stream, uint compressedBytes, Span<byte> buffer)
@ -44,8 +44,8 @@ namespace SixLabors.ImageSharp.Formats.OpenExr.Compression.Compressors
totalRead += bytesRead; totalRead += bytesRead;
} }
Reconstruct(uncompressed, this.UncompressedBytes); Reconstruct(uncompressed, (uint)totalRead);
Interleave(uncompressed, this.UncompressedBytes, buffer); Interleave(uncompressed, (uint)totalRead, buffer);
} }
protected override void Dispose(bool disposing) => this.tmpBuffer.Dispose(); protected override void Dispose(bool disposing) => this.tmpBuffer.Dispose();

10
src/ImageSharp/Formats/OpenExr/Compression/ExrDecompressorFactory.cs

@ -8,16 +8,18 @@ namespace SixLabors.ImageSharp.Formats.OpenExr.Compression
{ {
internal static class ExrDecompressorFactory internal static class ExrDecompressorFactory
{ {
public static ExrBaseDecompressor Create(ExrCompressionType method, MemoryAllocator memoryAllocator, uint bytesPerRow) public static ExrBaseDecompressor Create(ExrCompressionType method, MemoryAllocator memoryAllocator, uint uncompressedBytes)
{ {
switch (method) switch (method)
{ {
case ExrCompressionType.None: case ExrCompressionType.None:
return new NoneExrCompression(memoryAllocator, bytesPerRow); return new NoneExrCompression(memoryAllocator, uncompressedBytes);
case ExrCompressionType.Zips: case ExrCompressionType.Zips:
return new ZipsExrCompression(memoryAllocator, bytesPerRow); return new ZipExrCompression(memoryAllocator, uncompressedBytes);
case ExrCompressionType.Zip:
return new ZipExrCompression(memoryAllocator, uncompressedBytes);
case ExrCompressionType.RunLengthEncoded: case ExrCompressionType.RunLengthEncoded:
return new RunLengthCompression(memoryAllocator, bytesPerRow); return new RunLengthCompression(memoryAllocator, uncompressedBytes);
default: default:
throw ExrThrowHelper.NotSupportedDecompressor(nameof(method)); throw ExrThrowHelper.NotSupportedDecompressor(nameof(method));
} }

336
src/ImageSharp/Formats/OpenExr/ExrDecoderCore.cs

@ -9,7 +9,6 @@ using System.Runtime.CompilerServices;
using System.Text; using System.Text;
using System.Threading; using System.Threading;
using SixLabors.ImageSharp.Formats.OpenExr.Compression; using SixLabors.ImageSharp.Formats.OpenExr.Compression;
using SixLabors.ImageSharp.Formats.OpenExr.Compression.Compressors;
using SixLabors.ImageSharp.Formats.Pbm; using SixLabors.ImageSharp.Formats.Pbm;
using SixLabors.ImageSharp.IO; using SixLabors.ImageSharp.IO;
using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Memory;
@ -77,7 +76,7 @@ namespace SixLabors.ImageSharp.Formats.OpenExr
{ {
this.ReadExrHeader(stream); this.ReadExrHeader(stream);
if (this.Compression is not ExrCompressionType.None and not ExrCompressionType.Zips and not ExrCompressionType.RunLengthEncoded) if (this.Compression is not ExrCompressionType.None and not ExrCompressionType.Zips and not ExrCompressionType.Zip and not ExrCompressionType.RunLengthEncoded)
{ {
ExrThrowHelper.ThrowNotSupported($"Compression {this.Compression} is not yet supported"); ExrThrowHelper.ThrowNotSupported($"Compression {this.Compression} is not yet supported");
} }
@ -109,19 +108,21 @@ namespace SixLabors.ImageSharp.Formats.OpenExr
{ {
bool hasAlpha = this.HasAlpha(); bool hasAlpha = this.HasAlpha();
uint bytesPerRow = this.CalculateBytesPerRow(); uint bytesPerRow = this.CalculateBytesPerRow();
uint rowsPerBlock = this.RowsPerBlock();
uint bytesPerBlock = bytesPerRow * rowsPerBlock;
using IMemoryOwner<float> rowBuffer = this.memoryAllocator.Allocate<float>(this.Width * 4); using IMemoryOwner<float> rowBuffer = this.memoryAllocator.Allocate<float>(this.Width * 4);
using IMemoryOwner<byte> decompressedPixelDataBuffer = this.memoryAllocator.Allocate<byte>((int)bytesPerRow); using IMemoryOwner<byte> decompressedPixelDataBuffer = this.memoryAllocator.Allocate<byte>((int)bytesPerBlock);
Span<byte> decompressedPixelData = decompressedPixelDataBuffer.GetSpan(); Span<byte> decompressedPixelData = decompressedPixelDataBuffer.GetSpan();
Span<float> redPixelData = rowBuffer.GetSpan().Slice(0, this.Width); Span<float> redPixelData = rowBuffer.GetSpan().Slice(0, this.Width);
Span<float> greenPixelData = rowBuffer.GetSpan().Slice(this.Width, this.Width); Span<float> greenPixelData = rowBuffer.GetSpan().Slice(this.Width, this.Width);
Span<float> bluePixelData = rowBuffer.GetSpan().Slice(this.Width * 2, this.Width); Span<float> bluePixelData = rowBuffer.GetSpan().Slice(this.Width * 2, this.Width);
Span<float> alphaPixelData = rowBuffer.GetSpan().Slice(this.Width * 3, this.Width); Span<float> alphaPixelData = rowBuffer.GetSpan().Slice(this.Width * 3, this.Width);
using ExrBaseDecompressor decompressor = ExrDecompressorFactory.Create(this.Compression, this.memoryAllocator, bytesPerRow); using ExrBaseDecompressor decompressor = ExrDecompressorFactory.Create(this.Compression, this.memoryAllocator, bytesPerBlock);
TPixel color = default; TPixel color = default;
for (int y = 0; y < this.Height; y++) for (uint y = 0; y < this.Height; y += rowsPerBlock)
{ {
stream.Read(this.buffer, 0, 8); stream.Read(this.buffer, 0, 8);
ulong rowOffset = BinaryPrimitives.ReadUInt64LittleEndian(this.buffer); ulong rowOffset = BinaryPrimitives.ReadUInt64LittleEndian(this.buffer);
@ -129,100 +130,102 @@ namespace SixLabors.ImageSharp.Formats.OpenExr
stream.Position = (long)rowOffset; stream.Position = (long)rowOffset;
stream.Read(this.buffer, 0, 4); stream.Read(this.buffer, 0, 4);
uint rowIndex = BinaryPrimitives.ReadUInt32LittleEndian(this.buffer); uint rowStartIndex = BinaryPrimitives.ReadUInt32LittleEndian(this.buffer);
Span<TPixel> pixelRow = pixels.DangerousGetRowSpan((int)rowIndex);
stream.Read(this.buffer, 0, 4); stream.Read(this.buffer, 0, 4);
uint compressedBytesCount = BinaryPrimitives.ReadUInt32LittleEndian(this.buffer); uint compressedBytesCount = BinaryPrimitives.ReadUInt32LittleEndian(this.buffer);
decompressor.Decompress(stream, compressedBytesCount, decompressedPixelData); decompressor.Decompress(stream, compressedBytesCount, decompressedPixelData);
int offset = 0; int offset = 0;
for (int channelIdx = 0; channelIdx < this.Channels.Count; channelIdx++) for (uint rowIndex = rowStartIndex; rowIndex < rowStartIndex + rowsPerBlock && rowIndex < this.Height; rowIndex++)
{ {
ExrChannelInfo channel = this.Channels[channelIdx]; Span<TPixel> pixelRow = pixels.DangerousGetRowSpan((int)rowIndex);
switch (channel.ChannelName) for (int channelIdx = 0; channelIdx < this.Channels.Count; channelIdx++)
{ {
case ExrConstants.ChannelNames.Red: ExrChannelInfo channel = this.Channels[channelIdx];
switch (channel.PixelType) switch (channel.ChannelName)
{ {
case ExrPixelType.Half: case ExrConstants.ChannelNames.Red:
offset += this.ReadPixelRowChannelHalfSingle(decompressedPixelData.Slice(offset), redPixelData); switch (channel.PixelType)
break; {
case ExrPixelType.Float: case ExrPixelType.Half:
offset += this.ReadPixelRowChannelSingle(decompressedPixelData.Slice(offset), redPixelData); offset += this.ReadPixelRowChannelHalfSingle(decompressedPixelData.Slice(offset), redPixelData);
break; break;
} case ExrPixelType.Float:
offset += this.ReadPixelRowChannelSingle(decompressedPixelData.Slice(offset), redPixelData);
break; break;
}
case ExrConstants.ChannelNames.Blue:
switch (channel.PixelType) break;
{
case ExrPixelType.Half: case ExrConstants.ChannelNames.Blue:
offset += this.ReadPixelRowChannelHalfSingle(decompressedPixelData.Slice(offset), bluePixelData); switch (channel.PixelType)
break; {
case ExrPixelType.Float: case ExrPixelType.Half:
offset += this.ReadPixelRowChannelSingle(decompressedPixelData.Slice(offset), bluePixelData); offset += this.ReadPixelRowChannelHalfSingle(decompressedPixelData.Slice(offset), bluePixelData);
break; break;
} case ExrPixelType.Float:
offset += this.ReadPixelRowChannelSingle(decompressedPixelData.Slice(offset), bluePixelData);
break; break;
}
case ExrConstants.ChannelNames.Green:
switch (channel.PixelType) break;
{
case ExrPixelType.Half: case ExrConstants.ChannelNames.Green:
offset += this.ReadPixelRowChannelHalfSingle(decompressedPixelData.Slice(offset), greenPixelData); switch (channel.PixelType)
break; {
case ExrPixelType.Float: case ExrPixelType.Half:
offset += this.ReadPixelRowChannelSingle(decompressedPixelData.Slice(offset), greenPixelData); offset += this.ReadPixelRowChannelHalfSingle(decompressedPixelData.Slice(offset), greenPixelData);
break; break;
} case ExrPixelType.Float:
offset += this.ReadPixelRowChannelSingle(decompressedPixelData.Slice(offset), greenPixelData);
break; break;
}
case ExrConstants.ChannelNames.Alpha:
switch (channel.PixelType) break;
{
case ExrPixelType.Half: case ExrConstants.ChannelNames.Alpha:
offset += this.ReadPixelRowChannelHalfSingle(decompressedPixelData.Slice(offset), alphaPixelData); switch (channel.PixelType)
break; {
case ExrPixelType.Float: case ExrPixelType.Half:
offset += this.ReadPixelRowChannelSingle(decompressedPixelData.Slice(offset), alphaPixelData); offset += this.ReadPixelRowChannelHalfSingle(decompressedPixelData.Slice(offset), alphaPixelData);
break; break;
} case ExrPixelType.Float:
offset += this.ReadPixelRowChannelSingle(decompressedPixelData.Slice(offset), alphaPixelData);
break; break;
}
default:
// Skip unknown channel. break;
int channelDataSizeInBytes = channel.PixelType is ExrPixelType.Float or ExrPixelType.UnsignedInt ? 4 : 2;
stream.Position += this.Width * channelDataSizeInBytes; default:
break; // Skip unknown channel.
int channelDataSizeInBytes = channel.PixelType is ExrPixelType.Float or ExrPixelType.UnsignedInt ? 4 : 2;
stream.Position += this.Width * channelDataSizeInBytes;
break;
}
} }
}
stream.Position = nextRowOffsetPosition;
if (hasAlpha) if (hasAlpha)
{
for (int x = 0; x < this.Width; x++)
{ {
var pixelValue = new HalfVector4(redPixelData[x], greenPixelData[x], bluePixelData[x], alphaPixelData[x]); for (int x = 0; x < this.Width; x++)
color.FromVector4(pixelValue.ToVector4()); {
pixelRow[x] = color; var pixelValue = new HalfVector4(redPixelData[x], greenPixelData[x], bluePixelData[x], alphaPixelData[x]);
color.FromVector4(pixelValue.ToVector4());
pixelRow[x] = color;
}
} }
} else
else
{
for (int x = 0; x < this.Width; x++)
{ {
var pixelValue = new HalfVector4(redPixelData[x], greenPixelData[x], bluePixelData[x], 1.0f); for (int x = 0; x < this.Width; x++)
color.FromVector4(pixelValue.ToVector4()); {
pixelRow[x] = color; var pixelValue = new HalfVector4(redPixelData[x], greenPixelData[x], bluePixelData[x], 1.0f);
color.FromVector4(pixelValue.ToVector4());
pixelRow[x] = color;
}
} }
} }
stream.Position = nextRowOffsetPosition;
} }
} }
@ -231,19 +234,21 @@ namespace SixLabors.ImageSharp.Formats.OpenExr
{ {
bool hasAlpha = this.HasAlpha(); bool hasAlpha = this.HasAlpha();
uint bytesPerRow = this.CalculateBytesPerRow(); uint bytesPerRow = this.CalculateBytesPerRow();
uint rowsPerBlock = this.RowsPerBlock();
uint bytesPerBlock = bytesPerRow * rowsPerBlock;
using IMemoryOwner<uint> rowBuffer = this.memoryAllocator.Allocate<uint>(this.Width * 4); using IMemoryOwner<uint> rowBuffer = this.memoryAllocator.Allocate<uint>(this.Width * 4);
using IMemoryOwner<byte> decompressedPixelDataBuffer = this.memoryAllocator.Allocate<byte>((int)bytesPerRow); using IMemoryOwner<byte> decompressedPixelDataBuffer = this.memoryAllocator.Allocate<byte>((int)bytesPerBlock);
Span<byte> decompressedPixelData = decompressedPixelDataBuffer.GetSpan(); Span<byte> decompressedPixelData = decompressedPixelDataBuffer.GetSpan();
Span<uint> redPixelData = rowBuffer.GetSpan().Slice(0, this.Width); Span<uint> redPixelData = rowBuffer.GetSpan().Slice(0, this.Width);
Span<uint> greenPixelData = rowBuffer.GetSpan().Slice(this.Width, this.Width); Span<uint> greenPixelData = rowBuffer.GetSpan().Slice(this.Width, this.Width);
Span<uint> bluePixelData = rowBuffer.GetSpan().Slice(this.Width * 2, this.Width); Span<uint> bluePixelData = rowBuffer.GetSpan().Slice(this.Width * 2, this.Width);
Span<uint> alphaPixelData = rowBuffer.GetSpan().Slice(this.Width * 3, this.Width); Span<uint> alphaPixelData = rowBuffer.GetSpan().Slice(this.Width * 3, this.Width);
using ExrBaseDecompressor decompressor = ExrDecompressorFactory.Create(this.Compression, this.memoryAllocator, bytesPerRow); using ExrBaseDecompressor decompressor = ExrDecompressorFactory.Create(this.Compression, this.memoryAllocator, bytesPerBlock);
TPixel color = default; TPixel color = default;
for (int y = 0; y < this.Height; y++) for (uint y = 0; y < this.Height; y += rowsPerBlock)
{ {
stream.Read(this.buffer, 0, 8); stream.Read(this.buffer, 0, 8);
ulong rowOffset = BinaryPrimitives.ReadUInt64LittleEndian(this.buffer); ulong rowOffset = BinaryPrimitives.ReadUInt64LittleEndian(this.buffer);
@ -251,86 +256,88 @@ namespace SixLabors.ImageSharp.Formats.OpenExr
stream.Position = (long)rowOffset; stream.Position = (long)rowOffset;
stream.Read(this.buffer, 0, 4); stream.Read(this.buffer, 0, 4);
uint rowIndex = BinaryPrimitives.ReadUInt32LittleEndian(this.buffer); uint rowStartIndex = BinaryPrimitives.ReadUInt32LittleEndian(this.buffer);
Span<TPixel> pixelRow = pixels.DangerousGetRowSpan((int)rowIndex);
stream.Read(this.buffer, 0, 4); stream.Read(this.buffer, 0, 4);
uint compressedBytesCount = BinaryPrimitives.ReadUInt32LittleEndian(this.buffer); uint compressedBytesCount = BinaryPrimitives.ReadUInt32LittleEndian(this.buffer);
decompressor.Decompress(stream, compressedBytesCount, decompressedPixelData); decompressor.Decompress(stream, compressedBytesCount, decompressedPixelData);
int offset = 0; int offset = 0;
for (int channelIdx = 0; channelIdx < this.Channels.Count; channelIdx++) for (uint rowIndex = rowStartIndex; rowIndex < rowStartIndex + rowsPerBlock && rowIndex < this.Height; rowIndex++)
{ {
ExrChannelInfo channel = this.Channels[channelIdx]; Span<TPixel> pixelRow = pixels.DangerousGetRowSpan((int)rowIndex);
switch (channel.ChannelName) for (int channelIdx = 0; channelIdx < this.Channels.Count; channelIdx++)
{ {
case ExrConstants.ChannelNames.Red: ExrChannelInfo channel = this.Channels[channelIdx];
switch (channel.PixelType) switch (channel.ChannelName)
{ {
case ExrPixelType.UnsignedInt: case ExrConstants.ChannelNames.Red:
offset += this.ReadPixelRowChannelUnsignedInt(decompressedPixelData.Slice(offset), redPixelData); switch (channel.PixelType)
break; {
} case ExrPixelType.UnsignedInt:
offset += this.ReadPixelRowChannelUnsignedInt(decompressedPixelData.Slice(offset), redPixelData);
break; break;
}
case ExrConstants.ChannelNames.Blue:
switch (channel.PixelType) break;
{
case ExrPixelType.UnsignedInt: case ExrConstants.ChannelNames.Blue:
offset += this.ReadPixelRowChannelUnsignedInt(decompressedPixelData.Slice(offset), bluePixelData); switch (channel.PixelType)
break; {
} case ExrPixelType.UnsignedInt:
offset += this.ReadPixelRowChannelUnsignedInt(decompressedPixelData.Slice(offset), bluePixelData);
break; break;
}
case ExrConstants.ChannelNames.Green:
switch (channel.PixelType) break;
{
case ExrPixelType.UnsignedInt: case ExrConstants.ChannelNames.Green:
offset += this.ReadPixelRowChannelUnsignedInt(decompressedPixelData.Slice(offset), greenPixelData); switch (channel.PixelType)
break; {
} case ExrPixelType.UnsignedInt:
offset += this.ReadPixelRowChannelUnsignedInt(decompressedPixelData.Slice(offset), greenPixelData);
break; break;
}
case ExrConstants.ChannelNames.Alpha:
switch (channel.PixelType) break;
{
case ExrPixelType.UnsignedInt: case ExrConstants.ChannelNames.Alpha:
offset += this.ReadPixelRowChannelUnsignedInt(decompressedPixelData.Slice(offset), alphaPixelData); switch (channel.PixelType)
break; {
} case ExrPixelType.UnsignedInt:
offset += this.ReadPixelRowChannelUnsignedInt(decompressedPixelData.Slice(offset), alphaPixelData);
break; break;
}
default:
// Skip unknown channel. break;
int channelDataSizeInBytes = channel.PixelType is ExrPixelType.Float or ExrPixelType.UnsignedInt ? 4 : 2;
stream.Position += this.Width * channelDataSizeInBytes; default:
break; // Skip unknown channel.
int channelDataSizeInBytes = channel.PixelType is ExrPixelType.Float or ExrPixelType.UnsignedInt ? 4 : 2;
stream.Position += this.Width * channelDataSizeInBytes;
break;
}
} }
}
stream.Position = nextRowOffsetPosition; stream.Position = nextRowOffsetPosition;
if (hasAlpha) if (hasAlpha)
{
for (int x = 0; x < this.Width; x++)
{ {
var pixelValue = new Rgba128(redPixelData[x], greenPixelData[x], bluePixelData[x], alphaPixelData[x]); for (int x = 0; x < this.Width; x++)
color.FromVector4(pixelValue.ToVector4()); {
pixelRow[x] = color; var pixelValue = new Rgba128(redPixelData[x], greenPixelData[x], bluePixelData[x], alphaPixelData[x]);
color.FromVector4(pixelValue.ToVector4());
pixelRow[x] = color;
}
} }
} else
else
{
for (int x = 0; x < this.Width; x++)
{ {
var pixelValue = new Rgb96(redPixelData[x], greenPixelData[x], bluePixelData[x]); for (int x = 0; x < this.Width; x++)
color.FromVector4(pixelValue.ToVector4()); {
pixelRow[x] = color; var pixelValue = new Rgb96(redPixelData[x], greenPixelData[x], bluePixelData[x]);
color.FromVector4(pixelValue.ToVector4());
pixelRow[x] = color;
}
} }
} }
} }
@ -339,10 +346,9 @@ namespace SixLabors.ImageSharp.Formats.OpenExr
private int ReadPixelRowChannelHalfSingle(Span<byte> decompressedPixelData, Span<float> channelData) private int ReadPixelRowChannelHalfSingle(Span<byte> decompressedPixelData, Span<float> channelData)
{ {
int offset = 0; int offset = 0;
ushort shortValue = 0;
for (int x = 0; x < this.Width; x++) for (int x = 0; x < this.Width; x++)
{ {
shortValue = BinaryPrimitives.ReadUInt16LittleEndian(decompressedPixelData.Slice(offset, 2)); ushort shortValue = BinaryPrimitives.ReadUInt16LittleEndian(decompressedPixelData.Slice(offset, 2));
channelData[x] = HalfTypeHelper.Unpack(shortValue); channelData[x] = HalfTypeHelper.Unpack(shortValue);
offset += 2; offset += 2;
} }
@ -353,10 +359,9 @@ namespace SixLabors.ImageSharp.Formats.OpenExr
private int ReadPixelRowChannelSingle(Span<byte> decompressedPixelData, Span<float> channelData) private int ReadPixelRowChannelSingle(Span<byte> decompressedPixelData, Span<float> channelData)
{ {
int offset = 0; int offset = 0;
int intValue = 0;
for (int x = 0; x < this.Width; x++) for (int x = 0; x < this.Width; x++)
{ {
intValue = BinaryPrimitives.ReadInt32LittleEndian(decompressedPixelData.Slice(offset, 4)); int intValue = BinaryPrimitives.ReadInt32LittleEndian(decompressedPixelData.Slice(offset, 4));
channelData[x] = Unsafe.As<int, float>(ref intValue); channelData[x] = Unsafe.As<int, float>(ref intValue);
offset += 4; offset += 4;
} }
@ -668,5 +673,22 @@ namespace SixLabors.ImageSharp.Formats.OpenExr
return bytesPerRow; return bytesPerRow;
} }
private uint RowsPerBlock()
{
switch (this.Compression)
{
case ExrCompressionType.Zip:
case ExrCompressionType.Pxr24:
return 16;
case ExrCompressionType.B44:
case ExrCompressionType.B44A:
case ExrCompressionType.Piz:
return 32;
default:
return 1;
}
}
} }
} }

Loading…
Cancel
Save