Java Arena
Step 1 of 7

Drawing on a canvas

Module 51 drew charts, and JavaFX decided where each line and bar went. This module draws by itself. A canvas is an empty area of a window that a program draws on: rectangles, ovals and lines, wherever it wants them and in the colors it picks. Then come images: pictures from files, read and changed one pixel at a time. Sound comes at the end.

Drawings and images in Java Arena

As with the windows of part 13 (module 48 explained it) and the charts of module 51, Java Arena's JavaFX is a practice version written for this site, and the page draws the window from what your program told JavaFX. A canvas here keeps a list of what the program drew on it, in order and with the colors, and the page draws that list for you, approximately: real JavaFX smooths the edges of shapes in its own way. Under each drawing, "The window as text" lists every call, and that list is what the challenges compare. The check that runs every model solution on real JavaFX (module 48) also draws each canvas's recorded calls again on real JavaFX and compares the result with the real canvas, pixel by pixel, and it compares every pixel of each image. What it doesn't check is how this page draws the list.

Images are different: they're read and written pixel by pixel, exactly, and the challenges compare every pixel. The images that this module's programs read are small pictures that Java Arena makes for its lessons. Sound can't play on this page at all: step 7 shows sound code to read, and the MOOC's exercise with sound needs your own computer.

A canvas and its pen

A rowing club (made up for this lesson) wants its flag on a screen:

import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.stage.Stage;

public class Main extends Application {

    @Override
    public void start(Stage stage) {
        Canvas canvas = new Canvas(150, 100);
        GraphicsContext pen = canvas.getGraphicsContext2D();
        pen.setFill(Color.NAVY);
        pen.fillRect(0, 0, 150, 100);
        pen.setFill(Color.WHITE);
        pen.fillRect(0, 40, 150, 20);
        pen.setFill(Color.GOLD);
        pen.fillOval(55, 30, 40, 40);

        stage.setScene(new Scene(new Pane(canvas)));
        stage.setTitle("Rowing club");
        stage.show();
    }

    public static void main(String[] args) {
        launch(Main.class);
    }
}
The window

This window is wider than the screen: scroll it sideways, or read it as text below.

The window as text
Window "Rowing club"
  Pane
    Canvas 150 x 100
      fillRect 0 0 150 100, fill #000080
      fillRect 0 40 150 20, fill #ffffff
      fillOval 55 30 40 40, fill #ffd700
  • new Canvas(150, 100) is a canvas 150 pixels wide and 100 pixels high. It's a node, like a label, so it goes into a layout. Pane (from javafx.scene.layout) is the plainest layout: it shows its children and doesn't arrange them, and a canvas needs nothing more.
  • canvas.getGraphicsContext2D() returns the canvas's GraphicsContext: the object that draws on it, so the code calls it pen. Each canvas has one, and the method always returns the same object.
  • setFill(Color.NAVY) picks the color for the drawing that follows. The pen keeps it until the next setFill.
  • fillRect(x, y, width, height) fills a rectangle whose top left corner is at (x, y).
  • fillOval(x, y, width, height) fills the oval that just fits inside that same rectangle. A width equal to the height gives a circle: here one 40 pixels across.

Where the pixels are

Every point of the canvas has an x and a y, counted in pixels from the top left corner, which is (0, 0). x grows to the right, as in a math class, but y grows down: (0, 40) is 40 pixels below the corner. So the white stripe starts 40 pixels from the top and is 20 high, and the flag's last row of pixels is at y 99.

An oval is given by its rectangle, not by its center. A circle with its center at (75, 50) and a radius of 20 fits in the square from (55, 30), 40 wide and 40 high: fillOval(75 - 20, 50 - 20, 40, 40). That's the gold circle, in the middle of the flag.

Order matters. Each call draws over what's already there, like paint. The navy rectangle comes first and covers the whole canvas, the stripe goes over it, and the circle over the stripe. With the navy rectangle drawn last, the flag would be all navy.

The window as text

For a canvas, "The window as text" lists every drawing call, in order, with its numbers and the color it used:

Window "Rowing club"
  Pane
    Canvas 150 x 100
      fillRect 0 0 150 100, fill #000080
      fillRect 0 40 150 20, fill #ffffff
      fillOval 55 30 40 40, fill #ffd700

Colors are written as # and six hexadecimal digits: two for red, two for green and two for blue, from 00 (none) to ff (all). #000080 is navy: no red, no green, half the blue. The challenges compare these lines exactly, so a picture that looks the same but is drawn with other calls doesn't pass: a task always says which shapes to draw, where, in which colors and in what order.

Colors

The class Color (from javafx.scene.paint) gives colors in four ways:

  • By name: Color.RED, Color.NAVY, Color.GOLD, Color.DARKGREEN. JavaFX has 147 of the color names that web pages use, plus Color.TRANSPARENT.
  • By their amounts of red, green and blue, each from 0 to 255: Color.rgb(255, 128, 0) is orange (all the red, half the green, no blue). Screens mix light, so Color.rgb(255, 255, 0) is yellow, Color.rgb(0, 0, 0) black and Color.rgb(255, 255, 255) white.
  • By the code that web pages use: Color.web("#e63946").
  • With new Color(red, green, blue, opacity), each amount a double from 0.0 to 1.0: new Color(1.0, 0.5, 0.0, 1.0) is orange too. An amount outside that range throws an IllegalArgumentException.

Printing a color shows its code with its opacity at the end: System.out.println(Color.rgb(255, 128, 0)) prints 0xff8000ff, where the last ff means fully opaque.

