1use crate::RenderMode;
7use crate::dispatch::Dispatcher;
8#[cfg(feature = "multithreading")]
9use crate::dispatch::multi_threaded::MultiThreadedDispatcher;
10#[cfg(feature = "text")]
11use crate::text::{GlyphAtlasResources, GlyphRunBuilder};
12#[cfg(feature = "text")]
13use glifo::GlyphPrepCache;
14
15use crate::dispatch::single_threaded::SingleThreadedDispatcher;
16use crate::kurbo::{PathEl, Point};
17use alloc::boxed::Box;
18use alloc::sync::Arc;
19use alloc::vec;
20use alloc::vec::Vec;
21use hashbrown::HashMap;
22use vello_common::blurred_rounded_rect::BlurredRoundedRectangle;
23use vello_common::encode::{EncodeExt, EncodedPaint};
24use vello_common::fearless_simd::Level;
25use vello_common::filter::FilterData;
26use vello_common::filter_effects::Filter;
27use vello_common::kurbo::{Affine, BezPath, Rect, Stroke};
28use vello_common::mask::Mask;
29use vello_common::paint::{ImageId, ImageResolver, Paint, PaintType, Tint};
30use vello_common::peniko::color::palette::css::BLACK;
31use vello_common::peniko::{BlendMode, Fill};
32use vello_common::pixmap::{Pixmap, PixmapMut};
33use vello_common::render_state::RenderState;
34use vello_common::transforms::{RootTransforms, Transforms};
35use vello_common::util::is_axis_aligned;
36
37#[cfg(feature = "text")]
38pub(crate) const DEFAULT_GLYPH_ATLAS_SIZE: u16 = 4096;
39pub(crate) const ATLAS_IMAGE_ID_BASE: u32 = u32::MAX / 2;
58
59#[derive(Debug, Default)]
63pub struct Resources {
64 pub(crate) image_registry: ImageRegistry,
65 #[cfg(feature = "text")]
66 pub(crate) glyph_prep_cache: GlyphPrepCache,
67 #[cfg(feature = "text")]
69 pub(crate) glyph_resources: Option<GlyphAtlasResources>,
70}
71
72impl Resources {
73 pub fn new() -> Self {
75 Self::default()
76 }
77
78 pub(crate) fn before_render(&mut self, render_mode: RenderMode) {
79 #[cfg(feature = "text")]
80 self.prepare_glyph_cache(render_mode);
81
82 #[cfg(not(feature = "text"))]
83 let _ = render_mode;
84 }
85
86 pub(crate) fn after_render(&mut self) {
87 #[cfg(feature = "text")]
88 self.maintain_glyph_cache();
89 }
90}
91
92#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
101pub enum CompositeMode {
102 #[default]
104 Replace,
105 SrcOver,
107}
108
109#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
111pub enum PixelFormat {
112 #[default]
114 Rgba8,
115}
116
117#[derive(Copy, Clone, Debug, PartialEq, Eq)]
119pub struct RasterizerSettings {
120 pub render_mode: RenderMode,
130 pub composite_mode: CompositeMode,
132 pub pixel_format: PixelFormat,
134 pub offset: (u16, u16),
138}
139
140impl Default for RasterizerSettings {
141 fn default() -> Self {
142 Self {
143 render_mode: RenderMode::OptimizeSpeed,
144 composite_mode: CompositeMode::Replace,
145 pixel_format: PixelFormat::Rgba8,
146 offset: (0, 0),
147 }
148 }
149}
150
151#[derive(Debug)]
157pub struct RenderContext {
158 pub(crate) width: u16,
160 pub(crate) height: u16,
162 pub(crate) state: RenderState,
164 root_transforms: RootTransforms,
165 pub(crate) mask: Option<Mask>,
167 pub(crate) temp_path: BezPath,
169 pub(crate) aliasing_threshold: Option<u8>,
171 pub(crate) encoded_paints: Vec<EncodedPaint>,
172 pub(crate) filter: Option<Filter>,
173 #[cfg_attr(
174 not(feature = "text"),
175 allow(dead_code, reason = "used when the `text` feature is enabled")
176 )]
177 pub(crate) render_settings: RenderSettings,
178 dispatcher: Box<dyn Dispatcher>,
179}
180
181#[derive(Copy, Clone, Debug)]
183pub struct RenderSettings {
184 pub level: Level,
186 pub num_threads: u16,
189}
190
191impl Default for RenderSettings {
192 fn default() -> Self {
193 Self {
194 level: Level::try_detect().unwrap_or(Level::baseline()),
195 #[cfg(feature = "multithreading")]
196 num_threads: (std::thread::available_parallelism()
197 .unwrap()
198 .get()
199 .saturating_sub(1) as u16)
200 .min(8),
201 #[cfg(not(feature = "multithreading"))]
202 num_threads: 0,
203 }
204 }
205}
206
207impl RenderContext {
208 pub fn new(width: u16, height: u16) -> Self {
210 Self::new_with(width, height, RenderSettings::default())
211 }
212
213 pub fn new_with(width: u16, height: u16, settings: RenderSettings) -> Self {
215 #[cfg(feature = "multithreading")]
216 let dispatcher: Box<dyn Dispatcher> = if settings.num_threads == 0 {
217 Box::new(SingleThreadedDispatcher::new(width, height, settings.level))
218 } else {
219 Box::new(MultiThreadedDispatcher::new(
220 width,
221 height,
222 settings.num_threads,
223 settings.level,
224 ))
225 };
226
227 #[cfg(not(feature = "multithreading"))]
228 let dispatcher: Box<dyn Dispatcher> =
229 { Box::new(SingleThreadedDispatcher::new(width, height, settings.level)) };
230
231 let encoded_paints = vec![];
232 let temp_path = BezPath::new();
233 let aliasing_threshold = None;
234
235 Self {
236 width,
237 height,
238 dispatcher,
239 state: RenderState::default(),
240 root_transforms: RootTransforms::default(),
241 aliasing_threshold,
242 render_settings: settings,
243 mask: None,
244 temp_path,
245 encoded_paints,
246 filter: None,
247 }
248 }
249
250 fn transforms(&self) -> &Transforms {
251 &self.state.transforms
252 }
253
254 fn transforms_mut(&mut self) -> &mut Transforms {
255 &mut self.state.transforms
256 }
257
258 fn encode_current_paint(&mut self) -> Paint {
259 match self.state.paint.clone() {
260 PaintType::Solid(s) => s.into(),
261 PaintType::Gradient(g) => {
262 let transform = self
263 .root_transforms
264 .effective_paint_transform(self.transforms());
265 g.encode_into(&mut self.encoded_paints, transform, None)
267 }
268 PaintType::Image(i) => {
269 let transform = self
270 .root_transforms
271 .effective_paint_transform(self.transforms());
272 i.encode_into(&mut self.encoded_paints, transform, self.state.tint)
273 }
274 }
275 }
276
277 pub fn fill_path(&mut self, path: &BezPath) {
279 self.with_optional_filter(|ctx| {
282 let paint = ctx.encode_current_paint();
283 let transform = ctx
284 .root_transforms
285 .effective_path_transform(ctx.transforms());
286 ctx.dispatcher.fill_path(
287 path,
288 ctx.state.fill_rule,
289 transform,
290 paint,
291 ctx.state.blend_mode,
292 ctx.aliasing_threshold,
293 ctx.mask.clone(),
294 );
295 });
296 }
297
298 pub fn stroke_path(&mut self, path: &BezPath) {
300 self.with_optional_filter(|ctx| {
301 let paint = ctx.encode_current_paint();
302 let transform = ctx
303 .root_transforms
304 .effective_path_transform(ctx.transforms());
305 ctx.dispatcher.stroke_path(
306 path,
307 &ctx.state.stroke,
308 transform,
309 paint,
310 ctx.state.blend_mode,
311 ctx.aliasing_threshold,
312 ctx.mask.clone(),
313 );
314 });
315 }
316
317 pub fn fill_rect(&mut self, rect: &Rect) {
319 self.with_optional_filter(|ctx| {
320 let paint = ctx.encode_current_paint();
321 let transform = ctx
322 .root_transforms
323 .effective_path_transform(ctx.transforms());
324
325 if is_axis_aligned(&transform) && ctx.aliasing_threshold.is_none() {
329 let transformed_rect = transform.transform_rect_bbox(*rect);
331 ctx.dispatcher.fill_rect_fast(
332 &transformed_rect,
333 paint,
334 ctx.state.blend_mode,
335 ctx.mask.clone(),
336 );
337 } else {
338 ctx.rect_to_temp_path(rect);
340 ctx.dispatcher.fill_path(
341 &ctx.temp_path,
342 ctx.state.fill_rule,
343 transform,
344 paint,
345 ctx.state.blend_mode,
346 ctx.aliasing_threshold,
347 ctx.mask.clone(),
348 );
349 }
350 });
351 }
352
353 pub fn stroke_rect(&mut self, rect: &Rect) {
355 self.with_optional_filter(|ctx| {
356 ctx.rect_to_temp_path(rect);
357 let paint = ctx.encode_current_paint();
358 let transform = ctx
359 .root_transforms
360 .effective_path_transform(ctx.transforms());
361 ctx.dispatcher.stroke_path(
362 &ctx.temp_path,
363 &ctx.state.stroke,
364 transform,
365 paint,
366 ctx.state.blend_mode,
367 ctx.aliasing_threshold,
368 ctx.mask.clone(),
369 );
370 });
371 }
372
373 fn rect_to_temp_path(&mut self, rect: &Rect) {
374 self.temp_path.truncate(0);
375 self.temp_path
376 .push(PathEl::MoveTo(Point::new(rect.x0, rect.y0)));
377 self.temp_path
378 .push(PathEl::LineTo(Point::new(rect.x1, rect.y0)));
379 self.temp_path
380 .push(PathEl::LineTo(Point::new(rect.x1, rect.y1)));
381 self.temp_path
382 .push(PathEl::LineTo(Point::new(rect.x0, rect.y1)));
383 self.temp_path.push(PathEl::ClosePath);
384 }
385
386 pub fn fill_blurred_rounded_rect(
395 &mut self,
396 rect: &Rect,
397 radius: f32,
398 std_dev: f32,
399 invert: bool,
400 ) {
401 let rect = rect.abs();
402 let color = match self.state.paint {
403 PaintType::Solid(s) => s,
404 _ => BLACK,
406 };
407
408 let blurred_rect = BlurredRoundedRectangle {
409 rect,
410 color,
411 radius,
412 std_dev,
413 invert,
414 };
415
416 let kernel_size = 2.5 * std_dev;
421 let inflated_rect = rect.inflate(f64::from(kernel_size), f64::from(kernel_size));
422 let transform = self
423 .root_transforms
424 .effective_path_transform(self.transforms());
425 let paint_transform = self
426 .root_transforms
427 .effective_paint_transform(self.transforms());
428
429 self.rect_to_temp_path(&inflated_rect);
430
431 let paint = blurred_rect.encode_into(&mut self.encoded_paints, paint_transform, None);
432 self.dispatcher.fill_path(
433 &self.temp_path,
434 Fill::NonZero,
435 transform,
436 paint,
437 self.state.blend_mode,
438 self.aliasing_threshold,
439 self.mask.clone(),
440 );
441 }
442
443 #[cfg(feature = "text")]
445 pub fn glyph_run<'a>(
446 &'a mut self,
447 resources: &'a mut Resources,
448 font: &crate::peniko::FontData,
449 ) -> GlyphRunBuilder<'a> {
450 glifo::GlyphRunBuilder::new(
451 font.clone(),
452 self.transforms().scene_transform(),
453 *self.transforms().paint_transform(),
454 crate::text::CpuGlyphRunBackend {
455 ctx: self,
456 resources,
457 atlas_cache_enabled: false,
458 },
459 )
460 }
461
462 pub fn push_layer(
472 &mut self,
473 clip_path: Option<&BezPath>,
474 blend_mode: Option<BlendMode>,
475 opacity: Option<f32>,
476 mask: Option<Mask>,
477 filter: Option<Filter>,
478 ) {
479 let mask = mask.and_then(|m| {
480 if m.width() != self.width || m.height() != self.height {
481 None
482 } else {
483 Some(m)
484 }
485 });
486
487 let blend_mode = blend_mode.unwrap_or_default();
488 let opacity = opacity.unwrap_or(1.0);
489 let layer_transform = self
490 .root_transforms
491 .effective_path_transform(self.transforms());
492 let filter_data = filter.map(|filter| FilterData::new(filter, layer_transform));
493
494 let relative_transform = filter_data.as_ref().map_or(Affine::IDENTITY, |data| {
501 let (shift_x, shift_y) = data.source_shift();
502 Affine::translate((f64::from(shift_x), f64::from(shift_y)))
503 });
504 self.root_transforms.push_root(relative_transform);
505
506 self.dispatcher.push_layer(
507 clip_path,
508 self.state.fill_rule,
509 layer_transform,
510 blend_mode,
511 opacity,
512 self.aliasing_threshold,
513 mask,
514 filter_data,
515 );
516 }
517
518 pub fn push_clip_layer(&mut self, path: &BezPath) {
523 self.push_layer(Some(path), None, None, None, None);
524 }
525
526 pub fn push_blend_layer(&mut self, blend_mode: BlendMode) {
528 self.push_layer(None, Some(blend_mode), None, None, None);
529 }
530
531 pub fn push_opacity_layer(&mut self, opacity: f32) {
533 self.push_layer(None, None, Some(opacity), None, None);
534 }
535
536 pub fn push_mask_layer(&mut self, mask: Mask) {
543 self.push_layer(None, None, None, Some(mask), None);
544 }
545
546 pub fn push_filter_layer(&mut self, filter: Filter) {
556 self.push_layer(None, None, None, None, Some(filter));
557 }
558
559 pub fn set_aliasing_threshold(&mut self, aliasing_threshold: Option<u8>) {
571 self.aliasing_threshold = aliasing_threshold;
572 }
573
574 pub fn pop_layer(&mut self) {
576 self.dispatcher.pop_layer();
577 self.root_transforms.pop_root();
578 }
579
580 pub fn set_stroke(&mut self, stroke: Stroke) {
582 self.state.stroke = stroke;
583 }
584
585 pub fn stroke(&self) -> &Stroke {
587 &self.state.stroke
588 }
589
590 #[cfg(feature = "text")]
592 pub(crate) fn stroke_mut(&mut self) -> &mut Stroke {
593 &mut self.state.stroke
594 }
595
596 pub fn set_paint(&mut self, paint: impl Into<PaintType>) {
602 self.state.paint = paint.into();
603 }
604
605 pub fn paint(&self) -> &PaintType {
607 &self.state.paint
608 }
609
610 pub fn set_tint(&mut self, tint: Option<Tint>) {
612 self.state.tint = tint;
613 }
614
615 pub fn reset_tint(&mut self) {
617 self.state.tint = None;
618 }
619
620 pub fn set_blend_mode(&mut self, blend_mode: BlendMode) {
622 self.state.blend_mode = blend_mode;
623 }
624
625 pub fn blend_mode(&self) -> BlendMode {
627 self.state.blend_mode
628 }
629
630 pub fn set_paint_transform(&mut self, paint_transform: Affine) {
636 self.transforms_mut().set_paint_transform(paint_transform);
637 }
638
639 pub fn paint_transform(&self) -> &Affine {
641 self.transforms().paint_transform()
642 }
643
644 pub fn reset_paint_transform(&mut self) {
646 self.transforms_mut().reset_paint_transform();
647 }
648
649 pub fn set_fill_rule(&mut self, fill_rule: Fill) {
651 self.state.fill_rule = fill_rule;
652 }
653
654 pub fn set_mask(&mut self, mask: Mask) {
661 self.mask = Some(mask);
662 }
663
664 pub fn reset_mask(&mut self) {
666 self.mask = None;
667 }
668
669 pub fn fill_rule(&self) -> &Fill {
671 &self.state.fill_rule
672 }
673
674 pub fn set_transform(&mut self, transform: Affine) {
676 self.transforms_mut().set_transform(transform);
677 }
678
679 pub fn transform(&self) -> &Affine {
681 self.transforms().transform()
682 }
683
684 pub fn reset_transform(&mut self) {
686 self.transforms_mut().reset_transform();
687 }
688
689 pub fn set_filter_effect(&mut self, filter: Filter) {
697 self.filter = Some(filter);
698 }
699
700 pub fn reset_filter_effect(&mut self) {
702 self.filter = None;
703 }
704
705 pub fn reset_and_resize(&mut self, width: u16, height: u16) {
707 self.width = width;
708 self.height = height;
709
710 self.reset();
711 }
712
713 pub fn reset(&mut self) {
715 self.dispatcher.reset(self.width, self.height);
716 self.encoded_paints.clear();
717 self.mask = None;
718 self.root_transforms.reset();
719 self.state.reset();
720 }
721
722 pub fn push_clip_path(&mut self, path: &BezPath) {
727 let transform = self.transforms().clip_path_transform();
728 self.dispatcher.push_clip_path(
729 path,
730 self.state.fill_rule,
731 transform,
732 self.aliasing_threshold,
733 );
734 }
735
736 pub fn pop_clip_path(&mut self) {
741 self.dispatcher.pop_clip_path();
742 }
743
744 pub fn flush(&mut self) {
750 self.dispatcher.flush();
751 }
752
753 pub fn render<'a>(&self, target: impl Into<PixmapMut<'a>>, resources: &mut Resources) {
757 self.render_with(target, resources, RasterizerSettings::default());
758 }
759
760 pub fn render_with<'a>(
794 &self,
795 target: impl Into<PixmapMut<'a>>,
796 resources: &mut Resources,
797 settings: RasterizerSettings,
798 ) {
799 assert!(
801 !self.dispatcher.has_layers(),
802 "some layers haven't been popped yet"
803 );
804
805 resources.before_render(settings.render_mode);
806 let mut target = target.into();
807 let target_fully_covered = settings.offset == (0, 0)
808 && self.width >= target.width()
809 && self.height >= target.height();
810 if settings.composite_mode == CompositeMode::Replace && !target_fully_covered {
814 target.data_mut().fill(0);
815 }
816
817 self.dispatcher.rasterize(
818 target,
819 self.width,
820 self.height,
821 settings,
822 &self.encoded_paints,
823 &resources.image_registry,
824 );
825 resources.after_render();
831 }
832
833 pub fn width(&self) -> u16 {
835 self.width
836 }
837
838 pub fn height(&self) -> u16 {
840 self.height
841 }
842
843 pub fn render_settings(&self) -> &RenderSettings {
845 &self.render_settings
846 }
847
848 fn with_optional_filter<F>(&mut self, mut f: F)
850 where
851 F: FnMut(&mut Self),
852 {
853 if let Some(filter) = self.filter.clone() {
854 self.push_filter_layer(filter);
855 f(self);
856 self.pop_layer();
857 } else {
858 f(self);
859 }
860 }
861
862 pub fn take_current_state(&mut self) -> RenderState {
864 core::mem::take(&mut self.state)
865 }
866
867 pub fn save_current_state(&mut self) -> RenderState {
869 self.state.clone()
870 }
871
872 pub fn restore_state(&mut self, state: RenderState) {
874 self.state = state;
875 }
876
877 pub fn is_multi_threaded(&self) -> bool {
879 self.dispatcher.is_multi_threaded()
880 }
881}
882
883impl Resources {
885 pub fn register_image(&mut self, pixmap: Arc<Pixmap>) -> ImageId {
887 self.image_registry.register(pixmap)
888 }
889
890 pub fn destroy_image(&mut self, id: ImageId) -> bool {
892 self.image_registry.destroy(id)
893 }
894
895 pub fn resolve_image(&self, id: ImageId) -> Option<Arc<Pixmap>> {
897 self.image_registry.resolve(id)
898 }
899
900 pub fn clear_images(&mut self) {
902 self.image_registry.clear();
903 }
904}
905
906#[derive(Debug, Default)]
910pub(crate) struct ImageRegistry {
911 images: HashMap<u32, Arc<Pixmap>>,
912 next_id: u32,
913}
914
915impl ImageRegistry {
916 fn register(&mut self, pixmap: Arc<Pixmap>) -> ImageId {
917 let id = self.next_id;
918 assert!(
919 id < ATLAS_IMAGE_ID_BASE,
920 "image registry exhausted non-atlas image IDs"
921 );
922
923 self.next_id += 1;
924 self.images.insert(id, pixmap);
925 ImageId::new(id)
926 }
927
928 #[cfg(feature = "text")]
929 pub(crate) fn register_atlas_page(&mut self, page_index: u32, pixmap: Arc<Pixmap>) {
930 self.images.insert(
931 ImageId::new(ATLAS_IMAGE_ID_BASE + page_index).as_u32(),
932 pixmap,
933 );
934 }
935
936 pub(crate) fn destroy(&mut self, id: ImageId) -> bool {
937 self.images.remove(&id.as_u32()).is_some()
938 }
939
940 #[cfg(feature = "text")]
941 pub(crate) fn destroy_atlas_page(&mut self, page_index: u32) -> bool {
942 self.destroy(ImageId::new(ATLAS_IMAGE_ID_BASE + page_index))
943 }
944
945 fn resolve(&self, id: ImageId) -> Option<Arc<Pixmap>> {
946 self.images.get(&id.as_u32()).cloned()
947 }
948
949 fn clear(&mut self) {
950 self.images.clear();
951 self.next_id = 0;
952 }
953}
954
955impl ImageResolver for ImageRegistry {
956 fn resolve(&self, id: ImageId) -> Option<Arc<Pixmap>> {
957 self.images.get(&id.as_u32()).cloned()
958 }
959}
960
961#[cfg(test)]
962mod tests {
963 #[cfg(feature = "text")]
964 use crate::peniko::{Blob, FontData};
965 use crate::{CompositeMode, RasterizerSettings, RenderContext, Resources};
966 #[cfg(feature = "text")]
967 use alloc::sync::Arc;
968 use alloc::vec;
969 #[cfg(feature = "text")]
970 use glifo::Glyph;
971 use vello_common::color::PremulRgba8;
972 use vello_common::color::palette::css::{BLUE, RED};
973 use vello_common::kurbo::{Rect, Shape};
974 use vello_common::pixmap::{Pixmap, PixmapMut};
975 use vello_common::tile::Tile;
976
977 const GRAY: PremulRgba8 = PremulRgba8 {
978 r: 9,
979 g: 10,
980 b: 11,
981 a: 255,
982 };
983
984 fn red_pixel() -> PremulRgba8 {
985 RED.premultiply().to_rgba8()
986 }
987
988 fn blue_pixel() -> PremulRgba8 {
989 BLUE.premultiply().to_rgba8()
990 }
991
992 fn transparent_pixel() -> PremulRgba8 {
993 PremulRgba8::from_u32(0)
994 }
995
996 fn solid_pixmap(width: u16, height: u16, color: PremulRgba8) -> Pixmap {
997 Pixmap::from_parts(
998 vec![color; usize::from(width) * usize::from(height)],
999 width,
1000 height,
1001 )
1002 }
1003
1004 fn red_rect_context(width: u16, height: u16, rect: Rect) -> RenderContext {
1005 let mut ctx = RenderContext::new(width, height);
1006 ctx.set_paint(RED);
1007 ctx.fill_rect(&rect);
1008 ctx.flush();
1009 ctx
1010 }
1011
1012 #[test]
1013 fn clip_overflow() {
1014 let mut ctx = RenderContext::new(100, 100);
1015
1016 for _ in 0..(usize::from(u16::MAX) + 1).div_ceil(usize::from(Tile::HEIGHT * Tile::WIDTH)) {
1017 ctx.fill_rect(&Rect::new(0.0, 0.0, 1.0, 1.0));
1018 }
1019
1020 ctx.push_clip_layer(&Rect::new(20.0, 20.0, 180.0, 180.0).to_path(0.1));
1021 ctx.pop_layer();
1022 ctx.flush();
1023 }
1024
1025 #[test]
1026 fn render_with_offset_clears_pixels_outside_scene() {
1027 let ctx = red_rect_context(2, 2, Rect::new(0.0, 0.0, 2.0, 2.0));
1028 let mut resources = Resources::new();
1029 let mut pixmap = solid_pixmap(4, 3, GRAY);
1030
1031 ctx.render_with(
1032 &mut pixmap,
1033 &mut resources,
1034 RasterizerSettings {
1035 offset: (1, 1),
1036 ..Default::default()
1037 },
1038 );
1039
1040 for y in 0..3 {
1041 for x in 0..4 {
1042 let expected = if (1..=2).contains(&x) && (1..=2).contains(&y) {
1043 red_pixel()
1044 } else {
1045 transparent_pixel()
1046 };
1047
1048 assert_eq!(pixmap.sample(x, y), expected, "pixel at ({x}, {y})");
1049 }
1050 }
1051 }
1052
1053 #[test]
1054 fn render_clips_scene_to_target_bounds() {
1055 let ctx = red_rect_context(3, 3, Rect::new(0.0, 0.0, 3.0, 3.0));
1056 let mut resources = Resources::new();
1057 let mut pixmap = solid_pixmap(4, 4, GRAY);
1058
1059 ctx.render_with(
1060 &mut pixmap,
1061 &mut resources,
1062 RasterizerSettings {
1063 offset: (2, 1),
1064 ..Default::default()
1065 },
1066 );
1067
1068 for y in 0..4 {
1069 for x in 0..4 {
1070 let expected = if (2..=3).contains(&x) && (1..=3).contains(&y) {
1071 red_pixel()
1072 } else {
1073 transparent_pixel()
1074 };
1075 assert_eq!(pixmap.sample(x, y), expected, "pixel at ({x}, {y})");
1076 }
1077 }
1078 }
1079
1080 #[test]
1081 fn render_into_padded_pixmap() {
1082 let ctx = red_rect_context(2, 2, Rect::new(0.0, 0.0, 2.0, 2.0));
1083 let mut resources = Resources::new();
1084 let mut pixmap = solid_pixmap(4, 2, GRAY);
1085
1086 ctx.render(&mut pixmap, &mut resources);
1087
1088 for y in 0..2 {
1089 for x in 0..4 {
1090 let expected = if x < 2 {
1091 red_pixel()
1092 } else {
1093 transparent_pixel()
1094 };
1095 assert_eq!(pixmap.sample(x, y), expected, "pixel at ({x}, {y})");
1096 }
1097 }
1098 }
1099
1100 #[test]
1101 fn reset_and_resize_updates_scene_size() {
1102 let mut ctx = RenderContext::new(8, 4);
1103 let mut resources = Resources::new();
1104 let mut pixmap = Pixmap::new(4, 8);
1105
1106 ctx.reset_and_resize(4, 8);
1107 assert_eq!(ctx.width(), 4);
1108 assert_eq!(ctx.height(), 8);
1109
1110 ctx.set_paint(BLUE);
1111 ctx.fill_rect(&Rect::new(0.0, 0.0, 4.0, 8.0));
1112 ctx.flush();
1113 ctx.render(&mut pixmap, &mut resources);
1114
1115 for y in 0..8 {
1116 for x in 0..4 {
1117 assert_eq!(pixmap.sample(x, y), blue_pixel(), "pixel at ({x}, {y})");
1118 }
1119 }
1120 }
1121
1122 #[test]
1123 fn render_into_raw_buffer() {
1124 let ctx = red_rect_context(2, 1, Rect::new(0.0, 0.0, 2.0, 1.0));
1125 let mut resources = Resources::new();
1126 let mut buffer = vec![0; 3 * 2 * 4];
1127 for pixel in buffer.chunks_exact_mut(4) {
1128 pixel.copy_from_slice(&[GRAY.r, GRAY.g, GRAY.b, GRAY.a]);
1129 }
1130
1131 {
1132 let pixmap = PixmapMut::new(3, 2, &mut buffer).unwrap();
1133 ctx.render_with(
1134 pixmap,
1135 &mut resources,
1136 RasterizerSettings {
1137 offset: (1, 1),
1138 ..Default::default()
1139 },
1140 );
1141 }
1142
1143 let expected = [
1144 transparent_pixel(),
1145 transparent_pixel(),
1146 transparent_pixel(),
1147 transparent_pixel(),
1148 red_pixel(),
1149 red_pixel(),
1150 ];
1151 for (pixel, expected) in buffer.chunks_exact(4).zip(expected) {
1152 assert_eq!(pixel, [expected.r, expected.g, expected.b, expected.a]);
1153 }
1154 }
1155
1156 #[test]
1157 fn pixmap_mut_validates_buffer_length() {
1158 let mut short_buffer = vec![0; 3 * 2 * 4 - 1];
1159 assert!(PixmapMut::new(3, 2, &mut short_buffer).is_none());
1160
1161 let mut exact_buffer = vec![0; 3 * 2 * 4];
1162 assert!(PixmapMut::new(3, 2, &mut exact_buffer).is_some());
1163 }
1164
1165 #[test]
1166 fn render_src_over_opaque() {
1167 let ctx = red_rect_context(2, 1, Rect::new(0.0, 0.0, 1.0, 1.0));
1168 let mut resources = Resources::new();
1169 let mut pixmap = solid_pixmap(2, 1, blue_pixel());
1170
1171 ctx.render_with(
1172 &mut pixmap,
1173 &mut resources,
1174 RasterizerSettings {
1175 composite_mode: CompositeMode::SrcOver,
1176 ..Default::default()
1177 },
1178 );
1179
1180 assert_eq!(pixmap.sample(0, 0), red_pixel());
1181 assert_eq!(pixmap.sample(1, 0), blue_pixel());
1182 }
1183
1184 #[test]
1185 fn render_src_over_transparent() {
1186 let mut ctx = RenderContext::new(1, 1);
1187 ctx.set_paint(RED.with_alpha(0.5));
1188 ctx.fill_rect(&Rect::new(0.0, 0.0, 1.0, 1.0));
1189 ctx.flush();
1190
1191 let mut resources = Resources::new();
1192 let mut pixmap = solid_pixmap(1, 1, blue_pixel());
1193
1194 ctx.render_with(
1195 &mut pixmap,
1196 &mut resources,
1197 RasterizerSettings {
1198 composite_mode: CompositeMode::SrcOver,
1199 ..Default::default()
1200 },
1201 );
1202
1203 assert_eq!(
1204 pixmap.sample(0, 0),
1205 PremulRgba8 {
1206 r: 128,
1207 g: 0,
1208 b: 127,
1209 a: 255,
1210 }
1211 );
1212 }
1213
1214 #[cfg(feature = "multithreading")]
1215 #[test]
1216 fn multithreaded_crash_after_reset() {
1217 use crate::{Level, RasterizerSettings, RenderMode, RenderSettings};
1218
1219 let mut pixmap = Pixmap::new(200, 200);
1220 let settings = RenderSettings {
1221 level: Level::try_detect().unwrap_or(Level::baseline()),
1222 num_threads: 1,
1223 };
1224 let rasterizer_settings = RasterizerSettings {
1225 render_mode: RenderMode::OptimizeQuality,
1226 ..Default::default()
1227 };
1228
1229 let mut resources = Resources::new();
1230 let mut ctx = RenderContext::new_with(200, 200, settings);
1231 ctx.reset();
1232 ctx.fill_path(&Rect::new(0.0, 0.0, 100.0, 100.0).to_path(0.1));
1233 ctx.flush();
1234 ctx.render_with(&mut pixmap, &mut resources, rasterizer_settings);
1235 ctx.flush();
1236 ctx.render_with(&mut pixmap, &mut resources, rasterizer_settings);
1237 }
1238
1239 #[cfg(feature = "multithreading")]
1240 #[test]
1241 fn multithreaded_render_empty_frame_after_reset() {
1242 use crate::RenderSettings;
1243
1244 let mut ctx = RenderContext::new_with(
1245 100,
1246 100,
1247 RenderSettings {
1248 num_threads: 4,
1249 ..Default::default()
1250 },
1251 );
1252 let mut resources = Resources::new();
1253 let mut pixmap = Pixmap::new(100, 100);
1254
1255 ctx.fill_rect(&Rect::new(0.0, 0.0, 100.0, 100.0));
1256 ctx.flush();
1257 ctx.render(&mut pixmap, &mut resources);
1258
1259 ctx.reset();
1260 ctx.flush();
1261 ctx.render(&mut pixmap, &mut resources);
1262 }
1263
1264 #[cfg(feature = "multithreading")]
1265 #[test]
1266 fn multithreaded_push_clip_path_before_draw() {
1267 use crate::RenderSettings;
1268
1269 let mut ctx = RenderContext::new_with(
1270 100,
1271 100,
1272 RenderSettings {
1273 num_threads: 1,
1274 ..Default::default()
1275 },
1276 );
1277 let clip = Rect::new(0.0, 0.0, 50.0, 50.0).to_path(0.1);
1278
1279 ctx.push_clip_path(&clip);
1281 ctx.flush();
1282 ctx.pop_clip_path();
1283 ctx.flush();
1284 }
1285
1286 #[cfg(feature = "multithreading")]
1287 #[test]
1288 fn multithreaded_reset_with_pending_tasks() {
1289 use crate::RenderSettings;
1290
1291 let mut ctx = RenderContext::new_with(
1292 100,
1293 100,
1294 RenderSettings {
1295 num_threads: 4,
1296 ..Default::default()
1297 },
1298 );
1299
1300 for _ in 0..300 {
1303 ctx.fill_rect(&Rect::new(0.0, 0.0, 100., 100.0));
1304 }
1305
1306 ctx.reset();
1307 }
1308
1309 #[cfg(feature = "multithreading")]
1310 #[test]
1311 fn multithreaded_drop_with_pending_tasks() {
1312 use crate::RenderSettings;
1313
1314 for _ in 0..10 {
1315 let mut ctx = RenderContext::new_with(
1316 100,
1317 100,
1318 RenderSettings {
1319 num_threads: 4,
1320 ..Default::default()
1321 },
1322 );
1323
1324 for _ in 0..300 {
1327 ctx.fill_rect(&Rect::new(0.0, 0.0, 100., 100.0));
1328 }
1329
1330 drop(ctx);
1331 }
1332 }
1333
1334 #[cfg(feature = "text")]
1335 #[test]
1336 fn glyph_atlas_resources_are_lazy() {
1337 const ROBOTO_FONT: &[u8] =
1338 include_bytes!("../../../examples/assets/roboto/Roboto-Regular.ttf");
1339
1340 let font = FontData::new(Blob::new(Arc::new(ROBOTO_FONT)), 0);
1341 let glyphs = [Glyph {
1342 id: 1,
1343 x: 0.0,
1344 y: 0.0,
1345 }];
1346
1347 let mut resources = Resources::new();
1348 let mut ctx = RenderContext::new(100, 100);
1349
1350 ctx.fill_rect(&Rect::new(0.0, 0.0, 10.0, 10.0));
1351 ctx.fill_path(&Rect::new(10.0, 10.0, 20.0, 20.0).to_path(0.1));
1352 ctx.glyph_run(&mut resources, &font)
1353 .fill_glyphs(glyphs.into_iter());
1354
1355 assert!(resources.glyph_resources.is_none());
1356
1357 ctx.glyph_run(&mut resources, &font)
1358 .atlas_cache(true)
1359 .fill_glyphs(glyphs.into_iter());
1360
1361 assert!(resources.glyph_resources.is_some());
1362 }
1363}