Чому javafx псує мої напівпрозорі курсори?


75

Нижче розташовані два зображення PNG:

введіть тут опис зображення введіть тут опис зображення

Візуально вони абсолютно ідентичні - єдина відмінність полягає в тому, що один має напівпрозорий фон у деяких пікселях (ви можете завантажити зображення, щоб перевірити це).

Але коли я використовую ці зображення як курсор зображень на вузлах JavaFX, я отримую такий результат:

введіть тут опис зображення введіть тут опис зображення

Перший курсор (без частково прозорих пікселів) все ще чіткий, але другий спотворюється.

Деякий час боровшись із проблемою, я виявив алгоритм, який враховує цю різницю - режим змішування:

  • «Очікуваний» шлях (який ви можете бачити в цьому браузері, наприклад), щоб взяти суму значень для кожного каналу, зважений альфа значень: (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();
    }
}

Як я можу досягти належного відображення таких напівпрозорих курсорів?



2
@Tschallacka - Так, я зробив. Курсор JFXCustom не дуже хороший (він заїкається з додатком, тоді як рідний курсор працює безперебійно), і, наприклад, у запитанні, яке ви зв’язали, страждає від тієї ж проблеми - білий колір робиться чорним, коли напівпрозорий. Ось трохи переписаний код з цього питання, який висвітлює проблему: pastebin.com/G5D0wK80
Рогач,

2
Растрову графіку не слід використовувати в таких графічних елементах, як курсори. Зображення, яке ви використовуєте, масштабується і таким чином спотворюється. Спробуйте додати кілька зображень різних розмірів та скористайтеся ImageCursor.chooseBestCursor()функцією. Для мене зображення розміром 240 х 240 пікселів чудово працювало.

11
@Rogach Не могли б ви подати помилку для цього і дозволити команді JavaFX поглянути?
ІтачіУчіха

Відповіді:


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
    )
  }


Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.