1

I am getting my feet wet with JavaFX, and have a simple drawing program which writes to a Canvas using a PixelWriter. The program draws a pixel at a time, reflecting each pixel over a number of axes to create a growing and evolving pattern centered on the canvas:

enter image description here

The Canvas is in the Center region of a BorderPane, and I have written the code to resize the canvas when the application window is resized. That works OK.

However, I would like to re-center the image on the new resized canvas so that the drawing can continue to grow on the larger canvas. What might be the best approach?

My ideas/attempts so far:

  • Capture a snapshot of the canvas and write it back to the resized canvas, but that comes out blurry (a couple of code examples below).
  • I dug into GraphicsContext translations, but that does not seem to move the existing image, just adjusts future drawing.
  • Maybe instead of resizing the canvas, I make a huge canvas bigger than I would expect my app window to be, and center it over the center region of the border pane (perhaps using a viewport of some kind?) I'm not thrilled about the whole idea of making some arbitrarily huge canvas that I think will be big enough though. I don't want to get into scaling - I am using PixelWriter so that I get the crispest image without antialiasing and other processing.

My snapshot attempt looked like this, but was blurry:

SnapshotParameters params = new SnapshotParameters();
params.setFill(Color.WHITE);
WritableImage image = canvas.snapshot(params, null);
canvas.getGraphicsContext2D().drawImage(image, 50, 50);

The 50, 50 offset above is just for my testing/learning - I'll replace it with a proper computed offset once I get the basic copy working. From the post How to copy contents of one canvas to another? I played with the setFill() parameter, to no effect.

From the post How to save a high DPI snapshot of a JavaFX Canvas I tried the following code. It was more clear, but I have not been able to figure out how to find or compute the pixelScale to get the most accurate snapshot (the value 10 is just some number I typed in bigger than 1 to see how it reacted):

int pixelScale = 10;
WritableImage image = new WritableImage((int)Math.rint(pixelScale * canvas.getWidth()),(int)Math.rint(pixelScale * canvas.getHeight()));
SnapshotParameters params = new SnapshotParameters();
params.setTransform(Transform.scale(pixelScale, pixelScale));
params.setFill(Color.WHITE);
canvas.snapshot(params, image);     
canvas.getGraphicsContext2D().drawImage(image, 50, 50);    

Thanks for any direction y'all can point me in!

LSkip
  • 11
  • 2
  • 2
    This [example](https://stackoverflow.com/a/70089503/230513) contrasts altering the pixels of the `GraphicsContext` directly vs. those of an arbitrary `WritableImage`; the latter can be re-centered easily when the `Canvas` is resized. – trashgod Aug 27 '22 at 02:28
  • 1
    The first thing I would do is ask myself whether using a Canvas is the right approach anyway. Maybe using the scene graph instead would be the better solution but we can't answer this because you did not say what you actually want to do with that Canvas. – mipa Aug 27 '22 at 08:30
  • In particular, is your drawing program a _bitmap_ drawing program or a _vecor_ drawing program or a _hybrid_ of the two? – trashgod Aug 27 '22 at 12:06
  • The program is a bitmap drawing program using PixelWriter to draw pixels one at a time, and then we do some math to draw reflections of each pixel as it is drawn. Depending on the number of reflections and the color palette, the result comes out like a stained glass window, or a lacy snowflake, etc..., with the pattern centered on the canvas. – LSkip Aug 27 '22 at 15:41
  • Some social context: I am using this to teach coding and math to my 10 year old who has been dabbling in entry-level languages so far. The challenge is that I am an old-school C developer with my professional experience mostly with boring business stuff, so I am learning Java, JavaFX, and graphics right alongside my kid. This snowflake program is actually a modernization of a program I first wrote 40 years ago on an Apple II. – LSkip Aug 27 '22 at 15:48
  • Some related examples are cited in the last bullet [here](https://stackoverflow.com/a/70408263/230513). Clone the [project](https://github.com/trashgod/modular) and implement the `Modular` interface to get started. – trashgod Aug 27 '22 at 16:08
  • @trashgod - Thanks for the example. I see the benefit of "PixelWriting" to a WritableImage, then drawing WritableImage centered on the Canvas. The challenge I see is that the image grows and transforms itself over time, drawing and reflecting several points a second. With the WritableImage approach, would I then need to draw the WritableImage to the Canvas several times a second? I was envisioning that, while the window was actively being resized, I would pause the drawing timer to keep from making a terrible mess, then re-center the drawing and resume the timer. – LSkip Aug 27 '22 at 16:21
  • Our messages overlapped. I'll check out that second example too. Thanks again – LSkip Aug 27 '22 at 16:24
  • 1
    If all you are doing is writing pixels, you don't need a Canvas at all, you can just write them using the PixelWriter for the WriteableImage. Place the WriteableImage in an ImageView and you can adjust the viewport as needed. Place the ImageView in a StackPane to center it. You can resize the WriteableImage, but you can create a new one with a larger size as needed and copy the pixels from the old image to the new one. Not saying this is a good approach for you, or a good solution to your problem, just providing some ideas. – jewelsea Aug 28 '22 at 06:38
  • Thanks for the ideas. @jewelsea, I think you meant to write "You **can't** resize the WriteableImage" ? – LSkip Aug 28 '22 at 14:11
  • It looks like I can't shift the existing image (pixels) already on a canvas or read the pixels from a canvas to redraw them myself. So basically, as I evolve my image, I need to keep the pixels somewhere where I can get back at them (in a WritableImage, or just a big array). One way is to evolve my image in WritableImage and periodically refresh my Canvas (or ImageView) from it. Another is to evolve my image directly on the Canvas as I do now, and maintain the WritableImage (or array) in parallel, which is only used to redraw a fresh image using setPixels() when the screen is resized. Thanks! – LSkip Aug 28 '22 at 14:15
  • As an `Image` may be sufficient, also consider the approaches mentioned [here](https://stackoverflow.com/a/44141878/230513). – trashgod Aug 28 '22 at 17:56
  • `I think you meant to write "You can't resize the WriteableImage"` —> yes, that is what I meant to write :-) – jewelsea Aug 29 '22 at 02:32
  • If you go with an image, also consider scroll pane panning like [this](https://stackoverflow.com/a/73328643/230513). – trashgod Aug 29 '22 at 13:14

0 Answers0