JavaFX:如何在调整大小后强制重新渲染 WriteableImage?

JavaFX: How to force the re-rendering of a WriteableImage after resize?

这个问题和下面的代码有历史,比较

import javafx.application.Application;
import javafx.beans.Observable;
import javafx.beans.property.SimpleDoubleProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.event.EventHandler;
import javafx.geometry.Rectangle2D;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.image.ImageView;
import javafx.scene.image.PixelWriter;
import javafx.scene.image.WritableImage;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;
import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color;
import javafx.stage.Screen;
import javafx.stage.Stage;

public class ImageOperationApp extends Application 
{
private SimpleDoubleProperty gridSize  = new SimpleDoubleProperty(3.0);
private SimpleDoubleProperty hueFactor = new SimpleDoubleProperty(12.0);
private SimpleDoubleProperty hueOffset = new SimpleDoubleProperty(240.0);

Rectangle2D scr=Screen.getPrimary().getVisualBounds();
int size1 = (int) (scr.getWidth()  * 0.62);
int size2 = (int) (scr.getHeight() * 0.62);

SimpleIntegerProperty sip1=new SimpleIntegerProperty(888); 
SimpleIntegerProperty sip2=new SimpleIntegerProperty(888); 


private  void renderImage(WritableImage img, double gridSize, double hueFactor, double hueOffset) 
{
    PixelWriter pw = img.getPixelWriter();
    double w = sip1.doubleValue();
    double h = sip2.doubleValue();
    System.out.println("w "+w+" h "+h);             
    double xRatio = 0.0;
    double yRatio = 0.0;
    double hue = 0.0;

    for (int y = 0; y < h; y++) 
        for (int x = 0; x < w; x++) 
        {
            xRatio = x / w;
            yRatio = y / h;
            hue = Math.sin(yRatio * (gridSize * Math.PI)) * Math.sin(xRatio * (gridSize * Math.PI))
                    * Math.tan(hueFactor / 20.0) * 360.0 + hueOffset;
            Color c = Color.hsb(hue, 1.0, 1.0);
            pw.setColor(x, y, c);
        }
}

public Parent createContent() 
{
    StackPane root = new StackPane();
    WritableImage img = new WritableImage(sip1.get(), sip2.get());
    gridSize.addListener((Observable observable) -> {
        renderImage(img, gridSize.doubleValue(), hueFactor.doubleValue(), hueOffset.doubleValue());
    });

    hueFactor.addListener((Observable observable) -> {
        renderImage(img, gridSize.doubleValue(), hueFactor.doubleValue(), hueOffset.doubleValue());
    });
    hueOffset.addListener((Observable observable) -> {
        renderImage(img, gridSize.doubleValue(), hueFactor.doubleValue(), hueOffset.doubleValue());
    });

    sip1.addListener((Observable observable) -> {
        renderImage(img, gridSize.doubleValue(), hueFactor.doubleValue(), hueOffset.doubleValue());
    });

    sip2.addListener((Observable observable) -> {
        renderImage(img, gridSize.doubleValue(), hueFactor.doubleValue(), hueOffset.doubleValue());
    });     
    renderImage(img, 3.0, 12.0, 240.0);
    ImageView view = new ImageView(img);
    root.getChildren().add(view);
    return root;
}

@Override
public void start(Stage primaryStage) throws Exception 
{
    Scene scene = new Scene(createContent());
    scene.setOnKeyPressed(new EventHandler<KeyEvent>() 
    {
        @Override
        public void handle(KeyEvent event) 
        {
            if (event.getCode() == KeyCode.X)
                gridSize.set(gridSize.get() + 0.5);
            if (event.getCode() == KeyCode.Y) 
            {
                double t = gridSize.get();
                t -= .5;
                if (t < 0)
                    t = 0;
                gridSize.set(t);
            }

            if (event.getCode() == KeyCode.V)
                hueFactor.set(hueFactor.get() + 0.5);
            if (event.getCode() == KeyCode.C) 
            {
                double t = hueFactor.get();
                t -= .5;
                if (t < 0)
                    t = 0;
                hueFactor.set(t);
            }

            if (event.getCode() == KeyCode.N)
                hueOffset.set(hueOffset.get() + 10);
            if (event.getCode() == KeyCode.B) 
            {
                double t = hueOffset.get();
                t -= 10;
                if (t < 0)
                    t = 0;
                hueOffset.set(t);
            }

        }
    });


    scene.widthProperty().addListener(new ChangeListener<Number>() {
        @Override public void changed(ObservableValue<? extends Number> observableValue, Number oldSceneWidth, Number newSceneWidth) {
            System.out.println("Width: " + newSceneWidth);
            sip1.set(newSceneWidth.intValue());
        }
    });

    scene.heightProperty().addListener(new ChangeListener<Number>() {
        @Override public void changed(ObservableValue<? extends Number> observableValue, Number oldSceneHeight, Number newSceneHeight) {
            System.out.println("Height: " + newSceneHeight);
            sip2.set(newSceneHeight.intValue());
        }
    });
    primaryStage.setTitle("JavaFX Demo");
    primaryStage.setScene(scene);
    primaryStage.show();
}

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

为了使其独立且可运行,我发布了整个内容。
正如您可能说服自己的那样,renderImage()-方法注意到了调整大小事件,但它没有显示出预期的行为。
显然,for-循环不会重新着色所有像素,但是只是一部分。是否有简单的修复或 WriteableImage 原则上无法处理调整大小事件?

我对您的代码做了一些更改:

1) 新实例变量(包括WritableImage):

