Skip to content

Commit 5cb1b48

Browse files
committed
#446 Flood fill, circles, font editor fixes.
1 parent 764d2e7 commit 5cb1b48

File tree

6 files changed

+163
-41
lines changed

6 files changed

+163
-41
lines changed

tcMenuGenerator/src/main/java/com/thecoderscorner/menu/editorui/gfxui/CreateFontUtilityController.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,7 @@ private void recalcFont() {
126126
for(var item : allItems) {
127127
item.setSelected(selAllCheck.isSelected());
128128
}
129-
for(int i=blockRange.getStartingCode(); i<=blockRange.getEndingCode();i++) {
129+
for(int i=minimumStartingCode(blockRange); i<=blockRange.getEndingCode();i++) {
130130
currentlySelected.put(i, selAllCheck.isSelected());
131131
}
132132
}

tcMenuGenerator/src/main/java/com/thecoderscorner/menu/editorui/gfxui/NativeFreeFontLoadedFont.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,11 +41,11 @@ public NativeFreeFontLoadedFont(Path path, int dpi) {
4141
FunctionDescriptor.of(ValueLayout.JAVA_INT, ValueLayout.ADDRESS, ValueLayout.JAVA_INT, ValueLayout.JAVA_INT)
4242
);
4343
fontGetGlyph = linker.downcallHandle(
44-
fontLib.find("canDisplay").orElseThrow(),
44+
fontLib.find("getFontGlyph").orElseThrow(),
4545
FunctionDescriptor.of(ValueLayout.JAVA_INT, ValueLayout.JAVA_INT, ValueLayout.JAVA_INT, ValueLayout.ADDRESS)
4646
);
4747
canDisplayFn = linker.downcallHandle(
48-
fontLib.find("getFontGlyph").orElseThrow(),
48+
fontLib.find("canDisplay").orElseThrow(),
4949
FunctionDescriptor.of(ValueLayout.JAVA_INT, ValueLayout.JAVA_INT, ValueLayout.JAVA_INT)
5050
);
5151
setPixelsPerInch = linker.downcallHandle(

tcMenuGenerator/src/main/java/com/thecoderscorner/menu/editorui/gfxui/imgedit/ImageDrawingGrid.java

Lines changed: 70 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,13 @@
99

1010
import java.util.function.BiConsumer;
1111

12+
/**
13+
* Heavily based on Adafruit_GFX code, this edits a bitmap in a way similar to how Adafruit GFX draws onto native
14+
* displays. It is done this way as we must avoid any aliasing whatsoever in graphics libraries given these bitmaps
15+
* are going to be used later potentially on native displays.
16+
*/
1217
public class ImageDrawingGrid extends Canvas {
13-
public enum DrawingMode { NONE, DOT, LINE, OUTLINE_RECT, FILLED_RECT }
18+
public enum DrawingMode { NONE, DOT, LINE, OUTLINE_RECT, FILLED_RECT, OUTLINE_CIRCLE, FLOOD_FILL }
1419
private final BmpDataManager bitmap;
1520
private final PortablePalette palette;
1621
private final boolean editMode;
@@ -33,7 +38,7 @@ public ImageDrawingGrid(BmpDataManager bitmap, PortablePalette palette, boolean
3338
setOnMousePressed(event -> {
3439
xStart = (int) (event.getX() / fitWidth * bitmap.getPixelWidth());
3540
yStart = (int) (event.getY() / fitHeight * bitmap.getPixelHeight());
36-
mode = DrawingMode.DOT;
41+
mode = currentShape == DrawingMode.FLOOD_FILL ? DrawingMode.FLOOD_FILL : DrawingMode.DOT;
3742
});
3843

3944
setOnMouseDragged(event -> {
@@ -56,28 +61,84 @@ public ImageDrawingGrid(BmpDataManager bitmap, PortablePalette palette, boolean
5661
int yEnd = (int) (event.getY() / fitHeight * bitmap.getPixelHeight());
5762
if (xEnd >= bitmap.getPixelWidth() || yEnd >= bitmap.getPixelHeight()) return;
5863
if(mode == DrawingMode.DOT) {
59-
bitmap.setDataAt(xEnd, yEnd, colorIndex);
6064
recordChange();
65+
bitmap.setDataAt(xEnd, yEnd, colorIndex);
6166
onPaintSurface(getGraphicsContext2D());
6267
} else if(mode == DrawingMode.FILLED_RECT) {
6368
recordChange();
6469
filledRectangle(bitmap, xEnd, yEnd);
70+
} else if(mode == DrawingMode.OUTLINE_CIRCLE) {
71+
recordChange();
72+
int halfX = (xEnd - xStart) / 2;
73+
drawCircle(xStart + halfX, yStart + halfX, halfX);
6574
} else if(mode == DrawingMode.OUTLINE_RECT) {
6675
recordChange();
67-
drawLine(xStart, yStart, xEnd, yStart);
68-
drawLine(xEnd, yStart, xEnd, yEnd);
69-
drawLine(xStart, yEnd, xEnd, yEnd);
70-
drawLine(xStart, yStart, xStart, yEnd);
76+
drawBoxOutline(xEnd, yEnd);
7177
} else if(mode == DrawingMode.LINE) {
7278
recordChange();
7379
drawLine(xStart, yStart, xEnd, yEnd);
80+
} else if(mode == DrawingMode.FLOOD_FILL) {
81+
recordChange();
82+
floodFill(xEnd, yEnd, bitmap.getDataAt(xEnd, yEnd));
7483
}
7584
mode = DrawingMode.NONE;
7685
onPaintSurface(getGraphicsContext2D());
7786
});
7887
}
7988
}
8089

90+
private void floodFill(int x, int y, int startingCol) {
91+
// make sure we are inside bounds, and still able to fill, IE colour is still the starting colour
92+
if(x < 0 || y < 0 || x >= bitmap.getPixelWidth() || y >= bitmap.getPixelHeight()) return;
93+
if(bitmap.getDataAt(x, y) != startingCol) return;
94+
95+
bitmap.setDataAt(x, y, colorIndex);
96+
floodFill(x, y + 1, startingCol);
97+
floodFill(x, y - 1, startingCol);
98+
floodFill(x - 1, y, startingCol);
99+
floodFill(x + 1, y, startingCol);
100+
}
101+
102+
private void drawBoxOutline(int xEnd, int yEnd) {
103+
drawLine(xStart, yStart, xEnd, yStart);
104+
drawLine(xEnd, yStart, xEnd, yEnd);
105+
drawLine(xStart, yEnd, xEnd, yEnd);
106+
drawLine(xStart, yStart, xStart, yEnd);
107+
}
108+
109+
private void drawCircle(int x0, int y0, int r) {
110+
int f = 1 - r;
111+
int ddF_x = 1;
112+
int ddF_y = -2 * r;
113+
int x = 0;
114+
int y = r;
115+
116+
bitmap.setDataAt(x0, y0 + r, colorIndex);
117+
bitmap.setDataAt(x0, y0 - r, colorIndex);
118+
bitmap.setDataAt(x0 + r, y0, colorIndex);
119+
bitmap.setDataAt(x0 - r, y0, colorIndex);
120+
121+
while (x < y) {
122+
if (f >= 0) {
123+
y--;
124+
ddF_y += 2;
125+
f += ddF_y;
126+
}
127+
x++;
128+
ddF_x += 2;
129+
f += ddF_x;
130+
131+
bitmap.setDataAt(x0 + x, y0 + y, colorIndex);
132+
bitmap.setDataAt(x0 - x, y0 + y, colorIndex);
133+
bitmap.setDataAt(x0 + x, y0 - y, colorIndex);
134+
bitmap.setDataAt(x0 - x, y0 - y, colorIndex);
135+
bitmap.setDataAt(x0 + y, y0 + x, colorIndex);
136+
bitmap.setDataAt(x0 - y, y0 + x, colorIndex);
137+
bitmap.setDataAt(x0 + y, y0 - x, colorIndex);
138+
bitmap.setDataAt(x0 - y, y0 - x, colorIndex);
139+
}
140+
}
141+
81142
private void recordChange() {
82143
dirty = true;
83144
}
@@ -190,7 +251,7 @@ protected void onPaintSurface(GraphicsContext gc) {
190251

191252
double wid = ((xNow - xStart) + 1) * perSquareX;
192253
double hei = ((yNow - yStart) + 1) * perSquareY;
193-
if(mode == DrawingMode.FILLED_RECT || mode == DrawingMode.OUTLINE_RECT) {
254+
if(mode == DrawingMode.FILLED_RECT || mode == DrawingMode.OUTLINE_RECT || mode == DrawingMode.OUTLINE_CIRCLE) {
194255
gc.strokeRect(xStart * perSquareX, yStart * perSquareY, wid, hei);
195256
} else if(mode == DrawingMode.LINE) {
196257
gc.strokeLine(xStart * perSquareX, yStart * perSquareY, xNow * perSquareX, yNow * perSquareY);
@@ -218,7 +279,7 @@ public boolean isModified() {
218279
return dirty;
219280
}
220281

221-
public void onPositionUpdate(BiConsumer<Integer, Integer> consumer) {
282+
public void setPositionUpdateListener(BiConsumer<Integer, Integer> consumer) {
222283
positionConsumer = consumer;
223284
}
224285
}
Lines changed: 77 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package com.thecoderscorner.menu.editorui.gfxui.imgedit;
22

3+
import com.thecoderscorner.embedcontrol.core.service.GlobalSettings;
34
import com.thecoderscorner.menu.domain.util.PortablePalette;
45
import com.thecoderscorner.menu.editorui.dialog.BaseDialogSupport;
56
import com.thecoderscorner.menu.editorui.gfxui.pixmgr.BmpDataManager;
@@ -14,6 +15,7 @@
1415
import javafx.scene.control.Button;
1516
import javafx.scene.control.ComboBox;
1617
import javafx.scene.control.Label;
18+
import javafx.scene.input.*;
1719
import javafx.scene.layout.BorderPane;
1820
import javafx.scene.layout.HBox;
1921
import javafx.stage.Stage;
@@ -23,13 +25,15 @@
2325
import java.nio.file.Paths;
2426
import java.util.Optional;
2527

26-
import static com.thecoderscorner.menu.editorui.gfxui.imgedit.ImageDrawingGrid.DrawingMode;
28+
import static com.thecoderscorner.menu.editorui.gfxui.imgedit.ImageDrawingGrid.DrawingMode.*;
2729
import static com.thecoderscorner.menu.editorui.gfxui.imgedit.SimpleImagePane.shortFmtText;
2830

2931
public class SimpleImageEditor {
3032
private final BmpDataManager bitmap;
3133
private final PortablePalette palette;
3234
private final NativePixelFormat format;
35+
private CurrentProjectEditorUI editorUI;
36+
private ComboBox<TextDrawingMode> modeComboBox;
3337

3438
public SimpleImageEditor(BmpDataManager bitmap, NativePixelFormat format, PortablePalette palette) {
3539
this.bitmap = bitmap;
@@ -40,38 +44,36 @@ public SimpleImageEditor(BmpDataManager bitmap, NativePixelFormat format, Portab
4044
public boolean presentUI(CurrentProjectEditorUI editorUI) {
4145
BorderPane pane = new BorderPane();
4246
pane.setOpaqueInsets(new Insets(10));
47+
this.editorUI = editorUI;
4348

4449
HBox hbox = new HBox(4);
4550
hbox.setAlignment(Pos.CENTER_LEFT);
4651
hbox.getChildren().add(new Label("Function"));
4752
pane.setTop(hbox);
4853
ImageDrawingGrid canvas = new ImageDrawingGrid(bitmap, palette, true);
49-
var modeComboBox = new ComboBox<>(FXCollections.observableArrayList(
50-
DrawingMode.LINE, DrawingMode.OUTLINE_RECT, DrawingMode.FILLED_RECT
54+
modeComboBox = new ComboBox<>(FXCollections.observableArrayList(
55+
new TextDrawingMode("Pixel - D", DOT),
56+
new TextDrawingMode("Line - L", LINE),
57+
new TextDrawingMode("Box Outline - R", OUTLINE_RECT),
58+
new TextDrawingMode("Box Filled - B", FILLED_RECT),
59+
new TextDrawingMode("Circle - I", OUTLINE_CIRCLE),
60+
new TextDrawingMode("Flood Fill - F", FLOOD_FILL)
5161
));
5262
modeComboBox.getSelectionModel().select(0);
53-
modeComboBox.setOnAction(_ -> canvas.setCurrentShape(modeComboBox.getValue()));
63+
modeComboBox.setOnAction(_ -> canvas.setCurrentShape(modeComboBox.getValue().mode()));
5464
hbox.getChildren().add(modeComboBox);
5565

5666
hbox.getChildren().add(new Label("Palette"));
5767
UIColorPaletteControl paletteControl = new UIColorPaletteControl();
5868
hbox.getChildren().add(paletteControl.swatchControl(palette, canvas::setCurrentColor));
5969

70+
var copyButton = new Button("Copy");
71+
copyButton.setOnAction(_ -> copyContents());
6072
var saveButton = new Button("Save");
61-
saveButton.setOnAction(_ -> {
62-
var file = editorUI.findFileNameFromUser(Optional.empty(), false, "*.png");
63-
if(file.isEmpty()) return;
64-
try {
65-
var img = bitmap.createImageFromBitmap(palette);
66-
ImageIO.write(SwingFXUtils.fromFXImage(img, null), "png", Paths.get(file.get()).toFile());
67-
} catch (IOException e) {
68-
editorUI.alertOnError("Error Saving Image", "An error occurred while saving the image.");
69-
}
70-
});
71-
hbox.getChildren().add(saveButton);
73+
saveButton.setOnAction(_ -> saveContents());
7274
var xyLabel = new Label("");
73-
canvas.onPositionUpdate((x, y) -> xyLabel.setText(STR."X=\{x}, Y=\{y}"));
74-
hbox.getChildren().add(xyLabel);
75+
canvas.setPositionUpdateListener((x, y) -> xyLabel.setText(STR."X=\{x}, Y=\{y}"));
76+
hbox.getChildren().addAll(copyButton, saveButton, xyLabel);
7577

7678
canvas.widthProperty().bind(pane.widthProperty());
7779
canvas.heightProperty().bind(pane.heightProperty().multiply(0.9));
@@ -81,8 +83,32 @@ public boolean presentUI(CurrentProjectEditorUI editorUI) {
8183

8284
pane.setCenter(canvas);
8385
pane.getStyleClass().add("background");
86+
pane.setStyle(STR."-fx-font-size: \{GlobalSettings.defaultFontSize()}");
8487

8588
Scene scene = new Scene(pane);
89+
90+
KeyCombination copyPressed = new KeyCodeCombination(KeyCode.C, KeyCombination.SHORTCUT_DOWN);
91+
KeyCombination savePressed = new KeyCodeCombination(KeyCode.S, KeyCombination.SHORTCUT_DOWN);
92+
KeyCombination dotPressed = new KeyCodeCombination(KeyCode.D);
93+
KeyCombination linePressed = new KeyCodeCombination(KeyCode.L);
94+
KeyCombination circlePressed = new KeyCodeCombination(KeyCode.I);
95+
KeyCombination boxPressed = new KeyCodeCombination(KeyCode.B);
96+
KeyCombination rectPressed = new KeyCodeCombination(KeyCode.R);
97+
KeyCombination fillPressed = new KeyCodeCombination(KeyCode.F);
98+
scene.addEventFilter(KeyEvent.KEY_PRESSED, ke -> {
99+
if (copyPressed.match(ke)) {
100+
copyContents();
101+
ke.consume(); // <-- stops passing the event to next node
102+
} else if(savePressed.match(ke)) {
103+
saveContents();
104+
ke.consume(); // <-- stops passing the event to next node
105+
} else if(dotPressed.match(ke)) changeShape(DOT);
106+
else if(linePressed.match(ke)) changeShape(LINE);
107+
else if(circlePressed.match(ke)) changeShape(OUTLINE_CIRCLE);
108+
else if(boxPressed.match(ke)) changeShape(FILLED_RECT);
109+
else if(rectPressed.match(ke)) changeShape(OUTLINE_RECT);
110+
else if(fillPressed.match(ke)) changeShape(FLOOD_FILL);
111+
});
86112
Stage stage = new Stage();
87113
stage.setMaximized(true);
88114
stage.setWidth(800);
@@ -93,4 +119,38 @@ public boolean presentUI(CurrentProjectEditorUI editorUI) {
93119
stage.showAndWait();
94120
return canvas.isModified();
95121
}
122+
123+
private void changeShape(ImageDrawingGrid.DrawingMode drawingMode) {
124+
for(int i=0;i<modeComboBox.getItems().size(); i++) {
125+
if (modeComboBox.getItems().get(i).mode() == drawingMode) {
126+
modeComboBox.getSelectionModel().select(i);
127+
break;
128+
}
129+
}
130+
}
131+
132+
private void saveContents() {
133+
var file = editorUI.findFileNameFromUser(Optional.empty(), false, "*.png");
134+
if(file.isEmpty()) return;
135+
try {
136+
var img = bitmap.createImageFromBitmap(palette);
137+
ImageIO.write(SwingFXUtils.fromFXImage(img, null), "png", Paths.get(file.get()).toFile());
138+
} catch (IOException e) {
139+
editorUI.alertOnError("Error Saving Image", "An error occurred while saving the image.");
140+
}
141+
}
142+
143+
private void copyContents() {
144+
Clipboard clipboard = Clipboard.getSystemClipboard();
145+
ClipboardContent content = new ClipboardContent();
146+
content.putImage(bitmap.createImageFromBitmap(palette));
147+
clipboard.setContent(content);
148+
}
149+
150+
record TextDrawingMode(String name, ImageDrawingGrid.DrawingMode mode) {
151+
@Override
152+
public String toString() {
153+
return name;
154+
}
155+
}
96156
}

tcMenuGenerator/src/main/java/com/thecoderscorner/menu/editorui/gfxui/pixmgr/UIColorPaletteControl.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,8 @@ private static void populateColorsIntoBox(PortablePalette pal, ArrayList<Rectang
9292
hBox.getChildren().clear();
9393
listOfSwatches.clear();
9494
for(var col : pal.getColorArray()) {
95-
var r = new Rectangle(20, 20, ControlColor.asFxColor(col));
95+
int size = GlobalSettings.defaultFontSize() * 2;
96+
var r = new Rectangle(size, size, ControlColor.asFxColor(col));
9697
listOfSwatches.add(r);
9798
hBox.getChildren().add(r);
9899
}

tcMenuGenerator/src/main/resources/ui/createFontPanel.fxml

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -51,19 +51,19 @@
5151
</Label>
5252
<ComboBox fx:id="fontStyleCombo" onAction="#onFontStyleChanged" maxWidth="99999"
5353
GridPane.columnIndex="3" GridPane.rowIndex="3"/>
54-
<ScrollPane prefHeight="200.0" prefWidth="200.0" GridPane.columnSpan="4" GridPane.rowIndex="5">
54+
<ScrollPane prefHeight="200.0" prefWidth="200.0" maxWidth="9999" fitToWidth="true" GridPane.columnSpan="4" GridPane.rowIndex="5">
5555
<GridPane fx:id="fontRenderArea">
5656
<columnConstraints>
57-
<ColumnConstraints hgrow="SOMETIMES" />
58-
<ColumnConstraints hgrow="SOMETIMES" />
59-
<ColumnConstraints hgrow="SOMETIMES" />
60-
<ColumnConstraints hgrow="SOMETIMES" />
61-
<ColumnConstraints hgrow="SOMETIMES" />
62-
<ColumnConstraints hgrow="SOMETIMES" />
63-
<ColumnConstraints hgrow="SOMETIMES" />
64-
<ColumnConstraints hgrow="SOMETIMES" />
65-
<ColumnConstraints hgrow="SOMETIMES" />
66-
<ColumnConstraints hgrow="SOMETIMES" />
57+
<ColumnConstraints hgrow="SOMETIMES" percentWidth="9.8"/>
58+
<ColumnConstraints hgrow="SOMETIMES" percentWidth="9.8"/>
59+
<ColumnConstraints hgrow="SOMETIMES" percentWidth="9.8"/>
60+
<ColumnConstraints hgrow="SOMETIMES" percentWidth="9.8"/>
61+
<ColumnConstraints hgrow="SOMETIMES" percentWidth="9.8"/>
62+
<ColumnConstraints hgrow="SOMETIMES" percentWidth="9.8"/>
63+
<ColumnConstraints hgrow="SOMETIMES" percentWidth="9.8"/>
64+
<ColumnConstraints hgrow="SOMETIMES" percentWidth="9.8"/>
65+
<ColumnConstraints hgrow="SOMETIMES" percentWidth="9.8"/>
66+
<ColumnConstraints hgrow="SOMETIMES" percentWidth="9.8"/>
6767
</columnConstraints>
6868
<rowConstraints>
6969
<RowConstraints vgrow="SOMETIMES"/>

0 commit comments

Comments
 (0)