code wiki / (root) / nx_tga_decode.nx

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}