Skip to content

Commit 04ff1e9

Browse files
Merge pull request #3136 from Erik-White/png-cgbi
Add support for Apple CgBI PNG images
2 parents f6d369d + a3e9cc6 commit 04ff1e9

18 files changed

Lines changed: 528 additions & 57 deletions

src/ImageSharp/Common/Helpers/Shuffle/IPad3Shuffle4.cs

Lines changed: 30 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -31,19 +31,23 @@ public void Shuffle(ReadOnlySpan<byte> source, Span<byte> destination)
3131

3232
SimdUtils.Shuffle.InverseMMShuffle(this.Control, out uint p3, out uint p2, out uint p1, out uint p0);
3333

34-
Span<byte> temp = stackalloc byte[4];
35-
ref byte t = ref MemoryMarshal.GetReference(temp);
36-
ref uint tu = ref Unsafe.As<byte, uint>(ref t);
37-
3834
for (nuint i = 0, j = 0; i < (uint)source.Length; i += 3, j += 4)
3935
{
40-
ref byte s = ref Unsafe.Add(ref sBase, i);
41-
tu = Unsafe.As<byte, uint>(ref s) | 0xFF000000;
42-
43-
Unsafe.Add(ref dBase, j + 0) = Unsafe.Add(ref t, p0);
44-
Unsafe.Add(ref dBase, j + 1) = Unsafe.Add(ref t, p1);
45-
Unsafe.Add(ref dBase, j + 2) = Unsafe.Add(ref t, p2);
46-
Unsafe.Add(ref dBase, j + 3) = Unsafe.Add(ref t, p3);
36+
// Expanding 3-byte pixels to 4 bytes can overwrite the next source
37+
// triplet when spans overlap. Assemble the padded pixel first, then
38+
// shuffle from the staged uint.
39+
uint packed =
40+
Unsafe.Add(ref sBase, i + 0u) |
41+
((uint)Unsafe.Add(ref sBase, i + 1u) << 8) |
42+
((uint)Unsafe.Add(ref sBase, i + 2u) << 16) |
43+
0xFF000000;
44+
45+
ref byte pBase = ref Unsafe.As<uint, byte>(ref packed);
46+
47+
Unsafe.Add(ref dBase, j + 0u) = Unsafe.Add(ref pBase, p0);
48+
Unsafe.Add(ref dBase, j + 1u) = Unsafe.Add(ref pBase, p1);
49+
Unsafe.Add(ref dBase, j + 2u) = Unsafe.Add(ref pBase, p2);
50+
Unsafe.Add(ref dBase, j + 3u) = Unsafe.Add(ref pBase, p3);
4751
}
4852
}
4953
}
@@ -65,18 +69,28 @@ public void Shuffle(ReadOnlySpan<byte> source, Span<byte> destination)
6569

6670
while (Unsafe.IsAddressLessThan(ref sBase, ref sLoopEnd))
6771
{
68-
Unsafe.As<byte, uint>(ref dBase) = Unsafe.As<byte, uint>(ref sBase) | 0xFF000000;
72+
// The fast scalar path reads one extra byte past the source triplet.
73+
// Keep that widened read in a local before writing the expanded pixel
74+
// so overlapping destinations cannot change what was read.
75+
uint packed = Unsafe.As<byte, uint>(ref sBase) | 0xFF000000;
76+
77+
Unsafe.As<byte, uint>(ref dBase) = packed;
6978

7079
sBase = ref Unsafe.Add(ref sBase, 3);
7180
dBase = ref Unsafe.Add(ref dBase, 4);
7281
}
7382

