JavaFX缩放包含Canvas网格的Pane时节点间出现背景白线如何解决
问题描述
我有一个Pane容器,内部排布了由Canvas组成的网格。给这些Canvas上色后,调整Pane的缩放比例时,Canvas之间会出现白色线条。
缩放前效果图
缩放后效果图
现有代码逻辑:当前场景中包含一个Pane,鼠标拖拽时,通过存储了所有Canvas对应x、y索引的HashMap查找对应位置的Canvas,将找到的Canvas填充为黑色;鼠标滚动时,Pane的缩放比例每次调整0.01。
原有完整代码:
import javafx.application.Application; import javafx.scene.Scene; import javafx.scene.canvas.Canvas; import javafx.scene.layout.Pane; import javafx.scene.paint.Color; import javafx.stage.Stage; import java.util.HashMap; import java.util.Map; import java.util.Objects; public class Main extends Application { private static final double SIZE = 10; public static void main(String[] args) { launch(); } @Override public void start(Stage stage) { Pane pane = new Pane(); Scene scene = new Scene(pane); pane.setOnMouseDragged(mouseEvent -> { int x = (int) (mouseEvent.getX() / SIZE); int y = (int) (mouseEvent.getY() / SIZE); Plot plot = new Plot(x, y); Canvas canvas = canvases.computeIfAbsent(plot, plot1 -> { Canvas newCanvas = new Canvas(SIZE, SIZE); pane.getChildren().add(newCanvas); newCanvas.setLayoutX(x * SIZE); newCanvas.setLayoutY(y * SIZE); return newCanvas; }); canvas.getGraphicsContext2D().setFill(Color.BLACK); canvas.getGraphicsContext2D().fillRect(0, 0, 50, 50); }); scene.setOnScroll(scrollEvent -> { double scale = pane.getScaleX() + (scrollEvent.getDeltaY() > 0 ? 0.01 : -0.01); pane.setScaleX(scale); pane.setScaleY(scale); }); stage.setScene(scene); stage.show(); } private static class Plot { private final int x; private final int y; private Plot(int x, int y) { this.x = x; this.y = y; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; Plot plot = (Plot) o; return x == plot.x && y == plot.y; } @Override public int hashCode() { return Objects.hash(x, y); } } private final Map<Plot, Canvas> canvases = new HashMap<>(); }
问题原因
- JavaFX对节点执行缩放渲染时,浮点坐标计算存在精度误差,再加上默认开启的抗锯齿采样,会导致相邻Canvas的边缘出现亚像素级别的间隙,透出Pane的白色背景,就表现为你看到的白色线条。
- 你代码中Canvas的尺寸和布局坐标都为整数,缩放后坐标转换为浮点数出现小数,相邻节点的边缘无法完全贴合,进一步放大了间隙问题。
解决方案
按下面的步骤修改代码即可彻底消除白线:
- 关闭Canvas的图像平滑渲染,避免抗锯齿导致的亚像素间隙
- 给Pane设置与Canvas填充色一致的黑色背景,就算存在极微小的间隙也不会透出白色
- 给Canvas的宽高各加0.5px的冗余量,完全覆盖可能的间隙区域
- 增加缩放倍数的范围限制,避免缩放值过大或过小导致的精度异常
修改后的完整代码:
import javafx.application.Application; import javafx.scene.Scene; import javafx.scene.canvas.Canvas; import javafx.scene.canvas.GraphicsContext; import javafx.scene.layout.Background; import javafx.scene.layout.BackgroundFill; import javafx.scene.layout.Pane; import javafx.scene.paint.Color; import javafx.stage.Stage; import java.util.HashMap; import java.util.Map; import java.util.Objects; public class Main extends Application { private static final double SIZE = 10; // 边缘冗余像素,完全覆盖间隙 private static final double EDGE_PADDING = 0.5; private final Map<Plot, Canvas> canvases = new HashMap<>(); public static void main(String[] args) { launch(); } @Override public void start(Stage stage) { Pane pane = new Pane(); // Pane设置黑色背景,避免间隙透出白色 pane.setBackground(new Background(new BackgroundFill(Color.BLACK, null, null))); Scene scene = new Scene(pane, Color.BLACK); pane.setOnMouseDragged(mouseEvent -> { int x = (int) (mouseEvent.getX() / SIZE); int y = (int) (mouseEvent.getY() / SIZE); Plot plot = new Plot(x, y); Canvas canvas = canvases.computeIfAbsent(plot, plot1 -> { Canvas newCanvas = new Canvas(SIZE + EDGE_PADDING, SIZE + EDGE_PADDING); GraphicsContext gc = newCanvas.getGraphicsContext2D(); // 关闭图像平滑,消除抗锯齿带来的间隙 gc.setImageSmoothing(false); pane.getChildren().add(newCanvas); newCanvas.setLayoutX(x * SIZE); newCanvas.setLayoutY(y * SIZE); return newCanvas; }); canvas.getGraphicsContext2D().setFill(Color.BLACK); canvas.getGraphicsContext2D().fillRect(0, 0, SIZE + EDGE_PADDING, SIZE + EDGE_PADDING); }); scene.setOnScroll(scrollEvent -> { double scale = pane.getScaleX() + (scrollEvent.getDeltaY() > 0 ? 0.01 : -0.01); // 限制缩放范围,避免精度异常 scale = Math.max(0.1, Math.min(10, scale)); pane.setScaleX(scale); pane.setScaleY(scale); }); stage.setScene(scene); stage.show(); } private static class Plot { private final int x; private final int y; private Plot(int x, int y) { this.x = x; this.y = y; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; Plot plot = (Plot) o; return x == plot.x && y == plot.y; } @Override public int hashCode() { return Objects.hash(x, y); } } }
内容的提问来源于stack exchange,提问作者Zev G
相关产品推荐
相关产品推荐

