From ea1aaa79c41c28d2c1bd886e7203e3467755ff39 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Sun, 18 Sep 2022 14:21:56 +0200 Subject: [PATCH] Fix build errors --- .../Compressors/NoneExrCompression.cs | 24 +- .../Compressors/RunLengthCompression.cs | 136 +- .../Compressors/ZipExrCompression.cs | 74 +- .../OpenExr/Compression/ExrBaseCompression.cs | 56 +- .../Compression/ExrBaseDecompressor.cs | 50 +- .../OpenExr/Compression/ExrCompressionType.cs | 113 +- .../Compression/ExrDecompressorFactory.cs | 33 +- .../Formats/OpenExr/ExrAttribute.cs | 32 +- src/ImageSharp/Formats/OpenExr/ExrBox2i.cs | 31 +- .../Formats/OpenExr/ExrChannelInfo.cs | 37 +- .../Formats/OpenExr/ExrConfigurationModule.cs | 25 +- .../Formats/OpenExr/ExrConstants.cs | 105 +- src/ImageSharp/Formats/OpenExr/ExrDecoder.cs | 91 +- .../Formats/OpenExr/ExrDecoderCore.cs | 1136 ++++++++--------- .../Formats/OpenExr/ExrDecoderOptions.cs | 13 + src/ImageSharp/Formats/OpenExr/ExrEncoder.cs | 48 +- .../Formats/OpenExr/ExrEncoderCore.cs | 633 +++++---- src/ImageSharp/Formats/OpenExr/ExrFormat.cs | 48 +- .../Formats/OpenExr/ExrHeaderAttributes.cs | 114 +- .../Formats/OpenExr/ExrImageDataType.cs | 17 +- .../Formats/OpenExr/ExrImageFormatDetector.cs | 38 +- .../Formats/OpenExr/ExrImageType.cs | 13 +- .../Formats/OpenExr/ExrLineOrder.cs | 15 +- src/ImageSharp/Formats/OpenExr/ExrMetadata.cs | 43 +- .../Formats/OpenExr/ExrPixelType.cs | 35 +- .../Formats/OpenExr/ExrThrowHelper.cs | 40 +- .../Formats/OpenExr/IExrDecoderOptions.cs | 12 - .../Formats/OpenExr/IExrEncoderOptions.cs | 19 +- .../Formats/OpenExr/MetadataExtensions.cs | 23 +- src/ImageSharp/IO/BufferedReadStream.cs | 26 + .../Formats/Exr/ImageExtensionsTest.cs | 163 +-- .../Formats/GeneralFormatTests.cs | 5 - 32 files changed, 1558 insertions(+), 1690 deletions(-) create mode 100644 src/ImageSharp/Formats/OpenExr/ExrDecoderOptions.cs delete mode 100644 src/ImageSharp/Formats/OpenExr/IExrDecoderOptions.cs diff --git a/src/ImageSharp/Formats/OpenExr/Compression/Compressors/NoneExrCompression.cs b/src/ImageSharp/Formats/OpenExr/Compression/Compressors/NoneExrCompression.cs index d4eaf67ebb..333c453ebd 100644 --- a/src/ImageSharp/Formats/OpenExr/Compression/Compressors/NoneExrCompression.cs +++ b/src/ImageSharp/Formats/OpenExr/Compression/Compressors/NoneExrCompression.cs @@ -1,24 +1,22 @@ // Copyright (c) Six Labors. -// Licensed under the Apache License, Version 2.0. +// Licensed under the Six Labors Split License. -using System; using SixLabors.ImageSharp.IO; using SixLabors.ImageSharp.Memory; -namespace SixLabors.ImageSharp.Formats.OpenExr.Compression.Compressors +namespace SixLabors.ImageSharp.Formats.OpenExr.Compression.Compressors; + +internal class NoneExrCompression : ExrBaseDecompressor { - internal class NoneExrCompression : ExrBaseDecompressor + public NoneExrCompression(MemoryAllocator allocator, uint uncompressedBytes) + : base(allocator, uncompressedBytes) { - public NoneExrCompression(MemoryAllocator allocator, uint uncompressedBytes) - : base(allocator, uncompressedBytes) - { - } + } - public override void Decompress(BufferedReadStream stream, uint compressedBytes, Span buffer) - => stream.Read(buffer, 0, Math.Min(buffer.Length, (int)this.UncompressedBytes)); + public override void Decompress(BufferedReadStream stream, uint compressedBytes, Span buffer) + => stream.Read(buffer, 0, Math.Min(buffer.Length, (int)this.UncompressedBytes)); - protected override void Dispose(bool disposing) - { - } + protected override void Dispose(bool disposing) + { } } diff --git a/src/ImageSharp/Formats/OpenExr/Compression/Compressors/RunLengthCompression.cs b/src/ImageSharp/Formats/OpenExr/Compression/Compressors/RunLengthCompression.cs index de1bf52328..08126c4de5 100644 --- a/src/ImageSharp/Formats/OpenExr/Compression/Compressors/RunLengthCompression.cs +++ b/src/ImageSharp/Formats/OpenExr/Compression/Compressors/RunLengthCompression.cs @@ -1,94 +1,92 @@ // Copyright (c) Six Labors. -// Licensed under the Apache License, Version 2.0. +// Licensed under the Six Labors Split License. -using System; using System.Buffers; using SixLabors.ImageSharp.IO; using SixLabors.ImageSharp.Memory; -namespace SixLabors.ImageSharp.Formats.OpenExr.Compression.Compressors +namespace SixLabors.ImageSharp.Formats.OpenExr.Compression.Compressors; + +internal class RunLengthCompression : ExrBaseDecompressor { - internal class RunLengthCompression : ExrBaseDecompressor - { - private readonly IMemoryOwner tmpBuffer; + private readonly IMemoryOwner tmpBuffer; - public RunLengthCompression(MemoryAllocator allocator, uint uncompressedBytes) - : base(allocator, uncompressedBytes) => this.tmpBuffer = allocator.Allocate((int)uncompressedBytes); + public RunLengthCompression(MemoryAllocator allocator, uint uncompressedBytes) + : base(allocator, uncompressedBytes) => this.tmpBuffer = allocator.Allocate((int)uncompressedBytes); - public override void Decompress(BufferedReadStream stream, uint compressedBytes, Span buffer) + public override void Decompress(BufferedReadStream stream, uint compressedBytes, Span buffer) + { + Span uncompressed = this.tmpBuffer.GetSpan(); + int maxLength = (int)this.UncompressedBytes; + int offset = 0; + while (compressedBytes > 0) { - Span uncompressed = this.tmpBuffer.GetSpan(); - int maxLength = (int)this.UncompressedBytes; - int offset = 0; - while (compressedBytes > 0) + byte nextByte = ReadNextByte(stream); + + sbyte input = (sbyte)nextByte; + if (input < 0) { - byte nextByte = ReadNextByte(stream); + int count = -input; + compressedBytes -= (uint)(count + 1); - sbyte input = (sbyte)nextByte; - if (input < 0) + if ((maxLength -= count) < 0) { - int count = -input; - compressedBytes -= (uint)(count + 1); - - if ((maxLength -= count) < 0) - { - return; - } - - // Check the input buffer is big enough to contain 'count' bytes of remaining data. - if (compressedBytes < 0) - { - return; - } - - for (int i = 0; i < count; i++) - { - uncompressed[offset + i] = ReadNextByte(stream); - } - - offset += count; + return; } - else + + // Check the input buffer is big enough to contain 'count' bytes of remaining data. + if (compressedBytes < 0) { - int count = input; - byte value = ReadNextByte(stream); - compressedBytes -= 2; - - if ((maxLength -= count + 1) < 0) - { - return; - } - - // Check the input buffer is big enough to contain byte to be duplicated. - if (compressedBytes < 0) - { - return; - } - - for (int i = 0; i < count + 1; i++) - { - uncompressed[offset + i] = value; - } - - offset += count + 1; + return; } - } - Reconstruct(uncompressed, this.UncompressedBytes); - Interleave(uncompressed, this.UncompressedBytes, buffer); - } + for (int i = 0; i < count; i++) + { + uncompressed[offset + i] = ReadNextByte(stream); + } - private static byte ReadNextByte(BufferedReadStream stream) - { - int nextByte = stream.ReadByte(); - if (nextByte == -1) + offset += count; + } + else { - ExrThrowHelper.ThrowInvalidImageContentException("Not enough data to decompress RLE image!"); + int count = input; + byte value = ReadNextByte(stream); + compressedBytes -= 2; + + if ((maxLength -= count + 1) < 0) + { + return; + } + + // Check the input buffer is big enough to contain byte to be duplicated. + if (compressedBytes < 0) + { + return; + } + + for (int i = 0; i < count + 1; i++) + { + uncompressed[offset + i] = value; + } + + offset += count + 1; } + } + + Reconstruct(uncompressed, this.UncompressedBytes); + Interleave(uncompressed, this.UncompressedBytes, buffer); + } - return (byte)nextByte; + private static byte ReadNextByte(BufferedReadStream stream) + { + int nextByte = stream.ReadByte(); + if (nextByte == -1) + { + ExrThrowHelper.ThrowInvalidImageContentException("Not enough data to decompress RLE image!"); } - protected override void Dispose(bool disposing) => this.tmpBuffer.Dispose(); + return (byte)nextByte; } + + protected override void Dispose(bool disposing) => this.tmpBuffer.Dispose(); } diff --git a/src/ImageSharp/Formats/OpenExr/Compression/Compressors/ZipExrCompression.cs b/src/ImageSharp/Formats/OpenExr/Compression/Compressors/ZipExrCompression.cs index 65a2ea3485..d90f684e64 100644 --- a/src/ImageSharp/Formats/OpenExr/Compression/Compressors/ZipExrCompression.cs +++ b/src/ImageSharp/Formats/OpenExr/Compression/Compressors/ZipExrCompression.cs @@ -1,58 +1,56 @@ // Copyright (c) Six Labors. -// Licensed under the Apache License, Version 2.0. +// Licensed under the Six Labors Split License. -using System; using System.Buffers; using System.IO.Compression; using SixLabors.ImageSharp.Compression.Zlib; using SixLabors.ImageSharp.IO; using SixLabors.ImageSharp.Memory; -namespace SixLabors.ImageSharp.Formats.OpenExr.Compression.Compressors +namespace SixLabors.ImageSharp.Formats.OpenExr.Compression.Compressors; + +internal class ZipExrCompression : ExrBaseDecompressor { - internal class ZipExrCompression : ExrBaseDecompressor - { - private readonly IMemoryOwner tmpBuffer; + private readonly IMemoryOwner tmpBuffer; - public ZipExrCompression(MemoryAllocator allocator, uint uncompressedBytes) - : base(allocator, uncompressedBytes) => this.tmpBuffer = allocator.Allocate((int)uncompressedBytes); + public ZipExrCompression(MemoryAllocator allocator, uint uncompressedBytes) + : base(allocator, uncompressedBytes) => this.tmpBuffer = allocator.Allocate((int)uncompressedBytes); - public override void Decompress(BufferedReadStream stream, uint compressedBytes, Span buffer) + public override void Decompress(BufferedReadStream stream, uint compressedBytes, Span buffer) + { + Span uncompressed = this.tmpBuffer.GetSpan(); + + long pos = stream.Position; + using ZlibInflateStream deframeStream = new( + stream, + () => + { + int left = (int)(compressedBytes - (stream.Position - pos)); + return left > 0 ? left : 0; + }); + deframeStream.AllocateNewBytes((int)this.UncompressedBytes, true); + DeflateStream dataStream = deframeStream.CompressedStream; + + int totalRead = 0; + while (totalRead < buffer.Length) { - Span uncompressed = this.tmpBuffer.GetSpan(); - - long pos = stream.Position; - using var deframeStream = new ZlibInflateStream( - stream, - () => - { - int left = (int)(compressedBytes - (stream.Position - pos)); - return left > 0 ? left : 0; - }); - deframeStream.AllocateNewBytes((int)this.UncompressedBytes, true); - DeflateStream dataStream = deframeStream.CompressedStream; - - int totalRead = 0; - while (totalRead < buffer.Length) + int bytesRead = dataStream.Read(uncompressed, totalRead, buffer.Length - totalRead); + if (bytesRead <= 0) { - int bytesRead = dataStream.Read(uncompressed, totalRead, buffer.Length - totalRead); - if (bytesRead <= 0) - { - break; - } - - totalRead += bytesRead; + break; } - if (totalRead == 0) - { - ExrThrowHelper.ThrowInvalidImageContentException("Could not read zip compressed image data!"); - } + totalRead += bytesRead; + } - Reconstruct(uncompressed, (uint)totalRead); - Interleave(uncompressed, (uint)totalRead, buffer); + if (totalRead == 0) + { + ExrThrowHelper.ThrowInvalidImageContentException("Could not read zip compressed image data!"); } - protected override void Dispose(bool disposing) => this.tmpBuffer.Dispose(); + Reconstruct(uncompressed, (uint)totalRead); + Interleave(uncompressed, (uint)totalRead, buffer); } + + protected override void Dispose(bool disposing) => this.tmpBuffer.Dispose(); } diff --git a/src/ImageSharp/Formats/OpenExr/Compression/ExrBaseCompression.cs b/src/ImageSharp/Formats/OpenExr/Compression/ExrBaseCompression.cs index 7faf4e77d6..4fd5778cb6 100644 --- a/src/ImageSharp/Formats/OpenExr/Compression/ExrBaseCompression.cs +++ b/src/ImageSharp/Formats/OpenExr/Compression/ExrBaseCompression.cs @@ -1,43 +1,41 @@ // Copyright (c) Six Labors. -// Licensed under the Apache License, Version 2.0. +// Licensed under the Six Labors Split License. -using System; using SixLabors.ImageSharp.Memory; -namespace SixLabors.ImageSharp.Formats.OpenExr.Compression +namespace SixLabors.ImageSharp.Formats.OpenExr.Compression; + +internal abstract class ExrBaseCompression : IDisposable { - internal abstract class ExrBaseCompression : IDisposable - { - private bool isDisposed; + private bool isDisposed; - protected ExrBaseCompression(MemoryAllocator allocator, uint bytePerRow) - { - this.Allocator = allocator; - this.UncompressedBytes = bytePerRow; - } + protected ExrBaseCompression(MemoryAllocator allocator, uint bytePerRow) + { + this.Allocator = allocator; + this.UncompressedBytes = bytePerRow; + } - /// - /// Gets the memory allocator. - /// - protected MemoryAllocator Allocator { get; } + /// + /// Gets the memory allocator. + /// + protected MemoryAllocator Allocator { get; } - /// - /// Gets the uncompressed bytes. - /// - public uint UncompressedBytes { get; } + /// + /// Gets the uncompressed bytes. + /// + public uint UncompressedBytes { get; } - /// - public void Dispose() + /// + public void Dispose() + { + if (this.isDisposed) { - if (this.isDisposed) - { - return; - } - - this.isDisposed = true; - this.Dispose(true); + return; } - protected abstract void Dispose(bool disposing); + this.isDisposed = true; + this.Dispose(true); } + + protected abstract void Dispose(bool disposing); } diff --git a/src/ImageSharp/Formats/OpenExr/Compression/ExrBaseDecompressor.cs b/src/ImageSharp/Formats/OpenExr/Compression/ExrBaseDecompressor.cs index f8b811e502..2af814eb12 100644 --- a/src/ImageSharp/Formats/OpenExr/Compression/ExrBaseDecompressor.cs +++ b/src/ImageSharp/Formats/OpenExr/Compression/ExrBaseDecompressor.cs @@ -1,42 +1,40 @@ // Copyright (c) Six Labors. -// Licensed under the Apache License, Version 2.0. +// Licensed under the Six Labors Split License. -using System; using SixLabors.ImageSharp.IO; using SixLabors.ImageSharp.Memory; -namespace SixLabors.ImageSharp.Formats.OpenExr.Compression +namespace SixLabors.ImageSharp.Formats.OpenExr.Compression; + +internal abstract class ExrBaseDecompressor : ExrBaseCompression { - internal abstract class ExrBaseDecompressor : ExrBaseCompression + protected ExrBaseDecompressor(MemoryAllocator allocator, uint bytePerRow) + : base(allocator, bytePerRow) { - protected ExrBaseDecompressor(MemoryAllocator allocator, uint bytePerRow) - : base(allocator, bytePerRow) - { - } + } - public abstract void Decompress(BufferedReadStream stream, uint compressedBytes, Span buffer); + public abstract void Decompress(BufferedReadStream stream, uint compressedBytes, Span buffer); - protected static void Reconstruct(Span buffer, uint unCompressedBytes) + protected static void Reconstruct(Span buffer, uint unCompressedBytes) + { + int offset = 0; + for (int i = 0; i < unCompressedBytes - 1; i++) { - int offset = 0; - for (int i = 0; i < unCompressedBytes - 1; i++) - { - byte d = (byte)(buffer[offset] + (buffer[offset + 1] - 128)); - buffer[offset + 1] = d; - offset++; - } + byte d = (byte)(buffer[offset] + (buffer[offset + 1] - 128)); + buffer[offset + 1] = d; + offset++; } + } - protected static void Interleave(Span source, uint unCompressedBytes, Span output) + protected static void Interleave(Span source, uint unCompressedBytes, Span output) + { + int sourceOffset = 0; + int offset0 = 0; + int offset1 = (int)((unCompressedBytes + 1) / 2); + while (sourceOffset < unCompressedBytes) { - int sourceOffset = 0; - int offset0 = 0; - int offset1 = (int)((unCompressedBytes + 1) / 2); - while (sourceOffset < unCompressedBytes) - { - output[sourceOffset++] = source[offset0++]; - output[sourceOffset++] = source[offset1++]; - } + output[sourceOffset++] = source[offset0++]; + output[sourceOffset++] = source[offset1++]; } } } diff --git a/src/ImageSharp/Formats/OpenExr/Compression/ExrCompressionType.cs b/src/ImageSharp/Formats/OpenExr/Compression/ExrCompressionType.cs index 3b47ed6ecb..53fc2e2dc9 100644 --- a/src/ImageSharp/Formats/OpenExr/Compression/ExrCompressionType.cs +++ b/src/ImageSharp/Formats/OpenExr/Compression/ExrCompressionType.cs @@ -1,61 +1,60 @@ // Copyright (c) Six Labors. -// Licensed under the Apache License, Version 2.0. +// Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Formats.OpenExr.Compression +namespace SixLabors.ImageSharp.Formats.OpenExr.Compression; + +internal enum ExrCompressionType { - internal enum ExrCompressionType - { - /// - /// Pixel data is not compressed. - /// - None = 0, - - /// - /// Differences between horizontally adjacent pixels are run-length encoded. - /// This method is fast, and works well for images with large flat areas, but for photographic images, - /// the compressed file size is usually between 60 and 75 percent of the uncompressed size. - /// Compression is lossless. - /// - RunLengthEncoded = 1, - - /// - /// Uses the open source zlib library for compression. Unlike ZIP compression, this operates one scan line at a time. - /// Compression is lossless. - /// - Zips = 2, - - /// - /// Differences between horizontally adjacent pixels are compressed using the open source zlib library. - /// Unlike ZIPS compression, this operates in in blocks of 16 scan lines. - /// Compression is lossless. - /// - Zip = 3, - - /// - /// A wavelet transform is applied to the pixel data, and the result is Huffman-encoded. - /// Compression is lossless. - /// - Piz = 4, - - /// - /// After reducing 32-bit floating-point data to 24 bits by rounding, differences between horizontally adjacent pixels are compressed with zlib, - /// similar to ZIP. PXR24 compression preserves image channels of type HALF and UINT exactly, but the relative error of FLOAT data increases to about 3×10-5. - /// Compression is lossy. - /// - Pxr24 = 5, - - /// - /// Channels of type HALF are split into blocks of four by four pixels or 32 bytes. Each block is then packed into 14 bytes, - /// reducing the data to 44 percent of their uncompressed size. - /// Compression is lossy. - /// - B44 = 6, - - /// - /// Like B44, except for blocks of four by four pixels where all pixels have the same value, which are packed into 3 instead of 14 bytes. - /// For images with large uniform areas, B44A produces smaller files than B44 compression. - /// Compression is lossy. - /// - B44A = 7 - } + /// + /// Pixel data is not compressed. + /// + None = 0, + + /// + /// Differences between horizontally adjacent pixels are run-length encoded. + /// This method is fast, and works well for images with large flat areas, but for photographic images, + /// the compressed file size is usually between 60 and 75 percent of the uncompressed size. + /// Compression is lossless. + /// + RunLengthEncoded = 1, + + /// + /// Uses the open source zlib library for compression. Unlike ZIP compression, this operates one scan line at a time. + /// Compression is lossless. + /// + Zips = 2, + + /// + /// Differences between horizontally adjacent pixels are compressed using the open source zlib library. + /// Unlike ZIPS compression, this operates in in blocks of 16 scan lines. + /// Compression is lossless. + /// + Zip = 3, + + /// + /// A wavelet transform is applied to the pixel data, and the result is Huffman-encoded. + /// Compression is lossless. + /// + Piz = 4, + + /// + /// After reducing 32-bit floating-point data to 24 bits by rounding, differences between horizontally adjacent pixels are compressed with zlib, + /// similar to ZIP. PXR24 compression preserves image channels of type HALF and UINT exactly, but the relative error of FLOAT data increases to about 3×10-5. + /// Compression is lossy. + /// + Pxr24 = 5, + + /// + /// Channels of type HALF are split into blocks of four by four pixels or 32 bytes. Each block is then packed into 14 bytes, + /// reducing the data to 44 percent of their uncompressed size. + /// Compression is lossy. + /// + B44 = 6, + + /// + /// Like B44, except for blocks of four by four pixels where all pixels have the same value, which are packed into 3 instead of 14 bytes. + /// For images with large uniform areas, B44A produces smaller files than B44 compression. + /// Compression is lossy. + /// + B44A = 7 } diff --git a/src/ImageSharp/Formats/OpenExr/Compression/ExrDecompressorFactory.cs b/src/ImageSharp/Formats/OpenExr/Compression/ExrDecompressorFactory.cs index 8ba255d41c..2d01efd87b 100644 --- a/src/ImageSharp/Formats/OpenExr/Compression/ExrDecompressorFactory.cs +++ b/src/ImageSharp/Formats/OpenExr/Compression/ExrDecompressorFactory.cs @@ -1,28 +1,27 @@ // Copyright (c) Six Labors. -// Licensed under the Apache License, Version 2.0. +// Licensed under the Six Labors Split License. using SixLabors.ImageSharp.Formats.OpenExr.Compression.Compressors; using SixLabors.ImageSharp.Memory; -namespace SixLabors.ImageSharp.Formats.OpenExr.Compression +namespace SixLabors.ImageSharp.Formats.OpenExr.Compression; + +internal static class ExrDecompressorFactory { - internal static class ExrDecompressorFactory + public static ExrBaseDecompressor Create(ExrCompressionType method, MemoryAllocator memoryAllocator, uint uncompressedBytes) { - public static ExrBaseDecompressor Create(ExrCompressionType method, MemoryAllocator memoryAllocator, uint uncompressedBytes) + switch (method) { - switch (method) - { - case ExrCompressionType.None: - return new NoneExrCompression(memoryAllocator, uncompressedBytes); - case ExrCompressionType.Zips: - return new ZipExrCompression(memoryAllocator, uncompressedBytes); - case ExrCompressionType.Zip: - return new ZipExrCompression(memoryAllocator, uncompressedBytes); - case ExrCompressionType.RunLengthEncoded: - return new RunLengthCompression(memoryAllocator, uncompressedBytes); - default: - throw ExrThrowHelper.NotSupportedDecompressor(nameof(method)); - } + case ExrCompressionType.None: + return new NoneExrCompression(memoryAllocator, uncompressedBytes); + case ExrCompressionType.Zips: + return new ZipExrCompression(memoryAllocator, uncompressedBytes); + case ExrCompressionType.Zip: + return new ZipExrCompression(memoryAllocator, uncompressedBytes); + case ExrCompressionType.RunLengthEncoded: + return new RunLengthCompression(memoryAllocator, uncompressedBytes); + default: + throw ExrThrowHelper.NotSupportedDecompressor(nameof(method)); } } } diff --git a/src/ImageSharp/Formats/OpenExr/ExrAttribute.cs b/src/ImageSharp/Formats/OpenExr/ExrAttribute.cs index 66423f50dc..ad5a8290dc 100644 --- a/src/ImageSharp/Formats/OpenExr/ExrAttribute.cs +++ b/src/ImageSharp/Formats/OpenExr/ExrAttribute.cs @@ -1,26 +1,26 @@ // Copyright (c) Six Labors. -// Licensed under the Apache License, Version 2.0. +// Licensed under the Six Labors Split License.. using System.Diagnostics; -namespace SixLabors.ImageSharp.Formats.OpenExr +namespace SixLabors.ImageSharp.Formats.OpenExr; + +[DebuggerDisplay("Name: {Name}, Type: {Type}, Length: {Length}")] +internal class ExrAttribute { - [DebuggerDisplay("Name: {Name}, Type: {Type}, Length: {Length}")] - internal class ExrAttribute - { - public static readonly ExrAttribute EmptyAttribute = new(string.Empty, string.Empty, 0); + public static readonly ExrAttribute EmptyAttribute = new(string.Empty, string.Empty, 0); - public ExrAttribute(string name, string type, int length) - { - this.Name = name; - this.Type = type; - this.Length = length; - } + public ExrAttribute(string name, string type, int length) + { + this.Name = name; + this.Type = type; + this.Length = length; + } - public string Name { get; } + public string Name { get; } - public string Type { get; } + public string Type { get; } - public int Length { get; } - } + public int Length { get; } } + diff --git a/src/ImageSharp/Formats/OpenExr/ExrBox2i.cs b/src/ImageSharp/Formats/OpenExr/ExrBox2i.cs index 89424b4259..1f5b0c0df8 100644 --- a/src/ImageSharp/Formats/OpenExr/ExrBox2i.cs +++ b/src/ImageSharp/Formats/OpenExr/ExrBox2i.cs @@ -1,27 +1,26 @@ // Copyright (c) Six Labors. -// Licensed under the Apache License, Version 2.0. +// Licensed under the Six Labors Split License. using System.Diagnostics; -namespace SixLabors.ImageSharp.Formats.OpenExr +namespace SixLabors.ImageSharp.Formats.OpenExr; + +[DebuggerDisplay("xMin: {XMin}, yMin: {YMin}, xMax: {XMax}, yMax: {YMax}")] +internal struct ExrBox2i { - [DebuggerDisplay("xMin: {XMin}, yMin: {YMin}, xMax: {XMax}, yMax: {YMax}")] - internal struct ExrBox2i + public ExrBox2i(int xMin, int yMin, int xMax, int yMax) { - public ExrBox2i(int xMin, int yMin, int xMax, int yMax) - { - this.XMin = xMin; - this.YMin = yMin; - this.XMax = xMax; - this.YMax = yMax; - } + this.XMin = xMin; + this.YMin = yMin; + this.XMax = xMax; + this.YMax = yMax; + } - public int XMin { get; } + public int XMin { get; } - public int YMin { get; } + public int YMin { get; } - public int XMax { get; } + public int XMax { get; } - public int YMax { get; } - } + public int YMax { get; } } diff --git a/src/ImageSharp/Formats/OpenExr/ExrChannelInfo.cs b/src/ImageSharp/Formats/OpenExr/ExrChannelInfo.cs index 28a014853d..d39bc08a5a 100644 --- a/src/ImageSharp/Formats/OpenExr/ExrChannelInfo.cs +++ b/src/ImageSharp/Formats/OpenExr/ExrChannelInfo.cs @@ -1,32 +1,31 @@ // Copyright (c) Six Labors. -// Licensed under the Apache License, Version 2.0. +// Licensed under the Six Labors Split License. using System.Diagnostics; using System.Runtime.InteropServices; -namespace SixLabors.ImageSharp.Formats.OpenExr +namespace SixLabors.ImageSharp.Formats.OpenExr; + +[DebuggerDisplay("Name: {ChannelName}, PixelType: {PixelType}")] +[StructLayout(LayoutKind.Sequential, Pack = 1)] +internal readonly struct ExrChannelInfo { - [DebuggerDisplay("Name: {ChannelName}, PixelType: {PixelType}")] - [StructLayout(LayoutKind.Sequential, Pack = 1)] - internal readonly struct ExrChannelInfo + public ExrChannelInfo(string channelName, ExrPixelType pixelType, byte pLinear, int xSampling, int ySampling) { - public ExrChannelInfo(string channelName, ExrPixelType pixelType, byte pLinear, int xSampling, int ySampling) - { - this.ChannelName = channelName; - this.PixelType = pixelType; - this.PLinear = pLinear; - this.XSampling = xSampling; - this.YSampling = ySampling; - } + this.ChannelName = channelName; + this.PixelType = pixelType; + this.PLinear = pLinear; + this.XSampling = xSampling; + this.YSampling = ySampling; + } - public string ChannelName { get; } + public string ChannelName { get; } - public ExrPixelType PixelType { get; } + public ExrPixelType PixelType { get; } - public byte PLinear { get; } + public byte PLinear { get; } - public int XSampling { get; } + public int XSampling { get; } - public int YSampling { get; } - } + public int YSampling { get; } } diff --git a/src/ImageSharp/Formats/OpenExr/ExrConfigurationModule.cs b/src/ImageSharp/Formats/OpenExr/ExrConfigurationModule.cs index d5a03cd5dd..0b0058805d 100644 --- a/src/ImageSharp/Formats/OpenExr/ExrConfigurationModule.cs +++ b/src/ImageSharp/Formats/OpenExr/ExrConfigurationModule.cs @@ -1,19 +1,18 @@ // Copyright (c) Six Labors. -// Licensed under the Apache License, Version 2.0. +// Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Formats.OpenExr +namespace SixLabors.ImageSharp.Formats.OpenExr; + +/// +/// Registers the image encoders, decoders and mime type detectors for the OpenExr format. +/// +public sealed class ExrConfigurationModule : IConfigurationModule { - /// - /// Registers the image encoders, decoders and mime type detectors for the OpenExr format. - /// - public sealed class ExrConfigurationModule : IConfigurationModule + /// + public void Configure(Configuration configuration) { - /// - public void Configure(Configuration configuration) - { - configuration.ImageFormatsManager.SetEncoder(ExrFormat.Instance, new ExrEncoder()); - configuration.ImageFormatsManager.SetDecoder(ExrFormat.Instance, new ExrDecoder()); - configuration.ImageFormatsManager.AddImageFormatDetector(new ExrImageFormatDetector()); - } + configuration.ImageFormatsManager.SetEncoder(ExrFormat.Instance, new ExrEncoder()); + configuration.ImageFormatsManager.SetDecoder(ExrFormat.Instance, new ExrDecoder()); + configuration.ImageFormatsManager.AddImageFormatDetector(new ExrImageFormatDetector()); } } diff --git a/src/ImageSharp/Formats/OpenExr/ExrConstants.cs b/src/ImageSharp/Formats/OpenExr/ExrConstants.cs index 29adb7b887..42ccf27fbd 100644 --- a/src/ImageSharp/Formats/OpenExr/ExrConstants.cs +++ b/src/ImageSharp/Formats/OpenExr/ExrConstants.cs @@ -1,85 +1,82 @@ // Copyright (c) Six Labors. -// Licensed under the Apache License, Version 2.0. +// Licensed under the Six Labors Split License. -using System.Collections.Generic; +namespace SixLabors.ImageSharp.Formats.OpenExr; -namespace SixLabors.ImageSharp.Formats.OpenExr +/// +/// Defines constants relating to OpenExr images. +/// +internal static class ExrConstants { /// - /// Defines constants relating to OpenExr images. + /// The list of mimetypes that equate to a OpenExr image. /// - internal static class ExrConstants - { - /// - /// The list of mimetypes that equate to a OpenExr image. - /// - public static readonly IEnumerable MimeTypes = new[] { "image/x-exr" }; + public static readonly IEnumerable MimeTypes = new[] { "image/x-exr" }; - /// - /// The list of file extensions that equate to a OpenExr image. - /// - public static readonly IEnumerable FileExtensions = new[] { "exr" }; + /// + /// The list of file extensions that equate to a OpenExr image. + /// + public static readonly IEnumerable FileExtensions = new[] { "exr" }; - /// - /// The magick bytes identifying an OpenExr image. - /// - public static readonly int MagickBytes = 20000630; + /// + /// The magick bytes identifying an OpenExr image. + /// + public static readonly int MagickBytes = 20000630; - /// - /// EXR attribute names. - /// - internal static class AttributeNames - { - public const string Channels = "channels"; + /// + /// EXR attribute names. + /// + internal static class AttributeNames + { + public const string Channels = "channels"; - public const string Compression = "compression"; + public const string Compression = "compression"; - public const string DataWindow = "dataWindow"; + public const string DataWindow = "dataWindow"; - public const string DisplayWindow = "displayWindow"; + public const string DisplayWindow = "displayWindow"; - public const string LineOrder = "lineOrder"; + public const string LineOrder = "lineOrder"; - public const string PixelAspectRatio = "pixelAspectRatio"; + public const string PixelAspectRatio = "pixelAspectRatio"; - public const string ScreenWindowCenter = "screenWindowCenter"; + public const string ScreenWindowCenter = "screenWindowCenter"; - public const string ScreenWindowWidth = "screenWindowWidth"; + public const string ScreenWindowWidth = "screenWindowWidth"; - public const string Tiles = "tiles"; + public const string Tiles = "tiles"; - public const string ChunkCount = "chunkCount"; - } + public const string ChunkCount = "chunkCount"; + } - /// - /// EXR attribute types. - /// - internal static class AttibuteTypes - { - public const string ChannelList = "chlist"; + /// + /// EXR attribute types. + /// + internal static class AttibuteTypes + { + public const string ChannelList = "chlist"; - public const string Compression = "compression"; + public const string Compression = "compression"; - public const string Float = "float"; + public const string Float = "float"; - public const string LineOrder = "lineOrder"; + public const string LineOrder = "lineOrder"; - public const string TwoFloat = "v2f"; + public const string TwoFloat = "v2f"; - public const string BoxInt = "box2i"; - } + public const string BoxInt = "box2i"; + } - internal static class ChannelNames - { - public const string Red = "R"; + internal static class ChannelNames + { + public const string Red = "R"; - public const string Green = "G"; + public const string Green = "G"; - public const string Blue = "B"; + public const string Blue = "B"; - public const string Alpha = "A"; + public const string Alpha = "A"; - public const string Luminance = "Y"; - } + public const string Luminance = "Y"; } } diff --git a/src/ImageSharp/Formats/OpenExr/ExrDecoder.cs b/src/ImageSharp/Formats/OpenExr/ExrDecoder.cs index 0c645d71e1..dcbe80c277 100644 --- a/src/ImageSharp/Formats/OpenExr/ExrDecoder.cs +++ b/src/ImageSharp/Formats/OpenExr/ExrDecoder.cs @@ -1,61 +1,46 @@ // Copyright (c) Six Labors. -// Licensed under the Apache License, Version 2.0. +// Licensed under the Six Labors Split License. -using System.IO; -using System.Threading; -using System.Threading.Tasks; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Formats.OpenExr +namespace SixLabors.ImageSharp.Formats.OpenExr; + +/// +/// Image decoder for generating an image out of a OpenExr stream. +/// +public class ExrDecoder : IImageDecoderSpecialized { - /// - /// Image decoder for generating an image out of a OpenExr stream. - /// - public sealed class ExrDecoder : IImageDecoder, IExrDecoderOptions, IImageInfoDetector + /// + IImageInfo IImageInfoDetector.Identify(DecoderOptions options, Stream stream, CancellationToken cancellationToken) { - /// - public Image Decode(Configuration configuration, Stream stream) - where TPixel : unmanaged, IPixel - { - Guard.NotNull(stream, nameof(stream)); - - var decoder = new ExrDecoderCore(configuration, this); - return decoder.Decode(configuration, stream); - } - - /// - public Image Decode(Configuration configuration, Stream stream) - => this.Decode(configuration, stream); - - /// - public Task> DecodeAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken) - where TPixel : unmanaged, IPixel - { - Guard.NotNull(stream, nameof(stream)); - - var decoder = new ExrDecoderCore(configuration, this); - return decoder.DecodeAsync(configuration, stream, cancellationToken); - } - - /// - public async Task DecodeAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken) - => await this.DecodeAsync(configuration, stream, cancellationToken) - .ConfigureAwait(false); - - /// - public IImageInfo Identify(Configuration configuration, Stream stream) - { - Guard.NotNull(stream, nameof(stream)); - - return new ExrDecoderCore(configuration, this).Identify(configuration, stream); - } - - /// - public Task IdentifyAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken) - { - Guard.NotNull(stream, nameof(stream)); - - return new ExrDecoderCore(configuration, this).IdentifyAsync(configuration, stream, cancellationToken); - } + Guard.NotNull(options, nameof(options)); + Guard.NotNull(stream, nameof(stream)); + + return new ExrDecoderCore(new() { GeneralOptions = options }).Identify(options.Configuration, stream, cancellationToken); } + + /// + Image IImageDecoder.Decode(DecoderOptions options, Stream stream, CancellationToken cancellationToken) + => ((IImageDecoderSpecialized)this).Decode(new() { GeneralOptions = options }, stream, cancellationToken); + + /// + Image IImageDecoder.Decode(DecoderOptions options, Stream stream, CancellationToken cancellationToken) + => ((IImageDecoderSpecialized)this).Decode(new() { GeneralOptions = options }, stream, cancellationToken); + + /// + Image IImageDecoderSpecialized.Decode(ExrDecoderOptions options, Stream stream, CancellationToken cancellationToken) + { + Guard.NotNull(options, nameof(options)); + Guard.NotNull(stream, nameof(stream)); + + Image image = new ExrDecoderCore(options).Decode(options.GeneralOptions.Configuration, stream, cancellationToken); + + ImageDecoderUtilities.Resize(options.GeneralOptions, image); + + return image; + } + + /// + Image IImageDecoderSpecialized.Decode(ExrDecoderOptions options, Stream stream, CancellationToken cancellationToken) + => ((IImageDecoderSpecialized)this).Decode(options, stream, cancellationToken); } diff --git a/src/ImageSharp/Formats/OpenExr/ExrDecoderCore.cs b/src/ImageSharp/Formats/OpenExr/ExrDecoderCore.cs index d414babf28..ca244c5674 100644 --- a/src/ImageSharp/Formats/OpenExr/ExrDecoderCore.cs +++ b/src/ImageSharp/Formats/OpenExr/ExrDecoderCore.cs @@ -1,765 +1,759 @@ // Copyright (c) Six Labors. -// Licensed under the Apache License, Version 2.0. +// Licensed under the Six Labors Split License. -using System; using System.Buffers; using System.Buffers.Binary; -using System.Collections.Generic; using System.Runtime.CompilerServices; using System.Text; -using System.Threading; using SixLabors.ImageSharp.Formats.OpenExr.Compression; -using SixLabors.ImageSharp.Formats.Pbm; using SixLabors.ImageSharp.IO; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Metadata; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Formats.OpenExr +namespace SixLabors.ImageSharp.Formats.OpenExr; + +/// +/// Performs the OpenExr decoding operation. +/// +internal sealed class ExrDecoderCore : IImageDecoderInternals { /// - /// Performs the OpenExr decoding operation. + /// Reusable buffer. /// - internal sealed class ExrDecoderCore : IImageDecoderInternals - { - /// - /// Reusable buffer. - /// - private readonly byte[] buffer = new byte[8]; - - /// - /// Used for allocating memory during processing operations. - /// - private readonly MemoryAllocator memoryAllocator; + private readonly byte[] buffer = new byte[8]; - /// - /// The bitmap decoder options. - /// - private readonly IExrDecoderOptions options; + /// + /// Used for allocating memory during processing operations. + /// + private readonly MemoryAllocator memoryAllocator; - /// - /// The metadata. - /// - private ImageMetadata metadata; + /// + /// The global configuration. + /// + private readonly Configuration configuration; - /// - /// Initializes a new instance of the class. - /// - /// The configuration. - /// The options. - public ExrDecoderCore(Configuration configuration, IExrDecoderOptions options) - { - this.Configuration = configuration; - this.memoryAllocator = configuration.MemoryAllocator; - this.options = options; - } + /// + /// The metadata. + /// + private ImageMetadata metadata; - /// - public Configuration Configuration { get; } + /// + /// Initializes a new instance of the class. + /// + /// The options. + public ExrDecoderCore(ExrDecoderOptions options) + { + this.Options = options.GeneralOptions; + this.configuration = options.GeneralOptions.Configuration; + this.memoryAllocator = this.configuration.MemoryAllocator; + } - /// - /// Gets the dimensions of the image. - /// - public Size Dimensions => new(this.Width, this.Height); + /// + public DecoderOptions Options { get; } - private int Width { get; set; } + /// + /// Gets the dimensions of the image. + /// + public Size Dimensions => new(this.Width, this.Height); - private int Height { get; set; } + private int Width { get; set; } - private IList Channels { get; set; } + private int Height { get; set; } - private ExrCompressionType Compression { get; set; } + private IList Channels { get; set; } - private ExrImageDataType ImageDataType { get; set; } + private ExrCompressionType Compression { get; set; } - private ExrImageType ImageType { get; set; } + private ExrImageDataType ImageDataType { get; set; } - private ExrHeaderAttributes HeaderAttributes { get; set; } + private ExrImageType ImageType { get; set; } - /// - public Image Decode(BufferedReadStream stream, CancellationToken cancellationToken) - where TPixel : unmanaged, IPixel - { - this.ReadExrHeader(stream); - this.IsSupportedCompression(); - ExrPixelType pixelType = this.ValidateChannels(); - this.ReadImageDataType(); + private ExrHeaderAttributes HeaderAttributes { get; set; } - var image = new Image(this.Configuration, this.Width, this.Height, this.metadata); - Buffer2D pixels = image.GetRootFramePixelBuffer(); + /// + public Image Decode(BufferedReadStream stream, CancellationToken cancellationToken) + where TPixel : unmanaged, IPixel + { + this.ReadExrHeader(stream); + this.IsSupportedCompression(); + ExrPixelType pixelType = this.ValidateChannels(); + this.ReadImageDataType(); - switch (pixelType) - { - case ExrPixelType.Half: - case ExrPixelType.Float: - this.DecodeFloatingPointPixelData(stream, pixels); - break; - case ExrPixelType.UnsignedInt: - this.DecodeUnsignedIntPixelData(stream, pixels); - break; - default: - ExrThrowHelper.ThrowNotSupported("Pixel type is not supported"); - break; - } + Image image = new Image(this.configuration, this.Width, this.Height, this.metadata); + Buffer2D pixels = image.GetRootFramePixelBuffer(); - return image; + switch (pixelType) + { + case ExrPixelType.Half: + case ExrPixelType.Float: + this.DecodeFloatingPointPixelData(stream, pixels); + break; + case ExrPixelType.UnsignedInt: + this.DecodeUnsignedIntPixelData(stream, pixels); + break; + default: + ExrThrowHelper.ThrowNotSupported("Pixel type is not supported"); + break; } - private void DecodeFloatingPointPixelData(BufferedReadStream stream, Buffer2D pixels) - where TPixel : unmanaged, IPixel + return image; + } + + private void DecodeFloatingPointPixelData(BufferedReadStream stream, Buffer2D pixels) + where TPixel : unmanaged, IPixel + { + bool hasAlpha = this.HasAlpha(); + uint bytesPerRow = this.CalculateBytesPerRow((uint)this.Width); + uint rowsPerBlock = this.RowsPerBlock(); + uint bytesPerBlock = bytesPerRow * rowsPerBlock; + int width = this.Width; + int height = this.Height; + + using IMemoryOwner rowBuffer = this.memoryAllocator.Allocate(width * 4); + using IMemoryOwner decompressedPixelDataBuffer = this.memoryAllocator.Allocate((int)bytesPerBlock); + Span decompressedPixelData = decompressedPixelDataBuffer.GetSpan(); + Span redPixelData = rowBuffer.GetSpan().Slice(0, width); + Span greenPixelData = rowBuffer.GetSpan().Slice(width, width); + Span bluePixelData = rowBuffer.GetSpan().Slice(width * 2, width); + Span alphaPixelData = rowBuffer.GetSpan().Slice(width * 3, width); + + using ExrBaseDecompressor decompressor = ExrDecompressorFactory.Create(this.Compression, this.memoryAllocator, bytesPerBlock); + + TPixel color = default; + for (uint y = 0; y < height; y += rowsPerBlock) { - bool hasAlpha = this.HasAlpha(); - uint bytesPerRow = this.CalculateBytesPerRow((uint)this.Width); - uint rowsPerBlock = this.RowsPerBlock(); - uint bytesPerBlock = bytesPerRow * rowsPerBlock; - int width = this.Width; - int height = this.Height; + ulong rowOffset = this.ReadUnsignedLong(stream); + long nextRowOffsetPosition = stream.Position; - using IMemoryOwner rowBuffer = this.memoryAllocator.Allocate(width * 4); - using IMemoryOwner decompressedPixelDataBuffer = this.memoryAllocator.Allocate((int)bytesPerBlock); - Span decompressedPixelData = decompressedPixelDataBuffer.GetSpan(); - Span redPixelData = rowBuffer.GetSpan().Slice(0, width); - Span greenPixelData = rowBuffer.GetSpan().Slice(width, width); - Span bluePixelData = rowBuffer.GetSpan().Slice(width * 2, width); - Span alphaPixelData = rowBuffer.GetSpan().Slice(width * 3, width); + stream.Position = (long)rowOffset; + uint rowStartIndex = this.ReadUnsignedInteger(stream); - using ExrBaseDecompressor decompressor = ExrDecompressorFactory.Create(this.Compression, this.memoryAllocator, bytesPerBlock); + uint compressedBytesCount = this.ReadUnsignedInteger(stream); + decompressor.Decompress(stream, compressedBytesCount, decompressedPixelData); - TPixel color = default; - for (uint y = 0; y < height; y += rowsPerBlock) + int offset = 0; + for (uint rowIndex = rowStartIndex; rowIndex < rowStartIndex + rowsPerBlock && rowIndex < height; rowIndex++) { - ulong rowOffset = this.ReadUnsignedLong(stream); - long nextRowOffsetPosition = stream.Position; - - stream.Position = (long)rowOffset; - uint rowStartIndex = this.ReadUnsignedInteger(stream); - - uint compressedBytesCount = this.ReadUnsignedInteger(stream); - decompressor.Decompress(stream, compressedBytesCount, decompressedPixelData); - - int offset = 0; - for (uint rowIndex = rowStartIndex; rowIndex < rowStartIndex + rowsPerBlock && rowIndex < height; rowIndex++) + Span pixelRow = pixels.DangerousGetRowSpan((int)rowIndex); + for (int channelIdx = 0; channelIdx < this.Channels.Count; channelIdx++) { - Span pixelRow = pixels.DangerousGetRowSpan((int)rowIndex); - for (int channelIdx = 0; channelIdx < this.Channels.Count; channelIdx++) - { - ExrChannelInfo channel = this.Channels[channelIdx]; - offset += this.ReadFloatChannelData(stream, channel, decompressedPixelData.Slice(offset), redPixelData, greenPixelData, bluePixelData, alphaPixelData, width); - } - - for (int x = 0; x < width; x++) - { - var pixelValue = new HalfVector4(redPixelData[x], greenPixelData[x], bluePixelData[x], hasAlpha ? alphaPixelData[x] : 1.0f); - color.FromVector4(pixelValue.ToVector4()); - pixelRow[x] = color; - } + ExrChannelInfo channel = this.Channels[channelIdx]; + offset += this.ReadFloatChannelData(stream, channel, decompressedPixelData.Slice(offset), redPixelData, greenPixelData, bluePixelData, alphaPixelData, width); + } + for (int x = 0; x < width; x++) + { + var pixelValue = new HalfVector4(redPixelData[x], greenPixelData[x], bluePixelData[x], hasAlpha ? alphaPixelData[x] : 1.0f); + color.FromVector4(pixelValue.ToVector4()); + pixelRow[x] = color; } - stream.Position = nextRowOffsetPosition; } + + stream.Position = nextRowOffsetPosition; } + } - private void DecodeUnsignedIntPixelData(BufferedReadStream stream, Buffer2D pixels) - where TPixel : unmanaged, IPixel + private void DecodeUnsignedIntPixelData(BufferedReadStream stream, Buffer2D pixels) + where TPixel : unmanaged, IPixel + { + bool hasAlpha = this.HasAlpha(); + uint bytesPerRow = this.CalculateBytesPerRow((uint)this.Width); + uint rowsPerBlock = this.RowsPerBlock(); + uint bytesPerBlock = bytesPerRow * rowsPerBlock; + int width = this.Width; + int height = this.Height; + + using IMemoryOwner rowBuffer = this.memoryAllocator.Allocate(width * 4); + using IMemoryOwner decompressedPixelDataBuffer = this.memoryAllocator.Allocate((int)bytesPerBlock); + Span decompressedPixelData = decompressedPixelDataBuffer.GetSpan(); + Span redPixelData = rowBuffer.GetSpan().Slice(0, width); + Span greenPixelData = rowBuffer.GetSpan().Slice(width, width); + Span bluePixelData = rowBuffer.GetSpan().Slice(width * 2, width); + Span alphaPixelData = rowBuffer.GetSpan().Slice(width * 3, width); + + using ExrBaseDecompressor decompressor = ExrDecompressorFactory.Create(this.Compression, this.memoryAllocator, bytesPerBlock); + + TPixel color = default; + for (uint y = 0; y < height; y += rowsPerBlock) { - bool hasAlpha = this.HasAlpha(); - uint bytesPerRow = this.CalculateBytesPerRow((uint)this.Width); - uint rowsPerBlock = this.RowsPerBlock(); - uint bytesPerBlock = bytesPerRow * rowsPerBlock; - int width = this.Width; - int height = this.Height; + ulong rowOffset = this.ReadUnsignedLong(stream); + long nextRowOffsetPosition = stream.Position; - using IMemoryOwner rowBuffer = this.memoryAllocator.Allocate(width * 4); - using IMemoryOwner decompressedPixelDataBuffer = this.memoryAllocator.Allocate((int)bytesPerBlock); - Span decompressedPixelData = decompressedPixelDataBuffer.GetSpan(); - Span redPixelData = rowBuffer.GetSpan().Slice(0, width); - Span greenPixelData = rowBuffer.GetSpan().Slice(width, width); - Span bluePixelData = rowBuffer.GetSpan().Slice(width * 2, width); - Span alphaPixelData = rowBuffer.GetSpan().Slice(width * 3, width); + stream.Position = (long)rowOffset; + uint rowStartIndex = this.ReadUnsignedInteger(stream); - using ExrBaseDecompressor decompressor = ExrDecompressorFactory.Create(this.Compression, this.memoryAllocator, bytesPerBlock); + uint compressedBytesCount = this.ReadUnsignedInteger(stream); + decompressor.Decompress(stream, compressedBytesCount, decompressedPixelData); - TPixel color = default; - for (uint y = 0; y < height; y += rowsPerBlock) + int offset = 0; + for (uint rowIndex = rowStartIndex; rowIndex < rowStartIndex + rowsPerBlock && rowIndex < height; rowIndex++) { - ulong rowOffset = this.ReadUnsignedLong(stream); - long nextRowOffsetPosition = stream.Position; - - stream.Position = (long)rowOffset; - uint rowStartIndex = this.ReadUnsignedInteger(stream); - - uint compressedBytesCount = this.ReadUnsignedInteger(stream); - decompressor.Decompress(stream, compressedBytesCount, decompressedPixelData); - - int offset = 0; - for (uint rowIndex = rowStartIndex; rowIndex < rowStartIndex + rowsPerBlock && rowIndex < height; rowIndex++) + Span pixelRow = pixels.DangerousGetRowSpan((int)rowIndex); + for (int channelIdx = 0; channelIdx < this.Channels.Count; channelIdx++) { - Span pixelRow = pixels.DangerousGetRowSpan((int)rowIndex); - for (int channelIdx = 0; channelIdx < this.Channels.Count; channelIdx++) - { - ExrChannelInfo channel = this.Channels[channelIdx]; - offset += this.ReadUnsignedIntChannelData(stream, channel, decompressedPixelData, redPixelData, greenPixelData, bluePixelData, alphaPixelData, width); - } + ExrChannelInfo channel = this.Channels[channelIdx]; + offset += this.ReadUnsignedIntChannelData(stream, channel, decompressedPixelData, redPixelData, greenPixelData, bluePixelData, alphaPixelData, width); + } - stream.Position = nextRowOffsetPosition; + stream.Position = nextRowOffsetPosition; - for (int x = 0; x < width; x++) - { - var pixelValue = new Rgba128(redPixelData[x], greenPixelData[x], bluePixelData[x], hasAlpha ? alphaPixelData[x] : uint.MaxValue); - color.FromVector4(pixelValue.ToVector4()); - pixelRow[x] = color; - } + for (int x = 0; x < width; x++) + { + var pixelValue = new Rgba128(redPixelData[x], greenPixelData[x], bluePixelData[x], hasAlpha ? alphaPixelData[x] : uint.MaxValue); + color.FromVector4(pixelValue.ToVector4()); + pixelRow[x] = color; } } } + } - private int ReadFloatChannelData( - BufferedReadStream stream, - ExrChannelInfo channel, - Span decompressedPixelData, - Span redPixelData, - Span greenPixelData, - Span bluePixelData, - Span alphaPixelData, - int width) + private int ReadFloatChannelData( + BufferedReadStream stream, + ExrChannelInfo channel, + Span decompressedPixelData, + Span redPixelData, + Span greenPixelData, + Span bluePixelData, + Span alphaPixelData, + int width) + { + switch (channel.ChannelName) { - switch (channel.ChannelName) - { - case ExrConstants.ChannelNames.Red: - return this.ReadChannelData(channel, decompressedPixelData, redPixelData, width); + case ExrConstants.ChannelNames.Red: + return this.ReadChannelData(channel, decompressedPixelData, redPixelData, width); - case ExrConstants.ChannelNames.Blue: - return this.ReadChannelData(channel, decompressedPixelData, bluePixelData, width); + case ExrConstants.ChannelNames.Blue: + return this.ReadChannelData(channel, decompressedPixelData, bluePixelData, width); - case ExrConstants.ChannelNames.Green: - return this.ReadChannelData(channel, decompressedPixelData, greenPixelData, width); + case ExrConstants.ChannelNames.Green: + return this.ReadChannelData(channel, decompressedPixelData, greenPixelData, width); - case ExrConstants.ChannelNames.Alpha: - return this.ReadChannelData(channel, decompressedPixelData, alphaPixelData, width); + case ExrConstants.ChannelNames.Alpha: + return this.ReadChannelData(channel, decompressedPixelData, alphaPixelData, width); - case ExrConstants.ChannelNames.Luminance: - int bytesRead = this.ReadChannelData(channel, decompressedPixelData, redPixelData, width); - redPixelData.CopyTo(bluePixelData); - redPixelData.CopyTo(greenPixelData); + case ExrConstants.ChannelNames.Luminance: + int bytesRead = this.ReadChannelData(channel, decompressedPixelData, redPixelData, width); + redPixelData.CopyTo(bluePixelData); + redPixelData.CopyTo(greenPixelData); - return bytesRead; + return bytesRead; - default: - // Skip unknown channel. - int channelDataSizeInBytes = channel.PixelType is ExrPixelType.Float or ExrPixelType.UnsignedInt ? 4 : 2; - stream.Position += width * channelDataSizeInBytes; - return channelDataSizeInBytes; - } + default: + // Skip unknown channel. + int channelDataSizeInBytes = channel.PixelType is ExrPixelType.Float or ExrPixelType.UnsignedInt ? 4 : 2; + stream.Position += width * channelDataSizeInBytes; + return channelDataSizeInBytes; } + } - private int ReadUnsignedIntChannelData( - BufferedReadStream stream, - ExrChannelInfo channel, - Span decompressedPixelData, - Span redPixelData, - Span greenPixelData, - Span bluePixelData, - Span alphaPixelData, - int width) + private int ReadUnsignedIntChannelData( + BufferedReadStream stream, + ExrChannelInfo channel, + Span decompressedPixelData, + Span redPixelData, + Span greenPixelData, + Span bluePixelData, + Span alphaPixelData, + int width) + { + switch (channel.ChannelName) { - switch (channel.ChannelName) - { - case ExrConstants.ChannelNames.Red: - return this.ReadChannelData(channel, decompressedPixelData, redPixelData, width); + case ExrConstants.ChannelNames.Red: + return this.ReadChannelData(channel, decompressedPixelData, redPixelData, width); - case ExrConstants.ChannelNames.Blue: - return this.ReadChannelData(channel, decompressedPixelData, bluePixelData, width); + case ExrConstants.ChannelNames.Blue: + return this.ReadChannelData(channel, decompressedPixelData, bluePixelData, width); - case ExrConstants.ChannelNames.Green: - return this.ReadChannelData(channel, decompressedPixelData, greenPixelData, width); + case ExrConstants.ChannelNames.Green: + return this.ReadChannelData(channel, decompressedPixelData, greenPixelData, width); - case ExrConstants.ChannelNames.Alpha: - return this.ReadChannelData(channel, decompressedPixelData, alphaPixelData, width); + case ExrConstants.ChannelNames.Alpha: + return this.ReadChannelData(channel, decompressedPixelData, alphaPixelData, width); - case ExrConstants.ChannelNames.Luminance: - int bytesRead = this.ReadChannelData(channel, decompressedPixelData, redPixelData, width); - redPixelData.CopyTo(bluePixelData); - redPixelData.CopyTo(greenPixelData); - return bytesRead; + case ExrConstants.ChannelNames.Luminance: + int bytesRead = this.ReadChannelData(channel, decompressedPixelData, redPixelData, width); + redPixelData.CopyTo(bluePixelData); + redPixelData.CopyTo(greenPixelData); + return bytesRead; - default: - // Skip unknown channel. - int channelDataSizeInBytes = channel.PixelType is ExrPixelType.Float or ExrPixelType.UnsignedInt ? 4 : 2; - stream.Position += this.Width * channelDataSizeInBytes; - return channelDataSizeInBytes; - } + default: + // Skip unknown channel. + int channelDataSizeInBytes = channel.PixelType is ExrPixelType.Float or ExrPixelType.UnsignedInt ? 4 : 2; + stream.Position += this.Width * channelDataSizeInBytes; + return channelDataSizeInBytes; } + } - private int ReadChannelData(ExrChannelInfo channel, Span decompressedPixelData, Span pixelData, int width) + private int ReadChannelData(ExrChannelInfo channel, Span decompressedPixelData, Span pixelData, int width) + { + switch (channel.PixelType) { - switch (channel.PixelType) - { - case ExrPixelType.Half: - return this.ReadPixelRowChannelHalfSingle(decompressedPixelData, pixelData, width); - case ExrPixelType.Float: - return this.ReadPixelRowChannelSingle(decompressedPixelData, pixelData, width); - } - - return 0; + case ExrPixelType.Half: + return this.ReadPixelRowChannelHalfSingle(decompressedPixelData, pixelData, width); + case ExrPixelType.Float: + return this.ReadPixelRowChannelSingle(decompressedPixelData, pixelData, width); } - private int ReadChannelData(ExrChannelInfo channel, Span decompressedPixelData, Span pixelData, int width) - { - switch (channel.PixelType) - { - case ExrPixelType.UnsignedInt: - return this.ReadPixelRowChannelUnsignedInt(decompressedPixelData, pixelData, width); - } + return 0; + } - return 0; + private int ReadChannelData(ExrChannelInfo channel, Span decompressedPixelData, Span pixelData, int width) + { + switch (channel.PixelType) + { + case ExrPixelType.UnsignedInt: + return this.ReadPixelRowChannelUnsignedInt(decompressedPixelData, pixelData, width); } - private int ReadPixelRowChannelHalfSingle(Span decompressedPixelData, Span channelData, int width) - { - int offset = 0; - for (int x = 0; x < width; x++) - { - ushort shortValue = BinaryPrimitives.ReadUInt16LittleEndian(decompressedPixelData.Slice(offset, 2)); - channelData[x] = HalfTypeHelper.Unpack(shortValue); - offset += 2; - } + return 0; + } - return offset; + private int ReadPixelRowChannelHalfSingle(Span decompressedPixelData, Span channelData, int width) + { + int offset = 0; + for (int x = 0; x < width; x++) + { + ushort shortValue = BinaryPrimitives.ReadUInt16LittleEndian(decompressedPixelData.Slice(offset, 2)); + channelData[x] = HalfTypeHelper.Unpack(shortValue); + offset += 2; } - private int ReadPixelRowChannelSingle(Span decompressedPixelData, Span channelData, int width) - { - int offset = 0; - for (int x = 0; x < width; x++) - { - int intValue = BinaryPrimitives.ReadInt32LittleEndian(decompressedPixelData.Slice(offset, 4)); - channelData[x] = Unsafe.As(ref intValue); - offset += 4; - } + return offset; + } - return offset; + private int ReadPixelRowChannelSingle(Span decompressedPixelData, Span channelData, int width) + { + int offset = 0; + for (int x = 0; x < width; x++) + { + int intValue = BinaryPrimitives.ReadInt32LittleEndian(decompressedPixelData.Slice(offset, 4)); + channelData[x] = Unsafe.As(ref intValue); + offset += 4; } - private int ReadPixelRowChannelUnsignedInt(Span decompressedPixelData, Span channelData, int width) - { - int offset = 0; - for (int x = 0; x < width; x++) - { - channelData[x] = BinaryPrimitives.ReadUInt32LittleEndian(decompressedPixelData.Slice(offset, 4)); - offset += 4; - } + return offset; + } - return offset; + private int ReadPixelRowChannelUnsignedInt(Span decompressedPixelData, Span channelData, int width) + { + int offset = 0; + for (int x = 0; x < width; x++) + { + channelData[x] = BinaryPrimitives.ReadUInt32LittleEndian(decompressedPixelData.Slice(offset, 4)); + offset += 4; } - /// - public IImageInfo Identify(BufferedReadStream stream, CancellationToken cancellationToken) - { - ExrHeaderAttributes header = this.ReadExrHeader(stream); + return offset; + } + + /// + public IImageInfo Identify(BufferedReadStream stream, CancellationToken cancellationToken) + { + ExrHeaderAttributes header = this.ReadExrHeader(stream); - int bitsPerPixel = this.CalculateBitsPerPixel(); + int bitsPerPixel = this.CalculateBitsPerPixel(); - return new ImageInfo(new PixelTypeInfo(bitsPerPixel), this.Width, this.Height, new ImageMetadata()); - } + return new ImageInfo(new PixelTypeInfo(bitsPerPixel), this.Width, this.Height, new ImageMetadata()); + } - private int CalculateBitsPerPixel() + private int CalculateBitsPerPixel() + { + int bitsPerPixel = 0; + for (int i = 0; i < this.Channels.Count; i++) { - int bitsPerPixel = 0; - for (int i = 0; i < this.Channels.Count; i++) + ExrChannelInfo channel = this.Channels[0]; + if (channel.PixelType is ExrPixelType.Float or ExrPixelType.UnsignedInt) { - ExrChannelInfo channel = this.Channels[0]; - if (channel.PixelType is ExrPixelType.Float or ExrPixelType.UnsignedInt) - { - bitsPerPixel += 32; - } - else if (channel.PixelType == ExrPixelType.Half) - { - bitsPerPixel += 16; - } + bitsPerPixel += 32; } - - return bitsPerPixel; - } - - private ExrPixelType ValidateChannels() - { - if (this.Channels.Count == 0) + else if (channel.PixelType == ExrPixelType.Half) { - ExrThrowHelper.ThrowInvalidImageContentException("At least one channel of pixel data is expected!"); + bitsPerPixel += 16; } - - // Find pixel the type of any channel which is R, G, B or A. - ExrPixelType pixelType = this.FindPixelType(); - - return pixelType; } - private ExrHeaderAttributes ReadExrHeader(BufferedReadStream stream) + return bitsPerPixel; + } + + private ExrPixelType ValidateChannels() + { + if (this.Channels.Count == 0) { - // Skip over the magick bytes, we already know its an EXR image. - stream.Skip(4); + ExrThrowHelper.ThrowInvalidImageContentException("At least one channel of pixel data is expected!"); + } - // Read version number. - byte version = (byte)stream.ReadByte(); - if (version != 2) - { - ExrThrowHelper.ThrowNotSupportedVersion(); - } + // Find pixel the type of any channel which is R, G, B or A. + ExrPixelType pixelType = this.FindPixelType(); - // Next three bytes contain info's about the image. - byte flagsByte0 = (byte)stream.ReadByte(); - byte flagsByte1 = (byte)stream.ReadByte(); - byte flagsByte2 = (byte)stream.ReadByte(); - if ((flagsByte0 & (1 << 1)) != 0) - { - this.ImageType = ExrImageType.Tiled; - } + return pixelType; + } - this.HeaderAttributes = this.ParseHeaderAttributes(stream); + private ExrHeaderAttributes ReadExrHeader(BufferedReadStream stream) + { + // Skip over the magick bytes, we already know its an EXR image. + stream.Skip(4); - if (!this.HeaderAttributes.IsValid()) - { - ExrThrowHelper.ThrowInvalidImageHeader(); - } + // Read version number. + byte version = (byte)stream.ReadByte(); + if (version != 2) + { + ExrThrowHelper.ThrowNotSupportedVersion(); + } - this.Width = this.HeaderAttributes.DataWindow.Value.XMax - this.HeaderAttributes.DataWindow.Value.XMin + 1; - this.Height = this.HeaderAttributes.DataWindow.Value.YMax - this.HeaderAttributes.DataWindow.Value.YMin + 1; - this.Channels = this.HeaderAttributes.Channels; - this.Compression = this.HeaderAttributes.Compression.GetValueOrDefault(); + // Next three bytes contain info's about the image. + byte flagsByte0 = (byte)stream.ReadByte(); + byte flagsByte1 = (byte)stream.ReadByte(); + byte flagsByte2 = (byte)stream.ReadByte(); + if ((flagsByte0 & (1 << 1)) != 0) + { + this.ImageType = ExrImageType.Tiled; + } - this.metadata = new ImageMetadata(); + this.HeaderAttributes = this.ParseHeaderAttributes(stream); - return this.HeaderAttributes; + if (!this.HeaderAttributes.IsValid()) + { + ExrThrowHelper.ThrowInvalidImageHeader(); } - private ExrHeaderAttributes ParseHeaderAttributes(BufferedReadStream stream) - { - ExrAttribute attribute = this.ReadAttribute(stream); - var header = new ExrHeaderAttributes(); + this.Width = this.HeaderAttributes.DataWindow.Value.XMax - this.HeaderAttributes.DataWindow.Value.XMin + 1; + this.Height = this.HeaderAttributes.DataWindow.Value.YMax - this.HeaderAttributes.DataWindow.Value.YMin + 1; + this.Channels = this.HeaderAttributes.Channels; + this.Compression = this.HeaderAttributes.Compression.GetValueOrDefault(); - while (!attribute.Equals(ExrAttribute.EmptyAttribute)) - { - switch (attribute.Name) - { - case ExrConstants.AttributeNames.Channels: - IList channels = this.ReadChannelList(stream, attribute.Length); - header.Channels = channels; - break; - case ExrConstants.AttributeNames.Compression: - header.Compression = (ExrCompressionType)stream.ReadByte(); - break; - case ExrConstants.AttributeNames.DataWindow: - ExrBox2i dataWindow = this.ReadBoxInteger(stream); - header.DataWindow = dataWindow; - break; - case ExrConstants.AttributeNames.DisplayWindow: - ExrBox2i displayWindow = this.ReadBoxInteger(stream); - header.DisplayWindow = displayWindow; - break; - case ExrConstants.AttributeNames.LineOrder: - var lineOrder = (ExrLineOrder)stream.ReadByte(); - header.LineOrder = lineOrder; - break; - case ExrConstants.AttributeNames.PixelAspectRatio: - float aspectRatio = stream.ReadSingle(this.buffer); - header.AspectRatio = aspectRatio; - break; - case ExrConstants.AttributeNames.ScreenWindowCenter: - float screenWindowCenterX = stream.ReadSingle(this.buffer); - float screenWindowCenterY = stream.ReadSingle(this.buffer); - header.ScreenWindowCenter = new PointF(screenWindowCenterX, screenWindowCenterY); - break; - case ExrConstants.AttributeNames.ScreenWindowWidth: - float screenWindowWidth = stream.ReadSingle(this.buffer); - header.ScreenWindowWidth = screenWindowWidth; - break; - case ExrConstants.AttributeNames.Tiles: - header.TileXSize = this.ReadUnsignedInteger(stream); - header.TileYSize = this.ReadUnsignedInteger(stream); - break; - case ExrConstants.AttributeNames.ChunkCount: - header.ChunkCount = this.ReadSignedInteger(stream); - break; - default: - // Skip unknown attribute bytes. - stream.Skip(attribute.Length); - break; - } + this.metadata = new ImageMetadata(); - attribute = this.ReadAttribute(stream); - } + return this.HeaderAttributes; + } - return header; - } + private ExrHeaderAttributes ParseHeaderAttributes(BufferedReadStream stream) + { + ExrAttribute attribute = this.ReadAttribute(stream); + var header = new ExrHeaderAttributes(); - private ExrAttribute ReadAttribute(BufferedReadStream stream) + while (!attribute.Equals(ExrAttribute.EmptyAttribute)) { - string attributeName = ReadString(stream); - if (attributeName.Equals(string.Empty)) + switch (attribute.Name) { - return ExrAttribute.EmptyAttribute; + case ExrConstants.AttributeNames.Channels: + IList channels = this.ReadChannelList(stream, attribute.Length); + header.Channels = channels; + break; + case ExrConstants.AttributeNames.Compression: + header.Compression = (ExrCompressionType)stream.ReadByte(); + break; + case ExrConstants.AttributeNames.DataWindow: + ExrBox2i dataWindow = this.ReadBoxInteger(stream); + header.DataWindow = dataWindow; + break; + case ExrConstants.AttributeNames.DisplayWindow: + ExrBox2i displayWindow = this.ReadBoxInteger(stream); + header.DisplayWindow = displayWindow; + break; + case ExrConstants.AttributeNames.LineOrder: + var lineOrder = (ExrLineOrder)stream.ReadByte(); + header.LineOrder = lineOrder; + break; + case ExrConstants.AttributeNames.PixelAspectRatio: + float aspectRatio = stream.ReadSingle(this.buffer); + header.AspectRatio = aspectRatio; + break; + case ExrConstants.AttributeNames.ScreenWindowCenter: + float screenWindowCenterX = stream.ReadSingle(this.buffer); + float screenWindowCenterY = stream.ReadSingle(this.buffer); + header.ScreenWindowCenter = new PointF(screenWindowCenterX, screenWindowCenterY); + break; + case ExrConstants.AttributeNames.ScreenWindowWidth: + float screenWindowWidth = stream.ReadSingle(this.buffer); + header.ScreenWindowWidth = screenWindowWidth; + break; + case ExrConstants.AttributeNames.Tiles: + header.TileXSize = this.ReadUnsignedInteger(stream); + header.TileYSize = this.ReadUnsignedInteger(stream); + break; + case ExrConstants.AttributeNames.ChunkCount: + header.ChunkCount = this.ReadSignedInteger(stream); + break; + default: + // Skip unknown attribute bytes. + stream.Skip(attribute.Length); + break; } - string attributeType = ReadString(stream); + attribute = this.ReadAttribute(stream); + } - int attributeSize = this.ReadSignedInteger(stream); + return header; + } - return new ExrAttribute(attributeName, attributeType, attributeSize); + private ExrAttribute ReadAttribute(BufferedReadStream stream) + { + string attributeName = ReadString(stream); + if (attributeName.Equals(string.Empty)) + { + return ExrAttribute.EmptyAttribute; } - private ExrBox2i ReadBoxInteger(BufferedReadStream stream) - { - int xMin = this.ReadSignedInteger(stream); - int yMin = this.ReadSignedInteger(stream); - int xMax = this.ReadSignedInteger(stream); - int yMax = this.ReadSignedInteger(stream); + string attributeType = ReadString(stream); - return new ExrBox2i(xMin, yMin, xMax, yMax); - } + int attributeSize = this.ReadSignedInteger(stream); - private List ReadChannelList(BufferedReadStream stream, int attributeSize) - { - var channels = new List(); - while (attributeSize > 1) - { - ExrChannelInfo channelInfo = this.ReadChannelInfo(stream, out int bytesRead); - channels.Add(channelInfo); - attributeSize -= bytesRead; - } + return new ExrAttribute(attributeName, attributeType, attributeSize); + } - // Last byte should be a null byte. - int byteRead = stream.ReadByte(); + private ExrBox2i ReadBoxInteger(BufferedReadStream stream) + { + int xMin = this.ReadSignedInteger(stream); + int yMin = this.ReadSignedInteger(stream); + int xMax = this.ReadSignedInteger(stream); + int yMax = this.ReadSignedInteger(stream); - return channels; - } + return new ExrBox2i(xMin, yMin, xMax, yMax); + } - private ExrChannelInfo ReadChannelInfo(BufferedReadStream stream, out int bytesRead) + private List ReadChannelList(BufferedReadStream stream, int attributeSize) + { + var channels = new List(); + while (attributeSize > 1) { - string channelName = ReadString(stream); - bytesRead = channelName.Length + 1; + ExrChannelInfo channelInfo = this.ReadChannelInfo(stream, out int bytesRead); + channels.Add(channelInfo); + attributeSize -= bytesRead; + } - var pixelType = (ExrPixelType)this.ReadSignedInteger(stream); - bytesRead += 4; + // Last byte should be a null byte. + int byteRead = stream.ReadByte(); - byte pLinear = (byte)stream.ReadByte(); - byte reserved0 = (byte)stream.ReadByte(); - byte reserved1 = (byte)stream.ReadByte(); - byte reserved2 = (byte)stream.ReadByte(); - bytesRead += 4; + return channels; + } - int xSampling = this.ReadSignedInteger(stream); - bytesRead += 4; + private ExrChannelInfo ReadChannelInfo(BufferedReadStream stream, out int bytesRead) + { + string channelName = ReadString(stream); + bytesRead = channelName.Length + 1; - int ySampling = this.ReadSignedInteger(stream); - bytesRead += 4; + var pixelType = (ExrPixelType)this.ReadSignedInteger(stream); + bytesRead += 4; - return new ExrChannelInfo(channelName, pixelType, pLinear, xSampling, ySampling); - } + byte pLinear = (byte)stream.ReadByte(); + byte reserved0 = (byte)stream.ReadByte(); + byte reserved1 = (byte)stream.ReadByte(); + byte reserved2 = (byte)stream.ReadByte(); + bytesRead += 4; + + int xSampling = this.ReadSignedInteger(stream); + bytesRead += 4; + + int ySampling = this.ReadSignedInteger(stream); + bytesRead += 4; - private static string ReadString(BufferedReadStream stream) + return new ExrChannelInfo(channelName, pixelType, pLinear, xSampling, ySampling); + } + + private static string ReadString(BufferedReadStream stream) + { + var str = new StringBuilder(); + int character = stream.ReadByte(); + if (character == 0) { - var str = new StringBuilder(); - int character = stream.ReadByte(); - if (character == 0) - { - // End of file header reached. - return string.Empty; - } + // End of file header reached. + return string.Empty; + } - while (character != 0) + while (character != 0) + { + if (character == -1) { - if (character == -1) - { - ExrThrowHelper.ThrowInvalidImageHeader(); - } - - str.Append((char)character); - character = stream.ReadByte(); + ExrThrowHelper.ThrowInvalidImageHeader(); } - return str.ToString(); + str.Append((char)character); + character = stream.ReadByte(); } - private ExrPixelType FindPixelType() + return str.ToString(); + } + + private ExrPixelType FindPixelType() + { + ExrPixelType? pixelType = null; + for (int i = 0; i < this.Channels.Count; i++) { - 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 (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) { - if (!pixelType.HasValue) - { - pixelType = this.Channels[i].PixelType; - } - else + pixelType = this.Channels[i].PixelType; + } + else + { + if (pixelType != this.Channels[i].PixelType) { - if (pixelType != this.Channels[i].PixelType) - { - ExrThrowHelper.ThrowNotSupported("Pixel channel data is expected to be the same for all channels."); - } + 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; + if (!pixelType.HasValue) + { + ExrThrowHelper.ThrowNotSupported("Pixel channel data is unknown! Only R, G, B, A and Y are supported."); } - private void IsSupportedCompression() + 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) { - 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"); - } + ExrThrowHelper.ThrowNotSupported($"Compression {this.Compression} is not yet supported"); } + } - private void ReadImageDataType() + private void ReadImageDataType() + { + bool hasRedChannel = false; + bool hasGreenChannel = false; + bool hasBlueChannel = false; + bool hasAlphaChannel = false; + bool hasLuminance = false; + foreach (ExrChannelInfo channelInfo in this.Channels) { - 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")) { - 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; - } + hasAlphaChannel = true; } - if (hasRedChannel && hasGreenChannel && hasBlueChannel && hasAlphaChannel) + if (channelInfo.ChannelName.Equals("R")) { - this.ImageDataType = ExrImageDataType.Rgba; - return; + hasRedChannel = true; } - if (hasRedChannel && hasGreenChannel && hasBlueChannel) + if (channelInfo.ChannelName.Equals("G")) { - this.ImageDataType = ExrImageDataType.Rgb; - return; + hasGreenChannel = true; } - if (hasLuminance && this.Channels.Count == 1) + if (channelInfo.ChannelName.Equals("B")) { - this.ImageDataType = ExrImageDataType.Gray; - return; + hasBlueChannel = true; } - ExrThrowHelper.ThrowNotSupported("The image contains channels, which are not supported!"); - } - - private bool HasAlpha() - { - foreach (ExrChannelInfo channelInfo in this.Channels) + if (channelInfo.ChannelName.Equals("Y")) { - if (channelInfo.ChannelName.Equals("A")) - { - return true; - } + hasLuminance = true; } + } - return false; + if (hasRedChannel && hasGreenChannel && hasBlueChannel && hasAlphaChannel) + { + this.ImageDataType = ExrImageDataType.Rgba; + return; } - private uint CalculateBytesPerRow(uint width) + if (hasRedChannel && hasGreenChannel && hasBlueChannel) { - 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") || channelInfo.ChannelName.Equals("Y")) - { - if (channelInfo.PixelType == ExrPixelType.Half) - { - bytesPerRow += 2 * width; - } - else - { - bytesPerRow += 4 * width; - } - } - } + this.ImageDataType = ExrImageDataType.Rgb; + return; + } - return bytesPerRow; + if (hasLuminance && this.Channels.Count == 1) + { + this.ImageDataType = ExrImageDataType.Gray; + return; } - private uint RowsPerBlock() + ExrThrowHelper.ThrowNotSupported("The image contains channels, which are not supported!"); + } + + private bool HasAlpha() + { + foreach (ExrChannelInfo channelInfo in this.Channels) { - switch (this.Compression) + if (channelInfo.ChannelName.Equals("A")) { - case ExrCompressionType.Zip: - case ExrCompressionType.Pxr24: - return 16; - case ExrCompressionType.B44: - case ExrCompressionType.B44A: - case ExrCompressionType.Piz: - return 32; - - default: - return 1; + return true; } } - private ulong ReadUnsignedLong(BufferedReadStream stream) + return false; + } + + private uint CalculateBytesPerRow(uint width) + { + uint bytesPerRow = 0; + foreach (ExrChannelInfo channelInfo in this.Channels) { - int bytesRead = stream.Read(this.buffer, 0, 8); - if (bytesRead != 8) + if (channelInfo.ChannelName.Equals("A") || channelInfo.ChannelName.Equals("R") || channelInfo.ChannelName.Equals("G") || channelInfo.ChannelName.Equals("B") || channelInfo.ChannelName.Equals("Y")) { - ExrThrowHelper.ThrowInvalidImageContentException("Could not read enough data from the stream!"); + if (channelInfo.PixelType == ExrPixelType.Half) + { + bytesPerRow += 2 * width; + } + else + { + bytesPerRow += 4 * width; + } } - - return BinaryPrimitives.ReadUInt64LittleEndian(this.buffer); } - private uint ReadUnsignedInteger(BufferedReadStream stream) + return bytesPerRow; + } + + private uint RowsPerBlock() + { + switch (this.Compression) { - int bytesRead = stream.Read(this.buffer, 0, 4); - if (bytesRead != 4) - { - ExrThrowHelper.ThrowInvalidImageContentException("Could not read enough data from the stream!"); - } + case ExrCompressionType.Zip: + case ExrCompressionType.Pxr24: + return 16; + case ExrCompressionType.B44: + case ExrCompressionType.B44A: + case ExrCompressionType.Piz: + return 32; + + default: + return 1; + } + } - return BinaryPrimitives.ReadUInt32LittleEndian(this.buffer); + private ulong ReadUnsignedLong(BufferedReadStream stream) + { + int bytesRead = stream.Read(this.buffer, 0, 8); + if (bytesRead != 8) + { + ExrThrowHelper.ThrowInvalidImageContentException("Could not read enough data from the stream!"); } - private int ReadSignedInteger(BufferedReadStream stream) + return BinaryPrimitives.ReadUInt64LittleEndian(this.buffer); + } + + private uint ReadUnsignedInteger(BufferedReadStream stream) + { + int bytesRead = stream.Read(this.buffer, 0, 4); + if (bytesRead != 4) { - int bytesRead = stream.Read(this.buffer, 0, 4); - if (bytesRead != 4) - { - ExrThrowHelper.ThrowInvalidImageContentException("Could not read enough data from the stream!"); - } + ExrThrowHelper.ThrowInvalidImageContentException("Could not read enough data from the stream!"); + } + + return BinaryPrimitives.ReadUInt32LittleEndian(this.buffer); + } - return BinaryPrimitives.ReadInt32LittleEndian(this.buffer); + private int ReadSignedInteger(BufferedReadStream stream) + { + int bytesRead = stream.Read(this.buffer, 0, 4); + if (bytesRead != 4) + { + ExrThrowHelper.ThrowInvalidImageContentException("Could not read enough data from the stream!"); } + + return BinaryPrimitives.ReadInt32LittleEndian(this.buffer); } } diff --git a/src/ImageSharp/Formats/OpenExr/ExrDecoderOptions.cs b/src/ImageSharp/Formats/OpenExr/ExrDecoderOptions.cs new file mode 100644 index 0000000000..f29e9cfb92 --- /dev/null +++ b/src/ImageSharp/Formats/OpenExr/ExrDecoderOptions.cs @@ -0,0 +1,13 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Formats.OpenExr; + +/// +/// Image decoder options for decoding OpenExr streams. +/// +public sealed class ExrDecoderOptions : ISpecializedDecoderOptions +{ + /// + public DecoderOptions GeneralOptions { get; set; } = new(); +} diff --git a/src/ImageSharp/Formats/OpenExr/ExrEncoder.cs b/src/ImageSharp/Formats/OpenExr/ExrEncoder.cs index 71d69596a6..54145afdac 100644 --- a/src/ImageSharp/Formats/OpenExr/ExrEncoder.cs +++ b/src/ImageSharp/Formats/OpenExr/ExrEncoder.cs @@ -1,38 +1,34 @@ // Copyright (c) Six Labors. -// Licensed under the Apache License, Version 2.0. +// Licensed under the Six Labors Split License. -using System.IO; -using System.Threading; -using System.Threading.Tasks; using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Formats.OpenExr +namespace SixLabors.ImageSharp.Formats.OpenExr; + +/// +/// Image encoder for writing an image to a stream in the OpenExr Format. +/// +public sealed class ExrEncoder : IImageEncoder, IExrEncoderOptions { /// - /// Image encoder for writing an image to a stream in the OpenExr Format. + /// Gets or sets the pixel type of the image. /// - public sealed class ExrEncoder : IImageEncoder, IExrEncoderOptions - { - /// - /// Gets or sets the pixel type of the image. - /// - public ExrPixelType? PixelType { get; set; } + public ExrPixelType? PixelType { get; set; } - /// - public void Encode(Image image, Stream stream) - where TPixel : unmanaged, IPixel - { - var encoder = new ExrEncoderCore(this, image.GetMemoryAllocator()); - encoder.Encode(image, stream); - } + /// + public void Encode(Image image, Stream stream) + where TPixel : unmanaged, IPixel + { + ExrEncoderCore encoder = new(this, image.GetMemoryAllocator()); + encoder.Encode(image, stream); + } - /// - public Task EncodeAsync(Image image, Stream stream, CancellationToken cancellationToken) - where TPixel : unmanaged, IPixel - { - var encoder = new ExrEncoderCore(this, image.GetMemoryAllocator()); - return encoder.EncodeAsync(image, stream, cancellationToken); - } + /// + public Task EncodeAsync(Image image, Stream stream, CancellationToken cancellationToken) + where TPixel : unmanaged, IPixel + { + ExrEncoderCore encoder = new(this, image.GetMemoryAllocator()); + return encoder.EncodeAsync(image, stream, cancellationToken); } } diff --git a/src/ImageSharp/Formats/OpenExr/ExrEncoderCore.cs b/src/ImageSharp/Formats/OpenExr/ExrEncoderCore.cs index eb626dc1b2..76e186eb8a 100644 --- a/src/ImageSharp/Formats/OpenExr/ExrEncoderCore.cs +++ b/src/ImageSharp/Formats/OpenExr/ExrEncoderCore.cs @@ -1,422 +1,417 @@ // Copyright (c) Six Labors. -// Licensed under the Apache License, Version 2.0. +// Licensed under the Six Labors Split License. -using System; using System.Buffers; using System.Buffers.Binary; -using System.Collections.Generic; -using System.IO; using System.Runtime.CompilerServices; -using System.Threading; using SixLabors.ImageSharp.Formats.OpenExr.Compression; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Metadata; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Formats.OpenExr +namespace SixLabors.ImageSharp.Formats.OpenExr; + +/// +/// Image encoder for writing an image to a stream in the OpenExr format. +/// +internal sealed class ExrEncoderCore : IImageEncoderInternals { /// - /// Image encoder for writing an image to a stream in the OpenExr format. + /// Reusable buffer. /// - internal sealed class ExrEncoderCore : IImageEncoderInternals - { - /// - /// Reusable buffer. - /// - private readonly byte[] buffer = new byte[8]; - - /// - /// Used for allocating memory during processing operations. - /// - private readonly MemoryAllocator memoryAllocator; - - /// - /// The pixel type of the image. - /// - private ExrPixelType? pixelType; - - /// - /// Initializes a new instance of the class. - /// - /// The encoder options. - /// The memory manager. - public ExrEncoderCore(IExrEncoderOptions options, MemoryAllocator memoryAllocator) - { - this.memoryAllocator = memoryAllocator; - this.pixelType = options.PixelType; - } - - /// - /// Encodes the image to the specified stream from the . - /// - /// The pixel format. - /// The to encode from. - /// The to encode the image data to. - /// The token to request cancellation. - public void Encode(Image image, Stream stream, CancellationToken cancellationToken) - where TPixel : unmanaged, IPixel - { - Guard.NotNull(image, nameof(image)); - Guard.NotNull(stream, nameof(stream)); + private readonly byte[] buffer = new byte[8]; - Buffer2D pixels = image.Frames.RootFrame.PixelBuffer; - - ImageMetadata metadata = image.Metadata; - ExrMetadata exrMetadata = metadata.GetExrMetadata(); - this.pixelType ??= exrMetadata.PixelType; - int width = image.Width; - int height = image.Height; - var header = new ExrHeaderAttributes() - { - Compression = ExrCompressionType.None, - AspectRatio = 1.0f, - DataWindow = new ExrBox2i(0, 0, width - 1, height - 1), - DisplayWindow = new ExrBox2i(0, 0, width - 1, height - 1), - LineOrder = ExrLineOrder.IncreasingY, - ScreenWindowCenter = new PointF(0.0f, 0.0f), - ScreenWindowWidth = 1, - Channels = new List() - { - new(ExrConstants.ChannelNames.Blue, this.pixelType.Value, 0, 1, 1), - new(ExrConstants.ChannelNames.Green, this.pixelType.Value, 0, 1, 1), - new(ExrConstants.ChannelNames.Red, this.pixelType.Value, 0, 1, 1), - } - }; - - // Write magick bytes. - BinaryPrimitives.WriteInt32LittleEndian(this.buffer, ExrConstants.MagickBytes); - stream.Write(this.buffer.AsSpan(0, 4)); - - // Version number. - this.buffer[0] = 2; + /// + /// Used for allocating memory during processing operations. + /// + private readonly MemoryAllocator memoryAllocator; - // Second, third and fourth bytes store info about the image, set all to default: zero. - this.buffer[1] = 0; - this.buffer[2] = 0; - this.buffer[3] = 0; - stream.Write(this.buffer.AsSpan(0, 4)); + /// + /// The pixel type of the image. + /// + private ExrPixelType? pixelType; - // Write EXR header. - this.WriteHeader(stream, header); + /// + /// Initializes a new instance of the class. + /// + /// The encoder options. + /// The memory manager. + public ExrEncoderCore(IExrEncoderOptions options, MemoryAllocator memoryAllocator) + { + this.memoryAllocator = memoryAllocator; + this.pixelType = options.PixelType; + } - // Write offsets to each pixel row. - int bytesPerChannel = this.pixelType == ExrPixelType.Half ? 2 : 4; - int numberOfChannels = 3; - uint rowSizeBytes = (uint)(width * numberOfChannels * bytesPerChannel); - this.WriteRowOffsets(stream, height, rowSizeBytes); + /// + /// Encodes the image to the specified stream from the . + /// + /// The pixel format. + /// The to encode from. + /// The to encode the image data to. + /// The token to request cancellation. + public void Encode(Image image, Stream stream, CancellationToken cancellationToken) + where TPixel : unmanaged, IPixel + { + Guard.NotNull(image, nameof(image)); + Guard.NotNull(stream, nameof(stream)); - // Write pixel data. - switch (this.pixelType) - { - case ExrPixelType.Half: - case ExrPixelType.Float: - this.EncodeFloatingPointPixelData(stream, pixels, width, height, rowSizeBytes); - break; - case ExrPixelType.UnsignedInt: - this.EncodeUnsignedIntPixelData(stream, pixels, width, height, rowSizeBytes); - break; - } - } + Buffer2D pixels = image.Frames.RootFrame.PixelBuffer; - private void EncodeFloatingPointPixelData(Stream stream, Buffer2D pixels, int width, int height, uint rowSizeBytes) - where TPixel : unmanaged, IPixel + ImageMetadata metadata = image.Metadata; + ExrMetadata exrMetadata = metadata.GetExrMetadata(); + this.pixelType ??= exrMetadata.PixelType; + int width = image.Width; + int height = image.Height; + var header = new ExrHeaderAttributes() { - using IMemoryOwner rgbBuffer = this.memoryAllocator.Allocate(width * 3); - Span redBuffer = rgbBuffer.GetSpan().Slice(0, width); - Span greenBuffer = rgbBuffer.GetSpan().Slice(width, width); - Span blueBuffer = rgbBuffer.GetSpan().Slice(width * 2, width); - - for (int y = 0; y < height; y++) + Compression = ExrCompressionType.None, + AspectRatio = 1.0f, + DataWindow = new ExrBox2i(0, 0, width - 1, height - 1), + DisplayWindow = new ExrBox2i(0, 0, width - 1, height - 1), + LineOrder = ExrLineOrder.IncreasingY, + ScreenWindowCenter = new PointF(0.0f, 0.0f), + ScreenWindowWidth = 1, + Channels = new List() { - Span pixelRowSpan = pixels.DangerousGetRowSpan(y); - - for (int x = 0; x < width; x++) - { - var vector4 = pixelRowSpan[x].ToVector4(); - redBuffer[x] = vector4.X; - greenBuffer[x] = vector4.Y; - blueBuffer[x] = vector4.Z; - } - - // Write row index. - BinaryPrimitives.WriteUInt32LittleEndian(this.buffer, (uint)y); - stream.Write(this.buffer.AsSpan(0, 4)); - - // Write pixel row data size. - BinaryPrimitives.WriteUInt32LittleEndian(this.buffer, rowSizeBytes); - stream.Write(this.buffer.AsSpan(0, 4)); - - switch (this.pixelType) - { - case ExrPixelType.Float: - this.WriteSingleRow(stream, width, blueBuffer, greenBuffer, redBuffer); - break; - case ExrPixelType.Half: - this.WriteHalfSingleRow(stream, width, blueBuffer, greenBuffer, redBuffer); - break; - } + new(ExrConstants.ChannelNames.Blue, this.pixelType.Value, 0, 1, 1), + new(ExrConstants.ChannelNames.Green, this.pixelType.Value, 0, 1, 1), + new(ExrConstants.ChannelNames.Red, this.pixelType.Value, 0, 1, 1), } - } - - private void EncodeUnsignedIntPixelData(Stream stream, Buffer2D pixels, int width, int height, uint rowSizeBytes) - where TPixel : unmanaged, IPixel - { - using IMemoryOwner rgbBuffer = this.memoryAllocator.Allocate(width * 3); - Span redBuffer = rgbBuffer.GetSpan().Slice(0, width); - Span greenBuffer = rgbBuffer.GetSpan().Slice(width, width); - Span blueBuffer = rgbBuffer.GetSpan().Slice(width * 2, width); - - var rgb = default(Rgb96); - for (int y = 0; y < height; y++) - { - Span pixelRowSpan = pixels.DangerousGetRowSpan(y); + }; - for (int x = 0; x < width; x++) - { - var vector4 = pixelRowSpan[x].ToVector4(); - rgb.FromVector4(vector4); + // Write magick bytes. + BinaryPrimitives.WriteInt32LittleEndian(this.buffer, ExrConstants.MagickBytes); + stream.Write(this.buffer.AsSpan(0, 4)); - redBuffer[x] = rgb.R; - greenBuffer[x] = rgb.G; - blueBuffer[x] = rgb.B; - } + // Version number. + this.buffer[0] = 2; - // Write row index. - BinaryPrimitives.WriteUInt32LittleEndian(this.buffer, (uint)y); - stream.Write(this.buffer.AsSpan(0, 4)); + // Second, third and fourth bytes store info about the image, set all to default: zero. + this.buffer[1] = 0; + this.buffer[2] = 0; + this.buffer[3] = 0; + stream.Write(this.buffer.AsSpan(0, 4)); - // Write pixel row data size. - BinaryPrimitives.WriteUInt32LittleEndian(this.buffer, rowSizeBytes); - stream.Write(this.buffer.AsSpan(0, 4)); + // Write EXR header. + this.WriteHeader(stream, header); - this.WriteUnsignedIntRow(stream, width, blueBuffer, greenBuffer, redBuffer); - } - } + // Write offsets to each pixel row. + int bytesPerChannel = this.pixelType == ExrPixelType.Half ? 2 : 4; + int numberOfChannels = 3; + uint rowSizeBytes = (uint)(width * numberOfChannels * bytesPerChannel); + this.WriteRowOffsets(stream, height, rowSizeBytes); - private void WriteHeader(Stream stream, ExrHeaderAttributes header) + // Write pixel data. + switch (this.pixelType) { - this.WriteChannels(stream, header.Channels); - this.WriteCompression(stream, header.Compression.Value); - this.WriteDataWindow(stream, header.DataWindow.Value); - this.WriteDisplayWindow(stream, header.DisplayWindow.Value); - this.WritePixelAspectRatio(stream, header.AspectRatio.Value); - this.WriteLineOrder(stream, header.LineOrder.Value); - this.WriteScreenWindowCenter(stream, header.ScreenWindowCenter.Value); - this.WriteScreenWindowWidth(stream, header.ScreenWindowWidth.Value); - stream.WriteByte(0); + case ExrPixelType.Half: + case ExrPixelType.Float: + this.EncodeFloatingPointPixelData(stream, pixels, width, height, rowSizeBytes); + break; + case ExrPixelType.UnsignedInt: + this.EncodeUnsignedIntPixelData(stream, pixels, width, height, rowSizeBytes); + break; } + } + + private void EncodeFloatingPointPixelData(Stream stream, Buffer2D pixels, int width, int height, uint rowSizeBytes) + where TPixel : unmanaged, IPixel + { + using IMemoryOwner rgbBuffer = this.memoryAllocator.Allocate(width * 3); + Span redBuffer = rgbBuffer.GetSpan().Slice(0, width); + Span greenBuffer = rgbBuffer.GetSpan().Slice(width, width); + Span blueBuffer = rgbBuffer.GetSpan().Slice(width * 2, width); - private void WriteSingleRow(Stream stream, int width, Span blueBuffer, Span greenBuffer, Span redBuffer) + for (int y = 0; y < height; y++) { - for (int x = 0; x < width; x++) - { - this.WriteSingle(stream, blueBuffer[x]); - } + Span pixelRowSpan = pixels.DangerousGetRowSpan(y); for (int x = 0; x < width; x++) { - this.WriteSingle(stream, greenBuffer[x]); + var vector4 = pixelRowSpan[x].ToVector4(); + redBuffer[x] = vector4.X; + greenBuffer[x] = vector4.Y; + blueBuffer[x] = vector4.Z; } - for (int x = 0; x < width; x++) + // Write row index. + BinaryPrimitives.WriteUInt32LittleEndian(this.buffer, (uint)y); + stream.Write(this.buffer.AsSpan(0, 4)); + + // Write pixel row data size. + BinaryPrimitives.WriteUInt32LittleEndian(this.buffer, rowSizeBytes); + stream.Write(this.buffer.AsSpan(0, 4)); + + switch (this.pixelType) { - this.WriteSingle(stream, redBuffer[x]); + case ExrPixelType.Float: + this.WriteSingleRow(stream, width, blueBuffer, greenBuffer, redBuffer); + break; + case ExrPixelType.Half: + this.WriteHalfSingleRow(stream, width, blueBuffer, greenBuffer, redBuffer); + break; } } + } + + private void EncodeUnsignedIntPixelData(Stream stream, Buffer2D pixels, int width, int height, uint rowSizeBytes) + where TPixel : unmanaged, IPixel + { + using IMemoryOwner rgbBuffer = this.memoryAllocator.Allocate(width * 3); + Span redBuffer = rgbBuffer.GetSpan().Slice(0, width); + Span greenBuffer = rgbBuffer.GetSpan().Slice(width, width); + Span blueBuffer = rgbBuffer.GetSpan().Slice(width * 2, width); - private void WriteHalfSingleRow(Stream stream, int width, Span blueBuffer, Span greenBuffer, Span redBuffer) + var rgb = default(Rgb96); + for (int y = 0; y < height; y++) { - for (int x = 0; x < width; x++) - { - this.WriteHalfSingle(stream, blueBuffer[x]); - } + Span pixelRowSpan = pixels.DangerousGetRowSpan(y); for (int x = 0; x < width; x++) { - this.WriteHalfSingle(stream, greenBuffer[x]); - } + var vector4 = pixelRowSpan[x].ToVector4(); + rgb.FromVector4(vector4); - for (int x = 0; x < width; x++) - { - this.WriteHalfSingle(stream, redBuffer[x]); + redBuffer[x] = rgb.R; + greenBuffer[x] = rgb.G; + blueBuffer[x] = rgb.B; } - } - private void WriteUnsignedIntRow(Stream stream, int width, Span blueBuffer, Span greenBuffer, Span redBuffer) - { - for (int x = 0; x < width; x++) - { - this.WriteUnsignedInt(stream, blueBuffer[x]); - } + // Write row index. + BinaryPrimitives.WriteUInt32LittleEndian(this.buffer, (uint)y); + stream.Write(this.buffer.AsSpan(0, 4)); - for (int x = 0; x < width; x++) - { - this.WriteUnsignedInt(stream, greenBuffer[x]); - } + // Write pixel row data size. + BinaryPrimitives.WriteUInt32LittleEndian(this.buffer, rowSizeBytes); + stream.Write(this.buffer.AsSpan(0, 4)); - for (int x = 0; x < width; x++) - { - this.WriteUnsignedInt(stream, redBuffer[x]); - } + this.WriteUnsignedIntRow(stream, width, blueBuffer, greenBuffer, redBuffer); } + } - private void WriteRowOffsets(Stream stream, int height, uint rowSizeBytes) + private void WriteHeader(Stream stream, ExrHeaderAttributes header) + { + this.WriteChannels(stream, header.Channels); + this.WriteCompression(stream, header.Compression.Value); + this.WriteDataWindow(stream, header.DataWindow.Value); + this.WriteDisplayWindow(stream, header.DisplayWindow.Value); + this.WritePixelAspectRatio(stream, header.AspectRatio.Value); + this.WriteLineOrder(stream, header.LineOrder.Value); + this.WriteScreenWindowCenter(stream, header.ScreenWindowCenter.Value); + this.WriteScreenWindowWidth(stream, header.ScreenWindowWidth.Value); + stream.WriteByte(0); + } + + private void WriteSingleRow(Stream stream, int width, Span blueBuffer, Span greenBuffer, Span redBuffer) + { + for (int x = 0; x < width; x++) { - ulong startOfPixelData = (ulong)stream.Position + (8 * (ulong)height); - ulong offset = startOfPixelData; - for (int i = 0; i < height; i++) - { - BinaryPrimitives.WriteUInt64LittleEndian(this.buffer, offset); - stream.Write(this.buffer); - offset += 4 + 4 + rowSizeBytes; - } + this.WriteSingle(stream, blueBuffer[x]); } - private void WriteChannels(Stream stream, IList channels) + for (int x = 0; x < width; x++) { - int attributeSize = 0; - foreach (ExrChannelInfo channelInfo in channels) - { - attributeSize += channelInfo.ChannelName.Length + 1; - attributeSize += 16; - } - - // Last zero byte. - attributeSize++; - this.WriteAttributeInformation(stream, ExrConstants.AttributeNames.Channels, ExrConstants.AttibuteTypes.ChannelList, attributeSize); - - foreach (ExrChannelInfo channelInfo in channels) - { - this.WriteChannelInfo(stream, channelInfo); - } + this.WriteSingle(stream, greenBuffer[x]); + } - // Last byte should be zero. - stream.WriteByte(0); + for (int x = 0; x < width; x++) + { + this.WriteSingle(stream, redBuffer[x]); } + } - private void WriteCompression(Stream stream, ExrCompressionType compression) + private void WriteHalfSingleRow(Stream stream, int width, Span blueBuffer, Span greenBuffer, Span redBuffer) + { + for (int x = 0; x < width; x++) { - this.WriteAttributeInformation(stream, ExrConstants.AttributeNames.Compression, ExrConstants.AttibuteTypes.Compression, 1); - stream.WriteByte((byte)compression); + this.WriteHalfSingle(stream, blueBuffer[x]); } - private void WritePixelAspectRatio(Stream stream, float aspectRatio) + for (int x = 0; x < width; x++) { - this.WriteAttributeInformation(stream, ExrConstants.AttributeNames.PixelAspectRatio, ExrConstants.AttibuteTypes.Float, 4); - this.WriteSingle(stream, aspectRatio); + this.WriteHalfSingle(stream, greenBuffer[x]); } - private void WriteLineOrder(Stream stream, ExrLineOrder lineOrder) + for (int x = 0; x < width; x++) { - this.WriteAttributeInformation(stream, ExrConstants.AttributeNames.LineOrder, ExrConstants.AttibuteTypes.LineOrder, 1); - stream.WriteByte((byte)lineOrder); + this.WriteHalfSingle(stream, redBuffer[x]); } + } - private void WriteScreenWindowCenter(Stream stream, PointF screenWindowCenter) + private void WriteUnsignedIntRow(Stream stream, int width, Span blueBuffer, Span greenBuffer, Span redBuffer) + { + for (int x = 0; x < width; x++) { - this.WriteAttributeInformation(stream, ExrConstants.AttributeNames.ScreenWindowCenter, ExrConstants.AttibuteTypes.TwoFloat, 8); - this.WriteSingle(stream, screenWindowCenter.X); - this.WriteSingle(stream, screenWindowCenter.Y); + this.WriteUnsignedInt(stream, blueBuffer[x]); } - private void WriteScreenWindowWidth(Stream stream, float screenWindowWidth) + for (int x = 0; x < width; x++) { - this.WriteAttributeInformation(stream, ExrConstants.AttributeNames.ScreenWindowWidth, ExrConstants.AttibuteTypes.Float, 4); - this.WriteSingle(stream, screenWindowWidth); + this.WriteUnsignedInt(stream, greenBuffer[x]); } - private void WriteDataWindow(Stream stream, ExrBox2i dataWindow) + for (int x = 0; x < width; x++) { - this.WriteAttributeInformation(stream, ExrConstants.AttributeNames.DataWindow, ExrConstants.AttibuteTypes.BoxInt, 16); - this.WriteBoxInteger(stream, dataWindow); + this.WriteUnsignedInt(stream, redBuffer[x]); } + } - private void WriteDisplayWindow(Stream stream, ExrBox2i displayWindow) + private void WriteRowOffsets(Stream stream, int height, uint rowSizeBytes) + { + ulong startOfPixelData = (ulong)stream.Position + (8 * (ulong)height); + ulong offset = startOfPixelData; + for (int i = 0; i < height; i++) { - this.WriteAttributeInformation(stream, ExrConstants.AttributeNames.DisplayWindow, ExrConstants.AttibuteTypes.BoxInt, 16); - this.WriteBoxInteger(stream, displayWindow); + BinaryPrimitives.WriteUInt64LittleEndian(this.buffer, offset); + stream.Write(this.buffer); + offset += 4 + 4 + rowSizeBytes; } + } - private void WriteAttributeInformation(Stream stream, string name, string type, int size) + private void WriteChannels(Stream stream, IList channels) + { + int attributeSize = 0; + foreach (ExrChannelInfo channelInfo in channels) { - // Write attribute name. - this.WriteString(stream, name); + attributeSize += channelInfo.ChannelName.Length + 1; + attributeSize += 16; + } - // Write attribute type. - this.WriteString(stream, type); + // Last zero byte. + attributeSize++; + this.WriteAttributeInformation(stream, ExrConstants.AttributeNames.Channels, ExrConstants.AttibuteTypes.ChannelList, attributeSize); - // Write attribute size. - BinaryPrimitives.WriteUInt32LittleEndian(this.buffer, (uint)size); - stream.Write(this.buffer.AsSpan(0, 4)); + foreach (ExrChannelInfo channelInfo in channels) + { + this.WriteChannelInfo(stream, channelInfo); } - private void WriteChannelInfo(Stream stream, ExrChannelInfo channelInfo) - { - this.WriteString(stream, channelInfo.ChannelName); + // Last byte should be zero. + stream.WriteByte(0); + } - BinaryPrimitives.WriteInt32LittleEndian(this.buffer, (int)channelInfo.PixelType); - stream.Write(this.buffer.AsSpan(0, 4)); + private void WriteCompression(Stream stream, ExrCompressionType compression) + { + this.WriteAttributeInformation(stream, ExrConstants.AttributeNames.Compression, ExrConstants.AttibuteTypes.Compression, 1); + stream.WriteByte((byte)compression); + } - stream.WriteByte(channelInfo.PLinear); + private void WritePixelAspectRatio(Stream stream, float aspectRatio) + { + this.WriteAttributeInformation(stream, ExrConstants.AttributeNames.PixelAspectRatio, ExrConstants.AttibuteTypes.Float, 4); + this.WriteSingle(stream, aspectRatio); + } - // Next 3 bytes are reserved and will set to zero. - stream.WriteByte(0); - stream.WriteByte(0); - stream.WriteByte(0); + private void WriteLineOrder(Stream stream, ExrLineOrder lineOrder) + { + this.WriteAttributeInformation(stream, ExrConstants.AttributeNames.LineOrder, ExrConstants.AttibuteTypes.LineOrder, 1); + stream.WriteByte((byte)lineOrder); + } - BinaryPrimitives.WriteInt32LittleEndian(this.buffer, channelInfo.XSampling); - stream.Write(this.buffer.AsSpan(0, 4)); + private void WriteScreenWindowCenter(Stream stream, PointF screenWindowCenter) + { + this.WriteAttributeInformation(stream, ExrConstants.AttributeNames.ScreenWindowCenter, ExrConstants.AttibuteTypes.TwoFloat, 8); + this.WriteSingle(stream, screenWindowCenter.X); + this.WriteSingle(stream, screenWindowCenter.Y); + } - BinaryPrimitives.WriteInt32LittleEndian(this.buffer, channelInfo.YSampling); - stream.Write(this.buffer.AsSpan(0, 4)); - } + private void WriteScreenWindowWidth(Stream stream, float screenWindowWidth) + { + this.WriteAttributeInformation(stream, ExrConstants.AttributeNames.ScreenWindowWidth, ExrConstants.AttibuteTypes.Float, 4); + this.WriteSingle(stream, screenWindowWidth); + } - private void WriteString(Stream stream, string str) - { - foreach (char c in str) - { - stream.WriteByte((byte)c); - } + private void WriteDataWindow(Stream stream, ExrBox2i dataWindow) + { + this.WriteAttributeInformation(stream, ExrConstants.AttributeNames.DataWindow, ExrConstants.AttibuteTypes.BoxInt, 16); + this.WriteBoxInteger(stream, dataWindow); + } - // Write termination byte. - stream.WriteByte(0); - } + private void WriteDisplayWindow(Stream stream, ExrBox2i displayWindow) + { + this.WriteAttributeInformation(stream, ExrConstants.AttributeNames.DisplayWindow, ExrConstants.AttibuteTypes.BoxInt, 16); + this.WriteBoxInteger(stream, displayWindow); + } - private void WriteBoxInteger(Stream stream, ExrBox2i box) - { - BinaryPrimitives.WriteInt32LittleEndian(this.buffer, box.XMin); - stream.Write(this.buffer.AsSpan(0, 4)); + private void WriteAttributeInformation(Stream stream, string name, string type, int size) + { + // Write attribute name. + this.WriteString(stream, name); - BinaryPrimitives.WriteInt32LittleEndian(this.buffer, box.YMin); - stream.Write(this.buffer.AsSpan(0, 4)); + // Write attribute type. + this.WriteString(stream, type); - BinaryPrimitives.WriteInt32LittleEndian(this.buffer, box.XMax); - stream.Write(this.buffer.AsSpan(0, 4)); + // Write attribute size. + BinaryPrimitives.WriteUInt32LittleEndian(this.buffer, (uint)size); + stream.Write(this.buffer.AsSpan(0, 4)); + } - BinaryPrimitives.WriteInt32LittleEndian(this.buffer, box.YMax); - stream.Write(this.buffer.AsSpan(0, 4)); - } + private void WriteChannelInfo(Stream stream, ExrChannelInfo channelInfo) + { + this.WriteString(stream, channelInfo.ChannelName); - [MethodImpl(InliningOptions.ShortMethod)] - private unsafe void WriteSingle(Stream stream, float value) - { - BinaryPrimitives.WriteInt32LittleEndian(this.buffer, *(int*)&value); - stream.Write(this.buffer.AsSpan(0, 4)); - } + BinaryPrimitives.WriteInt32LittleEndian(this.buffer, (int)channelInfo.PixelType); + stream.Write(this.buffer.AsSpan(0, 4)); - [MethodImpl(InliningOptions.ShortMethod)] - private void WriteHalfSingle(Stream stream, float value) - { - ushort valueAsShort = HalfTypeHelper.Pack(value); - BinaryPrimitives.WriteUInt16LittleEndian(this.buffer, valueAsShort); - stream.Write(this.buffer.AsSpan(0, 2)); - } + stream.WriteByte(channelInfo.PLinear); + + // Next 3 bytes are reserved and will set to zero. + stream.WriteByte(0); + stream.WriteByte(0); + stream.WriteByte(0); + + BinaryPrimitives.WriteInt32LittleEndian(this.buffer, channelInfo.XSampling); + stream.Write(this.buffer.AsSpan(0, 4)); - [MethodImpl(InliningOptions.ShortMethod)] - private void WriteUnsignedInt(Stream stream, uint value) + BinaryPrimitives.WriteInt32LittleEndian(this.buffer, channelInfo.YSampling); + stream.Write(this.buffer.AsSpan(0, 4)); + } + + private void WriteString(Stream stream, string str) + { + foreach (char c in str) { - BinaryPrimitives.WriteUInt32LittleEndian(this.buffer, value); - stream.Write(this.buffer.AsSpan(0, 4)); + stream.WriteByte((byte)c); } + + // Write termination byte. + stream.WriteByte(0); + } + + private void WriteBoxInteger(Stream stream, ExrBox2i box) + { + BinaryPrimitives.WriteInt32LittleEndian(this.buffer, box.XMin); + stream.Write(this.buffer.AsSpan(0, 4)); + + BinaryPrimitives.WriteInt32LittleEndian(this.buffer, box.YMin); + stream.Write(this.buffer.AsSpan(0, 4)); + + BinaryPrimitives.WriteInt32LittleEndian(this.buffer, box.XMax); + stream.Write(this.buffer.AsSpan(0, 4)); + + BinaryPrimitives.WriteInt32LittleEndian(this.buffer, box.YMax); + stream.Write(this.buffer.AsSpan(0, 4)); + } + + [MethodImpl(InliningOptions.ShortMethod)] + private unsafe void WriteSingle(Stream stream, float value) + { + BinaryPrimitives.WriteInt32LittleEndian(this.buffer, *(int*)&value); + stream.Write(this.buffer.AsSpan(0, 4)); + } + + [MethodImpl(InliningOptions.ShortMethod)] + private void WriteHalfSingle(Stream stream, float value) + { + ushort valueAsShort = HalfTypeHelper.Pack(value); + BinaryPrimitives.WriteUInt16LittleEndian(this.buffer, valueAsShort); + stream.Write(this.buffer.AsSpan(0, 2)); + } + + [MethodImpl(InliningOptions.ShortMethod)] + private void WriteUnsignedInt(Stream stream, uint value) + { + BinaryPrimitives.WriteUInt32LittleEndian(this.buffer, value); + stream.Write(this.buffer.AsSpan(0, 4)); } } diff --git a/src/ImageSharp/Formats/OpenExr/ExrFormat.cs b/src/ImageSharp/Formats/OpenExr/ExrFormat.cs index 2cbce0970c..d367977fc6 100644 --- a/src/ImageSharp/Formats/OpenExr/ExrFormat.cs +++ b/src/ImageSharp/Formats/OpenExr/ExrFormat.cs @@ -1,38 +1,34 @@ // Copyright (c) Six Labors. -// Licensed under the Apache License, Version 2.0. +// Licensed under the Six Labors Split License. -using System.Collections.Generic; -using SixLabors.ImageSharp.Formats.Bmp; +namespace SixLabors.ImageSharp.Formats.OpenExr; -namespace SixLabors.ImageSharp.Formats.OpenExr +/// +/// Registers the image encoders, decoders and mime type detectors for the OpenExr format. +/// +public sealed class ExrFormat : IImageFormat { - /// - /// Registers the image encoders, decoders and mime type detectors for the OpenExr format. - /// - public sealed class ExrFormat : IImageFormat + private ExrFormat() { - private ExrFormat() - { - } + } - /// - /// Gets the current instance. - /// - public static ExrFormat Instance { get; } = new(); + /// + /// Gets the current instance. + /// + public static ExrFormat Instance { get; } = new(); - /// - public string Name => "EXR"; + /// + public string Name => "EXR"; - /// - public string DefaultMimeType => "image/x-exr"; + /// + public string DefaultMimeType => "image/x-exr"; - /// - public IEnumerable MimeTypes => ExrConstants.MimeTypes; + /// + public IEnumerable MimeTypes => ExrConstants.MimeTypes; - /// - public IEnumerable FileExtensions => ExrConstants.FileExtensions; + /// + public IEnumerable FileExtensions => ExrConstants.FileExtensions; - /// - public ExrMetadata CreateDefaultFormatMetadata() => new(); - } + /// + public ExrMetadata CreateDefaultFormatMetadata() => new(); } diff --git a/src/ImageSharp/Formats/OpenExr/ExrHeaderAttributes.cs b/src/ImageSharp/Formats/OpenExr/ExrHeaderAttributes.cs index 76d14311a5..b3b3cdb604 100644 --- a/src/ImageSharp/Formats/OpenExr/ExrHeaderAttributes.cs +++ b/src/ImageSharp/Formats/OpenExr/ExrHeaderAttributes.cs @@ -1,78 +1,76 @@ // Copyright (c) Six Labors. -// Licensed under the Apache License, Version 2.0. +// Licensed under the Six Labors Split License. -using System.Collections.Generic; using SixLabors.ImageSharp.Formats.OpenExr.Compression; -namespace SixLabors.ImageSharp.Formats.OpenExr +namespace SixLabors.ImageSharp.Formats.OpenExr; + +internal class ExrHeaderAttributes { - internal class ExrHeaderAttributes - { - public IList Channels { get; set; } + public IList Channels { get; set; } - public ExrCompressionType? Compression { get; set; } + public ExrCompressionType? Compression { get; set; } - public ExrBox2i? DataWindow { get; set; } + public ExrBox2i? DataWindow { get; set; } - public ExrBox2i? DisplayWindow { get; set; } + public ExrBox2i? DisplayWindow { get; set; } - public ExrLineOrder? LineOrder { get; set; } + public ExrLineOrder? LineOrder { get; set; } - public float? AspectRatio { get; set; } + public float? AspectRatio { get; set; } - public float? ScreenWindowWidth { get; set; } + public float? ScreenWindowWidth { get; set; } - public PointF? ScreenWindowCenter { get; set; } + public PointF? ScreenWindowCenter { get; set; } - public uint? TileXSize { get; set; } + public uint? TileXSize { get; set; } - public uint? TileYSize { get; set; } + public uint? TileYSize { get; set; } - public int? ChunkCount { get; set; } + public int? ChunkCount { get; set; } - public bool IsValid() + public bool IsValid() + { + if (!this.Compression.HasValue) { - if (!this.Compression.HasValue) - { - return false; - } - - if (!this.DataWindow.HasValue) - { - return false; - } - - if (!this.DisplayWindow.HasValue) - { - return false; - } - - if (!this.LineOrder.HasValue) - { - return false; - } - - if (!this.AspectRatio.HasValue) - { - return false; - } - - if (!this.ScreenWindowWidth.HasValue) - { - return false; - } - - if (!this.ScreenWindowCenter.HasValue) - { - return false; - } - - if (this.Channels is null) - { - return false; - } - - return true; + return false; } + + if (!this.DataWindow.HasValue) + { + return false; + } + + if (!this.DisplayWindow.HasValue) + { + return false; + } + + if (!this.LineOrder.HasValue) + { + return false; + } + + if (!this.AspectRatio.HasValue) + { + return false; + } + + if (!this.ScreenWindowWidth.HasValue) + { + return false; + } + + if (!this.ScreenWindowCenter.HasValue) + { + return false; + } + + if (this.Channels is null) + { + return false; + } + + return true; } } diff --git a/src/ImageSharp/Formats/OpenExr/ExrImageDataType.cs b/src/ImageSharp/Formats/OpenExr/ExrImageDataType.cs index d17c3ad61d..4c3e19af81 100644 --- a/src/ImageSharp/Formats/OpenExr/ExrImageDataType.cs +++ b/src/ImageSharp/Formats/OpenExr/ExrImageDataType.cs @@ -1,16 +1,15 @@ // Copyright (c) Six Labors. -// Licensed under the Apache License, Version 2.0. +// Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Formats.OpenExr +namespace SixLabors.ImageSharp.Formats.OpenExr; + +internal enum ExrImageDataType { - internal enum ExrImageDataType - { - Unknown = 0, + Unknown = 0, - Rgb = 1, + Rgb = 1, - Rgba = 2, + Rgba = 2, - Gray = 3, - } + Gray = 3, } diff --git a/src/ImageSharp/Formats/OpenExr/ExrImageFormatDetector.cs b/src/ImageSharp/Formats/OpenExr/ExrImageFormatDetector.cs index 27230306db..c14ab947a9 100644 --- a/src/ImageSharp/Formats/OpenExr/ExrImageFormatDetector.cs +++ b/src/ImageSharp/Formats/OpenExr/ExrImageFormatDetector.cs @@ -1,31 +1,29 @@ // Copyright (c) Six Labors. -// Licensed under the Apache License, Version 2.0. +// Licensed under the Six Labors Split License. -using System; using System.Buffers.Binary; -namespace SixLabors.ImageSharp.Formats.OpenExr +namespace SixLabors.ImageSharp.Formats.OpenExr; + +/// +/// Detects OpenExr file headers. +/// +public sealed class ExrImageFormatDetector : IImageFormatDetector { - /// - /// Detects OpenExr file headers. - /// - public sealed class ExrImageFormatDetector : IImageFormatDetector - { - /// - public int HeaderSize => 4; + /// + public int HeaderSize => 4; - /// - public IImageFormat DetectFormat(ReadOnlySpan header) => this.IsSupportedFileFormat(header) ? ExrFormat.Instance : null; + /// + public IImageFormat DetectFormat(ReadOnlySpan header) => this.IsSupportedFileFormat(header) ? ExrFormat.Instance : null; - private bool IsSupportedFileFormat(ReadOnlySpan header) + private bool IsSupportedFileFormat(ReadOnlySpan header) + { + if (header.Length >= this.HeaderSize) { - if (header.Length >= this.HeaderSize) - { - int fileTypeMarker = BinaryPrimitives.ReadInt32LittleEndian(header); - return fileTypeMarker == ExrConstants.MagickBytes; - } - - return false; + int fileTypeMarker = BinaryPrimitives.ReadInt32LittleEndian(header); + return fileTypeMarker == ExrConstants.MagickBytes; } + + return false; } } diff --git a/src/ImageSharp/Formats/OpenExr/ExrImageType.cs b/src/ImageSharp/Formats/OpenExr/ExrImageType.cs index e18ee363f2..a59aadbd62 100644 --- a/src/ImageSharp/Formats/OpenExr/ExrImageType.cs +++ b/src/ImageSharp/Formats/OpenExr/ExrImageType.cs @@ -1,12 +1,11 @@ // Copyright (c) Six Labors. -// Licensed under the Apache License, Version 2.0. +// Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Formats.OpenExr +namespace SixLabors.ImageSharp.Formats.OpenExr; + +internal enum ExrImageType { - internal enum ExrImageType - { - ScanLine = 0, + ScanLine = 0, - Tiled = 1 - } + Tiled = 1 } diff --git a/src/ImageSharp/Formats/OpenExr/ExrLineOrder.cs b/src/ImageSharp/Formats/OpenExr/ExrLineOrder.cs index 1005414f22..dcf1c37c9e 100644 --- a/src/ImageSharp/Formats/OpenExr/ExrLineOrder.cs +++ b/src/ImageSharp/Formats/OpenExr/ExrLineOrder.cs @@ -1,14 +1,13 @@ // Copyright (c) Six Labors. -// Licensed under the Apache License, Version 2.0. +// Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Formats.OpenExr +namespace SixLabors.ImageSharp.Formats.OpenExr; + +internal enum ExrLineOrder : byte { - internal enum ExrLineOrder : byte - { - IncreasingY = 0, + IncreasingY = 0, - DecreasingY = 1, + DecreasingY = 1, - RandomY = 2 - } + RandomY = 2 } diff --git a/src/ImageSharp/Formats/OpenExr/ExrMetadata.cs b/src/ImageSharp/Formats/OpenExr/ExrMetadata.cs index 7af01a38f0..8b090d6842 100644 --- a/src/ImageSharp/Formats/OpenExr/ExrMetadata.cs +++ b/src/ImageSharp/Formats/OpenExr/ExrMetadata.cs @@ -1,32 +1,31 @@ // Copyright (c) Six Labors. -// Licensed under the Apache License, Version 2.0. +// Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Formats.OpenExr +namespace SixLabors.ImageSharp.Formats.OpenExr; + +/// +/// Provides OpenExr specific metadata information for the image. +/// +public class ExrMetadata : IDeepCloneable { /// - /// Provides OpenExr specific metadata information for the image. + /// Initializes a new instance of the class. /// - public class ExrMetadata : IDeepCloneable + public ExrMetadata() { - /// - /// Initializes a new instance of the class. - /// - public ExrMetadata() - { - } + } - /// - /// Initializes a new instance of the class. - /// - /// The metadata to create an instance from. - private ExrMetadata(ExrMetadata other) => this.PixelType = other.PixelType; + /// + /// Initializes a new instance of the class. + /// + /// The metadata to create an instance from. + private ExrMetadata(ExrMetadata other) => this.PixelType = other.PixelType; - /// - /// Gets or sets the pixel format. - /// - public ExrPixelType PixelType { get; set; } = ExrPixelType.Float; + /// + /// Gets or sets the pixel format. + /// + public ExrPixelType PixelType { get; set; } = ExrPixelType.Float; - /// - public IDeepCloneable DeepClone() => new ExrMetadata(this); - } + /// + public IDeepCloneable DeepClone() => new ExrMetadata(this); } diff --git a/src/ImageSharp/Formats/OpenExr/ExrPixelType.cs b/src/ImageSharp/Formats/OpenExr/ExrPixelType.cs index 2a57afe988..0dd49eaf8f 100644 --- a/src/ImageSharp/Formats/OpenExr/ExrPixelType.cs +++ b/src/ImageSharp/Formats/OpenExr/ExrPixelType.cs @@ -1,26 +1,25 @@ // Copyright (c) Six Labors. -// Licensed under the Apache License, Version 2.0. +// Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Formats.OpenExr +namespace SixLabors.ImageSharp.Formats.OpenExr; + +/// +/// The different pixel formats for a OpenEXR image. +/// +public enum ExrPixelType { /// - /// The different pixel formats for a OpenEXR image. + /// unsigned int (32 bit). /// - public enum ExrPixelType - { - /// - /// unsigned int (32 bit). - /// - UnsignedInt = 0, + UnsignedInt = 0, - /// - /// half (16 bit floating point). - /// - Half = 1, + /// + /// half (16 bit floating point). + /// + Half = 1, - /// - /// float (32 bit floating point). - /// - Float = 2 - } + /// + /// float (32 bit floating point). + /// + Float = 2 } diff --git a/src/ImageSharp/Formats/OpenExr/ExrThrowHelper.cs b/src/ImageSharp/Formats/OpenExr/ExrThrowHelper.cs index b68fb5a2f4..62f9f1b2f4 100644 --- a/src/ImageSharp/Formats/OpenExr/ExrThrowHelper.cs +++ b/src/ImageSharp/Formats/OpenExr/ExrThrowHelper.cs @@ -1,32 +1,30 @@ // Copyright (c) Six Labors. -// Licensed under the Apache License, Version 2.0. +// Licensed under the Six Labors Split License. -using System; using System.Runtime.CompilerServices; -namespace SixLabors.ImageSharp.Formats.OpenExr +namespace SixLabors.ImageSharp.Formats.OpenExr; + +/// +/// Cold path optimizations for throwing exr format based exceptions. +/// +internal static class ExrThrowHelper { - /// - /// Cold path optimizations for throwing exr format based exceptions. - /// - internal static class ExrThrowHelper - { - [MethodImpl(InliningOptions.ColdPath)] - public static Exception NotSupportedDecompressor(string compressionType) => throw new NotSupportedException($"Not supported decoder compression method: {compressionType}"); + [MethodImpl(InliningOptions.ColdPath)] + public static Exception NotSupportedDecompressor(string compressionType) => throw new NotSupportedException($"Not supported decoder compression method: {compressionType}"); - [MethodImpl(MethodImplOptions.NoInlining)] - public static void ThrowInvalidImageContentException(string errorMessage) => throw new InvalidImageContentException(errorMessage); + [MethodImpl(MethodImplOptions.NoInlining)] + public static void ThrowInvalidImageContentException(string errorMessage) => throw new InvalidImageContentException(errorMessage); - [MethodImpl(InliningOptions.ColdPath)] - public static void ThrowNotSupportedVersion() => throw new NotSupportedException("Unsupported EXR version"); + [MethodImpl(InliningOptions.ColdPath)] + public static void ThrowNotSupportedVersion() => throw new NotSupportedException("Unsupported EXR version"); - [MethodImpl(InliningOptions.ColdPath)] - public static void ThrowNotSupported(string msg) => throw new NotSupportedException(msg); + [MethodImpl(InliningOptions.ColdPath)] + public static void ThrowNotSupported(string msg) => throw new NotSupportedException(msg); - [MethodImpl(InliningOptions.ColdPath)] - public static void ThrowInvalidImageHeader() => throw new InvalidImageContentException("Invalid EXR image header"); + [MethodImpl(InliningOptions.ColdPath)] + public static void ThrowInvalidImageHeader() => throw new InvalidImageContentException("Invalid EXR image header"); - [MethodImpl(InliningOptions.ColdPath)] - public static void ThrowInvalidImageHeader(string msg) => throw new InvalidImageContentException(msg); - } + [MethodImpl(InliningOptions.ColdPath)] + public static void ThrowInvalidImageHeader(string msg) => throw new InvalidImageContentException(msg); } diff --git a/src/ImageSharp/Formats/OpenExr/IExrDecoderOptions.cs b/src/ImageSharp/Formats/OpenExr/IExrDecoderOptions.cs deleted file mode 100644 index e61ce85378..0000000000 --- a/src/ImageSharp/Formats/OpenExr/IExrDecoderOptions.cs +++ /dev/null @@ -1,12 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Apache License, Version 2.0. - -namespace SixLabors.ImageSharp.Formats.OpenExr -{ - /// - /// Image decoder options for decoding OpenExr streams. - /// - internal interface IExrDecoderOptions - { - } -} diff --git a/src/ImageSharp/Formats/OpenExr/IExrEncoderOptions.cs b/src/ImageSharp/Formats/OpenExr/IExrEncoderOptions.cs index 938236b692..ba2db62a00 100644 --- a/src/ImageSharp/Formats/OpenExr/IExrEncoderOptions.cs +++ b/src/ImageSharp/Formats/OpenExr/IExrEncoderOptions.cs @@ -1,16 +1,15 @@ // Copyright (c) Six Labors. -// Licensed under the Apache License, Version 2.0. +// Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Formats.OpenExr +namespace SixLabors.ImageSharp.Formats.OpenExr; + +/// +/// Configuration options for use during OpenExr encoding. +/// +internal interface IExrEncoderOptions { /// - /// Configuration options for use during OpenExr encoding. + /// Gets the pixel type of the image. /// - internal interface IExrEncoderOptions - { - /// - /// Gets the pixel type of the image. - /// - ExrPixelType? PixelType { get; } - } + ExrPixelType? PixelType { get; } } diff --git a/src/ImageSharp/Formats/OpenExr/MetadataExtensions.cs b/src/ImageSharp/Formats/OpenExr/MetadataExtensions.cs index 24508f06a3..2af0a57b07 100644 --- a/src/ImageSharp/Formats/OpenExr/MetadataExtensions.cs +++ b/src/ImageSharp/Formats/OpenExr/MetadataExtensions.cs @@ -1,21 +1,20 @@ // Copyright (c) Six Labors. -// Licensed under the Apache License, Version 2.0. +// Licensed under the Six Labors Split License. using SixLabors.ImageSharp.Formats.OpenExr; using SixLabors.ImageSharp.Metadata; -namespace SixLabors.ImageSharp +namespace SixLabors.ImageSharp; + +/// +/// Extension methods for the type. +/// +public static partial class MetadataExtensions { /// - /// Extension methods for the type. + /// Gets the open exr format specific metadata for the image. /// - public static partial class MetadataExtensions - { - /// - /// Gets the open exr format specific metadata for the image. - /// - /// The metadata this method extends. - /// The . - public static ExrMetadata GetExrMetadata(this ImageMetadata metadata) => metadata.GetFormatMetadata(ExrFormat.Instance); - } + /// The metadata this method extends. + /// The . + public static ExrMetadata GetExrMetadata(this ImageMetadata metadata) => metadata.GetFormatMetadata(ExrFormat.Instance); } diff --git a/src/ImageSharp/IO/BufferedReadStream.cs b/src/ImageSharp/IO/BufferedReadStream.cs index 7e233c9655..b6d7f6b3f4 100644 --- a/src/ImageSharp/IO/BufferedReadStream.cs +++ b/src/ImageSharp/IO/BufferedReadStream.cs @@ -2,6 +2,7 @@ // Licensed under the Six Labors Split License. using System.Buffers; +using System.Buffers.Binary; using System.Runtime.CompilerServices; namespace SixLabors.ImageSharp.IO; @@ -153,6 +154,31 @@ internal sealed class BufferedReadStream : Stream } } + /// + /// Reads a float value. + /// + /// The value. + 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"); + } + + bytesToRead -= bytesRead; + offset += bytesRead; + } + + int intValue = BinaryPrimitives.ReadInt32BigEndian(data); + + return Unsafe.As(ref intValue); + } + /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public override int Read(byte[] buffer, int offset, int count) diff --git a/tests/ImageSharp.Tests/Formats/Exr/ImageExtensionsTest.cs b/tests/ImageSharp.Tests/Formats/Exr/ImageExtensionsTest.cs index 9ff506efad..323000bc59 100644 --- a/tests/ImageSharp.Tests/Formats/Exr/ImageExtensionsTest.cs +++ b/tests/ImageSharp.Tests/Formats/Exr/ImageExtensionsTest.cs @@ -1,156 +1,67 @@ // Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. -using System.IO; -using System.Threading.Tasks; using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Formats.OpenExr; using SixLabors.ImageSharp.PixelFormats; -using Xunit; -namespace SixLabors.ImageSharp.Tests.Formats.Exr +namespace SixLabors.ImageSharp.Tests.Formats.Exr; + +[Trait("Format", "Exr")] +public class ImageExtensionsTest { - [Trait("Format", "Exr")] - public class ImageExtensionsTest + [Fact] + public void SaveAsExr_Stream() { - [Fact] - public void SaveAsExr_Path() - { - string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageExtensionsTest)); - string file = Path.Combine(dir, "SaveAsExr_Path.exr"); - - using (var image = new Image(10, 10)) - { - image.SaveAsOpenExr(file); - } - - using (Image.Load(file, out IImageFormat mime)) - { - Assert.Equal("image/x-exr", mime.DefaultMimeType); - } - } + using var memoryStream = new MemoryStream(); - [Fact] - public async Task SaveAsExrAsync_Path() + using (var image = new Image(10, 10)) { - string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageExtensionsTest)); - string file = Path.Combine(dir, "SaveAsExrAsync_Path.exr"); - - using (var image = new Image(10, 10)) - { - await image.SaveAsOpenExrAsync(file); - } - - using (Image.Load(file, out IImageFormat mime)) - { - Assert.Equal("image/x-exr", mime.DefaultMimeType); - } + image.SaveAsOpenExr(memoryStream, new ExrEncoder()); } - [Fact] - public void SaveAsExr_Path_Encoder() - { - string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageExtensions)); - string file = Path.Combine(dir, "SaveAsExr_Path_Encoder.exr"); - - using (var image = new Image(10, 10)) - { - image.SaveAsOpenExr(file, new ExrEncoder()); - } + memoryStream.Position = 0; - using (Image.Load(file, out IImageFormat mime)) - { - Assert.Equal("image/x-exr", mime.DefaultMimeType); - } - } - - [Fact] - public async Task SaveAsExrAsync_Path_Encoder() + using (Image.Load(memoryStream, out IImageFormat mime)) { - string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageExtensions)); - string file = Path.Combine(dir, "SaveAsExrAsync_Path_Encoder.tiff"); - - using (var image = new Image(10, 10)) - { - await image.SaveAsOpenExrAsync(file, new ExrEncoder()); - } - - using (Image.Load(file, out IImageFormat mime)) - { - Assert.Equal("image/x-exr", mime.DefaultMimeType); - } + Assert.Equal("image/x-exr", mime.DefaultMimeType); } + } - [Fact] - public void SaveAsExr_Stream() - { - using var memoryStream = new MemoryStream(); - - using (var image = new Image(10, 10)) - { - image.SaveAsOpenExr(memoryStream); - } - - memoryStream.Position = 0; - - using (Image.Load(memoryStream, out IImageFormat mime)) - { - Assert.Equal("image/x-exr", mime.DefaultMimeType); - } - } + [Fact] + public void SaveAsExr_Stream_Encoder() + { + using var memoryStream = new MemoryStream(); - [Fact] - public async Task SaveAsExrAsync_StreamAsync() + using (var image = new Image(10, 10)) { - using var memoryStream = new MemoryStream(); - - using (var image = new Image(10, 10)) - { - await image.SaveAsOpenExrAsync(memoryStream); - } - - memoryStream.Position = 0; - - using (Image.Load(memoryStream, out IImageFormat mime)) - { - Assert.Equal("image/x-exr", mime.DefaultMimeType); - } + image.SaveAsOpenExr(memoryStream, new ExrEncoder()); } - [Fact] - public void SaveAsExr_Stream_Encoder() - { - using var memoryStream = new MemoryStream(); - - using (var image = new Image(10, 10)) - { - image.SaveAsOpenExr(memoryStream, new ExrEncoder()); - } - - memoryStream.Position = 0; + memoryStream.Position = 0; - using (Image.Load(memoryStream, out IImageFormat mime)) - { - Assert.Equal("image/x-exr", mime.DefaultMimeType); - } + using (Image.Load(memoryStream, out IImageFormat mime)) + { + Assert.Equal("image/x-exr", mime.DefaultMimeType); } + } - [Fact] - public async Task SaveAsExrAsync_Stream_Encoder() - { - using var memoryStream = new MemoryStream(); + [Fact] + public async Task SaveAsExrAsync_Stream_Encoder() + { + using var memoryStream = new MemoryStream(); - using (var image = new Image(10, 10)) - { - await image.SaveAsOpenExrAsync(memoryStream, new ExrEncoder()); - } + using (var image = new Image(10, 10)) + { + await image.SaveAsOpenExrAsync(memoryStream, new ExrEncoder()); + } - memoryStream.Position = 0; + memoryStream.Position = 0; - using (Image.Load(memoryStream, out IImageFormat mime)) - { - Assert.Equal("image/x-exr", mime.DefaultMimeType); - } + using (Image.Load(memoryStream, out IImageFormat mime)) + { + Assert.Equal("image/x-exr", mime.DefaultMimeType); } } } + diff --git a/tests/ImageSharp.Tests/Formats/GeneralFormatTests.cs b/tests/ImageSharp.Tests/Formats/GeneralFormatTests.cs index 0fa7f1684a..fa5e4bdeff 100644 --- a/tests/ImageSharp.Tests/Formats/GeneralFormatTests.cs +++ b/tests/ImageSharp.Tests/Formats/GeneralFormatTests.cs @@ -144,11 +144,6 @@ public class GeneralFormatTests { image.SaveAsTiff(output); } - - using (FileStream output = File.OpenWrite(Path.Combine(path, $"{file.FileNameWithoutExtension}.exr"))) - { - image.SaveAsOpenExr(output); - } } }