From fdc7b61b4b2a6d73fd8a7032ba10c88425a23c64 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Sun, 23 Jan 2022 19:43:38 +0100 Subject: [PATCH] Add support for decoding gray exr images --- .../Compressors/ZipExrCompression.cs | 5 + .../Formats/OpenExr/ExrConstants.cs | 2 + .../Formats/OpenExr/ExrDecoderCore.cs | 161 +++++++++++++----- .../Formats/OpenExr/ExrImageType.cs | 16 ++ 4 files changed, 143 insertions(+), 41 deletions(-) create mode 100644 src/ImageSharp/Formats/OpenExr/ExrImageType.cs diff --git a/src/ImageSharp/Formats/OpenExr/Compression/Compressors/ZipExrCompression.cs b/src/ImageSharp/Formats/OpenExr/Compression/Compressors/ZipExrCompression.cs index 508a5b4ac7..65a2ea3485 100644 --- a/src/ImageSharp/Formats/OpenExr/Compression/Compressors/ZipExrCompression.cs +++ b/src/ImageSharp/Formats/OpenExr/Compression/Compressors/ZipExrCompression.cs @@ -44,6 +44,11 @@ namespace SixLabors.ImageSharp.Formats.OpenExr.Compression.Compressors totalRead += bytesRead; } + if (totalRead == 0) + { + ExrThrowHelper.ThrowInvalidImageContentException("Could not read zip compressed image data!"); + } + Reconstruct(uncompressed, (uint)totalRead); Interleave(uncompressed, (uint)totalRead, buffer); } diff --git a/src/ImageSharp/Formats/OpenExr/ExrConstants.cs b/src/ImageSharp/Formats/OpenExr/ExrConstants.cs index 079adec7ed..ad6b1763d9 100644 --- a/src/ImageSharp/Formats/OpenExr/ExrConstants.cs +++ b/src/ImageSharp/Formats/OpenExr/ExrConstants.cs @@ -74,6 +74,8 @@ namespace SixLabors.ImageSharp.Formats.OpenExr public const string Blue = "B"; public const string Alpha = "A"; + + public const string Luminance = "Y"; } } } diff --git a/src/ImageSharp/Formats/OpenExr/ExrDecoderCore.cs b/src/ImageSharp/Formats/OpenExr/ExrDecoderCore.cs index aadbe425e5..a41ceb0766 100644 --- a/src/ImageSharp/Formats/OpenExr/ExrDecoderCore.cs +++ b/src/ImageSharp/Formats/OpenExr/ExrDecoderCore.cs @@ -70,18 +70,16 @@ namespace SixLabors.ImageSharp.Formats.OpenExr private ExrCompressionType Compression { get; set; } + private ExrImageType ImageType { get; set; } + /// public Image Decode(BufferedReadStream stream, CancellationToken cancellationToken) where TPixel : unmanaged, IPixel { this.ReadExrHeader(stream); - - if (this.Compression is not ExrCompressionType.None and not ExrCompressionType.Zips and not ExrCompressionType.Zip and not ExrCompressionType.RunLengthEncoded) - { - ExrThrowHelper.ThrowNotSupported($"Compression {this.Compression} is not yet supported"); - } - + this.IsSupportedCompression(); ExrPixelType pixelType = this.ValidateChannels(); + this.ReadImageType(); var image = new Image(this.Configuration, this.Width, this.Height, this.metadata); Buffer2D pixels = image.GetRootFramePixelBuffer(); @@ -197,6 +195,22 @@ namespace SixLabors.ImageSharp.Formats.OpenExr break; + case ExrConstants.ChannelNames.Luminance: + switch (channel.PixelType) + { + case ExrPixelType.Half: + offset += this.ReadPixelRowChannelHalfSingle(decompressedPixelData.Slice(offset), redPixelData); + break; + case ExrPixelType.Float: + offset += this.ReadPixelRowChannelSingle(decompressedPixelData.Slice(offset), redPixelData); + break; + } + + redPixelData.CopyTo(bluePixelData); + redPixelData.CopyTo(greenPixelData); + + break; + default: // Skip unknown channel. int channelDataSizeInBytes = channel.PixelType is ExrPixelType.Float or ExrPixelType.UnsignedInt ? 4 : 2; @@ -418,41 +432,9 @@ namespace SixLabors.ImageSharp.Formats.OpenExr } // Find pixel the type of any channel which is R, G, B or A. - ExrPixelType? pixelType = null; - for (int i = 0; i < this.Channels.Count; i++) - { - if (this.Channels[i].ChannelName.Equals(ExrConstants.ChannelNames.Blue) || - this.Channels[i].ChannelName.Equals(ExrConstants.ChannelNames.Green) || - this.Channels[i].ChannelName.Equals(ExrConstants.ChannelNames.Red) || - this.Channels[i].ChannelName.Equals(ExrConstants.ChannelNames.Alpha)) - { - pixelType = this.Channels[i].PixelType; - break; - } - } - - if (!pixelType.HasValue) - { - ExrThrowHelper.ThrowInvalidImageContentException("Pixel channel data is unknown! Only R, G, B and A are supported."); - } + ExrPixelType pixelType = this.FindPixelType(); - for (int i = 0; i < this.Channels.Count; i++) - { - // Ignore channels which we cannot interpret. - if (!(this.Channels[i].ChannelName.Equals(ExrConstants.ChannelNames.Blue) || - this.Channels[i].ChannelName.Equals(ExrConstants.ChannelNames.Green) || - this.Channels[i].ChannelName.Equals(ExrConstants.ChannelNames.Red))) - { - continue; - } - - if (pixelType != this.Channels[i].PixelType) - { - ExrThrowHelper.ThrowInvalidImageContentException("All channels are expected to have the same pixel data!"); - } - } - - return pixelType.Value; + return pixelType; } private ExrHeader ReadExrHeader(BufferedReadStream stream) @@ -640,6 +622,103 @@ namespace SixLabors.ImageSharp.Formats.OpenExr return str.ToString(); } + private ExrPixelType FindPixelType() + { + ExrPixelType? pixelType = null; + for (int i = 0; i < this.Channels.Count; i++) + { + if (this.Channels[i].ChannelName.Equals(ExrConstants.ChannelNames.Blue) || + this.Channels[i].ChannelName.Equals(ExrConstants.ChannelNames.Green) || + this.Channels[i].ChannelName.Equals(ExrConstants.ChannelNames.Red) || + this.Channels[i].ChannelName.Equals(ExrConstants.ChannelNames.Alpha) || + this.Channels[i].ChannelName.Equals(ExrConstants.ChannelNames.Luminance)) + { + if (!pixelType.HasValue) + { + pixelType = this.Channels[i].PixelType; + } + else + { + if (pixelType != this.Channels[i].PixelType) + { + ExrThrowHelper.ThrowNotSupported("Pixel channel data is expected to be the same for all channels."); + } + } + } + } + + if (!pixelType.HasValue) + { + ExrThrowHelper.ThrowNotSupported("Pixel channel data is unknown! Only R, G, B, A and Y are supported."); + } + + return pixelType.Value; + } + + private void IsSupportedCompression() + { + if (this.Compression is not ExrCompressionType.None and not ExrCompressionType.Zips and not ExrCompressionType.Zip and not ExrCompressionType.RunLengthEncoded) + { + ExrThrowHelper.ThrowNotSupported($"Compression {this.Compression} is not yet supported"); + } + } + + private void ReadImageType() + { + 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")) + { + hasAlphaChannel = true; + } + + if (channelInfo.ChannelName.Equals("R")) + { + hasRedChannel = true; + } + + if (channelInfo.ChannelName.Equals("G")) + { + hasGreenChannel = true; + } + + if (channelInfo.ChannelName.Equals("B")) + { + hasBlueChannel = true; + } + + if (channelInfo.ChannelName.Equals("Y")) + { + hasLuminance = true; + } + } + + if (hasRedChannel && hasGreenChannel && hasBlueChannel && hasAlphaChannel) + { + this.ImageType = ExrImageType.Rgba; + return; + } + + if (hasRedChannel && hasGreenChannel && hasBlueChannel) + { + this.ImageType = ExrImageType.Rgb; + return; + } + + if (hasLuminance && this.Channels.Count == 1) + { + this.ImageType = ExrImageType.Gray; + return; + } + + ExrThrowHelper.ThrowNotSupported("The image contains channels, which are not supported!"); + } + private bool HasAlpha() { foreach (ExrChannelInfo channelInfo in this.Channels) @@ -658,7 +737,7 @@ namespace SixLabors.ImageSharp.Formats.OpenExr uint bytesPerRow = 0; foreach (ExrChannelInfo channelInfo in this.Channels) { - if (channelInfo.ChannelName.Equals("A") || channelInfo.ChannelName.Equals("R") || channelInfo.ChannelName.Equals("G") || channelInfo.ChannelName.Equals("B")) + if (channelInfo.ChannelName.Equals("A") || channelInfo.ChannelName.Equals("R") || channelInfo.ChannelName.Equals("G") || channelInfo.ChannelName.Equals("B") || channelInfo.ChannelName.Equals("Y")) { if (channelInfo.PixelType == ExrPixelType.Half) { diff --git a/src/ImageSharp/Formats/OpenExr/ExrImageType.cs b/src/ImageSharp/Formats/OpenExr/ExrImageType.cs new file mode 100644 index 0000000000..e4e9c8a649 --- /dev/null +++ b/src/ImageSharp/Formats/OpenExr/ExrImageType.cs @@ -0,0 +1,16 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Formats.OpenExr +{ + internal enum ExrImageType + { + Unknown = 0, + + Rgb = 1, + + Rgba = 2, + + Gray = 3, + } +}