Skip to content

Commit ea7d111

Browse files
authored
Refactor bounding shape drawing (#103)
* Refactor bounding shape drawing. * Add license headers.
1 parent 10520bc commit ea7d111

File tree

9 files changed

+454
-216
lines changed

9 files changed

+454
-216
lines changed

src/main/java/com/github/mfl28/boundingboxeditor/controller/Controller.java

Lines changed: 29 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -201,15 +201,8 @@ public Controller(final Stage mainStage, final MainView view, final HostServices
201201
this.view = view;
202202
this.hostServices = hostServices;
203203

204-
stage.setTitle(PROGRAM_IDENTIFIER);
205-
stage.getIcons().add(MainView.APPLICATION_ICON);
206-
stage.setOnCloseRequest(event -> {
207-
onRegisterExitAction();
208-
event.consume();
209-
});
210-
204+
setupStage();
211205
loadPreferences();
212-
213206
view.connectToController(this);
214207
setUpModelListeners();
215208
setUpServices();
@@ -463,6 +456,8 @@ public void onRegisterAddObjectCategoryAction() {
463456
* Handles the event of the user requesting to exit the application.
464457
*/
465458
public void onRegisterExitAction() {
459+
view.getEditorImagePane().finalizeBoundingShapeDrawing();
460+
466461
updateModelFromView();
467462

468463
if(!model.isSaved()) {
@@ -608,14 +603,10 @@ public void onRegisterImageViewMouseReleasedEvent(MouseEvent event) {
608603
view.getEditorImageView().setCursor(Cursor.OPEN_HAND);
609604
}
610605

611-
if(view.getObjectCategoryTable().isCategorySelected()) {
612-
if(imagePane.getDrawingMode() == EditorImagePaneView.DrawingMode.BOX
613-
&& imagePane.isBoundingBoxDrawingInProgress()) {
614-
imagePane.finalizeBoundingBox();
615-
} else if(imagePane.getDrawingMode() == EditorImagePaneView.DrawingMode.FREEHAND
616-
&& imagePane.isFreehandDrawingInProgress()) {
617-
imagePane.finalizeFreehandShape();
618-
}
606+
if(view.getObjectCategoryTable().isCategorySelected() &&
607+
(Objects.equals(imagePane.getCurrentBoundingShapeDrawingMode(), EditorImagePaneView.DrawingMode.BOX) ||
608+
Objects.equals(imagePane.getCurrentBoundingShapeDrawingMode(), EditorImagePaneView.DrawingMode.FREEHAND))) {
609+
imagePane.finalizeBoundingShapeDrawing();
619610
}
620611
}
621612
}
@@ -632,16 +623,15 @@ public void onRegisterImageViewMousePressedEvent(MouseEvent event) {
632623
&& !event.isShortcutDown()
633624
&& imagePaneView.isCategorySelected()) {
634625
if(event.getButton().equals(MouseButton.PRIMARY)) {
635-
if(imagePaneView.getDrawingMode() == EditorImagePaneView.DrawingMode.BOX) {
636-
imagePaneView.initializeBoundingRectangle(event);
637-
} else if(imagePaneView.getDrawingMode() == EditorImagePaneView.DrawingMode.POLYGON) {
638-
imagePaneView.initializeBoundingPolygon(event);
639-
} else if(imagePaneView.getDrawingMode() == EditorImagePaneView.DrawingMode.FREEHAND) {
640-
imagePaneView.initializeBoundingFreehandShape(event);
626+
if(!imagePaneView.isDrawingInProgress()) {
627+
imagePaneView.initializeBoundingShapeDrawing(event);
628+
} else {
629+
imagePaneView.updateBoundingShapeDrawing(event);
641630
}
642631
} else if(event.getButton().equals(MouseButton.SECONDARY)
643-
&& imagePaneView.getDrawingMode() == EditorImagePaneView.DrawingMode.POLYGON) {
644-
imagePaneView.setBoundingPolygonsEditingAndConstructing(false);
632+
&& Objects.equals(imagePaneView.getCurrentBoundingShapeDrawingMode(),
633+
EditorImagePaneView.DrawingMode.POLYGON)) {
634+
imagePaneView.finalizeBoundingShapeDrawing();
645635
}
646636
}
647637
}
@@ -1598,6 +1588,21 @@ private void setCurrentAnnotationLoadingDirectory(File source) {
15981588
}
15991589
}
16001590

1591+
private void setupStage() {
1592+
stage.setTitle(PROGRAM_IDENTIFIER);
1593+
stage.getIcons().add(MainView.APPLICATION_ICON);
1594+
stage.setOnCloseRequest(event -> {
1595+
onRegisterExitAction();
1596+
event.consume();
1597+
});
1598+
1599+
stage.addEventFilter(MouseEvent.MOUSE_PRESSED, event -> {
1600+
if (event.getTarget() != view.getEditorImageView()) {
1601+
view.getEditorImagePane().finalizeBoundingShapeDrawing();
1602+
}
1603+
});
1604+
}
1605+
16011606
/**
16021607
* Class containing possible key-combinations.
16031608
*/
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
/*
2+
* Copyright (C) 2023 Markus Fleischhacker <[email protected]>
3+
*
4+
* This file is part of Bounding Box Editor
5+
*
6+
* Bounding Box Editor is free software: you can redistribute it and/or modify
7+
* it under the terms of the GNU General Public License as published by
8+
* the Free Software Foundation, either version 3 of the License, or
9+
* (at your option) any later version.
10+
*
11+
* Bounding Box Editor is distributed in the hope that it will be useful,
12+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
13+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14+
* GNU General Public License for more details.
15+
*
16+
* You should have received a copy of the GNU General Public License
17+
* along with Bounding Box Editor. If not, see <http://www.gnu.org/licenses/>.
18+
*/
19+
package com.github.mfl28.boundingboxeditor.ui;
20+
21+
import com.github.mfl28.boundingboxeditor.model.data.ObjectCategory;
22+
import com.github.mfl28.boundingboxeditor.utils.MathUtils;
23+
import javafx.geometry.Point2D;
24+
import javafx.scene.control.ToggleGroup;
25+
import javafx.scene.image.ImageView;
26+
import javafx.scene.input.MouseButton;
27+
import javafx.scene.input.MouseEvent;
28+
29+
import java.util.List;
30+
31+
32+
public class BoundingBoxDrawer implements BoundingShapeDrawer {
33+
private final ImageView imageView;
34+
private final ToggleGroup toggleGroup;
35+
private final List<BoundingShapeViewable> boundingShapes;
36+
37+
private BoundingBoxView boundingBoxView;
38+
39+
private boolean drawingInProgress = false;
40+
41+
public BoundingBoxDrawer(ImageView imageView, ToggleGroup toggleGroup, List<BoundingShapeViewable> boundingShapes) {
42+
this.imageView = imageView;
43+
this.toggleGroup = toggleGroup;
44+
this.boundingShapes = boundingShapes;
45+
}
46+
47+
@Override
48+
public void initializeShape(MouseEvent event, ObjectCategory objectCategory) {
49+
if (event.getEventType().equals(MouseEvent.MOUSE_PRESSED) && event.getButton().equals(MouseButton.PRIMARY)) {
50+
Point2D parentCoordinates = imageView.localToParent(event.getX(), event.getY());
51+
52+
boundingBoxView = new BoundingBoxView(objectCategory);
53+
boundingBoxView.getConstructionAnchorLocal().setFromMouseEvent(event);
54+
boundingBoxView.setToggleGroup(toggleGroup);
55+
56+
boundingBoxView.setX(parentCoordinates.getX());
57+
boundingBoxView.setY(parentCoordinates.getY());
58+
boundingBoxView.setWidth(0);
59+
boundingBoxView.setHeight(0);
60+
61+
boundingShapes.add(boundingBoxView);
62+
63+
boundingBoxView.autoScaleWithBounds(imageView.boundsInParentProperty());
64+
toggleGroup.selectToggle(boundingBoxView);
65+
66+
drawingInProgress = true;
67+
}
68+
}
69+
70+
@Override
71+
public void updateShape(MouseEvent event) {
72+
if (event.getEventType().equals(MouseEvent.MOUSE_DRAGGED) && event.getButton().equals(MouseButton.PRIMARY)) {
73+
final Point2D clampedEventXY =
74+
MathUtils.clampWithinBounds(event.getX(), event.getY(), imageView.getBoundsInLocal());
75+
76+
DragAnchor constructionAnchor = boundingBoxView.getConstructionAnchorLocal();
77+
Point2D parentCoordinates =
78+
imageView.localToParent(Math.min(clampedEventXY.getX(), constructionAnchor.getX()),
79+
Math.min(clampedEventXY.getY(), constructionAnchor.getY()));
80+
81+
boundingBoxView.setX(parentCoordinates.getX());
82+
boundingBoxView.setY(parentCoordinates.getY());
83+
boundingBoxView.setWidth(Math.abs(clampedEventXY.getX() - constructionAnchor.getX()));
84+
boundingBoxView.setHeight(Math.abs(clampedEventXY.getY() - constructionAnchor.getY()));
85+
}
86+
}
87+
88+
@Override
89+
public void finalizeShape() {
90+
drawingInProgress = false;
91+
}
92+
93+
@Override
94+
public boolean isDrawingInProgress() {
95+
return drawingInProgress;
96+
}
97+
98+
@Override
99+
public EditorImagePaneView.DrawingMode getDrawingMode() {
100+
return EditorImagePaneView.DrawingMode.BOX;
101+
}
102+
}
Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
/*
2+
* Copyright (C) 2023 Markus Fleischhacker <[email protected]>
3+
*
4+
* This file is part of Bounding Box Editor
5+
*
6+
* Bounding Box Editor is free software: you can redistribute it and/or modify
7+
* it under the terms of the GNU General Public License as published by
8+
* the Free Software Foundation, either version 3 of the License, or
9+
* (at your option) any later version.
10+
*
11+
* Bounding Box Editor is distributed in the hope that it will be useful,
12+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
13+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14+
* GNU General Public License for more details.
15+
*
16+
* You should have received a copy of the GNU General Public License
17+
* along with Bounding Box Editor. If not, see <http://www.gnu.org/licenses/>.
18+
*/
19+
package com.github.mfl28.boundingboxeditor.ui;
20+
21+
import com.github.mfl28.boundingboxeditor.model.data.ObjectCategory;
22+
import com.github.mfl28.boundingboxeditor.utils.MathUtils;
23+
import javafx.beans.property.BooleanProperty;
24+
import javafx.beans.property.DoubleProperty;
25+
import javafx.geometry.Point2D;
26+
import javafx.scene.control.ToggleGroup;
27+
import javafx.scene.image.ImageView;
28+
import javafx.scene.input.MouseButton;
29+
import javafx.scene.input.MouseEvent;
30+
import javafx.scene.shape.ClosePath;
31+
32+
import java.util.List;
33+
34+
public class BoundingFreeHandShapeDrawer implements BoundingShapeDrawer {
35+
36+
private final ImageView imageView;
37+
private final ToggleGroup toggleGroup;
38+
private final List<BoundingShapeViewable> boundingShapes;
39+
private final BooleanProperty autoSimplify;
40+
private final DoubleProperty simplifyRelativeDistanceTolerance;
41+
private boolean drawingInProgress = false;
42+
private BoundingFreehandShapeView boundingFreehandShapeView;
43+
44+
public BoundingFreeHandShapeDrawer(ImageView imageView, ToggleGroup toggleGroup, List<BoundingShapeViewable> boundingShapes,
45+
BooleanProperty autoSimplify, DoubleProperty simplifyRelativeDistanceTolerance) {
46+
this.imageView = imageView;
47+
this.toggleGroup = toggleGroup;
48+
this.boundingShapes = boundingShapes;
49+
this.autoSimplify = autoSimplify;
50+
this.simplifyRelativeDistanceTolerance = simplifyRelativeDistanceTolerance;
51+
}
52+
53+
@Override
54+
public void initializeShape(MouseEvent event, ObjectCategory objectCategory) {
55+
if (event.getEventType().equals(MouseEvent.MOUSE_PRESSED) && event.getButton().equals(MouseButton.PRIMARY)) {
56+
Point2D parentCoordinates = imageView.localToParent(event.getX(), event.getY());
57+
58+
boundingFreehandShapeView = new BoundingFreehandShapeView(objectCategory);
59+
boundingFreehandShapeView.setToggleGroup(toggleGroup);
60+
61+
boundingShapes.add(boundingFreehandShapeView);
62+
63+
boundingFreehandShapeView.autoScaleWithBounds(imageView.boundsInParentProperty());
64+
65+
boundingFreehandShapeView.setVisible(true);
66+
toggleGroup.selectToggle(boundingFreehandShapeView);
67+
boundingFreehandShapeView.addMoveTo(parentCoordinates.getX(), parentCoordinates.getY());
68+
drawingInProgress = true;
69+
}
70+
}
71+
72+
@Override
73+
public void updateShape(MouseEvent event) {
74+
if (event.getEventType().equals(MouseEvent.MOUSE_DRAGGED) && event.getButton().equals(MouseButton.PRIMARY)) {
75+
final Point2D clampedEventXY =
76+
MathUtils.clampWithinBounds(event.getX(), event.getY(), imageView.getBoundsInLocal());
77+
78+
Point2D parentCoordinates =
79+
imageView.localToParent(clampedEventXY.getX(), clampedEventXY.getY());
80+
81+
boundingFreehandShapeView.addLineTo(parentCoordinates.getX(), parentCoordinates.getY());
82+
}
83+
}
84+
85+
@Override
86+
public void finalizeShape() {
87+
boundingFreehandShapeView.getElements().add(new ClosePath());
88+
89+
BoundingPolygonView boundingPolygonView = new BoundingPolygonView(
90+
boundingFreehandShapeView.getViewData().getObjectCategory());
91+
92+
final List<Double> pointsInImage = boundingFreehandShapeView.getPointsInImage();
93+
94+
boundingPolygonView.setEditing(true);
95+
96+
for(int i = 0; i < pointsInImage.size(); i += 2) {
97+
boundingPolygonView.appendNode(pointsInImage.get(i), pointsInImage.get(i + 1));
98+
}
99+
100+
if(autoSimplify.get()) {
101+
boundingPolygonView.simplify(simplifyRelativeDistanceTolerance.get(),
102+
boundingFreehandShapeView.getViewData().autoScaleBounds().getValue());
103+
}
104+
105+
boundingPolygonView.setToggleGroup(toggleGroup);
106+
107+
boundingShapes.remove(boundingFreehandShapeView);
108+
109+
ObjectCategoryTreeItem parentTreeItem = (ObjectCategoryTreeItem) boundingFreehandShapeView.getViewData()
110+
.getTreeItem().getParent();
111+
parentTreeItem.detachBoundingShapeTreeItemChild(boundingFreehandShapeView.getViewData().getTreeItem());
112+
113+
if(parentTreeItem.getChildren().isEmpty()) {
114+
parentTreeItem.getParent().getChildren().remove(parentTreeItem);
115+
}
116+
117+
boundingShapes.add(boundingPolygonView);
118+
119+
boundingPolygonView.autoScaleWithBounds(imageView.boundsInParentProperty());
120+
boundingPolygonView.setVisible(true);
121+
toggleGroup.selectToggle(boundingPolygonView);
122+
123+
boundingPolygonView.setConstructing(false);
124+
boundingPolygonView.setEditing(false);
125+
126+
drawingInProgress = false;
127+
}
128+
129+
@Override
130+
public boolean isDrawingInProgress() {
131+
return drawingInProgress;
132+
}
133+
134+
@Override
135+
public EditorImagePaneView.DrawingMode getDrawingMode() {
136+
return EditorImagePaneView.DrawingMode.FREEHAND;
137+
}
138+
}

0 commit comments

Comments
 (0)