1use brush_data::BrushToolData;
2use std::{cmp::Ordering, mem, sync::mpsc::Receiver, thread};
3
4use super::{
5 BRUSH_NAME, Manipulate,
6 core::{
7 HeldKey, Mover, ReleasedKey, change_annos, check_autopaste, check_erase_mode,
8 check_instance_label_display_change, deselect_all, instance_label_display_sort,
9 label_change_key, map_held_key, map_released_key, on_selection_keys,
10 },
11 instance_anno_shared::get_rot90_data,
12};
13use crate::{
14 Annotation, BrushAnnotation, Line, ShapeI, annotations_accessor_mut,
15 cfg::ExportPath,
16 events::{Events, KeyCode},
17 history::{History, Record},
18 instance_annotations_accessor, make_tool_transform,
19 meta_data::MetaData,
20 result::trace_ok_err,
21 tools::{
22 core::{check_recolorboxes, check_trigger_history_update, check_trigger_redraw},
23 instance_anno_shared::{check_cocoimport, predictive_labeling},
24 },
25 tools_data::{
26 self, ExportAsCoco, InstanceAnnotate, LabelInfo, Rot90ToolData,
27 annotations::{BrushAnnotations, InstanceAnnotations},
28 brush_data::{self, MAX_INTENSITY, MAX_THICKNESS, MIN_INTENSITY, MIN_THICKNESS},
29 coco_io::to_per_file_crowd,
30 vis_from_lfoption,
31 },
32 tools_data_accessors, tools_data_accessors_objects,
33 util::Visibility,
34 world::World,
35 world_annotations_accessor,
36};
37use rvimage_domain::{BrushLine, Canvas, PtF, TPtF};
38
39pub const ACTOR_NAME: &str = "Brush";
40const MISSING_ANNO_MSG: &str = "brush annotations have not yet been initialized";
41const MISSING_DATA_MSG: &str = "brush data not available";
42annotations_accessor_mut!(ACTOR_NAME, brush_mut, MISSING_ANNO_MSG, BrushAnnotations);
43world_annotations_accessor!(ACTOR_NAME, brush, MISSING_ANNO_MSG, BrushAnnotations);
44instance_annotations_accessor!(Canvas);
45tools_data_accessors!(
46 ACTOR_NAME,
47 MISSING_DATA_MSG,
48 brush_data,
49 BrushToolData,
50 brush,
51 brush_mut
52);
53tools_data_accessors_objects!(
54 ACTOR_NAME,
55 MISSING_DATA_MSG,
56 brush_data,
57 BrushToolData,
58 brush,
59 brush_mut
60);
61pub(super) fn change_annos_brush(world: &mut World, change: impl FnOnce(&mut BrushAnnotations)) {
62 change_annos::<_, DataAccessors, InstanceAnnoAccessors>(world, change);
63}
64
65fn import_coco(
66 meta_data: &MetaData,
67 coco_file: &ExportPath,
68 rot90_data: Option<&Rot90ToolData>,
69) -> Option<BrushToolData> {
70 trace_ok_err(tools_data::coco_io::read_coco(meta_data, coco_file, rot90_data).map(|(_, d)| d))
71}
72
73fn max_select_dist(shape: ShapeI) -> TPtF {
74 (TPtF::from(shape.w.pow(2) + shape.h.pow(2)).sqrt() / 100.0).max(50.0)
75}
76
77fn draw_erase_circle(mut world: World, mp: PtF) -> World {
78 let show_only_current = get_specific(&world).map(|d| d.label_info.show_only_current);
79 let options = get_options(&world).copied();
80 let idx_current = get_specific(&world).map(|d| d.label_info.cat_idx_current);
81 if let Some(options) = options {
82 let erase = |annos: &mut BrushAnnotations| {
83 let to_be_removed_line_idx = find_closest_canvas(annos, mp, |idx| {
84 annos.is_of_current_label(idx, idx_current, show_only_current)
85 });
86 if let Some((idx, _)) = to_be_removed_line_idx
87 && let Some(canvas) = annos.edit(idx)
88 {
89 trace_ok_err(canvas.draw_circle(mp, options.thickness, 0));
90 }
91 };
92 change_annos_brush(&mut world, erase);
93 set_visible(&mut world);
94 }
95 world
96}
97fn mouse_released(events: &Events, mut world: World, mut history: History) -> (World, History) {
98 if events.held_ctrl() {
99 let shape_orig = world.shape_orig();
100 let show_only_current = get_specific(&world).map(|d| d.label_info.show_only_current);
101 let idx_current = get_specific(&world).map(|d| d.label_info.cat_idx_current);
102 if let (Some(mp), Some(annos)) = (events.mouse_pos_on_orig, get_annos_mut(&mut world)) {
103 let to_be_selected_line_idx = find_closest_canvas(annos, mp, |idx| {
104 annos.is_of_current_label(idx, idx_current, show_only_current)
105 });
106 if let Some((idx, dist)) = to_be_selected_line_idx {
107 if dist < max_select_dist(shape_orig) {
108 if annos.selected_mask().get(idx) == Some(&true) {
109 annos.deselect(idx);
110 } else {
111 annos.select(idx);
112 }
113 } else {
114 world =
115 deselect_all::<_, DataAccessors, InstanceAnnoAccessors>(world, BRUSH_NAME);
116 }
117 }
118 }
119 set_visible(&mut world);
120 } else if !(events.held_alt() || events.held_shift()) {
121 let erase = get_options(&world).map(|o| o.core.erase);
124 let cat_idx = get_specific(&world).map(|o| o.label_info.cat_idx_current);
125 if erase != Some(true) {
126 let shape_orig = world.shape_orig();
127 let line = get_specific_mut(&mut world).and_then(|d| mem::take(&mut d.tmp_line));
128 let line = if let Some((line, _)) = line {
129 Some(line)
130 } else if let (Some(mp), Some(options)) =
131 (events.mouse_pos_on_orig, get_options(&world))
132 {
133 Some(BrushLine {
134 line: Line::from(mp),
135 intensity: options.intensity,
136 thickness: options.thickness,
137 })
138 } else {
139 None
140 };
141 let ild = get_instance_label_display(&world);
142
143 let change_annos = |annos: &mut BrushAnnotations| {
144 if let (Some(line), Some(cat_idx)) = (line, cat_idx) {
145 let canvas = Canvas::new(&line, shape_orig, None);
146 if let Ok(canvas) = canvas {
147 annos.add_elt(canvas, cat_idx, ild);
148 }
149 }
150 };
151 change_annos_brush(&mut world, change_annos);
152 set_visible(&mut world);
153 } else if let Some(mp) = events.mouse_pos_on_orig {
154 world = draw_erase_circle(world, mp);
155 }
156 history.push(Record::new(world.clone(), ACTOR_NAME));
157 }
158 (world, history)
159}
160fn mouse_pressed_left(events: &Events, mut world: World) -> World {
161 if !(events.held_alt() || events.held_ctrl() || events.held_shift()) {
162 world = deselect_all::<_, DataAccessors, InstanceAnnoAccessors>(world, BRUSH_NAME);
163 }
164 if !events.held_ctrl() {
165 let options = get_options(&world).copied();
166 let idx_current = get_specific(&world).map(|d| d.label_info.cat_idx_current);
167 if let (Some(mp), Some(options)) = (events.mouse_pos_on_orig, options) {
168 let erase = options.core.erase;
169 if !erase && let (Some(d), Some(cat_idx)) = (get_specific_mut(&mut world), idx_current)
170 {
171 let line = Line::from(mp);
172 d.tmp_line = Some((
173 BrushLine {
174 line,
175 intensity: options.intensity,
176 thickness: options.thickness,
177 },
178 cat_idx,
179 ));
180 }
181 }
182 set_visible(&mut world);
183 }
184 world
185}
186fn key_released(events: &Events, mut world: World, mut history: History) -> (World, History) {
187 let released_key = map_released_key(events);
188 (world, history) = on_selection_keys::<_, DataAccessors, InstanceAnnoAccessors>(
189 world,
190 history,
191 released_key,
192 events.held_ctrl(),
193 BRUSH_NAME,
194 );
195 let mut trigger_redraw = false;
196 if let Some(label_info) = get_specific_mut(&mut world).map(|s| &mut s.label_info) {
197 (*label_info, trigger_redraw) = label_change_key(released_key, mem::take(label_info));
198 }
199 if trigger_redraw {
200 let visible = get_options(&world).map(|o| o.core.visible) == Some(true);
201 let vis = vis_from_lfoption(get_label_info(&world), visible);
202 world.request_redraw_annotations(BRUSH_NAME, vis);
203 }
204 match released_key {
205 ReleasedKey::H if events.held_ctrl() => {
206 if let Some(options_mut) = get_options_mut(&mut world) {
208 options_mut.core.visible = !options_mut.core.visible;
209 }
210 let vis = get_visible(&world);
211 world.request_redraw_annotations(BRUSH_NAME, vis);
212 }
213 _ => (),
214 }
215 world = check_instance_label_display_change::<_, DataAccessors, InstanceAnnoAccessors>(
216 world,
217 released_key,
218 ACTOR_NAME,
219 );
220 world = check_erase_mode::<DataAccessors>(released_key, set_visible, world);
221 (world, history)
222}
223fn find_closest_canvas(
224 annos: &BrushAnnotations,
225 p: PtF,
226 predicate: impl Fn(usize) -> bool,
227) -> Option<(usize, f64)> {
228 annos
229 .elts()
230 .iter()
231 .enumerate()
232 .map(|(i, cvs)| {
233 (
234 i,
235 cvs.dist_to_boundary(p) * if cvs.contains(p) { 0.0 } else { 1.0 },
236 )
237 })
238 .filter(|(i, _)| predicate(*i))
239 .min_by(|(_, x), (_, y)| match x.partial_cmp(y) {
240 Some(o) => o,
241 None => Ordering::Greater,
242 })
243}
244
245fn check_selected_intensity_thickness(mut world: World) -> World {
246 let options = get_options(&world).copied();
247 let annos = get_annos_mut(&mut world);
248 let mut any_selected = false;
249 if let (Some(annos), Some(options)) = (annos, options)
250 && options.is_selection_change_needed
251 {
252 for brushline in annos.selected_elts_iter_mut() {
253 brushline.intensity = options.intensity;
254 any_selected = true;
255 }
256 }
257 let options_mut = get_options_mut(&mut world);
258 if let Some(options_mut) = options_mut {
259 options_mut.is_selection_change_needed = false;
260 if any_selected {
261 options_mut.core.is_redraw_annos_triggered = true;
262 }
263 }
264 world
265}
266
267fn check_export(mut world: World) -> World {
268 let options = get_options(&world);
269 let specifics = get_specific(&world);
270
271 if options.map(|o| o.core.import_export_trigger.export_triggered()) == Some(true) {
272 let rot90_data = get_rot90_data(&world).cloned();
273 if let Some(data) = specifics {
274 let meta_data = world.data.meta_data.clone();
275 let mut data = data.clone();
276 let per_file_crowd = options.map(|o| o.per_file_crowd) == Some(true);
277 let double_check_shape =
278 options.map(|o| o.core.doublecheck_cocoexport_shape) == Some(true);
279 let f_export = move || {
280 let start = std::time::Instant::now();
281 if per_file_crowd {
282 to_per_file_crowd(&mut data.annotations_map);
283 }
284 let coco_file_conn = data.cocofile_conn();
285 match tools_data::write_coco(
286 &meta_data,
287 data,
288 rot90_data.as_ref(),
289 &coco_file_conn,
290 double_check_shape,
291 ) {
292 Ok((p, _)) => tracing::info!("export to {p:?} successfully triggered"),
293 Err(e) => tracing::error!("trigger export failed due to {e:?}"),
294 };
295 tracing::info!("export took {} seconds", start.elapsed().as_secs_f32());
296 };
297 thread::spawn(f_export);
298 }
299 if let Some(options_mut) = get_options_mut(&mut world) {
300 options_mut.core.import_export_trigger.untrigger_export();
301 }
302 }
303 world
304}
305
306pub(super) fn on_mouse_held_right(
307 mouse_pos: Option<PtF>,
308 mover: &mut Mover,
309 mut world: World,
310 history: History,
311) -> (World, History) {
312 if get_options(&world).map(|o| o.core.erase) != Some(true) {
313 let orig_shape = world.data.shape();
314 let move_boxes = |mpo_from, mpo_to| {
315 let annos = get_annos_mut(&mut world);
316 if let Some(annos) = annos {
317 let (mut elts, cat_idxs, selected_mask) = mem::take(annos).separate_data();
318 for (i, anno) in elts.iter_mut().enumerate() {
319 if selected_mask.get(i) == Some(&true) {
320 anno.follow_movement(mpo_from, mpo_to, orig_shape);
321 }
322 }
323 *annos = InstanceAnnotations::new(elts, cat_idxs, selected_mask).unwrap();
324 }
325 Some(())
326 };
327 mover.move_mouse_held(move_boxes, mouse_pos);
328 let vis = get_visible(&world);
329 world.request_redraw_annotations(ACTOR_NAME, vis);
330 }
331 (world, history)
332}
333#[derive(Debug)]
334pub struct Brush {
335 mover: Mover,
336 prediction_receiver: Option<Receiver<(World, History)>>,
337}
338
339impl Brush {
340 fn mouse_pressed(
341 &mut self,
342 events: &Events,
343 mut world: World,
344 history: History,
345 ) -> (World, History) {
346 if events.pressed(KeyCode::MouseRight) {
347 self.mover.move_mouse_pressed(events.mouse_pos_on_orig);
348 } else {
349 world = mouse_pressed_left(events, world);
350 }
351 (world, history)
352 }
353 fn mouse_held(
354 &mut self,
355 events: &Events,
356 mut world: World,
357 history: History,
358 ) -> (World, History) {
359 if events.held(KeyCode::MouseRight) {
360 on_mouse_held_right(events.mouse_pos_on_orig, &mut self.mover, world, history)
361 } else {
362 if !events.held_ctrl() {
363 let options = get_options(&world).copied();
364 if let (Some(mp), Some(options)) = (events.mouse_pos_on_orig, options) {
365 if options.core.erase {
366 world = draw_erase_circle(world, mp);
367 } else {
368 let line = if let Some((line, _)) =
369 get_specific_mut(&mut world).and_then(|d| d.tmp_line.as_mut())
370 {
371 let last_point = line.line.last_point();
372 let dist = if let Some(last_point) = last_point {
373 last_point.dist_square(&mp)
374 } else {
375 100.0
376 };
377 if dist >= 3.0 {
378 line.line.push(mp);
379 }
380 Some(line.clone())
381 } else {
382 None
383 };
384 if let (Some(line), Some(color)) = (
385 line,
386 get_specific(&world)
387 .and_then(|d| {
388 d.label_info.colors().get(d.label_info.cat_idx_current)
389 })
390 .copied(),
391 ) {
392 let orig_shape = world.shape_orig();
393 let canvas_with_new_buffer = || {
394 let lower_buffer_bound = 100;
395 let extension_factor = if line.line.points.len() < 10 {
396 4.0
397 } else if line.line.points.len() < 50 {
398 3.0
399 } else {
400 2.0
401 };
402 Canvas::from_line_extended(
403 &line,
404 orig_shape,
405 extension_factor,
406 lower_buffer_bound,
407 )
408 };
409 let canvas = if let Some(buffer) =
410 mem::take(&mut world.update_view.tmp_anno_buffer)
411 {
412 match buffer {
413 Annotation::Brush(brush_anno) => {
414 tracing::debug!("found buffer for tmp anno");
415 Canvas::new(&line, orig_shape, Some(brush_anno.canvas.mask))
416 }
417 _ => canvas_with_new_buffer(),
418 }
419 } else {
420 canvas_with_new_buffer()
421 };
422
423 let canvas = trace_ok_err(canvas);
424 if let Some(canvas) = canvas {
425 world.request_redraw_tmp_anno(Annotation::Brush(BrushAnnotation {
426 canvas,
427 color,
428 label: None,
429 is_selected: None,
430 fill_alpha: options.fill_alpha,
431 instance_display_label: options.core.instance_label_display,
432 }));
433 }
434 }
435 }
436 }
437 }
438
439 (world, history)
440 }
441 }
442
443 fn mouse_released(
444 &mut self,
445 events: &Events,
446 world: World,
447 history: History,
448 ) -> (World, History) {
449 mouse_released(events, world, history)
450 }
451
452 #[allow(clippy::unused_self)]
453 fn key_released(
454 &mut self,
455 events: &Events,
456 world: World,
457 history: History,
458 ) -> (World, History) {
459 key_released(events, world, history)
460 }
461 fn key_held(
462 &mut self,
463 events: &Events,
464 mut world: World,
465 history: History,
466 ) -> (World, History) {
467 const INTENSITY_STEP: f64 = MAX_INTENSITY / 20.0;
468 const THICKNESS_STEP: f64 = MAX_THICKNESS / 20.0;
469 let held_key = map_held_key(events);
470 let snap_to_step = |x: TPtF, step: TPtF| {
471 if x < 2.0 * step {
472 (x.div_euclid(step)) * step
473 } else {
474 x
475 }
476 };
477 match held_key {
478 HeldKey::I if events.held_alt() => {
479 if let Some(o) = get_options_mut(&mut world) {
480 o.intensity = MIN_INTENSITY
481 .max(snap_to_step(o.intensity - INTENSITY_STEP, INTENSITY_STEP));
482 o.is_selection_change_needed = true;
483 }
484 }
485 HeldKey::I => {
486 if let Some(o) = get_options_mut(&mut world) {
487 o.intensity = MAX_INTENSITY
488 .min(snap_to_step(o.intensity + INTENSITY_STEP, INTENSITY_STEP));
489 o.is_selection_change_needed = true;
490 }
491 }
492 HeldKey::T if events.held_alt() => {
493 if let Some(o) = get_options_mut(&mut world) {
494 o.thickness = MIN_THICKNESS
495 .max(snap_to_step(o.thickness - THICKNESS_STEP, THICKNESS_STEP));
496 o.is_selection_change_needed = true;
497 }
498 }
499 HeldKey::T => {
500 if let Some(o) = get_options_mut(&mut world) {
501 o.thickness = MAX_THICKNESS
502 .min(snap_to_step(o.thickness + THICKNESS_STEP, THICKNESS_STEP));
503 o.is_selection_change_needed = true;
504 }
505 }
506 HeldKey::None => (),
507 }
508 (world, history)
509 }
510}
511
512impl Clone for Brush {
513 fn clone(&self) -> Self {
514 Self {
515 mover: self.mover,
516 prediction_receiver: None, }
518 }
519}
520impl Manipulate for Brush {
521 fn new() -> Self {
522 Self {
523 mover: Mover::new(),
524 prediction_receiver: None,
525 }
526 }
527
528 fn on_filechange(&mut self, mut world: World, mut history: History) -> (World, History) {
529 use_currentimageshape_for_annos(&mut world);
530
531 let brush_data = get_specific_mut(&mut world);
532 if let Some(brush_data) = brush_data {
533 for (_, (anno, _)) in brush_data.anno_iter_mut() {
534 anno.deselect_all();
535 }
536 let ild = get_instance_label_display(&world);
537 world = instance_label_display_sort::<_, DataAccessors, InstanceAnnoAccessors>(
538 world, ild, ACTOR_NAME,
539 );
540 }
541 (world, history) =
542 check_autopaste::<_, DataAccessors, InstanceAnnoAccessors>(world, history, ACTOR_NAME);
543 set_visible(&mut world);
544 (world, history)
545 }
546 fn on_activate(&mut self, mut world: World) -> World {
547 if let Some(data) = trace_ok_err(get_data_mut(&mut world)) {
548 data.menu_active = true;
549 }
550 set_visible(&mut world);
551 world
552 }
553 fn on_deactivate(&mut self, mut world: World) -> World {
554 if let Some(data) = trace_ok_err(get_data_mut(&mut world)) {
555 data.menu_active = false;
556 }
557 world.request_redraw_annotations(BRUSH_NAME, Visibility::None);
558 world
559 }
560 fn on_always_active_zoom(&mut self, mut world: World, history: History) -> (World, History) {
561 let visible = get_options(&world).map(|o| o.core.visible) == Some(true);
562 let vis = vis_from_lfoption(get_label_info(&world), visible);
563 world.request_redraw_annotations(BRUSH_NAME, vis);
564 (world, history)
565 }
566 fn events_tf(
567 &mut self,
568 mut world: World,
569 mut history: History,
570 events: &Events,
571 ) -> (World, History) {
572 world = check_trigger_redraw::<DataAccessors>(world, BRUSH_NAME);
573 (world, history) =
574 check_trigger_history_update::<DataAccessors>(world, history, BRUSH_NAME);
575 let imported;
576 (world, imported) = check_cocoimport::<_, _, DataAccessors>(
577 world,
578 get_specific,
579 get_specific_mut,
580 import_coco,
581 );
582 if imported {
583 set_visible(&mut world);
584 }
585 predictive_labeling::<DataAccessors>(
586 &mut world,
587 &mut history,
588 ACTOR_NAME,
589 &mut self.prediction_receiver,
590 );
591 world = check_recolorboxes::<DataAccessors>(world, BRUSH_NAME);
592 world = check_selected_intensity_thickness(world);
593 world = check_export(world);
594 make_tool_transform!(
595 self,
596 world,
597 history,
598 events,
599 [
600 (pressed, KeyCode::MouseLeft, mouse_pressed),
601 (pressed, KeyCode::MouseRight, mouse_pressed),
602 (held, KeyCode::MouseLeft, mouse_held),
603 (held, KeyCode::MouseRight, mouse_held),
604 (released, KeyCode::MouseLeft, mouse_released),
605 (released, KeyCode::Back, key_released),
606 (released, KeyCode::Delete, key_released),
607 (released, KeyCode::A, key_released),
608 (released, KeyCode::C, key_released),
609 (released, KeyCode::D, key_released),
610 (released, KeyCode::E, key_released),
611 (released, KeyCode::H, key_released),
612 (held, KeyCode::I, key_held),
613 (released, KeyCode::L, key_released),
614 (held, KeyCode::T, key_held),
615 (released, KeyCode::V, key_released),
616 (released, KeyCode::Key1, key_released),
617 (released, KeyCode::Key2, key_released),
618 (released, KeyCode::Key3, key_released),
619 (released, KeyCode::Key4, key_released),
620 (released, KeyCode::Key5, key_released),
621 (released, KeyCode::Key6, key_released),
622 (released, KeyCode::Key7, key_released),
623 (released, KeyCode::Key8, key_released),
624 (released, KeyCode::Key9, key_released)
625 ]
626 )
627 }
628}
629
630#[cfg(test)]
631use {
632 crate::{
633 tracing_setup::init_tracing_for_tests,
634 types::{ThumbIms, ViewImage},
635 },
636 image::DynamicImage,
637};
638
639#[cfg(test)]
640pub fn test_data() -> (Option<PtF>, World, History) {
641 use std::path::Path;
642
643 use crate::ToolsDataMap;
644 let im_test = DynamicImage::ImageRgb8(ViewImage::new(64, 64));
645 let mut world = World::from_real_im(
646 im_test,
647 ThumbIms::default(),
648 ToolsDataMap::new(),
649 None,
650 Some("superimage.png".to_string()),
651 Path::new("superimage.png"),
652 Some(0),
653 );
654 world.data.meta_data.flags.is_loading_screen_active = Some(false);
655 get_specific_mut(&mut world)
656 .unwrap()
657 .label_info
658 .push("label".to_string(), None, None)
659 .unwrap();
660 let history = History::default();
661 let mouse_pos = Some((32.0, 32.0).into());
662 (mouse_pos, world, history)
663}
664
665#[test]
666fn test_mouse_released() {
667 init_tracing_for_tests();
668 let (mp, mut world, history) = test_data();
669 let options = get_options_mut(&mut world).unwrap();
670 options.thickness = 1.0;
671 let mut events = Events::default();
672 events.mouse_pos_on_orig = mp;
673 let (world, history) = mouse_released(&events, world, history);
674 let annos = get_annos(&world).unwrap();
675 assert_eq!(annos.len(), 1);
676 assert_eq!(annos.elts()[0].bb.x, 32);
677 assert_eq!(annos.elts()[0].bb.y, 32);
678 events.mouse_pos_on_orig = Some((40, 40).into());
679 let world = mouse_pressed_left(&events, world);
680 let (world, history) = mouse_released(&events, world, history);
681 let annos = get_annos(&world).unwrap();
682 assert_eq!(annos.len(), 2);
683 assert_eq!(annos.elts()[0].bb.x, 32);
684 assert_eq!(annos.elts()[0].bb.y, 32);
685 assert_eq!(annos.elts()[1].bb.x, 40);
686 assert_eq!(annos.elts()[1].bb.y, 40);
687 events.mouse_pos_on_orig = Some((10, 10).into());
688 let (world, _) = mouse_released(&events, world, history);
689 let annos = get_annos(&world).unwrap();
690 assert_eq!(annos.len(), 3);
691 assert_eq!(annos.elts()[0].bb.x, 32);
692 assert_eq!(annos.elts()[0].bb.y, 32);
693 assert_eq!(annos.elts()[1].bb.x, 40);
694 assert_eq!(annos.elts()[1].bb.y, 40);
695 assert_eq!(annos.elts()[2].bb.x, 10);
696 assert_eq!(annos.elts()[2].bb.y, 10);
697}