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