diff --git a/src/ImageSharp/Formats/OpenExr/ExrCompression.cs b/src/ImageSharp/Formats/OpenExr/ExrCompression.cs
index bcc177bde3..64df462ed3 100644
--- a/src/ImageSharp/Formats/OpenExr/ExrCompression.cs
+++ b/src/ImageSharp/Formats/OpenExr/ExrCompression.cs
@@ -5,6 +5,57 @@ namespace SixLabors.ImageSharp.Formats.OpenExr
{
internal enum ExrCompression
{
- None = 0
+ ///
+ /// Pixel data is not compressed.
+ ///
+ None = 0,
+
+ ///
+ /// Differences between horizontally adjacent pixels are run-length encoded.
+ /// This method is fast, and works well for images with large flat areas, but for photographic images,
+ /// the compressed file size is usually between 60 and 75 percent of the uncompressed size.
+ /// Compression is lossless.
+ ///
+ RunLengthEncoded = 1,
+
+ ///
+ /// Uses the open source zlib library for compression. Unlike ZIP compression, this operates one scan line at a time.
+ /// Compression is lossless.
+ ///
+ Zips = 2,
+
+ ///
+ /// Differences between horizontally adjacent pixels are compressed using the open source zlib library.
+ /// Unlike ZIPS compression, this operates in in blocks of 16 scan lines.
+ /// Compression is lossless.
+ ///
+ Zip = 3,
+
+ ///
+ /// A wavelet transform is applied to the pixel data, and the result is Huffman-encoded.
+ /// Compression is lossless.
+ ///
+ Piz = 4,
+
+ ///
+ /// After reducing 32-bit floating-point data to 24 bits by rounding, differences between horizontally adjacent pixels are compressed with zlib,
+ /// similar to ZIP. PXR24 compression preserves image channels of type HALF and UINT exactly, but the relative error of FLOAT data increases to about 3×10-5.
+ /// Compression is lossy.
+ ///
+ Pxr24 = 5,
+
+ ///
+ /// Channels of type HALF are split into blocks of four by four pixels or 32 bytes. Each block is then packed into 14 bytes,
+ /// reducing the data to 44 percent of their uncompressed size.
+ /// Compression is lossy.
+ ///
+ B44 = 6,
+
+ ///
+ /// Like B44, except for blocks of four by four pixels where all pixels have the same value, which are packed into 3 instead of 14 bytes.
+ /// For images with large uniform areas, B44A produces smaller files than B44 compression.
+ /// Compression is lossy.
+ ///
+ B44A = 7
}
}
diff --git a/src/ImageSharp/Formats/OpenExr/ExrConstants.cs b/src/ImageSharp/Formats/OpenExr/ExrConstants.cs
index 40c34cce23..079adec7ed 100644
--- a/src/ImageSharp/Formats/OpenExr/ExrConstants.cs
+++ b/src/ImageSharp/Formats/OpenExr/ExrConstants.cs
@@ -47,6 +47,9 @@ namespace SixLabors.ImageSharp.Formats.OpenExr
public const string ScreenWindowWidth = "screenWindowWidth";
}
+ ///
+ /// EXR attribute types.
+ ///
internal static class AttibuteTypes
{
public const string ChannelList = "chlist";
@@ -61,5 +64,16 @@ namespace SixLabors.ImageSharp.Formats.OpenExr
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";
+ }
}
}
diff --git a/src/ImageSharp/Formats/OpenExr/ExrDecoderCore.cs b/src/ImageSharp/Formats/OpenExr/ExrDecoderCore.cs
index 053adbd074..4a86dbad82 100644
--- a/src/ImageSharp/Formats/OpenExr/ExrDecoderCore.cs
+++ b/src/ImageSharp/Formats/OpenExr/ExrDecoderCore.cs
@@ -74,9 +74,9 @@ namespace SixLabors.ImageSharp.Formats.OpenExr
{
this.ReadExrHeader(stream);
- if (this.Compression != ExrCompression.None)
+ if (this.Compression is not ExrCompression.None)
{
- ExrThrowHelper.ThrowNotSupported("Only uncompressed EXR images are supported");
+ ExrThrowHelper.ThrowNotSupported($"Compression {this.Compression} is not yet supported");
}
ExrPixelType pixelType = this.ValidateChannels();
@@ -104,10 +104,11 @@ namespace SixLabors.ImageSharp.Formats.OpenExr
private void DecodeFloatingPointPixelData(BufferedReadStream stream, Buffer2D pixels)
where TPixel : unmanaged, IPixel
{
- using IMemoryOwner rowBuffer = this.memoryAllocator.Allocate(this.Width * 3);
+ using IMemoryOwner rowBuffer = this.memoryAllocator.Allocate(this.Width * 4);
Span redPixelData = rowBuffer.GetSpan().Slice(0, this.Width);
Span greenPixelData = rowBuffer.GetSpan().Slice(this.Width, this.Width);
Span bluePixelData = rowBuffer.GetSpan().Slice(this.Width * 2, this.Width);
+ Span alphaPixelData = rowBuffer.GetSpan().Slice(this.Width * 3, this.Width);
TPixel color = default;
for (int y = 0; y < this.Height; y++)
@@ -125,12 +126,13 @@ namespace SixLabors.ImageSharp.Formats.OpenExr
stream.Read(this.buffer, 0, 4);
uint pixelDataSize = BinaryPrimitives.ReadUInt32LittleEndian(this.buffer);
+ bool hasAlpha = false;
for (int channelIdx = 0; channelIdx < this.Channels.Count; channelIdx++)
{
ExrChannelInfo channel = this.Channels[channelIdx];
switch (channel.ChannelName)
{
- case "R":
+ case ExrConstants.ChannelNames.Red:
switch (channel.PixelType)
{
case ExrPixelType.Half:
@@ -143,7 +145,7 @@ namespace SixLabors.ImageSharp.Formats.OpenExr
break;
- case "B":
+ case ExrConstants.ChannelNames.Blue:
switch (channel.PixelType)
{
case ExrPixelType.Half:
@@ -156,7 +158,7 @@ namespace SixLabors.ImageSharp.Formats.OpenExr
break;
- case "G":
+ case ExrConstants.ChannelNames.Green:
switch (channel.PixelType)
{
case ExrPixelType.Half:
@@ -169,6 +171,20 @@ namespace SixLabors.ImageSharp.Formats.OpenExr
break;
+ case ExrConstants.ChannelNames.Alpha:
+ hasAlpha = true;
+ switch (channel.PixelType)
+ {
+ case ExrPixelType.Half:
+ this.ReadPixelRowChannelHalfSingle(stream, alphaPixelData);
+ break;
+ case ExrPixelType.Float:
+ this.ReadPixelRowChannelSingle(stream, alphaPixelData);
+ break;
+ }
+
+ break;
+
default:
// Skip unknown channel.
int channelDataSizeInBytes = channel.PixelType is ExrPixelType.Float or ExrPixelType.UnsignedInt ? 4 : 2;
@@ -179,11 +195,23 @@ namespace SixLabors.ImageSharp.Formats.OpenExr
stream.Position = nextRowOffsetPosition;
- for (int x = 0; x < this.Width; x++)
+ if (hasAlpha)
+ {
+ for (int x = 0; x < this.Width; x++)
+ {
+ var pixelValue = new HalfVector4(redPixelData[x], greenPixelData[x], bluePixelData[x], alphaPixelData[x]);
+ color.FromVector4(pixelValue.ToVector4());
+ pixelRow[x] = color;
+ }
+ }
+ else
{
- var pixelValue = new HalfVector4(redPixelData[x], greenPixelData[x], bluePixelData[x], 1.0f);
- color.FromVector4(pixelValue.ToVector4());
- pixelRow[x] = color;
+ for (int x = 0; x < this.Width; x++)
+ {
+ var pixelValue = new HalfVector4(redPixelData[x], greenPixelData[x], bluePixelData[x], 1.0f);
+ color.FromVector4(pixelValue.ToVector4());
+ pixelRow[x] = color;
+ }
}
}
}
@@ -191,10 +219,11 @@ namespace SixLabors.ImageSharp.Formats.OpenExr
private void DecodeUnsignedIntPixelData(BufferedReadStream stream, Buffer2D pixels)
where TPixel : unmanaged, IPixel
{
- using IMemoryOwner rowBuffer = this.memoryAllocator.Allocate(this.Width * 3);
+ using IMemoryOwner rowBuffer = this.memoryAllocator.Allocate(this.Width * 4);
Span redPixelData = rowBuffer.GetSpan().Slice(0, this.Width);
Span greenPixelData = rowBuffer.GetSpan().Slice(this.Width, this.Width);
Span bluePixelData = rowBuffer.GetSpan().Slice(this.Width * 2, this.Width);
+ Span alphaPixelData = rowBuffer.GetSpan().Slice(this.Width * 3, this.Width);
TPixel color = default;
for (int y = 0; y < this.Height; y++)
@@ -212,12 +241,13 @@ namespace SixLabors.ImageSharp.Formats.OpenExr
stream.Read(this.buffer, 0, 4);
uint pixelDataSize = BinaryPrimitives.ReadUInt32LittleEndian(this.buffer);
+ bool hasAlpha = false;
for (int channelIdx = 0; channelIdx < this.Channels.Count; channelIdx++)
{
ExrChannelInfo channel = this.Channels[channelIdx];
switch (channel.ChannelName)
{
- case "R":
+ case ExrConstants.ChannelNames.Red:
switch (channel.PixelType)
{
case ExrPixelType.UnsignedInt:
@@ -227,7 +257,7 @@ namespace SixLabors.ImageSharp.Formats.OpenExr
break;
- case "B":
+ case ExrConstants.ChannelNames.Blue:
switch (channel.PixelType)
{
case ExrPixelType.UnsignedInt:
@@ -237,7 +267,7 @@ namespace SixLabors.ImageSharp.Formats.OpenExr
break;
- case "G":
+ case ExrConstants.ChannelNames.Green:
switch (channel.PixelType)
{
case ExrPixelType.UnsignedInt:
@@ -247,6 +277,17 @@ namespace SixLabors.ImageSharp.Formats.OpenExr
break;
+ case ExrConstants.ChannelNames.Alpha:
+ hasAlpha = true;
+ switch (channel.PixelType)
+ {
+ case ExrPixelType.UnsignedInt:
+ this.ReadPixelRowChannelUnsignedInt(stream, alphaPixelData);
+ break;
+ }
+
+ break;
+
default:
// Skip unknown channel.
int channelDataSizeInBytes = channel.PixelType is ExrPixelType.Float or ExrPixelType.UnsignedInt ? 4 : 2;
@@ -257,11 +298,23 @@ namespace SixLabors.ImageSharp.Formats.OpenExr
stream.Position = nextRowOffsetPosition;
- for (int x = 0; x < this.Width; x++)
+ if (hasAlpha)
{
- var pixelValue = new Rgb96(redPixelData[x], greenPixelData[x], bluePixelData[x]);
- color.FromVector4(pixelValue.ToVector4());
- pixelRow[x] = color;
+ for (int x = 0; x < this.Width; x++)
+ {
+ var pixelValue = new Rgba128(redPixelData[x], greenPixelData[x], bluePixelData[x], alphaPixelData[x]);
+ color.FromVector4(pixelValue.ToVector4());
+ pixelRow[x] = color;
+ }
+ }
+ else
+ {
+ for (int x = 0; x < this.Width; x++)
+ {
+ var pixelValue = new Rgb96(redPixelData[x], greenPixelData[x], bluePixelData[x]);
+ color.FromVector4(pixelValue.ToVector4());
+ pixelRow[x] = color;
+ }
}
}
}
@@ -324,19 +377,45 @@ namespace SixLabors.ImageSharp.Formats.OpenExr
{
if (this.Channels.Count == 0)
{
- ExrThrowHelper.ThrowInvalidImageContentException("At least one channel of pixel data expected!");
+ 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) ||
+ this.Channels[i].ChannelName.Equals(ExrConstants.ChannelNames.Green) ||
+ this.Channels[i].ChannelName.Equals(ExrConstants.ChannelNames.Red) ||
+ this.Channels[i].ChannelName.Equals(ExrConstants.ChannelNames.Alpha))
+ {
+ pixelType = this.Channels[i].PixelType;
+ break;
+ }
}
- ExrPixelType pixelType = this.Channels[0].PixelType;
- for (int i = 1; i < this.Channels.Count; i++)
+ if (!pixelType.HasValue)
{
+ ExrThrowHelper.ThrowInvalidImageContentException("Pixel channel data is unknown! Only R, G, B and A are supported.");
+ }
+
+ for (int i = 0; i < this.Channels.Count; i++)
+ {
+ // Ignore channels which we cannot interpret.
+ if (!(this.Channels[i].ChannelName.Equals(ExrConstants.ChannelNames.Blue) ||
+ this.Channels[i].ChannelName.Equals(ExrConstants.ChannelNames.Green) ||
+ this.Channels[i].ChannelName.Equals(ExrConstants.ChannelNames.Red)))
+ {
+ continue;
+ }
+
if (pixelType != this.Channels[i].PixelType)
{
ExrThrowHelper.ThrowInvalidImageContentException("All channels are expected to have the same pixel data!");
}
}
- return pixelType;
+ return pixelType.Value;
}
private ExrHeader ReadExrHeader(BufferedReadStream stream)
diff --git a/src/ImageSharp/Formats/OpenExr/ExrEncoderCore.cs b/src/ImageSharp/Formats/OpenExr/ExrEncoderCore.cs
index 221877a063..1890d0b2b1 100644
--- a/src/ImageSharp/Formats/OpenExr/ExrEncoderCore.cs
+++ b/src/ImageSharp/Formats/OpenExr/ExrEncoderCore.cs
@@ -76,9 +76,9 @@ namespace SixLabors.ImageSharp.Formats.OpenExr
ScreenWindowWidth = 1,
Channels = new List()
{
- new("B", this.pixelType.Value, 0, 1, 1),
- new("G", this.pixelType.Value, 0, 1, 1),
- new("R", 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),
}
};
diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/Rgb96.cs b/src/ImageSharp/PixelFormats/PixelImplementations/Rgb96.cs
index 83333a377e..2809a4c319 100644
--- a/src/ImageSharp/PixelFormats/PixelImplementations/Rgb96.cs
+++ b/src/ImageSharp/PixelFormats/PixelImplementations/Rgb96.cs
@@ -10,7 +10,7 @@ namespace SixLabors.ImageSharp.PixelFormats
{
///
/// Pixel type containing three 32-bit unsigned normalized values ranging from 0 to 4294967295.
- /// The color components are stored in red, green, blue
+ /// The color components are stored in red, green, blue.
///
/// Ranges from [0, 0, 0] to [1, 1, 1] in vector form.
///
@@ -23,17 +23,17 @@ namespace SixLabors.ImageSharp.PixelFormats
private const double Max = uint.MaxValue;
///
- /// Gets or sets the red component.
+ /// Gets the red component.
///
public uint R;
///
- /// Gets or sets the green component.
+ /// Gets the green component.
///
public uint G;
///
- /// Gets or sets the blue component.
+ /// Gets the blue component.
///
public uint B;
diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/Rgba128.cs b/src/ImageSharp/PixelFormats/PixelImplementations/Rgba128.cs
new file mode 100644
index 0000000000..01fc16d45a
--- /dev/null
+++ b/src/ImageSharp/PixelFormats/PixelImplementations/Rgba128.cs
@@ -0,0 +1,183 @@
+// Copyright (c) Six Labors.
+// Licensed under the Apache License, Version 2.0.
+
+using System;
+using System.Numerics;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+namespace SixLabors.ImageSharp.PixelFormats
+{
+ ///
+ /// 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.
+ ///
+ /// Ranges from [0, 0, 0, 0] to [1, 1, 1, 1] in vector form.
+ ///
+ ///
+ [StructLayout(LayoutKind.Sequential)]
+ public partial struct Rgba128 : IPixel
+ {
+ private const float InvMax = 1.0f / uint.MaxValue;
+
+ private const double Max = uint.MaxValue;
+
+ ///
+ /// Gets the red component.
+ ///
+ public uint R;
+
+ ///
+ /// Gets the green component.
+ ///
+ public uint G;
+
+ ///
+ /// Gets the blue component.
+ ///
+ public uint B;
+
+ ///
+ /// Gets the alpha channel.
+ ///
+ public uint A;
+
+ ///
+ /// Initializes a new instance of the struct.
+ ///
+ /// The red component.
+ /// The green component.
+ /// The blue component.
+ /// The alpha component.
+ [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;
+ }
+
+ ///
+ /// Compares two objects for equality.
+ ///
+ /// The on the left side of the operand.
+ ///
+ /// True if the parameter is equal to the parameter; otherwise, false.
+ ///
+ /// The on the right side of the operand.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static bool operator ==(Rgba128 left, Rgba128 right) => left.Equals(right);
+
+ ///
+ /// Compares two objects for equality.
+ ///
+ /// The on the left side of the operand.
+ /// The on the right side of the operand.
+ ///
+ /// True if the parameter is not equal to the parameter; otherwise, false.
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static bool operator !=(Rgba128 left, Rgba128 right) => !left.Equals(right);
+
+ ///
+ public PixelOperations CreatePixelOperations() => new();
+
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public void FromScaledVector4(Vector4 vector) => this.FromVector4(vector);
+
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public Vector4 ToScaledVector4() => this.ToVector4();
+
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public void FromVector4(Vector4 vector)
+ {
+ vector = Numerics.Clamp(vector, Vector4.Zero, Vector4.One);
+ this.R = (uint)(vector.X * Max);
+ this.G = (uint)(vector.Y * Max);
+ this.B = (uint)(vector.Z * Max);
+ this.A = (uint)(vector.W * Max);
+ }
+
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public Vector4 ToVector4() => new(
+ this.R * InvMax,
+ this.G * InvMax,
+ this.B * InvMax,
+ this.A * InvMax);
+
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public void FromBgra5551(Bgra5551 source) => this.FromScaledVector4(source.ToScaledVector4());
+
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public void FromArgb32(Argb32 source) => this.FromScaledVector4(source.ToScaledVector4());
+
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public void FromBgr24(Bgr24 source) => this.FromScaledVector4(source.ToScaledVector4());
+
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public void FromBgra32(Bgra32 source) => this.FromScaledVector4(source.ToScaledVector4());
+
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public void FromL8(L8 source) => this.FromScaledVector4(source.ToScaledVector4());
+
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public void FromL16(L16 source) => this.FromScaledVector4(source.ToScaledVector4());
+
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public void FromLa16(La16 source) => this.FromScaledVector4(source.ToScaledVector4());
+
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public void FromLa32(La32 source) => this.FromScaledVector4(source.ToScaledVector4());
+
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public void FromRgb24(Rgb24 source) => this.FromScaledVector4(source.ToScaledVector4());
+
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public void FromRgba32(Rgba32 source) => this.FromScaledVector4(source.ToScaledVector4());
+
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public void FromAbgr32(Abgr32 source) => this.FromScaledVector4(source.ToScaledVector4());
+
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public void ToRgba32(ref Rgba32 dest) => dest.FromScaledVector4(this.ToScaledVector4());
+
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public void FromRgb48(Rgb48 source) => this.FromScaledVector4(source.ToScaledVector4());
+
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public void FromRgba64(Rgba64 source) => this.FromScaledVector4(source.ToScaledVector4());
+
+ ///
+ public override bool Equals(object obj) => obj is Rgba128 other && this.Equals(other);
+
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public override int GetHashCode() => HashCode.Combine(this.R, this.G, this.B, this.A);
+
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public bool Equals(Rgba128 other) => this.R.Equals(other.R) && this.G.Equals(other.G) && this.B.Equals(other.B) && this.A.Equals(other.A);
+
+ ///
+ public override string ToString() => FormattableString.Invariant($"Rgba128({this.R}, {this.G}, {this.B}, {this.A})");
+ }
+}