© The Author(s), under exclusive license to APress Media, LLC, part of Springer Nature 2022
K. Sharan, P. SpäthLearn JavaFX 17https://doi.org/10.1007/978-1-4842-7848-2_22

22. Drawing on a Canvas

Kishori Sharan1   and Peter Späth2
(1)
Montgomery, AL, USA
(2)
Leipzig, Sachsen, Germany
 
In this chapter, you will learn:
  • What the Canvas API is

  • How to create a canvas

  • How to draw on a canvas such as basic shapes, text, paths, and images

  • How to clear the canvas area

  • How to save and restore the drawing states in a GraphicsContext

The examples of this chapter lie in the com.jdojo.canvas package. In order for them to work, you must add a corresponding line to the module-info.java file:
...
opens com.jdojo.canvas to javafx.graphics, javafx.base;
...

What Is the Canvas API?

Through the javafx.scene.canvas package, JavaFX provides the Canvas API that offers a drawing surface to draw shapes, images, and text using drawing commands. The API also gives pixel-level access to the drawing surface where you can write any pixels on the surface. The API consists of only two classes:
  • Canvas

  • GraphicsContext

A canvas is a bitmap image, which is used as a drawing surface. An instance of the Canvas class represents a canvas. It inherits from the Node class. Therefore, a canvas is a node. It can be added to a scene graph, and effects and transformations can be applied to it.

A canvas has a graphics context associated with it that is used to issue drawing commands to the canvas. An instance of the GraphicsContext class represents a graphics context.

Creating a Canvas

The Canvas class has two constructors. The no-args constructor creates an empty canvas. Later, you can set the size of the canvas using its width and height properties. The other constructor takes the width and height of the canvas as parameters:
// Create a Canvas of zero width and height
Canvas canvas = new Canvas();
// Set the canvas size
canvas.setWidth(400);
canvas.setHeight(200);
// Create a 400X200 canvas
Canvas canvas = new Canvas(400, 200);

Drawing on the Canvas

Once you create a canvas, you need to get its graphics context using the getGraphicsContext2D() method , as in the following snippet of code:
// Get the graphics context of the canvas
GraphicsContext gc = canvas.getGraphicsContext2D();
All drawing commands are provided in the GraphicsContext class as methods. Drawings that fall outside the bounds of the canvas are clipped. The canvas uses a buffer. The drawing commands push necessary parameters to the buffer. It is important to note that you should use the graphics context from any one thread before adding the Canvas to the scene graph. Once the Canvas is added to the scene graph, the graphics context should be used only on the JavaFX Application Thread. The GraphicsContext class contains methods to draw the following types of objects:
  • Basic shapes

  • Text

  • Paths

  • Images

  • Pixels

Drawing Basic Shapes

The GraphicsContext class provides two types of methods to draw the basic shapes. The method fillXxx() draws a shape Xxx and fills it with the current fill paint. The method strokeXxx() draws a shape Xxx with the current stroke. Use the following methods for drawing shapes:
  • fillArc()

  • fillOval()

  • fillPolygon()

  • fillRect()

  • fillRoundRect()

  • strokeArc()

  • strokeLine()

  • strokeOval()

  • strokePolygon()

  • strokePolyline()

  • strokeRect()

  • strokeRoundRect()

The following snippet of code draws a rectangle. The stroke color is red, and the stroke width is 2px. The upper-left corner of the rectangle is at (0, 0). The rectangle is 100px wide and 50px high:
Canvas canvas = new Canvas(200, 100);
GraphicsContext gc = canvas.getGraphicsContext2D();
gc.setLineWidth(2.0);
gc.setStroke(Color.RED);
gc.strokeRect(0, 0, 100, 50);

Drawing Text

You can draw text using the fillText() and strokeText() methods of the GraphicsContext using the following snippets of code:
  • void strokeText(String text, double x, double y)

  • void strokeText(String text, double x, double y, double maxWidth)

  • void fillText(String text, double x, double y)

  • void fillText(String text, double x, double y, double maxWidth)

