Browse Source

Parse open EXR header

pull/3096/head
Brian Popow 4 years ago
parent
commit
9dda678aeb
  1. 11
      src/ImageSharp/Configuration.cs
  2. 5
      src/ImageSharp/Formats/Bmp/BmpArrayFileHeader.cs
  3. 4
      src/ImageSharp/Formats/Bmp/BmpConstants.cs
  4. 2
      src/ImageSharp/Formats/Bmp/BmpDecoder.cs
  5. 2
      src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs
  6. 6
      src/ImageSharp/Formats/Bmp/BmpFormat.cs
  7. 5
      src/ImageSharp/Formats/Bmp/BmpImageFormatDetector.cs
  8. 20
      src/ImageSharp/Formats/Bmp/BmpInfoHeader.cs
  9. 26
      src/ImageSharp/Formats/OpenExr/ExrAttribute.cs
  10. 24
      src/ImageSharp/Formats/OpenExr/ExrBox2i.cs
  11. 30
      src/ImageSharp/Formats/OpenExr/ExrChannelInfo.cs
  12. 10
      src/ImageSharp/Formats/OpenExr/ExrCompression.cs
  13. 18
      src/ImageSharp/Formats/OpenExr/ExrConfigurationModule.cs
  14. 28
      src/ImageSharp/Formats/OpenExr/ExrConstants.cs
  15. 61
      src/ImageSharp/Formats/OpenExr/ExrDecoder.cs
  16. 256
      src/ImageSharp/Formats/OpenExr/ExrDecoderCore.cs
  17. 38
      src/ImageSharp/Formats/OpenExr/ExrFormat.cs
  18. 71
      src/ImageSharp/Formats/OpenExr/ExrHeader.cs
  19. 31
      src/ImageSharp/Formats/OpenExr/ExrImageFormatDetector.cs
  20. 14
      src/ImageSharp/Formats/OpenExr/ExrLineOrder.cs
  21. 29
      src/ImageSharp/Formats/OpenExr/ExrMetadata.cs
  22. 23
      src/ImageSharp/Formats/OpenExr/ExrPixelType.cs
  23. 26
      src/ImageSharp/Formats/OpenExr/ExrThrowHelper.cs
  24. 12
      src/ImageSharp/Formats/OpenExr/IExrDecoderOptions.cs
  25. 4
      src/ImageSharp/Formats/OpenExr/README.md
  26. 26
      src/ImageSharp/Formats/Pbm/BufferedReadStreamExtensions.cs

11
src/ImageSharp/Configuration.cs

@ -8,6 +8,7 @@ using SixLabors.ImageSharp.Formats;
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;
@ -124,7 +125,7 @@ namespace SixLabors.ImageSharp
/// <summary>
/// Gets or sets the <see cref="ImageFormatManager"/> that is currently in use.
/// </summary>
public ImageFormatManager ImageFormatsManager { get; set; } = new ImageFormatManager();
public ImageFormatManager ImageFormatsManager { get; set; } = new();
/// <summary>
/// Gets or sets the <see cref="ImageSharp.Memory.MemoryAllocator"/> that is currently in use.
@ -192,7 +193,7 @@ namespace SixLabors.ImageSharp
/// Creates a shallow copy of the <see cref="Configuration"/>.
/// </summary>
/// <returns>A new configuration instance.</returns>
public Configuration Clone() => new Configuration
public Configuration Clone() => new()
{
MaxDegreeOfParallelism = this.MaxDegreeOfParallelism,
StreamProcessingBufferSize = this.StreamProcessingBufferSize,
@ -214,9 +215,10 @@ namespace SixLabors.ImageSharp
/// <see cref="TgaConfigurationModule"/>.
/// <see cref="TiffConfigurationModule"/>.
/// <see cref="WebpConfigurationModule"/>.
/// <see cref="ExrConfigurationModule"/>.
/// </summary>
/// <returns>The default configuration of <see cref="Configuration"/>.</returns>
internal static Configuration CreateDefaultInstance() => new Configuration(
internal static Configuration CreateDefaultInstance() => new(
new PngConfigurationModule(),
new JpegConfigurationModule(),
new GifConfigurationModule(),
@ -224,6 +226,7 @@ namespace SixLabors.ImageSharp
new PbmConfigurationModule(),
new TgaConfigurationModule(),
new TiffConfigurationModule(),
new WebpConfigurationModule());
new WebpConfigurationModule(),
new ExrConfigurationModule());
}
}

