diff --git a/src/ImageSharp/Configuration.cs b/src/ImageSharp/Configuration.cs
index 7f4528d9d7..760b37dc17 100644
--- a/src/ImageSharp/Configuration.cs
+++ b/src/ImageSharp/Configuration.cs
@@ -213,6 +213,7 @@ public sealed class Configuration
/// .
/// .
/// .
+ /// .
///
/// The default configuration of .
internal static Configuration CreateDefaultInstance() => new(
@@ -223,5 +224,6 @@ public sealed class Configuration
new PbmConfigurationModule(),
new TgaConfigurationModule(),
new TiffConfigurationModule(),
- new WebpConfigurationModule());
+ new WebpConfigurationModule(),
+ new ExrConfigurationModule());
}
diff --git a/src/ImageSharp/Formats/Bmp/BmpFormat.cs b/src/ImageSharp/Formats/Bmp/BmpFormat.cs
index a67b06cb88..d67a04d26a 100644
--- a/src/ImageSharp/Formats/Bmp/BmpFormat.cs
+++ b/src/ImageSharp/Formats/Bmp/BmpFormat.cs
@@ -30,5 +30,5 @@ public sealed class BmpFormat : IImageFormat
public IEnumerable FileExtensions => BmpConstants.FileExtensions;
///
- public BmpMetadata CreateDefaultFormatMetadata() => new();
+ public BmpMetadata CreateDefaultFormatMetadata() => new BmpMetadata();
}
diff --git a/src/ImageSharp/Formats/ImageDecoderUtilities.cs b/src/ImageSharp/Formats/ImageDecoderUtilities.cs
index e2c61c8eb3..3ed64b6a81 100644
--- a/src/ImageSharp/Formats/ImageDecoderUtilities.cs
+++ b/src/ImageSharp/Formats/ImageDecoderUtilities.cs
@@ -8,17 +8,56 @@ using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Formats;
///
-/// Utility methods for .
+/// Utility methods for .
///
internal static class ImageDecoderUtilities
{
- internal static ImageInfo Identify(
+ ///
+ /// Performs a resize operation against the decoded image. If the target size is not set, or the image size
+ /// already matches the target size, the image is untouched.
+ ///
+ /// The decoder options.
+ /// The decoded image.
+ public static void Resize(DecoderOptions options, Image image)
+ {
+ if (ShouldResize(options, image))
+ {
+ ResizeOptions resizeOptions = new()
+ {
+ Size = options.TargetSize.Value,
+ Sampler = options.Sampler,
+ Mode = ResizeMode.Max
+ };
+
+ image.Mutate(x => x.Resize(resizeOptions));
+ }
+ }
+
+ ///
+ /// Determines whether the decoded image should be resized.
+ ///
+ /// The decoder options.
+ /// The decoded image.
+ /// if the image should be resized, otherwise; .
+ private static bool ShouldResize(DecoderOptions options, Image image)
+ {
+ if (options.TargetSize is null)
+ {
+ return false;
+ }
+
+ Size targetSize = options.TargetSize.Value;
+ Size currentSize = image.Size();
+ return currentSize.Width != targetSize.Width && currentSize.Height != targetSize.Height;
+ }
+
+ internal static IImageInfo Identify(
this IImageDecoderInternals decoder,
Configuration configuration,
Stream stream,
CancellationToken cancellationToken)
{
- using BufferedReadStream bufferedReadStream = new(configuration, stream, cancellationToken);
+ using var bufferedReadStream = new BufferedReadStream(configuration, stream);
try
{
@@ -60,10 +99,6 @@ internal static class ImageDecoderUtilities
{
throw largeImageExceptionFactory(ex, decoder.Dimensions);
}
- catch (Exception)
- {
- throw;
- }
}
private static InvalidImageContentException DefaultLargeImageExceptionFactory(
diff --git a/src/ImageSharp/Formats/ImageExtensions.Save.cs b/src/ImageSharp/Formats/ImageExtensions.Save.cs
index 0270114c8c..bb450ba658 100644
--- a/src/ImageSharp/Formats/ImageExtensions.Save.cs
+++ b/src/ImageSharp/Formats/ImageExtensions.Save.cs
@@ -837,4 +837,36 @@ public static partial class ImageExtensions
encoder ?? source.GetConfiguration().ImageFormatsManager.GetEncoder(TiffFormat.Instance),
cancellationToken);
+ encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(TiffFormat.Instance),
+ cancellationToken);
+
+ ///
+ /// Saves the image to the given stream with the Open Exr format.
+ ///
+ /// The image this method extends.
+ /// The stream to save the image to.
+ /// The encoder to save the image with.
+ /// Thrown if the stream is null.
+ /// A representing the asynchronous operation.
+ public static void SaveAsOpenExr(this Image source, Stream stream, ExrEncoder encoder)
+ => source.Save(
+ stream,
+ encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(ExrFormat.Instance));
+
+ ///
+ /// Saves the image to the given stream with the Open Exr format.
+ ///
+ /// The image this method extends.
+ /// The stream to save the image to.
+ /// The encoder to save the image with.
+ /// The token to monitor for cancellation requests.
+ /// Thrown if the stream is null.
+ /// A representing the asynchronous operation.
+ public static Task SaveAsOpenExrAsync(this Image source, Stream stream, ExrEncoder encoder, CancellationToken cancellationToken = default) =>
+ source.SaveAsync(
+ stream,
+ encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(ExrFormat.Instance),
+ cancellationToken);
+
+
}
diff --git a/src/ImageSharp/Formats/OpenExr/Compression/Compressors/NoneExrCompression.cs b/src/ImageSharp/Formats/OpenExr/Compression/Compressors/NoneExrCompression.cs
deleted file mode 100644
index d4eaf67ebb..0000000000
--- a/src/ImageSharp/Formats/OpenExr/Compression/Compressors/NoneExrCompression.cs
+++ /dev/null
@@ -1,24 +0,0 @@
-// Copyright (c) Six Labors.
-// Licensed under the Apache License, Version 2.0.
-
-using System;
-using SixLabors.ImageSharp.IO;
-using SixLabors.ImageSharp.Memory;
-
-namespace SixLabors.ImageSharp.Formats.OpenExr.Compression.Compressors
-{
- internal class NoneExrCompression : ExrBaseDecompressor
- {
- 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));
-
- 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
deleted file mode 100644
index de1bf52328..0000000000
--- a/src/ImageSharp/Formats/OpenExr/Compression/Compressors/RunLengthCompression.cs
+++ /dev/null
@@ -1,94 +0,0 @@
-// Copyright (c) Six Labors.
-// Licensed under the Apache License, Version 2.0.
-
-using System;
-using System.Buffers;
-using SixLabors.ImageSharp.IO;
-using SixLabors.ImageSharp.Memory;
-
-namespace SixLabors.ImageSharp.Formats.OpenExr.Compression.Compressors
-{
- internal class RunLengthCompression : ExrBaseDecompressor
- {
- private readonly IMemoryOwner tmpBuffer;
-
- 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)
- {
- 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)
- {
- 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;
- }
- else
- {
- 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);
- }
-
- private static byte ReadNextByte(BufferedReadStream stream)
- {
- int nextByte = stream.ReadByte();
- if (nextByte == -1)
- {
- ExrThrowHelper.ThrowInvalidImageContentException("Not enough data to decompress RLE image!");
- }
-
- 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
deleted file mode 100644
index 65a2ea3485..0000000000
--- a/src/ImageSharp/Formats/OpenExr/Compression/Compressors/ZipExrCompression.cs
+++ /dev/null
@@ -1,58 +0,0 @@
-// Copyright (c) Six Labors.
-// Licensed under the Apache License, Version 2.0.
-
-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
-{
- internal class ZipExrCompression : ExrBaseDecompressor
- {
- private readonly IMemoryOwner tmpBuffer;
-
- 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)
- {
- 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)
- {
- break;
- }
-
- totalRead += bytesRead;
- }
-
- if (totalRead == 0)
- {
- ExrThrowHelper.ThrowInvalidImageContentException("Could not read zip compressed image data!");
- }
-
- 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/Decompressors/B44Compression.cs b/src/ImageSharp/Formats/OpenExr/Compression/Decompressors/B44Compression.cs
new file mode 100644
index 0000000000..55e9003bc1
--- /dev/null
+++ b/src/ImageSharp/Formats/OpenExr/Compression/Decompressors/B44Compression.cs
@@ -0,0 +1,191 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+
+using System.Buffers;
+using System.Runtime.InteropServices;
+using SixLabors.ImageSharp.IO;
+using SixLabors.ImageSharp.Memory;
+
+namespace SixLabors.ImageSharp.Formats.OpenExr.Compression.Decompressors;
+
+internal class B44Compression : ExrBaseDecompressor
+{
+ private readonly int width;
+
+ private readonly int height;
+
+ private readonly uint rowsPerBlock;
+
+ private readonly int channelCount;
+
+ private byte[] scratch = new byte[14];
+
+ private ushort[] s = new ushort[16];
+
+ private IMemoryOwner tmpBuffer;
+
+ public B44Compression(MemoryAllocator allocator, uint uncompressedBytes, int width, int height, uint rowsPerBlock, int channelCount)
+ : base(allocator, uncompressedBytes)
+ {
+ this.width = width;
+ this.height = height;
+ this.rowsPerBlock = rowsPerBlock;
+ this.channelCount = channelCount;
+ this.tmpBuffer = allocator.Allocate((int)(width * rowsPerBlock * channelCount));
+ }
+
+ public override void Decompress(BufferedReadStream stream, uint compressedBytes, Span buffer)
+ {
+ Span outputBuffer = MemoryMarshal.Cast(buffer);
+ Span decompressed = this.tmpBuffer.GetSpan();
+ int outputOffset = 0;
+ int bytesLeft = (int)compressedBytes;
+ for (int i = 0; i < this.channelCount && bytesLeft > 0; i++)
+ {
+ for (int y = 0; y < this.rowsPerBlock; y += 4)
+ {
+ Span row0 = decompressed.Slice(outputOffset, this.width);
+ outputOffset += this.width;
+ Span row1 = decompressed.Slice(outputOffset, this.width);
+ outputOffset += this.width;
+ Span row2 = decompressed.Slice(outputOffset, this.width);
+ outputOffset += this.width;
+ Span row3 = decompressed.Slice(outputOffset, this.width);
+ outputOffset += this.width;
+
+ int rowOffset = 0;
+ for (int x = 0; x < this.width && bytesLeft > 0; x += 4)
+ {
+ int bytesRead = stream.Read(this.scratch, 0, 3);
+ if (bytesRead == 0)
+ {
+ ExrThrowHelper.ThrowInvalidImageContentException("Could not read enough data from the stream");
+ }
+
+ if (this.scratch[2] >= 13 << 2)
+ {
+ Unpack3(this.scratch, this.s);
+ bytesLeft -= 3;
+ }
+ else
+ {
+ bytesRead = stream.Read(this.scratch, 3, 11);
+ if (bytesRead == 0)
+ {
+ ExrThrowHelper.ThrowInvalidImageContentException("Could not read enough data from the stream");
+ }
+
+ Unpack14(this.scratch, this.s);
+ bytesLeft -= 14;
+ }
+
+ int n = x + 3 < this.width ? 4 : this.width - x;
+ if (y + 3 < this.rowsPerBlock)
+ {
+ this.s.AsSpan(0, n).CopyTo(row0.Slice(rowOffset));
+ this.s.AsSpan(4, n).CopyTo(row1.Slice(rowOffset));
+ this.s.AsSpan(8, n).CopyTo(row2.Slice(rowOffset));
+ this.s.AsSpan(12, n).CopyTo(row3.Slice(rowOffset));
+ }
+ else
+ {
+ this.s.AsSpan(0, n).CopyTo(row0.Slice(rowOffset));
+ if (y + 1 < this.rowsPerBlock)
+ {
+ this.s.AsSpan(4, n).CopyTo(row1.Slice(rowOffset));
+ }
+
+ if (y + 2 < this.rowsPerBlock)
+ {
+ this.s.AsSpan(8, n).CopyTo(row2.Slice(rowOffset));
+ }
+ }
+
+ rowOffset += 4;
+ }
+
+ if (bytesLeft <= 0)
+ {
+ break;
+ }
+ }
+ }
+
+ // Rearrange the decompressed data such that the data for each scan line form a contiguous block.
+ int offsetDecompressed = 0;
+ int offsetOutput = 0;
+ int blockSize = (int)(this.width * this.rowsPerBlock);
+ for (int y = 0; y < this.rowsPerBlock; y++)
+ {
+ for (int i = 0; i < this.channelCount; i++)
+ {
+ decompressed.Slice(offsetDecompressed + (i * blockSize), this.width).CopyTo(outputBuffer.Slice(offsetOutput));
+ offsetOutput += this.width;
+ }
+
+ offsetDecompressed += this.width;
+ }
+ }
+
+ // Unpack a 14-byte block into 4 by 4 16-bit pixels.
+ private static void Unpack14(Span b, Span s)
+ {
+ s[0] = (ushort)((b[0] << 8) | b[1]);
+
+ ushort shift = (ushort)(b[2] >> 2);
+ ushort bias = (ushort)(0x20u << shift);
+
+ s[4] = (ushort)(s[0] + ((((b[2] << 4) | (b[3] >> 4)) & 0x3fu) << shift) - bias);
+ s[8] = (ushort)(s[4] + ((((b[3] << 2) | (b[4] >> 6)) & 0x3fu) << shift) - bias);
+ s[12] = (ushort)(s[8] + ((b[4] & 0x3fu) << shift) - bias);
+
+ s[1] = (ushort)(s[0] + ((uint)(b[5] >> 2) << shift) - bias);
+ s[5] = (ushort)(s[4] + ((((b[5] << 4) | (b[6] >> 4)) & 0x3fu) << shift) - bias);
+ s[9] = (ushort)(s[8] + ((((b[6] << 2) | (b[7] >> 6)) & 0x3fu) << shift) - bias);
+ s[13] = (ushort)(s[12] + ((b[7] & 0x3fu) << shift) - bias);
+
+ s[2] = (ushort)(s[1] + ((uint)(b[8] >> 2) << shift) - bias);
+ s[6] = (ushort)(s[5] + ((((b[8] << 4) | (b[9] >> 4)) & 0x3fu) << shift) - bias);
+ s[10] = (ushort)(s[9] + ((((b[9] << 2) | (b[10] >> 6)) & 0x3fu) << shift) - bias);
+ s[14] = (ushort)(s[13] + ((b[10] & 0x3fu) << shift) - bias);
+
+ s[3] = (ushort)(s[2] + ((uint)(b[11] >> 2) << shift) - bias);
+ s[7] = (ushort)(s[6] + ((((b[11] << 4) | (b[12] >> 4)) & 0x3fu) << shift) - bias);
+ s[11] = (ushort)(s[10] + ((((b[12] << 2) | (b[13] >> 6)) & 0x3fu) << shift) - bias);
+ s[15] = (ushort)(s[14] + ((b[13] & 0x3fu) << shift) - bias);
+
+ for (int i = 0; i < 16; ++i)
+ {
+ if ((s[i] & 0x8000) != 0)
+ {
+ s[i] &= 0x7fff;
+ }
+ else
+ {
+ s[i] = (ushort)~s[i];
+ }
+ }
+ }
+
+ // Unpack a 3-byte block into 4 by 4 identical 16-bit pixels.
+ private static void Unpack3(Span b, Span s)
+ {
+ s[0] = (ushort)((b[0] << 8) | b[1]);
+
+ if ((s[0] & 0x8000) != 0)
+ {
+ s[0] &= 0x7fff;
+ }
+ else
+ {
+ s[0] = (ushort)~s[0];
+ }
+
+ for (int i = 1; i < 16; ++i)
+ {
+ s[i] = s[0];
+ }
+ }
+
+ protected override void Dispose(bool disposing) => this.tmpBuffer.Dispose();
+}
diff --git a/src/ImageSharp/Formats/OpenExr/Compression/Decompressors/NoneExrCompression.cs b/src/ImageSharp/Formats/OpenExr/Compression/Decompressors/NoneExrCompression.cs
new file mode 100644
index 0000000000..f5f16a0fd4
--- /dev/null
+++ b/src/ImageSharp/Formats/OpenExr/Compression/Decompressors/NoneExrCompression.cs
@@ -0,0 +1,22 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+
+using SixLabors.ImageSharp.IO;
+using SixLabors.ImageSharp.Memory;
+
+namespace SixLabors.ImageSharp.Formats.OpenExr.Compression.Decompressors;
+
+internal class NoneExrCompression : ExrBaseDecompressor
+{
+ 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));
+
+ protected override void Dispose(bool disposing)
+ {
+ }
+}
diff --git a/src/ImageSharp/Formats/OpenExr/Compression/Decompressors/RunLengthCompression.cs b/src/ImageSharp/Formats/OpenExr/Compression/Decompressors/RunLengthCompression.cs
new file mode 100644
index 0000000000..0838dc1dcf
--- /dev/null
+++ b/src/ImageSharp/Formats/OpenExr/Compression/Decompressors/RunLengthCompression.cs
@@ -0,0 +1,82 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+
+using System.Buffers;
+using SixLabors.ImageSharp.IO;
+using SixLabors.ImageSharp.Memory;
+
+namespace SixLabors.ImageSharp.Formats.OpenExr.Compression.Decompressors;
+
+internal class RunLengthCompression : ExrBaseDecompressor
+{
+ private readonly IMemoryOwner tmpBuffer;
+
+ private readonly ushort[] s = new ushort[16];
+
+ 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)
+ {
+ 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)
+ {
+ int count = -input;
+ compressedBytes -= (uint)(count + 1);
+
+ if ((maxLength -= count) < 0)
+ {
+ return;
+ }
+
+ for (int i = 0; i < count; i++)
+ {
+ uncompressed[offset + i] = ReadNextByte(stream);
+ }
+
+ offset += count;
+ }
+ else
+ {
+ int count = input;
+ byte value = ReadNextByte(stream);
+ compressedBytes -= 2;
+
+ if ((maxLength -= count + 1) < 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);
+ }
+
+ private static byte ReadNextByte(BufferedReadStream stream)
+ {
+ int nextByte = stream.ReadByte();
+ if (nextByte == -1)
+ {
+ ExrThrowHelper.ThrowInvalidImageContentException("Not enough data to decompress RLE image!");
+ }
+
+ return (byte)nextByte;
+ }
+
+ protected override void Dispose(bool disposing) => this.tmpBuffer.Dispose();
+}
diff --git a/src/ImageSharp/Formats/OpenExr/Compression/Decompressors/ZipExrCompression.cs b/src/ImageSharp/Formats/OpenExr/Compression/Decompressors/ZipExrCompression.cs
new file mode 100644
index 0000000000..e315af8e8f
--- /dev/null
+++ b/src/ImageSharp/Formats/OpenExr/Compression/Decompressors/ZipExrCompression.cs
@@ -0,0 +1,56 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+
+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.Decompressors;
+
+internal class ZipExrCompression : ExrBaseDecompressor
+{
+ private readonly IMemoryOwner tmpBuffer;
+
+ 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)
+ {
+ Span uncompressed = this.tmpBuffer.GetSpan();
+
+ long pos = stream.Position;
+ using ZlibInflateStream inflateStream = new(
+ stream,
+ () =>
+ {
+ int left = (int)(compressedBytes - (stream.Position - pos));
+ return left > 0 ? left : 0;
+ });
+ inflateStream.AllocateNewBytes((int)this.UncompressedBytes, true);
+ DeflateStream dataStream = inflateStream.CompressedStream;
+
+ int totalRead = 0;
+ while (totalRead < buffer.Length)
+ {
+ int bytesRead = dataStream.Read(uncompressed, totalRead, buffer.Length - totalRead);
+ if (bytesRead <= 0)
+ {
+ break;
+ }
+
+ totalRead += bytesRead;
+ }
+
+ if (totalRead == 0)
+ {
+ ExrThrowHelper.ThrowInvalidImageContentException("Could not read zip compressed image data!");
+ }
+
+ 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..538231548c 100644
--- a/src/ImageSharp/Formats/OpenExr/Compression/ExrDecompressorFactory.cs
+++ b/src/ImageSharp/Formats/OpenExr/Compression/ExrDecompressorFactory.cs
@@ -1,28 +1,29 @@
// 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.Formats.OpenExr.Compression.Decompressors;
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, int width, int height, uint rowsPerBlock, int channelCount)
{
- 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);
+ case ExrCompressionType.B44:
+ return new B44Compression(memoryAllocator, uncompressedBytes, width, height, rowsPerBlock, channelCount);
+ default:
+ throw ExrThrowHelper.NotSupportedDecompressor(nameof(method));
}
}
}
diff --git a/src/ImageSharp/Formats/OpenExr/ExrAttribute.cs b/src/ImageSharp/Formats/OpenExr/ExrAttribute.cs
index 66423f50dc..0d6ca86cc9 100644
--- a/src/ImageSharp/Formats/OpenExr/ExrAttribute.cs
+++ b/src/ImageSharp/Formats/OpenExr/ExrAttribute.cs
@@ -1,26 +1,25 @@
// 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 2baa60b49b..3e554fb74d 100644
--- a/src/ImageSharp/Formats/OpenExr/ExrDecoderCore.cs
+++ b/src/ImageSharp/Formats/OpenExr/ExrDecoderCore.cs
@@ -1,881 +1,787 @@
// 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];
+ private readonly byte[] buffer = new byte[8];
- ///
- /// Used for allocating memory during processing operations.
- ///
- private readonly MemoryAllocator memoryAllocator;
+ ///
+ /// Used for allocating memory during processing operations.
+ ///
+ private readonly MemoryAllocator memoryAllocator;
- ///
- /// The bitmap decoder options.
- ///
- private readonly IExrDecoderOptions options;
+ ///
+ /// The global configuration.
+ ///
+ private readonly Configuration configuration;
- ///
- /// The metadata.
- ///
- private ImageMetadata metadata;
+ ///
+ /// The metadata.
+ ///
+ private ImageMetadata metadata;
- ///
- /// 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;
- }
+ ///
+ /// 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;
+ }
- ///
- public Configuration Configuration { get; }
+ ///
+ public DecoderOptions Options { get; }
- ///
- /// Gets the dimensions of the image.
- ///
- public Size Dimensions => new(this.Width, this.Height);
+ ///
+ /// Gets the dimensions of the image.
+ ///
+ public Size Dimensions => new(this.Width, this.Height);
- private int Width { get; set; }
+ ///
+ /// Gets or sets the image width.
+ ///
+ private int Width { get; set; }
- private int Height { get; set; }
+ ///
+ /// Gets or sets the image height.
+ ///
+ private int Height { get; set; }
- private IList Channels { get; set; }
+ ///
+ /// Gets or sets the image channel info's.
+ ///
+ private IList Channels { get; set; }
- private ExrCompressionType Compression { get; set; }
+ ///
+ /// Gets or sets the compression method.
+ ///
+ private ExrCompressionType Compression { get; set; }
- private ExrImageDataType ImageDataType { get; set; }
+ private ExrImageDataType ImageDataType { get; set; }
- private ExrImageType ImageType { get; set; }
+ private ExrImageType ImageType { get; set; }
- private ExrHeaderAttributes HeaderAttributes { get; set; }
+ private ExrHeaderAttributes HeaderAttributes { get; set; }
- ///
- public Image Decode(BufferedReadStream stream, CancellationToken cancellationToken)
- where TPixel : unmanaged, IPixel
+ ///
+ public Image Decode(BufferedReadStream stream, CancellationToken cancellationToken)
+ where TPixel : unmanaged, IPixel
+ {
+ this.ReadExrHeader(stream);
+ if (!this.IsSupportedCompression())
{
- this.ReadExrHeader(stream);
- if (!this.IsSupportedCompression())
- {
- ExrThrowHelper.ThrowNotSupported($"Compression {this.Compression} is not yet supported");
- }
-
- ExrPixelType pixelType = this.ValidateChannels();
- this.ReadImageDataType();
+ ExrThrowHelper.ThrowNotSupported($"Compression {this.Compression} is not yet supported");
+ }
- var image = new Image(this.Configuration, this.Width, this.Height, this.metadata);
- Buffer2D pixels = image.GetRootFramePixelBuffer();
+ ExrPixelType pixelType = this.ValidateChannels();
+ this.ReadImageDataType();
- switch (pixelType)
- {
- case ExrPixelType.Half:
- case ExrPixelType.Float:
- if (this.ImageType is ExrImageType.ScanLine)
- {
- this.DecodeFloatingPointPixelData(stream, pixels);
- }
- else
- {
- this.DecodeTiledFloatingPointPixelData(stream, pixels);
- }
-
- break;
- case ExrPixelType.UnsignedInt:
- this.DecodeUnsignedIntPixelData(stream, pixels);
- break;
- default:
- ExrThrowHelper.ThrowNotSupported("Pixel type is not supported");
- break;
- }
+ Image image = new(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;
+ int channelCount = this.Channels.Count;
+
+ 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, width, height, rowsPerBlock, channelCount);
+
+ 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 += ReadFloatChannelData(stream, channel, decompressedPixelData.Slice(offset), redPixelData, greenPixelData, bluePixelData, alphaPixelData, width);
}
- stream.Position = nextRowOffsetPosition;
+ for (int x = 0; x < width; x++)
+ {
+ HalfVector4 pixelValue = new(redPixelData[x], greenPixelData[x], bluePixelData[x], hasAlpha ? alphaPixelData[x] : 1.0f);
+ color.FromVector4(pixelValue.ToVector4());
+ pixelRow[x] = color;
+ }
}
+
+ 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;
+ int channelCount = this.Channels.Count;
+
+ 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, width, height, rowsPerBlock, channelCount);
+
+ 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);
- }
-
- 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;
- }
+ ExrChannelInfo channel = this.Channels[channelIdx];
+ offset += this.ReadUnsignedIntChannelData(stream, channel, decompressedPixelData, redPixelData, greenPixelData, bluePixelData, alphaPixelData, width);
}
- }
- }
- private void DecodeTiledFloatingPointPixelData(BufferedReadStream stream, Buffer2D pixels)
- where TPixel : unmanaged, IPixel
- {
- if (!this.HeaderAttributes.ChunkCount.HasValue)
- {
- ExrThrowHelper.ThrowInvalidImageContentException("Missing chunk count in tiled image");
- }
+ stream.Position = nextRowOffsetPosition;
- int chunks = this.HeaderAttributes.ChunkCount.Value;
-
- bool hasAlpha = this.HasAlpha();
- uint tileWidth = (uint)this.HeaderAttributes.TileXSize;
- uint tileHeight = (uint)this.HeaderAttributes.TileYSize;
- int width = this.Width;
- int height = this.Height;
- uint bytesPerRow = this.CalculateBytesPerRow((uint)this.Width);
- uint bytesPerBlockRow = this.CalculateBytesPerRow(tileWidth);
- uint rowsPerBlock = this.RowsPerBlock();
- uint bytesPerBlock = bytesPerBlockRow * rowsPerBlock;
- uint columnsPerBlock = (uint)(this.Height / tileHeight);
- uint tilesPerScanline = (uint)((this.Width + tileWidth - 1) / tileWidth);
-
- using IMemoryOwner rowBuffer = this.memoryAllocator.Allocate((int)(tileWidth * 4));
- using IMemoryOwner decompressedPixelDataBuffer = this.memoryAllocator.Allocate((width * height) / chunks);
- Span decompressedPixelData = decompressedPixelDataBuffer.GetSpan();
- Span redPixelData = rowBuffer.GetSpan().Slice(0, (int)tileWidth);
- Span greenPixelData = rowBuffer.GetSpan().Slice((int)tileWidth, (int)tileWidth);
- Span bluePixelData = rowBuffer.GetSpan().Slice((int)(tileWidth * 2), (int)tileWidth);
- Span alphaPixelData = rowBuffer.GetSpan().Slice((int)(tileWidth * 3), (int)tileWidth);
-
- using ExrBaseDecompressor decompressor = ExrDecompressorFactory.Create(this.Compression, this.memoryAllocator, bytesPerBlock);
-
- uint y = 0;
- uint x = 0;
- TPixel color = default;
- for (int chunk = 0; chunk < chunks; chunk++)
- {
- ulong dataOffset = this.ReadUnsignedLong(stream);
- long nextOffsetPosition = stream.Position;
- stream.Position = (long)dataOffset;
- uint tileX = this.ReadUnsignedInteger(stream);
- uint tileY = this.ReadUnsignedInteger(stream);
- uint levelX = this.ReadUnsignedInteger(stream);
- uint levelY = this.ReadUnsignedInteger(stream);
-
- uint compressedBytesCount = this.ReadUnsignedInteger(stream);
- decompressor.Decompress(stream, compressedBytesCount, decompressedPixelData);
-
- int offset = 0;
- for (y = 0; y < height; y += rowsPerBlock)
+ for (int x = 0; x < width; x++)
{
- for (x = 0; x < tilesPerScanline; x++)
- {
- uint rowStartIndex = tileHeight * tileY;
- uint columnStartIndex = tileWidth * tileX;
- 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++)
- {
- ExrChannelInfo channel = this.Channels[channelIdx];
- offset += this.ReadFloatChannelData(
- stream,
- channel,
- decompressedPixelData.Slice(offset),
- redPixelData,
- greenPixelData,
- bluePixelData,
- alphaPixelData,
- (int)tileWidth);
- }
-
- uint columnEndIdx = (uint)Math.Min(columnStartIndex + tileWidth, width);
- int channelOffset = 0;
- for (int pixelRowIdx = (int)columnStartIndex; pixelRowIdx < columnEndIdx; pixelRowIdx++)
- {
- var pixelValue = new HalfVector4(redPixelData[channelOffset],
- greenPixelData[channelOffset], bluePixelData[channelOffset],
- hasAlpha ? alphaPixelData[channelOffset] : 1.0f);
- color.FromVector4(pixelValue.ToVector4());
- pixelRow[pixelRowIdx] = color;
- channelOffset++;
- }
- }
- }
+ Rgba128 pixelValue = new(redPixelData[x], greenPixelData[x], bluePixelData[x], hasAlpha ? alphaPixelData[x] : uint.MaxValue);
+ color.FromVector4(pixelValue.ToVector4());
+ pixelRow[x] = color;
}
-
- stream.Position = nextOffsetPosition;
}
}
+ }
- private int ReadFloatChannelData(
- BufferedReadStream stream,
- ExrChannelInfo channel,
- Span decompressedPixelData,
- Span redPixelData,
- Span greenPixelData,
- Span bluePixelData,
- Span alphaPixelData,
- int width)
+ private static 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 ReadChannelData(channel, decompressedPixelData, redPixelData, width);
- case ExrConstants.ChannelNames.Blue:
- return this.ReadChannelData(channel, decompressedPixelData, bluePixelData, width);
+ case ExrConstants.ChannelNames.Blue:
+ return ReadChannelData(channel, decompressedPixelData, bluePixelData, width);
- case ExrConstants.ChannelNames.Green:
- return this.ReadChannelData(channel, decompressedPixelData, greenPixelData, width);
+ case ExrConstants.ChannelNames.Green:
+ return ReadChannelData(channel, decompressedPixelData, greenPixelData, width);
- case ExrConstants.ChannelNames.Alpha:
- return this.ReadChannelData(channel, decompressedPixelData, alphaPixelData, width);
+ case ExrConstants.ChannelNames.Alpha:
+ return 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 = 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 ReadChannelData(channel, decompressedPixelData, redPixelData, width);
- case ExrConstants.ChannelNames.Blue:
- return this.ReadChannelData(channel, decompressedPixelData, bluePixelData, width);
+ case ExrConstants.ChannelNames.Blue:
+ return ReadChannelData(channel, decompressedPixelData, bluePixelData, width);
- case ExrConstants.ChannelNames.Green:
- return this.ReadChannelData(channel, decompressedPixelData, greenPixelData, width);
+ case ExrConstants.ChannelNames.Green:
+ return ReadChannelData(channel, decompressedPixelData, greenPixelData, width);
- case ExrConstants.ChannelNames.Alpha:
- return this.ReadChannelData(channel, decompressedPixelData, alphaPixelData, width);
+ case ExrConstants.ChannelNames.Alpha:
+ return 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 = 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 static 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 ReadPixelRowChannelHalfSingle(decompressedPixelData, pixelData, width);
+ case ExrPixelType.Float:
+ return 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 static int ReadChannelData(ExrChannelInfo channel, Span decompressedPixelData, Span pixelData, int width)
+ {
+ switch (channel.PixelType)
+ {
+ case ExrPixelType.UnsignedInt:
+ return 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 static 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 static 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 static 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;
+ }
- int bitsPerPixel = this.CalculateBitsPerPixel();
+ ///
+ public IImageInfo Identify(BufferedReadStream stream, CancellationToken cancellationToken)
+ {
+ ExrHeaderAttributes header = this.ReadExrHeader(stream);
- return new ImageInfo(new PixelTypeInfo(bitsPerPixel), this.Width, this.Height, new ImageMetadata());
- }
+ int bitsPerPixel = this.CalculateBitsPerPixel();
+
+ 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);
- int mode = stream.ReadByte();
- if (mode != 0)
- {
- ExrThrowHelper.ThrowNotSupported("Unsupported tile mode. Only mode 0 is supported yet.");
- }
-
- 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);
+ ExrHeaderAttributes header = new();
- 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:
+ ExrLineOrder 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, StringComparison.Ordinal))
+ {
+ 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)
+ {
+ List channels = new();
+ while (attributeSize > 1)
{
- string channelName = ReadString(stream);
- bytesRead = channelName.Length + 1;
+ ExrChannelInfo channelInfo = this.ReadChannelInfo(stream, out int bytesRead);
+ channels.Add(channelInfo);
+ attributeSize -= bytesRead;
+ }
+
+ // Last byte should be a null byte.
+ int byteRead = stream.ReadByte();
- var pixelType = (ExrPixelType)this.ReadSignedInteger(stream);
- bytesRead += 4;
+ return channels;
+ }
- byte pLinear = (byte)stream.ReadByte();
- byte reserved0 = (byte)stream.ReadByte();
- byte reserved1 = (byte)stream.ReadByte();
- byte reserved2 = (byte)stream.ReadByte();
- bytesRead += 4;
+ private ExrChannelInfo ReadChannelInfo(BufferedReadStream stream, out int bytesRead)
+ {
+ string channelName = ReadString(stream);
+ bytesRead = channelName.Length + 1;
- int xSampling = this.ReadSignedInteger(stream);
- bytesRead += 4;
+ ExrPixelType pixelType = (ExrPixelType)this.ReadSignedInteger(stream);
+ bytesRead += 4;
- int ySampling = this.ReadSignedInteger(stream);
- bytesRead += 4;
+ byte pLinear = (byte)stream.ReadByte();
+ byte reserved0 = (byte)stream.ReadByte();
+ byte reserved1 = (byte)stream.ReadByte();
+ byte reserved2 = (byte)stream.ReadByte();
+ bytesRead += 4;
- return new ExrChannelInfo(channelName, pixelType, pLinear, xSampling, ySampling);
- }
+ int xSampling = this.ReadSignedInteger(stream);
+ bytesRead += 4;
- private static string ReadString(BufferedReadStream stream)
+ int ySampling = this.ReadSignedInteger(stream);
+ bytesRead += 4;
+
+ return new ExrChannelInfo(channelName, pixelType, pLinear, xSampling, ySampling);
+ }
+
+ private static string ReadString(BufferedReadStream stream)
+ {
+ StringBuilder str = new();
+ 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, StringComparison.Ordinal) ||
+ this.Channels[i].ChannelName.Equals(ExrConstants.ChannelNames.Green, StringComparison.Ordinal) ||
+ this.Channels[i].ChannelName.Equals(ExrConstants.ChannelNames.Red, StringComparison.Ordinal) ||
+ this.Channels[i].ChannelName.Equals(ExrConstants.ChannelNames.Alpha, StringComparison.Ordinal) ||
+ this.Channels[i].ChannelName.Equals(ExrConstants.ChannelNames.Luminance, StringComparison.Ordinal))
{
- 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;
}
- private bool IsSupportedCompression()
+ if (!pixelType.HasValue)
{
- switch (this.Compression)
- {
- case ExrCompressionType.None:
- case ExrCompressionType.Zip:
- case ExrCompressionType.Zips:
- case ExrCompressionType.RunLengthEncoded:
- case ExrCompressionType.B44:
- return true;
- }
-
- return false;
+ ExrThrowHelper.ThrowNotSupported("Pixel channel data is unknown! Only R, G, B, A and Y are supported.");
}
- private void ReadImageDataType()
- {
- bool hasRedChannel = false;
- bool hasGreenChannel = false;
- bool hasBlueChannel = false;
- bool hasAlphaChannel = false;
- bool hasLuminance = false;
- foreach (ExrChannelInfo channelInfo in this.Channels)
- {
- if (channelInfo.ChannelName.Equals("A"))
- {
- hasAlphaChannel = true;
- }
-
- if (channelInfo.ChannelName.Equals("R"))
- {
- hasRedChannel = true;
- }
+ return pixelType.Value;
+ }
- if (channelInfo.ChannelName.Equals("G"))
- {
- hasGreenChannel = true;
- }
+ private bool IsSupportedCompression()
+ {
+ switch (this.Compression)
+ {
+ case ExrCompressionType.None:
+ case ExrCompressionType.Zip:
+ case ExrCompressionType.Zips:
+ case ExrCompressionType.RunLengthEncoded:
+ case ExrCompressionType.B44:
+ return true;
+ }
- if (channelInfo.ChannelName.Equals("B"))
- {
- hasBlueChannel = true;
- }
+ return false;
+ }
- if (channelInfo.ChannelName.Equals("Y"))
- {
- hasLuminance = true;
- }
+ private void ReadImageDataType()
+ {
+ bool hasRedChannel = false;
+ bool hasGreenChannel = false;
+ bool hasBlueChannel = false;
+ bool hasAlphaChannel = false;
+ bool hasLuminance = false;
+ foreach (ExrChannelInfo channelInfo in this.Channels)
+ {
+ if (channelInfo.ChannelName.Equals("A", StringComparison.Ordinal))
+ {
+ hasAlphaChannel = true;
}
- if (hasRedChannel && hasGreenChannel && hasBlueChannel && hasAlphaChannel)
+ if (channelInfo.ChannelName.Equals("R", StringComparison.Ordinal))
{
- this.ImageDataType = ExrImageDataType.Rgba;
- return;
+ hasRedChannel = true;
}
- if (hasRedChannel && hasGreenChannel && hasBlueChannel)
+ if (channelInfo.ChannelName.Equals("G", StringComparison.Ordinal))
{
- this.ImageDataType = ExrImageDataType.Rgb;
- return;
+ hasGreenChannel = true;
}
- if (hasLuminance && this.Channels.Count == 1)
+ if (channelInfo.ChannelName.Equals("B", StringComparison.Ordinal))
{
- 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", StringComparison.Ordinal))
{
- 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", StringComparison.Ordinal))
{
- 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", StringComparison.Ordinal)
+ || channelInfo.ChannelName.Equals("R", StringComparison.Ordinal)
+ || channelInfo.ChannelName.Equals("G", StringComparison.Ordinal)
+ || channelInfo.ChannelName.Equals("B", StringComparison.Ordinal)
+ || channelInfo.ChannelName.Equals("Y", StringComparison.Ordinal))
{
- 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.ReadInt32LittleEndian(this.buffer);
+ return BinaryPrimitives.ReadUInt32LittleEndian(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..21fb0ade49 100644
--- a/src/ImageSharp/Formats/OpenExr/ExrEncoderCore.cs
+++ b/src/ImageSharp/Formats/OpenExr/ExrEncoderCore.cs
@@ -1,422 +1,418 @@
// 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.Numerics;
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