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}