Skip to content

Commit a2cb252

Browse files
authored
Merge pull request #2 from xxDark/master
Add PixelPainter to reduce memory churn
2 parents c67faab + c1943d8 commit a2cb252

File tree

2 files changed

+336
-82
lines changed

2 files changed

+336
-82
lines changed

src/main/java/software/coley/bentofx/control/PixelCanvas.java

Lines changed: 50 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,9 @@
33
import jakarta.annotation.Nonnull;
44
import javafx.scene.canvas.Canvas;
55
import javafx.scene.image.ImageView;
6-
import javafx.scene.image.PixelFormat;
76
import javafx.scene.image.WritableImage;
87
import javafx.scene.layout.Region;
98

10-
import java.util.Arrays;
11-
129
/**
1310
* This is a very simple alternative to {@link Canvas} that <i>does not</i> keep track of draw operations.
1411
* In some super niche cases, the default canvas is a memory hog, where this operates on a flat ARGB {@code int[]}.
@@ -18,25 +15,25 @@
1815
public class PixelCanvas extends Region {
1916
private static final int OP_RECT_FILL = 100;
2017
private static final int OP_PX_SET = 200;
18+
/** Pixel painter. */
19+
private final PixelPainter pixelPainter;
2120
/** Wrapped display. */
2221
private final ImageView view = new ImageView();
23-
/** The image to draw with. */
22+
/** The image to display. */
2423
private WritableImage image;
25-
/** ARGB pixel buffer to draw with. */
26-
private int[] drawBuffer;
27-
/** Current width of {@link #image}. */
28-
private int imageWidth;
29-
/** Current height of {@link #image}. */
30-
private int imageHeight;
3124
/** Last committed draw hash. */
3225
private int lastDrawHash;
3326
/** Current draw hash. */
3427
private int currentDrawHash;
3528

