Why is javafx mangling my semi-transparent cursors? Why is javafx mangling my semi-transparent cursors? java java

Why is javafx mangling my semi-transparent cursors?


UPDATE: Upon deeper inspection it seems that JavaFX is not at fault - the fault seems to be in video driver implementations. Code below does work on some combinations of hardware, drivers and OSes - but not on all of them.

Unfortunately it seems that for now the best solution is to avoid cursors that have partially-transparent white or gray pixels. Partially-transparent black pixels are fine, though.


I found a way to work around the problem (tested on JDK 8 and Linux&Windows). It's ugly and requires reflection, but seems to work. Code below (in Scala syntax, but can easily be adapted to 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    )  }