5
src/ImageSharp/Formats/Bmp/BmpArrayFileHeader.cs

@ -45,9 +45,6 @@ namespace SixLabors.ImageSharp.Formats.Bmp
/// </summary>
public short ScreenHeight { get; }
public static BmpArrayFileHeader Parse(Span<byte> data)
{
return MemoryMarshal.Cast<byte, BmpArrayFileHeader>(data)[0];
}
public static BmpArrayFileHeader Parse(Span<byte> data) => MemoryMarshal.Cast<byte, BmpArrayFileHeader>(data)[0];
}
}

4
src/ImageSharp/Formats/Bmp/BmpConstants.cs

@ -1,4 +1,4 @@
// Copyright (c) Six Labors.
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System.Collections.Generic;
@ -6,7 +6,7 @@ using System.Collections.Generic;
namespace SixLabors.ImageSharp.Formats.Bmp
{
/// <summary>
/// Defines constants relating to BMPs
/// Defines constants relating to BMP images.
/// </summary>
internal static class BmpConstants
{

2
src/ImageSharp/Formats/Bmp/BmpDecoder.cs

@ -4,8 +4,6 @@
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using SixLabors.ImageSharp.IO;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Formats.Bmp

2
src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs

@ -116,7 +116,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp
/// <summary>
/// Gets the dimensions of the image.
/// </summary>
public Size Dimensions => new Size(this.infoHeader.Width, this.infoHeader.Height);
public Size Dimensions => new(this.infoHeader.Width, this.infoHeader.Height);
/// <inheritdoc />
public Image<TPixel> Decode<TPixel>(BufferedReadStream stream, CancellationToken cancellationToken)

6
src/ImageSharp/Formats/Bmp/BmpFormat.cs

@ -1,4 +1,4 @@
// Copyright (c) Six Labors.
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System.Collections.Generic;
@ -17,7 +17,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp
/// <summary>
/// Gets the current instance.
/// </summary>
public static BmpFormat Instance { get; } = new BmpFormat();
public static BmpFormat Instance { get; } = new();
/// <inheritdoc/>
public string Name => "BMP";
@ -32,6 +32,6 @@ namespace SixLabors.ImageSharp.Formats.Bmp
public IEnumerable<string> FileExtensions => BmpConstants.FileExtensions;
/// <inheritdoc/>
public BmpMetadata CreateDefaultFormatMetadata() => new BmpMetadata();
public BmpMetadata CreateDefaultFormatMetadata() => new();
}
}

5
src/ImageSharp/Formats/Bmp/BmpImageFormatDetector.cs

