code wiki / (root) / nx_webp_alpha.nx

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}