3629
/**
3730
* New pixel canvas.
31+
*
32+
* @param pixelPainter
33+
* Painter to draw pixels.
3834
*/
39-
public PixelCanvas() {
35+
public PixelCanvas(@Nonnull PixelPainter pixelPainter) {
36+
this.pixelPainter = pixelPainter;
4037
getChildren().add(view);
4138

4239
view.fitWidthProperty().bind(widthProperty());
@@ -46,15 +43,25 @@ public PixelCanvas() {
4643
heightProperty().addListener((ob, old, cur) -> markDirty());
4744
}
4845

46+
/**
47+
* New pixel canvas.
48+
*/
49+
public PixelCanvas() {
50+
this(new PixelPainter());
51+
}
52+
4953
/**
5054
* New pixel canvas.
5155
*
56+
* @param pixelPainter
57+
* Painter to draw pixels.
5258
* @param width
5359
* Assigned width.
5460
* @param height
5561
* Assigned height.
5662
*/
57-
public PixelCanvas(int width, int height) {
63+
public PixelCanvas(@Nonnull PixelPainter pixelPainter, int width, int height) {
64+
this.pixelPainter = pixelPainter;
5865
getChildren().add(view);
5966

6067
setMinSize(width, height);
@@ -65,15 +72,27 @@ public PixelCanvas(int width, int height) {
6572
view.setFitHeight(height);
6673
}
6774

75+
/**
76+
* New pixel canvas.
77+
*
78+
* @param width
79+
* Assigned width.
80+
* @param height
81+
* Assigned height.
82+
*/
83+
public PixelCanvas(int width, int height) {
84+
this(new PixelPainter(), width, height);
85+
}
86+
6887
/**
6988
* Commits any pending state in the canvas buffer to the display.
7089
*/
7190
public void commit() {
7291
if (lastDrawHash == currentDrawHash) return;
7392
lastDrawHash = currentDrawHash;
7493

75-
checkDirty();
76-
image.getPixelWriter().setPixels(0, 0, imageWidth, imageHeight, PixelFormat.getIntArgbInstance(), drawBuffer, 0, imageWidth);
94+
pixelPainter.commit(image.getPixelWriter());
95+
view.setImage(image);
7796
}
7897

7998
/**
@@ -133,10 +152,7 @@ public void fillRect(double x, double y, double width, double height, int color)
133152
*/
134153
public void fillRect(int x, int y, int width, int height, int color) {
135154
updateDrawHash(hash(OP_RECT_FILL, x, y, width, height));
136-
adaptEach(x, y, width, height, (lx, ly, i) -> {
137-
if (i >= 0 && i < drawBuffer.length)
138-
drawBuffer[i] = color;
139-
});
155+
pixelPainter.fillRect(x, y, width, height, color);
140156
}
141157

142158
/**
@@ -176,10 +192,8 @@ public void drawRect(double x, double y, double width, double height, double bor
176192
* Color to draw.
177193
*/
178194
public void drawRect(int x, int y, int width, int height, int borderSize, int color) {
179-
fillRect(x, y, width, borderSize, color);
180-
fillRect(x, y + height - borderSize, width, borderSize, color);
181-
fillRect(x, y + borderSize, borderSize, height - borderSize, color);
182-
fillRect(x + width - borderSize, y + borderSize, borderSize, height - borderSize, color);
195+
updateDrawHash(hash(x, y, width, height, borderSize, color));
196+
pixelPainter.drawRect(x, y, width, height, borderSize, color);
183197
}
184198

185199
/**
@@ -215,7 +229,8 @@ public void drawHorizontalLine(double x, double y, double lineLength, double lin
215229
* Color to draw.
216230
*/
217231
public void drawHorizontalLine(int x, int y, int lineLength, int lineWidth, int color) {
218-
fillRect(x, y - Math.max(1, lineWidth / 2), lineLength, lineWidth, color);
232+
updateDrawHash(hash(x, y, lineLength, lineWidth, color));
233+
pixelPainter.drawHorizontalLine(x, y, lineLength, lineWidth, color);
219234
}
220235

221236
/**
@@ -251,7 +266,8 @@ public void drawVerticalLine(double x, double y, double lineLength, double lineW
251266
* Color to draw.
252267
*/
253268
public void drawVerticalLine(int x, int y, int lineLength, int lineWidth, int color) {
254-
fillRect(x - Math.max(1, lineWidth / 2), y, lineWidth, lineLength, color);
269+
updateDrawHash(hash(x, y, lineLength, lineWidth, color));
270+
pixelPainter.drawVerticalLine(x, y, lineLength, lineWidth, color);
255271
}
256272

257273
/**
@@ -266,9 +282,7 @@ public void drawVerticalLine(int x, int y, int lineLength, int lineWidth, int co
266282
*/
267283
public void setColor(int x, int y, int color) {
268284
updateDrawHash(hash(OP_PX_SET, x, y, color));
269-
int i = adapt(x, y);
270-
if (i >= 0 && i < drawBuffer.length)
271-
drawBuffer[i] = color;
285+
pixelPainter.setColor(x, y, color);
272286
}
273287

274288
/**
@@ -277,7 +291,7 @@ public void setColor(int x, int y, int color) {
277291
public void clear() {
278292
currentDrawHash = 0;
279293
checkDirty();
280-
Arrays.fill(drawBuffer, 0);
294+
pixelPainter.clear();
281295
}
282296

283297
/**
@@ -320,12 +334,13 @@ public boolean isDirty() {
320334
*/
321335
public void markDirty() {
322336
image = null;
337+
currentDrawHash = ~lastDrawHash;
323338
}
324339

325340
/**
326341
* Check if {@code dirty} and allocates a new buffer when dirty.
327342
*/
328-
protected void checkDirty() {
343+
public void checkDirty() {
329344
if (isDirty())
330345
reallocate();
331346
}
@@ -334,60 +349,13 @@ protected void checkDirty() {
334349
* Allocate the image and associated values.
335350
*/
336351
protected void reallocate() {
337-
imageWidth = (int) Math.max(1, getWidth());
338-
imageHeight = (int) Math.max(1, getHeight());
339-
image = new WritableImage(imageWidth, imageHeight);
340-
drawBuffer = new int[imageWidth * imageHeight];
341-
view.setImage(image);
342-
}
343-
344-
/**
345-
* @param x
346-
* Canvas x coordinate.
347-
* @param y
348-
* Canvas y coordinate.
349-
*
350-
* @return Offset into {@link #drawBuffer}.
351-
*/
352-
protected int adapt(int x, int y) {
353-
return (y * imageWidth) + x;
354-
}
352+
int imageWidth = (int) Math.max(1, getWidth());
353+
int imageHeight = (int) Math.max(1, getHeight());
354+
pixelPainter.initialize(imageWidth, imageHeight);
355355

356-
/**
357-
* @param x
358-
* Rect x coordinate.
359-
* @param y
360-
* Rect y coordinate.
361-
* @param width
362-
* Rect width.
363-
* @param height
364-
* Rect height.
365-
* @param consumer
366-
* Consumer to operate on all locations in the given rectangle.
367-
*/
368-
protected void adaptEach(int x, int y, int width, int height, @Nonnull XYConsumer consumer) {
369-
int yBound = Math.min(y + height, imageHeight);
370-
int xBound = Math.min(x + width, imageWidth);
371-
for (int ly = y; ly < yBound; ly++) {
372-
int yOffset = ly * imageWidth;
373-
for (int lx = x; lx < xBound; lx++) {
374-
consumer.consume(lx, ly, yOffset + lx);
375-
}
356+
WritableImage image = this.image;
357+
if (image == null || imageWidth != image.getWidth() || imageHeight != image.getHeight()) {
358+
this.image = new WritableImage(imageWidth, imageHeight);
376359
}
377360
}
378-
379-
/**
380-
* @see #adaptEach(int, int, int, int, XYConsumer)
381-
*/
382-
protected interface XYConsumer {
383-
/**
384-
* @param x
385-
* Canvas x coordinate.
386-
* @param y
387-
* Canvas y coordinate.
388-
* @param xy
389-
* Offset into {@link #drawBuffer}.
390-
*/
391-
void consume(int x, int y, int xy);
392-
}
393361
}

0 commit comments

Comments
 (0)