为什么javafx会扭曲半透明游标?


75

以下是两个PNG图片:

在此处输入图片说明 在此处输入图片说明

在视觉上它们是完全相同的-唯一的不同是一个像素的某些像素具有半透明的背景(您可以下载图像进行检查)。

但是,当我将这些图像用作JavaFX节点上的图像光标时,得到以下结果:

在此处输入图片说明 在此处输入图片说明

第一个光标(没有部分透明的像素)仍然清晰,但是第二个光标变形了。

解决了一段时间后,我发现了解决这种差异的算法-混合模式:

  • “预期”方式(例如,您可以在此浏览器中看到)是获取每个通道的值之和,并以alpha值加权:(1 - alpha) * background_color + alpha * foreground_color

  • “ JavaFX Cursor”给出了不同的公式:((1 - alpha) * background_color + alpha^2 * foreground_color请注意方框)。

我发现了这种失真,但是我无法弄清楚自己做错了什么以及如何解决此问题。

这是我的测试程序的完整可运行源代码:

import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
import javafx.scene.ImageCursor;
import javafx.scene.image.Image;

public class HelloWorld extends Application {

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

    @Override
    public void start(Stage primaryStage) {
        System.out.println(ImageCursor.getBestSize(32, 32));

        primaryStage.setTitle("Hello World!");

        StackPane root = new StackPane();
        root.setCursor(new ImageCursor(new Image("/test-cursor.png"), 0, 0));

        primaryStage.setScene(new Scene(root, 100, 100));
        primaryStage.show();
    }
}

如何实现这种半透明游标的正确渲染?



2
@Tschallacka-是的,我做到了。JFXCustom游标的性能不好(它在应用程序中结结巴巴,而本机游标则运行顺畅),并且您链接的问题中的示例也遇到了同样的问题-半透明时白色呈现为黑色。这是从该问题略作重写的代码,突出了该问题:pastebin.com/G5D0wK80
Rogach '18

2
光栅图形不应在光标之类的图形元素中使用。您正在使用的图像会放大并失真。尝试添加多个不同尺寸的图像并使用ImageCursor.chooseBestCursor()功能。对我来说,尺寸为240 x 240像素的图像效果很好。

11
@Rogach您可以为此提交一个错误,让JavaFX团队看看吗?
ItachiUchiha

Answers:


1

更新:经过更深入的检查,似乎JavaFX没错-错误似乎出在视频驱动程序实现中。下面的代码确实可以在硬件,驱动程序和操作系统的某些组合上工作,但不适用于所有这些。

不幸的是,目前看来,最好的解决方案是避免具有部分透明的白色或灰色像素的光标。不过,部分透明的黑色像素也可以。


我找到了解决该问题的方法(已在JDK 8和Linux&Windows上测试)。这很丑陋,需要反思,但似乎可行。下面的代码(采用Scala语法,但可以轻松地适应Java):

  import com.sun.prism.PixelFormat
  import javafx.scene.ImageCursor
  import javafx.scene.image.{Image, WritableImage}

  private def undoPremultipliedAlpha(image: Image): Image = {
    // Fixes JavaFX bug with semi-transparent cursors -
    // somewhere deep in JavaFX code they premultiply alpha
    // on already premultiplied image, which screws up transparencies.
    // This method attempts to counteract it by removing premultiplied alpha
    // directly from bytes of internal JavaFX image.

    def getPlatformImage(image: Image) = image.impl_getPlatformImage()

    val platformImage = getPlatformImage(image)

    val pixelFormat = platformImage.getClass.getDeclaredMethod("getPixelFormat").invoke(platformImage).asInstanceOf[PixelFormat]
    if (pixelFormat != PixelFormat.BYTE_BGRA_PRE) {
      println(s"wrong platform image pixel format (${pixelFormat}), unable to apply cursor transparency bug workaround")
    } else {
      val pixelBufferField = platformImage.getClass.getDeclaredField("pixelBuffer")
      pixelBufferField.setAccessible(true)
      val pixelBuffer = pixelBufferField.get(platformImage).asInstanceOf[java.nio.Buffer]
      val pixelArray = pixelBuffer.array().asInstanceOf[Array[Byte]]
      for (i <- 0 until pixelArray.length / 4) {

        val alpha = (pixelArray(i * 4 + 3).toInt & 0xff) / 255.0
        if (alpha != 0) {
          pixelArray(i * 4) = math.min(255, math.max(0, ((pixelArray(i * 4).toInt & 0xff).toDouble / alpha))).toInt.toByte
          pixelArray(i * 4 + 1) = math.min(255, math.max(0, ((pixelArray(i * 4 + 1).toInt & 0xff).toDouble / alpha))).toInt.toByte
          pixelArray(i * 4 + 2) = math.min(255, math.max(0, ((pixelArray(i * 4 + 2).toInt & 0xff).toDouble / alpha))).toInt.toByte
        }
      }
    }

    image
  }

  def createImageCursor(resource: String, hotspotX: Int, hotspotY: Int): ImageCursor = {
    new ImageCursor(
      undoPremultipliedAlpha(
        new Image(resource)),
      hotspotX,
      hotspotY
    )
  }


By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.