From 799dafe4140eee14c19ecad15fa1c9a8bc6a7eb8 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Fri, 31 Dec 2021 23:18:52 +0100 Subject: [PATCH] Encode EXR image with float pixel data --- src/ImageSharp/Formats/Bmp/BmpFileHeader.cs | 5 +- .../Formats/ImageExtensions.Save.cs | 104 ++++++ .../Formats/OpenExr/ExrConfigurationModule.cs | 1 + .../Formats/OpenExr/ExrDecoderCore.cs | 5 +- src/ImageSharp/Formats/OpenExr/ExrEncoder.cs | 38 +++ .../Formats/OpenExr/ExrEncoderCore.cs | 310 ++++++++++++++++++ src/ImageSharp/Formats/OpenExr/ExrMetadata.cs | 9 +- .../Formats/OpenExr/ExrPixelType.cs | 5 +- .../Formats/OpenExr/IExrEncoderOptions.cs | 16 + .../Formats/OpenExr/MetadataExtensions.cs | 21 ++ .../PixelImplementations/HalfVector4.cs | 59 +++- 11 files changed, 556 insertions(+), 17 deletions(-) create mode 100644 src/ImageSharp/Formats/OpenExr/ExrEncoder.cs create mode 100644 src/ImageSharp/Formats/OpenExr/ExrEncoderCore.cs create mode 100644 src/ImageSharp/Formats/OpenExr/IExrEncoderOptions.cs create mode 100644 src/ImageSharp/Formats/OpenExr/MetadataExtensions.cs diff --git a/src/ImageSharp/Formats/Bmp/BmpFileHeader.cs b/src/ImageSharp/Formats/Bmp/BmpFileHeader.cs index acbcdaef3a..ab56bd246b 100644 --- a/src/ImageSharp/Formats/Bmp/BmpFileHeader.cs +++ b/src/ImageSharp/Formats/Bmp/BmpFileHeader.cs @@ -57,10 +57,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp /// public int Offset { get; } - public static BmpFileHeader Parse(Span data) - { - return MemoryMarshal.Cast(data)[0]; - } + public static BmpFileHeader Parse(Span data) => MemoryMarshal.Cast(data)[0]; public void WriteTo(Span buffer) { diff --git a/src/ImageSharp/Formats/ImageExtensions.Save.cs b/src/ImageSharp/Formats/ImageExtensions.Save.cs index a6a65aef62..315b942c64 100644 --- a/src/ImageSharp/Formats/ImageExtensions.Save.cs +++ b/src/ImageSharp/Formats/ImageExtensions.Save.cs @@ -10,6 +10,7 @@ using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Formats.Bmp; using SixLabors.ImageSharp.Formats.Gif; using SixLabors.ImageSharp.Formats.Jpeg; +using SixLabors.ImageSharp.Formats.OpenExr; using SixLabors.ImageSharp.Formats.Pbm; using SixLabors.ImageSharp.Formats.Png; using SixLabors.ImageSharp.Formats.Tga; @@ -847,5 +848,108 @@ namespace SixLabors.ImageSharp encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(TiffFormat.Instance), cancellationToken); + // foo + /// + /// Saves the image to the given stream with the Open Exr format. + /// + /// The image this method extends. + /// The file path to save the image to. + /// Thrown if the path is null. + public static void SaveAsExr(this Image source, string path) => SaveAsExr(source, path, null); + + /// + /// Saves the image to the given stream with the Open Exr format. + /// + /// The image this method extends. + /// The file path to save the image to. + /// Thrown if the path is null. + /// A representing the asynchronous operation. + public static Task SaveAsExrAsync(this Image source, string path) => SaveAsExrAsync(source, path, null); + + /// + /// Saves the image to the given stream with the Open Exr format. + /// + /// The image this method extends. + /// The file path to save the image to. + /// The token to monitor for cancellation requests. + /// Thrown if the path is null. + /// A representing the asynchronous operation. + public static Task SaveAsExrAsync(this Image source, string path, CancellationToken cancellationToken) + => SaveAsExrAsync(source, path, null, cancellationToken); + + /// + /// Saves the image to the given stream with the Open Exr format. + /// + /// The image this method extends. + /// The file path to save the image to. + /// The encoder to save the image with. + /// Thrown if the path is null. + public static void SaveAsExr(this Image source, string path, ExrEncoder encoder) => + source.Save( + path, + encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(ExrFormat.Instance)); + + /// + /// Saves the image to the given stream with the Open Exr format. + /// + /// The image this method extends. + /// The file path to save the image to. + /// The encoder to save the image with. + /// The token to monitor for cancellation requests. + /// Thrown if the path is null. + /// A representing the asynchronous operation. + public static Task SaveAsExrAsync(this Image source, string path, ExrEncoder encoder, CancellationToken cancellationToken = default) => + source.SaveAsync( + path, + encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(ExrFormat.Instance), + cancellationToken); + + /// + /// Saves the image to the given stream with the Open Exr format. + /// + /// The image this method extends. + /// The stream to save the image to. + /// Thrown if the stream is null. + public static void SaveAsExr(this Image source, Stream stream) + => SaveAsExr(source, stream, null); + + /// + /// Saves the image to the given stream with the Open Exr format. + /// + /// The image this method extends. + /// The stream to save the image to. + /// The token to monitor for cancellation requests. + /// Thrown if the stream is null. + /// A representing the asynchronous operation. + public static Task SaveAsExrAsync(this Image source, Stream stream, CancellationToken cancellationToken = default) + => SaveAsExrAsync(source, stream, null, cancellationToken); + + /// + /// Saves the image to the given stream with the Open Exr format. + /// + /// The image this method extends. + /// The stream to save the image to. + /// The encoder to save the image with. + /// Thrown if the stream is null. + /// A representing the asynchronous operation. + public static void SaveAsExr(this Image source, Stream stream, ExrEncoder encoder) + => source.Save( + stream, + encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(ExrFormat.Instance)); + + /// + /// Saves the image to the given stream with the Open Exr format. + /// + /// The image this method extends. + /// The stream to save the image to. + /// The encoder to save the image with. + /// The token to monitor for cancellation requests. + /// Thrown if the stream is null. + /// A representing the asynchronous operation. + public static Task SaveAsExrAsync(this Image source, Stream stream, ExrEncoder encoder, CancellationToken cancellationToken = default) => + source.SaveAsync( + stream, + encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(ExrFormat.Instance), + cancellationToken); } } diff --git a/src/ImageSharp/Formats/OpenExr/ExrConfigurationModule.cs b/src/ImageSharp/Formats/OpenExr/ExrConfigurationModule.cs index ff0d3ee6a4..d5a03cd5dd 100644 --- a/src/ImageSharp/Formats/OpenExr/ExrConfigurationModule.cs +++ b/src/ImageSharp/Formats/OpenExr/ExrConfigurationModule.cs @@ -11,6 +11,7 @@ namespace SixLabors.ImageSharp.Formats.OpenExr /// public void Configure(Configuration configuration) { + configuration.ImageFormatsManager.SetEncoder(ExrFormat.Instance, new ExrEncoder()); configuration.ImageFormatsManager.SetDecoder(ExrFormat.Instance, new ExrDecoder()); configuration.ImageFormatsManager.AddImageFormatDetector(new ExrImageFormatDetector()); } diff --git a/src/ImageSharp/Formats/OpenExr/ExrDecoderCore.cs b/src/ImageSharp/Formats/OpenExr/ExrDecoderCore.cs index 79b117b736..b0424857ce 100644 --- a/src/ImageSharp/Formats/OpenExr/ExrDecoderCore.cs +++ b/src/ImageSharp/Formats/OpenExr/ExrDecoderCore.cs @@ -90,6 +90,9 @@ namespace SixLabors.ImageSharp.Formats.OpenExr case ExrPixelType.Float: this.DecodeFloatingPointPixelData(stream, pixels); break; + case ExrPixelType.UnsignedInt: + this.DecodeUnsignedIntPixelData(stream, pixels); + break; default: ExrThrowHelper.ThrowNotSupported("Pixel type is not supported"); break; @@ -349,7 +352,7 @@ namespace SixLabors.ImageSharp.Formats.OpenExr } // Next three bytes contain info's about the image. - // We ignore those for now. + // TODO: We ignore those for now. stream.Read(this.buffer, 0, 3); ExrHeader header = this.ParseHeader(stream); diff --git a/src/ImageSharp/Formats/OpenExr/ExrEncoder.cs b/src/ImageSharp/Formats/OpenExr/ExrEncoder.cs new file mode 100644 index 0000000000..71d69596a6 --- /dev/null +++ b/src/ImageSharp/Formats/OpenExr/ExrEncoder.cs @@ -0,0 +1,38 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System.IO; +using System.Threading; +using System.Threading.Tasks; +using SixLabors.ImageSharp.Advanced; +using SixLabors.ImageSharp.PixelFormats; + +namespace SixLabors.ImageSharp.Formats.OpenExr +{ + /// + /// Image encoder for writing an image to a stream in the OpenExr Format. + /// + public sealed class ExrEncoder : IImageEncoder, IExrEncoderOptions + { + /// + /// Gets or sets the pixel type of the image. + /// + public ExrPixelType? PixelType { get; set; } + + /// + public void Encode(Image image, Stream stream) + where TPixel : unmanaged, IPixel + { + var encoder = new ExrEncoderCore(this, image.GetMemoryAllocator()); + encoder.Encode(image, stream); + } + + /// + public Task EncodeAsync(Image image, Stream stream, CancellationToken cancellationToken) + where TPixel : unmanaged, IPixel + { + var encoder = new ExrEncoderCore(this, image.GetMemoryAllocator()); + return encoder.EncodeAsync(image, stream, cancellationToken); + } + } +} diff --git a/src/ImageSharp/Formats/OpenExr/ExrEncoderCore.cs b/src/ImageSharp/Formats/OpenExr/ExrEncoderCore.cs new file mode 100644 index 0000000000..35b381e9be --- /dev/null +++ b/src/ImageSharp/Formats/OpenExr/ExrEncoderCore.cs @@ -0,0 +1,310 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Buffers; +using System.Buffers.Binary; +using System.Collections.Generic; +using System.IO; +using System.Threading; +using SixLabors.ImageSharp.Advanced; +using SixLabors.ImageSharp.Memory; +using SixLabors.ImageSharp.Metadata; +using SixLabors.ImageSharp.PixelFormats; + +namespace SixLabors.ImageSharp.Formats.OpenExr +{ + /// + /// Image encoder for writing an image to a stream in the OpenExr format. + /// + internal sealed class ExrEncoderCore : IImageEncoderInternals + { + /// + /// Reusable buffer. + /// + private readonly byte[] buffer = new byte[8]; + + /// + /// Used for allocating memory during processing operations. + /// + private readonly MemoryAllocator memoryAllocator; + + /// + /// The global configuration. + /// + private Configuration configuration; + + /// + /// The pixel type of the image. + /// + private ExrPixelType? pixelType; + + /// + /// Initializes a new instance of the class. + /// + /// The encoder options. + /// The memory manager. + public ExrEncoderCore(IExrEncoderOptions options, MemoryAllocator memoryAllocator) + { + this.memoryAllocator = memoryAllocator; + this.pixelType = options.PixelType; + } + + /// + /// Encodes the image to the specified stream from the . + /// + /// The pixel format. + /// The to encode from. + /// The to encode the image data to. + /// The token to request cancellation. + public void Encode(Image image, Stream stream, CancellationToken cancellationToken) + where TPixel : unmanaged, IPixel + { + Guard.NotNull(image, nameof(image)); + Guard.NotNull(stream, nameof(stream)); + + this.configuration = image.GetConfiguration(); + ImageMetadata metadata = image.Metadata; + ExrMetadata exrMetadata = metadata.GetExrMetadata(); + this.pixelType ??= exrMetadata.PixelType; + + int width = image.Width; + int height = image.Height; + ExrPixelType pixelType = ExrPixelType.Float; + var header = new ExrHeader() + { + Compression = ExrCompression.None, + AspectRatio = 1.0f, + DataWindow = new ExrBox2i(0, 0, width - 1, height - 1), + DisplayWindow = new ExrBox2i(0, 0, width - 1, height - 1), + LineOrder = ExrLineOrder.IncreasingY, + ScreenWindowCenter = new PointF(0.0f, 0.0f), + ScreenWindowWidth = 1, + Channels = new List() + { + new("B", pixelType, 0, 1, 1), + new("G", pixelType, 0, 1, 1), + new("R", pixelType, 0, 1, 1), + } + }; + + // Write magick bytes. + BinaryPrimitives.WriteInt32LittleEndian(this.buffer, ExrConstants.MagickBytes); + stream.Write(this.buffer.AsSpan(0, 4)); + + // Version number. + this.buffer[0] = 2; + + // Second, third and fourth bytes store info about the image, all set to 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); + + // Write pixel data. + Buffer2D pixels = image.Frames.RootFrame.PixelBuffer; + using IMemoryOwner rgbBuffer = this.memoryAllocator.Allocate(width * 3); + Span redBuffer = rgbBuffer.GetSpan().Slice(0, width); + Span blueBuffer = rgbBuffer.GetSpan().Slice(width, width); + Span greenBuffer = rgbBuffer.GetSpan().Slice(width * 2, width); + + // Write offsets to each pixel row. + uint rowSizeBytes = (uint)(width * 3 * 4); + this.WriteRowOffsets(stream, height, rowSizeBytes); + for (int y = 0; y < height; y++) + { + Span pixelRowSpan = pixels.DangerousGetRowSpan(y); + + for (int x = 0; x < width; x++) + { + var vector4 = pixelRowSpan[x].ToVector4(); + redBuffer[x] = vector4.X; + greenBuffer[x] = vector4.Y; + blueBuffer[x] = vector4.Z; + } + + // Write row index. + BinaryPrimitives.WriteUInt32LittleEndian(this.buffer, (uint)y); + stream.Write(this.buffer.AsSpan(0, 4)); + + // Write pixel row data size. + BinaryPrimitives.WriteUInt32LittleEndian(this.buffer, rowSizeBytes); + stream.Write(this.buffer.AsSpan(0, 4)); + + for (int x = 0; x < width; x++) + { + this.WriteSingle(stream, blueBuffer[x]); + } + + for (int x = 0; x < width; x++) + { + this.WriteSingle(stream, greenBuffer[x]); + } + + for (int x = 0; x < width; x++) + { + this.WriteSingle(stream, redBuffer[x]); + } + } + } + + private void WriteHeader(Stream stream, ExrHeader header) + { + this.WriteChannels(stream, header.Channels); + this.WriteCompression(stream, header.Compression.Value); + this.WriteDataWindow(stream, header.DataWindow.Value); + this.WriteDisplayWindow(stream, header.DisplayWindow.Value); + this.WritePixelAspectRatio(stream, header.AspectRatio.Value); + this.WriteLineOrder(stream, header.LineOrder.Value); + this.WriteScreenWindowCenter(stream, header.ScreenWindowCenter.Value); + this.WriteScreenWindowWidth(stream, header.ScreenWindowWidth.Value); + stream.WriteByte(0); + } + + private void WriteRowOffsets(Stream stream, int height, uint rowSizeBytes) + { + ulong startOfPixelData = (ulong)stream.Position + (8 * (ulong)height); + ulong offset = startOfPixelData; + for (int i = 0; i < height; i++) + { + BinaryPrimitives.WriteUInt64LittleEndian(this.buffer, offset); + stream.Write(this.buffer); + offset += 4 + 4 + rowSizeBytes; + } + } + + private void WriteChannels(Stream stream, IList channels) + { + int attributeSize = 0; + foreach (ExrChannelInfo channelInfo in channels) + { + attributeSize += channelInfo.ChannelName.Length + 1; + attributeSize += 16; + } + + // Last zero byte. + attributeSize++; + this.WriteAttributeInformation(stream, "channels", "chlist", attributeSize); + + foreach (ExrChannelInfo channelInfo in channels) + { + this.WriteChannelInfo(stream, channelInfo); + } + + // Last byte should be zero. + stream.WriteByte(0); + } + + private void WriteCompression(Stream stream, ExrCompression compression) + { + this.WriteAttributeInformation(stream, "compression", "compression", 1); + stream.WriteByte((byte)compression); + } + + private void WritePixelAspectRatio(Stream stream, float aspectRatio) + { + this.WriteAttributeInformation(stream, "pixelAspectRatio", "float", 4); + this.WriteSingle(stream, aspectRatio); + } + + private void WriteLineOrder(Stream stream, ExrLineOrder lineOrder) + { + this.WriteAttributeInformation(stream, "lineOrder", "lineOrder", 1); + stream.WriteByte((byte)lineOrder); + } + + private void WriteScreenWindowCenter(Stream stream, PointF screenWindowCenter) + { + this.WriteAttributeInformation(stream, "screenWindowCenter", "v2f", 8); + this.WriteSingle(stream, screenWindowCenter.X); + this.WriteSingle(stream, screenWindowCenter.Y); + } + + private void WriteScreenWindowWidth(Stream stream, float screenWindowWidth) + { + this.WriteAttributeInformation(stream, "screenWindowWidth", "float", 4); + this.WriteSingle(stream, screenWindowWidth); + } + + private void WriteDataWindow(Stream stream, ExrBox2i dataWindow) + { + this.WriteAttributeInformation(stream, "dataWindow", "box2i", 16); + this.WriteBox2i(stream, dataWindow); + } + + private void WriteDisplayWindow(Stream stream, ExrBox2i displayWindow) + { + this.WriteAttributeInformation(stream, "displayWindow", "box2i", 16); + this.WriteBox2i(stream, displayWindow); + } + + private void WriteAttributeInformation(Stream stream, string name, string type, int size) + { + // Write attribute name. + this.WriteString(stream, name); + + // Write attribute type. + this.WriteString(stream, type); + + // Write attribute size. + BinaryPrimitives.WriteUInt32LittleEndian(this.buffer, (uint)size); + stream.Write(this.buffer.AsSpan(0, 4)); + } + + private void WriteChannelInfo(Stream stream, ExrChannelInfo channelInfo) + { + this.WriteString(stream, channelInfo.ChannelName); + + BinaryPrimitives.WriteInt32LittleEndian(this.buffer, (int)channelInfo.PixelType); + stream.Write(this.buffer.AsSpan(0, 4)); + + stream.WriteByte(channelInfo.PLinear); + + // Next 3 bytes are reserved and will set to zero. + stream.WriteByte(0); + stream.WriteByte(0); + stream.WriteByte(0); + + BinaryPrimitives.WriteInt32LittleEndian(this.buffer, channelInfo.XSampling); + stream.Write(this.buffer.AsSpan(0, 4)); + + BinaryPrimitives.WriteInt32LittleEndian(this.buffer, channelInfo.YSampling); + stream.Write(this.buffer.AsSpan(0, 4)); + } + + private void WriteString(Stream stream, string str) + { + foreach (char c in str) + { + stream.WriteByte((byte)c); + } + + // Write termination byte. + stream.WriteByte(0); + } + + private void WriteBox2i(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)); + } + + private unsafe void WriteSingle(Stream stream, float value) + { + BinaryPrimitives.WriteInt32LittleEndian(this.buffer, *(int*)&value); + stream.Write(this.buffer.AsSpan(0, 4)); + } + } +} diff --git a/src/ImageSharp/Formats/OpenExr/ExrMetadata.cs b/src/ImageSharp/Formats/OpenExr/ExrMetadata.cs index f3723c5426..1e6eaf2bb6 100644 --- a/src/ImageSharp/Formats/OpenExr/ExrMetadata.cs +++ b/src/ImageSharp/Formats/OpenExr/ExrMetadata.cs @@ -19,9 +19,12 @@ namespace SixLabors.ImageSharp.Formats.OpenExr /// Initializes a new instance of the class. /// /// The metadata to create an instance from. - private ExrMetadata(ExrMetadata other) - { - } + private ExrMetadata(ExrMetadata other) => this.PixelType = other.PixelType; + + /// + /// Gets or sets the pixel format. + /// + public ExrPixelType PixelType { get; set; } = ExrPixelType.Half; /// public IDeepCloneable DeepClone() => new ExrMetadata(this); diff --git a/src/ImageSharp/Formats/OpenExr/ExrPixelType.cs b/src/ImageSharp/Formats/OpenExr/ExrPixelType.cs index fc3dc8ef9c..2a57afe988 100644 --- a/src/ImageSharp/Formats/OpenExr/ExrPixelType.cs +++ b/src/ImageSharp/Formats/OpenExr/ExrPixelType.cs @@ -3,7 +3,10 @@ namespace SixLabors.ImageSharp.Formats.OpenExr { - internal enum ExrPixelType + /// + /// The different pixel formats for a OpenEXR image. + /// + public enum ExrPixelType { /// /// unsigned int (32 bit). diff --git a/src/ImageSharp/Formats/OpenExr/IExrEncoderOptions.cs b/src/ImageSharp/Formats/OpenExr/IExrEncoderOptions.cs new file mode 100644 index 0000000000..938236b692 --- /dev/null +++ b/src/ImageSharp/Formats/OpenExr/IExrEncoderOptions.cs @@ -0,0 +1,16 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Formats.OpenExr +{ + /// + /// Configuration options for use during OpenExr encoding. + /// + internal interface IExrEncoderOptions + { + /// + /// Gets the pixel type of the image. + /// + ExrPixelType? PixelType { get; } + } +} diff --git a/src/ImageSharp/Formats/OpenExr/MetadataExtensions.cs b/src/ImageSharp/Formats/OpenExr/MetadataExtensions.cs new file mode 100644 index 0000000000..24508f06a3 --- /dev/null +++ b/src/ImageSharp/Formats/OpenExr/MetadataExtensions.cs @@ -0,0 +1,21 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.Formats.OpenExr; +using SixLabors.ImageSharp.Metadata; + +namespace SixLabors.ImageSharp +{ + /// + /// Extension methods for the type. + /// + public static partial class MetadataExtensions + { + /// + /// Gets the open exr format specific metadata for the image. + /// + /// The metadata this method extends. + /// The . + public static ExrMetadata GetExrMetadata(this ImageMetadata metadata) => metadata.GetFormatMetadata(ExrFormat.Instance); + } +} diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/HalfVector4.cs b/src/ImageSharp/PixelFormats/PixelImplementations/HalfVector4.cs index 94051e263e..3d4298d803 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/HalfVector4.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/HalfVector4.cs @@ -4,6 +4,7 @@ using System; using System.Numerics; using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; namespace SixLabors.ImageSharp.PixelFormats { @@ -13,8 +14,29 @@ namespace SixLabors.ImageSharp.PixelFormats /// Ranges from [-1, -1, -1, -1] to [1, 1, 1, 1] in vector form. /// /// + [StructLayout(LayoutKind.Sequential)] public partial struct HalfVector4 : IPixel, IPackedVector { + /// + /// Gets or sets the red component. + /// + public float R; + + /// + /// Gets or sets the green component. + /// + public float G; + + /// + /// Gets or sets the blue component. + /// + public float B; + + /// + /// Gets or sets the alpha component. + /// + public float A; + /// /// Initializes a new instance of the struct. /// @@ -23,18 +45,43 @@ namespace SixLabors.ImageSharp.PixelFormats /// The z-component. /// The w-component. public HalfVector4(float x, float y, float z, float w) - : this(new Vector4(x, y, z, w)) { + this.R = x; + this.G = y; + this.B = z; + this.A = w; } /// /// Initializes a new instance of the struct. /// /// A vector containing the initial values for the components - public HalfVector4(Vector4 vector) => this.PackedValue = Pack(ref vector); + public HalfVector4(Vector4 vector) + { + this.R = vector.X; + this.G = vector.Y; + this.B = vector.Z; + this.A = vector.W; + } + + /// + /// Gets or sets the packed representation of the HalfVector4 struct. + /// + public ulong HalfVector + { + [MethodImpl(InliningOptions.ShortMethod)] + readonly get => Unsafe.As(ref Unsafe.AsRef(this)); + + [MethodImpl(InliningOptions.ShortMethod)] + set => Unsafe.As(ref this) = value; + } /// - public ulong PackedValue { get; set; } + public ulong PackedValue + { + readonly get => this.HalfVector; + set => this.HalfVector = value; + } /// /// Compares two objects for equality. @@ -86,11 +133,7 @@ namespace SixLabors.ImageSharp.PixelFormats /// [MethodImpl(InliningOptions.ShortMethod)] - public readonly Vector4 ToVector4() => new( - HalfTypeHelper.Unpack((ushort)this.PackedValue), - HalfTypeHelper.Unpack((ushort)(this.PackedValue >> 0x10)), - HalfTypeHelper.Unpack((ushort)(this.PackedValue >> 0x20)), - HalfTypeHelper.Unpack((ushort)(this.PackedValue >> 0x30))); + public readonly Vector4 ToVector4() => new(this.R, this.G, this.B, this.A); /// [MethodImpl(InliningOptions.ShortMethod)]