@ -15,10 +15,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp
public int HeaderSize => 2;
/// <inheritdoc/>
public IImageFormat DetectFormat(ReadOnlySpan<byte> header)
{
return this.IsSupportedFileFormat(header) ? BmpFormat.Instance : null;
}
public IImageFormat DetectFormat(ReadOnlySpan<byte> header) => this.IsSupportedFileFormat(header) ? BmpFormat.Instance : null;
private bool IsSupportedFileFormat(ReadOnlySpan<byte> header)
{

20
src/ImageSharp/Formats/Bmp/BmpInfoHeader.cs

@ -279,15 +279,12 @@ namespace SixLabors.ImageSharp.Formats.Bmp
/// <param name="data">The data to parse.</param>
/// <returns>The parsed header.</returns>
/// <seealso href="https://msdn.microsoft.com/en-us/library/windows/desktop/dd183372.aspx"/>
public static BmpInfoHeader ParseCore(ReadOnlySpan<byte> data)
{
return new BmpInfoHeader(
public static BmpInfoHeader ParseCore(ReadOnlySpan<byte> data) => new(
headerSize: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(0, 4)),
width: BinaryPrimitives.ReadUInt16LittleEndian(data.Slice(4, 2)),
height: BinaryPrimitives.ReadUInt16LittleEndian(data.Slice(6, 2)),
planes: BinaryPrimitives.ReadInt16LittleEndian(data.Slice(8, 2)),
bitsPerPixel: BinaryPrimitives.ReadInt16LittleEndian(data.Slice(10, 2)));
}
/// <summary>
/// Parses a short variant of the OS22XBITMAPHEADER. It is identical to the BITMAPCOREHEADER, except that the width and height
@ -296,15 +293,12 @@ namespace SixLabors.ImageSharp.Formats.Bmp
/// <param name="data">The data to parse.</param>
/// <returns>The parsed header.</returns>
/// <seealso href="https://www.fileformat.info/format/os2bmp/egff.htm"/>
public static BmpInfoHeader ParseOs22Short(ReadOnlySpan<byte> data)
{
return new BmpInfoHeader(
public static BmpInfoHeader ParseOs22Short(ReadOnlySpan<byte> data) => new(
headerSize: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(0, 4)),
width: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(4, 4)),
height: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(8, 4)),
planes: BinaryPrimitives.ReadInt16LittleEndian(data.Slice(12, 2)),
bitsPerPixel: BinaryPrimitives.ReadInt16LittleEndian(data.Slice(14, 2)));
}
/// <summary>
/// Parses the full BMP Version 3 BITMAPINFOHEADER header (40 bytes).
@ -312,9 +306,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp
/// <param name="data">The data to parse.</param>
/// <returns>The parsed header.</returns>
/// <seealso href="http://www.fileformat.info/format/bmp/egff.htm"/>
public static BmpInfoHeader ParseV3(ReadOnlySpan<byte> data)
{
return new BmpInfoHeader(
public static BmpInfoHeader ParseV3(ReadOnlySpan<byte> data) => new(
headerSize: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(0, 4)),
width: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(4, 4)),
height: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(8, 4)),
@ -326,7 +318,6 @@ namespace SixLabors.ImageSharp.Formats.Bmp
yPelsPerMeter: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(28, 4)),
clrUsed: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(32, 4)),
clrImportant: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(36, 4)));
}
/// <summary>
/// Special case of the BITMAPINFOHEADER V3 used by adobe where the color bitmasks are part of the info header instead of following it.
@ -336,9 +327,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp
/// <param name="withAlpha">Indicates, if the alpha bitmask is present.</param>
/// <returns>The parsed header.</returns>
/// <seealso href="https://forums.adobe.com/message/3272950#3272950"/>
public static BmpInfoHeader ParseAdobeV3(ReadOnlySpan<byte> data, bool withAlpha = true)
{
return new BmpInfoHeader(
public static BmpInfoHeader ParseAdobeV3(ReadOnlySpan<byte> data, bool withAlpha = true) => new(
headerSize: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(0, 4)),
width: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(4, 4)),
height: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(8, 4)),
@ -354,7 +343,6 @@ namespace SixLabors.ImageSharp.Formats.Bmp
greenMask: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(44, 4)),
blueMask: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(48, 4)),
alphaMask: withAlpha ? BinaryPrimitives.ReadInt32LittleEndian(data.Slice(52, 4)) : 0);
}
/// <summary>
/// Parses a OS/2 version 2 bitmap header (64 bytes). Only the first 40 bytes are parsed which are

26
src/ImageSharp/Formats/OpenExr/ExrAttribute.cs

@ -0,0 +1,26 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System.Diagnostics;
namespace SixLabors.ImageSharp.Formats.OpenExr
{
[DebuggerDisplay("Name: {Name}, Type: {Type}, Length: {Length}")]
internal class ExrAttribute
{
public static readonly ExrAttribute EmptyAttribute = new(string.Empty, string.Empty, 0);
public ExrAttribute(string name, string type, int length)
{
this.Name = name;
this.Type = type;
this.Length = length;
}
public string Name { get; }
public string Type { get; }
public int Length { get; }
}
}

24
src/ImageSharp/Formats/OpenExr/ExrBox2i.cs

@ -0,0 +1,24 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
namespace SixLabors.ImageSharp.Formats.OpenExr
{
internal struct ExrBox2i
{
public ExrBox2i(int xMin, int yMin, int xMax, int yMax)
{
this.xMin = xMin;
this.yMin = yMin;
this.xMax = xMax;
this.yMax = yMax;
}
public int xMin { get; }
public int yMin { get; }
public int xMax { get; }
public int yMax { get; }
}
}

30
src/ImageSharp/Formats/OpenExr/ExrChannelInfo.cs

