Browse Source

Merge remote-tracking branch 'origin/bp/openExr' into bp/openExr

# Conflicts:
#	src/ImageSharp/Formats/Bmp/BmpFormat.cs
#	src/ImageSharp/Formats/ImageDecoderUtilities.cs
#	src/ImageSharp/Formats/ImageExtensions.Save.cs
#	src/ImageSharp/Formats/OpenExr/ExrDecoderCore.cs
pull/3096/head
Brian Popow 3 years ago
parent
commit
d0d5e869dd
  1. 4
      src/ImageSharp/Configuration.cs
  2. 2
      src/ImageSharp/Formats/Bmp/BmpFormat.cs
  3. 49
      src/ImageSharp/Formats/ImageDecoderUtilities.cs
  4. 32
      src/ImageSharp/Formats/ImageExtensions.Save.cs
  5. 24
      src/ImageSharp/Formats/OpenExr/Compression/Compressors/NoneExrCompression.cs
  6. 94
      src/ImageSharp/Formats/OpenExr/Compression/Compressors/RunLengthCompression.cs
  7. 58
      src/ImageSharp/Formats/OpenExr/Compression/Compressors/ZipExrCompression.cs
  8. 191
      src/ImageSharp/Formats/OpenExr/Compression/Decompressors/B44Compression.cs
  9. 22
      src/ImageSharp/Formats/OpenExr/Compression/Decompressors/NoneExrCompression.cs
  10. 82
      src/ImageSharp/Formats/OpenExr/Compression/Decompressors/RunLengthCompression.cs
  11. 56
      src/ImageSharp/Formats/OpenExr/Compression/Decompressors/ZipExrCompression.cs
  12. 56
      src/ImageSharp/Formats/OpenExr/Compression/ExrBaseCompression.cs
  13. 50
      src/ImageSharp/Formats/OpenExr/Compression/ExrBaseDecompressor.cs
  14. 113
      src/ImageSharp/Formats/OpenExr/Compression/ExrCompressionType.cs
  15. 37
      src/ImageSharp/Formats/OpenExr/Compression/ExrDecompressorFactory.cs
  16. 31
      src/ImageSharp/Formats/OpenExr/ExrAttribute.cs
  17. 31
      src/ImageSharp/Formats/OpenExr/ExrBox2i.cs
  18. 37
      src/ImageSharp/Formats/OpenExr/ExrChannelInfo.cs
  19. 25
      src/ImageSharp/Formats/OpenExr/ExrConfigurationModule.cs
  20. 105
      src/ImageSharp/Formats/OpenExr/ExrConstants.cs
  21. 91
      src/ImageSharp/Formats/OpenExr/ExrDecoder.cs
  22. 1274
      src/ImageSharp/Formats/OpenExr/ExrDecoderCore.cs
  23. 13
      src/ImageSharp/Formats/OpenExr/ExrDecoderOptions.cs
  24. 48
      src/ImageSharp/Formats/OpenExr/ExrEncoder.cs
  25. 634
      src/ImageSharp/Formats/OpenExr/ExrEncoderCore.cs
  26. 48
      src/ImageSharp/Formats/OpenExr/ExrFormat.cs
  27. 114
      src/ImageSharp/Formats/OpenExr/ExrHeaderAttributes.cs
  28. 17
      src/ImageSharp/Formats/OpenExr/ExrImageDataType.cs
  29. 38
      src/ImageSharp/Formats/OpenExr/ExrImageFormatDetector.cs
  30. 13
      src/ImageSharp/Formats/OpenExr/ExrImageType.cs
  31. 15
      src/ImageSharp/Formats/OpenExr/ExrLineOrder.cs
  32. 43
      src/ImageSharp/Formats/OpenExr/ExrMetadata.cs
  33. 35
      src/ImageSharp/Formats/OpenExr/ExrPixelType.cs
  34. 34
      src/ImageSharp/Formats/OpenExr/ExrThrowHelper.cs
  35. 12
      src/ImageSharp/Formats/OpenExr/IExrDecoderOptions.cs
  36. 19
      src/ImageSharp/Formats/OpenExr/IExrEncoderOptions.cs
  37. 23
      src/ImageSharp/Formats/OpenExr/MetadataExtensions.cs
  38. 26
      src/ImageSharp/IO/BufferedReadStream.cs
  39. 324
      src/ImageSharp/PixelFormats/PixelImplementations/Rgb96.cs
  40. 340
      src/ImageSharp/PixelFormats/PixelImplementations/Rgba128.cs
  41. 64
      tests/ImageSharp.Tests/Formats/Exr/ExrDecoderTests.cs
  42. 164
      tests/ImageSharp.Tests/Formats/Exr/ImageExtensionsTest.cs
  43. 9
      tests/ImageSharp.Tests/TestImages.cs
  44. 6
      tests/ImageSharp.Tests/TestUtilities/TestEnvironment.Formats.cs
  45. 3
      tests/Images/Input/Exr/Calliphora_b44.exr
  46. 3
      tests/Images/Input/Exr/Calliphora_rle.exr
  47. 3
      tests/Images/Input/Exr/Calliphora_uncompressed.exr
  48. 3
      tests/Images/Input/Exr/Calliphora_zip.exr
  49. 3
      tests/Images/Input/Exr/Calliphora_zips.exr

4
src/ImageSharp/Configuration.cs

@ -213,6 +213,7 @@ public sealed class Configuration
/// <see cref="TgaConfigurationModule"/>.
/// <see cref="TiffConfigurationModule"/>.
/// <see cref="WebpConfigurationModule"/>.
/// <see cref="ExrConfigurationModule"/>.
/// </summary>
/// <returns>The default configuration of <see cref="Configuration"/>.</returns>
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());
}

2
src/ImageSharp/Formats/Bmp/BmpFormat.cs

@ -30,5 +30,5 @@ public sealed class BmpFormat : IImageFormat<BmpMetadata>
public IEnumerable<string> FileExtensions => BmpConstants.FileExtensions;
/// <inheritdoc/>
public BmpMetadata CreateDefaultFormatMetadata() => new();
public BmpMetadata CreateDefaultFormatMetadata() => new BmpMetadata();
}

49
src/ImageSharp/Formats/ImageDecoderUtilities.cs

@ -8,17 +8,56 @@ using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Formats;
/// <summary>
/// Utility methods for <see cref="IImageDecoderInternals"/>.
/// Utility methods for <see cref="IImageDecoder"/>.
/// </summary>
internal static class ImageDecoderUtilities
{
internal static ImageInfo Identify(
/// <summary>
/// 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.
/// </summary>
/// <param name="options">The decoder options.</param>
/// <param name="image">The decoded image.</param>
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));
}
}
/// <summary>
/// Determines whether the decoded image should be resized.
/// </summary>
/// <param name="options">The decoder options.</param>
/// <param name="image">The decoded image.</param>
/// <returns><see langword="true"/> if the image should be resized, otherwise; <see langword="false"/>.</returns>
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(

32
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);
/// <summary>
/// Saves the image to the given stream with the Open Exr format.
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="stream">The stream to save the image to.</param>
/// <param name="encoder">The encoder to save the image with.</param>
/// <exception cref="System.ArgumentNullException">Thrown if the stream is null.</exception>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
public static void SaveAsOpenExr(this Image source, Stream stream, ExrEncoder encoder)
=> source.Save(
stream,
encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(ExrFormat.Instance));
/// <summary>
/// Saves the image to the given stream with the Open Exr format.
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="stream">The stream to save the image to.</param>
/// <param name="encoder">The encoder to save the image with.</param>
/// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
/// <exception cref="System.ArgumentNullException">Thrown if the stream is null.</exception>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
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);
}

24
src/ImageSharp/Formats/OpenExr/Compression/Compressors/NoneExrCompression.cs

@ -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<byte> buffer)
=> stream.Read(buffer, 0, Math.Min(buffer.Length, (int)this.UncompressedBytes));
protected override void Dispose(bool disposing)
{
}
}
}

94
src/ImageSharp/Formats/OpenExr/Compression/Compressors/RunLengthCompression.cs

@ -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<byte> tmpBuffer;
public RunLengthCompression(MemoryAllocator allocator, uint uncompressedBytes)
: base(allocator, uncompressedBytes) => this.tmpBuffer = allocator.Allocate<byte>((int)uncompressedBytes);
public override void Decompress(BufferedReadStream stream, uint compressedBytes, Span<byte> buffer)
{
Span<byte> 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();
}
}

58
src/ImageSharp/Formats/OpenExr/Compression/Compressors/ZipExrCompression.cs

@ -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<byte> tmpBuffer;
public ZipExrCompression(MemoryAllocator allocator, uint uncompressedBytes)
: base(allocator, uncompressedBytes) => this.tmpBuffer = allocator.Allocate<byte>((int)uncompressedBytes);
public override void Decompress(BufferedReadStream stream, uint compressedBytes, Span<byte> buffer)
{
Span<byte> 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();
}
}