7483
while (Unsafe.IsAddressLessThan(ref sBase, ref sEnd))
7584
{
76-
Unsafe.Add(ref dBase, 0) = Unsafe.Add(ref sBase, 0);
77-
Unsafe.Add(ref dBase, 1) = Unsafe.Add(ref sBase, 1);
78-
Unsafe.Add(ref dBase, 2) = Unsafe.Add(ref sBase, 2);
79-
Unsafe.Add(ref dBase, 3) = byte.MaxValue;
85+
// The final triplet cannot use the widened read above, so assemble
86+
// the same padded uint byte-by-byte before the overlapping store.
87+
uint packed =
88+
Unsafe.Add(ref sBase, 0u) |
89+
((uint)Unsafe.Add(ref sBase, 1u) << 8) |
90+
((uint)Unsafe.Add(ref sBase, 2u) << 16) |
91+
0xFF000000;
92+
93+
Unsafe.As<byte, uint>(ref dBase) = packed;
8094

8195
sBase = ref Unsafe.Add(ref sBase, 3);
8296
dBase = ref Unsafe.Add(ref dBase, 4);

src/ImageSharp/Common/Helpers/Shuffle/IShuffle3.cs

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,9 +33,19 @@ public void Shuffle(ReadOnlySpan<byte> source, Span<byte> destination)
3333

3434
for (nuint i = 0; i < (uint)source.Length; i += 3)
3535
{
36-
Unsafe.Add(ref dBase, i + 0) = Unsafe.Add(ref sBase, p0 + i);
37-
Unsafe.Add(ref dBase, i + 1) = Unsafe.Add(ref sBase, p1 + i);
38-
Unsafe.Add(ref dBase, i + 2) = Unsafe.Add(ref sBase, p2 + i);
36+
// The scalar remainder can run in-place after the vector body. Load
37+
// the full 3-byte pixel into a register-sized value before stores so
38+
// channel swaps cannot corrupt later reads from the same pixel.
39+
uint packed =
40+
Unsafe.Add(ref sBase, i + 0u) |
41+
((uint)Unsafe.Add(ref sBase, i + 1u) << 8) |
42+
((uint)Unsafe.Add(ref sBase, i + 2u) << 16);
43+
44+
ref byte pBase = ref Unsafe.As<uint, byte>(ref packed);
45+
46+
Unsafe.Add(ref dBase, i + 0u) = Unsafe.Add(ref pBase, p0);
47+
Unsafe.Add(ref dBase, i + 1u) = Unsafe.Add(ref pBase, p1);
48+
Unsafe.Add(ref dBase, i + 2u) = Unsafe.Add(ref pBase, p2);
3949
}
4050
}
4151
}

src/ImageSharp/Common/Helpers/Shuffle/IShuffle4.cs

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -35,10 +35,16 @@ public void Shuffle(ReadOnlySpan<byte> source, Span<byte> destination)
3535

3636
for (nuint i = 0; i < (uint)source.Length; i += 4)
3737
{
38-
Unsafe.Add(ref dBase, i + 0) = Unsafe.Add(ref sBase, p0 + i);
39-
Unsafe.Add(ref dBase, i + 1) = Unsafe.Add(ref sBase, p1 + i);
40-
Unsafe.Add(ref dBase, i + 2) = Unsafe.Add(ref sBase, p2 + i);
41-
Unsafe.Add(ref dBase, i + 3) = Unsafe.Add(ref sBase, p3 + i);
38+
// The generic path may be used with source and destination pointing
39+
// at the same pixel. Load all channels first so subsequent stores
40+
// index only staged bytes, matching the specialized uint shuffles.
41+
uint packed = Unsafe.As<byte, uint>(ref Unsafe.Add(ref sBase, i));
42+
ref byte pBase = ref Unsafe.As<uint, byte>(ref packed);
43+
44+
Unsafe.Add(ref dBase, i + 0u) = Unsafe.Add(ref pBase, p0);
45+
Unsafe.Add(ref dBase, i + 1u) = Unsafe.Add(ref pBase, p1);
46+
Unsafe.Add(ref dBase, i + 2u) = Unsafe.Add(ref pBase, p2);
47+
Unsafe.Add(ref dBase, i + 3u) = Unsafe.Add(ref pBase, p3);
4248
}
4349
}
4450
}

src/ImageSharp/Common/Helpers/Shuffle/IShuffle4Slice3.cs

Lines changed: 26 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -33,9 +33,15 @@ public void Shuffle(ReadOnlySpan<byte> source, Span<byte> destination)
3333

