nx_png_dynamic_huffman_test.nx source
↩ module page · 166 lines · 5225 B
1// nx_png_dynamic_huffman_test.nx -- regression smoke proving nx_png_decode
2// can consume a real-world dynamic-Huffman zlib stream (the format
3// every gen-img PNG uses).
4//
5// SURFACED 2026-05-18 via empirical run: nx_image_grade_cli reports
6// IGV2_ERR_DECODER=3 on any real PNG. The companion smoke
7// nx_deflate_huffman_test.nx proved nx_deflate_inflate handles the
8// SAME 61-byte raw DEFLATE bytes when called directly. So the bug
9// is in nx_png_decode (or in how it hands IDAT bytes to nx_zlib_inflate).
10//
11// Fixture: 124-byte PNG built by python zlib.compress(raw, 6) where
12// raw = 50 scanlines of "0x00 [200,170,150]*50". IDAT inner zlib
13// stream = 67 bytes (0x78 0x9c header + 61 raw DEFLATE + 4 adler).
14// Expected decode: width=50, height=50, pixels_size = 50*50*3 = 7500.
15
16import "nx_syscalls.nx"
17import "nx_runtime.nx"
18import "nx_png_decoder.nx"
19
20func main() -> nx_int {
21 let png_in: *u8 = (sys_mmap(128)) as *u8
22
23 png_in[0] = 0x89 as u8
24 png_in[1] = 0x50 as u8
25 png_in[2] = 0x4e as u8
26 png_in[3] = 0x47 as u8
27 png_in[4] = 0x0d as u8
28 png_in[5] = 0x0a as u8
29 png_in[6] = 0x1a as u8
30 png_in[7] = 0x0a as u8
31 png_in[8] = 0x00 as u8
32 png_in[9] = 0x00 as u8
33 png_in[10] = 0x00 as u8
34 png_in[11] = 0x0d as u8
35 png_in[12] = 0x49 as u8
36 png_in[13] = 0x48 as u8
37 png_in[14] = 0x44 as u8
38 png_in[15] = 0x52 as u8
39 png_in[16] = 0x00 as u8
40 png_in[17] = 0x00 as u8
41 png_in[18] = 0x00 as u8
42 png_in[19] = 0x32 as u8
43 png_in[20] = 0x00 as u8
44 png_in[21] = 0x00 as u8
45 png_in[22] = 0x00 as u8
46 png_in[23] = 0x32 as u8
47 png_in[24] = 0x08 as u8
48 png_in[25] = 0x02 as u8
49 png_in[26] = 0x00 as u8
50 png_in[27] = 0x00 as u8
51 png_in[28] = 0x00 as u8
52 png_in[29] = 0x91 as u8
53 png_in[30] = 0x5d as u8
54 png_in[31] = 0x1f as u8
55 png_in[32] = 0xe6 as u8
56 png_in[33] = 0x00 as u8
57 png_in[34] = 0x00 as u8
58 png_in[35] = 0x00 as u8
59 png_in[36] = 0x43 as u8
60 png_in[37] = 0x49 as u8
61 png_in[38] = 0x44 as u8
62 png_in[39] = 0x41 as u8
63 png_in[40] = 0x54 as u8
64 png_in[41] = 0x78 as u8
65 png_in[42] = 0x9c as u8
66 png_in[43] = 0xed as u8
67 png_in[44] = 0xce as u8
68 png_in[45] = 0x41 as u8
69 png_in[46] = 0x0d as u8
70 png_in[47] = 0x00 as u8
71 png_in[48] = 0x20 as u8
72 png_in[49] = 0x0c as u8
73 png_in[50] = 0x00 as u8
74 png_in[51] = 0xb1 as u8
75 png_in[52] = 0xf9 as u8
76 png_in[53] = 0x17 as u8
77 png_in[54] = 0x80 as u8
78 png_in[55] = 0x08 as u8
79 png_in[56] = 0x94 as u8
80 png_in[57] = 0x4c as u8
81 png_in[58] = 0x16 as u8
82 png_in[59] = 0x32 as u8
83 png_in[60] = 0xb8 as u8
84 png_in[61] = 0x47 as u8
85 png_in[62] = 0x93 as u8
86 png_in[63] = 0x0a as u8
87 png_in[64] = 0xe8 as u8
88 png_in[65] = 0xec as u8
89 png_in[66] = 0x3d as u8
90 png_in[67] = 0x41 as u8
91 png_in[68] = 0xf3 as u8
92 png_in[69] = 0x7d as u8
93 png_in[70] = 0xa0 as u8
94 png_in[71] = 0xa5 as u8
95 png_in[72] = 0xa5 as u8
96 png_in[73] = 0xa5 as u8
97 png_in[74] = 0xa5 as u8
98 png_in[75] = 0xa5 as u8
99 png_in[76] = 0xd5 as u8
100 png_in[77] = 0xa0 as u8
101 png_in[78] = 0xa5 as u8
102 png_in[79] = 0xa5 as u8
103 png_in[80] = 0x55 as u8
104 png_in[81] = 0xa0 as u8
105 png_in[82] = 0xa5 as u8
106 png_in[83] = 0xa5 as u8
107 png_in[84] = 0x55 as u8
108 png_in[85] = 0xa0 as u8
109 png_in[86] = 0xa5 as u8
110 png_in[87] = 0xa5 as u8
111 png_in[88] = 0x55 as u8
112 png_in[89] = 0xa0 as u8
113 png_in[90] = 0xa5 as u8
114 png_in[91] = 0xa5 as u8
115 png_in[92] = 0x55 as u8
116 png_in[93] = 0xa0 as u8
117 png_in[94] = 0xa5 as u8
118 png_in[95] = 0xa5 as u8
119 png_in[96] = 0x55 as u8
120 png_in[97] = 0xa0 as u8
121 png_in[98] = 0xa5 as u8
122 png_in[99] = 0xa5 as u8
123 png_in[100] = 0x55 as u8
124 png_in[101] = 0x10 as u8
125 png_in[102] = 0x6d as u8
126 png_in[103] = 0x3d as u8
127 png_in[104] = 0xac as u8
128 png_in[105] = 0x61 as u8
129 png_in[106] = 0xd7 as u8
130 png_in[107] = 0x3e as u8
131 png_in[108] = 0x53 as u8
132 png_in[109] = 0xb2 as u8
133 png_in[110] = 0x87 as u8
134 png_in[111] = 0xed as u8
135 png_in[112] = 0x00 as u8
136 png_in[113] = 0x00 as u8
137 png_in[114] = 0x00 as u8
138 png_in[115] = 0x00 as u8
139 png_in[116] = 0x49 as u8
140 png_in[117] = 0x45 as u8
141 png_in[118] = 0x4e as u8
142 png_in[119] = 0x44 as u8
143 png_in[120] = 0xae as u8
144 png_in[121] = 0x42 as u8
145 png_in[122] = 0x60 as u8
146 png_in[123] = 0x82 as u8
147
148 let r: *NxPngResult = nx_png_decode(png_in, 124)
149 if r == (0 as *NxPngResult) { return 1 }
150 // Return the actual error code so we can see exactly what failed.
151 // Add 100 so non-zero error codes don't collide with our test
152 // assertion numbers.
153 if r.error_code != NX_PNG_OK { return 100 + r.error_code }
154
155 if r.header == (0 as *NxPngHeader) { return 2 }
156 if r.header.width != 50 { return 3 }
157 if r.header.height != 50 { return 4 }
158 if r.n_channels != 3 { return 5 }
159 if r.pixels_size != 7500 { return 6 }
160 // First unfiltered pixel: (200, 170, 150) = (0xc8, 0xaa, 0x96)
161 if (r.pixels[0] as nx_int) & 255 != 0xc8 { return 10 }
162 if (r.pixels[1] as nx_int) & 255 != 0xaa { return 11 }
163 if (r.pixels[2] as nx_int) & 255 != 0x96 { return 12 }
164
165 return 0
166}