Skip to content

Commit 936a65b

Browse files
Merge pull request #3124 from SixLabors/bp/decodePxr24
Add support for decoding PXR24 compressed Exr images
2 parents d7c7eb6 + 1d99ec2 commit 936a65b

22 files changed

Lines changed: 337 additions & 92 deletions

src/ImageSharp/Formats/Exr/Compression/Compressors/NoneExrCompressor.cs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,10 @@ internal class NoneExrCompressor : ExrBaseCompressor
1717
/// <param name="allocator">The memory allocator.</param>
1818
/// <param name="bytesPerBlock">Bytes per row block.</param>
1919
/// <param name="bytesPerRow">Bytes per pixel row.</param>
20-
public NoneExrCompressor(Stream output, MemoryAllocator allocator, uint bytesPerBlock, uint bytesPerRow)
21-
: base(output, allocator, bytesPerBlock, bytesPerRow)
20+
/// <param name="rowsPerBlock">The pixel rows per block.</param>
21+
/// <param name="width">The witdh of one row in pixels.</param>
22+
public NoneExrCompressor(Stream output, MemoryAllocator allocator, uint bytesPerBlock, uint bytesPerRow, uint rowsPerBlock, int width)
23+
: base(output, allocator, bytesPerBlock, bytesPerRow, rowsPerBlock, width)
2224
{
2325
}
2426

