Browse Source

Decode EXR images with unsigned int pixel data

pull/3096/head
Brian Popow 4 years ago
parent
commit
aaf21434a8
  1. 2
      src/ImageSharp/Formats/ImageDecoderUtilities.cs
  2. 16
      src/ImageSharp/Formats/OpenExr/ExrBox2i.cs
  3. 174
      src/ImageSharp/Formats/OpenExr/ExrDecoderCore.cs
  4. 4
      src/ImageSharp/Formats/OpenExr/ExrPixelType.cs
  5. 3
      src/ImageSharp/Formats/OpenExr/ExrThrowHelper.cs
  6. 172
      src/ImageSharp/PixelFormats/PixelImplementations/Rgb96.cs

2
src/ImageSharp/Formats/ImageDecoderUtilities.cs

@ -170,6 +170,6 @@ namespace SixLabors.ImageSharp.Formats
private static InvalidImageContentException DefaultLargeImageExceptionFactory(
InvalidMemoryOperationException memoryOperationException,
Size dimensions) =>
new InvalidImageContentException(dimensions, memoryOperationException);
new(dimensions, memoryOperationException);
}
}

16
src/ImageSharp/Formats/OpenExr/ExrBox2i.cs

@ -7,18 +7,18 @@ namespace SixLabors.ImageSharp.Formats.OpenExr
{
public ExrBox2i(int xMin, int yMin, int xMax, int yMax)
{
this.xMin = xMin;
this.yMin = yMin;
this.xMax = xMax;
this.yMax = yMax;
this.XMin = xMin;
this.YMin = yMin;
this.XMax = xMax;
this.YMax = yMax;
}
public int xMin { get; }
public int XMin { get; }
public int yMin { get; }
public int YMin { get; }
public int xMax { get; }
public int XMax { get; }
public int yMax { get; }
public int YMax { get; }
}
}

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

