1use crate::{Brush, Color, ColorFilter, ImageBitmap, ImageSampling};
4use std::ops::AddAssign;
5
6#[derive(Clone, Copy, Debug, PartialEq, Default)]
7pub struct Point {
8 pub x: f32,
9 pub y: f32,
10}
11
12impl Point {
13 pub const fn new(x: f32, y: f32) -> Self {
14 Self { x, y }
15 }
16
17 pub const ZERO: Point = Point { x: 0.0, y: 0.0 };
18}
19
20#[derive(Clone, Copy, Debug, PartialEq, Default)]
21pub struct Size {
22 pub width: f32,
23 pub height: f32,
24}
25
26impl Size {
27 pub const fn new(width: f32, height: f32) -> Self {
28 Self { width, height }
29 }
30
31 pub const ZERO: Size = Size {
32 width: 0.0,
33 height: 0.0,
34 };
35}
36
37#[derive(Clone, Copy, Debug, PartialEq)]
38pub struct Rect {
39 pub x: f32,
40 pub y: f32,
41 pub width: f32,
42 pub height: f32,
43}
44
45impl Rect {
46 pub fn from_origin_size(origin: Point, size: Size) -> Self {
47 Self {
48 x: origin.x,
49 y: origin.y,
50 width: size.width,
51 height: size.height,
52 }
53 }
54
55 pub fn from_size(size: Size) -> Self {
56 Self {
57 x: 0.0,
58 y: 0.0,
59 width: size.width,
60 height: size.height,
61 }
62 }
63
64 pub fn translate(&self, dx: f32, dy: f32) -> Self {
65 Self {
66 x: self.x + dx,
67 y: self.y + dy,
68 width: self.width,
69 height: self.height,
70 }
71 }
72
73 pub fn contains(&self, x: f32, y: f32) -> bool {
74 x >= self.x && y >= self.y && x <= self.x + self.width && y <= self.y + self.height
75 }
76
77 pub fn intersect(&self, other: Rect) -> Option<Rect> {
79 let left = self.x.max(other.x);
80 let top = self.y.max(other.y);
81 let right = (self.x + self.width).min(other.x + other.width);
82 let bottom = (self.y + self.height).min(other.y + other.height);
83 let width = right - left;
84 let height = bottom - top;
85 if width <= 0.0 || height <= 0.0 {
86 None
87 } else {
88 Some(Rect {
89 x: left,
90 y: top,
91 width,
92 height,
93 })
94 }
95 }
96
97 pub fn union(&self, other: Rect) -> Rect {
98 let left = self.x.min(other.x);
99 let top = self.y.min(other.y);
100 let right = (self.x + self.width).max(other.x + other.width);
101 let bottom = (self.y + self.height).max(other.y + other.height);
102 Rect {
103 x: left,
104 y: top,
105 width: (right - left).max(0.0),
106 height: (bottom - top).max(0.0),
107 }
108 }
109}
110
111#[derive(Clone, Copy, Debug, Default, PartialEq)]
113pub struct EdgeInsets {
114 pub left: f32,
115 pub top: f32,
116 pub right: f32,
117 pub bottom: f32,
118}
119
120impl EdgeInsets {
121 pub fn uniform(all: f32) -> Self {
122 Self {
123 left: all,
124 top: all,
125 right: all,
126 bottom: all,
127 }
128 }
129
130 pub fn horizontal(horizontal: f32) -> Self {
131 Self {
132 left: horizontal,
133 right: horizontal,
134 ..Self::default()
135 }
136 }
137
138 pub fn vertical(vertical: f32) -> Self {
139 Self {
140 top: vertical,
141 bottom: vertical,
142 ..Self::default()
143 }
144 }
145
146 pub fn symmetric(horizontal: f32, vertical: f32) -> Self {
147 Self {
148 left: horizontal,
149 right: horizontal,
150 top: vertical,
151 bottom: vertical,
152 }
153 }
154
155 pub fn from_components(left: f32, top: f32, right: f32, bottom: f32) -> Self {
156 Self {
157 left,
158 top,
159 right,
160 bottom,
161 }
162 }
163
164 pub fn is_zero(&self) -> bool {
165 self.left == 0.0 && self.top == 0.0 && self.right == 0.0 && self.bottom == 0.0
166 }
167
168 pub fn horizontal_sum(&self) -> f32 {
169 self.left + self.right
170 }
171
172 pub fn vertical_sum(&self) -> f32 {
173 self.top + self.bottom
174 }
175}
176
177impl AddAssign for EdgeInsets {
178 fn add_assign(&mut self, rhs: Self) {
179 self.left += rhs.left;
180 self.top += rhs.top;
181 self.right += rhs.right;
182 self.bottom += rhs.bottom;
183 }
184}
185
186#[derive(Clone, Copy, Debug, Default, PartialEq)]
187pub struct CornerRadii {
188 pub top_left: f32,
189 pub top_right: f32,
190 pub bottom_right: f32,
191 pub bottom_left: f32,
192}
193
194impl CornerRadii {
195 pub fn uniform(radius: f32) -> Self {
196 Self {
197 top_left: radius,
198 top_right: radius,
199 bottom_right: radius,
200 bottom_left: radius,
201 }
202 }
203}
204
205#[derive(Clone, Copy, Debug, PartialEq)]
206pub struct RoundedCornerShape {
207 radii: CornerRadii,
208}
209
210impl RoundedCornerShape {
211 pub fn new(top_left: f32, top_right: f32, bottom_right: f32, bottom_left: f32) -> Self {
212 Self {
213 radii: CornerRadii {
214 top_left,
215 top_right,
216 bottom_right,
217 bottom_left,
218 },
219 }
220 }
221
222 pub fn uniform(radius: f32) -> Self {
223 Self {
224 radii: CornerRadii::uniform(radius),
225 }
226 }
227
228 pub fn with_radii(radii: CornerRadii) -> Self {
229 Self { radii }
230 }
231
232 pub fn resolve(&self, width: f32, height: f32) -> CornerRadii {
233 let mut resolved = self.radii;
234 let max_width = (width / 2.0).max(0.0);
235 let max_height = (height / 2.0).max(0.0);
236 resolved.top_left = resolved.top_left.clamp(0.0, max_width).min(max_height);
237 resolved.top_right = resolved.top_right.clamp(0.0, max_width).min(max_height);
238 resolved.bottom_right = resolved.bottom_right.clamp(0.0, max_width).min(max_height);
239 resolved.bottom_left = resolved.bottom_left.clamp(0.0, max_width).min(max_height);
240 resolved
241 }
242
243 pub fn radii(&self) -> CornerRadii {
244 self.radii
245 }
246}
247
248#[derive(Clone, Copy, Debug, PartialEq)]
249pub struct TransformOrigin {
250 pub pivot_fraction_x: f32,
251 pub pivot_fraction_y: f32,
252}
253
254impl TransformOrigin {
255 pub const fn new(pivot_fraction_x: f32, pivot_fraction_y: f32) -> Self {
256 Self {
257 pivot_fraction_x,
258 pivot_fraction_y,
259 }
260 }
261
262 pub const CENTER: TransformOrigin = TransformOrigin::new(0.5, 0.5);
263}
264
265impl Default for TransformOrigin {
266 fn default() -> Self {
267 Self::CENTER
268 }
269}
270
271#[derive(Clone, Copy, Debug, Default, PartialEq)]
272pub enum LayerShape {
273 #[default]
274 Rectangle,
275 Rounded(RoundedCornerShape),
276}
277
278#[derive(Clone, Debug, PartialEq)]
279pub struct GraphicsLayer {
280 pub alpha: f32,
281 pub scale: f32,
282 pub scale_x: f32,
283 pub scale_y: f32,
284 pub rotation_x: f32,
285 pub rotation_y: f32,
286 pub rotation_z: f32,
287 pub camera_distance: f32,
288 pub transform_origin: TransformOrigin,
289 pub translation_x: f32,
290 pub translation_y: f32,
291 pub shadow_elevation: f32,
292 pub ambient_shadow_color: Color,
293 pub spot_shadow_color: Color,
294 pub shape: LayerShape,
295 pub clip: bool,
296 pub compositing_strategy: CompositingStrategy,
297 pub blend_mode: BlendMode,
298 pub color_filter: Option<ColorFilter>,
299 pub render_effect: Option<crate::render_effect::RenderEffect>,
300 pub backdrop_effect: Option<crate::render_effect::RenderEffect>,
301}
302
303impl Default for GraphicsLayer {
304 fn default() -> Self {
305 Self {
306 alpha: 1.0,
307 scale: 1.0,
308 scale_x: 1.0,
309 scale_y: 1.0,
310 rotation_x: 0.0,
311 rotation_y: 0.0,
312 rotation_z: 0.0,
313 camera_distance: 8.0,
314 transform_origin: TransformOrigin::CENTER,
315 translation_x: 0.0,
316 translation_y: 0.0,
317 shadow_elevation: 0.0,
318 ambient_shadow_color: Color::BLACK,
319 spot_shadow_color: Color::BLACK,
320 shape: LayerShape::Rectangle,
321 clip: false,
322 compositing_strategy: CompositingStrategy::Auto,
323 blend_mode: BlendMode::SrcOver,
324 color_filter: None,
325 render_effect: None,
326 backdrop_effect: None,
327 }
328 }
329}
330
331#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
336pub enum BlendMode {
337 Clear,
338 Src,
339 Dst,
340 #[default]
341 SrcOver,
342 DstOver,
343 SrcIn,
344 DstIn,
345 SrcOut,
346 DstOut,
347 SrcAtop,
348 DstAtop,
349 Xor,
350 Plus,
351 Modulate,
352 Screen,
353 Overlay,
354 Darken,
355 Lighten,
356 ColorDodge,
357 ColorBurn,
358 HardLight,
359 SoftLight,
360 Difference,
361 Exclusion,
362 Multiply,
363 Hue,
364 Saturation,
365 Color,
366 Luminosity,
367}
368
369#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
371pub enum CompositingStrategy {
372 #[default]
374 Auto,
375 Offscreen,
377 ModulateAlpha,
379}
380
381#[derive(Clone, Debug, PartialEq)]
382pub enum DrawPrimitive {
383 Content,
386 Blend {
388 primitive: Box<DrawPrimitive>,
389 blend_mode: BlendMode,
390 },
391 Rect {
392 rect: Rect,
393 brush: Brush,
394 },
395 RoundRect {
396 rect: Rect,
397 brush: Brush,
398 radii: CornerRadii,
399 },
400 Image {
401 rect: Rect,
402 image: ImageBitmap,
403 alpha: f32,
404 color_filter: Option<ColorFilter>,
405 sampling: ImageSampling,
406 src_rect: Option<Rect>,
410 },
411 Shadow(ShadowPrimitive),
414}
415
416#[derive(Clone, Debug, PartialEq)]
418pub enum ShadowPrimitive {
419 Drop {
421 shape: Box<DrawPrimitive>,
422 blur_radius: f32,
423 blend_mode: BlendMode,
424 },
425 Inner {
427 fill: Box<DrawPrimitive>,
428 cutout: Box<DrawPrimitive>,
429 blur_radius: f32,
430 blend_mode: BlendMode,
431 clip_rect: Rect,
433 },
434}
435
436pub trait DrawScope {
437 fn size(&self) -> Size;
438 fn draw_content(&mut self);
439 fn draw_rect(&mut self, brush: Brush);
440 fn draw_rect_blend(&mut self, brush: Brush, blend_mode: BlendMode);
441 fn draw_rect_at(&mut self, rect: Rect, brush: Brush);
443 fn draw_rect_at_blend(&mut self, rect: Rect, brush: Brush, blend_mode: BlendMode);
444 fn draw_round_rect(&mut self, brush: Brush, radii: CornerRadii);
445 fn draw_round_rect_blend(&mut self, brush: Brush, radii: CornerRadii, blend_mode: BlendMode);
446 fn draw_image(&mut self, image: ImageBitmap);
447 fn draw_image_blend(&mut self, image: ImageBitmap, blend_mode: BlendMode);
448 fn draw_image_at(
449 &mut self,
450 rect: Rect,
451 image: ImageBitmap,
452 alpha: f32,
453 color_filter: Option<ColorFilter>,
454 );
455 fn draw_image_at_sampled(
456 &mut self,
457 rect: Rect,
458 image: ImageBitmap,
459 alpha: f32,
460 color_filter: Option<ColorFilter>,
461 sampling: ImageSampling,
462 );
463 fn draw_image_at_blend(
464 &mut self,
465 rect: Rect,
466 image: ImageBitmap,
467 alpha: f32,
468 color_filter: Option<ColorFilter>,
469 blend_mode: BlendMode,
470 );
471 fn draw_image_src(
474 &mut self,
475 image: ImageBitmap,
476 src_rect: Rect,
477 dst_rect: Rect,
478 alpha: f32,
479 color_filter: Option<ColorFilter>,
480 );
481 fn draw_image_src_sampled(
482 &mut self,
483 image: ImageBitmap,
484 src_rect: Rect,
485 dst_rect: Rect,
486 alpha: f32,
487 color_filter: Option<ColorFilter>,
488 sampling: ImageSampling,
489 );
490 fn draw_image_src_blend(
491 &mut self,
492 image: ImageBitmap,
493 src_rect: Rect,
494 dst_rect: Rect,
495 alpha: f32,
496 color_filter: Option<ColorFilter>,
497 blend_mode: BlendMode,
498 );
499 fn into_primitives(self) -> Vec<DrawPrimitive>;
500}
501
502#[derive(Default)]
503pub struct DrawScopeDefault {
504 size: Size,
505 primitives: Vec<DrawPrimitive>,
506}
507
508impl DrawScopeDefault {
509 pub fn new(size: Size) -> Self {
510 Self {
511 size,
512 primitives: Vec::new(),
513 }
514 }
515
516 fn push_blended_primitive(&mut self, primitive: DrawPrimitive, blend_mode: BlendMode) {
517 if blend_mode == BlendMode::SrcOver {
518 self.primitives.push(primitive);
519 } else {
520 self.primitives.push(DrawPrimitive::Blend {
521 primitive: Box::new(primitive),
522 blend_mode,
523 });
524 }
525 }
526}
527
528impl DrawScope for DrawScopeDefault {
529 fn size(&self) -> Size {
530 self.size
531 }
532
533 fn draw_content(&mut self) {
534 self.primitives.push(DrawPrimitive::Content);
535 }
536
537 fn draw_rect(&mut self, brush: Brush) {
538 self.draw_rect_blend(brush, BlendMode::SrcOver);
539 }
540
541 fn draw_rect_blend(&mut self, brush: Brush, blend_mode: BlendMode) {
542 self.push_blended_primitive(
543 DrawPrimitive::Rect {
544 rect: Rect::from_size(self.size),
545 brush,
546 },
547 blend_mode,
548 );
549 }
550
551 fn draw_rect_at(&mut self, rect: Rect, brush: Brush) {
552 self.draw_rect_at_blend(rect, brush, BlendMode::SrcOver);
553 }
554
555 fn draw_rect_at_blend(&mut self, rect: Rect, brush: Brush, blend_mode: BlendMode) {
556 self.push_blended_primitive(DrawPrimitive::Rect { rect, brush }, blend_mode);
557 }
558
559 fn draw_round_rect(&mut self, brush: Brush, radii: CornerRadii) {
560 self.draw_round_rect_blend(brush, radii, BlendMode::SrcOver);
561 }
562
563 fn draw_round_rect_blend(&mut self, brush: Brush, radii: CornerRadii, blend_mode: BlendMode) {
564 self.push_blended_primitive(
565 DrawPrimitive::RoundRect {
566 rect: Rect::from_size(self.size),
567 brush,
568 radii,
569 },
570 blend_mode,
571 );
572 }
573
574 fn draw_image(&mut self, image: ImageBitmap) {
575 self.draw_image_blend(image, BlendMode::SrcOver);
576 }
577
578 fn draw_image_blend(&mut self, image: ImageBitmap, blend_mode: BlendMode) {
579 self.push_blended_primitive(
580 DrawPrimitive::Image {
581 rect: Rect::from_size(self.size),
582 image,
583 alpha: 1.0,
584 color_filter: None,
585 sampling: ImageSampling::Nearest,
586 src_rect: None,
587 },
588 blend_mode,
589 );
590 }
591
592 fn draw_image_at(
593 &mut self,
594 rect: Rect,
595 image: ImageBitmap,
596 alpha: f32,
597 color_filter: Option<ColorFilter>,
598 ) {
599 self.draw_image_at_sampled(rect, image, alpha, color_filter, ImageSampling::Nearest);
600 }
601
602 fn draw_image_at_sampled(
603 &mut self,
604 rect: Rect,
605 image: ImageBitmap,
606 alpha: f32,
607 color_filter: Option<ColorFilter>,
608 sampling: ImageSampling,
609 ) {
610 self.push_blended_primitive(
611 DrawPrimitive::Image {
612 rect,
613 image,
614 alpha: alpha.clamp(0.0, 1.0),
615 color_filter,
616 sampling,
617 src_rect: None,
618 },
619 BlendMode::SrcOver,
620 );
621 }
622
623 fn draw_image_at_blend(
624 &mut self,
625 rect: Rect,
626 image: ImageBitmap,
627 alpha: f32,
628 color_filter: Option<ColorFilter>,
629 blend_mode: BlendMode,
630 ) {
631 self.push_blended_primitive(
632 DrawPrimitive::Image {
633 rect,
634 image,
635 alpha: alpha.clamp(0.0, 1.0),
636 color_filter,
637 sampling: ImageSampling::Nearest,
638 src_rect: None,
639 },
640 blend_mode,
641 );
642 }
643
644 fn draw_image_src(
645 &mut self,
646 image: ImageBitmap,
647 src_rect: Rect,
648 dst_rect: Rect,
649 alpha: f32,
650 color_filter: Option<ColorFilter>,
651 ) {
652 self.draw_image_src_blend(
653 image,
654 src_rect,
655 dst_rect,
656 alpha,
657 color_filter,
658 BlendMode::SrcOver,
659 );
660 }
661
662 fn draw_image_src_sampled(
663 &mut self,
664 image: ImageBitmap,
665 src_rect: Rect,
666 dst_rect: Rect,
667 alpha: f32,
668 color_filter: Option<ColorFilter>,
669 sampling: ImageSampling,
670 ) {
671 self.push_blended_primitive(
672 DrawPrimitive::Image {
673 rect: dst_rect,
674 image,
675 alpha: alpha.clamp(0.0, 1.0),
676 color_filter,
677 sampling,
678 src_rect: Some(src_rect),
679 },
680 BlendMode::SrcOver,
681 );
682 }
683
684 fn draw_image_src_blend(
685 &mut self,
686 image: ImageBitmap,
687 src_rect: Rect,
688 dst_rect: Rect,
689 alpha: f32,
690 color_filter: Option<ColorFilter>,
691 blend_mode: BlendMode,
692 ) {
693 self.push_blended_primitive(
694 DrawPrimitive::Image {
695 rect: dst_rect,
696 image,
697 alpha: alpha.clamp(0.0, 1.0),
698 color_filter,
699 sampling: ImageSampling::Nearest,
700 src_rect: Some(src_rect),
701 },
702 blend_mode,
703 );
704 }
705
706 fn into_primitives(self) -> Vec<DrawPrimitive> {
707 self.primitives
708 }
709}
710
711#[cfg(test)]
712mod tests {
713 use super::*;
714 use crate::{Color, ImageBitmap, RenderEffect};
715
716 fn assert_image_alpha(primitive: &DrawPrimitive, expected: f32) {
717 match primitive {
718 DrawPrimitive::Image { alpha, .. } => assert!((alpha - expected).abs() < 1e-5),
719 DrawPrimitive::Blend { primitive, .. } => assert_image_alpha(primitive, expected),
720 other => panic!("expected image primitive, got {other:?}"),
721 }
722 }
723
724 fn unwrap_image(primitive: &DrawPrimitive) -> &DrawPrimitive {
725 match primitive {
726 DrawPrimitive::Image { .. } => primitive,
727 DrawPrimitive::Blend { primitive, .. } => unwrap_image(primitive),
728 other => panic!("expected image primitive, got {other:?}"),
729 }
730 }
731
732 #[test]
733 fn draw_content_inserts_content_marker() {
734 let mut scope = DrawScopeDefault::new(Size::new(8.0, 8.0));
735 scope.draw_rect(Brush::solid(Color::WHITE));
736 scope.draw_content();
737 scope.draw_rect_blend(Brush::solid(Color::BLACK), BlendMode::DstOut);
738
739 let primitives = scope.into_primitives();
740 assert!(matches!(primitives[1], DrawPrimitive::Content));
741 assert!(matches!(
742 primitives[2],
743 DrawPrimitive::Blend {
744 blend_mode: BlendMode::DstOut,
745 ..
746 }
747 ));
748 }
749
750 #[test]
751 fn draw_rect_blend_wraps_non_default_modes() {
752 let mut scope = DrawScopeDefault::new(Size::new(10.0, 10.0));
753 scope.draw_rect_blend(Brush::solid(Color::RED), BlendMode::DstOut);
754
755 let primitives = scope.into_primitives();
756 assert_eq!(primitives.len(), 1);
757 match &primitives[0] {
758 DrawPrimitive::Blend {
759 primitive,
760 blend_mode,
761 } => {
762 assert_eq!(*blend_mode, BlendMode::DstOut);
763 assert!(matches!(**primitive, DrawPrimitive::Rect { .. }));
764 }
765 other => panic!("expected blended primitive, got {other:?}"),
766 }
767 }
768
769 #[test]
770 fn rect_union_encloses_both_inputs() {
771 let lhs = Rect {
772 x: 10.0,
773 y: 5.0,
774 width: 8.0,
775 height: 4.0,
776 };
777 let rhs = Rect {
778 x: 4.0,
779 y: 7.0,
780 width: 10.0,
781 height: 6.0,
782 };
783
784 assert_eq!(
785 lhs.union(rhs),
786 Rect {
787 x: 4.0,
788 y: 5.0,
789 width: 14.0,
790 height: 8.0,
791 }
792 );
793 }
794
795 #[test]
796 fn draw_image_uses_scope_size_as_default_rect() {
797 let mut scope = DrawScopeDefault::new(Size::new(40.0, 24.0));
798 let image = ImageBitmap::from_rgba8(2, 2, vec![255; 16]).expect("image");
799 scope.draw_image(image.clone());
800 let primitives = scope.into_primitives();
801 assert_eq!(primitives.len(), 1);
802 match unwrap_image(&primitives[0]) {
803 DrawPrimitive::Image {
804 rect,
805 image: actual,
806 alpha,
807 color_filter,
808 sampling,
809 src_rect,
810 } => {
811 assert_eq!(*rect, Rect::from_size(Size::new(40.0, 24.0)));
812 assert_eq!(*actual, image);
813 assert_eq!(*alpha, 1.0);
814 assert!(color_filter.is_none());
815 assert_eq!(*sampling, ImageSampling::Nearest);
816 assert!(src_rect.is_none());
817 }
818 other => panic!("expected image primitive, got {other:?}"),
819 }
820 }
821
822 #[test]
823 fn draw_image_src_stores_src_rect() {
824 let mut scope = DrawScopeDefault::new(Size::new(100.0, 100.0));
825 let image = ImageBitmap::from_rgba8(64, 64, vec![255; 64 * 64 * 4]).expect("image");
826 let src = Rect {
827 x: 10.0,
828 y: 20.0,
829 width: 30.0,
830 height: 40.0,
831 };
832 let dst = Rect {
833 x: 0.0,
834 y: 0.0,
835 width: 60.0,
836 height: 80.0,
837 };
838 scope.draw_image_src(image.clone(), src, dst, 0.8, None);
839 let primitives = scope.into_primitives();
840 assert_eq!(primitives.len(), 1);
841 match unwrap_image(&primitives[0]) {
842 DrawPrimitive::Image {
843 rect,
844 image: actual,
845 alpha,
846 sampling,
847 src_rect,
848 ..
849 } => {
850 assert_eq!(*rect, dst);
851 assert_eq!(*actual, image);
852 assert!((alpha - 0.8).abs() < 1e-5);
853 assert_eq!(*sampling, ImageSampling::Nearest);
854 assert_eq!(*src_rect, Some(src));
855 }
856 other => panic!("expected image primitive, got {other:?}"),
857 }
858 }
859
860 #[test]
861 fn draw_image_at_sampled_records_requested_sampling() {
862 let mut scope = DrawScopeDefault::new(Size::new(100.0, 100.0));
863 let image = ImageBitmap::from_rgba8(8, 8, vec![255; 8 * 8 * 4]).expect("image");
864 let dst = Rect {
865 x: 2.0,
866 y: 3.0,
867 width: 40.0,
868 height: 30.0,
869 };
870
871 scope.draw_image_at_sampled(dst, image.clone(), 0.7, None, ImageSampling::Linear);
872
873 let primitives = scope.into_primitives();
874 assert_eq!(primitives.len(), 1);
875 match unwrap_image(&primitives[0]) {
876 DrawPrimitive::Image {
877 rect,
878 image: actual,
879 alpha,
880 sampling,
881 src_rect,
882 ..
883 } => {
884 assert_eq!(*rect, dst);
885 assert_eq!(*actual, image);
886 assert!((alpha - 0.7).abs() < 1e-5);
887 assert_eq!(*sampling, ImageSampling::Linear);
888 assert!(src_rect.is_none());
889 }
890 other => panic!("expected image primitive, got {other:?}"),
891 }
892 }
893
894 #[test]
895 fn draw_image_src_sampled_records_requested_sampling() {
896 let mut scope = DrawScopeDefault::new(Size::new(100.0, 100.0));
897 let image = ImageBitmap::from_rgba8(64, 64, vec![255; 64 * 64 * 4]).expect("image");
898 let src = Rect {
899 x: 4.0,
900 y: 6.0,
901 width: 16.0,
902 height: 20.0,
903 };
904 let dst = Rect {
905 x: 8.0,
906 y: 10.0,
907 width: 32.0,
908 height: 40.0,
909 };
910
911 scope.draw_image_src_sampled(image.clone(), src, dst, 0.5, None, ImageSampling::Linear);
912
913 let primitives = scope.into_primitives();
914 assert_eq!(primitives.len(), 1);
915 match unwrap_image(&primitives[0]) {
916 DrawPrimitive::Image {
917 rect,
918 image: actual,
919 alpha,
920 sampling,
921 src_rect,
922 ..
923 } => {
924 assert_eq!(*rect, dst);
925 assert_eq!(*actual, image);
926 assert!((alpha - 0.5).abs() < 1e-5);
927 assert_eq!(*sampling, ImageSampling::Linear);
928 assert_eq!(*src_rect, Some(src));
929 }
930 other => panic!("expected image primitive, got {other:?}"),
931 }
932 }
933
934 #[test]
935 fn draw_image_at_clamps_alpha() {
936 let mut scope = DrawScopeDefault::new(Size::new(10.0, 10.0));
937 let image = ImageBitmap::from_rgba8(1, 1, vec![255, 255, 255, 255]).expect("image");
938 scope.draw_image_at(
939 Rect::from_origin_size(Point::new(2.0, 3.0), Size::new(5.0, 6.0)),
940 image,
941 3.0,
942 Some(ColorFilter::Tint(Color::from_rgba_u8(128, 128, 255, 255))),
943 );
944 assert_image_alpha(&scope.into_primitives()[0], 1.0);
945 }
946
947 #[test]
948 fn graphics_layer_clone_with_render_effect() {
949 let layer = GraphicsLayer {
950 render_effect: Some(RenderEffect::blur(10.0)),
951 backdrop_effect: Some(RenderEffect::blur(6.0)),
952 color_filter: Some(ColorFilter::tint(Color::from_rgba_u8(128, 200, 255, 255))),
953 alpha: 0.5,
954 rotation_z: 12.0,
955 shadow_elevation: 4.0,
956 shape: LayerShape::Rounded(RoundedCornerShape::uniform(6.0)),
957 clip: true,
958 compositing_strategy: CompositingStrategy::Offscreen,
959 blend_mode: BlendMode::SrcOver,
960 ..Default::default()
961 };
962 let cloned = layer.clone();
963 assert_eq!(cloned.alpha, 0.5);
964 assert!(cloned.render_effect.is_some());
965 assert!(cloned.backdrop_effect.is_some());
966 assert_eq!(layer.color_filter, cloned.color_filter);
967 assert_eq!(layer.render_effect, cloned.render_effect);
968 assert_eq!(layer.backdrop_effect, cloned.backdrop_effect);
969 assert!((cloned.rotation_z - 12.0).abs() < 1e-6);
970 assert!((cloned.shadow_elevation - 4.0).abs() < 1e-6);
971 assert_eq!(
972 cloned.shape,
973 LayerShape::Rounded(RoundedCornerShape::uniform(6.0))
974 );
975 assert!(cloned.clip);
976 assert_eq!(cloned.compositing_strategy, CompositingStrategy::Offscreen);
977 assert_eq!(cloned.blend_mode, BlendMode::SrcOver);
978 }
979
980 #[test]
981 fn graphics_layer_default_has_no_effect() {
982 let layer = GraphicsLayer::default();
983 assert!(layer.color_filter.is_none());
984 assert!(layer.render_effect.is_none());
985 assert!(layer.backdrop_effect.is_none());
986 assert_eq!(layer.compositing_strategy, CompositingStrategy::Auto);
987 assert_eq!(layer.blend_mode, BlendMode::SrcOver);
988 assert_eq!(layer.alpha, 1.0);
989 assert_eq!(layer.transform_origin, TransformOrigin::CENTER);
990 assert!((layer.camera_distance - 8.0).abs() < 1e-6);
991 assert_eq!(layer.shape, LayerShape::Rectangle);
992 assert!(!layer.clip);
993 assert_eq!(layer.ambient_shadow_color, Color::BLACK);
994 assert_eq!(layer.spot_shadow_color, Color::BLACK);
995 }
996
997 #[test]
998 fn transform_origin_construction() {
999 let origin = TransformOrigin::new(0.25, 0.75);
1000 assert!((origin.pivot_fraction_x - 0.25).abs() < 1e-6);
1001 assert!((origin.pivot_fraction_y - 0.75).abs() < 1e-6);
1002 }
1003
1004 #[test]
1005 fn layer_shape_default_is_rectangle() {
1006 assert_eq!(LayerShape::default(), LayerShape::Rectangle);
1007 }
1008}