Unten sind zwei PNG-Bilder:
Optisch sind sie genau identisch - der einzige Unterschied besteht darin, dass einige Pixel einen halbtransparenten Hintergrund haben (Sie können die Bilder herunterladen, um sie zu überprüfen).
Wenn ich diese Bilder jedoch als Bildcursor auf JavaFX-Knoten verwende, erhalte ich das folgende Ergebnis:
Der erste Cursor (ohne teilweise transparente Pixel) ist immer noch scharf, der zweite wird jedoch verzerrt.
Nachdem ich eine Weile mit dem Problem gekämpft hatte, entdeckte ich den Algorithmus, der diesen Unterschied erklärt - den Mischmodus:
Der "erwartete" Weg (den Sie beispielsweise in diesem Browser sehen können) besteht darin, die Summe der Werte pro Kanal zu nehmen, gewichtet mit Alpha-Werten :
(1 - alpha) * background_color + alpha * foreground_color
."JavaFX Cursor" gibt die andere Formel an:
(1 - alpha) * background_color + alpha^2 * foreground_color
(beachten Sie das Quadrat).
Ich habe die Verzerrung entdeckt, kann aber nicht herausfinden, was ich falsch gemacht habe und wie ich dieses Problem beheben kann.
Hier ist der vollständige ausführbare Quellcode für mein Testprogramm:
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();
}
}
Wie kann ich solche halbtransparenten Cursor richtig rendern?
ImageCursor.chooseBestCursor()
Funktion. Für mich hat ein Bild mit den Abmessungen 240 x 240 Pixel großartig funktioniert.Antworten:
UPDATE : Bei genauerer Betrachtung scheint JavaFX nicht fehlerhaft zu sein - der Fehler scheint in Implementierungen von Videotreibern zu liegen. Der folgende Code funktioniert auf einigen Kombinationen von Hardware, Treibern und Betriebssystemen - jedoch nicht auf allen.
Leider scheint es derzeit die beste Lösung zu sein, Cursor mit teilweise transparenten weißen oder grauen Pixeln zu vermeiden. Teiltransparente schwarze Pixel sind jedoch in Ordnung.
Ich habe einen Weg gefunden, um das Problem zu umgehen (getestet unter JDK 8 und Linux & Windows). Es ist hässlich und erfordert Reflexion, scheint aber zu funktionieren. Code unten (in Scala-Syntax, kann aber leicht an Java angepasst werden):
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 ) }
quelle