Both methods are overloaded. One version lets you specify the text and its position. The other version lets you specify the maximum width of the text as well. If the actual text width exceeds the specified maximum width, the text is resized to fit the specified maximum width. The following snippet of code draws two strings. Figure 22-1 shows the two strings on the canvas.
Canvas canvas = new Canvas(200, 50);
GraphicsContext gc = canvas.getGraphicsContext2D();
gc.setLineWidth(1.0);
gc.setStroke(Color.BLACK);
gc.strokeText("Drawing Text", 10, 10);
gc.strokeText("Drawing Text", 100, 10, 40);
Figure 22-1

Drawing text on a canvas

Drawing Paths

You can use path commands and SVG path strings to create a shape of your choice. A path consists of multiple subpaths. The following methods are used to draw paths:
  • beginPath()

  • lineTo(double x1, double y1)

  • moveTo(double x0, double y0)

  • quadraticCurveTo(double xc, double yc, double x1, double y1)

  • appendSVGPath(String svgpath)

  • arc(double centerX, double centerY, double radiusX, double radiusY, double startAngle, double length)

  • arcTo(double x1, double y1, double x2, double y2, double radius)

  • bezierCurveTo(double xc1, double yc1, double xc2, double yc2, double x1, double y1)

  • closePath()

  • stroke()

  • fill()

The beginPath() and closePath() methods start and close a path, respectively. Methods such as arcTo() and lineTo() are the path commands to draw a specific type of subpath. Do not forget to call the stroke() or fill() method at the end, which will draw an outline or fill the path. The following snippet of code draws a triangle, as shown in Figure 22-2:
Canvas canvas = new Canvas(200, 50);
GraphicsContext gc = canvas.getGraphicsContext2D();
gc.setLineWidth(2.0);
gc.setStroke(Color.BLACK);
gc.beginPath();
gc.moveTo(25, 0);
gc.appendSVGPath("L50, 25L0, 25");
gc.closePath();
gc.stroke();
Figure 22-2

Drawing a triangle

Drawing Images

You can draw an image on the canvas using the drawImage() method . The method has three versions:
  • void drawImage(Image img, double x, double y)

  • void drawImage(Image img, double x, double y, double w, double h)

  • void drawImage(Image img, double sx, double sy, double sw, double sh, double dx, double dy, double dw, double dh)

You can draw the whole or part of the image. The drawn image can be stretched or shortened on the canvas. The following snippet of code draws the whole image in its original size on the canvas at (10, 10):
Image image = new Image("your_image_URL");
Canvas canvas = new Canvas(400, 400);
GraphicsContext gc = canvas.getGraphicsContext2D();
gc.drawImage(image, 10, 10);
The following statement will draw the whole image on the canvas by resizing it to fit in a 100px wide by 150px high area. Whether the image is stretched or shortened depends on its original size:
// Draw the whole image in 100X150 area at (10, 10)
gc.drawImage(image, 10, 10, 100, 150);
The following statement will draw part of an image on the canvas. Here, it is assumed that the source image is bigger than 100px by 150px. The image part being drawn is 100px wide and 150px high, and its upper-left corner is at (0, 0) in the source image. The part of the image is drawn on the canvas at (10, 10), and it is stretched to fit 200px wide and 200px high area on the canvas:
// Draw part of the image in 200X200 area at (10, 10)
gc.drawImage(image, 0, 0, 100, 150, 10, 10, 200, 200);

Writing Pixels

You can also directly modify pixels on the canvas. The getPixelWriter() method of the GraphicsContext object returns a PixelWriter that can be used to write pixels to the associated canvas:
Canvas canvas = new Canvas(200, 100);
GraphicsContext gc = canvas.getGraphicsContext2D();
PixelWriter pw = gc.getPixelWriter();

Once you get a PixelWriter, you can write pixels to the canvas. Chapter 21 presented more details on how to write pixels using a PixelWriter.

