【问题标题】:JavaFX - Prevent blurring on scaled Canvas for a pixelated zoomJavaFX - 防止在缩放的画布上模糊以进行像素化缩放
【发布时间】:2022-01-21 05:14:31
【问题描述】:

在缩放 Canvas 时,有什么方法可以阻止这种模糊? 我认为它与 GPU 插值有关。但我需要的是像素完美的“像素化”缩放。只需使用最近的“真实”相邻像素的颜色即可。

我已经看到here 的解决方案,但在这两个建议中(#1 / #4),#4 绝对是 CPU 扩展,我猜 #1 也是。

这种缩放需要快速 - 我希望能够支持多达 20-25 层(可能是 StackPane 中的画布,但我愿意接受其他想法,只要它们不会融化 CPU )。我怀疑这可以在没有 JFX 提供的 GPU 支持的情况下完成,但可能没有足够灵活的 API。链接答案中的 #4 等依赖于 CPU 重新缩放的策略可能不起作用。

如果您使用此代码放大到最高缩放级别,则模糊很明显。

我们是否需要更新 JFX API 以支持此功能? 这应该是可以做到的。

import javafx.application.Application;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.scene.Scene;
import javafx.scene.canvas.Canvas;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.layout.Pane;
import javafx.scene.layout.StackPane;
import javafx.scene.paint.Paint;
import javafx.stage.Stage;

public class ScaleTest extends Application {
    
    private static final int width = 1200;
    private static final int height = 800;
    private static final int topMargin = 32;
    private StackPane stackPane;
    private IntegerProperty zoomLevel = new SimpleIntegerProperty(100);
    
    @Override
    public void start(Stage stage) {
        stage.setWidth(width);
        stage.setMinHeight(height);
        
        stackPane = new StackPane();
        stackPane.setLayoutY(topMargin);
        Canvas canvas = new Canvas(width, height - topMargin);
        
        Label label = new Label();
        label.setLayoutY(2);
        label.setLayoutX(2);
        label.setStyle("-fx-text-fill: #FFFFFF");
        label.textProperty().bind(zoomLevel.asString());
        Button zoomInBtn = new Button("Zoom In");
        zoomInBtn.setLayoutY(2);
        zoomInBtn.setLayoutX(50);
        zoomInBtn.onActionProperty().set((e) -> {
            if (zoomLevel.get() < 3200) {
                zoomLevel.set(zoomLevel.get() * 2);
                stackPane.setScaleX(zoomLevel.get() / 100.0);
                stackPane.setScaleY(zoomLevel.get() / 100.0);
            }
        });
        Button zoomOutBtn = new Button("Zoom Out");
        zoomOutBtn.setLayoutY(2);
        zoomOutBtn.setLayoutX(140);
        zoomOutBtn.onActionProperty().set((e) -> {
            if (zoomLevel.get() > 25) {
                zoomLevel.set(zoomLevel.get() / 2);
                stackPane.setScaleX(zoomLevel.get() / 100.0);
                stackPane.setScaleY(zoomLevel.get() / 100.0);
            }
        });
        
        Pane mainPane = new Pane(stackPane, label, zoomInBtn, zoomOutBtn);
        mainPane.setStyle("-fx-background-color: #000000");
        Scene scene = new Scene(mainPane);
        stage.setScene(scene);
        
        drawGrid(canvas, 0, 0, width, height - topMargin, 16);
        stackPane.getChildren().add(canvas);
        
        stage.show();
    }
    
    private void drawGrid(Canvas canvas, int xPos, int yPos, int width, int height, int gridSize) {
        boolean darkX = true;
        String darkCol = "#111111";
        String lightCol = "#222266";
        
        for (int x = xPos; x < canvas.getWidth(); x += gridSize) {
            boolean dark = darkX;
            darkX = !darkX;
            if (x > width) {
                break;
            }
            
            for (int y = yPos; y < canvas.getHeight(); y += gridSize) {
                if (y > height) {
                    break;
                }
                
                dark = !dark;
                String color;
                if (dark) {
                    color = darkCol;
                } else {
                    color = lightCol;
                }
                canvas.getGraphicsContext2D().setFill(Paint.valueOf(color));
                canvas.getGraphicsContext2D().fillRect(x, y, gridSize, gridSize);
            }
        }
    }
}

【问题讨论】:

    标签: javafx


    【解决方案1】:

    您的示例将节点的缩放属性调整为resample 固定大小的图像,这不可避免地会导致这种伪影。只有vector 表示可以任意精度进行缩放。您可能需要决定您的应用程序将如何支持矢量和/或位图。例如,如果您的应用程序真的是关于缩放矩形,您将使用缩放坐标调用 fillRect(),而不是缩放较小矩形的图片。

    你引用了一个很好的总结或resizing,所以我将专注于向量机会:

    • Shape 的具体子类实际上是可以以任何比例渲染的几何元素的矢量表示;调整herehere 所示示例的大小以查看效果;滚动以缩放并单击以拖动圆圈here,注意它的边框在所有比例下都保持清晰。

    • SVGPath 是一个Shape,可以像here 所示那样缩放。

    • 在内部,Font 存储单个字形的矢量表示。实例化时,字形为rasterized,具有一定的磅值。

    • 如果已知合适的矢量表示,则可以将绘图绑定到Canvas 的大小,如here 所示。

    【讨论】:

    • 我想我不应该使用这样的网格作为示例,因为它有点误导。我可以很好地通过 CPU 进行扩展,但显然对于大图像或特别是分层图像来说,这并不能“放大”(没有双关语)。我也会用矢量形状做一些工作,但正如你所暗示的那样,这更容易。我需要的基本上只是像素精确的放大,就像“绘画程序”一样。为了更容易,我决定像我发布的演示那样将缩放级别限制为 2 的幂 - 所以这应该只是每个像素的干净调整大小的问题。这对光栅图形来说不是很好吗?
    • 抱歉,我对您的预期用途知之甚少,无法确定;许多editors 支持矢量和位图;我添加了一个示例,该示例提供了有关矢量缩放的不同视角。
    • 是的,np 我不想用细节过多地夸大这个问题,因为人们可能会因为过于具体而想要关闭它——我不指望读心术。 :) 我确信其中一些链接在我进入后期阶段后会有所帮助,谢谢。
    【解决方案2】:

    这似乎可以解决问题:

    
    package com.analogideas.scratch;
    import javafx.application.Application;
    import javafx.beans.property.IntegerProperty;
    import javafx.beans.property.SimpleIntegerProperty;
    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.image.Image;
    import javafx.scene.image.WritableImage;
    import javafx.scene.layout.Pane;
    import javafx.scene.layout.StackPane;
    import javafx.scene.paint.Paint;
    import javafx.stage.Stage;
    
    
    public class ImagePixelator extends Application {
    
        private static final int width = 1200;
        private static final int height = 800;
        private static final int topMargin = 32;
        private IntegerProperty zoomLevel = new SimpleIntegerProperty(1);
    
        public static void main(String[] args) {
            launch(args);
        }
        
        @Override
        public void start(Stage stage) {
            stage.setWidth(width);
            stage.setMinHeight(height);
    
            Canvas canvasOut = new Canvas(width, height - topMargin);
            Canvas canvas = new Canvas(width, height - topMargin);
            drawGrid(canvas, 0, 0, width, height - topMargin, 16);
            WritableImage img = canvas.snapshot(null, null);
            drawImage(canvasOut, img, 0, 0, 1);
            StackPane pane = new StackPane(canvasOut);
    
            Label label = new Label();
            label.setLayoutY(2);
            label.setLayoutX(2);
            label.setStyle("-fx-text-fill: #FFFFFF");
            label.textProperty().bind(zoomLevel.asString());
            Button zoomInBtn = new Button("Zoom In");
            zoomInBtn.setLayoutY(2);
            zoomInBtn.setLayoutX(50);
            zoomInBtn.onActionProperty().set((e) -> {
                if (zoomLevel.get() < 3200) {
                    zoomLevel.set(zoomLevel.get() * 2);
                    int z = zoomLevel.get();
                    drawImage(canvasOut, img, 0, 0, z);
                }
            });
            Button zoomOutBtn = new Button("Zoom Out");
            zoomOutBtn.setLayoutY(2);
            zoomOutBtn.setLayoutX(140);
            zoomOutBtn.onActionProperty().set((e) -> {
                if (zoomLevel.get() > 1) {
                    zoomLevel.set(zoomLevel.get() /2);
                    int z = zoomLevel.get();
                    drawImage(canvasOut, img, 0, 0, z);
                }
            });
    
            Pane mainPane = new Pane(pane, label, zoomInBtn, zoomOutBtn);
            mainPane.setStyle("-fx-background-color: #000000");
            Scene scene = new Scene(mainPane);
            stage.setScene(scene);
            stage.show();
        }
    
        private void drawImage(Canvas canvas, Image image, int x, int y, float zoom) {
            GraphicsContext g = canvas.getGraphicsContext2D();
            g.setImageSmoothing(false);
            int w = (int) (image.getWidth() * zoom);
            int h = (int) (image.getHeight() * zoom);
            g.drawImage(image, x, y, w, h);
    
        }
    
        private void drawGrid(Canvas canvas, int xPos, int yPos, int width, int height, int gridSize) {
            Paint darkPaint = Paint.valueOf("#111111");
            Paint lightPaint = Paint.valueOf("#222266");
            GraphicsContext g = canvas.getGraphicsContext2D();
            g.setImageSmoothing(false);
            int xLimit = width / gridSize;
            int yLimit = height / gridSize;
            for (int x = 0; x <= xLimit; x++) {
                for (int y = 0; y <= yLimit; y++) {
                    boolean dark = (((x ^ y) & 1) == 0);
                    g.setFill(dark ? darkPaint : lightPaint);
                    g.fillRect(xPos + x * gridSize, yPos + y * gridSize, gridSize, gridSize);
                }
            }
        }
    }
    
    

    【讨论】:

    • 矩形在没有平滑的情况下很好地重新采样;调用g.fillOval() 来查看差异。
    • @Manius 如果你已经 11 岁了,那么不要太担心。你已经度过了所有真正艰难的突破性变化。我发现 11 点之后的一切都很顺利。我现在 17 岁,我围绕 Java 8 设计和构建的大型项目在适应 Java 11 的变化后没有任何问题。
    • 可能/希望 - 这就是我现在正在研究的内容,当然还有升级到 Java 17 所必需的 Gradle ......!所以,是的,有一些工作要做。 :)
    • @Manius 我也使用 Gradle,所以如果您对这方面有任何疑问,请告诉我。我已经将我的项目升级到 Gradle 7.3.1。
    • 明白了,谢谢。 :) 我使用的 sdkman 工具也告诉我 Gradle 7.3.2 现在可用!跟不上!
    猜你喜欢
    • 1970-01-01
    • 2011-07-18
    • 2011-04-17
    • 1970-01-01
    • 1970-01-01
    • 2021-04-25
    • 2015-06-20
    • 2020-08-12
    • 1970-01-01
    相关资源
    最近更新 更多