1use crate::os_lib::css::parse_color;
2pub(crate) use super::css_filters::*;
3pub(crate) use super::bilinear::sample_bilinear_rgba;
4
5pub(super) fn draw_individual_borders(
8 screen: &crate::kernel::draw::Screen,
9 mut x0: i32,
10 mut y0: i32,
11 mut x1: i32,
12 mut y1: i32,
13 el: &super::RenderElement,
14 is_hovered: bool,
15 is_focused: bool,
16 is_active: bool,
17) {
18 if let Some((top, right, bottom, left)) = el.client_insets {
20 x0 += left;
21 y0 += top;
22 x1 -= right;
23 y1 -= bottom;
24 if x0 > x1 || y0 > y1 {
26 return;
27 }
28 }
29 if x0 >= x1 || y0 >= y1 {
30 return;
31 }
32
33 let all_transparent = [
48 el.border_top_color,
49 el.border_right_color,
50 el.border_bottom_color,
51 el.border_left_color,
52 el.border_color,
53 ]
54 .iter()
55 .flatten()
56 .all(|c| (c >> 24) & 0xFF == 0);
57 let has_any_color = [
58 el.border_top_color,
59 el.border_right_color,
60 el.border_bottom_color,
61 el.border_left_color,
62 el.border_color,
63 ]
64 .iter()
65 .any(|c| c.is_some());
66 if has_any_color && all_transparent {
67 return;
68 }
69
70 if el.border_top_width > 0 && !el.border_top_style.is_none() {
72 let color = if is_active {
73 el.active_border_color
74 .or(el.border_top_color)
75 .or(el.border_color)
76 .unwrap_or(el.color)
77 } else if is_hovered {
78 el.hover_border_color
79 .or(el.border_top_color)
80 .or(el.border_color)
81 .unwrap_or(el.color)
82 } else if is_focused {
83 el.focus_border_color
84 .or(el.border_top_color)
85 .or(el.border_color)
86 .unwrap_or(el.color)
87 } else {
88 el.border_top_color.or(el.border_color).unwrap_or(el.color)
89 };
90 let t_w = el.border_top_width;
91 if el.border_top_style == super::BorderStyle::Dashed {
92 let dash = 6;
93 let gap = 4;
94 let mut x = x0;
95 while x < x1 {
96 let end = (x + dash).min(x1);
97 screen.boxfill(
98 x as u32,
99 y0 as u32,
100 (end - 1).max(x) as u32,
101 (y0 + t_w - 1).max(y0) as u32,
102 crate::kernel::draw::Color(color),
103 );
104 x += dash + gap;
105 }
106 } else {
107 screen.boxfill(
108 x0 as u32,
109 y0 as u32,
110 (x1 - 1).max(x0) as u32,
111 (y0 + t_w - 1).max(y0) as u32,
112 crate::kernel::draw::Color(color),
113 );
114 }
115 }
116
117 if el.border_bottom_width > 0 && !el.border_bottom_style.is_none() {
119 let color = if is_active {
120 el.active_border_color
121 .or(el.border_bottom_color)
122 .or(el.border_color)
123 .unwrap_or(el.color)
124 } else if is_hovered {
125 el.hover_border_color
126 .or(el.border_bottom_color)
127 .or(el.border_color)
128 .unwrap_or(el.color)
129 } else if is_focused {
130 el.focus_border_color
131 .or(el.border_bottom_color)
132 .or(el.border_color)
133 .unwrap_or(el.color)
134 } else {
135 el.border_bottom_color
136 .or(el.border_color)
137 .unwrap_or(el.color)
138 };
139 let b_w = el.border_bottom_width;
140 if el.border_bottom_style == super::BorderStyle::Dashed {
141 let dash = 6;
142 let gap = 4;
143 let mut x = x0;
144 while x < x1 {
145 let end = (x + dash).min(x1);
146 screen.boxfill(
147 x as u32,
148 (y1 - b_w) as u32,
149 (end - 1).max(x) as u32,
150 (y1 - 1).max(y1 - b_w) as u32,
151 crate::kernel::draw::Color(color),
152 );
153 x += dash + gap;
154 }
155 } else {
156 screen.boxfill(
157 x0 as u32,
158 (y1 - b_w) as u32,
159 (x1 - 1).max(x0) as u32,
160 (y1 - 1).max(y1 - b_w) as u32,
161 crate::kernel::draw::Color(color),
162 );
163 }
164 }
165
166 if el.border_left_width > 0 && !el.border_left_style.is_none() {
168 let color = if is_active {
169 el.active_border_color
170 .or(el.border_left_color)
171 .or(el.border_color)
172 .unwrap_or(el.color)
173 } else if is_hovered {
174 el.hover_border_color
175 .or(el.border_left_color)
176 .or(el.border_color)
177 .unwrap_or(el.color)
178 } else if is_focused {
179 el.focus_border_color
180 .or(el.border_left_color)
181 .or(el.border_color)
182 .unwrap_or(el.color)
183 } else {
184 el.border_left_color.or(el.border_color).unwrap_or(el.color)
185 };
186 let l_w = el.border_left_width;
187 if el.border_left_style == super::BorderStyle::Dashed {
188 let dash = 6;
189 let gap = 4;
190 let mut y = y0;
191 while y < y1 {
192 let end = (y + dash).min(y1);
193 screen.boxfill(
194 x0 as u32,
195 y as u32,
196 (x0 + l_w - 1).max(x0) as u32,
197 (end - 1).max(y) as u32,
198 crate::kernel::draw::Color(color),
199 );
200 y += dash + gap;
201 }
202 } else {
203 screen.boxfill(
204 x0 as u32,
205 y0 as u32,
206 (x0 + l_w - 1).max(x0) as u32,
207 (y1 - 1).max(y0) as u32,
208 crate::kernel::draw::Color(color),
209 );
210 }
211 }
212
213 if el.border_right_width > 0 && !el.border_right_style.is_none() {
215 let color = if is_active {
216 el.active_border_color
217 .or(el.border_right_color)
218 .or(el.border_color)
219 .unwrap_or(el.color)
220 } else if is_hovered {
221 el.hover_border_color
222 .or(el.border_right_color)
223 .or(el.border_color)
224 .unwrap_or(el.color)
225 } else if is_focused {
226 el.focus_border_color
227 .or(el.border_right_color)
228 .or(el.border_color)
229 .unwrap_or(el.color)
230 } else {
231 el.border_right_color
232 .or(el.border_color)
233 .unwrap_or(el.color)
234 };
235 let r_w = el.border_right_width;
236 if el.border_right_style == super::BorderStyle::Dashed {
237 let dash = 6;
238 let gap = 4;
239 let mut y = y0;
240 while y < y1 {
241 let end = (y + dash).min(y1);
242 screen.boxfill(
243 (x1 - r_w) as u32,
244 y as u32,
245 (x1 - 1).max(x1 - r_w) as u32,
246 (end - 1).max(y) as u32,
247 crate::kernel::draw::Color(color),
248 );
249 y += dash + gap;
250 }
251 } else {
252 screen.boxfill(
253 (x1 - r_w) as u32,
254 y0 as u32,
255 (x1 - 1).max(x1 - r_w) as u32,
256 (y1 - 1).max(y0) as u32,
257 crate::kernel::draw::Color(color),
258 );
259 }
260 }
261}
262
263pub fn draw_border(
264 screen: &crate::kernel::draw::Screen,
265 x0: i32,
266 y0: i32,
267 x1: i32,
268 y1: i32,
269 color_val: u32,
270) {
271 if x0 >= x1 || y0 >= y1 {
272 return;
273 }
274 let color = crate::kernel::draw::Color(color_val);
275 let ux0 = x0 as u32;
276 let uy0 = y0 as u32;
277 let ux1 = (x1 - 1) as u32;
278 let uy1 = (y1 - 1) as u32;
279
280 screen.boxfill(ux0, uy0, ux1, uy0, color);
282 if y1 - y0 > 1 {
283 screen.boxfill(ux0, uy1, ux1, uy1, color);
285 }
286 if y1 - y0 > 2 {
287 screen.boxfill(ux0, uy0 + 1, ux0, uy1 - 1, color);
289 if x1 - x0 > 1 {
290 screen.boxfill(ux1, uy0 + 1, ux1, uy1 - 1, color);
292 }
293 }
294}
295
296pub(super) fn draw_border_with_style(
297 screen: &crate::kernel::draw::Screen,
298 x0: i32,
299 y0: i32,
300 x1: i32,
301 y1: i32,
302 color_val: u32,
303 style: super::BorderStyle,
304) {
305 if style.is_none() {
306 return;
307 }
308 if style == super::BorderStyle::Dashed {
309 draw_dashed_border(screen, x0, y0, x1, y1, color_val);
310 return;
311 }
312 draw_border(screen, x0, y0, x1, y1, color_val);
313}
314
315fn draw_dashed_border(
316 screen: &crate::kernel::draw::Screen,
317 x0: i32,
318 y0: i32,
319 x1: i32,
320 y1: i32,
321 color_val: u32,
322) {
323 if x0 >= x1 || y0 >= y1 {
324 return;
325 }
326 let color = crate::kernel::draw::Color(color_val);
327 let dash = 6;
328 let gap = 4;
329 let mut x = x0;
330 while x < x1 {
331 let end = (x + dash).min(x1);
332 screen.boxfill(
333 x as u32,
334 y0 as u32,
335 (end - 1).max(x) as u32,
336 y0 as u32,
337 color,
338 );
339 if y1 - y0 > 1 {
340 screen.boxfill(
341 x as u32,
342 (y1 - 1) as u32,
343 (end - 1).max(x) as u32,
344 (y1 - 1) as u32,
345 color,
346 );
347 }
348 x += dash + gap;
349 }
350 let mut y = y0;
351 while y < y1 {
352 let end = (y + dash).min(y1);
353 screen.boxfill(
354 x0 as u32,
355 y as u32,
356 x0 as u32,
357 (end - 1).max(y) as u32,
358 color,
359 );
360 if x1 - x0 > 1 {
361 screen.boxfill(
362 (x1 - 1) as u32,
363 y as u32,
364 (x1 - 1) as u32,
365 (end - 1).max(y) as u32,
366 color,
367 );
368 }
369 y += dash + gap;
370 }
371}
372
373pub(super) fn extract_border_color(
374 styled: &crate::os_lib::css::StyledNode<'_>,
375 default_color: u32,
376) -> Option<u32> {
377 for key in [
378 "border-color",
379 "border-top-color",
380 "border-right-color",
381 "border-bottom-color",
382 "border-left-color",
383 ] {
384 if let Some(v) = styled.value(key) {
385 if let Some(c) = parse_color(&v) {
386 return Some(c);
387 }
388 }
389 }
390
391 if let Some(v) = styled.value("border") {
392 for token in v.split_whitespace().rev() {
393 if let Some(c) = parse_color(token) {
394 return Some(c);
395 }
396 }
397 }
398
399 Some(default_color)
400}
401
402pub(super) fn extract_border_style(styled: &crate::os_lib::css::StyledNode<'_>) -> super::BorderStyle {
403 if let Some(v) = styled.value("border-style") {
404 let style = v.trim();
405 if !style.is_empty() {
406 return super::BorderStyle::parse(style);
407 }
408 }
409
410 if let Some(v) = styled.value("border") {
411 for token in v.split_whitespace() {
412 let token_l = token.trim().to_lowercase();
413 if matches!(
414 token_l.as_str(),
415 "none" | "solid" | "dashed" | "dotted" | "double"
416 ) {
417 return super::BorderStyle::parse(&token_l);
418 }
419 }
420 }
421
422 super::BorderStyle::Solid
423}
424
425pub fn draw_rotated_rect_filled(
428 screen: &crate::kernel::draw::Screen,
429 cx: i32,
430 cy: i32,
431 w: i32,
432 h: i32,
433 angle_deg: f32,
434 color: u32,
435) {
436 if w <= 0 || h <= 0 {
437 return;
438 }
439 let rad = angle_deg * core::f32::consts::PI / 180.0;
440 let cos_a = libm::cosf(rad);
441 let sin_a = libm::sinf(rad);
442 let hw = w as f32 / 2.0;
444 let hh = h as f32 / 2.0;
445 let bound_w = libm::ceilf(libm::fabsf(hw * cos_a) + libm::fabsf(hh * sin_a)) as i32;
446 let bound_h = libm::ceilf(libm::fabsf(hw * sin_a) + libm::fabsf(hh * cos_a)) as i32;
447 let draw_color = crate::kernel::draw::Color(color | 0xFF000000);
448 for oy in -bound_h..=bound_h {
449 for ox in -bound_w..=bound_w {
450 let sx = ox as f32 * cos_a + oy as f32 * sin_a;
452 let sy = -ox as f32 * sin_a + oy as f32 * cos_a;
453 if sx >= -hw && sx <= hw && sy >= -hh && sy <= hh {
454 let px = cx + ox;
455 let py = cy + oy;
456 if px >= 0 && py >= 0 {
457 screen.draw_pixel(px as u32, py as u32, draw_color);
458 }
459 }
460 }
461 }
462}
463
464pub fn draw_skewed_rect_filled(
466 screen: &crate::kernel::draw::Screen,
467 cx: i32,
468 cy: i32,
469 w: i32,
470 h: i32,
471 skew_x_deg: f32,
472 skew_y_deg: f32,
473 color: u32,
474) {
475 if w <= 0 || h <= 0 {
476 return;
477 }
478 let rad_x = skew_x_deg * core::f32::consts::PI / 180.0;
479 let rad_y = skew_y_deg * core::f32::consts::PI / 180.0;
480 let tan_x = libm::tanf(rad_x);
481 let tan_y = libm::tanf(rad_y);
482
483 let hw = w as f32 / 2.0;
484 let hh = h as f32 / 2.0;
485
486 let det = 1.0 - tan_x * tan_y;
487 if det.abs() < 1e-6 {
488 return;
489 }
490
491 let bound_w = libm::ceilf(hw + hh * libm::fabsf(tan_x)) as i32;
492 let bound_h = libm::ceilf(hh + hw * libm::fabsf(tan_y)) as i32;
493 let draw_color = crate::kernel::draw::Color(color | 0xFF000000);
494
495 for oy in -bound_h..=bound_h {
496 for ox in -bound_w..=bound_w {
497 let sx = (ox as f32 - oy as f32 * tan_x) / det;
498 let sy = (oy as f32 - ox as f32 * tan_y) / det;
499 if sx >= -hw && sx <= hw && sy >= -hh && sy <= hh {
500 let px = cx + ox;
501 let py = cy + oy;
502 if px >= 0 && py >= 0 {
503 screen.draw_pixel(px as u32, py as u32, draw_color);
504 }
505 }
506 }
507 }
508}
509
510pub use super::clip_path::*;
511
512pub fn draw_gradation(
513 screen: &crate::kernel::draw::Screen,
514 x0: i32,
515 y0: i32,
516 w: i32,
517 h: i32,
518 c1: u32,
519 c2: u32,
520) {
521 if w <= 0 || h <= 0 {
522 return;
523 }
524 let x_end = (x0 + w - 1).max(x0) as u32;
525 let r1 = ((c1 >> 16) & 0xFF) as i32;
526 let g1 = ((c1 >> 8) & 0xFF) as i32;
527 let b1 = (c1 & 0xFF) as i32;
528
529 let r2 = ((c2 >> 16) & 0xFF) as i32;
530 let g2 = ((c2 >> 8) & 0xFF) as i32;
531 let b2 = (c2 & 0xFF) as i32;
532
533 let denom = (h - 1).max(1);
534 for dy in 0..h {
535 let py = (y0 + dy) as u32;
536 let r = ((r1 * (denom - dy) + r2 * dy) / denom) as u32;
537 let g = ((g1 * (denom - dy) + g2 * dy) / denom) as u32;
538 let b = ((b1 * (denom - dy) + b2 * dy) / denom) as u32;
539 let c = 0xFF000000 | (r << 16) | (g << 8) | b;
540 screen.boxfill(
541 x0 as u32,
542 py,
543 x_end,
544 py,
545 crate::kernel::draw::Color(c),
546 );
547 }
548}
549
550pub fn draw_linear_gradient(
564 screen: &crate::kernel::draw::Screen,
565 x0: i32,
566 y0: i32,
567 w: i32,
568 h: i32,
569 grad: &super::LinearGradient,
570) {
571 if w <= 0 || h <= 0 || grad.stops.is_empty() {
572 return;
573 }
574
575 let angle_rad = grad.angle_deg * core::f32::consts::PI / 180.0;
576 let dx = libm::sinf(angle_rad);
578 let dy = -libm::cosf(angle_rad);
579
580 let corners = [
582 (0.0, 0.0),
583 (w as f32, 0.0),
584 (0.0, h as f32),
585 (w as f32, h as f32),
586 ];
587
588 let mut min_proj = f32::MAX;
589 let mut max_proj = f32::MIN;
590 for &(cx, cy) in &corners {
591 let p = cx * dx + cy * dy;
592 if p < min_proj { min_proj = p; }
593 if p > max_proj { max_proj = p; }
594 }
595
596 let length = max_proj - min_proj;
597 let x_end = (x0 + w - 1).max(x0) as u32;
598 if length <= 0.0 {
599 let c = grad.stops[0].color;
600 let y_end = (y0 + h - 1).max(y0) as u32;
601 screen.boxfill(x0 as u32, y0 as u32, x_end, y_end, crate::kernel::draw::Color(c));
602 return;
603 }
604
605 use super::gradient_lut::{classify_axis, lut_index, lut_size, Axis};
615 let lut_n = lut_size(length);
616 let mut lut: alloc::vec::Vec<u32> = alloc::vec::Vec::with_capacity(lut_n);
617 for i in 0..lut_n {
618 let t = if lut_n <= 1 {
619 0.0
620 } else {
621 i as f32 / (lut_n - 1) as f32
622 };
623 lut.push(sample_gradient_stops(&grad.stops, t));
624 }
625
626 let axis = classify_axis(dx, dy);
629
630 if axis == Axis::Horizontal {
631 let mut row_colors = alloc::vec::Vec::with_capacity(w as usize);
634 for x in 0..w {
635 let p_x = (x as f32) * dx;
636 let t = (p_x - min_proj) / length;
637 row_colors.push(lut[lut_index(t, lut_n)]);
638 }
639 for y in 0..h {
640 let py = y0 + y;
641 screen.draw_scanline_colors(x0, py, &row_colors);
642 }
643 return;
644 }
645
646 let mut row_colors = alloc::vec![0u32; w as usize];
647 for y in 0..h {
648 let py = (y0 + y) as u32;
649 let p_y = (y as f32) * dy;
650
651 if axis == Axis::Vertical {
652 let t = (p_y - min_proj) / length;
655 let color = lut[lut_index(t, lut_n)];
656 screen.boxfill(
657 x0 as u32,
658 py,
659 x_end,
660 py,
661 crate::kernel::draw::Color(color),
662 );
663 continue;
664 }
665
666 for x in 0..w {
668 let p_x = (x as f32) * dx;
669 let p = p_x + p_y;
670 let t = (p - min_proj) / length;
671 row_colors[x as usize] = lut[lut_index(t, lut_n)];
672 }
673 screen.draw_scanline_colors(x0, py as i32, &row_colors);
674 }
675}
676
677pub(crate) fn sample_gradient_stops(stops: &[super::GradientStop], t: f32) -> u32 {
684 if stops.is_empty() {
685 return 0;
686 }
687 if t <= stops[0].position {
688 return stops[0].color;
689 }
690 let last = stops.last().unwrap_or(&stops[0]);
691 if t >= last.position {
692 return last.color;
693 }
694 for i in 0..stops.len() - 1 {
695 let s1 = &stops[i];
696 let s2 = &stops[i + 1];
697 if t >= s1.position && t <= s2.position {
698 let range = s2.position - s1.position;
699 let local_t = if range > 0.0 { (t - s1.position) / range } else { 0.0 };
700 let a1 = ((s1.color >> 24) & 0xFF) as f32;
701 let r1 = ((s1.color >> 16) & 0xFF) as f32;
702 let g1 = ((s1.color >> 8) & 0xFF) as f32;
703 let b1 = (s1.color & 0xFF) as f32;
704 let a2 = ((s2.color >> 24) & 0xFF) as f32;
705 let r2 = ((s2.color >> 16) & 0xFF) as f32;
706 let g2 = ((s2.color >> 8) & 0xFF) as f32;
707 let b2 = (s2.color & 0xFF) as f32;
708 let a = ((1.0 - local_t) * a1 + local_t * a2) as u32;
709 let r = ((1.0 - local_t) * r1 + local_t * r2) as u32;
710 let g = ((1.0 - local_t) * g1 + local_t * g2) as u32;
711 let b = ((1.0 - local_t) * b1 + local_t * b2) as u32;
712 return (a << 24) | (r << 16) | (g << 8) | b;
713 }
714 }
715 last.color
716}
717
718pub fn draw_conic_gradient(
723 screen: &crate::kernel::draw::Screen,
724 x0: i32,
725 y0: i32,
726 w: i32,
727 h: i32,
728 stops: &[super::GradientStop],
729) {
730 if w <= 0 || h <= 0 || stops.is_empty() {
731 return;
732 }
733 let cx = w as f32 / 2.0;
734 let cy = h as f32 / 2.0;
735 const PI: f32 = core::f32::consts::PI;
736 for dy in 0..h {
737 for dx in 0..w {
738 let px = dx as f32 - cx;
739 let py = dy as f32 - cy;
740 let mut angle = libm::atan2f(px, -py);
743 if angle < 0.0 {
744 angle += 2.0 * PI;
745 }
746 let t = angle / (2.0 * PI);
747 let color = sample_gradient_stops(stops, t);
748 screen.draw_pixel_alpha(
749 (x0 + dx) as u32,
750 (y0 + dy) as u32,
751 crate::kernel::draw::Color(color),
752 255,
753 );
754 }
755 }
756}
757
758pub fn draw_radial_gradient(
759 screen: &crate::kernel::draw::Screen,
760 x0: i32,
761 y0: i32,
762 w: i32,
763 h: i32,
764 grad: &super::RadialGradient,
765) {
766 if w <= 0 || h <= 0 || grad.stops.is_empty() {
767 return;
768 }
769
770 let cx = (w as f32) * grad.pos_x;
771 let cy = (h as f32) * grad.pos_y;
772
773 let max_rx = if grad.pos_x > 0.5 { cx } else { (w as f32) - cx };
775 let max_ry = if grad.pos_y > 0.5 { cy } else { (h as f32) - cy };
776
777 let (r_x, r_y) = match grad.shape {
778 super::RadialShape::Circle => {
779 let r = libm::sqrtf(max_rx * max_rx + max_ry * max_ry);
780 (r, r)
781 },
782 super::RadialShape::Ellipse => {
783 (max_rx, max_ry)
784 }
785 };
786
787 if r_x <= 0.0 || r_y <= 0.0 {
788 let c = grad.stops[0].color;
789 screen.boxfill(x0 as u32, y0 as u32, (x0 + w) as u32, (y0 + h) as u32, crate::kernel::draw::Color(c));
790 return;
791 }
792
793 for y in 0..h {
794 let py = y0 + y;
795 let dy = (y as f32) - cy;
796 for x in 0..w {
797 let px = x0 + x;
798 let dx = (x as f32) - cx;
799
800 let t = libm::sqrtf((dx * dx) / (r_x * r_x) + (dy * dy) / (r_y * r_y));
801 let color = sample_gradient_stops(&grad.stops, t);
802 let alpha = ((color >> 24) & 0xFF) as u8;
803 screen.draw_pixel_alpha(px as u32, py as u32, crate::kernel::draw::Color(color), alpha);
804 }
805 }
806}
807
808fn box_inset(el: &super::RenderElement, mode: &str) -> (i32, i32, i32, i32) {
812 match mode {
813 "content-box" => (
814 el.border_top_width + el.padding_top,
815 el.border_right_width + el.padding_right,
816 el.border_bottom_width + el.padding_bottom,
817 el.border_left_width + el.padding_left,
818 ),
819 "padding-box" => (
820 el.border_top_width,
821 el.border_right_width,
822 el.border_bottom_width,
823 el.border_left_width,
824 ),
825 _ => (0, 0, 0, 0),
826 }
827}
828
829pub(super) fn bg_clip_rect(
832 el: &super::RenderElement,
833 x0_rect: i32,
834 y0_rect: i32,
835 x1_rect: i32,
836 y1_rect: i32,
837) -> (u32, u32, u32, u32) {
838 let (top, right, bottom, left) = box_inset(el, &el.bg_clip);
839 (
840 (x0_rect + left).max(0) as u32,
841 (y0_rect + top).max(0) as u32,
842 (x1_rect - right).max(0) as u32,
843 (y1_rect - bottom).max(0) as u32,
844 )
845}
846
847pub(super) fn bg_origin_shift(
852 el: &super::RenderElement,
853 eff_w: i32,
854 eff_h: i32,
855) -> (i32, i32, i32, i32) {
856 let (top, right, bottom, left) = box_inset(el, &el.bg_origin);
857 (
858 left,
859 top,
860 (eff_w - left - right).max(1),
861 (eff_h - top - bottom).max(1),
862 )
863}
864
865pub(crate) static BG_IMG_TICKS: core::sync::atomic::AtomicUsize =
874 core::sync::atomic::AtomicUsize::new(0);
875
876pub(crate) static GRAD_TICKS: core::sync::atomic::AtomicUsize =
878 core::sync::atomic::AtomicUsize::new(0);
879pub(crate) static BORDER_TICKS: core::sync::atomic::AtomicUsize =
881 core::sync::atomic::AtomicUsize::new(0);
882pub(crate) static IMG_TICKS: core::sync::atomic::AtomicUsize =
884 core::sync::atomic::AtomicUsize::new(0);
885pub(crate) static OTHERFILL_TICKS: core::sync::atomic::AtomicUsize =
889 core::sync::atomic::AtomicUsize::new(0);
890
891struct TickTimer(usize, &'static core::sync::atomic::AtomicUsize);
893impl Drop for TickTimer {
894 fn drop(&mut self) {
895 self.1.fetch_add(
896 crate::kernel::timer::get_ticks().wrapping_sub(self.0),
897 core::sync::atomic::Ordering::Relaxed,
898 );
899 }
900}
901
902struct BgImgTimer(usize);
904impl Drop for BgImgTimer {
905 fn drop(&mut self) {
906 BG_IMG_TICKS.fetch_add(
907 crate::kernel::timer::get_ticks().wrapping_sub(self.0),
908 core::sync::atomic::Ordering::Relaxed,
909 );
910 }
911}
912
913pub(super) fn draw_background_image(
922 screen: &crate::kernel::draw::Screen,
923 img: &super::DecodedImage,
924 el: &super::RenderElement,
925 x0_rect: i32,
926 y0_rect: i32,
927 eff_w: i32,
928 eff_h: i32,
929 clip_s: &Option<ClipShape>,
930 attachment_shift_y: i32,
935 origin_shift_x: i32,
942 origin_shift_y: i32,
943 origin_w: i32,
944 origin_h: i32,
945 layer_override: Option<(
946 super::BgSizeMode,
947 super::BgRepeatMode,
948 super::BgRepeatMode,
949 f32,
950 f32,
951 )>,
952) {
953 use super::{BgSizeMode, BgSizeVal};
954 let _bg_timer = BgImgTimer(crate::kernel::timer::get_ticks());
955 let (mut x0_rect, mut y0_rect, mut eff_w, mut eff_h) = (x0_rect, y0_rect, eff_w, eff_h);
957 if let Some((top, right, bottom, left)) = el.client_insets {
958 x0_rect += left;
959 y0_rect += top;
960 eff_w -= left + right;
961 eff_h -= top + bottom;
962 if eff_w <= 0 || eff_h <= 0 {
963 return;
964 }
965 }
966 let (el_bg_size, el_bg_repeat_x, el_bg_repeat_y, el_bg_pos_x, el_bg_pos_y) =
967 layer_override.unwrap_or((el.bg_size, el.bg_repeat_x, el.bg_repeat_y, el.bg_pos_x, el.bg_pos_y));
968 let box_w = eff_w.max(1) as u32;
972 let box_h = eff_h.max(1) as u32;
973 let origin_box_w = origin_w.max(1) as u32;
976 let origin_box_h = origin_h.max(1) as u32;
977 if img.width == 0 || img.height == 0 {
978 return;
979 }
980
981 fn resolve_axis(v: BgSizeVal, box_dim: u32, img_dim: u32) -> Option<u32> {
982 match v {
983 BgSizeVal::Auto => None,
984 BgSizeVal::Px(px) => Some(px.max(1) as u32),
985 BgSizeVal::Percent(p) => Some(((box_dim as f32) * p).max(1.0) as u32),
986 }
987 .or(Some(img_dim))
988 }
989
990 let (draw_w, draw_h): (u32, u32) = match el_bg_size {
991 BgSizeMode::Stretch => (origin_box_w, origin_box_h),
992 BgSizeMode::Cover => {
993 let scale = (origin_box_w as f32 / img.width as f32)
994 .max(origin_box_h as f32 / img.height as f32);
995 (
996 ((img.width as f32) * scale).max(1.0) as u32,
997 ((img.height as f32) * scale).max(1.0) as u32,
998 )
999 }
1000 BgSizeMode::Contain => {
1001 let scale = (origin_box_w as f32 / img.width as f32)
1002 .min(origin_box_h as f32 / img.height as f32);
1003 (
1004 ((img.width as f32) * scale).max(1.0) as u32,
1005 ((img.height as f32) * scale).max(1.0) as u32,
1006 )
1007 }
1008 BgSizeMode::Explicit(wv, hv) => {
1009 match (wv, hv) {
1011 (BgSizeVal::Auto, BgSizeVal::Auto) => (img.width, img.height),
1012 (BgSizeVal::Auto, _) => {
1013 let h = resolve_axis(hv, origin_box_h, img.height).unwrap_or(img.height);
1014 let w = ((img.width as f32) * (h as f32 / img.height as f32)).max(1.0) as u32;
1015 (w, h)
1016 }
1017 (_, BgSizeVal::Auto) => {
1018 let w = resolve_axis(wv, origin_box_w, img.width).unwrap_or(img.width);
1019 let h = ((img.height as f32) * (w as f32 / img.width as f32)).max(1.0) as u32;
1020 (w, h)
1021 }
1022 _ => (
1023 resolve_axis(wv, origin_box_w, img.width).unwrap_or(img.width),
1024 resolve_axis(hv, origin_box_h, img.height).unwrap_or(img.height),
1025 ),
1026 }
1027 }
1028 };
1029
1030 fn tile_layout(mode: super::BgRepeatMode, box_dim: u32, tile_dim: u32) -> (u32, u32, bool) {
1035 use super::BgRepeatMode;
1036 match mode {
1037 BgRepeatMode::Round => {
1038 let n =
1039 (libm::roundf(box_dim as f32 / tile_dim.max(1) as f32) as u32).max(1);
1040 let eff = (box_dim / n).max(1);
1041 (eff, eff, true)
1042 }
1043 BgRepeatMode::Space => {
1044 let n = box_dim / tile_dim.max(1);
1045 if n <= 1 {
1046 (tile_dim, tile_dim, false)
1047 } else {
1048 let gap = box_dim.saturating_sub(n * tile_dim) / (n - 1);
1049 (tile_dim, tile_dim + gap, true)
1050 }
1051 }
1052 _ => (tile_dim, tile_dim, false),
1053 }
1054 }
1055
1056 let (draw_w_eff, pitch_w, ignore_pos_x) = tile_layout(el_bg_repeat_x, box_w, draw_w);
1057 let (draw_h_eff, pitch_h, ignore_pos_y) = tile_layout(el_bg_repeat_y, box_h, draw_h);
1058 let off_x = if ignore_pos_x {
1063 0
1064 } else {
1065 ((origin_box_w as i32 - draw_w as i32) as f32 * el_bg_pos_x) as i32
1066 };
1067 let off_y = if ignore_pos_y {
1068 0
1069 } else {
1070 ((origin_box_h as i32 - draw_h as i32) as f32 * el_bg_pos_y) as i32
1071 };
1072
1073 let col_src: alloc::vec::Vec<Option<u32>> = (0..box_w)
1075 .map(|col| {
1076 let rel_x = (col as i32 - origin_shift_x) - off_x;
1077 let sample_x = match el_bg_repeat_x {
1078 super::BgRepeatMode::NoRepeat => rel_x,
1079 super::BgRepeatMode::Space => {
1080 let m = rel_x.rem_euclid(pitch_w as i32);
1081 if m >= draw_w_eff as i32 {
1082 return None;
1083 }
1084 m
1085 }
1086 _ => rel_x.rem_euclid(draw_w_eff as i32),
1087 };
1088 if sample_x < 0 || sample_x >= draw_w_eff as i32 {
1089 None
1090 } else {
1091 Some((sample_x as u32 * img.width) / draw_w_eff.max(1))
1092 }
1093 })
1094 .collect();
1095
1096 let has_blend = !el.background_blend_mode.is_empty() && el.background_blend_mode != "normal";
1097 let blend_dst = if has_blend {
1098 el.bg_color.map(|bg| (
1099 ((bg >> 16) & 0xFF) as u8,
1100 ((bg >> 8) & 0xFF) as u8,
1101 (bg & 0xFF) as u8,
1102 ))
1103 } else {
1104 None
1105 };
1106
1107 let mut row_buf = if clip_s.is_none() {
1108 alloc::vec![0u32; box_w as usize]
1109 } else {
1110 alloc::vec![]
1111 };
1112
1113 for row in 0..box_h {
1114 let rel_y = (row as i32 + attachment_shift_y - origin_shift_y) - off_y;
1115 let sample_y = match el_bg_repeat_y {
1116 super::BgRepeatMode::NoRepeat => rel_y,
1117 super::BgRepeatMode::Space => {
1118 let m = rel_y.rem_euclid(pitch_h as i32);
1119 if m >= draw_h_eff as i32 {
1120 continue;
1121 }
1122 m
1123 }
1124 _ => rel_y.rem_euclid(draw_h_eff as i32),
1125 };
1126 if sample_y < 0 || sample_y >= draw_h_eff as i32 {
1127 continue;
1128 }
1129 let src_row = (sample_y as u32 * img.height) / draw_h_eff.max(1);
1130 let row_offset = (src_row * img.width) as usize;
1131 let py = (y0_rect as u32).wrapping_add(row);
1132
1133 if clip_s.is_none() {
1134 for col in 0..box_w {
1136 let src_col = match col_src[col as usize] {
1137 Some(sc) => sc,
1138 None => {
1139 row_buf[col as usize] = 0;
1140 continue;
1141 }
1142 };
1143 let idx = (row_offset + src_col as usize) * 4;
1144 if idx + 4 <= img.rgba.len() {
1145 let mut r = img.rgba[idx];
1146 let mut g = img.rgba[idx + 1];
1147 let mut b = img.rgba[idx + 2];
1148 let raw_a = img.rgba[idx + 3];
1149 if raw_a == 0 {
1150 row_buf[col as usize] = 0;
1151 continue;
1152 }
1153 let a = ((raw_a as u32 * el.opacity as u32) / 255) as u8;
1154 if a == 0 {
1155 row_buf[col as usize] = 0;
1156 continue;
1157 }
1158
1159 if let Some(dst) = blend_dst {
1160 let (br, bgc, bb) = blend_pixel(&el.background_blend_mode, (r, g, b), dst);
1161 r = br;
1162 g = bgc;
1163 b = bb;
1164 }
1165 row_buf[col as usize] = ((a as u32) << 24) | ((r as u32) << 16) | ((g as u32) << 8) | (b as u32);
1166 } else {
1167 row_buf[col as usize] = 0;
1168 }
1169 }
1170 screen.draw_scanline_colors(x0_rect, py as i32, &row_buf);
1171 } else {
1172 for col in 0..box_w {
1174 let src_col = match col_src[col as usize] {
1175 Some(sc) => sc,
1176 None => continue,
1177 };
1178 if let Some(ref cs) = clip_s {
1179 if !clip_inside(cs, col as i32, row as i32) {
1180 continue;
1181 }
1182 }
1183
1184 let idx = (row_offset + src_col as usize) * 4;
1185 if idx + 4 <= img.rgba.len() {
1186 let mut r = img.rgba[idx];
1187 let mut g = img.rgba[idx + 1];
1188 let mut b = img.rgba[idx + 2];
1189 let raw_a = img.rgba[idx + 3];
1190 if raw_a == 0 {
1191 continue;
1192 }
1193 let a = ((raw_a as u32 * el.opacity as u32) / 255) as u8;
1194 if a == 0 {
1195 continue;
1196 }
1197
1198 if let Some(dst) = blend_dst {
1199 let (br, bgc, bb) = blend_pixel(&el.background_blend_mode, (r, g, b), dst);
1200 r = br;
1201 g = bgc;
1202 b = bb;
1203 }
1204 let color = crate::kernel::draw::Color(
1205 0xFF000000 | ((r as u32) << 16) | ((g as u32) << 8) | (b as u32),
1206 );
1207 screen.draw_pixel_alpha((x0_rect as u32).wrapping_add(col), py, color, a);
1208 }
1209 }
1210 }
1211 }
1212}
1213
1214pub(super) fn draw_decoration_line(
1218 screen: &crate::kernel::draw::Screen,
1219 x0: u32,
1220 y: u32,
1221 x1: u32,
1222 style: &str,
1223 color: u32,
1224 thickness: u32,
1225) {
1226 if x1 <= x0 {
1227 return;
1228 }
1229 let t = thickness.max(1);
1230 let color_val = crate::kernel::draw::Color(color);
1231 match style {
1232 "dotted" => {
1233 let mut x = x0;
1234 while x < x1 {
1235 let seg_end = (x + 2).min(x1);
1236 screen.boxfill(x, y, (seg_end - 1).max(x), y + t - 1, color_val);
1237 x += 4;
1238 }
1239 }
1240 "dashed" => {
1241 let mut x = x0;
1242 while x < x1 {
1243 let seg_end = (x + 5).min(x1);
1244 screen.boxfill(x, y, (seg_end - 1).max(x), y + t - 1, color_val);
1245 x += 8;
1246 }
1247 }
1248 "wavy" => {
1249 let amplitude = 2i32;
1251 let period = 6i32;
1252 for x in x0..x1 {
1253 let phase = (x as i32 - x0 as i32) % period;
1254 let dy = if phase < period / 2 { phase - amplitude / 2 } else { period - phase - amplitude / 2 };
1255 let py = (y as i32 + dy).max(0) as u32;
1256 screen.boxfill(x, py, x, py + t - 1, color_val);
1257 }
1258 }
1259 "double" => {
1260 let x_end = (x1 - 1).max(x0);
1261 screen.boxfill(x0, y, x_end, y + t - 1, color_val);
1262 let y2 = y + t + 2;
1263 screen.boxfill(x0, y2, x_end, y2 + t - 1, color_val);
1264 }
1265 _ => {
1266 let x_end = (x1 - 1).max(x0);
1267 screen.boxfill(x0, y, x_end, y + t - 1, color_val);
1268 }
1269 }
1270}
1271
1272pub(super) fn draw_outline(
1275 screen: &crate::kernel::draw::Screen,
1276 x0: i32,
1277 y0: i32,
1278 x1: i32,
1279 y1: i32,
1280 width: i32,
1281 offset: i32,
1282 color: u32,
1283 style: &str,
1284) {
1285 let ox0 = x0 - offset - width;
1286 let oy0 = y0 - offset - width;
1287 let ox1 = x1 + offset + width;
1288 let oy1 = y1 + offset + width;
1289 if matches!(style, "dashed" | "dotted") {
1292 for i in 0..width {
1293 draw_dashed_border(screen, ox0 + i, oy0 + i, ox1 - i, oy1 - i, color);
1294 }
1295 return;
1296 }
1297 for i in 0..width {
1298 draw_border(screen, ox0 + i, oy0 + i, ox1 - i, oy1 - i, color);
1299 }
1300}
1301
1302pub(crate) fn parse_drop_shadow(filter: &str) -> Option<(i32, i32, i32, u32, bool)> {
1307 let lower = filter.to_ascii_lowercase();
1308 let start = lower.find("drop-shadow(")?;
1309 #[allow(clippy::string_slice)]
1310 let rest = &lower[start + 12..];
1311 let close = rest.find(')')?;
1312 #[allow(clippy::string_slice)]
1313 let arg = rest[..close].trim();
1314
1315 let mut ox = 0i32;
1316 let mut oy = 0i32;
1317 let mut blur = 0i32;
1318 let mut shadow_color = 0x80000000u32;
1319 let mut px_count = 0;
1320
1321 for token in arg.split_whitespace() {
1322 if let Some(n) = token.strip_suffix("px") {
1323 if let Ok(n) = n.trim().parse::<f32>().map(|v| v as i32) {
1327 match px_count {
1328 0 => { ox = n; px_count += 1; }
1329 1 => { oy = n; px_count += 1; }
1330 2 => { blur = n; px_count += 1; }
1331 _ => {}
1332 }
1333 }
1334 } else if let Some(c) = crate::os_lib::css::parse_color(token) {
1335 shadow_color = c;
1336 }
1337 }
1338
1339 if px_count >= 2 {
1340 Some((ox, oy, blur, shadow_color, false))
1341 } else {
1342 None
1343 }
1344}
1345
1346pub(crate) fn sample_box_blur(
1349 img: &super::DecodedImage,
1350 src_row: u32,
1351 src_col: u32,
1352 radius: i32,
1353) -> (u8, u8, u8) {
1354 let r = radius.clamp(1, 8);
1355 let (mut sr, mut sg, mut sb, mut n) = (0i64, 0i64, 0i64, 0i64);
1356 for dy in -r..=r {
1357 let ry = src_row as i32 + dy;
1358 if ry < 0 || ry as u32 >= img.height {
1359 continue;
1360 }
1361 for dx in -r..=r {
1362 let rx = src_col as i32 + dx;
1363 if rx < 0 || rx as u32 >= img.width {
1364 continue;
1365 }
1366 let idx = ((ry as u32 * img.width + rx as u32) as usize) * 4;
1367 if idx + 3 < img.rgba.len() {
1368 sr += img.rgba[idx] as i64;
1369 sg += img.rgba[idx + 1] as i64;
1370 sb += img.rgba[idx + 2] as i64;
1371 n += 1;
1372 }
1373 }
1374 }
1375 if n == 0 {
1376 return (0, 0, 0);
1377 }
1378 ((sr / n) as u8, (sg / n) as u8, (sb / n) as u8)
1379}
1380
1381pub(crate) fn blur_screen_rect(
1383 screen: &crate::kernel::draw::Screen,
1384 x0: i32,
1385 y0: i32,
1386 x1: i32,
1387 y1: i32,
1388 radius: i32,
1389) {
1390 if radius <= 0 || x1 <= x0 || y1 <= y0 {
1391 return;
1392 }
1393 let r = radius.clamp(1, 8);
1394 let w = (x1 - x0) as usize;
1395 let h = (y1 - y0) as usize;
1396
1397 let mut buf = alloc::vec::Vec::with_capacity(w * h);
1399 for y in y0..y1 {
1400 for x in x0..x1 {
1401 buf.push(screen.read_pixel(x as u32, y as u32).0);
1402 }
1403 }
1404
1405 let blurred = super::blur_box::box_blur_rgb(&buf, w, h, r as usize);
1417
1418 for y in 0..h {
1419 let sy = y0 + y as i32;
1420 if sy < 0 || sy as u32 >= screen.height {
1421 continue;
1422 }
1423 for x in 0..w {
1424 let sx = x0 + x as i32;
1425 if sx < 0 || sx as u32 >= screen.width {
1426 continue;
1427 }
1428 screen.draw_pixel_alpha(
1429 sx as u32,
1430 sy as u32,
1431 crate::kernel::draw::Color(blurred[y * w + x]),
1432 255,
1433 );
1434 }
1435 }
1436}
1437
1438pub(crate) fn apply_backdrop_filters_to_screen_rect(
1439 screen: &crate::kernel::draw::Screen,
1440 x0: i32,
1441 y0: i32,
1442 x1: i32,
1443 y1: i32,
1444 backdrop_filter: &str,
1445) {
1446 if backdrop_filter.is_empty() || x1 <= x0 || y1 <= y0 {
1447 return;
1448 }
1449 let backdrop_blur_radius = parse_blur_radius(backdrop_filter);
1450 if backdrop_blur_radius > 0 {
1451 blur_screen_rect(screen, x0, y0, x1, y1, backdrop_blur_radius);
1452 }
1453
1454 let tokens = parse_filter_tokens(backdrop_filter);
1456 let has_color_filters = tokens.iter().any(|(name, _)| name != "blur");
1457 if !has_color_filters {
1458 return;
1459 }
1460
1461 for sy in y0..y1 {
1462 if sy < 0 || sy as u32 >= screen.height { continue; }
1463 for sx in x0..x1 {
1464 if sx < 0 || sx as u32 >= screen.width { continue; }
1465 let orig = screen.read_pixel(sx as u32, sy as u32).0;
1466 let (r, g, b) = (((orig >> 16) & 0xFF) as u8, ((orig >> 8) & 0xFF) as u8, (orig & 0xFF) as u8);
1467 let (fr, fg, fb) = apply_css_filters(backdrop_filter, r, g, b);
1468 let new_col = 0xFF000000 | ((fr as u32) << 16) | ((fg as u32) << 8) | (fb as u32);
1469 screen.draw_pixel_alpha(sx as u32, sy as u32, crate::kernel::draw::Color(new_col), 255);
1470 }
1471 }
1472}
1473
1474pub(crate) fn blend_pixel(mode: &str, src: (u8, u8, u8), dst: (u8, u8, u8)) -> (u8, u8, u8) {
1481 if matches!(mode, "hue" | "saturation" | "color" | "luminosity") {
1482 return blend_non_separable(mode, src, dst);
1483 }
1484 if mode == "soft-light" {
1485 fn soft(s: u8, d: u8) -> u8 {
1486 let (cs, cb) = (s as f32 / 255.0, d as f32 / 255.0);
1487 let dd = if cb <= 0.25 {
1488 ((16.0 * cb - 12.0) * cb + 4.0) * cb
1489 } else {
1490 libm::sqrtf(cb)
1491 };
1492 let b = if cs <= 0.5 {
1493 cb - (1.0 - 2.0 * cs) * cb * (1.0 - cb)
1494 } else {
1495 cb + (2.0 * cs - 1.0) * (dd - cb)
1496 };
1497 (b.clamp(0.0, 1.0) * 255.0) as u8
1498 }
1499 return (soft(src.0, dst.0), soft(src.1, dst.1), soft(src.2, dst.2));
1500 }
1501 fn ch(mode: &str, s: u8, d: u8) -> u8 {
1502 let (sf, df) = (s as i32, d as i32);
1503 match mode {
1504 "multiply" => (sf * df / 255) as u8,
1505 "screen" => (255 - (255 - sf) * (255 - df) / 255) as u8,
1506 "darken" => sf.min(df) as u8,
1507 "lighten" => sf.max(df) as u8,
1508 "difference" => (sf - df).unsigned_abs() as u8,
1509 "overlay" => {
1510 if df <= 127 {
1511 (2 * sf * df / 255).clamp(0, 255) as u8
1512 } else {
1513 (255 - 2 * (255 - sf) * (255 - df) / 255).clamp(0, 255) as u8
1514 }
1515 }
1516 "hard-light" => {
1517 if sf <= 127 {
1518 (2 * sf * df / 255).clamp(0, 255) as u8
1519 } else {
1520 (255 - 2 * (255 - sf) * (255 - df) / 255).clamp(0, 255) as u8
1521 }
1522 }
1523 "exclusion" => (sf + df - 2 * sf * df / 255).clamp(0, 255) as u8,
1524 "color-dodge" => {
1525 if df == 0 {
1526 0
1527 } else if sf >= 255 {
1528 255
1529 } else {
1530 (255 * df / (255 - sf)).min(255) as u8
1531 }
1532 }
1533 "color-burn" => {
1534 if df >= 255 {
1535 255
1536 } else if sf == 0 {
1537 0
1538 } else {
1539 (255 - (255 * (255 - df) / sf).min(255)) as u8
1540 }
1541 }
1542 _ => s,
1543 }
1544 }
1545 (
1546 ch(mode, src.0, dst.0),
1547 ch(mode, src.1, dst.1),
1548 ch(mode, src.2, dst.2),
1549 )
1550}
1551
1552fn blend_non_separable(mode: &str, src: (u8, u8, u8), dst: (u8, u8, u8)) -> (u8, u8, u8) {
1556 fn to_f(c: (u8, u8, u8)) -> (f32, f32, f32) {
1557 (c.0 as f32 / 255.0, c.1 as f32 / 255.0, c.2 as f32 / 255.0)
1558 }
1559 fn to_u8(c: (f32, f32, f32)) -> (u8, u8, u8) {
1560 (
1561 (c.0.clamp(0.0, 1.0) * 255.0) as u8,
1562 (c.1.clamp(0.0, 1.0) * 255.0) as u8,
1563 (c.2.clamp(0.0, 1.0) * 255.0) as u8,
1564 )
1565 }
1566 fn lum(c: (f32, f32, f32)) -> f32 {
1567 0.3 * c.0 + 0.59 * c.1 + 0.11 * c.2
1568 }
1569 fn clip_color(c: (f32, f32, f32)) -> (f32, f32, f32) {
1570 let l = lum(c);
1571 let n = c.0.min(c.1).min(c.2);
1572 let x = c.0.max(c.1).max(c.2);
1573 let mut c = c;
1574 if n < 0.0 && l != n {
1575 c = (
1576 l + (c.0 - l) * l / (l - n),
1577 l + (c.1 - l) * l / (l - n),
1578 l + (c.2 - l) * l / (l - n),
1579 );
1580 }
1581 if x > 1.0 && x != l {
1582 c = (
1583 l + (c.0 - l) * (1.0 - l) / (x - l),
1584 l + (c.1 - l) * (1.0 - l) / (x - l),
1585 l + (c.2 - l) * (1.0 - l) / (x - l),
1586 );
1587 }
1588 c
1589 }
1590 fn set_lum(c: (f32, f32, f32), l: f32) -> (f32, f32, f32) {
1591 let d = l - lum(c);
1592 clip_color((c.0 + d, c.1 + d, c.2 + d))
1593 }
1594 fn sat(c: (f32, f32, f32)) -> f32 {
1595 c.0.max(c.1).max(c.2) - c.0.min(c.1).min(c.2)
1596 }
1597 fn set_sat(c: (f32, f32, f32), s: f32) -> (f32, f32, f32) {
1598 let mut v = [c.0, c.1, c.2];
1599 let (mut lo, mut mid, mut hi) = (0usize, 1usize, 2usize);
1600 if v[lo] > v[mid] {
1602 core::mem::swap(&mut lo, &mut mid);
1603 }
1604 if v[mid] > v[hi] {
1605 core::mem::swap(&mut mid, &mut hi);
1606 }
1607 if v[lo] > v[mid] {
1608 core::mem::swap(&mut lo, &mut mid);
1609 }
1610 if v[hi] > v[lo] {
1611 v[mid] = (v[mid] - v[lo]) * s / (v[hi] - v[lo]);
1612 v[hi] = s;
1613 } else {
1614 v[mid] = 0.0;
1615 v[hi] = 0.0;
1616 }
1617 v[lo] = 0.0;
1618 (v[0], v[1], v[2])
1619 }
1620 let (cb, cs) = (to_f(dst), to_f(src));
1621 let result = match mode {
1622 "hue" => set_lum(set_sat(cs, sat(cb)), lum(cb)),
1623 "saturation" => set_lum(set_sat(cb, sat(cs)), lum(cb)),
1624 "color" => set_lum(cs, lum(cb)),
1625 "luminosity" => set_lum(cb, lum(cs)),
1626 _ => cs,
1627 };
1628 to_u8(result)
1629}
1630
1631pub(super) fn draw_image_nn_blit(
1634 screen: &crate::kernel::draw::Screen,
1635 img: &super::DecodedImage,
1636 x0: i32,
1637 y0: i32,
1638 dst_w: u32,
1639 dst_h: u32,
1640) {
1641 if img.width == 0 || img.height == 0 || dst_w == 0 || dst_h == 0 {
1642 return;
1643 }
1644 for row in 0..dst_h {
1645 let src_row = (row * img.height) / dst_h;
1646 for col in 0..dst_w {
1647 let src_col = (col * img.width) / dst_w;
1648 let idx = ((src_row * img.width + src_col) as usize) * 4;
1649 if idx + 4 <= img.rgba.len() {
1650 let r = img.rgba[idx];
1651 let g = img.rgba[idx + 1];
1652 let b = img.rgba[idx + 2];
1653 let a = img.rgba[idx + 3];
1654 let px = x0 + col as i32;
1655 let py = y0 + row as i32;
1656 if px >= 0 && py >= 0 {
1657 let color = crate::kernel::draw::Color(
1658 0xFF000000 | ((r as u32) << 16) | ((g as u32) << 8) | (b as u32),
1659 );
1660 screen.draw_pixel_alpha(px as u32, py as u32, color, a);
1661 }
1662 }
1663 }
1664 }
1665}
1666
1667pub(super) fn draw_img_object_fit(
1671 screen: &crate::kernel::draw::Screen,
1672 img: &super::DecodedImage,
1673 el: &super::RenderElement,
1674 x0_rect: i32,
1675 y0_rect: i32,
1676 clip_s: &Option<ClipShape>,
1677) {
1678 use super::ObjectFit;
1679 let box_w = el.width.max(1) as u32;
1680 let box_h = el.height.max(1) as u32;
1681 if img.width == 0 || img.height == 0 {
1682 return;
1683 }
1684
1685 let (draw_w, draw_h): (u32, u32) = match el.object_fit {
1686 ObjectFit::Fill => (box_w, box_h),
1687 ObjectFit::Cover => {
1688 let scale = (box_w as f32 / img.width as f32).max(box_h as f32 / img.height as f32);
1689 (
1690 ((img.width as f32) * scale).max(1.0) as u32,
1691 ((img.height as f32) * scale).max(1.0) as u32,
1692 )
1693 }
1694 ObjectFit::Contain => {
1695 let scale = (box_w as f32 / img.width as f32).min(box_h as f32 / img.height as f32);
1696 (
1697 ((img.width as f32) * scale).max(1.0) as u32,
1698 ((img.height as f32) * scale).max(1.0) as u32,
1699 )
1700 }
1701 ObjectFit::None => (img.width, img.height),
1702 ObjectFit::ScaleDown => {
1703 let contain_scale =
1704 (box_w as f32 / img.width as f32).min(box_h as f32 / img.height as f32);
1705 let scale = contain_scale.min(1.0);
1706 (
1707 ((img.width as f32) * scale).max(1.0) as u32,
1708 ((img.height as f32) * scale).max(1.0) as u32,
1709 )
1710 }
1711 };
1712
1713 let off_x = ((box_w as i32 - draw_w as i32) as f32 * el.object_pos_x) as i32;
1714 let off_y = ((box_h as i32 - draw_h as i32) as f32 * el.object_pos_y) as i32;
1715 let blur_radius = parse_blur_radius(&el.filter);
1716
1717 let is_pixelated = el.image_rendering == "pixelated" || el.image_rendering == "crisp-edges";
1718 let use_nearest = is_pixelated || (draw_w == img.width && draw_h == img.height);
1719 let col_src_lut: alloc::vec::Vec<u32> = if use_nearest {
1720 (0..draw_w).map(|c| (c * img.width) / draw_w.max(1)).collect()
1721 } else {
1722 alloc::vec![]
1723 };
1724
1725 let mut row_buf = alloc::vec![0u32; draw_w as usize];
1726
1727 for row in 0..draw_h {
1728 let py = row as i32 + off_y;
1729 if py < 0 || py as u32 >= box_h {
1730 continue;
1731 }
1732 let src_row = (row * img.height) / draw_h.max(1);
1733 for col in 0..draw_w {
1734 let px = col as i32 + off_x;
1735 if px < 0 || px as u32 >= box_w {
1736 row_buf[col as usize] = 0;
1737 continue;
1738 }
1739 if let Some(ref cs) = clip_s {
1740 if !clip_inside(cs, px, py) {
1741 row_buf[col as usize] = 0;
1742 continue;
1743 }
1744 }
1745
1746 let (raw_r, raw_g, raw_b, a) = if use_nearest {
1747 let src_col = col_src_lut[col as usize];
1748 let idx = ((src_row * img.width + src_col) as usize) * 4;
1749 if idx + 4 <= img.rgba.len() {
1750 let (br_r, br_g, br_b) = if blur_radius > 0 {
1751 sample_box_blur(img, src_row, src_col, blur_radius)
1752 } else {
1753 (img.rgba[idx], img.rgba[idx + 1], img.rgba[idx + 2])
1754 };
1755 (br_r, br_g, br_b, img.rgba[idx + 3])
1756 } else {
1757 (0, 0, 0, 0)
1758 }
1759 } else {
1760 let u = ((col as f32) + 0.5) * (img.width as f32) / (draw_w as f32) - 0.5;
1761 let v = ((row as f32) + 0.5) * (img.height as f32) / (draw_h as f32) - 0.5;
1762 sample_bilinear_rgba(img, u, v)
1763 };
1764 if a == 0 {
1765 row_buf[col as usize] = 0;
1766 continue;
1767 }
1768 let (r, g, b) = apply_css_filters(&el.filter, raw_r, raw_g, raw_b);
1769 row_buf[col as usize] = ((a as u32) << 24) | ((r as u32) << 16) | ((g as u32) << 8) | (b as u32);
1770 }
1771
1772 screen.draw_scanline_colors(x0_rect + off_x, y0_rect + py, &row_buf);
1773 }
1774}
1775