Skip to content

Commit 1066dac

Browse files
committed
#446 improved font and bitmap editor
1 parent b587427 commit 1066dac

File tree

6 files changed

+292
-29
lines changed

6 files changed

+292
-29
lines changed

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

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import com.thecoderscorner.menu.domain.state.PortableColor;
44
import com.thecoderscorner.menu.domain.util.PortablePalette;
55
import com.thecoderscorner.menu.editorui.dialog.AppInformationPanel;
6+
import com.thecoderscorner.menu.editorui.gfxui.imgedit.SimpleImageEditor;
67
import com.thecoderscorner.menu.editorui.gfxui.imgedit.SimpleImagePane;
78
import com.thecoderscorner.menu.editorui.gfxui.pixmgr.*;
89
import com.thecoderscorner.menu.editorui.uimodel.CurrentProjectEditorUI;
@@ -90,13 +91,20 @@ private void refreshGridComponents() {
9091
LoadedImage img = loadedImages.get(i);
9192
var editButton = new Button("edit");
9293
var removeButton = new Button("remove");
94+
var buttons = List.of(editButton, removeButton);
95+
var imageView = new SimpleImagePane(img.bmpData(), img.pixelFormat(),false, img.palette(), buttons);
96+
97+
editButton.setOnAction(_ -> {
98+
var editor = new SimpleImageEditor(img.bmpData(), img.pixelFormat(), img.palette());
99+
if(editor.presentUI(editorUI)) {
100+
imageView.invalidate();
101+
}
102+
});
93103
removeButton.setOnAction(_ -> {
94104
loadedImages.remove(img);
95105
refreshGridComponents();
96106
refreshButtonStates();
97107
});
98-
var buttons = List.of(editButton, removeButton);
99-
var imageView = new SimpleImagePane(img.bmpData(), img.pixelFormat(),false, img.palette(), buttons);
100108
GridPane.setConstraints(imageView, i % cols, i / cols);
101109
imageGridPane.getChildren().add(imageView);
102110
}
@@ -179,7 +187,7 @@ public void onCreateWidget(ActionEvent ignoredActionEvent) {
179187
exportSuccessful(maybeName.get());
180188
} catch (Exception e) {
181189
logger.log(System.Logger.Level.ERROR, "File could not be written", e);
182-
editorUI.alertOnError("Not exported to file", "Not exported to file " + e.getMessage());
190+
editorUI.alertOnError("Not exported to file", STR."Not exported to file \{e.getMessage()}");
183191
}
184192
}
185193
}
@@ -212,7 +220,7 @@ public void onCreateBitmaps(ActionEvent ignoredActionEvent) {
212220
}
213221
catch (Exception e) {
214222
logger.log(System.Logger.Level.ERROR, "Could not put file content on clipboard", e);
215-
editorUI.alertOnError("Not exported to Clipboard", "Not exported to Clipboard " + e.getMessage());
223+
editorUI.alertOnError("Not exported to Clipboard", STR."Not exported to Clipboard \{e.getMessage()}");
216224
}
217225
return;
218226
}
@@ -223,7 +231,7 @@ public void onCreateBitmaps(ActionEvent ignoredActionEvent) {
223231
exportSuccessful(maybeName.get());
224232
} catch (Exception e) {
225233
logger.log(System.Logger.Level.ERROR, "File could not be written", e);
226-
editorUI.alertOnError("File not written", "Error while writing file " + e.getMessage());
234+
editorUI.alertOnError("File not written", STR."Error while writing file \{e.getMessage()}");
227235
}
228236
}
229237
}
Lines changed: 166 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,155 @@
11
package com.thecoderscorner.menu.editorui.gfxui.imgedit;
22

3+
import com.thecoderscorner.embedcontrol.core.controlmgr.color.ControlColor;
4+
import com.thecoderscorner.menu.domain.util.PortablePalette;
5+
import com.thecoderscorner.menu.editorui.gfxui.pixmgr.BmpDataManager;
36
import javafx.scene.canvas.Canvas;
47
import javafx.scene.canvas.GraphicsContext;
5-
import javafx.scene.image.Image;
68
import javafx.scene.paint.Color;
79

