Browse Source

Add support for decoding exr with zips compression

pull/3096/head
Brian Popow 5 years ago
parent
commit
3279762768
  1. 24
      src/ImageSharp/Formats/OpenExr/Compression/Compressors/NoneExrCompression.cs
  2. 75
      src/ImageSharp/Formats/OpenExr/Compression/Compressors/ZipsExrCompression.cs
  3. 43
      src/ImageSharp/Formats/OpenExr/Compression/ExrBaseCompression.cs
  4. 19
      src/ImageSharp/Formats/OpenExr/Compression/ExrBaseDecompressor.cs
  5. 4
      src/ImageSharp/Formats/OpenExr/Compression/ExrCompressionType.cs
  6. 24
      src/ImageSharp/Formats/OpenExr/Compression/ExrDecompressorFactory.cs
  7. 121
      src/ImageSharp/Formats/OpenExr/ExrDecoderCore.cs
  8. 5
      src/ImageSharp/Formats/OpenExr/ExrEncoderCore.cs
  9. 3
      src/ImageSharp/Formats/OpenExr/ExrHeader.cs
  10. 3
      src/ImageSharp/Formats/OpenExr/ExrThrowHelper.cs

24
src/ImageSharp/Formats/OpenExr/Compression/Compressors/NoneExrCompression.cs

@ -0,0 +1,24 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System;
using SixLabors.ImageSharp.IO;
using SixLabors.ImageSharp.Memory;
namespace SixLabors.ImageSharp.Formats.OpenExr.Compression.Compressors
{
internal class NoneExrCompression : ExrBaseDecompressor
{
public NoneExrCompression(MemoryAllocator allocator, uint uncompressedBytes)
: base(allocator, uncompressedBytes)
{
}
public override void Decompress(BufferedReadStream stream, uint compressedBytes, Span<byte> buffer)
=> stream.Read(buffer, 0, Math.Min(buffer.Length, (int)this.UncompressedBytes));
protected override void Dispose(bool disposing)
{
}
}
}

75
src/ImageSharp/Formats/OpenExr/Compression/Compressors/ZipsExrCompression.cs

@ -0,0 +1,75 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Buffers;
using System.IO.Compression;
using SixLabors.ImageSharp.Compression.Zlib;
using SixLabors.ImageSharp.IO;
using SixLabors.ImageSharp.Memory;
namespace SixLabors.ImageSharp.Formats.OpenExr.Compression.Compressors
{
internal class ZipsExrCompression : ExrBaseDecompressor
{
private readonly IMemoryOwner<byte> tmpBuffer;
public ZipsExrCompression(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)
{
long pos = stream.Position;
using var deframeStream = new ZlibInflateStream(
stream,
() =>
{
int left = (int)(compressedBytes - (stream.Position - pos));
return left > 0 ? left : 0;
});
deframeStream.AllocateNewBytes((int)this.UncompressedBytes, true);
DeflateStream dataStream = deframeStream.CompressedStream;
Span<byte> tmp = this.tmpBuffer.GetSpan();
int totalRead = 0;
while (totalRead < buffer.Length)
{
int bytesRead = dataStream.Read(tmp, totalRead, buffer.Length - totalRead);
if (bytesRead <= 0)
{
break;
}
totalRead += bytesRead;
}
Reconstruct(tmp, this.UncompressedBytes);
Interleave(tmp, this.UncompressedBytes, buffer);
}
private static void Reconstruct(Span<byte> buffer, uint unCompressedBytes)
{
int offset = 0;
for (int i = 0; i < unCompressedBytes - 1; i++)
{
byte d = (byte)(buffer[offset] + (buffer[offset + 1] - 128));
buffer[offset + 1] = d;
offset++;
}
}
private static void Interleave(Span<byte> source, uint unCompressedBytes, Span<byte> output)
{
int sourceOffset = 0;
int offset0 = 0;
int offset1 = (int)((unCompressedBytes + 1) / 2);
while (sourceOffset < unCompressedBytes)
{
output[sourceOffset++] = source[offset0++];
output[sourceOffset++] = source[offset1++];
}
}
protected override void Dispose(bool disposing) => this.tmpBuffer.Dispose();
}
}

43
src/ImageSharp/Formats/OpenExr/Compression/ExrBaseCompression.cs

