1#![allow(clippy::identity_op)] use super::{Fixed, PhysicalLength, PhysicalRect};
10use derive_more::{Add, Mul, Sub};
11use i_slint_core::Color;
12use i_slint_core::graphics::{Rgb8Pixel, TexturePixelFormat};
13use i_slint_core::lengths::{PointLengths, SizeLengths};
14use integer_sqrt::IntegerSquareRoot;
15#[allow(unused_imports)]
16use num_traits::Float;
17
18pub(super) fn draw_texture_line(
21 span: &PhysicalRect,
22 line: PhysicalLength,
23 texture: &super::SceneTexture,
24 line_buffer: &mut [impl TargetPixel],
25 extra_clip_begin: i16,
26 extra_clip_end: i16,
27) {
28 let super::SceneTexture {
29 data,
30 format,
31 pixel_stride,
32 extra: super::SceneTextureExtra { colorize, alpha, rotation, dx, dy, off_x, off_y },
33 } = *texture;
34
35 let source_size = texture.source_size().cast::<i32>();
36 let len = line_buffer.len();
37 let y = line - span.origin.y_length();
38 let y = if rotation.mirror_width() { span.size.height - y.get() - 1 } else { y.get() } as i32;
39
40 let off_y = Fixed::<i32, 8>::from_fixed(off_y);
41 let dx = Fixed::<i32, 8>::from_fixed(dx);
42 let dy = Fixed::<i32, 8>::from_fixed(dy);
43 let off_x = Fixed::<i32, 8>::from_fixed(off_x);
44
45 if !rotation.is_transpose() {
46 let mut delta = dx;
47 let row = off_y + dy * y;
48 let row_offset = (row.truncate() % source_size.height) as usize * pixel_stride as usize;
50 let mut tile_start = 0;
51
52 let tile_len = (Fixed::from_integer(source_size.width) / delta) as usize;
54 let mut remainder = Fixed::from_integer(source_size.width) % delta;
56 let mut pos;
58 let mut end;
60 let mut acc_err;
62 if rotation.mirror_height() {
63 let o = (off_x + (delta * (extra_clip_end as i32 + len as i32 - 1)))
64 % Fixed::from_integer(source_size.width);
65 pos = o;
66 tile_start = source_size.width as i32;
67 end = (o / delta) as usize + 1;
68 acc_err = -delta + o % delta;
69 delta = -delta;
70 remainder = -remainder;
71 } else {
72 let o =
73 (off_x + delta * extra_clip_begin as i32) % Fixed::from_integer(source_size.width);
74 pos = o;
75 end = ((Fixed::from_integer(source_size.width) - o) / delta) as usize;
76 acc_err = (Fixed::from_integer(source_size.width) - o) % delta;
77 if acc_err != Fixed::default() {
78 acc_err = delta - acc_err;
79 end += 1;
80 }
81 }
82 end = end.min(len);
83 let mut begin = 0;
84 let row_fract = row.fract();
85 while begin < len {
86 fetch_blend_pixel(
87 &mut line_buffer[begin..end],
88 format,
89 data,
90 alpha,
91 colorize,
92 (pixel_stride as usize, dy),
93 #[inline(always)]
94 |bpp| {
95 let p = ((row_offset + pos.truncate() as usize) * bpp, pos.fract(), row_fract);
96 pos += delta;
97 p
98 },
99 );
100 begin = end;
101 end += tile_len;
102 pos = acc_err + Fixed::from_integer(tile_start);
103 if remainder != Fixed::from_integer(0) {
104 acc_err -= remainder;
105 let wrap = if rotation.mirror_height() {
106 acc_err >= Fixed::from_integer(0)
107 } else {
108 acc_err < Fixed::from_integer(0)
109 };
110 if wrap {
111 acc_err += delta;
112 end += 1;
113 }
114 };
115 end = end.min(len);
116 }
117 } else {
118 let bpp = format.bpp();
119 let col = off_x + dx * y;
120 let col_fract = col.fract();
121 let col = (col.truncate() % source_size.width) as usize * bpp;
122 let stride = pixel_stride as usize * bpp;
123 let mut row_delta = dy;
124 let tile_len = (Fixed::from_integer(source_size.height) / row_delta) as usize;
125 let mut remainder = Fixed::from_integer(source_size.height) % row_delta;
126 let mut end;
127 let mut row_init = Fixed::default();
128 let mut row;
129 let mut acc_err;
130 if rotation.mirror_height() {
131 row_init = Fixed::from_integer(source_size.height);
132 row = (off_y + (row_delta * (extra_clip_end as i32 + len as i32 - 1)))
133 % Fixed::from_integer(source_size.height);
134 end = (row / row_delta) as usize + 1;
135 acc_err = -row_delta + row % row_delta;
136 row_delta = -row_delta;
137 remainder = -remainder;
138 } else {
139 row = (off_y + row_delta * extra_clip_begin as i32)
140 % Fixed::from_integer(source_size.height);
141 end = ((Fixed::from_integer(source_size.height) - row) / row_delta) as usize;
142 acc_err = (Fixed::from_integer(source_size.height) - row) % row_delta;
143 if acc_err != Fixed::default() {
144 acc_err = row_delta - acc_err;
145 end += 1;
146 }
147 };
148 end = end.min(len);
149 let mut begin = 0;
150 while begin < len {
151 fetch_blend_pixel(
152 &mut line_buffer[begin..end],
153 format,
154 data,
155 alpha,
156 colorize,
157 (stride, dy),
158 #[inline(always)]
159 |_| {
160 let pos = (row.truncate() as usize * stride + col, col_fract, row.fract());
161 row += row_delta;
162 pos
163 },
164 );
165 begin = end;
166 end += tile_len;
167 row = row_init;
168 row += acc_err;
169 if remainder != Fixed::from_integer(0) {
170 acc_err -= remainder;
171 let wrap = if rotation.mirror_height() {
172 acc_err >= Fixed::from_integer(0)
173 } else {
174 acc_err < Fixed::from_integer(0)
175 };
176 if wrap {
177 acc_err += row_delta;
178 end += 1;
179 }
180 };
181 end = end.min(len);
182 }
183 };
184
185 fn fetch_blend_pixel(
186 line_buffer: &mut [impl TargetPixel],
187 format: TexturePixelFormat,
188 data: &[u8],
189 alpha: u8,
190 color: Color,
191 (stride, delta): (usize, Fixed<i32, 8>),
192 mut pos: impl FnMut(usize) -> (usize, u8, u8),
193 ) {
194 match format {
195 TexturePixelFormat::Rgb => {
196 for pix in line_buffer {
197 let pos = pos(3).0;
198 let p: &[u8] = &data[pos..pos + 3];
199 if alpha == 0xff {
200 *pix = TargetPixel::from_rgb(p[0], p[1], p[2]);
201 } else {
202 pix.blend(PremultipliedRgbaColor::premultiply(Color::from_argb_u8(
203 alpha, p[0], p[1], p[2],
204 )))
205 }
206 }
207 }
208 TexturePixelFormat::Rgba => {
209 if color.alpha() == 0 {
210 for pix in line_buffer {
211 let pos = pos(4).0;
212 let alpha = ((data[pos + 3] as u16 * alpha as u16) / 255) as u8;
213 let c = PremultipliedRgbaColor::premultiply(Color::from_argb_u8(
214 alpha,
215 data[pos + 0],
216 data[pos + 1],
217 data[pos + 2],
218 ));
219 pix.blend(c);
220 }
221 } else {
222 for pix in line_buffer {
223 let pos = pos(4).0;
224 let alpha = ((data[pos + 3] as u16 * alpha as u16) / 255) as u8;
225 let c = PremultipliedRgbaColor::premultiply(Color::from_argb_u8(
226 alpha,
227 color.red(),
228 color.green(),
229 color.blue(),
230 ));
231 pix.blend(c);
232 }
233 }
234 }
235 TexturePixelFormat::RgbaPremultiplied => {
236 if color.alpha() > 0 {
237 for pix in line_buffer {
238 let pos = pos(4).0;
239 let c = PremultipliedRgbaColor::premultiply(Color::from_argb_u8(
240 ((data[pos + 3] as u16 * alpha as u16) / 255) as u8,
241 color.red(),
242 color.green(),
243 color.blue(),
244 ));
245 pix.blend(c);
246 }
247 } else if alpha == 0xff {
248 for pix in line_buffer {
249 let pos = pos(4).0;
250 let c = PremultipliedRgbaColor {
251 alpha: data[pos + 3],
252 red: data[pos + 0],
253 green: data[pos + 1],
254 blue: data[pos + 2],
255 };
256 pix.blend(c);
257 }
258 } else {
259 for pix in line_buffer {
260 let pos = pos(4).0;
261 let c = PremultipliedRgbaColor {
262 alpha: (data[pos + 3] as u16 * alpha as u16 / 255) as u8,
263 red: (data[pos + 0] as u16 * alpha as u16 / 255) as u8,
264 green: (data[pos + 1] as u16 * alpha as u16 / 255) as u8,
265 blue: (data[pos + 2] as u16 * alpha as u16 / 255) as u8,
266 };
267 pix.blend(c);
268 }
269 }
270 }
271 TexturePixelFormat::AlphaMap => {
272 for pix in line_buffer {
273 let pos = pos(1).0;
274 let c = PremultipliedRgbaColor::premultiply(Color::from_argb_u8(
275 ((data[pos] as u16 * alpha as u16) / 255) as u8,
276 color.red(),
277 color.green(),
278 color.blue(),
279 ));
280 pix.blend(c);
281 }
282 }
283 TexturePixelFormat::SignedDistanceField => {
284 const RANGE: i32 = 6;
285 let factor = (362 * 256 / delta.0) * RANGE; for pix in line_buffer {
287 let (pos, col_f, row_f) = pos(1);
288 let (col_f, row_f) = (col_f as i32, row_f as i32);
289 let mut dist = ((data[pos] as i8 as i32) * (256 - col_f)
290 + (data[pos + 1] as i8 as i32) * col_f)
291 * (256 - row_f);
292 if pos + stride + 1 < data.len() {
293 dist += ((data[pos + stride] as i8 as i32) * (256 - col_f)
294 + (data[pos + stride + 1] as i8 as i32) * col_f)
295 * row_f
296 } else {
297 debug_assert_eq!(row_f, 0);
298 }
299 let a = ((((dist >> 8) * factor) >> 16) + 128).clamp(0, 255) * alpha as i32;
300 let c = PremultipliedRgbaColor::premultiply(Color::from_argb_u8(
301 (a / 255) as u8,
302 color.red(),
303 color.green(),
304 color.blue(),
305 ));
306 pix.blend(c);
307 }
308 }
309 };
310 }
311}
312
313#[allow(clippy::unnecessary_cast)] pub(super) fn draw_rounded_rectangle_line(
316 span: &PhysicalRect,
317 line: PhysicalLength,
318 rr: &super::RoundedRectangle,
319 line_buffer: &mut [impl TargetPixel],
320 extra_left_clip: i16,
321 extra_right_clip: i16,
322) {
323 #[derive(Clone, Copy, PartialEq, Ord, PartialOrd, Eq, Add, Sub, Mul)]
327 struct Shifted(u32);
328 impl Shifted {
329 const ONE: Self = Shifted(1 << 4);
330 #[track_caller]
331 #[inline]
332 pub fn new(value: impl TryInto<u32> + core::fmt::Debug + Copy) -> Self {
333 Self(value.try_into().unwrap_or_else(|_| panic!("Overflow {value:?}")) << 4)
334 }
335 #[inline(always)]
336 pub fn floor(self) -> u32 {
337 self.0 >> 4
338 }
339 #[inline(always)]
340 pub fn ceil(self) -> u32 {
341 (self.0 + Self::ONE.0 - 1) >> 4
342 }
343 #[inline(always)]
344 pub fn saturating_sub(self, other: Self) -> Self {
345 Self(self.0.saturating_sub(other.0))
346 }
347 #[inline(always)]
348 pub fn sqrt(self) -> Self {
349 Self(self.0.integer_sqrt())
350 }
351 }
352 impl core::ops::Mul for Shifted {
353 type Output = Shifted;
354 #[inline(always)]
355 fn mul(self, rhs: Self) -> Self::Output {
356 Self(self.0 * rhs.0)
357 }
358 }
359 let width = line_buffer.len();
360 let y1 = (line - span.origin.y_length()) + rr.top_clip;
361 let y2 = (span.origin.y_length() + span.size.height_length() - line) + rr.bottom_clip
362 - PhysicalLength::new(1);
363 let y = y1.min(y2);
364 debug_assert!(y.get() >= 0,);
365 let border = Shifted::new(rr.width.get());
366 const ONE: Shifted = Shifted::ONE;
367 const ZERO: Shifted = Shifted(0);
368 let anti_alias = |x1: Shifted, x2: Shifted, process_pixel: &mut dyn FnMut(usize, u32)| {
369 for x in x1.floor()..x2.ceil() {
374 let cov = ((ONE + Shifted::new(x) - x1).0 << 8) / (ONE + x2 - x1).0;
376 process_pixel(x as usize, cov);
377 }
378 };
379 let rev = |x: Shifted| {
380 (Shifted::new(width) + Shifted::new(rr.right_clip.get() + extra_right_clip))
381 .saturating_sub(x)
382 };
383 let calculate_xxxx = |r: i16, y: i16| {
384 let r = Shifted::new(r);
385 let y = r - Shifted::new(y);
387 let x2 = r - (r * r).saturating_sub(y * y).sqrt();
390 let x1 = r - (r * r).saturating_sub((y - ONE) * (y - ONE)).sqrt();
391 let r2 = r.saturating_sub(border);
392 let x4 = r - (r2 * r2).saturating_sub(y * y).sqrt();
393 let x3 = r - (r2 * r2).saturating_sub((y - ONE) * (y - ONE)).sqrt();
394 (x1, x2, x3, x4)
395 };
396
397 let (x1, x2, x3, x4, x5, x6, x7, x8) = if let Some(r) = rr.radius.as_uniform() {
398 let (x1, x2, x3, x4) =
399 if y.get() < r { calculate_xxxx(r, y.get()) } else { (ZERO, ZERO, border, border) };
400 (x1, x2, x3, x4, rev(x4), rev(x3), rev(x2), rev(x1))
401 } else {
402 let (x1, x2, x3, x4) = if y1 < PhysicalLength::new(rr.radius.top_left) {
403 calculate_xxxx(rr.radius.top_left, y.get())
404 } else if y2 < PhysicalLength::new(rr.radius.bottom_left) {
405 calculate_xxxx(rr.radius.bottom_left, y.get())
406 } else {
407 (ZERO, ZERO, border, border)
408 };
409 let (x5, x6, x7, x8) = if y1 < PhysicalLength::new(rr.radius.top_right) {
410 let x = calculate_xxxx(rr.radius.top_right, y.get());
411 (x.3, x.2, x.1, x.0)
412 } else if y2 < PhysicalLength::new(rr.radius.bottom_right) {
413 let x = calculate_xxxx(rr.radius.bottom_right, y.get());
414 (x.3, x.2, x.1, x.0)
415 } else {
416 (border, border, ZERO, ZERO)
417 };
418 (x1, x2, x3, x4, rev(x5), rev(x6), rev(x7), rev(x8))
419 };
420 anti_alias(
421 x1.saturating_sub(Shifted::new(rr.left_clip.get() + extra_left_clip)),
422 x2.saturating_sub(Shifted::new(rr.left_clip.get() + extra_left_clip)),
423 &mut |x, cov| {
424 if x >= width {
425 return;
426 }
427 let c = if border == ZERO { rr.inner_color } else { rr.border_color };
428 let col = PremultipliedRgbaColor {
429 alpha: (((c.alpha as u32) * cov as u32) / 255) as u8,
430 red: (((c.red as u32) * cov as u32) / 255) as u8,
431 green: (((c.green as u32) * cov as u32) / 255) as u8,
432 blue: (((c.blue as u32) * cov as u32) / 255) as u8,
433 };
434 line_buffer[x].blend(col);
435 },
436 );
437 if y < rr.width {
438 let l = x2
440 .ceil()
441 .saturating_sub((rr.left_clip.get() + extra_left_clip) as u32)
442 .min(width as u32) as usize;
443 let r = x7.floor().min(width as u32) as usize;
444 if l < r {
445 TargetPixel::blend_slice(&mut line_buffer[l..r], rr.border_color)
446 }
447 } else {
448 if border > ZERO {
449 if ONE + x2 <= x3 {
451 TargetPixel::blend_slice(
452 &mut line_buffer[x2
453 .ceil()
454 .saturating_sub((rr.left_clip.get() + extra_left_clip) as u32)
455 .min(width as u32) as usize
456 ..x3.floor()
457 .saturating_sub((rr.left_clip.get() + extra_left_clip) as u32)
458 .min(width as u32) as usize],
459 rr.border_color,
460 )
461 }
462 anti_alias(
464 x3.saturating_sub(Shifted::new(rr.left_clip.get() + extra_left_clip)),
465 x4.saturating_sub(Shifted::new(rr.left_clip.get() + extra_left_clip)),
466 &mut |x, cov| {
467 if x >= width {
468 return;
469 }
470 let col = interpolate_color(cov, rr.border_color, rr.inner_color);
471 line_buffer[x].blend(col);
472 },
473 );
474 }
475 if rr.inner_color.alpha > 0 {
476 let begin = x4
478 .ceil()
479 .saturating_sub((rr.left_clip.get() + extra_left_clip) as u32)
480 .min(width as u32);
481 let end = x5.floor().min(width as u32);
482 if begin < end {
483 TargetPixel::blend_slice(
484 &mut line_buffer[begin as usize..end as usize],
485 rr.inner_color,
486 )
487 }
488 }
489 if border > ZERO {
490 anti_alias(x5, x6, &mut |x, cov| {
492 if x >= width {
493 return;
494 }
495 let col = interpolate_color(cov, rr.inner_color, rr.border_color);
496 line_buffer[x].blend(col)
497 });
498 if ONE + x6 <= x7 {
500 TargetPixel::blend_slice(
501 &mut line_buffer[x6.ceil().min(width as u32) as usize
502 ..x7.floor().min(width as u32) as usize],
503 rr.border_color,
504 )
505 }
506 }
507 }
508 anti_alias(x7, x8, &mut |x, cov| {
509 if x >= width {
510 return;
511 }
512 let c = if border == ZERO { rr.inner_color } else { rr.border_color };
513 let col = PremultipliedRgbaColor {
514 alpha: (((c.alpha as u32) * (255 - cov) as u32) / 255) as u8,
515 red: (((c.red as u32) * (255 - cov) as u32) / 255) as u8,
516 green: (((c.green as u32) * (255 - cov) as u32) / 255) as u8,
517 blue: (((c.blue as u32) * (255 - cov) as u32) / 255) as u8,
518 };
519 line_buffer[x].blend(col);
520 });
521}
522
523fn interpolate_color(
525 a: u32,
526 color1: PremultipliedRgbaColor,
527 color2: PremultipliedRgbaColor,
528) -> PremultipliedRgbaColor {
529 let b = 255 - a;
530
531 let al1 = color1.alpha as u32;
532 let al2 = color2.alpha as u32;
533
534 let a_ = a * al2;
535 let b_ = b * al1;
536 let m = a_ + b_;
537
538 if m == 0 {
539 return PremultipliedRgbaColor::default();
540 }
541
542 PremultipliedRgbaColor {
543 alpha: (m / 255) as u8,
544 red: ((b * color1.red as u32 + a * color2.red as u32) / 255) as u8,
545 green: ((b * color1.green as u32 + a * color2.green as u32) / 255) as u8,
546 blue: ((b * color1.blue as u32 + a * color2.blue as u32) / 255) as u8,
547 }
548}
549
550pub(super) fn draw_linear_gradient(
551 rect: &PhysicalRect,
552 line: PhysicalLength,
553 g: &super::LinearGradientCommand,
554 mut buffer: &mut [impl TargetPixel],
555 extra_left_clip: i16,
556) {
557 let fill_col1 = g.flags & 0b010 != 0;
558 let fill_col2 = g.flags & 0b100 != 0;
559 let invert_slope = g.flags & 0b1 != 0;
560
561 let y = (line.get() - rect.min_y() + g.top_clip.get()) as i32;
562 let size_y = (rect.height() + g.top_clip.get() + g.bottom_clip.get()) as i32;
563 let start = g.start as i32;
564
565 let (mut color1, mut color2) = (g.color1, g.color2);
566
567 if g.start == 0 {
568 let p = if invert_slope {
569 (255 - start) * y / size_y
570 } else {
571 start + (255 - start) * y / size_y
572 };
573 if (fill_col1 || p >= 0) && (fill_col2 || p < 255) {
574 let col = interpolate_color(p.clamp(0, 255) as u32, color1, color2);
575 TargetPixel::blend_slice(buffer, col);
576 }
577 return;
578 }
579
580 let size_x = (rect.width() + g.left_clip.get() + g.right_clip.get()) as i32;
581
582 let mut x = if invert_slope {
583 (y * size_x * (255 - start)) / (size_y * start)
584 } else {
585 (size_y - y) * size_x * (255 - start) / (size_y * start)
586 } + g.left_clip.get() as i32
587 + extra_left_clip as i32;
588
589 let len = ((255 * size_x) / start) as usize;
590
591 if x < 0 {
592 let l = (-x as usize).min(buffer.len());
593 if invert_slope {
594 if fill_col1 {
595 TargetPixel::blend_slice(&mut buffer[..l], g.color1);
596 }
597 } else if fill_col2 {
598 TargetPixel::blend_slice(&mut buffer[..l], g.color2);
599 }
600 buffer = &mut buffer[l..];
601 x = 0;
602 }
603
604 if buffer.len() + x as usize > len {
605 let l = len.saturating_sub(x as usize);
606 if invert_slope {
607 if fill_col2 {
608 TargetPixel::blend_slice(&mut buffer[l..], g.color2);
609 }
610 } else if fill_col1 {
611 TargetPixel::blend_slice(&mut buffer[l..], g.color1);
612 }
613 buffer = &mut buffer[..l];
614 }
615
616 if buffer.is_empty() {
617 return;
618 }
619
620 if !invert_slope {
621 core::mem::swap(&mut color1, &mut color2);
622 }
623
624 let dr = (((color2.red as i32 - color1.red as i32) * start) << 15) / (255 * size_x);
625 let dg = (((color2.green as i32 - color1.green as i32) * start) << 15) / (255 * size_x);
626 let db = (((color2.blue as i32 - color1.blue as i32) * start) << 15) / (255 * size_x);
627 let da = (((color2.alpha as i32 - color1.alpha as i32) * start) << 15) / (255 * size_x);
628
629 let mut r = ((color1.red as u32) << 15).wrapping_add((x * dr) as _);
630 let mut g = ((color1.green as u32) << 15).wrapping_add((x * dg) as _);
631 let mut b = ((color1.blue as u32) << 15).wrapping_add((x * db) as _);
632 let mut a = ((color1.alpha as u32) << 15).wrapping_add((x * da) as _);
633
634 if color1.alpha == 255 && color2.alpha == 255 {
635 buffer.fill_with(|| {
636 let pix = TargetPixel::from_rgb((r >> 15) as u8, (g >> 15) as u8, (b >> 15) as u8);
637 r = r.wrapping_add(dr as _);
638 g = g.wrapping_add(dg as _);
639 b = b.wrapping_add(db as _);
640 pix
641 })
642 } else {
643 for pix in buffer {
644 pix.blend(PremultipliedRgbaColor {
645 red: (r >> 15) as u8,
646 green: (g >> 15) as u8,
647 blue: (b >> 15) as u8,
648 alpha: (a >> 15) as u8,
649 });
650 r = r.wrapping_add(dr as _);
651 g = g.wrapping_add(dg as _);
652 b = b.wrapping_add(db as _);
653 a = a.wrapping_add(da as _);
654 }
655 }
656}
657
658pub(super) fn draw_radial_gradient(
660 rect: &PhysicalRect,
661 line: PhysicalLength,
662 g: &super::RadialGradientCommand,
663 buffer: &mut [impl TargetPixel],
664 extra_left_clip: i16,
665 _extra_right_clip: i16,
666) {
667 if g.stops.is_empty() {
668 return;
669 }
670
671 let center_x = (rect.min_x() + g.center_x.get()) as i32;
672 let center_y = (rect.min_y() + g.center_y.get()) as i32;
673
674 let max_radius = {
676 let dx1 = ((rect.min_x() as i32) - center_x).abs();
677 let dx2 = ((rect.max_x() as i32) - center_x).abs();
678 let dy1 = ((rect.min_y() as i32) - center_y).abs();
679 let dy2 = ((rect.max_y() as i32) - center_y).abs();
680 let max_dx = dx1.max(dx2) as f32;
681 let max_dy = dy1.max(dy2) as f32;
682 (max_dx * max_dx + max_dy * max_dy).sqrt()
683 };
684
685 let start_x = rect.min_x() + extra_left_clip;
686 let dy = (line.get() as i32 - center_y) as f32;
688 let dy_squared = dy * dy;
689
690 for (i, pixel) in buffer.iter_mut().enumerate() {
691 let x = start_x + i as i16;
692 let dx = (x as i32 - center_x) as f32;
693 let distance = (dx * dx + dy_squared).sqrt();
694 let position = (distance / max_radius).clamp(0.0, 1.0);
695
696 let mut color = g.stops.first().map(|s| s.color).unwrap_or_default();
698
699 for window in g.stops.windows(2) {
700 let stop1 = &window[0];
701 let stop2 = &window[1];
702
703 if position >= stop1.position && position <= stop2.position {
704 let t = if stop2.position == stop1.position {
706 0.0
707 } else {
708 (position - stop1.position) / (stop2.position - stop1.position)
709 };
710
711 let c1 = stop1.color.to_argb_u8();
712 let c2 = stop2.color.to_argb_u8();
713
714 let alpha = ((1.0 - t) * c1.alpha as f32 + t * c2.alpha as f32) as u8;
715 let red = ((1.0 - t) * c1.red as f32 + t * c2.red as f32) as u8;
716 let green = ((1.0 - t) * c1.green as f32 + t * c2.green as f32) as u8;
717 let blue = ((1.0 - t) * c1.blue as f32 + t * c2.blue as f32) as u8;
718
719 color = Color::from_argb_u8(alpha, red, green, blue);
720 break;
721 } else if position > stop2.position {
722 color = stop2.color;
723 }
724 }
725
726 pixel.blend(super::PremultipliedRgbaColor::from(color));
727 }
728}
729
730pub(super) fn draw_conic_gradient(
732 rect: &PhysicalRect,
733 line: PhysicalLength,
734 g: &super::ConicGradientCommand,
735 buffer: &mut [impl TargetPixel],
736 extra_left_clip: i16,
737 _extra_right_clip: i16,
738) {
739 if g.stops.is_empty() {
740 return;
741 }
742
743 let center_x = (rect.min_x() + rect.width() / 2) as f32;
745 let center_y = (rect.min_y() + rect.height() / 2) as f32;
746
747 let start_x = rect.min_x() + extra_left_clip;
748 let y = line.get() as f32;
749
750 for (i, pixel) in buffer.iter_mut().enumerate() {
751 let x = (start_x + i as i16) as f32;
752
753 let dx = x - center_x;
755 let dy = y - center_y;
756
757 let mut angle = dy.atan2(dx) + core::f32::consts::FRAC_PI_2;
760
761 while angle < 0.0 {
763 angle += 2.0 * core::f32::consts::PI;
764 }
765 while angle >= 2.0 * core::f32::consts::PI {
766 angle -= 2.0 * core::f32::consts::PI;
767 }
768
769 let position = angle / (2.0 * core::f32::consts::PI);
771
772 let mut color = g.stops.first().map(|s| s.color).unwrap_or_default();
774
775 for window in g.stops.windows(2) {
776 let stop1 = &window[0];
777 let stop2 = &window[1];
778
779 if position >= stop1.position && position <= stop2.position {
780 let t = if stop2.position == stop1.position {
782 0.0
783 } else {
784 (position - stop1.position) / (stop2.position - stop1.position)
785 };
786
787 let c1 = stop1.color.to_argb_u8();
788 let c2 = stop2.color.to_argb_u8();
789
790 let alpha = ((1.0 - t) * c1.alpha as f32 + t * c2.alpha as f32) as u8;
791 let red = ((1.0 - t) * c1.red as f32 + t * c2.red as f32) as u8;
792 let green = ((1.0 - t) * c1.green as f32 + t * c2.green as f32) as u8;
793 let blue = ((1.0 - t) * c1.blue as f32 + t * c2.blue as f32) as u8;
794
795 color = Color::from_argb_u8(alpha, red, green, blue);
796 break;
797 } else if position > stop2.position {
798 color = stop2.color;
799 }
800 }
801
802 pixel.blend(super::PremultipliedRgbaColor::from(color));
803 }
804}
805
806#[allow(missing_docs)]
815#[derive(Clone, Copy, Debug, Default, bytemuck::Pod, bytemuck::Zeroable)]
816#[repr(C)]
817pub struct PremultipliedRgbaColor {
818 pub red: u8,
819 pub green: u8,
820 pub blue: u8,
821 pub alpha: u8,
822}
823
824impl From<Color> for PremultipliedRgbaColor {
826 fn from(col: Color) -> Self {
827 Self::premultiply(col)
828 }
829}
830
831impl PremultipliedRgbaColor {
832 fn premultiply(col: Color) -> Self {
834 let a = col.alpha() as u16;
835 Self {
836 alpha: col.alpha(),
837 red: (col.red() as u16 * a / 255) as u8,
838 green: (col.green() as u16 * a / 255) as u8,
839 blue: (col.blue() as u16 * a / 255) as u8,
840 }
841 }
842}
843
844pub trait TargetPixel: Sized + Copy {
846 fn blend(&mut self, color: PremultipliedRgbaColor);
848 fn blend_slice(slice: &mut [Self], color: PremultipliedRgbaColor) {
850 if color.alpha == u8::MAX {
851 slice.fill(Self::from_rgb(color.red, color.green, color.blue))
852 } else {
853 for x in slice {
854 Self::blend(x, color);
855 }
856 }
857 }
858 fn from_rgb(red: u8, green: u8, blue: u8) -> Self;
860
861 fn background() -> Self {
863 Self::from_rgb(0, 0, 0)
864 }
865}
866
867impl TargetPixel for Rgb8Pixel {
868 fn blend(&mut self, color: PremultipliedRgbaColor) {
869 let a = (u8::MAX - color.alpha) as u16;
870 self.r = (self.r as u16 * a / 255) as u8 + color.red;
871 self.g = (self.g as u16 * a / 255) as u8 + color.green;
872 self.b = (self.b as u16 * a / 255) as u8 + color.blue;
873 }
874
875 fn from_rgb(r: u8, g: u8, b: u8) -> Self {
876 Self::new(r, g, b)
877 }
878}
879
880impl TargetPixel for PremultipliedRgbaColor {
881 fn blend(&mut self, color: PremultipliedRgbaColor) {
882 let a = (u8::MAX - color.alpha) as u16;
883 self.red = (self.red as u16 * a / 255) as u8 + color.red;
884 self.green = (self.green as u16 * a / 255) as u8 + color.green;
885 self.blue = (self.blue as u16 * a / 255) as u8 + color.blue;
886 self.alpha = (self.alpha as u16 + color.alpha as u16
887 - (self.alpha as u16 * color.alpha as u16) / 255) as u8;
888 }
889
890 fn from_rgb(r: u8, g: u8, b: u8) -> Self {
891 Self { red: r, green: g, blue: b, alpha: 255 }
892 }
893
894 fn background() -> Self {
895 Self { red: 0, green: 0, blue: 0, alpha: 0 }
896 }
897}
898
899#[repr(transparent)]
901#[derive(Copy, Clone, Debug, PartialEq, Eq, Default, bytemuck::Pod, bytemuck::Zeroable)]
902pub struct Rgb565Pixel(pub u16);
903
904impl Rgb565Pixel {
905 const R_MASK: u16 = 0b1111_1000_0000_0000;
906 const G_MASK: u16 = 0b0000_0111_1110_0000;
907 const B_MASK: u16 = 0b0000_0000_0001_1111;
908
909 fn red(self) -> u8 {
913 ((self.0 & Self::R_MASK) >> 8) as u8
914 }
915 fn green(self) -> u8 {
919 ((self.0 & Self::G_MASK) >> 3) as u8
920 }
921 fn blue(self) -> u8 {
925 ((self.0 & Self::B_MASK) << 3) as u8
926 }
927}
928
929impl TargetPixel for Rgb565Pixel {
930 fn blend(&mut self, color: PremultipliedRgbaColor) {
931 let a = (u8::MAX - color.alpha) as u32;
932 let a = (a + 4) >> 3;
934
935 let expanded = (self.0 & (Self::R_MASK | Self::B_MASK)) as u32
937 | (((self.0 & Self::G_MASK) as u32) << 16);
938
939 let c =
941 ((color.red as u32) << 13) | ((color.green as u32) << 24) | ((color.blue as u32) << 2);
942 let c = c & 0b11111100_00011111_00000011_11100000;
944
945 let res = expanded * a + c;
946
947 self.0 = ((res >> 21) as u16 & Self::G_MASK)
948 | ((res >> 5) as u16 & (Self::R_MASK | Self::B_MASK));
949 }
950
951 fn from_rgb(r: u8, g: u8, b: u8) -> Self {
952 Self(((r as u16 & 0b11111000) << 8) | ((g as u16 & 0b11111100) << 3) | (b as u16 >> 3))
953 }
954}
955
956impl From<Rgb8Pixel> for Rgb565Pixel {
957 fn from(p: Rgb8Pixel) -> Self {
958 Self::from_rgb(p.r, p.g, p.b)
959 }
960}
961
962impl From<Rgb565Pixel> for Rgb8Pixel {
963 fn from(p: Rgb565Pixel) -> Self {
964 Rgb8Pixel { r: p.red(), g: p.green(), b: p.blue() }
965 }
966}
967
968#[test]
969fn rgb565() {
970 let pix565 = Rgb565Pixel::from_rgb(0xff, 0x25, 0);
971 let pix888: Rgb8Pixel = pix565.into();
972 assert_eq!(pix565, pix888.into());
973
974 let pix565 = Rgb565Pixel::from_rgb(0x56, 0x42, 0xe3);
975 let pix888: Rgb8Pixel = pix565.into();
976 assert_eq!(pix565, pix888.into());
977}