src/ImageSharp/Formats/Exr/Compression/Compressors/ZipExrCompressor.cs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,11 @@ internal class ZipExrCompressor : ExrBaseCompressor
2424
/// <param name="allocator">The memory allocator.</param>
2525
/// <param name="bytesPerBlock">The bytes per block.</param>
2626
/// <param name="bytesPerRow">The bytes per row.</param>
27+
/// <param name="rowsPerBlock">The pixel rows per block.</param>
28+
/// <param name="width">The witdh of one row in pixels.</param>
2729
/// <param name="compressionLevel">The compression level for deflate compression.</param>
28-
public ZipExrCompressor(Stream output, MemoryAllocator allocator, uint bytesPerBlock, uint bytesPerRow, DeflateCompressionLevel compressionLevel)
29-
: base(output, allocator, bytesPerBlock, bytesPerRow)
30+
public ZipExrCompressor(Stream output, MemoryAllocator allocator, uint bytesPerBlock, uint bytesPerRow, uint rowsPerBlock, int width, DeflateCompressionLevel compressionLevel)
31+
: base(output, allocator, bytesPerBlock, bytesPerRow, rowsPerBlock, width)
3032
{
3133
this.compressionLevel = compressionLevel;
3234
this.buffer = allocator.Allocate<byte>((int)bytesPerBlock);

src/ImageSharp/Formats/Exr/Compression/Decompressors/B44ExrCompression.cs

Lines changed: 22 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,6 @@ namespace SixLabors.ImageSharp.Formats.Exr.Compression.Decompressors;
1313
/// </summary>
1414
internal class B44ExrCompression : ExrBaseDecompressor
1515
{
16-
private readonly int width;
17-
18-
private readonly uint rowsPerBlock;
19-
2016
private readonly int channelCount;
2117

2218
private readonly byte[] scratch = new byte[14];
@@ -31,14 +27,12 @@ internal class B44ExrCompression : ExrBaseDecompressor
3127
/// <param name="allocator">The memory allocator.</param>
3228
/// <param name="bytesPerBlock">The bytes per pixel row block.</param>
3329
/// <param name="bytesPerRow">The bytes per row.</param>
34-
/// <param name="rowsPerBlock">The rows per block.</param>
30+
/// <param name="rowsPerBlock">The pixel rows per block.</param>
3531
/// <param name="width">The width of a pixel row in pixels.</param>
3632
/// <param name="channelCount">The number of channels of the image.</param>
3733
public B44ExrCompression(MemoryAllocator allocator, uint bytesPerBlock, uint bytesPerRow, uint rowsPerBlock, int width, int channelCount)
38-
: base(allocator, bytesPerBlock, bytesPerRow)
34+
: base(allocator, bytesPerBlock, bytesPerRow, rowsPerBlock, width)
3935
{
40-
this.width = width;
41-
this.rowsPerBlock = rowsPerBlock;
4236
this.channelCount = channelCount;
4337
this.tmpBuffer = allocator.Allocate<ushort>((int)(width * rowsPerBlock * channelCount));
4438
}
@@ -52,26 +46,27 @@ public override void Decompress(BufferedReadStream stream, uint compressedBytes,
5246
int bytesLeft = (int)compressedBytes;
5347
for (int i = 0; i < this.channelCount && bytesLeft > 0; i++)
5448
{
55-
for (int y = 0; y < this.rowsPerBlock; y += 4)
49+
for (int y = 0; y < this.RowsPerBlock; y += 4)
5650
{
57-
Span<ushort> row0 = decompressed.Slice(outputOffset, this.width);
58-
outputOffset += this.width;
59-
Span<ushort> row1 = decompressed.Slice(outputOffset, this.width);
60-
outputOffset += this.width;
61-
Span<ushort> row2 = decompressed.Slice(outputOffset, this.width);
62-
outputOffset += this.width;
63-
Span<ushort> row3 = decompressed.Slice(outputOffset, this.width);
64-
outputOffset += this.width;
51+
Span<ushort> row0 = decompressed.Slice(outputOffset, this.Width);
52+
outputOffset += this.Width;
53+
Span<ushort> row1 = decompressed.Slice(outputOffset, this.Width);
54+
outputOffset += this.Width;
55+
Span<ushort> row2 = decompressed.Slice(outputOffset, this.Width);
56+
outputOffset += this.Width;
57+
Span<ushort> row3 = decompressed.Slice(outputOffset, this.Width);
58+
outputOffset += this.Width;
6559

6660
int rowOffset = 0;
67-
for (int x = 0; x < this.width && bytesLeft > 0; x += 4)
61+
for (int x = 0; x < this.Width && bytesLeft > 0; x += 4)
6862
{
6963
int bytesRead = stream.Read(this.scratch, 0, 3);
7064
if (bytesRead == 0)
7165
{
7266
ExrThrowHelper.ThrowInvalidImageContentException("Could not read enough data from the stream!");
7367
}
7468

69+
// Check if 3-byte encoded flat field.
7570
if (this.scratch[2] >= 13 << 2)
7671
{
7772
Unpack3(this.scratch, this.s);
@@ -89,8 +84,8 @@ public override void Decompress(BufferedReadStream stream, uint compressedBytes,
8984
bytesLeft -= 14;
9085
}
9186

92-
int n = x + 3 < this.width ? 4 : this.width - x;
93-
if (y + 3 < this.rowsPerBlock)
87+
int n = x + 3 < this.Width ? 4 : this.Width - x;
88+
if (y + 3 < this.RowsPerBlock)
9489
{
9590
this.s.AsSpan(0, n).CopyTo(row0[rowOffset..]);
9691
this.s.AsSpan(4, n).CopyTo(row1[rowOffset..]);
@@ -100,12 +95,12 @@ public override void Decompress(BufferedReadStream stream, uint compressedBytes,
10095
else
10196
{
10297
this.s.AsSpan(0, n).CopyTo(row0[rowOffset..]);
103-
if (y + 1 < this.rowsPerBlock)
98+
if (y + 1 < this.RowsPerBlock)
10499
{
105100
this.s.AsSpan(4, n).CopyTo(row1[rowOffset..]);
106101
}
107102

108-
if (y + 2 < this.rowsPerBlock)
103+
if (y + 2 < this.RowsPerBlock)
109104
{
110105
this.s.AsSpan(8, n).CopyTo(row2[rowOffset..]);
111106
}
@@ -124,16 +119,16 @@ public override void Decompress(BufferedReadStream stream, uint compressedBytes,
124119
// Rearrange the decompressed data such that the data for each scan line form a contiguous block.
125120
int offsetDecompressed = 0;
126121
int offsetOutput = 0;
127-
int blockSize = (int)(this.width * this.rowsPerBlock);
128-
for (int y = 0; y < this.rowsPerBlock; y++)
122+
int blockSize = (int)(this.Width * this.RowsPerBlock);
123+
for (int y = 0; y < this.RowsPerBlock; y++)
129124
{
130125
for (int i = 0; i < this.channelCount; i++)
131126
{
132-
decompressed.Slice(offsetDecompressed + (i * blockSize), this.width).CopyTo(outputBuffer[offsetOutput..]);
133-
offsetOutput += this.width;
127+
decompressed.Slice(offsetDecompressed + (i * blockSize), this.Width).CopyTo(outputBuffer[offsetOutput..]);
128+
offsetOutput += this.Width;
134129
}
135130

136-
offsetDecompressed += this.width;
131+
offsetDecompressed += this.Width;
137132
}
138133
}
139134

src/ImageSharp/Formats/Exr/Compression/Decompressors/NoneExrCompression.cs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,10 @@ internal class NoneExrCompression : ExrBaseDecompressor
1717
/// <param name="allocator">The memory allocator.</param>
1818
/// <param name="bytesPerBlock">The bytes per pixel row block.</param>
1919
/// <param name="bytesPerRow">The bytes per pixel row.</param>
20-
public NoneExrCompression(MemoryAllocator allocator, uint bytesPerBlock, uint bytesPerRow)
21-
: base(allocator, bytesPerBlock, bytesPerRow)
20+
/// <param name="rowsPerBlock">The pixel rows per block.</param>
21+
/// <param name="width">The number of pixels per row.</param>
22+
public NoneExrCompression(MemoryAllocator allocator, uint bytesPerBlock, uint bytesPerRow, uint rowsPerBlock, int width)
23+
: base(allocator, bytesPerBlock, bytesPerRow, rowsPerBlock, width)
2224
{
2325
}
2426

Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
// Copyright (c) Six Labors.
2+
// Licensed under the Six Labors Split License.
3+
4+
using System.Buffers;
5+
using System.Runtime.InteropServices;
6+
using SixLabors.ImageSharp.Formats.Exr.Constants;
7+
using SixLabors.ImageSharp.IO;
8+
using SixLabors.ImageSharp.Memory;
9+
10+
namespace SixLabors.ImageSharp.Formats.Exr.Compression.Decompressors;
11+
12+
/// <summary>
13+
/// Implementation of PXR24 decompressor for EXR image data.
14+
/// </summary>
15+
internal class Pxr24Compression : ExrBaseDecompressor
16+
{
17+
private readonly IMemoryOwner<byte> tmpBuffer;
18+
19+
private readonly int channelCount;
20+
21+
private readonly ExrPixelType pixelType;
22+
23+
/// <summary>
24+
/// Initializes a new instance of the <see cref="Pxr24Compression" /> class.
25+
/// </summary>
26+
/// <param name="allocator">The memory allocator.</param>
27+
/// <param name="bytesPerBlock">The bytes per pixel row block.</param>
28+
/// <param name="bytesPerRow">The bytes per pixel row.</param>
29+
/// <param name="rowsPerBlock">The pixel rows per block.</param>
30+
/// <param name="width">The witdh of one row in pixels.</param>
31+
/// <param name="channelCount">The number of channels for a pixel.</param>
32+
/// <param name="pixelType">The pixel type.</param>
33+
public Pxr24Compression(MemoryAllocator allocator, uint bytesPerBlock, uint bytesPerRow, uint rowsPerBlock, int width, int channelCount, ExrPixelType pixelType)
34+
: base(allocator, bytesPerBlock, bytesPerRow, rowsPerBlock, width)
35+
{
36+
this.tmpBuffer = allocator.Allocate<byte>((int)bytesPerBlock);
37+
this.channelCount = channelCount;
38+
this.pixelType = pixelType;
39+
}
40+
41+
/// <inheritdoc/>
42+
public override void Decompress(BufferedReadStream stream, uint compressedBytes, Span<byte> buffer)
43+
{
44+
Span<byte> uncompressed = this.tmpBuffer.GetSpan();
45+
Span<ushort> outputBufferHalf = MemoryMarshal.Cast<byte, ushort>(buffer);
46+
Span<uint> outputBufferFloat = MemoryMarshal.Cast<byte, uint>(buffer);
47+
Span<uint> outputBufferUint = MemoryMarshal.Cast<byte, uint>(buffer);
48+
49+
uint uncompressedBytes = this.BytesPerBlock;
50+
UndoZipCompression(stream, compressedBytes, uncompressed, uncompressedBytes);
51+
52+
int lastIn = 0;
53+
int outputOffset = 0;
54+
for (int y = 0; y < this.RowsPerBlock; y++)
55+
{
56+
for (int c = 0; c < this.channelCount; c++)
57+
{
58+
switch (this.pixelType)
59+
{
60+
case ExrPixelType.UnsignedInt:
61+
{
62+
int offsetT0 = lastIn;
63+
lastIn += this.Width;
64+
int offsetT1 = lastIn;
65+
lastIn += this.Width;
66+
int offsetT2 = lastIn;
67+
lastIn += this.Width;
68+
int offsetT3 = lastIn;
69+
lastIn += this.Width;
70+
71+
uint pixel = 0;
72+
for (int x = 0; x < this.Width; x++)
73+
{
74+
uint t0 = uncompressed[offsetT0];
75+
uint t1 = uncompressed[offsetT1];
76+
uint t2 = uncompressed[offsetT2];
77+
uint t3 = uncompressed[offsetT3];
78+
uint diff = (t0 << 24) | (t1 << 16) | (t2 << 8) | t3;
79+
80+
pixel += diff;
81+
outputBufferUint[outputOffset] = pixel;
82+
83+
offsetT0++;
84+
offsetT1++;
85+
offsetT2++;
86+
offsetT3++;
87+
outputOffset++;
88+
}
89+
90+
break;
91+
}
92+
93+
case ExrPixelType.Half:
94+
{
95+
int offsetT0 = lastIn;
96+
lastIn += this.Width;
97+
int offsetT1 = lastIn;
98+
lastIn += this.Width;
99+
100+
uint pixel = 0;
101+
for (int x = 0; x < this.Width; x++)
102+
{
103+
uint t0 = uncompressed[offsetT0];
104+
uint t1 = uncompressed[offsetT1];
105+
uint diff = (t0 << 8) | t1;
106+
107+
pixel += diff;
108+
outputBufferHalf[outputOffset] = (ushort)pixel;
109+
110+
offsetT0++;
111+
offsetT1++;
112+
outputOffset++;
113+
}
114+
115+
break;
116+
}
117+
118+
case ExrPixelType.Float:
119+
{
120+
int offsetT0 = lastIn;
121+
lastIn += this.Width;
122+
int offsetT1 = lastIn;
123+
lastIn += this.Width;
124+
int offsetT2 = lastIn;
125+
lastIn += this.Width;
126+
127+
uint pixel = 0;
128+
for (int x = 0; x < this.Width; x++)
129+
{
130+
uint t0 = uncompressed[offsetT0];
131+
uint t1 = uncompressed[offsetT1];
132+
uint t2 = uncompressed[offsetT2];
133+
uint diff = (t0 << 24) | (t1 << 16) | (t2 << 8);
134+
135+
pixel += diff;
136+
outputBufferFloat[outputOffset] = pixel;
137+
138+
offsetT0++;
139+
offsetT1++;
140+
offsetT2++;
141+
outputOffset++;
142+
}
143+
144+
break;
145+
}
146+
}
147+
}
148+
}
149+
}
150+
151+
/// <inheritdoc/>
152+
protected override void Dispose(bool disposing) => this.tmpBuffer.Dispose();
153+
}

src/ImageSharp/Formats/Exr/Compression/Decompressors/RunLengthExrCompression.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,16 +14,16 @@ internal class RunLengthExrCompression : ExrBaseDecompressor
1414
{
1515
private readonly IMemoryOwner<byte> tmpBuffer;
1616

17-
private readonly ushort[] s = new ushort[16];
18-
1917
/// <summary>
2018
/// Initializes a new instance of the <see cref="RunLengthExrCompression" /> class.
2119
/// </summary>
2220
/// <param name="allocator">The memory allocator.</param>
2321
/// <param name="bytesPerBlock">The bytes per pixel row block.</param>
2422
/// <param name="bytesPerRow">The bytes per row.</param>
25-
public RunLengthExrCompression(MemoryAllocator allocator, uint bytesPerBlock, uint bytesPerRow)
26-
: base(allocator, bytesPerBlock, bytesPerRow) => this.tmpBuffer = allocator.Allocate<byte>((int)bytesPerBlock);
23+
/// <param name="rowsPerBlock">The pixel rows per block.</param>
24+
/// <param name="width">The witdh of one row in pixels.</param>
25+
public RunLengthExrCompression(MemoryAllocator allocator, uint bytesPerBlock, uint bytesPerRow, uint rowsPerBlock, int width)
26+
: base(allocator, bytesPerBlock, bytesPerRow, rowsPerBlock, width) => this.tmpBuffer = allocator.Allocate<byte>((int)bytesPerBlock);
2727

2828
/// <inheritdoc/>
2929
public override void Decompress(BufferedReadStream stream, uint compressedBytes, Span<byte> buffer)

src/ImageSharp/Formats/Exr/Compression/Decompressors/ZipExrCompression.cs

Lines changed: 6 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,6 @@
22
// Licensed under the Six Labors Split License.
33

44
using System.Buffers;
5-
using System.IO.Compression;
6-
using SixLabors.ImageSharp.Compression.Zlib;
75
using SixLabors.ImageSharp.IO;
86
using SixLabors.ImageSharp.Memory;
97

@@ -22,41 +20,18 @@ internal class ZipExrCompression : ExrBaseDecompressor
2220
/// <param name="allocator">The memory allocator.</param>
2321
/// <param name="bytesPerBlock">The bytes per pixel row block.</param>
2422
/// <param name="bytesPerRow">The bytes per pixel row.</param>
25-
public ZipExrCompression(MemoryAllocator allocator, uint bytesPerBlock, uint bytesPerRow)
26-
: base(allocator, bytesPerBlock, bytesPerRow) => this.tmpBuffer = allocator.Allocate<byte>((int)bytesPerBlock);
23+
/// <param name="rowsPerBlock">The pixel rows per block.</param>
24+
/// <param name="width">The witdh of one row in pixels.</param>
25+
public ZipExrCompression(MemoryAllocator allocator, uint bytesPerBlock, uint bytesPerRow, uint rowsPerBlock, int width)
26+
: base(allocator, bytesPerBlock, bytesPerRow, rowsPerBlock, width) => this.tmpBuffer = allocator.Allocate<byte>((int)bytesPerBlock);
2727

2828
/// <inheritdoc/>
2929
public override void Decompress(BufferedReadStream stream, uint compressedBytes, Span<byte> buffer)
3030
{
3131
Span<byte> uncompressed = this.tmpBuffer.GetSpan();
3232

33-
long pos = stream.Position;
34-
using ZlibInflateStream inflateStream = new(
35-
stream,
36-
() =>
37-
{
38-
int left = (int)(compressedBytes - (stream.Position - pos));
39-
return left > 0 ? left : 0;
40-
});
41-
inflateStream.AllocateNewBytes((int)this.BytesPerBlock, true);
42-
using DeflateStream dataStream = inflateStream.CompressedStream!;
43-
44-
int totalRead = 0;
45-
while (totalRead < buffer.Length)
46-
{
47-
int bytesRead = dataStream.Read(uncompressed, totalRead, buffer.Length - totalRead);
48-
if (bytesRead <= 0)
49-
{
50-
break;
51-
}
52-
53-
totalRead += bytesRead;
54-
}
55-
56-
if (totalRead == 0)
57-
{
58-
ExrThrowHelper.ThrowInvalidImageContentException("Could not read enough data for zip compressed image data!");
59-
}
33+
uint uncompressedBytes = (uint)buffer.Length;
34+
int totalRead = UndoZipCompression(stream, compressedBytes, uncompressed, uncompressedBytes);
6035

6136
Reconstruct(uncompressed, (uint)totalRead);
6237
Interleave(uncompressed, (uint)totalRead, buffer);

0 commit comments

Comments
 (0)