33import jakarta .annotation .Nonnull ;
44import javafx .scene .canvas .Canvas ;
55import javafx .scene .image .ImageView ;
6- import javafx .scene .image .PixelFormat ;
76import javafx .scene .image .WritableImage ;
87import 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[]}.
1815public 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