Clearing the Canvas Area

The canvas is a transparent area. Pixels will have colors and opacity depending on what is drawn at those pixels. Sometimes, you may want to clear the whole or part of the canvas so the pixels are transparent again. The clearRect() method of the GraphicsContext lets you clear a specified area on the canvas:
// Clear the top-left 100X100 rectangular area from the canvas
gc.clearRect(0, 0, 100, 100);

Saving and Restoring the Drawing States

The current settings for the GraphicsContext are used for all subsequent drawing. For example, if you set the line width to 5px, all subsequent strokes will be 5px in width. Sometimes, you may want to modify the state of the graphics context temporarily and, after some time, restore the state that existed before the modification.

The save() and restore() methods of the GraphicsContext object let you save the current state and restore it afterward, respectively. Before you use these methods, let’s discuss its need. Suppose you want to issue the following commands to the GraphicsContext object in order:
  • Draw a rectangle without any effects

  • Draw a string with a reflection effect

  • Draw a rectangle without any effects

The following is the first (and incorrect) attempt of achieving this:
Canvas canvas = new Canvas(200, 120);
GraphicsContext gc = canvas.getGraphicsContext2D();
gc.strokeRect(10, 10, 50, 20);
gc.setEffect(new Reflection());
gc.strokeText("Chatar", 70, 20);
gc.strokeRect(120, 10, 50, 20);
Figure 22-3 shows the drawing of the canvas. Notice that the reflection effect was also applied to the second rectangle, which was not wanted.
Figure 22-3

Drawing shapes and text

You can fix the problem by setting the Effect to null after you draw the text. You had modified several properties for the GraphicsContext and then had to restore them all manually. Sometimes, a GraphicsContext may be passed to your code, but you do not want to modify its existing state.

The save() method stores the current state of the GraphicsContext on a stack. The restore() method restores the state of the GraphicsContext to the last saved state. Figure 22-4 shows the results of this. You can fix the problem using the following methods:
Canvas canvas = new Canvas(200, 120);
GraphicsContext gc = canvas.getGraphicsContext2D();
gc.strokeRect(10, 10, 50, 20);
// Save the current state
gc.save();
// Modify the current state to add an effect and darw the text
gc.setEffect(new Reflection());
gc.strokeText("Chatar", 70, 20);
// Restore the state what it was when the last save() was called and draw the
// second rectangle
gc.restore();
gc.strokeRect(120, 10, 50, 20);
Figure 22-4

Drawing shapes and text using save() and restore() methods

A Canvas Drawing Example

