diff --git a/src/ImageSharp/Formats/OpenExr/ExrDecoderCore.cs b/src/ImageSharp/Formats/OpenExr/ExrDecoderCore.cs
index c5c9425066..ea40857d8d 100644
--- a/src/ImageSharp/Formats/OpenExr/ExrDecoderCore.cs
+++ b/src/ImageSharp/Formats/OpenExr/ExrDecoderCore.cs
@@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0.
using System;
+using System.Buffers;
using System.Buffers.Binary;
using System.Collections.Generic;
using System.Text;
@@ -22,7 +23,7 @@ namespace SixLabors.ImageSharp.Formats.OpenExr
///
/// Reusable buffer.
///
- private readonly byte[] buffer = new byte[4];
+ private readonly byte[] buffer = new byte[8];
///
/// Used for allocating memory during processing operations.
@@ -34,6 +35,11 @@ namespace SixLabors.ImageSharp.Formats.OpenExr
///
private readonly IExrDecoderOptions options;
+ ///
+ /// The metadata.
+ ///
+ private ImageMetadata metadata;
+
///
/// Initializes a new instance of the class.
///
@@ -58,12 +64,102 @@ namespace SixLabors.ImageSharp.Formats.OpenExr
private int Height { get; set; }
+ private IList Channels { get; set; }
+
///
public Image Decode(BufferedReadStream stream, CancellationToken cancellationToken)
- where TPixel : unmanaged, IPixel => throw new NotImplementedException();
+ where TPixel : unmanaged, IPixel
+ {
+ ExrHeader header = this.ReadExrHeader(stream);
+
+ int bitsPerPixel = CalculateBitsPerPixel(header.Channels);
+
+ var image = new Image(this.Configuration, this.Width, this.Height, this.metadata);
+
+ Buffer2D pixels = image.GetRootFramePixelBuffer();
+
+ // TODO: we for now assume the image pixel type is HalfSingle.
+ using IMemoryOwner rowBuffer = this.memoryAllocator.Allocate(this.Width * 3);
+ Span redPixelData = rowBuffer.GetSpan().Slice(0, this.Width);
+ Span bluePixelData = rowBuffer.GetSpan().Slice(this.Width, this.Width);
+ Span 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 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++)
+ {
+ switch (this.Channels[channelIdx].ChannelName)
+ {
+ case "R":
+ for (int x = 0; x < this.Width; x++)
+ {
+ redPixelData[x] = stream.ReadHalfSingle(this.buffer);
+ }
+
+ break;
+
+ case "B":
+ for (int x = 0; x < this.Width; x++)
+ {
+ bluePixelData[x] = stream.ReadHalfSingle(this.buffer);
+ }
+
+ break;
+
+ case "G":
+ for (int x = 0; x < this.Width; x++)
+ {
+ greenPixelData[x] = stream.ReadHalfSingle(this.buffer);
+ }
+
+ break;
+
+ default:
+ // Skip unknown channel.
+ // TODO: can we assume the same data size as the others here?
+ stream.Position += this.Width * 2;
+ break;
+ }
+ }
+
+ stream.Position = nextRowOffsetPosition;
+
+ for (int x = 0; x < this.Width; x++)
+ {
+ var pixelValue = new HalfVector4(redPixelData[x], greenPixelData[x], bluePixelData[x], 1.0f);
+ color.FromVector4(pixelValue.ToVector4());
+ pixelRow[x] = color;
+ }
+ }
+
+ return image;
+ }
///
public IImageInfo Identify(BufferedReadStream stream, CancellationToken cancellationToken)
+ {
+ ExrHeader header = this.ReadExrHeader(stream);
+
+ int bitsPerPixel = CalculateBitsPerPixel(header.Channels);
+
+ return new ImageInfo(new PixelTypeInfo(bitsPerPixel), this.Width, this.Height, new ImageMetadata());
+ }
+
+ private ExrHeader ReadExrHeader(BufferedReadStream stream)
{
// Skip over the magick bytes.
stream.Read(this.buffer, 0, 4);
@@ -93,11 +189,11 @@ namespace SixLabors.ImageSharp.Formats.OpenExr
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;
- // TODO: calculate bits per pixel.
- int bitsPerPixel = 48;
+ this.metadata = new ImageMetadata();
- return new ImageInfo(new PixelTypeInfo(bitsPerPixel), this.Width, this.Height, new ImageMetadata());
+ return header;
}
private ExrHeader ParseHeader(BufferedReadStream stream)
@@ -110,11 +206,16 @@ namespace SixLabors.ImageSharp.Formats.OpenExr
switch (attribute.Name)
{
case "channels":
- IList channels = this.ParseChannelList(stream, attribute.Length);
+ IList channels = this.ReadChannelList(stream, attribute.Length);
header.Channels = channels;
break;
case "compression":
var compression = (ExrCompression)stream.ReadByte();
+ if (compression != ExrCompression.None)
+ {
+ ExrThrowHelper.ThrowNotSupported("Only uncompressed EXR images are supported");
+ }
+
header.Compression = compression;
break;
case "dataWindow":
@@ -187,7 +288,7 @@ namespace SixLabors.ImageSharp.Formats.OpenExr
return new ExrBox2i(xMin, yMin, xMax, yMax);
}
- private List ParseChannelList(BufferedReadStream stream, int attributeSize)
+ private List ReadChannelList(BufferedReadStream stream, int attributeSize)
{
var channels = new List();
while (attributeSize > 1)
@@ -229,6 +330,25 @@ namespace SixLabors.ImageSharp.Formats.OpenExr
return new ExrChannelInfo(channelName, pixelType, pLinear, xSampling, ySampling);
}
+ private static int CalculateBitsPerPixel(IList 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();
diff --git a/src/ImageSharp/Formats/Pbm/BufferedReadStreamExtensions.cs b/src/ImageSharp/Formats/Pbm/BufferedReadStreamExtensions.cs
index 5aa6430d1c..95095edc6e 100644
--- a/src/ImageSharp/Formats/Pbm/BufferedReadStreamExtensions.cs
+++ b/src/ImageSharp/Formats/Pbm/BufferedReadStreamExtensions.cs
@@ -6,6 +6,7 @@ using System.Buffers.Binary;
using System.IO;
using System.Runtime.CompilerServices;
using SixLabors.ImageSharp.IO;
+using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Formats.Pbm
{
@@ -66,12 +67,12 @@ namespace SixLabors.ImageSharp.Formats.Pbm
}
///
- /// Reads a float.
+ /// Reads a 32 bit float.
///
/// The stream to read from.
/// A scratch buffer of size 4 bytes.
/// The byte order. Defaults to little endian.
- /// the value.
+ /// the float value.
public static float ReadSingle(this BufferedReadStream stream, Span scratch, ByteOrder byteOrder = ByteOrder.LittleEndian)
{
stream.Read(scratch, 0, 4);
@@ -87,5 +88,28 @@ namespace SixLabors.ImageSharp.Formats.Pbm
return Unsafe.As(ref intValue);
}
+
+ ///
+ /// Reads a 16 bit float.
+ ///
+ /// The stream to read from.
+ /// A scratch buffer of size 2 bytes.
+ /// The byte order. Defaults to little endian.
+ /// The float value.
+ public static float ReadHalfSingle(this BufferedReadStream stream, Span scratch, ByteOrder byteOrder = ByteOrder.LittleEndian)
+ {
+ stream.Read(scratch, 0, 2);
+ ushort shortValue;
+ if (byteOrder == ByteOrder.LittleEndian)
+ {
+ shortValue = BinaryPrimitives.ReadUInt16LittleEndian(scratch);
+ }
+ else
+ {
+ shortValue = BinaryPrimitives.ReadUInt16BigEndian(scratch);
+ }
+
+ return HalfTypeHelper.Unpack(shortValue);
+ }
}
}