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 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 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}