The program in Listing 22-1 shows how to draw basic shapes, text, images, and row pixels to a canvas. Figure 22-5 shows the resulting canvas with all drawings.
// CanvasTest.java
package com.jdojo.canvas;
import com.jdojo.util.ResourceUtil;
import java.nio.ByteBuffer;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.image.Image;
import javafx.scene.image.PixelFormat;
import javafx.scene.image.PixelWriter;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.stage.Stage;
public class CanvasTest extends Application {
        private static final int RECT_WIDTH = 20;
        private static final int RECT_HEIGHT = 20;
        public static void main(String[] args) {
               Application.launch(args);
        }
        @Override
        public void start(Stage stage) {
               Canvas canvas = new Canvas(400, 100);
               GraphicsContext gc = canvas.getGraphicsContext2D();
               // Set line width and fill color
               gc.setLineWidth(2.0);
               gc.setFill(Color.RED);
               // Draw a rounded rectangle
               gc.strokeRoundRect(10, 10, 50, 50, 10, 10);
               // Fill an oval
               gc.fillOval(70, 10, 50, 20);
               // Draw text
               gc.strokeText("Hello Canvas", 10, 85);
               // Draw an Image
               String imagePath =
                        ResourceUtil.getResourceURLStr("picture/ksharan.jpg");
               Image image = new Image(imagePath);
               gc.drawImage(image, 130, 10, 60, 80);
               // Write custom pixels to create a pattern
               writePixels(gc);
               Pane root = new Pane();
               root.getChildren().add(canvas);
               Scene scene = new Scene(root);
               stage.setScene(scene);
               stage.setTitle("Drawing on a Canvas");
               stage.show();
        }
        private void writePixels(GraphicsContext gc) {
               byte[] pixels = this.getPixelsData();
               PixelWriter pixelWriter = gc.getPixelWriter();
               // Our data is in BYTE_RGB format
               PixelFormat<ByteBuffer> pixelFormat =
                        PixelFormat.getByteRgbInstance();
               int spacing = 5;
               int imageWidth = 200;
               int imageHeight = 100;
               // Roughly compute the number of rows and columns
               int rows = imageHeight/(RECT_HEIGHT + spacing);
               int columns = imageWidth/(RECT_WIDTH + spacing);
               // Write the pixels to the canvas
               for (int y = 0; y < rows; y++) {
                   for (int x = 0; x < columns; x++) {
                       int xPos = 200 + x * (RECT_WIDTH + spacing);
                       int yPos = y * (RECT_HEIGHT + spacing);
                       pixelWriter.setPixels(xPos, yPos,
                           RECT_WIDTH, RECT_HEIGHT,
                          pixelFormat,
                          pixels, 0,
                          RECT_WIDTH * 3);
                   }
               }
        }
        private byte[] getPixelsData() {
               // Each pixel in the w X h region will take 3 bytes
               byte[] pixels = new byte[RECT_WIDTH * RECT_HEIGHT * 3];
               // Height to width ration
               double ratio = 1.0 * RECT_HEIGHT/RECT_WIDTH;
               // Generate pixel data
               for (int y = 0; y < RECT_HEIGHT; y++) {
                   for (int x = 0; x < RECT_WIDTH; x++) {
                       int i = y * RECT_WIDTH * 3 + x * 3;
                       if (x <= y/ratio) {
                          pixels[i] = -1;  // red -1 means
                                           // 255 (-1 & 0xff = 255)
                          pixels[i+1] = 0; // green = 0
                          pixels[i+2] = 0; // blue = 0
                       } else {
                          pixels[i] = 0;    // red = 0
                          pixels[i+1] = -1; // Green 255
                          pixels[i+2] = 0;  // blue = 0
                       }
                   }
               }
               return pixels;
        }
}
Listing 22-1

Drawing on a Canvas

Figure 22-5

A canvas with shapes, text, images, and raw pixels drawn on it

Summary

Through the javafx.scene.canvas package, JavaFX provides the Canvas API that offers a drawing surface to draw shapes, images, and text using drawing commands. The API also gives pixel-level access to the drawing surface where you can write any pixels on the surface. The API consists of only two classes: Canvas and GraphicsContext. A canvas is a bitmap image, which is used as a drawing surface. An instance of the Canvas class represents a canvas. It inherits from the Node class. Therefore, a canvas is a node. It can be added to a scene graph, and effects and transformations can be applied to it. A canvas has a graphics context associated with it that is used to issue drawing commands to the canvas. An instance of the GraphicsContext class represents a graphics context.

The Canvas class contains a getGraphicsContext2D() method that returns an instance of the GraphicsContext class. After obtaining the GraphicsContext of a canvas, you issue drawing commands to the GraphicsContext that performs the drawing.

Drawings falling outside the bounds of the canvas are clipped. The canvas uses a buffer. The drawing commands push necessary parameters to the buffer. The GraphicsContext of a canvas can be used from any one thread before the canvas is added to the scene graph. Once the canvas is added to the scene graph, the graphics context should be used only on the JavaFX Application Thread. The GraphicsContext class contains methods to draw the following types of objects: basic shapes, text, paths, images, and pixels.

The next chapter will discuss how to use the drag-and-drop gesture to transfer data between nodes in the same JavaFX application, between two different JavaFX applications, and between a JavaFX application and a native application.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.129.70.74