rvlib/tools/
brush.rs

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