nx_tga_decode.nx source
↩ module page · 166 lines · 5821 B
1// nx_tga_decode.nx -- Truevision TGA reader (the DOS/Amiga-era workhorse).
2//
3// Types handled: 1/9 colour-mapped (8-bit index), 2/10 truecolour (16/24/32),
4// 3/11 grayscale (8) -- the 9/10/11 variants being the RLE forms of the same
5// pixel layouts, which is why the RLE unpack sits BEFORE the pixel decode and
6// both paths share it. Refused: types 32/33 (Huffman+delta), colour maps
7// whose entry size is not 15/16/24/32.
8//
9// THE ORIGIN BIT IS NOT DECORATION. Bit 5 of the image descriptor selects
10// top-left vs bottom-left row order. A reader that ignores it returns a
11// vertically MIRRORED image that is otherwise perfectly valid-looking -- the
12// single most common TGA defect, and invisible to any check that only
13// compares dimensions or means.
14//
15// 16-bit is A1R5G5B5 with 5-bit channels replicated to 8 (v<<3|v>>2), NOT
16// shifted-and-zero-filled: the latter loses white (31 -> 248, never 255).
17//
18// genealogy_id: tga_spec_2_0
19// lineage_id: nx_tga_v1
20// license_tier: ORIGINAL
21
22import "nx_syscalls.nx"
23
24func tga_b(d: *u8, o: i64) -> i64 { return (d[o] as i64) & 255 }
25func tga_u16(d: *u8, o: i64) -> i64 { return tga_b(d,o) | (tga_b(d,o+1) << 8) }
26
27// expand a 5-bit channel to 8 bits by replication (31 -> 255)
28func tga_x5(v: i64) -> i64 { return (v << 3) | (v >> 2) }
29
30// Decode one pixel at src offset `so` of `bpp` bytes into rgb[do..do+2].
31// TGA stores BGR(A); 16-bit is A1R5G5B5 little-endian.
32func tga_px(src: *u8, so: i64, bpp: i64, rgb: *u8, dof: i64) -> i64 {
33 if bpp == 1 {
34 let g: i64 = tga_b(src, so)
35 rgb[dof] = g as u8
36 rgb[dof+1] = g as u8
37 rgb[dof+2] = g as u8
38 return 0
39 }
40 if bpp == 2 {
41 let v: i64 = tga_u16(src, so)
42 rgb[dof] = tga_x5((v >> 10) & 31) as u8
43 rgb[dof+1] = tga_x5((v >> 5) & 31) as u8
44 rgb[dof+2] = tga_x5(v & 31) as u8
45 return 0
46 }
47 rgb[dof] = src[so+2]
48 rgb[dof+1] = src[so+1]
49 rgb[dof+2] = src[so]
50 return 0
51}
52
53// RLE unpack into a flat pixel buffer of npix*bpp bytes. Returns 1 on success.
54// A packet that would overrun the output is a REFUSAL, not a truncation.
55func tga_unrle(raw: *u8, n: i64, start: i64, npix: i64, bpp: i64, out: *u8) -> i64 {
56 var sp: i64 = start
57 var dp: i64 = 0
58 while dp < npix * bpp {
59 if sp >= n { return 0 }
60 let hdr: i64 = tga_b(raw, sp)
61 sp = sp + 1
62 let cnt: i64 = (hdr & 127) + 1
63 if (hdr & 128) == 128 {
64 if sp + bpp > n { return 0 }
65 if dp + cnt*bpp > npix*bpp { return 0 }
66 var k: i64 = 0
67 while k < cnt {
68 var b: i64 = 0
69 while b < bpp { out[dp+b] = raw[sp+b]; b = b + 1 }
70 dp = dp + bpp
71 k = k + 1
72 }
73 sp = sp + bpp
74 } else {
75 if sp + cnt*bpp > n { return 0 }
76 if dp + cnt*bpp > npix*bpp { return 0 }
77 var k: i64 = 0
78 while k < cnt*bpp { out[dp+k] = raw[sp+k]; k = k + 1 }
79 dp = dp + cnt*bpp
80 sp = sp + cnt*bpp
81 }
82 }
83 return 1
84}
85
86func tga_decode_rgb(raw: *u8, n: i64, out_wh: *i64) -> *u8 {
87 if n < 18 { return 0 as *u8 }
88 let idlen: i64 = tga_b(raw, 0)
89 let cmaptype: i64 = tga_b(raw, 1)
90 let itype: i64 = tga_b(raw, 2)
91 let cmap_len: i64 = tga_u16(raw, 5)
92 let cmap_es: i64 = tga_b(raw, 7)
93 let w: i64 = tga_u16(raw, 12)
94 let h: i64 = tga_u16(raw, 14)
95 let depth: i64 = tga_b(raw, 16)
96 let desc: i64 = tga_b(raw, 17)
97 if w <= 0 { return 0 as *u8 }
98 if h <= 0 { return 0 as *u8 }
99 if w > 65535 { return 0 as *u8 }
100 if h > 65535 { return 0 as *u8 }
101
102 var rle: i64 = 0
103 var base: i64 = itype
104 if itype >= 9 { if itype <= 11 { rle = 1; base = itype - 8 } }
105 if base != 1 { if base != 2 { if base != 3 { return 0 as *u8 } } }
106
107 // colour map (only for type 1/9)
108 var cmap_bytes: i64 = 0
109 if cmaptype == 1 {
110 if cmap_es != 15 { if cmap_es != 16 { if cmap_es != 24 { if cmap_es != 32 { return 0 as *u8 } } } }
111 var ebytes: i64 = cmap_es / 8
112 if cmap_es == 15 { ebytes = 2 }
113 cmap_bytes = cmap_len * ebytes
114 }
115 let cmap_off: i64 = 18 + idlen
116 let data_off: i64 = cmap_off + cmap_bytes
117 if data_off > n { return 0 as *u8 }
118
119 var bpp: i64 = depth / 8
120 if depth == 15 { bpp = 2 }
121 if bpp < 1 { return 0 as *u8 }
122 if bpp > 4 { return 0 as *u8 }
123 if base == 1 { if bpp != 1 { return 0 as *u8 } }
124
125 let npix: i64 = w * h
126 // gather the pixel bytes, unpacking RLE when present
127 var px: *u8 = 0 as *u8
128 if rle == 1 {
129 px = sys_mmap(npix * bpp + 64)
130 if tga_unrle(raw, n, data_off, npix, bpp, px) == 0 { return 0 as *u8 }
131 } else {
132 if data_off + npix*bpp > n { return 0 as *u8 }
133 px = raw + data_off
134 }
135
136 let rgb: *u8 = sys_mmap(npix * 3 + 64)
137 // bit 5 of the descriptor: 1 = rows stored top-to-bottom
138 let topdown: i64 = (desc >> 5) & 1
139 var y: i64 = 0
140 while y < h {
141 var dy: i64 = y
142 if topdown == 0 { dy = h - 1 - y }
143 var x: i64 = 0
144 while x < w {
145 let so: i64 = (y*w + x) * bpp
146 let dof: i64 = (dy*w + x) * 3
147 if base == 1 {
148 // colour-mapped: index into the map, entries are BGR(A)
149 var idx: i64 = tga_b(px, so)
150 if idx >= cmap_len { idx = 0 }
151 var ebytes: i64 = cmap_es / 8
152 if cmap_es == 15 { ebytes = 2 }
153 let eo: i64 = cmap_off + idx*ebytes
154 if eo + ebytes > n { return 0 as *u8 }
155 tga_px(raw, eo, ebytes, rgb, dof)
156 } else {
157 tga_px(px, so, bpp, rgb, dof)
158 }
159 x = x + 1
160 }
161 y = y + 1
162 }
163 out_wh[0] = w
164 out_wh[1] = h
165 return rgb
166}