3434
for (nuint i = 0, j = 0; i < (uint)destination.Length; i += 3, j += 4)
3535
{
36-
Unsafe.Add(ref dBase, i + 0) = Unsafe.Add(ref sBase, p0 + j);
37-
Unsafe.Add(ref dBase, i + 1) = Unsafe.Add(ref sBase, p1 + j);
38-
Unsafe.Add(ref dBase, i + 2) = Unsafe.Add(ref sBase, p2 + j);
36+
// Shrinking 4-byte pixels to 3 bytes can still be called in-place by
37+
// tail code. Read the complete source pixel first, then write only
38+
// the requested channels into the destination triplet.
39+
uint packed = Unsafe.As<byte, uint>(ref Unsafe.Add(ref sBase, j));
40+
ref byte pBase = ref Unsafe.As<uint, byte>(ref packed);
41+
42+
Unsafe.Add(ref dBase, i + 0u) = Unsafe.Add(ref pBase, p0);
43+
Unsafe.Add(ref dBase, i + 1u) = Unsafe.Add(ref pBase, p1);
44+
Unsafe.Add(ref dBase, i + 2u) = Unsafe.Add(ref pBase, p2);
3945
}
4046
}
4147
}
@@ -61,18 +67,30 @@ public void Shuffle(ReadOnlySpan<byte> source, Span<byte> destination)
6167

6268
while (Unsafe.IsAddressLessThan(ref sBase, ref sLoopEnd))
6369
{
64-
Unsafe.Add(ref dBase, 0) = Unsafe.As<uint, Byte3>(ref Unsafe.Add(ref sBase, 0));
65-
Unsafe.Add(ref dBase, 1) = Unsafe.As<uint, Byte3>(ref Unsafe.Add(ref sBase, 1));
66-
Unsafe.Add(ref dBase, 2) = Unsafe.As<uint, Byte3>(ref Unsafe.Add(ref sBase, 2));
67-
Unsafe.Add(ref dBase, 3) = Unsafe.As<uint, Byte3>(ref Unsafe.Add(ref sBase, 3));
70+
// Stage the four source pixels before the 3-byte stores. Even
71+
// though this path preserves XYZ order, the packed loads must happen
72+
// before destination writes when the spans overlap.
73+
uint packed0 = Unsafe.Add(ref sBase, 0u);
74+
uint packed1 = Unsafe.Add(ref sBase, 1u);
75+
uint packed2 = Unsafe.Add(ref sBase, 2u);
76+
uint packed3 = Unsafe.Add(ref sBase, 3u);
77+
78+
Unsafe.Add(ref dBase, 0u) = Unsafe.As<uint, Byte3>(ref packed0);
79+
Unsafe.Add(ref dBase, 1u) = Unsafe.As<uint, Byte3>(ref packed1);
80+
Unsafe.Add(ref dBase, 2u) = Unsafe.As<uint, Byte3>(ref packed2);
81+
Unsafe.Add(ref dBase, 3u) = Unsafe.As<uint, Byte3>(ref packed3);
6882

6983
sBase = ref Unsafe.Add(ref sBase, 4);
7084
dBase = ref Unsafe.Add(ref dBase, 4);
7185
}
7286

