为啥 javafx 会破坏我的半透明游标?
Posted
技术标签:
【中文标题】为啥 javafx 会破坏我的半透明游标?【英文标题】:Why is javafx mangling my semi-transparent cursors?为什么 javafx 会破坏我的半透明游标? 【发布时间】:2018-03-07 16:00:51 【问题描述】:下面是两张PNG图片:
在视觉上它们完全一样——唯一的区别是在某些像素中具有半透明背景(您可以下载图像进行检查)。
但是当我将这些图像用作 JavaFX 节点上的图像光标时,我得到以下结果:
第一个光标(没有部分透明像素)仍然清晰,但第二个会失真。
在与问题斗争了一段时间后,我发现了导致这种差异的算法 - 混合模式:
“预期”方式(例如,您可以在此浏览器中看到)是对每个通道的值求和,按 alpha 值加权:(1 - alpha) * background_color + alpha * foreground_color
。
“JavaFX 光标”给出了不同的公式:(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();
我怎样才能实现这种半透明光标的正确渲染?
【问题讨论】:
你看过***.com/questions/39692625/… @Tschallacka - 是的,我做到了。 JFXCustom 光标在性能方面不好(它与应用程序结结巴巴,而本机光标工作顺利),并且您链接的问题中的示例遇到了同样的问题 - 白色在半透明时呈现为黑色。这是该问题的稍微重写的代码,突出了问题:pastebin.com/G5D0wK80 光栅图形不应用于光标等图形元素。您使用的图像被放大并因此失真。尝试添加多个不同尺寸的图像并使用ImageCursor.chooseBestCursor()
函数。对我来说,尺寸为 240 x 240 像素的图像效果很好。
@Rogach 你能不能 file a bug 让 JavaFX 团队看看?
github.com/javafxports/openjdk-jfx/issues/101
【参考方案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
)
【讨论】:
以上是关于为啥 javafx 会破坏我的半透明游标?的主要内容,如果未能解决你的问题,请参考以下文章