You maybe have seen some apps that when the String in a tab can't be fully shown then it is animated and is going back and front so the user can see what String the tab contains.Android is doing that is settings when your phone display is not enough to show the the whole label.
Below is a code to achieve it in JavaFX using Service,but it is not a good way.
The Question is:
Here is how i can do it using Animation or another build in JavaFX class?
Code:
import javafx.application.Platform;
import javafx.concurrent.Service;
import javafx.concurrent.Task;
import javafx.scene.control.Label;
import javafx.scene.paint.Color;
import javafx.scene.text.Font;
import javafx.scene.text.FontWeight;
import tools.InfoTool;
public class MoveTitleService extends Service<Void>{
private String title;
volatile boolean doAnimation;
private int counter;
public Label movingText = new Label("A reallyyy big teeeeexxxxxxxxxxxxxxxxxxxxxxxt");
/**
*Constructor
*/
public MoveTitleService() {
movingText.setFont(Font.font("null",FontWeight.BOLD,14));
movingText.setTextFill(Color.WHITE);
setOnSucceeded( s ->{
movingText.setText("");
});
}
//Start the Service
public void startTheService(String title) {
this.title = title;
doAnimation = true;
restart();
}
//Stop the Service
public void stopService(){
doAnimation=false;
}
#Override
protected Task<Void> createTask() {
return new Task<Void>() {
#Override
protected Void call() throws Exception {
while (doAnimation) {
//System.out.println("MoveTitleService is Running...");
// One letter at a time
for (int m = 0; m <= title.length(); m++) {
counter=m;
Platform.runLater( () ->{
movingText.setText(title.substring(0, counter) + addSpaces(title.length() - counter));
});
if(!doAnimation) break;
Thread.sleep(150);
}
// Disappearing to back
for (int m = 0; m < title.length(); m++) {
counter=m;
Platform.runLater( () ->{
movingText.setText(title.substring(counter));
});
if(!doAnimation) break;
Thread.sleep(150);
}
// Appearing to front
for (int m = 1; m <= title.length(); m++) {
counter=m;
Platform.runLater( () ->{
movingText.setText(title.substring(title.length() - counter));
});
if(!doAnimation) break;
Thread.sleep(150);
}
if(!doAnimation) break;
for(int i=0; i<3000/150; i++)
Thread.sleep(150);
Thread.sleep(3000);
}
return null;
}
private String addSpaces(int spaces) {
String z = "";
for (int i = 0; i <= spaces; i++)
z += " ";
return z;
}
};
}
}
You could use a Timeline for this:
// min distance to Pane bounds
private static final double OFFSET = 25;
#Override
public void start(Stage primaryStage) {
Text text = new Text("A reallyyy big teeeeexxxxxxxxxxxxxxxxxxxxxxxt!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!");
text.setLayoutY(25);
text.setManaged(false);
text.setLayoutX(OFFSET);
Pane pane = new Pane(text);
pane.setMinHeight(50);
Timeline timeline = new Timeline();
KeyFrame updateFrame = new KeyFrame(Duration.seconds(1 / 60d), new EventHandler<ActionEvent>() {
private boolean rightMovement;
#Override
public void handle(ActionEvent event) {
double tW = text.getLayoutBounds().getWidth();
double pW = pane.getWidth();
double layoutX = text.getLayoutX();
if (2 * OFFSET + tW <= pW && layoutX >= OFFSET) {
// stop, if the pane is large enough and the position is correct
text.setLayoutX(OFFSET);
timeline.stop();
} else {
if ((rightMovement && layoutX >= OFFSET) || (!rightMovement && layoutX + tW + OFFSET <= pW)) {
// invert movement, if bounds are reached
rightMovement = !rightMovement;
}
// update position
if (rightMovement) {
layoutX += 1;
} else {
layoutX -= 1;
}
text.setLayoutX(layoutX);
}
}
});
timeline.getKeyFrames().add(updateFrame);
timeline.setCycleCount(Animation.INDEFINITE);
// listen to bound changes of the elements to start/stop the animation
InvalidationListener listener = o -> {
double textWidth = text.getLayoutBounds().getWidth();
double paneWidth = pane.getWidth();
if (textWidth + 2 * OFFSET > paneWidth
&& timeline.getStatus() != Animation.Status.RUNNING) {
timeline.play();
}
};
text.layoutBoundsProperty().addListener(listener);
pane.widthProperty().addListener(listener);
Scene scene = new Scene(pane);
primaryStage.setScene(scene);
primaryStage.show();
}
Note that repeadedly updating the position yourself needs to be done, since Animations cannot be adjusted while running, so for any resizing to take effect during the animation, you need repeated updates...
Related
You maybe have seen some apps that when the String in a tab can't be fully shown then it is animated and is going back and front so the user can see what String the tab contains.Android is doing that is settings when your phone display is not enough to show the the whole label.
Below is a code to achieve it in JavaFX using Service,but it is not a good way.
The Question is:
Here is how i can do it using Animation or another build in JavaFX class?
Code:
import javafx.application.Platform;
import javafx.concurrent.Service;
import javafx.concurrent.Task;
import javafx.scene.control.Label;
import javafx.scene.paint.Color;
import javafx.scene.text.Font;
import javafx.scene.text.FontWeight;
import tools.InfoTool;
public class MoveTitleService extends Service<Void>{
private String title;
volatile boolean doAnimation;
private int counter;
public Label movingText = new Label("A reallyyy big teeeeexxxxxxxxxxxxxxxxxxxxxxxt");
/**
*Constructor
*/
public MoveTitleService() {
movingText.setFont(Font.font("null",FontWeight.BOLD,14));
movingText.setTextFill(Color.WHITE);
setOnSucceeded( s ->{
movingText.setText("");
});
}
//Start the Service
public void startTheService(String title) {
this.title = title;
doAnimation = true;
restart();
}
//Stop the Service
public void stopService(){
doAnimation=false;
}
#Override
protected Task<Void> createTask() {
return new Task<Void>() {
#Override
protected Void call() throws Exception {
while (doAnimation) {
//System.out.println("MoveTitleService is Running...");
// One letter at a time
for (int m = 0; m <= title.length(); m++) {
counter=m;
Platform.runLater( () ->{
movingText.setText(title.substring(0, counter) + addSpaces(title.length() - counter));
});
if(!doAnimation) break;
Thread.sleep(150);
}
// Disappearing to back
for (int m = 0; m < title.length(); m++) {
counter=m;
Platform.runLater( () ->{
movingText.setText(title.substring(counter));
});
if(!doAnimation) break;
Thread.sleep(150);
}
// Appearing to front
for (int m = 1; m <= title.length(); m++) {
counter=m;
Platform.runLater( () ->{
movingText.setText(title.substring(title.length() - counter));
});
if(!doAnimation) break;
Thread.sleep(150);
}
if(!doAnimation) break;
for(int i=0; i<3000/150; i++)
Thread.sleep(150);
Thread.sleep(3000);
}
return null;
}
private String addSpaces(int spaces) {
String z = "";
for (int i = 0; i <= spaces; i++)
z += " ";
return z;
}
};
}
}
You could use a Timeline for this:
// min distance to Pane bounds
private static final double OFFSET = 25;
#Override
public void start(Stage primaryStage) {
Text text = new Text("A reallyyy big teeeeexxxxxxxxxxxxxxxxxxxxxxxt!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!");
text.setLayoutY(25);
text.setManaged(false);
text.setLayoutX(OFFSET);
Pane pane = new Pane(text);
pane.setMinHeight(50);
Timeline timeline = new Timeline();
KeyFrame updateFrame = new KeyFrame(Duration.seconds(1 / 60d), new EventHandler<ActionEvent>() {
private boolean rightMovement;
#Override
public void handle(ActionEvent event) {
double tW = text.getLayoutBounds().getWidth();
double pW = pane.getWidth();
double layoutX = text.getLayoutX();
if (2 * OFFSET + tW <= pW && layoutX >= OFFSET) {
// stop, if the pane is large enough and the position is correct
text.setLayoutX(OFFSET);
timeline.stop();
} else {
if ((rightMovement && layoutX >= OFFSET) || (!rightMovement && layoutX + tW + OFFSET <= pW)) {
// invert movement, if bounds are reached
rightMovement = !rightMovement;
}
// update position
if (rightMovement) {
layoutX += 1;
} else {
layoutX -= 1;
}
text.setLayoutX(layoutX);
}
}
});
timeline.getKeyFrames().add(updateFrame);
timeline.setCycleCount(Animation.INDEFINITE);
// listen to bound changes of the elements to start/stop the animation
InvalidationListener listener = o -> {
double textWidth = text.getLayoutBounds().getWidth();
double paneWidth = pane.getWidth();
if (textWidth + 2 * OFFSET > paneWidth
&& timeline.getStatus() != Animation.Status.RUNNING) {
timeline.play();
}
};
text.layoutBoundsProperty().addListener(listener);
pane.widthProperty().addListener(listener);
Scene scene = new Scene(pane);
primaryStage.setScene(scene);
primaryStage.show();
}
Note that repeadedly updating the position yourself needs to be done, since Animations cannot be adjusted while running, so for any resizing to take effect during the animation, you need repeated updates...
I'm trying to change the value of buttons which are created in a for loop. The value of the buttons must be saved in a hashmap which contains the id of the button and the value.
This is what I currently have:
private void createMap(int blocksX, int blocksY) {
// blocksX and blocksY are the amount of buttons to be placed
for (int x = 0; x < blocksX; x++) {
for (int y = 0; y < blocksY; y++) {
Button btn = new Button();
btn.setText("0");
btn.setPrefSize(32, 32);
btn.setLayoutX(32 * x);
btn.setLayoutY(32 * y);
btn.setId(String.valueOf(button_id));
map_list.put(button_id, 0);
button_id+=1;
items.getChildren().addAll(btn);
// If the user clicks a button, change the value of it...
btn.setOnAction(click -> {
if(btn.getText() == "0"){
changeButtonValue(Integer.parseInt(btn.getId()), 1);
btn.setText("1");
} else if(btn.getText() == "1") {
changeButtonValue(Integer.parseInt(btn.getId()), 0);
btn.setText("0");
}
});
}
}
}
But now the only item in the HashMap to be updated is the last created button. How can I change this so it will update all button values?
I completed the code to a runnable example. And I can't see your problem.
import java.util.HashMap;
import java.util.Map;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.AnchorPane;
import javafx.stage.Stage;
public class FX01 extends Application {
public int button_id = 0;
public Map<Integer, Integer> map_list = new HashMap<>();
public AnchorPane items;
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage stage) throws Exception {
items = new AnchorPane();
createMap(5, 5);
Scene scene = new Scene(items);
stage.setScene(scene);
stage.show();
}
private void createMap(int blocksX, int blocksY) {
// blocksX and blocksY are the amount of buttons to be placed
for (int x = 0; x < blocksX; x++) {
for (int y = 0; y < blocksY; y++) {
Button btn = new Button();
btn.setText("0");
btn.setPrefSize(32, 32);
btn.setLayoutX(32 * x);
btn.setLayoutY(32 * y);
btn.setId(String.valueOf(button_id));
map_list.put(button_id, 0);
button_id+=1;
items.getChildren().addAll(btn);
// If the user clicks a button, change the value of it...
btn.setOnAction(click -> {
if(btn.getText() == "0"){
changeButtonValue(Integer.parseInt(btn.getId()), 1);
btn.setText("1");
} else if(btn.getText() == "1") {
changeButtonValue(Integer.parseInt(btn.getId()), 0);
btn.setText("0");
}
});
}
}
}
private void changeButtonValue(int id, int value) {
map_list.put(id, value);
System.out.println("map_list: " + map_list);
}
}
I initialized rowSize (number of rows) and colSize (number of columns) inside the MapTest class. I created 3 methods: startStage1(), startStage2() and startStage3(); each is assigned to a button. Each method assigns the rowSize and colSize to a new integer.
I want to be able to the re-size of my GridPane whenever I click a button but it does not works that way.
What could I have done differently?
import javafx.application.Application;
import javafx.event.Event;
import javafx.event.EventHandler;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.input.KeyEvent;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
public class MapTest extends Application implements EventHandler<KeyEvent> {
static Stage theStage;
static Scene scene1, scene2;
// top box
HBox topBox = new HBox();
// bottom box
HBox bottomBox = new HBox();
// grid dimensions (I'm trying to manipulate these variables)
int rowSize;
int colSize;
int tileSize;
GridPane gridPane;
GridMapT gridMap;
#Override
public void start(Stage firstStage) throws Exception {
theStage = firstStage;
// scene 2 //////////////
Label label2 = new Label("scene 2: choose a stage");
Button stage1_btn = new Button("Room 5x5");
Button stage2_btn = new Button("Room 7x7");
Button stage3_btn = new Button("Room 10x10");
Button button5 = new Button("Exit");
stage1_btn.setOnAction(e -> {
startStage1();
});
stage2_btn.setOnAction(e -> {
startStage2();
});
stage3_btn.setOnAction(e -> {
startStage3();
});
button5.setOnAction(e -> System.exit(0));
// Layout1
VBox layout = new VBox(20);
layout.setAlignment(Pos.CENTER);
layout.getChildren().addAll(label2, stage1_btn, stage2_btn, stage3_btn, button5);
scene1 = new Scene(layout, 800, 600);
// Scene 3 ////////////////////
// top box
Label title = new Label("Map test");
topBox.getChildren().add(title);
// bottom box
Label instruction = new Label("");
bottomBox.getChildren().add(instruction);
// scene 3
BorderPane gameScreen = new BorderPane();
scene2 = new Scene(gameScreen);
// set up gridPane
gridPane = new GridPane();
gridMap = new GridMapT(rowSize, colSize);
for (int x = 0; x < rowSize; x++) {
for (int y = 0; y < colSize; y++) {
String grid = gridMap.getMap()[x][y];
// floor labels
if (grid == "floor") {
Label table = new Label("F");
table.setMinWidth(tileSize);
table.setMinHeight(tileSize);
gridPane.add(table, x, y);
}
// wall labels
if (grid == "wall") {
Label table = new Label("W");
table.setMinWidth(tileSize);
table.setMinHeight(tileSize);
gridPane.add(table, x, y);
}
}
}
////// ////////////////////////////////////////////////////////////////////////
// add a clickable reset and Debug Mode to bottom box
Button resetBtn = new Button();
resetBtn.setText("Quit game");
bottomBox.getChildren().add(resetBtn);
resetBtn.addEventHandler(MouseEvent.MOUSE_CLICKED, new EventHandler<Event>() {
#Override
public void handle(Event event) {
try {
restart(firstStage);
} catch (Exception e) {
e.printStackTrace();
}
}
});
// keyboard input
scene2.setOnKeyPressed(this);
// setting up the whole borderPane
gameScreen.setTop(topBox);
gameScreen.setBottom(bottomBox);
gameScreen.setCenter(gridPane);
// set scene1 as start up screen
firstStage.setScene(scene1);
firstStage.show();
}
// restart method
public void restart(Stage stage) throws Exception {
topBox.getChildren().clear();
bottomBox.getChildren().clear();
gridPane.getChildren().clear();
gridMap = new GridMapT(rowSize, colSize);
start(stage);
}
///////////////////////////////////////////////////////////////////////////////////////
// stage setting methods
public void startStage1() {
rowSize = 21;
colSize = 21;
tileSize = 40;
theStage.setScene(scene2);
}
public void startStage2() {
rowSize = 29;
colSize = 29;
tileSize = 30;
theStage.setScene(scene2);
}
public void startStage3() {
rowSize = 41;
colSize = 41;
tileSize = 20;
theStage.setScene(scene2);
}
#Override
public void handle(KeyEvent event) {
// TODO Auto-generated method stub
}
public static void main(String[] args) {
launch(args);
}
/////////////////////////////////////////////////////////////////////////////////////////////////
// GridMap Class
public class GridMapT {
private String[][] map;
public GridMapT(int rowSize, int colSize) {
this.map = new String[rowSize][colSize];
// set up wall and fog
for (int i = 0; i < rowSize; i++) {
for (int j = 0; j < colSize; j++) {
if (i % 4 == 0 || j % 4 == 0) {
map[i][j] = "wall";
} else {
map[i][j] = "floor";
}
}
}
}
public String[][] getMap() {
return map;
}
}
}
I made a couple of changes to your code and made it work.
Firstly Application#start method should not be called by anybody but system. It is your app's entry point and calling it manually breaks app's lifecycle.
Secondly there's no need to create GridMapT instance in start method just because you don't know by the time it runs how many columns and rows there should be. And that's why there's no need to put map into your GridPane here too.
Thirdly you need to create new GridMapT instance or change existing any time you need to update your map. So after you created new map or updated existing (added or removed items) you need to put it into your GridPane. Both pieces of code that creates map and updates grid seems to be ok, but they are called at the wrong time and in the wrong place.
Lastly int type is a primitive in Java. Changing any int value affects only it and you still need to recreate and/or update anything that depends on it manually. This means that you need manually create/update map with new colSize and rowSize values each time you change them. If you create new map you need manually pass it to your GridPane instance.
In general your app's logic should look like that:
Create stage, scenes, components.
Show first scene
Wait for user's input (click, size, etc.)
Update second scene
Show second scene
Wait for user's input (click Quit)
Close second scene
Repeat 3-7 according to user's actions
There are still a couple of things that can be done better here and I wish you luck to find and improve them :)
Improved code:
public class Test extends Application implements EventHandler<KeyEvent>{
static Stage theStage;
static Scene scene1, scene2;
// top box
HBox topBox = new HBox();
// bottom box
HBox bottomBox = new HBox();
// grid dimensions (I'm trying to manipulate these variables)
int rowSize;
int colSize;
int tileSize;
GridPane gridPane;
GridMapT gridMap;
#Override
public void start(Stage firstStage) throws Exception{
theStage = firstStage;
// scene 2 //////////////
Label label2 = new Label("scene 2: choose a stage");
Button stage1_btn = new Button("Room 5x5");
Button stage2_btn = new Button("Room 7x7");
Button stage3_btn = new Button("Room 10x10");
Button button5 = new Button("Exit");
stage1_btn.setOnAction(e -> {
startStage1();
});
stage2_btn.setOnAction(e -> {
startStage2();
});
stage3_btn.setOnAction(e -> {
startStage3();
});
button5.setOnAction(e -> System.exit(0));
// Layout1
VBox layout = new VBox(20);
layout.setAlignment(Pos.CENTER);
layout.getChildren().addAll(label2, stage1_btn, stage2_btn, stage3_btn, button5);
scene1 = new Scene(layout, 800, 600);
// Scene 3 ////////////////////
// top box
Label title = new Label("Map test");
topBox.getChildren().add(title);
// bottom box
Label instruction = new Label("");
bottomBox.getChildren().add(instruction);
// scene 3
BorderPane gameScreen = new BorderPane();
scene2 = new Scene(gameScreen);
// set up gridPane
gridPane = new GridPane();
////// ////////////////////////////////////////////////////////////////////////
// add a clickable reset and Debug Mode to bottom box
Button resetBtn = new Button();
resetBtn.setText("Quit game");
bottomBox.getChildren().add(resetBtn);
resetBtn.addEventHandler(MouseEvent.MOUSE_CLICKED, new EventHandler<Event>(){
#Override
public void handle(Event event){
try {
//There's no need to call start method anymore.
//Think of the stage like it's your app's window
//and of a scene like it is window's content.
//restart(firstStage);
firstStage.setScene(scene1);
} catch (Exception e) {
e.printStackTrace();
}
}
});
// keyboard input
scene2.setOnKeyPressed(this);
// setting up the whole borderPane
gameScreen.setTop(topBox);
gameScreen.setBottom(bottomBox);
gameScreen.setCenter(gridPane);
// set scene1 as start up screen
firstStage.setScene(scene1);
firstStage.show();
}
//TODO pass rowSize and colSize here
private void createMap(){
gridMap = new GridMapT(rowSize, colSize);
}
//TODO pass gridMap
private void redrawMap(){
gridPane.getChildren().clear();
for (int x = 0; x < rowSize; x++) {
for (int y = 0; y < colSize; y++) {
String grid = gridMap.getMap()[x][y];
// floor labels
if (grid == "floor") {
Label table = new Label("F");
table.setMinWidth(tileSize);
table.setMinHeight(tileSize);
gridPane.add(table, x, y);
}
// wall labels
if (grid == "wall") {
Label table = new Label("W");
table.setMinWidth(tileSize);
table.setMinHeight(tileSize);
gridPane.add(table, x, y);
}
}
}
}
// restart method
public void restart(Stage stage) throws Exception{
topBox.getChildren().clear();
bottomBox.getChildren().clear();
gridPane.getChildren().clear();
gridMap = new GridMapT(rowSize, colSize);
start(stage);
}
///////////////////////////////////////////////////////////////////////////////////////
// stage setting methods
public void startStage1(){
rowSize = 21;
colSize = 21;
tileSize = 40;
createMap();
redrawMap();
theStage.setScene(scene2);
}
public void startStage2(){
rowSize = 29;
colSize = 29;
tileSize = 30;
createMap();
redrawMap();
theStage.setScene(scene2);
}
public void startStage3(){
rowSize = 41;
colSize = 41;
tileSize = 20;
createMap();
redrawMap();
theStage.setScene(scene2);
}
#Override
public void handle(KeyEvent event){
// TODO Auto-generated method stub
}
public static void main(String[] args){
launch(args);
}
/////////////////////////////////////////////////////////////////////////////////////////////////
// GridMap Class
public class GridMapT{
private String[][] map;
public GridMapT(int rowSize, int colSize){
this.map = new String[rowSize][colSize];
// set up wall and fog
for (int i = 0; i < rowSize; i++) {
for (int j = 0; j < colSize; j++) {
if (i % 4 == 0 || j % 4 == 0) {
map[i][j] = "wall";
} else {
map[i][j] = "floor";
}
}
}
}
public String[][] getMap(){
return map;
}
}
}
I'm currently working on an application with animation in JavaFX. The application is used by human corrector, who is correcting computer-generated subtitles. In the animation there is a floating text. My problem is that the animation sometimes shutters. You can see the following image for demonstration:
This flaw occurs mainly after resizing. When the animation breaks, it never gets to the fully functioning state again.
I use the JFXpanel which is in inserted in Swing UI. I use it this way because I've created quite a lot of code in Swing and I didn't want to toss it all away. I don't use Swing for animation because I wasn't able to create an animation that is smooth enough.
Here is the animation-related code:
public class AnimationPanel extends JFXPanel {
public MyAnimationTimer animationTimer;
public EditObject editObject;
public Color colorHOST1;
public Color colorHOST2;
public Color colorGUEST1;
public Color colorGUEST2;
public Color colorUSER;
public Color colorSIGNING;
public Color basicColor = Color.WHITE;
public Color currentColor = Color.WHITE;
public AnimationPanel(EditObject editObject) {
super();
this.editObject = editObject;
Group group = new Group();
this.animationTimer = new MyAnimationTimer((List<MyText>)(List<?>)group.getChildren(), this);
final Scene scene = new Scene(group, 800, 600, Color.BLACK);
this.setScene(scene);
this.animationTimer.start();
/* // Update animation when component is resized
this.addComponentListener(new ComponentListener() {
#Override
public void componentResized(ComponentEvent e) {
animationTimer.updateAnimations();
}
#Override
public void componentMoved(ComponentEvent e) {
}
#Override
public void componentShown(ComponentEvent e) {
}
#Override
public void componentHidden(ComponentEvent e) {
}
});*/
}
public void setColors(Gui g) {
this.colorHOST1 = Color.rgb(g.colorHOST1.getRed(), g.colorHOST1.getGreen(), g.colorHOST1.getBlue(), g.colorHOST1.getAlpha()/255.0);
this.colorHOST2 = Color.rgb(g.colorHOST2.getRed(), g.colorHOST2.getGreen(), g.colorHOST2.getBlue(), g.colorHOST2.getAlpha()/255.0);
this.colorGUEST1 = Color.rgb(g.colorGUEST1.getRed(), g.colorGUEST1.getGreen(), g.colorGUEST1.getBlue(), g.colorGUEST1.getAlpha()/255.0);
this.colorGUEST2 = Color.rgb(g.colorGUEST2.getRed(), g.colorGUEST2.getGreen(), g.colorGUEST2.getBlue(), g.colorGUEST2.getAlpha()/255.0);
this.colorUSER = Color.rgb(g.colorUSER.getRed(), g.colorUSER.getGreen(), g.colorUSER.getBlue(), g.colorUSER.getAlpha()/255.0);
this.colorSIGNING = Color.rgb(g.colorSIGNING.getRed(), g.colorSIGNING.getGreen(), g.colorSIGNING.getBlue(), g.colorSIGNING.getAlpha()/255.0);
}
}
public class MyAnimationTimer extends AnimationTimer {
private List<MyText> nodes;
private long subtitle_max_time_in_app;
private AnimationPanel animationPanel;
private boolean stopAtTheEnd = false;
private boolean isAtTheEnd = false;
private int currentPos = 0;
public MyAnimationTimer(List<MyText> nodes, AnimationPanel animationPanel) {
super();
this.nodes = nodes;
this.animationPanel = animationPanel;
}
#Override
public void handle(long now) {
MyText node;
if(this.stopAtTheEnd) {
if(this.isAtTheEnd) {
for (int i = this.currentPos; i < this.nodes.size(); i += 2) {
node = nodes.get(i);
if(this.collides(nodes.get(i-2), node)) {
node.setTranslateXforTextandSubText(nodes.get(i-2).getBoundsInParent().getWidth() + nodes.get(i-2).getTranslateX() + 10);
this.currentPos+=2;
}
node.setTranslateXforTextandSubText(node.getTranslateX() - node.getVelocity());
}
} else {
if(nodes.size()!=0) {
node = nodes.get(0);
if((node.getTranslateX() - node.getVelocity()) < 0) {
node.setTranslateXforTextandSubText(0);
this.isAtTheEnd = true;
this.currentPos = 2;
} else {
for (int i = 0; i < this.nodes.size(); i += 2) {
node = nodes.get(i);
node.setTranslateXforTextandSubText(node.getTranslateX() - node.getVelocity());
}
}
}
}
} else {
for (int i = 0; i < this.nodes.size(); i += 2) {
node = nodes.get(i);
node.setTranslateXforTextandSubText(node.getTranslateX() - node.getVelocity());
}
}
}
private boolean collides(MyText node1, MyText node2) {
return (node1.getBoundsInParent().getWidth() + node1.getTranslateX() - node2.getTranslateX()) + 7 >= 0;
}
public void addNode(final MyText node) {
Platform.runLater(() -> {
node.setTranslateYforTextandSubText(animationPanel.getHeight() / 2);
node.setTranslateXforTextandSubText(animationPanel.getWidth());
node.setVelocity(this.getVelocity());
nodes.add(node);
nodes.add(node.id);
// Check for overlaying
if(nodes.size()>=4) {
int size = nodes.size();
double overlaying = (nodes.get(size-4).getBoundsInParent().getWidth() + nodes.get(size-4).getTranslateX() - nodes.get(size-2).getTranslateX()) + 7;
if(overlaying>0) {
nodes.get(size-2).setTranslateXforTextandSubText(nodes.get(size-2).getTranslateX()+overlaying);
}
}
});
}
public void recalculateGaps() {
Platform.runLater(() -> {
if (nodes.size() >= 4) {
double overlaying;
// System.out.println("Size: " + nodes.size());
for (int i = nodes.size() - 2; i > 0; i -= 2) {
overlaying = (nodes.get(i - 2).getBoundsInParent().getWidth() + nodes.get(i - 2).getTranslateX() - nodes.get(i).getTranslateX()) + 7;
if (overlaying > 0) {
nodes.get(i - 2).setTranslateXforTextandSubText(nodes.get(i - 2).getTranslateX() - overlaying);
}
}
}
});
}
public void removeNodesBehindTheScene() {
Platform.runLater(() -> {
MyText node;
for (int i=0; i<nodes.size(); i+=2) {
node = nodes.get(i);
if(node.getTranslateX() > 0) {
break;
} else {
if(!node.isOverdue()) {
animationPanel.editObject.setMessageToBeSendSoon(node);
}
nodes.remove(i);
nodes.remove(i);
i-=2;
}
}
});
}
/* public void updateAnimations() {
// This method is called when the window is resized.
for (int i=0; i<this.nodes.size(); i+=2) {
nodes.get(i).setTranslateYforTextandSubText(animationPanel.getHeight()/2);
}
this.setVelocity();
}*/
public double getVelocity() {
return (this.animationPanel.getWidth()/4)*3/((double) this.subtitle_max_time_in_app)*1000/60;
}
public void setSubtitle_max_time_in_app(long subtitle_max_time_in_app) {
this.subtitle_max_time_in_app = subtitle_max_time_in_app;
}
public void setStopAtTheEnd(boolean stopAtTheEnd) {
// Remove all overdue
if(stopAtTheEnd) {
Platform.runLater(() -> {
for (int i = 0; i < nodes.size(); i += 2) {
if (nodes.get(i).isOverdue()) {
nodes.remove(i);
// Remove ID number
nodes.remove(i);
i -= 2;
} else {
break;
}
}
});
this.isAtTheEnd = false;
this.currentPos = 0;
}
this.stopAtTheEnd = stopAtTheEnd;
}
public void removeUpToNode(MyText node) {
Platform.runLater(() -> {
if(nodes.contains(node)) {
for (int i = 0; i < nodes.size(); i += 2) {
if (nodes.get(i) == node) {
nodes.remove(i);
nodes.remove(i);
break;
}
else {
nodes.remove(i);
nodes.remove(i);
i-=2;
}
}
}
});
}
public void addNodesAtTheBeginning(List<MyText> nodes_list, double nodeposition) {
Platform.runLater(() -> {
MyText node;
double position;
for (int i = nodes_list.size() - 1; i >= 0; i--) {
node = nodes_list.get(i);
node.setTranslateYforTextandSubText(animationPanel.getHeight() / 2);
if(nodes.size()!=0) {
position = this.nodes.get(0).getTranslateX() - node.getBoundsInParent().getWidth() - 10;
} else {
position = animationPanel.getWidth();
}
if(i==(nodes_list.size() - 1)) {
double exactposition = nodeposition - node.getBoundsInParent().getWidth();
if(exactposition < position) {
node.setTranslateXforTextandSubText(exactposition);
} else {
node.setTranslateXforTextandSubText(position);
}
} else {
node.setTranslateXforTextandSubText(position);
}
node.setVelocity(this.getVelocity());
nodes.add(0, node.id);
nodes.add(0, node);
}
});
}
}
I've tested various versions of JavaFX(including the one packed in JDK9), but with no result. Thanks in advance
Finally I fixed the bug. The problem was that I was setting a property of an existing node from my own thread instead of JavaFX thread. Putting it in Platform.runLater method fixed it. I didn't notice the bug immediately because it didn't throw the illegal thread exception as it does when you try to add node. I should have red the documentation more thoroughly.
Thanks
Within the scope of a paper I am writing at high school I chose to make my own audio-file-to-spectrogram-converter from scratch in order to create landscapes out of these spectrograms.
I already do have my implementation of an FFT and of using that to make a heightmap, a spectrogram. But I often get weird artifacts in the form of vertical stripes when the frequencies get dense, as you can see in the image below.
The example is right at the beginning with a window length of 2048 and on a log^2-scale. The FFT I am using is flawless, I've already compared it to others and they produce the same result.
This is the function which transforms the amplitudes into frequencies and stores them in a 2D-array:
private void transform(int from, int until) {
double val, step;
for(int i=from; i<until; i++) {
for(int j=0; j<n; j++)
chunk[j] = data[0][i*n+j+start];
fft.realForward(chunk);
for(int j=0; j<height; j++) {
val = Math.sqrt(chunk[2*j]*chunk[2*j] + chunk[2*j+1]*chunk[2*j+1]);
map[i][j] = val;
}
}
}
Now my Question: Where do these vertical stripes come from and how do I get rid of them?
I currently don't employ a window function and every calculation is stringed to one another, which means there is no overlapping. It is the simplest way you can think of making a spectrogram. Could it help introducing a window function or doing each calculation independent of whether the frame was already involved in a previous calculation, that is to say overlapping the frame-windows?
Also, what other ways are there to improve on my basic approach in order to get a better result?
This is the whole class. I feed it the data and all the necessary information from an audio file:
import java.awt.*;
import java.awt.event.*;
import java.awt.image.*;
import java.io.*;
import javax.imageio.ImageIO;
import javax.swing.*;
import org.jtransforms.fft.DoubleFFT_1D;
public class Heightmap extends JFrame implements WindowListener{
public static final int LOG_SCALE = 0;
public static final int LOG_SQUARE_SCALE = 1;
public static final int SQUARE_SCALE = 2;
public static final int LINEAR_SCALE = 3;
private BufferedImage heightmap;
private FileDialog chooser;
private JMenuBar menuBar;
private JMenu fileMenu;
private JMenuItem save, close;
private DoubleFFT_1D fft;
private int[][] data;
private double[][] map;
private double[] chunk;
private int width, height, n, start, scale;
private String name;
private boolean inactive;
public Heightmap(int[][] data, int resolution, int start,
int width, int height, int scale, String name) {
this.data = data;
this.n = resolution;
this.start = start;
this.width = width;
this.height = height;
this.scale = scale;
this.name = name;
fft = new DoubleFFT_1D(n);
map = new double[width][height];
heightmap = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
chunk = new double[n];
System.out.println("Starting transformation...");
long time;
time = System.currentTimeMillis();
transform();
time = System.currentTimeMillis() - time;
System.out.println("Time taken for calculation: "+time+" ms");
time = System.currentTimeMillis();
makeHeightmap();
initComponents();
time = System.currentTimeMillis() - time;
System.out.println("Time taken for drawing heightmap: "+time+" ms");
}
private void initComponents() {
this.setSize(width, height);
this.setDefaultCloseOperation(DISPOSE_ON_CLOSE);
this.setResizable(false);
this.setLocationRelativeTo(null);
this.setTitle(name);
createMenuBar();
chooser = new FileDialog(this, "Choose a directory", FileDialog.SAVE);
chooser.setDirectory("/Users/<user>/Desktop");
this.addMouseListener(new HeightmapMouseListener());
this.addKeyListener(new HeightmapKeyListener());
this.addWindowListener(this);
this.setVisible(true);
}
private void createMenuBar() {
menuBar = new JMenuBar();
fileMenu = new JMenu();
fileMenu.setText("File");
save = new JMenuItem("Save...", KeyEvent.VK_S);
save.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_S, InputEvent.META_DOWN_MASK));
save.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent arg0) {
chooser.setVisible(true);
String fileName = chooser.getFile();
String dir = chooser.getDirectory();
chooser.setDirectory(dir);
if(fileName != null) {
try {
File outputfile = new File(dir + fileName + ".png");
ImageIO.write(heightmap, "png", outputfile);
} catch (IOException e) {
e.printStackTrace();
}
System.out.println("Saved "+fileName+".png to "+dir);
}
}
});
close = new JMenuItem("Close", KeyEvent.VK_C);
close.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_W, InputEvent.META_DOWN_MASK));
close.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
setVisible(false);
dispose();
}
});
fileMenu.add(save);
fileMenu.addSeparator();
fileMenu.add(close);
menuBar.add(fileMenu);
this.setJMenuBar(menuBar);
}
public void paint(Graphics g) {
g.drawImage(heightmap, 0, 0, null);
}
private void transform() {
transform(0, width);
}
private void transform(int from, int until) {
double max = Double.MIN_VALUE;
double min = Double.MAX_VALUE;
double val, step;
for(int i=from; i<until; i++) {
for(int j=0; j<n; j++) {
chunk[j] = data[0][i*n+j+start];
}
fft.realForward(chunk);
for(int j=0; j<height; j++) {
val = Math.sqrt(chunk[2*j]*chunk[2*j] + chunk[2*j+1]*chunk[2*j+1]);
if(val > max)
max = val;
if(val < min)
min = val;
map[i][j] = val;
}
if(min != 0) {
step = max/(max-min);
for(int j=0; j<height; j++)
map[i][j] = (map[i][j]-min)*step;
}
}
}
/*
* Paints heightmap into the BufferedImage
*/
private void makeHeightmap() {
double max = 0;
switch(scale) {
case LOG_SCALE: max = Math.log(findMax(map)+1); break;
case LOG_SQUARE_SCALE: max = Math.pow(Math.log(findMax(map)+1), 2); break;
case SQUARE_SCALE: max = Math.sqrt(findMax(map)); break;
case LINEAR_SCALE: max = findMax(map); break;
default: max = Math.pow(Math.log(findMax(map)+1), 2); break;
}
double stepsize = 255.0/max;
int val, rgb;
for(int x=0; x<width; x++)
for(int y=0; y<height; y++) {
switch(scale) {
case LOG_SCALE: val = (int) (Math.log(map[x][y]+1)*stepsize); break;
case LOG_SQUARE_SCALE: val = (int) (Math.log(map[x][y]+1)*stepsize); val *= val; break;
case SQUARE_SCALE: val = (int) (Math.sqrt(map[x][y])*stepsize); break;
case LINEAR_SCALE: val = (int) (map[x][y]*stepsize); break;
default: val = (int) (Math.log(map[x][y]+1)*stepsize); val *= val; break;
}
rgb = 255<<24 | val<<16 | val<<8 | val;
heightmap.setRGB(x, height-y-1, rgb);
}
}
private double findMax(double[][] data) {
double max = 0;
for(double[] val1: data)
for(double d: val1)
if(d > max)
max = d;
return max;
}
private class HeightmapKeyListener implements KeyListener {
boolean busy = false;
public void keyPressed(KeyEvent e) {
if(e.getKeyCode() == KeyEvent.VK_RIGHT && !busy && start < data[0].length-width*n) {
busy = true;
for(int x=0; x<width-1; x++)
map[x] = map[x+1].clone();
start += n;
transform(width-1, width);
makeHeightmap();
repaint();
busy = false;
}
else if(e.getKeyCode() == KeyEvent.VK_LEFT && !busy && start > 0) {
busy = true;
for(int x=width-1; x>0; x--)
map[x] = map[x-1];
start -= n;
transform(0, 1);
makeHeightmap();
repaint();
busy = false;
}
}
public void keyReleased(KeyEvent e) { }
public void keyTyped(KeyEvent e) { }
}
private class HeightmapMouseListener implements MouseListener {
public void mouseClicked(MouseEvent e) {
if(inactive) {
inactive = false;
return;
}
long time = System.currentTimeMillis();
int posX = e.getX();
int diff = posX - width/2; //difference between old and new center in pixels
int oldStart = start;
start = start + diff*n;
if(start < 0) start = 0;
int maxFrame = data[0].length-width*n;
if(start > maxFrame) start = maxFrame;
if(start == oldStart) return;
System.out.println("Changing center...");
int absDiff = Math.abs(diff);
if(start < oldStart) { //shift the start backward, recalculate the start
for(int x=width-1; x>=absDiff; x--)
map[x] = map[x-absDiff].clone();
transform(0, absDiff);
}
else if(start > oldStart) { //shift the back forward, recalculate the back
for(int x=0; x<width-absDiff; x++)
map[x] = map[x+absDiff].clone();
transform(width-absDiff, width);
}
makeHeightmap();
repaint();
System.out.println("Time taken: "+(System.currentTimeMillis()-time)+" ms");
}
public void mousePressed(MouseEvent e) { }
public void mouseReleased(MouseEvent e) { }
public void mouseEntered(MouseEvent e) { }
public void mouseExited(MouseEvent e) { }
}
public void windowActivated(WindowEvent arg0) { }
public void windowClosed(WindowEvent arg0) { }
public void windowClosing(WindowEvent arg0) { }
public void windowDeactivated(WindowEvent arg0) {
inactive = true;
}
public void windowDeiconified(WindowEvent arg0) { }
public void windowIconified(WindowEvent arg0) { }
public void windowOpened(WindowEvent arg0) { }
}
EDIT:
Implementing a window function improved the result drastically. I really didn't understand what a window function would do and therefore underestimated its effect.
However, after doing so I tried mapping a cosine wave with a frequency of 10kHz which (again) produced some strange artifacts:
What could be the cause of this one? I implemented a overflow protection by clipping everything under 0 to 0 and over 255 to 255 with no change whatsoever.
This type of artifact can be due to overflow or otherwise exceeding parameter bounds before or in your color mapping function, or perhaps with some function (log?) returning NaN values. You might be able to find this by putting in some asserts for out-of-range or illegal values.