7387
while (Unsafe.IsAddressLessThan(ref sBase, ref sEnd))
7488
{
75-
Unsafe.Add(ref dBase, 0) = Unsafe.As<uint, Byte3>(ref Unsafe.Add(ref sBase, 0));
89+
// Same overlap rule as the unrolled loop: take the 4-byte source
90+
// pixel before storing the 3-byte destination value.
91+
uint packed = Unsafe.Add(ref sBase, 0u);
92+
93+
Unsafe.Add(ref dBase, 0u) = Unsafe.As<uint, Byte3>(ref packed);
7694

7795
sBase = ref Unsafe.Add(ref sBase, 1);
7896
dBase = ref Unsafe.Add(ref dBase, 1);

src/ImageSharp/Common/Helpers/SimdUtils.Shuffle.cs

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
using System.Diagnostics;
55
using System.Diagnostics.CodeAnalysis;
6+
using System.Numerics;
67
using System.Runtime.CompilerServices;
78
using System.Runtime.InteropServices;
89

@@ -150,10 +151,15 @@ private static void Shuffle4Remainder(
150151

151152
for (nuint i = 0; i < (uint)source.Length; i += 4)
152153
{
153-
Unsafe.Add(ref dBase, i + 0) = Unsafe.Add(ref sBase, p0 + i);
154-
Unsafe.Add(ref dBase, i + 1) = Unsafe.Add(ref sBase, p1 + i);
155-
Unsafe.Add(ref dBase, i + 2) = Unsafe.Add(ref sBase, p2 + i);
156-
Unsafe.Add(ref dBase, i + 3) = Unsafe.Add(ref sBase, p3 + i);
154+
// Stage the scalar tail in a local Vector4 so p0..p3 index source
155+
// values that were captured before any overlapping destination writes.
156+
Vector4 v = Unsafe.As<float, Vector4>(ref Unsafe.Add(ref sBase, i));
157+
ref float pBase = ref Unsafe.As<Vector4, float>(ref v);
158+
159+
Unsafe.Add(ref dBase, i + 0u) = Unsafe.Add(ref pBase, p0);
160+
Unsafe.Add(ref dBase, i + 1u) = Unsafe.Add(ref pBase, p1);
161+
Unsafe.Add(ref dBase, i + 2u) = Unsafe.Add(ref pBase, p2);
162+
Unsafe.Add(ref dBase, i + 3u) = Unsafe.Add(ref pBase, p3);
157163
}
158164
}
159165

src/ImageSharp/Compression/Zlib/ZlibInflateStream.cs

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,12 +52,19 @@ internal sealed class ZlibInflateStream : Stream
5252
/// </summary>
5353
private readonly Func<int> getData;
5454

55+
/// <summary>
56+
/// When true, the inflated payload is treated as a raw DEFLATE stream with no zlib
57+
/// CMF/FLG header (and no Adler-32 trailer). This is required to decode IDATs in
58+
/// Apple's proprietary CgBI PNG variant.
59+
/// </summary>
60+
private readonly bool noHeader;
61+
5562
/// <summary>
5663
/// Initializes a new instance of the <see cref="ZlibInflateStream"/> class.
5764
/// </summary>
5865
/// <param name="innerStream">The inner raw stream.</param>
5966
public ZlibInflateStream(BufferedReadStream innerStream)
60-
: this(innerStream, GetDataNoOp)
67+
: this(innerStream, GetDataNoOp, noHeader: false)
6168
{
6269
}
6370

@@ -67,9 +74,23 @@ public ZlibInflateStream(BufferedReadStream innerStream)
6774
/// <param name="innerStream">The inner raw stream.</param>
6875
/// <param name="getData">A delegate to get more data from the inner stream.</param>
6976
public ZlibInflateStream(BufferedReadStream innerStream, Func<int> getData)
77+
: this(innerStream, getData, noHeader: false)
78+
{
79+
}
80+
81+
/// <summary>
82+
/// Initializes a new instance of the <see cref="ZlibInflateStream"/> class.
83+
/// </summary>
84+
/// <param name="innerStream">The inner raw stream.</param>
85+
/// <param name="getData">A delegate to get more data from the inner stream.</param>
86+
/// <param name="noHeader">
87+
/// When <see langword="true"/>, the payload is treated as raw DEFLATE with no zlib header.
88+
/// </param>
89+
public ZlibInflateStream(BufferedReadStream innerStream, Func<int> getData, bool noHeader)
7090
{
7191
this.innerStream = innerStream;
7292
this.getData = getData;
93+
this.noHeader = noHeader;
7394
}
7495

7596
/// <inheritdoc/>
@@ -210,6 +231,14 @@ protected override void Dispose(bool disposing)
210231
[MemberNotNullWhen(true, nameof(CompressedStream))]
211232
private bool InitializeInflateStream(bool isCriticalChunk)
212233
{
234+
// Apple CgBI IDATs omit the zlib CMF/FLG header and the Adler-32 trailer,
235+
// wrapping a raw DEFLATE payload directly. Skip the header parsing in that mode.
236+
if (this.noHeader)
237+
{
238+
this.CompressedStream = new DeflateStream(this, CompressionMode.Decompress, true);
239+
return true;
240+
}
241+
213242
// Read the zlib header : http://tools.ietf.org/html/rfc1950
214243
// CMF(Compression Method and flags)
215244
// This byte is divided into a 4 - bit compression method and a

0 commit comments

Comments
 (0)