@ -0,0 +1,43 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System;
using SixLabors.ImageSharp.Memory;
namespace SixLabors.ImageSharp.Formats.OpenExr.Compression
{
internal abstract class ExrBaseCompression : IDisposable
{
private bool isDisposed;
protected ExrBaseCompression(MemoryAllocator allocator, uint bytePerRow)
{
this.Allocator = allocator;
this.UncompressedBytes = bytePerRow;
}
/// <summary>
/// Gets the memory allocator.
/// </summary>
protected MemoryAllocator Allocator { get; }
/// <summary>
/// Gets the uncompressed bytes.
/// </summary>
public uint UncompressedBytes { get; }
/// <inheritdoc />
public void Dispose()
{
if (this.isDisposed)
{
return;
}
this.isDisposed = true;
this.Dispose(true);
}
protected abstract void Dispose(bool disposing);
}
}

19
src/ImageSharp/Formats/OpenExr/Compression/ExrBaseDecompressor.cs

@ -0,0 +1,19 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System;
using SixLabors.ImageSharp.IO;
using SixLabors.ImageSharp.Memory;
namespace SixLabors.ImageSharp.Formats.OpenExr.Compression
{
internal abstract class ExrBaseDecompressor : ExrBaseCompression
{
protected ExrBaseDecompressor(MemoryAllocator allocator, uint bytePerRow)
: base(allocator, bytePerRow)
{
}
public abstract void Decompress(BufferedReadStream stream, uint compressedBytes, Span<byte> buffer);
}
}

4
src/ImageSharp/Formats/OpenExr/ExrCompression.cs → src/ImageSharp/Formats/OpenExr/Compression/ExrCompressionType.cs

