This discussion is archived
1 Reply Latest reply: Dec 30, 2012 8:33 PM by jsmith RSS

Resizable Images in Canvas

user8929955 Newbie
Currently Being Moderated
During the last few days I tried to evaluate the use of JavaFX for a project of mine. One of the main points that is needed is the rendering of images, expecially when resizing the window. So basically what I want is a contol (like ImageView or Canvas) that automatically resizes the image when the application window is resized.
So far I managed:
package ch.sahits.javafx.test;

import java.io.IOException;
import java.util.logging.Level;
import java.util.logging.Logger;
import javafx.application.Application;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.scene.Scene;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.image.Image;
import javafx.scene.layout.Pane;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;

public class ResizeableCanvas extends Application {
    
    @Override
    public void start(Stage primaryStage) {
        try {
            StackPane root = new StackPane();
            Scene scene = new Scene(root, 300, 250);
            Image img = new Image(getClass().getResource("kajak4_small.png").openStream());
            Canvas canvas = new Canvas(root.getWidth(), root.getHeight());
            GraphicsContext context = canvas.getGraphicsContext2D();
            context.drawImage(img, 0, 0, root.getWidth(), root.getHeight());
            
            // Binding
            canvas.widthProperty().bind(root.widthProperty());
            canvas.heightProperty().bind(root.heightProperty());
            
            final ResizeChangeListener resizeChangeListener = new ResizeChangeListener(root, context, img);
            
            canvas.widthProperty().addListener(resizeChangeListener);
            canvas.heightProperty().addListener(resizeChangeListener);
           
            root.getChildren().add(canvas);            
            
            primaryStage.setTitle("Resizable Canvas Test");
            primaryStage.setScene(scene);
            primaryStage.show();
        } catch (IOException ex) {
            Logger.getLogger(ResizeableCanvas.class.getName()).log(Level.SEVERE, null, ex);
        }
    }

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

    private static class ResizeChangeListener implements ChangeListener<Number> {
        
        private final Pane parent;
        private final GraphicsContext context;
        private final Image img;

        public ResizeChangeListener(Pane parent, GraphicsContext context, Image image) {
            this.parent = parent;
            this.context = context;
            this.img = image;
        }

        @Override
        public void changed(ObservableValue<? extends Number> observable, Number oldValue, Number newValue) {
            final double width = parent.getWidth();
            final double height = parent.getHeight();
            context.clearRect(0, 0, width, height);
            context.drawImage(img, 0, 0, width, height);
        }
    }
}
This works well for testing and making a quick sketch up. So it works. However what I intend to use it for is as a control and with this step I failed misserably. I created the control class, the skin class, the CSS and a main class:
package ch.sahits.javafx.test;

import javafx.beans.property.DoubleProperty;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleDoubleProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.scene.control.Control;
import javafx.scene.image.Image;

public class MyCanvas extends Control {
    private final ObjectProperty<Image> image;
    private final DoubleProperty localWidth;
    private final DoubleProperty localHeight;

    public MyCanvas() {
        this(null, 0, 0);
    }
    public MyCanvas(double width, double height) {
        this(null, width, height);
    }
    public MyCanvas(Image img, double width, double height) {
        this.getStyleClass().add(this.getClass().getSimpleName());
        if (width <= 0) {
            width = getWidth();
        }
        if (height <= 0) {
            height = getHeight();
        }
        image = new SimpleObjectProperty<>(this, "image", img);
        localWidth = new SimpleDoubleProperty(this,"local-width", width);
        localHeight = new SimpleDoubleProperty(this, "local-height", height);
    }
    @Override
    protected String getUserAgentStylesheet() {
        return getClass().getResource("/ch/sahits/javafx/test/"+getClass().getSimpleName()+".css").toExternalForm();
    }
    public Image getImage() {
        return image.getValue();
    }
    public void setImage(Image img) {
        image.setValue(img);
    }
    public ObjectProperty<Image> imageProperty(){
        return image;
    }
    public double getLocalWidth(){
        return localWidth.doubleValue();
    }
    public void setLocalWidth(double width){
        localWidth.setValue(width);
    }
    public DoubleProperty localWidthProperty() {
        return localWidth;
    }
    public double getLocalHeight(){
        return localHeight.doubleValue();
    }
    public void setLocalHeigth(double heigth){
        localHeight.setValue(heigth);
    }
    public DoubleProperty localHeigthProperty() {
        return localHeight;
    }  
}
The MyCanvas is the extension of Control and holds the properties image, localWidth and localHeight. The local properties are needed as width and heigh in Control is read only.
package ch.sahits.javafx.test;

