diff --git a/src/ImageSharp/Configuration.cs b/src/ImageSharp/Configuration.cs
index ca83b0764e..a3e0c99489 100644
--- a/src/ImageSharp/Configuration.cs
+++ b/src/ImageSharp/Configuration.cs
@@ -8,6 +8,7 @@ using SixLabors.ImageSharp.Formats;
using SixLabors.ImageSharp.Formats.Bmp;
using SixLabors.ImageSharp.Formats.Gif;
using SixLabors.ImageSharp.Formats.Jpeg;
+using SixLabors.ImageSharp.Formats.OpenExr;
using SixLabors.ImageSharp.Formats.Pbm;
using SixLabors.ImageSharp.Formats.Png;
using SixLabors.ImageSharp.Formats.Tga;
@@ -124,7 +125,7 @@ namespace SixLabors.ImageSharp
///
/// Gets or sets the that is currently in use.
///
- public ImageFormatManager ImageFormatsManager { get; set; } = new ImageFormatManager();
+ public ImageFormatManager ImageFormatsManager { get; set; } = new();
///
/// Gets or sets the that is currently in use.
@@ -192,7 +193,7 @@ namespace SixLabors.ImageSharp
/// Creates a shallow copy of the .
///
/// A new configuration instance.
- public Configuration Clone() => new Configuration
+ public Configuration Clone() => new()
{
MaxDegreeOfParallelism = this.MaxDegreeOfParallelism,
StreamProcessingBufferSize = this.StreamProcessingBufferSize,
@@ -214,9 +215,10 @@ namespace SixLabors.ImageSharp
/// .
/// .
/// .
+ /// .
///
/// The default configuration of .
- internal static Configuration CreateDefaultInstance() => new Configuration(
+ internal static Configuration CreateDefaultInstance() => new(
new PngConfigurationModule(),
new JpegConfigurationModule(),
new GifConfigurationModule(),
@@ -224,6 +226,7 @@ namespace SixLabors.ImageSharp
new PbmConfigurationModule(),
new TgaConfigurationModule(),
new TiffConfigurationModule(),
- new WebpConfigurationModule());
+ new WebpConfigurationModule(),
+ new ExrConfigurationModule());
}
}
diff --git a/src/ImageSharp/Formats/Bmp/BmpArrayFileHeader.cs b/src/ImageSharp/Formats/Bmp/BmpArrayFileHeader.cs
index 2338572476..d68841ad1f 100644
--- a/src/ImageSharp/Formats/Bmp/BmpArrayFileHeader.cs
+++ b/src/ImageSharp/Formats/Bmp/BmpArrayFileHeader.cs
@@ -45,9 +45,6 @@ namespace SixLabors.ImageSharp.Formats.Bmp
///
public short ScreenHeight { get; }
- public static BmpArrayFileHeader Parse(Span data)
- {
- return MemoryMarshal.Cast(data)[0];
- }
+ public static BmpArrayFileHeader Parse(Span data) => MemoryMarshal.Cast(data)[0];
}
}
diff --git a/src/ImageSharp/Formats/Bmp/BmpConstants.cs b/src/ImageSharp/Formats/Bmp/BmpConstants.cs
index 0b9499eeb3..4684406b29 100644
--- a/src/ImageSharp/Formats/Bmp/BmpConstants.cs
+++ b/src/ImageSharp/Formats/Bmp/BmpConstants.cs
@@ -1,4 +1,4 @@
-// Copyright (c) Six Labors.
+// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System.Collections.Generic;
@@ -6,7 +6,7 @@ using System.Collections.Generic;
namespace SixLabors.ImageSharp.Formats.Bmp
{
///
- /// Defines constants relating to BMPs
+ /// Defines constants relating to BMP images.
///
internal static class BmpConstants
{
diff --git a/src/ImageSharp/Formats/Bmp/BmpDecoder.cs b/src/ImageSharp/Formats/Bmp/BmpDecoder.cs
index 129b3a1aa0..4c433b203a 100644
--- a/src/ImageSharp/Formats/Bmp/BmpDecoder.cs
+++ b/src/ImageSharp/Formats/Bmp/BmpDecoder.cs
@@ -4,8 +4,6 @@
using System.IO;
using System.Threading;
using System.Threading.Tasks;
-using SixLabors.ImageSharp.IO;
-using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Formats.Bmp
diff --git a/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs b/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs
index 41adc1cfff..fe31a686bf 100644
--- a/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs
+++ b/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs
@@ -116,7 +116,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp
///
/// Gets the dimensions of the image.
///
- public Size Dimensions => new Size(this.infoHeader.Width, this.infoHeader.Height);
+ public Size Dimensions => new(this.infoHeader.Width, this.infoHeader.Height);
///
public Image Decode(BufferedReadStream stream, CancellationToken cancellationToken)
diff --git a/src/ImageSharp/Formats/Bmp/BmpFormat.cs b/src/ImageSharp/Formats/Bmp/BmpFormat.cs
index d92a73104e..2b24c43879 100644
--- a/src/ImageSharp/Formats/Bmp/BmpFormat.cs
+++ b/src/ImageSharp/Formats/Bmp/BmpFormat.cs
@@ -1,4 +1,4 @@
-// Copyright (c) Six Labors.
+// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System.Collections.Generic;
@@ -17,7 +17,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp
///
/// Gets the current instance.
///
- public static BmpFormat Instance { get; } = new BmpFormat();
+ public static BmpFormat Instance { get; } = new();
///
public string Name => "BMP";
@@ -32,6 +32,6 @@ namespace SixLabors.ImageSharp.Formats.Bmp
public IEnumerable FileExtensions => BmpConstants.FileExtensions;
///
- public BmpMetadata CreateDefaultFormatMetadata() => new BmpMetadata();
+ public BmpMetadata CreateDefaultFormatMetadata() => new();
}
}
diff --git a/src/ImageSharp/Formats/Bmp/BmpImageFormatDetector.cs b/src/ImageSharp/Formats/Bmp/BmpImageFormatDetector.cs
index b380486a3f..82376a30cf 100644
--- a/src/ImageSharp/Formats/Bmp/BmpImageFormatDetector.cs
+++ b/src/ImageSharp/Formats/Bmp/BmpImageFormatDetector.cs
@@ -15,10 +15,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp
public int HeaderSize => 2;
///
- public IImageFormat DetectFormat(ReadOnlySpan header)
- {
- return this.IsSupportedFileFormat(header) ? BmpFormat.Instance : null;
- }
+ public IImageFormat DetectFormat(ReadOnlySpan header) => this.IsSupportedFileFormat(header) ? BmpFormat.Instance : null;
private bool IsSupportedFileFormat(ReadOnlySpan header)
{
diff --git a/src/ImageSharp/Formats/Bmp/BmpInfoHeader.cs b/src/ImageSharp/Formats/Bmp/BmpInfoHeader.cs
index 0d0c05c9f4..6cd9d19f5d 100644
--- a/src/ImageSharp/Formats/Bmp/BmpInfoHeader.cs
+++ b/src/ImageSharp/Formats/Bmp/BmpInfoHeader.cs
@@ -279,15 +279,12 @@ namespace SixLabors.ImageSharp.Formats.Bmp
/// The data to parse.
/// The parsed header.
///
- public static BmpInfoHeader ParseCore(ReadOnlySpan data)
- {
- return new BmpInfoHeader(
+ public static BmpInfoHeader ParseCore(ReadOnlySpan data) => new(
headerSize: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(0, 4)),
width: BinaryPrimitives.ReadUInt16LittleEndian(data.Slice(4, 2)),
height: BinaryPrimitives.ReadUInt16LittleEndian(data.Slice(6, 2)),
planes: BinaryPrimitives.ReadInt16LittleEndian(data.Slice(8, 2)),
bitsPerPixel: BinaryPrimitives.ReadInt16LittleEndian(data.Slice(10, 2)));
- }
///
/// Parses a short variant of the OS22XBITMAPHEADER. It is identical to the BITMAPCOREHEADER, except that the width and height
@@ -296,15 +293,12 @@ namespace SixLabors.ImageSharp.Formats.Bmp
/// The data to parse.
/// The parsed header.
///
- public static BmpInfoHeader ParseOs22Short(ReadOnlySpan data)
- {
- return new BmpInfoHeader(
+ public static BmpInfoHeader ParseOs22Short(ReadOnlySpan data) => new(
headerSize: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(0, 4)),
width: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(4, 4)),
height: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(8, 4)),
planes: BinaryPrimitives.ReadInt16LittleEndian(data.Slice(12, 2)),
bitsPerPixel: BinaryPrimitives.ReadInt16LittleEndian(data.Slice(14, 2)));
- }
///
/// Parses the full BMP Version 3 BITMAPINFOHEADER header (40 bytes).
@@ -312,9 +306,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp
/// The data to parse.
/// The parsed header.
///
- public static BmpInfoHeader ParseV3(ReadOnlySpan data)
- {
- return new BmpInfoHeader(
+ public static BmpInfoHeader ParseV3(ReadOnlySpan data) => new(
headerSize: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(0, 4)),
width: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(4, 4)),
height: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(8, 4)),
@@ -326,7 +318,6 @@ namespace SixLabors.ImageSharp.Formats.Bmp
yPelsPerMeter: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(28, 4)),
clrUsed: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(32, 4)),
clrImportant: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(36, 4)));
- }
///
/// Special case of the BITMAPINFOHEADER V3 used by adobe where the color bitmasks are part of the info header instead of following it.
@@ -336,9 +327,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp
/// Indicates, if the alpha bitmask is present.
/// The parsed header.
///
- public static BmpInfoHeader ParseAdobeV3(ReadOnlySpan data, bool withAlpha = true)
- {
- return new BmpInfoHeader(
+ public static BmpInfoHeader ParseAdobeV3(ReadOnlySpan data, bool withAlpha = true) => new(
headerSize: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(0, 4)),
width: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(4, 4)),
height: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(8, 4)),
@@ -354,7 +343,6 @@ namespace SixLabors.ImageSharp.Formats.Bmp
greenMask: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(44, 4)),
blueMask: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(48, 4)),
alphaMask: withAlpha ? BinaryPrimitives.ReadInt32LittleEndian(data.Slice(52, 4)) : 0);
- }
///
/// Parses a OS/2 version 2 bitmap header (64 bytes). Only the first 40 bytes are parsed which are
diff --git a/src/ImageSharp/Formats/OpenExr/ExrAttribute.cs b/src/ImageSharp/Formats/OpenExr/ExrAttribute.cs
new file mode 100644
index 0000000000..66423f50dc
--- /dev/null
+++ b/src/ImageSharp/Formats/OpenExr/ExrAttribute.cs
@@ -0,0 +1,26 @@
+// Copyright (c) Six Labors.
+// Licensed under the Apache License, Version 2.0.
+
+using System.Diagnostics;
+
+namespace SixLabors.ImageSharp.Formats.OpenExr
+{
+ [DebuggerDisplay("Name: {Name}, Type: {Type}, Length: {Length}")]
+ internal class ExrAttribute
+ {
+ public static readonly ExrAttribute EmptyAttribute = new(string.Empty, string.Empty, 0);
+
+ public ExrAttribute(string name, string type, int length)
+ {
+ this.Name = name;
+ this.Type = type;
+ this.Length = length;
+ }
+
+ public string Name { get; }
+
+ public string Type { get; }
+
+ public int Length { get; }
+ }
+}
diff --git a/src/ImageSharp/Formats/OpenExr/ExrBox2i.cs b/src/ImageSharp/Formats/OpenExr/ExrBox2i.cs
new file mode 100644
index 0000000000..c45dc087ed
--- /dev/null
+++ b/src/ImageSharp/Formats/OpenExr/ExrBox2i.cs
@@ -0,0 +1,24 @@
+// Copyright (c) Six Labors.
+// Licensed under the Apache License, Version 2.0.
+
+namespace SixLabors.ImageSharp.Formats.OpenExr
+{
+ internal struct ExrBox2i
+ {
+ public ExrBox2i(int xMin, int yMin, int xMax, int yMax)
+ {
+ this.xMin = xMin;
+ this.yMin = yMin;
+ this.xMax = xMax;
+ this.yMax = yMax;
+ }
+
+ public int xMin { get; }
+
+ public int yMin { get; }
+
+ public int xMax { get; }
+
+ public int yMax { get; }
+ }
+}
diff --git a/src/ImageSharp/Formats/OpenExr/ExrChannelInfo.cs b/src/ImageSharp/Formats/OpenExr/ExrChannelInfo.cs
new file mode 100644
index 0000000000..81b40ea66f
--- /dev/null
+++ b/src/ImageSharp/Formats/OpenExr/ExrChannelInfo.cs
@@ -0,0 +1,30 @@
+// Copyright (c) Six Labors.
+// Licensed under the Apache License, Version 2.0.
+
+using System.Runtime.InteropServices;
+
+namespace SixLabors.ImageSharp.Formats.OpenExr
+{
+ [StructLayout(LayoutKind.Sequential, Pack = 1)]
+ internal readonly struct ExrChannelInfo
+ {
+ public ExrChannelInfo(string channelName, ExrPixelType pixelType, byte pLinear, int xSampling, int ySampling)
+ {
+ this.ChannelName = channelName;
+ this.PixelType = pixelType;
+ this.PLinear = pLinear;
+ this.XSampling = xSampling;
+ this.YSampling = ySampling;
+ }
+
+ public string ChannelName { get; }
+
+ public ExrPixelType PixelType { get; }
+
+ public byte PLinear { get; }
+
+ public int XSampling { get; }
+
+ public int YSampling { get; }
+ }
+}
diff --git a/src/ImageSharp/Formats/OpenExr/ExrCompression.cs b/src/ImageSharp/Formats/OpenExr/ExrCompression.cs
new file mode 100644
index 0000000000..3fa8cadfd9
--- /dev/null
+++ b/src/ImageSharp/Formats/OpenExr/ExrCompression.cs
@@ -0,0 +1,10 @@
+// Copyright (c) Six Labors.
+// Licensed under the Apache License, Version 2.0.
+
+namespace SixLabors.ImageSharp.Formats.OpenExr
+{
+ internal enum ExrCompression : int
+ {
+ None = 0
+ }
+}
diff --git a/src/ImageSharp/Formats/OpenExr/ExrConfigurationModule.cs b/src/ImageSharp/Formats/OpenExr/ExrConfigurationModule.cs
new file mode 100644
index 0000000000..ff0d3ee6a4
--- /dev/null
+++ b/src/ImageSharp/Formats/OpenExr/ExrConfigurationModule.cs
@@ -0,0 +1,18 @@
+// Copyright (c) Six Labors.
+// Licensed under the Apache License, Version 2.0.
+
+namespace SixLabors.ImageSharp.Formats.OpenExr
+{
+ ///
+ /// Registers the image encoders, decoders and mime type detectors for the OpenExr format.
+ ///
+ public sealed class ExrConfigurationModule : IConfigurationModule
+ {
+ ///
+ public void Configure(Configuration configuration)
+ {
+ configuration.ImageFormatsManager.SetDecoder(ExrFormat.Instance, new ExrDecoder());
+ configuration.ImageFormatsManager.AddImageFormatDetector(new ExrImageFormatDetector());
+ }
+ }
+}
diff --git a/src/ImageSharp/Formats/OpenExr/ExrConstants.cs b/src/ImageSharp/Formats/OpenExr/ExrConstants.cs
new file mode 100644
index 0000000000..b3f1a34245
--- /dev/null
+++ b/src/ImageSharp/Formats/OpenExr/ExrConstants.cs
@@ -0,0 +1,28 @@
+// Copyright (c) Six Labors.
+// Licensed under the Apache License, Version 2.0.
+
+using System.Collections.Generic;
+
+namespace SixLabors.ImageSharp.Formats.OpenExr
+{
+ ///
+ /// Defines constants relating to OpenExr images.
+ ///
+ internal static class ExrConstants
+ {
+ ///
+ /// The list of mimetypes that equate to a OpenExr image.
+ ///
+ public static readonly IEnumerable MimeTypes = new[] { "image/x-exr" };
+
+ ///
+ /// The list of file extensions that equate to a OpenExr image.
+ ///
+ public static readonly IEnumerable FileExtensions = new[] { "exr" };
+
+ ///
+ /// The magick bytes identifying an OpenExr image.
+ ///
+ public static readonly int MagickBytes = 20000630;
+ }
+}
diff --git a/src/ImageSharp/Formats/OpenExr/ExrDecoder.cs b/src/ImageSharp/Formats/OpenExr/ExrDecoder.cs
new file mode 100644
index 0000000000..0c645d71e1
--- /dev/null
+++ b/src/ImageSharp/Formats/OpenExr/ExrDecoder.cs
@@ -0,0 +1,61 @@
+// Copyright (c) Six Labors.
+// Licensed under the Apache License, Version 2.0.
+
+using System.IO;
+using System.Threading;
+using System.Threading.Tasks;
+using SixLabors.ImageSharp.PixelFormats;
+
+namespace SixLabors.ImageSharp.Formats.OpenExr
+{
+ ///
+ /// Image decoder for generating an image out of a OpenExr stream.
+ ///
+ public sealed class ExrDecoder : IImageDecoder, IExrDecoderOptions, IImageInfoDetector
+ {
+ ///
+ public Image Decode(Configuration configuration, Stream stream)
+ where TPixel : unmanaged, IPixel
+ {
+ Guard.NotNull(stream, nameof(stream));
+
+ var decoder = new ExrDecoderCore(configuration, this);
+ return decoder.Decode(configuration, stream);
+ }
+
+ ///
+ public Image Decode(Configuration configuration, Stream stream)
+ => this.Decode(configuration, stream);
+
+ ///
+ public Task> DecodeAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken)
+ where TPixel : unmanaged, IPixel
+ {
+ Guard.NotNull(stream, nameof(stream));
+
+ var decoder = new ExrDecoderCore(configuration, this);
+ return decoder.DecodeAsync(configuration, stream, cancellationToken);
+ }
+
+ ///
+ public async Task DecodeAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken)
+ => await this.DecodeAsync(configuration, stream, cancellationToken)
+ .ConfigureAwait(false);
+
+ ///
+ public IImageInfo Identify(Configuration configuration, Stream stream)
+ {
+ Guard.NotNull(stream, nameof(stream));
+
+ return new ExrDecoderCore(configuration, this).Identify(configuration, stream);
+ }
+
+ ///
+ public Task IdentifyAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken)
+ {
+ Guard.NotNull(stream, nameof(stream));
+
+ return new ExrDecoderCore(configuration, this).IdentifyAsync(configuration, stream, cancellationToken);
+ }
+ }
+}
diff --git a/src/ImageSharp/Formats/OpenExr/ExrDecoderCore.cs b/src/ImageSharp/Formats/OpenExr/ExrDecoderCore.cs
new file mode 100644
index 0000000000..c5c9425066
--- /dev/null
+++ b/src/ImageSharp/Formats/OpenExr/ExrDecoderCore.cs
@@ -0,0 +1,256 @@
+// Copyright (c) Six Labors.
+// Licensed under the Apache License, Version 2.0.
+
+using System;
+using System.Buffers.Binary;
+using System.Collections.Generic;
+using System.Text;
+using System.Threading;
+using SixLabors.ImageSharp.Formats.Pbm;
+using SixLabors.ImageSharp.IO;
+using SixLabors.ImageSharp.Memory;
+using SixLabors.ImageSharp.Metadata;
+using SixLabors.ImageSharp.PixelFormats;
+
+namespace SixLabors.ImageSharp.Formats.OpenExr
+{
+ ///
+ /// Performs the OpenExr decoding operation.
+ ///
+ internal sealed class ExrDecoderCore : IImageDecoderInternals
+ {
+ ///
+ /// Reusable buffer.
+ ///
+ private readonly byte[] buffer = new byte[4];
+
+ ///
+ /// Used for allocating memory during processing operations.
+ ///
+ private readonly MemoryAllocator memoryAllocator;
+
+ ///
+ /// The bitmap decoder options.
+ ///
+ private readonly IExrDecoderOptions options;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The configuration.
+ /// The options.
+ public ExrDecoderCore(Configuration configuration, IExrDecoderOptions options)
+ {
+ this.Configuration = configuration;
+ this.memoryAllocator = configuration.MemoryAllocator;
+ this.options = options;
+ }
+
+ ///
+ public Configuration Configuration { get; }
+
+ ///
+ /// Gets the dimensions of the image.
+ ///
+ public Size Dimensions => new(this.Width, this.Height);
+
+ private int Width { get; set; }
+
+ private int Height { get; set; }
+
+ ///
+ public Image Decode(BufferedReadStream stream, CancellationToken cancellationToken)
+ where TPixel : unmanaged, IPixel => throw new NotImplementedException();
+
+ ///
+ public IImageInfo Identify(BufferedReadStream stream, CancellationToken cancellationToken)
+ {
+ // Skip over the magick bytes.
+ stream.Read(this.buffer, 0, 4);
+
+ // Read version number.
+ byte version = (byte)stream.ReadByte();
+ if (version != 2)
+ {
+ ExrThrowHelper.ThrowNotSupportedVersion();
+ }
+
+ // Next three bytes contain info's about the image.
+ // We ignore those for now.
+ stream.Read(this.buffer, 0, 3);
+
+ ExrHeader header = this.ParseHeader(stream);
+
+ if (!header.IsValid())
+ {
+ ExrThrowHelper.ThrowInvalidImageHeader();
+ }
+
+ if (header.Channels.Count != 3)
+ {
+ ExrThrowHelper.ThrowNotSupported("Only 3 channels are supported!");
+ }
+
+ this.Width = header.DataWindow.Value.xMax - header.DataWindow.Value.xMin + 1;
+ this.Height = header.DataWindow.Value.yMax - header.DataWindow.Value.yMin + 1;
+
+ // TODO: calculate bits per pixel.
+ int bitsPerPixel = 48;
+
+ return new ImageInfo(new PixelTypeInfo(bitsPerPixel), this.Width, this.Height, new ImageMetadata());
+ }
+
+ private ExrHeader ParseHeader(BufferedReadStream stream)
+ {
+ ExrAttribute attribute = this.ReadAttribute(stream);
+ var header = new ExrHeader();
+
+ while (!attribute.Equals(ExrAttribute.EmptyAttribute))
+ {
+ switch (attribute.Name)
+ {
+ case "channels":
+ IList channels = this.ParseChannelList(stream, attribute.Length);
+ header.Channels = channels;
+ break;
+ case "compression":
+ var compression = (ExrCompression)stream.ReadByte();
+ header.Compression = compression;
+ break;
+ case "dataWindow":
+ ExrBox2i dataWindow = this.ReadBox2i(stream);
+ header.DataWindow = dataWindow;
+ break;
+ case "displayWindow":
+ ExrBox2i displayWindow = this.ReadBox2i(stream);
+ header.DisplayWindow = displayWindow;
+ break;
+ case "lineOrder":
+ var lineOrder = (ExrLineOrder)stream.ReadByte();
+ header.LineOrder = lineOrder;
+ break;
+ case "pixelAspectRatio":
+ float aspectRatio = stream.ReadSingle(this.buffer);
+ header.AspectRatio = aspectRatio;
+ break;
+ case "screenWindowCenter":
+ float screenWindowCenterX = stream.ReadSingle(this.buffer);
+ float screenWindowCenterY = stream.ReadSingle(this.buffer);
+ header.ScreenWindowCenter = new PointF(screenWindowCenterX, screenWindowCenterY);
+ break;
+ case "screenWindowWidth":
+ float screenWindowWidth = stream.ReadSingle(this.buffer);
+ header.ScreenWindowWidth = screenWindowWidth;
+ break;
+ default:
+ // Skip unknown attribute bytes.
+ stream.Skip(attribute.Length);
+ break;
+ }
+
+ attribute = this.ReadAttribute(stream);
+ }
+
+ return header;
+ }
+
+ private ExrAttribute ReadAttribute(BufferedReadStream stream)
+ {
+ string attributeName = ReadString(stream);
+ if (attributeName.Equals(string.Empty))
+ {
+ return ExrAttribute.EmptyAttribute;
+ }
+
+ string attributeType = ReadString(stream);
+
+ stream.Read(this.buffer, 0, 4);
+ int attributeSize = BinaryPrimitives.ReadInt32LittleEndian(this.buffer);
+
+ return new ExrAttribute(attributeName, attributeType, attributeSize);
+ }
+
+ private ExrBox2i ReadBox2i(BufferedReadStream stream)
+ {
+ stream.Read(this.buffer, 0, 4);
+ int xMin = BinaryPrimitives.ReadInt32LittleEndian(this.buffer);
+
+ stream.Read(this.buffer, 0, 4);
+ int yMin = BinaryPrimitives.ReadInt32LittleEndian(this.buffer);
+
+ stream.Read(this.buffer, 0, 4);
+ int xMax = BinaryPrimitives.ReadInt32LittleEndian(this.buffer);
+
+ stream.Read(this.buffer, 0, 4);
+ int yMax = BinaryPrimitives.ReadInt32LittleEndian(this.buffer);
+
+ return new ExrBox2i(xMin, yMin, xMax, yMax);
+ }
+
+ private List ParseChannelList(BufferedReadStream stream, int attributeSize)
+ {
+ var channels = new List();
+ while (attributeSize > 1)
+ {
+ ExrChannelInfo channelInfo = this.ReadChannelInfo(stream, out int bytesRead);
+ channels.Add(channelInfo);
+ attributeSize -= bytesRead;
+ }
+
+ // Last byte should be a null byte.
+ int byteRead = stream.ReadByte();
+
+ return channels;
+ }
+
+ private ExrChannelInfo ReadChannelInfo(BufferedReadStream stream, out int bytesRead)
+ {
+ string channelName = ReadString(stream);
+ bytesRead = channelName.Length + 1;
+
+ stream.Read(this.buffer, 0, 4);
+ bytesRead += 4;
+ var pixelType = (ExrPixelType)BinaryPrimitives.ReadInt32LittleEndian(this.buffer);
+
+ byte pLinear = (byte)stream.ReadByte();
+ byte reserved0 = (byte)stream.ReadByte();
+ byte reserved1 = (byte)stream.ReadByte();
+ byte reserved2 = (byte)stream.ReadByte();
+ bytesRead += 4;
+
+ stream.Read(this.buffer, 0, 4);
+ bytesRead += 4;
+ int xSampling = BinaryPrimitives.ReadInt32LittleEndian(this.buffer);
+
+ stream.Read(this.buffer, 0, 4);
+ bytesRead += 4;
+ int ySampling = BinaryPrimitives.ReadInt32LittleEndian(this.buffer);
+
+ return new ExrChannelInfo(channelName, pixelType, pLinear, xSampling, ySampling);
+ }
+
+ private static string ReadString(BufferedReadStream stream)
+ {
+ var str = new StringBuilder();
+ int character = stream.ReadByte();
+ if (character == 0)
+ {
+ // End of file header reached.
+ return string.Empty;
+ }
+
+ while (character != 0)
+ {
+ if (character == -1)
+ {
+ ExrThrowHelper.ThrowInvalidImageHeader();
+ }
+
+ str.Append((char)character);
+ character = stream.ReadByte();
+ }
+
+ return str.ToString();
+ }
+ }
+}
diff --git a/src/ImageSharp/Formats/OpenExr/ExrFormat.cs b/src/ImageSharp/Formats/OpenExr/ExrFormat.cs
new file mode 100644
index 0000000000..2cbce0970c
--- /dev/null
+++ b/src/ImageSharp/Formats/OpenExr/ExrFormat.cs
@@ -0,0 +1,38 @@
+// Copyright (c) Six Labors.
+// Licensed under the Apache License, Version 2.0.
+
+using System.Collections.Generic;
+using SixLabors.ImageSharp.Formats.Bmp;
+
+namespace SixLabors.ImageSharp.Formats.OpenExr
+{
+ ///
+ /// Registers the image encoders, decoders and mime type detectors for the OpenExr format.
+ ///
+ public sealed class ExrFormat : IImageFormat
+ {
+ private ExrFormat()
+ {
+ }
+
+ ///
+ /// Gets the current instance.
+ ///
+ public static ExrFormat Instance { get; } = new();
+
+ ///
+ public string Name => "EXR";
+
+ ///
+ public string DefaultMimeType => "image/x-exr";
+
+ ///
+ public IEnumerable MimeTypes => ExrConstants.MimeTypes;
+
+ ///
+ public IEnumerable FileExtensions => ExrConstants.FileExtensions;
+
+ ///
+ public ExrMetadata CreateDefaultFormatMetadata() => new();
+ }
+}
diff --git a/src/ImageSharp/Formats/OpenExr/ExrHeader.cs b/src/ImageSharp/Formats/OpenExr/ExrHeader.cs
new file mode 100644
index 0000000000..1ed464ebd2
--- /dev/null
+++ b/src/ImageSharp/Formats/OpenExr/ExrHeader.cs
@@ -0,0 +1,71 @@
+// Copyright (c) Six Labors.
+// Licensed under the Apache License, Version 2.0.
+
+using System.Collections.Generic;
+
+namespace SixLabors.ImageSharp.Formats.OpenExr
+{
+ internal class ExrHeader
+ {
+ public IList Channels { get; set; }
+
+ public ExrCompression? Compression { get; set; }
+
+ public ExrBox2i? DataWindow { get; set; }
+
+ public ExrBox2i? DisplayWindow { get; set; }
+
+ public ExrLineOrder? LineOrder { get; set; }
+
+ public float? AspectRatio { get; set; }
+
+ public float? ScreenWindowWidth { get; set; }
+
+ public PointF? ScreenWindowCenter { get; set; }
+
+ public bool IsValid()
+ {
+ if (!this.Compression.HasValue)
+ {
+ return false;
+ }
+
+ if (!this.DataWindow.HasValue)
+ {
+ return false;
+ }
+
+ if (!this.DisplayWindow.HasValue)
+ {
+ return false;
+ }
+
+ if (!this.LineOrder.HasValue)
+ {
+ return false;
+ }
+
+ if (!this.AspectRatio.HasValue)
+ {
+ return false;
+ }
+
+ if (!this.ScreenWindowWidth.HasValue)
+ {
+ return false;
+ }
+
+ if (!this.ScreenWindowCenter.HasValue)
+ {
+ return false;
+ }
+
+ if (this.Channels is null)
+ {
+ return false;
+ }
+
+ return true;
+ }
+ }
+}
diff --git a/src/ImageSharp/Formats/OpenExr/ExrImageFormatDetector.cs b/src/ImageSharp/Formats/OpenExr/ExrImageFormatDetector.cs
new file mode 100644
index 0000000000..27230306db
--- /dev/null
+++ b/src/ImageSharp/Formats/OpenExr/ExrImageFormatDetector.cs
@@ -0,0 +1,31 @@
+// Copyright (c) Six Labors.
+// Licensed under the Apache License, Version 2.0.
+
+using System;
+using System.Buffers.Binary;
+
+namespace SixLabors.ImageSharp.Formats.OpenExr
+{
+ ///
+ /// Detects OpenExr file headers.
+ ///
+ public sealed class ExrImageFormatDetector : IImageFormatDetector
+ {
+ ///
+ public int HeaderSize => 4;
+
+ ///
+ public IImageFormat DetectFormat(ReadOnlySpan header) => this.IsSupportedFileFormat(header) ? ExrFormat.Instance : null;
+
+ private bool IsSupportedFileFormat(ReadOnlySpan header)
+ {
+ if (header.Length >= this.HeaderSize)
+ {
+ int fileTypeMarker = BinaryPrimitives.ReadInt32LittleEndian(header);
+ return fileTypeMarker == ExrConstants.MagickBytes;
+ }
+
+ return false;
+ }
+ }
+}
diff --git a/src/ImageSharp/Formats/OpenExr/ExrLineOrder.cs b/src/ImageSharp/Formats/OpenExr/ExrLineOrder.cs
new file mode 100644
index 0000000000..1005414f22
--- /dev/null
+++ b/src/ImageSharp/Formats/OpenExr/ExrLineOrder.cs
@@ -0,0 +1,14 @@
+// Copyright (c) Six Labors.
+// Licensed under the Apache License, Version 2.0.
+
+namespace SixLabors.ImageSharp.Formats.OpenExr
+{
+ internal enum ExrLineOrder : byte
+ {
+ IncreasingY = 0,
+
+ DecreasingY = 1,
+
+ RandomY = 2
+ }
+}
diff --git a/src/ImageSharp/Formats/OpenExr/ExrMetadata.cs b/src/ImageSharp/Formats/OpenExr/ExrMetadata.cs
new file mode 100644
index 0000000000..f3723c5426
--- /dev/null
+++ b/src/ImageSharp/Formats/OpenExr/ExrMetadata.cs
@@ -0,0 +1,29 @@
+// Copyright (c) Six Labors.
+// Licensed under the Apache License, Version 2.0.
+
+namespace SixLabors.ImageSharp.Formats.OpenExr
+{
+ ///
+ /// Provides OpenExr specific metadata information for the image.
+ ///
+ public class ExrMetadata : IDeepCloneable
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public ExrMetadata()
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The metadata to create an instance from.
+ private ExrMetadata(ExrMetadata other)
+ {
+ }
+
+ ///
+ public IDeepCloneable DeepClone() => new ExrMetadata(this);
+ }
+}
diff --git a/src/ImageSharp/Formats/OpenExr/ExrPixelType.cs b/src/ImageSharp/Formats/OpenExr/ExrPixelType.cs
new file mode 100644
index 0000000000..4e3427cf43
--- /dev/null
+++ b/src/ImageSharp/Formats/OpenExr/ExrPixelType.cs
@@ -0,0 +1,23 @@
+// Copyright (c) Six Labors.
+// Licensed under the Apache License, Version 2.0.
+
+namespace SixLabors.ImageSharp.Formats.OpenExr
+{
+ internal enum ExrPixelType : int
+ {
+ ///
+ /// unsigned int (32 bit).
+ ///
+ Uint = 0,
+
+ ///
+ /// half (16 bit floating point).
+ ///
+ Half = 1,
+
+ ///
+ /// float (32 bit floating point).
+ ///
+ Float = 2
+ }
+}
diff --git a/src/ImageSharp/Formats/OpenExr/ExrThrowHelper.cs b/src/ImageSharp/Formats/OpenExr/ExrThrowHelper.cs
new file mode 100644
index 0000000000..227a961188
--- /dev/null
+++ b/src/ImageSharp/Formats/OpenExr/ExrThrowHelper.cs
@@ -0,0 +1,26 @@
+// Copyright (c) Six Labors.
+// Licensed under the Apache License, Version 2.0.
+
+using System;
+using System.Runtime.CompilerServices;
+
+namespace SixLabors.ImageSharp.Formats.OpenExr
+{
+ ///
+ /// Cold path optimizations for throwing exr format based exceptions.
+ ///
+ internal static class ExrThrowHelper
+ {
+ [MethodImpl(InliningOptions.ColdPath)]
+ public static void ThrowNotSupportedVersion() => throw new NotSupportedException("Unsupported EXR version");
+
+ [MethodImpl(InliningOptions.ColdPath)]
+ public static void ThrowNotSupported(string msg) => throw new NotSupportedException(msg);
+
+ [MethodImpl(InliningOptions.ColdPath)]
+ public static void ThrowInvalidImageHeader() => throw new InvalidImageContentException("Invalid EXR image header");
+
+ [MethodImpl(InliningOptions.ColdPath)]
+ public static void ThrowInvalidImageHeader(string msg) => throw new InvalidImageContentException(msg);
+ }
+}
diff --git a/src/ImageSharp/Formats/OpenExr/IExrDecoderOptions.cs b/src/ImageSharp/Formats/OpenExr/IExrDecoderOptions.cs
new file mode 100644
index 0000000000..e61ce85378
--- /dev/null
+++ b/src/ImageSharp/Formats/OpenExr/IExrDecoderOptions.cs
@@ -0,0 +1,12 @@
+// Copyright (c) Six Labors.
+// Licensed under the Apache License, Version 2.0.
+
+namespace SixLabors.ImageSharp.Formats.OpenExr
+{
+ ///
+ /// Image decoder options for decoding OpenExr streams.
+ ///
+ internal interface IExrDecoderOptions
+ {
+ }
+}
diff --git a/src/ImageSharp/Formats/OpenExr/README.md b/src/ImageSharp/Formats/OpenExr/README.md
new file mode 100644
index 0000000000..c71ab113d1
--- /dev/null
+++ b/src/ImageSharp/Formats/OpenExr/README.md
@@ -0,0 +1,4 @@
+### Some useful links for documentation about the OpenEXR format:
+
+- [Technical Introduction](https://openexr.readthedocs.io/en/latest/TechnicalIntroduction.html)
+- [OpenExr file layout](https://openexr.readthedocs.io/en/latest/OpenEXRFileLayout.html)
\ No newline at end of file
diff --git a/src/ImageSharp/Formats/Pbm/BufferedReadStreamExtensions.cs b/src/ImageSharp/Formats/Pbm/BufferedReadStreamExtensions.cs
index 581d3e592b..5aa6430d1c 100644
--- a/src/ImageSharp/Formats/Pbm/BufferedReadStreamExtensions.cs
+++ b/src/ImageSharp/Formats/Pbm/BufferedReadStreamExtensions.cs
@@ -1,7 +1,10 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
+using System;
+using System.Buffers.Binary;
using System.IO;
+using System.Runtime.CompilerServices;
using SixLabors.ImageSharp.IO;
namespace SixLabors.ImageSharp.Formats.Pbm
@@ -61,5 +64,28 @@ namespace SixLabors.ImageSharp.Formats.Pbm
return value;
}
+
+ ///
+ /// Reads a float.
+ ///
+ /// The stream to read from.
+ /// A scratch buffer of size 4 bytes.
+ /// The byte order. Defaults to little endian.
+ /// the value.
+ public static float ReadSingle(this BufferedReadStream stream, Span scratch, ByteOrder byteOrder = ByteOrder.LittleEndian)
+ {
+ stream.Read(scratch, 0, 4);
+ int intValue;
+ if (byteOrder == ByteOrder.LittleEndian)
+ {
+ intValue = BinaryPrimitives.ReadInt32LittleEndian(scratch);
+ }
+ else
+ {
+ intValue = BinaryPrimitives.ReadInt32BigEndian(scratch);
+ }
+
+ return Unsafe.As(ref intValue);
+ }
}
}