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