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_circle(&mut self, brush: Brush, center: Point, radius: f32);
447 fn draw_circle_blend(
448 &mut self,
449 brush: Brush,
450 center: Point,
451 radius: f32,
452 blend_mode: BlendMode,
453 );
454 fn draw_image(&mut self, image: ImageBitmap);
455 fn draw_image_blend(&mut self, image: ImageBitmap, blend_mode: BlendMode);
456 fn draw_image_at(
457 &mut self,
458 rect: Rect,
459 image: ImageBitmap,
460 alpha: f32,
461 color_filter: Option<ColorFilter>,
462 );
463 fn draw_image_at_sampled(
464 &mut self,
465 rect: Rect,
466 image: ImageBitmap,
467 alpha: f32,
468 color_filter: Option<ColorFilter>,
469 sampling: ImageSampling,
470 );
471 fn draw_image_at_blend(
472 &mut self,
473 rect: Rect,
474 image: ImageBitmap,
475 alpha: f32,
476 color_filter: Option<ColorFilter>,
477 blend_mode: BlendMode,
478 );
479 fn draw_image_src(
482 &mut self,
483 image: ImageBitmap,
484 src_rect: Rect,
485 dst_rect: Rect,
486 alpha: f32,
487 color_filter: Option<ColorFilter>,
488 );
489 fn draw_image_src_sampled(
490 &mut self,
491 image: ImageBitmap,
492 src_rect: Rect,
493 dst_rect: Rect,
494 alpha: f32,
495 color_filter: Option<ColorFilter>,
496 sampling: ImageSampling,
497 );
498 fn draw_image_src_blend(
499 &mut self,
500 image: ImageBitmap,
501 src_rect: Rect,
502 dst_rect: Rect,
503 alpha: f32,
504 color_filter: Option<ColorFilter>,
505 blend_mode: BlendMode,
506 );
507 fn into_primitives(self) -> Vec<DrawPrimitive>;
508}
509
510#[derive(Default)]
511pub struct DrawScopeDefault {
512 size: Size,
513 primitives: Vec<DrawPrimitive>,
514}
515
516impl DrawScopeDefault {
517 pub fn new(size: Size) -> Self {
518 Self {
519 size,
520 primitives: Vec::new(),
521 }
522 }
523
524 fn push_blended_primitive(&mut self, primitive: DrawPrimitive, blend_mode: BlendMode) {
525 if blend_mode == BlendMode::SrcOver {
526 self.primitives.push(primitive);
527 } else {
528 self.primitives.push(DrawPrimitive::Blend {
529 primitive: Box::new(primitive),
530 blend_mode,
531 });
532 }
533 }
534}
535
536impl DrawScope for DrawScopeDefault {
537 fn size(&self) -> Size {
538 self.size
539 }
540
541 fn draw_content(&mut self) {
542 self.primitives.push(DrawPrimitive::Content);
543 }
544
545 fn draw_rect(&mut self, brush: Brush) {
546 self.draw_rect_blend(brush, BlendMode::SrcOver);
547 }
548
549 fn draw_rect_blend(&mut self, brush: Brush, blend_mode: BlendMode) {
550 self.push_blended_primitive(
551 DrawPrimitive::Rect {
552 rect: Rect::from_size(self.size),
553 brush,
554 },
555 blend_mode,
556 );
557 }
558
559 fn draw_rect_at(&mut self, rect: Rect, brush: Brush) {
560 self.draw_rect_at_blend(rect, brush, BlendMode::SrcOver);
561 }
562
563 fn draw_rect_at_blend(&mut self, rect: Rect, brush: Brush, blend_mode: BlendMode) {
564 self.push_blended_primitive(DrawPrimitive::Rect { rect, brush }, blend_mode);
565 }
566
567 fn draw_round_rect(&mut self, brush: Brush, radii: CornerRadii) {
568 self.draw_round_rect_blend(brush, radii, BlendMode::SrcOver);
569 }
570
571 fn draw_round_rect_blend(&mut self, brush: Brush, radii: CornerRadii, blend_mode: BlendMode) {
572 self.push_blended_primitive(
573 DrawPrimitive::RoundRect {
574 rect: Rect::from_size(self.size),
575 brush,
576 radii,
577 },
578 blend_mode,
579 );
580 }
581
582 fn draw_circle(&mut self, brush: Brush, center: Point, radius: f32) {
583 self.draw_circle_blend(brush, center, radius, BlendMode::SrcOver);
584 }
585
586 fn draw_circle_blend(
587 &mut self,
588 brush: Brush,
589 center: Point,
590 radius: f32,
591 blend_mode: BlendMode,
592 ) {
593 let radius = radius.max(0.0);
594 let diameter = radius * 2.0;
595 self.push_blended_primitive(
596 DrawPrimitive::RoundRect {
597 rect: Rect {
598 x: center.x - radius,
599 y: center.y - radius,
600 width: diameter,
601 height: diameter,
602 },
603 brush,
604 radii: CornerRadii::uniform(radius),
605 },
606 blend_mode,
607 );
608 }
609
610 fn draw_image(&mut self, image: ImageBitmap) {
611 self.draw_image_blend(image, BlendMode::SrcOver);
612 }
613
614 fn draw_image_blend(&mut self, image: ImageBitmap, blend_mode: BlendMode) {
615 self.push_blended_primitive(
616 DrawPrimitive::Image {
617 rect: Rect::from_size(self.size),
618 image,
619 alpha: 1.0,
620 color_filter: None,
621 sampling: ImageSampling::Nearest,
622 src_rect: None,
623 },
624 blend_mode,
625 );
626 }
627
628 fn draw_image_at(
629 &mut self,
630 rect: Rect,
631 image: ImageBitmap,
632 alpha: f32,
633 color_filter: Option<ColorFilter>,
634 ) {
635 self.draw_image_at_sampled(rect, image, alpha, color_filter, ImageSampling::Nearest);
636 }
637
638 fn draw_image_at_sampled(
639 &mut self,
640 rect: Rect,
641 image: ImageBitmap,
642 alpha: f32,
643 color_filter: Option<ColorFilter>,
644 sampling: ImageSampling,
645 ) {
646 self.push_blended_primitive(
647 DrawPrimitive::Image {
648 rect,
649 image,
650 alpha: alpha.clamp(0.0, 1.0),
651 color_filter,
652 sampling,
653 src_rect: None,
654 },
655 BlendMode::SrcOver,
656 );
657 }
658
659 fn draw_image_at_blend(
660 &mut self,
661 rect: Rect,
662 image: ImageBitmap,
663 alpha: f32,
664 color_filter: Option<ColorFilter>,
665 blend_mode: BlendMode,
666 ) {
667 self.push_blended_primitive(
668 DrawPrimitive::Image {
669 rect,
670 image,
671 alpha: alpha.clamp(0.0, 1.0),
672 color_filter,
673 sampling: ImageSampling::Nearest,
674 src_rect: None,
675 },
676 blend_mode,
677 );
678 }
679
680 fn draw_image_src(
681 &mut self,
682 image: ImageBitmap,
683 src_rect: Rect,
684 dst_rect: Rect,
685 alpha: f32,
686 color_filter: Option<ColorFilter>,
687 ) {
688 self.draw_image_src_blend(
689 image,
690 src_rect,
691 dst_rect,
692 alpha,
693 color_filter,
694 BlendMode::SrcOver,
695 );
696 }
697
698 fn draw_image_src_sampled(
699 &mut self,
700 image: ImageBitmap,
701 src_rect: Rect,
702 dst_rect: Rect,
703 alpha: f32,
704 color_filter: Option<ColorFilter>,
705 sampling: ImageSampling,
706 ) {
707 self.push_blended_primitive(
708 DrawPrimitive::Image {
709 rect: dst_rect,
710 image,
711 alpha: alpha.clamp(0.0, 1.0),
712 color_filter,
713 sampling,
714 src_rect: Some(src_rect),
715 },
716 BlendMode::SrcOver,
717 );
718 }
719
720 fn draw_image_src_blend(
721 &mut self,
722 image: ImageBitmap,
723 src_rect: Rect,
724 dst_rect: Rect,
725 alpha: f32,
726 color_filter: Option<ColorFilter>,
727 blend_mode: BlendMode,
728 ) {
729 self.push_blended_primitive(
730 DrawPrimitive::Image {
731 rect: dst_rect,
732 image,
733 alpha: alpha.clamp(0.0, 1.0),
734 color_filter,
735 sampling: ImageSampling::Nearest,
736 src_rect: Some(src_rect),
737 },
738 blend_mode,
739 );
740 }
741
742 fn into_primitives(self) -> Vec<DrawPrimitive> {
743 self.primitives
744 }
745}
746
747#[cfg(test)]
748mod tests {
749 use super::*;
750 use crate::{Color, ImageBitmap, RenderEffect};
751
752 fn assert_image_alpha(primitive: &DrawPrimitive, expected: f32) {
753 match primitive {
754 DrawPrimitive::Image { alpha, .. } => assert!((alpha - expected).abs() < 1e-5),
755 DrawPrimitive::Blend { primitive, .. } => assert_image_alpha(primitive, expected),
756 other => panic!("expected image primitive, got {other:?}"),
757 }
758 }
759
760 fn unwrap_image(primitive: &DrawPrimitive) -> &DrawPrimitive {
761 match primitive {
762 DrawPrimitive::Image { .. } => primitive,
763 DrawPrimitive::Blend { primitive, .. } => unwrap_image(primitive),
764 other => panic!("expected image primitive, got {other:?}"),
765 }
766 }
767
768 #[test]
769 fn draw_content_inserts_content_marker() {
770 let mut scope = DrawScopeDefault::new(Size::new(8.0, 8.0));
771 scope.draw_rect(Brush::solid(Color::WHITE));
772 scope.draw_content();
773 scope.draw_rect_blend(Brush::solid(Color::BLACK), BlendMode::DstOut);
774
775 let primitives = scope.into_primitives();
776 assert!(matches!(primitives[1], DrawPrimitive::Content));
777 assert!(matches!(
778 primitives[2],
779 DrawPrimitive::Blend {
780 blend_mode: BlendMode::DstOut,
781 ..
782 }
783 ));
784 }
785
786 #[test]
787 fn draw_rect_blend_wraps_non_default_modes() {
788 let mut scope = DrawScopeDefault::new(Size::new(10.0, 10.0));
789 scope.draw_rect_blend(Brush::solid(Color::RED), BlendMode::DstOut);
790
791 let primitives = scope.into_primitives();
792 assert_eq!(primitives.len(), 1);
793 match &primitives[0] {
794 DrawPrimitive::Blend {
795 primitive,
796 blend_mode,
797 } => {
798 assert_eq!(*blend_mode, BlendMode::DstOut);
799 assert!(matches!(**primitive, DrawPrimitive::Rect { .. }));
800 }
801 other => panic!("expected blended primitive, got {other:?}"),
802 }
803 }
804
805 #[test]
806 fn draw_circle_records_centered_round_rect() {
807 let mut scope = DrawScopeDefault::new(Size::new(40.0, 40.0));
808 scope.draw_circle(Brush::solid(Color::BLUE), Point::new(12.0, 16.0), 5.0);
809
810 let primitives = scope.into_primitives();
811 assert_eq!(primitives.len(), 1);
812 match &primitives[0] {
813 DrawPrimitive::RoundRect { rect, radii, .. } => {
814 assert_eq!(
815 *rect,
816 Rect {
817 x: 7.0,
818 y: 11.0,
819 width: 10.0,
820 height: 10.0,
821 }
822 );
823 assert_eq!(*radii, CornerRadii::uniform(5.0));
824 }
825 other => panic!("expected circular round rect, got {other:?}"),
826 }
827 }
828
829 #[test]
830 fn draw_circle_blend_wraps_non_default_modes() {
831 let mut scope = DrawScopeDefault::new(Size::new(10.0, 10.0));
832 scope.draw_circle_blend(
833 Brush::solid(Color::RED),
834 Point::new(5.0, 5.0),
835 3.0,
836 BlendMode::Plus,
837 );
838
839 let primitives = scope.into_primitives();
840 assert_eq!(primitives.len(), 1);
841 match &primitives[0] {
842 DrawPrimitive::Blend {
843 primitive,
844 blend_mode,
845 } => {
846 assert_eq!(*blend_mode, BlendMode::Plus);
847 assert!(matches!(**primitive, DrawPrimitive::RoundRect { .. }));
848 }
849 other => panic!("expected blended circle primitive, got {other:?}"),
850 }
851 }
852
853 #[test]
854 fn rect_union_encloses_both_inputs() {
855 let lhs = Rect {
856 x: 10.0,
857 y: 5.0,
858 width: 8.0,
859 height: 4.0,
860 };
861 let rhs = Rect {
862 x: 4.0,
863 y: 7.0,
864 width: 10.0,
865 height: 6.0,
866 };
867
868 assert_eq!(
869 lhs.union(rhs),
870 Rect {
871 x: 4.0,
872 y: 5.0,
873 width: 14.0,
874 height: 8.0,
875 }
876 );
877 }
878
879 #[test]
880 fn draw_image_uses_scope_size_as_default_rect() {
881 let mut scope = DrawScopeDefault::new(Size::new(40.0, 24.0));
882 let image = ImageBitmap::from_rgba8(2, 2, vec![255; 16]).expect("image");
883 scope.draw_image(image.clone());
884 let primitives = scope.into_primitives();
885 assert_eq!(primitives.len(), 1);
886 match unwrap_image(&primitives[0]) {
887 DrawPrimitive::Image {
888 rect,
889 image: actual,
890 alpha,
891 color_filter,
892 sampling,
893 src_rect,
894 } => {
895 assert_eq!(*rect, Rect::from_size(Size::new(40.0, 24.0)));
896 assert_eq!(*actual, image);
897 assert_eq!(*alpha, 1.0);
898 assert!(color_filter.is_none());
899 assert_eq!(*sampling, ImageSampling::Nearest);
900 assert!(src_rect.is_none());
901 }
902 other => panic!("expected image primitive, got {other:?}"),
903 }
904 }
905
906 #[test]
907 fn draw_image_src_stores_src_rect() {
908 let mut scope = DrawScopeDefault::new(Size::new(100.0, 100.0));
909 let image = ImageBitmap::from_rgba8(64, 64, vec![255; 64 * 64 * 4]).expect("image");
910 let src = Rect {
911 x: 10.0,
912 y: 20.0,
913 width: 30.0,
914 height: 40.0,
915 };
916 let dst = Rect {
917 x: 0.0,
918 y: 0.0,
919 width: 60.0,
920 height: 80.0,
921 };
922 scope.draw_image_src(image.clone(), src, dst, 0.8, None);
923 let primitives = scope.into_primitives();
924 assert_eq!(primitives.len(), 1);
925 match unwrap_image(&primitives[0]) {
926 DrawPrimitive::Image {
927 rect,
928 image: actual,
929 alpha,
930 sampling,
931 src_rect,
932 ..
933 } => {
934 assert_eq!(*rect, dst);
935 assert_eq!(*actual, image);
936 assert!((alpha - 0.8).abs() < 1e-5);
937 assert_eq!(*sampling, ImageSampling::Nearest);
938 assert_eq!(*src_rect, Some(src));
939 }
940 other => panic!("expected image primitive, got {other:?}"),
941 }
942 }
943
944 #[test]
945 fn draw_image_at_sampled_records_requested_sampling() {
946 let mut scope = DrawScopeDefault::new(Size::new(100.0, 100.0));
947 let image = ImageBitmap::from_rgba8(8, 8, vec![255; 8 * 8 * 4]).expect("image");
948 let dst = Rect {
949 x: 2.0,
950 y: 3.0,
951 width: 40.0,
952 height: 30.0,
953 };
954
955 scope.draw_image_at_sampled(dst, image.clone(), 0.7, None, ImageSampling::Linear);
956
957 let primitives = scope.into_primitives();
958 assert_eq!(primitives.len(), 1);
959 match unwrap_image(&primitives[0]) {
960 DrawPrimitive::Image {
961 rect,
962 image: actual,
963 alpha,
964 sampling,
965 src_rect,
966 ..
967 } => {
968 assert_eq!(*rect, dst);
969 assert_eq!(*actual, image);
970 assert!((alpha - 0.7).abs() < 1e-5);
971 assert_eq!(*sampling, ImageSampling::Linear);
972 assert!(src_rect.is_none());
973 }
974 other => panic!("expected image primitive, got {other:?}"),
975 }
976 }
977
978 #[test]
979 fn draw_image_src_sampled_records_requested_sampling() {
980 let mut scope = DrawScopeDefault::new(Size::new(100.0, 100.0));
981 let image = ImageBitmap::from_rgba8(64, 64, vec![255; 64 * 64 * 4]).expect("image");
982 let src = Rect {
983 x: 4.0,
984 y: 6.0,
985 width: 16.0,
986 height: 20.0,
987 };
988 let dst = Rect {
989 x: 8.0,
990 y: 10.0,
991 width: 32.0,
992 height: 40.0,
993 };
994
995 scope.draw_image_src_sampled(image.clone(), src, dst, 0.5, None, ImageSampling::Linear);
996
997 let primitives = scope.into_primitives();
998 assert_eq!(primitives.len(), 1);
999 match unwrap_image(&primitives[0]) {
1000 DrawPrimitive::Image {
1001 rect,
1002 image: actual,
1003 alpha,
1004 sampling,
1005 src_rect,
1006 ..
1007 } => {
1008 assert_eq!(*rect, dst);
1009 assert_eq!(*actual, image);
1010 assert!((alpha - 0.5).abs() < 1e-5);
1011 assert_eq!(*sampling, ImageSampling::Linear);
1012 assert_eq!(*src_rect, Some(src));
1013 }
1014 other => panic!("expected image primitive, got {other:?}"),
1015 }
1016 }
1017
1018 #[test]
1019 fn draw_image_at_clamps_alpha() {
1020 let mut scope = DrawScopeDefault::new(Size::new(10.0, 10.0));
1021 let image = ImageBitmap::from_rgba8(1, 1, vec![255, 255, 255, 255]).expect("image");
1022 scope.draw_image_at(
1023 Rect::from_origin_size(Point::new(2.0, 3.0), Size::new(5.0, 6.0)),
1024 image,
1025 3.0,
1026 Some(ColorFilter::Tint(Color::from_rgba_u8(128, 128, 255, 255))),
1027 );
1028 assert_image_alpha(&scope.into_primitives()[0], 1.0);
1029 }
1030
1031 #[test]
1032 fn graphics_layer_clone_with_render_effect() {
1033 let layer = GraphicsLayer {
1034 render_effect: Some(RenderEffect::blur(10.0)),
1035 backdrop_effect: Some(RenderEffect::blur(6.0)),
1036 color_filter: Some(ColorFilter::tint(Color::from_rgba_u8(128, 200, 255, 255))),
1037 alpha: 0.5,
1038 rotation_z: 12.0,
1039 shadow_elevation: 4.0,
1040 shape: LayerShape::Rounded(RoundedCornerShape::uniform(6.0)),
1041 clip: true,
1042 compositing_strategy: CompositingStrategy::Offscreen,
1043 blend_mode: BlendMode::SrcOver,
1044 ..Default::default()
1045 };
1046 let cloned = layer.clone();
1047 assert_eq!(cloned.alpha, 0.5);
1048 assert!(cloned.render_effect.is_some());
1049 assert!(cloned.backdrop_effect.is_some());
1050 assert_eq!(layer.color_filter, cloned.color_filter);
1051 assert_eq!(layer.render_effect, cloned.render_effect);
1052 assert_eq!(layer.backdrop_effect, cloned.backdrop_effect);
1053 assert!((cloned.rotation_z - 12.0).abs() < 1e-6);
1054 assert!((cloned.shadow_elevation - 4.0).abs() < 1e-6);
1055 assert_eq!(
1056 cloned.shape,
1057 LayerShape::Rounded(RoundedCornerShape::uniform(6.0))
1058 );
1059 assert!(cloned.clip);
1060 assert_eq!(cloned.compositing_strategy, CompositingStrategy::Offscreen);
1061 assert_eq!(cloned.blend_mode, BlendMode::SrcOver);
1062 }
1063
1064 #[test]
1065 fn graphics_layer_default_has_no_effect() {
1066 let layer = GraphicsLayer::default();
1067 assert!(layer.color_filter.is_none());
1068 assert!(layer.render_effect.is_none());
1069 assert!(layer.backdrop_effect.is_none());
1070 assert_eq!(layer.compositing_strategy, CompositingStrategy::Auto);
1071 assert_eq!(layer.blend_mode, BlendMode::SrcOver);
1072 assert_eq!(layer.alpha, 1.0);
1073 assert_eq!(layer.transform_origin, TransformOrigin::CENTER);
1074 assert!((layer.camera_distance - 8.0).abs() < 1e-6);
1075 assert_eq!(layer.shape, LayerShape::Rectangle);
1076 assert!(!layer.clip);
1077 assert_eq!(layer.ambient_shadow_color, Color::BLACK);
1078 assert_eq!(layer.spot_shadow_color, Color::BLACK);
1079 }
1080
1081 #[test]
1082 fn transform_origin_construction() {
1083 let origin = TransformOrigin::new(0.25, 0.75);
1084 assert!((origin.pivot_fraction_x - 0.25).abs() < 1e-6);
1085 assert!((origin.pivot_fraction_y - 0.75).abs() < 1e-6);
1086 }
1087
1088 #[test]
1089 fn layer_shape_default_is_rectangle() {
1090 assert_eq!(LayerShape::default(), LayerShape::Rectangle);
1091 }
1092}