diff --git a/src/ImageSharp/Configuration.cs b/src/ImageSharp/Configuration.cs
index 6e431ba31b..a7f84363e6 100644
--- a/src/ImageSharp/Configuration.cs
+++ b/src/ImageSharp/Configuration.cs
@@ -5,10 +5,10 @@ using System.Collections.Concurrent;
using SixLabors.ImageSharp.Formats;
using SixLabors.ImageSharp.Formats.Bmp;
using SixLabors.ImageSharp.Formats.Cur;
+using SixLabors.ImageSharp.Formats.Exr;
using SixLabors.ImageSharp.Formats.Gif;
using SixLabors.ImageSharp.Formats.Ico;
using SixLabors.ImageSharp.Formats.Jpeg;
-using SixLabors.ImageSharp.Formats.Exr;
using SixLabors.ImageSharp.Formats.Pbm;
using SixLabors.ImageSharp.Formats.Png;
using SixLabors.ImageSharp.Formats.Qoi;
diff --git a/src/ImageSharp/Formats/Exr/ExrDecoder.cs b/src/ImageSharp/Formats/Exr/ExrDecoder.cs
index 11937eed4f..2e27717282 100644
--- a/src/ImageSharp/Formats/Exr/ExrDecoder.cs
+++ b/src/ImageSharp/Formats/Exr/ExrDecoder.cs
@@ -28,7 +28,7 @@ public class ExrDecoder : ImageDecoder
return new ExrDecoderCore(new ExrDecoderOptions { GeneralOptions = options }).Identify(options.Configuration, stream, cancellationToken);
}
- ////
+ ///
protected override Image Decode(DecoderOptions options, Stream stream, CancellationToken cancellationToken)
{
Guard.NotNull(options, nameof(options));
diff --git a/src/ImageSharp/Formats/Exr/ExrDecoderCore.cs b/src/ImageSharp/Formats/Exr/ExrDecoderCore.cs
index 555b07bacf..4f3f113536 100644
--- a/src/ImageSharp/Formats/Exr/ExrDecoderCore.cs
+++ b/src/ImageSharp/Formats/Exr/ExrDecoderCore.cs
@@ -6,7 +6,6 @@ using System.Buffers;
using System.Buffers.Binary;
using System.Numerics;
using System.Runtime.CompilerServices;
-using System.Runtime.Intrinsics;
using System.Text;
using SixLabors.ImageSharp.Formats.Exr.Compression;
using SixLabors.ImageSharp.Formats.Exr.Constants;
@@ -23,7 +22,6 @@ namespace SixLabors.ImageSharp.Formats.Exr;
internal sealed class ExrDecoderCore : ImageDecoderCore
{
private const float Scale32Bit = 1f / 0xFFFFFFFF;
- private static readonly Vector4 Scale32BitVector = Vector128.Create(Scale32Bit, Scale32Bit, Scale32Bit, 1f).AsVector4();
///
/// Reusable buffer.
@@ -56,11 +54,6 @@ internal sealed class ExrDecoderCore : ImageDecoderCore
this.memoryAllocator = this.configuration.MemoryAllocator;
}
- ///
- /// Gets the dimensions of the image.
- ///
- public Size Dimensions => new(this.Width, this.Height);
-
///
/// Gets or sets the image width.
///
@@ -81,16 +74,6 @@ internal sealed class ExrDecoderCore : ImageDecoderCore
///
private ExrCompression Compression { get; set; }
- ///
- /// Gets or sets the image data type, either RGB, RGBA or gray.
- ///
- private ExrImageDataType ImageDataType { get; set; }
-
- ///
- /// Gets or sets the image type, either ScanLine or tiled.
- ///
- private ExrImageType ImageType { get; set; }
-
///
/// Gets or sets the header attributes.
///
@@ -106,7 +89,6 @@ internal sealed class ExrDecoderCore : ImageDecoderCore
}
ExrPixelType pixelType = this.ValidateChannels();
- this.ReadImageDataType();
Image image = new(this.configuration, this.Width, this.Height, this.metadata);
Buffer2D pixels = image.GetRootFramePixelBuffer();
@@ -226,7 +208,7 @@ internal sealed class ExrDecoderCore : ImageDecoderCore
for (int channelIdx = 0; channelIdx < this.Channels.Count; channelIdx++)
{
ExrChannelInfo channel = this.Channels[channelIdx];
- offset += this.ReadUnsignedIntChannelData(stream, channel, decompressedPixelData.Slice(offset), redPixelData, greenPixelData, bluePixelData, alphaPixelData, width);
+ offset += this.ReadUnsignedIntChannelData(stream, channel, decompressedPixelData[offset..], redPixelData, greenPixelData, bluePixelData, alphaPixelData, width);
}
for (int x = 0; x < width; x++)
@@ -320,29 +302,18 @@ internal sealed class ExrDecoderCore : ImageDecoderCore
}
}
- private static int ReadChannelData(ExrChannelInfo channel, Span decompressedPixelData, Span pixelData, int width)
+ private static int ReadChannelData(ExrChannelInfo channel, Span decompressedPixelData, Span pixelData, int width) => channel.PixelType switch
{
- switch (channel.PixelType)
- {
- case ExrPixelType.Half:
- return ReadPixelRowChannelHalfSingle(decompressedPixelData, pixelData, width);
- case ExrPixelType.Float:
- return ReadPixelRowChannelSingle(decompressedPixelData, pixelData, width);
- }
-
- return 0;
- }
+ ExrPixelType.Half => ReadPixelRowChannelHalfSingle(decompressedPixelData, pixelData, width),
+ ExrPixelType.Float => ReadPixelRowChannelSingle(decompressedPixelData, pixelData, width),
+ _ => 0,
+ };
- private static int ReadChannelData(ExrChannelInfo channel, Span decompressedPixelData, Span pixelData, int width)
+ private static int ReadChannelData(ExrChannelInfo channel, Span decompressedPixelData, Span pixelData, int width) => channel.PixelType switch
{
- switch (channel.PixelType)
- {
- case ExrPixelType.UnsignedInt:
- return ReadPixelRowChannelUnsignedInt(decompressedPixelData, pixelData, width);
- }
-
- return 0;
- }
+ ExrPixelType.UnsignedInt => ReadPixelRowChannelUnsignedInt(decompressedPixelData, pixelData, width),
+ _ => 0,
+ };
private static int ReadPixelRowChannelHalfSingle(Span decompressedPixelData, Span channelData, int width)
{
@@ -442,7 +413,7 @@ internal sealed class ExrDecoderCore : ImageDecoderCore
byte flagsByte2 = (byte)stream.ReadByte();
if ((flagsByte0 & (1 << 1)) != 0)
{
- this.ImageType = ExrImageType.Tiled;
+ ExrThrowHelper.ThrowNotSupported("Decoding tiled exr images is not supported yet!");
}
this.HeaderAttributes = this.ParseHeaderAttributes(stream);
@@ -560,7 +531,7 @@ internal sealed class ExrDecoderCore : ImageDecoderCore
private List ReadChannelList(BufferedReadStream stream, int attributeSize)
{
- List channels = new();
+ List channels = [];
while (attributeSize > 1)
{
ExrChannelInfo channelInfo = this.ReadChannelInfo(stream, out int bytesRead);
@@ -654,76 +625,11 @@ internal sealed class ExrDecoderCore : ImageDecoderCore
return pixelType.Value;
}
- private bool IsSupportedCompression()
- {
- switch (this.Compression)
- {
- case ExrCompression.None:
- case ExrCompression.Zip:
- case ExrCompression.Zips:
- case ExrCompression.RunLengthEncoded:
- case ExrCompression.B44:
- return true;
- }
-
- return false;
- }
-
- private void ReadImageDataType()
+ private bool IsSupportedCompression() => this.Compression switch
{
- bool hasRedChannel = false;
- bool hasGreenChannel = false;
- bool hasBlueChannel = false;
- bool hasAlphaChannel = false;
- bool hasLuminance = false;
- foreach (ExrChannelInfo channelInfo in this.Channels)
- {
- if (channelInfo.ChannelName.Equals("A", StringComparison.Ordinal))
- {
- hasAlphaChannel = true;
- }
-
- if (channelInfo.ChannelName.Equals("R", StringComparison.Ordinal))
- {
- hasRedChannel = true;
- }
-
- if (channelInfo.ChannelName.Equals("G", StringComparison.Ordinal))
- {
- hasGreenChannel = true;
- }
-
- if (channelInfo.ChannelName.Equals("B", StringComparison.Ordinal))
- {
- hasBlueChannel = true;
- }
-
- if (channelInfo.ChannelName.Equals("Y", StringComparison.Ordinal))
- {
- hasLuminance = true;
- }
- }
-
- if (hasRedChannel && hasGreenChannel && hasBlueChannel && hasAlphaChannel)
- {
- this.ImageDataType = ExrImageDataType.Rgba;
- return;
- }
-
- if (hasRedChannel && hasGreenChannel && hasBlueChannel)
- {
- this.ImageDataType = ExrImageDataType.Rgb;
- return;
- }
-
- if (hasLuminance && this.Channels.Count == 1)
- {
- this.ImageDataType = ExrImageDataType.Gray;
- return;
- }
-
- ExrThrowHelper.ThrowNotSupported("The image contains channels, which are not supported!");
- }
+ ExrCompression.None or ExrCompression.Zip or ExrCompression.Zips or ExrCompression.RunLengthEncoded or ExrCompression.B44 => true,
+ _ => false,
+ };
private bool HasAlpha()
{
diff --git a/src/ImageSharp/Formats/Exr/ExrMetadata.cs b/src/ImageSharp/Formats/Exr/ExrMetadata.cs
index 552d2d1133..32b9ef3c55 100644
--- a/src/ImageSharp/Formats/Exr/ExrMetadata.cs
+++ b/src/ImageSharp/Formats/Exr/ExrMetadata.cs
@@ -30,17 +30,22 @@ public class ExrMetadata : IFormatMetadata
///
public ExrPixelType PixelType { get; set; } = ExrPixelType.Float;
+ ///
public static ExrMetadata FromFormatConnectingMetadata(FormatConnectingMetadata metadata) => throw new NotImplementedException();
+ ///
public void AfterImageApply(Image destination, Matrix4x4 matrix)
where TPixel : unmanaged, IPixel => throw new NotImplementedException();
///
public IDeepCloneable DeepClone() => new ExrMetadata(this);
+ ///
public PixelTypeInfo GetPixelTypeInfo() => throw new NotImplementedException();
+ ///
public FormatConnectingMetadata ToFormatConnectingMetadata() => throw new NotImplementedException();
+ ///
ExrMetadata IDeepCloneable.DeepClone() => throw new NotImplementedException();
}
diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/Rgba32.cs b/src/ImageSharp/PixelFormats/PixelImplementations/Rgba32.cs
index 34a7f67871..4913724473 100644
--- a/src/ImageSharp/PixelFormats/PixelImplementations/Rgba32.cs
+++ b/src/ImageSharp/PixelFormats/PixelImplementations/Rgba32.cs
@@ -318,6 +318,7 @@ public partial struct Rgba32 : IPixel, IPackedVector
/// Initializes the pixel instance from an value.
///
/// The value.
+ /// The pixel value as Rgba32.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Rgba32 FromRgb96(Rgb96 source)
=> new()
diff --git a/tests/ImageSharp.Benchmarks/Codecs/Exr/DecodeExr.cs b/tests/ImageSharp.Benchmarks/Codecs/Exr/DecodeExr.cs
new file mode 100644
index 0000000000..87d68f40f6
--- /dev/null
+++ b/tests/ImageSharp.Benchmarks/Codecs/Exr/DecodeExr.cs
@@ -0,0 +1,68 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+
+using BenchmarkDotNet.Attributes;
+
+using ImageMagick;
+using SixLabors.ImageSharp.Formats.Exr;
+using SixLabors.ImageSharp.PixelFormats;
+using SixLabors.ImageSharp.Tests;
+
+namespace SixLabors.ImageSharp.Benchmarks.Codecs;
+
+[MarkdownExporter]
+[HtmlExporter]
+[Config(typeof(Config.Short))]
+public class DecodeExr
+{
+ private Configuration configuration;
+
+ private byte[] imageBytes;
+
+ private string TestImageFullPath => Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, this.TestImage);
+
+ [Params(TestImages.Exr.Benchamrk)]
+ public string TestImage { get; set; }
+
+ [GlobalSetup]
+ public void ReadImages()
+ {
+ this.configuration = Configuration.CreateDefaultInstance();
+ new ExrConfigurationModule().Configure(this.configuration);
+
+ this.imageBytes ??= File.ReadAllBytes(this.TestImageFullPath);
+ }
+
+ [Benchmark(Description = "Magick Exr")]
+ public int ExrImageMagick()
+ {
+ MagickReadSettings settings = new() { Format = MagickFormat.Exr };
+ using MemoryStream memoryStream = new(this.imageBytes);
+ using MagickImage image = new(memoryStream, settings);
+ return image.Width;
+ }
+
+ [Benchmark(Description = "ImageSharp Exr")]
+ public int ExrImageSharp()
+ {
+ using MemoryStream memoryStream = new(this.imageBytes);
+ using Image image = Image.Load(memoryStream);
+ return image.Height;
+ }
+
+ /* Results 27.03.2026
+ BenchmarkDotNet v0.15.8, Windows 11 (10.0.26200.8037/25H2/2025Update/HudsonValley2)
+ Intel Core i7-14700T 1.30GHz, 1 CPU, 28 logical and 20 physical cores
+ .NET SDK 10.0.201
+ [Host] : .NET 8.0.25 (8.0.25, 8.0.2526.11203), X64 RyuJIT x86-64-v3
+ Job-VDWIGO : .NET 8.0.25 (8.0.25, 8.0.2526.11203), X64 RyuJIT x86-64-v3
+
+ Runtime=.NET 8.0 Arguments=/p:DebugType=portable IterationCount=3
+ LaunchCount=1 WarmupCount=3
+
+ | Method | TestImage | Mean | Error | StdDev | Allocated |
+ |----------------- |----------------------------- |---------:|---------:|---------:|----------:|
+ | 'Magick Exr' | Exr/Calliphora_benchmark.exr | 20.37 ms | 0.790 ms | 0.043 ms | 12.98 KB |
+ | 'ImageSharp Exr' | Exr/Calliphora_benchmark.exr | 45.68 ms | 4.999 ms | 0.274 ms | 34.09 KB |
+ */
+}
diff --git a/tests/ImageSharp.Tests/TestImages.cs b/tests/ImageSharp.Tests/TestImages.cs
index 1b0c0c865b..8758b74f5d 100644
--- a/tests/ImageSharp.Tests/TestImages.cs
+++ b/tests/ImageSharp.Tests/TestImages.cs
@@ -1383,6 +1383,7 @@ public static class TestImages
public static class Exr
{
+ public const string Benchamrk = "Exr/Calliphora_benchmark.exr";
public const string Uncompressed = "Exr/Calliphora_uncompressed.exr";
public const string UncompressedFloatRgb = "Exr/rgb_float32_uncompressed.exr";
public const string UncompressedUintRgb = "Exr/rgb_uint32_uncompressed.exr";
diff --git a/tests/Images/Input/Exr/Calliphora_benchmark.exr b/tests/Images/Input/Exr/Calliphora_benchmark.exr
new file mode 100644
index 0000000000..da416b3c82
--- /dev/null
+++ b/tests/Images/Input/Exr/Calliphora_benchmark.exr
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:e3368860692927e709365f2d37b92411068e77a0f23624ff57af6089ec69f357
+size 2592888