JavaFX渲染PNG图片时透明度和清晰度丢失的问题

5

我正在加载并在渐变背景上显示透明PNG,结果如下:

enter image description here

如果我在Paint.NET中打开同一个文件,并添加背景,则看起来像这样:

enter image description here

不知何故,JavaFX使图像失去清晰度,我担心这可能是我的应用程序中所有图像的问题,只是在这种特殊情况下最容易看到。

以下是提取的代码,展示了我如何加载这个特定的图像:

ImageView imgDownload = new ImageView(this.getClass().getResource("/img/docstore/document_downloaded_btn.png").toExternalForm());
imgDownload.setFitWidth(59);
imgDownload.setFitHeight(32);
GridPane.setHalignment(imgDownload, HPos.CENTER);
grid_item.add(imgDownload, 3, 0);

供参考,这里是原始图片的链接

我正在寻找一个可能导致这种情况发生的原因的答案


看起来它正在尝试进行亚像素定位,可能会略微拉伸一点。 - Donal Fellows
这是我可以关闭的东西吗?或者有其他方法可以解决吗? - Dreen
我尝试将其设置为true或false,但没有帮助:http://docs.oracle.com/javafx/2/api/javafx/scene/layout/Region.html#snapToPixelProperty - Dreen
请接受jewelsea的答案;子像素定位是否由(可能的)工具包错误引起?棘手!(考虑到布局引擎要求渲染器执行的可怕任务,渲染器似乎做得非常好。) - Donal Fellows
1个回答

10

更新

Java 8中的JavaFX图像现在在所有情况下都可以清晰地呈现。

原先在问题描述中提到的问题已得到解决。

与此行为相关的Dreen提交的错误RT-28765 Image的子像素放置不正确,已被关闭为RT-19282 [StackPane] ImageView产生的不必要模糊的一个副本。RT-19282已在Java 8中修复。

我在Windows 7上测试了Java 8 build 108。此答案中的示例应用程序现在显示正确(没有模糊图像,有时在x或y方向上偏移半个像素)。


这是一个非常好的问题和非常好奇的行为。
我编写了一个示例程序,提供了可能的解释和解决方法。
运行该程序并拖动边框进行调整大小后,输出如下。左侧的模糊云是放置在GridPane中的标准ImageView。右侧的清晰云是包装在我的解决方案类(CenteredRegion)中的ImageView,也放置在同一个GridPane中。

fuzzyAndCrispClouds

当显示以上图片时,程序的输出为:
Layout SnapToPixel: true
...
fuzzy: New Bounds: BoundingBox [minX:14.5, minY:12.5, minZ:0.0, width:59.0, height:32.0, depth:0.0, maxX:73.5, maxY:44.5, maxZ:0.0]
fuzzy: xDisplacement: 0.5, yDisplacement: 0.5
crisp: New Bounds: BoundingBox [minX:84.0, minY:13.0, minZ:0.0, width:59.0, height:32.0, depth:0.0, maxX:143.0, maxY:45.0, maxZ:0.0]
crisp: xDisplacement: 0.0, yDisplacement: 0.0

如您所见,这张模糊的图片没有像素对齐,x和y方向偏移了半个像素,导致它看起来模糊。尽管网格区域的“snapToPixel”设置为“true”。
由于包含布局的舞台是动态调整大小的,因此模糊图像将在像素对齐和不对齐之间交替(取决于场景的宽度和高度的奇偶性)。这会在调整舞台边框时创建令人讨厌的闪烁效果,因为模糊图像不断在模糊和清晰之间交替。
当父容器的“snapToPixel”设置为true时,模糊图像的行为似乎是一个错误,因此建议针对JavaFX运行时项目提交漏洞报告,并在漏洞报告中链接回这个堆栈溢出问题,并在评论中放置一个指向创建的漏洞的链接。
清晰版本保持清晰,因为它位于自定义区域实现中,可以确保像素对齐。
测试系统为win7 + jdk8b77 + ATI HD4600图形卡。
import javafx.application.Application;
import static javafx.application.Application.launch;
import javafx.beans.value.*;
import javafx.geometry.*;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.image.*;
import javafx.scene.layout.*;
import static javafx.scene.layout.Region.USE_PREF_SIZE;
import javafx.stage.Stage;

public class TransparentPngSample extends Application {  
  public static final String IMAGE_LOC = "http://i.imgur.com/byY8whh.png";

  @Override public void start(Stage stage) {
    Pane layout = createSceneContent();
    stage.setScene(new Scene(layout));
    stage.show();

    System.out.println("Layout SnapToPixel: " + layout.snapToPixelProperty().get());
  }

  private Pane createSceneContent() {
    final Image cloudImage = new Image(IMAGE_LOC);
    final ImageView fuzzyCloud = new ImageView(cloudImage);
    final CenteredRegion crispCloud = new CenteredRegion(new ImageView(cloudImage));

    GridPane layout = new GridPane();
    layout.setHgap(10);
    layout.setVgap(10);
    layout.addRow(0, fuzzyCloud, crispCloud);
    layout.setAlignment(Pos.CENTER);
    layout.setStyle("-fx-padding: 10px; -fx-background-color: slategrey;");

    fuzzyCloud.boundsInParentProperty().addListener(new BoundsReporter("fuzzy"));
    crispCloud.boundsInParentProperty().addListener(new BoundsReporter("crisp"));

    return layout;
  }

  class CenteredRegion extends Region {
    private Node content;

    CenteredRegion(Node content) {
      this.content = content;
      getChildren().add(content);
    }

    @Override protected void layoutChildren() {
      content.relocate(
        Math.round(getWidth()  / 2 - content.prefWidth(USE_PREF_SIZE)  / 2), 
        Math.round(getHeight() / 2 - content.prefHeight(USE_PREF_SIZE) / 2)
      );

      System.out.println("crisp content relocated to: " +
        getLayoutX() + "," + getLayoutY()
      );
    }

    public Node getContent() {
      return content;
    }
  }

  class BoundsReporter implements ChangeListener<Bounds> {
    final String logPrefix;

    BoundsReporter(String logPrefix) {
      this.logPrefix = logPrefix;
    }

    @Override public void changed(ObservableValue<? extends Bounds> ov, Bounds oldBounds, Bounds newBounds) {
      System.out.println(logPrefix + ": " + 
        "New Bounds: " + newBounds
      );
      double xDisplacement = newBounds.getMinX() - Math.floor(newBounds.getMinX());
      double yDisplacement = newBounds.getMinY() - Math.floor(newBounds.getMinY());
      System.out.println(logPrefix + ": " + 
        "xDisplacement: " + xDisplacement + ", " + 
        "yDisplacement: " + yDisplacement
      );
    }
  }          

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

听起来可能是由于有人忘记在浮点上下文中使用了dimension / 2,或者有人将类型从整数切换到浮点(以修复其他问题)所致。这种微妙的问题是一个真正的经典! - Donal Fellows
感谢您的回答,jewelsea。我会在实现和测试后尽快接受它,希望今天能完成。 - Dreen

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接