nx_pcx_decode.nx source
↩ module page · 132 lines · 4725 B
1// nx_pcx_decode.nx -- ZSoft PCX reader (PC Paintbrush; the DOS-era standard).
2//
3// THE RLE IS PER-SCANLINE, NOT PER-IMAGE. A run may not cross a scanline
4// boundary, and a decoder that treats the stream as one flat run-length
5// sequence will still produce a plausible image on most files and shear on
6// the ones that pad. We decode exactly nplanes*bytes_per_line per row and
7// stop, which is also what makes a malformed row detectable.
8//
9// PLANES ARE SEPARATE, NOT INTERLEAVED. 24-bit PCX stores a whole row of R,
10// then a whole row of G, then B -- not RGB triples. The same row buffer is
11// therefore indexed plane*bytes_per_line + x, which is the one addressing
12// mistake that yields a recognisable-but-colour-smeared image.
13//
14// 8-bit palette lives at the END of the file: the last 769 bytes, introduced
15// by a 0x0C marker byte. Its absence on a 1-plane 8-bit file is a refusal,
16// not a reason to invent a grayscale ramp.
17//
18// genealogy_id: zsoft_pcx_5
19// lineage_id: nx_pcx_v1
20// license_tier: ORIGINAL
21
22import "nx_syscalls.nx"
23
24const NX_PCX_HDR: i64 = 128
25const NX_PCX_PAL: i64 = 769
26
27func pcx_b(d: *u8, o: i64) -> i64 { return (d[o] as i64) & 255 }
28func pcx_u16(d: *u8, o: i64) -> i64 { return pcx_b(d,o) | (pcx_b(d,o+1) << 8) }
29
30// Decode ONE scanline of `total` bytes into row[]. Returns the new source
31// position, or 0-1 on a malformed/overrunning stream.
32func pcx_row(raw: *u8, n: i64, sp0: i64, total: i64, row: *u8) -> i64 {
33 var sp: i64 = sp0
34 var dp: i64 = 0
35 while dp < total {
36 if sp >= n { return 0 - 1 }
37 let b: i64 = pcx_b(raw, sp)
38 sp = sp + 1
39 if (b & 192) == 192 {
40 let cnt: i64 = b & 63
41 if sp >= n { return 0 - 1 }
42 let v: u8 = raw[sp]
43 sp = sp + 1
44 if dp + cnt > total { return 0 - 1 }
45 var k: i64 = 0
46 while k < cnt { row[dp+k] = v; k = k + 1 }
47 dp = dp + cnt
48 } else {
49 row[dp] = b as u8
50 dp = dp + 1
51 }
52 }
53 return sp
54}
55
56func pcx_decode_rgb(raw: *u8, n: i64, out_wh: *i64) -> *u8 {
57 if n < NX_PCX_HDR + 1 { return 0 as *u8 }
58 if pcx_b(raw, 0) != 10 { return 0 as *u8 }
59 if pcx_b(raw, 2) != 1 { return 0 as *u8 } // only RLE encoding is defined
60 let bpp: i64 = pcx_b(raw, 3)
61 let xmin: i64 = pcx_u16(raw, 4)
62 let ymin: i64 = pcx_u16(raw, 6)
63 let xmax: i64 = pcx_u16(raw, 8)
64 let ymax: i64 = pcx_u16(raw, 10)
65 let nplanes: i64 = pcx_b(raw, 65)
66 let bpl: i64 = pcx_u16(raw, 66)
67 if xmax < xmin { return 0 as *u8 }
68 if ymax < ymin { return 0 as *u8 }
69 let w: i64 = xmax - xmin + 1
70 let h: i64 = ymax - ymin + 1
71 if w <= 0 { return 0 as *u8 }
72 if h <= 0 { return 0 as *u8 }
73 if bpl <= 0 { return 0 as *u8 }
74 if nplanes < 1 { return 0 as *u8 }
75 if nplanes > 4 { return 0 as *u8 }
76 // supported: 8bpp x1 plane (palette), 8bpp x3 planes (truecolour),
77 // 1bpp x1 plane (monochrome). Everything else refuses.
78 var mode: i64 = 0
79 if bpp == 8 { if nplanes == 1 { mode = 1 } }
80 if bpp == 8 { if nplanes == 3 { mode = 2 } }
81 if bpp == 1 { if nplanes == 1 { mode = 3 } }
82 if mode == 0 { return 0 as *u8 }
83
84 // 256-colour palette: last 769 bytes, marker 0x0C
85 var pal: i64 = 0 - 1
86 if mode == 1 {
87 if n < NX_PCX_HDR + NX_PCX_PAL { return 0 as *u8 }
88 let po: i64 = n - NX_PCX_PAL
89 if pcx_b(raw, po) != 12 { return 0 as *u8 }
90 pal = po + 1
91 }
92
93 let total: i64 = nplanes * bpl
94 let row: *u8 = sys_mmap(total + 64)
95 let rgb: *u8 = sys_mmap(w * h * 3 + 64)
96 var sp: i64 = NX_PCX_HDR
97 var y: i64 = 0
98 while y < h {
99 sp = pcx_row(raw, n, sp, total, row)
100 if sp < 0 { return 0 as *u8 }
101 var x: i64 = 0
102 while x < w {
103 let dof: i64 = (y*w + x) * 3
104 if mode == 2 {
105 rgb[dof] = row[x]
106 rgb[dof+1] = row[bpl + x]
107 rgb[dof+2] = row[2*bpl + x]
108 } else {
109 if mode == 1 {
110 let idx: i64 = pcx_b(row, x)
111 let eo: i64 = pal + idx*3
112 if eo + 3 > n { return 0 as *u8 }
113 rgb[dof] = raw[eo]
114 rgb[dof+1] = raw[eo+1]
115 rgb[dof+2] = raw[eo+2]
116 } else {
117 // 1bpp monochrome: MSB-first within each byte
118 let bit: i64 = (pcx_b(row, x >> 3) >> (7 - (x & 7))) & 1
119 var v: i64 = 0
120 if bit == 1 { v = 255 }
121 rgb[dof] = v as u8
122 rgb[dof+1] = v as u8
123 rgb[dof+2] = v as u8
124 } }
125 x = x + 1
126 }
127 y = y + 1
128 }
129 out_wh[0] = w
130 out_wh[1] = h
131 return rgb
132}