Warum verstümmelt javafx meine halbtransparenten Cursor?

Lesezeit: 4 Minuten

Benutzer-Avatar
Rogach

Unten sind zwei PNG-Bilder:

Geben Sie hier die Bildbeschreibung ein
Geben Sie hier die Bildbeschreibung ein

Optisch sind sie genau identisch – der einzige Unterschied besteht darin, dass man in einigen Pixeln den halbtransparenten Hintergrund hat (Sie können die Bilder herunterladen, um es zu überprüfen).

Aber wenn ich diese Bilder als Bildcursor auf JavaFX-Knoten verwende, erhalte ich das folgende Ergebnis:

Geben Sie hier die Bildbeschreibung ein
Geben Sie hier die Bildbeschreibung ein

Der erste Cursor (ohne teilweise transparente Pixel) ist immer noch scharf, aber der zweite wird verzerrt.

Nachdem ich eine Weile mit dem Problem gekämpft hatte, entdeckte ich den Algorithmus, der diesen Unterschied ausmacht – 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, aber ich kann nicht herausfinden, was ich falsch gemacht habe und wie ich dieses Problem beheben kann.

Hier ist der vollständige lauffähige 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 ein korrektes Rendering solcher halbtransparenter Cursor erreichen?

  • hast du dir stackoverflow.com/questions/39692625/… angesehen

    – Tschallacka

    24. Mai 2018 um 8:17 Uhr

  • @Tschallacka – Ja, das habe ich. Der JFXCustom-Cursor ist leistungsmäßig nicht gut (er stottert mit der Anwendung, während der native Cursor reibungslos funktioniert), und das Beispiel in der von Ihnen verlinkten Frage leidet unter demselben Problem – weiße Farbe wird als schwarz gerendert, wenn sie halbtransparent ist. Hier ist ein leicht umgeschriebener Code aus dieser Frage, der das Problem hervorhebt: pastebin.com/G5D0wK80

    – Rogach

    24. Mai 2018 um 10:12 Uhr

  • Rastergrafiken sollten nicht in Grafikelementen wie Cursors verwendet werden. Das verwendete Bild wird vergrößert und somit verzerrt. Versuchen Sie, mehrere Bilder mit unterschiedlichen Abmessungen hinzuzufügen und zu verwenden ImageCursor.chooseBestCursor() Funktion. Für mich hat ein Bild der Größe 240 x 240 px hervorragend funktioniert.

    Benutzer7909000

    24. Mai 2018 um 19:24 Uhr

  • @ Rogach Könntest du einen Fehler melden dafür und lassen Sie das JavaFX-Team einen Blick darauf werfen?

    – ItachiUchiha

    25. Mai 2018 um 11:05 Uhr

  • github.com/javafxports/openjdk-jfx/issues/101

    – Ryan Leach

    13. Juni 2018 um 2:09 Uhr

Benutzer-Avatar
Rogach

AKTUALISIEREN: Bei genauerer Betrachtung scheint JavaFX nicht schuld zu sein – der Fehler scheint in den Videotreiberimplementierungen zu liegen. Der folgende Code funktioniert auf einigen Kombinationen von Hardware, Treibern und Betriebssystemen – aber nicht auf allen.

Leider scheint es vorerst die beste Lösung zu sein, Cursor mit teilweise transparenten weißen oder grauen Pixeln zu vermeiden. Teilweise transparente schwarze Pixel sind jedoch in Ordnung.


Ich habe einen Weg gefunden, das Problem zu umgehen (getestet auf 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
    )
  }


1176690cookie-checkWarum verstümmelt javafx meine halbtransparenten Cursor?

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy