diff --git a/src/ImageSharp/Formats/ImageExtensions.Save.cs b/src/ImageSharp/Formats/ImageExtensions.Save.cs index 315b942c64..55fd00cf95 100644 --- a/src/ImageSharp/Formats/ImageExtensions.Save.cs +++ b/src/ImageSharp/Formats/ImageExtensions.Save.cs @@ -848,14 +848,13 @@ 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); + public static void SaveAsOpenExr(this Image source, string path) => SaveAsOpenExr(source, path, null); /// /// Saves the image to the given stream with the Open Exr format. @@ -864,7 +863,7 @@ namespace SixLabors.ImageSharp /// 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); + public static Task SaveAsOpenExrAsync(this Image source, string path) => SaveAsOpenExrAsync(source, path, null); /// /// Saves the image to the given stream with the Open Exr format. @@ -874,8 +873,8 @@ namespace SixLabors.ImageSharp /// 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); + public static Task SaveAsOpenExrAsync(this Image source, string path, CancellationToken cancellationToken) + => SaveAsOpenExrAsync(source, path, null, cancellationToken); /// /// Saves the image to the given stream with the Open Exr format. @@ -884,7 +883,7 @@ namespace SixLabors.ImageSharp /// 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) => + public static void SaveAsOpenExr(this Image source, string path, ExrEncoder encoder) => source.Save( path, encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(ExrFormat.Instance)); @@ -898,7 +897,7 @@ namespace SixLabors.ImageSharp /// 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) => + public static Task SaveAsOpenExrAsync(this Image source, string path, ExrEncoder encoder, CancellationToken cancellationToken = default) => source.SaveAsync( path, encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(ExrFormat.Instance), @@ -910,8 +909,8 @@ namespace SixLabors.ImageSharp /// 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); + public static void SaveAsOpenExr(this Image source, Stream stream) + => SaveAsOpenExr(source, stream, null); /// /// Saves the image to the given stream with the Open Exr format. @@ -921,8 +920,8 @@ namespace SixLabors.ImageSharp /// 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); + public static Task SaveAsOpenExrAsync(this Image source, Stream stream, CancellationToken cancellationToken = default) + => SaveAsOpenExrAsync(source, stream, null, cancellationToken); /// /// Saves the image to the given stream with the Open Exr format. @@ -932,7 +931,7 @@ namespace SixLabors.ImageSharp /// 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) + public static void SaveAsOpenExr(this Image source, Stream stream, ExrEncoder encoder) => source.Save( stream, encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(ExrFormat.Instance)); @@ -946,7 +945,7 @@ namespace SixLabors.ImageSharp /// 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) => + public static Task SaveAsOpenExrAsync(this Image source, Stream stream, ExrEncoder encoder, CancellationToken cancellationToken = default) => source.SaveAsync( stream, encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(ExrFormat.Instance), diff --git a/src/ImageSharp/Formats/OpenExr/ExrBox2i.cs b/src/ImageSharp/Formats/OpenExr/ExrBox2i.cs index 6fd610026b..89424b4259 100644 --- a/src/ImageSharp/Formats/OpenExr/ExrBox2i.cs +++ b/src/ImageSharp/Formats/OpenExr/ExrBox2i.cs @@ -1,8 +1,11 @@ // Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. +using System.Diagnostics; + namespace SixLabors.ImageSharp.Formats.OpenExr { + [DebuggerDisplay("xMin: {XMin}, yMin: {YMin}, xMax: {XMax}, yMax: {YMax}")] internal struct ExrBox2i { public ExrBox2i(int xMin, int yMin, int xMax, int yMax) diff --git a/src/ImageSharp/Formats/OpenExr/ExrConstants.cs b/src/ImageSharp/Formats/OpenExr/ExrConstants.cs index b3f1a34245..40c34cce23 100644 --- a/src/ImageSharp/Formats/OpenExr/ExrConstants.cs +++ b/src/ImageSharp/Formats/OpenExr/ExrConstants.cs @@ -24,5 +24,42 @@ namespace SixLabors.ImageSharp.Formats.OpenExr /// The magick bytes identifying an OpenExr image. /// public static readonly int MagickBytes = 20000630; + + /// + /// EXR attribute names. + /// + internal static class AttributeNames + { + public const string Channels = "channels"; + + 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"; + } + + 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"; + } } } diff --git a/src/ImageSharp/Formats/OpenExr/ExrDecoderCore.cs b/src/ImageSharp/Formats/OpenExr/ExrDecoderCore.cs index b0424857ce..46d3941f36 100644 --- a/src/ImageSharp/Formats/OpenExr/ExrDecoderCore.cs +++ b/src/ImageSharp/Formats/OpenExr/ExrDecoderCore.cs @@ -381,35 +381,35 @@ namespace SixLabors.ImageSharp.Formats.OpenExr { switch (attribute.Name) { - case "channels": + case ExrConstants.AttributeNames.Channels: IList channels = this.ReadChannelList(stream, attribute.Length); header.Channels = channels; break; - case "compression": + case ExrConstants.AttributeNames.Compression: header.Compression = (ExrCompression)stream.ReadByte(); break; - case "dataWindow": + case ExrConstants.AttributeNames.DataWindow: ExrBox2i dataWindow = this.ReadBox2i(stream); header.DataWindow = dataWindow; break; - case "displayWindow": + case ExrConstants.AttributeNames.DisplayWindow: ExrBox2i displayWindow = this.ReadBox2i(stream); header.DisplayWindow = displayWindow; break; - case "lineOrder": + case ExrConstants.AttributeNames.LineOrder: var lineOrder = (ExrLineOrder)stream.ReadByte(); header.LineOrder = lineOrder; break; - case "pixelAspectRatio": + case ExrConstants.AttributeNames.PixelAspectRatio: float aspectRatio = stream.ReadSingle(this.buffer); header.AspectRatio = aspectRatio; break; - case "screenWindowCenter": + case ExrConstants.AttributeNames.ScreenWindowCenter: float screenWindowCenterX = stream.ReadSingle(this.buffer); float screenWindowCenterY = stream.ReadSingle(this.buffer); header.ScreenWindowCenter = new PointF(screenWindowCenterX, screenWindowCenterY); break; - case "screenWindowWidth": + case ExrConstants.AttributeNames.ScreenWindowWidth: float screenWindowWidth = stream.ReadSingle(this.buffer); header.ScreenWindowWidth = screenWindowWidth; break; diff --git a/src/ImageSharp/Formats/OpenExr/ExrEncoderCore.cs b/src/ImageSharp/Formats/OpenExr/ExrEncoderCore.cs index 35b381e9be..e3c4376708 100644 --- a/src/ImageSharp/Formats/OpenExr/ExrEncoderCore.cs +++ b/src/ImageSharp/Formats/OpenExr/ExrEncoderCore.cs @@ -7,7 +7,6 @@ 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; @@ -29,11 +28,6 @@ namespace SixLabors.ImageSharp.Formats.OpenExr /// private readonly MemoryAllocator memoryAllocator; - /// - /// The global configuration. - /// - private Configuration configuration; - /// /// The pixel type of the image. /// @@ -63,14 +57,11 @@ namespace SixLabors.ImageSharp.Formats.OpenExr 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, @@ -82,9 +73,9 @@ namespace SixLabors.ImageSharp.Formats.OpenExr ScreenWindowWidth = 1, Channels = new List() { - new("B", pixelType, 0, 1, 1), - new("G", pixelType, 0, 1, 1), - new("R", pixelType, 0, 1, 1), + new("B", this.pixelType.Value, 0, 1, 1), + new("G", this.pixelType.Value, 0, 1, 1), + new("R", this.pixelType.Value, 0, 1, 1), } }; @@ -112,7 +103,9 @@ namespace SixLabors.ImageSharp.Formats.OpenExr Span greenBuffer = rgbBuffer.GetSpan().Slice(width * 2, width); // Write offsets to each pixel row. - uint rowSizeBytes = (uint)(width * 3 * 4); + int bytesPerPixel = this.pixelType == ExrPixelType.Half ? 2 : 4; + int numberOfChannels = 3; + uint rowSizeBytes = (uint)(width * numberOfChannels * bytesPerPixel); this.WriteRowOffsets(stream, height, rowSizeBytes); for (int y = 0; y < height; y++) { @@ -134,19 +127,14 @@ namespace SixLabors.ImageSharp.Formats.OpenExr 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++) + switch (this.pixelType) { - this.WriteSingle(stream, greenBuffer[x]); - } - - for (int x = 0; x < width; x++) - { - this.WriteSingle(stream, redBuffer[x]); + case ExrPixelType.Float: + this.WriteSingleRow(stream, width, blueBuffer, greenBuffer, redBuffer); + break; + case ExrPixelType.Half: + this.WriteHalfSingleRow(stream, width, blueBuffer, greenBuffer, redBuffer); + break; } } } @@ -164,6 +152,42 @@ namespace SixLabors.ImageSharp.Formats.OpenExr stream.WriteByte(0); } + private void WriteSingleRow(Stream stream, int width, Span blueBuffer, Span greenBuffer, Span redBuffer) + { + 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 WriteHalfSingleRow(Stream stream, int width, Span blueBuffer, Span greenBuffer, Span redBuffer) + { + for (int x = 0; x < width; x++) + { + this.WriteHalfSingle(stream, blueBuffer[x]); + } + + for (int x = 0; x < width; x++) + { + this.WriteHalfSingle(stream, greenBuffer[x]); + } + + for (int x = 0; x < width; x++) + { + this.WriteHalfSingle(stream, redBuffer[x]); + } + } + private void WriteRowOffsets(Stream stream, int height, uint rowSizeBytes) { ulong startOfPixelData = (ulong)stream.Position + (8 * (ulong)height); @@ -187,7 +211,7 @@ namespace SixLabors.ImageSharp.Formats.OpenExr // Last zero byte. attributeSize++; - this.WriteAttributeInformation(stream, "channels", "chlist", attributeSize); + this.WriteAttributeInformation(stream, ExrConstants.AttributeNames.Channels, ExrConstants.AttibuteTypes.ChannelList, attributeSize); foreach (ExrChannelInfo channelInfo in channels) { @@ -200,45 +224,45 @@ namespace SixLabors.ImageSharp.Formats.OpenExr private void WriteCompression(Stream stream, ExrCompression compression) { - this.WriteAttributeInformation(stream, "compression", "compression", 1); + this.WriteAttributeInformation(stream, ExrConstants.AttributeNames.Compression, ExrConstants.AttibuteTypes.Compression, 1); stream.WriteByte((byte)compression); } private void WritePixelAspectRatio(Stream stream, float aspectRatio) { - this.WriteAttributeInformation(stream, "pixelAspectRatio", "float", 4); + this.WriteAttributeInformation(stream, ExrConstants.AttributeNames.PixelAspectRatio, ExrConstants.AttibuteTypes.Float, 4); this.WriteSingle(stream, aspectRatio); } private void WriteLineOrder(Stream stream, ExrLineOrder lineOrder) { - this.WriteAttributeInformation(stream, "lineOrder", "lineOrder", 1); + this.WriteAttributeInformation(stream, ExrConstants.AttributeNames.LineOrder, ExrConstants.AttibuteTypes.LineOrder, 1); stream.WriteByte((byte)lineOrder); } private void WriteScreenWindowCenter(Stream stream, PointF screenWindowCenter) { - this.WriteAttributeInformation(stream, "screenWindowCenter", "v2f", 8); + this.WriteAttributeInformation(stream, ExrConstants.AttributeNames.ScreenWindowCenter, ExrConstants.AttibuteTypes.TwoFloat, 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.WriteAttributeInformation(stream, ExrConstants.AttributeNames.ScreenWindowWidth, ExrConstants.AttibuteTypes.Float, 4); this.WriteSingle(stream, screenWindowWidth); } private void WriteDataWindow(Stream stream, ExrBox2i dataWindow) { - this.WriteAttributeInformation(stream, "dataWindow", "box2i", 16); - this.WriteBox2i(stream, dataWindow); + this.WriteAttributeInformation(stream, ExrConstants.AttributeNames.DataWindow, ExrConstants.AttibuteTypes.BoxInt, 16); + this.WriteBoxInteger(stream, dataWindow); } private void WriteDisplayWindow(Stream stream, ExrBox2i displayWindow) { - this.WriteAttributeInformation(stream, "displayWindow", "box2i", 16); - this.WriteBox2i(stream, displayWindow); + this.WriteAttributeInformation(stream, ExrConstants.AttributeNames.DisplayWindow, ExrConstants.AttibuteTypes.BoxInt, 16); + this.WriteBoxInteger(stream, displayWindow); } private void WriteAttributeInformation(Stream stream, string name, string type, int size) @@ -286,7 +310,7 @@ namespace SixLabors.ImageSharp.Formats.OpenExr stream.WriteByte(0); } - private void WriteBox2i(Stream stream, ExrBox2i box) + private void WriteBoxInteger(Stream stream, ExrBox2i box) { BinaryPrimitives.WriteInt32LittleEndian(this.buffer, box.XMin); stream.Write(this.buffer.AsSpan(0, 4)); @@ -306,5 +330,12 @@ namespace SixLabors.ImageSharp.Formats.OpenExr BinaryPrimitives.WriteInt32LittleEndian(this.buffer, *(int*)&value); stream.Write(this.buffer.AsSpan(0, 4)); } + + private void WriteHalfSingle(Stream stream, float value) + { + ushort valueAsShort = HalfTypeHelper.Pack(value); + BinaryPrimitives.WriteUInt16LittleEndian(this.buffer, valueAsShort); + stream.Write(this.buffer.AsSpan(0, 2)); + } } } diff --git a/src/ImageSharp/Formats/OpenExr/ExrMetadata.cs b/src/ImageSharp/Formats/OpenExr/ExrMetadata.cs index 1e6eaf2bb6..7af01a38f0 100644 --- a/src/ImageSharp/Formats/OpenExr/ExrMetadata.cs +++ b/src/ImageSharp/Formats/OpenExr/ExrMetadata.cs @@ -24,7 +24,7 @@ namespace SixLabors.ImageSharp.Formats.OpenExr /// /// Gets or sets the pixel format. /// - public ExrPixelType PixelType { get; set; } = ExrPixelType.Half; + public ExrPixelType PixelType { get; set; } = ExrPixelType.Float; /// public IDeepCloneable DeepClone() => new ExrMetadata(this); diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/HalfVector4.cs b/src/ImageSharp/PixelFormats/PixelImplementations/HalfVector4.cs index 3d4298d803..94051e263e 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/HalfVector4.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/HalfVector4.cs @@ -4,7 +4,6 @@ using System; using System.Numerics; using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; namespace SixLabors.ImageSharp.PixelFormats { @@ -14,29 +13,8 @@ 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. /// @@ -45,43 +23,18 @@ 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.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 HalfVector4(Vector4 vector) => this.PackedValue = Pack(ref vector); /// - public ulong PackedValue - { - readonly get => this.HalfVector; - set => this.HalfVector = value; - } + public ulong PackedValue { get; set; } /// /// Compares two objects for equality. @@ -133,7 +86,11 @@ namespace SixLabors.ImageSharp.PixelFormats /// [MethodImpl(InliningOptions.ShortMethod)] - public readonly Vector4 ToVector4() => new(this.R, this.G, this.B, this.A); + 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))); /// [MethodImpl(InliningOptions.ShortMethod)]