nx_webp_alpha.nx source
↩ module page · 229 lines · 7671 B
1// nx_webp_alpha.nx -- WebP VP8X extended container + ALPH alpha channel.
2//
3// Completes the extended half of WebP. Transparent WebP is everywhere on the
4// real web, and it does NOT live in the VP8/VP8L bitstream: a VP8X file
5// carries the colour planes in one chunk and an entirely separate ALPH chunk
6// holding the alpha plane, filtered independently.
7//
8// Filters are implemented in BOTH directions so the pair proves by exact
9// round-trip rather than against reference images.
10//
11// THE FIRST ROW AND COLUMN PREDICT DIFFERENTLY PER FILTER. Horizontal
12// predicts the first column from ABOVE; vertical predicts the first row from
13// the LEFT. Using the nominal direction at the edges reads outside the plane,
14// and clamping that to zero instead produces an alpha channel that is correct
15// in the interior and wrong along two edges -- which looks like a subtle halo
16// rather than a decode failure.
17//
18// THE GRADIENT PREDICTOR CLAMPS BEFORE ADDING. clamp(left + top - topleft) is
19// clamped to 0..255 and THEN the residual is added modulo 256. Clamping after
20// the addition, or not clamping at all, diverges only where the gradient
21// overshoots -- i.e. at sharp alpha edges, exactly where it is visible.
22//
23// genealogy_id: webp_container_spec_vp8x_alph
24// lineage_id: nx_webp_alpha_v1
25// license_tier: ORIGINAL
26
27import "nx_syscalls.nx"
28const NX_MAGIC_16777216: i64 = 16777216
29
30const NX_VP8X_FLAG_ICC: i64 = 0x20
31const NX_VP8X_FLAG_ALPHA: i64 = 0x10
32const NX_VP8X_FLAG_EXIF: i64 = 0x08
33const NX_VP8X_FLAG_XMP: i64 = 0x04
34const NX_VP8X_FLAG_ANIM: i64 = 0x02
35
36const NX_ALPH_FILTER_NONE: i64 = 0
37const NX_ALPH_FILTER_HORIZ: i64 = 1
38const NX_ALPH_FILTER_VERT: i64 = 2
39const NX_ALPH_FILTER_GRAD: i64 = 3
40
41const NX_VP8X_FLD_FLAGS: i64 = 0
42const NX_VP8X_FLD_WIDTH: i64 = 1
43const NX_VP8X_FLD_HEIGHT: i64 = 2
44
45const NX_ALPH_FLD_PREPROC: i64 = 0
46const NX_ALPH_FLD_FILTER: i64 = 1
47const NX_ALPH_FLD_COMPRESS: i64 = 2
48
49func nx_wa_at(d: *u8, i: i64) -> i64 { return (d[i] as i64) & 255 }
50
51func nx_wa_le24(d: *u8, off: i64) -> i64 {
52 return nx_wa_at(d, off) | (nx_wa_at(d, off+1) << 8) | (nx_wa_at(d, off+2) << 16)
53}
54
55func nx_wa_le24_w(o: *u8, off: i64, v: i64) -> i64 {
56 o[off] = (v & 255) as u8
57 o[off+1] = ((v >> 8) & 255) as u8
58 o[off+2] = ((v >> 16) & 255) as u8
59 return 3
60}
61
62func nx_wa_clamp255(v: i64) -> i64 {
63 if v < 0 { return 0 }
64 if v > 255 { return 255 }
65 return v
66}
67
68// ===== VP8X chunk =================================================
69//
70// Ten payload bytes: one flags byte, three reserved, then canvas width-1 and
71// height-1 as 24-bit little-endian.
72
73func nx_vp8x_write(o: *u8, cap: i64, flags: i64, width: i64, height: i64) -> i64 {
74 if width <= 0 { return 0 }
75 if height <= 0 { return 0 }
76 if width > NX_MAGIC_16777216 { return 0 }
77 if height > NX_MAGIC_16777216 { return 0 }
78 if cap < 18 { return 0 }
79 o[0] = 0x56 as u8; o[1] = 0x50 as u8; o[2] = 0x38 as u8; o[3] = 0x58 as u8
80 o[4] = 10 as u8; o[5] = 0 as u8; o[6] = 0 as u8; o[7] = 0 as u8
81 o[8] = (flags & 255) as u8
82 o[9] = 0 as u8; o[10] = 0 as u8; o[11] = 0 as u8
83 nx_wa_le24_w(o, 12, width - 1)
84 nx_wa_le24_w(o, 15, height - 1)
85 return 18
86}
87
88func nx_vp8x_parse(d: *u8, n: i64, off: i64, fld: *i64) -> i64 {
89 if off + 18 > n { return 0 }
90 if nx_wa_at(d, off) != 0x56 { return 0 }
91 if nx_wa_at(d, off+1) != 0x50 { return 0 }
92 if nx_wa_at(d, off+2) != 0x38 { return 0 }
93 if nx_wa_at(d, off+3) != 0x58 { return 0 }
94 if nx_wa_le24(d, off+4) != 10 { return 0 }
95 fld[NX_VP8X_FLD_FLAGS] = nx_wa_at(d, off+8)
96 fld[NX_VP8X_FLD_WIDTH] = nx_wa_le24(d, off+12) + 1
97 fld[NX_VP8X_FLD_HEIGHT] = nx_wa_le24(d, off+15) + 1
98 return 1
99}
100
101func nx_vp8x_has_alpha(flags: i64) -> i64 {
102 if (flags & NX_VP8X_FLAG_ALPHA) != 0 { return 1 }
103 return 0
104}
105
106// ===== ALPH header ================================================
107//
108// One byte: two reserved bits, then preprocessing, filtering and compression
109// as two-bit fields.
110
111func nx_alph_parse_header(b: i64, fld: *i64) -> i64 {
112 if ((b >> 6) & 3) != 0 { return 0 }
113 fld[NX_ALPH_FLD_PREPROC] = (b >> 4) & 3
114 fld[NX_ALPH_FLD_FILTER] = (b >> 2) & 3
115 fld[NX_ALPH_FLD_COMPRESS] = b & 3
116 return 1
117}
118
119func nx_alph_build_header(preproc: i64, filter: i64, compress: i64) -> i64 {
120 if preproc < 0 { return 0 - 1 }
121 if preproc > 3 { return 0 - 1 }
122 if filter < 0 { return 0 - 1 }
123 if filter > 3 { return 0 - 1 }
124 if compress < 0 { return 0 - 1 }
125 if compress > 1 { return 0 - 1 }
126 return ((preproc & 3) << 4) | ((filter & 3) << 2) | (compress & 3)
127}
128
129// ===== the four inverse filters ===================================
130
131func nx_alph_unfilter(data: *u8, out: *u8, w: i64, h: i64, filter: i64) -> i64 {
132 if w <= 0 { return 0 }
133 if h <= 0 { return 0 }
134 if filter < 0 { return 0 }
135 if filter > 3 { return 0 }
136
137 if filter == NX_ALPH_FILTER_NONE {
138 var i: i64 = 0
139 while i < w * h { out[i] = data[i]; i = i + 1 }
140 return 1
141 }
142
143 out[0] = data[0]
144
145 // first row
146 var x: i64 = 1
147 while x < w {
148 // every filter predicts the first row from the LEFT
149 out[x] = ((nx_wa_at(data, x) + nx_wa_at(out, x - 1)) & 255) as u8
150 x = x + 1
151 }
152
153 var y: i64 = 1
154 while y < h {
155 let row: i64 = y * w
156 // ...and the first column from ABOVE
157 out[row] = ((nx_wa_at(data, row) + nx_wa_at(out, row - w)) & 255) as u8
158 x = 1
159 while x < w {
160 var pred: i64 = 0
161 if filter == NX_ALPH_FILTER_HORIZ {
162 pred = nx_wa_at(out, row + x - 1)
163 } else {
164 if filter == NX_ALPH_FILTER_VERT {
165 pred = nx_wa_at(out, row + x - w)
166 } else {
167 let left: i64 = nx_wa_at(out, row + x - 1)
168 let top: i64 = nx_wa_at(out, row + x - w)
169 let tl: i64 = nx_wa_at(out, row + x - w - 1)
170 // CLAMP the predictor, THEN add the residual modulo 256
171 pred = nx_wa_clamp255(left + top - tl)
172 } }
173 out[row + x] = ((nx_wa_at(data, row + x) + pred) & 255) as u8
174 x = x + 1
175 }
176 y = y + 1
177 }
178 return 1
179}
180
181// ===== the four forward filters ===================================
182//
183// Exact inverses of the above, so the pair round-trips.
184
185func nx_alph_filter(src: *u8, out: *u8, w: i64, h: i64, filter: i64) -> i64 {
186 if w <= 0 { return 0 }
187 if h <= 0 { return 0 }
188 if filter < 0 { return 0 }
189 if filter > 3 { return 0 }
190
191 if filter == NX_ALPH_FILTER_NONE {
192 var i: i64 = 0
193 while i < w * h { out[i] = src[i]; i = i + 1 }
194 return 1
195 }
196
197 out[0] = src[0]
198
199 var x: i64 = 1
200 while x < w {
201 out[x] = ((nx_wa_at(src, x) - nx_wa_at(src, x - 1)) & 255) as u8
202 x = x + 1
203 }
204
205 var y: i64 = 1
206 while y < h {
207 let row: i64 = y * w
208 out[row] = ((nx_wa_at(src, row) - nx_wa_at(src, row - w)) & 255) as u8
209 x = 1
210 while x < w {
211 var pred: i64 = 0
212 if filter == NX_ALPH_FILTER_HORIZ {
213 pred = nx_wa_at(src, row + x - 1)
214 } else {
215 if filter == NX_ALPH_FILTER_VERT {
216 pred = nx_wa_at(src, row + x - w)
217 } else {
218 let left: i64 = nx_wa_at(src, row + x - 1)
219 let top: i64 = nx_wa_at(src, row + x - w)
220 let tl: i64 = nx_wa_at(src, row + x - w - 1)
221 pred = nx_wa_clamp255(left + top - tl)
222 } }
223 out[row + x] = ((nx_wa_at(src, row + x) - pred) & 255) as u8
224 x = x + 1
225 }
226 y = y + 1
227 }
228 return 1
229}