import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.control.SkinBase;
import javafx.scene.image.Image;

public class MyCanvasSkin extends SkinBase<MyCanvas> {
    
    private final Canvas canvas;
    
    public MyCanvasSkin(MyCanvas control) {
        super(control);
        canvas = new Canvas(control.getLocalWidth(), control.getLocalHeight());
        createChangeListeners();
        
        this.getChildren().add(canvas);
     }

    private void createChangeListeners() {
        MyCanvas myCanvas = getSkinnable();
        ResizeChangeListener resizeListener = new ResizeChangeListener();
        myCanvas.localHeigthProperty().addListener(resizeListener);
        myCanvas.localWidthProperty().addListener(resizeListener);
        myCanvas.imageProperty().addListener(new ChangeListener<Image>(){

            @Override
            public void changed(ObservableValue<? extends Image> ov, Image t, Image t1) {
                final GraphicsContext context = canvas.getGraphicsContext2D();
                final double width = getSkinnable().getLocalWidth();
                final double height = getSkinnable().getLocalHeight();
                context.clearRect(0, 0, width, height);
                context.drawImage(getSkinnable().getImage(), 0, 0, width, height);
            }
            
        });
    }
    
    private class ResizeChangeListener implements ChangeListener<Number> {
        
        private final GraphicsContext context;

        public ResizeChangeListener() {
            this.context = canvas.getGraphicsContext2D();
        }

        @Override
        public void changed(ObservableValue<? extends Number> observable, Number oldValue, Number newValue) {
            final MyCanvas myCanvas = getSkinnable();
            final double width = myCanvas.getLocalWidth();
            final double height = myCanvas.getLocalHeight();
            context.clearRect(0, 0, width, height);
            if (myCanvas.getImage() != null ) {
                context.drawImage(myCanvas.getImage(), 0, 0, width, height);
                System.out.println("Draw image");
            } else {
                System.out.println("Image not set");
            }
        }
    }
}
The skin class.
.MyCanvas {
    -fx-skin: "ch.sahits.javafx.test.MyCanvasSkin";
}
The CSS file MyCanvas.css.
And finally the main class:
package ch.sahits.javafx.test;

import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.logging.Level;
import java.util.logging.Logger;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.image.Image;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;

public class MyCanvasTest extends Application {
    
    @Override
    public void start(Stage primaryStage) {
        try {
            StackPane root = new StackPane();
            Scene scene = new Scene(root, 300, 250);
            Image img = new Image( getClass().getResource("kajak4_small.png").openStream());
            MyCanvas canvas = new MyCanvas(img, root.getWidth(), root.getHeight());
            
            // Binding
            canvas.localWidthProperty().bind(root.widthProperty());
            canvas.localHeigthProperty().bind(root.heightProperty());            
           
            root.getChildren().add(canvas);            
            
            primaryStage.setTitle("Resizable Canvas separate Test");
            primaryStage.setScene(scene);
            primaryStage.show();
        } catch (FileNotFoundException ex) {
            Logger.getLogger(MyCanvasTest.class.getName()).log(Level.SEVERE, null, ex);
        } catch (IOException ex) {
            Logger.getLogger(MyCanvasTest.class.getName()).log(Level.SEVERE, null, ex);
        }
    }

    public static void main(String[] args) {
        launch(args);
    }
}
Using this no image at all is drawn. I realy do not understand why that is. Have I overlooked something? Or is this just not possible?
By the way I'm using the OpenJDK 8 which includes JavaFX, so some API is different than in 2.2. I exported all the code from the NetBeans project: [ResizeableCanvas.zip|http://sahits.ch/blog/wp-content/uploads/2012/12/ResizeableCanvas.zip]

Edited by: user8929955 on Dec 30, 2012 6:30 AM: Add link to source zip file

Edited by: user8929955 on Dec 30, 2012 6:34 AM: Fix title
  • 1. Re: Resizable Images in Canvas
    jsmith Guru
    Currently Being Moderated
    You might be interested in:
    http://javafx-jira.kenai.com/browse/RT-10610 "make ImageView resizable".

    Which seems similar to your request and has a sample implementation attached to it. The sample solution is just to use a custom Pane for resizing the image.

Legend

  • Correct Answers - 10 points
  • Helpful Answers - 5 points