@ -0,0 +1,30 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System.Runtime.InteropServices;
namespace SixLabors.ImageSharp.Formats.OpenExr
{
[StructLayout(LayoutKind.Sequential, Pack = 1)]
internal readonly struct ExrChannelInfo
{
public ExrChannelInfo(string channelName, ExrPixelType pixelType, byte pLinear, int xSampling, int ySampling)
{
this.ChannelName = channelName;
this.PixelType = pixelType;
this.PLinear = pLinear;
this.XSampling = xSampling;
this.YSampling = ySampling;
}
public string ChannelName { get; }
public ExrPixelType PixelType { get; }
public byte PLinear { get; }
public int XSampling { get; }
public int YSampling { get; }
}
}

10
src/ImageSharp/Formats/OpenExr/ExrCompression.cs

@ -0,0 +1,10 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
namespace SixLabors.ImageSharp.Formats.OpenExr
{
internal enum ExrCompression : int
{
None = 0
}
}

18
src/ImageSharp/Formats/OpenExr/ExrConfigurationModule.cs

@ -0,0 +1,18 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
namespace SixLabors.ImageSharp.Formats.OpenExr
{
/// <summary>
/// Registers the image encoders, decoders and mime type detectors for the OpenExr format.
/// </summary>
public sealed class ExrConfigurationModule : IConfigurationModule
{
/// <inheritdoc/>
public void Configure(Configuration configuration)
{
configuration.ImageFormatsManager.SetDecoder(ExrFormat.Instance, new ExrDecoder());
configuration.ImageFormatsManager.AddImageFormatDetector(new ExrImageFormatDetector());
}
}
}

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

@ -0,0 +1,28 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System.Collections.Generic;
namespace SixLabors.ImageSharp.Formats.OpenExr
{
/// <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;
}
}

61
src/ImageSharp/Formats/OpenExr/ExrDecoder.cs

@ -0,0 +1,61 @@
// 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.PixelFormats;
namespace SixLabors.ImageSharp.Formats.OpenExr
{
/// <summary>
/// Image decoder for generating an image out of a OpenExr stream.
/// </summary>
public sealed class ExrDecoder : IImageDecoder, IExrDecoderOptions, IImageInfoDetector
{
/// <inheritdoc/>
public Image<TPixel> Decode<TPixel>(Configuration configuration, Stream stream)
where TPixel : unmanaged, IPixel<TPixel>
{
Guard.NotNull(stream, nameof(stream));
var decoder = new ExrDecoderCore(configuration, this);
return decoder.Decode<TPixel>(configuration, stream);
}
/// <inheritdoc />
public Image Decode(Configuration configuration, Stream stream)
=> this.Decode<Rgba32>(configuration, stream);
/// <inheritdoc/>
public Task<Image<TPixel>> DecodeAsync<TPixel>(Configuration configuration, Stream stream, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel>
{
Guard.NotNull(stream, nameof(stream));
var decoder = new ExrDecoderCore(configuration, this);
return decoder.DecodeAsync<TPixel>(configuration, stream, cancellationToken);
}
/// <inheritdoc />
public async Task<Image> DecodeAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken)
=> await this.DecodeAsync<Rgba32>(configuration, stream, cancellationToken)
.ConfigureAwait(false);
/// <inheritdoc/>
public IImageInfo Identify(Configuration configuration, Stream stream)
{
Guard.NotNull(stream, nameof(stream));
return new ExrDecoderCore(configuration, this).Identify(configuration, stream);
}
/// <inheritdoc/>
public Task<IImageInfo> IdentifyAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken)
{
Guard.NotNull(stream, nameof(stream));
return new ExrDecoderCore(configuration, this).IdentifyAsync(configuration, stream, cancellationToken);
}
}
}

256
src/ImageSharp/Formats/OpenExr/ExrDecoderCore.cs

