1use crate::os_lib::css::parse_color;
4use alloc::vec::Vec;
5
6pub(super) fn draw_individual_borders(
9 screen: &crate::kernel::draw::Screen,
10 mut x0: i32,
11 mut y0: i32,
12 mut x1: i32,
13 mut y1: i32,
14 el: &super::RenderElement,
15 is_hovered: bool,
16 is_focused: bool,
17 is_active: bool,
18) {
19 if let Some((top, right, bottom, left)) = el.client_insets {
21 x0 += left;
22 y0 += top;
23 x1 -= right;
24 y1 -= bottom;
25 if x0 > x1 || y0 > y1 {
27 return;
28 }
29 }
30 if x0 >= x1 || y0 >= y1 {
31 return;
32 }
33
34 let all_transparent = [
49 el.border_top_color,
50 el.border_right_color,
51 el.border_bottom_color,
52 el.border_left_color,
53 el.border_color,
54 ]
55 .iter()
56 .flatten()
57 .all(|c| (c >> 24) & 0xFF == 0);
58 let has_any_color = [
59 el.border_top_color,
60 el.border_right_color,
61 el.border_bottom_color,
62 el.border_left_color,
63 el.border_color,
64 ]
65 .iter()
66 .any(|c| c.is_some());
67 if has_any_color && all_transparent {
68 return;
69 }
70
71 if el.border_top_width > 0 && !el.border_top_style.is_none() {
73 let color = if is_active {
74 el.active_border_color
75 .or(el.border_top_color)
76 .or(el.border_color)
77 .unwrap_or(el.color)
78 } else if is_hovered {
79 el.hover_border_color
80 .or(el.border_top_color)
81 .or(el.border_color)
82 .unwrap_or(el.color)
83 } else if is_focused {
84 el.focus_border_color
85 .or(el.border_top_color)
86 .or(el.border_color)
87 .unwrap_or(el.color)
88 } else {
89 el.border_top_color.or(el.border_color).unwrap_or(el.color)
90 };
91 let t_w = el.border_top_width;
92 if el.border_top_style == super::BorderStyle::Dashed {
93 let dash = 6;
94 let gap = 4;
95 let mut x = x0;
96 while x < x1 {
97 let end = (x + dash).min(x1);
98 screen.boxfill(
99 x as u32,
100 y0 as u32,
101 end as u32,
102 (y0 + t_w) as u32,
103 crate::kernel::draw::Color(color),
104 );
105 x += dash + gap;
106 }
107 } else {
108 screen.boxfill(
109 x0 as u32,
110 y0 as u32,
111 x1 as u32,
112 (y0 + t_w) as u32,
113 crate::kernel::draw::Color(color),
114 );
115 }
116 }
117
118 if el.border_bottom_width > 0 && !el.border_bottom_style.is_none() {
120 let color = if is_active {
121 el.active_border_color
122 .or(el.border_bottom_color)
123 .or(el.border_color)
124 .unwrap_or(el.color)
125 } else if is_hovered {
126 el.hover_border_color
127 .or(el.border_bottom_color)
128 .or(el.border_color)
129 .unwrap_or(el.color)
130 } else if is_focused {
131 el.focus_border_color
132 .or(el.border_bottom_color)
133 .or(el.border_color)
134 .unwrap_or(el.color)
135 } else {
136 el.border_bottom_color
137 .or(el.border_color)
138 .unwrap_or(el.color)
139 };
140 let b_w = el.border_bottom_width;
141 if el.border_bottom_style == super::BorderStyle::Dashed {
142 let dash = 6;
143 let gap = 4;
144 let mut x = x0;
145 while x < x1 {
146 let end = (x + dash).min(x1);
147 screen.boxfill(
148 x as u32,
149 (y1 - b_w) as u32,
150 end as u32,
151 y1 as u32,
152 crate::kernel::draw::Color(color),
153 );
154 x += dash + gap;
155 }
156 } else {
157 screen.boxfill(
158 x0 as u32,
159 (y1 - b_w) as u32,
160 x1 as u32,
161 y1 as u32,
162 crate::kernel::draw::Color(color),
163 );
164 }
165 }
166
167 if el.border_left_width > 0 && !el.border_left_style.is_none() {
169 let color = if is_active {
170 el.active_border_color
171 .or(el.border_left_color)
172 .or(el.border_color)
173 .unwrap_or(el.color)
174 } else if is_hovered {
175 el.hover_border_color
176 .or(el.border_left_color)
177 .or(el.border_color)
178 .unwrap_or(el.color)
179 } else if is_focused {
180 el.focus_border_color
181 .or(el.border_left_color)
182 .or(el.border_color)
183 .unwrap_or(el.color)
184 } else {
185 el.border_left_color.or(el.border_color).unwrap_or(el.color)
186 };
187 let l_w = el.border_left_width;
188 if el.border_left_style == super::BorderStyle::Dashed {
189 let dash = 6;
190 let gap = 4;
191 let mut y = y0;
192 while y < y1 {
193 let end = (y + dash).min(y1);
194 screen.boxfill(
195 x0 as u32,
196 y as u32,
197 (x0 + l_w) as u32,
198 end as u32,
199 crate::kernel::draw::Color(color),
200 );
201 y += dash + gap;
202 }
203 } else {
204 screen.boxfill(
205 x0 as u32,
206 y0 as u32,
207 (x0 + l_w) as u32,
208 y1 as u32,
209 crate::kernel::draw::Color(color),
210 );
211 }
212 }
213
214 if el.border_right_width > 0 && !el.border_right_style.is_none() {
216 let color = if is_active {
217 el.active_border_color
218 .or(el.border_right_color)
219 .or(el.border_color)
220 .unwrap_or(el.color)
221 } else if is_hovered {
222 el.hover_border_color
223 .or(el.border_right_color)
224 .or(el.border_color)
225 .unwrap_or(el.color)
226 } else if is_focused {
227 el.focus_border_color
228 .or(el.border_right_color)
229 .or(el.border_color)
230 .unwrap_or(el.color)
231 } else {
232 el.border_right_color
233 .or(el.border_color)
234 .unwrap_or(el.color)
235 };
236 let r_w = el.border_right_width;
237 if el.border_right_style == super::BorderStyle::Dashed {
238 let dash = 6;
239 let gap = 4;
240 let mut y = y0;
241 while y < y1 {
242 let end = (y + dash).min(y1);
243 screen.boxfill(
244 (x1 - r_w) as u32,
245 y as u32,
246 x1 as u32,
247 end as u32,
248 crate::kernel::draw::Color(color),
249 );
250 y += dash + gap;
251 }
252 } else {
253 screen.boxfill(
254 (x1 - r_w) as u32,
255 y0 as u32,
256 x1 as u32,
257 y1 as u32,
258 crate::kernel::draw::Color(color),
259 );
260 }
261 }
262}
263
264pub fn draw_border(
265 screen: &crate::kernel::draw::Screen,
266 x0: i32,
267 y0: i32,
268 x1: i32,
269 y1: i32,
270 color_val: u32,
271) {
272 if x0 >= x1 || y0 >= y1 {
273 return;
274 }
275 screen.boxfill(
276 x0 as u32,
277 y0 as u32,
278 x1 as u32,
279 (y0 + 1) as u32,
280 crate::kernel::draw::Color(color_val),
281 );
282 screen.boxfill(
283 x0 as u32,
284 (y1 - 1) as u32,
285 x1 as u32,
286 y1 as u32,
287 crate::kernel::draw::Color(color_val),
288 );
289 screen.boxfill(
290 x0 as u32,
291 y0 as u32,
292 (x0 + 1) as u32,
293 y1 as u32,
294 crate::kernel::draw::Color(color_val),
295 );
296 screen.boxfill(
297 (x1 - 1) as u32,
298 y0 as u32,
299 x1 as u32,
300 y1 as u32,
301 crate::kernel::draw::Color(color_val),
302 );
303}
304
305pub(super) fn draw_border_with_style(
306 screen: &crate::kernel::draw::Screen,
307 x0: i32,
308 y0: i32,
309 x1: i32,
310 y1: i32,
311 color_val: u32,
312 style: super::BorderStyle,
313) {
314 if style.is_none() {
315 return;
316 }
317 if style == super::BorderStyle::Dashed {
318 draw_dashed_border(screen, x0, y0, x1, y1, color_val);
319 return;
320 }
321 draw_border(screen, x0, y0, x1, y1, color_val);
322}
323
324fn draw_dashed_border(
325 screen: &crate::kernel::draw::Screen,
326 x0: i32,
327 y0: i32,
328 x1: i32,
329 y1: i32,
330 color_val: u32,
331) {
332 if x0 >= x1 || y0 >= y1 {
333 return;
334 }
335 let dash = 6;
336 let gap = 4;
337 let mut x = x0;
338 while x < x1 {
339 let end = (x + dash).min(x1);
340 screen.boxfill(
341 x as u32,
342 y0 as u32,
343 end as u32,
344 (y0 + 1) as u32,
345 crate::kernel::draw::Color(color_val),
346 );
347 screen.boxfill(
348 x as u32,
349 (y1 - 1) as u32,
350 end as u32,
351 y1 as u32,
352 crate::kernel::draw::Color(color_val),
353 );
354 x += dash + gap;
355 }
356 let mut y = y0;
357 while y < y1 {
358 let end = (y + dash).min(y1);
359 screen.boxfill(
360 x0 as u32,
361 y as u32,
362 (x0 + 1) as u32,
363 end as u32,
364 crate::kernel::draw::Color(color_val),
365 );
366 screen.boxfill(
367 (x1 - 1) as u32,
368 y as u32,
369 x1 as u32,
370 end as u32,
371 crate::kernel::draw::Color(color_val),
372 );
373 y += dash + gap;
374 }
375}
376
377pub(super) fn extract_border_color(
378 styled: &crate::os_lib::css::StyledNode<'_>,
379 default_color: u32,
380) -> Option<u32> {
381 for key in [
382 "border-color",
383 "border-top-color",
384 "border-right-color",
385 "border-bottom-color",
386 "border-left-color",
387 ] {
388 if let Some(v) = styled.value(key) {
389 if let Some(c) = parse_color(&v) {
390 return Some(c);
391 }
392 }
393 }
394
395 if let Some(v) = styled.value("border") {
396 for token in v.split_whitespace().rev() {
397 if let Some(c) = parse_color(token) {
398 return Some(c);
399 }
400 }
401 }
402
403 Some(default_color)
404}
405
406pub(super) fn extract_border_style(styled: &crate::os_lib::css::StyledNode<'_>) -> super::BorderStyle {
407 if let Some(v) = styled.value("border-style") {
408 let style = v.trim();
409 if !style.is_empty() {
410 return super::BorderStyle::parse(style);
411 }
412 }
413
414 if let Some(v) = styled.value("border") {
415 for token in v.split_whitespace() {
416 let token_l = token.trim().to_lowercase();
417 if matches!(
418 token_l.as_str(),
419 "none" | "solid" | "dashed" | "dotted" | "double"
420 ) {
421 return super::BorderStyle::parse(&token_l);
422 }
423 }
424 }
425
426 super::BorderStyle::Solid
427}
428
429pub fn draw_rotated_rect_filled(
432 screen: &crate::kernel::draw::Screen,
433 cx: i32,
434 cy: i32,
435 w: i32,
436 h: i32,
437 angle_deg: f32,
438 color: u32,
439) {
440 if w <= 0 || h <= 0 {
441 return;
442 }
443 let rad = angle_deg * core::f32::consts::PI / 180.0;
444 let cos_a = libm::cosf(rad);
445 let sin_a = libm::sinf(rad);
446 let hw = w as f32 / 2.0;
448 let hh = h as f32 / 2.0;
449 let bound_w = libm::ceilf(libm::fabsf(hw * cos_a) + libm::fabsf(hh * sin_a)) as i32;
450 let bound_h = libm::ceilf(libm::fabsf(hw * sin_a) + libm::fabsf(hh * cos_a)) as i32;
451 let draw_color = crate::kernel::draw::Color(color | 0xFF000000);
452 for oy in -bound_h..=bound_h {
453 for ox in -bound_w..=bound_w {
454 let sx = ox as f32 * cos_a + oy as f32 * sin_a;
456 let sy = -ox as f32 * sin_a + oy as f32 * cos_a;
457 if sx >= -hw && sx <= hw && sy >= -hh && sy <= hh {
458 let px = cx + ox;
459 let py = cy + oy;
460 if px >= 0 && py >= 0 {
461 screen.draw_pixel(px as u32, py as u32, draw_color);
462 }
463 }
464 }
465 }
466}
467
468pub fn draw_skewed_rect_filled(
470 screen: &crate::kernel::draw::Screen,
471 cx: i32,
472 cy: i32,
473 w: i32,
474 h: i32,
475 skew_x_deg: f32,
476 skew_y_deg: f32,
477 color: u32,
478) {
479 if w <= 0 || h <= 0 {
480 return;
481 }
482 let rad_x = skew_x_deg * core::f32::consts::PI / 180.0;
483 let rad_y = skew_y_deg * core::f32::consts::PI / 180.0;
484 let tan_x = libm::tanf(rad_x);
485 let tan_y = libm::tanf(rad_y);
486
487 let hw = w as f32 / 2.0;
488 let hh = h as f32 / 2.0;
489
490 let det = 1.0 - tan_x * tan_y;
491 if det.abs() < 1e-6 {
492 return;
493 }
494
495 let bound_w = libm::ceilf(hw + hh * libm::fabsf(tan_x)) as i32;
496 let bound_h = libm::ceilf(hh + hw * libm::fabsf(tan_y)) as i32;
497 let draw_color = crate::kernel::draw::Color(color | 0xFF000000);
498
499 for oy in -bound_h..=bound_h {
500 for ox in -bound_w..=bound_w {
501 let sx = (ox as f32 - oy as f32 * tan_x) / det;
502 let sy = (oy as f32 - ox as f32 * tan_y) / det;
503 if sx >= -hw && sx <= hw && sy >= -hh && sy <= hh {
504 let px = cx + ox;
505 let py = cy + oy;
506 if px >= 0 && py >= 0 {
507 screen.draw_pixel(px as u32, py as u32, draw_color);
508 }
509 }
510 }
511 }
512}
513
514pub enum ClipShape {
516 Circle { cx: i32, cy: i32, r_sq: i64 },
517 Ellipse { cx: i32, cy: i32, rx: i32, ry: i32 },
518 Inset { x0: i32, y0: i32, x1: i32, y1: i32, radius: (i32, i32, i32, i32) },
519 Polygon { points: Vec<(i32, i32)>, nonzero: bool },
520}
521
522pub fn parse_clip_shape(css: &str, w: i32, h: i32) -> Option<ClipShape> {
525 let s = css.trim();
526
527 if let Some(inner) = s.strip_prefix("circle(").and_then(|t| t.strip_suffix(')')) {
528 let mut parts = inner.splitn(2, "at");
529 let r_str = parts.next().unwrap_or("50%").trim();
530 let r = clip_len(r_str, w.min(h)) as i32;
531 let (cx, cy) = if let Some(at) = parts.next() {
532 let mut ps = at.split_whitespace();
533 let cx = ps.next().map(|sv| clip_pos(sv, w)).unwrap_or(w / 2);
534 let cy = ps.next().map(|sv| clip_pos(sv, h)).unwrap_or(h / 2);
535 (cx, cy)
536 } else {
537 (w / 2, h / 2)
538 };
539 return Some(ClipShape::Circle { cx, cy, r_sq: (r as i64) * (r as i64) });
540 }
541
542 if let Some(inner) = s.strip_prefix("ellipse(").and_then(|t| t.strip_suffix(')')) {
543 let mut parts = inner.splitn(2, "at");
544 let radii_str = parts.next().unwrap_or("50% 50%").trim();
545 let mut radii = radii_str.split_whitespace();
546 let rx = radii.next().map(|sv| clip_len(sv, w) as i32).unwrap_or(w / 2);
547 let ry = radii.next().map(|sv| clip_len(sv, h) as i32).unwrap_or(h / 2);
548 let (cx, cy) = if let Some(at) = parts.next() {
549 let mut ps = at.split_whitespace();
550 let cx = ps.next().map(|sv| clip_pos(sv, w)).unwrap_or(w / 2);
551 let cy = ps.next().map(|sv| clip_pos(sv, h)).unwrap_or(h / 2);
552 (cx, cy)
553 } else {
554 (w / 2, h / 2)
555 };
556 return Some(ClipShape::Ellipse { cx, cy, rx, ry });
557 }
558
559 if let Some(inner) = s.strip_prefix("inset(").and_then(|t| t.strip_suffix(')')) {
560 let mut parts = inner.split("round");
561 let body = parts.next().unwrap_or(inner).trim();
562 let round_part = parts.next().map(|s| s.trim());
563 let mut vals = body.split_whitespace().map(|sv| clip_len(sv, w.max(h)) as i32);
564 let top = vals.next().unwrap_or(0);
565 let right = vals.next().unwrap_or(top);
566 let bottom = vals.next().unwrap_or(top);
567 let left = vals.next().unwrap_or(right);
568
569 let radius = if let Some(r_str) = round_part {
570 let mut r_vals = r_str.split_whitespace().map(|sv| clip_len(sv, w.min(h)) as i32);
571 let r_tl = r_vals.next().unwrap_or(0);
572 let r_tr = r_vals.next().unwrap_or(r_tl);
573 let r_br = r_vals.next().unwrap_or(r_tl);
574 let r_bl = r_vals.next().unwrap_or(r_tr);
575 (r_tl, r_tr, r_br, r_bl)
576 } else {
577 (0, 0, 0, 0)
578 };
579
580 return Some(ClipShape::Inset { x0: left, y0: top, x1: w - right, y1: h - bottom, radius });
581 }
582
583 if let Some(inner) = s.strip_prefix("polygon(").and_then(|t| t.strip_suffix(')')) {
584 let mut nonzero = false;
585 let mut pairs_src = inner;
586 if let Some(rest) = inner.trim_start().strip_prefix("nonzero") {
587 nonzero = true;
588 pairs_src = rest.trim_start().strip_prefix(',').unwrap_or(rest);
589 } else if let Some(rest) = inner.trim_start().strip_prefix("evenodd") {
590 pairs_src = rest.trim_start().strip_prefix(',').unwrap_or(rest);
591 }
592 let mut points = Vec::new();
593 for pair in pairs_src.split(',') {
594 let mut coords = pair.split_whitespace();
595 if let (Some(xs), Some(ys)) = (coords.next(), coords.next()) {
596 points.push((clip_len(xs, w) as i32, clip_len(ys, h) as i32));
597 }
598 }
599 if points.len() >= 3 {
600 return Some(ClipShape::Polygon { points, nonzero });
601 }
602 }
603
604 None
605}
606
607pub fn clip_inside(shape: &ClipShape, ox: i32, oy: i32) -> bool {
612 match shape {
613 ClipShape::Circle { cx, cy, r_sq } => {
614 let dx = (ox - cx) as i64;
615 let dy = (oy - cy) as i64;
616 dx * dx + dy * dy <= *r_sq
617 }
618 ClipShape::Ellipse { cx, cy, rx, ry } => {
619 if *rx == 0 || *ry == 0 { return false; }
620 let dx = (ox - cx) as f32 / (*rx as f32);
621 let dy = (oy - cy) as f32 / (*ry as f32);
622 dx * dx + dy * dy <= 1.0
623 }
624 ClipShape::Inset { x0, y0, x1, y1, radius } => {
625 if ox >= *x0 && ox < *x1 && oy >= *y0 && oy < *y1 {
626 if *radius == (0, 0, 0, 0) {
627 true
628 } else {
629 let rx = ox - *x0;
630 let ry = oy - *y0;
631 let iw = *x1 - *x0;
632 let ih = *y1 - *y0;
633 point_in_rounded_rect(rx, ry, iw, ih, *radius)
634 }
635 } else {
636 false
637 }
638 }
639 ClipShape::Polygon { points, nonzero } => {
640 if *nonzero {
641 polygon_contains_nonzero(points, ox, oy)
642 } else {
643 polygon_contains(points, ox, oy)
644 }
645 }
646 }
647}
648
649fn polygon_contains(points: &[(i32, i32)], px: i32, py: i32) -> bool {
652 let n = points.len();
653 if n < 3 { return false; }
654 let mut inside = false;
655 let mut j = n - 1;
656 for i in 0..n {
657 let (xi, yi) = points[i];
658 let (xj, yj) = points[j];
659 if (yi > py) != (yj > py) && yj != yi {
660 let x_intersect = (xj - xi) * (py - yi) / (yj - yi) + xi;
661 if px < x_intersect {
662 inside = !inside;
663 }
664 }
665 j = i;
666 }
667 inside
668}
669
670fn polygon_contains_nonzero(points: &[(i32, i32)], px: i32, py: i32) -> bool {
674 let n = points.len();
675 if n < 3 { return false; }
676 let mut winding = 0i32;
677 let mut j = n - 1;
678 for i in 0..n {
679 let (xi, yi) = points[i];
680 let (xj, yj) = points[j];
681 if yj <= py {
682 if yi > py {
683 let cross = (xi - xj) * (py - yj) - (px - xj) * (yi - yj);
685 if cross > 0 {
686 winding += 1;
687 }
688 }
689 } else if yi <= py {
690 let cross = (xi - xj) * (py - yj) - (px - xj) * (yi - yj);
692 if cross < 0 {
693 winding -= 1;
694 }
695 }
696 j = i;
697 }
698 winding != 0
699}
700
701fn clip_pos(s: &str, base: i32) -> i32 {
707 match s.trim().to_ascii_lowercase().as_str() {
708 "left" | "top" => 0,
709 "right" | "bottom" => base,
710 "center" => base / 2,
711 _ => clip_len(s, base) as i32,
712 }
713}
714
715fn clip_len(s: &str, base: i32) -> f32 {
716 let s = s.trim();
717 if let Some(pct) = s.strip_suffix('%') {
718 pct.trim().parse::<f32>().unwrap_or(50.0) * base as f32 / 100.0
719 } else if let Some(px) = s.strip_suffix("px") {
720 px.trim().parse::<f32>().unwrap_or(0.0)
721 } else {
722 s.parse::<f32>().unwrap_or(0.0)
723 }
724}
725
726pub fn point_in_rounded_rect(px: i32, py: i32, w: i32, h: i32, r: (i32, i32, i32, i32)) -> bool {
735 if px < 0 || py < 0 || px >= w || py >= h {
736 return false;
737 }
738
739 let tl = r.0.min(w / 2).min(h / 2).max(0);
740 let tr = r.1.min(w / 2).min(h / 2).max(0);
741 let br = r.2.min(w / 2).min(h / 2).max(0);
742 let bl = r.3.min(w / 2).min(h / 2).max(0);
743
744 if tl > 0 && px < tl && py < tl {
745 let dx = (px - tl) as i64;
746 let dy = (py - tl) as i64;
747 return dx * dx + dy * dy <= (tl as i64) * (tl as i64);
748 }
749 if tr > 0 && px >= w - tr && py < tr {
750 let dx = (px - (w - tr)) as i64;
751 let dy = (py - tr) as i64;
752 return dx * dx + dy * dy <= (tr as i64) * (tr as i64);
753 }
754 if bl > 0 && px < bl && py >= h - bl {
755 let dx = (px - bl) as i64;
756 let dy = (py - (h - bl)) as i64;
757 return dx * dx + dy * dy <= (bl as i64) * (bl as i64);
758 }
759 if br > 0 && px >= w - br && py >= h - br {
760 let dx = (px - (w - br)) as i64;
761 let dy = (py - (h - br)) as i64;
762 return dx * dx + dy * dy <= (br as i64) * (br as i64);
763 }
764 true
765}
766
767pub fn draw_gradation(
768 screen: &crate::kernel::draw::Screen,
769 x0: i32,
770 y0: i32,
771 w: i32,
772 h: i32,
773 c1: u32,
774 c2: u32,
775) {
776 let r1 = ((c1 >> 16) & 0xFF) as f32;
777 let g1 = ((c1 >> 8) & 0xFF) as f32;
778 let b1 = (c1 & 0xFF) as f32;
779
780 let r2 = ((c2 >> 16) & 0xFF) as f32;
781 let g2 = ((c2 >> 8) & 0xFF) as f32;
782 let b2 = (c2 & 0xFF) as f32;
783
784 for dy in 0..h {
785 let py = y0 + dy;
786 let t = dy as f32 / h as f32;
787 let r = ((1.0 - t) * r1 + t * r2) as u32;
788 let g = ((1.0 - t) * g1 + t * g2) as u32;
789 let b = ((1.0 - t) * b1 + t * b2) as u32;
790 let c = 0xFF000000 | (r << 16) | (g << 8) | b;
791 screen.boxfill(
792 x0 as u32,
793 py as u32,
794 (x0 + w) as u32,
795 (py + 1) as u32,
796 crate::kernel::draw::Color(c),
797 );
798 }
799}
800
801pub fn draw_linear_gradient(
815 screen: &crate::kernel::draw::Screen,
816 x0: i32,
817 y0: i32,
818 w: i32,
819 h: i32,
820 grad: &super::LinearGradient,
821) {
822 if w <= 0 || h <= 0 || grad.stops.is_empty() {
823 return;
824 }
825
826 let angle_rad = grad.angle_deg * core::f32::consts::PI / 180.0;
827 let dx = libm::sinf(angle_rad);
829 let dy = -libm::cosf(angle_rad);
830
831 let corners = [
833 (0.0, 0.0),
834 (w as f32, 0.0),
835 (0.0, h as f32),
836 (w as f32, h as f32),
837 ];
838
839 let mut min_proj = f32::MAX;
840 let mut max_proj = f32::MIN;
841 for &(cx, cy) in &corners {
842 let p = cx * dx + cy * dy;
843 if p < min_proj { min_proj = p; }
844 if p > max_proj { max_proj = p; }
845 }
846
847 let length = max_proj - min_proj;
848 if length <= 0.0 {
849 let c = grad.stops[0].color;
850 screen.boxfill(x0 as u32, y0 as u32, (x0 + w) as u32, (y0 + h) as u32, crate::kernel::draw::Color(c));
851 return;
852 }
853
854 use super::gradient_lut::{classify_axis, lut_index, lut_size, Axis};
864 let lut_n = lut_size(length);
865 let mut lut: alloc::vec::Vec<u32> = alloc::vec::Vec::with_capacity(lut_n);
866 for i in 0..lut_n {
867 let t = if lut_n <= 1 {
868 0.0
869 } else {
870 i as f32 / (lut_n - 1) as f32
871 };
872 lut.push(sample_gradient_stops(&grad.stops, t));
873 }
874
875 let axis = classify_axis(dx, dy);
878
879 for y in 0..h {
880 let py = y0 + y;
881 let p_y = (y as f32) * dy;
882
883 if axis == Axis::Vertical {
884 let t = (p_y - min_proj) / length;
886 let color = lut[lut_index(t, lut_n)];
887 let alpha = ((color >> 24) & 0xFF) as u8;
888 if alpha == 0xFF {
889 screen.boxfill(
890 x0 as u32,
891 py as u32,
892 (x0 + w) as u32,
893 (py + 1) as u32,
894 crate::kernel::draw::Color(color),
895 );
896 } else {
897 for x in 0..w {
899 screen.draw_pixel_alpha(
900 (x0 + x) as u32,
901 py as u32,
902 crate::kernel::draw::Color(color),
903 alpha,
904 );
905 }
906 }
907 continue;
908 }
909
910 for x in 0..w {
911 let px = x0 + x;
912 let p_x = (x as f32) * dx;
913 let p = p_x + p_y;
914 let t = (p - min_proj) / length;
915
916 let color = lut[lut_index(t, lut_n)];
917 let alpha = ((color >> 24) & 0xFF) as u8;
918 screen.draw_pixel_alpha(px as u32, py as u32, crate::kernel::draw::Color(color), alpha);
919 }
920 }
921}
922
923pub(crate) fn sample_gradient_stops(stops: &[super::GradientStop], t: f32) -> u32 {
930 if stops.is_empty() {
931 return 0;
932 }
933 if t <= stops[0].position {
934 return stops[0].color;
935 }
936 let last = stops.last().unwrap_or(&stops[0]);
937 if t >= last.position {
938 return last.color;
939 }
940 for i in 0..stops.len() - 1 {
941 let s1 = &stops[i];
942 let s2 = &stops[i + 1];
943 if t >= s1.position && t <= s2.position {
944 let range = s2.position - s1.position;
945 let local_t = if range > 0.0 { (t - s1.position) / range } else { 0.0 };
946 let a1 = ((s1.color >> 24) & 0xFF) as f32;
947 let r1 = ((s1.color >> 16) & 0xFF) as f32;
948 let g1 = ((s1.color >> 8) & 0xFF) as f32;
949 let b1 = (s1.color & 0xFF) as f32;
950 let a2 = ((s2.color >> 24) & 0xFF) as f32;
951 let r2 = ((s2.color >> 16) & 0xFF) as f32;
952 let g2 = ((s2.color >> 8) & 0xFF) as f32;
953 let b2 = (s2.color & 0xFF) as f32;
954 let a = ((1.0 - local_t) * a1 + local_t * a2) as u32;
955 let r = ((1.0 - local_t) * r1 + local_t * r2) as u32;
956 let g = ((1.0 - local_t) * g1 + local_t * g2) as u32;
957 let b = ((1.0 - local_t) * b1 + local_t * b2) as u32;
958 return (a << 24) | (r << 16) | (g << 8) | b;
959 }
960 }
961 last.color
962}
963
964pub fn draw_conic_gradient(
969 screen: &crate::kernel::draw::Screen,
970 x0: i32,
971 y0: i32,
972 w: i32,
973 h: i32,
974 stops: &[super::GradientStop],
975) {
976 if w <= 0 || h <= 0 || stops.is_empty() {
977 return;
978 }
979 let cx = w as f32 / 2.0;
980 let cy = h as f32 / 2.0;
981 const PI: f32 = core::f32::consts::PI;
982 for dy in 0..h {
983 for dx in 0..w {
984 let px = dx as f32 - cx;
985 let py = dy as f32 - cy;
986 let mut angle = libm::atan2f(px, -py);
989 if angle < 0.0 {
990 angle += 2.0 * PI;
991 }
992 let t = angle / (2.0 * PI);
993 let color = sample_gradient_stops(stops, t);
994 screen.draw_pixel_alpha(
995 (x0 + dx) as u32,
996 (y0 + dy) as u32,
997 crate::kernel::draw::Color(color),
998 255,
999 );
1000 }
1001 }
1002}
1003
1004pub fn draw_radial_gradient(
1005 screen: &crate::kernel::draw::Screen,
1006 x0: i32,
1007 y0: i32,
1008 w: i32,
1009 h: i32,
1010 grad: &super::RadialGradient,
1011) {
1012 if w <= 0 || h <= 0 || grad.stops.is_empty() {
1013 return;
1014 }
1015
1016 let cx = (w as f32) * grad.pos_x;
1017 let cy = (h as f32) * grad.pos_y;
1018
1019 let max_rx = if grad.pos_x > 0.5 { cx } else { (w as f32) - cx };
1021 let max_ry = if grad.pos_y > 0.5 { cy } else { (h as f32) - cy };
1022
1023 let (r_x, r_y) = match grad.shape {
1024 super::RadialShape::Circle => {
1025 let r = libm::sqrtf(max_rx * max_rx + max_ry * max_ry);
1026 (r, r)
1027 },
1028 super::RadialShape::Ellipse => {
1029 (max_rx, max_ry)
1030 }
1031 };
1032
1033 if r_x <= 0.0 || r_y <= 0.0 {
1034 let c = grad.stops[0].color;
1035 screen.boxfill(x0 as u32, y0 as u32, (x0 + w) as u32, (y0 + h) as u32, crate::kernel::draw::Color(c));
1036 return;
1037 }
1038
1039 for y in 0..h {
1040 let py = y0 + y;
1041 let dy = (y as f32) - cy;
1042 for x in 0..w {
1043 let px = x0 + x;
1044 let dx = (x as f32) - cx;
1045
1046 let t = libm::sqrtf((dx * dx) / (r_x * r_x) + (dy * dy) / (r_y * r_y));
1047 let color = sample_gradient_stops(&grad.stops, t);
1048 let alpha = ((color >> 24) & 0xFF) as u8;
1049 screen.draw_pixel_alpha(px as u32, py as u32, crate::kernel::draw::Color(color), alpha);
1050 }
1051 }
1052}
1053
1054fn box_inset(el: &super::RenderElement, mode: &str) -> (i32, i32, i32, i32) {
1058 match mode {
1059 "content-box" => (
1060 el.border_top_width + el.padding_top,
1061 el.border_right_width + el.padding_right,
1062 el.border_bottom_width + el.padding_bottom,
1063 el.border_left_width + el.padding_left,
1064 ),
1065 "padding-box" => (
1066 el.border_top_width,
1067 el.border_right_width,
1068 el.border_bottom_width,
1069 el.border_left_width,
1070 ),
1071 _ => (0, 0, 0, 0),
1072 }
1073}
1074
1075pub(super) fn bg_clip_rect(
1078 el: &super::RenderElement,
1079 x0_rect: i32,
1080 y0_rect: i32,
1081 x1_rect: i32,
1082 y1_rect: i32,
1083) -> (u32, u32, u32, u32) {
1084 let (top, right, bottom, left) = box_inset(el, &el.bg_clip);
1085 (
1086 (x0_rect + left).max(0) as u32,
1087 (y0_rect + top).max(0) as u32,
1088 (x1_rect - right).max(0) as u32,
1089 (y1_rect - bottom).max(0) as u32,
1090 )
1091}
1092
1093pub(super) fn bg_origin_shift(
1098 el: &super::RenderElement,
1099 eff_w: i32,
1100 eff_h: i32,
1101) -> (i32, i32, i32, i32) {
1102 let (top, right, bottom, left) = box_inset(el, &el.bg_origin);
1103 (
1104 left,
1105 top,
1106 (eff_w - left - right).max(1),
1107 (eff_h - top - bottom).max(1),
1108 )
1109}
1110
1111pub(crate) static BG_IMG_TICKS: core::sync::atomic::AtomicUsize =
1120 core::sync::atomic::AtomicUsize::new(0);
1121
1122pub(crate) static GRAD_TICKS: core::sync::atomic::AtomicUsize =
1124 core::sync::atomic::AtomicUsize::new(0);
1125pub(crate) static BORDER_TICKS: core::sync::atomic::AtomicUsize =
1127 core::sync::atomic::AtomicUsize::new(0);
1128pub(crate) static IMG_TICKS: core::sync::atomic::AtomicUsize =
1130 core::sync::atomic::AtomicUsize::new(0);
1131pub(crate) static OTHERFILL_TICKS: core::sync::atomic::AtomicUsize =
1135 core::sync::atomic::AtomicUsize::new(0);
1136
1137struct TickTimer(usize, &'static core::sync::atomic::AtomicUsize);
1139impl Drop for TickTimer {
1140 fn drop(&mut self) {
1141 self.1.fetch_add(
1142 crate::kernel::timer::get_ticks().wrapping_sub(self.0),
1143 core::sync::atomic::Ordering::Relaxed,
1144 );
1145 }
1146}
1147
1148struct BgImgTimer(usize);
1150impl Drop for BgImgTimer {
1151 fn drop(&mut self) {
1152 BG_IMG_TICKS.fetch_add(
1153 crate::kernel::timer::get_ticks().wrapping_sub(self.0),
1154 core::sync::atomic::Ordering::Relaxed,
1155 );
1156 }
1157}
1158
1159pub(super) fn draw_background_image(
1168 screen: &crate::kernel::draw::Screen,
1169 img: &super::DecodedImage,
1170 el: &super::RenderElement,
1171 x0_rect: i32,
1172 y0_rect: i32,
1173 eff_w: i32,
1174 eff_h: i32,
1175 clip_s: &Option<ClipShape>,
1176 attachment_shift_y: i32,
1181 origin_shift_x: i32,
1188 origin_shift_y: i32,
1189 origin_w: i32,
1190 origin_h: i32,
1191 layer_override: Option<(
1192 super::BgSizeMode,
1193 super::BgRepeatMode,
1194 super::BgRepeatMode,
1195 f32,
1196 f32,
1197 )>,
1198) {
1199 use super::{BgSizeMode, BgSizeVal};
1200 let _bg_timer = BgImgTimer(crate::kernel::timer::get_ticks());
1201 let (mut x0_rect, mut y0_rect, mut eff_w, mut eff_h) = (x0_rect, y0_rect, eff_w, eff_h);
1203 if let Some((top, right, bottom, left)) = el.client_insets {
1204 x0_rect += left;
1205 y0_rect += top;
1206 eff_w -= left + right;
1207 eff_h -= top + bottom;
1208 if eff_w <= 0 || eff_h <= 0 {
1209 return;
1210 }
1211 }
1212 let (el_bg_size, el_bg_repeat_x, el_bg_repeat_y, el_bg_pos_x, el_bg_pos_y) =
1213 layer_override.unwrap_or((el.bg_size, el.bg_repeat_x, el.bg_repeat_y, el.bg_pos_x, el.bg_pos_y));
1214 let box_w = eff_w.max(1) as u32;
1218 let box_h = eff_h.max(1) as u32;
1219 let origin_box_w = origin_w.max(1) as u32;
1222 let origin_box_h = origin_h.max(1) as u32;
1223 if img.width == 0 || img.height == 0 {
1224 return;
1225 }
1226
1227 fn resolve_axis(v: BgSizeVal, box_dim: u32, img_dim: u32) -> Option<u32> {
1228 match v {
1229 BgSizeVal::Auto => None,
1230 BgSizeVal::Px(px) => Some(px.max(1) as u32),
1231 BgSizeVal::Percent(p) => Some(((box_dim as f32) * p).max(1.0) as u32),
1232 }
1233 .or(Some(img_dim))
1234 }
1235
1236 let (draw_w, draw_h): (u32, u32) = match el_bg_size {
1237 BgSizeMode::Stretch => (origin_box_w, origin_box_h),
1238 BgSizeMode::Cover => {
1239 let scale = (origin_box_w as f32 / img.width as f32)
1240 .max(origin_box_h as f32 / img.height as f32);
1241 (
1242 ((img.width as f32) * scale).max(1.0) as u32,
1243 ((img.height as f32) * scale).max(1.0) as u32,
1244 )
1245 }
1246 BgSizeMode::Contain => {
1247 let scale = (origin_box_w as f32 / img.width as f32)
1248 .min(origin_box_h as f32 / img.height as f32);
1249 (
1250 ((img.width as f32) * scale).max(1.0) as u32,
1251 ((img.height as f32) * scale).max(1.0) as u32,
1252 )
1253 }
1254 BgSizeMode::Explicit(wv, hv) => {
1255 match (wv, hv) {
1257 (BgSizeVal::Auto, BgSizeVal::Auto) => (img.width, img.height),
1258 (BgSizeVal::Auto, _) => {
1259 let h = resolve_axis(hv, origin_box_h, img.height).unwrap_or(img.height);
1260 let w = ((img.width as f32) * (h as f32 / img.height as f32)).max(1.0) as u32;
1261 (w, h)
1262 }
1263 (_, BgSizeVal::Auto) => {
1264 let w = resolve_axis(wv, origin_box_w, img.width).unwrap_or(img.width);
1265 let h = ((img.height as f32) * (w as f32 / img.width as f32)).max(1.0) as u32;
1266 (w, h)
1267 }
1268 _ => (
1269 resolve_axis(wv, origin_box_w, img.width).unwrap_or(img.width),
1270 resolve_axis(hv, origin_box_h, img.height).unwrap_or(img.height),
1271 ),
1272 }
1273 }
1274 };
1275
1276 fn tile_layout(mode: super::BgRepeatMode, box_dim: u32, tile_dim: u32) -> (u32, u32, bool) {
1281 use super::BgRepeatMode;
1282 match mode {
1283 BgRepeatMode::Round => {
1284 let n =
1285 (libm::roundf(box_dim as f32 / tile_dim.max(1) as f32) as u32).max(1);
1286 let eff = (box_dim / n).max(1);
1287 (eff, eff, true)
1288 }
1289 BgRepeatMode::Space => {
1290 let n = box_dim / tile_dim.max(1);
1291 if n <= 1 {
1292 (tile_dim, tile_dim, false)
1293 } else {
1294 let gap = box_dim.saturating_sub(n * tile_dim) / (n - 1);
1295 (tile_dim, tile_dim + gap, true)
1296 }
1297 }
1298 _ => (tile_dim, tile_dim, false),
1299 }
1300 }
1301
1302 let (draw_w_eff, pitch_w, ignore_pos_x) = tile_layout(el_bg_repeat_x, box_w, draw_w);
1303 let (draw_h_eff, pitch_h, ignore_pos_y) = tile_layout(el_bg_repeat_y, box_h, draw_h);
1304 let off_x = if ignore_pos_x {
1309 0
1310 } else {
1311 ((origin_box_w as i32 - draw_w as i32) as f32 * el_bg_pos_x) as i32
1312 };
1313 let off_y = if ignore_pos_y {
1314 0
1315 } else {
1316 ((origin_box_h as i32 - draw_h as i32) as f32 * el_bg_pos_y) as i32
1317 };
1318
1319 for row in 0..box_h {
1320 let rel_y = (row as i32 + attachment_shift_y - origin_shift_y) - off_y;
1321 let sample_y = match el_bg_repeat_y {
1322 super::BgRepeatMode::NoRepeat => rel_y,
1323 super::BgRepeatMode::Space => {
1324 let m = rel_y.rem_euclid(pitch_h as i32);
1325 if m >= draw_h_eff as i32 {
1326 continue;
1327 }
1328 m
1329 }
1330 _ => rel_y.rem_euclid(draw_h_eff as i32),
1331 };
1332 if sample_y < 0 || sample_y >= draw_h_eff as i32 {
1333 continue;
1334 }
1335 let src_row = (sample_y as u32 * img.height) / draw_h_eff.max(1);
1336 for col in 0..box_w {
1337 if clip_s
1338 .as_ref()
1339 .map(|cs| !clip_inside(cs, col as i32, row as i32))
1340 .unwrap_or(false)
1341 {
1342 continue;
1343 }
1344 let rel_x = (col as i32 - origin_shift_x) - off_x;
1345 let sample_x = match el_bg_repeat_x {
1346 super::BgRepeatMode::NoRepeat => rel_x,
1347 super::BgRepeatMode::Space => {
1348 let m = rel_x.rem_euclid(pitch_w as i32);
1349 if m >= draw_w_eff as i32 {
1350 continue;
1351 }
1352 m
1353 }
1354 _ => rel_x.rem_euclid(draw_w_eff as i32),
1355 };
1356 if sample_x < 0 || sample_x >= draw_w_eff as i32 {
1357 continue;
1358 }
1359 let src_col = (sample_x as u32 * img.width) / draw_w_eff.max(1);
1360 let idx = ((src_row * img.width + src_col) as usize) * 4;
1361 if idx + 4 <= img.rgba.len() {
1362 let mut r = img.rgba[idx];
1363 let mut g = img.rgba[idx + 1];
1364 let mut b = img.rgba[idx + 2];
1365 let a = img.rgba[idx + 3];
1366 if !el.background_blend_mode.is_empty()
1370 && el.background_blend_mode != "normal"
1371 {
1372 if let Some(bg) = el.bg_color {
1373 let dst = (
1374 ((bg >> 16) & 0xFF) as u8,
1375 ((bg >> 8) & 0xFF) as u8,
1376 (bg & 0xFF) as u8,
1377 );
1378 let (br, bgc, bb) =
1379 blend_pixel(&el.background_blend_mode, (r, g, b), dst);
1380 r = br;
1381 g = bgc;
1382 b = bb;
1383 }
1384 }
1385 let color = crate::kernel::draw::Color(
1386 0xFF000000 | ((r as u32) << 16) | ((g as u32) << 8) | (b as u32),
1387 );
1388 screen.draw_pixel_alpha(x0_rect as u32 + col, y0_rect as u32 + row, color, a);
1389 }
1390 }
1391 }
1392}
1393
1394pub(super) fn draw_decoration_line(
1398 screen: &crate::kernel::draw::Screen,
1399 x0: u32,
1400 y: u32,
1401 x1: u32,
1402 style: &str,
1403 color: u32,
1404 thickness: u32,
1405) {
1406 if x1 <= x0 {
1407 return;
1408 }
1409 let t = thickness.max(1);
1410 match style {
1411 "dotted" => {
1412 let mut x = x0;
1413 while x < x1 {
1414 let seg_end = (x + 2).min(x1);
1415 screen.boxfill(x, y, seg_end, y + t, crate::kernel::draw::Color(color));
1416 x += 4;
1417 }
1418 }
1419 "dashed" => {
1420 let mut x = x0;
1421 while x < x1 {
1422 let seg_end = (x + 5).min(x1);
1423 screen.boxfill(x, y, seg_end, y + t, crate::kernel::draw::Color(color));
1424 x += 8;
1425 }
1426 }
1427 "wavy" => {
1428 let amplitude = 2i32;
1430 let period = 6i32;
1431 for x in x0..x1 {
1432 let phase = (x as i32 - x0 as i32) % period;
1433 let dy = if phase < period / 2 { phase - amplitude / 2 } else { period - phase - amplitude / 2 };
1434 let py = (y as i32 + dy).max(0) as u32;
1435 screen.boxfill(x, py, x + 1, py + t, crate::kernel::draw::Color(color));
1436 }
1437 }
1438 "double" => {
1439 screen.boxfill(x0, y, x1, y + t, crate::kernel::draw::Color(color));
1440 let y2 = y + t + 2;
1441 screen.boxfill(x0, y2, x1, y2 + t, crate::kernel::draw::Color(color));
1442 }
1443 _ => {
1444 screen.boxfill(x0, y, x1, y + t, crate::kernel::draw::Color(color));
1445 }
1446 }
1447}
1448
1449pub(super) fn draw_outline(
1452 screen: &crate::kernel::draw::Screen,
1453 x0: i32,
1454 y0: i32,
1455 x1: i32,
1456 y1: i32,
1457 width: i32,
1458 offset: i32,
1459 color: u32,
1460 style: &str,
1461) {
1462 let ox0 = x0 - offset - width;
1463 let oy0 = y0 - offset - width;
1464 let ox1 = x1 + offset + width;
1465 let oy1 = y1 + offset + width;
1466 if matches!(style, "dashed" | "dotted") {
1469 for i in 0..width {
1470 draw_dashed_border(screen, ox0 + i, oy0 + i, ox1 - i, oy1 - i, color);
1471 }
1472 return;
1473 }
1474 for i in 0..width {
1475 draw_border(screen, ox0 + i, oy0 + i, ox1 - i, oy1 - i, color);
1476 }
1477}
1478
1479fn parse_filter_tokens(filter: &str) -> alloc::vec::Vec<(alloc::string::String, f32)> {
1484 let mut out = alloc::vec::Vec::new();
1485 let lower = filter.to_ascii_lowercase();
1486 let mut rest = lower.as_str();
1487 #[allow(clippy::string_slice)] while let Some(paren) = rest.find('(') {
1489 let name = rest[..paren].trim();
1490 if name.is_empty() {
1491 break;
1492 }
1493 let Some(close) = rest[paren..].find(')') else {
1494 break;
1495 };
1496 let arg = rest[paren + 1..paren + close].trim();
1497 let empty_default = if name == "hue-rotate" { 0.0 } else { 1.0 };
1502 let value = if arg.is_empty() {
1503 empty_default
1504 } else if let Some(pct) = arg.strip_suffix('%') {
1505 pct.parse::<f32>().unwrap_or(100.0) / 100.0
1506 } else if let Some(deg) = arg.strip_suffix("deg") {
1507 deg.parse::<f32>().unwrap_or(0.0)
1508 } else if let Some(px) = arg.strip_suffix("px") {
1509 px.parse::<f32>().unwrap_or(0.0)
1510 } else {
1511 arg.parse::<f32>().unwrap_or(empty_default)
1512 };
1513 out.push((alloc::string::String::from(name), value));
1514 rest = &rest[paren + close + 1..];
1515 }
1516 out
1517}
1518
1519pub(crate) fn parse_blur_radius(filter: &str) -> i32 {
1523 let lower = filter.to_ascii_lowercase();
1524 let Some(start) = lower.find("blur(") else {
1525 return 0;
1526 };
1527 #[allow(clippy::string_slice)] let rest = &lower[start + 5..];
1529 let Some(close) = rest.find(')') else {
1530 return 0;
1531 };
1532 #[allow(clippy::string_slice)]
1533 let arg = rest[..close].trim();
1534 let num = arg.strip_suffix("px").unwrap_or(arg).trim();
1535 match num.parse::<f32>() {
1536 Ok(v) => (v.max(0.0) as i32).clamp(0, 8),
1537 Err(_) => 0,
1538 }
1539}
1540
1541pub(crate) fn parse_drop_shadow(filter: &str) -> Option<(i32, i32, i32, u32, bool)> {
1543 let lower = filter.to_ascii_lowercase();
1544 let start = lower.find("drop-shadow(")?;
1545 #[allow(clippy::string_slice)]
1546 let rest = &lower[start + 12..];
1547 let close = rest.find(')')?;
1548 #[allow(clippy::string_slice)]
1549 let arg = rest[..close].trim();
1550
1551 let mut ox = 0i32;
1552 let mut oy = 0i32;
1553 let mut blur = 0i32;
1554 let mut shadow_color = 0x80000000u32;
1555 let mut px_count = 0;
1556
1557 for token in arg.split_whitespace() {
1558 if let Some(n) = token.strip_suffix("px") {
1559 if let Ok(n) = n.trim().parse::<f32>().map(|v| v as i32) {
1563 match px_count {
1564 0 => { ox = n; px_count += 1; }
1565 1 => { oy = n; px_count += 1; }
1566 2 => { blur = n; px_count += 1; }
1567 _ => {}
1568 }
1569 }
1570 } else if let Some(c) = crate::os_lib::css::parse_color(token) {
1571 shadow_color = c;
1572 }
1573 }
1574
1575 if px_count >= 2 {
1576 Some((ox, oy, blur, shadow_color, false))
1577 } else {
1578 None
1579 }
1580}
1581
1582pub(crate) fn sample_box_blur(
1585 img: &super::DecodedImage,
1586 src_row: u32,
1587 src_col: u32,
1588 radius: i32,
1589) -> (u8, u8, u8) {
1590 let r = radius.clamp(1, 8);
1591 let (mut sr, mut sg, mut sb, mut n) = (0i64, 0i64, 0i64, 0i64);
1592 for dy in -r..=r {
1593 let ry = src_row as i32 + dy;
1594 if ry < 0 || ry as u32 >= img.height {
1595 continue;
1596 }
1597 for dx in -r..=r {
1598 let rx = src_col as i32 + dx;
1599 if rx < 0 || rx as u32 >= img.width {
1600 continue;
1601 }
1602 let idx = ((ry as u32 * img.width + rx as u32) as usize) * 4;
1603 if idx + 3 < img.rgba.len() {
1604 sr += img.rgba[idx] as i64;
1605 sg += img.rgba[idx + 1] as i64;
1606 sb += img.rgba[idx + 2] as i64;
1607 n += 1;
1608 }
1609 }
1610 }
1611 if n == 0 {
1612 return (0, 0, 0);
1613 }
1614 ((sr / n) as u8, (sg / n) as u8, (sb / n) as u8)
1615}
1616
1617pub(crate) fn blur_screen_rect(
1619 screen: &crate::kernel::draw::Screen,
1620 x0: i32,
1621 y0: i32,
1622 x1: i32,
1623 y1: i32,
1624 radius: i32,
1625) {
1626 if radius <= 0 || x1 <= x0 || y1 <= y0 {
1627 return;
1628 }
1629 let r = radius.clamp(1, 8);
1630 let w = (x1 - x0) as usize;
1631 let h = (y1 - y0) as usize;
1632
1633 let mut buf = alloc::vec::Vec::with_capacity(w * h);
1635 for y in y0..y1 {
1636 for x in x0..x1 {
1637 buf.push(screen.read_pixel(x as u32, y as u32).0);
1638 }
1639 }
1640
1641 let blurred = super::blur_box::box_blur_rgb(&buf, w, h, r as usize);
1653
1654 for y in 0..h {
1655 let sy = y0 + y as i32;
1656 if sy < 0 || sy as u32 >= screen.height {
1657 continue;
1658 }
1659 for x in 0..w {
1660 let sx = x0 + x as i32;
1661 if sx < 0 || sx as u32 >= screen.width {
1662 continue;
1663 }
1664 screen.draw_pixel_alpha(
1665 sx as u32,
1666 sy as u32,
1667 crate::kernel::draw::Color(blurred[y * w + x]),
1668 255,
1669 );
1670 }
1671 }
1672}
1673
1674pub(crate) fn apply_backdrop_filters_to_screen_rect(
1675 screen: &crate::kernel::draw::Screen,
1676 x0: i32,
1677 y0: i32,
1678 x1: i32,
1679 y1: i32,
1680 backdrop_filter: &str,
1681) {
1682 if backdrop_filter.is_empty() || x1 <= x0 || y1 <= y0 {
1683 return;
1684 }
1685 let backdrop_blur_radius = parse_blur_radius(backdrop_filter);
1686 if backdrop_blur_radius > 0 {
1687 blur_screen_rect(screen, x0, y0, x1, y1, backdrop_blur_radius);
1688 }
1689
1690 let tokens = parse_filter_tokens(backdrop_filter);
1692 let has_color_filters = tokens.iter().any(|(name, _)| name != "blur");
1693 if !has_color_filters {
1694 return;
1695 }
1696
1697 for sy in y0..y1 {
1698 if sy < 0 || sy as u32 >= screen.height { continue; }
1699 for sx in x0..x1 {
1700 if sx < 0 || sx as u32 >= screen.width { continue; }
1701 let orig = screen.read_pixel(sx as u32, sy as u32).0;
1702 let (r, g, b) = (((orig >> 16) & 0xFF) as u8, ((orig >> 8) & 0xFF) as u8, (orig & 0xFF) as u8);
1703 let (fr, fg, fb) = apply_css_filters(backdrop_filter, r, g, b);
1704 let new_col = 0xFF000000 | ((fr as u32) << 16) | ((fg as u32) << 8) | (fb as u32);
1705 screen.draw_pixel_alpha(sx as u32, sy as u32, crate::kernel::draw::Color(new_col), 255);
1706 }
1707 }
1708}
1709
1710pub(crate) fn apply_css_filters(filter: &str, r: u8, g: u8, b: u8) -> (u8, u8, u8) {
1712 if filter.is_empty() {
1713 return (r, g, b);
1714 }
1715 let (mut rf, mut gf, mut bf) = (r as f32, g as f32, b as f32);
1716 for (name, amount) in parse_filter_tokens(filter) {
1717 match name.as_str() {
1718 "grayscale" => {
1719 let amount = amount.clamp(0.0, 1.0);
1720 let gray = 0.299 * rf + 0.587 * gf + 0.114 * bf;
1721 rf = rf + (gray - rf) * amount;
1722 gf = gf + (gray - gf) * amount;
1723 bf = bf + (gray - bf) * amount;
1724 }
1725 "brightness" => {
1726 let amount = amount.max(0.0);
1727 rf *= amount;
1728 gf *= amount;
1729 bf *= amount;
1730 }
1731 "invert" => {
1732 let amount = amount.clamp(0.0, 1.0);
1733 rf = rf + (255.0 - rf - rf) * amount;
1734 gf = gf + (255.0 - gf - gf) * amount;
1735 bf = bf + (255.0 - bf - bf) * amount;
1736 }
1737 "sepia" => {
1738 let amount = amount.clamp(0.0, 1.0);
1739 let sr = 0.393 * rf + 0.769 * gf + 0.189 * bf;
1740 let sg = 0.349 * rf + 0.686 * gf + 0.168 * bf;
1741 let sb = 0.272 * rf + 0.534 * gf + 0.131 * bf;
1742 rf = rf + (sr - rf) * amount;
1743 gf = gf + (sg - gf) * amount;
1744 bf = bf + (sb - bf) * amount;
1745 }
1746 "contrast" => {
1747 let amount = amount.max(0.0);
1748 rf = (rf - 128.0) * amount + 128.0;
1749 gf = (gf - 128.0) * amount + 128.0;
1750 bf = (bf - 128.0) * amount + 128.0;
1751 }
1752 "saturate" => {
1753 let amount = amount.max(0.0);
1754 let gray = 0.299 * rf + 0.587 * gf + 0.114 * bf;
1755 rf = gray + (rf - gray) * amount;
1756 gf = gray + (gf - gray) * amount;
1757 bf = gray + (bf - gray) * amount;
1758 }
1759 "hue-rotate" => {
1760 let deg = amount;
1761
1762 let r = rf / 255.0;
1763 let g = gf / 255.0;
1764 let b = bf / 255.0;
1765
1766 let max = r.max(g).max(b);
1767 let min = r.min(g).min(b);
1768 let l = (max + min) / 2.0;
1769
1770 let (mut h, s) = if max == min {
1771 (0.0, 0.0)
1772 } else {
1773 let d = max - min;
1774 let s = if l > 0.5 { d / (2.0 - max - min) } else { d / (max + min) };
1775 let mut h = if max == r {
1776 (g - b) / d + (if g < b { 6.0 } else { 0.0 })
1777 } else if max == g {
1778 (b - r) / d + 2.0
1779 } else {
1780 (r - g) / d + 4.0
1781 };
1782 h /= 6.0;
1783 (h, s)
1784 };
1785
1786 h = (h * 360.0 + deg) % 360.0;
1788 if h < 0.0 { h += 360.0; }
1789 h /= 360.0;
1790
1791 let hue2rgb = |p: f32, q: f32, mut t: f32| -> f32 {
1792 if t < 0.0 { t += 1.0; }
1793 if t > 1.0 { t -= 1.0; }
1794 if t < 1.0 / 6.0 { return p + (q - p) * 6.0 * t; }
1795 if t < 1.0 / 2.0 { return q; }
1796 if t < 2.0 / 3.0 { return p + (q - p) * (2.0 / 3.0 - t) * 6.0; }
1797 p
1798 };
1799
1800 if s == 0.0 {
1801 rf = l * 255.0;
1802 gf = l * 255.0;
1803 bf = l * 255.0;
1804 } else {
1805 let q = if l < 0.5 { l * (1.0 + s) } else { l + s - l * s };
1806 let p = 2.0 * l - q;
1807 rf = hue2rgb(p, q, h + 1.0 / 3.0) * 255.0;
1808 gf = hue2rgb(p, q, h) * 255.0;
1809 bf = hue2rgb(p, q, h - 1.0 / 3.0) * 255.0;
1810 }
1811 }
1812 _ => {} }
1814 }
1815 (
1816 rf.clamp(0.0, 255.0) as u8,
1817 gf.clamp(0.0, 255.0) as u8,
1818 bf.clamp(0.0, 255.0) as u8,
1819 )
1820}
1821
1822pub(crate) fn apply_css_filters_argb(filter: &str, argb: u32) -> u32 {
1824 if filter.is_empty() {
1825 return argb;
1826 }
1827 let a = argb & 0xFF000000;
1828 let r = ((argb >> 16) & 0xFF) as u8;
1829 let g = ((argb >> 8) & 0xFF) as u8;
1830 let b = (argb & 0xFF) as u8;
1831 let (r2, g2, b2) = apply_css_filters(filter, r, g, b);
1832 a | ((r2 as u32) << 16) | ((g2 as u32) << 8) | (b2 as u32)
1833}
1834
1835pub(crate) fn blend_pixel(mode: &str, src: (u8, u8, u8), dst: (u8, u8, u8)) -> (u8, u8, u8) {
1842 if matches!(mode, "hue" | "saturation" | "color" | "luminosity") {
1843 return blend_non_separable(mode, src, dst);
1844 }
1845 if mode == "soft-light" {
1846 fn soft(s: u8, d: u8) -> u8 {
1847 let (cs, cb) = (s as f32 / 255.0, d as f32 / 255.0);
1848 let dd = if cb <= 0.25 {
1849 ((16.0 * cb - 12.0) * cb + 4.0) * cb
1850 } else {
1851 libm::sqrtf(cb)
1852 };
1853 let b = if cs <= 0.5 {
1854 cb - (1.0 - 2.0 * cs) * cb * (1.0 - cb)
1855 } else {
1856 cb + (2.0 * cs - 1.0) * (dd - cb)
1857 };
1858 (b.clamp(0.0, 1.0) * 255.0) as u8
1859 }
1860 return (soft(src.0, dst.0), soft(src.1, dst.1), soft(src.2, dst.2));
1861 }
1862 fn ch(mode: &str, s: u8, d: u8) -> u8 {
1863 let (sf, df) = (s as i32, d as i32);
1864 match mode {
1865 "multiply" => (sf * df / 255) as u8,
1866 "screen" => (255 - (255 - sf) * (255 - df) / 255) as u8,
1867 "darken" => sf.min(df) as u8,
1868 "lighten" => sf.max(df) as u8,
1869 "difference" => (sf - df).unsigned_abs() as u8,
1870 "overlay" => {
1871 if df <= 127 {
1872 (2 * sf * df / 255).clamp(0, 255) as u8
1873 } else {
1874 (255 - 2 * (255 - sf) * (255 - df) / 255).clamp(0, 255) as u8
1875 }
1876 }
1877 "hard-light" => {
1878 if sf <= 127 {
1879 (2 * sf * df / 255).clamp(0, 255) as u8
1880 } else {
1881 (255 - 2 * (255 - sf) * (255 - df) / 255).clamp(0, 255) as u8
1882 }
1883 }
1884 "exclusion" => (sf + df - 2 * sf * df / 255).clamp(0, 255) as u8,
1885 "color-dodge" => {
1886 if df == 0 {
1887 0
1888 } else if sf >= 255 {
1889 255
1890 } else {
1891 (255 * df / (255 - sf)).min(255) as u8
1892 }
1893 }
1894 "color-burn" => {
1895 if df >= 255 {
1896 255
1897 } else if sf == 0 {
1898 0
1899 } else {
1900 (255 - (255 * (255 - df) / sf).min(255)) as u8
1901 }
1902 }
1903 _ => s,
1904 }
1905 }
1906 (
1907 ch(mode, src.0, dst.0),
1908 ch(mode, src.1, dst.1),
1909 ch(mode, src.2, dst.2),
1910 )
1911}
1912
1913fn blend_non_separable(mode: &str, src: (u8, u8, u8), dst: (u8, u8, u8)) -> (u8, u8, u8) {
1917 fn to_f(c: (u8, u8, u8)) -> (f32, f32, f32) {
1918 (c.0 as f32 / 255.0, c.1 as f32 / 255.0, c.2 as f32 / 255.0)
1919 }
1920 fn to_u8(c: (f32, f32, f32)) -> (u8, u8, u8) {
1921 (
1922 (c.0.clamp(0.0, 1.0) * 255.0) as u8,
1923 (c.1.clamp(0.0, 1.0) * 255.0) as u8,
1924 (c.2.clamp(0.0, 1.0) * 255.0) as u8,
1925 )
1926 }
1927 fn lum(c: (f32, f32, f32)) -> f32 {
1928 0.3 * c.0 + 0.59 * c.1 + 0.11 * c.2
1929 }
1930 fn clip_color(c: (f32, f32, f32)) -> (f32, f32, f32) {
1931 let l = lum(c);
1932 let n = c.0.min(c.1).min(c.2);
1933 let x = c.0.max(c.1).max(c.2);
1934 let mut c = c;
1935 if n < 0.0 && l != n {
1936 c = (
1937 l + (c.0 - l) * l / (l - n),
1938 l + (c.1 - l) * l / (l - n),
1939 l + (c.2 - l) * l / (l - n),
1940 );
1941 }
1942 if x > 1.0 && x != l {
1943 c = (
1944 l + (c.0 - l) * (1.0 - l) / (x - l),
1945 l + (c.1 - l) * (1.0 - l) / (x - l),
1946 l + (c.2 - l) * (1.0 - l) / (x - l),
1947 );
1948 }
1949 c
1950 }
1951 fn set_lum(c: (f32, f32, f32), l: f32) -> (f32, f32, f32) {
1952 let d = l - lum(c);
1953 clip_color((c.0 + d, c.1 + d, c.2 + d))
1954 }
1955 fn sat(c: (f32, f32, f32)) -> f32 {
1956 c.0.max(c.1).max(c.2) - c.0.min(c.1).min(c.2)
1957 }
1958 fn set_sat(c: (f32, f32, f32), s: f32) -> (f32, f32, f32) {
1959 let mut v = [c.0, c.1, c.2];
1960 let (mut lo, mut mid, mut hi) = (0usize, 1usize, 2usize);
1961 if v[lo] > v[mid] {
1963 core::mem::swap(&mut lo, &mut mid);
1964 }
1965 if v[mid] > v[hi] {
1966 core::mem::swap(&mut mid, &mut hi);
1967 }
1968 if v[lo] > v[mid] {
1969 core::mem::swap(&mut lo, &mut mid);
1970 }
1971 if v[hi] > v[lo] {
1972 v[mid] = (v[mid] - v[lo]) * s / (v[hi] - v[lo]);
1973 v[hi] = s;
1974 } else {
1975 v[mid] = 0.0;
1976 v[hi] = 0.0;
1977 }
1978 v[lo] = 0.0;
1979 (v[0], v[1], v[2])
1980 }
1981 let (cb, cs) = (to_f(dst), to_f(src));
1982 let result = match mode {
1983 "hue" => set_lum(set_sat(cs, sat(cb)), lum(cb)),
1984 "saturation" => set_lum(set_sat(cb, sat(cs)), lum(cb)),
1985 "color" => set_lum(cs, lum(cb)),
1986 "luminosity" => set_lum(cb, lum(cs)),
1987 _ => cs,
1988 };
1989 to_u8(result)
1990}
1991
1992pub(super) fn draw_image_nn_blit(
1995 screen: &crate::kernel::draw::Screen,
1996 img: &super::DecodedImage,
1997 x0: i32,
1998 y0: i32,
1999 dst_w: u32,
2000 dst_h: u32,
2001) {
2002 if img.width == 0 || img.height == 0 || dst_w == 0 || dst_h == 0 {
2003 return;
2004 }
2005 for row in 0..dst_h {
2006 let src_row = (row * img.height) / dst_h;
2007 for col in 0..dst_w {
2008 let src_col = (col * img.width) / dst_w;
2009 let idx = ((src_row * img.width + src_col) as usize) * 4;
2010 if idx + 4 <= img.rgba.len() {
2011 let r = img.rgba[idx];
2012 let g = img.rgba[idx + 1];
2013 let b = img.rgba[idx + 2];
2014 let a = img.rgba[idx + 3];
2015 let px = x0 + col as i32;
2016 let py = y0 + row as i32;
2017 if px >= 0 && py >= 0 {
2018 let color = crate::kernel::draw::Color(
2019 0xFF000000 | ((r as u32) << 16) | ((g as u32) << 8) | (b as u32),
2020 );
2021 screen.draw_pixel_alpha(px as u32, py as u32, color, a);
2022 }
2023 }
2024 }
2025 }
2026}
2027
2028pub(super) fn draw_img_object_fit(
2032 screen: &crate::kernel::draw::Screen,
2033 img: &super::DecodedImage,
2034 el: &super::RenderElement,
2035 x0_rect: i32,
2036 y0_rect: i32,
2037 clip_s: &Option<ClipShape>,
2038) {
2039 use super::ObjectFit;
2040 let box_w = el.width.max(1) as u32;
2041 let box_h = el.height.max(1) as u32;
2042 if img.width == 0 || img.height == 0 {
2043 return;
2044 }
2045
2046 let (draw_w, draw_h): (u32, u32) = match el.object_fit {
2047 ObjectFit::Fill => (box_w, box_h),
2048 ObjectFit::Cover => {
2049 let scale = (box_w as f32 / img.width as f32).max(box_h as f32 / img.height as f32);
2050 (
2051 ((img.width as f32) * scale).max(1.0) as u32,
2052 ((img.height as f32) * scale).max(1.0) as u32,
2053 )
2054 }
2055 ObjectFit::Contain => {
2056 let scale = (box_w as f32 / img.width as f32).min(box_h as f32 / img.height as f32);
2057 (
2058 ((img.width as f32) * scale).max(1.0) as u32,
2059 ((img.height as f32) * scale).max(1.0) as u32,
2060 )
2061 }
2062 ObjectFit::None => (img.width, img.height),
2063 ObjectFit::ScaleDown => {
2064 let contain_scale =
2065 (box_w as f32 / img.width as f32).min(box_h as f32 / img.height as f32);
2066 let scale = contain_scale.min(1.0);
2067 (
2068 ((img.width as f32) * scale).max(1.0) as u32,
2069 ((img.height as f32) * scale).max(1.0) as u32,
2070 )
2071 }
2072 };
2073
2074 let off_x = ((box_w as i32 - draw_w as i32) as f32 * el.object_pos_x) as i32;
2075 let off_y = ((box_h as i32 - draw_h as i32) as f32 * el.object_pos_y) as i32;
2076 let blur_radius = parse_blur_radius(&el.filter);
2077
2078 for row in 0..draw_h {
2079 let py = row as i32 + off_y;
2080 if py < 0 || py as u32 >= box_h {
2081 continue;
2082 }
2083 let src_row = (row * img.height) / draw_h.max(1);
2084 for col in 0..draw_w {
2085 let px = col as i32 + off_x;
2086 if px < 0 || px as u32 >= box_w {
2087 continue;
2088 }
2089 if clip_s
2090 .as_ref()
2091 .map(|cs| !clip_inside(cs, px, py))
2092 .unwrap_or(false)
2093 {
2094 continue;
2095 }
2096 let is_pixelated = el.image_rendering == "pixelated" || el.image_rendering == "crisp-edges";
2097 let (raw_r, raw_g, raw_b, a) = if is_pixelated || (draw_w == img.width && draw_h == img.height) {
2098 let src_col = (col * img.width) / draw_w.max(1);
2099 let idx = ((src_row * img.width + src_col) as usize) * 4;
2100 if idx + 4 <= img.rgba.len() {
2101 let (br_r, br_g, br_b) = if blur_radius > 0 {
2102 sample_box_blur(img, src_row, src_col, blur_radius)
2103 } else {
2104 (img.rgba[idx], img.rgba[idx + 1], img.rgba[idx + 2])
2105 };
2106 (br_r, br_g, br_b, img.rgba[idx + 3])
2107 } else {
2108 (0, 0, 0, 0)
2109 }
2110 } else {
2111 let u = ((col as f32) + 0.5) * (img.width as f32) / (draw_w as f32) - 0.5;
2112 let v = ((row as f32) + 0.5) * (img.height as f32) / (draw_h as f32) - 0.5;
2113 sample_bilinear_rgba(img, u, v)
2114 };
2115 let (r, g, b) = apply_css_filters(&el.filter, raw_r, raw_g, raw_b);
2116 if a > 0 {
2117 let color = crate::kernel::draw::Color(
2118 0xFF000000 | ((r as u32) << 16) | ((g as u32) << 8) | (b as u32),
2119 );
2120 screen.draw_pixel_alpha(x0_rect as u32 + px as u32, y0_rect as u32 + py as u32, color, a);
2121 }
2122 }
2123 }
2124}
2125
2126pub(super) fn sample_bilinear_rgba(img: &super::DecodedImage, u: f32, v: f32) -> (u8, u8, u8, u8) {
2129 if img.width == 0 || img.height == 0 || img.rgba.len() < (img.width * img.height * 4) as usize {
2130 return (0, 0, 0, 0);
2131 }
2132 let x = u.clamp(0.0, (img.width - 1) as f32);
2133 let y = v.clamp(0.0, (img.height - 1) as f32);
2134 let x0 = x as u32;
2135 let y0 = y as u32;
2136 let x1 = (x0 + 1).min(img.width - 1);
2137 let y1 = (y0 + 1).min(img.height - 1);
2138 let fx = x - (x0 as f32);
2139 let fy = y - (y0 as f32);
2140
2141 let get_pixel = |px: u32, py: u32| {
2142 let idx = ((py * img.width + px) as usize) * 4;
2143 (img.rgba[idx] as f32, img.rgba[idx + 1] as f32, img.rgba[idx + 2] as f32, img.rgba[idx + 3] as f32)
2144 };
2145
2146 let p00 = get_pixel(x0, y0);
2147 let p10 = get_pixel(x1, y0);
2148 let p01 = get_pixel(x0, y1);
2149 let p11 = get_pixel(x1, y1);
2150
2151 let w00 = (1.0 - fx) * (1.0 - fy);
2152 let w10 = fx * (1.0 - fy);
2153 let w01 = (1.0 - fx) * fy;
2154 let w11 = fx * fy;
2155
2156 let r = (p00.0 * w00 + p10.0 * w10 + p01.0 * w01 + p11.0 * w11) as u8;
2157 let g = (p00.1 * w00 + p10.1 * w10 + p01.1 * w01 + p11.1 * w11) as u8;
2158 let b = (p00.2 * w00 + p10.2 * w10 + p01.2 * w01 + p11.2 * w11) as u8;
2159 let a = (p00.3 * w00 + p10.3 * w10 + p01.3 * w01 + p11.3 * w11) as u8;
2160 (r, g, b, a)
2161}