Skip to content

Add support for OpenEXR image format#3096

Open
brianpopow wants to merge 73 commits intomainfrom
bp/openExr
Open

Add support for OpenEXR image format#3096
brianpopow wants to merge 73 commits intomainfrom
bp/openExr

Conversation

@brianpopow
Copy link
Copy Markdown
Collaborator

@brianpopow brianpopow commented Mar 29, 2026

Prerequisites

  • I have written a descriptive pull-request title
  • I have verified that there are no overlapping pull-requests open
  • I have verified that I am following the existing coding patterns and practice as demonstrated in the repository. These follow strict Stylecop rules 👮.
  • I have provided test coverage for my change (where applicable)

Description

This PR adds support for decoding and encoding of images in the OpenEXR format. OpenEXR is a HDR image format, therefore I have added two new pixel formats Rgb96 and Rgba128 which store each color channel as UInt32.

The specification can be found here: https://openexr.com/en/latest/index.html#openexr

# Conflicts:
#	src/ImageSharp/Configuration.cs
# Conflicts:
#	src/ImageSharp/Formats/Bmp/BmpConstants.cs
#	src/ImageSharp/Formats/Bmp/BmpDecoder.cs
#	src/ImageSharp/Formats/Bmp/BmpFormat.cs
# Conflicts:
#	src/ImageSharp/Configuration.cs
#	src/ImageSharp/Formats/Bmp/BmpArrayFileHeader.cs
#	src/ImageSharp/Formats/Bmp/BmpConstants.cs
#	src/ImageSharp/Formats/Bmp/BmpFormat.cs
#	src/ImageSharp/Formats/Bmp/BmpImageFormatDetector.cs
#	src/ImageSharp/Formats/ImageExtensions.Save.cs
#	src/ImageSharp/Formats/Pbm/BufferedReadStreamExtensions.cs
#	tests/ImageSharp.Tests/ConfigurationTests.cs
#	tests/ImageSharp.Tests/Formats/GeneralFormatTests.cs
# Conflicts:
#	src/ImageSharp/Configuration.cs
#	src/ImageSharp/Formats/Bmp/BmpArrayFileHeader.cs
#	src/ImageSharp/Formats/Bmp/BmpConstants.cs
#	src/ImageSharp/Formats/Bmp/BmpDecoder.cs
#	src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs
#	src/ImageSharp/Formats/Bmp/BmpFileHeader.cs
#	src/ImageSharp/Formats/Bmp/BmpFormat.cs
#	src/ImageSharp/Formats/Bmp/BmpImageFormatDetector.cs
#	src/ImageSharp/Formats/Bmp/BmpInfoHeader.cs
#	src/ImageSharp/Formats/ImageDecoderUtilities.cs
#	src/ImageSharp/Formats/ImageExtensions.Save.cs
#	src/ImageSharp/Formats/Pbm/BufferedReadStreamExtensions.cs
#	src/ImageSharp/Image.Decode.cs
#	tests/ImageSharp.Tests/ConfigurationTests.cs
#	tests/ImageSharp.Tests/Formats/GeneralFormatTests.cs
# Conflicts:
#	src/ImageSharp/Formats/Bmp/BmpFormat.cs
#	src/ImageSharp/Formats/ImageDecoderUtilities.cs
#	src/ImageSharp/Formats/ImageExtensions.Save.cs
#	src/ImageSharp/Formats/OpenExr/ExrDecoderCore.cs
# Conflicts:
#	src/ImageSharp/Configuration.cs
#	src/ImageSharp/Formats/ImageDecoderUtilities.cs
#	tests/ImageSharp.Tests/TestUtilities/TestEnvironment.Formats.cs
@brianpopow brianpopow added the formats:exr Any PR or issue related to OpenExr file format label Mar 29, 2026
@JimBobSquarePants
Copy link
Copy Markdown
Member

@brianpopow I'll review this asap.

/// <param name="component">The 32 bit component value.</param>
/// <returns>The <see cref="byte"/> value.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static byte From32BitTo8Bit(uint component) => (byte)(component * Scale32Bit);
Copy link
Copy Markdown
Member

@antonfirsov antonfirsov Mar 31, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perf is maybe not a primary concern at this point but I wonder if there is a cheaper way to correctly calculate this that avoids FP conversion? How does the reference implementation calculate this (if they care about decoding into lower res format)?

Comment on lines +174 to +183
public float ReadSingle(byte[] data)
{
int offset = 0;
int bytesToRead = 4;
while (bytesToRead > 0)
{
int bytesRead = this.Read(data, offset, bytesToRead);
if (bytesRead == 0)
{
throw new ImageFormatException("Not enough data to read a float value from the stream");
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

BufferedReadStream doesn't expose such methods and it is especially odd to throw ImageFormatException here. Wouldn't it be better to keep this in the codec implementation?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see now that there are similar methods in the codec implementation, but they don't seem to iterate. Does it make sense to do it here? Doesn't the BufferedReadStream logic make it a very rare case that we can't read such small buffers immediately? What is our standard way of handling this in other codecs?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don't have any extensions on BufferedReadStream like this elsewhere. There was one attempted in this PR but I've already said no to that because it is dangerous.

Personally, I would avoid them because we then start introducing endianess.

Copy link
Copy Markdown
Member

@antonfirsov antonfirsov left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Couple of places where the return value from stream.Read(...) is not checked. I may have missed some, it is worth a comprehensive check.


/// <inheritdoc/>
public override void Decompress(BufferedReadStream stream, uint compressedBytes, Span<byte> buffer)
=> stream.Read(buffer, 0, Math.Min(buffer.Length, (int)this.BytesPerBlock));
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it safe to omit checking the return value here?

Comment on lines +457 to +458
stream.ReadByte();
stream.ReadByte();
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Return value checks omitted here

}

// Last byte should be a null byte.
stream.ReadByte();
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also here

Comment on lines +648 to +653
byte pLinear = (byte)stream.ReadByte();

// Next 3 bytes are reserved bytes and not use.
stream.ReadByte();
stream.ReadByte();
stream.ReadByte();
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also here. Would it make sense to read 4 bytes to this.buffer in this case?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

formats:exr Any PR or issue related to OpenExr file format

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants