Browse Source

Encode EXR image with HalfSingle pixel data

pull/3096/head
Brian Popow 4 years ago
parent
commit
4cf95a52df
  1. 25
      src/ImageSharp/Formats/ImageExtensions.Save.cs
  2. 3
      src/ImageSharp/Formats/OpenExr/ExrBox2i.cs
  3. 37
      src/ImageSharp/Formats/OpenExr/ExrConstants.cs
  4. 16
      src/ImageSharp/Formats/OpenExr/ExrDecoderCore.cs
  5. 103
      src/ImageSharp/Formats/OpenExr/ExrEncoderCore.cs
  6. 2
      src/ImageSharp/Formats/OpenExr/ExrMetadata.cs
  7. 59
      src/ImageSharp/PixelFormats/PixelImplementations/HalfVector4.cs

25
src/ImageSharp/Formats/ImageExtensions.Save.cs

@ -848,14 +848,13 @@ namespace SixLabors.ImageSharp
encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(TiffFormat.Instance),
cancellationToken);
// foo
/// <summary>
/// Saves the image to the given stream with the Open Exr format.
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="path">The file path to save the image to.</param>
/// <exception cref="System.ArgumentNullException">Thrown if the path is null.</exception>
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);
/// <summary>
/// Saves the image to the given stream with the Open Exr format.
@ -864,7 +863,7 @@ namespace SixLabors.ImageSharp
/// <param name="path">The file path to save the image to.</param>
/// <exception cref="System.ArgumentNullException">Thrown if the path is null.</exception>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
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);
/// <summary>
/// Saves the image to the given stream with the Open Exr format.
@ -874,8 +873,8 @@ namespace SixLabors.ImageSharp
/// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
/// <exception cref="System.ArgumentNullException">Thrown if the path is null.</exception>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
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);
/// <summary>
/// Saves the image to the given stream with the Open Exr format.
@ -884,7 +883,7 @@ namespace SixLabors.ImageSharp
/// <param name="path">The file path to save the image to.</param>
/// <param name="encoder">The encoder to save the image with.</param>
/// <exception cref="System.ArgumentNullException">Thrown if the path is null.</exception>
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
/// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
/// <exception cref="System.ArgumentNullException">Thrown if the path is null.</exception>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
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
/// <param name="source">The image this method extends.</param>
/// <param name="stream">The stream to save the image to.</param>
/// <exception cref="System.ArgumentNullException">Thrown if the stream is null.</exception>
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);
/// <summary>
/// Saves the image to the given stream with the Open Exr format.
@ -921,8 +920,8 @@ namespace SixLabors.ImageSharp
/// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
/// <exception cref="System.ArgumentNullException">Thrown if the stream is null.</exception>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
public static Task 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);
/// <summary>
/// Saves the image to the given stream with the Open Exr format.
@ -932,7 +931,7 @@ namespace SixLabors.ImageSharp
/// <param name="encoder">The encoder to save the image with.</param>
/// <exception cref="System.ArgumentNullException">Thrown if the stream is null.</exception>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
public static void 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
/// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
/// <exception cref="System.ArgumentNullException">Thrown if the stream is null.</exception>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
public static Task 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),

3
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)

37
src/ImageSharp/Formats/OpenExr/ExrConstants.cs

@ -24,5 +24,42 @@ namespace SixLabors.ImageSharp.Formats.OpenExr
/// 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";
}
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";
}
}
}

16
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<ExrChannelInfo> 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;

103
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
/// </summary>
private readonly MemoryAllocator memoryAllocator;
/// <summary>
/// The global configuration.
/// </summary>
private Configuration configuration;
/// <summary>
/// The pixel type of the image.
/// </summary>
@ -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<ExrChannelInfo>()
{
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<float> 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<float> blueBuffer, Span<float> greenBuffer, Span<float> 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<float> blueBuffer, Span<float> greenBuffer, Span<float> 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));
}
}
}

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

@ -24,7 +24,7 @@ namespace SixLabors.ImageSharp.Formats.OpenExr
/// <summary>
/// Gets or sets the pixel format.
/// </summary>
public ExrPixelType PixelType { get; set; } = ExrPixelType.Half;
public ExrPixelType PixelType { get; set; } = ExrPixelType.Float;
/// <inheritdoc/>
public IDeepCloneable DeepClone() => new ExrMetadata(this);

59
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.
/// </para>
/// </summary>
[StructLayout(LayoutKind.Sequential)]
public partial struct HalfVector4 : IPixel<HalfVector4>, IPackedVector<ulong>
{
/// <summary>
/// Gets or sets the red component.
/// </summary>
public float R;
/// <summary>
/// Gets or sets the green component.
/// </summary>
public float G;
/// <summary>
/// Gets or sets the blue component.
/// </summary>
public float B;
/// <summary>
/// Gets or sets the alpha component.
/// </summary>
public float A;
/// <summary>
/// Initializes a new instance of the <see cref="HalfVector4"/> struct.
/// </summary>
@ -45,43 +23,18 @@ namespace SixLabors.ImageSharp.PixelFormats
/// <param name="z">The z-component.</param>
/// <param name="w">The w-component.</param>
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;
}
/// <summary>
/// Initializes a new instance of the <see cref="HalfVector4"/> struct.
/// </summary>
/// <param name="vector">A vector containing the initial values for the components</param>
public HalfVector4(Vector4 vector)
{
this.R = vector.X;
this.G = vector.Y;
this.B = vector.Z;
this.A = vector.W;
}
/// <summary>
/// Gets or sets the packed representation of the HalfVector4 struct.
/// </summary>
public ulong HalfVector
{
[MethodImpl(InliningOptions.ShortMethod)]
readonly get => Unsafe.As<HalfVector4, ulong>(ref Unsafe.AsRef(this));
[MethodImpl(InliningOptions.ShortMethod)]
set => Unsafe.As<HalfVector4, ulong>(ref this) = value;
}
public HalfVector4(Vector4 vector) => this.PackedValue = Pack(ref vector);
/// <inheritdoc/>
public ulong PackedValue
{
readonly get => this.HalfVector;
set => this.HalfVector = value;
}
public ulong PackedValue { get; set; }
/// <summary>
/// Compares two <see cref="HalfVector4"/> objects for equality.
@ -133,7 +86,11 @@ namespace SixLabors.ImageSharp.PixelFormats
/// <inheritdoc />
[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)));
/// <inheritdoc />
[MethodImpl(InliningOptions.ShortMethod)]

Loading…
Cancel
Save