nx_paint_solid_rect.nx source
↩ module page · 141 lines · 4700 B
1// nx_paint_solid_rect.nx -- fill a rectangle of pixels in a
2// caller-supplied framebuffer with a solid RGBA color. Phase 4 first
3// primitive of NISHI_BROWSER_ROADMAP -- bridges Phase 3 layout
4// (assigns x/y/w/h per box) to actual pixel output.
5//
6// Framebuffer layout: row-major RGBA u8 quads.
7// pixels[(y * width + x) * 4 + 0] = R
8// pixels[(y * width + x) * 4 + 1] = G
9// pixels[(y * width + x) * 4 + 2] = B
10// pixels[(y * width + x) * 4 + 3] = A
11//
12// Caller-owns-memory contract: the Framebuffer struct points at a
13// pre-allocated pixel array of width * height * 4 bytes.
14//
15// Clipping: rectangles outside the framebuffer bounds are clipped to
16// the visible region; out-of-bounds writes never happen. Zero-area
17// rectangles are silently skipped.
18//
19// What it does NOT handle yet (Phase 4b queued):
20// - alpha blending (currently overwrites destination -- assumes
21// opaque foreground; transparent fills via this primitive will
22// paint solid black behind transparent areas, wrong for layered
23// elements). Composes with future nx_paint_blend.
24// - sub-pixel positioning (integer-pixel only)
25// - anti-aliased edges (Phase 4b after sub-pixel)
26// - clip-rect stacking for overflow:hidden
27// - SIMD acceleration (Phase 4c when nx_simd lands)
28//
29// Per cardinal feedback-honest-perf-verdict-no-aspirational-claims:
30// gap list is EXACT.
31//
32// genealogy_id: substrate_browser_phase_4_paint_seed +
33// classical_2d_rect_fill
34// lineage_id: nishi_browser_paint_solid_rect_v1
35//
36// nx_safety_envelope:
37// intended_use: "Solid rect fill -- browser paint substrate"
38// sil_target: SIL1
39// evidence: [bounded_pixel_loop,
40// explicit_clipping,
41// no_internal_allocation,
42// no_FP_arithmetic]
43// verdict: NOT_YET_EVALUATED
44
45import "nx_syscalls.nx"
46import "nx_css_color_decode.nx"
47
48// Caller-supplied framebuffer wrapping a row-major RGBA8888 pixel
49// array.
50struct Framebuffer {
51 pixels: *u8,
52 width: i64,
53 height: i64
54}
55
56const NX_FRAMEBUFFER_BYTES: i64 = 24
57
58// ---- public API ----
59
60// Initialize a framebuffer wrapper. Does NOT zero the pixel array.
61// Caller is responsible for clearing if desired.
62func nx_framebuffer_init(fb: *Framebuffer, pixels: *u8,
63 width: i64, height: i64) -> i64 {
64 fb.pixels = pixels
65 fb.width = width
66 fb.height = height
67 return 0
68}
69
70// Zero the framebuffer's pixel array (4 bytes per pixel).
71func nx_framebuffer_clear(fb: *Framebuffer) -> i64 {
72 let total: i64 = fb.width * fb.height * 4
73 var i: i64 = 0
74 while i < total {
75 fb.pixels[i] = 0 as u8
76 i = i + 1
77 }
78 return 0
79}
80
81// Read a single pixel's RGBA bytes into the caller-supplied CssColor
82// struct. Returns 1 on success, 0 if (x, y) is out of bounds.
83func nx_framebuffer_get_pixel(fb: *Framebuffer, x: i64, y: i64,
84 out: *CssColor) -> i64 {
85 if x < 0 { return 0 }
86 if y < 0 { return 0 }
87 if x >= fb.width { return 0 }
88 if y >= fb.height { return 0 }
89 let off: i64 = (y * fb.width + x) * 4
90 out.r = (fb.pixels[off + 0] as i64) & 255
91 out.g = (fb.pixels[off + 1] as i64) & 255
92 out.b = (fb.pixels[off + 2] as i64) & 255
93 out.a = (fb.pixels[off + 3] as i64) & 255
94 return 1
95}
96
97// Fill the rectangle (x, y, w, h) in `fb` with the solid color
98// `color`. Returns the number of pixels actually written (zero on
99// fully-clipped or zero-area rectangles).
100//
101// Clipping: rectangle is intersected with [0, fb.width) x [0, fb.height).
102func nx_paint_solid_rect(fb: *Framebuffer,
103 x: i64, y: i64, w: i64, h: i64,
104 color: *CssColor) -> i64 {
105 if w <= 0 { return 0 }
106 if h <= 0 { return 0 }
107
108 // Clip to framebuffer bounds.
109 var x0: i64 = x
110 var y0: i64 = y
111 var x1: i64 = x + w
112 var y1: i64 = y + h
113 if x0 < 0 { x0 = 0 }
114 if y0 < 0 { y0 = 0 }
115 if x1 > fb.width { x1 = fb.width }
116 if y1 > fb.height { y1 = fb.height }
117 if x0 >= x1 { return 0 }
118 if y0 >= y1 { return 0 }
119
120 let r: i64 = color.r & 255
121 let g: i64 = color.g & 255
122 let b: i64 = color.b & 255
123 let a: i64 = color.a & 255
124
125 var written: i64 = 0
126 var py: i64 = y0
127 while py < y1 {
128 var px: i64 = x0
129 while px < x1 {
130 let off: i64 = (py * fb.width + px) * 4
131 fb.pixels[off + 0] = r as u8
132 fb.pixels[off + 1] = g as u8
133 fb.pixels[off + 2] = b as u8
134 fb.pixels[off + 3] = a as u8
135 written = written + 1
136 px = px + 1
137 }
138 py = py + 1
139 }
140 return written
141}