Browse Source

Initialize omitted EXR color channels

pull/3187/head
James Jackson-South 3 weeks ago
parent
commit
6f9d16910d
  1. 6
      src/ImageSharp/Formats/Exr/ExrDecoderCore.cs
  2. 56
      tests/ImageSharp.Tests/Formats/Exr/ExrZipDecoderTests.cs

6
src/ImageSharp/Formats/Exr/ExrDecoderCore.cs

@ -166,7 +166,8 @@ internal sealed class ExrDecoderCore : ImageDecoderCore
int height = this.Height; int height = this.Height;
int channelCount = this.Channels.Count; int channelCount = this.Channels.Count;
using IMemoryOwner<float> rowBuffer = this.memoryAllocator.Allocate<float>(width * 4); // EXR can omit color channels. Initialize their planes once so absent channels remain black on every row.
using IMemoryOwner<float> rowBuffer = this.memoryAllocator.Allocate<float>(width * 4, AllocationOptions.Clean);
using IMemoryOwner<byte> decompressedPixelDataBuffer = this.memoryAllocator.Allocate<byte>((int)bytesPerBlock); using IMemoryOwner<byte> decompressedPixelDataBuffer = this.memoryAllocator.Allocate<byte>((int)bytesPerBlock);
Span<byte> decompressedPixelData = decompressedPixelDataBuffer.GetSpan(); Span<byte> decompressedPixelData = decompressedPixelDataBuffer.GetSpan();
Span<float> redPixelData = rowBuffer.GetSpan()[..width]; Span<float> redPixelData = rowBuffer.GetSpan()[..width];
@ -254,7 +255,8 @@ internal sealed class ExrDecoderCore : ImageDecoderCore
int height = this.Height; int height = this.Height;
int channelCount = this.Channels.Count; int channelCount = this.Channels.Count;
using IMemoryOwner<uint> rowBuffer = this.memoryAllocator.Allocate<uint>(width * 4); // EXR can omit color channels. Initialize their planes once so absent channels remain black on every row.
using IMemoryOwner<uint> rowBuffer = this.memoryAllocator.Allocate<uint>(width * 4, AllocationOptions.Clean);
using IMemoryOwner<byte> decompressedPixelDataBuffer = this.memoryAllocator.Allocate<byte>((int)bytesPerBlock); using IMemoryOwner<byte> decompressedPixelDataBuffer = this.memoryAllocator.Allocate<byte>((int)bytesPerBlock);
Span<byte> decompressedPixelData = decompressedPixelDataBuffer.GetSpan(); Span<byte> decompressedPixelData = decompressedPixelDataBuffer.GetSpan();
Span<uint> redPixelData = rowBuffer.GetSpan()[..width]; Span<uint> redPixelData = rowBuffer.GetSpan()[..width];

56
tests/ImageSharp.Tests/Formats/Exr/ExrZipDecoderTests.cs

@ -3,8 +3,12 @@
using System.Buffers.Binary; using System.Buffers.Binary;
using System.IO.Compression; using System.IO.Compression;
using System.Numerics;
using System.Text; using System.Text;
using SixLabors.ImageSharp.Formats;
using SixLabors.ImageSharp.Formats.Exr.Constants;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Tests.Memory;
namespace SixLabors.ImageSharp.Tests.Formats.Exr; namespace SixLabors.ImageSharp.Tests.Formats.Exr;
@ -15,11 +19,48 @@ public class ExrZipDecoderTests
[Fact] [Fact]
public void Decode_ShortInflatedBlock_Throws() public void Decode_ShortInflatedBlock_Throws()
{ {
byte[] data = BuildExr(ZlibCompress(new byte[8])); byte[] data = BuildExr(ZlibCompress(new byte[8]), ExrPixelType.Float, 2);
Assert.Throws<InvalidImageContentException>(() => Image.Load<RgbaVector>(data)); Assert.Throws<InvalidImageContentException>(() => Image.Load<RgbaVector>(data));
} }
/// <summary>
/// Missing color channels must not inherit the allocator's previous contents.
/// </summary>
/// <param name="pixelType">The stored sample type.</param>
/// <param name="compression">The ZIP compression code.</param>
[Theory]
[InlineData(ExrPixelType.Half, 2)]
[InlineData(ExrPixelType.Float, 2)]
[InlineData(ExrPixelType.UnsignedInt, 2)]
[InlineData(ExrPixelType.Half, 3)]
[InlineData(ExrPixelType.Float, 3)]
[InlineData(ExrPixelType.UnsignedInt, 3)]
public void Decode_SingleRedChannel_InitializesMissingColorChannels(ExrPixelType pixelType, byte compression)
{
byte[] predicted = new byte[256 * (pixelType == ExrPixelType.Half ? 2 : 4)];
// A zero first byte followed by 128-valued differences reconstructs an all-zero sample plane.
predicted.AsSpan(1).Fill(128);
byte[] data = BuildExr(ZlibCompress(predicted), pixelType, compression);
Configuration configuration = Configuration.Default.Clone();
configuration.MemoryAllocator = new TestMemoryAllocator(0x3F);
DecoderOptions options = new() { Configuration = configuration };
using Image<RgbaVector> image = Image.Load<RgbaVector>(options, data);
Assert.Equal(new Size(256, 1), image.Size);
for (int x = 0; x < image.Width; x++)
{
Assert.Equal(new Vector4(0, 0, 0, 1), image[x, 0].ToVector4());
}
}
/// <summary>
/// Compresses the predictor bytes for a scanline block.
/// </summary>
/// <param name="data">The predictor bytes.</param>
/// <returns>The zlib stream.</returns>
private static byte[] ZlibCompress(byte[] data) private static byte[] ZlibCompress(byte[] data)
{ {
using MemoryStream output = new(); using MemoryStream output = new();
@ -31,7 +72,14 @@ public class ExrZipDecoderTests
return output.ToArray(); return output.ToArray();
} }
private static byte[] BuildExr(byte[] compressed) /// <summary>
/// Builds a single-row EXR containing only the red channel.
/// </summary>
/// <param name="compressed">The compressed scanline bytes.</param>
/// <param name="pixelType">The stored sample type.</param>
/// <param name="compression">The ZIP compression code.</param>
/// <returns>The encoded image.</returns>
private static byte[] BuildExr(byte[] compressed, ExrPixelType pixelType, byte compression)
{ {
const int width = 256; const int width = 256;
const int height = 1; const int height = 1;
@ -47,7 +95,7 @@ public class ExrZipDecoderTests
using (BinaryWriter channelWriter = new(channelStream)) using (BinaryWriter channelWriter = new(channelStream))
{ {
WriteString(channelWriter, "R"); WriteString(channelWriter, "R");
channelWriter.Write(2); channelWriter.Write((int)pixelType);
channelWriter.Write((byte)0); channelWriter.Write((byte)0);
channelWriter.Write(new byte[] { 0, 0, 0 }); channelWriter.Write(new byte[] { 0, 0, 0 });
channelWriter.Write(1); channelWriter.Write(1);
@ -57,7 +105,7 @@ public class ExrZipDecoderTests
WriteAttribute(writer, "channels", "chlist", channelStream.ToArray()); WriteAttribute(writer, "channels", "chlist", channelStream.ToArray());
} }
WriteAttribute(writer, "compression", "compression", [2]); WriteAttribute(writer, "compression", "compression", [compression]);
using (MemoryStream boxStream = new()) using (MemoryStream boxStream = new())
using (BinaryWriter boxWriter = new(boxStream)) using (BinaryWriter boxWriter = new(boxStream))

Loading…
Cancel
Save