From 7e7472d575d6510b3d8cca1d7c4ebf115caa2d9a Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Sat, 1 Jan 2022 18:09:31 +0100 Subject: [PATCH] Encode EXR image with usigned int pixel data --- .../Formats/OpenExr/ExrChannelInfo.cs | 2 + .../Formats/OpenExr/ExrDecoderCore.cs | 14 +-- .../Formats/OpenExr/ExrEncoderCore.cs | 98 +++++++++++++++++-- src/ImageSharp/Image.Decode.cs | 2 - 4 files changed, 98 insertions(+), 18 deletions(-) diff --git a/src/ImageSharp/Formats/OpenExr/ExrChannelInfo.cs b/src/ImageSharp/Formats/OpenExr/ExrChannelInfo.cs index 81b40ea66f..28a014853d 100644 --- a/src/ImageSharp/Formats/OpenExr/ExrChannelInfo.cs +++ b/src/ImageSharp/Formats/OpenExr/ExrChannelInfo.cs @@ -1,10 +1,12 @@ // Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. +using System.Diagnostics; using System.Runtime.InteropServices; namespace SixLabors.ImageSharp.Formats.OpenExr { + [DebuggerDisplay("Name: {ChannelName}, PixelType: {PixelType}")] [StructLayout(LayoutKind.Sequential, Pack = 1)] internal readonly struct ExrChannelInfo { diff --git a/src/ImageSharp/Formats/OpenExr/ExrDecoderCore.cs b/src/ImageSharp/Formats/OpenExr/ExrDecoderCore.cs index 46d3941f36..053adbd074 100644 --- a/src/ImageSharp/Formats/OpenExr/ExrDecoderCore.cs +++ b/src/ImageSharp/Formats/OpenExr/ExrDecoderCore.cs @@ -106,8 +106,8 @@ namespace SixLabors.ImageSharp.Formats.OpenExr { using IMemoryOwner rowBuffer = this.memoryAllocator.Allocate(this.Width * 3); Span redPixelData = rowBuffer.GetSpan().Slice(0, this.Width); - Span bluePixelData = rowBuffer.GetSpan().Slice(this.Width, this.Width); - Span greenPixelData = rowBuffer.GetSpan().Slice(this.Width * 2, this.Width); + Span greenPixelData = rowBuffer.GetSpan().Slice(this.Width, this.Width); + Span bluePixelData = rowBuffer.GetSpan().Slice(this.Width * 2, this.Width); TPixel color = default; for (int y = 0; y < this.Height; y++) @@ -193,8 +193,8 @@ namespace SixLabors.ImageSharp.Formats.OpenExr { using IMemoryOwner rowBuffer = this.memoryAllocator.Allocate(this.Width * 3); Span redPixelData = rowBuffer.GetSpan().Slice(0, this.Width); - Span bluePixelData = rowBuffer.GetSpan().Slice(this.Width, this.Width); - Span greenPixelData = rowBuffer.GetSpan().Slice(this.Width * 2, this.Width); + Span greenPixelData = rowBuffer.GetSpan().Slice(this.Width, this.Width); + Span bluePixelData = rowBuffer.GetSpan().Slice(this.Width * 2, this.Width); TPixel color = default; for (int y = 0; y < this.Height; y++) @@ -389,11 +389,11 @@ namespace SixLabors.ImageSharp.Formats.OpenExr header.Compression = (ExrCompression)stream.ReadByte(); break; case ExrConstants.AttributeNames.DataWindow: - ExrBox2i dataWindow = this.ReadBox2i(stream); + ExrBox2i dataWindow = this.ReadBoxInteger(stream); header.DataWindow = dataWindow; break; case ExrConstants.AttributeNames.DisplayWindow: - ExrBox2i displayWindow = this.ReadBox2i(stream); + ExrBox2i displayWindow = this.ReadBoxInteger(stream); header.DisplayWindow = displayWindow; break; case ExrConstants.AttributeNames.LineOrder: @@ -441,7 +441,7 @@ namespace SixLabors.ImageSharp.Formats.OpenExr return new ExrAttribute(attributeName, attributeType, attributeSize); } - private ExrBox2i ReadBox2i(BufferedReadStream stream) + private ExrBox2i ReadBoxInteger(BufferedReadStream stream) { stream.Read(this.buffer, 0, 4); int xMin = BinaryPrimitives.ReadInt32LittleEndian(this.buffer); diff --git a/src/ImageSharp/Formats/OpenExr/ExrEncoderCore.cs b/src/ImageSharp/Formats/OpenExr/ExrEncoderCore.cs index e3c4376708..221877a063 100644 --- a/src/ImageSharp/Formats/OpenExr/ExrEncoderCore.cs +++ b/src/ImageSharp/Formats/OpenExr/ExrEncoderCore.cs @@ -6,6 +6,7 @@ using System.Buffers; using System.Buffers.Binary; using System.Collections.Generic; using System.IO; +using System.Runtime.CompilerServices; using System.Threading; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Metadata; @@ -57,6 +58,8 @@ namespace SixLabors.ImageSharp.Formats.OpenExr Guard.NotNull(image, nameof(image)); Guard.NotNull(stream, nameof(stream)); + Buffer2D pixels = image.Frames.RootFrame.PixelBuffer; + ImageMetadata metadata = image.Metadata; ExrMetadata exrMetadata = metadata.GetExrMetadata(); this.pixelType ??= exrMetadata.PixelType; @@ -86,7 +89,7 @@ namespace SixLabors.ImageSharp.Formats.OpenExr // Version number. this.buffer[0] = 2; - // Second, third and fourth bytes store info about the image, all set to zero. + // Second, third and fourth bytes store info about the image, set all to default: zero. this.buffer[1] = 0; this.buffer[2] = 0; this.buffer[3] = 0; @@ -95,18 +98,33 @@ namespace SixLabors.ImageSharp.Formats.OpenExr // Write EXR header. this.WriteHeader(stream, header); + // Write offsets to each pixel row. + int bytesPerChannel = this.pixelType == ExrPixelType.Half ? 2 : 4; + int numberOfChannels = 3; + uint rowSizeBytes = (uint)(width * numberOfChannels * bytesPerChannel); + this.WriteRowOffsets(stream, height, rowSizeBytes); + // Write pixel data. - Buffer2D pixels = image.Frames.RootFrame.PixelBuffer; + switch (this.pixelType) + { + case ExrPixelType.Half: + case ExrPixelType.Float: + this.EncodeFloatingPointPixelData(stream, pixels, width, height, rowSizeBytes); + break; + case ExrPixelType.UnsignedInt: + this.EncodeUnsignedIntPixelData(stream, pixels, width, height, rowSizeBytes); + break; + } + } + + private void EncodeFloatingPointPixelData(Stream stream, Buffer2D pixels, int width, int height, uint rowSizeBytes) + where TPixel : unmanaged, IPixel + { 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); + Span greenBuffer = rgbBuffer.GetSpan().Slice(width, width); + Span blueBuffer = rgbBuffer.GetSpan().Slice(width * 2, width); - // Write offsets to each pixel row. - 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++) { Span pixelRowSpan = pixels.DangerousGetRowSpan(y); @@ -139,6 +157,41 @@ namespace SixLabors.ImageSharp.Formats.OpenExr } } + private void EncodeUnsignedIntPixelData(Stream stream, Buffer2D pixels, int width, int height, uint rowSizeBytes) + where TPixel : unmanaged, IPixel + { + using IMemoryOwner rgbBuffer = this.memoryAllocator.Allocate(width * 3); + Span redBuffer = rgbBuffer.GetSpan().Slice(0, width); + Span greenBuffer = rgbBuffer.GetSpan().Slice(width, width); + Span blueBuffer = rgbBuffer.GetSpan().Slice(width * 2, width); + + var rgb = default(Rgb96); + for (int y = 0; y < height; y++) + { + Span pixelRowSpan = pixels.DangerousGetRowSpan(y); + + for (int x = 0; x < width; x++) + { + var vector4 = pixelRowSpan[x].ToVector4(); + rgb.FromVector4(vector4); + + redBuffer[x] = rgb.R; + greenBuffer[x] = rgb.G; + blueBuffer[x] = rgb.B; + } + + // 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)); + + this.WriteUnsignedIntRow(stream, width, blueBuffer, greenBuffer, redBuffer); + } + } + private void WriteHeader(Stream stream, ExrHeader header) { this.WriteChannels(stream, header.Channels); @@ -188,6 +241,24 @@ namespace SixLabors.ImageSharp.Formats.OpenExr } } + private void WriteUnsignedIntRow(Stream stream, int width, Span blueBuffer, Span greenBuffer, Span redBuffer) + { + for (int x = 0; x < width; x++) + { + this.WriteUnsignedInt(stream, blueBuffer[x]); + } + + for (int x = 0; x < width; x++) + { + this.WriteUnsignedInt(stream, greenBuffer[x]); + } + + for (int x = 0; x < width; x++) + { + this.WriteUnsignedInt(stream, redBuffer[x]); + } + } + private void WriteRowOffsets(Stream stream, int height, uint rowSizeBytes) { ulong startOfPixelData = (ulong)stream.Position + (8 * (ulong)height); @@ -325,17 +396,26 @@ namespace SixLabors.ImageSharp.Formats.OpenExr stream.Write(this.buffer.AsSpan(0, 4)); } + [MethodImpl(InliningOptions.ShortMethod)] private unsafe void WriteSingle(Stream stream, float value) { BinaryPrimitives.WriteInt32LittleEndian(this.buffer, *(int*)&value); stream.Write(this.buffer.AsSpan(0, 4)); } + [MethodImpl(InliningOptions.ShortMethod)] private void WriteHalfSingle(Stream stream, float value) { ushort valueAsShort = HalfTypeHelper.Pack(value); BinaryPrimitives.WriteUInt16LittleEndian(this.buffer, valueAsShort); stream.Write(this.buffer.AsSpan(0, 2)); } + + [MethodImpl(InliningOptions.ShortMethod)] + private void WriteUnsignedInt(Stream stream, uint value) + { + BinaryPrimitives.WriteUInt32LittleEndian(this.buffer, value); + stream.Write(this.buffer.AsSpan(0, 4)); + } } } diff --git a/src/ImageSharp/Image.Decode.cs b/src/ImageSharp/Image.Decode.cs index ee340bf86e..3e1ffe9baf 100644 --- a/src/ImageSharp/Image.Decode.cs +++ b/src/ImageSharp/Image.Decode.cs @@ -2,9 +2,7 @@ // Licensed under the Apache License, Version 2.0. using System; -using System.Buffers; using System.IO; -using System.Linq; using System.Threading; using System.Threading.Tasks; using SixLabors.ImageSharp.Formats;