@ -1,9 +1,9 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
namespace SixLabors.ImageSharp.Formats.OpenExr
namespace SixLabors.ImageSharp.Formats.OpenExr.Compression
{
internal enum ExrCompression
internal enum ExrCompressionType
{
/// <summary>
/// Pixel data is not compressed.

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

@ -0,0 +1,24 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using SixLabors.ImageSharp.Formats.OpenExr.Compression.Compressors;
using SixLabors.ImageSharp.Memory;
namespace SixLabors.ImageSharp.Formats.OpenExr.Compression
{
internal static class ExrDecompressorFactory
{
public static ExrBaseDecompressor Create(ExrCompressionType method, MemoryAllocator memoryAllocator, uint bytesPerRow)
{
switch (method)
{
case ExrCompressionType.None:
return new NoneExrCompression(memoryAllocator, bytesPerRow);
case ExrCompressionType.Zips:
return new ZipsExrCompression(memoryAllocator, bytesPerRow);
default:
throw ExrThrowHelper.NotSupportedDecompressor(nameof(method));
}
}
}
}

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

@ -5,8 +5,10 @@ using System;
using System.Buffers;
using System.Buffers.Binary;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading;
using SixLabors.ImageSharp.Formats.OpenExr.Compression;
using SixLabors.ImageSharp.Formats.Pbm;
using SixLabors.ImageSharp.IO;
using SixLabors.ImageSharp.Memory;
@ -66,7 +68,7 @@ namespace SixLabors.ImageSharp.Formats.OpenExr
private IList<ExrChannelInfo> Channels { get; set; }
private ExrCompression Compression { get; set; }
private ExrCompressionType Compression { get; set; }
/// <inheritdoc />
public Image<TPixel> Decode<TPixel>(BufferedReadStream stream, CancellationToken cancellationToken)
@ -74,7 +76,7 @@ namespace SixLabors.ImageSharp.Formats.OpenExr
{
this.ReadExrHeader(stream);
if (this.Compression is not ExrCompression.None)
if (this.Compression is not ExrCompressionType.None and not ExrCompressionType.Zips)
{
ExrThrowHelper.ThrowNotSupported($"Compression {this.Compression} is not yet supported");
}
@ -104,12 +106,19 @@ namespace SixLabors.ImageSharp.Formats.OpenExr
private void DecodeFloatingPointPixelData<TPixel>(BufferedReadStream stream, Buffer2D<TPixel> pixels)
where TPixel : unmanaged, IPixel<TPixel>
{
bool hasAlpha = this.HasAlpha();
uint bytesPerRow = this.CalculateBytesPerRow();
using IMemoryOwner<float> rowBuffer = this.memoryAllocator.Allocate<float>(this.Width * 4);
using IMemoryOwner<byte> decompressedPixelDataBuffer = this.memoryAllocator.Allocate<byte>((int)bytesPerRow);
Span<byte> decompressedPixelData = decompressedPixelDataBuffer.GetSpan();
Span<float> redPixelData = rowBuffer.GetSpan().Slice(0, 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> alphaPixelData = rowBuffer.GetSpan().Slice(this.Width * 3, this.Width);
using ExrBaseDecompressor decompressor = ExrDecompressorFactory.Create(this.Compression, this.memoryAllocator, bytesPerRow);
TPixel color = default;
for (int y = 0; y < this.Height; y++)
{
@ -124,9 +133,10 @@ namespace SixLabors.ImageSharp.Formats.OpenExr
Span<TPixel> pixelRow = pixels.DangerousGetRowSpan((int)rowIndex);
stream.Read(this.buffer, 0, 4);
uint pixelDataSize = BinaryPrimitives.ReadUInt32LittleEndian(this.buffer);
uint compressedBytesCount = BinaryPrimitives.ReadUInt32LittleEndian(this.buffer);
decompressor.Decompress(stream, compressedBytesCount, decompressedPixelData);
bool hasAlpha = false;
int offset = 0;
for (int channelIdx = 0; channelIdx < this.Channels.Count; channelIdx++)
{
ExrChannelInfo channel = this.Channels[channelIdx];
@ -136,10 +146,10 @@ namespace SixLabors.ImageSharp.Formats.OpenExr
switch (channel.PixelType)
{
case ExrPixelType.Half:
this.ReadPixelRowChannelHalfSingle(stream, redPixelData);
offset += this.ReadPixelRowChannelHalfSingle(decompressedPixelData.Slice(offset), redPixelData);
break;
case ExrPixelType.Float:
this.ReadPixelRowChannelSingle(stream, redPixelData);
offset += this.ReadPixelRowChannelSingle(decompressedPixelData.Slice(offset), redPixelData);
break;
}
@ -149,10 +159,10 @@ namespace SixLabors.ImageSharp.Formats.OpenExr
switch (channel.PixelType)
{
case ExrPixelType.Half:
this.ReadPixelRowChannelHalfSingle(stream, bluePixelData);
offset += this.ReadPixelRowChannelHalfSingle(decompressedPixelData.Slice(offset), bluePixelData);
break;
case ExrPixelType.Float:
this.ReadPixelRowChannelSingle(stream, bluePixelData);
offset += this.ReadPixelRowChannelSingle(decompressedPixelData.Slice(offset), bluePixelData);
break;
}
@ -162,24 +172,23 @@ namespace SixLabors.ImageSharp.Formats.OpenExr
switch (channel.PixelType)
{
case ExrPixelType.Half:
this.ReadPixelRowChannelHalfSingle(stream, greenPixelData);
offset += this.ReadPixelRowChannelHalfSingle(decompressedPixelData.Slice(offset), greenPixelData);
break;
case ExrPixelType.Float:
this.ReadPixelRowChannelSingle(stream, greenPixelData);
offset += this.ReadPixelRowChannelSingle(decompressedPixelData.Slice(offset), greenPixelData);
break;
}
break;
case ExrConstants.ChannelNames.Alpha:
hasAlpha = true;
switch (channel.PixelType)
{
case ExrPixelType.Half:
this.ReadPixelRowChannelHalfSingle(stream, alphaPixelData);
offset += this.ReadPixelRowChannelHalfSingle(decompressedPixelData.Slice(offset), alphaPixelData);
break;
case ExrPixelType.Float:
this.ReadPixelRowChannelSingle(stream, alphaPixelData);
offset += this.ReadPixelRowChannelSingle(decompressedPixelData.Slice(offset), alphaPixelData);
break;
}
@ -219,12 +228,19 @@ namespace SixLabors.ImageSharp.Formats.OpenExr
private void DecodeUnsignedIntPixelData<TPixel>(BufferedReadStream stream, Buffer2D<TPixel> pixels)
where TPixel : unmanaged, IPixel<TPixel>
{
bool hasAlpha = this.HasAlpha();
uint bytesPerRow = this.CalculateBytesPerRow();
using IMemoryOwner<uint> rowBuffer = this.memoryAllocator.Allocate<uint>(this.Width * 4);
using IMemoryOwner<byte> decompressedPixelDataBuffer = this.memoryAllocator.Allocate<byte>((int)bytesPerRow);
Span<byte> decompressedPixelData = decompressedPixelDataBuffer.GetSpan();
Span<uint> redPixelData = rowBuffer.GetSpan().Slice(0, 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> alphaPixelData = rowBuffer.GetSpan().Slice(this.Width * 3, this.Width);
using ExrBaseDecompressor decompressor = ExrDecompressorFactory.Create(this.Compression, this.memoryAllocator, bytesPerRow);
TPixel color = default;
for (int y = 0; y < this.Height; y++)
{
@ -239,9 +255,10 @@ namespace SixLabors.ImageSharp.Formats.OpenExr
Span<TPixel> pixelRow = pixels.DangerousGetRowSpan((int)rowIndex);
stream.Read(this.buffer, 0, 4);
uint pixelDataSize = BinaryPrimitives.ReadUInt32LittleEndian(this.buffer);
uint compressedBytesCount = BinaryPrimitives.ReadUInt32LittleEndian(this.buffer);
decompressor.Decompress(stream, compressedBytesCount, decompressedPixelData);
bool hasAlpha = false;
int offset = 0;
for (int channelIdx = 0; channelIdx < this.Channels.Count; channelIdx++)
{
ExrChannelInfo channel = this.Channels[channelIdx];
@ -251,7 +268,7 @@ namespace SixLabors.ImageSharp.Formats.OpenExr
switch (channel.PixelType)
{
case ExrPixelType.UnsignedInt:
this.ReadPixelRowChannelUnsignedInt(stream, redPixelData);
offset += this.ReadPixelRowChannelUnsignedInt(decompressedPixelData.Slice(offset), redPixelData);
break;
}
@ -261,7 +278,7 @@ namespace SixLabors.ImageSharp.Formats.OpenExr
switch (channel.PixelType)
{
case ExrPixelType.UnsignedInt:
this.ReadPixelRowChannelUnsignedInt(stream, bluePixelData);
offset += this.ReadPixelRowChannelUnsignedInt(decompressedPixelData.Slice(offset), bluePixelData);
break;
}
@ -271,18 +288,17 @@ namespace SixLabors.ImageSharp.Formats.OpenExr
switch (channel.PixelType)
{
case ExrPixelType.UnsignedInt:
this.ReadPixelRowChannelUnsignedInt(stream, greenPixelData);
offset += this.ReadPixelRowChannelUnsignedInt(decompressedPixelData.Slice(offset), greenPixelData);
break;
}
break;
case ExrConstants.ChannelNames.Alpha:
hasAlpha = true;
switch (channel.PixelType)
{
case ExrPixelType.UnsignedInt:
this.ReadPixelRowChannelUnsignedInt(stream, alphaPixelData);
offset += this.ReadPixelRowChannelUnsignedInt(decompressedPixelData.Slice(offset), alphaPixelData);
break;
}
@ -319,29 +335,44 @@ namespace SixLabors.ImageSharp.Formats.OpenExr
}
}
private void ReadPixelRowChannelHalfSingle(BufferedReadStream stream, Span<float> channelData)
private int ReadPixelRowChannelHalfSingle(Span<byte> decompressedPixelData, Span<float> channelData)
{
int offset = 0;
ushort shortValue = 0;
for (int x = 0; x < this.Width; x++)
{
channelData[x] = stream.ReadHalfSingle(this.buffer);
shortValue = BinaryPrimitives.ReadUInt16LittleEndian(decompressedPixelData.Slice(offset, 2));
channelData[x] = HalfTypeHelper.Unpack(shortValue);
offset += 2;
}
return offset;
}
private void ReadPixelRowChannelSingle(BufferedReadStream stream, Span<float> channelData)
private int ReadPixelRowChannelSingle(Span<byte> decompressedPixelData, Span<float> channelData)
{
int offset = 0;
int intValue = 0;
for (int x = 0; x < this.Width; x++)
{
channelData[x] = stream.ReadSingle(this.buffer);
intValue = BinaryPrimitives.ReadInt32LittleEndian(decompressedPixelData.Slice(offset, 4));
channelData[x] = Unsafe.As<int, float>(ref intValue);
offset += 4;
}
return offset;
}
private void ReadPixelRowChannelUnsignedInt(BufferedReadStream stream, Span<uint> channelData)
private int ReadPixelRowChannelUnsignedInt(Span<byte> decompressedPixelData, Span<uint> channelData)
{
int offset = 0;
for (int x = 0; x < this.Width; x++)
{
stream.Read(this.buffer, 0, 4);
channelData[x] = BinaryPrimitives.ReadUInt32LittleEndian(this.buffer);
channelData[x] = BinaryPrimitives.ReadUInt32LittleEndian(decompressedPixelData.Slice(offset, 4));
offset += 4;
}
return offset;
}
/// <inheritdoc />
@ -465,7 +496,7 @@ namespace SixLabors.ImageSharp.Formats.OpenExr
header.Channels = channels;
break;
case ExrConstants.AttributeNames.Compression:
header.Compression = (ExrCompression)stream.ReadByte();
header.Compression = (ExrCompressionType)stream.ReadByte();
break;
case ExrConstants.AttributeNames.DataWindow:
ExrBox2i dataWindow = this.ReadBoxInteger(stream);
@ -602,5 +633,39 @@ namespace SixLabors.ImageSharp.Formats.OpenExr
return str.ToString();
}
private bool HasAlpha()
{
foreach (ExrChannelInfo channelInfo in this.Channels)
{
if (channelInfo.ChannelName.Equals("A"))
{
return true;
}
}
return false;
}
private uint CalculateBytesPerRow()
{
uint bytesPerRow = 0;
foreach (ExrChannelInfo channelInfo in this.Channels)
{
if (channelInfo.ChannelName.Equals("A") || channelInfo.ChannelName.Equals("R") || channelInfo.ChannelName.Equals("G") || channelInfo.ChannelName.Equals("B"))
{
if (channelInfo.PixelType == ExrPixelType.Half)
{
bytesPerRow += 2 * (uint)this.Width;
}
else
{
bytesPerRow += 4 * (uint)this.Width;
}
}
}
return bytesPerRow;
}
}
}

5
src/ImageSharp/Formats/OpenExr/ExrEncoderCore.cs

@ -8,6 +8,7 @@ using System.Collections.Generic;
using System.IO;
using System.Runtime.CompilerServices;
using System.Threading;
using SixLabors.ImageSharp.Formats.OpenExr.Compression;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.Metadata;
using SixLabors.ImageSharp.PixelFormats;
@ -67,7 +68,7 @@ namespace SixLabors.ImageSharp.Formats.OpenExr
int height = image.Height;
var header = new ExrHeader()
{
Compression = ExrCompression.None,
Compression = ExrCompressionType.None,
AspectRatio = 1.0f,
DataWindow = new ExrBox2i(0, 0, width - 1, height - 1),
DisplayWindow = new ExrBox2i(0, 0, width - 1, height - 1),
@ -293,7 +294,7 @@ namespace SixLabors.ImageSharp.Formats.OpenExr
stream.WriteByte(0);
}
private void WriteCompression(Stream stream, ExrCompression compression)
private void WriteCompression(Stream stream, ExrCompressionType compression)
{
this.WriteAttributeInformation(stream, ExrConstants.AttributeNames.Compression, ExrConstants.AttibuteTypes.Compression, 1);
stream.WriteByte((byte)compression);

3
src/ImageSharp/Formats/OpenExr/ExrHeader.cs

@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0.
using System.Collections.Generic;
using SixLabors.ImageSharp.Formats.OpenExr.Compression;
namespace SixLabors.ImageSharp.Formats.OpenExr
{
@ -9,7 +10,7 @@ namespace SixLabors.ImageSharp.Formats.OpenExr
{
public IList<ExrChannelInfo> Channels { get; set; }
public ExrCompression? Compression { get; set; }
public ExrCompressionType? Compression { get; set; }
public ExrBox2i? DataWindow { get; set; }

3
src/ImageSharp/Formats/OpenExr/ExrThrowHelper.cs

@ -11,6 +11,9 @@ namespace SixLabors.ImageSharp.Formats.OpenExr
/// </summary>
internal static class ExrThrowHelper
{
[MethodImpl(InliningOptions.ColdPath)]
public static Exception NotSupportedDecompressor(string compressionType) => throw new NotSupportedException($"Not supported decoder compression method: {compressionType}");
[MethodImpl(MethodImplOptions.NoInlining)]
public static void ThrowInvalidImageContentException(string errorMessage) => throw new InvalidImageContentException(errorMessage);

Loading…
Cancel
Save