From c8c3656aaf767c6977cdd598738f2d3c418bcd41 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Sat, 28 Mar 2026 18:18:42 +0100 Subject: [PATCH] Implement GetPixelTypeInfo() for EXR --- .../Formats/Exr/Constants/ExrImageDataType.cs | 17 ++++- src/ImageSharp/Formats/Exr/ExrDecoderCore.cs | 60 ++++++++++++++++++ src/ImageSharp/Formats/Exr/ExrMetadata.cs | 63 ++++++++++++++++++- .../Formats/Exr/ExrDecoderTests.cs | 28 ++++++++- 4 files changed, 163 insertions(+), 5 deletions(-) diff --git a/src/ImageSharp/Formats/Exr/Constants/ExrImageDataType.cs b/src/ImageSharp/Formats/Exr/Constants/ExrImageDataType.cs index d63a8e2198..9453a7d9cd 100644 --- a/src/ImageSharp/Formats/Exr/Constants/ExrImageDataType.cs +++ b/src/ImageSharp/Formats/Exr/Constants/ExrImageDataType.cs @@ -3,13 +3,28 @@ namespace SixLabors.ImageSharp.Formats.Exr.Constants; -internal enum ExrImageDataType +/// +/// This enum represents the type of pixel data in the EXR image. +/// +public enum ExrImageDataType { + /// + /// The pixel data is unknown. + /// Unknown = 0, + /// + /// The pixel data has 3 channels: red, green and blue. + /// Rgb = 1, + /// + /// The pixel data has four channels: red, green, blue and a alpha channel. + /// Rgba = 2, + /// + /// There is only one channel with the luminance. + /// Gray = 3, } diff --git a/src/ImageSharp/Formats/Exr/ExrDecoderCore.cs b/src/ImageSharp/Formats/Exr/ExrDecoderCore.cs index 9cff42a4e0..1127b146c5 100644 --- a/src/ImageSharp/Formats/Exr/ExrDecoderCore.cs +++ b/src/ImageSharp/Formats/Exr/ExrDecoderCore.cs @@ -79,6 +79,11 @@ 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 pixel type. /// @@ -403,6 +408,59 @@ internal sealed class ExrDecoderCore : ImageDecoderCore return pixelType; } + private ExrImageDataType ReadImageDataType() + { + 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) + { + return ExrImageDataType.Rgba; + } + + if (hasRedChannel && hasGreenChannel && hasBlueChannel) + { + return ExrImageDataType.Rgb; + } + + if (hasLuminance && this.Channels.Count == 1) + { + return ExrImageDataType.Gray; + } + + return ExrImageDataType.Unknown; + } + private ExrHeaderAttributes ReadExrHeader(BufferedReadStream stream) { // Skip over the magick bytes, we already know its an EXR image. @@ -431,11 +489,13 @@ internal sealed class ExrDecoderCore : ImageDecoderCore this.Channels = this.HeaderAttributes.Channels; this.Compression = this.HeaderAttributes.Compression; this.PixelType = this.ValidateChannels(); + this.ImageDataType = this.ReadImageDataType(); this.metadata = new ImageMetadata(); this.exrMetadata = this.metadata.GetExrMetadata(); this.exrMetadata.PixelType = this.PixelType; + this.exrMetadata.ImageDataType = this.ImageDataType; return this.HeaderAttributes; } diff --git a/src/ImageSharp/Formats/Exr/ExrMetadata.cs b/src/ImageSharp/Formats/Exr/ExrMetadata.cs index b32da8f038..7abba6da89 100644 --- a/src/ImageSharp/Formats/Exr/ExrMetadata.cs +++ b/src/ImageSharp/Formats/Exr/ExrMetadata.cs @@ -30,12 +30,69 @@ public class ExrMetadata : IFormatMetadata /// public ExrPixelType PixelType { get; set; } = ExrPixelType.Half; + /// + /// Gets or sets the image data type, either RGB, RGBA or gray. + /// + public ExrImageDataType ImageDataType { get; set; } = ExrImageDataType.Unknown; + /// - public void AfterImageApply(Image destination, Matrix4x4 matrix) - where TPixel : unmanaged, IPixel => throw new NotImplementedException(); + public PixelTypeInfo GetPixelTypeInfo() + { + bool hasAlpha = this.ImageDataType is ExrImageDataType.Rgba; + + int bitsPerComponent = 32; + int bitsPerPixel = hasAlpha ? bitsPerComponent * 4 : bitsPerComponent * 3; + if (this.PixelType == ExrPixelType.Half) + { + bitsPerComponent = 16; + bitsPerPixel = hasAlpha ? bitsPerComponent * 4 : bitsPerComponent * 3; + } + + PixelAlphaRepresentation alpha = hasAlpha ? PixelAlphaRepresentation.Unassociated : PixelAlphaRepresentation.None; + PixelColorType color = PixelColorType.RGB; + + int componentsCount = 0; + int[] precision = []; + switch (this.ImageDataType) + { + case ExrImageDataType.Rgb: + color = PixelColorType.RGB; + componentsCount = 3; + precision = new int[componentsCount]; + precision[0] = bitsPerComponent; + precision[1] = bitsPerComponent; + precision[2] = bitsPerComponent; + break; + case ExrImageDataType.Rgba: + color = PixelColorType.RGB | PixelColorType.Alpha; + componentsCount = 4; + precision = new int[componentsCount]; + precision[0] = bitsPerComponent; + precision[1] = bitsPerComponent; + precision[2] = bitsPerComponent; + precision[3] = bitsPerComponent; + break; + case ExrImageDataType.Gray: + color = PixelColorType.Luminance; + componentsCount = 1; + precision = new int[componentsCount]; + precision[0] = bitsPerComponent; + break; + } + + PixelComponentInfo info = PixelComponentInfo.Create(componentsCount, bitsPerPixel, precision); + return new PixelTypeInfo(bitsPerPixel) + { + AlphaRepresentation = alpha, + ComponentInfo = info, + ColorType = color + }; + } + /// - public PixelTypeInfo GetPixelTypeInfo() => throw new NotImplementedException(); + public void AfterImageApply(Image destination, Matrix4x4 matrix) + where TPixel : unmanaged, IPixel => throw new NotImplementedException(); /// public FormatConnectingMetadata ToFormatConnectingMetadata() => throw new NotImplementedException(); diff --git a/tests/ImageSharp.Tests/Formats/Exr/ExrDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Exr/ExrDecoderTests.cs index 0077523ad2..0d6ea213a6 100644 --- a/tests/ImageSharp.Tests/Formats/Exr/ExrDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Exr/ExrDecoderTests.cs @@ -30,7 +30,20 @@ public class ExrDecoderTests [Theory] [InlineData(TestImages.Exr.Uncompressed)] - public void ExrDecoder_Identify_DetectsCorrectPixelType(string imagePath) + public void ExrDecoder_Identify_DetectsCorrectPixelType_Half(string imagePath) + { + TestFile testFile = TestFile.Create(imagePath); + using MemoryStream stream = new(testFile.Bytes, false); + ImageInfo imageInfo = Image.Identify(stream); + ExrMetadata exrMetaData = imageInfo.Metadata.GetExrMetadata(); + + Assert.NotNull(imageInfo); + Assert.Equal(ExrPixelType.Half, exrMetaData.PixelType); + } + + [Theory] + [InlineData(TestImages.Exr.UncompressedFloatRgb)] + public void ExrDecoder_Identify_DetectsCorrectPixelType_Float(string imagePath) { TestFile testFile = TestFile.Create(imagePath); using MemoryStream stream = new(testFile.Bytes, false); @@ -41,6 +54,19 @@ public class ExrDecoderTests Assert.Equal(ExrPixelType.Float, exrMetaData.PixelType); } + [Theory] + [InlineData(TestImages.Exr.UncompressedUintRgb)] + public void ExrDecoder_Identify_DetectsCorrectPixelType_Int(string imagePath) + { + TestFile testFile = TestFile.Create(imagePath); + using MemoryStream stream = new(testFile.Bytes, false); + ImageInfo imageInfo = Image.Identify(stream); + ExrMetadata exrMetaData = imageInfo.Metadata.GetExrMetadata(); + + Assert.NotNull(imageInfo); + Assert.Equal(ExrPixelType.UnsignedInt, exrMetaData.PixelType); + } + [Theory] [WithFile(TestImages.Exr.Uncompressed, PixelTypes.Rgba32)] public void ExrDecoder_CanDecode_Uncompressed_Rgba_ExrPixelType_Half(TestImageProvider provider)