-
-
Notifications
You must be signed in to change notification settings - Fork 893
Add support for OpenEXR image format #3096
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
brianpopow
wants to merge
73
commits into
main
Choose a base branch
from
bp/openExr
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
73 commits
Select commit
Hold shift + click to select a range
9dda678
Parse open EXR header
brianpopow a27696a
Decode uncompressed EXR images with 16 bit pixel data for each channel
brianpopow d3993f7
Decode EXR images with 32 bit float pixel data
brianpopow aaf2143
Decode EXR images with unsigned int pixel data
brianpopow 799dafe
Encode EXR image with float pixel data
brianpopow 4cf95a5
Encode EXR image with HalfSingle pixel data
brianpopow 7e7472d
Encode EXR image with usigned int pixel data
brianpopow 652b385
Update tests for EXR images
brianpopow 70cfcc9
Add support for decoding EXR images with alpha channel
brianpopow 3279762
Add support for decoding exr with zips compression
brianpopow 85501f3
Add support for decoding RLE exr images
brianpopow 69c8cd8
Add support for ZIP compressed exr images
brianpopow fdc7b61
Add support for decoding gray exr images
brianpopow fd0c245
Refactor
brianpopow 88a06e8
Merge remote-tracking branch 'origin/main' into bp/openExr
brianpopow aabe561
Merge remote-tracking branch 'origin/main' into bp/openExr
brianpopow 440408b
Merge remote-tracking branch 'origin/main' into bp/openExr
brianpopow ea1aaa7
Fix build errors
brianpopow ce21f1a
Add exr decoder tests
brianpopow a9620ee
Add support for decoding b44 compressed exr files
brianpopow ec8163a
Fix warnings
brianpopow 28dcdf2
Add DecodeTiledFloatingPointPixelData
brianpopow 94cd5b0
Merge remote-tracking branch 'origin/main' into bp/openExr
brianpopow d0d5e86
Merge remote-tracking branch 'origin/bp/openExr' into bp/openExr
brianpopow 7748467
Merge branch 'main' into bp/openExr
brianpopow fa52ab7
Adjust exr decoder / encoder to ImageSharp changes
brianpopow 83dba58
Change exr name space to SixLabors.ImageSharp.Formats.Exr
brianpopow b78402d
Fix issue in ExrImageFormatDetector.cs returning wrong format
brianpopow faf87fb
Use MagickReference decoder for exr decoding tests
brianpopow eaea042
Fix issue assigning pixel value from HalfVector4
brianpopow 490d4a0
Fix mistake in Identify
brianpopow d121039
Add test for Identify
brianpopow 1b97d8c
Smaller test files
brianpopow e4c22d8
Add tests for decode RGB and gray exr files
brianpopow 1abfe69
Add test image for decoding exr with float piyel type
brianpopow fc40209
Test case for pixel type uint
brianpopow a0eaefb
Try fix decode unsigned int pixel data
brianpopow b79ba21
Fix not using offset when accessing decompressedPixelData
brianpopow 51f3ab2
Fix test image for float32: it is now actually uncompressed
brianpopow 3b7aa4d
Move constants to own folder
brianpopow 137ebbb
Add Exr compressor factory
brianpopow 3ff9d24
Refactor exr encoder for compression
brianpopow 0d51514
Use compressor when writing pixel row data with exr encoder
brianpopow 614a7f6
Re-arrange pixeldata and add predictor for zip compressed data
brianpopow 5ee52ef
Fix issue in re-ordering pixel value, does not work inplace
brianpopow f212af8
Cleanup Compressor/Decompressors: Remove not needed fields
brianpopow f9df911
Add support for writing exr with zip compression with 16 rows per block
brianpopow a3349ef
Add ExrEncoder tests
brianpopow 9e80da9
Try fix issue with undisposed buffers
brianpopow d409a3b
Use magick Reference decoder
brianpopow d5f4e23
Avoid code duplication for CalculateBytesPerRow and RowsPerBlock
brianpopow 7cc73d9
Fix issue decoding last row block with zip compression
brianpopow 6a92b0c
Implement missing Rgb96 methods
brianpopow 355d1bc
Implement missing methods for Rgba128
brianpopow c0e3d28
Use cancellation token, when decoding exr images
brianpopow 3e7ef68
Use cancellation token when encoding exr images
brianpopow d29fe89
Add benchmark for decoding exr image
brianpopow 9ead3eb
Update Magick.NET-Q16 to version 14.11.1 for fix to decoding zip comp…
brianpopow aa0af92
Use CompareToReferenceOutput for int pixel type
brianpopow 53230af
Use tolerant comparer for B44 compression
brianpopow a741e84
Use exact comparer as default for exr decoder tests
brianpopow 8c0c854
Set exr pixel type when decoding an image
brianpopow c8c3656
Implement GetPixelTypeInfo() for EXR
brianpopow b956634
Add tests for exr metadata
brianpopow a9f6dbc
Add compression to exr metadata
brianpopow 7939cc9
Throw InvalidImageContentException when requiered header fields are m…
brianpopow a1152c5
Add some more comments
brianpopow 65df7c2
Merge branch 'main' into bp/openExr
brianpopow 0c3ca3d
Fix failing bmp tests
brianpopow e6a9980
Change expectedDefaultConfigurationCount to 12
brianpopow 802e275
Use CompareToReferenceOutput for failing bmp tests on linux
brianpopow fccd5b5
Remove unused variables
brianpopow 84182c5
Remove more unused variables
brianpopow File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
30 changes: 30 additions & 0 deletions
30
src/ImageSharp/Formats/Exr/Compression/Compressors/NoneExrCompressor.cs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,30 @@ | ||
| // Copyright (c) Six Labors. | ||
| // Licensed under the Six Labors Split License. | ||
|
|
||
| using SixLabors.ImageSharp.Formats.Exr.Constants; | ||
| using SixLabors.ImageSharp.Memory; | ||
|
|
||
| namespace SixLabors.ImageSharp.Formats.Exr.Compression.Compressors; | ||
|
|
||
| internal class NoneExrCompressor : ExrBaseCompressor | ||
| { | ||
| public NoneExrCompressor(Stream output, MemoryAllocator allocator, uint bytesPerBlock, uint bytesPerRow) | ||
| : base(output, allocator, bytesPerBlock, bytesPerRow) | ||
| { | ||
| } | ||
|
|
||
| /// <inheritdoc/> | ||
| public override ExrCompression Method => ExrCompression.Zip; | ||
|
|
||
| /// <inheritdoc/> | ||
| public override uint CompressRowBlock(Span<byte> rows, int rowCount) | ||
| { | ||
| this.Output.Write(rows); | ||
| return (uint)rows.Length; | ||
| } | ||
|
|
||
| /// <inheritdoc/> | ||
| protected override void Dispose(bool disposing) | ||
| { | ||
| } | ||
| } |
77 changes: 77 additions & 0 deletions
77
src/ImageSharp/Formats/Exr/Compression/Compressors/ZipExrCompressor.cs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,77 @@ | ||
| // Copyright (c) Six Labors. | ||
| // Licensed under the Six Labors Split License. | ||
|
|
||
| using SixLabors.ImageSharp.Compression.Zlib; | ||
| using SixLabors.ImageSharp.Formats.Exr.Constants; | ||
| using SixLabors.ImageSharp.Memory; | ||
|
|
||
| namespace SixLabors.ImageSharp.Formats.Exr.Compression.Compressors; | ||
|
|
||
| internal class ZipExrCompressor : ExrBaseCompressor | ||
| { | ||
| private readonly DeflateCompressionLevel compressionLevel; | ||
|
|
||
| private readonly MemoryStream memoryStream; | ||
|
|
||
| private readonly System.Buffers.IMemoryOwner<byte> buffer; | ||
|
|
||
| public ZipExrCompressor(Stream output, MemoryAllocator allocator, uint bytesPerBlock, uint bytesPerRow, DeflateCompressionLevel compressionLevel) | ||
| : base(output, allocator, bytesPerBlock, bytesPerRow) | ||
| { | ||
| this.compressionLevel = compressionLevel; | ||
| this.buffer = allocator.Allocate<byte>((int)bytesPerBlock); | ||
| this.memoryStream = new(); | ||
| } | ||
|
|
||
| /// <inheritdoc/> | ||
| public override ExrCompression Method => ExrCompression.Zip; | ||
|
|
||
| /// <inheritdoc/> | ||
| public override uint CompressRowBlock(Span<byte> rows, int rowCount) | ||
| { | ||
| // Re-oder pixel values. | ||
| Span<byte> reordered = this.buffer.GetSpan()[..(int)(rowCount * this.BytesPerRow)]; | ||
| int n = reordered.Length; | ||
| int t1 = 0; | ||
| int t2 = (n + 1) >> 1; | ||
| for (int i = 0; i < n; i++) | ||
| { | ||
| bool isOdd = (i & 1) == 1; | ||
| reordered[isOdd ? t2++ : t1++] = rows[i]; | ||
| } | ||
|
|
||
| // Predictor. | ||
| Span<byte> predicted = reordered; | ||
| byte p = predicted[0]; | ||
| for (int i = 1; i < predicted.Length; i++) | ||
| { | ||
| int d = (predicted[i] - p + 128 + 256) & 255; | ||
| p = predicted[i]; | ||
| predicted[i] = (byte)d; | ||
| } | ||
|
|
||
| this.memoryStream.Seek(0, SeekOrigin.Begin); | ||
| using (ZlibDeflateStream stream = new(this.Allocator, this.memoryStream, this.compressionLevel)) | ||
| { | ||
| stream.Write(predicted); | ||
| stream.Flush(); | ||
| } | ||
|
|
||
| int size = (int)this.memoryStream.Position; | ||
| byte[] buffer = this.memoryStream.GetBuffer(); | ||
| this.Output.Write(buffer, 0, size); | ||
|
|
||
| // Reset memory stream for next pixel row. | ||
| this.memoryStream.Seek(0, SeekOrigin.Begin); | ||
| this.memoryStream.SetLength(0); | ||
|
|
||
| return (uint)size; | ||
| } | ||
|
|
||
| /// <inheritdoc/> | ||
| protected override void Dispose(bool disposing) | ||
| { | ||
| this.buffer.Dispose(); | ||
| this.memoryStream?.Dispose(); | ||
| } | ||
| } |
190 changes: 190 additions & 0 deletions
190
src/ImageSharp/Formats/Exr/Compression/Decompressors/B44ExrCompression.cs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,190 @@ | ||
| // Copyright (c) Six Labors. | ||
| // Licensed under the Six Labors Split License. | ||
|
|
||
| using System.Buffers; | ||
| using System.Runtime.InteropServices; | ||
| using SixLabors.ImageSharp.IO; | ||
| using SixLabors.ImageSharp.Memory; | ||
|
|
||
| namespace SixLabors.ImageSharp.Formats.Exr.Compression.Decompressors; | ||
|
|
||
| internal class B44ExrCompression : ExrBaseDecompressor | ||
| { | ||
| private readonly int width; | ||
|
|
||
| private readonly uint rowsPerBlock; | ||
|
|
||
| private readonly int channelCount; | ||
|
|
||
| private readonly byte[] scratch = new byte[14]; | ||
|
|
||
| private readonly ushort[] s = new ushort[16]; | ||
|
|
||
| private readonly IMemoryOwner<ushort> tmpBuffer; | ||
|
|
||
| public B44ExrCompression(MemoryAllocator allocator, uint bytesPerBlock, uint bytesPerRow, uint rowsPerBlock, int width, int channelCount) | ||
| : base(allocator, bytesPerBlock, bytesPerRow) | ||
| { | ||
| this.width = width; | ||
| this.rowsPerBlock = rowsPerBlock; | ||
| this.channelCount = channelCount; | ||
| this.tmpBuffer = allocator.Allocate<ushort>((int)(width * rowsPerBlock * channelCount)); | ||
| } | ||
|
|
||
| /// <inheritdoc/> | ||
| public override void Decompress(BufferedReadStream stream, uint compressedBytes, Span<byte> buffer) | ||
| { | ||
| Span<ushort> outputBuffer = MemoryMarshal.Cast<byte, ushort>(buffer); | ||
| Span<ushort> decompressed = this.tmpBuffer.GetSpan(); | ||
| int outputOffset = 0; | ||
| int bytesLeft = (int)compressedBytes; | ||
| for (int i = 0; i < this.channelCount && bytesLeft > 0; i++) | ||
| { | ||
| for (int y = 0; y < this.rowsPerBlock; y += 4) | ||
| { | ||
| Span<ushort> row0 = decompressed.Slice(outputOffset, this.width); | ||
| outputOffset += this.width; | ||
| Span<ushort> row1 = decompressed.Slice(outputOffset, this.width); | ||
| outputOffset += this.width; | ||
| Span<ushort> row2 = decompressed.Slice(outputOffset, this.width); | ||
| outputOffset += this.width; | ||
| Span<ushort> row3 = decompressed.Slice(outputOffset, this.width); | ||
| outputOffset += this.width; | ||
|
|
||
| int rowOffset = 0; | ||
| for (int x = 0; x < this.width && bytesLeft > 0; x += 4) | ||
| { | ||
| int bytesRead = stream.Read(this.scratch, 0, 3); | ||
| if (bytesRead == 0) | ||
| { | ||
| ExrThrowHelper.ThrowInvalidImageContentException("Could not read enough data from the stream"); | ||
| } | ||
|
|
||
| if (this.scratch[2] >= 13 << 2) | ||
| { | ||
| Unpack3(this.scratch, this.s); | ||
| bytesLeft -= 3; | ||
| } | ||
| else | ||
| { | ||
| bytesRead = stream.Read(this.scratch, 3, 11); | ||
| if (bytesRead == 0) | ||
| { | ||
| ExrThrowHelper.ThrowInvalidImageContentException("Could not read enough data from the stream"); | ||
| } | ||
|
|
||
| Unpack14(this.scratch, this.s); | ||
| bytesLeft -= 14; | ||
| } | ||
|
|
||
| int n = x + 3 < this.width ? 4 : this.width - x; | ||
| if (y + 3 < this.rowsPerBlock) | ||
| { | ||
| this.s.AsSpan(0, n).CopyTo(row0.Slice(rowOffset)); | ||
| this.s.AsSpan(4, n).CopyTo(row1.Slice(rowOffset)); | ||
| this.s.AsSpan(8, n).CopyTo(row2.Slice(rowOffset)); | ||
| this.s.AsSpan(12, n).CopyTo(row3.Slice(rowOffset)); | ||
| } | ||
| else | ||
| { | ||
| this.s.AsSpan(0, n).CopyTo(row0.Slice(rowOffset)); | ||
| if (y + 1 < this.rowsPerBlock) | ||
| { | ||
| this.s.AsSpan(4, n).CopyTo(row1.Slice(rowOffset)); | ||
| } | ||
|
|
||
| if (y + 2 < this.rowsPerBlock) | ||
| { | ||
| this.s.AsSpan(8, n).CopyTo(row2.Slice(rowOffset)); | ||
| } | ||
| } | ||
|
|
||
| rowOffset += 4; | ||
| } | ||
|
|
||
| if (bytesLeft <= 0) | ||
| { | ||
| break; | ||
| } | ||
| } | ||
| } | ||
|
|
||
| // Rearrange the decompressed data such that the data for each scan line form a contiguous block. | ||
| int offsetDecompressed = 0; | ||
| int offsetOutput = 0; | ||
| int blockSize = (int)(this.width * this.rowsPerBlock); | ||
| for (int y = 0; y < this.rowsPerBlock; y++) | ||
| { | ||
| for (int i = 0; i < this.channelCount; i++) | ||
| { | ||
| decompressed.Slice(offsetDecompressed + (i * blockSize), this.width).CopyTo(outputBuffer.Slice(offsetOutput)); | ||
| offsetOutput += this.width; | ||
| } | ||
|
|
||
| offsetDecompressed += this.width; | ||
| } | ||
| } | ||
|
|
||
| // Unpack a 14-byte block into 4 by 4 16-bit pixels. | ||
| private static void Unpack14(Span<byte> b, Span<ushort> s) | ||
| { | ||
| s[0] = (ushort)((b[0] << 8) | b[1]); | ||
|
|
||
| ushort shift = (ushort)(b[2] >> 2); | ||
| ushort bias = (ushort)(0x20u << shift); | ||
|
|
||
| s[4] = (ushort)(s[0] + ((((b[2] << 4) | (b[3] >> 4)) & 0x3fu) << shift) - bias); | ||
| s[8] = (ushort)(s[4] + ((((b[3] << 2) | (b[4] >> 6)) & 0x3fu) << shift) - bias); | ||
| s[12] = (ushort)(s[8] + ((b[4] & 0x3fu) << shift) - bias); | ||
|
|
||
| s[1] = (ushort)(s[0] + ((uint)(b[5] >> 2) << shift) - bias); | ||
| s[5] = (ushort)(s[4] + ((((b[5] << 4) | (b[6] >> 4)) & 0x3fu) << shift) - bias); | ||
| s[9] = (ushort)(s[8] + ((((b[6] << 2) | (b[7] >> 6)) & 0x3fu) << shift) - bias); | ||
| s[13] = (ushort)(s[12] + ((b[7] & 0x3fu) << shift) - bias); | ||
|
|
||
| s[2] = (ushort)(s[1] + ((uint)(b[8] >> 2) << shift) - bias); | ||
| s[6] = (ushort)(s[5] + ((((b[8] << 4) | (b[9] >> 4)) & 0x3fu) << shift) - bias); | ||
| s[10] = (ushort)(s[9] + ((((b[9] << 2) | (b[10] >> 6)) & 0x3fu) << shift) - bias); | ||
| s[14] = (ushort)(s[13] + ((b[10] & 0x3fu) << shift) - bias); | ||
|
|
||
| s[3] = (ushort)(s[2] + ((uint)(b[11] >> 2) << shift) - bias); | ||
| s[7] = (ushort)(s[6] + ((((b[11] << 4) | (b[12] >> 4)) & 0x3fu) << shift) - bias); | ||
| s[11] = (ushort)(s[10] + ((((b[12] << 2) | (b[13] >> 6)) & 0x3fu) << shift) - bias); | ||
| s[15] = (ushort)(s[14] + ((b[13] & 0x3fu) << shift) - bias); | ||
|
|
||
| for (int i = 0; i < 16; ++i) | ||
| { | ||
| if ((s[i] & 0x8000) != 0) | ||
| { | ||
| s[i] &= 0x7fff; | ||
| } | ||
| else | ||
| { | ||
| s[i] = (ushort)~s[i]; | ||
| } | ||
| } | ||
| } | ||
|
|
||
| // Unpack a 3-byte block into 4 by 4 identical 16-bit pixels. | ||
| private static void Unpack3(Span<byte> b, Span<ushort> s) | ||
| { | ||
| s[0] = (ushort)((b[0] << 8) | b[1]); | ||
|
|
||
| if ((s[0] & 0x8000) != 0) | ||
| { | ||
| s[0] &= 0x7fff; | ||
| } | ||
| else | ||
| { | ||
| s[0] = (ushort)~s[0]; | ||
| } | ||
|
|
||
| for (int i = 1; i < 16; ++i) | ||
| { | ||
| s[i] = s[0]; | ||
| } | ||
| } | ||
|
|
||
| /// <inheritdoc/> | ||
| protected override void Dispose(bool disposing) => this.tmpBuffer.Dispose(); | ||
| } |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
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)?