810
public class ImageDrawingGrid extends Canvas {
9-
private final Image image;
11+
public enum DrawingMode { NONE, DOT, LINE, OUTLINE_RECT, FILLED_RECT }
12+
private final BmpDataManager bitmap;
13+
private final PortablePalette palette;
1014
private final boolean editMode;
15+
private DrawingMode mode = DrawingMode.NONE;
16+
private DrawingMode currentShape = DrawingMode.LINE;
17+
private int colorIndex = 0;
1118
private double fitWidth;
1219
private double fitHeight;
20+
private int xStart, yStart;
21+
private int xNow, yNow;
22+
private boolean dirty = false;
1323

14-
public ImageDrawingGrid(Image image, boolean editMode) {
15-
this.image = image;
24+
public ImageDrawingGrid(BmpDataManager bitmap, PortablePalette palette, boolean editMode) {
25+
this.bitmap = bitmap;
26+
this.palette = palette;
1627
this.editMode = editMode;
28+
29+
if(editMode) {
30+
setOnMousePressed(event -> {
31+
xStart = (int) (event.getX() / fitWidth * bitmap.getPixelWidth());
32+
yStart = (int) (event.getY() / fitHeight * bitmap.getPixelHeight());
33+
mode = DrawingMode.DOT;
34+
});
35+
36+
setOnMouseDragged(event -> {
37+
mode = currentShape;
38+
39+
xNow = (int) (event.getX() / fitWidth * bitmap.getPixelWidth());
40+
yNow = (int) (event.getY() / fitHeight * bitmap.getPixelHeight());
41+
if (xNow >= bitmap.getPixelWidth() || yNow >= bitmap.getPixelHeight()) return;
42+
onPaintSurface(getGraphicsContext2D());
43+
});
44+
45+
setOnMouseReleased(event -> {
46+
int xEnd = (int) (event.getX() / fitWidth * bitmap.getPixelWidth());
47+
int yEnd = (int) (event.getY() / fitHeight * bitmap.getPixelHeight());
48+
if (xEnd >= bitmap.getPixelWidth() || yEnd >= bitmap.getPixelHeight()) return;
49+
if(mode == DrawingMode.DOT) {
50+
bitmap.setDataAt(xEnd, yEnd, colorIndex);
51+
recordChange();
52+
onPaintSurface(getGraphicsContext2D());
53+
} else if(mode == DrawingMode.FILLED_RECT) {
54+
recordChange();
55+
filledRectangle(bitmap, xEnd, yEnd);
56+
} else if(mode == DrawingMode.OUTLINE_RECT) {
57+
recordChange();
58+
drawLine(xStart, yStart, xEnd, yStart);
59+
drawLine(xEnd, yStart, xEnd, yEnd);
60+
drawLine(xStart, yEnd, xEnd, yEnd);
61+
drawLine(xStart, yStart, xStart, yEnd);
62+
} else if(mode == DrawingMode.LINE) {
63+
recordChange();
64+
drawLine(xStart, yStart, xEnd, yEnd);
65+
}
66+
mode = DrawingMode.NONE;
67+
onPaintSurface(getGraphicsContext2D());
68+
});
69+
}
70+
}
71+
72+
private void recordChange() {
73+
dirty = true;
74+
}
75+
76+
void setCurrentShape(DrawingMode shape) {
77+
currentShape = shape;
78+
}
79+
80+
private void filledRectangle(BmpDataManager bitmap, int xEnd, int yEnd) {
81+
int xMin = Math.min(xStart, xEnd);
82+
int yMin = Math.min(yStart, yEnd);
83+
int xMax = Math.max(xStart, xEnd);
84+
int yMax = Math.max(yStart, yEnd);
85+
for (int x = xMin; x <= xMax; x++) {
86+
for (int y = yMin; y <= yMax; y++) {
87+
bitmap.setDataAt(x, y, colorIndex);
88+
}
89+
}
90+
}
91+
92+
private void drawLine(int x0, int y0, int x1, int y1) {
93+
// roughly copied from Adafruit_GFX, Thanks!
94+
boolean steep = Math.abs(y1 - y0) > Math.abs(x1 - x0);
95+
if (steep) {
96+
var tmp = y0;
97+
y0 = x0;
98+
x0 = tmp;
99+
100+
tmp = y1;
101+
y1 = x1;
102+
x1 = tmp;
103+
}
104+
105+
if (x0 > x1) {
106+
var tmp = x0;
107+
x0 = x1;
108+
x1 = tmp;
109+
110+
tmp = y0;
111+
y0 = y1;
112+
y1 = tmp;
113+
}
114+
115+
int dx, dy;
116+
dx = x1 - x0;
117+
dy = Math.abs(y1 - y0);
118+
119+
int err = dx / 2;
120+
int ystep;
121+
122+
if (y0 < y1) {
123+
ystep = 1;
124+
} else {
125+
ystep = -1;
126+
}
127+
128+
for (; x0 <= x1; x0++) {
129+
if (steep) {
130+
bitmap.setDataAt(y0, x0, colorIndex);
131+
} else {
132+
bitmap.setDataAt(x0, y0, colorIndex);
133+
}
134+
err -= dy;
135+
if (err < 0) {
136+
y0 += ystep;
137+
err += dx;
138+
}
139+
}
140+
}
141+
142+
public void setCurrentColor(int paletteIdx) {
143+
colorIndex = paletteIdx;
17144
}
18145

19146
public boolean isResizable() {
20147
return true;
21148
}
22149

23150
void prepareRatios() {
24-
double ratio = image.getWidth() / image.getHeight();
25-
if (image.getWidth() > image.getHeight()) {
151+
double ratio = (double) bitmap.getPixelWidth() / (double) bitmap.getPixelHeight();
152+
if (bitmap.getPixelWidth() > bitmap.getPixelHeight()) {
26153
double maxWid = this.getWidth();
27154
fitWidth = maxWid;
28155
fitHeight = (maxWid / ratio);
@@ -35,31 +162,50 @@ void prepareRatios() {
35162

36163
protected void onPaintSurface(GraphicsContext gc) {
37164
prepareRatios();
38-
var perSquareX = fitWidth / image.getWidth();
39-
var perSquareY = fitHeight / image.getHeight();
165+
var perSquareX = fitWidth / bitmap.getPixelWidth();
166+
var perSquareY = fitHeight / bitmap.getPixelHeight();
40167

41168
// first render the image as it appears.
42-
var pixReader = image.getPixelReader();
43-
for (int y = 0; y < image.getHeight(); y++) {
44-
for (int x = 0; x < image.getWidth(); x++) {
45-
gc.setFill(pixReader.getColor(x, y));
46-
gc.fillRect(x * perSquareX, y * perSquareY, perSquareX, perSquareY);
169+
for (int y = 0; y < bitmap.getPixelHeight(); y++) {
170+
for (int x = 0; x < bitmap.getPixelWidth(); x++) {
171+
gc.setFill(ControlColor.asFxColor(palette.getColorAt(bitmap.getDataAt(x, y))));
172+
gc.fillRect(x * perSquareX, y * perSquareY, perSquareX + 0.6, perSquareY + 0.6);
47173
}
48174
}
49175

176+
if(!editMode) return;
177+
178+
gc.setStroke(Color.BLUE);
179+
gc.setLineWidth(3);
180+
gc.setLineDashes(10, 3);
181+
182+
double wid = ((xNow - xStart) + 1) * perSquareX;
183+
double hei = ((yNow - yStart) + 1) * perSquareY;
184+
if(mode == DrawingMode.FILLED_RECT || mode == DrawingMode.OUTLINE_RECT) {
185+
gc.strokeRect(xStart * perSquareX, yStart * perSquareY, wid, hei);
186+
} else if(mode == DrawingMode.LINE) {
187+
gc.strokeLine(xStart * perSquareX, yStart * perSquareY, xNow * perSquareX, yNow * perSquareY);
188+
}
189+
50190
// only use grid squares if the magnification is sufficient.
51191
if(perSquareX > 3 && perSquareY > 3) {
192+
gc.setLineDashes();
193+
gc.setStroke(Color.LIGHTGRAY);
194+
gc.setLineWidth(1);
195+
gc.setLineDashes(4, 2);
196+
52197
// now draw the grid ines into the image
53-
gc.setFill(Color.GRAY);
54-
for (int y = 0; y < image.getHeight(); y++) {
55-
gc.moveTo(0, y * perSquareY);
56-
gc.lineTo(this.getHeight(), y * perSquareY);
198+
for (int y = 0; y < bitmap.getPixelHeight(); y++) {
199+
gc.strokeLine(0, y * perSquareY, fitWidth, y * perSquareY);
57200
}
58201

59-
for (int x = 0; x < image.getWidth(); x++) {
60-
gc.moveTo(x * perSquareX, 0);
61-
gc.lineTo(x * perSquareX, this.getWidth());
202+
for (int x = 0; x < bitmap.getPixelWidth(); x++) {
203+
gc.strokeLine(x * perSquareX, 0, x * perSquareX, fitHeight);
62204
}
63205
}
64206
}
207+
208+
public boolean isModified() {
209+
return dirty;
210+
}
65211
}
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
package com.thecoderscorner.menu.editorui.gfxui.imgedit;
2+
3+
import com.thecoderscorner.menu.domain.util.PortablePalette;
4+
import com.thecoderscorner.menu.editorui.gfxui.pixmgr.BmpDataManager;
5+
import com.thecoderscorner.menu.editorui.gfxui.pixmgr.NativePixelFormat;
6+
import com.thecoderscorner.menu.editorui.gfxui.pixmgr.UIColorPaletteControl;
7+
import com.thecoderscorner.menu.editorui.uimodel.CurrentProjectEditorUI;
8+
import javafx.collections.FXCollections;
9+
import javafx.embed.swing.SwingFXUtils;
10+
import javafx.geometry.Insets;
11+
import javafx.geometry.Pos;
12+
import javafx.scene.Scene;
13+
import javafx.scene.control.Button;
14+
import javafx.scene.control.ComboBox;
15+
import javafx.scene.control.Label;
16+
import javafx.scene.layout.BorderPane;
17+
import javafx.scene.layout.HBox;
18+
import javafx.stage.Stage;
19+
20+
import javax.imageio.ImageIO;
21+
import java.io.IOException;
22+
import java.nio.file.Paths;
23+
import java.util.Optional;
24+
25+
import static com.thecoderscorner.menu.editorui.gfxui.imgedit.ImageDrawingGrid.DrawingMode;
26+
import static com.thecoderscorner.menu.editorui.gfxui.imgedit.SimpleImagePane.shortFmtText;
27+
28+
public class SimpleImageEditor {
29+
private final BmpDataManager bitmap;
30+
private final PortablePalette palette;
31+
private final NativePixelFormat format;
32+
33+
public SimpleImageEditor(BmpDataManager bitmap, NativePixelFormat format, PortablePalette palette) {
34+
this.bitmap = bitmap;
35+
this.palette = palette;
36+
this.format = format;
37+
}
38+
39+
public boolean presentUI(CurrentProjectEditorUI editorUI) {
40+
BorderPane pane = new BorderPane();
41+
pane.setOpaqueInsets(new Insets(10));
42+
43+
HBox hbox = new HBox(4);
44+
hbox.setAlignment(Pos.CENTER_LEFT);
45+
hbox.getChildren().add(new Label("Toolbar"));
46+
pane.setTop(hbox);
47+
ImageDrawingGrid canvas = new ImageDrawingGrid(bitmap, palette, true);
48+
var modeComboBox = new ComboBox<>(FXCollections.observableArrayList(
49+
DrawingMode.LINE, DrawingMode.OUTLINE_RECT, DrawingMode.FILLED_RECT
50+
));
51+
modeComboBox.getSelectionModel().select(0);
52+
modeComboBox.setOnAction(_ -> canvas.setCurrentShape(modeComboBox.getValue()));
53+
hbox.getChildren().add(modeComboBox);
54+
UIColorPaletteControl paletteControl = new UIColorPaletteControl();
55+
hbox.getChildren().add(paletteControl.swatchControl(palette, canvas::setCurrentColor));
56+
57+
var saveButton = new Button("Save");
58+
saveButton.setOnAction(_ -> {
59+
var file = editorUI.findFileNameFromUser(Optional.empty(), false, "*.png");
60+
if(file.isEmpty()) return;
61+
try {
62+
var img = bitmap.createImageFromBitmap(palette);
63+
ImageIO.write(SwingFXUtils.fromFXImage(img, null), "png", Paths.get(file.get()).toFile());
64+
} catch (IOException e) {
65+
editorUI.alertOnError("Error Saving Image", "An error occurred while saving the image.");
66+
}
67+
});
68+
hbox.getChildren().add(saveButton);
69+
70+
71+
canvas.widthProperty().bind(pane.widthProperty());
72+
canvas.heightProperty().bind(pane.heightProperty().multiply(0.9));
73+
74+
pane.widthProperty().addListener((_) -> canvas.onPaintSurface(canvas.getGraphicsContext2D()));
75+
pane.heightProperty().addListener((_) -> canvas.onPaintSurface(canvas.getGraphicsContext2D()));
76+
77+
pane.setCenter(canvas);
78+
79+
Scene scene = new Scene(pane);
80+
Stage stage = new Stage();
81+
stage.setScene(scene);
82+
stage.setTitle(STR."Bitmap Editor \{shortFmtText(format)} \{bitmap.getPixelWidth()} x \{bitmap.getPixelHeight()}");
83+
stage.showAndWait();
84+
return canvas.isModified();
85+
}
86+
}

0 commit comments

Comments
 (0)