nx_pnm_decode.nx source
↩ module page · 187 lines · 5858 B
1// nx_pnm_decode.nx -- Netpbm reader: P1/P2/P3 (ASCII) and P4/P5/P6 (binary),
2// the Unix lingua franca that every 90s toolchain could emit.
3//
4// EXACTLY ONE WHITESPACE BYTE FOLLOWS THE HEADER of a binary variant, and the
5// payload starts immediately after it. Skipping "whitespace" greedily eats the
6// first pixel row whenever that row happens to begin with 0x20 or 0x0A -- a
7// corruption that appears only on some images, which is the worst kind.
8//
9// COMMENTS MAY APPEAR BETWEEN ANY TWO HEADER FIELDS, including in the middle
10// of the dimensions, so comment skipping belongs in the token reader rather
11// than in a one-shot header pre-pass.
12//
13// P1/P4 bitmaps invert the usual convention: 1 means BLACK.
14//
15// genealogy_id: netpbm_pnm
16// lineage_id: nx_pnm_v1
17// license_tier: ORIGINAL
18
19import "nx_syscalls.nx"
20
21func pnm_b(d: *u8, o: i64) -> i64 { return (d[o] as i64) & 255 }
22
23func pnm_is_ws(c: i64) -> i64 {
24 if c == 32 { return 1 }
25 if c == 9 { return 1 }
26 if c == 10 { return 1 }
27 if c == 13 { return 1 }
28 if c == 11 { return 1 }
29 if c == 12 { return 1 }
30 return 0
31}
32
33// Read the next integer token, skipping whitespace AND #-to-end-of-line
34// comments. pos[0] is advanced past the token; returns 0-1 on failure.
35func pnm_tok(raw: *u8, n: i64, pos: *i64) -> i64 {
36 var p: i64 = pos[0]
37 var scan: i64 = 1
38 while scan == 1 {
39 if p >= n { return 0 - 1 }
40 let c: i64 = pnm_b(raw, p)
41 if pnm_is_ws(c) == 1 {
42 p = p + 1
43 } else {
44 if c == 35 {
45 // '#' comment: consume through the newline (or to EOF)
46 var incm: i64 = 1
47 while incm == 1 {
48 if p >= n { incm = 0 } else {
49 if pnm_b(raw, p) == 10 { incm = 0 }
50 p = p + 1
51 }
52 }
53 } else {
54 scan = 0
55 }
56 }
57 }
58 var v: i64 = 0
59 var got: i64 = 0
60 var dig: i64 = 1
61 while dig == 1 {
62 if p >= n { dig = 0 } else {
63 let c: i64 = pnm_b(raw, p)
64 if c < 48 { dig = 0 } else {
65 if c > 57 { dig = 0 } else {
66 v = v*10 + (c - 48)
67 got = 1
68 p = p + 1
69 }
70 }
71 }
72 }
73 if got == 0 { return 0 - 1 }
74 pos[0] = p
75 return v
76}
77
78func pnm_decode_rgb(raw: *u8, n: i64, out_wh: *i64) -> *u8 {
79 if n < 8 { return 0 as *u8 }
80 if pnm_b(raw, 0) != 80 { return 0 as *u8 } // 'P'
81 let kind: i64 = pnm_b(raw, 1) - 48
82 if kind < 1 { return 0 as *u8 }
83 if kind > 6 { return 0 as *u8 }
84
85 let pos: *i64 = sys_mmap(16) as *i64
86 pos[0] = 2
87 let w: i64 = pnm_tok(raw, n, pos)
88 if w <= 0 { return 0 as *u8 }
89 let h: i64 = pnm_tok(raw, n, pos)
90 if h <= 0 { return 0 as *u8 }
91 var maxv: i64 = 1
92 if kind != 1 { if kind != 4 {
93 maxv = pnm_tok(raw, n, pos)
94 if maxv <= 0 { return 0 as *u8 }
95 if maxv > 65535 { return 0 as *u8 }
96 } }
97
98 let rgb: *u8 = sys_mmap(w * h * 3 + 64)
99 let npix: i64 = w * h
100
101 // ---- ASCII variants: every sample is a token ----
102 if kind <= 3 {
103 var i: i64 = 0
104 while i < npix {
105 let dof: i64 = i * 3
106 if kind == 3 {
107 let r: i64 = pnm_tok(raw, n, pos)
108 let g: i64 = pnm_tok(raw, n, pos)
109 let b: i64 = pnm_tok(raw, n, pos)
110 if r < 0 { return 0 as *u8 }
111 if g < 0 { return 0 as *u8 }
112 if b < 0 { return 0 as *u8 }
113 rgb[dof] = ((r*255)/maxv) as u8
114 rgb[dof+1] = ((g*255)/maxv) as u8
115 rgb[dof+2] = ((b*255)/maxv) as u8
116 } else {
117 let v: i64 = pnm_tok(raw, n, pos)
118 if v < 0 { return 0 as *u8 }
119 var g: i64 = (v*255)/maxv
120 if kind == 1 { if v == 1 { g = 0 } else { g = 255 } } // 1 = black
121 rgb[dof] = g as u8
122 rgb[dof+1] = g as u8
123 rgb[dof+2] = g as u8
124 }
125 i = i + 1
126 }
127 out_wh[0] = w
128 out_wh[1] = h
129 return rgb
130 }
131
132 // ---- binary variants: EXACTLY ONE whitespace byte, then the payload ----
133 var p: i64 = pos[0]
134 if p >= n { return 0 as *u8 }
135 if pnm_is_ws(pnm_b(raw, p)) == 0 { return 0 as *u8 }
136 p = p + 1
137
138 var bps: i64 = 1
139 if maxv > 255 { bps = 2 }
140
141 if kind == 4 {
142 // packed bitmap: rows are byte-aligned, MSB first, 1 = black
143 let stride: i64 = (w + 7) / 8
144 if p + stride*h > n { return 0 as *u8 }
145 var y: i64 = 0
146 while y < h {
147 var x: i64 = 0
148 while x < w {
149 let bit: i64 = (pnm_b(raw, p + y*stride + (x >> 3)) >> (7 - (x & 7))) & 1
150 var g: i64 = 255
151 if bit == 1 { g = 0 }
152 let dof: i64 = (y*w + x) * 3
153 rgb[dof] = g as u8
154 rgb[dof+1] = g as u8
155 rgb[dof+2] = g as u8
156 x = x + 1
157 }
158 y = y + 1
159 }
160 out_wh[0] = w
161 out_wh[1] = h
162 return rgb
163 }
164
165 var nch: i64 = 1
166 if kind == 6 { nch = 3 }
167 if p + npix*nch*bps > n { return 0 as *u8 }
168 var i: i64 = 0
169 while i < npix {
170 let dof: i64 = i * 3
171 let so: i64 = p + i*nch*bps
172 if nch == 3 {
173 rgb[dof] = ((pnm_b(raw, so)*255)/maxv) as u8
174 rgb[dof+1] = ((pnm_b(raw, so+bps)*255)/maxv) as u8
175 rgb[dof+2] = ((pnm_b(raw, so+2*bps)*255)/maxv) as u8
176 } else {
177 let g: i64 = (pnm_b(raw, so)*255)/maxv
178 rgb[dof] = g as u8
179 rgb[dof+1] = g as u8
180 rgb[dof+2] = g as u8
181 }
182 i = i + 1
183 }
184 out_wh[0] = w
185 out_wh[1] = h
186 return rgb
187}