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