@ -0,0 +1,256 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Buffers.Binary;
using System.Collections.Generic;
using System.Text;
using System.Threading;
using SixLabors.ImageSharp.Formats.Pbm;
using SixLabors.ImageSharp.IO;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.Metadata;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Formats.OpenExr
{
/// <summary>
/// Performs the OpenExr decoding operation.
/// </summary>
internal sealed class ExrDecoderCore : IImageDecoderInternals
{
/// <summary>
/// Reusable buffer.
/// </summary>
private readonly byte[] buffer = new byte[4];
/// <summary>
/// Used for allocating memory during processing operations.
/// </summary>
private readonly MemoryAllocator memoryAllocator;
/// <summary>
/// The bitmap decoder options.
/// </summary>
private readonly IExrDecoderOptions options;
/// <summary>
/// Initializes a new instance of the <see cref="ExrDecoderCore"/> class.
/// </summary>
/// <param name="configuration">The configuration.</param>
/// <param name="options">The options.</param>
public ExrDecoderCore(Configuration configuration, IExrDecoderOptions options)
{
this.Configuration = configuration;
this.memoryAllocator = configuration.MemoryAllocator;
this.options = options;
}
/// <inheritdoc />
public Configuration Configuration { get; }
/// <summary>
/// Gets the dimensions of the image.
/// </summary>
public Size Dimensions => new(this.Width, this.Height);
private int Width { get; set; }
private int Height { get; set; }
/// <inheritdoc />
public Image<TPixel> Decode<TPixel>(BufferedReadStream stream, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel> => throw new NotImplementedException();
/// <inheritdoc />
public IImageInfo Identify(BufferedReadStream stream, CancellationToken cancellationToken)
{
// Skip over the magick bytes.
stream.Read(this.buffer, 0, 4);
// Read version number.
byte version = (byte)stream.ReadByte();
if (version != 2)
{
ExrThrowHelper.ThrowNotSupportedVersion();
}
// Next three bytes contain info's about the image.
// We ignore those for now.
stream.Read(this.buffer, 0, 3);
ExrHeader header = this.ParseHeader(stream);
if (!header.IsValid())
{
ExrThrowHelper.ThrowInvalidImageHeader();
}
if (header.Channels.Count != 3)
{
ExrThrowHelper.ThrowNotSupported("Only 3 channels are supported!");
}
this.Width = header.DataWindow.Value.xMax - header.DataWindow.Value.xMin + 1;
this.Height = header.DataWindow.Value.yMax - header.DataWindow.Value.yMin + 1;
// TODO: calculate bits per pixel.
int bitsPerPixel = 48;
return new ImageInfo(new PixelTypeInfo(bitsPerPixel), this.Width, this.Height, new ImageMetadata());
}
private ExrHeader ParseHeader(BufferedReadStream stream)
{
ExrAttribute attribute = this.ReadAttribute(stream);
var header = new ExrHeader();
while (!attribute.Equals(ExrAttribute.EmptyAttribute))
{
switch (attribute.Name)
{
case "channels":
IList<ExrChannelInfo> channels = this.ParseChannelList(stream, attribute.Length);
header.Channels = channels;
break;
case "compression":
var compression = (ExrCompression)stream.ReadByte();
header.Compression = compression;
break;
case "dataWindow":
ExrBox2i dataWindow = this.ReadBox2i(stream);
header.DataWindow = dataWindow;
break;
case "displayWindow":
ExrBox2i displayWindow = this.ReadBox2i(stream);
header.DisplayWindow = displayWindow;
break;
case "lineOrder":
var lineOrder = (ExrLineOrder)stream.ReadByte();
header.LineOrder = lineOrder;
break;
case "pixelAspectRatio":
float aspectRatio = stream.ReadSingle(this.buffer);
header.AspectRatio = aspectRatio;
break;
case "screenWindowCenter":
float screenWindowCenterX = stream.ReadSingle(this.buffer);
float screenWindowCenterY = stream.ReadSingle(this.buffer);
header.ScreenWindowCenter = new PointF(screenWindowCenterX, screenWindowCenterY);
break;
case "screenWindowWidth":
float screenWindowWidth = stream.ReadSingle(this.buffer);
header.ScreenWindowWidth = screenWindowWidth;
break;
default:
// Skip unknown attribute bytes.
stream.Skip(attribute.Length);
break;
}
attribute = this.ReadAttribute(stream);
}
return header;
}
private ExrAttribute ReadAttribute(BufferedReadStream stream)
{
string attributeName = ReadString(stream);
if (attributeName.Equals(string.Empty))
{
return ExrAttribute.EmptyAttribute;
}
string attributeType = ReadString(stream);
stream.Read(this.buffer, 0, 4);
int attributeSize = BinaryPrimitives.ReadInt32LittleEndian(this.buffer);
return new ExrAttribute(attributeName, attributeType, attributeSize);
}
private ExrBox2i ReadBox2i(BufferedReadStream stream)
{
stream.Read(this.buffer, 0, 4);
int xMin = BinaryPrimitives.ReadInt32LittleEndian(this.buffer);
stream.Read(this.buffer, 0, 4);
int yMin = BinaryPrimitives.ReadInt32LittleEndian(this.buffer);
stream.Read(this.buffer, 0, 4);
int xMax = BinaryPrimitives.ReadInt32LittleEndian(this.buffer);
stream.Read(this.buffer, 0, 4);
int yMax = BinaryPrimitives.ReadInt32LittleEndian(this.buffer);
return new ExrBox2i(xMin, yMin, xMax, yMax);
}
private List<ExrChannelInfo> ParseChannelList(BufferedReadStream stream, int attributeSize)
{
var channels = new List<ExrChannelInfo>();
while (attributeSize > 1)
{
ExrChannelInfo channelInfo = this.ReadChannelInfo(stream, out int bytesRead);
channels.Add(channelInfo);
attributeSize -= bytesRead;
}
// Last byte should be a null byte.
int byteRead = stream.ReadByte();
return channels;
}
private ExrChannelInfo ReadChannelInfo(BufferedReadStream stream, out int bytesRead)
{
string channelName = ReadString(stream);
bytesRead = channelName.Length + 1;
stream.Read(this.buffer, 0, 4);
bytesRead += 4;
var pixelType = (ExrPixelType)BinaryPrimitives.ReadInt32LittleEndian(this.buffer);
byte pLinear = (byte)stream.ReadByte();
byte reserved0 = (byte)stream.ReadByte();
byte reserved1 = (byte)stream.ReadByte();
byte reserved2 = (byte)stream.ReadByte();
bytesRead += 4;
stream.Read(this.buffer, 0, 4);
bytesRead += 4;
int xSampling = BinaryPrimitives.ReadInt32LittleEndian(this.buffer);
stream.Read(this.buffer, 0, 4);
bytesRead += 4;
int ySampling = BinaryPrimitives.ReadInt32LittleEndian(this.buffer);
return new ExrChannelInfo(channelName, pixelType, pLinear, xSampling, ySampling);
}
private static string ReadString(BufferedReadStream stream)
{
var str = new StringBuilder();
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();
}
}
}

38
src/ImageSharp/Formats/OpenExr/ExrFormat.cs

@ -0,0 +1,38 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System.Collections.Generic;
using SixLabors.ImageSharp.Formats.Bmp;
namespace SixLabors.ImageSharp.Formats.OpenExr
{
/// <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();
}
}

