mirror of https://github.com/SixLabors/ImageSharp
committed by
GitHub
88 changed files with 5351 additions and 35 deletions
@ -0,0 +1,36 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using SixLabors.ImageSharp.Memory; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Exr.Compression.Compressors; |
|||
|
|||
/// <summary>
|
|||
/// Compressor for EXR image data which does not use any compression method.
|
|||
/// </summary>
|
|||
internal class NoneExrCompressor : ExrBaseCompressor |
|||
{ |
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="NoneExrCompressor"/> class.
|
|||
/// </summary>
|
|||
/// <param name="output">The output stream to write the compressed image data to.</param>
|
|||
/// <param name="allocator">The memory allocator.</param>
|
|||
/// <param name="bytesPerBlock">Bytes per row block.</param>
|
|||
/// <param name="bytesPerRow">Bytes per pixel row.</param>
|
|||
public NoneExrCompressor(Stream output, MemoryAllocator allocator, uint bytesPerBlock, uint bytesPerRow) |
|||
: base(output, allocator, bytesPerBlock, bytesPerRow) |
|||
{ |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public override uint CompressRowBlock(Span<byte> rows, int rowCount) |
|||
{ |
|||
this.Output.Write(rows); |
|||
return (uint)rows.Length; |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
protected override void Dispose(bool disposing) |
|||
{ |
|||
} |
|||
} |
|||
@ -0,0 +1,84 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using SixLabors.ImageSharp.Compression.Zlib; |
|||
using SixLabors.ImageSharp.Memory; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Exr.Compression.Compressors; |
|||
|
|||
/// <summary>
|
|||
/// Compressor for EXR image data using the ZIP compression.
|
|||
/// </summary>
|
|||
internal class ZipExrCompressor : ExrBaseCompressor |
|||
{ |
|||
private readonly DeflateCompressionLevel compressionLevel; |
|||
|
|||
private readonly MemoryStream memoryStream; |
|||
|
|||
private readonly System.Buffers.IMemoryOwner<byte> buffer; |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="ZipExrCompressor"/> class.
|
|||
/// </summary>
|
|||
/// <param name="output">The stream to write the compressed data to.</param>
|
|||
/// <param name="allocator">The memory allocator.</param>
|
|||
/// <param name="bytesPerBlock">The bytes per block.</param>
|
|||
/// <param name="bytesPerRow">The bytes per row.</param>
|
|||
/// <param name="compressionLevel">The compression level for deflate compression.</param>
|
|||
public ZipExrCompressor(Stream output, MemoryAllocator allocator, uint bytesPerBlock, uint bytesPerRow, DeflateCompressionLevel compressionLevel) |
|||
: base(output, allocator, bytesPerBlock, bytesPerRow) |
|||
{ |
|||
this.compressionLevel = compressionLevel; |
|||
this.buffer = allocator.Allocate<byte>((int)bytesPerBlock); |
|||
this.memoryStream = new(); |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public override uint CompressRowBlock(Span<byte> rows, int rowCount) |
|||
{ |
|||
// Re-oder pixel values.
|
|||
Span<byte> reordered = this.buffer.GetSpan()[..(int)(rowCount * this.BytesPerRow)]; |
|||
int n = reordered.Length; |
|||
int t1 = 0; |
|||
int t2 = (n + 1) >> 1; |
|||
for (int i = 0; i < n; i++) |
|||
{ |
|||
bool isOdd = (i & 1) == 1; |
|||
reordered[isOdd ? t2++ : t1++] = rows[i]; |
|||
} |
|||
|
|||
// Predictor.
|
|||
Span<byte> predicted = reordered; |
|||
byte p = predicted[0]; |
|||
for (int i = 1; i < predicted.Length; i++) |
|||
{ |
|||
int d = (predicted[i] - p + 128 + 256) & 255; |
|||
p = predicted[i]; |
|||
predicted[i] = (byte)d; |
|||
} |
|||
|
|||
this.memoryStream.Seek(0, SeekOrigin.Begin); |
|||
using (ZlibDeflateStream stream = new(this.Allocator, this.memoryStream, this.compressionLevel)) |
|||
{ |
|||
stream.Write(predicted); |
|||
stream.Flush(); |
|||
} |
|||
|
|||
int size = (int)this.memoryStream.Position; |
|||
byte[] buffer = this.memoryStream.GetBuffer(); |
|||
this.Output.Write(buffer, 0, size); |
|||
|
|||
// Reset memory stream for next pixel row.
|
|||
this.memoryStream.Seek(0, SeekOrigin.Begin); |
|||
this.memoryStream.SetLength(0); |
|||
|
|||
return (uint)size; |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
protected override void Dispose(bool disposing) |
|||
{ |
|||
this.buffer.Dispose(); |
|||
this.memoryStream?.Dispose(); |
|||
} |
|||
} |
|||
@ -0,0 +1,210 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using System.Buffers; |
|||
using System.Runtime.InteropServices; |
|||
using SixLabors.ImageSharp.IO; |
|||
using SixLabors.ImageSharp.Memory; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Exr.Compression.Decompressors; |
|||
|
|||
/// <summary>
|
|||
/// Implementation of B44 decompressor for EXR image data.
|
|||
/// </summary>
|
|||
internal class B44ExrCompression : ExrBaseDecompressor |
|||
{ |
|||
private readonly int width; |
|||
|
|||
private readonly uint rowsPerBlock; |
|||
|
|||
private readonly int channelCount; |
|||
|
|||
private readonly byte[] scratch = new byte[14]; |
|||
|
|||
private readonly ushort[] s = new ushort[16]; |
|||
|
|||
private readonly IMemoryOwner<ushort> tmpBuffer; |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="B44ExrCompression" /> class.
|
|||
/// </summary>
|
|||
/// <param name="allocator">The memory allocator.</param>
|
|||
/// <param name="bytesPerBlock">The bytes per pixel row block.</param>
|
|||
/// <param name="bytesPerRow">The bytes per row.</param>
|
|||
/// <param name="rowsPerBlock">The rows per block.</param>
|
|||
/// <param name="width">The width of a pixel row in pixels.</param>
|
|||
/// <param name="channelCount">The number of channels of the image.</param>
|
|||
public B44ExrCompression(MemoryAllocator allocator, uint bytesPerBlock, uint bytesPerRow, uint rowsPerBlock, int width, int channelCount) |
|||
: base(allocator, bytesPerBlock, bytesPerRow) |
|||
{ |
|||
this.width = width; |
|||
this.rowsPerBlock = rowsPerBlock; |
|||
this.channelCount = channelCount; |
|||
this.tmpBuffer = allocator.Allocate<ushort>((int)(width * rowsPerBlock * channelCount)); |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public override void Decompress(BufferedReadStream stream, uint compressedBytes, Span<byte> buffer) |
|||
{ |
|||
Span<ushort> outputBuffer = MemoryMarshal.Cast<byte, ushort>(buffer); |
|||
Span<ushort> decompressed = this.tmpBuffer.GetSpan(); |
|||
int outputOffset = 0; |
|||
int bytesLeft = (int)compressedBytes; |
|||
for (int i = 0; i < this.channelCount && bytesLeft > 0; i++) |
|||
{ |
|||
for (int y = 0; y < this.rowsPerBlock; y += 4) |
|||
{ |
|||
Span<ushort> row0 = decompressed.Slice(outputOffset, this.width); |
|||
outputOffset += this.width; |
|||
Span<ushort> row1 = decompressed.Slice(outputOffset, this.width); |
|||
outputOffset += this.width; |
|||
Span<ushort> row2 = decompressed.Slice(outputOffset, this.width); |
|||
outputOffset += this.width; |
|||
Span<ushort> row3 = decompressed.Slice(outputOffset, this.width); |
|||
outputOffset += this.width; |
|||
|
|||
int rowOffset = 0; |
|||
for (int x = 0; x < this.width && bytesLeft > 0; x += 4) |
|||
{ |
|||
int bytesRead = stream.Read(this.scratch, 0, 3); |
|||
if (bytesRead == 0) |
|||
{ |
|||
ExrThrowHelper.ThrowInvalidImageContentException("Could not read enough data from the stream!"); |
|||
} |
|||
|
|||
if (this.scratch[2] >= 13 << 2) |
|||
{ |
|||
Unpack3(this.scratch, this.s); |
|||
bytesLeft -= 3; |
|||
} |
|||
else |
|||
{ |
|||
bytesRead = stream.Read(this.scratch, 3, 11); |
|||
if (bytesRead == 0) |
|||
{ |
|||
ExrThrowHelper.ThrowInvalidImageContentException("Could not read enough data from the stream!"); |
|||
} |
|||
|
|||
Unpack14(this.scratch, this.s); |
|||
bytesLeft -= 14; |
|||
} |
|||
|
|||
int n = x + 3 < this.width ? 4 : this.width - x; |
|||
if (y + 3 < this.rowsPerBlock) |
|||
{ |
|||
this.s.AsSpan(0, n).CopyTo(row0[rowOffset..]); |
|||
this.s.AsSpan(4, n).CopyTo(row1[rowOffset..]); |
|||
this.s.AsSpan(8, n).CopyTo(row2[rowOffset..]); |
|||
this.s.AsSpan(12, n).CopyTo(row3[rowOffset..]); |
|||
} |
|||
else |
|||
{ |
|||
this.s.AsSpan(0, n).CopyTo(row0[rowOffset..]); |
|||
if (y + 1 < this.rowsPerBlock) |
|||
{ |
|||
this.s.AsSpan(4, n).CopyTo(row1[rowOffset..]); |
|||
} |
|||
|
|||
if (y + 2 < this.rowsPerBlock) |
|||
{ |
|||
this.s.AsSpan(8, n).CopyTo(row2[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[offsetOutput..]); |
|||
offsetOutput += this.width; |
|||
} |
|||
|
|||
offsetDecompressed += this.width; |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Unpack a 14-byte block into 4 by 4 16-bit pixels.
|
|||
/// </summary>
|
|||
/// <param name="b">The source byte data to unpack.</param>
|
|||
/// <param name="s">Destintation buffer.</param>
|
|||
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]; |
|||
} |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// // Unpack a 3-byte block into 4 by 4 identical 16-bit pixels.
|
|||
/// </summary>
|
|||
/// <param name="b">The source byte data to unpack.</param>
|
|||
/// <param name="s">The destination buffer.</param>
|
|||
private static void Unpack3(Span<byte> b, Span<ushort> s) |
|||
{ |
|||
s[0] = (ushort)((b[0] << 8) | b[1]); |
|||
|
|||
if ((s[0] & 0x8000) != 0) |
|||
{ |
|||
s[0] &= 0x7fff; |
|||
} |
|||
else |
|||
{ |
|||
s[0] = (ushort)~s[0]; |
|||
} |
|||
|
|||
for (int i = 1; i < 16; ++i) |
|||
{ |
|||
s[i] = s[0]; |
|||
} |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
protected override void Dispose(bool disposing) => this.tmpBuffer.Dispose(); |
|||
} |
|||
@ -0,0 +1,39 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using SixLabors.ImageSharp.IO; |
|||
using SixLabors.ImageSharp.Memory; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Exr.Compression.Decompressors; |
|||
|
|||
/// <summary>
|
|||
/// Decompressor for EXR image data which do not use any compression.
|
|||
/// </summary>
|
|||
internal class NoneExrCompression : ExrBaseDecompressor |
|||
{ |
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="NoneExrCompression" /> class.
|
|||
/// </summary>
|
|||
/// <param name="allocator">The memory allocator.</param>
|
|||
/// <param name="bytesPerBlock">The bytes per pixel row block.</param>
|
|||
/// <param name="bytesPerRow">The bytes per pixel row.</param>
|
|||
public NoneExrCompression(MemoryAllocator allocator, uint bytesPerBlock, uint bytesPerRow) |
|||
: base(allocator, bytesPerBlock, bytesPerRow) |
|||
{ |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public override void Decompress(BufferedReadStream stream, uint compressedBytes, Span<byte> buffer) |
|||
{ |
|||
int bytesRead = stream.Read(buffer, 0, Math.Min(buffer.Length, (int)this.BytesPerBlock)); |
|||
if (bytesRead != (int)this.BytesPerBlock) |
|||
{ |
|||
ExrThrowHelper.ThrowInvalidImageContentException("Could not read enough pixel data from the stream!"); |
|||
} |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
protected override void Dispose(bool disposing) |
|||
{ |
|||
} |
|||
} |
|||
@ -0,0 +1,98 @@ |
|||
// 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.Exr.Compression.Decompressors; |
|||
|
|||
/// <summary>
|
|||
/// Implementation of RLE decompressor for EXR images.
|
|||
/// </summary>
|
|||
internal class RunLengthExrCompression : ExrBaseDecompressor |
|||
{ |
|||
private readonly IMemoryOwner<byte> tmpBuffer; |
|||
|
|||
private readonly ushort[] s = new ushort[16]; |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="RunLengthExrCompression" /> class.
|
|||
/// </summary>
|
|||
/// <param name="allocator">The memory allocator.</param>
|
|||
/// <param name="bytesPerBlock">The bytes per pixel row block.</param>
|
|||
/// <param name="bytesPerRow">The bytes per row.</param>
|
|||
public RunLengthExrCompression(MemoryAllocator allocator, uint bytesPerBlock, uint bytesPerRow) |
|||
: base(allocator, bytesPerBlock, bytesPerRow) => this.tmpBuffer = allocator.Allocate<byte>((int)bytesPerBlock); |
|||
|
|||
/// <inheritdoc/>
|
|||
public override void Decompress(BufferedReadStream stream, uint compressedBytes, Span<byte> buffer) |
|||
{ |
|||
Span<byte> uncompressed = this.tmpBuffer.GetSpan(); |
|||
int maxLength = (int)this.BytesPerBlock; |
|||
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.BytesPerBlock); |
|||
Interleave(uncompressed, this.BytesPerBlock, buffer); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Reads the next byte from the stream.
|
|||
/// </summary>
|
|||
/// <param name="stream">The stream.</param>
|
|||
/// <returns>The next byte.</returns>
|
|||
private static byte ReadNextByte(BufferedReadStream stream) |
|||
{ |
|||
int nextByte = stream.ReadByte(); |
|||
if (nextByte == -1) |
|||
{ |
|||
ExrThrowHelper.ThrowInvalidImageContentException("Not enough data to decompress RLE encoded EXR image!"); |
|||
} |
|||
|
|||
return (byte)nextByte; |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
protected override void Dispose(bool disposing) => this.tmpBuffer.Dispose(); |
|||
} |
|||
@ -0,0 +1,67 @@ |
|||
// 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.Exr.Compression.Decompressors; |
|||
|
|||
/// <summary>
|
|||
/// Implementation of zhe Zip decompressor for EXR image data.
|
|||
/// </summary>
|
|||
internal class ZipExrCompression : ExrBaseDecompressor |
|||
{ |
|||
private readonly IMemoryOwner<byte> tmpBuffer; |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="ZipExrCompression" /> class.
|
|||
/// </summary>
|
|||
/// <param name="allocator">The memory allocator.</param>
|
|||
/// <param name="bytesPerBlock">The bytes per pixel row block.</param>
|
|||
/// <param name="bytesPerRow">The bytes per pixel row.</param>
|
|||
public ZipExrCompression(MemoryAllocator allocator, uint bytesPerBlock, uint bytesPerRow) |
|||
: base(allocator, bytesPerBlock, bytesPerRow) => this.tmpBuffer = allocator.Allocate<byte>((int)bytesPerBlock); |
|||
|
|||
/// <inheritdoc/>
|
|||
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.BytesPerBlock, true); |
|||
using 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 enough data for zip compressed image data!"); |
|||
} |
|||
|
|||
Reconstruct(uncompressed, (uint)totalRead); |
|||
Interleave(uncompressed, (uint)totalRead, buffer); |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
protected override void Dispose(bool disposing) => this.tmpBuffer.Dispose(); |
|||
} |
|||
@ -0,0 +1,66 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using SixLabors.ImageSharp.Memory; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Exr.Compression; |
|||
|
|||
/// <summary>
|
|||
/// Base class for EXR compression.
|
|||
/// </summary>
|
|||
internal abstract class ExrBaseCompression : IDisposable |
|||
{ |
|||
private bool isDisposed; |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="ExrBaseCompression" /> class.
|
|||
/// </summary>
|
|||
/// <param name="allocator">The memory allocator.</param>
|
|||
/// <param name="bytesPerBlock">The bytes per block.</param>
|
|||
/// <param name="bytesPerRow">The bytes per row.</param>
|
|||
protected ExrBaseCompression(MemoryAllocator allocator, uint bytesPerBlock, uint bytesPerRow) |
|||
{ |
|||
this.Allocator = allocator; |
|||
this.BytesPerBlock = bytesPerBlock; |
|||
this.BytesPerRow = bytesPerRow; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the memory allocator.
|
|||
/// </summary>
|
|||
protected MemoryAllocator Allocator { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the bits per pixel.
|
|||
/// </summary>
|
|||
public int BitsPerPixel { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the bytes per row.
|
|||
/// </summary>
|
|||
public uint BytesPerRow { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the uncompressed bytes per block.
|
|||
/// </summary>
|
|||
public uint BytesPerBlock { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the image width.
|
|||
/// </summary>
|
|||
public int Width { get; } |
|||
|
|||
/// <inheritdoc />
|
|||
public void Dispose() |
|||
{ |
|||
if (this.isDisposed) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
this.isDisposed = true; |
|||
this.Dispose(true); |
|||
} |
|||
|
|||
protected abstract void Dispose(bool disposing); |
|||
} |
|||
@ -0,0 +1,67 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using SixLabors.ImageSharp.IO; |
|||
using SixLabors.ImageSharp.Memory; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Exr.Compression; |
|||
|
|||
/// <summary>
|
|||
/// The base EXR decompressor class.
|
|||
/// </summary>
|
|||
internal abstract class ExrBaseDecompressor : ExrBaseCompression |
|||
{ |
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="ExrBaseDecompressor" /> class.
|
|||
/// </summary>
|
|||
/// <param name="allocator">The memory allocator.</param>
|
|||
/// <param name="bytesPerBlock">The bytes per row block.</param>
|
|||
/// <param name="bytesPerRow">The bytes per row.</param>
|
|||
protected ExrBaseDecompressor(MemoryAllocator allocator, uint bytesPerBlock, uint bytesPerRow) |
|||
: base(allocator, bytesPerBlock, bytesPerRow) |
|||
{ |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Decompresses the specified stream.
|
|||
/// </summary>
|
|||
/// <param name="stream">The buffered stream to decompress.</param>
|
|||
/// <param name="compressedBytes">The compressed bytes.</param>
|
|||
/// <param name="buffer">The buffer to write the decompressed data to.</param>
|
|||
public abstract void Decompress(BufferedReadStream stream, uint compressedBytes, Span<byte> buffer); |
|||
|
|||
/// <summary>
|
|||
/// Integrate over all differences to the previous value in order to
|
|||
/// reconstruct sample values.
|
|||
/// </summary>
|
|||
/// <param name="buffer">The buffer with the data.</param>
|
|||
/// <param name="unCompressedBytes">The un compressed bytes.</param>
|
|||
protected static void Reconstruct(Span<byte> buffer, uint unCompressedBytes) |
|||
{ |
|||
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++; |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Interleaves the input data.
|
|||
/// </summary>
|
|||
/// <param name="source">The source data.</param>
|
|||
/// <param name="unCompressedBytes">The uncompressed bytes.</param>
|
|||
/// <param name="output">The output to write to.</param>
|
|||
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) |
|||
{ |
|||
output[sourceOffset++] = source[offset0++]; |
|||
output[sourceOffset++] = source[offset1++]; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,39 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using SixLabors.ImageSharp.Compression.Zlib; |
|||
using SixLabors.ImageSharp.Formats.Exr.Compression.Compressors; |
|||
using SixLabors.ImageSharp.Formats.Exr.Constants; |
|||
using SixLabors.ImageSharp.Memory; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Exr.Compression; |
|||
|
|||
/// <summary>
|
|||
/// Factory class for creating a compressor for EXR image data.
|
|||
/// </summary>
|
|||
internal static class ExrCompressorFactory |
|||
{ |
|||
/// <summary>
|
|||
/// Creates the specified exr data compressor.
|
|||
/// </summary>
|
|||
/// <param name="method">The compression method.</param>
|
|||
/// <param name="allocator">The memory allocator.</param>
|
|||
/// <param name="output">The output stream.</param>
|
|||
/// <param name="bytesPerBlock">The bytes per block.</param>
|
|||
/// <param name="bytesPerRow">The bytes per row.</param>
|
|||
/// <param name="compressionLevel">The deflate compression level.</param>
|
|||
/// <returns>A compressor for EXR image data.</returns>
|
|||
public static ExrBaseCompressor Create( |
|||
ExrCompression method, |
|||
MemoryAllocator allocator, |
|||
Stream output, |
|||
uint bytesPerBlock, |
|||
uint bytesPerRow, |
|||
DeflateCompressionLevel compressionLevel = DeflateCompressionLevel.DefaultCompression) => method switch |
|||
{ |
|||
ExrCompression.None => new NoneExrCompressor(output, allocator, bytesPerBlock, bytesPerRow), |
|||
ExrCompression.Zips => new ZipExrCompressor(output, allocator, bytesPerBlock, bytesPerRow, compressionLevel), |
|||
ExrCompression.Zip => new ZipExrCompressor(output, allocator, bytesPerBlock, bytesPerRow, compressionLevel), |
|||
_ => throw ExrThrowHelper.NotSupportedCompressor(method.ToString()), |
|||
}; |
|||
} |
|||
@ -0,0 +1,42 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using SixLabors.ImageSharp.Formats.Exr.Compression.Decompressors; |
|||
using SixLabors.ImageSharp.Formats.Exr.Constants; |
|||
using SixLabors.ImageSharp.Memory; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Exr.Compression; |
|||
|
|||
/// <summary>
|
|||
/// The Factory class for creating a EXR data decompressor.
|
|||
/// </summary>
|
|||
internal static class ExrDecompressorFactory |
|||
{ |
|||
/// <summary>
|
|||
/// Creates a decomprssor for a specific EXR compression type.
|
|||
/// </summary>
|
|||
/// <param name="method">The compression method.</param>
|
|||
/// <param name="memoryAllocator">The memory allocator.</param>
|
|||
/// <param name="width">The width in pixels of the image.</param>
|
|||
/// <param name="bytesPerBlock">The bytes per block.</param>
|
|||
/// <param name="bytesPerRow">The bytes per row.</param>
|
|||
/// <param name="rowsPerBlock">The rows per block.</param>
|
|||
/// <param name="channelCount">The number of image channels.</param>
|
|||
/// <returns>Decompressor for EXR image data.</returns>
|
|||
public static ExrBaseDecompressor Create( |
|||
ExrCompression method, |
|||
MemoryAllocator memoryAllocator, |
|||
int width, |
|||
uint bytesPerBlock, |
|||
uint bytesPerRow, |
|||
uint rowsPerBlock, |
|||
int channelCount) => method switch |
|||
{ |
|||
ExrCompression.None => new NoneExrCompression(memoryAllocator, bytesPerBlock, bytesPerRow), |
|||
ExrCompression.Zips => new ZipExrCompression(memoryAllocator, bytesPerBlock, bytesPerRow), |
|||
ExrCompression.Zip => new ZipExrCompression(memoryAllocator, bytesPerBlock, bytesPerRow), |
|||
ExrCompression.RunLengthEncoded => new RunLengthExrCompression(memoryAllocator, bytesPerBlock, bytesPerRow), |
|||
ExrCompression.B44 => new B44ExrCompression(memoryAllocator, bytesPerBlock, bytesPerRow, rowsPerBlock, width, channelCount), |
|||
_ => throw ExrThrowHelper.NotSupportedDecompressor(nameof(method)), |
|||
}; |
|||
} |
|||
@ -0,0 +1,63 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Exr.Constants; |
|||
|
|||
/// <summary>
|
|||
/// Enumeration representing the compression formats defined by the EXR file-format.
|
|||
/// </summary>
|
|||
public enum ExrCompression |
|||
{ |
|||
/// <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 |
|||
} |
|||
@ -0,0 +1,30 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Exr.Constants; |
|||
|
|||
/// <summary>
|
|||
/// This enum represents the type of pixel data in the EXR image.
|
|||
/// </summary>
|
|||
public enum ExrImageDataType |
|||
{ |
|||
/// <summary>
|
|||
/// The pixel data is unknown.
|
|||
/// </summary>
|
|||
Unknown = 0, |
|||
|
|||
/// <summary>
|
|||
/// The pixel data has 3 channels: red, green and blue.
|
|||
/// </summary>
|
|||
Rgb = 1, |
|||
|
|||
/// <summary>
|
|||
/// The pixel data has four channels: red, green, blue and a alpha channel.
|
|||
/// </summary>
|
|||
Rgba = 2, |
|||
|
|||
/// <summary>
|
|||
/// There is only one channel with the luminance.
|
|||
/// </summary>
|
|||
Gray = 3, |
|||
} |
|||
@ -0,0 +1,21 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Exr.Constants; |
|||
|
|||
/// <summary>
|
|||
/// Enum for the differnt exr image type.
|
|||
/// </summary>
|
|||
internal enum ExrImageType |
|||
{ |
|||
/// <summary>
|
|||
/// The image data is stored in scan lines.
|
|||
/// </summary>
|
|||
ScanLine = 0, |
|||
|
|||
/// <summary>
|
|||
/// The image data is stored in tile.
|
|||
/// This is not yet supported.
|
|||
/// </summary>
|
|||
Tiled = 1 |
|||
} |
|||
@ -0,0 +1,25 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Exr.Constants; |
|||
|
|||
/// <summary>
|
|||
/// Enum for the different scan line ordering.
|
|||
/// </summary>
|
|||
internal enum ExrLineOrder : byte |
|||
{ |
|||
/// <summary>
|
|||
/// The scan lines are written from top-to-bottom.
|
|||
/// </summary>
|
|||
IncreasingY = 0, |
|||
|
|||
/// <summary>
|
|||
/// The scan lines are written from bottom-to-top.
|
|||
/// </summary>
|
|||
DecreasingY = 1, |
|||
|
|||
/// <summary>
|
|||
/// The Scan lines are written in no particular oder.
|
|||
/// </summary>
|
|||
RandomY = 2 |
|||
} |
|||
@ -0,0 +1,25 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Exr.Constants; |
|||
|
|||
/// <summary>
|
|||
/// The different pixel formats for a OpenEXR image.
|
|||
/// </summary>
|
|||
public enum ExrPixelType |
|||
{ |
|||
/// <summary>
|
|||
/// unsigned int (32 bit).
|
|||
/// </summary>
|
|||
UnsignedInt = 0, |
|||
|
|||
/// <summary>
|
|||
/// half (16 bit floating point).
|
|||
/// </summary>
|
|||
Half = 1, |
|||
|
|||
/// <summary>
|
|||
/// float (32 bit floating point).
|
|||
/// </summary>
|
|||
Float = 2 |
|||
} |
|||
@ -0,0 +1,43 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using System.Diagnostics; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Exr; |
|||
|
|||
/// <summary>
|
|||
/// Repressents an exr image attribute.
|
|||
/// </summary>
|
|||
[DebuggerDisplay("Name: {Name}, Type: {Type}, Length: {Length}")] |
|||
internal class ExrAttribute |
|||
{ |
|||
public static readonly ExrAttribute EmptyAttribute = new(string.Empty, string.Empty, 0); |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="ExrAttribute"/> class.
|
|||
/// </summary>
|
|||
/// <param name="name">The name of the attribute.</param>
|
|||
/// <param name="type">The type of the attribute.</param>
|
|||
/// <param name="length">The length in bytes.</param>
|
|||
public ExrAttribute(string name, string type, int length) |
|||
{ |
|||
this.Name = name; |
|||
this.Type = type; |
|||
this.Length = length; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the name of the attribute.
|
|||
/// </summary>
|
|||
public string Name { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the type of the attribute.
|
|||
/// </summary>
|
|||
public string Type { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the length in bytes of the attribute.
|
|||
/// </summary>
|
|||
public int Length { get; } |
|||
} |
|||
@ -0,0 +1,33 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using SixLabors.ImageSharp.Memory; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Exr.Compression; |
|||
|
|||
internal abstract class ExrBaseCompressor : ExrBaseCompression |
|||
{ |
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="ExrBaseCompressor"/> class.
|
|||
/// </summary>
|
|||
/// <param name="output">The output stream to write the compressed image to.</param>
|
|||
/// <param name="allocator">The memory allocator.</param>
|
|||
/// <param name="bytesPerBlock">Bytes per row block.</param>
|
|||
/// <param name="bytesPerRow">Bytes per pixel row.</param>
|
|||
protected ExrBaseCompressor(Stream output, MemoryAllocator allocator, uint bytesPerBlock, uint bytesPerRow) |
|||
: base(allocator, bytesPerBlock, bytesPerRow) |
|||
=> this.Output = output; |
|||
|
|||
/// <summary>
|
|||
/// Gets the output stream to write the compressed image to.
|
|||
/// </summary>
|
|||
public Stream Output { get; } |
|||
|
|||
/// <summary>
|
|||
/// Compresses a block of rows of the image.
|
|||
/// </summary>
|
|||
/// <param name="rows">Image rows to compress.</param>
|
|||
/// <param name="rowCount">The number of rows to compress.</param>
|
|||
/// <returns>Number of bytes of of the compressed data.</returns>
|
|||
public abstract uint CompressRowBlock(Span<byte> rows, int rowCount); |
|||
} |
|||
@ -0,0 +1,48 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using System.Diagnostics; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Exr; |
|||
|
|||
/// <summary>
|
|||
/// Integer region definition.
|
|||
/// </summary>
|
|||
[DebuggerDisplay("xMin: {XMin}, yMin: {YMin}, xMax: {XMax}, yMax: {YMax}")] |
|||
internal readonly struct ExrBox2i |
|||
{ |
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="ExrBox2i"/> struct.
|
|||
/// </summary>
|
|||
/// <param name="xMin">The minimum x value.</param>
|
|||
/// <param name="yMin">The minimum y value.</param>
|
|||
/// <param name="xMax">The maximum x value.</param>
|
|||
/// <param name="yMax">The maximum y value.</param>
|
|||
public ExrBox2i(int xMin, int yMin, int xMax, int yMax) |
|||
{ |
|||
this.XMin = xMin; |
|||
this.YMin = yMin; |
|||
this.XMax = xMax; |
|||
this.YMax = yMax; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the minimum x value.
|
|||
/// </summary>
|
|||
public int XMin { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the minimum y value.
|
|||
/// </summary>
|
|||
public int YMin { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the maximum x value.
|
|||
/// </summary>
|
|||
public int XMax { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the maximum y value.
|
|||
/// </summary>
|
|||
public int YMax { get; } |
|||
} |
|||
@ -0,0 +1,60 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using System.Diagnostics; |
|||
using System.Runtime.InteropServices; |
|||
using SixLabors.ImageSharp.Formats.Exr.Constants; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Exr; |
|||
|
|||
/// <summary>
|
|||
/// Information about a pixel channel.
|
|||
/// </summary>
|
|||
[DebuggerDisplay("Name: {ChannelName}, PixelType: {PixelType}")] |
|||
[StructLayout(LayoutKind.Sequential, Pack = 1)] |
|||
internal readonly struct ExrChannelInfo |
|||
{ |
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="ExrChannelInfo" /> struct.
|
|||
/// </summary>
|
|||
/// <param name="channelName">Name of the channel.</param>
|
|||
/// <param name="pixelType">The type of the pixel data.</param>
|
|||
/// <param name="linear">Linear flag, possible values are 0 and 1.</param>
|
|||
/// <param name="xSampling">X sampling.</param>
|
|||
/// <param name="ySampling">Y sampling.</param>
|
|||
public ExrChannelInfo(string channelName, ExrPixelType pixelType, byte linear, int xSampling, int ySampling) |
|||
{ |
|||
this.ChannelName = channelName; |
|||
this.PixelType = pixelType; |
|||
this.Linear = linear; |
|||
this.XSampling = xSampling; |
|||
this.YSampling = ySampling; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the channel name.
|
|||
/// </summary>
|
|||
public string ChannelName { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the type of the pixel data.
|
|||
/// </summary>
|
|||
public ExrPixelType PixelType { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the linear flag. Hint to lossy compression methods that indicates whether
|
|||
/// human perception of the quantity represented by this channel
|
|||
/// is closer to linear or closer to logarithmic.
|
|||
/// </summary>
|
|||
public byte Linear { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the x sampling value.
|
|||
/// </summary>
|
|||
public int XSampling { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the y sampling value.
|
|||
/// </summary>
|
|||
public int YSampling { get; } |
|||
} |
|||
@ -0,0 +1,18 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Exr; |
|||
|
|||
/// <summary>
|
|||
/// Registers the image encoders, decoders and mime type detectors for the OpenExr format.
|
|||
/// </summary>
|
|||
public sealed class ExrConfigurationModule : IImageFormatConfigurationModule |
|||
{ |
|||
/// <inheritdoc/>
|
|||
public void Configure(Configuration configuration) |
|||
{ |
|||
configuration.ImageFormatsManager.SetEncoder(ExrFormat.Instance, new ExrEncoder()); |
|||
configuration.ImageFormatsManager.SetDecoder(ExrFormat.Instance, ExrDecoder.Instance); |
|||
configuration.ImageFormatsManager.AddImageFormatDetector(new ExrImageFormatDetector()); |
|||
} |
|||
} |
|||
@ -0,0 +1,82 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Exr; |
|||
|
|||
/// <summary>
|
|||
/// Defines constants relating to OpenExr images.
|
|||
/// </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" }; |
|||
|
|||
/// <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>
|
|||
/// EXR attribute names.
|
|||
/// </summary>
|
|||
internal static class AttributeNames |
|||
{ |
|||
public const string Channels = "channels"; |
|||
|
|||
public const string Compression = "compression"; |
|||
|
|||
public const string DataWindow = "dataWindow"; |
|||
|
|||
public const string DisplayWindow = "displayWindow"; |
|||
|
|||
public const string LineOrder = "lineOrder"; |
|||
|
|||
public const string PixelAspectRatio = "pixelAspectRatio"; |
|||
|
|||
public const string ScreenWindowCenter = "screenWindowCenter"; |
|||
|
|||
public const string ScreenWindowWidth = "screenWindowWidth"; |
|||
|
|||
public const string Tiles = "tiles"; |
|||
|
|||
public const string ChunkCount = "chunkCount"; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// EXR attribute types.
|
|||
/// </summary>
|
|||
internal static class AttibuteTypes |
|||
{ |
|||
public const string ChannelList = "chlist"; |
|||
|
|||
public const string Compression = "compression"; |
|||
|
|||
public const string Float = "float"; |
|||
|
|||
public const string LineOrder = "lineOrder"; |
|||
|
|||
public const string TwoFloat = "v2f"; |
|||
|
|||
public const string BoxInt = "box2i"; |
|||
} |
|||
|
|||
internal static class ChannelNames |
|||
{ |
|||
public const string Red = "R"; |
|||
|
|||
public const string Green = "G"; |
|||
|
|||
public const string Blue = "B"; |
|||
|
|||
public const string Alpha = "A"; |
|||
|
|||
public const string Luminance = "Y"; |
|||
} |
|||
} |
|||
@ -0,0 +1,48 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using SixLabors.ImageSharp.PixelFormats; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Exr; |
|||
|
|||
/// <summary>
|
|||
/// Image decoder for generating an image out of a OpenExr stream.
|
|||
/// </summary>
|
|||
public class ExrDecoder : ImageDecoder |
|||
{ |
|||
private ExrDecoder() |
|||
{ |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the shared instance.
|
|||
/// </summary>
|
|||
public static ExrDecoder Instance { get; } = new(); |
|||
|
|||
/// <inheritdoc/>
|
|||
protected override ImageInfo Identify(DecoderOptions options, Stream stream, CancellationToken cancellationToken) |
|||
{ |
|||
Guard.NotNull(options, nameof(options)); |
|||
Guard.NotNull(stream, nameof(stream)); |
|||
|
|||
return new ExrDecoderCore(new ExrDecoderOptions { GeneralOptions = options }).Identify(options.Configuration, stream, cancellationToken); |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
protected override Image<TPixel> Decode<TPixel>(DecoderOptions options, Stream stream, CancellationToken cancellationToken) |
|||
{ |
|||
Guard.NotNull(options, nameof(options)); |
|||
Guard.NotNull(stream, nameof(stream)); |
|||
|
|||
ExrDecoderCore decoder = new(new ExrDecoderOptions { GeneralOptions = options }); |
|||
Image<TPixel> image = decoder.Decode<TPixel>(options.Configuration, stream, cancellationToken); |
|||
|
|||
ScaleToTargetSize(options, image); |
|||
|
|||
return image; |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
protected override Image Decode(DecoderOptions options, Stream stream, CancellationToken cancellationToken) |
|||
=> this.Decode<Rgba32>(options, stream, cancellationToken); |
|||
} |
|||
@ -0,0 +1,935 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
#nullable disable |
|||
|
|||
using System.Buffers; |
|||
using System.Buffers.Binary; |
|||
using System.Runtime.CompilerServices; |
|||
using System.Text; |
|||
using SixLabors.ImageSharp.Formats.Exr.Compression; |
|||
using SixLabors.ImageSharp.Formats.Exr.Constants; |
|||
using SixLabors.ImageSharp.IO; |
|||
using SixLabors.ImageSharp.Memory; |
|||
using SixLabors.ImageSharp.Metadata; |
|||
using SixLabors.ImageSharp.PixelFormats; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Exr; |
|||
|
|||
/// <summary>
|
|||
/// Performs the OpenExr decoding operation.
|
|||
/// </summary>
|
|||
internal sealed class ExrDecoderCore : ImageDecoderCore |
|||
{ |
|||
/// <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 global configuration.
|
|||
/// </summary>
|
|||
private readonly Configuration configuration; |
|||
|
|||
/// <summary>
|
|||
/// The metadata.
|
|||
/// </summary>
|
|||
private ImageMetadata metadata; |
|||
|
|||
/// <summary>
|
|||
/// The exr specific metadata.
|
|||
/// </summary>
|
|||
private ExrMetadata exrMetadata; |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="ExrDecoderCore"/> class.
|
|||
/// </summary>
|
|||
/// <param name="options">The options.</param>
|
|||
public ExrDecoderCore(ExrDecoderOptions options) |
|||
: base(options.GeneralOptions) |
|||
{ |
|||
this.configuration = options.GeneralOptions.Configuration; |
|||
this.memoryAllocator = this.configuration.MemoryAllocator; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the image width.
|
|||
/// </summary>
|
|||
private int Width { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the image height.
|
|||
/// </summary>
|
|||
private int Height { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the image channel info's.
|
|||
/// </summary>
|
|||
private IList<ExrChannelInfo> Channels { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the compression method.
|
|||
/// </summary>
|
|||
private ExrCompression Compression { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the image data type, either RGB, RGBA or gray.
|
|||
/// </summary>
|
|||
private ExrImageDataType ImageDataType { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the pixel type.
|
|||
/// </summary>
|
|||
private ExrPixelType PixelType { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the header attributes.
|
|||
/// </summary>
|
|||
private ExrHeaderAttributes HeaderAttributes { get; set; } |
|||
|
|||
/// <inheritdoc />
|
|||
protected override Image<TPixel> Decode<TPixel>(BufferedReadStream stream, CancellationToken cancellationToken) |
|||
{ |
|||
this.ReadExrHeader(stream); |
|||
if (!this.IsSupportedCompression()) |
|||
{ |
|||
ExrThrowHelper.ThrowNotSupported($"Compression {this.Compression} is not yet supported"); |
|||
} |
|||
|
|||
Image<TPixel> image = new(this.configuration, this.Width, this.Height, this.metadata); |
|||
Buffer2D<TPixel> pixels = image.GetRootFramePixelBuffer(); |
|||
|
|||
switch (this.PixelType) |
|||
{ |
|||
case ExrPixelType.Half: |
|||
case ExrPixelType.Float: |
|||
this.DecodeFloatingPointPixelData(stream, pixels, cancellationToken); |
|||
break; |
|||
case ExrPixelType.UnsignedInt: |
|||
this.DecodeUnsignedIntPixelData(stream, pixels, cancellationToken); |
|||
break; |
|||
default: |
|||
ExrThrowHelper.ThrowNotSupported("Pixel type is not supported"); |
|||
break; |
|||
} |
|||
|
|||
return image; |
|||
} |
|||
|
|||
/// <inheritdoc />
|
|||
protected override ImageInfo Identify(BufferedReadStream stream, CancellationToken cancellationToken) |
|||
{ |
|||
ExrHeaderAttributes header = this.ReadExrHeader(stream); |
|||
|
|||
return new ImageInfo(new Size(header.DataWindow.XMax, header.DataWindow.YMax), this.metadata); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Decodes image data with floating point pixel data.
|
|||
/// </summary>
|
|||
/// <typeparam name="TPixel">The type of the pixels.</typeparam>
|
|||
/// <param name="stream">The stream to read from.</param>
|
|||
/// <param name="pixels">The pixel buffer.</param>
|
|||
/// <param name="cancellationToken">The cancellation token.</param>
|
|||
private void DecodeFloatingPointPixelData<TPixel>(BufferedReadStream stream, Buffer2D<TPixel> pixels, CancellationToken cancellationToken) |
|||
where TPixel : unmanaged, IPixel<TPixel> |
|||
{ |
|||
bool hasAlpha = this.HasAlpha(); |
|||
uint bytesPerRow = ExrUtils.CalculateBytesPerRow(this.Channels, (uint)this.Width); |
|||
uint rowsPerBlock = ExrUtils.RowsPerBlock(this.Compression); |
|||
uint bytesPerBlock = bytesPerRow * rowsPerBlock; |
|||
int width = this.Width; |
|||
int height = this.Height; |
|||
int channelCount = this.Channels.Count; |
|||
|
|||
using IMemoryOwner<float> rowBuffer = this.memoryAllocator.Allocate<float>(width * 4); |
|||
using IMemoryOwner<byte> decompressedPixelDataBuffer = this.memoryAllocator.Allocate<byte>((int)bytesPerBlock); |
|||
Span<byte> decompressedPixelData = decompressedPixelDataBuffer.GetSpan(); |
|||
Span<float> redPixelData = rowBuffer.GetSpan()[..width]; |
|||
Span<float> greenPixelData = rowBuffer.GetSpan().Slice(width, width); |
|||
Span<float> bluePixelData = rowBuffer.GetSpan().Slice(width * 2, width); |
|||
Span<float> alphaPixelData = rowBuffer.GetSpan().Slice(width * 3, width); |
|||
|
|||
using ExrBaseDecompressor decompressor = ExrDecompressorFactory.Create(this.Compression, this.memoryAllocator, width, bytesPerBlock, bytesPerRow, rowsPerBlock, channelCount); |
|||
|
|||
int decodedRows = 0; |
|||
while (decodedRows < height) |
|||
{ |
|||
ulong rowOffset = this.ReadUnsignedLong(stream); |
|||
long nextRowOffsetPosition = stream.Position; |
|||
|
|||
stream.Position = (long)rowOffset; |
|||
uint rowStartIndex = this.ReadUnsignedInteger(stream); |
|||
|
|||
uint compressedBytesCount = this.ReadUnsignedInteger(stream); |
|||
decompressor.Decompress(stream, compressedBytesCount, decompressedPixelData); |
|||
|
|||
int offset = 0; |
|||
for (uint rowIndex = rowStartIndex; rowIndex < rowStartIndex + rowsPerBlock && rowIndex < height; rowIndex++) |
|||
{ |
|||
Span<TPixel> pixelRow = pixels.DangerousGetRowSpan((int)rowIndex); |
|||
for (int channelIdx = 0; channelIdx < this.Channels.Count; channelIdx++) |
|||
{ |
|||
ExrChannelInfo channel = this.Channels[channelIdx]; |
|||
offset += ReadFloatChannelData(stream, channel, decompressedPixelData[offset..], redPixelData, greenPixelData, bluePixelData, alphaPixelData, width); |
|||
} |
|||
|
|||
for (int x = 0; x < width; x++) |
|||
{ |
|||
HalfVector4 pixelValue = new(redPixelData[x], greenPixelData[x], bluePixelData[x], hasAlpha ? alphaPixelData[x] : 1.0f); |
|||
pixelRow[x] = TPixel.FromVector4(pixelValue.ToVector4()); |
|||
} |
|||
|
|||
decodedRows++; |
|||
} |
|||
|
|||
stream.Position = nextRowOffsetPosition; |
|||
|
|||
cancellationToken.ThrowIfCancellationRequested(); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Decodes image data with unsigned int pixel data.
|
|||
/// </summary>
|
|||
/// <typeparam name="TPixel">The type of the pixels.</typeparam>
|
|||
/// <param name="stream">The stream to read from.</param>
|
|||
/// <param name="pixels">The pixel buffer.</param>
|
|||
/// <param name="cancellationToken">The cancellation token.</param>
|
|||
private void DecodeUnsignedIntPixelData<TPixel>(BufferedReadStream stream, Buffer2D<TPixel> pixels, CancellationToken cancellationToken) |
|||
where TPixel : unmanaged, IPixel<TPixel> |
|||
{ |
|||
bool hasAlpha = this.HasAlpha(); |
|||
uint bytesPerRow = ExrUtils.CalculateBytesPerRow(this.Channels, (uint)this.Width); |
|||
uint rowsPerBlock = ExrUtils.RowsPerBlock(this.Compression); |
|||
uint bytesPerBlock = bytesPerRow * rowsPerBlock; |
|||
int width = this.Width; |
|||
int height = this.Height; |
|||
int channelCount = this.Channels.Count; |
|||
|
|||
using IMemoryOwner<uint> rowBuffer = this.memoryAllocator.Allocate<uint>(width * 4); |
|||
using IMemoryOwner<byte> decompressedPixelDataBuffer = this.memoryAllocator.Allocate<byte>((int)bytesPerBlock); |
|||
Span<byte> decompressedPixelData = decompressedPixelDataBuffer.GetSpan(); |
|||
Span<uint> redPixelData = rowBuffer.GetSpan()[..width]; |
|||
Span<uint> greenPixelData = rowBuffer.GetSpan().Slice(width, width); |
|||
Span<uint> bluePixelData = rowBuffer.GetSpan().Slice(width * 2, width); |
|||
Span<uint> alphaPixelData = rowBuffer.GetSpan().Slice(width * 3, width); |
|||
|
|||
using ExrBaseDecompressor decompressor = ExrDecompressorFactory.Create(this.Compression, this.memoryAllocator, width, bytesPerBlock, bytesPerRow, rowsPerBlock, channelCount); |
|||
|
|||
int decodedRows = 0; |
|||
while (decodedRows < height) |
|||
{ |
|||
ulong rowOffset = this.ReadUnsignedLong(stream); |
|||
long nextRowOffsetPosition = stream.Position; |
|||
|
|||
stream.Position = (long)rowOffset; |
|||
uint rowStartIndex = this.ReadUnsignedInteger(stream); |
|||
|
|||
uint compressedBytesCount = this.ReadUnsignedInteger(stream); |
|||
decompressor.Decompress(stream, compressedBytesCount, decompressedPixelData); |
|||
|
|||
int offset = 0; |
|||
for (uint rowIndex = rowStartIndex; rowIndex < rowStartIndex + rowsPerBlock && rowIndex < height; rowIndex++) |
|||
{ |
|||
Span<TPixel> pixelRow = pixels.DangerousGetRowSpan((int)rowIndex); |
|||
for (int channelIdx = 0; channelIdx < this.Channels.Count; channelIdx++) |
|||
{ |
|||
ExrChannelInfo channel = this.Channels[channelIdx]; |
|||
offset += this.ReadUnsignedIntChannelData(stream, channel, decompressedPixelData[offset..], redPixelData, greenPixelData, bluePixelData, alphaPixelData, width); |
|||
} |
|||
|
|||
for (int x = 0; x < width; x++) |
|||
{ |
|||
Rgba128 pixelValue = new(redPixelData[x], greenPixelData[x], bluePixelData[x], hasAlpha ? alphaPixelData[x] : uint.MaxValue); |
|||
pixelRow[x] = TPixel.FromVector4(pixelValue.ToVector4()); |
|||
} |
|||
|
|||
decodedRows++; |
|||
} |
|||
|
|||
stream.Position = nextRowOffsetPosition; |
|||
|
|||
cancellationToken.ThrowIfCancellationRequested(); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Reads float image channel data.
|
|||
/// </summary>
|
|||
/// <param name="stream">The stream to read from.</param>
|
|||
/// <param name="channel">The channel info.</param>
|
|||
/// <param name="decompressedPixelData">The decompressed pixel data.</param>
|
|||
/// <param name="redPixelData">The red channel pixel data.</param>
|
|||
/// <param name="greenPixelData">The green channel pixel data.</param>
|
|||
/// <param name="bluePixelData">The blue channel pixel data.</param>
|
|||
/// <param name="alphaPixelData">The alpha channel pixel data.</param>
|
|||
/// <param name="width">The width of a row in pixels.</param>
|
|||
/// <returns>The bytes read.</returns>
|
|||
private static int ReadFloatChannelData( |
|||
BufferedReadStream stream, |
|||
ExrChannelInfo channel, |
|||
Span<byte> decompressedPixelData, |
|||
Span<float> redPixelData, |
|||
Span<float> greenPixelData, |
|||
Span<float> bluePixelData, |
|||
Span<float> alphaPixelData, |
|||
int width) |
|||
{ |
|||
switch (channel.ChannelName) |
|||
{ |
|||
case ExrConstants.ChannelNames.Red: |
|||
return ReadChannelData(channel, decompressedPixelData, redPixelData, width); |
|||
|
|||
case ExrConstants.ChannelNames.Blue: |
|||
return ReadChannelData(channel, decompressedPixelData, bluePixelData, width); |
|||
|
|||
case ExrConstants.ChannelNames.Green: |
|||
return ReadChannelData(channel, decompressedPixelData, greenPixelData, width); |
|||
|
|||
case ExrConstants.ChannelNames.Alpha: |
|||
return ReadChannelData(channel, decompressedPixelData, alphaPixelData, width); |
|||
|
|||
case ExrConstants.ChannelNames.Luminance: |
|||
int bytesRead = ReadChannelData(channel, decompressedPixelData, redPixelData, width); |
|||
redPixelData.CopyTo(bluePixelData); |
|||
redPixelData.CopyTo(greenPixelData); |
|||
|
|||
return bytesRead; |
|||
|
|||
default: |
|||
// Skip unknown channel.
|
|||
int channelDataSizeInBytes = channel.PixelType is ExrPixelType.Float or ExrPixelType.UnsignedInt ? 4 : 2; |
|||
stream.Position += width * channelDataSizeInBytes; |
|||
return channelDataSizeInBytes; |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Reads UINT image channel data.
|
|||
/// </summary>
|
|||
/// <param name="stream">The stream to read from.</param>
|
|||
/// <param name="channel">The channel info.</param>
|
|||
/// <param name="decompressedPixelData">The decompressed pixel data.</param>
|
|||
/// <param name="redPixelData">The red channel pixel data.</param>
|
|||
/// <param name="greenPixelData">The green channel pixel data.</param>
|
|||
/// <param name="bluePixelData">The blue channel pixel data.</param>
|
|||
/// <param name="alphaPixelData">The alpha channel pixel data.</param>
|
|||
/// <param name="width">The width of a row in pixels.</param>
|
|||
/// <returns>The bytes read.</returns>
|
|||
private int ReadUnsignedIntChannelData( |
|||
BufferedReadStream stream, |
|||
ExrChannelInfo channel, |
|||
Span<byte> decompressedPixelData, |
|||
Span<uint> redPixelData, |
|||
Span<uint> greenPixelData, |
|||
Span<uint> bluePixelData, |
|||
Span<uint> alphaPixelData, |
|||
int width) |
|||
{ |
|||
switch (channel.ChannelName) |
|||
{ |
|||
case ExrConstants.ChannelNames.Red: |
|||
return ReadChannelData(channel, decompressedPixelData, redPixelData, width); |
|||
|
|||
case ExrConstants.ChannelNames.Blue: |
|||
return ReadChannelData(channel, decompressedPixelData, bluePixelData, width); |
|||
|
|||
case ExrConstants.ChannelNames.Green: |
|||
return ReadChannelData(channel, decompressedPixelData, greenPixelData, width); |
|||
|
|||
case ExrConstants.ChannelNames.Alpha: |
|||
return ReadChannelData(channel, decompressedPixelData, alphaPixelData, width); |
|||
|
|||
case ExrConstants.ChannelNames.Luminance: |
|||
int bytesRead = ReadChannelData(channel, decompressedPixelData, redPixelData, width); |
|||
redPixelData.CopyTo(bluePixelData); |
|||
redPixelData.CopyTo(greenPixelData); |
|||
return bytesRead; |
|||
|
|||
default: |
|||
// Skip unknown channel.
|
|||
int channelDataSizeInBytes = channel.PixelType is ExrPixelType.Float or ExrPixelType.UnsignedInt ? 4 : 2; |
|||
stream.Position += this.Width * channelDataSizeInBytes; |
|||
return channelDataSizeInBytes; |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Reads the channel data for pixel type HALF or FLOAT.
|
|||
/// </summary>
|
|||
/// <param name="channel">The channel info.</param>
|
|||
/// <param name="decompressedPixelData">The decompressed pixel data.</param>
|
|||
/// <param name="pixelData">The pixel data as float.</param>
|
|||
/// <param name="width">The width in pixel of a row.</param>
|
|||
/// <returns>The bytes read.</returns>
|
|||
private static int ReadChannelData(ExrChannelInfo channel, Span<byte> decompressedPixelData, Span<float> pixelData, int width) => channel.PixelType switch |
|||
{ |
|||
ExrPixelType.Half => ReadPixelRowChannelHalfSingle(decompressedPixelData, pixelData, width), |
|||
ExrPixelType.Float => ReadPixelRowChannelSingle(decompressedPixelData, pixelData, width), |
|||
_ => 0, |
|||
}; |
|||
|
|||
/// <summary>
|
|||
/// Reads the channel data for pixel type UINT.
|
|||
/// </summary>
|
|||
/// <param name="channel">The channel info.</param>
|
|||
/// <param name="decompressedPixelData">The decompressed pixel data.</param>
|
|||
/// <param name="pixelData">The pixel data as uint.</param>
|
|||
/// <param name="width">The width in pixels.</param>
|
|||
/// <returns>The bytes read.</returns>
|
|||
private static int ReadChannelData(ExrChannelInfo channel, Span<byte> decompressedPixelData, Span<uint> pixelData, int width) => channel.PixelType switch |
|||
{ |
|||
ExrPixelType.UnsignedInt => ReadPixelRowChannelUnsignedInt(decompressedPixelData, pixelData, width), |
|||
_ => 0, |
|||
}; |
|||
|
|||
/// <summary>
|
|||
/// Reads a pixel row with the pixel data being 16 bit half values.
|
|||
/// </summary>
|
|||
/// <param name="decompressedPixelData">The decompressed pixel data.</param>
|
|||
/// <param name="channelData">The channel data as float.</param>
|
|||
/// <param name="width">The width of a row in pixels.</param>
|
|||
/// <returns>The bytes read.</returns>
|
|||
private static int ReadPixelRowChannelHalfSingle(Span<byte> decompressedPixelData, Span<float> channelData, int width) |
|||
{ |
|||
int offset = 0; |
|||
for (int x = 0; x < width; x++) |
|||
{ |
|||
ushort shortValue = BinaryPrimitives.ReadUInt16LittleEndian(decompressedPixelData.Slice(offset, 2)); |
|||
channelData[x] = HalfTypeHelper.Unpack(shortValue); |
|||
offset += 2; |
|||
} |
|||
|
|||
return offset; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Reads a pixel row with 32 bit float pixel data.
|
|||
/// </summary>
|
|||
/// <param name="decompressedPixelData">The decompressed pixel data.</param>
|
|||
/// <param name="channelData">The pixel data as float.</param>
|
|||
/// <param name="width">The width in pixels of a row.</param>
|
|||
/// <returns>The bytes read.</returns>
|
|||
private static int ReadPixelRowChannelSingle(Span<byte> decompressedPixelData, Span<float> channelData, int width) |
|||
{ |
|||
int offset = 0; |
|||
for (int x = 0; x < width; x++) |
|||
{ |
|||
int intValue = BinaryPrimitives.ReadInt32LittleEndian(decompressedPixelData.Slice(offset, 4)); |
|||
channelData[x] = Unsafe.As<int, float>(ref intValue); |
|||
offset += 4; |
|||
} |
|||
|
|||
return offset; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Reads a pixel row with the pixel typ UINT.
|
|||
/// </summary>
|
|||
/// <param name="decompressedPixelData">The decompressed pixel bytes.</param>
|
|||
/// <param name="channelData">The uint pixel data.</param>
|
|||
/// <param name="width">The width of a row in pixels.</param>
|
|||
/// <returns>The bytes read.</returns>
|
|||
private static int ReadPixelRowChannelUnsignedInt(Span<byte> decompressedPixelData, Span<uint> channelData, int width) |
|||
{ |
|||
int offset = 0; |
|||
for (int x = 0; x < width; x++) |
|||
{ |
|||
channelData[x] = BinaryPrimitives.ReadUInt32LittleEndian(decompressedPixelData.Slice(offset, 4)); |
|||
offset += 4; |
|||
} |
|||
|
|||
return offset; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Validates that all image channels have the same type and are among the supported pixel types.
|
|||
/// </summary>
|
|||
/// <returns>The pixel type.</returns>
|
|||
private ExrPixelType ValidateChannels() |
|||
{ |
|||
if (this.Channels.Count == 0) |
|||
{ |
|||
ExrThrowHelper.ThrowInvalidImageContentException("At least one channel of pixel data is expected!"); |
|||
} |
|||
|
|||
// Find pixel the type of any channel which is R, G, B or A.
|
|||
ExrPixelType? pixelType = null; |
|||
for (int i = 0; i < this.Channels.Count; i++) |
|||
{ |
|||
if (this.Channels[i].ChannelName.Equals(ExrConstants.ChannelNames.Blue, StringComparison.Ordinal) || |
|||
this.Channels[i].ChannelName.Equals(ExrConstants.ChannelNames.Green, StringComparison.Ordinal) || |
|||
this.Channels[i].ChannelName.Equals(ExrConstants.ChannelNames.Red, StringComparison.Ordinal) || |
|||
this.Channels[i].ChannelName.Equals(ExrConstants.ChannelNames.Alpha, StringComparison.Ordinal) || |
|||
this.Channels[i].ChannelName.Equals(ExrConstants.ChannelNames.Luminance, StringComparison.Ordinal)) |
|||
{ |
|||
if (!pixelType.HasValue) |
|||
{ |
|||
pixelType = this.Channels[i].PixelType; |
|||
} |
|||
else |
|||
{ |
|||
if (pixelType != this.Channels[i].PixelType) |
|||
{ |
|||
ExrThrowHelper.ThrowNotSupported("Pixel channel data is expected to be the same for all channels."); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
if (!pixelType.HasValue) |
|||
{ |
|||
ExrThrowHelper.ThrowNotSupported("Pixel channel data is unknown! Only R, G, B, A and Y are supported."); |
|||
} |
|||
|
|||
return pixelType.Value; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Determines the type image from the channel information.
|
|||
/// </summary>
|
|||
/// <returns>The image data type.</returns>
|
|||
private ExrImageDataType DetermineImageDataType() |
|||
{ |
|||
bool hasRedChannel = false; |
|||
bool hasGreenChannel = false; |
|||
bool hasBlueChannel = false; |
|||
bool hasAlphaChannel = false; |
|||
bool hasLuminance = false; |
|||
foreach (ExrChannelInfo channelInfo in this.Channels) |
|||
{ |
|||
if (channelInfo.ChannelName.Equals("A", StringComparison.Ordinal)) |
|||
{ |
|||
hasAlphaChannel = true; |
|||
} |
|||
|
|||
if (channelInfo.ChannelName.Equals("R", StringComparison.Ordinal)) |
|||
{ |
|||
hasRedChannel = true; |
|||
} |
|||
|
|||
if (channelInfo.ChannelName.Equals("G", StringComparison.Ordinal)) |
|||
{ |
|||
hasGreenChannel = true; |
|||
} |
|||
|
|||
if (channelInfo.ChannelName.Equals("B", StringComparison.Ordinal)) |
|||
{ |
|||
hasBlueChannel = true; |
|||
} |
|||
|
|||
if (channelInfo.ChannelName.Equals("Y", StringComparison.Ordinal)) |
|||
{ |
|||
hasLuminance = true; |
|||
} |
|||
} |
|||
|
|||
if (hasRedChannel && hasGreenChannel && hasBlueChannel && hasAlphaChannel) |
|||
{ |
|||
return ExrImageDataType.Rgba; |
|||
} |
|||
|
|||
if (hasRedChannel && hasGreenChannel && hasBlueChannel) |
|||
{ |
|||
return ExrImageDataType.Rgb; |
|||
} |
|||
|
|||
if (hasLuminance && this.Channels.Count == 1) |
|||
{ |
|||
return ExrImageDataType.Gray; |
|||
} |
|||
|
|||
return ExrImageDataType.Unknown; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Reads the exr image header.
|
|||
/// <see href="https://openexr.com/en/latest/OpenEXRFileLayout.html#header-attributes-all-files/"/>
|
|||
/// </summary>
|
|||
/// <param name="stream">The stream.</param>
|
|||
/// <returns>The image header attributes.</returns>
|
|||
private ExrHeaderAttributes ReadExrHeader(BufferedReadStream stream) |
|||
{ |
|||
// Skip over the magick bytes, we already know its an EXR image.
|
|||
stream.Skip(4); |
|||
|
|||
// Read version number.
|
|||
byte version = (byte)stream.ReadByte(); |
|||
if (version != 2) |
|||
{ |
|||
ExrThrowHelper.ThrowNotSupportedVersion(); |
|||
} |
|||
|
|||
// Next three bytes contain info's about the image.
|
|||
byte flagsByte0 = (byte)stream.ReadByte(); |
|||
if ((flagsByte0 & (1 << 1)) != 0) |
|||
{ |
|||
ExrThrowHelper.ThrowNotSupported("Decoding tiled exr images is not supported yet!"); |
|||
} |
|||
|
|||
// Discard the next two bytes.
|
|||
int bytesRead = stream.Read(this.buffer, 0, 2); |
|||
if (bytesRead != 2) |
|||
{ |
|||
ExrThrowHelper.ThrowInvalidImageContentException("Could not read enough data for exr file!"); |
|||
} |
|||
|
|||
this.HeaderAttributes = this.ParseHeaderAttributes(stream); |
|||
|
|||
this.Width = this.HeaderAttributes.DataWindow.XMax - this.HeaderAttributes.DataWindow.XMin + 1; |
|||
this.Height = this.HeaderAttributes.DataWindow.YMax - this.HeaderAttributes.DataWindow.YMin + 1; |
|||
this.Channels = this.HeaderAttributes.Channels; |
|||
this.Compression = this.HeaderAttributes.Compression; |
|||
this.PixelType = this.ValidateChannels(); |
|||
this.ImageDataType = this.DetermineImageDataType(); |
|||
|
|||
this.metadata = new ImageMetadata(); |
|||
|
|||
this.exrMetadata = this.metadata.GetExrMetadata(); |
|||
this.exrMetadata.PixelType = this.PixelType; |
|||
this.exrMetadata.ImageDataType = this.ImageDataType; |
|||
this.exrMetadata.Compression = this.Compression; |
|||
|
|||
return this.HeaderAttributes; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Parses the image header attributes.
|
|||
/// </summary>
|
|||
/// <param name="stream">The stream to read from.</param>
|
|||
/// <returns>The image header attributes.</returns>
|
|||
private ExrHeaderAttributes ParseHeaderAttributes(BufferedReadStream stream) |
|||
{ |
|||
ExrAttribute attribute = this.ReadAttribute(stream); |
|||
|
|||
IList<ExrChannelInfo> channels = null; |
|||
ExrBox2i? dataWindow = null; |
|||
ExrCompression? compression = null; |
|||
ExrBox2i? displayWindow = null; |
|||
ExrLineOrder? lineOrder = null; |
|||
float? aspectRatio = null; |
|||
float? screenWindowCenterX = null; |
|||
float? screenWindowCenterY = null; |
|||
float? screenWindowWidth = null; |
|||
uint? tileXSize = null; |
|||
uint? tileYSize = null; |
|||
int? chunkCount = null; |
|||
while (!attribute.Equals(ExrAttribute.EmptyAttribute)) |
|||
{ |
|||
switch (attribute.Name) |
|||
{ |
|||
case ExrConstants.AttributeNames.Channels: |
|||
channels = this.ReadChannelList(stream, attribute.Length); |
|||
break; |
|||
case ExrConstants.AttributeNames.Compression: |
|||
compression = (ExrCompression)stream.ReadByte(); |
|||
break; |
|||
case ExrConstants.AttributeNames.DataWindow: |
|||
dataWindow = this.ReadBoxInteger(stream); |
|||
break; |
|||
case ExrConstants.AttributeNames.DisplayWindow: |
|||
displayWindow = this.ReadBoxInteger(stream); |
|||
break; |
|||
case ExrConstants.AttributeNames.LineOrder: |
|||
lineOrder = (ExrLineOrder)stream.ReadByte(); |
|||
break; |
|||
case ExrConstants.AttributeNames.PixelAspectRatio: |
|||
aspectRatio = this.ReadSingle(stream); |
|||
break; |
|||
case ExrConstants.AttributeNames.ScreenWindowCenter: |
|||
screenWindowCenterX = this.ReadSingle(stream); |
|||
screenWindowCenterY = this.ReadSingle(stream); |
|||
break; |
|||
case ExrConstants.AttributeNames.ScreenWindowWidth: |
|||
screenWindowWidth = this.ReadSingle(stream); |
|||
break; |
|||
case ExrConstants.AttributeNames.Tiles: |
|||
tileXSize = this.ReadUnsignedInteger(stream); |
|||
tileYSize = this.ReadUnsignedInteger(stream); |
|||
break; |
|||
case ExrConstants.AttributeNames.ChunkCount: |
|||
chunkCount = this.ReadSignedInteger(stream); |
|||
break; |
|||
default: |
|||
// Skip unknown attribute bytes.
|
|||
stream.Skip(attribute.Length); |
|||
break; |
|||
} |
|||
|
|||
attribute = this.ReadAttribute(stream); |
|||
} |
|||
|
|||
if (!displayWindow.HasValue) |
|||
{ |
|||
ExrThrowHelper.ThrowInvalidImageContentException("Invalid exr image header, the displayWindow attribute is missing!"); |
|||
} |
|||
|
|||
if (!dataWindow.HasValue) |
|||
{ |
|||
ExrThrowHelper.ThrowInvalidImageContentException("Invalid exr image header, the dataWindow attribute is missing!"); |
|||
} |
|||
|
|||
if (channels is null) |
|||
{ |
|||
ExrThrowHelper.ThrowInvalidImageContentException("Invalid exr image header, the channels attribute is missing!"); |
|||
} |
|||
|
|||
if (!compression.HasValue) |
|||
{ |
|||
ExrThrowHelper.ThrowInvalidImageContentException("Invalid exr image header, the compression attribute is missing!"); |
|||
} |
|||
|
|||
if (!lineOrder.HasValue) |
|||
{ |
|||
ExrThrowHelper.ThrowInvalidImageContentException("Invalid exr image header, the lineOrder attribute is missing!"); |
|||
} |
|||
|
|||
if (!aspectRatio.HasValue) |
|||
{ |
|||
ExrThrowHelper.ThrowInvalidImageContentException("Invalid exr image header, the aspectRatio attribute is missing!"); |
|||
} |
|||
|
|||
if (!screenWindowWidth.HasValue) |
|||
{ |
|||
ExrThrowHelper.ThrowInvalidImageContentException("Invalid exr image header, the screenWindowWidth attribute is missing!"); |
|||
} |
|||
|
|||
if (!screenWindowCenterX.HasValue || !screenWindowCenterY.HasValue) |
|||
{ |
|||
ExrThrowHelper.ThrowInvalidImageContentException("Invalid exr image header, the screenWindowCenter attribute is missing!"); |
|||
} |
|||
|
|||
ExrHeaderAttributes header = new( |
|||
channels, |
|||
compression.Value, |
|||
dataWindow.Value, |
|||
displayWindow.Value, |
|||
lineOrder.Value, |
|||
aspectRatio.Value, |
|||
screenWindowWidth.Value, |
|||
new PointF(screenWindowCenterX.Value, screenWindowCenterY.Value), |
|||
tileXSize, |
|||
tileYSize, |
|||
chunkCount); |
|||
return header; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Reads a attrbute from the stream, which consist of a name, a type and a size in bytes.
|
|||
/// </summary>
|
|||
/// <param name="stream">The stream to read from.</param>
|
|||
/// <returns>A attribute.</returns>
|
|||
private ExrAttribute ReadAttribute(BufferedReadStream stream) |
|||
{ |
|||
string attributeName = ReadString(stream); |
|||
if (attributeName.Equals(string.Empty, StringComparison.Ordinal)) |
|||
{ |
|||
return ExrAttribute.EmptyAttribute; |
|||
} |
|||
|
|||
string attributeType = ReadString(stream); |
|||
int attributeSize = this.ReadSignedInteger(stream); |
|||
|
|||
return new ExrAttribute(attributeName, attributeType, attributeSize); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Reads a box attribute, which is a xMin, xMax and yMin, yMax value.
|
|||
/// </summary>
|
|||
/// <param name="stream">The stream to reaad from.</param>
|
|||
/// <returns>A box struct.</returns>
|
|||
private ExrBox2i ReadBoxInteger(BufferedReadStream stream) |
|||
{ |
|||
int xMin = this.ReadSignedInteger(stream); |
|||
int yMin = this.ReadSignedInteger(stream); |
|||
int xMax = this.ReadSignedInteger(stream); |
|||
int yMax = this.ReadSignedInteger(stream); |
|||
|
|||
return new ExrBox2i(xMin, yMin, xMax, yMax); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Reads the channel list from the stream.
|
|||
/// </summary>
|
|||
/// <param name="stream">The stream to read from.</param>
|
|||
/// <param name="attributeSize">The size in bytes of the channel list attribute.</param>
|
|||
/// <returns>The channel list.</returns>
|
|||
private List<ExrChannelInfo> ReadChannelList(BufferedReadStream stream, int attributeSize) |
|||
{ |
|||
List<ExrChannelInfo> channels = []; |
|||
while (attributeSize > 1) |
|||
{ |
|||
ExrChannelInfo channelInfo = this.ReadChannelInfo(stream, out int bytesRead); |
|||
channels.Add(channelInfo); |
|||
attributeSize -= bytesRead; |
|||
} |
|||
|
|||
// Last byte should be a null byte.
|
|||
if (stream.ReadByte() == -1) |
|||
{ |
|||
ExrThrowHelper.ThrowInvalidImageContentException("Could not read enough data to read the exr channel list!"); |
|||
} |
|||
|
|||
return channels; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Reads the channel information from the stream.
|
|||
/// </summary>
|
|||
/// <param name="stream">The stream to read from.</param>
|
|||
/// <param name="bytesRead">The bytes read.</param>
|
|||
/// <returns>Channel info.</returns>
|
|||
private ExrChannelInfo ReadChannelInfo(BufferedReadStream stream, out int bytesRead) |
|||
{ |
|||
string channelName = ReadString(stream); |
|||
bytesRead = channelName.Length + 1; |
|||
|
|||
ExrPixelType pixelType = (ExrPixelType)this.ReadSignedInteger(stream); |
|||
bytesRead += 4; |
|||
|
|||
byte pLinear = (byte)stream.ReadByte(); |
|||
|
|||
// Next 3 bytes are reserved bytes and not use.
|
|||
if (stream.Read(this.buffer, 0, 3) != 3) |
|||
{ |
|||
ExrThrowHelper.ThrowInvalidImageContentException("Could not read enough data to read exr channel info!"); |
|||
} |
|||
|
|||
bytesRead += 4; |
|||
|
|||
int xSampling = this.ReadSignedInteger(stream); |
|||
bytesRead += 4; |
|||
|
|||
int ySampling = this.ReadSignedInteger(stream); |
|||
bytesRead += 4; |
|||
|
|||
return new ExrChannelInfo(channelName, pixelType, pLinear, xSampling, ySampling); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Reads a the string from the stream.
|
|||
/// </summary>
|
|||
/// <param name="stream">The stream to read from.</param>
|
|||
/// <returns>A string.</returns>
|
|||
private static string ReadString(BufferedReadStream stream) |
|||
{ |
|||
StringBuilder str = new(); |
|||
int character = stream.ReadByte(); |
|||
if (character == 0) |
|||
{ |
|||
// End of file header reached.
|
|||
return string.Empty; |
|||
} |
|||
|
|||
while (character != 0) |
|||
{ |
|||
if (character == -1) |
|||
{ |
|||
ExrThrowHelper.ThrowInvalidImageHeader(); |
|||
} |
|||
|
|||
str.Append((char)character); |
|||
character = stream.ReadByte(); |
|||
} |
|||
|
|||
return str.ToString(); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Determines whether the compression is supported.
|
|||
/// </summary>
|
|||
/// <returns> True if the compression is supported; otherwise, false>. </returns>
|
|||
private bool IsSupportedCompression() => this.Compression switch |
|||
{ |
|||
ExrCompression.None or ExrCompression.Zip or ExrCompression.Zips or ExrCompression.RunLengthEncoded or ExrCompression.B44 => true, |
|||
_ => false, |
|||
}; |
|||
|
|||
/// <summary>
|
|||
/// Determines whether this image has alpha channel.
|
|||
/// </summary>
|
|||
/// <returns> True if this image has a alpha channel; otherwise, false. </returns>
|
|||
private bool HasAlpha() |
|||
{ |
|||
foreach (ExrChannelInfo channelInfo in this.Channels) |
|||
{ |
|||
if (channelInfo.ChannelName.Equals("A", StringComparison.Ordinal)) |
|||
{ |
|||
return true; |
|||
} |
|||
} |
|||
|
|||
return false; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Reads a unsigned long value from the stream.
|
|||
/// </summary>
|
|||
/// <param name="stream">The stream to read the data from.</param>
|
|||
/// <returns>The unsigned long value.</returns>
|
|||
private ulong ReadUnsignedLong(BufferedReadStream stream) |
|||
{ |
|||
int bytesRead = stream.Read(this.buffer, 0, 8); |
|||
if (bytesRead != 8) |
|||
{ |
|||
ExrThrowHelper.ThrowInvalidImageContentException("Not enough data to read a unsigned long from the stream!"); |
|||
} |
|||
|
|||
return BinaryPrimitives.ReadUInt64LittleEndian(this.buffer); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Reads a unsigned integer value from the stream.
|
|||
/// </summary>
|
|||
/// <param name="stream">The stream to read the data from.</param>
|
|||
/// <returns>The integer value.</returns>
|
|||
private uint ReadUnsignedInteger(BufferedReadStream stream) |
|||
{ |
|||
int bytesRead = stream.Read(this.buffer, 0, 4); |
|||
if (bytesRead != 4) |
|||
{ |
|||
ExrThrowHelper.ThrowInvalidImageContentException("Not enough data to read a unsigned int from the stream!"); |
|||
} |
|||
|
|||
return BinaryPrimitives.ReadUInt32LittleEndian(this.buffer); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Reads a signed integer value from the stream.
|
|||
/// </summary>
|
|||
/// <param name="stream">The stream to read the data from.</param>
|
|||
/// <returns>The integer value.</returns>
|
|||
private int ReadSignedInteger(BufferedReadStream stream) |
|||
{ |
|||
int bytesRead = stream.Read(this.buffer, 0, 4); |
|||
if (bytesRead != 4) |
|||
{ |
|||
ExrThrowHelper.ThrowInvalidImageContentException("Not enough data to read a signed int from the stream!"); |
|||
} |
|||
|
|||
return BinaryPrimitives.ReadInt32LittleEndian(this.buffer); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Reads a float value from the stream.
|
|||
/// </summary>
|
|||
/// <param name="stream">The stream to read the data from.</param>
|
|||
/// <returns>The float value.</returns>
|
|||
private float ReadSingle(BufferedReadStream stream) |
|||
{ |
|||
int bytesRead = stream.Read(this.buffer, 0, 4); |
|||
if (bytesRead != 4) |
|||
{ |
|||
ExrThrowHelper.ThrowInvalidImageContentException("Not enough data to read a float value from the stream!"); |
|||
} |
|||
|
|||
int intValue = BinaryPrimitives.ReadInt32BigEndian(this.buffer); |
|||
|
|||
return Unsafe.As<int, float>(ref intValue); |
|||
} |
|||
} |
|||
@ -0,0 +1,13 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Exr; |
|||
|
|||
/// <summary>
|
|||
/// Image decoder options for decoding OpenExr streams.
|
|||
/// </summary>
|
|||
public sealed class ExrDecoderOptions : ISpecializedDecoderOptions |
|||
{ |
|||
/// <inheritdoc/>
|
|||
public DecoderOptions GeneralOptions { get; init; } = new(); |
|||
} |
|||
@ -0,0 +1,29 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using SixLabors.ImageSharp.Formats.Exr.Constants; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Exr; |
|||
|
|||
/// <summary>
|
|||
/// Image encoder for writing an image to a stream in the OpenExr Format.
|
|||
/// </summary>
|
|||
public sealed class ExrEncoder : ImageEncoder |
|||
{ |
|||
/// <summary>
|
|||
/// Gets or sets the pixel type of the image.
|
|||
/// </summary>
|
|||
public ExrPixelType? PixelType { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the compression type to use.
|
|||
/// </summary>
|
|||
public ExrCompression? Compression { get; init; } |
|||
|
|||
/// <inheritdoc />
|
|||
protected override void Encode<TPixel>(Image<TPixel> image, Stream stream, CancellationToken cancellationToken) |
|||
{ |
|||
ExrEncoderCore encoder = new(this, image.Configuration, image.Configuration.MemoryAllocator); |
|||
encoder.Encode(image, stream, cancellationToken); |
|||
} |
|||
} |
|||
@ -0,0 +1,699 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using System.Buffers; |
|||
using System.Buffers.Binary; |
|||
using System.Numerics; |
|||
using System.Runtime.CompilerServices; |
|||
using SixLabors.ImageSharp.Formats.Exr.Compression; |
|||
using SixLabors.ImageSharp.Formats.Exr.Constants; |
|||
using SixLabors.ImageSharp.Memory; |
|||
using SixLabors.ImageSharp.Metadata; |
|||
using SixLabors.ImageSharp.PixelFormats; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Exr; |
|||
|
|||
/// <summary>
|
|||
/// Image encoder for writing an image to a stream in the OpenExr format.
|
|||
/// </summary>
|
|||
internal sealed class ExrEncoderCore |
|||
{ |
|||
/// <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 global configuration.
|
|||
/// </summary>
|
|||
private readonly Configuration configuration; |
|||
|
|||
/// <summary>
|
|||
/// The encoder with options.
|
|||
/// </summary>
|
|||
private readonly ExrEncoder encoder; |
|||
|
|||
/// <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="encoder">The encoder with options.</param>
|
|||
/// <param name="configuration">The configuration.</param>
|
|||
/// <param name="memoryAllocator">The memory manager.</param>
|
|||
public ExrEncoderCore(ExrEncoder encoder, Configuration configuration, MemoryAllocator memoryAllocator) |
|||
{ |
|||
this.configuration = configuration; |
|||
this.encoder = encoder; |
|||
this.memoryAllocator = memoryAllocator; |
|||
this.Compression = encoder.Compression ?? ExrCompression.None; |
|||
this.pixelType = encoder.PixelType; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the compression implementation to use when encoding the image.
|
|||
/// </summary>
|
|||
internal ExrCompression Compression { get; set; } |
|||
|
|||
/// <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)); |
|||
|
|||
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; |
|||
float aspectRatio = 1.0f; |
|||
ExrBox2i dataWindow = new(0, 0, width - 1, height - 1); |
|||
ExrBox2i displayWindow = new(0, 0, width - 1, height - 1); |
|||
ExrLineOrder lineOrder = ExrLineOrder.IncreasingY; |
|||
PointF screenWindowCenter = new(0.0f, 0.0f); |
|||
int screenWindowWidth = 1; |
|||
List<ExrChannelInfo> channels = |
|||
[ |
|||
new(ExrConstants.ChannelNames.Alpha, this.pixelType.Value, 0, 1, 1), |
|||
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), |
|||
]; |
|||
ExrHeaderAttributes header = new( |
|||
channels, |
|||
this.Compression, |
|||
dataWindow, |
|||
displayWindow, |
|||
lineOrder, |
|||
aspectRatio, |
|||
screenWindowWidth, |
|||
screenWindowCenter); |
|||
|
|||
// Write magick bytes.
|
|||
BinaryPrimitives.WriteInt32LittleEndian(this.buffer, ExrConstants.MagickBytes); |
|||
stream.Write(this.buffer.AsSpan(0, 4)); |
|||
|
|||
// Version number.
|
|||
this.buffer[0] = 2; |
|||
|
|||
// 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 EXR header.
|
|||
this.WriteHeader(stream, header); |
|||
|
|||
// Next is offsets table to each pixel row, which will be written after the pixel data was written.
|
|||
ulong startOfRowOffsetData = (ulong)stream.Position; |
|||
stream.Position += 8 * height; |
|||
|
|||
// Write pixel data.
|
|||
switch (this.pixelType) |
|||
{ |
|||
case ExrPixelType.Half: |
|||
case ExrPixelType.Float: |
|||
{ |
|||
ulong[] rowOffsets = this.EncodeFloatingPointPixelData(stream, pixels, width, height, channels, this.Compression, cancellationToken); |
|||
stream.Position = (long)startOfRowOffsetData; |
|||
this.WriteRowOffsets(stream, height, rowOffsets); |
|||
break; |
|||
} |
|||
|
|||
case ExrPixelType.UnsignedInt: |
|||
{ |
|||
ulong[] rowOffsets = this.EncodeUnsignedIntPixelData(stream, pixels, width, height, channels, this.Compression, cancellationToken); |
|||
stream.Position = (long)startOfRowOffsetData; |
|||
this.WriteRowOffsets(stream, height, rowOffsets); |
|||
break; |
|||
} |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Encodes and writes pixel data with float pixel data to the stream.
|
|||
/// </summary>
|
|||
/// <typeparam name="TPixel">The type of the pixels.</typeparam>
|
|||
/// <param name="stream">The stream to write to.</param>
|
|||
/// <param name="pixels">The pixel bufer.</param>
|
|||
/// <param name="width">The width of the image in pixels.</param>
|
|||
/// <param name="height">The height of the image in pixels.</param>
|
|||
/// <param name="channels">The imagechannels.</param>
|
|||
/// <param name="compression">The compression to use.</param>
|
|||
/// <param name="cancellationToken">The cancellation token.</param>
|
|||
/// <returns>The array of pixel row offsets.</returns>
|
|||
private ulong[] EncodeFloatingPointPixelData<TPixel>( |
|||
Stream stream, |
|||
Buffer2D<TPixel> pixels, |
|||
int width, |
|||
int height, |
|||
List<ExrChannelInfo> channels, |
|||
ExrCompression compression, |
|||
CancellationToken cancellationToken) |
|||
where TPixel : unmanaged, IPixel<TPixel> |
|||
{ |
|||
uint bytesPerRow = ExrUtils.CalculateBytesPerRow(channels, (uint)width); |
|||
uint rowsPerBlock = ExrUtils.RowsPerBlock(compression); |
|||
uint bytesPerBlock = bytesPerRow * rowsPerBlock; |
|||
|
|||
using IMemoryOwner<float> rgbBuffer = this.memoryAllocator.Allocate<float>(width * 4, AllocationOptions.Clean); |
|||
using IMemoryOwner<byte> rowBlockBuffer = this.memoryAllocator.Allocate<byte>((int)bytesPerBlock, AllocationOptions.Clean); |
|||
Span<float> redBuffer = rgbBuffer.GetSpan()[..width]; |
|||
Span<float> greenBuffer = rgbBuffer.GetSpan().Slice(width, width); |
|||
Span<float> blueBuffer = rgbBuffer.GetSpan().Slice(width * 2, width); |
|||
Span<float> alphaBuffer = rgbBuffer.GetSpan().Slice(width * 3, width); |
|||
|
|||
using ExrBaseCompressor compressor = ExrCompressorFactory.Create(compression, this.memoryAllocator, stream, bytesPerBlock, bytesPerRow); |
|||
|
|||
ulong[] rowOffsets = new ulong[height]; |
|||
for (uint y = 0; y < height; y += rowsPerBlock) |
|||
{ |
|||
rowOffsets[y] = (ulong)stream.Position; |
|||
|
|||
// Write row index.
|
|||
BinaryPrimitives.WriteUInt32LittleEndian(this.buffer, y); |
|||
stream.Write(this.buffer.AsSpan(0, 4)); |
|||
|
|||
// At this point, it is not yet known how much bytes the compressed data will take up, keep stream position.
|
|||
long pixelDataSizePos = stream.Position; |
|||
stream.Position = pixelDataSizePos + 4; |
|||
|
|||
uint rowsInBlockCount = 0; |
|||
for (uint rowIndex = y; rowIndex < y + rowsPerBlock && rowIndex < height; rowIndex++) |
|||
{ |
|||
Span<TPixel> pixelRowSpan = pixels.DangerousGetRowSpan((int)rowIndex); |
|||
for (int x = 0; x < width; x++) |
|||
{ |
|||
Vector4 vector4 = pixelRowSpan[x].ToVector4(); |
|||
redBuffer[x] = vector4.X; |
|||
greenBuffer[x] = vector4.Y; |
|||
blueBuffer[x] = vector4.Z; |
|||
alphaBuffer[x] = vector4.W; |
|||
} |
|||
|
|||
// Write pixel data to row block buffer.
|
|||
Span<byte> rowBlockSpan = rowBlockBuffer.GetSpan().Slice((int)(rowsInBlockCount * bytesPerRow), (int)bytesPerRow); |
|||
switch (this.pixelType) |
|||
{ |
|||
case ExrPixelType.Float: |
|||
WriteSingleRow(rowBlockSpan, width, alphaBuffer, blueBuffer, greenBuffer, redBuffer); |
|||
break; |
|||
case ExrPixelType.Half: |
|||
WriteHalfSingleRow(rowBlockSpan, width, alphaBuffer, blueBuffer, greenBuffer, redBuffer); |
|||
break; |
|||
} |
|||
|
|||
rowsInBlockCount++; |
|||
} |
|||
|
|||
// Write compressed pixel row data to the stream.
|
|||
uint compressedBytes = compressor.CompressRowBlock(rowBlockBuffer.GetSpan(), (int)rowsInBlockCount); |
|||
long positionAfterPixelData = stream.Position; |
|||
|
|||
// Write pixel row data size.
|
|||
BinaryPrimitives.WriteUInt32LittleEndian(this.buffer, compressedBytes); |
|||
stream.Position = pixelDataSizePos; |
|||
stream.Write(this.buffer.AsSpan(0, 4)); |
|||
stream.Position = positionAfterPixelData; |
|||
|
|||
cancellationToken.ThrowIfCancellationRequested(); |
|||
} |
|||
|
|||
return rowOffsets; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Encodes and writes pixel data with the unsigned int pixel type to the stream.
|
|||
/// </summary>
|
|||
/// <typeparam name="TPixel">The type of the pixels.</typeparam>
|
|||
/// <param name="stream">The stream to write to.</param>
|
|||
/// <param name="pixels">The pixel bufer.</param>
|
|||
/// <param name="width">The width of the image in pixels.</param>
|
|||
/// <param name="height">The height of the image in pixels.</param>
|
|||
/// <param name="channels">The imagechannels.</param>
|
|||
/// <param name="compression">The compression to use.</param>
|
|||
/// <param name="cancellationToken">The cancellation token.</param>
|
|||
/// <returns>The array of pixel row offsets.</returns>
|
|||
private ulong[] EncodeUnsignedIntPixelData<TPixel>( |
|||
Stream stream, |
|||
Buffer2D<TPixel> pixels, |
|||
int width, |
|||
int height, |
|||
List<ExrChannelInfo> channels, |
|||
ExrCompression compression, |
|||
CancellationToken cancellationToken) |
|||
where TPixel : unmanaged, IPixel<TPixel> |
|||
{ |
|||
uint bytesPerRow = ExrUtils.CalculateBytesPerRow(channels, (uint)width); |
|||
uint rowsPerBlock = ExrUtils.RowsPerBlock(compression); |
|||
uint bytesPerBlock = bytesPerRow * rowsPerBlock; |
|||
|
|||
using IMemoryOwner<uint> rgbBuffer = this.memoryAllocator.Allocate<uint>(width * 4, AllocationOptions.Clean); |
|||
using IMemoryOwner<byte> rowBlockBuffer = this.memoryAllocator.Allocate<byte>((int)bytesPerBlock, AllocationOptions.Clean); |
|||
Span<uint> redBuffer = rgbBuffer.GetSpan()[..width]; |
|||
Span<uint> greenBuffer = rgbBuffer.GetSpan().Slice(width, width); |
|||
Span<uint> blueBuffer = rgbBuffer.GetSpan().Slice(width * 2, width); |
|||
Span<uint> alphaBuffer = rgbBuffer.GetSpan().Slice(width * 3, width); |
|||
|
|||
using ExrBaseCompressor compressor = ExrCompressorFactory.Create(compression, this.memoryAllocator, stream, bytesPerBlock, bytesPerRow); |
|||
|
|||
Rgba128 rgb = default; |
|||
ulong[] rowOffsets = new ulong[height]; |
|||
for (uint y = 0; y < height; y += rowsPerBlock) |
|||
{ |
|||
rowOffsets[y] = (ulong)stream.Position; |
|||
|
|||
// Write row index.
|
|||
BinaryPrimitives.WriteUInt32LittleEndian(this.buffer, y); |
|||
stream.Write(this.buffer.AsSpan(0, 4)); |
|||
|
|||
// At this point, it is not yet known how much bytes the compressed data will take up, keep stream position.
|
|||
long pixelDataSizePos = stream.Position; |
|||
stream.Position = pixelDataSizePos + 4; |
|||
|
|||
uint rowsInBlockCount = 0; |
|||
for (uint rowIndex = y; rowIndex < y + rowsPerBlock && rowIndex < height; rowIndex++) |
|||
{ |
|||
Span<TPixel> pixelRowSpan = pixels.DangerousGetRowSpan((int)rowIndex); |
|||
for (int x = 0; x < width; x++) |
|||
{ |
|||
Vector4 vector4 = pixelRowSpan[x].ToVector4(); |
|||
rgb = Rgba128.FromVector4(vector4); |
|||
|
|||
redBuffer[x] = rgb.R; |
|||
greenBuffer[x] = rgb.G; |
|||
blueBuffer[x] = rgb.B; |
|||
alphaBuffer[x] = rgb.A; |
|||
} |
|||
|
|||
// Write row data to row block buffer.
|
|||
Span<byte> rowBlockSpan = rowBlockBuffer.GetSpan().Slice((int)(rowsInBlockCount * bytesPerRow), (int)bytesPerRow); |
|||
WriteUnsignedIntRow(rowBlockSpan, width, alphaBuffer, blueBuffer, greenBuffer, redBuffer); |
|||
rowsInBlockCount++; |
|||
} |
|||
|
|||
// Write pixel row data compressed to the stream.
|
|||
uint compressedBytes = compressor.CompressRowBlock(rowBlockBuffer.GetSpan(), (int)rowsInBlockCount); |
|||
long positionAfterPixelData = stream.Position; |
|||
|
|||
// Write pixel row data size.
|
|||
BinaryPrimitives.WriteUInt32LittleEndian(this.buffer, compressedBytes); |
|||
stream.Position = pixelDataSizePos; |
|||
stream.Write(this.buffer.AsSpan(0, 4)); |
|||
stream.Position = positionAfterPixelData; |
|||
|
|||
cancellationToken.ThrowIfCancellationRequested(); |
|||
} |
|||
|
|||
return rowOffsets; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Writes the image header to the stream.
|
|||
/// </summary>
|
|||
/// <param name="stream">The stream to write to.</param>
|
|||
/// <param name="header">The header.</param>
|
|||
private void WriteHeader(Stream stream, ExrHeaderAttributes header) |
|||
{ |
|||
this.WriteChannels(stream, header.Channels); |
|||
this.WriteCompression(stream, header.Compression); |
|||
this.WriteDataWindow(stream, header.DataWindow); |
|||
this.WriteDisplayWindow(stream, header.DisplayWindow); |
|||
this.WritePixelAspectRatio(stream, header.AspectRatio); |
|||
this.WriteLineOrder(stream, header.LineOrder); |
|||
this.WriteScreenWindowCenter(stream, header.ScreenWindowCenter); |
|||
this.WriteScreenWindowWidth(stream, header.ScreenWindowWidth); |
|||
stream.WriteByte(0); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Writes a row of pixels with the FLOAT pixel type to a buffer.
|
|||
/// </summary>
|
|||
/// <param name="buffer">The buffer to write to.</param>
|
|||
/// <param name="width">The width of a row in pixels.</param>
|
|||
/// <param name="alphaBuffer">The alpha channel buffer.</param>
|
|||
/// <param name="blueBuffer">The blue channel buffer.</param>
|
|||
/// <param name="greenBuffer">The green channel buffer.</param>
|
|||
/// <param name="redBuffer">The red channel buffer.</param>
|
|||
private static void WriteSingleRow(Span<byte> buffer, int width, Span<float> alphaBuffer, Span<float> blueBuffer, Span<float> greenBuffer, Span<float> redBuffer) |
|||
{ |
|||
int offset = 0; |
|||
for (int x = 0; x < width; x++) |
|||
{ |
|||
WriteSingleToBuffer(buffer.Slice(offset, 4), alphaBuffer[x]); |
|||
offset += 4; |
|||
} |
|||
|
|||
for (int x = 0; x < width; x++) |
|||
{ |
|||
WriteSingleToBuffer(buffer.Slice(offset, 4), blueBuffer[x]); |
|||
offset += 4; |
|||
} |
|||
|
|||
for (int x = 0; x < width; x++) |
|||
{ |
|||
WriteSingleToBuffer(buffer.Slice(offset, 4), greenBuffer[x]); |
|||
offset += 4; |
|||
} |
|||
|
|||
for (int x = 0; x < width; x++) |
|||
{ |
|||
WriteSingleToBuffer(buffer.Slice(offset, 4), redBuffer[x]); |
|||
offset += 4; |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Writes a row of pixels with the HALF pixel type to a buffer.
|
|||
/// </summary>
|
|||
/// <param name="buffer">The buffer to write to.</param>
|
|||
/// <param name="width">The width of a row in pixels.</param>
|
|||
/// <param name="alphaBuffer">The alpha channel buffer.</param>
|
|||
/// <param name="blueBuffer">The blue channel buffer.</param>
|
|||
/// <param name="greenBuffer">The green channel buffer.</param>
|
|||
/// <param name="redBuffer">The red channel buffer.</param>
|
|||
private static void WriteHalfSingleRow(Span<byte> buffer, int width, Span<float> alphaBuffer, Span<float> blueBuffer, Span<float> greenBuffer, Span<float> redBuffer) |
|||
{ |
|||
int offset = 0; |
|||
for (int x = 0; x < width; x++) |
|||
{ |
|||
WriteHalfSingleToBuffer(buffer.Slice(offset, 2), alphaBuffer[x]); |
|||
offset += 2; |
|||
} |
|||
|
|||
for (int x = 0; x < width; x++) |
|||
{ |
|||
WriteHalfSingleToBuffer(buffer.Slice(offset, 2), blueBuffer[x]); |
|||
offset += 2; |
|||
} |
|||
|
|||
for (int x = 0; x < width; x++) |
|||
{ |
|||
WriteHalfSingleToBuffer(buffer.Slice(offset, 2), greenBuffer[x]); |
|||
offset += 2; |
|||
} |
|||
|
|||
for (int x = 0; x < width; x++) |
|||
{ |
|||
WriteHalfSingleToBuffer(buffer.Slice(offset, 2), redBuffer[x]); |
|||
offset += 2; |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Writes a row of pixels with unsigned int pixel data to a buffer.
|
|||
/// </summary>
|
|||
/// <param name="buffer">The buffer to write to.</param>
|
|||
/// <param name="width">The width of the row in pixels.</param>
|
|||
/// <param name="alphaBuffer">The alpha channel buffer.</param>
|
|||
/// <param name="blueBuffer">The blue channel buffer.</param>
|
|||
/// <param name="greenBuffer">The green channel buffer.</param>
|
|||
/// <param name="redBuffer">The red channel buffer.</param>
|
|||
private static void WriteUnsignedIntRow(Span<byte> buffer, int width, Span<uint> alphaBuffer, Span<uint> blueBuffer, Span<uint> greenBuffer, Span<uint> redBuffer) |
|||
{ |
|||
int offset = 0; |
|||
for (int x = 0; x < width; x++) |
|||
{ |
|||
WriteUnsignedIntToBuffer(buffer.Slice(offset, 4), alphaBuffer[x]); |
|||
offset += 4; |
|||
} |
|||
|
|||
for (int x = 0; x < width; x++) |
|||
{ |
|||
WriteUnsignedIntToBuffer(buffer.Slice(offset, 4), blueBuffer[x]); |
|||
offset += 4; |
|||
} |
|||
|
|||
for (int x = 0; x < width; x++) |
|||
{ |
|||
WriteUnsignedIntToBuffer(buffer.Slice(offset, 4), greenBuffer[x]); |
|||
offset += 4; |
|||
} |
|||
|
|||
for (int x = 0; x < width; x++) |
|||
{ |
|||
WriteUnsignedIntToBuffer(buffer.Slice(offset, 4), redBuffer[x]); |
|||
offset += 4; |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Writes the row offsets to the stream.
|
|||
/// </summary>
|
|||
/// <param name="stream">The stream to write to.</param>
|
|||
/// <param name="height">The height in pixels of the image.</param>
|
|||
/// <param name="rowOffsets">The row offsets.</param>
|
|||
private void WriteRowOffsets(Stream stream, int height, ulong[] rowOffsets) |
|||
{ |
|||
for (int i = 0; i < height; i++) |
|||
{ |
|||
BinaryPrimitives.WriteUInt64LittleEndian(this.buffer, rowOffsets[i]); |
|||
stream.Write(this.buffer); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Writes the channel infos to the stream.
|
|||
/// </summary>
|
|||
/// <param name="stream">The stream to write to.</param>
|
|||
/// <param name="channels">The channels.</param>
|
|||
private void WriteChannels(Stream stream, IList<ExrChannelInfo> channels) |
|||
{ |
|||
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); |
|||
} |
|||
|
|||
// Last byte should be zero.
|
|||
stream.WriteByte(0); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Writes info about a single channel to the stream.
|
|||
/// </summary>
|
|||
/// <param name="stream">The stream to write to.</param>
|
|||
/// <param name="channelInfo">The channel information.</param>
|
|||
private void WriteChannelInfo(Stream stream, ExrChannelInfo channelInfo) |
|||
{ |
|||
WriteString(stream, channelInfo.ChannelName); |
|||
|
|||
BinaryPrimitives.WriteInt32LittleEndian(this.buffer, (int)channelInfo.PixelType); |
|||
stream.Write(this.buffer.AsSpan(0, 4)); |
|||
|
|||
stream.WriteByte(channelInfo.Linear); |
|||
|
|||
// 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)); |
|||
|
|||
BinaryPrimitives.WriteInt32LittleEndian(this.buffer, channelInfo.YSampling); |
|||
stream.Write(this.buffer.AsSpan(0, 4)); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Writes the compression type to the stream.
|
|||
/// </summary>
|
|||
/// <param name="stream">The stream to write to.</param>
|
|||
/// <param name="compression">The compression type.</param>
|
|||
private void WriteCompression(Stream stream, ExrCompression compression) |
|||
{ |
|||
this.WriteAttributeInformation(stream, ExrConstants.AttributeNames.Compression, ExrConstants.AttibuteTypes.Compression, 1); |
|||
stream.WriteByte((byte)compression); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Writes the pixel aspect ratio to the stream.
|
|||
/// </summary>
|
|||
/// <param name="stream">The stream to write to.</param>
|
|||
/// <param name="aspectRatio">The aspect ratio.</param>
|
|||
private void WritePixelAspectRatio(Stream stream, float aspectRatio) |
|||
{ |
|||
this.WriteAttributeInformation(stream, ExrConstants.AttributeNames.PixelAspectRatio, ExrConstants.AttibuteTypes.Float, 4); |
|||
this.WriteSingle(stream, aspectRatio); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Writes the line order to the stream.
|
|||
/// </summary>
|
|||
/// <param name="stream">The stream to write to.</param>
|
|||
/// <param name="lineOrder">The line order.</param>
|
|||
private void WriteLineOrder(Stream stream, ExrLineOrder lineOrder) |
|||
{ |
|||
this.WriteAttributeInformation(stream, ExrConstants.AttributeNames.LineOrder, ExrConstants.AttibuteTypes.LineOrder, 1); |
|||
stream.WriteByte((byte)lineOrder); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Writes the screen window center to the stream.
|
|||
/// </summary>
|
|||
/// <param name="stream">The stream to write to.</param>
|
|||
/// <param name="screenWindowCenter">The screen window center.</param>
|
|||
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); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Writes the screen width to the stream.
|
|||
/// </summary>
|
|||
/// <param name="stream">The stream to write to.</param>
|
|||
/// <param name="screenWindowWidth">Width of the screen window.</param>
|
|||
private void WriteScreenWindowWidth(Stream stream, float screenWindowWidth) |
|||
{ |
|||
this.WriteAttributeInformation(stream, ExrConstants.AttributeNames.ScreenWindowWidth, ExrConstants.AttibuteTypes.Float, 4); |
|||
this.WriteSingle(stream, screenWindowWidth); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Writes the data window to the stream.
|
|||
/// </summary>
|
|||
/// <param name="stream">The stream to write to.</param>
|
|||
/// <param name="dataWindow">The data window.</param>
|
|||
private void WriteDataWindow(Stream stream, ExrBox2i dataWindow) |
|||
{ |
|||
this.WriteAttributeInformation(stream, ExrConstants.AttributeNames.DataWindow, ExrConstants.AttibuteTypes.BoxInt, 16); |
|||
this.WriteBoxInteger(stream, dataWindow); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Writes the display window to the stream.
|
|||
/// </summary>
|
|||
/// <param name="stream">The stream to write to.</param>
|
|||
/// <param name="displayWindow">The display window.</param>
|
|||
private void WriteDisplayWindow(Stream stream, ExrBox2i displayWindow) |
|||
{ |
|||
this.WriteAttributeInformation(stream, ExrConstants.AttributeNames.DisplayWindow, ExrConstants.AttibuteTypes.BoxInt, 16); |
|||
this.WriteBoxInteger(stream, displayWindow); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Writes attribute information to the stream.
|
|||
/// </summary>
|
|||
/// <param name="stream">The stream to write to.</param>
|
|||
/// <param name="name">The name of the attribute.</param>
|
|||
/// <param name="type">The type of the attribute.</param>
|
|||
/// <param name="size">The size in bytes of the attribute.</param>
|
|||
private void WriteAttributeInformation(Stream stream, string name, string type, int size) |
|||
{ |
|||
// Write attribute name.
|
|||
WriteString(stream, name); |
|||
|
|||
// Write attribute type.
|
|||
WriteString(stream, type); |
|||
|
|||
// Write attribute size.
|
|||
BinaryPrimitives.WriteUInt32LittleEndian(this.buffer, (uint)size); |
|||
stream.Write(this.buffer.AsSpan(0, 4)); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Writes a string to the stream.
|
|||
/// </summary>
|
|||
/// <param name="stream">The stream to write to.</param>
|
|||
/// <param name="str">The string to write.</param>
|
|||
private static void WriteString(Stream stream, string str) |
|||
{ |
|||
foreach (char c in str) |
|||
{ |
|||
stream.WriteByte((byte)c); |
|||
} |
|||
|
|||
// Write termination byte.
|
|||
stream.WriteByte(0); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Writes box struct with xmin, xmax, ymin and y max to the stream.
|
|||
/// </summary>
|
|||
/// <param name="stream">The stream to write to.</param>
|
|||
/// <param name="box">The box to write.</param>
|
|||
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)); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Writes 32 bit float value to the stream.
|
|||
/// </summary>
|
|||
/// <param name="stream">The stream to write to.</param>
|
|||
/// <param name="value">The float value to write.</param>
|
|||
[MethodImpl(InliningOptions.ShortMethod)] |
|||
private unsafe void WriteSingle(Stream stream, float value) |
|||
{ |
|||
BinaryPrimitives.WriteInt32LittleEndian(this.buffer, *(int*)&value); |
|||
stream.Write(this.buffer.AsSpan(0, 4)); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Writes a 32 bit float value to a buffer.
|
|||
/// </summary>
|
|||
/// <param name="buffer">The buffer to write to.</param>
|
|||
/// <param name="value">The float value to write.</param>
|
|||
[MethodImpl(InliningOptions.ShortMethod)] |
|||
private static unsafe void WriteSingleToBuffer(Span<byte> buffer, float value) => BinaryPrimitives.WriteInt32LittleEndian(buffer, *(int*)&value); |
|||
|
|||
/// <summary>
|
|||
/// Writes a 16 bit float value to a buffer.
|
|||
/// </summary>
|
|||
/// <param name="buffer">The buffer to write to.</param>
|
|||
/// <param name="value">The float value to write.</param>
|
|||
[MethodImpl(InliningOptions.ShortMethod)] |
|||
private static void WriteHalfSingleToBuffer(Span<byte> buffer, float value) |
|||
{ |
|||
ushort valueAsShort = HalfTypeHelper.Pack(value); |
|||
BinaryPrimitives.WriteUInt16LittleEndian(buffer, valueAsShort); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Writes one unsigned int to a buffer.
|
|||
/// </summary>
|
|||
/// <param name="buffer">The buffer to write to.</param>
|
|||
/// <param name="value">The uint value to write.</param>
|
|||
[MethodImpl(InliningOptions.ShortMethod)] |
|||
private static void WriteUnsignedIntToBuffer(Span<byte> buffer, uint value) => BinaryPrimitives.WriteUInt32LittleEndian(buffer, value); |
|||
} |
|||
@ -0,0 +1,34 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Exr; |
|||
|
|||
/// <summary>
|
|||
/// Registers the image encoders, decoders and mime type detectors for the OpenExr format.
|
|||
/// </summary>
|
|||
public sealed class ExrFormat : IImageFormat<ExrMetadata> |
|||
{ |
|||
private ExrFormat() |
|||
{ |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the current instance.
|
|||
/// </summary>
|
|||
public static ExrFormat Instance { get; } = new(); |
|||
|
|||
/// <inheritdoc/>
|
|||
public string Name => "EXR"; |
|||
|
|||
/// <inheritdoc/>
|
|||
public string DefaultMimeType => "image/x-exr"; |
|||
|
|||
/// <inheritdoc/>
|
|||
public IEnumerable<string> MimeTypes => ExrConstants.MimeTypes; |
|||
|
|||
/// <inheritdoc/>
|
|||
public IEnumerable<string> FileExtensions => ExrConstants.FileExtensions; |
|||
|
|||
/// <inheritdoc/>
|
|||
public ExrMetadata CreateDefaultFormatMetadata() => new(); |
|||
} |
|||
@ -0,0 +1,108 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using SixLabors.ImageSharp.Formats.Exr.Constants; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Exr; |
|||
|
|||
/// <summary>
|
|||
/// The header of an EXR image.
|
|||
/// <see href="https://openexr.com/en/latest/TechnicalIntroduction.html#header"/>
|
|||
/// </summary>
|
|||
internal class ExrHeaderAttributes |
|||
{ |
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="ExrHeaderAttributes" /> class.
|
|||
/// </summary>
|
|||
/// <param name="channels">The image channels.</param>
|
|||
/// <param name="compression">The compression used.</param>
|
|||
/// <param name="dataWindow">The data window.</param>
|
|||
/// <param name="displayWindow">The display window.</param>
|
|||
/// <param name="lineOrder">The line order.</param>
|
|||
/// <param name="aspectRatio">The aspect ratio.</param>
|
|||
/// <param name="screenWindowWidth">Width of the screen window.</param>
|
|||
/// <param name="screenWindowCenter">The screen window center.</param>
|
|||
/// <param name="tileXSize">Size of the tile in x dimension.</param>
|
|||
/// <param name="tileYSize">Size of the tile in y dimension.</param>
|
|||
/// <param name="chunkCount">The chunk count.</param>
|
|||
public ExrHeaderAttributes( |
|||
IList<ExrChannelInfo> channels, |
|||
ExrCompression compression, |
|||
ExrBox2i dataWindow, |
|||
ExrBox2i displayWindow, |
|||
ExrLineOrder lineOrder, |
|||
float aspectRatio, |
|||
float screenWindowWidth, |
|||
PointF screenWindowCenter, |
|||
uint? tileXSize = null, |
|||
uint? tileYSize = null, |
|||
int? chunkCount = null) |
|||
{ |
|||
this.Channels = channels; |
|||
this.Compression = compression; |
|||
this.DataWindow = dataWindow; |
|||
this.DisplayWindow = displayWindow; |
|||
this.LineOrder = lineOrder; |
|||
this.AspectRatio = aspectRatio; |
|||
this.ScreenWindowWidth = screenWindowWidth; |
|||
this.ScreenWindowCenter = screenWindowCenter; |
|||
this.TileXSize = tileXSize; |
|||
this.TileYSize = tileYSize; |
|||
this.ChunkCount = chunkCount; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets a description of the image channels stored in the file.
|
|||
/// </summary>
|
|||
public IList<ExrChannelInfo> Channels { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the compression method applied to the pixel data of all channels in the file.
|
|||
/// </summary>
|
|||
public ExrCompression Compression { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the image’s data window.
|
|||
/// </summary>
|
|||
public ExrBox2i DataWindow { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the image’s display window.
|
|||
/// </summary>
|
|||
public ExrBox2i DisplayWindow { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets in what order the scan lines in the file are stored in the file (increasing Y, decreasing Y, or, for tiled images, also random Y).
|
|||
/// </summary>
|
|||
public ExrLineOrder LineOrder { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the aspect ratio of the image.
|
|||
/// </summary>
|
|||
public float AspectRatio { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the screen width.
|
|||
/// </summary>
|
|||
public float ScreenWindowWidth { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the screen window center.
|
|||
/// </summary>
|
|||
public PointF ScreenWindowCenter { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the number of horizontal tiles.
|
|||
/// </summary>
|
|||
public uint? TileXSize { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the number of vertical tiles.
|
|||
/// </summary>
|
|||
public uint? TileYSize { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the chunk count. Indicates the number of chunks in this part. Required if the multipart bit (12) is set.
|
|||
/// </summary>
|
|||
public int? ChunkCount { get; set; } |
|||
} |
|||
@ -0,0 +1,34 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using System.Buffers.Binary; |
|||
using System.Diagnostics.CodeAnalysis; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Exr; |
|||
|
|||
/// <summary>
|
|||
/// Detects OpenExr file headers.
|
|||
/// </summary>
|
|||
public sealed class ExrImageFormatDetector : IImageFormatDetector |
|||
{ |
|||
/// <inheritdoc/>
|
|||
public int HeaderSize => 4; |
|||
|
|||
private bool IsSupportedFileFormat(ReadOnlySpan<byte> header) |
|||
{ |
|||
if (header.Length >= this.HeaderSize) |
|||
{ |
|||
int fileTypeMarker = BinaryPrimitives.ReadInt32LittleEndian(header); |
|||
return fileTypeMarker == ExrConstants.MagickBytes; |
|||
} |
|||
|
|||
return false; |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public bool TryDetectFormat(ReadOnlySpan<byte> header, [NotNullWhen(true)] out IImageFormat? format) |
|||
{ |
|||
format = this.IsSupportedFileFormat(header) ? ExrFormat.Instance : null; |
|||
return format != null; |
|||
} |
|||
} |
|||
@ -0,0 +1,156 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using System.Numerics; |
|||
using SixLabors.ImageSharp.Formats.Exr.Constants; |
|||
using SixLabors.ImageSharp.PixelFormats; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Exr; |
|||
|
|||
/// <summary>
|
|||
/// Provides OpenExr specific metadata information for the image.
|
|||
/// </summary>
|
|||
public class ExrMetadata : IFormatMetadata<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>
|
|||
/// Gets or sets the pixel format.
|
|||
/// </summary>
|
|||
public ExrPixelType PixelType { get; set; } = ExrPixelType.Half; |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the image data type, either RGB, RGBA or gray.
|
|||
/// </summary>
|
|||
public ExrImageDataType ImageDataType { get; set; } = ExrImageDataType.Unknown; |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the compression method.
|
|||
/// </summary>
|
|||
public ExrCompression Compression { get; set; } = ExrCompression.None; |
|||
|
|||
/// <inheritdoc/>
|
|||
public PixelTypeInfo GetPixelTypeInfo() |
|||
{ |
|||
bool hasAlpha = this.ImageDataType is ExrImageDataType.Rgba; |
|||
|
|||
int bitsPerComponent = 32; |
|||
int bitsPerPixel = hasAlpha ? bitsPerComponent * 4 : bitsPerComponent * 3; |
|||
if (this.PixelType == ExrPixelType.Half) |
|||
{ |
|||
bitsPerComponent = 16; |
|||
bitsPerPixel = hasAlpha ? bitsPerComponent * 4 : bitsPerComponent * 3; |
|||
} |
|||
|
|||
PixelAlphaRepresentation alpha = hasAlpha ? PixelAlphaRepresentation.Unassociated : PixelAlphaRepresentation.None; |
|||
PixelColorType color = PixelColorType.RGB; |
|||
|
|||
int componentsCount = 0; |
|||
int[] precision = []; |
|||
switch (this.ImageDataType) |
|||
{ |
|||
case ExrImageDataType.Rgb: |
|||
color = PixelColorType.RGB; |
|||
componentsCount = 3; |
|||
precision = new int[componentsCount]; |
|||
precision[0] = bitsPerComponent; |
|||
precision[1] = bitsPerComponent; |
|||
precision[2] = bitsPerComponent; |
|||
break; |
|||
case ExrImageDataType.Rgba: |
|||
color = PixelColorType.RGB | PixelColorType.Alpha; |
|||
componentsCount = 4; |
|||
precision = new int[componentsCount]; |
|||
precision[0] = bitsPerComponent; |
|||
precision[1] = bitsPerComponent; |
|||
precision[2] = bitsPerComponent; |
|||
precision[3] = bitsPerComponent; |
|||
break; |
|||
case ExrImageDataType.Gray: |
|||
color = PixelColorType.Luminance; |
|||
componentsCount = 1; |
|||
precision = new int[componentsCount]; |
|||
precision[0] = bitsPerComponent; |
|||
break; |
|||
} |
|||
|
|||
PixelComponentInfo info = PixelComponentInfo.Create(componentsCount, bitsPerPixel, precision); |
|||
return new PixelTypeInfo(bitsPerPixel) |
|||
{ |
|||
AlphaRepresentation = alpha, |
|||
ComponentInfo = info, |
|||
ColorType = color |
|||
}; |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public FormatConnectingMetadata ToFormatConnectingMetadata() |
|||
{ |
|||
EncodingType type = this.Compression is ExrCompression.B44 or ExrCompression.B44A or ExrCompression.Pxr24 |
|||
? EncodingType.Lossy |
|||
: EncodingType.Lossless; |
|||
|
|||
return new() |
|||
{ |
|||
EncodingType = type, |
|||
PixelTypeInfo = this.GetPixelTypeInfo() |
|||
}; |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public static ExrMetadata FromFormatConnectingMetadata(FormatConnectingMetadata metadata) |
|||
{ |
|||
PixelTypeInfo pixelTypeInfo = metadata.PixelTypeInfo; |
|||
PixelComponentInfo? info = pixelTypeInfo.ComponentInfo; |
|||
PixelColorType colorType = pixelTypeInfo.ColorType; |
|||
|
|||
int bitsPerComponent = info?.GetMaximumComponentPrecision() |
|||
?? (pixelTypeInfo.BitsPerPixel <= 16 ? 16 : 32); |
|||
|
|||
int componentCount = info?.ComponentCount ?? 0; |
|||
ExrImageDataType imageDataType = colorType switch |
|||
{ |
|||
PixelColorType.Luminance => ExrImageDataType.Gray, |
|||
PixelColorType.RGB or PixelColorType.BGR => ExrImageDataType.Rgb, |
|||
PixelColorType.RGB | PixelColorType.Alpha |
|||
or PixelColorType.BGR | PixelColorType.Alpha |
|||
or PixelColorType.Luminance | PixelColorType.Alpha => ExrImageDataType.Rgba, |
|||
_ => componentCount switch |
|||
{ |
|||
>= 4 => ExrImageDataType.Rgba, |
|||
>= 3 => ExrImageDataType.Rgb, |
|||
1 => ExrImageDataType.Gray, |
|||
_ => ExrImageDataType.Unknown, |
|||
} |
|||
}; |
|||
|
|||
return new() |
|||
{ |
|||
PixelType = bitsPerComponent <= 16 ? ExrPixelType.Half : ExrPixelType.Float, |
|||
ImageDataType = imageDataType, |
|||
}; |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
ExrMetadata IDeepCloneable<ExrMetadata>.DeepClone() => new(this); |
|||
|
|||
/// <inheritdoc/>
|
|||
public IDeepCloneable DeepClone() => new ExrMetadata(this); |
|||
|
|||
/// <inheritdoc/>
|
|||
public void AfterImageApply<TPixel>(Image<TPixel> destination, Matrix4x4 matrix) |
|||
where TPixel : unmanaged, IPixel<TPixel> |
|||
{ |
|||
} |
|||
} |
|||
@ -0,0 +1,33 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using System.Diagnostics.CodeAnalysis; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Exr; |
|||
|
|||
/// <summary>
|
|||
/// Cold path optimizations for throwing exr format based exceptions.
|
|||
/// </summary>
|
|||
internal static class ExrThrowHelper |
|||
{ |
|||
[DoesNotReturn] |
|||
public static Exception NotSupportedDecompressor(string compressionType) => throw new NotSupportedException($"Not supported decoder compression method: {compressionType}"); |
|||
|
|||
[DoesNotReturn] |
|||
public static void ThrowInvalidImageContentException(string errorMessage) => throw new InvalidImageContentException(errorMessage); |
|||
|
|||
[DoesNotReturn] |
|||
public static void ThrowNotSupportedVersion() => throw new NotSupportedException("Unsupported EXR version"); |
|||
|
|||
[DoesNotReturn] |
|||
public static void ThrowNotSupported(string msg) => throw new NotSupportedException(msg); |
|||
|
|||
[DoesNotReturn] |
|||
public static void ThrowInvalidImageHeader() => throw new InvalidImageContentException("Invalid EXR image header"); |
|||
|
|||
[DoesNotReturn] |
|||
public static void ThrowInvalidImageHeader(string msg) => throw new InvalidImageContentException(msg); |
|||
|
|||
[DoesNotReturn] |
|||
public static Exception NotSupportedCompressor(string compressionType) => throw new NotSupportedException($"Not supported encoder compression method: {compressionType}"); |
|||
} |
|||
@ -0,0 +1,52 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using SixLabors.ImageSharp.Formats.Exr.Constants; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Exr; |
|||
|
|||
internal static class ExrUtils |
|||
{ |
|||
/// <summary>
|
|||
/// Calcualtes the required bytes for a pixel row.
|
|||
/// </summary>
|
|||
/// <param name="channels">The image channels array.</param>
|
|||
/// <param name="width">The width in pixels of a row.</param>
|
|||
/// <returns>The number of bytes per row.</returns>
|
|||
public static uint CalculateBytesPerRow(IList<ExrChannelInfo> channels, uint width) |
|||
{ |
|||
uint bytesPerRow = 0; |
|||
foreach (ExrChannelInfo channelInfo in channels) |
|||
{ |
|||
if (channelInfo.ChannelName.Equals("A", StringComparison.Ordinal) |
|||
|| channelInfo.ChannelName.Equals("R", StringComparison.Ordinal) |
|||
|| channelInfo.ChannelName.Equals("G", StringComparison.Ordinal) |
|||
|| channelInfo.ChannelName.Equals("B", StringComparison.Ordinal) |
|||
|| channelInfo.ChannelName.Equals("Y", StringComparison.Ordinal)) |
|||
{ |
|||
if (channelInfo.PixelType == ExrPixelType.Half) |
|||
{ |
|||
bytesPerRow += 2 * width; |
|||
} |
|||
else |
|||
{ |
|||
bytesPerRow += 4 * width; |
|||
} |
|||
} |
|||
} |
|||
|
|||
return bytesPerRow; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Determines how many pixel rows there are in a block. This varies depending on the compression used.
|
|||
/// </summary>
|
|||
/// <param name="compression">The compression used.</param>
|
|||
/// <returns>Pixel rows in a block.</returns>
|
|||
public static uint RowsPerBlock(ExrCompression compression) => compression switch |
|||
{ |
|||
ExrCompression.Zip or ExrCompression.Pxr24 => 16, |
|||
ExrCompression.B44 or ExrCompression.B44A or ExrCompression.Piz => 32, |
|||
_ => 1, |
|||
}; |
|||
} |
|||
@ -0,0 +1,4 @@ |
|||
### Some useful links for documentation about the OpenEXR format: |
|||
|
|||
- [Technical Introduction](https://openexr.readthedocs.io/en/latest/TechnicalIntroduction.html) |
|||
- [OpenExr file layout](https://openexr.readthedocs.io/en/latest/OpenEXRFileLayout.html) |
|||
@ -0,0 +1,203 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using System.Numerics; |
|||
using System.Runtime.CompilerServices; |
|||
using System.Runtime.InteropServices; |
|||
|
|||
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>, IEquatable<Rgb96> |
|||
{ |
|||
private const float InvMax = 1.0f / uint.MaxValue; |
|||
|
|||
// Use double here because at this magnitude a float cannot represent all 32-bit
|
|||
// integer values exactly. A float only has 24 bits of precision, so around
|
|||
// uint.MaxValue it can only represent multiples of 256 and will round
|
|||
// 4294967295 up to 4294967296. Double has 53 bits of precision and can
|
|||
// represent all uint values exactly, avoiding precision loss before scaling.
|
|||
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/>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public readonly Vector4 ToScaledVector4() => this.ToVector4(); |
|||
|
|||
/// <inheritdoc />
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public readonly Vector4 ToVector4() => new( |
|||
this.R * InvMax, |
|||
this.G * InvMax, |
|||
this.B * InvMax, |
|||
1.0f); |
|||
|
|||
/// <inheritdoc/>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static PixelOperations<Rgb96> CreatePixelOperations() => new(); |
|||
|
|||
/// <inheritdoc/>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static Rgb96 FromScaledVector4(Vector4 source) => FromVector4(source); |
|||
|
|||
/// <inheritdoc/>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static Rgb96 FromVector4(Vector4 source) |
|||
{ |
|||
source = Numerics.Clamp(source, Vector4.Zero, Vector4.One); |
|||
return new Rgb96( |
|||
(uint)Math.Round(source.X * Max), |
|||
(uint)Math.Round(source.Y * Max), |
|||
(uint)Math.Round(source.Z * Max)); |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static Rgb96 FromAbgr32(Abgr32 source) => new(ColorNumerics.From8BitTo32Bit(source.R), ColorNumerics.From8BitTo32Bit(source.G), ColorNumerics.From8BitTo32Bit(source.B)); |
|||
|
|||
/// <inheritdoc/>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static Rgb96 FromArgb32(Argb32 source) => new(ColorNumerics.From8BitTo32Bit(source.R), ColorNumerics.From8BitTo32Bit(source.G), ColorNumerics.From8BitTo32Bit(source.B)); |
|||
|
|||
/// <inheritdoc/>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static Rgb96 FromBgra5551(Bgra5551 source) => FromScaledVector4(source.ToScaledVector4()); |
|||
|
|||
/// <inheritdoc/>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static Rgb96 FromBgr24(Bgr24 source) => new(ColorNumerics.From8BitTo32Bit(source.R), ColorNumerics.From8BitTo32Bit(source.G), ColorNumerics.From8BitTo32Bit(source.B)); |
|||
|
|||
/// <inheritdoc/>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static Rgb96 FromBgra32(Bgra32 source) => new(ColorNumerics.From8BitTo32Bit(source.R), ColorNumerics.From8BitTo32Bit(source.G), ColorNumerics.From8BitTo32Bit(source.B)); |
|||
|
|||
/// <inheritdoc/>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static Rgb96 FromL8(L8 source) |
|||
{ |
|||
uint rgb = ColorNumerics.From8BitTo32Bit(source.PackedValue); |
|||
return new Rgb96(rgb, rgb, rgb); |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static Rgb96 FromL16(L16 source) |
|||
{ |
|||
uint rgb = ColorNumerics.From16BitTo32Bit(source.PackedValue); |
|||
return new(rgb, rgb, rgb); |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static Rgb96 FromLa16(La16 source) |
|||
{ |
|||
uint rgb = ColorNumerics.From8BitTo32Bit((byte)source.PackedValue); |
|||
return new(rgb, rgb, rgb); |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static Rgb96 FromLa32(La32 source) |
|||
{ |
|||
uint rgb = ColorNumerics.From16BitTo32Bit(source.L); |
|||
return new(rgb, rgb, rgb); |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static Rgb96 FromRgb24(Rgb24 source) => new(ColorNumerics.From8BitTo32Bit(source.R), ColorNumerics.From8BitTo32Bit(source.G), ColorNumerics.From8BitTo32Bit(source.B)); |
|||
|
|||
/// <inheritdoc/>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static Rgb96 FromRgba32(Rgba32 source) => new(ColorNumerics.From8BitTo32Bit(source.R), ColorNumerics.From8BitTo32Bit(source.G), ColorNumerics.From8BitTo32Bit(source.B)); |
|||
|
|||
/// <inheritdoc/>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static Rgb96 FromRgb48(Rgb48 source) => new(ColorNumerics.From16BitTo32Bit(source.R), ColorNumerics.From16BitTo32Bit(source.G), ColorNumerics.From16BitTo32Bit(source.B)); |
|||
|
|||
/// <inheritdoc/>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static Rgb96 FromRgba64(Rgba64 source) => new(ColorNumerics.From16BitTo32Bit(source.R), ColorNumerics.From16BitTo32Bit(source.G), ColorNumerics.From16BitTo32Bit(source.B)); |
|||
|
|||
/// <inheritdoc/>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static PixelTypeInfo GetPixelTypeInfo() => PixelTypeInfo.Create<Rgb96>( |
|||
PixelComponentInfo.Create<Rgb96>(3, 32, 32, 32), |
|||
PixelColorType.RGB, |
|||
PixelAlphaRepresentation.None); |
|||
|
|||
/// <inheritdoc/>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public readonly Rgba32 ToRgba32() => Rgba32.FromRgb96(this); |
|||
|
|||
/// <inheritdoc />
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public override readonly int GetHashCode() => HashCode.Combine(this.R, this.G, this.B); |
|||
|
|||
/// <inheritdoc />
|
|||
public override readonly string ToString() => FormattableString.Invariant($"Rgb96({this.R}, {this.G}, {this.B})"); |
|||
|
|||
/// <inheritdoc/>
|
|||
public override readonly bool Equals(object? obj) => obj is Rgb96 rgb && rgb.R == this.R && rgb.G == this.G && rgb.B == this.B; |
|||
|
|||
/// <inheritdoc />
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public readonly bool Equals(Rgb96 other) => this.R.Equals(other.R) && this.G.Equals(other.G) && this.B.Equals(other.B); |
|||
} |
|||
@ -0,0 +1,200 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using System.Numerics; |
|||
using System.Runtime.CompilerServices; |
|||
using System.Runtime.InteropServices; |
|||
|
|||
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>, IEquatable<Rgba128> |
|||
{ |
|||
private const float InvMax = 1.0f / uint.MaxValue; |
|||
|
|||
// Use double here because at this magnitude a float cannot represent all 32-bit
|
|||
// integer values exactly. A float only has 24 bits of precision, so around
|
|||
// uint.MaxValue it can only represent multiples of 256 and will round
|
|||
// 4294967295 up to 4294967296. Double has 53 bits of precision and can
|
|||
// represent all uint values exactly, avoiding precision loss before scaling.
|
|||
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 />
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public readonly Vector4 ToVector4() => new( |
|||
this.R * InvMax, |
|||
this.G * InvMax, |
|||
this.B * InvMax, |
|||
this.A * InvMax); |
|||
|
|||
/// <inheritdoc/>
|
|||
public static PixelOperations<Rgba128> CreatePixelOperations() => new(); |
|||
|
|||
/// <inheritdoc/>
|
|||
public static Rgba128 FromScaledVector4(Vector4 source) => FromVector4(source); |
|||
|
|||
/// <inheritdoc/>
|
|||
public static Rgba128 FromVector4(Vector4 source) |
|||
{ |
|||
source = Numerics.Clamp(source, Vector4.Zero, Vector4.One); |
|||
return new Rgba128( |
|||
(uint)Math.Round(source.X * Max), |
|||
(uint)Math.Round(source.Y * Max), |
|||
(uint)Math.Round(source.Z * Max), |
|||
(uint)Math.Round(source.W * Max)); |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public static Rgba128 FromAbgr32(Abgr32 source) |
|||
=> new(ColorNumerics.From8BitTo32Bit(source.R), ColorNumerics.From8BitTo32Bit(source.G), ColorNumerics.From8BitTo32Bit(source.B), ColorNumerics.From8BitTo32Bit(source.A)); |
|||
|
|||
/// <inheritdoc/>
|
|||
public static Rgba128 FromArgb32(Argb32 source) |
|||
=> new(ColorNumerics.From8BitTo32Bit(source.R), ColorNumerics.From8BitTo32Bit(source.G), ColorNumerics.From8BitTo32Bit(source.B), ColorNumerics.From8BitTo32Bit(source.A)); |
|||
|
|||
/// <inheritdoc/>
|
|||
public static Rgba128 FromBgra5551(Bgra5551 source) => FromScaledVector4(source.ToScaledVector4()); |
|||
|
|||
/// <inheritdoc/>
|
|||
public static Rgba128 FromBgr24(Bgr24 source) |
|||
=> new(ColorNumerics.From8BitTo32Bit(source.R), ColorNumerics.From8BitTo32Bit(source.G), ColorNumerics.From8BitTo32Bit(source.B), uint.MaxValue); |
|||
|
|||
/// <inheritdoc/>
|
|||
public static Rgba128 FromBgra32(Bgra32 source) |
|||
=> new(ColorNumerics.From8BitTo32Bit(source.R), ColorNumerics.From8BitTo32Bit(source.G), ColorNumerics.From8BitTo32Bit(source.B), ColorNumerics.From8BitTo32Bit(source.A)); |
|||
|
|||
/// <inheritdoc/>
|
|||
public static Rgba128 FromL8(L8 source) |
|||
{ |
|||
uint rgb = ColorNumerics.From8BitTo32Bit(source.PackedValue); |
|||
return new Rgba128(rgb, rgb, rgb, rgb); |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public static Rgba128 FromL16(L16 source) |
|||
{ |
|||
uint rgb = ColorNumerics.From16BitTo32Bit(source.PackedValue); |
|||
return new(rgb, rgb, rgb, rgb); |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public static Rgba128 FromLa16(La16 source) |
|||
{ |
|||
uint rgb = ColorNumerics.From8BitTo32Bit((byte)source.PackedValue); |
|||
return new(rgb, rgb, rgb, rgb); |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public static Rgba128 FromLa32(La32 source) |
|||
{ |
|||
uint rgb = ColorNumerics.From16BitTo32Bit(source.L); |
|||
return new(rgb, rgb, rgb, rgb); |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public static Rgba128 FromRgb24(Rgb24 source) |
|||
=> new(ColorNumerics.From8BitTo32Bit(source.R), ColorNumerics.From8BitTo32Bit(source.G), ColorNumerics.From8BitTo32Bit(source.B), uint.MaxValue); |
|||
|
|||
/// <inheritdoc/>
|
|||
public static Rgba128 FromRgba32(Rgba32 source) |
|||
=> new(ColorNumerics.From8BitTo32Bit(source.R), ColorNumerics.From8BitTo32Bit(source.G), ColorNumerics.From8BitTo32Bit(source.B), ColorNumerics.From8BitTo32Bit(source.A)); |
|||
|
|||
/// <inheritdoc/>
|
|||
public static Rgba128 FromRgb48(Rgb48 source) |
|||
=> new(ColorNumerics.From16BitTo32Bit(source.R), ColorNumerics.From16BitTo32Bit(source.G), ColorNumerics.From16BitTo32Bit(source.B), uint.MaxValue); |
|||
|
|||
/// <inheritdoc/>
|
|||
public static Rgba128 FromRgba64(Rgba64 source) |
|||
=> new(ColorNumerics.From16BitTo32Bit(source.R), ColorNumerics.From16BitTo32Bit(source.G), ColorNumerics.From16BitTo32Bit(source.B), ColorNumerics.From16BitTo32Bit(source.A)); |
|||
|
|||
/// <inheritdoc/>
|
|||
public static PixelTypeInfo GetPixelTypeInfo() => PixelTypeInfo.Create<Rgba128>( |
|||
PixelComponentInfo.Create<Rgba128>(4, 32, 32, 32, 32), |
|||
PixelColorType.RGB | PixelColorType.Alpha, |
|||
PixelAlphaRepresentation.Unassociated); |
|||
|
|||
/// <inheritdoc/>
|
|||
public readonly Rgba32 ToRgba32() => Rgba32.FromRgba128(this); |
|||
|
|||
/// <inheritdoc/>
|
|||
public readonly Vector4 ToScaledVector4() => this.ToVector4(); |
|||
|
|||
/// <inheritdoc />
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public override readonly int GetHashCode() => HashCode.Combine(this.R, this.G, this.B, this.A); |
|||
|
|||
/// <inheritdoc />
|
|||
public override readonly string ToString() => FormattableString.Invariant($"Rgba128({this.R}, {this.G}, {this.B}, {this.A})"); |
|||
|
|||
/// <inheritdoc/>
|
|||
public override readonly bool Equals(object? obj) => obj is Rgba128 rgb && rgb.R == this.R && rgb.G == this.G && rgb.B == this.B && rgb.A == this.A; |
|||
|
|||
/// <inheritdoc />
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public readonly bool Equals(Rgba128 other) => this.R.Equals(other.R) && this.G.Equals(other.G) && this.B.Equals(other.B) && this.A.Equals(other.A); |
|||
} |
|||
@ -0,0 +1,68 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using BenchmarkDotNet.Attributes; |
|||
|
|||
using ImageMagick; |
|||
using SixLabors.ImageSharp.Formats.Exr; |
|||
using SixLabors.ImageSharp.PixelFormats; |
|||
using SixLabors.ImageSharp.Tests; |
|||
|
|||
namespace SixLabors.ImageSharp.Benchmarks.Codecs; |
|||
|
|||
[MarkdownExporter] |
|||
[HtmlExporter] |
|||
[Config(typeof(Config.Short))] |
|||
public class DecodeExr |
|||
{ |
|||
private Configuration configuration; |
|||
|
|||
private byte[] imageBytes; |
|||
|
|||
private string TestImageFullPath => Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, this.TestImage); |
|||
|
|||
[Params(TestImages.Exr.Benchmark)] |
|||
public string TestImage { get; set; } |
|||
|
|||
[GlobalSetup] |
|||
public void ReadImages() |
|||
{ |
|||
this.configuration = Configuration.CreateDefaultInstance(); |
|||
new ExrConfigurationModule().Configure(this.configuration); |
|||
|
|||
this.imageBytes ??= File.ReadAllBytes(this.TestImageFullPath); |
|||
} |
|||
|
|||
[Benchmark(Description = "Magick Exr")] |
|||
public uint ExrImageMagick() |
|||
{ |
|||
MagickReadSettings settings = new() { Format = MagickFormat.Exr }; |
|||
using MemoryStream memoryStream = new(this.imageBytes); |
|||
using MagickImage image = new(memoryStream, settings); |
|||
return image.Width; |
|||
} |
|||
|
|||
[Benchmark(Description = "ImageSharp Exr")] |
|||
public int ExrImageSharp() |
|||
{ |
|||
using MemoryStream memoryStream = new(this.imageBytes); |
|||
using Image<Rgba32> image = Image.Load<Rgba32>(memoryStream); |
|||
return image.Height; |
|||
} |
|||
|
|||
/* Results 27.03.2026 |
|||
BenchmarkDotNet v0.15.8, Windows 11 (10.0.26200.8037/25H2/2025Update/HudsonValley2) |
|||
Intel Core i7-14700T 1.30GHz, 1 CPU, 28 logical and 20 physical cores |
|||
.NET SDK 10.0.201 |
|||
[Host] : .NET 8.0.25 (8.0.25, 8.0.2526.11203), X64 RyuJIT x86-64-v3 |
|||
Job-VDWIGO : .NET 8.0.25 (8.0.25, 8.0.2526.11203), X64 RyuJIT x86-64-v3 |
|||
|
|||
Runtime=.NET 8.0 Arguments=/p:DebugType=portable IterationCount=3 |
|||
LaunchCount=1 WarmupCount=3 |
|||
|
|||
| Method | TestImage | Mean | Error | StdDev | Allocated | |
|||
|----------------- |----------------------------- |---------:|---------:|---------:|----------:| |
|||
| 'Magick Exr' | Exr/Calliphora_benchmark.exr | 20.37 ms | 0.790 ms | 0.043 ms | 12.98 KB | |
|||
| 'ImageSharp Exr' | Exr/Calliphora_benchmark.exr | 45.68 ms | 4.999 ms | 0.274 ms | 34.09 KB | |
|||
*/ |
|||
} |
|||
@ -0,0 +1,133 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using SixLabors.ImageSharp.Formats.Exr; |
|||
using SixLabors.ImageSharp.Formats.Exr.Constants; |
|||
using SixLabors.ImageSharp.PixelFormats; |
|||
using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; |
|||
using SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs; |
|||
|
|||
namespace SixLabors.ImageSharp.Tests.Formats.Exr; |
|||
|
|||
[Trait("Format", "Exr")] |
|||
[ValidateDisposedMemoryAllocations] |
|||
public class ExrDecoderTests |
|||
{ |
|||
private static MagickReferenceDecoder ReferenceDecoder => MagickReferenceDecoder.Exr; |
|||
|
|||
[Theory] |
|||
[WithFile(TestImages.Exr.Uncompressed, PixelTypes.Rgba32)] |
|||
public void ExrDecoder_CanDecode_Uncompressed_Rgb_ExrPixelType_Half<TPixel>(TestImageProvider<TPixel> provider) |
|||
where TPixel : unmanaged, IPixel<TPixel> |
|||
{ |
|||
using Image<TPixel> image = provider.GetImage(ExrDecoder.Instance); |
|||
ExrMetadata exrMetaData = image.Metadata.GetExrMetadata(); |
|||
image.DebugSave(provider); |
|||
image.CompareToOriginal(provider, ImageComparer.Exact, ReferenceDecoder); |
|||
Assert.Equal(ExrPixelType.Half, exrMetaData.PixelType); |
|||
} |
|||
|
|||
[Theory] |
|||
[WithFile(TestImages.Exr.UncompressedFloatRgb, PixelTypes.Rgba32)] |
|||
public void ExrDecoder_CanDecode_Uncompressed_Rgb_ExrPixelType_Float<TPixel>(TestImageProvider<TPixel> provider) |
|||
where TPixel : unmanaged, IPixel<TPixel> |
|||
{ |
|||
using Image<TPixel> image = provider.GetImage(ExrDecoder.Instance); |
|||
ExrMetadata exrMetaData = image.Metadata.GetExrMetadata(); |
|||
image.DebugSave(provider); |
|||
|
|||
// There is a 0,0059% difference to the Reference decoder.
|
|||
image.CompareToOriginal(provider, ImageComparer.Tolerant(0.0005f), ReferenceDecoder); |
|||
Assert.Equal(ExrPixelType.Float, exrMetaData.PixelType); |
|||
} |
|||
|
|||
[Theory] |
|||
[WithFile(TestImages.Exr.UncompressedUintRgb, PixelTypes.Rgba32)] |
|||
public void ExrDecoder_CanDecode_Uncompressed_Rgb_ExrPixelType_Uint<TPixel>(TestImageProvider<TPixel> provider) |
|||
where TPixel : unmanaged, IPixel<TPixel> |
|||
{ |
|||
using Image<TPixel> image = provider.GetImage(ExrDecoder.Instance); |
|||
ExrMetadata exrMetaData = image.Metadata.GetExrMetadata(); |
|||
image.DebugSave(provider); |
|||
|
|||
// Compare to referene output, since the reference decoder does not support this pixel type.
|
|||
image.CompareToReferenceOutput(provider); |
|||
Assert.Equal(ExrPixelType.UnsignedInt, exrMetaData.PixelType); |
|||
} |
|||
|
|||
[Theory] |
|||
[WithFile(TestImages.Exr.UintRgba, PixelTypes.Rgba32)] |
|||
public void ExrDecoder_CanDecode_Uncompressed_Rgba_ExrPixelType_Uint<TPixel>(TestImageProvider<TPixel> provider) |
|||
where TPixel : unmanaged, IPixel<TPixel> |
|||
{ |
|||
using Image<TPixel> image = provider.GetImage(ExrDecoder.Instance); |
|||
ExrMetadata exrMetaData = image.Metadata.GetExrMetadata(); |
|||
image.DebugSave(provider); |
|||
|
|||
// Compare to referene output, since the reference decoder does not support this pixel type.
|
|||
image.CompareToReferenceOutput(provider); |
|||
Assert.Equal(ExrPixelType.UnsignedInt, exrMetaData.PixelType); |
|||
} |
|||
|
|||
[Theory] |
|||
[WithFile(TestImages.Exr.Rgb, PixelTypes.Rgba32)] |
|||
public void ExrDecoder_CanDecode_Rgb<TPixel>(TestImageProvider<TPixel> provider) |
|||
where TPixel : unmanaged, IPixel<TPixel> |
|||
{ |
|||
using Image<TPixel> image = provider.GetImage(ExrDecoder.Instance); |
|||
image.DebugSave(provider); |
|||
image.CompareToOriginal(provider, ImageComparer.Exact, ReferenceDecoder); |
|||
} |
|||
|
|||
[Theory] |
|||
[WithFile(TestImages.Exr.Gray, PixelTypes.Rgba32)] |
|||
public void ExrDecoder_CanDecode_Gray<TPixel>(TestImageProvider<TPixel> provider) |
|||
where TPixel : unmanaged, IPixel<TPixel> |
|||
{ |
|||
using Image<TPixel> image = provider.GetImage(ExrDecoder.Instance); |
|||
image.DebugSave(provider); |
|||
image.CompareToOriginal(provider, ImageComparer.Exact, ReferenceDecoder); |
|||
} |
|||
|
|||
[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.Instance); |
|||
image.DebugSave(provider); |
|||
image.CompareToOriginal(provider, ImageComparer.Exact, ReferenceDecoder); |
|||
} |
|||
|
|||
[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.Instance); |
|||
image.DebugSave(provider); |
|||
image.CompareToOriginal(provider, ImageComparer.Exact, ReferenceDecoder); |
|||
} |
|||
|
|||
[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.Instance); |
|||
image.DebugSave(provider); |
|||
image.CompareToOriginal(provider, ImageComparer.Exact, ReferenceDecoder); |
|||
} |
|||
|
|||
[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.Instance); |
|||
image.DebugSave(provider); |
|||
|
|||
// Note: There is a 0,1190% difference to the reference decoder.
|
|||
image.CompareToOriginal(provider, ImageComparer.Tolerant(0.011f), ReferenceDecoder); |
|||
} |
|||
} |
|||
@ -0,0 +1,80 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using SixLabors.ImageSharp.Formats; |
|||
using SixLabors.ImageSharp.Formats.Exr; |
|||
using SixLabors.ImageSharp.Formats.Exr.Constants; |
|||
using SixLabors.ImageSharp.PixelFormats; |
|||
using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; |
|||
using SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs; |
|||
|
|||
namespace SixLabors.ImageSharp.Tests.Formats.Exr; |
|||
|
|||
[Trait("Format", "Exr")] |
|||
[ValidateDisposedMemoryAllocations] |
|||
public class ExrEncoderTests |
|||
{ |
|||
protected static readonly IImageDecoder ReferenceDecoder = new MagickReferenceDecoder(ExrFormat.Instance); |
|||
|
|||
[Theory] |
|||
[InlineData(null, ExrPixelType.Half)] |
|||
[InlineData(ExrPixelType.Float, ExrPixelType.Float)] |
|||
[InlineData(ExrPixelType.Half, ExrPixelType.Half)] |
|||
[InlineData(ExrPixelType.UnsignedInt, ExrPixelType.UnsignedInt)] |
|||
public void EncoderOptions_SetPixelType_Works(ExrPixelType? pixelType, ExrPixelType? expectedPixelType) |
|||
{ |
|||
// arrange
|
|||
ExrEncoder exrEncoder = new() { PixelType = pixelType }; |
|||
using Image input = new Image<Rgb24>(10, 10); |
|||
using MemoryStream memStream = new(); |
|||
|
|||
// act
|
|||
input.Save(memStream, exrEncoder); |
|||
|
|||
// assert
|
|||
memStream.Position = 0; |
|||
using Image<Rgba32> output = Image.Load<Rgba32>(memStream); |
|||
ExrMetadata exrMetaData = output.Metadata.GetExrMetadata(); |
|||
Assert.Equal(expectedPixelType, exrMetaData.PixelType); |
|||
} |
|||
|
|||
[Theory] |
|||
[WithFile(TestImages.Exr.Uncompressed, PixelTypes.Rgba32)] |
|||
public void ExrEncoder_WithNoCompression_Works<TPixel>(TestImageProvider<TPixel> provider) |
|||
where TPixel : unmanaged, IPixel<TPixel> => TestExrEncoderCore(provider, "NoCompression", compression: ExrCompression.None); |
|||
|
|||
[Theory] |
|||
[WithFile(TestImages.Exr.Uncompressed, PixelTypes.Rgba32)] |
|||
public void ExrEncoder_WithZipCompression_Works<TPixel>(TestImageProvider<TPixel> provider) |
|||
where TPixel : unmanaged, IPixel<TPixel> => TestExrEncoderCore(provider, "ZipCompression", compression: ExrCompression.Zip); |
|||
|
|||
[Theory] |
|||
[WithFile(TestImages.Exr.Uncompressed, PixelTypes.Rgba32)] |
|||
public void ExrEncoder_WithZipsCompression_Works<TPixel>(TestImageProvider<TPixel> provider) |
|||
where TPixel : unmanaged, IPixel<TPixel> => TestExrEncoderCore(provider, "ZipsCompression", compression: ExrCompression.Zips); |
|||
|
|||
protected static void TestExrEncoderCore<TPixel>( |
|||
TestImageProvider<TPixel> provider, |
|||
object testOutputDetails, |
|||
ExrCompression compression = ExrCompression.None, |
|||
bool useExactComparer = true, |
|||
float compareTolerance = 0.001f, |
|||
IImageDecoder imageDecoder = null) |
|||
where TPixel : unmanaged, IPixel<TPixel> |
|||
{ |
|||
using Image<TPixel> image = provider.GetImage(); |
|||
ExrEncoder encoder = new() |
|||
{ |
|||
Compression = compression, |
|||
}; |
|||
|
|||
// Does DebugSave & load reference CompareToReferenceInput():
|
|||
image.VerifyEncoder( |
|||
provider, |
|||
"exr", |
|||
testOutputDetails: testOutputDetails, |
|||
encoder: encoder, |
|||
customComparer: useExactComparer ? ImageComparer.Exact : ImageComparer.Tolerant(compareTolerance), |
|||
referenceDecoder: imageDecoder ?? ReferenceDecoder); |
|||
} |
|||
} |
|||
@ -0,0 +1,118 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using SixLabors.ImageSharp.Formats; |
|||
using SixLabors.ImageSharp.Formats.Exr; |
|||
using SixLabors.ImageSharp.Formats.Exr.Constants; |
|||
using SixLabors.ImageSharp.PixelFormats; |
|||
|
|||
namespace SixLabors.ImageSharp.Tests.Formats.Exr; |
|||
|
|||
[Trait("Format", "Exr")] |
|||
public class ExrMetadataTests |
|||
{ |
|||
[Fact] |
|||
public void CloneIsDeep() |
|||
{ |
|||
ExrMetadata meta = new() |
|||
{ ImageDataType = ExrImageDataType.Rgb, PixelType = ExrPixelType.Half, Compression = ExrCompression.None }; |
|||
ExrMetadata clone = (ExrMetadata)meta.DeepClone(); |
|||
|
|||
clone.ImageDataType = ExrImageDataType.Gray; |
|||
clone.PixelType = ExrPixelType.Float; |
|||
clone.Compression = ExrCompression.Zip; |
|||
|
|||
Assert.False(meta.ImageDataType.Equals(clone.ImageDataType)); |
|||
Assert.False(meta.PixelType.Equals(clone.PixelType)); |
|||
Assert.False(meta.Compression.Equals(clone.Compression)); |
|||
} |
|||
|
|||
[Theory] |
|||
[InlineData(TestImages.Exr.Uncompressed, 199, 297)] |
|||
public void Identify_DetectsCorrectWidthAndHeight<TPixel>(string imagePath, int expectedWidth, int expectedHeight) |
|||
{ |
|||
TestFile testFile = TestFile.Create(imagePath); |
|||
using MemoryStream stream = new(testFile.Bytes, false); |
|||
ImageInfo imageInfo = Image.Identify(stream); |
|||
|
|||
Assert.NotNull(imageInfo); |
|||
Assert.Equal(expectedWidth, imageInfo.Width); |
|||
Assert.Equal(expectedHeight, imageInfo.Height); |
|||
} |
|||
|
|||
[Theory] |
|||
[InlineData(TestImages.Exr.Uncompressed, ExrPixelType.Half)] |
|||
[InlineData(TestImages.Exr.UncompressedFloatRgb, ExrPixelType.Float)] |
|||
[InlineData(TestImages.Exr.UncompressedUintRgb, ExrPixelType.UnsignedInt)] |
|||
public void Identify_DetectsCorrectPixelType(string imagePath, ExrPixelType expectedPixelType) |
|||
{ |
|||
TestFile testFile = TestFile.Create(imagePath); |
|||
using MemoryStream stream = new(testFile.Bytes, false); |
|||
ImageInfo imageInfo = Image.Identify(stream); |
|||
|
|||
Assert.NotNull(imageInfo); |
|||
ExrMetadata metadata = imageInfo.Metadata.GetExrMetadata(); |
|||
Assert.NotNull(metadata); |
|||
Assert.Equal(expectedPixelType, metadata.PixelType); |
|||
} |
|||
|
|||
[Theory] |
|||
[InlineData(TestImages.Exr.UncompressedRgba, ExrImageDataType.Rgba)] |
|||
[InlineData(TestImages.Exr.Rgb, ExrImageDataType.Rgb)] |
|||
[InlineData(TestImages.Exr.Gray, ExrImageDataType.Gray)] |
|||
public void Identify_DetectsCorrectImageDataType(string imagePath, ExrImageDataType expectedImageDataType) |
|||
{ |
|||
TestFile testFile = TestFile.Create(imagePath); |
|||
using MemoryStream stream = new(testFile.Bytes, false); |
|||
ImageInfo imageInfo = Image.Identify(stream); |
|||
|
|||
Assert.NotNull(imageInfo); |
|||
ExrMetadata metadata = imageInfo.Metadata.GetExrMetadata(); |
|||
Assert.NotNull(metadata); |
|||
Assert.Equal(expectedImageDataType, metadata.ImageDataType); |
|||
} |
|||
|
|||
[Theory] |
|||
[InlineData(TestImages.Exr.UncompressedRgba, ExrCompression.None)] |
|||
[InlineData(TestImages.Exr.B44, ExrCompression.B44)] |
|||
[InlineData(TestImages.Exr.Rle, ExrCompression.RunLengthEncoded)] |
|||
public void Identify_DetectsCorrectCompression(string imagePath, ExrCompression expectedCompression) |
|||
{ |
|||
TestFile testFile = TestFile.Create(imagePath); |
|||
using MemoryStream stream = new(testFile.Bytes, false); |
|||
ImageInfo imageInfo = Image.Identify(stream); |
|||
|
|||
Assert.NotNull(imageInfo); |
|||
ExrMetadata metadata = imageInfo.Metadata.GetExrMetadata(); |
|||
Assert.NotNull(metadata); |
|||
Assert.Equal(expectedCompression, metadata.Compression); |
|||
} |
|||
|
|||
[Theory] |
|||
[InlineData(PixelColorType.Binary, 1, ExrImageDataType.Unknown, ExrPixelType.Half)] |
|||
[InlineData(PixelColorType.Indexed, 8, ExrImageDataType.Unknown, ExrPixelType.Half)] |
|||
[InlineData(PixelColorType.Luminance, 16, ExrImageDataType.Gray, ExrPixelType.Half)] |
|||
[InlineData(PixelColorType.RGB, 48, ExrImageDataType.Rgb, ExrPixelType.Float)] |
|||
[InlineData(PixelColorType.BGR, 48, ExrImageDataType.Rgb, ExrPixelType.Float)] |
|||
[InlineData(PixelColorType.RGB | PixelColorType.Alpha, 64, ExrImageDataType.Rgba, ExrPixelType.Float)] |
|||
[InlineData(PixelColorType.BGR | PixelColorType.Alpha, 64, ExrImageDataType.Rgba, ExrPixelType.Float)] |
|||
[InlineData(PixelColorType.Luminance | PixelColorType.Alpha, 32, ExrImageDataType.Rgba, ExrPixelType.Float)] |
|||
[InlineData(PixelColorType.YCbCr, 48, ExrImageDataType.Unknown, ExrPixelType.Float)] |
|||
[InlineData(PixelColorType.CMYK, 64, ExrImageDataType.Unknown, ExrPixelType.Float)] |
|||
[InlineData(PixelColorType.YCCK, 64, ExrImageDataType.Unknown, ExrPixelType.Float)] |
|||
public void FromFormatConnectingMetadata_ConvertColorTypeAsExpected(PixelColorType pixelColorType, int bitsPerPixel, ExrImageDataType expectedImageDataType, ExrPixelType expectedPixelType) |
|||
{ |
|||
FormatConnectingMetadata formatConnectingMetadata = new() |
|||
{ |
|||
PixelTypeInfo = new PixelTypeInfo(bitsPerPixel) |
|||
{ |
|||
ColorType = pixelColorType, |
|||
}, |
|||
}; |
|||
|
|||
ExrMetadata actual = ExrMetadata.FromFormatConnectingMetadata(formatConnectingMetadata); |
|||
|
|||
Assert.Equal(expectedImageDataType, actual.ImageDataType); |
|||
Assert.Equal(expectedPixelType, actual.PixelType); |
|||
} |
|||
} |
|||
@ -0,0 +1,136 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using SixLabors.ImageSharp.Formats; |
|||
using SixLabors.ImageSharp.Formats.Exr; |
|||
using SixLabors.ImageSharp.PixelFormats; |
|||
|
|||
namespace SixLabors.ImageSharp.Tests.Formats.Exr; |
|||
|
|||
[Trait("Format", "Exr")] |
|||
public class ImageExtensionsTest |
|||
{ |
|||
[Fact] |
|||
public void SaveAsExr_Path() |
|||
{ |
|||
string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageExtensionsTest)); |
|||
string file = Path.Combine(dir, "SaveAsExr_Path.exr"); |
|||
|
|||
using (Image<Rgba32> image = new(10, 10)) |
|||
{ |
|||
image.SaveAsExr(file); |
|||
} |
|||
|
|||
IImageFormat format = Image.DetectFormat(file); |
|||
Assert.True(format is ExrFormat); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task SaveAsExrAsync_Path() |
|||
{ |
|||
string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageExtensionsTest)); |
|||
string file = Path.Combine(dir, "SaveAsExrAsync_Path.exr"); |
|||
|
|||
using (Image<Rgba32> image = new(10, 10)) |
|||
{ |
|||
await image.SaveAsExrAsync(file); |
|||
} |
|||
|
|||
IImageFormat format = Image.DetectFormat(file); |
|||
Assert.True(format is ExrFormat); |
|||
} |
|||
|
|||
[Fact] |
|||
public void SaveAsExr_Path_Encoder() |
|||
{ |
|||
string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageExtensions)); |
|||
string file = Path.Combine(dir, "SaveAsExr_Path_Encoder.exr"); |
|||
|
|||
using (Image<Rgba32> image = new(10, 10)) |
|||
{ |
|||
image.SaveAsExr(file, new ExrEncoder()); |
|||
} |
|||
|
|||
IImageFormat format = Image.DetectFormat(file); |
|||
Assert.True(format is ExrFormat); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task SaveAsExrAsync_Path_Encoder() |
|||
{ |
|||
string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageExtensions)); |
|||
string file = Path.Combine(dir, "SaveAsExrAsync_Path_Encoder.exr"); |
|||
|
|||
using (Image<Rgba32> image = new(10, 10)) |
|||
{ |
|||
await image.SaveAsExrAsync(file, new ExrEncoder()); |
|||
} |
|||
|
|||
IImageFormat format = Image.DetectFormat(file); |
|||
Assert.True(format is ExrFormat); |
|||
} |
|||
|
|||
[Fact] |
|||
public void SaveAsExr_Stream() |
|||
{ |
|||
using MemoryStream memoryStream = new(); |
|||
|
|||
using (Image<Rgba32> image = new(10, 10)) |
|||
{ |
|||
image.SaveAsExr(memoryStream); |
|||
} |
|||
|
|||
memoryStream.Position = 0; |
|||
|
|||
IImageFormat format = Image.DetectFormat(memoryStream); |
|||
Assert.True(format is ExrFormat); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task SaveAsExrAsync_StreamAsync() |
|||
{ |
|||
using MemoryStream memoryStream = new(); |
|||
|
|||
using (Image<Rgba32> image = new(10, 10)) |
|||
{ |
|||
await image.SaveAsExrAsync(memoryStream); |
|||
} |
|||
|
|||
memoryStream.Position = 0; |
|||
|
|||
IImageFormat format = Image.DetectFormat(memoryStream); |
|||
Assert.True(format is ExrFormat); |
|||
} |
|||
|
|||
[Fact] |
|||
public void SaveAsExr_Stream_Encoder() |
|||
{ |
|||
using MemoryStream memoryStream = new(); |
|||
|
|||
using (Image<Rgba32> image = new(10, 10)) |
|||
{ |
|||
image.SaveAsExr(memoryStream, new ExrEncoder()); |
|||
} |
|||
|
|||
memoryStream.Position = 0; |
|||
|
|||
IImageFormat format = Image.DetectFormat(memoryStream); |
|||
Assert.True(format is ExrFormat); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task SaveAsExrAsync_Stream_Encoder() |
|||
{ |
|||
using MemoryStream memoryStream = new(); |
|||
|
|||
using (Image<Rgba32> image = new(10, 10)) |
|||
{ |
|||
await image.SaveAsExrAsync(memoryStream, new ExrEncoder()); |
|||
} |
|||
|
|||
memoryStream.Position = 0; |
|||
|
|||
IImageFormat format = Image.DetectFormat(memoryStream); |
|||
Assert.True(format is ExrFormat); |
|||
} |
|||
} |
|||
@ -0,0 +1,311 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using System.Numerics; |
|||
using System.Runtime.CompilerServices; |
|||
using SixLabors.ImageSharp.PixelFormats; |
|||
|
|||
namespace SixLabors.ImageSharp.Tests.PixelFormats; |
|||
|
|||
[Trait("Category", "PixelFormats")] |
|||
public class Rgb96Tests |
|||
{ |
|||
[Theory] |
|||
[InlineData(1, 2, 3)] |
|||
[InlineData(50, 70, 80)] |
|||
[InlineData(1000, 2000, 3000)] |
|||
public void Constructor(uint r, uint g, uint b) |
|||
{ |
|||
Rgb96 p = new(r, g, b); |
|||
|
|||
Assert.Equal(r, p.R); |
|||
Assert.Equal(g, p.G); |
|||
Assert.Equal(b, p.B); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Rgb96_ToVector4() |
|||
{ |
|||
Assert.Equal(Vector4.UnitW, new Rgb96(0, 0, 0).ToVector4()); |
|||
Assert.Equal(Vector4.One, new Rgb96(uint.MaxValue, uint.MaxValue, uint.MaxValue).ToVector4()); |
|||
Assert.Equal(new Vector4(0.5F, 0.5F, 0.5F, 1.0F), new Rgb96(uint.MaxValue / 2, uint.MaxValue / 2, uint.MaxValue / 2).ToVector4()); |
|||
} |
|||
|
|||
[Theory] |
|||
[InlineData(uint.MaxValue, uint.MaxValue, uint.MaxValue)] |
|||
[InlineData(0, 0, 0)] |
|||
[InlineData(uint.MaxValue / 2, 100, 2222)] |
|||
public void Rgb96_ToScaledVector4(uint r, uint g, uint b) |
|||
{ |
|||
// arrange
|
|||
Rgb96 rgb = new(r, g, b); |
|||
|
|||
float max = uint.MaxValue; |
|||
float rr = r / max; |
|||
float gg = g / max; |
|||
float bb = b / max; |
|||
|
|||
// act
|
|||
Vector4 actual = rgb.ToScaledVector4(); |
|||
|
|||
// assert
|
|||
Assert.Equal(rr, actual.X); |
|||
Assert.Equal(gg, actual.Y); |
|||
Assert.Equal(bb, actual.Z); |
|||
Assert.Equal(1.0F, actual.W); |
|||
} |
|||
|
|||
[Theory] |
|||
[InlineData(0, 0, 0)] |
|||
[InlineData(5000, 100, 2222)] |
|||
public void Rgb96_FromScaledVector4(uint r, uint g, uint b) |
|||
{ |
|||
// arrange
|
|||
Rgb96 source = new(r, g, b); |
|||
Vector4 scaled = source.ToScaledVector4(); |
|||
|
|||
// act
|
|||
Rgb96 actual = Rgb96.FromScaledVector4(scaled); |
|||
|
|||
// assert
|
|||
Assert.Equal(source, actual); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Rgb96_ToRgba32() |
|||
{ |
|||
// arrange
|
|||
Rgb96 rgb96 = new((uint)(uint.MaxValue * 0.1), uint.MaxValue / 2, uint.MaxValue); |
|||
Rgba32 expected = new(25, 127, 255, 255); |
|||
|
|||
// act
|
|||
Rgba32 actual = rgb96.ToRgba32(); |
|||
|
|||
// assert
|
|||
Assert.Equal(expected, actual); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Equality_WhenTrue() |
|||
{ |
|||
Rgb96 c1 = new(100, 2000, 3000); |
|||
Rgb96 c2 = new(100, 2000, 3000); |
|||
|
|||
Assert.True(c1.Equals(c2)); |
|||
Assert.True(c1.GetHashCode() == c2.GetHashCode()); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Equality_WhenFalse() |
|||
{ |
|||
Rgb96 c1 = new(100, 2000, 3000); |
|||
Rgb96 c2 = new(101, 2000, 3000); |
|||
Rgb96 c3 = new(100, 2000, 3001); |
|||
|
|||
Assert.False(c1.Equals(c2)); |
|||
Assert.False(c2.Equals(c3)); |
|||
Assert.False(c3.Equals(c1)); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Rgb96_FromRgba32() |
|||
{ |
|||
// arrange
|
|||
Rgba32 source = new(25, 127, 255, 255); |
|||
Rgb96 expected = new(421075225, 2139062143, uint.MaxValue); |
|||
|
|||
// act
|
|||
Rgb96 actual = Rgb96.FromRgba32(source); |
|||
|
|||
// assert
|
|||
Assert.Equal(expected, actual); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Rgb96_FromRgb24() |
|||
{ |
|||
// arrange
|
|||
Rgb24 source = new(25, 127, 255); |
|||
Rgb96 expected = new(421075225, 2139062143, uint.MaxValue); |
|||
|
|||
// act
|
|||
Rgb96 actual = Rgb96.FromRgb24(source); |
|||
|
|||
// assert
|
|||
Assert.Equal(expected, actual); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Rgb96_FromRgb48() |
|||
{ |
|||
// arrange
|
|||
Rgb48 source = new(0, 32767, ushort.MaxValue); |
|||
Rgb96 expected = new(0, 2147450879, uint.MaxValue); |
|||
|
|||
// act
|
|||
Rgb96 actual = Rgb96.FromRgb48(source); |
|||
|
|||
// assert
|
|||
Assert.Equal(expected, actual); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Rgb96_FromRgba64() |
|||
{ |
|||
// arrange
|
|||
Rgba64 source = new(0, 32767, ushort.MaxValue, ushort.MaxValue); |
|||
Rgb96 expected = new(0, 2147450879, uint.MaxValue); |
|||
|
|||
// act
|
|||
Rgb96 actual = Rgb96.FromRgba64(source); |
|||
|
|||
// assert
|
|||
Assert.Equal(expected, actual); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Rgb96_FromLa32() |
|||
{ |
|||
// arrange
|
|||
La32 source = new(32767, ushort.MaxValue); |
|||
Rgb96 expected = new(2147450879, 2147450879, 2147450879); |
|||
|
|||
// act
|
|||
Rgb96 actual = Rgb96.FromLa32(source); |
|||
|
|||
// assert
|
|||
Assert.Equal(expected, actual); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Rgb96_FromLa16() |
|||
{ |
|||
// arrange
|
|||
La16 source = new(127, byte.MaxValue); |
|||
Rgb96 expected = new(2139062143, 2139062143, 2139062143); |
|||
|
|||
// act
|
|||
Rgb96 actual = Rgb96.FromLa16(source); |
|||
|
|||
// assert
|
|||
Assert.Equal(expected, actual); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Rgb96_FromL16() |
|||
{ |
|||
// arrange
|
|||
L16 source = new(32767); |
|||
Rgb96 expected = new(2147450879, 2147450879, 2147450879); |
|||
|
|||
// act
|
|||
Rgb96 actual = Rgb96.FromL16(source); |
|||
|
|||
// assert
|
|||
Assert.Equal(expected, actual); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Rgb96_FromL8() |
|||
{ |
|||
// arrange
|
|||
L8 source = new(127); |
|||
Rgb96 expected = new(2139062143, 2139062143, 2139062143); |
|||
|
|||
// act
|
|||
Rgb96 actual = Rgb96.FromL8(source); |
|||
|
|||
// assert
|
|||
Assert.Equal(expected, actual); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Rgb96_FromBgra32() |
|||
{ |
|||
// arrange
|
|||
Bgra32 source = new(127, 25, 255); |
|||
Rgb96 expected = new(2139062143, 421075225, uint.MaxValue); |
|||
|
|||
// act
|
|||
Rgb96 actual = Rgb96.FromBgra32(source); |
|||
|
|||
// assert
|
|||
Assert.Equal(expected, actual); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Rgb96_FromBgr24() |
|||
{ |
|||
// arrange
|
|||
Bgr24 source = new(127, 25, 255); |
|||
Rgb96 expected = new(2139062143, 421075225, uint.MaxValue); |
|||
|
|||
// act
|
|||
Rgb96 actual = Rgb96.FromBgr24(source); |
|||
|
|||
// assert
|
|||
Assert.Equal(expected, actual); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Rgb96_FromBgra5551() |
|||
{ |
|||
// arrange
|
|||
Bgra5551 source = new(1.0f, 1.0f, 1.0f, 1.0f); |
|||
Rgb96 expected = new(uint.MaxValue, uint.MaxValue, uint.MaxValue); |
|||
|
|||
// act
|
|||
Rgb96 actual = Rgb96.FromBgra5551(source); |
|||
|
|||
// assert
|
|||
Assert.Equal(expected, actual); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Rgb96_FromArgb32() |
|||
{ |
|||
// arrange
|
|||
Argb32 source = new(127, 25, 255); |
|||
Rgb96 expected = new(2139062143, 421075225, uint.MaxValue); |
|||
|
|||
// act
|
|||
Rgb96 actual = Rgb96.FromArgb32(source); |
|||
|
|||
// assert
|
|||
Assert.Equal(expected, actual); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Rgb96_FromAbgr32() |
|||
{ |
|||
// arrange
|
|||
Abgr32 source = new(127, 25, 255); |
|||
Rgb96 expected = new(2139062143, 421075225, uint.MaxValue); |
|||
|
|||
// act
|
|||
Rgb96 actual = Rgb96.FromAbgr32(source); |
|||
|
|||
// assert
|
|||
Assert.Equal(expected, actual); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Rgb96_PixelInformation() |
|||
{ |
|||
PixelTypeInfo info = Rgb96.GetPixelTypeInfo(); |
|||
Assert.Equal(Unsafe.SizeOf<Rgb96>() * 8, info.BitsPerPixel); |
|||
Assert.Equal(PixelAlphaRepresentation.None, info.AlphaRepresentation); |
|||
Assert.Equal(PixelColorType.RGB, info.ColorType); |
|||
|
|||
PixelComponentInfo? maybeComponentInfo = info.ComponentInfo; |
|||
Assert.NotNull(maybeComponentInfo); |
|||
PixelComponentInfo componentInfo = maybeComponentInfo.Value; |
|||
|
|||
Assert.Equal(3, componentInfo.ComponentCount); |
|||
Assert.Equal(0, componentInfo.Padding); |
|||
Assert.Equal(32, componentInfo.GetComponentPrecision(0)); |
|||
Assert.Equal(32, componentInfo.GetComponentPrecision(1)); |
|||
Assert.Equal(32, componentInfo.GetComponentPrecision(2)); |
|||
Assert.Equal(32, componentInfo.GetMaximumComponentPrecision()); |
|||
} |
|||
} |
|||
@ -0,0 +1,313 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using System.Numerics; |
|||
using System.Runtime.CompilerServices; |
|||
using SixLabors.ImageSharp.PixelFormats; |
|||
|
|||
namespace SixLabors.ImageSharp.Tests.PixelFormats; |
|||
|
|||
[Trait("Category", "PixelFormats")] |
|||
public class Rgba128Tests |
|||
{ |
|||
[Theory] |
|||
[InlineData(1, 2, 3, 4)] |
|||
[InlineData(50, 70, 80, 90)] |
|||
[InlineData(1000, 2000, 3000, 4000)] |
|||
public void Constructor(uint r, uint g, uint b, uint a) |
|||
{ |
|||
Rgba128 p = new(r, g, b, a); |
|||
|
|||
Assert.Equal(r, p.R); |
|||
Assert.Equal(g, p.G); |
|||
Assert.Equal(b, p.B); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Rgba128_ToVector4() |
|||
{ |
|||
Assert.Equal(Vector4.Zero, new Rgba128(0, 0, 0, 0).ToVector4()); |
|||
Assert.Equal(Vector4.One, new Rgba128(uint.MaxValue, uint.MaxValue, uint.MaxValue, uint.MaxValue).ToVector4()); |
|||
Assert.Equal(new Vector4(0.5F, 0.5F, 0.5F, 0.5F), new Rgba128(uint.MaxValue / 2, uint.MaxValue / 2, uint.MaxValue / 2, uint.MaxValue / 2).ToVector4()); |
|||
} |
|||
|
|||
[Theory] |
|||
[InlineData(uint.MaxValue, uint.MaxValue, uint.MaxValue, uint.MaxValue)] |
|||
[InlineData(0, 0, 0, 0)] |
|||
[InlineData(uint.MaxValue / 2, 100, 2222, 3333)] |
|||
public void Rgba128_ToScaledVector4(uint r, uint g, uint b, uint a) |
|||
{ |
|||
// arrange
|
|||
Rgba128 rgb = new(r, g, b, a); |
|||
|
|||
float max = uint.MaxValue; |
|||
float rr = r / max; |
|||
float gg = g / max; |
|||
float bb = b / max; |
|||
float aa = a / max; |
|||
|
|||
// act
|
|||
Vector4 actual = rgb.ToScaledVector4(); |
|||
|
|||
// assert
|
|||
Assert.Equal(rr, actual.X); |
|||
Assert.Equal(gg, actual.Y); |
|||
Assert.Equal(bb, actual.Z); |
|||
Assert.Equal(aa, actual.W); |
|||
} |
|||
|
|||
[Theory] |
|||
[InlineData(0, 0, 0, 0)] |
|||
[InlineData(5000, 100, 2222, 3333)] |
|||
public void Rgba128_FromScaledVector4(uint r, uint g, uint b, uint a) |
|||
{ |
|||
// arrange
|
|||
Rgba128 source = new(r, g, b, a); |
|||
Vector4 scaled = source.ToScaledVector4(); |
|||
|
|||
// act
|
|||
Rgba128 actual = Rgba128.FromScaledVector4(scaled); |
|||
|
|||
// assert
|
|||
Assert.Equal(source, actual); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Rgba128_ToRgba32() |
|||
{ |
|||
// arrange
|
|||
Rgba128 rgb96 = new((uint)(uint.MaxValue * 0.1), uint.MaxValue / 2, uint.MaxValue, uint.MaxValue); |
|||
Rgba32 expected = new(25, 127, 255, 255); |
|||
|
|||
// act
|
|||
Rgba32 actual = rgb96.ToRgba32(); |
|||
|
|||
// assert
|
|||
Assert.Equal(expected, actual); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Equality_WhenTrue() |
|||
{ |
|||
Rgba128 c1 = new(100, 2000, 3000, 4000); |
|||
Rgba128 c2 = new(100, 2000, 3000, 4000); |
|||
|
|||
Assert.True(c1.Equals(c2)); |
|||
Assert.True(c1.GetHashCode() == c2.GetHashCode()); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Equality_WhenFalse() |
|||
{ |
|||
Rgba128 c1 = new(100, 2000, 3000, 4000); |
|||
Rgba128 c2 = new(101, 2000, 3000, 4000); |
|||
Rgba128 c3 = new(100, 2000, 3001, 4000); |
|||
|
|||
Assert.False(c1.Equals(c2)); |
|||
Assert.False(c2.Equals(c3)); |
|||
Assert.False(c3.Equals(c1)); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Rgba128_FromRgba32() |
|||
{ |
|||
// arrange
|
|||
Rgba32 source = new(25, 127, 255, 255); |
|||
Rgba128 expected = new(421075225, 2139062143, uint.MaxValue, uint.MaxValue); |
|||
|
|||
// act
|
|||
Rgba128 actual = Rgba128.FromRgba32(source); |
|||
|
|||
// assert
|
|||
Assert.Equal(expected, actual); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Rgba128_FromRgb24() |
|||
{ |
|||
// arrange
|
|||
Rgb24 source = new(25, 127, 255); |
|||
Rgba128 expected = new(421075225, 2139062143, uint.MaxValue, uint.MaxValue); |
|||
|
|||
// act
|
|||
Rgba128 actual = Rgba128.FromRgb24(source); |
|||
|
|||
// assert
|
|||
Assert.Equal(expected, actual); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Rgba128_FromRgb48() |
|||
{ |
|||
// arrange
|
|||
Rgb48 source = new(0, 32767, ushort.MaxValue); |
|||
Rgba128 expected = new(0, 2147450879, uint.MaxValue, uint.MaxValue); |
|||
|
|||
// act
|
|||
Rgba128 actual = Rgba128.FromRgb48(source); |
|||
|
|||
// assert
|
|||
Assert.Equal(expected, actual); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Rgba128_FromRgba64() |
|||
{ |
|||
// arrange
|
|||
Rgba64 source = new(0, 32767, ushort.MaxValue, ushort.MaxValue); |
|||
Rgba128 expected = new(0, 2147450879, uint.MaxValue, uint.MaxValue); |
|||
|
|||
// act
|
|||
Rgba128 actual = Rgba128.FromRgba64(source); |
|||
|
|||
// assert
|
|||
Assert.Equal(expected, actual); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Rgba128_FromLa32() |
|||
{ |
|||
// arrange
|
|||
La32 source = new(32767, ushort.MaxValue); |
|||
Rgba128 expected = new(2147450879, 2147450879, 2147450879, 2147450879); |
|||
|
|||
// act
|
|||
Rgba128 actual = Rgba128.FromLa32(source); |
|||
|
|||
// assert
|
|||
Assert.Equal(expected, actual); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Rgba128_FromLa16() |
|||
{ |
|||
// arrange
|
|||
La16 source = new(127, byte.MaxValue); |
|||
Rgba128 expected = new(2139062143, 2139062143, 2139062143, 2139062143); |
|||
|
|||
// act
|
|||
Rgba128 actual = Rgba128.FromLa16(source); |
|||
|
|||
// assert
|
|||
Assert.Equal(expected, actual); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Rgba128_FromL16() |
|||
{ |
|||
// arrange
|
|||
L16 source = new(32767); |
|||
Rgba128 expected = new(2147450879, 2147450879, 2147450879, 2147450879); |
|||
|
|||
// act
|
|||
Rgba128 actual = Rgba128.FromL16(source); |
|||
|
|||
// assert
|
|||
Assert.Equal(expected, actual); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Rgba128_FromL8() |
|||
{ |
|||
// arrange
|
|||
L8 source = new(127); |
|||
Rgba128 expected = new(2139062143, 2139062143, 2139062143, 2139062143); |
|||
|
|||
// act
|
|||
Rgba128 actual = Rgba128.FromL8(source); |
|||
|
|||
// assert
|
|||
Assert.Equal(expected, actual); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Rgba128_FromBgra32() |
|||
{ |
|||
// arrange
|
|||
Bgra32 source = new(127, 25, 255); |
|||
Rgba128 expected = new(2139062143, 421075225, uint.MaxValue, uint.MaxValue); |
|||
|
|||
// act
|
|||
Rgba128 actual = Rgba128.FromBgra32(source); |
|||
|
|||
// assert
|
|||
Assert.Equal(expected, actual); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Rgba128_FromBgr24() |
|||
{ |
|||
// arrange
|
|||
Bgr24 source = new(127, 25, 255); |
|||
Rgba128 expected = new(2139062143, 421075225, uint.MaxValue, uint.MaxValue); |
|||
|
|||
// act
|
|||
Rgba128 actual = Rgba128.FromBgr24(source); |
|||
|
|||
// assert
|
|||
Assert.Equal(expected, actual); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Rgba128_FromBgra5551() |
|||
{ |
|||
// arrange
|
|||
Bgra5551 source = new(1.0f, 1.0f, 1.0f, 1.0f); |
|||
Rgba128 expected = new(uint.MaxValue, uint.MaxValue, uint.MaxValue, uint.MaxValue); |
|||
|
|||
// act
|
|||
Rgba128 actual = Rgba128.FromBgra5551(source); |
|||
|
|||
// assert
|
|||
Assert.Equal(expected, actual); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Rgba128_FromArgb32() |
|||
{ |
|||
// arrange
|
|||
Argb32 source = new(127, 25, 255); |
|||
Rgba128 expected = new(2139062143, 421075225, uint.MaxValue, uint.MaxValue); |
|||
|
|||
// act
|
|||
Rgba128 actual = Rgba128.FromArgb32(source); |
|||
|
|||
// assert
|
|||
Assert.Equal(expected, actual); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Rgba128_FromAbgr32() |
|||
{ |
|||
// arrange
|
|||
Abgr32 source = new(127, 25, 255); |
|||
Rgba128 expected = new(2139062143, 421075225, uint.MaxValue, uint.MaxValue); |
|||
|
|||
// act
|
|||
Rgba128 actual = Rgba128.FromAbgr32(source); |
|||
|
|||
// assert
|
|||
Assert.Equal(expected, actual); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Rgba128_PixelInformation() |
|||
{ |
|||
PixelTypeInfo info = Rgba128.GetPixelTypeInfo(); |
|||
Assert.Equal(Unsafe.SizeOf<Rgba128>() * 8, info.BitsPerPixel); |
|||
Assert.Equal(PixelAlphaRepresentation.Unassociated, info.AlphaRepresentation); |
|||
Assert.Equal(PixelColorType.RGB | PixelColorType.Alpha, info.ColorType); |
|||
|
|||
PixelComponentInfo? maybeComponentInfo = info.ComponentInfo; |
|||
Assert.NotNull(maybeComponentInfo); |
|||
PixelComponentInfo componentInfo = maybeComponentInfo.Value; |
|||
|
|||
Assert.Equal(4, componentInfo.ComponentCount); |
|||
Assert.Equal(0, componentInfo.Padding); |
|||
Assert.Equal(32, componentInfo.GetComponentPrecision(0)); |
|||
Assert.Equal(32, componentInfo.GetComponentPrecision(1)); |
|||
Assert.Equal(32, componentInfo.GetComponentPrecision(2)); |
|||
Assert.Equal(32, componentInfo.GetComponentPrecision(3)); |
|||
Assert.Equal(32, componentInfo.GetMaximumComponentPrecision()); |
|||
} |
|||
} |
|||
@ -0,0 +1,3 @@ |
|||
version https://git-lfs.github.com/spec/v1 |
|||
oid sha256:15de93db72e2de0ad1a21b67931df57c1a129f062fe1ed3cb7f083761af4fe36 |
|||
size 61931 |
|||
@ -0,0 +1,3 @@ |
|||
version https://git-lfs.github.com/spec/v1 |
|||
oid sha256:7a568f9f12adc5606e5b74d25a4bf083e2b565d73635704e205400d5a073fcd3 |
|||
size 9895 |
|||
@ -0,0 +1,3 @@ |
|||
version https://git-lfs.github.com/spec/v1 |
|||
oid sha256:7a568f9f12adc5606e5b74d25a4bf083e2b565d73635704e205400d5a073fcd3 |
|||
size 9895 |
|||
@ -0,0 +1,3 @@ |
|||
version https://git-lfs.github.com/spec/v1 |
|||
oid sha256:c05e0b14a389d11f531d4238900106a81f25a741a55bbbcd160b8b67eb32adb6 |
|||
size 7019 |
|||
@ -0,0 +1,3 @@ |
|||
version https://git-lfs.github.com/spec/v1 |
|||
oid sha256:3d577ac117b8260ad08e6f051c949d9187f04699eed0f063476aaf42eada2366 |
|||
size 20640 |
|||
@ -0,0 +1,3 @@ |
|||
version https://git-lfs.github.com/spec/v1 |
|||
oid sha256:3d577ac117b8260ad08e6f051c949d9187f04699eed0f063476aaf42eada2366 |
|||
size 20640 |
|||
@ -0,0 +1,3 @@ |
|||
version https://git-lfs.github.com/spec/v1 |
|||
oid sha256:53a33da2c1a84c0e8426e1a2ba24e2fe2de17a7bc75760184fb10bf6dc96954a |
|||
size 7896 |
|||
@ -0,0 +1,3 @@ |
|||
version https://git-lfs.github.com/spec/v1 |
|||
oid sha256:f0c97446dec6af009423dcae431c44d23963179c3c3bfbeaf3f3b52022a33f68 |
|||
size 2414 |
|||
@ -0,0 +1,3 @@ |
|||
version https://git-lfs.github.com/spec/v1 |
|||
oid sha256:a651b81764696cd4fe42113d5ac26006f54820d2df8b2103793e82c66597b2b0 |
|||
size 2837 |
|||
@ -0,0 +1,3 @@ |
|||
version https://git-lfs.github.com/spec/v1 |
|||
oid sha256:55672d405a3d085c72cfc9fb26bc3955bf858e525c54c6c4505f093f011f378d |
|||
size 3188 |
|||
@ -0,0 +1,3 @@ |
|||
version https://git-lfs.github.com/spec/v1 |
|||
oid sha256:0e774cba4dda2fe9d3cdff141e7a8c1de7f3e9c8014093abf8697a34e6cc7144 |
|||
size 5379 |
|||
@ -0,0 +1,3 @@ |
|||
version https://git-lfs.github.com/spec/v1 |
|||
oid sha256:44d281c31cd264dcb812df8cb5dc9d5042e915d64e8013af5577dbfea6cbb1cf |
|||
size 3955 |
|||
@ -0,0 +1,3 @@ |
|||
version https://git-lfs.github.com/spec/v1 |
|||
oid sha256:f66bf45b5d80e412314341567b8cf240d56b5872f01ce9f3b0d6034d85e8e942 |
|||
size 163327 |
|||
@ -0,0 +1,3 @@ |
|||
version https://git-lfs.github.com/spec/v1 |
|||
oid sha256:107c6d26cdb8aac539011acf7213376e930cb705f5815b010b31ce4b9e738f9b |
|||
size 25780 |
|||
@ -0,0 +1,3 @@ |
|||
version https://git-lfs.github.com/spec/v1 |
|||
oid sha256:7a6ac7fd879fc2dae646adaa14382aa10e5b8d02634af4668dd04bce09148151 |
|||
size 157973 |
|||
@ -0,0 +1,3 @@ |
|||
version https://git-lfs.github.com/spec/v1 |
|||
oid sha256:e3368860692927e709365f2d37b92411068e77a0f23624ff57af6089ec69f357 |
|||
size 2592888 |
|||
@ -0,0 +1,3 @@ |
|||
version https://git-lfs.github.com/spec/v1 |
|||
oid sha256:0a5daadcfd4ff0e45282d39d6c54f9a13651da3fd8841abda580e76661555470 |
|||
size 124245 |
|||
@ -0,0 +1,3 @@ |
|||
version https://git-lfs.github.com/spec/v1 |
|||
oid sha256:f54e5e57df1b8cdf1b26418d5696dd9cefbd7ed3bf31cdcde06fd9a7bf5e3724 |
|||
size 362681 |
|||
@ -0,0 +1,3 @@ |
|||
version https://git-lfs.github.com/spec/v1 |
|||
oid sha256:ccd25a952240a75d9c8b646be5001a2b006c053314e30cdf12e35ac865a31ae2 |
|||
size 292545 |
|||
@ -0,0 +1,3 @@ |
|||
version https://git-lfs.github.com/spec/v1 |
|||
oid sha256:bed68d2e491cad0bdd0fa668ce57328951927d722f130ba7c5c5475f6a3e0d96 |
|||
size 289183 |
|||
@ -0,0 +1,3 @@ |
|||
version https://git-lfs.github.com/spec/v1 |
|||
oid sha256:f54e5e57df1b8cdf1b26418d5696dd9cefbd7ed3bf31cdcde06fd9a7bf5e3724 |
|||
size 362681 |
|||
@ -0,0 +1,3 @@ |
|||
version https://git-lfs.github.com/spec/v1 |
|||
oid sha256:8e9f6b5afa3c10c895ba67b51568295be40c2b1057224437600028487c581291 |
|||
size 481899 |
|||
@ -0,0 +1,3 @@ |
|||
version https://git-lfs.github.com/spec/v1 |
|||
oid sha256:a8b8e3b9a74179cb51a7a85031b3d177e109c20a56392804b10873273ff2163e |
|||
size 181871 |
|||
@ -0,0 +1,3 @@ |
|||
version https://git-lfs.github.com/spec/v1 |
|||
oid sha256:9c6962a5b06c677648d5e22ff30ba8a28e7801fffb32259984e23bbaa97061b1 |
|||
size 220159 |
|||
@ -0,0 +1,3 @@ |
|||
version https://git-lfs.github.com/spec/v1 |
|||
oid sha256:5ab8b71824ca384fc2cde1f74a6f34c38169fa328ccc67f17d08333e9de42f55 |
|||
size 243558 |
|||
@ -0,0 +1,3 @@ |
|||
version https://git-lfs.github.com/spec/v1 |
|||
oid sha256:0b45345cb1e4539e5298ccce3ccfc0f27b7a469836bfd7aa13c4d9f6c52baa7a |
|||
size 482731 |
|||
Loading…
Reference in new issue