Reputation: 7255
Edit-Answer:
You can check Fabian's answer
and also this library (https://github.com/goxr3plus/JFXCustomCursor)
Actual Question
I want to create a cursor which is fading out in JavaFX
so for that i am using a WritableImage
and i am continuously reading pixels from the original Image
and writing them to a new WritableImage
.Then i set a custom cursor to the Scene
using ImageCursor(writableImage)
,below is the full code(give it a try).
The problem is that a get black pixels where transparent pixels are expected.
Note that all the below classes have to be in package sample.
Code(Main):
package sample;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.layout.FlowPane;
import javafx.stage.Stage;
public class Main extends Application {
FadingCursor fade = new FadingCursor();
@Override
public void start(Stage primaryStage) throws Exception {
primaryStage.setWidth(300);
primaryStage.setHeight(300);
Scene scene = new Scene(new FlowPane());
primaryStage.setScene(scene);
fade.startFade(scene,100);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
Code(FadingCursor)(Edited):
package sample;
import java.util.concurrent.CountDownLatch;
import javafx.application.Platform;
import javafx.scene.ImageCursor;
import javafx.scene.Scene;
import javafx.scene.image.Image;
import javafx.scene.image.PixelReader;
import javafx.scene.image.PixelWriter;
import javafx.scene.image.WritableImage;
import javafx.scene.paint.Color;
public class FadingCursor {
private int counter;
private Image cursorImage;
/**
* Change the image of the Cursor
*
* @param image
*/
public void setCursorImage(Image image) {
this.cursorImage = image;
}
/**
* Start fading the Cursor
*
* @param scene
*/
public void startFade(Scene scene, int millisecondsDelay) {
// Create a Thread
new Thread(() -> {
// Keep the original image stored here
Image image = new Image(getClass().getResourceAsStream("fire.png"), 64, 64, true, true);
PixelReader pixelReader = image.getPixelReader();
// Let's go
counter = 10;
for (; counter >= 0; counter--) {
CountDownLatch count = new CountDownLatch(1);
Platform.runLater(() -> {
// Create the fading image
WritableImage writable = new WritableImage(64, 64);
PixelWriter pixelWriter = writable.getPixelWriter();
// Fade out the image
for (int readY = 0; readY < image.getHeight(); readY++) {
for (int readX = 0; readX < image.getWidth(); readX++) {
Color color = pixelReader.getColor(readX, readY);
// Now write a brighter color to the PixelWriter.
// -------------------------Here some way happens
// the problem------------------
color = new Color(color.getRed(), color.getGreen(), color.getBlue(), (counter / 10.00) * color.getOpacity());
pixelWriter.setColor(readX, readY, color);
}
}
System.out.println("With counter:"+counter+" opacity is:" + writable.getPixelReader().getColor(32, 32).getOpacity());
scene.setCursor(new ImageCursor(writable));
count.countDown();
});
try {
// Wait JavaFX Thread to change the cursor
count.await();
// Sleep some time
Thread.sleep(millisecondsDelay);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
}
}
The image(needs to be downloaded)(Right Click ->Save Image as...):
Upvotes: 1
Views: 896
Reputation: 7255
The reason of this question was to create a kind of cursor that can be modified.For example here i wanted to make it had a fade effect.For future users who want to create custom cursors i have created a library on github and i will show some code here: https://github.com/goxr3plus/JFXCustomCursor
Code:
import javafx.application.Platform;
import javafx.beans.Observable;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.collections.ObservableList;
import javafx.event.EventHandler;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.Pane;
/**
* This class allows you to set as a Cursor in a JavaFX Scene,whatever you want
* ,even a video!. <br>
* <br>
* <b>What you have to do is create a basic layout,for example:</b><br>
* #-->A BorderPane which contains a MediaView,<br>
* #-->A StackPane which contains an animated ImageView,<br>
* #-->A Pane which contains an animated Rectangle or something more complex
* etc..)<br>
*
* <br>
* <br>
* The options are unlimited!
*
* @author GOXR3PLUS
* @param <T>
* @Version 1.0
*/
public class JFXCustomCursor {
private SimpleIntegerProperty hotSpotX = new SimpleIntegerProperty();
private SimpleIntegerProperty hotSpotY = new SimpleIntegerProperty();
private Scene scene;
private Pane sceneRoot;
private Pane content;
private EventHandler<MouseEvent> eventHandler1;
private EventHandler<MouseEvent> eventHandler2;
private EventHandler<MouseEvent> eventHandler3;
/**
* Constructor
*
* @param scene
* The Scene of your Stage
* @param sceneRoot
* The Root of your Stage Scene
* @param content
* The content of the JFXCustomCursor class
* @param hotspotX
* Represents the location of the cursor inside the content on X
* axis
* @param hotspotY
* Represents the location of the cursor inside the content on Y
* axis
*/
public JFXCustomCursor(Scene scene, Pane sceneRoot, Pane content, int hotspotX, int hotspotY) {
// Go
setRoot(scene, sceneRoot, content, hotspotX, hotspotY);
}
/**
* This method changes the content of the JFXCustomCursor
*
* @param scene
* The Scene of your Stage
* @param sceneRoot
* The Root of your Stage Scene
* @param content
* The content of the JFXCustomCursor class
* @param hotspotX
* Represents the location of the cursor inside the content on X
* axis
* @param hotspotY
* Represents the location of the cursor inside the content on Y
* axis
*/
public void setRoot(Scene scene, Pane sceneRoot, Pane content, int hotSpotX, int hotSpotY) {
// Keep them in case of unRegister-reRegister
unRegister(); // has to be called before the below happens
this.scene = scene;
this.sceneRoot = sceneRoot;
this.content = content;
// hot spots
this.hotSpotX.set(hotSpotX);
this.hotSpotX.set(hotSpotY);
// cursor container
content.setManaged(false);
content.setMouseTransparent(true);
// Keep the Content on the top of Scene
ObservableList<Node> observable = sceneRoot.getChildren();
observable.addListener((Observable osb) -> {
if (content.getParent() != null && observable.get(observable.size() - 1) != content) {
// move the cursor on the top
Platform.runLater(content::toFront);
}
});
if (!observable.contains(content))
observable.add(content);
// Add the event handlers
eventHandler1 = evt -> {
if (!sceneRoot.getChildren().contains(content))
observable.add(content);
};
eventHandler2 = evt -> observable.remove(content);
eventHandler3 = evt -> {
content.setLayoutX(evt.getX() - hotSpotX);
content.setLayoutY(evt.getY() - hotSpotY);
};
scene.addEventFilter(MouseEvent.MOUSE_ENTERED, eventHandler1);
scene.addEventFilter(MouseEvent.MOUSE_EXITED, eventHandler2);
scene.addEventFilter(MouseEvent.MOUSE_MOVED, eventHandler3);
}
/**
* Unregisters the CustomCursor from the Scene completely
*/
public void unRegister() {
if (scene != null) {
sceneRoot.getChildren().remove(content);
scene.removeEventFilter(MouseEvent.MOUSE_ENTERED, eventHandler1);
scene.removeEventFilter(MouseEvent.MOUSE_EXITED, eventHandler2);
scene.removeEventFilter(MouseEvent.MOUSE_MOVED, eventHandler3);
}
}
/**
* Re register the CustomCursor to the Scene,<b>this method is
* experimental(use with caution!)</b>
*/
public void reRegister() {
if (scene != null)
setRoot(scene, sceneRoot, content, hotSpotX.get(), hotSpotY.get());
}
public SimpleIntegerProperty hotSpotXProperty() {
return hotSpotX;
}
public SimpleIntegerProperty hotSpotYProperty() {
return hotSpotY;
}
}
Upvotes: 1
Reputation: 82461
You set the opacity of every pixel to a value only depending on the loop variable here:
color = new Color(color.getRed(), color.getGreen(), color.getBlue(), counter / 10.00);
For transparent pixels (opacity = 0) you actually increase the opacity making the values stored in the other channels (in this case 0 / black) visible. You need to make sure transparent pixels remain transparent, which usually is done like this:
color = new Color(color.getRed(), color.getGreen(), color.getBlue(), (counter / 10.00) * color.getOpacity());
Alternatively you could use deriveColor
:
color = color.deriveColor(0, 1, 1, counter / 10d);
Edit
For some reason ImageCursor
doesn't seem to like a completely transparent image. You can check that this works, if at least one pixel is not completely transparent by adding
pixelWriter.setColor(0, 0, new Color(0, 0, 0, 0.01));
After the for loops writing the image.
To fix this you could simply use Cursor.NONE
instead of an ImageCursor
with a fully transparent image:
for (; counter >= 1; counter--) {
...
}
Platform.runLater(() -> scene.setCursor(Cursor.NONE));
You could simulate the cursor yourself by moving a image Across the root of the Scene
. This won't make the image show up beyond the bounds of the Scene
, but you can apply animations to the ImageView
for fading instead of modifying the opacity of each pixel manually...
public class CursorSimulator {
private final FadeTransition fade;
public CursorSimulator(Image image, Scene scene, ObservableList<Node> rootChildrenWriteable, double hotspotX, double hotspotY) {
ImageView imageView = new ImageView(image);
imageView.setManaged(false);
imageView.setMouseTransparent(true);
fade = new FadeTransition(Duration.seconds(2), imageView);
fade.setFromValue(0);
fade.setToValue(1);
// keep image on top
rootChildrenWriteable.addListener((Observable o) -> {
if (imageView.getParent() != null
&& rootChildrenWriteable.get(rootChildrenWriteable.size() - 1) != imageView) {
// move image to top, after changes are done...
Platform.runLater(() -> imageView.toFront());
}
});
scene.addEventFilter(MouseEvent.MOUSE_ENTERED, evt -> {
rootChildrenWriteable.add(imageView);
});
scene.addEventFilter(MouseEvent.MOUSE_EXITED, evt -> {
rootChildrenWriteable.remove(imageView);
});
scene.addEventFilter(MouseEvent.MOUSE_MOVED, evt -> {
imageView.setLayoutX(evt.getX() - hotspotX);
imageView.setLayoutY(evt.getY() - hotspotY);
});
scene.setCursor(Cursor.NONE);
}
public void fadeOut() {
fade.setRate(-1);
if (fade.getStatus() != Animation.Status.RUNNING) {
fade.playFrom(fade.getTotalDuration());
}
}
public void fadeIn() {
fade.setRate(1);
if (fade.getStatus() != Animation.Status.RUNNING) {
fade.playFromStart();
}
}
}
@Override
public void start(Stage primaryStage) {
Button btn = new Button("Say 'Hello World'");
btn.setOnAction((ActionEvent event) -> {
System.out.println("Hello World!");
});
StackPane root = new StackPane();
root.getChildren().add(btn);
Scene scene = new Scene(root, 500, 500);
Image image = new Image("https://i.sstatic.net/OHj1R.png");
CursorSimulator simulator = new CursorSimulator(image, scene, root.getChildren(), 32, 50);
scene.setOnMouseClicked(new EventHandler<MouseEvent>() {
private boolean fadeOut = true;
@Override
public void handle(MouseEvent event) {
if (fadeOut) {
simulator.fadeOut();
} else {
simulator.fadeIn();
}
fadeOut = !fadeOut;
}
});
primaryStage.setScene(scene);
primaryStage.show();
}
Upvotes: 2