71
src/ImageSharp/Formats/OpenExr/ExrHeader.cs

@ -0,0 +1,71 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System.Collections.Generic;
namespace SixLabors.ImageSharp.Formats.OpenExr
{
internal class ExrHeader
{
public IList<ExrChannelInfo> Channels { get; set; }
public ExrCompression? Compression { get; set; }
public ExrBox2i? DataWindow { get; set; }
public ExrBox2i? DisplayWindow { get; set; }
public ExrLineOrder? LineOrder { get; set; }
public float? AspectRatio { get; set; }
public float? ScreenWindowWidth { get; set; }
public PointF? ScreenWindowCenter { get; set; }
public bool IsValid()
{
if (!this.Compression.HasValue)
{
return false;
}
if (!this.DataWindow.HasValue)
{
return false;
}
if (!this.DisplayWindow.HasValue)
{
return false;
}
if (!this.LineOrder.HasValue)
{
return false;
}
if (!this.AspectRatio.HasValue)
{
return false;
}
if (!this.ScreenWindowWidth.HasValue)
{
return false;
}
if (!this.ScreenWindowCenter.HasValue)
{
return false;
}
if (this.Channels is null)
{
return false;
}
return true;
}
}
}

31
src/ImageSharp/Formats/OpenExr/ExrImageFormatDetector.cs

