code wiki / (root) / nx_paint_blend.nx

nx_paint_blend.nx source

↩ module page · 117 lines · 4132 B

1// nx_paint_blend.nx -- alpha-compositing rect fill into a Framebuffer 2// using the standard "source over" Porter-Duff operator. Phase 4 3// fourth primitive of NISHI_BROWSER_ROADMAP. Pair to nx_paint_solid_rect 4// (which OVERWRITES pixels regardless of source alpha); this primitive 5// MIXES per pixel. 6// 7// Formulas (Porter-Duff "A over B", premultiplied math executed with 8// straight-alpha 8-bit source + destination): 9// out.a = src.a + dst.a * (255 - src.a) / 255 10// out.r = (src.r * src.a + dst.r * (255 - src.a) * dst.a / 255) / out.a (compositing) 11// 12// Simplified for the common case where destination is opaque 13// (dst.a = 255), reducing to: 14// out.r = (src.r * src.a + dst.r * (255 - src.a)) / 255 15// out.g = (src.g * src.a + dst.g * (255 - src.a)) / 255 16// out.b = (src.b * src.a + dst.b * (255 - src.a)) / 255 17// out.a = 255 18// We use the simplified form because the framebuffer's destination 19// is always opaque (no transparency on the final screen surface). 20// 21// When src.a == 0 the pixel is unchanged (early-out). 22// When src.a == 255 the result is equivalent to nx_paint_solid_rect 23// (full overwrite, faster path). 24// 25// What it does NOT handle yet (Phase 4b queued): 26// - non-opaque destination (would need premultiplied-alpha buffer) 27// - non-source-over operators (multiply, screen, overlay, etc.) 28// - per-pixel-alpha source (currently solid color only) 29// - SIMD acceleration 30// 31// Per cardinal feedback-honest-perf-verdict-no-aspirational-claims: 32// gap list is EXACT. 33// 34// genealogy_id: porter_duff_compositing_1984 + w3c_css_compositing_module_1 35// lineage_id: nishi_browser_paint_blend_v1 36 37import "nx_syscalls.nx" 38import "nx_css_color_decode.nx" 39import "nx_paint_solid_rect.nx" 40 41// ---- helpers ---- 42 43// Integer (Source over) blend of one pixel. 44// src is (sr, sg, sb, sa) in [0, 255] 45// dst is the current pixel bytes; we read + overwrite in place. 46func _blend_pixel(fb_pixels: *u8, off: i64, 47 sr: i64, sg: i64, sb: i64, sa: i64) -> i64 { 48 let inv_a: i64 = 255 - sa 49 let dr: i64 = (fb_pixels[off + 0] as i64) & 255 50 let dg: i64 = (fb_pixels[off + 1] as i64) & 255 51 let db: i64 = (fb_pixels[off + 2] as i64) & 255 52 53 // Round-half-up by adding 127 before dividing -- matches CSS spec 54 // recommendation for 8-bit blend (avoids systematic bias toward 55 // darker pixels from truncation). 56 let nr: i64 = (sr * sa + dr * inv_a + 127) / 255 57 let ng: i64 = (sg * sa + dg * inv_a + 127) / 255 58 let nb: i64 = (sb * sa + db * inv_a + 127) / 255 59 60 fb_pixels[off + 0] = nr as u8 61 fb_pixels[off + 1] = ng as u8 62 fb_pixels[off + 2] = nb as u8 63 fb_pixels[off + 3] = 255 as u8 64 return 0 65} 66 67// ---- public API ---- 68 69// Blend a solid rect (x, y, w, h) with color `color` into `fb`. 70// Returns pixels written (0 on fully clipped / zero-area). Composes 71// the same clipping rules as nx_paint_solid_rect. 72// 73// Fast paths: src.a == 0 returns 0 with no fb writes; src.a == 255 74// falls through to nx_paint_solid_rect for the byte-overwrite 75// optimization. 76func nx_paint_blend_rect(fb: *Framebuffer, 77 x: i64, y: i64, w: i64, h: i64, 78 color: *CssColor) -> i64 { 79 if w <= 0 { return 0 } 80 if h <= 0 { return 0 } 81 82 let sa: i64 = color.a & 255 83 if sa == 0 { return 0 } 84 if sa == 255 { 85 return nx_paint_solid_rect(fb, x, y, w, h, color) 86 } 87 88 // Clip. 89 var x0: i64 = x 90 var y0: i64 = y 91 var x1: i64 = x + w 92 var y1: i64 = y + h 93 if x0 < 0 { x0 = 0 } 94 if y0 < 0 { y0 = 0 } 95 if x1 > fb.width { x1 = fb.width } 96 if y1 > fb.height { y1 = fb.height } 97 if x0 >= x1 { return 0 } 98 if y0 >= y1 { return 0 } 99 100 let sr: i64 = color.r & 255 101 let sg: i64 = color.g & 255 102 let sb: i64 = color.b & 255 103 104 var written: i64 = 0 105 var py: i64 = y0 106 while py < y1 { 107 var px: i64 = x0 108 while px < x1 { 109 let off: i64 = (py * fb.width + px) * 4 110 _blend_pixel(fb.pixels, off, sr, sg, sb, sa) 111 written = written + 1 112 px = px + 1 113 } 114 py = py + 1 115 } 116 return written 117}