nx_ico_decode.nx source
↩ module page · 167 lines · 6338 B
1// nx_ico_decode.nx -- Windows ICO/CUR reader. The one legacy format with
2// LIVE web demand: every favicon.ico a crawler meets is this.
3//
4// AN ICO IS A CONTAINER, NOT AN IMAGE. It holds N frames at different sizes,
5// and the entry's width/height byte is 0 for 256 -- a reader that takes the
6// byte literally reports a 0x0 image for the largest frame there is. We pick
7// the LARGEST frame by area (that is what a fingerprint wants) and decode it.
8//
9// A frame is EITHER a PNG (modern, and the only way 256x256 is normally
10// stored) OR a headerless BITMAPINFOHEADER DIB. The DIB's declared height is
11// DOUBLE the real height because an AND (transparency) mask is appended below
12// the XOR (colour) rows; halving it is not a heuristic, it is the format.
13// The mask itself is ignored: a fingerprint wants pixels, not alpha.
14//
15// genealogy_id: msdn_ico_format
16// lineage_id: nx_ico_v1
17// license_tier: ORIGINAL
18
19import "nx_syscalls.nx"
20import "nx_png_decoder.nx"
21
22func ico_b(d: *u8, o: i64) -> i64 { return (d[o] as i64) & 255 }
23func ico_u16(d: *u8, o: i64) -> i64 { return ico_b(d,o) | (ico_b(d,o+1) << 8) }
24func ico_u32(d: *u8, o: i64) -> i64 {
25 return ico_b(d,o) | (ico_b(d,o+1) << 8) | (ico_b(d,o+2) << 16) | (ico_b(d,o+3) << 24)
26}
27
28// a headerless DIB (BITMAPINFOHEADER + optional palette + XOR rows + AND mask)
29func ico_dib_rgb(raw: *u8, n: i64, off: i64, len: i64, out_wh: *i64) -> *u8 {
30 if off + 40 > n { return 0 as *u8 }
31 let hs: i64 = ico_u32(raw, off)
32 if hs < 40 { return 0 as *u8 }
33 let w: i64 = ico_u32(raw, off + 4)
34 let h2: i64 = ico_u32(raw, off + 8)
35 let bits: i64 = ico_u16(raw, off + 14)
36 let comp: i64 = ico_u32(raw, off + 16)
37 if comp != 0 { return 0 as *u8 } // BI_RGB only; no RLE/PNG-in-DIB
38 if w <= 0 { return 0 as *u8 }
39 if h2 <= 0 { return 0 as *u8 }
40 let h: i64 = h2 / 2 // XOR rows only; the AND mask is the rest
41 if h <= 0 { return 0 as *u8 }
42 if bits != 32 { if bits != 24 { if bits != 8 { if bits != 4 { return 0 as *u8 } } } }
43
44 var ncol: i64 = ico_u32(raw, off + 32)
45 if ncol == 0 { if bits <= 8 { ncol = 1 << bits } }
46 let pal: i64 = off + hs
47 var px: i64 = pal
48 if bits <= 8 { px = pal + ncol*4 }
49 // rows are 4-byte aligned, bottom-up
50 let stride: i64 = ((w * bits + 31) / 32) * 4
51 if px + stride*h > n { return 0 as *u8 }
52 if px + stride*h > off + len { return 0 as *u8 }
53
54 let rgb: *u8 = sys_mmap(w * h * 3 + 64)
55 var y: i64 = 0
56 while y < h {
57 let sy: i64 = h - 1 - y // stored bottom-up
58 var x: i64 = 0
59 while x < w {
60 let dof: i64 = (y*w + x) * 3
61 if bits == 32 {
62 let so: i64 = px + sy*stride + x*4
63 rgb[dof] = raw[so+2]
64 rgb[dof+1] = raw[so+1]
65 rgb[dof+2] = raw[so]
66 } else {
67 if bits == 24 {
68 let so: i64 = px + sy*stride + x*3
69 rgb[dof] = raw[so+2]
70 rgb[dof+1] = raw[so+1]
71 rgb[dof+2] = raw[so]
72 } else {
73 var idx: i64 = 0
74 if bits == 8 {
75 idx = ico_b(raw, px + sy*stride + x)
76 } else {
77 let byte: i64 = ico_b(raw, px + sy*stride + (x >> 1))
78 if (x & 1) == 0 { idx = (byte >> 4) & 15 } else { idx = byte & 15 }
79 }
80 if idx >= ncol { idx = 0 }
81 let eo: i64 = pal + idx*4 // palette entries are BGRA
82 if eo + 3 > n { return 0 as *u8 }
83 rgb[dof] = raw[eo+2]
84 rgb[dof+1] = raw[eo+1]
85 rgb[dof+2] = raw[eo]
86 } }
87 x = x + 1
88 }
89 y = y + 1
90 }
91 out_wh[0] = w
92 out_wh[1] = h
93 return rgb
94}
95
96func ico_decode_rgb(raw: *u8, n: i64, out_wh: *i64) -> *u8 {
97 if n < 22 { return 0 as *u8 }
98 if ico_u16(raw, 0) != 0 { return 0 as *u8 }
99 let kind: i64 = ico_u16(raw, 2)
100 if kind != 1 { if kind != 2 { return 0 as *u8 } } // 1=icon 2=cursor
101 let cnt: i64 = ico_u16(raw, 4)
102 if cnt <= 0 { return 0 as *u8 }
103 if 6 + cnt*16 > n { return 0 as *u8 }
104
105 // pick the largest frame by area; a 0 dimension byte MEANS 256
106 var best: i64 = 0 - 1
107 var best_area: i64 = 0 - 1
108 var i: i64 = 0
109 while i < cnt {
110 let e: i64 = 6 + i*16
111 var ew: i64 = ico_b(raw, e)
112 var eh: i64 = ico_b(raw, e+1)
113 if ew == 0 { ew = 256 }
114 if eh == 0 { eh = 256 }
115 let area: i64 = ew * eh
116 if area > best_area { best_area = area; best = i }
117 i = i + 1
118 }
119 if best < 0 { return 0 as *u8 }
120 let e: i64 = 6 + best*16
121 let flen: i64 = ico_u32(raw, e + 8)
122 let foff: i64 = ico_u32(raw, e + 12)
123 if flen <= 0 { return 0 as *u8 }
124 if foff + flen > n { return 0 as *u8 }
125
126 // a PNG-bodied frame decodes through the existing PNG decoder
127 var ispng: i64 = 0
128 if flen >= 8 {
129 if ico_b(raw, foff) == 137 { if ico_b(raw, foff+1) == 80 {
130 if ico_b(raw, foff+2) == 78 { if ico_b(raw, foff+3) == 71 { ispng = 1 } } } }
131 }
132 if ispng == 1 {
133 let res: *NxPngResult = nx_png_decode(raw + foff, flen)
134 if res == (0 as *NxPngResult) { return 0 as *u8 }
135 if res.error_code != 0 { return 0 as *u8 }
136 if res.header == (0 as *NxPngHeader) { return 0 as *u8 }
137 let pw: i64 = res.header.width
138 let ph: i64 = res.header.height
139 if pw <= 0 { return 0 as *u8 }
140 if ph <= 0 { return 0 as *u8 }
141 let ch: i64 = res.n_channels
142 let bpp: i64 = res.bytes_per_pix
143 var bps: i64 = 1
144 if ch > 0 { bps = bpp / ch }
145 if bps < 1 { bps = 1 }
146 let src: *u8 = res.pixels
147 let prgb: *u8 = sys_mmap(pw * ph * 3 + 64)
148 var p: i64 = 0
149 while p < pw*ph {
150 let b: i64 = p * bpp
151 if ch >= 3 {
152 prgb[p*3] = src[b]
153 prgb[p*3+1] = src[b+bps]
154 prgb[p*3+2] = src[b+2*bps]
155 } else {
156 prgb[p*3] = src[b]
157 prgb[p*3+1] = src[b]
158 prgb[p*3+2] = src[b]
159 }
160 p = p + 1
161 }
162 out_wh[0] = pw
163 out_wh[1] = ph
164 return prgb
165 }
166 return ico_dib_rgb(raw, n, foff, flen, out_wh)
167}