191
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<ushort> 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<ushort>((int)(width * rowsPerBlock * channelCount));
}
public override void Decompress(BufferedReadStream stream, uint compressedBytes, Span<byte> buffer)
{
Span<ushort> outputBuffer = MemoryMarshal.Cast<byte, ushort>(buffer);
Span<ushort> decompressed = this.tmpBuffer.GetSpan();
int outputOffset = 0;
int bytesLeft = (int)compressedBytes;
for (int i = 0; i < this.channelCount && bytesLeft > 0; i++)
{
for (int y = 0; y < this.rowsPerBlock; y += 4)
{
Span<ushort> row0 = decompressed.Slice(outputOffset, this.width);
outputOffset += this.width;
Span<ushort> row1 = decompressed.Slice(outputOffset, this.width);
outputOffset += this.width;
Span<ushort> row2 = decompressed.Slice(outputOffset, this.width);
outputOffset += this.width;
Span<ushort> row3 = decompressed.Slice(outputOffset, this.width);
outputOffset += this.width;
int rowOffset = 0;
for (int x = 0; x < this.width && bytesLeft > 0; x += 4)
{
int bytesRead = stream.Read(this.scratch, 0, 3);
if (bytesRead == 0)
{
ExrThrowHelper.ThrowInvalidImageContentException("Could not read enough data from the stream");
}
if (this.scratch[2] >= 13 << 2)
{
Unpack3(this.scratch, this.s);
bytesLeft -= 3;
}
else
{
bytesRead = stream.Read(this.scratch, 3, 11);
if (bytesRead == 0)
{
ExrThrowHelper.ThrowInvalidImageContentException("Could not read enough data from the stream");
}
Unpack14(this.scratch, this.s);
bytesLeft -= 14;
}
int n = x + 3 < this.width ? 4 : this.width - x;
if (y + 3 < this.rowsPerBlock)
{
this.s.AsSpan(0, n).CopyTo(row0.Slice(rowOffset));
this.s.AsSpan(4, n).CopyTo(row1.Slice(rowOffset));
this.s.AsSpan(8, n).CopyTo(row2.Slice(rowOffset));
this.s.AsSpan(12, n).CopyTo(row3.Slice(rowOffset));
}
else
{
this.s.AsSpan(0, n).CopyTo(row0.Slice(rowOffset));
if (y + 1 < this.rowsPerBlock)
{
this.s.AsSpan(4, n).CopyTo(row1.Slice(rowOffset));
}
if (y + 2 < this.rowsPerBlock)
{
this.s.AsSpan(8, n).CopyTo(row2.Slice(rowOffset));
}
}
rowOffset += 4;
}
if (bytesLeft <= 0)
{
break;
}
}
}
// Rearrange the decompressed data such that the data for each scan line form a contiguous block.
int offsetDecompressed = 0;
int offsetOutput = 0;
int blockSize = (int)(this.width * this.rowsPerBlock);
for (int y = 0; y < this.rowsPerBlock; y++)
{
for (int i = 0; i < this.channelCount; i++)
{
decompressed.Slice(offsetDecompressed + (i * blockSize), this.width).CopyTo(outputBuffer.Slice(offsetOutput));
offsetOutput += this.width;
}
offsetDecompressed += this.width;
}
}
// Unpack a 14-byte block into 4 by 4 16-bit pixels.
private static void Unpack14(Span<byte> b, Span<ushort> s)
{
s[0] = (ushort)((b[0] << 8) | b[1]);
ushort shift = (ushort)(b[2] >> 2);
ushort bias = (ushort)(0x20u << shift);
s[4] = (ushort)(s[0] + ((((b[2] << 4) | (b[3] >> 4)) & 0x3fu) << shift) - bias);
s[8] = (ushort)(s[4] + ((((b[3] << 2) | (b[4] >> 6)) & 0x3fu) << shift) - bias);
s[12] = (ushort)(s[8] + ((b[4] & 0x3fu) << shift) - bias);
s[1] = (ushort)(s[0] + ((uint)(b[5] >> 2) << shift) - bias);
s[5] = (ushort)(s[4] + ((((b[5] << 4) | (b[6] >> 4)) & 0x3fu) << shift) - bias);
s[9] = (ushort)(s[8] + ((((b[6] << 2) | (b[7] >> 6)) & 0x3fu) << shift) - bias);
s[13] = (ushort)(s[12] + ((b[7] & 0x3fu) << shift) - bias);
s[2] = (ushort)(s[1] + ((uint)(b[8] >> 2) << shift) - bias);
s[6] = (ushort)(s[5] + ((((b[8] << 4) | (b[9] >> 4)) & 0x3fu) << shift) - bias);
s[10] = (ushort)(s[9] + ((((b[9] << 2) | (b[10] >> 6)) & 0x3fu) << shift) - bias);
s[14] = (ushort)(s[13] + ((b[10] & 0x3fu) << shift) - bias);
s[3] = (ushort)(s[2] + ((uint)(b[11] >> 2) << shift) - bias);
s[7] = (ushort)(s[6] + ((((b[11] << 4) | (b[12] >> 4)) & 0x3fu) << shift) - bias);
s[11] = (ushort)(s[10] + ((((b[12] << 2) | (b[13] >> 6)) & 0x3fu) << shift) - bias);
s[15] = (ushort)(s[14] + ((b[13] & 0x3fu) << shift) - bias);
for (int i = 0; i < 16; ++i)
{
if ((s[i] & 0x8000) != 0)
{
s[i] &= 0x7fff;
}
else
{
s[i] = (ushort)~s[i];
}
}
}
// Unpack a 3-byte block into 4 by 4 identical 16-bit pixels.
private static void Unpack3(Span<byte> b, Span<ushort> s)
{
s[0] = (ushort)((b[0] << 8) | b[1]);
if ((s[0] & 0x8000) != 0)
{
s[0] &= 0x7fff;
}
else
{
s[0] = (ushort)~s[0];
}
for (int i = 1; i < 16; ++i)
{
s[i] = s[0];
}
}
protected override void Dispose(bool disposing) => this.tmpBuffer.Dispose();
}

22
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<byte> buffer)
=> stream.Read(buffer, 0, Math.Min(buffer.Length, (int)this.UncompressedBytes));
protected override void Dispose(bool disposing)
{
}
}

82
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<byte> tmpBuffer;
private readonly ushort[] s = new ushort[16];
public RunLengthCompression(MemoryAllocator allocator, uint uncompressedBytes)
: base(allocator, uncompressedBytes) => this.tmpBuffer = allocator.Allocate<byte>((int)uncompressedBytes);
public override void Decompress(BufferedReadStream stream, uint compressedBytes, Span<byte> buffer)
{
Span<byte> 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();
}

56
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<byte> tmpBuffer;
public ZipExrCompression(MemoryAllocator allocator, uint uncompressedBytes)
: base(allocator, uncompressedBytes) => this.tmpBuffer = allocator.Allocate<byte>((int)uncompressedBytes);
public override void Decompress(BufferedReadStream stream, uint compressedBytes, Span<byte> buffer)
{
Span<byte> 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();
}

56
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;
}
/// <summary>
/// Gets the memory allocator.
/// </summary>
protected MemoryAllocator Allocator { get; }
/// <summary>
/// Gets the memory allocator.
/// </summary>
protected MemoryAllocator Allocator { get; }
/// <summary>
/// Gets the uncompressed bytes.
/// </summary>
public uint UncompressedBytes { get; }
/// <summary>
/// Gets the uncompressed bytes.
/// </summary>
public uint UncompressedBytes { get; }
/// <inheritdoc />
public void Dispose()
/// <inheritdoc />
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);
}

50
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<byte> buffer);
public abstract void Decompress(BufferedReadStream stream, uint compressedBytes, Span<byte> buffer);
protected static void Reconstruct(Span<byte> buffer, uint unCompressedBytes)
protected static void Reconstruct(Span<byte> 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<byte> source, uint unCompressedBytes, Span<byte> output)
protected static void Interleave(Span<byte> source, uint unCompressedBytes, Span<byte> 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++];
}
}
}

113
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
{
/// <summary>
/// Pixel data is not compressed.
/// </summary>
None = 0,
/// <summary>
/// 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.
/// </summary>
RunLengthEncoded = 1,
/// <summary>
/// Uses the open source zlib library for compression. Unlike ZIP compression, this operates one scan line at a time.
/// Compression is lossless.
/// </summary>
Zips = 2,
/// <summary>
/// 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.
/// </summary>
Zip = 3,
/// <summary>
/// A wavelet transform is applied to the pixel data, and the result is Huffman-encoded.
/// Compression is lossless.
/// </summary>
Piz = 4,
/// <summary>
/// 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.
/// </summary>
Pxr24 = 5,
/// <summary>
/// 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.
/// </summary>
B44 = 6,
/// <summary>
/// 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.
/// </summary>
B44A = 7
}
/// <summary>
/// Pixel data is not compressed.
/// </summary>
None = 0,
/// <summary>
/// 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.
/// </summary>
RunLengthEncoded = 1,
/// <summary>
/// Uses the open source zlib library for compression. Unlike ZIP compression, this operates one scan line at a time.
/// Compression is lossless.
/// </summary>
Zips = 2,
/// <summary>
/// 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.
/// </summary>
Zip = 3,
/// <summary>
/// A wavelet transform is applied to the pixel data, and the result is Huffman-encoded.
/// Compression is lossless.
/// </summary>
Piz = 4,
/// <summary>
/// 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.
/// </summary>
Pxr24 = 5,
/// <summary>
/// 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.
/// </summary>
B44 = 6,
/// <summary>
/// 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.
/// </summary>
B44A = 7
}

37
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));
}
}
}

31
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; }
}

31
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; }
}

37
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; }
}

25
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;
/// <summary>
/// Registers the image encoders, decoders and mime type detectors for the OpenExr format.
/// </summary>
public sealed class ExrConfigurationModule : IConfigurationModule
{
/// <summary>
/// Registers the image encoders, decoders and mime type detectors for the OpenExr format.
/// </summary>
public sealed class ExrConfigurationModule : IConfigurationModule
/// <inheritdoc/>
public void Configure(Configuration configuration)
{
/// <inheritdoc/>
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());
}
}

105
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
/// <summary>
/// Defines constants relating to OpenExr images.
/// </summary>
internal static class ExrConstants
{
/// <summary>
/// Defines constants relating to OpenExr images.
/// The list of mimetypes that equate to a OpenExr image.
/// </summary>
internal static class ExrConstants
{
/// <summary>
/// The list of mimetypes that equate to a OpenExr image.
/// </summary>
public static readonly IEnumerable<string> MimeTypes = new[] { "image/x-exr" };
public static readonly IEnumerable<string> MimeTypes = new[] { "image/x-exr" };
/// <summary>
/// The list of file extensions that equate to a OpenExr image.
/// </summary>
public static readonly IEnumerable<string> FileExtensions = new[] { "exr" };
/// <summary>
/// The list of file extensions that equate to a OpenExr image.
/// </summary>
public static readonly IEnumerable<string> FileExtensions = new[] { "exr" };
/// <summary>
/// The magick bytes identifying an OpenExr image.
/// </summary>
public static readonly int MagickBytes = 20000630;
/// <summary>
/// The magick bytes identifying an OpenExr image.
/// </summary>
public static readonly int MagickBytes = 20000630;
/// <summary>
/// EXR attribute names.
/// </summary>
internal static class AttributeNames
{
public const string Channels = "channels";
/// <summary>
/// EXR attribute names.
/// </summary>
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";
}
/// <summary>
/// EXR attribute types.
/// </summary>
internal static class AttibuteTypes
{
public const string ChannelList = "chlist";
/// <summary>
/// EXR attribute types.
/// </summary>
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";
}
}

91
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;
/// <summary>
/// Image decoder for generating an image out of a OpenExr stream.
/// </summary>
public class ExrDecoder : IImageDecoderSpecialized<ExrDecoderOptions>
{
/// <summary>
/// Image decoder for generating an image out of a OpenExr stream.
/// </summary>
public sealed class ExrDecoder : IImageDecoder, IExrDecoderOptions, IImageInfoDetector
/// <inheritdoc/>
IImageInfo IImageInfoDetector.Identify(DecoderOptions options, Stream stream, CancellationToken cancellationToken)
{
/// <inheritdoc/>
public Image<TPixel> Decode<TPixel>(Configuration configuration, Stream stream)
where TPixel : unmanaged, IPixel<TPixel>
{
Guard.NotNull(stream, nameof(stream));
var decoder = new ExrDecoderCore(configuration, this);
return decoder.Decode<TPixel>(configuration, stream);
}
/// <inheritdoc />
public Image Decode(Configuration configuration, Stream stream)
=> this.Decode<Rgba32>(configuration, stream);
/// <inheritdoc/>
public Task<Image<TPixel>> DecodeAsync<TPixel>(Configuration configuration, Stream stream, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel>
{
Guard.NotNull(stream, nameof(stream));
var decoder = new ExrDecoderCore(configuration, this);
return decoder.DecodeAsync<TPixel>(configuration, stream, cancellationToken);
}
/// <inheritdoc />
public async Task<Image> DecodeAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken)
=> await this.DecodeAsync<Rgba32>(configuration, stream, cancellationToken)
.ConfigureAwait(false);
/// <inheritdoc/>
public IImageInfo Identify(Configuration configuration, Stream stream)
{
Guard.NotNull(stream, nameof(stream));
return new ExrDecoderCore(configuration, this).Identify(configuration, stream);
}
/// <inheritdoc/>
public Task<IImageInfo> 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);
}
/// <inheritdoc/>
Image<TPixel> IImageDecoder.Decode<TPixel>(DecoderOptions options, Stream stream, CancellationToken cancellationToken)
=> ((IImageDecoderSpecialized<ExrDecoderOptions>)this).Decode<TPixel>(new() { GeneralOptions = options }, stream, cancellationToken);
/// <inheritdoc/>
Image IImageDecoder.Decode(DecoderOptions options, Stream stream, CancellationToken cancellationToken)
=> ((IImageDecoderSpecialized<ExrDecoderOptions>)this).Decode(new() { GeneralOptions = options }, stream, cancellationToken);
/// <inheritdoc/>
Image<TPixel> IImageDecoderSpecialized<ExrDecoderOptions>.Decode<TPixel>(ExrDecoderOptions options, Stream stream, CancellationToken cancellationToken)
{
Guard.NotNull(options, nameof(options));
Guard.NotNull(stream, nameof(stream));
Image<TPixel> image = new ExrDecoderCore(options).Decode<TPixel>(options.GeneralOptions.Configuration, stream, cancellationToken);
ImageDecoderUtilities.Resize(options.GeneralOptions, image);
return image;
}
/// <inheritdoc/>
Image IImageDecoderSpecialized<ExrDecoderOptions>.Decode(ExrDecoderOptions options, Stream stream, CancellationToken cancellationToken)
=> ((IImageDecoderSpecialized<ExrDecoderOptions>)this).Decode<Rgba32>(options, stream, cancellationToken);
}