private static final int IMAGE_MAX_WIDTH = 888;
private static final int IMAGE_MAX_HEIGHT = 888;

private final SimpleIntegerProperty sip1 = new SimpleIntegerProperty(IMAGE_MAX_WIDTH);
private final SimpleIntegerProperty sip2 = new SimpleIntegerProperty(IMAGE_MAX_HEIGHT);
private WritableImage img = new WritableImage(sip1.get(), sip2.get());
private ImageView view;

2) 在 renderImage 的第一行实例化一个新的 WritableImage 并将其设置为 view

private void renderImage(double gridSize, double hueFactor, double hueOffset) {
    img = new WritableImage(sip1.intValue(), sip2.intValue());
    view.setImage(img);
    .....
}

3) 在 createContent

public Parent createContent() {
     StackPane root = new StackPane();
     img = new WritableImage(sip1.intValue(), sip2.intValue());
     view = new ImageView(img);
     ....
}

4) 在 start 中修复异常:

scene.widthProperty().addListener((observableValue, oldSceneWidth, newSceneWidth) -> {
    System.out.println("Width: " + newSceneWidth);
    int newValue = newSceneWidth.intValue();
    if (newValue < IMAGE_MAX_WIDTH) {
        sip1.set(newValue);
    }
});

scene.heightProperty().addListener((observableValue, oldSceneHeight, newSceneHeight) -> {
    System.out.println("Height: " + newSceneHeight);
    int newValue = newSceneHeight.intValue();
    if (newValue < IMAGE_MAX_HEIGHT) {
        sip2.set(newValue);
    }
});

如果我正确理解了您的问题,这些更改应该可以解决它。

整个class:

import javafx.application.Application;
import javafx.beans.Observable;
import javafx.beans.property.SimpleDoubleProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.event.EventHandler;
import javafx.geometry.Rectangle2D;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.image.ImageView;
import javafx.scene.image.PixelWriter;
import javafx.scene.image.WritableImage;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;
import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color;
import javafx.stage.Screen;
import javafx.stage.Stage;

public class ImageOperationApp extends Application {
    private SimpleDoubleProperty gridSize = new SimpleDoubleProperty(3.0);
    private SimpleDoubleProperty hueFactor = new SimpleDoubleProperty(12.0);
    private SimpleDoubleProperty hueOffset = new SimpleDoubleProperty(240.0);

    Rectangle2D scr = Screen.getPrimary().getVisualBounds();
    int size1 = (int) (scr.getWidth() * 0.62);
    int size2 = (int) (scr.getHeight() * 0.62);

    private static final int IMAGE_MAX_WIDTH = 888;
    private static final int IMAGE_MAX_HEIGHT = 888;

