Search code examples
javajavafxcanvasrendering

JavaFX Stretch Canvas To Dimensions


In my C++ Windows programs I draw directly to a fixed pixel buffer (u32 *) and stretch it to fit the window dimensions:

StretchDIBits(device_context, 
              0, 0, window_width, window_height,
              src_x, src_y, src_width, src_height,
              src_memory, src_info,
              DIB_RGB_COLORS, SRCCOPY);

I'm new to JavaFX. In JavaFX say I have:

@Override
public void
start(Stage primary_stage) throws Exception
{
   Group root = new Group();
   Scene scene = new Scene(root);
   primary_stage.setScene(scene);
   Canvas canvas = new Canvas(1280, 720);
   root.getChildren().add(canvas);
   GraphicsContext gc = canvas.getGraphicsContext2D();
}

I want to treat the canvas (it's GraphicsContext) as the fixed sized pixel buffer in my Windows example. Each frame I want to stretch or shrink it to the window dimensions (or whatever dimensions I specify). Many examples I have seen seem to increase the width and height of the canvas node itself. I want to stretch the content of the canvas, not the canvas node itself if that makes sense.

Is there an equivalent to StretchDIBits for JavaFX?


Solution

  • I don't believe there's a built-in method you can call to do what you want. But you can scale the Canvas to fill the available space. You'll need to continually update the scale factor as the canvas's parent resizes. This is probably best implemented via a custom layout. For example:

    import javafx.geometry.HPos;
    import javafx.geometry.VPos;
    import javafx.scene.canvas.Canvas;
    import javafx.scene.canvas.GraphicsContext;
    import javafx.scene.layout.Region;
    
    public class AutoScalingCanvas extends Region {
    
      private final Canvas canvas;
    
      public AutoScalingCanvas(double canvasWidth, double canvasHeight) {
        this.canvas = new Canvas(canvasWidth, canvasHeight);
        getChildren().add(canvas);
      }
    
      public GraphicsContext getGraphicsContext2D() {
        return canvas.getGraphicsContext2D();
      }
    
      @Override
      protected void layoutChildren() {
        double x = getInsets().getLeft();
        double y = getInsets().getTop();
        double w = getWidth() - getInsets().getRight() - x;
        double h = getHeight() - getInsets().getBottom() - y;
    
        // preserve aspect ratio while also staying within the available space
        double sf = Math.min(w / canvas.getWidth(), h / canvas.getHeight());
        canvas.setScaleX(sf);
        canvas.setScaleY(sf);
    
        positionInArea(canvas, x, y, w, h, -1, HPos.CENTER, VPos.CENTER);
      }
    }
    

    And here's an example using the above:

    import javafx.application.Application;
    import javafx.scene.Scene;
    import javafx.scene.paint.Color;
    import javafx.stage.Stage;
    
    public class Main extends Application {
    
      @Override
      public void start(Stage primaryStage) {
        AutoScalingCanvas canvas = new AutoScalingCanvas(500, 300);
        canvas.getGraphicsContext2D().fillOval(200, 100, 100, 100);
        canvas.getGraphicsContext2D().setLineWidth(5);
        canvas.getGraphicsContext2D().setStroke(Color.RED);
        canvas.getGraphicsContext2D().strokeRect(0, 0, 500, 300);
    
        primaryStage.setScene(new Scene(canvas));
        primaryStage.show();
      }
    }