1use std::sync::atomic::{AtomicUsize, Ordering};
6use std::sync::Arc;
7use std::{fmt, hash};
8
9use crate::simd::{f32x8, u32x8};
10use crate::{AffineTransform, CanonBits, Point};
11
12pub(crate) trait Ratio {
13 fn zero() -> Self;
14 fn one() -> Self;
15}
16
17impl Ratio for f32x8 {
18 #[inline]
19 fn zero() -> Self {
20 f32x8::splat(0.0)
21 }
22
23 #[inline]
24 fn one() -> Self {
25 f32x8::splat(1.0)
26 }
27}
28
29#[derive(Clone, Copy, Debug, Eq, PartialEq)]
30pub enum Channel {
31 Red,
32 Green,
33 Blue,
34 Alpha,
35 Zero,
36 One,
37}
38
39impl Channel {
40 pub(crate) fn select<R: Ratio>(self, r: R, g: R, b: R, a: R) -> R {
41 match self {
42 Channel::Red => r,
43 Channel::Green => g,
44 Channel::Blue => b,
45 Channel::Alpha => a,
46 Channel::Zero => R::zero(),
47 Channel::One => R::one(),
48 }
49 }
50}
51
52pub const RGBA: [Channel; 4] = [Channel::Red, Channel::Green, Channel::Blue, Channel::Alpha];
53pub const BGRA: [Channel; 4] = [Channel::Blue, Channel::Green, Channel::Red, Channel::Alpha];
54pub const RGB0: [Channel; 4] = [Channel::Red, Channel::Green, Channel::Blue, Channel::Zero];
55pub const BGR0: [Channel; 4] = [Channel::Blue, Channel::Green, Channel::Red, Channel::Zero];
56pub const RGB1: [Channel; 4] = [Channel::Red, Channel::Green, Channel::Blue, Channel::One];
57pub const BGR1: [Channel; 4] = [Channel::Blue, Channel::Green, Channel::Red, Channel::One];
58
59#[derive(Clone, Copy, Debug)]
60pub struct Color {
61 pub r: f32,
62 pub g: f32,
63 pub b: f32,
64 pub a: f32,
65}
66
67impl Eq for Color {}
68
69impl PartialEq for Color {
70 fn eq(&self, other: &Self) -> bool {
71 self.r == other.r && self.g == other.g && self.b == other.b && self.a == other.a
72 }
73}
74
75impl hash::Hash for Color {
76 fn hash<H: hash::Hasher>(&self, state: &mut H) {
77 self.r.to_canon_bits().hash(state);
78 self.g.to_canon_bits().hash(state);
79 self.b.to_canon_bits().hash(state);
80 self.a.to_canon_bits().hash(state);
81 }
82}
83
84impl Color {
85 pub const fn to_array(self) -> [f32; 4] {
86 [self.r, self.g, self.b, self.a]
87 }
88
89 pub(crate) fn max(&self) -> f32 {
90 self.r.max(self.g.max(self.b))
91 }
92 pub(crate) fn min(&self) -> f32 {
93 self.r.min(self.g.min(self.b))
94 }
95
96 pub(crate) fn sorted(&mut self) -> [&mut f32; 3] {
97 let c = [self.r, self.g, self.b];
98
99 match (c[0] < c[1], c[0] < c[2], c[1] < c[2]) {
100 (true, true, true) => [&mut self.r, &mut self.g, &mut self.b],
101 (true, true, false) => [&mut self.r, &mut self.b, &mut self.g],
102 (true, false, _) => [&mut self.b, &mut self.r, &mut self.g],
103 (false, true, true) => [&mut self.g, &mut self.r, &mut self.b],
104 (false, _, false) => [&mut self.b, &mut self.g, &mut self.r],
105 (false, false, true) => [&mut self.g, &mut self.b, &mut self.r],
106 }
107 }
108
109 pub(crate) fn channel(&self, c: Channel) -> f32 {
110 match c {
111 Channel::Red => self.r,
112 Channel::Green => self.g,
113 Channel::Blue => self.b,
114 Channel::Alpha => self.a,
115 Channel::Zero => 0.0,
116 Channel::One => 1.0,
117 }
118 }
119}
120
121impl Default for Color {
122 fn default() -> Self {
123 Self { r: 0.0, g: 0.0, b: 0.0, a: 1.0 }
124 }
125}
126
127#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
128pub enum FillRule {
129 NonZero,
130 EvenOdd,
131}
132
133impl Default for FillRule {
134 fn default() -> Self {
135 Self::NonZero
136 }
137}
138
139#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
140pub enum GradientType {
141 Linear,
142 Radial,
143}
144
145const NO_STOP: f32 = -0.0;
146
147#[derive(Clone, Debug)]
148pub struct GradientBuilder {
149 r#type: GradientType,
150 start: Point,
151 end: Point,
152 stops: Vec<(Color, f32)>,
153}
154
155impl GradientBuilder {
156 pub fn new(start: Point, end: Point) -> Self {
157 Self { r#type: GradientType::Linear, start, end, stops: Vec::new() }
158 }
159
160 pub fn r#type(&mut self, r#type: GradientType) -> &mut Self {
161 self.r#type = r#type;
162 self
163 }
164
165 pub fn color(&mut self, color: Color) -> &mut Self {
166 self.stops.push((color, NO_STOP));
167 self
168 }
169
170 pub fn color_with_stop(&mut self, color: Color, stop: f32) -> &mut Self {
171 if !(0.0..=1.0).contains(&stop) {
172 panic!("gradient stops must be between 0.0 and 1.0");
173 }
174
175 self.stops.push((color, stop));
176 self
177 }
178
179 pub fn build(mut self) -> Option<Gradient> {
180 if self.stops.len() < 2 {
181 return None;
182 }
183
184 let stop_increment = 1.0 / (self.stops.len() - 1) as f32;
185 for (i, (_, stop)) in self.stops.iter_mut().enumerate() {
186 if *stop == NO_STOP {
187 *stop = i as f32 * stop_increment;
188 }
189 }
190
191 Some(Gradient {
192 r#type: self.r#type,
193 start: self.start,
194 end: self.end,
195 stops: self.stops.into(),
196 })
197 }
198}
199
200#[derive(Clone, Debug)]
201pub struct Gradient {
202 r#type: GradientType,
203 start: Point,
204 end: Point,
205 stops: Arc<[(Color, f32)]>,
206}
207
208impl Eq for Gradient {}
209
210impl PartialEq for Gradient {
211 fn eq(&self, other: &Self) -> bool {
212 self.r#type == other.r#type
213 && self.start == other.start
214 && self.end == other.end
215 && self.stops == other.stops
216 }
217}
218
219impl hash::Hash for Gradient {
220 fn hash<H: hash::Hasher>(&self, state: &mut H) {
221 self.r#type.hash(state);
222 self.start.hash(state);
223 self.end.hash(state);
224
225 self.stops.len().hash(state);
226 for (color, stop) in self.stops.iter() {
227 (color, stop.to_canon_bits()).hash(state);
228 }
229 }
230}
231
232impl Gradient {
233 pub fn r#type(&self) -> GradientType {
234 self.r#type
235 }
236
237 pub fn start(&self) -> Point {
238 self.start
239 }
240
241 pub fn end(&self) -> Point {
242 self.end
243 }
244
245 #[inline]
246 pub fn colors_with_stops(&self) -> &[(Color, f32)] {
247 &self.stops
248 }
249
250 fn get_t(&self, x: f32, y: f32) -> f32x8 {
251 let dx = self.end.x - self.start.x;
252 let dy = self.end.y - self.start.y;
253
254 let dot = dx * dx + dy * dy;
255 let dot_recip = dot.recip();
256
257 match self.r#type {
258 GradientType::Linear => {
259 let tx = (x - self.start.x) * dx * dot_recip;
260 let ty = y - self.start.y;
261
262 ((f32x8::indexed() + f32x8::splat(ty)) * f32x8::splat(dy))
263 .mul_add(f32x8::splat(dot_recip), f32x8::splat(tx))
264 }
265 GradientType::Radial => {
266 let px = x - self.start.x;
267 let px2 = f32x8::splat(px * px);
268 let py = f32x8::indexed() + f32x8::splat(y - self.start.y);
269
270 (py.mul_add(py, px2) * f32x8::splat(dot_recip)).sqrt()
271 }
272 }
273 }
274
275 pub(crate) fn color_at(&self, x: f32, y: f32) -> [f32x8; 4] {
276 let mut channels = [f32x8::splat(0.0); 4];
277
278 let t = self.get_t(x, y);
279
280 let mask = t.le(f32x8::splat(self.stops[0].1));
281 if mask.any() {
282 let stop = self.stops[0].0;
283 for (channel, &stop_channel) in
284 channels.iter_mut().zip([stop.r, stop.g, stop.b, stop.a].iter())
285 {
286 *channel |= f32x8::splat(stop_channel).select(f32x8::splat(0.0), mask);
287 }
288 }
289
290 let mut start_stop = 0.0;
291 let mut start_color = self.stops[0].0;
292 let mut acc_mask = mask;
293
294 for &(color, end_stop) in self.stops.iter().skip(1) {
295 let mask = acc_mask ^ t.lt(f32x8::splat(end_stop));
296 if mask.any() {
297 let d = end_stop - start_stop;
298 let local_t = (t - f32x8::splat(start_stop)) * f32x8::splat(d.recip());
299
300 for (channel, (&start_channel, &end_channel)) in channels.iter_mut().zip(
301 [start_color.r, start_color.g, start_color.b, start_color.a]
302 .iter()
303 .zip([color.r, color.g, color.b, color.a].iter()),
304 ) {
305 *channel |= local_t
306 .mul_add(
307 f32x8::splat(end_channel),
308 (-local_t)
309 .mul_add(f32x8::splat(start_channel), f32x8::splat(start_channel)),
310 )
311 .select(f32x8::splat(0.0), mask);
312 }
313
314 acc_mask |= mask;
315 }
316
317 start_stop = end_stop;
318 start_color = color;
319 }
320
321 let mask = !acc_mask;
322 if mask.any() {
323 let stop = self.stops[self.stops.len() - 1].0;
324 for (channel, &stop_channel) in
325 channels.iter_mut().zip([stop.r, stop.g, stop.b, stop.a].iter())
326 {
327 *channel |= f32x8::splat(stop_channel).select(f32x8::splat(0.0), mask);
328 }
329 }
330
331 channels
332 }
333}
334#[derive(Debug)]
335pub enum ImageError {
336 SizeMismatch { len: usize, width: usize, height: usize },
337 TooLarge,
338}
339
340impl fmt::Display for ImageError {
341 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
342 match self {
343 Self::SizeMismatch { len, width, height } => {
344 write!(
345 f,
346 "buffer has {} pixels, which does not match \
347 the specified width ({}) and height ({})",
348 len, width, height
349 )
350 }
351 Self::TooLarge => {
352 write!(
353 f,
354 "image dimensions exceed what is addressable \
355 with f32; try to reduce the image size."
356 )
357 }
358 }
359 }
360}
361
362#[allow(non_camel_case_types)]
364#[repr(C)]
365#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
366pub struct f16(u16);
367
368impl f16 {
369 #[inline]
370 fn to_f32(self) -> f32 {
371 if self.0 != 0 {
372 f32::from_bits(0x3800_0000 + (u32::from(self.0) << 13))
373 } else {
374 0.0
375 }
376 }
377}
378
379impl From<f32> for f16 {
380 fn from(val: f32) -> Self {
381 if val != 0.0 {
382 f16(((val.to_bits() - 0x3800_0000) >> 13) as u16)
383 } else {
384 f16(0)
385 }
386 }
387}
388
389fn to_linear(l: u8) -> f32 {
391 let l = f32::from(l) * 255.0f32.recip();
392 if l <= 0.04045 {
393 l * 12.92f32.recip()
394 } else {
395 ((l + 0.055) * 1.055f32.recip()).powf(2.4)
396 }
397}
398
399#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
400pub struct ImageId(usize);
401impl ImageId {
402 fn new() -> ImageId {
403 static GENERATOR: AtomicUsize = AtomicUsize::new(0);
404
405 ImageId(GENERATOR.fetch_add(1, Ordering::SeqCst))
406 }
407}
408
409#[derive(Clone, Debug)]
410pub struct Image {
411 data: Box<[[f16; 4]]>,
413 max_x: f32,
415 max_y: f32,
417 width: u32,
419 id: ImageId,
421}
422
423impl Eq for Image {}
424
425impl PartialEq for Image {
426 fn eq(&self, other: &Self) -> bool {
427 self.data.as_ptr() == other.data.as_ptr()
428 && self.max_x == other.max_x
429 && self.max_y == other.max_y
430 }
431}
432
433impl hash::Hash for Image {
434 fn hash<H: hash::Hasher>(&self, state: &mut H) {
435 self.data.as_ptr().hash(state);
436 self.max_x.to_canon_bits().hash(state);
437 self.max_y.to_canon_bits().hash(state);
438 }
439}
440
441impl Image {
442 pub fn from_srgba(data: &[[u8; 4]], width: usize, height: usize) -> Result<Self, ImageError> {
445 let to_alpha = |a| f32::from(a) * f32::from(u8::MAX).recip();
446 let data = data
447 .iter()
448 .map(|c| {
449 [to_linear(c[0]), to_linear(c[1]), to_linear(c[2]), to_alpha(c[3])].map(f16::from)
450 })
451 .collect();
452 Self::new(data, width, height)
453 }
454
455 pub fn from_linear_rgba(
456 data: &[[f32; 4]],
457 width: usize,
458 height: usize,
459 ) -> Result<Self, ImageError> {
460 let data = data.iter().map(|c| c.map(f16::from)).collect();
461 Self::new(data, width, height)
462 }
463
464 fn new(data: Box<[[f16; 4]]>, width: usize, height: usize) -> Result<Self, ImageError> {
465 match width * height {
466 len if len > u32::MAX as usize => Err(ImageError::TooLarge),
467 len if len != data.len() => {
468 Err(ImageError::SizeMismatch { len: data.len(), width, height })
469 }
470 _ => Ok(Image {
471 data,
472 max_x: width as f32 - 1.0,
473 max_y: height as f32 - 1.0,
474 width: width as u32,
475 id: ImageId::new(),
476 }),
477 }
478 }
479
480 pub fn id(&self) -> ImageId {
481 self.id
482 }
483
484 pub fn width(&self) -> u32 {
485 self.width
486 }
487
488 pub fn height(&self) -> u32 {
489 self.max_y as u32 + 1
490 }
491
492 pub fn data(&self) -> &[[f16; 4]] {
493 self.data.as_ref()
494 }
495}
496
497#[derive(Clone, Debug, Eq, Hash, PartialEq)]
499pub struct Texture {
500 pub transform: AffineTransform,
502 pub image: Arc<Image>,
504}
505
506impl Texture {
507 #[inline]
508 pub(crate) fn color_at(&self, x: f32, y: f32) -> [f32x8; 4] {
509 let x = f32x8::splat(x);
510 let y = f32x8::splat(y) + f32x8::indexed();
511 let t = self.transform;
513 let tx = x.mul_add(f32x8::splat(t.ux), f32x8::splat(t.vx).mul_add(y, f32x8::splat(t.tx)));
514 let ty = x.mul_add(f32x8::splat(t.uy), f32x8::splat(t.vy).mul_add(y, f32x8::splat(t.ty)));
515
516 let tx = u32x8::from(tx.min(f32x8::splat(self.image.max_x)));
518 let ty = u32x8::from(ty.min(f32x8::splat(self.image.max_y)));
519 let offsets = ty.mul_add(u32x8::splat(self.image.width), tx);
522
523 let data = &*self.image.data;
524 let pixels = offsets.to_array().map(|o| unsafe { data.get_unchecked(o as usize) });
525
526 let get_channel = |c: usize| {
527 f32x8::from_array([
528 pixels[0][c].to_f32(),
529 pixels[1][c].to_f32(),
530 pixels[2][c].to_f32(),
531 pixels[3][c].to_f32(),
532 pixels[4][c].to_f32(),
533 pixels[5][c].to_f32(),
534 pixels[6][c].to_f32(),
535 pixels[7][c].to_f32(),
536 ])
537 };
538 [get_channel(0), get_channel(1), get_channel(2), get_channel(3)]
539 }
540}
541
542#[derive(Clone, Debug, Eq, Hash, PartialEq)]
543pub enum Fill {
544 Solid(Color),
545 Gradient(Gradient),
546 Texture(Texture),
547}
548
549impl Default for Fill {
550 fn default() -> Self {
551 Self::Solid(Color::default())
552 }
553}
554
555#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
556pub enum BlendMode {
557 Over,
558 Multiply,
559 Screen,
560 Overlay,
561 Darken,
562 Lighten,
563 ColorDodge,
564 ColorBurn,
565 HardLight,
566 SoftLight,
567 Difference,
568 Exclusion,
569 Hue,
570 Saturation,
571 Color,
572 Luminosity,
573}
574
575impl BlendMode {
576 pub(crate) fn blend_fn(self) -> fn(Channel, Color, Color) -> f32 {
577 fn multiply(dst: f32, src: f32) -> f32 {
578 dst * src
579 }
580
581 fn screen(dst: f32, src: f32) -> f32 {
582 dst + src - (dst * src)
583 }
584
585 fn hard_light(dst: f32, src: f32) -> f32 {
586 if src <= 0.5 {
587 multiply(dst, 2.0 * src)
588 } else {
589 screen(dst, 2.0 * src - 1.0)
590 }
591 }
592
593 fn lum(color: Color) -> f32 {
594 color.r.mul_add(0.3, color.g.mul_add(0.59, color.b * 0.11))
595 }
596
597 fn clip_color(c: Channel, color: Color) -> f32 {
598 let l = lum(color);
599 let n = color.min();
600 let x = color.max();
601 let mut c = color.channel(c);
602
603 if n < 0.0 {
604 let l_n_recip_l = (l - n).recip() * l;
605 c = l_n_recip_l.mul_add(c - l, l);
606 }
607
608 if x > 1.0 {
609 let l_1 = l - 1.0;
610 let x_l_recip = (x - l).recip();
611 c = x_l_recip.mul_add(l.mul_add(l_1 - c, c), l);
612 }
613 c
614 }
615
616 fn set_lum(c: Channel, mut color: Color, l: f32) -> f32 {
617 let d = l - lum(color);
618 color.r += d;
619 color.g += d;
620 color.b += d;
621 clip_color(c, color)
622 }
623
624 fn sat(color: Color) -> f32 {
625 color.max() - color.min()
626 }
627
628 fn set_sat(mut color: Color, s: f32) -> Color {
629 let [c_min, c_mid, c_max] = color.sorted();
630 if c_max > c_min {
631 *c_mid = s.mul_add(*c_mid, -s * *c_min) / (*c_max - *c_min);
632 *c_max = s;
633 } else {
634 *c_mid = 0.0;
635 *c_max = 0.0;
636 }
637 *c_min = 0.0;
638 color
639 }
640
641 match self {
642 Self::Over => |c, _, src| src.channel(c),
643 Self::Multiply => |c, dst, src| multiply(dst.channel(c), src.channel(c)),
644 Self::Screen => |c, dst, src| screen(dst.channel(c), src.channel(c)),
645 Self::Overlay => |c, dst, src| hard_light(src.channel(c), dst.channel(c)),
646 Self::Darken => |c, dst, src| dst.channel(c).min(src.channel(c)),
647 Self::Lighten => |c, dst, src| dst.channel(c).max(src.channel(c)),
648 Self::ColorDodge => |c, dst, src| {
649 if dst.channel(c) == 0.0 {
650 0.0
651 } else if src.channel(c) == 1.0 {
652 1.0
653 } else {
654 1.0f32.min(dst.channel(c) / (1.0 - src.channel(c)))
655 }
656 },
657 Self::ColorBurn => |c, dst, src| {
658 if dst.channel(c) == 1.0 {
659 1.0
660 } else if src.channel(c) == 0.0 {
661 0.0
662 } else {
663 1.0 - 1.0f32.min((1.0 - dst.channel(c)) / src.channel(c))
664 }
665 },
666 Self::HardLight => |c, dst, src| hard_light(dst.channel(c), src.channel(c)),
667 Self::SoftLight => |c, dst, src| {
668 fn d(dst: f32) -> f32 {
669 if dst <= 0.25 {
670 ((16.0 * dst - 12.0) * dst + 4.0) * dst
671 } else {
672 dst.sqrt()
673 }
674 }
675
676 if src.channel(c) <= 0.5 {
677 dst.channel(c)
678 - (1.0 - 2.0 * src.channel(c)) * dst.channel(c) * (1.0 - dst.channel(c))
679 } else {
680 dst.channel(c)
681 + (2.0 * src.channel(c) - 1.0) * (d(dst.channel(c)) - dst.channel(c))
682 }
683 },
684 Self::Difference => |c, dst, src| (dst.channel(c) - src.channel(c)).abs(),
685 Self::Exclusion => |c, dst, src| {
686 dst.channel(c) + src.channel(c) - 2.0 * dst.channel(c) * src.channel(c)
687 },
688 Self::Color => |c, dst, src| set_lum(c, src, lum(dst)),
689 Self::Luminosity => |c, dst, src| set_lum(c, dst, lum(src)),
690 Self::Hue => |c, dst, src| set_lum(c, set_sat(src, sat(dst)), lum(dst)),
691 Self::Saturation => |c, dst, src| set_lum(c, set_sat(dst, sat(src)), lum(dst)),
692 }
693 }
694
695 pub(crate) fn blend(self, dst: Color, src: Color) -> Color {
696 let f = self.blend_fn();
697
698 let inv_dst_a = 1.0 - dst.a;
699 let inv_dst_a_src_a = inv_dst_a * src.a;
700 let inv_src_a = 1.0 - src.a;
701 let dst_a_src_a = dst.a * src.a;
702
703 let current_r = src.r.mul_add(inv_dst_a_src_a, f(Channel::Red, dst, src) * dst_a_src_a);
704 let current_g = src.g.mul_add(inv_dst_a_src_a, f(Channel::Green, dst, src) * dst_a_src_a);
705 let current_b = src.b.mul_add(inv_dst_a_src_a, f(Channel::Blue, dst, src) * dst_a_src_a);
706
707 Color {
708 r: dst.r.mul_add(inv_src_a, current_r),
709 g: dst.g.mul_add(inv_src_a, current_g),
710 b: dst.b.mul_add(inv_src_a, current_b),
711 a: dst.a.mul_add(inv_src_a, src.a),
712 }
713 }
714}
715
716macro_rules! blend_function {
717 (
718 $mode:expr,
719 $dst_r:expr,
720 $dst_g:expr,
721 $dst_b:expr,
722 $src_r:expr,
723 $src_g:expr,
724 $src_b:expr
725 $( , )?
726 ) => {{
727 macro_rules! lum {
728 ($r:expr, $g:expr, $b:expr) => {
729 $r.mul_add(
730 f32x8::splat(0.3),
731 $g.mul_add(f32x8::splat(0.59), $b * f32x8::splat(0.11)),
732 )
733 };
734 }
735
736 macro_rules! sat {
737 ($r:expr, $g:expr, $b:expr) => {
738 $r.max($g.max($b)) - $r.min($g.min($b))
739 };
740 }
741
742 macro_rules! clip_color {
743 ($r:expr, $g:expr, $b:expr) => {{
744 let l = lum!($r, $g, $b);
745 let n = $r.min($g.min($b));
746 let x = $r.max($g.max($b));
747 let l_1 = l - f32x8::splat(1.0);
748 let x_l_recip = (x - l).recip();
749 let l_n_recip_l = (l - n).recip() * l;
750
751 [
752 x_l_recip.mul_add(l.mul_add(l_1 - $r, $r), l).select(
753 l_n_recip_l.mul_add($r - l, l).select($r, n.lt(f32x8::splat(0.0))),
754 f32x8::splat(1.0).lt(x),
755 ),
756 x_l_recip.mul_add(l.mul_add(l_1 - $g, $g), l).select(
757 l_n_recip_l.mul_add($g - l, l).select($g, n.lt(f32x8::splat(0.0))),
758 f32x8::splat(1.0).lt(x),
759 ),
760 x_l_recip.mul_add(l.mul_add(l_1 - $b, $b), l).select(
761 l_n_recip_l.mul_add($b - l, l).select($b, n.lt(f32x8::splat(0.0))),
762 f32x8::splat(1.0).lt(x),
763 ),
764 ]
765 }};
766 }
767
768 macro_rules! set_lum {
769 ($r:expr, $g:expr, $b:expr, $l:expr) => {{
770 let d = $l - lum!($r, $g, $b);
771 $r += d;
772 $g += d;
773 $b += d;
774 clip_color!($r, $g, $b)
775 }};
776 }
777
778 macro_rules! set_sat {
779 ($sat_dst:expr, $s_r:expr, $s_g:expr, $s_b:expr) => {{
780 let src_min = $s_r.min($s_g.min($s_b));
781 let src_max = $s_r.max($s_g.max($s_b));
782 let src_mid = $s_r + $s_g + $s_b - src_min - src_max;
783 let min_lt_max = src_min.lt(src_max);
784 let sat_mid = ($sat_dst.mul_add(-src_min, $sat_dst * src_mid)
785 / (src_max - src_min))
786 .select(f32x8::splat(0.0), min_lt_max);
787 let sat_max = $sat_dst.select(f32x8::splat(0.0), min_lt_max);
788
789 [
790 sat_max.select(
791 f32x8::splat(0.0).select(sat_mid, $s_r.eq(src_min)),
792 $s_r.eq(src_max),
793 ),
794 sat_max.select(
795 f32x8::splat(0.0).select(sat_mid, $s_g.eq(src_min)),
796 $s_g.eq(src_max),
797 ),
798 sat_max.select(
799 f32x8::splat(0.0).select(sat_mid, $s_b.eq(src_min)),
800 $s_b.eq(src_max),
801 ),
802 ]
803 }};
804 }
805
806 match $mode {
807 BlendMode::Over => [$src_r, $src_g, $src_b],
808 BlendMode::Multiply => [$dst_r * $src_r, $dst_g * $src_g, $dst_b * $src_b],
809 BlendMode::Screen => [
810 $dst_r.mul_add(-$src_r, $dst_r) + $src_r,
811 $dst_g.mul_add(-$src_g, $dst_g) + $src_g,
812 $dst_b.mul_add(-$src_b, $dst_b) + $src_b,
813 ],
814 BlendMode::Overlay => [
815 ($dst_r * $src_r * f32x8::splat(2.0)).select(
816 f32x8::splat(2.0)
817 * ($dst_r + $src_r - $dst_r.mul_add($src_r, f32x8::splat(0.5))),
818 $dst_r.le(f32x8::splat(0.5)),
819 ),
820 ($dst_g * $src_g * f32x8::splat(2.0)).select(
821 f32x8::splat(2.0)
822 * ($dst_g + $src_g - $dst_g.mul_add($src_g, f32x8::splat(0.5))),
823 $dst_g.le(f32x8::splat(0.5)),
824 ),
825 ($dst_b * $src_b * f32x8::splat(2.0)).select(
826 f32x8::splat(2.0)
827 * ($dst_b + $src_b - $dst_b.mul_add($src_b, f32x8::splat(0.5))),
828 $dst_b.le(f32x8::splat(0.5)),
829 ),
830 ],
831 BlendMode::Darken => [$dst_r.min($src_r), $dst_g.min($src_g), $dst_b.min($src_b)],
832 BlendMode::Lighten => [$dst_r.max($src_r), $dst_g.max($src_g), $dst_b.max($src_b)],
833 BlendMode::ColorDodge => [
834 f32x8::splat(1.0).select(
835 f32x8::splat(1.0).min($dst_r / (f32x8::splat(1.0) - $src_r)),
836 $src_r.eq(f32x8::splat(1.0)),
837 ),
838 f32x8::splat(1.0).select(
839 f32x8::splat(1.0).min($dst_g / (f32x8::splat(1.0) - $src_g)),
840 $src_g.eq(f32x8::splat(1.0)),
841 ),
842 f32x8::splat(1.0).select(
843 f32x8::splat(1.0).min($dst_b / (f32x8::splat(1.0) - $src_b)),
844 $src_b.eq(f32x8::splat(1.0)),
845 ),
846 ],
847 BlendMode::ColorBurn => [
848 f32x8::splat(0.0).select(
849 f32x8::splat(1.0)
850 - f32x8::splat(1.0).min((f32x8::splat(1.0) - $dst_r) / $src_r),
851 $src_r.eq(f32x8::splat(0.0)),
852 ),
853 f32x8::splat(0.0).select(
854 f32x8::splat(1.0)
855 - f32x8::splat(1.0).min((f32x8::splat(1.0) - $dst_g) / $src_g),
856 $src_g.eq(f32x8::splat(0.0)),
857 ),
858 f32x8::splat(0.0).select(
859 f32x8::splat(1.0)
860 - f32x8::splat(1.0).min((f32x8::splat(1.0) - $dst_b) / $src_b),
861 $src_b.eq(f32x8::splat(0.0)),
862 ),
863 ],
864 BlendMode::HardLight => [
865 ($dst_r * $src_r * f32x8::splat(2.0)).select(
866 f32x8::splat(2.0)
867 * ($dst_r + $src_r - $dst_r.mul_add($src_r, f32x8::splat(0.5))),
868 $src_r.le(f32x8::splat(0.5)),
869 ),
870 ($dst_g * $src_g * f32x8::splat(2.0)).select(
871 f32x8::splat(2.0)
872 * ($dst_g + $src_g - $dst_g.mul_add($src_g, f32x8::splat(0.5))),
873 $src_g.le(f32x8::splat(0.5)),
874 ),
875 ($dst_b * $src_b * f32x8::splat(2.0)).select(
876 f32x8::splat(2.0)
877 * ($dst_b + $src_b - $dst_b.mul_add($src_b, f32x8::splat(0.5))),
878 $src_b.le(f32x8::splat(0.5)),
879 ),
880 ],
881 BlendMode::SoftLight => {
882 let d0 = (f32x8::splat(16.0)
883 .mul_add($dst_r, f32x8::splat(-12.0))
884 .mul_add($dst_r, f32x8::splat(4.0))
885 * $dst_r)
886 .select($dst_r.sqrt(), $dst_r.le(f32x8::splat(0.25)));
887 let d1 = (f32x8::splat(16.0)
888 .mul_add($dst_g, f32x8::splat(-12.0))
889 .mul_add($dst_g, f32x8::splat(4.0))
890 * $dst_g)
891 .select($dst_g.sqrt(), $dst_g.le(f32x8::splat(0.25)));
892 let d2 = (f32x8::splat(16.0)
893 .mul_add($dst_b, f32x8::splat(-12.0))
894 .mul_add($dst_b, f32x8::splat(4.0))
895 * $dst_b)
896 .select($dst_b.sqrt(), $dst_b.le(f32x8::splat(0.25)));
897
898 [
899 (($dst_r * (f32x8::splat(1.0) - $dst_r))
900 .mul_add(f32x8::splat(2.0).mul_add($src_r, f32x8::splat(-1.0)), $dst_r))
901 .select(
902 (d0 - $dst_r)
903 .mul_add(f32x8::splat(2.0).mul_add($src_r, f32x8::splat(-1.0)), $dst_r),
904 $src_r.le(f32x8::splat(0.5)),
905 ),
906 (($dst_g * (f32x8::splat(1.0) - $dst_g))
907 .mul_add(f32x8::splat(2.0).mul_add($src_g, f32x8::splat(-1.0)), $dst_g))
908 .select(
909 (d1 - $dst_g)
910 .mul_add(f32x8::splat(2.0).mul_add($src_g, f32x8::splat(-1.0)), $dst_g),
911 $src_g.le(f32x8::splat(0.5)),
912 ),
913 (($dst_b * (f32x8::splat(1.0) - $dst_b))
914 .mul_add(f32x8::splat(2.0).mul_add($src_b, f32x8::splat(-1.0)), $dst_b))
915 .select(
916 (d2 - $dst_b)
917 .mul_add(f32x8::splat(2.0).mul_add($src_b, f32x8::splat(-1.0)), $dst_b),
918 $src_b.le(f32x8::splat(0.5)),
919 ),
920 ]
921 }
922 BlendMode::Difference => {
923 [($dst_r - $src_r).abs(), ($dst_g - $src_g).abs(), ($dst_b - $src_b).abs()]
924 }
925 BlendMode::Exclusion => [
926 (f32x8::splat(-2.0) * $dst_r).mul_add($src_r, $dst_r) + $src_r,
927 (f32x8::splat(-2.0) * $dst_g).mul_add($src_g, $dst_g) + $src_g,
928 (f32x8::splat(-2.0) * $dst_b).mul_add($src_b, $dst_b) + $src_b,
929 ],
930 BlendMode::Hue => {
931 let mut src = set_sat!(sat!($dst_r, $dst_g, $dst_b), $src_r, $src_g, $src_b);
932 set_lum!(src[0], src[1], src[2], lum!($dst_r, $dst_g, $dst_b))
933 }
934 BlendMode::Saturation => {
935 let mut dst = set_sat!(sat!($src_r, $src_g, $src_b), $dst_r, $dst_g, $dst_b);
936 set_lum!(dst[0], dst[1], dst[2], lum!($dst_r, $dst_g, $dst_b))
937 }
938 BlendMode::Color => {
939 let mut src = [$src_r, $src_g, $src_b];
940 set_lum!(src[0], src[1], src[2], lum!($dst_r, $dst_g, $dst_b))
941 }
942 BlendMode::Luminosity => {
943 let mut dst = [$dst_r, $dst_g, $dst_b];
944 set_lum!(dst[0], dst[1], dst[2], lum!($src_r, $src_g, $src_b))
945 }
946 }
947 }};
948}
949
950impl Default for BlendMode {
951 fn default() -> Self {
952 Self::Over
953 }
954}
955
956#[derive(Clone, Debug, Default, Eq, Hash, PartialEq)]
957pub struct Style {
958 pub is_clipped: bool,
959 pub fill: Fill,
960 pub blend_mode: BlendMode,
961}
962
963#[cfg(test)]
964mod tests {
965 use std::collections::HashSet;
966
967 use super::*;
968
969 const EPSILON: f32 = 0.001;
970
971 macro_rules! color {
972 ( $val:expr ) => {
973 Color { r: $val, g: $val, b: $val, a: $val }
974 };
975 }
976
977 macro_rules! color_array {
978 ( $val:expr ) => {
979 [$val, $val, $val, $val]
980 };
981 }
982
983 macro_rules! color_eq {
984 ( $val:expr ) => {{
985 assert_eq!($val[0], $val[1]);
986 assert_eq!($val[1], $val[2]);
987 assert_eq!($val[2], $val[3]);
988
989 $val[0]
990 }};
991 }
992
993 macro_rules! simd_assert_approx {
994 ( $left:expr, $right:expr ) => {{
995 assert!(
996 ($left - $right).abs().le(f32x8::splat(EPSILON)).all(),
997 "{:?} != {:?}",
998 $left,
999 $right,
1000 );
1001 }};
1002 }
1003
1004 impl Ratio for [f32; 8] {
1005 fn zero() -> Self {
1006 [0.0; 8]
1007 }
1008
1009 fn one() -> Self {
1010 [1.0; 8]
1011 }
1012 }
1013
1014 fn colors(separate: &[f32x8; 4]) -> [[f32; 4]; 8] {
1015 let mut colors = [[0.0, 0.0, 0.0, 0.0]; 8];
1016
1017 for (i, color) in colors.iter_mut().enumerate() {
1018 *color = [
1019 separate[0].to_array()[i],
1020 separate[1].to_array()[i],
1021 separate[2].to_array()[i],
1022 separate[3].to_array()[i],
1023 ];
1024 }
1025
1026 colors
1027 }
1028
1029 fn test_blend_mode(blend_mode: BlendMode) {
1030 let color_values = [
1031 Color { r: 0.125, g: 0.25, b: 0.625, a: 0.5 },
1032 Color { r: 0.25, g: 0.125, b: 0.75, a: 0.5 },
1033 Color { r: 0.625, g: 0.5, b: 0.125, a: 0.5 },
1034 Color { r: 0.375, g: 1.0, b: 0.875, a: 0.5 },
1035 Color { r: 0.5, g: 0.5, b: 0.5, a: 0.5 },
1036 Color { r: 0.875, g: 0.125, b: 0.0, a: 0.5 },
1037 ];
1038 let f = blend_mode.blend_fn();
1039
1040 for &dst in &color_values {
1041 for &src in &color_values {
1042 let [r, g, b] = blend_function!(
1043 blend_mode,
1044 f32x8::splat(dst.r),
1045 f32x8::splat(dst.g),
1046 f32x8::splat(dst.b),
1047 f32x8::splat(src.r),
1048 f32x8::splat(src.g),
1049 f32x8::splat(src.b),
1050 );
1051
1052 simd_assert_approx!(r, f32x8::splat(f(Channel::Red, dst, src)));
1053 simd_assert_approx!(g, f32x8::splat(f(Channel::Green, dst, src)));
1054 simd_assert_approx!(b, f32x8::splat(f(Channel::Blue, dst, src)));
1055 }
1056 }
1057 }
1058
1059 #[test]
1060 fn linear_gradient() {
1061 let mut builder = GradientBuilder::new(Point::new(0.0, 7.0), Point::new(7.0, 0.0));
1062
1063 builder
1064 .color(color!(0.25))
1065 .color(color!(0.75))
1066 .color(color!(0.25))
1067 .color(color!(0.75))
1068 .color(color!(0.25));
1069
1070 let gradient = builder.build().unwrap();
1071
1072 let col = colors(&gradient.color_at(0.0, 0.0));
1073 assert_eq!(col[0], color_array!(0.25));
1074 assert!(color_eq!(col[1]) < color_eq!(col[2]));
1075 assert!(color_eq!(col[2]) < color_eq!(col[3]));
1076 assert!(color_eq!(col[4]) > color_eq!(col[5]));
1077 assert!(color_eq!(col[5]) > color_eq!(col[6]));
1078 assert_eq!(col[7], color_array!(0.25));
1079
1080 let col = colors(&gradient.color_at(3.0, 0.0));
1081 assert!(color_eq!(col[0]) < 0.75);
1082 assert!(color_eq!(col[1]) > color_eq!(col[2]));
1083 assert!(color_eq!(col[2]) > color_eq!(col[3]));
1084 assert_eq!(col[3], color_array!(0.25));
1085 assert!(color_eq!(col[3]) < color_eq!(col[4]));
1086 assert!(color_eq!(col[4]) < color_eq!(col[5]));
1087 assert!(color_eq!(col[5]) < color_eq!(col[6]));
1088 assert!(color_eq!(col[7]) < 0.75);
1089
1090 let col = colors(&gradient.color_at(7.0, 0.0));
1091 assert_eq!(col[0], color_array!(0.25));
1092 assert!(color_eq!(col[1]) < color_eq!(col[2]));
1093 assert!(color_eq!(col[2]) < color_eq!(col[3]));
1094 assert!(color_eq!(col[4]) > color_eq!(col[5]));
1095 assert!(color_eq!(col[5]) > color_eq!(col[6]));
1096 assert_eq!(col[7], color_array!(0.25));
1097 }
1098
1099 #[test]
1100 fn radial_gradient() {
1101 let mut builder = GradientBuilder::new(
1102 Point::new(0.0, 0.0),
1103 Point::new(7.0 * (1.0 / 2.0f32.sqrt()), 7.0 * (1.0 / 2.0f32.sqrt())),
1104 );
1105
1106 builder.r#type(GradientType::Radial).color(color!(0.25)).color(color!(0.75));
1107
1108 let gradient = builder.build().unwrap();
1109
1110 let col = colors(&gradient.color_at(0.0, 0.0));
1111 assert_eq!(col[0], color_array!(0.25));
1112 assert!(color_eq!(col[1]) < color_eq!(col[2]));
1113 assert!(color_eq!(col[2]) < color_eq!(col[3]));
1114 assert!(color_eq!(col[3]) < color_eq!(col[4]));
1115 assert!(color_eq!(col[4]) < color_eq!(col[5]));
1116 assert!(color_eq!(col[5]) < color_eq!(col[6]));
1117 assert_eq!(col[7], color_array!(0.75));
1118
1119 let col = colors(&gradient.color_at(3.0, 0.0));
1120 assert!(color_eq!(col[0]) < color_eq!(col[1]));
1121 assert!(color_eq!(col[1]) < color_eq!(col[2]));
1122 assert!(color_eq!(col[2]) < color_eq!(col[3]));
1123 assert!(color_eq!(col[3]) < color_eq!(col[4]));
1124 assert!(color_eq!(col[4]) < color_eq!(col[5]));
1125 assert!(color_eq!(col[5]) < color_eq!(col[6]));
1126 assert_eq!(col[7], color_array!(0.75));
1127
1128 let col = colors(&gradient.color_at(4.0, 0.0));
1129 assert!(color_eq!(col[0]) < color_eq!(col[1]));
1130 assert!(color_eq!(col[1]) < color_eq!(col[2]));
1131 assert!(color_eq!(col[2]) < color_eq!(col[3]));
1132 assert!(color_eq!(col[3]) < color_eq!(col[4]));
1133 assert!(color_eq!(col[4]) < color_eq!(col[5]));
1134 assert_eq!(col[6], color_array!(0.75));
1135 assert_eq!(col[7], color_array!(0.75));
1136
1137 let col = colors(&gradient.color_at(7.0, 0.0));
1138 assert_eq!(col[0], color_array!(0.75));
1139 assert_eq!(col[1], color_array!(0.75));
1140 assert_eq!(col[2], color_array!(0.75));
1141 assert_eq!(col[3], color_array!(0.75));
1142 assert_eq!(col[4], color_array!(0.75));
1143 assert_eq!(col[5], color_array!(0.75));
1144 assert_eq!(col[6], color_array!(0.75));
1145 assert_eq!(col[7], color_array!(0.75));
1146 }
1147
1148 #[test]
1149 fn test_blend_mode_over() {
1150 test_blend_mode(BlendMode::Over);
1151 }
1152
1153 #[test]
1154 fn test_blend_mode_multiply() {
1155 test_blend_mode(BlendMode::Multiply);
1156 }
1157
1158 #[test]
1159 fn test_blend_mode_screen() {
1160 test_blend_mode(BlendMode::Screen);
1161 }
1162
1163 #[test]
1164 fn test_blend_mode_overlay() {
1165 test_blend_mode(BlendMode::Overlay);
1166 }
1167
1168 #[test]
1169 fn test_blend_mode_darken() {
1170 test_blend_mode(BlendMode::Darken);
1171 }
1172
1173 #[test]
1174 fn test_blend_mode_lighten() {
1175 test_blend_mode(BlendMode::Lighten);
1176 }
1177
1178 #[test]
1179 fn test_blend_mode_color_dodge() {
1180 test_blend_mode(BlendMode::ColorDodge);
1181 }
1182
1183 #[test]
1184 fn test_blend_mode_color_burn() {
1185 test_blend_mode(BlendMode::ColorBurn);
1186 }
1187
1188 #[test]
1189 fn test_blend_mode_hard_light() {
1190 test_blend_mode(BlendMode::HardLight);
1191 }
1192
1193 #[test]
1194 fn test_blend_mode_soft_light() {
1195 test_blend_mode(BlendMode::SoftLight);
1196 }
1197
1198 #[test]
1199 fn test_blend_mode_difference() {
1200 test_blend_mode(BlendMode::Difference);
1201 }
1202
1203 #[test]
1204 fn test_blend_mode_exclusion() {
1205 test_blend_mode(BlendMode::Exclusion);
1206 }
1207
1208 #[test]
1209 fn test_blend_mode_hue() {
1210 test_blend_mode(BlendMode::Hue);
1211 }
1212
1213 #[test]
1214 fn test_blend_mode_saturation() {
1215 test_blend_mode(BlendMode::Saturation);
1216 }
1217
1218 #[test]
1219 fn test_blend_mode_color() {
1220 test_blend_mode(BlendMode::Color);
1221 }
1222
1223 #[test]
1224 fn test_blend_mode_luminosity() {
1225 test_blend_mode(BlendMode::Luminosity);
1226 }
1227
1228 #[test]
1229 fn channel_select() {
1230 let channels: [Channel; 4] = [Channel::Blue, Channel::Green, Channel::Red, Channel::Alpha];
1231 let red = [3.0; 8];
1232 let green = [2.0; 8];
1233 let blue = [1.0; 8];
1234 let alpha = [1.0; 8];
1235 let color = channels.map(|c| c.select(red, green, blue, alpha));
1236 assert_eq!(color, [blue, green, red, alpha]);
1237 }
1238
1239 #[test]
1240 fn channel_select_zero() {
1241 let red = [4.0; 8];
1242 let green = [3.0; 8];
1243 let blue = [2.0; 8];
1244 let alpha = [2.0; 8];
1245 let color = BGR0.map(|c| c.select(red, green, blue, alpha));
1246 assert_eq!(color, [blue, green, red, [0.0; 8]]);
1247 }
1248
1249 #[test]
1250 fn channel_select_one() {
1251 let red = [4.0; 8];
1252 let green = [3.0; 8];
1253 let blue = [2.0; 8];
1254 let alpha = [2.0; 8];
1255 let color = BGR1.map(|c| c.select(red, green, blue, alpha));
1256 assert_eq!(color, [blue, green, red, [1.0; 8]]);
1257 }
1258
1259 #[test]
1260 fn channel_select_from_color() {
1261 let channels: [Channel; 4] = [Channel::Blue, Channel::Green, Channel::Red, Channel::Alpha];
1262 let color = Color { r: 3.0, g: 2.0, b: 1.0, a: 1.0 };
1263 let color = channels.map(|c| color.channel(c));
1264 assert_eq!(color, [1.0, 2.0, 3.0, 1.0]);
1265 }
1266
1267 #[test]
1268 fn channel_select_from_color_zero() {
1269 let color = Color { r: 4.0, g: 3.0, b: 2.0, a: 2.0 };
1270 let color = BGR0.map(|c| color.channel(c));
1271 assert_eq!(color, [2.0, 3.0, 4.0, 0.0]);
1272 }
1273
1274 #[test]
1275 fn channel_select_from_color_one() {
1276 let color = Color { r: 4.0, g: 3.0, b: 2.0, a: 2.0 };
1277 let color = BGR1.map(|c| color.channel(c));
1278 assert_eq!(color, [2.0, 3.0, 4.0, 1.0]);
1279 }
1280
1281 #[test]
1282 fn color_sorted() {
1283 let permutations = [
1284 (1.0, 2.0, 3.0),
1285 (1.0, 3.0, 2.0),
1286 (2.0, 1.0, 3.0),
1287 (2.0, 3.0, 1.0),
1288 (3.0, 1.0, 2.0),
1289 (3.0, 2.0, 1.0),
1290 ];
1291
1292 for (r, g, b) in permutations {
1293 let mut color = Color { r, g, b, a: 1.0 };
1294 let sorted = color.sorted();
1295 assert_eq!(sorted.map(|c| *c), [1.0, 2.0, 3.0]);
1296 }
1297 }
1298
1299 #[test]
1300 fn color_min() {
1301 let color = Color { r: 3.0, g: 2.0, b: 1.0, a: 1.0 };
1302 let min = color.min();
1303 assert_eq!(min, 1.0);
1304 }
1305
1306 #[test]
1307 fn color_max() {
1308 let color = Color { r: 3.0, g: 2.0, b: 1.0, a: 1.0 };
1309 let max = color.max();
1310 assert_eq!(max, 3.0);
1311 }
1312
1313 const C00: Color = Color { r: 0.0000, g: 0.03125, b: 0.0625, a: 0.09375 };
1314 const C01: Color = Color { r: 0.1250, g: 0.15625, b: 0.1875, a: 0.21875 };
1315 const C10: Color = Color { r: 0.2500, g: 0.28125, b: 0.3125, a: 0.34375 };
1316 const C11: Color = Color { r: 0.3750, g: 0.40625, b: 0.4375, a: 0.46875 };
1317 const C20: Color = Color { r: 0.5000, g: 0.53125, b: 0.5625, a: 0.59375 };
1318 const C21: Color = Color { r: 0.6250, g: 0.65625, b: 0.6875, a: 0.71875 };
1319
1320 fn apply_texture_color_at(transform: AffineTransform) -> Vec<[f32; 8]> {
1321 let data: Vec<_> = [C00, C01, C10, C11, C20, C21].iter().map(|c| c.to_array()).collect();
1322 let image = Arc::new(Image::from_linear_rgba(&data[..], 2, 3).unwrap());
1323 let texture = Texture { transform, image };
1324 texture.color_at(-2.0, -2.0).iter().map(|v| v.to_array()).collect()
1325 }
1326
1327 fn transpose(colors: [Color; 8]) -> Vec<[f32; 8]> {
1328 (0..4)
1329 .map(|i| {
1330 colors.iter().map(|c| c.to_array()[i]).collect::<Vec<f32>>().try_into().unwrap()
1331 })
1332 .collect()
1333 }
1334
1335 #[test]
1336 fn texture_color_at_with_identity() {
1337 assert_eq!(
1338 apply_texture_color_at(AffineTransform::default()),
1339 transpose([C00, C00, C00, C10, C20, C20, C20, C20])
1340 );
1341 }
1342
1343 #[test]
1344 fn texture_color_at_with_scale_x2() {
1345 assert_eq!(
1346 apply_texture_color_at(AffineTransform {
1347 ux: 0.5,
1348 uy: 0.0,
1349 vy: 0.5,
1350 vx: 0.0,
1351 tx: 0.0,
1352 ty: 0.0
1353 }),
1354 transpose([C00, C00, C00, C00, C10, C10, C20, C20])
1355 );
1356 }
1357
1358 #[test]
1359 fn texture_color_at_with_translation() {
1360 assert_eq!(
1361 apply_texture_color_at(AffineTransform {
1362 ux: 1.0,
1363 uy: 0.0,
1364 vx: 0.0,
1365 vy: 1.0,
1366 tx: 1.0,
1367 ty: 1.0
1368 }),
1369 transpose([C00, C00, C10, C20, C20, C20, C20, C20])
1370 );
1371 }
1372
1373 #[test]
1374 fn texture_color_at_with_axis_inverted() {
1375 assert_eq!(
1376 apply_texture_color_at(AffineTransform {
1377 ux: 0.0,
1378 uy: 1.0,
1379 vx: 1.0,
1380 vy: 0.0,
1381 tx: 0.0,
1382 ty: 0.0
1383 }),
1384 transpose([C00, C00, C00, C01, C01, C01, C01, C01])
1385 );
1386 }
1387
1388 #[test]
1389 fn f16_error() {
1390 let alpha_mse = (0u8..=255u8)
1392 .map(|u| f32::from(u) / 255.0)
1393 .map(|v| (v - f16::from(v).to_f32()))
1394 .map(|d| d * d)
1395 .sum::<f32>()
1396 / 256.0;
1397 assert!(alpha_mse < 5e-8, "alpha_mse: {}", alpha_mse);
1398
1399 let alpha_distinct =
1401 (0u8..=255u8).map(|a| f16::from(f32::from(a) / 255.0)).collect::<HashSet<f16>>().len();
1402 assert_eq!(alpha_distinct, 256);
1403
1404 let component_mse = (0u8..=255u8)
1406 .map(to_linear)
1407 .map(|v| (v - f16::from(v).to_f32()))
1408 .map(|d| d * d)
1409 .sum::<f32>()
1410 / 256.0;
1411 assert!(component_mse < 3e-8, "component_mse: {}", component_mse);
1412
1413 let component_distinct =
1415 (0u8..=255u8).map(|c| f16::from(to_linear(c))).collect::<HashSet<f16>>().len();
1416 assert_eq!(component_distinct, 256);
1417
1418 assert_eq!(f16::from(0.0).to_f32(), 0.0);
1420 assert_eq!(f16::from(1.0).to_f32(), 1.0);
1421 }
1422
1423 #[test]
1424 fn f16_conversion() {
1425 for i in 0..255 {
1426 let value = (i as f32) / 255.0;
1427 let value_f16 = f16::from(value);
1428 assert!(half::f16::from_f32(value).to_bits().abs_diff(value_f16.0) <= 1);
1429 assert_eq!(half::f16::from_bits(value_f16.0).to_f32(), value_f16.to_f32());
1430 }
1431 }
1432}