Lines and outlines

Next to the fill color, the pen has a stroke color for lines and outlines, and a line width. A cottage with a window:

import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.stage.Stage;

public class Main extends Application {

    @Override
    public void start(Stage stage) {
        Canvas canvas = new Canvas(160, 140);
        GraphicsContext pen = canvas.getGraphicsContext2D();
        pen.setFill(Color.BURLYWOOD);
        pen.fillRect(30, 60, 100, 70);
        pen.setFill(Color.FIREBRICK);
        double[] roofX = {20, 80, 140};
        double[] roofY = {60, 15, 60};
        pen.fillPolygon(roofX, roofY, 3);
        pen.setFill(Color.SADDLEBROWN);
        pen.fillRect(90, 90, 20, 40);

        pen.setStroke(Color.BLACK);
        pen.setLineWidth(2);
        pen.strokeRect(45, 75, 24, 24);
        pen.strokeLine(57, 75, 57, 99);
        pen.strokeLine(45, 87, 69, 87);

        stage.setScene(new Scene(new Pane(canvas)));
        stage.setTitle("Cottage");
        stage.show();
    }

    public static void main(String[] args) {
        launch(Main.class);
    }
}
The window

This window is wider than the screen: scroll it sideways, or read it as text below.

The window as text
Window "Cottage"
  Pane
    Canvas 160 x 140
      fillRect 30 60 100 70, fill #deb887
      fillPolygon (20, 60) (80, 15) (140, 60), fill #b22222
      fillRect 90 90 20 40, fill #8b4513
      strokeRect 45 75 24 24, stroke #000000, width 2
      strokeLine 57 75 57 99, stroke #000000, width 2
      strokeLine 45 87 69 87, stroke #000000, width 2
  • setStroke(Color.BLACK) and setLineWidth(2) set the color and the width (in pixels) of what the stroke methods draw. A new pen strokes in black, 1 pixel wide, and fills in black too.
  • strokeRect(x, y, width, height) draws only the outline of a rectangle, and strokeOval the outline of an oval.
  • strokeLine(x1, y1, x2, y2) draws a line from (x1, y1) to (x2, y2). The two lines here make the cross of the window.
  • fillPolygon(xs, ys, count) fills a shape with corners: the first count values of the array xs are the corners' x values, and those of ys their y values, in the same order. The roof is a triangle with corners (20, 60), (80, 15) and (140, 60). strokePolygon draws its outline.

In the window as text, a stroke call shows its stroke color and width (strokeLine 57 75 57 99, stroke #000000, width 2), and the polygon its corners.

Shapes in a loop

The numbers can be computed, so a loop can draw a row of shapes. A weather map shows a scale of six squares, from yellow for warm to red for hot:

import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.stage.Stage;

public class Main extends Application {

    @Override
    public void start(Stage stage) {
        Canvas canvas = new Canvas(180, 30);
        GraphicsContext pen = canvas.getGraphicsContext2D();
        for (int i = 0; i < 6; i++) {
            pen.setFill(Color.rgb(255, 220 - i * 40, 0));
            pen.fillRect(i * 30, 0, 30, 30);
        }

        stage.setScene(new Scene(new Pane(canvas)));
        stage.setTitle("Heat scale");
        stage.show();
    }

    public static void main(String[] args) {
        launch(Main.class);
    }
}
The window

This window is wider than the screen: scroll it sideways, or read it as text below.

The window as text
Window "Heat scale"
  Pane
    Canvas 180 x 30
      fillRect 0 0 30 30, fill #ffdc00
      fillRect 30 0 30 30, fill #ffb400
      fillRect 60 0 30 30, fill #ff8c00
      fillRect 90 0 30 30, fill #ff6400
      fillRect 120 0 30 30, fill #ff3c00
      fillRect 150 0 30 30, fill #ff1400

Square i starts at x i * 30, right where the one before it ends, and each square has 40 less green than the one before, which turns yellow into red step by step.

Your task

Challenge 1 of 3

A hiking club's flag, 120 pixels wide and 80 high. Fill in the blanks so that it draws, in this order: a dark green background (Color.DARKGREEN) over the whole canvas, a white stripe 20 pixels wide from x 30 down the whole height, and a black circle 30 pixels across whose center is at (90, 40). The window it should show is drawn below.

Challenge 2

A road sign: no entry. Draw it on the canvas of 120 x 160 pixels that the starter code makes, with these calls in this order:

  1. a gray post (Color.GRAY): a filled rectangle 10 wide and 110 high, from (55, 50)
  2. the sign: a red (Color.RED) filled circle 100 pixels across, in the square from (10, 0)
  3. a white (Color.WHITE) filled bar 70 wide and 20 high, from (25, 40)
  4. the sign's edge: the outline of the same circle, in dark red (Color.DARKRED) and 3 pixels wide

The post starts in the middle of the sign, so the circle covers its top. Keep the window's title and the Pane. The window it should show is drawn below.

Challenge 3

A paint shop prints cards of color samples. The file colors.txt holds one web color code on each line, such as #e63946. Show them on a canvas 300 pixels wide and 60 high: for the color on line i of the file (counting from 0), a filled square 40 x 40 with its top left corner at x 10 + 50 * i and y 10, in the color Color.web(code). Under the canvas, in a VBox with no spacing, a label says how many colors there are: Colors: 3. The window's title is Color card.

For the file below, the window should look like the drawing under it.