@ -72,19 +72,35 @@ namespace SixLabors.ImageSharp.Formats.OpenExr
public Image<TPixel> Decode<TPixel>(BufferedReadStream stream, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel>
{
ExrHeader header = this.ReadExrHeader(stream);
this.ReadExrHeader(stream);
if (this.Compression != ExrCompression.None)
{
ExrThrowHelper.ThrowNotSupported("Only uncompressed EXR images are supported");
}
int bitsPerPixel = CalculateBitsPerPixel(header.Channels);
ExrPixelType pixelType = this.ValidateChannels();
var image = new Image<TPixel>(this.Configuration, this.Width, this.Height, this.metadata);
Buffer2D<TPixel> pixels = image.GetRootFramePixelBuffer();
switch (pixelType)
{
case ExrPixelType.Half:
case ExrPixelType.Float:
this.DecodeFloatingPointPixelData(stream, pixels);
break;
default:
ExrThrowHelper.ThrowNotSupported("Pixel type is not supported");
break;
}
return image;
}
private void DecodeFloatingPointPixelData<TPixel>(BufferedReadStream stream, Buffer2D<TPixel> pixels)
where TPixel : unmanaged, IPixel<TPixel>
{
using IMemoryOwner<float> rowBuffer = this.memoryAllocator.Allocate<float>(this.Width * 3);
Span<float> redPixelData = rowBuffer.GetSpan().Slice(0, this.Width);
Span<float> bluePixelData = rowBuffer.GetSpan().Slice(this.Width, this.Width);
@ -152,7 +168,7 @@ namespace SixLabors.ImageSharp.Formats.OpenExr
default:
// Skip unknown channel.
int channelDataSizeInBytes = channel.PixelType is ExrPixelType.Float or ExrPixelType.Uint ? 4 : 2;
int channelDataSizeInBytes = channel.PixelType is ExrPixelType.Float or ExrPixelType.UnsignedInt ? 4 : 2;
stream.Position += this.Width * channelDataSizeInBytes;
break;
}
@ -167,8 +183,84 @@ namespace SixLabors.ImageSharp.Formats.OpenExr
pixelRow[x] = color;
}
}
}
return image;
private void DecodeUnsignedIntPixelData<TPixel>(BufferedReadStream stream, Buffer2D<TPixel> pixels)
where TPixel : unmanaged, IPixel<TPixel>
{
using IMemoryOwner<uint> rowBuffer = this.memoryAllocator.Allocate<uint>(this.Width * 3);
Span<uint> redPixelData = rowBuffer.GetSpan().Slice(0, this.Width);
Span<uint> bluePixelData = rowBuffer.GetSpan().Slice(this.Width, this.Width);
Span<uint> greenPixelData = rowBuffer.GetSpan().Slice(this.Width * 2, this.Width);
TPixel color = default;
for (int y = 0; y < this.Height; y++)
{
stream.Read(this.buffer, 0, 8);
ulong rowOffset = BinaryPrimitives.ReadUInt64LittleEndian(this.buffer);
long nextRowOffsetPosition = stream.Position;
stream.Position = (long)rowOffset;
stream.Read(this.buffer, 0, 4);
uint rowIndex = BinaryPrimitives.ReadUInt32LittleEndian(this.buffer);
Span<TPixel> pixelRow = pixels.DangerousGetRowSpan((int)rowIndex);
stream.Read(this.buffer, 0, 4);
uint pixelDataSize = BinaryPrimitives.ReadUInt32LittleEndian(this.buffer);
for (int channelIdx = 0; channelIdx < this.Channels.Count; channelIdx++)
{
ExrChannelInfo channel = this.Channels[channelIdx];
switch (channel.ChannelName)
{
case "R":
switch (channel.PixelType)
{
case ExrPixelType.UnsignedInt:
this.ReadPixelRowChannelUnsignedInt(stream, redPixelData);
break;
}
break;
case "B":
switch (channel.PixelType)
{
case ExrPixelType.UnsignedInt:
this.ReadPixelRowChannelUnsignedInt(stream, bluePixelData);
break;
}
break;
case "G":
switch (channel.PixelType)
{
case ExrPixelType.UnsignedInt:
this.ReadPixelRowChannelUnsignedInt(stream, greenPixelData);
break;
}
break;
default:
// Skip unknown channel.
int channelDataSizeInBytes = channel.PixelType is ExrPixelType.Float or ExrPixelType.UnsignedInt ? 4 : 2;
stream.Position += this.Width * channelDataSizeInBytes;
break;
}
}
stream.Position = nextRowOffsetPosition;
for (int x = 0; x < this.Width; x++)
{
var pixelValue = new Rgb96(redPixelData[x], greenPixelData[x], bluePixelData[x]);
color.FromVector4(pixelValue.ToVector4());
pixelRow[x] = color;
}
}
}
private void ReadPixelRowChannelHalfSingle(BufferedReadStream stream, Span<float> channelData)
@ -187,16 +279,63 @@ namespace SixLabors.ImageSharp.Formats.OpenExr
}
}
private void ReadPixelRowChannelUnsignedInt(BufferedReadStream stream, Span<uint> channelData)
{
for (int x = 0; x < this.Width; x++)
{
stream.Read(this.buffer, 0, 4);
channelData[x] = BinaryPrimitives.ReadUInt32LittleEndian(this.buffer);
}
}
/// <inheritdoc />
public IImageInfo Identify(BufferedReadStream stream, CancellationToken cancellationToken)
{
ExrHeader header = this.ReadExrHeader(stream);
int bitsPerPixel = CalculateBitsPerPixel(header.Channels);
int bitsPerPixel = this.CalculateBitsPerPixel();
return new ImageInfo(new PixelTypeInfo(bitsPerPixel), this.Width, this.Height, new ImageMetadata());
}
private int CalculateBitsPerPixel()
{
int bitsPerPixel = 0;
for (int i = 0; i < this.Channels.Count; i++)
{
ExrChannelInfo channel = this.Channels[0];
if (channel.PixelType is ExrPixelType.Float or ExrPixelType.UnsignedInt)
{
bitsPerPixel += 32;
}
else if (channel.PixelType == ExrPixelType.Half)
{
bitsPerPixel += 16;
}
}
return bitsPerPixel;
}
private ExrPixelType ValidateChannels()
{
if (this.Channels.Count == 0)
{
ExrThrowHelper.ThrowInvalidImageContentException("At least one channel of pixel data expected!");
}
ExrPixelType pixelType = this.Channels[0].PixelType;
for (int i = 1; i < this.Channels.Count; i++)
{
if (pixelType != this.Channels[i].PixelType)
{
ExrThrowHelper.ThrowInvalidImageContentException("All channels are expected to have the same pixel data!");
}
}
return pixelType;
}
private ExrHeader ReadExrHeader(BufferedReadStream stream)
{
// Skip over the magick bytes.
@ -220,8 +359,8 @@ namespace SixLabors.ImageSharp.Formats.OpenExr
ExrThrowHelper.ThrowInvalidImageHeader();
}
this.Width = header.DataWindow.Value.xMax - header.DataWindow.Value.xMin + 1;
this.Height = header.DataWindow.Value.yMax - header.DataWindow.Value.yMin + 1;
this.Width = header.DataWindow.Value.XMax - header.DataWindow.Value.XMin + 1;
this.Height = header.DataWindow.Value.YMax - header.DataWindow.Value.YMin + 1;
this.Channels = header.Channels;
this.Compression = header.Compression.GetValueOrDefault();
@ -358,25 +497,6 @@ namespace SixLabors.ImageSharp.Formats.OpenExr
return new ExrChannelInfo(channelName, pixelType, pLinear, xSampling, ySampling);
}
private static int CalculateBitsPerPixel(IList<ExrChannelInfo> channels)
{
int bitsPerPixel = 0;
for (int i = 0; i < channels.Count; i++)
{
ExrChannelInfo channel = channels[0];
if (channel.PixelType is ExrPixelType.Float or ExrPixelType.Uint)
{
bitsPerPixel += 32;
}
else if (channel.PixelType == ExrPixelType.Half)
{
bitsPerPixel += 16;
}
}
return bitsPerPixel;
}
private static string ReadString(BufferedReadStream stream)
{
var str = new StringBuilder();

4
src/ImageSharp/Formats/OpenExr/ExrPixelType.cs

@ -3,12 +3,12 @@
namespace SixLabors.ImageSharp.Formats.OpenExr
{
internal enum ExrPixelType : int
internal enum ExrPixelType
{
/// <summary>
/// unsigned int (32 bit).
/// </summary>
Uint = 0,
UnsignedInt = 0,
/// <summary>
/// half (16 bit floating point).

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

@ -11,6 +11,9 @@ namespace SixLabors.ImageSharp.Formats.OpenExr
/// </summary>
internal static class ExrThrowHelper
{
[MethodImpl(MethodImplOptions.NoInlining)]
public static void ThrowInvalidImageContentException(string errorMessage) => throw new InvalidImageContentException(errorMessage);
[MethodImpl(InliningOptions.ColdPath)]
public static void ThrowNotSupportedVersion() => throw new NotSupportedException("Unsupported EXR version");

172
src/ImageSharp/PixelFormats/PixelImplementations/Rgb96.cs

@ -0,0 +1,172 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace SixLabors.ImageSharp.PixelFormats
{
/// <summary>
/// Pixel type containing three 32-bit unsigned normalized values ranging from 0 to 4294967295.
/// The color components are stored in red, green, blue
/// <para>
/// Ranges from [0, 0, 0] to [1, 1, 1] in vector form.
/// </para>
/// </summary>
[StructLayout(LayoutKind.Sequential)]
public partial struct Rgb96 : IPixel<Rgb96>
{
private const float Max = uint.MaxValue;
/// <summary>
/// Gets or sets the red component.
/// </summary>
public uint R;
/// <summary>
/// Gets or sets the green component.
/// </summary>
public uint G;
/// <summary>
/// Gets or sets the blue component.
/// </summary>
public uint B;
/// <summary>
/// Initializes a new instance of the <see cref="Rgb96"/> struct.
/// </summary>
/// <param name="r">The red component.</param>
/// <param name="g">The green component.</param>
/// <param name="b">The blue component.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Rgb96(uint r, uint g, uint b)
{
this.R = r;
this.G = g;
this.B = b;
}
/// <summary>
/// Compares two <see cref="Rgb96"/> objects for equality.
/// </summary>
/// <param name="left">The <see cref="Rgb96"/> on the left side of the operand.</param>
/// <returns>
/// True if the <paramref name="left"/> parameter is equal to the <paramref name="right"/> parameter; otherwise, false.
/// </returns>
/// <param name="right">The <see cref="Rgb96"/> on the right side of the operand.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool operator ==(Rgb96 left, Rgb96 right) => left.Equals(right);
/// <summary>
/// Compares two <see cref="Rgb96"/> objects for equality.
/// </summary>
/// <param name="left">The <see cref="Rgb96"/> on the left side of the operand.</param>
/// <param name="right">The <see cref="Rgb96"/> on the right side of the operand.</param>
/// <returns>
/// True if the <paramref name="left"/> parameter is not equal to the <paramref name="right"/> parameter; otherwise, false.
/// </returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool operator !=(Rgb96 left, Rgb96 right) => !left.Equals(right);
/// <inheritdoc />
public PixelOperations<Rgb96> CreatePixelOperations() => new();
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void FromScaledVector4(Vector4 vector) => this.FromVector4(vector);
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Vector4 ToScaledVector4() => this.ToVector4();
/// <inheritdoc />
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void FromVector4(Vector4 vector)
{
this.R = (uint)(vector.X * Max);
this.G = (uint)(vector.Y * Max);
this.B = (uint)(vector.Z * Max);
}
/// <inheritdoc />
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Vector4 ToVector4() => new(
this.R / Max,
this.G / Max,
this.B / Max,
1.0f);
/// <inheritdoc />
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void FromBgra5551(Bgra5551 source) => this.FromScaledVector4(source.ToScaledVector4());
/// <inheritdoc />
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void FromArgb32(Argb32 source) => this.FromScaledVector4(source.ToScaledVector4());
/// <inheritdoc />
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void FromBgr24(Bgr24 source) => this.FromScaledVector4(source.ToScaledVector4());
/// <inheritdoc />
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void FromBgra32(Bgra32 source) => this.FromScaledVector4(source.ToScaledVector4());
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void FromL8(L8 source) => this.FromScaledVector4(source.ToScaledVector4());
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void FromL16(L16 source) => this.FromScaledVector4(source.ToScaledVector4());
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void FromLa16(La16 source) => this.FromScaledVector4(source.ToScaledVector4());
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void FromLa32(La32 source) => this.FromScaledVector4(source.ToScaledVector4());
/// <inheritdoc />
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void FromRgb24(Rgb24 source) => this.FromScaledVector4(source.ToScaledVector4());
/// <inheritdoc />
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void FromRgba32(Rgba32 source) => this.FromScaledVector4(source.ToScaledVector4());
/// <inheritdoc />
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void FromAbgr32(Abgr32 source) => this.FromScaledVector4(source.ToScaledVector4());
/// <inheritdoc />
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void ToRgba32(ref Rgba32 dest) => dest.FromScaledVector4(this.ToScaledVector4());
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void FromRgb48(Rgb48 source) => this.FromScaledVector4(source.ToScaledVector4());
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void FromRgba64(Rgba64 source) => this.FromScaledVector4(source.ToScaledVector4());
/// <inheritdoc />
public override bool Equals(object obj) => obj is Rgb96 other && this.Equals(other);
/// <inheritdoc />
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public override int GetHashCode() => HashCode.Combine(this.R, this.G, this.B);
/// <inheritdoc />
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool Equals(Rgb96 other) => this.R.Equals(other.R) && this.G.Equals(other.G) && this.B.Equals(other.B);
/// <inheritdoc />
public override string ToString() => FormattableString.Invariant($"Rgb96({this.R}, {this.G}, {this.B})");
}
}
Loading…
Cancel
Save