@ -0,0 +1,31 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Buffers.Binary;
namespace SixLabors.ImageSharp.Formats.OpenExr
{
/// <summary>
/// Detects OpenExr file headers.
/// </summary>
public sealed class ExrImageFormatDetector : IImageFormatDetector
{
/// <inheritdoc/>
public int HeaderSize => 4;
/// <inheritdoc/>
public IImageFormat DetectFormat(ReadOnlySpan<byte> header) => this.IsSupportedFileFormat(header) ? ExrFormat.Instance : null;
private bool IsSupportedFileFormat(ReadOnlySpan<byte> header)
{
if (header.Length >= this.HeaderSize)
{
int fileTypeMarker = BinaryPrimitives.ReadInt32LittleEndian(header);
return fileTypeMarker == ExrConstants.MagickBytes;
}
return false;
}
}
}

14
src/ImageSharp/Formats/OpenExr/ExrLineOrder.cs

@ -0,0 +1,14 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
namespace SixLabors.ImageSharp.Formats.OpenExr
{
internal enum ExrLineOrder : byte
{
IncreasingY = 0,
DecreasingY = 1,
RandomY = 2
}
}

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

@ -0,0 +1,29 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
namespace SixLabors.ImageSharp.Formats.OpenExr
{
/// <summary>
/// Provides OpenExr specific metadata information for the image.
/// </summary>
public class ExrMetadata : IDeepCloneable
{
/// <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)
{
}
/// <inheritdoc/>
public IDeepCloneable DeepClone() => new ExrMetadata(this);
}
}

23
src/ImageSharp/Formats/OpenExr/ExrPixelType.cs

@ -0,0 +1,23 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
namespace SixLabors.ImageSharp.Formats.OpenExr
{
internal enum ExrPixelType : int
{
/// <summary>
/// unsigned int (32 bit).
/// </summary>
Uint = 0,
/// <summary>
/// half (16 bit floating point).
/// </summary>
Half = 1,
/// <summary>
/// float (32 bit floating point).
/// </summary>
Float = 2
}
}

26
src/ImageSharp/Formats/OpenExr/ExrThrowHelper.cs

@ -0,0 +1,26 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Runtime.CompilerServices;
namespace SixLabors.ImageSharp.Formats.OpenExr
{
/// <summary>
/// Cold path optimizations for throwing exr format based exceptions.
/// </summary>
internal static class ExrThrowHelper
{
[MethodImpl(InliningOptions.ColdPath)]
public static void ThrowNotSupportedVersion() => throw new NotSupportedException("Unsupported EXR version");
[MethodImpl(InliningOptions.ColdPath)]
public static void ThrowNotSupported(string msg) => throw new NotSupportedException(msg);
[MethodImpl(InliningOptions.ColdPath)]
public static void ThrowInvalidImageHeader() => throw new InvalidImageContentException("Invalid EXR image header");
[MethodImpl(InliningOptions.ColdPath)]
public static void ThrowInvalidImageHeader(string msg) => throw new InvalidImageContentException(msg);
}
}

12
src/ImageSharp/Formats/OpenExr/IExrDecoderOptions.cs

@ -0,0 +1,12 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
namespace SixLabors.ImageSharp.Formats.OpenExr
{
/// <summary>
/// Image decoder options for decoding OpenExr streams.
/// </summary>
internal interface IExrDecoderOptions
{
}
}

4
src/ImageSharp/Formats/OpenExr/README.md

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

26
src/ImageSharp/Formats/Pbm/BufferedReadStreamExtensions.cs

@ -1,7 +1,10 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Buffers.Binary;
using System.IO;
using System.Runtime.CompilerServices;
using SixLabors.ImageSharp.IO;
namespace SixLabors.ImageSharp.Formats.Pbm
@ -61,5 +64,28 @@ namespace SixLabors.ImageSharp.Formats.Pbm
return value;
}
/// <summary>
/// Reads a float.
/// </summary>
/// <param name="stream">The stream to read from.</param>
/// <param name="scratch">A scratch buffer of size 4 bytes.</param>
/// <param name="byteOrder">The byte order. Defaults to little endian.</param>
/// <returns>the value.</returns>
public static float ReadSingle(this BufferedReadStream stream, Span<byte> scratch, ByteOrder byteOrder = ByteOrder.LittleEndian)
{
stream.Read(scratch, 0, 4);
int intValue;
if (byteOrder == ByteOrder.LittleEndian)
{
intValue = BinaryPrimitives.ReadInt32LittleEndian(scratch);
}
else
{
intValue = BinaryPrimitives.ReadInt32BigEndian(scratch);
}
return Unsafe.As<int, float>(ref intValue);
}
}
}

Loading…
Cancel
Save