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