1274
src/ImageSharp/Formats/OpenExr/ExrDecoderCore.cs

File diff suppressed because it is too large

13
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;
/// <summary>
/// Image decoder options for decoding OpenExr streams.
/// </summary>
public sealed class ExrDecoderOptions : ISpecializedDecoderOptions
{
/// <inheritdoc/>
public DecoderOptions GeneralOptions { get; set; } = new();
}

48
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;
/// <summary>
/// Image encoder for writing an image to a stream in the OpenExr Format.
/// </summary>
public sealed class ExrEncoder : IImageEncoder, IExrEncoderOptions
{
/// <summary>
/// Image encoder for writing an image to a stream in the OpenExr Format.
/// Gets or sets the pixel type of the image.
/// </summary>
public sealed class ExrEncoder : IImageEncoder, IExrEncoderOptions
{
/// <summary>
/// Gets or sets the pixel type of the image.
/// </summary>
public ExrPixelType? PixelType { get; set; }
public ExrPixelType? PixelType { get; set; }
/// <inheritdoc/>
public void Encode<TPixel>(Image<TPixel> image, Stream stream)
where TPixel : unmanaged, IPixel<TPixel>
{
var encoder = new ExrEncoderCore(this, image.GetMemoryAllocator());
encoder.Encode(image, stream);
}
/// <inheritdoc/>
public void Encode<TPixel>(Image<TPixel> image, Stream stream)
where TPixel : unmanaged, IPixel<TPixel>
{
ExrEncoderCore encoder = new(this, image.GetMemoryAllocator());
encoder.Encode(image, stream);
}
/// <inheritdoc/>
public Task EncodeAsync<TPixel>(Image<TPixel> image, Stream stream, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel>
{
var encoder = new ExrEncoderCore(this, image.GetMemoryAllocator());
return encoder.EncodeAsync(image, stream, cancellationToken);
}
/// <inheritdoc/>
public Task EncodeAsync<TPixel>(Image<TPixel> image, Stream stream, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel>
{
ExrEncoderCore encoder = new(this, image.GetMemoryAllocator());
return encoder.EncodeAsync(image, stream, cancellationToken);
}
}

634
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;
/// <summary>
/// Image encoder for writing an image to a stream in the OpenExr format.
/// </summary>
internal sealed class ExrEncoderCore : IImageEncoderInternals
{
/// <summary>
/// Image encoder for writing an image to a stream in the OpenExr format.
/// Reusable buffer.
/// </summary>
internal sealed class ExrEncoderCore : IImageEncoderInternals
{
/// <summary>
/// Reusable buffer.
/// </summary>
private readonly byte[] buffer = new byte[8];
/// <summary>
/// Used for allocating memory during processing operations.
/// </summary>
private readonly MemoryAllocator memoryAllocator;
/// <summary>
/// The pixel type of the image.
/// </summary>
private ExrPixelType? pixelType;
/// <summary>
/// Initializes a new instance of the <see cref="ExrEncoderCore"/> class.
/// </summary>
/// <param name="options">The encoder options.</param>
/// <param name="memoryAllocator">The memory manager.</param>
public ExrEncoderCore(IExrEncoderOptions options, MemoryAllocator memoryAllocator)
{
this.memoryAllocator = memoryAllocator;
this.pixelType = options.PixelType;
}
/// <summary>
/// Encodes the image to the specified stream from the <see cref="ImageFrame{TPixel}"/>.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="image">The <see cref="ImageFrame{TPixel}"/> to encode from.</param>
/// <param name="stream">The <see cref="Stream"/> to encode the image data to.</param>
/// <param name="cancellationToken">The token to request cancellation.</param>
public void Encode<TPixel>(Image<TPixel> image, Stream stream, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel>
{
Guard.NotNull(image, nameof(image));
Guard.NotNull(stream, nameof(stream));
private readonly byte[] buffer = new byte[8];
Buffer2D<TPixel> 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<ExrChannelInfo>()
{
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;
/// <summary>
/// Used for allocating memory during processing operations.
/// </summary>
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));
/// <summary>
/// The pixel type of the image.
/// </summary>
private ExrPixelType? pixelType;
// Write EXR header.
this.WriteHeader(stream, header);
/// <summary>
/// Initializes a new instance of the <see cref="ExrEncoderCore"/> class.
/// </summary>
/// <param name="options">The encoder options.</param>
/// <param name="memoryAllocator">The memory manager.</param>
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);
/// <summary>
/// Encodes the image to the specified stream from the <see cref="ImageFrame{TPixel}"/>.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="image">The <see cref="ImageFrame{TPixel}"/> to encode from.</param>
/// <param name="stream">The <see cref="Stream"/> to encode the image data to.</param>
/// <param name="cancellationToken">The token to request cancellation.</param>
public void Encode<TPixel>(Image<TPixel> image, Stream stream, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel>
{
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<TPixel> pixels = image.Frames.RootFrame.PixelBuffer;
private void EncodeFloatingPointPixelData<TPixel>(Stream stream, Buffer2D<TPixel> pixels, int width, int height, uint rowSizeBytes)
where TPixel : unmanaged, IPixel<TPixel>
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<float> rgbBuffer = this.memoryAllocator.Allocate<float>(width * 3);
Span<float> redBuffer = rgbBuffer.GetSpan().Slice(0, width);
Span<float> greenBuffer = rgbBuffer.GetSpan().Slice(width, width);
Span<float> 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<ExrChannelInfo>()
{
Span<TPixel> 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<TPixel>(Stream stream, Buffer2D<TPixel> pixels, int width, int height, uint rowSizeBytes)
where TPixel : unmanaged, IPixel<TPixel>
{
using IMemoryOwner<uint> rgbBuffer = this.memoryAllocator.Allocate<uint>(width * 3);
Span<uint> redBuffer = rgbBuffer.GetSpan().Slice(0, width);
Span<uint> greenBuffer = rgbBuffer.GetSpan().Slice(width, width);
Span<uint> blueBuffer = rgbBuffer.GetSpan().Slice(width * 2, width);
var rgb = default(Rgb96);
for (int y = 0; y < height; y++)
{
Span<TPixel> 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<TPixel>(Stream stream, Buffer2D<TPixel> pixels, int width, int height, uint rowSizeBytes)
where TPixel : unmanaged, IPixel<TPixel>
{
using IMemoryOwner<float> rgbBuffer = this.memoryAllocator.Allocate<float>(width * 3);
Span<float> redBuffer = rgbBuffer.GetSpan().Slice(0, width);
Span<float> greenBuffer = rgbBuffer.GetSpan().Slice(width, width);
Span<float> blueBuffer = rgbBuffer.GetSpan().Slice(width * 2, width);
private void WriteSingleRow(Stream stream, int width, Span<float> blueBuffer, Span<float> greenBuffer, Span<float> redBuffer)
for (int y = 0; y < height; y++)
{
for (int x = 0; x < width; x++)
{
this.WriteSingle(stream, blueBuffer[x]);
}
Span<TPixel> 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<TPixel>(Stream stream, Buffer2D<TPixel> pixels, int width, int height, uint rowSizeBytes)
where TPixel : unmanaged, IPixel<TPixel>
{
using IMemoryOwner<uint> rgbBuffer = this.memoryAllocator.Allocate<uint>(width * 3);
Span<uint> redBuffer = rgbBuffer.GetSpan().Slice(0, width);
Span<uint> greenBuffer = rgbBuffer.GetSpan().Slice(width, width);
Span<uint> blueBuffer = rgbBuffer.GetSpan().Slice(width * 2, width);
private void WriteHalfSingleRow(Stream stream, int width, Span<float> blueBuffer, Span<float> greenBuffer, Span<float> redBuffer)
Rgb96 rgb = default;
for (int y = 0; y < height; y++)
{
for (int x = 0; x < width; x++)
{
this.WriteHalfSingle(stream, blueBuffer[x]);
}
Span<TPixel> pixelRowSpan = pixels.DangerousGetRowSpan(y);
for (int x = 0; x < width; x++)
{
this.WriteHalfSingle(stream, greenBuffer[x]);
}
Vector4 vector4 = pixelRowSpan[x].ToVector4();
rgb.FromVector4(vector4);
for (int x = 0; x < width; x++)
{
this.WriteHalfSingle(stream, redBuffer[x]);
redBuffer[x] = rgb.R;
greenBuffer[x] = rgb.G;
blueBuffer[x] = rgb.B;
}
}
private void WriteUnsignedIntRow(Stream stream, int width, Span<uint> blueBuffer, Span<uint> greenBuffer, Span<uint> redBuffer)
{
for (int x = 0; x < width; x++)
{
this.WriteUnsignedInt(stream, blueBuffer[x]);
}
// Write row index.
BinaryPrimitives.WriteUInt32LittleEndian(this.buffer, (uint)y);
stream.Write(this.buffer.AsSpan(0, 4));
for (int x = 0; x < width; x++)
{
this.WriteUnsignedInt(stream, greenBuffer[x]);
}
// Write pixel row data size.
BinaryPrimitives.WriteUInt32LittleEndian(this.buffer, rowSizeBytes);
stream.Write(this.buffer.AsSpan(0, 4));
for (int x = 0; x < width; x++)
{
this.WriteUnsignedInt(stream, redBuffer[x]);
}
this.WriteUnsignedIntRow(stream, width, blueBuffer, greenBuffer, redBuffer);
}
}
private void WriteRowOffsets(Stream stream, int height, uint rowSizeBytes)
private void WriteHeader(Stream stream, ExrHeaderAttributes header)
{
this.WriteChannels(stream, header.Channels);
this.WriteCompression(stream, header.Compression.Value);
this.WriteDataWindow(stream, header.DataWindow.Value);
this.WriteDisplayWindow(stream, header.DisplayWindow.Value);
this.WritePixelAspectRatio(stream, header.AspectRatio.Value);
this.WriteLineOrder(stream, header.LineOrder.Value);
this.WriteScreenWindowCenter(stream, header.ScreenWindowCenter.Value);
this.WriteScreenWindowWidth(stream, header.ScreenWindowWidth.Value);
stream.WriteByte(0);
}
private void WriteSingleRow(Stream stream, int width, Span<float> blueBuffer, Span<float> greenBuffer, Span<float> redBuffer)
{
for (int x = 0; x < width; x++)
{
ulong startOfPixelData = (ulong)stream.Position + (8 * (ulong)height);
ulong offset = startOfPixelData;
for (int i = 0; i < height; i++)
{
BinaryPrimitives.WriteUInt64LittleEndian(this.buffer, offset);
stream.Write(this.buffer);
offset += 4 + 4 + rowSizeBytes;
}
this.WriteSingle(stream, blueBuffer[x]);
}
private void WriteChannels(Stream stream, IList<ExrChannelInfo> channels)
for (int x = 0; x < width; x++)
{
int attributeSize = 0;
foreach (ExrChannelInfo channelInfo in channels)
{
attributeSize += channelInfo.ChannelName.Length + 1;
attributeSize += 16;
}
// Last zero byte.
attributeSize++;
this.WriteAttributeInformation(stream, ExrConstants.AttributeNames.Channels, ExrConstants.AttibuteTypes.ChannelList, attributeSize);
foreach (ExrChannelInfo channelInfo in channels)
{
this.WriteChannelInfo(stream, channelInfo);
}
this.WriteSingle(stream, greenBuffer[x]);
}
// Last byte should be zero.
stream.WriteByte(0);
for (int x = 0; x < width; x++)
{
this.WriteSingle(stream, redBuffer[x]);
}
}
private void WriteCompression(Stream stream, ExrCompressionType compression)
private void WriteHalfSingleRow(Stream stream, int width, Span<float> blueBuffer, Span<float> greenBuffer, Span<float> redBuffer)
{
for (int x = 0; x < width; x++)
{
this.WriteAttributeInformation(stream, ExrConstants.AttributeNames.Compression, ExrConstants.AttibuteTypes.Compression, 1);
stream.WriteByte((byte)compression);
this.WriteHalfSingle(stream, blueBuffer[x]);
}
private void WritePixelAspectRatio(Stream stream, float aspectRatio)
for (int x = 0; x < width; x++)
{
this.WriteAttributeInformation(stream, ExrConstants.AttributeNames.PixelAspectRatio, ExrConstants.AttibuteTypes.Float, 4);
this.WriteSingle(stream, aspectRatio);
this.WriteHalfSingle(stream, greenBuffer[x]);
}
private void WriteLineOrder(Stream stream, ExrLineOrder lineOrder)
for (int x = 0; x < width; x++)
{
this.WriteAttributeInformation(stream, ExrConstants.AttributeNames.LineOrder, ExrConstants.AttibuteTypes.LineOrder, 1);
stream.WriteByte((byte)lineOrder);
this.WriteHalfSingle(stream, redBuffer[x]);
}
}
private void WriteScreenWindowCenter(Stream stream, PointF screenWindowCenter)
private void WriteUnsignedIntRow(Stream stream, int width, Span<uint> blueBuffer, Span<uint> greenBuffer, Span<uint> redBuffer)
{
for (int x = 0; x < width; x++)
{
this.WriteAttributeInformation(stream, ExrConstants.AttributeNames.ScreenWindowCenter, ExrConstants.AttibuteTypes.TwoFloat, 8);
this.WriteSingle(stream, screenWindowCenter.X);
this.WriteSingle(stream, screenWindowCenter.Y);
this.WriteUnsignedInt(stream, blueBuffer[x]);
}
private void WriteScreenWindowWidth(Stream stream, float screenWindowWidth)
for (int x = 0; x < width; x++)
{
this.WriteAttributeInformation(stream, ExrConstants.AttributeNames.ScreenWindowWidth, ExrConstants.AttibuteTypes.Float, 4);
this.WriteSingle(stream, screenWindowWidth);
this.WriteUnsignedInt(stream, greenBuffer[x]);
}
private void WriteDataWindow(Stream stream, ExrBox2i dataWindow)
for (int x = 0; x < width; x++)
{
this.WriteAttributeInformation(stream, ExrConstants.AttributeNames.DataWindow, ExrConstants.AttibuteTypes.BoxInt, 16);
this.WriteBoxInteger(stream, dataWindow);
this.WriteUnsignedInt(stream, redBuffer[x]);
}
}
private void WriteDisplayWindow(Stream stream, ExrBox2i displayWindow)
private void WriteRowOffsets(Stream stream, int height, uint rowSizeBytes)
{
ulong startOfPixelData = (ulong)stream.Position + (8 * (ulong)height);
ulong offset = startOfPixelData;
for (int i = 0; i < height; i++)
{
this.WriteAttributeInformation(stream, ExrConstants.AttributeNames.DisplayWindow, ExrConstants.AttibuteTypes.BoxInt, 16);
this.WriteBoxInteger(stream, displayWindow);
BinaryPrimitives.WriteUInt64LittleEndian(this.buffer, offset);
stream.Write(this.buffer);
offset += 4 + 4 + rowSizeBytes;
}
}
private void WriteAttributeInformation(Stream stream, string name, string type, int size)
private void WriteChannels(Stream stream, IList<ExrChannelInfo> channels)
{
int attributeSize = 0;
foreach (ExrChannelInfo channelInfo in channels)
{
// Write attribute name.
this.WriteString(stream, name);
attributeSize += channelInfo.ChannelName.Length + 1;
attributeSize += 16;
}
// Write attribute type.
this.WriteString(stream, type);
// Last zero byte.
attributeSize++;
this.WriteAttributeInformation(stream, ExrConstants.AttributeNames.Channels, ExrConstants.AttibuteTypes.ChannelList, attributeSize);
// Write attribute size.
BinaryPrimitives.WriteUInt32LittleEndian(this.buffer, (uint)size);
stream.Write(this.buffer.AsSpan(0, 4));
foreach (ExrChannelInfo channelInfo in channels)
{
this.WriteChannelInfo(stream, channelInfo);
}
private void WriteChannelInfo(Stream stream, ExrChannelInfo channelInfo)
{
this.WriteString(stream, channelInfo.ChannelName);
// Last byte should be zero.
stream.WriteByte(0);
}
BinaryPrimitives.WriteInt32LittleEndian(this.buffer, (int)channelInfo.PixelType);
stream.Write(this.buffer.AsSpan(0, 4));
private void WriteCompression(Stream stream, ExrCompressionType compression)
{
this.WriteAttributeInformation(stream, ExrConstants.AttributeNames.Compression, ExrConstants.AttibuteTypes.Compression, 1);
stream.WriteByte((byte)compression);
}
stream.WriteByte(channelInfo.PLinear);
private void WritePixelAspectRatio(Stream stream, float aspectRatio)
{
this.WriteAttributeInformation(stream, ExrConstants.AttributeNames.PixelAspectRatio, ExrConstants.AttibuteTypes.Float, 4);
this.WriteSingle(stream, aspectRatio);
}
// Next 3 bytes are reserved and will set to zero.
stream.WriteByte(0);
stream.WriteByte(0);
stream.WriteByte(0);
private void WriteLineOrder(Stream stream, ExrLineOrder lineOrder)
{
this.WriteAttributeInformation(stream, ExrConstants.AttributeNames.LineOrder, ExrConstants.AttibuteTypes.LineOrder, 1);
stream.WriteByte((byte)lineOrder);
}
BinaryPrimitives.WriteInt32LittleEndian(this.buffer, channelInfo.XSampling);
stream.Write(this.buffer.AsSpan(0, 4));
private void WriteScreenWindowCenter(Stream stream, PointF screenWindowCenter)
{
this.WriteAttributeInformation(stream, ExrConstants.AttributeNames.ScreenWindowCenter, ExrConstants.AttibuteTypes.TwoFloat, 8);
this.WriteSingle(stream, screenWindowCenter.X);
this.WriteSingle(stream, screenWindowCenter.Y);
}
BinaryPrimitives.WriteInt32LittleEndian(this.buffer, channelInfo.YSampling);
stream.Write(this.buffer.AsSpan(0, 4));
}
private void WriteScreenWindowWidth(Stream stream, float screenWindowWidth)
{
this.WriteAttributeInformation(stream, ExrConstants.AttributeNames.ScreenWindowWidth, ExrConstants.AttibuteTypes.Float, 4);
this.WriteSingle(stream, screenWindowWidth);
}
private void WriteString(Stream stream, string str)
{
foreach (char c in str)
{
stream.WriteByte((byte)c);
}
private void WriteDataWindow(Stream stream, ExrBox2i dataWindow)
{
this.WriteAttributeInformation(stream, ExrConstants.AttributeNames.DataWindow, ExrConstants.AttibuteTypes.BoxInt, 16);
this.WriteBoxInteger(stream, dataWindow);
}
// Write termination byte.
stream.WriteByte(0);
}
private void WriteDisplayWindow(Stream stream, ExrBox2i displayWindow)
{
this.WriteAttributeInformation(stream, ExrConstants.AttributeNames.DisplayWindow, ExrConstants.AttibuteTypes.BoxInt, 16);
this.WriteBoxInteger(stream, displayWindow);
}
private void WriteBoxInteger(Stream stream, ExrBox2i box)
{
BinaryPrimitives.WriteInt32LittleEndian(this.buffer, box.XMin);
stream.Write(this.buffer.AsSpan(0, 4));
private void WriteAttributeInformation(Stream stream, string name, string type, int size)
{
// Write attribute name.
WriteString(stream, name);
BinaryPrimitives.WriteInt32LittleEndian(this.buffer, box.YMin);
stream.Write(this.buffer.AsSpan(0, 4));
// Write attribute type.
WriteString(stream, type);
BinaryPrimitives.WriteInt32LittleEndian(this.buffer, box.XMax);
stream.Write(this.buffer.AsSpan(0, 4));
// Write attribute size.
BinaryPrimitives.WriteUInt32LittleEndian(this.buffer, (uint)size);
stream.Write(this.buffer.AsSpan(0, 4));
}
BinaryPrimitives.WriteInt32LittleEndian(this.buffer, box.YMax);
stream.Write(this.buffer.AsSpan(0, 4));
}
private void WriteChannelInfo(Stream stream, ExrChannelInfo channelInfo)
{
WriteString(stream, channelInfo.ChannelName);
[MethodImpl(InliningOptions.ShortMethod)]
private unsafe void WriteSingle(Stream stream, float value)
{
BinaryPrimitives.WriteInt32LittleEndian(this.buffer, *(int*)&value);
stream.Write(this.buffer.AsSpan(0, 4));
}
BinaryPrimitives.WriteInt32LittleEndian(this.buffer, (int)channelInfo.PixelType);
stream.Write(this.buffer.AsSpan(0, 4));
[MethodImpl(InliningOptions.ShortMethod)]
private void WriteHalfSingle(Stream stream, float value)
{
ushort valueAsShort = HalfTypeHelper.Pack(value);
BinaryPrimitives.WriteUInt16LittleEndian(this.buffer, valueAsShort);
stream.Write(this.buffer.AsSpan(0, 2));
}
stream.WriteByte(channelInfo.PLinear);
// Next 3 bytes are reserved and will set to zero.
stream.WriteByte(0);
stream.WriteByte(0);
stream.WriteByte(0);
BinaryPrimitives.WriteInt32LittleEndian(this.buffer, channelInfo.XSampling);
stream.Write(this.buffer.AsSpan(0, 4));
[MethodImpl(InliningOptions.ShortMethod)]
private void WriteUnsignedInt(Stream stream, uint value)
BinaryPrimitives.WriteInt32LittleEndian(this.buffer, channelInfo.YSampling);
stream.Write(this.buffer.AsSpan(0, 4));
}
private static void WriteString(Stream stream, string str)
{
foreach (char c in str)
{
BinaryPrimitives.WriteUInt32LittleEndian(this.buffer, value);
stream.Write(this.buffer.AsSpan(0, 4));
stream.WriteByte((byte)c);
}
// Write termination byte.
stream.WriteByte(0);
}
private void WriteBoxInteger(Stream stream, ExrBox2i box)
{
BinaryPrimitives.WriteInt32LittleEndian(this.buffer, box.XMin);
stream.Write(this.buffer.AsSpan(0, 4));
BinaryPrimitives.WriteInt32LittleEndian(this.buffer, box.YMin);
stream.Write(this.buffer.AsSpan(0, 4));
BinaryPrimitives.WriteInt32LittleEndian(this.buffer, box.XMax);
stream.Write(this.buffer.AsSpan(0, 4));
BinaryPrimitives.WriteInt32LittleEndian(this.buffer, box.YMax);
stream.Write(this.buffer.AsSpan(0, 4));
}
[MethodImpl(InliningOptions.ShortMethod)]
private unsafe void WriteSingle(Stream stream, float value)
{
BinaryPrimitives.WriteInt32LittleEndian(this.buffer, *(int*)&value);
stream.Write(this.buffer.AsSpan(0, 4));
}
[MethodImpl(InliningOptions.ShortMethod)]
private void WriteHalfSingle(Stream stream, float value)
{
ushort valueAsShort = HalfTypeHelper.Pack(value);
BinaryPrimitives.WriteUInt16LittleEndian(this.buffer, valueAsShort);
stream.Write(this.buffer.AsSpan(0, 2));
}
[MethodImpl(InliningOptions.ShortMethod)]
private void WriteUnsignedInt(Stream stream, uint value)
{
BinaryPrimitives.WriteUInt32LittleEndian(this.buffer, value);
stream.Write(this.buffer.AsSpan(0, 4));
}
}

48
src/ImageSharp/Formats/OpenExr/ExrFormat.cs

@ -1,38 +1,34 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
// Licensed under the Six Labors Split License.
using System.Collections.Generic;
using SixLabors.ImageSharp.Formats.Bmp;
namespace SixLabors.ImageSharp.Formats.OpenExr;
namespace SixLabors.ImageSharp.Formats.OpenExr
/// <summary>
/// Registers the image encoders, decoders and mime type detectors for the OpenExr format.
/// </summary>
public sealed class ExrFormat : IImageFormat<ExrMetadata>
{
/// <summary>
/// Registers the image encoders, decoders and mime type detectors for the OpenExr format.
/// </summary>
public sealed class ExrFormat : IImageFormat<ExrMetadata>
private ExrFormat()
{
private ExrFormat()
{
}
}
/// <summary>
/// Gets the current instance.
/// </summary>
public static ExrFormat Instance { get; } = new();
/// <summary>
/// Gets the current instance.
/// </summary>
public static ExrFormat Instance { get; } = new();
/// <inheritdoc/>
public string Name => "EXR";
/// <inheritdoc/>
public string Name => "EXR";
/// <inheritdoc/>
public string DefaultMimeType => "image/x-exr";
/// <inheritdoc/>
public string DefaultMimeType => "image/x-exr";
/// <inheritdoc/>
public IEnumerable<string> MimeTypes => ExrConstants.MimeTypes;
/// <inheritdoc/>
public IEnumerable<string> MimeTypes => ExrConstants.MimeTypes;
/// <inheritdoc/>
public IEnumerable<string> FileExtensions => ExrConstants.FileExtensions;
/// <inheritdoc/>
public IEnumerable<string> FileExtensions => ExrConstants.FileExtensions;
/// <inheritdoc/>
public ExrMetadata CreateDefaultFormatMetadata() => new();
}
/// <inheritdoc/>
public ExrMetadata CreateDefaultFormatMetadata() => new();
}

114
src/ImageSharp/Formats/OpenExr/ExrHeaderAttributes.cs

@ -1,78 +1,76 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
// Licensed under the Six Labors Split License.
using System.Collections.Generic;
using SixLabors.ImageSharp.Formats.OpenExr.Compression;
namespace SixLabors.ImageSharp.Formats.OpenExr
namespace SixLabors.ImageSharp.Formats.OpenExr;
internal class ExrHeaderAttributes
{
internal class ExrHeaderAttributes
{
public IList<ExrChannelInfo> Channels { get; set; }
public IList<ExrChannelInfo> Channels { get; set; }
public ExrCompressionType? Compression { get; set; }
public ExrCompressionType? Compression { get; set; }
public ExrBox2i? DataWindow { get; set; }
public ExrBox2i? DataWindow { get; set; }
public ExrBox2i? DisplayWindow { get; set; }
public ExrBox2i? DisplayWindow { get; set; }
public ExrLineOrder? LineOrder { get; set; }
public ExrLineOrder? LineOrder { get; set; }
public float? AspectRatio { get; set; }
public float? AspectRatio { get; set; }
public float? ScreenWindowWidth { get; set; }
public float? ScreenWindowWidth { get; set; }
public PointF? ScreenWindowCenter { get; set; }
public PointF? ScreenWindowCenter { get; set; }
public uint? TileXSize { get; set; }
public uint? TileXSize { get; set; }
public uint? TileYSize { get; set; }
public uint? TileYSize { get; set; }
public int? ChunkCount { get; set; }
public int? ChunkCount { get; set; }
public bool IsValid()
public bool IsValid()
{
if (!this.Compression.HasValue)
{
if (!this.Compression.HasValue)
{
return false;
}
if (!this.DataWindow.HasValue)
{
return false;
}
if (!this.DisplayWindow.HasValue)
{
return false;
}
if (!this.LineOrder.HasValue)
{
return false;
}
if (!this.AspectRatio.HasValue)
{
return false;
}
if (!this.ScreenWindowWidth.HasValue)
{
return false;
}
if (!this.ScreenWindowCenter.HasValue)
{
return false;
}
if (this.Channels is null)
{
return false;
}
return true;
return false;
}
if (!this.DataWindow.HasValue)
{
return false;
}
if (!this.DisplayWindow.HasValue)
{
return false;
}
if (!this.LineOrder.HasValue)
{
return false;
}
if (!this.AspectRatio.HasValue)
{
return false;
}
if (!this.ScreenWindowWidth.HasValue)
{
return false;
}
if (!this.ScreenWindowCenter.HasValue)
{
return false;
}
if (this.Channels is null)
{
return false;
}
return true;
}
}

17
src/ImageSharp/Formats/OpenExr/ExrImageDataType.cs

@ -1,16 +1,15 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
// Licensed under the Six Labors Split License.
namespace SixLabors.ImageSharp.Formats.OpenExr
namespace SixLabors.ImageSharp.Formats.OpenExr;
internal enum ExrImageDataType
{
internal enum ExrImageDataType
{
Unknown = 0,
Unknown = 0,
Rgb = 1,
Rgb = 1,
Rgba = 2,
Rgba = 2,
Gray = 3,
}
Gray = 3,
}

38
src/ImageSharp/Formats/OpenExr/ExrImageFormatDetector.cs

@ -1,31 +1,29 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
// Licensed under the Six Labors Split License.
using System;
using System.Buffers.Binary;
namespace SixLabors.ImageSharp.Formats.OpenExr
namespace SixLabors.ImageSharp.Formats.OpenExr;
/// <summary>
/// Detects OpenExr file headers.
/// </summary>
public sealed class ExrImageFormatDetector : IImageFormatDetector
{
/// <summary>
/// Detects OpenExr file headers.
/// </summary>
public sealed class ExrImageFormatDetector : IImageFormatDetector
{
/// <inheritdoc/>
public int HeaderSize => 4;
/// <inheritdoc/>
public int HeaderSize => 4;
/// <inheritdoc/>
public IImageFormat DetectFormat(ReadOnlySpan<byte> header) => this.IsSupportedFileFormat(header) ? ExrFormat.Instance : null;
/// <inheritdoc/>
public IImageFormat DetectFormat(ReadOnlySpan<byte> header) => this.IsSupportedFileFormat(header) ? ExrFormat.Instance : null;
private bool IsSupportedFileFormat(ReadOnlySpan<byte> header)
private bool IsSupportedFileFormat(ReadOnlySpan<byte> header)
{
if (header.Length >= this.HeaderSize)
{
if (header.Length >= this.HeaderSize)
{
int fileTypeMarker = BinaryPrimitives.ReadInt32LittleEndian(header);
return fileTypeMarker == ExrConstants.MagickBytes;
}
return false;
int fileTypeMarker = BinaryPrimitives.ReadInt32LittleEndian(header);
return fileTypeMarker == ExrConstants.MagickBytes;
}
return false;
}
}

13
src/ImageSharp/Formats/OpenExr/ExrImageType.cs

@ -1,12 +1,11 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
// Licensed under the Six Labors Split License.
namespace SixLabors.ImageSharp.Formats.OpenExr
namespace SixLabors.ImageSharp.Formats.OpenExr;
internal enum ExrImageType
{
internal enum ExrImageType
{
ScanLine = 0,
ScanLine = 0,
Tiled = 1
}
Tiled = 1
}

15
src/ImageSharp/Formats/OpenExr/ExrLineOrder.cs

@ -1,14 +1,13 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
// Licensed under the Six Labors Split License.
namespace SixLabors.ImageSharp.Formats.OpenExr
namespace SixLabors.ImageSharp.Formats.OpenExr;
internal enum ExrLineOrder : byte
{
internal enum ExrLineOrder : byte
{
IncreasingY = 0,
IncreasingY = 0,
DecreasingY = 1,
DecreasingY = 1,
RandomY = 2
}
RandomY = 2
}

43
src/ImageSharp/Formats/OpenExr/ExrMetadata.cs

@ -1,32 +1,31 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
// Licensed under the Six Labors Split License.
namespace SixLabors.ImageSharp.Formats.OpenExr
namespace SixLabors.ImageSharp.Formats.OpenExr;
/// <summary>
/// Provides OpenExr specific metadata information for the image.
/// </summary>
public class ExrMetadata : IDeepCloneable
{
/// <summary>
/// Provides OpenExr specific metadata information for the image.
/// Initializes a new instance of the <see cref="ExrMetadata"/> class.
/// </summary>
public class ExrMetadata : IDeepCloneable
public ExrMetadata()
{
/// <summary>
/// Initializes a new instance of the <see cref="ExrMetadata"/> class.
/// </summary>
public ExrMetadata()
{
}
}
/// <summary>
/// Initializes a new instance of the <see cref="ExrMetadata"/> class.
/// </summary>
/// <param name="other">The metadata to create an instance from.</param>
private ExrMetadata(ExrMetadata other) => this.PixelType = other.PixelType;
/// <summary>
/// Initializes a new instance of the <see cref="ExrMetadata"/> class.
/// </summary>
/// <param name="other">The metadata to create an instance from.</param>
private ExrMetadata(ExrMetadata other) => this.PixelType = other.PixelType;
/// <summary>
/// Gets or sets the pixel format.
/// </summary>
public ExrPixelType PixelType { get; set; } = ExrPixelType.Float;
/// <summary>
/// Gets or sets the pixel format.
/// </summary>
public ExrPixelType PixelType { get; set; } = ExrPixelType.Float;
/// <inheritdoc/>
public IDeepCloneable DeepClone() => new ExrMetadata(this);
}
/// <inheritdoc/>
public IDeepCloneable DeepClone() => new ExrMetadata(this);
}

35
src/ImageSharp/Formats/OpenExr/ExrPixelType.cs

@ -1,26 +1,25 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
// Licensed under the Six Labors Split License.
namespace SixLabors.ImageSharp.Formats.OpenExr
namespace SixLabors.ImageSharp.Formats.OpenExr;
/// <summary>
/// The different pixel formats for a OpenEXR image.
/// </summary>
public enum ExrPixelType
{
/// <summary>
/// The different pixel formats for a OpenEXR image.
/// unsigned int (32 bit).
/// </summary>
public enum ExrPixelType
{
/// <summary>
/// unsigned int (32 bit).
/// </summary>
UnsignedInt = 0,
UnsignedInt = 0,
/// <summary>
/// half (16 bit floating point).
/// </summary>
Half = 1,
/// <summary>
/// half (16 bit floating point).
/// </summary>
Half = 1,
/// <summary>
/// float (32 bit floating point).
/// </summary>
Float = 2
}
/// <summary>
/// float (32 bit floating point).
/// </summary>
Float = 2
}

34
src/ImageSharp/Formats/OpenExr/ExrThrowHelper.cs

@ -1,32 +1,22 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
// Licensed under the Six Labors Split License.
using System;
using System.Runtime.CompilerServices;
namespace SixLabors.ImageSharp.Formats.OpenExr;
namespace SixLabors.ImageSharp.Formats.OpenExr
/// <summary>
/// Cold path optimizations for throwing exr format based exceptions.
/// </summary>
internal static class ExrThrowHelper
{
/// <summary>
/// Cold path optimizations for throwing exr format based exceptions.
/// </summary>
internal static class ExrThrowHelper
{
[MethodImpl(InliningOptions.ColdPath)]
public static Exception NotSupportedDecompressor(string compressionType) => throw new NotSupportedException($"Not supported decoder compression method: {compressionType}");
public static Exception NotSupportedDecompressor(string compressionType) => throw new NotSupportedException($"Not supported decoder compression method: {compressionType}");
[MethodImpl(MethodImplOptions.NoInlining)]
public static void ThrowInvalidImageContentException(string errorMessage) => throw new InvalidImageContentException(errorMessage);
public static void ThrowInvalidImageContentException(string errorMessage) => throw new InvalidImageContentException(errorMessage);
[MethodImpl(InliningOptions.ColdPath)]
public static void ThrowNotSupportedVersion() => throw new NotSupportedException("Unsupported EXR version");
public static void ThrowNotSupportedVersion() => throw new NotSupportedException("Unsupported EXR version");
[MethodImpl(InliningOptions.ColdPath)]
public static void ThrowNotSupported(string msg) => throw new NotSupportedException(msg);
public static void ThrowNotSupported(string msg) => throw new NotSupportedException(msg);
[MethodImpl(InliningOptions.ColdPath)]
public static void ThrowInvalidImageHeader() => throw new InvalidImageContentException("Invalid EXR image header");
public static void ThrowInvalidImageHeader() => throw new InvalidImageContentException("Invalid EXR image header");
[MethodImpl(InliningOptions.ColdPath)]
public static void ThrowInvalidImageHeader(string msg) => throw new InvalidImageContentException(msg);
}
public static void ThrowInvalidImageHeader(string msg) => throw new InvalidImageContentException(msg);
}

12
src/ImageSharp/Formats/OpenExr/IExrDecoderOptions.cs

@ -1,12 +0,0 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
namespace SixLabors.ImageSharp.Formats.OpenExr
{
/// <summary>
/// Image decoder options for decoding OpenExr streams.
/// </summary>
internal interface IExrDecoderOptions
{
}
}

19
src/ImageSharp/Formats/OpenExr/IExrEncoderOptions.cs

@ -1,16 +1,15 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
// Licensed under the Six Labors Split License.
namespace SixLabors.ImageSharp.Formats.OpenExr
namespace SixLabors.ImageSharp.Formats.OpenExr;
/// <summary>
/// Configuration options for use during OpenExr encoding.
/// </summary>
internal interface IExrEncoderOptions
{
/// <summary>
/// Configuration options for use during OpenExr encoding.
/// Gets the pixel type of the image.
/// </summary>
internal interface IExrEncoderOptions
{
/// <summary>
/// Gets the pixel type of the image.
/// </summary>
ExrPixelType? PixelType { get; }
}
ExrPixelType? PixelType { get; }
}

23
src/ImageSharp/Formats/OpenExr/MetadataExtensions.cs

@ -1,21 +1,20 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
// Licensed under the Six Labors Split License.
using SixLabors.ImageSharp.Formats.OpenExr;
using SixLabors.ImageSharp.Metadata;
namespace SixLabors.ImageSharp
namespace SixLabors.ImageSharp;
/// <summary>
/// Extension methods for the <see cref="ImageMetadata"/> type.
/// </summary>
public static partial class MetadataExtensions
{
/// <summary>
/// Extension methods for the <see cref="ImageMetadata"/> type.
/// Gets the open exr format specific metadata for the image.
/// </summary>
public static partial class MetadataExtensions
{
/// <summary>
/// Gets the open exr format specific metadata for the image.
/// </summary>
/// <param name="metadata">The metadata this method extends.</param>
/// <returns>The <see cref="ExrMetadata"/>.</returns>
public static ExrMetadata GetExrMetadata(this ImageMetadata metadata) => metadata.GetFormatMetadata(ExrFormat.Instance);
}
/// <param name="metadata">The metadata this method extends.</param>
/// <returns>The <see cref="ExrMetadata"/>.</returns>
public static ExrMetadata GetExrMetadata(this ImageMetadata metadata) => metadata.GetFormatMetadata(ExrFormat.Instance);
}

26
src/ImageSharp/IO/BufferedReadStream.cs

@ -2,6 +2,7 @@
// Licensed under the Six Labors Split License.
using System.Buffers;
using System.Buffers.Binary;
using System.Runtime.CompilerServices;
namespace SixLabors.ImageSharp.IO;
@ -160,6 +161,31 @@ internal sealed class BufferedReadStream : Stream
}
}
/// <summary>
/// Reads a float value.
/// </summary>
/// <returns>The value.</returns>
public float ReadSingle(byte[] data)
{
int offset = 0;
int bytesToRead = 4;
while (bytesToRead > 0)
{
int bytesRead = this.Read(data, offset, bytesToRead);
if (bytesRead == 0)
{
throw new ImageFormatException("Not enough data to read a float value from the stream");
}
bytesToRead -= bytesRead;
offset += bytesRead;
}
int intValue = BinaryPrimitives.ReadInt32BigEndian(data);
return Unsafe.As<int, float>(ref intValue);
}
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public override int Read(byte[] buffer, int offset, int count)

324
src/ImageSharp/PixelFormats/PixelImplementations/Rgb96.cs

@ -1,175 +1,173 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
// Licensed under the Six Labors Split License.
using System;
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace SixLabors.ImageSharp.PixelFormats
namespace SixLabors.ImageSharp.PixelFormats;
/// <summary>
/// Pixel type containing three 32-bit unsigned normalized values ranging from 0 to 4294967295.
/// The color components are stored in red, green, blue.
/// <para>
/// Ranges from [0, 0, 0] to [1, 1, 1] in vector form.
/// </para>
/// </summary>
[StructLayout(LayoutKind.Sequential)]
public partial struct Rgb96 : IPixel<Rgb96>
{
private const float InvMax = 1.0f / uint.MaxValue;
private const double Max = uint.MaxValue;
/// <summary>
/// Gets the red component.
/// </summary>
public uint R;
/// <summary>
/// Gets the green component.
/// </summary>
public uint G;
/// <summary>
/// Gets the blue component.
/// </summary>
public uint B;
/// <summary>
/// Initializes a new instance of the <see cref="Rgb96"/> struct.
/// </summary>
/// <param name="r">The red component.</param>
/// <param name="g">The green component.</param>
/// <param name="b">The blue component.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Rgb96(uint r, uint g, uint b)
{
this.R = r;
this.G = g;
this.B = b;
}
/// <summary>
/// Compares two <see cref="Rgb96"/> objects for equality.
/// </summary>
/// <param name="left">The <see cref="Rgb96"/> on the left side of the operand.</param>
/// <returns>
/// True if the <paramref name="left"/> parameter is equal to the <paramref name="right"/> parameter; otherwise, false.
/// </returns>
/// <param name="right">The <see cref="Rgb96"/> on the right side of the operand.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool operator ==(Rgb96 left, Rgb96 right) => left.Equals(right);
/// <summary>
/// Pixel type containing three 32-bit unsigned normalized values ranging from 0 to 4294967295.
/// The color components are stored in red, green, blue.
/// <para>
/// Ranges from [0, 0, 0] to [1, 1, 1] in vector form.
/// </para>
/// Compares two <see cref="Rgb96"/> objects for equality.
/// </summary>
[StructLayout(LayoutKind.Sequential)]
public partial struct Rgb96 : IPixel<Rgb96>
/// <param name="left">The <see cref="Rgb96"/> on the left side of the operand.</param>
/// <param name="right">The <see cref="Rgb96"/> on the right side of the operand.</param>
/// <returns>
/// True if the <paramref name="left"/> parameter is not equal to the <paramref name="right"/> parameter; otherwise, false.
/// </returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool operator !=(Rgb96 left, Rgb96 right) => !left.Equals(right);
/// <inheritdoc />
public PixelOperations<Rgb96> CreatePixelOperations() => new();
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void FromScaledVector4(Vector4 vector) => this.FromVector4(vector);
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Vector4 ToScaledVector4() => this.ToVector4();
/// <inheritdoc />
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void FromVector4(Vector4 vector)
{
private const float InvMax = 1.0f / uint.MaxValue;
private const double Max = uint.MaxValue;
/// <summary>
/// Gets the red component.
/// </summary>
public uint R;
/// <summary>
/// Gets the green component.
/// </summary>
public uint G;
/// <summary>
/// Gets the blue component.
/// </summary>
public uint B;
/// <summary>
/// Initializes a new instance of the <see cref="Rgb96"/> struct.
/// </summary>
/// <param name="r">The red component.</param>
/// <param name="g">The green component.</param>
/// <param name="b">The blue component.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Rgb96(uint r, uint g, uint b)
{
this.R = r;
this.G = g;
this.B = b;
}
/// <summary>
/// Compares two <see cref="Rgb96"/> objects for equality.
/// </summary>
/// <param name="left">The <see cref="Rgb96"/> on the left side of the operand.</param>
/// <returns>
/// True if the <paramref name="left"/> parameter is equal to the <paramref name="right"/> parameter; otherwise, false.
/// </returns>
/// <param name="right">The <see cref="Rgb96"/> on the right side of the operand.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool operator ==(Rgb96 left, Rgb96 right) => left.Equals(right);
/// <summary>
/// Compares two <see cref="Rgb96"/> objects for equality.
/// </summary>
/// <param name="left">The <see cref="Rgb96"/> on the left side of the operand.</param>
/// <param name="right">The <see cref="Rgb96"/> on the right side of the operand.</param>
/// <returns>
/// True if the <paramref name="left"/> parameter is not equal to the <paramref name="right"/> parameter; otherwise, false.
/// </returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool operator !=(Rgb96 left, Rgb96 right) => !left.Equals(right);
/// <inheritdoc />
public PixelOperations<Rgb96> CreatePixelOperations() => new();
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void FromScaledVector4(Vector4 vector) => this.FromVector4(vector);
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Vector4 ToScaledVector4() => this.ToVector4();
/// <inheritdoc />
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void FromVector4(Vector4 vector)
{
vector = Numerics.Clamp(vector, Vector4.Zero, Vector4.One);
this.R = (uint)(vector.X * Max);
this.G = (uint)(vector.Y * Max);
this.B = (uint)(vector.Z * Max);
}
/// <inheritdoc />
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Vector4 ToVector4() => new(
this.R * InvMax,
this.G * InvMax,
this.B * InvMax,
1.0f);
/// <inheritdoc />
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void FromBgra5551(Bgra5551 source) => this.FromScaledVector4(source.ToScaledVector4());
/// <inheritdoc />
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void FromArgb32(Argb32 source) => this.FromScaledVector4(source.ToScaledVector4());
/// <inheritdoc />
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void FromBgr24(Bgr24 source) => this.FromScaledVector4(source.ToScaledVector4());
/// <inheritdoc />
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void FromBgra32(Bgra32 source) => this.FromScaledVector4(source.ToScaledVector4());
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void FromL8(L8 source) => this.FromScaledVector4(source.ToScaledVector4());
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void FromL16(L16 source) => this.FromScaledVector4(source.ToScaledVector4());
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void FromLa16(La16 source) => this.FromScaledVector4(source.ToScaledVector4());
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void FromLa32(La32 source) => this.FromScaledVector4(source.ToScaledVector4());
/// <inheritdoc />
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void FromRgb24(Rgb24 source) => this.FromScaledVector4(source.ToScaledVector4());
/// <inheritdoc />
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void FromRgba32(Rgba32 source) => this.FromScaledVector4(source.ToScaledVector4());
/// <inheritdoc />
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void FromAbgr32(Abgr32 source) => this.FromScaledVector4(source.ToScaledVector4());
/// <inheritdoc />
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void ToRgba32(ref Rgba32 dest) => dest.FromScaledVector4(this.ToScaledVector4());
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void FromRgb48(Rgb48 source) => this.FromScaledVector4(source.ToScaledVector4());
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void FromRgba64(Rgba64 source) => this.FromScaledVector4(source.ToScaledVector4());
/// <inheritdoc />
public override bool Equals(object obj) => obj is Rgb96 other && this.Equals(other);
/// <inheritdoc />
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public override int GetHashCode() => HashCode.Combine(this.R, this.G, this.B);
/// <inheritdoc />
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool Equals(Rgb96 other) => this.R.Equals(other.R) && this.G.Equals(other.G) && this.B.Equals(other.B);
/// <inheritdoc />
public override string ToString() => FormattableString.Invariant($"Rgb96({this.R}, {this.G}, {this.B})");
vector = Numerics.Clamp(vector, Vector4.Zero, Vector4.One);
this.R = (uint)(vector.X * Max);
this.G = (uint)(vector.Y * Max);
this.B = (uint)(vector.Z * Max);
}
/// <inheritdoc />
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Vector4 ToVector4() => new(
this.R * InvMax,
this.G * InvMax,
this.B * InvMax,
1.0f);
/// <inheritdoc />
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void FromBgra5551(Bgra5551 source) => this.FromScaledVector4(source.ToScaledVector4());
/// <inheritdoc />
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void FromArgb32(Argb32 source) => this.FromScaledVector4(source.ToScaledVector4());
/// <inheritdoc />
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void FromBgr24(Bgr24 source) => this.FromScaledVector4(source.ToScaledVector4());
/// <inheritdoc />
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void FromBgra32(Bgra32 source) => this.FromScaledVector4(source.ToScaledVector4());
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void FromL8(L8 source) => this.FromScaledVector4(source.ToScaledVector4());
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void FromL16(L16 source) => this.FromScaledVector4(source.ToScaledVector4());
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void FromLa16(La16 source) => this.FromScaledVector4(source.ToScaledVector4());
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void FromLa32(La32 source) => this.FromScaledVector4(source.ToScaledVector4());
/// <inheritdoc />
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void FromRgb24(Rgb24 source) => this.FromScaledVector4(source.ToScaledVector4());
/// <inheritdoc />
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void FromRgba32(Rgba32 source) => this.FromScaledVector4(source.ToScaledVector4());
/// <inheritdoc />
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void FromAbgr32(Abgr32 source) => this.FromScaledVector4(source.ToScaledVector4());
/// <inheritdoc />
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void ToRgba32(ref Rgba32 dest) => dest.FromScaledVector4(this.ToScaledVector4());
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void FromRgb48(Rgb48 source) => this.FromScaledVector4(source.ToScaledVector4());
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void FromRgba64(Rgba64 source) => this.FromScaledVector4(source.ToScaledVector4());
/// <inheritdoc />
public override bool Equals(object obj) => obj is Rgb96 other && this.Equals(other);
/// <inheritdoc />
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public override int GetHashCode() => HashCode.Combine(this.R, this.G, this.B);
/// <inheritdoc />
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool Equals(Rgb96 other) => this.R.Equals(other.R) && this.G.Equals(other.G) && this.B.Equals(other.B);
/// <inheritdoc />
public override string ToString() => FormattableString.Invariant($"Rgb96({this.R}, {this.G}, {this.B})");
}

340
src/ImageSharp/PixelFormats/PixelImplementations/Rgba128.cs

@ -1,183 +1,181 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
// Licensed under the Six Labors Split License.
using System;
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace SixLabors.ImageSharp.PixelFormats
namespace SixLabors.ImageSharp.PixelFormats;
/// <summary>
/// Pixel type containing four 32-bit unsigned normalized values ranging from 0 to 4294967295.
/// The color components are stored in red, green, blue and alpha.
/// <para>
/// Ranges from [0, 0, 0, 0] to [1, 1, 1, 1] in vector form.
/// </para>
/// </summary>
[StructLayout(LayoutKind.Sequential)]
public partial struct Rgba128 : IPixel<Rgba128>
{
private const float InvMax = 1.0f / uint.MaxValue;
private const double Max = uint.MaxValue;
/// <summary>
/// Gets the red component.
/// </summary>
public uint R;
/// <summary>
/// Gets the green component.
/// </summary>
public uint G;
/// <summary>
/// Gets the blue component.
/// </summary>
public uint B;
/// <summary>
/// Pixel type containing four 32-bit unsigned normalized values ranging from 0 to 4294967295.
/// The color components are stored in red, green, blue and alpha.
/// <para>
/// Ranges from [0, 0, 0, 0] to [1, 1, 1, 1] in vector form.
/// </para>
/// Gets the alpha channel.
/// </summary>
[StructLayout(LayoutKind.Sequential)]
public partial struct Rgba128 : IPixel<Rgba128>
public uint A;
/// <summary>
/// Initializes a new instance of the <see cref="Rgba128"/> struct.
/// </summary>
/// <param name="r">The red component.</param>
/// <param name="g">The green component.</param>
/// <param name="b">The blue component.</param>
/// <param name="a">The alpha component.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Rgba128(uint r, uint g, uint b, uint a)
{
private const float InvMax = 1.0f / uint.MaxValue;
private const double Max = uint.MaxValue;
/// <summary>
/// Gets the red component.
/// </summary>
public uint R;
/// <summary>
/// Gets the green component.
/// </summary>
public uint G;
/// <summary>
/// Gets the blue component.
/// </summary>
public uint B;
/// <summary>
/// Gets the alpha channel.
/// </summary>
public uint A;
/// <summary>
/// Initializes a new instance of the <see cref="Rgba128"/> struct.
/// </summary>
/// <param name="r">The red component.</param>
/// <param name="g">The green component.</param>
/// <param name="b">The blue component.</param>
/// <param name="a">The alpha component.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Rgba128(uint r, uint g, uint b, uint a)
{
this.R = r;
this.G = g;
this.B = b;
this.A = a;
}
/// <summary>
/// Compares two <see cref="Rgba128"/> objects for equality.
/// </summary>
/// <param name="left">The <see cref="Rgba128"/> on the left side of the operand.</param>
/// <returns>
/// True if the <paramref name="left"/> parameter is equal to the <paramref name="right"/> parameter; otherwise, false.
/// </returns>
/// <param name="right">The <see cref="Rgba128"/> on the right side of the operand.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool operator ==(Rgba128 left, Rgba128 right) => left.Equals(right);
/// <summary>
/// Compares two <see cref="Rgba128"/> objects for equality.
/// </summary>
/// <param name="left">The <see cref="Rgba128"/> on the left side of the operand.</param>
/// <param name="right">The <see cref="Rgba128"/> on the right side of the operand.</param>
/// <returns>
/// True if the <paramref name="left"/> parameter is not equal to the <paramref name="right"/> parameter; otherwise, false.
/// </returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool operator !=(Rgba128 left, Rgba128 right) => !left.Equals(right);
/// <inheritdoc />
public PixelOperations<Rgba128> CreatePixelOperations() => new();
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void FromScaledVector4(Vector4 vector) => this.FromVector4(vector);
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Vector4 ToScaledVector4() => this.ToVector4();
/// <inheritdoc />
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void FromVector4(Vector4 vector)
{
vector = Numerics.Clamp(vector, Vector4.Zero, Vector4.One);
this.R = (uint)(vector.X * Max);
this.G = (uint)(vector.Y * Max);
this.B = (uint)(vector.Z * Max);
this.A = (uint)(vector.W * Max);
}
/// <inheritdoc />
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Vector4 ToVector4() => new(
this.R * InvMax,
this.G * InvMax,
this.B * InvMax,
this.A * InvMax);
/// <inheritdoc />
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void FromBgra5551(Bgra5551 source) => this.FromScaledVector4(source.ToScaledVector4());
/// <inheritdoc />
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void FromArgb32(Argb32 source) => this.FromScaledVector4(source.ToScaledVector4());
/// <inheritdoc />
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void FromBgr24(Bgr24 source) => this.FromScaledVector4(source.ToScaledVector4());
/// <inheritdoc />
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void FromBgra32(Bgra32 source) => this.FromScaledVector4(source.ToScaledVector4());
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void FromL8(L8 source) => this.FromScaledVector4(source.ToScaledVector4());
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void FromL16(L16 source) => this.FromScaledVector4(source.ToScaledVector4());
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void FromLa16(La16 source) => this.FromScaledVector4(source.ToScaledVector4());
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void FromLa32(La32 source) => this.FromScaledVector4(source.ToScaledVector4());
/// <inheritdoc />
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void FromRgb24(Rgb24 source) => this.FromScaledVector4(source.ToScaledVector4());
/// <inheritdoc />
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void FromRgba32(Rgba32 source) => this.FromScaledVector4(source.ToScaledVector4());
/// <inheritdoc />
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void FromAbgr32(Abgr32 source) => this.FromScaledVector4(source.ToScaledVector4());
/// <inheritdoc />
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void ToRgba32(ref Rgba32 dest) => dest.FromScaledVector4(this.ToScaledVector4());
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void FromRgb48(Rgb48 source) => this.FromScaledVector4(source.ToScaledVector4());
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void FromRgba64(Rgba64 source) => this.FromScaledVector4(source.ToScaledVector4());
/// <inheritdoc />
public override bool Equals(object obj) => obj is Rgba128 other && this.Equals(other);
/// <inheritdoc />
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public override int GetHashCode() => HashCode.Combine(this.R, this.G, this.B, this.A);
/// <inheritdoc />
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool Equals(Rgba128 other) => this.R.Equals(other.R) && this.G.Equals(other.G) && this.B.Equals(other.B) && this.A.Equals(other.A);
/// <inheritdoc />
public override string ToString() => FormattableString.Invariant($"Rgba128({this.R}, {this.G}, {this.B}, {this.A})");
this.R = r;
this.G = g;
this.B = b;
this.A = a;
}
/// <summary>
/// Compares two <see cref="Rgba128"/> objects for equality.
/// </summary>
/// <param name="left">The <see cref="Rgba128"/> on the left side of the operand.</param>
/// <returns>
/// True if the <paramref name="left"/> parameter is equal to the <paramref name="right"/> parameter; otherwise, false.
/// </returns>
/// <param name="right">The <see cref="Rgba128"/> on the right side of the operand.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool operator ==(Rgba128 left, Rgba128 right) => left.Equals(right);
/// <summary>
/// Compares two <see cref="Rgba128"/> objects for equality.
/// </summary>
/// <param name="left">The <see cref="Rgba128"/> on the left side of the operand.</param>
/// <param name="right">The <see cref="Rgba128"/> on the right side of the operand.</param>
/// <returns>
/// True if the <paramref name="left"/> parameter is not equal to the <paramref name="right"/> parameter; otherwise, false.
/// </returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool operator !=(Rgba128 left, Rgba128 right) => !left.Equals(right);
/// <inheritdoc />
public PixelOperations<Rgba128> CreatePixelOperations() => new();
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void FromScaledVector4(Vector4 vector) => this.FromVector4(vector);
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Vector4 ToScaledVector4() => this.ToVector4();
/// <inheritdoc />
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void FromVector4(Vector4 vector)
{
vector = Numerics.Clamp(vector, Vector4.Zero, Vector4.One);
this.R = (uint)(vector.X * Max);
this.G = (uint)(vector.Y * Max);
this.B = (uint)(vector.Z * Max);
this.A = (uint)(vector.W * Max);
}
/// <inheritdoc />
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Vector4 ToVector4() => new(
this.R * InvMax,
this.G * InvMax,
this.B * InvMax,
this.A * InvMax);
/// <inheritdoc />
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void FromBgra5551(Bgra5551 source) => this.FromScaledVector4(source.ToScaledVector4());
/// <inheritdoc />
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void FromArgb32(Argb32 source) => this.FromScaledVector4(source.ToScaledVector4());
/// <inheritdoc />
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void FromBgr24(Bgr24 source) => this.FromScaledVector4(source.ToScaledVector4());
/// <inheritdoc />
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void FromBgra32(Bgra32 source) => this.FromScaledVector4(source.ToScaledVector4());
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void FromL8(L8 source) => this.FromScaledVector4(source.ToScaledVector4());
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void FromL16(L16 source) => this.FromScaledVector4(source.ToScaledVector4());
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void FromLa16(La16 source) => this.FromScaledVector4(source.ToScaledVector4());
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void FromLa32(La32 source) => this.FromScaledVector4(source.ToScaledVector4());
/// <inheritdoc />
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void FromRgb24(Rgb24 source) => this.FromScaledVector4(source.ToScaledVector4());
/// <inheritdoc />
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void FromRgba32(Rgba32 source) => this.FromScaledVector4(source.ToScaledVector4());
/// <inheritdoc />
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void FromAbgr32(Abgr32 source) => this.FromScaledVector4(source.ToScaledVector4());
/// <inheritdoc />
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void ToRgba32(ref Rgba32 dest) => dest.FromScaledVector4(this.ToScaledVector4());
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void FromRgb48(Rgb48 source) => this.FromScaledVector4(source.ToScaledVector4());
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void FromRgba64(Rgba64 source) => this.FromScaledVector4(source.ToScaledVector4());
/// <inheritdoc />
public override bool Equals(object obj) => obj is Rgba128 other && this.Equals(other);
/// <inheritdoc />
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public override int GetHashCode() => HashCode.Combine(this.R, this.G, this.B, this.A);
/// <inheritdoc />
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool Equals(Rgba128 other) => this.R.Equals(other.R) && this.G.Equals(other.G) && this.B.Equals(other.B) && this.A.Equals(other.A);
/// <inheritdoc />
public override string ToString() => FormattableString.Invariant($"Rgba128({this.R}, {this.G}, {this.B}, {this.A})");
}

64
tests/ImageSharp.Tests/Formats/Exr/ExrDecoderTests.cs

@ -0,0 +1,64 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using SixLabors.ImageSharp.Formats.OpenExr;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Tests.Formats.Exr;
[Trait("Format", "Exr")]
[ValidateDisposedMemoryAllocations]
public class ExrDecoderTests
{
private static ExrDecoder ExrDecoder => new();
[Theory]
[WithFile(TestImages.Exr.Uncompressed, PixelTypes.Rgba32)]
public void ExrDecoder_CanDecode_Uncompressed<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel>
{
using Image<TPixel> image = provider.GetImage(ExrDecoder);
image.DebugSave(provider);
image.CompareToOriginal(provider);
}
[Theory]
[WithFile(TestImages.Exr.Zip, PixelTypes.Rgba32)]
public void ExrDecoder_CanDecode_ZipCompressed<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel>
{
using Image<TPixel> image = provider.GetImage(ExrDecoder);
image.DebugSave(provider);
image.CompareToOriginal(provider);
}
[Theory]
[WithFile(TestImages.Exr.Zips, PixelTypes.Rgba32)]
public void ExrDecoder_CanDecode_ZipsCompressed<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel>
{
using Image<TPixel> image = provider.GetImage(ExrDecoder);
image.DebugSave(provider);
image.CompareToOriginal(provider);
}
[Theory]
[WithFile(TestImages.Exr.Rle, PixelTypes.Rgba32)]
public void ExrDecoder_CanDecode_RunLengthCompressed<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel>
{
using Image<TPixel> image = provider.GetImage(ExrDecoder);
image.DebugSave(provider);
image.CompareToOriginal(provider);
}
[Theory]
[WithFile(TestImages.Exr.B44, PixelTypes.Rgba32)]
public void ExrDecoder_CanDecode_B44Compressed<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel>
{
using Image<TPixel> image = provider.GetImage(ExrDecoder);
image.DebugSave(provider);
image.CompareToOriginal(provider);
}
}

164
tests/ImageSharp.Tests/Formats/Exr/ImageExtensionsTest.cs

@ -1,156 +1,66 @@
// 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.Tasks;
using SixLabors.ImageSharp.Formats;
using SixLabors.ImageSharp.Formats.OpenExr;
using SixLabors.ImageSharp.PixelFormats;
using Xunit;
namespace SixLabors.ImageSharp.Tests.Formats.Exr
namespace SixLabors.ImageSharp.Tests.Formats.Exr;
[Trait("Format", "Exr")]
public class ImageExtensionsTest
{
[Trait("Format", "Exr")]
public class ImageExtensionsTest
[Fact]
public void SaveAsExr_Stream()
{
[Fact]
public void SaveAsExr_Path()
{
string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageExtensionsTest));
string file = Path.Combine(dir, "SaveAsExr_Path.exr");
using (var image = new Image<Rgba32>(10, 10))
{
image.SaveAsOpenExr(file);
}
using (Image.Load(file, out IImageFormat mime))
{
Assert.Equal("image/x-exr", mime.DefaultMimeType);
}
}
using var memoryStream = new MemoryStream();
[Fact]
public async Task SaveAsExrAsync_Path()
using (var image = new Image<Rgba32>(10, 10))
{
string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageExtensionsTest));
string file = Path.Combine(dir, "SaveAsExrAsync_Path.exr");
using (var image = new Image<Rgba32>(10, 10))
{
await image.SaveAsOpenExrAsync(file);
}
using (Image.Load(file, out IImageFormat mime))
{
Assert.Equal("image/x-exr", mime.DefaultMimeType);
}
image.SaveAsOpenExr(memoryStream, new ExrEncoder());
}
[Fact]
public void SaveAsExr_Path_Encoder()
{
string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageExtensions));
string file = Path.Combine(dir, "SaveAsExr_Path_Encoder.exr");
using (var image = new Image<Rgba32>(10, 10))
{
image.SaveAsOpenExr(file, new ExrEncoder());
}
memoryStream.Position = 0;
using (Image.Load(file, out IImageFormat mime))
{
Assert.Equal("image/x-exr", mime.DefaultMimeType);
}
}
[Fact]
public async Task SaveAsExrAsync_Path_Encoder()
using (Image.Load(memoryStream, out IImageFormat mime))
{
string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageExtensions));
string file = Path.Combine(dir, "SaveAsExrAsync_Path_Encoder.tiff");
using (var image = new Image<Rgba32>(10, 10))
{
await image.SaveAsOpenExrAsync(file, new ExrEncoder());
}
using (Image.Load(file, out IImageFormat mime))
{
Assert.Equal("image/x-exr", mime.DefaultMimeType);
}
Assert.Equal("image/x-exr", mime.DefaultMimeType);
}
}
[Fact]
public void SaveAsExr_Stream()
{
using var memoryStream = new MemoryStream();
using (var image = new Image<Rgba32>(10, 10))
{
image.SaveAsOpenExr(memoryStream);
}
memoryStream.Position = 0;
using (Image.Load(memoryStream, out IImageFormat mime))
{
Assert.Equal("image/x-exr", mime.DefaultMimeType);
}
}
[Fact]
public void SaveAsExr_Stream_Encoder()
{
using var memoryStream = new MemoryStream();
[Fact]
public async Task SaveAsExrAsync_StreamAsync()
using (var image = new Image<Rgba32>(10, 10))
{
using var memoryStream = new MemoryStream();
using (var image = new Image<Rgba32>(10, 10))
{
await image.SaveAsOpenExrAsync(memoryStream);
}
memoryStream.Position = 0;
using (Image.Load(memoryStream, out IImageFormat mime))
{
Assert.Equal("image/x-exr", mime.DefaultMimeType);
}
image.SaveAsOpenExr(memoryStream, new ExrEncoder());
}
[Fact]
public void SaveAsExr_Stream_Encoder()
{
using var memoryStream = new MemoryStream();
using (var image = new Image<Rgba32>(10, 10))
{
image.SaveAsOpenExr(memoryStream, new ExrEncoder());
}
memoryStream.Position = 0;
memoryStream.Position = 0;
using (Image.Load(memoryStream, out IImageFormat mime))
{
Assert.Equal("image/x-exr", mime.DefaultMimeType);
}
using (Image.Load(memoryStream, out IImageFormat mime))
{
Assert.Equal("image/x-exr", mime.DefaultMimeType);
}
}
[Fact]
public async Task SaveAsExrAsync_Stream_Encoder()
{
using var memoryStream = new MemoryStream();
[Fact]
public async Task SaveAsExrAsync_Stream_Encoder()
{
using var memoryStream = new MemoryStream();
using (var image = new Image<Rgba32>(10, 10))
{
await image.SaveAsOpenExrAsync(memoryStream, new ExrEncoder());
}
using (var image = new Image<Rgba32>(10, 10))
{
await image.SaveAsOpenExrAsync(memoryStream, new ExrEncoder());
}
memoryStream.Position = 0;
memoryStream.Position = 0;
using (Image.Load(memoryStream, out IImageFormat mime))
{
Assert.Equal("image/x-exr", mime.DefaultMimeType);
}
using (Image.Load(memoryStream, out IImageFormat mime))
{
Assert.Equal("image/x-exr", mime.DefaultMimeType);
}
}
}

9
tests/ImageSharp.Tests/TestImages.cs

@ -1029,4 +1029,13 @@ public static class TestImages
public const string RgbPlainNormalized = "Pbm/rgb_plain_normalized.ppm";
public const string RgbPlainMagick = "Pbm/rgb_plain_magick.ppm";
}
public static class Exr
{
public const string Uncompressed = "Exr/Calliphora_uncompressed.exr";
public const string Zip = "Exr/Calliphora_zip.exr";
public const string Zips = "Exr/Calliphora_zips.exr";
public const string Rle = "Exr/Calliphora_rle.exr";
public const string B44 = "Exr/Calliphora_b44.exr";
}
}

6
tests/ImageSharp.Tests/TestUtilities/TestEnvironment.Formats.cs

@ -6,6 +6,7 @@ using SixLabors.ImageSharp.Formats;
using SixLabors.ImageSharp.Formats.Bmp;
using SixLabors.ImageSharp.Formats.Gif;
using SixLabors.ImageSharp.Formats.Jpeg;
using SixLabors.ImageSharp.Formats.OpenExr;
using SixLabors.ImageSharp.Formats.Pbm;
using SixLabors.ImageSharp.Formats.Png;
using SixLabors.ImageSharp.Formats.Tga;
@ -62,12 +63,13 @@ public static partial class TestEnvironment
new PbmConfigurationModule(),
new TgaConfigurationModule(),
new WebpConfigurationModule(),
new TiffConfigurationModule());
new TiffConfigurationModule(),
new ExrConfigurationModule());
IImageEncoder pngEncoder = IsWindows ? SystemDrawingReferenceEncoder.Png : new ImageSharpPngEncoderWithDefaultConfiguration();
IImageEncoder bmpEncoder = IsWindows ? SystemDrawingReferenceEncoder.Bmp : new BmpEncoder();
// Magick codecs should work on all platforms
// Magick codecs should work on all platforms.
cfg.ConfigureCodecs(
PngFormat.Instance,
MagickReferenceDecoder.Instance,

3
tests/Images/Input/Exr/Calliphora_b44.exr

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:cde761051241cba1e7f8466d71602abdb130677de55602fe6afec4b287c75b9d
size 2533521

3
tests/Images/Input/Exr/Calliphora_rle.exr

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:6302e72676f5574b8a9dde7e4385cff1c115e9833b630118328b88aea07e31d0
size 4098454

3
tests/Images/Input/Exr/Calliphora_uncompressed.exr

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:71613ff5972fa9e7b405ac944d159d1791ad229f5560da616438c9d718eafd24
size 5798633

3
tests/Images/Input/Exr/Calliphora_zip.exr

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:92c78a7cf9fce29e725511acadf49f0f784914da5bef55b0e0c4e75ba09c3a75
size 2652397

3
tests/Images/Input/Exr/Calliphora_zips.exr

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:3e34a9b0bb7fd6eb24eef633dea737653812fd8ab5c5352f6e10523055823b1d
size 2918571
Loading…
Cancel
Save