Automatically Resize Canvas to Fill the Enclosing Parent

Automatically resize Canvas to fill the enclosing Parent

I combined both prior solutions ( @trashgod and @clataq's ) by putting the canvas in a Pane and binding it to it:

private static class CanvasPane extends Pane {

final Canvas canvas;

CanvasPane(double width, double height) {
setWidth(width);
setHeight(height);
canvas = new Canvas(width, height);
getChildren().add(canvas);

canvas.widthProperty().bind(this.widthProperty());
canvas.heightProperty().bind(this.heightProperty());
}
}

make canvas as wide and as high as parent

Here's a working example fixing the problems:

http://jsfiddle.net/PQS3A/7/

You had several problems with your example:

  1. A <div> does not have height or width attributes. You need to set those through CSS.
  2. Even if the div were sized correctly, it was using the default position:static, which means that it is NOT the positioning parent of any children. If you want the canvas to be the same size as the div, you must set the div to position:relative (or absolute or fixed).
  3. The width and height attributes on a Canvas specify the number of pixels of data to draw to (like the actual pixels in an image), and are separate from the display size of the canvas. These attributes must be set to integers.

The example linked to above uses CSS to set the div size and make it a positioned parent. It creates a JS function (shown below) to both set a canvas to be the same display size as its positioned parent, and then adjusts the internal width and height properties so that is has the same number of pixels as it shows.

var canvas = document.querySelector('canvas');
fitToContainer(canvas);

function fitToContainer(canvas){
// Make it visually fill the positioned parent
canvas.style.width ='100%';
canvas.style.height='100%';
// ...then set the internal size to match
canvas.width = canvas.offsetWidth;
canvas.height = canvas.offsetHeight;
}

HTML5 canvas resize to parent

The easiest thing to do is to always keep the canvas in its own div.

The only thing that will ever be in this div is the canvas.

Then you can use CSS to resize the div however you want. You want it as large as the body? Give the div width: 100%.

Then you can always rightfully do:

canvas.width = theDiv.clientWidth;
canvas.height = theDiv.clientHeight;

If you do this you won't have to worry about the weird issues that you're currently facing when the body is the direct parent of the div.

How to make canvas Resizable in javaFX?

There's a guide that I think that you may find useful for setting up a resizable canvas:

JavaFx tip - resizable canvas

Piece of code from the guide:

/**
* Tip 1: A canvas resizing itself to the size of
* the parent pane.
*/
public class Tip1ResizableCanvas extends Application {

class ResizableCanvas extends Canvas {

public ResizableCanvas() {
// Redraw canvas when size changes.
widthProperty().addListener(evt -> draw());
heightProperty().addListener(evt -> draw());
}

private void draw() {
double width = getWidth();
double height = getHeight();

GraphicsContext gc = getGraphicsContext2D();
gc.clearRect(0, 0, width, height);

gc.setStroke(Color.RED);
gc.strokeLine(0, 0, width, height);
gc.strokeLine(0, height, width, 0);
}

@Override
public boolean isResizable() {
return true;
}

@Override
public double prefWidth(double height) {
return getWidth();
}

@Override
public double prefHeight(double width) {
return getHeight();
}
}

JavaFX canvas size in Pane

Removing the setResizable(false); line seems to solve the problem.

Also placing primaryStage.sizeToScene(); after setResizable(false); seems to solve the problem.

WPF: How to make canvas auto-resize?

No this is not possible (see snippet from MSDN below). However, if you want to have scrollbars and auto-resizing, consider using a Grid instead, and use the Margin property to position your items on this Grid.. Grid will tell the ScrollViewer how big he wants to be, and you will get the scrollbars.. Canvas will always tells the ScrollViewer he doesn't need any size.. :)

Grid lets you enjoy both worlds - As long as you're putting all elements into a single cell, you get both: Arbitrary positioning and auto-sizing. In general it is good to remember that most panel controls (DockPanel, StackPanel, etc) can be implemented via a Grid control.

From MSDN:

Canvas is the only panel element that has no inherent layout characteristics. A Canvas has default Height and Width properties of zero, unless it is the child of an element that automatically sizes its child elements. Child elements of a Canvas are never resized, they are just positioned at their designated coordinates. This provides flexibility for situations in which inherent sizing constraints or alignment are not needed or wanted. For cases in which you want child content to be automatically resized and aligned, it is usually best to use a Grid element.

Hope this helps

Erasing Antialiased Shapes from a JavaFX Canvas

For expedience, note the difference between fillOval and strokeOval() in the GraphicsContext. You can conditionally erase the outline in drawCircles() as a function of a suitable boolean value:

if (stroke) {
gc.setStroke(BG_COLOR);
gc.strokeOval(screenX, screenY, CIRCLE_DIAMETER, CIRCLE_DIAMETER);
}

Try a few representative shapes, e.g. fillRect, to verify the desired result.

A better alternative, IMO, is to pursue the erase -> render strategy. Complete examples seen here and here may help you establish whether the approach is scalable to your use-case. See also this related examination of resampling artifact.

Expedient approach, as tested:

import javafx.application.Application;
import javafx.application.Platform;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.layout.*;
import javafx.scene.paint.Color;
import javafx.stage.Stage;

import java.util.Random;

public class Main extends Application {

static final int NUM_CIRCLES = 500;
static final int CIRCLE_DIAMETER = 10;
static final double PANEL_WIDTH = 75.0;
static final double PANEL_HEIGHT = 40.0;
static final Color FG_COLOR = Color.rgb(10, 0, 200);
static final Color BG_COLOR = Color.rgb(255, 255, 255);
static final double BUTTON_WIDTH = 50.0;

GraphicsContext gc;

Random rand = new Random();
private boolean stroke;

double[] px = new double[NUM_CIRCLES];
double[] py = new double[NUM_CIRCLES];

void randomizeParticlePositions() {
for (int i = 0; i < NUM_CIRCLES; i++) {
px[i] = rand.nextDouble() * PANEL_WIDTH;
py[i] = rand.nextDouble() * PANEL_HEIGHT;
}
}

void drawCircles(Color color) {
gc.setFill(color);
for (int i = 0; i < NUM_CIRCLES; i++) {
var screenX = px[i] * CIRCLE_DIAMETER;
var screenY = py[i] * CIRCLE_DIAMETER;
gc.fillOval(screenX, screenY, CIRCLE_DIAMETER, CIRCLE_DIAMETER);
if (stroke) {
gc.setStroke(BG_COLOR);
gc.strokeOval(screenX, screenY, CIRCLE_DIAMETER, CIRCLE_DIAMETER);
}
}
}

@Override
public void start(Stage stage) {
String javaVersion = System.getProperty("java.version");
String javafxVersion = System.getProperty("javafx.version");

stage.setTitle("AntiAliasingGhosts -- erasing objects leaves ghosts in JavaFX");

Label versionLabel = new Label("JavaFX " + javafxVersion
+ ", running on Java " + javaVersion + ".");

double canvasWidth = (PANEL_WIDTH * CIRCLE_DIAMETER);
double canvasHeight = (PANEL_HEIGHT * CIRCLE_DIAMETER);
Canvas canvasRef = new Canvas(canvasWidth, canvasHeight);
gc = canvasRef.getGraphicsContext2D();

Button deBtn = new Button("Draw");
deBtn.setPrefWidth(BUTTON_WIDTH);
deBtn.setOnAction(e -> {
String txt = deBtn.getText();
switch (txt) {
case "Draw" -> {
randomizeParticlePositions();
drawCircles(FG_COLOR);
deBtn.setText("Erase");
stroke = true;
}
case "Erase" -> {
drawCircles(BG_COLOR);
deBtn.setText("Draw");
stroke = false;
}
default ->
Platform.exit();
}
});

Button exBtn = new Button("Exit");
exBtn.setPrefWidth(BUTTON_WIDTH);
exBtn.setOnAction(e -> Platform.exit());

TilePane tp = new TilePane();
tp.setAlignment(Pos.CENTER);
tp.setHgap(10);
tp.getChildren().addAll(deBtn, exBtn);

VBox root = new VBox();
root.setPadding(new Insets(7));
root.setSpacing(10);
root.setAlignment(Pos.CENTER);
root.getChildren().addAll(versionLabel, canvasRef, tp);

StackPane sp = new StackPane(root);
BackgroundFill bf = new BackgroundFill(BG_COLOR, CornerRadii.EMPTY, Insets.EMPTY);
Background bg = new Background(bf);
sp.setBackground(bg);

Scene scene = new Scene(sp, 640.0, 480.0);

stage.setScene(scene);
stage.show();
}

public static void main(String[] args) {
launch();
}

}

How to preserve image ratio and resize image according to its actual size?

You also need to adjust the viewport to make it's dimentions have the correct ratio to fill the whole area with a ImageView:

@Override
public void start(Stage primaryStage) {
imageView.setImage(new Image("https://upload.wikimedia.org/wikipedia/commons/thumb/4/41/Siberischer_tiger_de_edit02.jpg/800px-Siberischer_tiger_de_edit02.jpg"));
imageView.setPreserveRatio(true);

StackPane root = new StackPane();
root.getChildren().add(imageView);
root.layoutBoundsProperty().addListener((observable, oldValue, newValue) -> {
double w = newValue.getWidth();
double h = newValue.getHeight();
imageView.setFitWidth(w);
imageView.setFitHeight(h);
double ratio = h / w;
Image image = imageView.getImage();
double ih = image.getHeight();
double iw = image.getWidth();
double vR = ih / iw;
imageView.setViewport((ratio < vR) ? new Rectangle2D(0, 0, iw, iw * ratio) : new Rectangle2D(0, 0, ih / ratio, ih));
});

Scene scene = new Scene(root, 300, 250);

primaryStage.setTitle("Test Image Resizing");
primaryStage.setScene(scene);
primaryStage.show();
}

In this case you can achieve the same effect by using the image as background image for root:

@Override
public void start(Stage primaryStage) {
StackPane root = new StackPane();
root.setBackground(new Background(
new BackgroundImage(new Image("https://upload.wikimedia.org/wikipedia/commons/thumb/4/41/Siberischer_tiger_de_edit02.jpg/800px-Siberischer_tiger_de_edit02.jpg"),
BackgroundRepeat.NO_REPEAT, BackgroundRepeat.NO_REPEAT, BackgroundPosition.DEFAULT, new BackgroundSize(0, 0, false, false, false, true)
)));

Scene scene = new Scene(root, 300, 250);

primaryStage.setTitle("Test Image Resizing");
primaryStage.setScene(scene);
primaryStage.show();
}


Related Topics



Leave a reply



Submit