    private final SimpleIntegerProperty sip1 = new SimpleIntegerProperty(IMAGE_MAX_WIDTH);
    private final SimpleIntegerProperty sip2 = new SimpleIntegerProperty(IMAGE_MAX_HEIGHT);
    private WritableImage img = new WritableImage(sip1.get(), sip2.get());
    private ImageView view;
    private void renderImage(double gridSize, double hueFactor, double hueOffset) {
        img = new WritableImage(sip1.get(), sip2.get());
        view.setImage(img);
        PixelWriter pw = img.getPixelWriter();
        double w = sip1.doubleValue();
        double h = sip2.doubleValue();
        System.out.println("w " + w + " h " + h);
        double xRatio = 0.0;
        double yRatio = 0.0;
        double hue = 0.0;

        for (int y = 0; y < h; y++)
            for (int x = 0; x < w; x++) {
                xRatio = x / w;
                yRatio = y / h;
                hue = Math.sin(yRatio * (gridSize * Math.PI)) * Math.sin(xRatio * (gridSize * Math.PI))
                        * Math.tan(hueFactor / 20.0) * 360.0 + hueOffset;
                Color c = Color.hsb(hue, 1.0, 1.0);
                pw.setColor(x, y, c);
            }
    }

    public Parent createContent() {
        StackPane root = new StackPane();
        img = new WritableImage(sip1.intValue(), sip2.intValue());
        view = new ImageView(img);

        gridSize.addListener((Observable observable) -> {
            renderImage(gridSize.doubleValue(), hueFactor.doubleValue(), hueOffset.doubleValue());
        });

        hueFactor.addListener((Observable observable) -> {
            renderImage(gridSize.doubleValue(), hueFactor.doubleValue(), hueOffset.doubleValue());
        });
        hueOffset.addListener((Observable observable) -> {
            renderImage(gridSize.doubleValue(), hueFactor.doubleValue(), hueOffset.doubleValue());
        });

        sip1.addListener((Observable observable) -> {
            renderImage(gridSize.doubleValue(), hueFactor.doubleValue(), hueOffset.doubleValue());
        });

        sip2.addListener((Observable observable) -> {
            renderImage(gridSize.doubleValue(), hueFactor.doubleValue(), hueOffset.doubleValue());
        });

        renderImage(3.0, 12.0, 240.0);
        root.getChildren().add(view);
        return root;
    }

    @Override
    public void start(Stage primaryStage) throws Exception {
        Scene scene = new Scene(createContent());
        scene.setOnKeyPressed(new EventHandler<KeyEvent>() {
            @Override
            public void handle(KeyEvent event) {
                if (event.getCode() == KeyCode.X)
                    gridSize.set(gridSize.get() + 0.5);
                if (event.getCode() == KeyCode.Y) {
                    double t = gridSize.get();
                    t -= .5;
                    if (t < 0)
                        t = 0;
                    gridSize.set(t);
                }

                if (event.getCode() == KeyCode.V)
                    hueFactor.set(hueFactor.get() + 0.5);
                if (event.getCode() == KeyCode.C) {
                    double t = hueFactor.get();
                    t -= .5;
                    if (t < 0)
                        t = 0;
                    hueFactor.set(t);
                }

                if (event.getCode() == KeyCode.N)
                    hueOffset.set(hueOffset.get() + 10);
                if (event.getCode() == KeyCode.B) {
                    double t = hueOffset.get();
                    t -= 10;
                    if (t < 0)
                        t = 0;
                    hueOffset.set(t);
                }

            }
        });

        scene.widthProperty().addListener((observableValue, oldSceneWidth, newSceneWidth) -> {
            System.out.println("Width: " + newSceneWidth);
            int newValue = newSceneWidth.intValue();
            if (newValue < IMAGE_MAX_WIDTH) {
                sip1.set(newValue);
            }
        });

        scene.heightProperty().addListener((observableValue, oldSceneHeight, newSceneHeight) -> {
            System.out.println("Height: " + newSceneHeight);
            int newValue = newSceneHeight.intValue();
            if (newValue < IMAGE_MAX_HEIGHT) {
                sip2.set(newValue);
            }
        });
        primaryStage.setTitle("JavaFX Demo");
        primaryStage.setScene(scene);
        primaryStage.show();
    }

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

结果: