Custom JPanel component with disappear display part - java

I'm building a custom ASCII terminal java display for make game or application.
I currently have a problem with Swing repaint processing. Some parts of the view is paint but after a little time, they disappear from the screen. This happens quickly when you click anywhere on the screen.
A example of error :
I read the Painting in AWT and Swing but I don't find the solution.
This is the current state of the AsciiPanel state.
package ui;
import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.Rectangle;
import java.awt.image.BufferedImage;
import java.awt.image.FilteredImageSource;
import java.awt.image.LookupOp;
import java.awt.image.LookupTable;
import java.awt.image.ShortLookupTable;
import java.io.IOException;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.imageio.ImageIO;
import javax.swing.JComponent;
import javax.swing.JPanel;
import javax.swing.RepaintManager;
/**
* JPanel with a ASCII render system
*
* #author julien MAITRE
*
*/
public class AsciiPanel extends JPanel {
private Dimension size;
private BufferedImage[] character;
private Color defaultCharacterColor;
private Color defaultCharacterBackgroundColor;
private Dimension characterSize;
private AsciiTerminalDataCell[][] terminal;
private AsciiTerminalDataCell[][] oldTerminal;
private Image image;
private Graphics2D graphics;
private int scale;
public AsciiPanel(Dimension dimension, String tilesetFile, int characterWidth, int characterHeight) {
this(dimension, tilesetFile, characterWidth, characterHeight, 1);
}
public AsciiPanel(Dimension dimension, String tilesetFile, int characterWidth, int characterHeight, int scale) {
this.size = dimension;
this.characterSize = new Dimension(characterWidth, characterHeight);
this.scale = scale;
this.defaultCharacterColor = Color.WHITE;
this.defaultCharacterBackgroundColor = Color.BLACK;
terminal = new AsciiTerminalDataCell[size.height][size.width];
oldTerminal = new AsciiTerminalDataCell[size.height][size.width];
for(int i = 0; i < size.height; i++){
for(int j = 0; j < size.width; j++){
terminal[i][j] = new AsciiTerminalDataCell();
oldTerminal[i][j] = new AsciiTerminalDataCell();
}
}
this.setPreferredSize(new Dimension(size.width*characterSize.width*scale, size.height*characterSize.height*scale));
try {
character = new BufferedImage[256];
BufferedImage tilesets = ImageIO.read(getClass().getResource(tilesetFile));
// Recuperation of the background color
BufferedImage imageBackgroundColor = tilesets.getSubimage(0, 0, 1, 1);
int color = imageBackgroundColor.getRGB(0, 0);
Color m_characterBackgroundColor = Color.getColor(null, color);
// Modification of characters background
Image characterBackgroundColorModified = createImage(new FilteredImageSource(tilesets.getSource(), new AsciiBackgroundFilter(m_characterBackgroundColor)));
// Creation of tileset with a modification of the background color
BufferedImage tilesetsModified = new BufferedImage(tilesets.getWidth(), tilesets.getHeight(), BufferedImage.TYPE_INT_ARGB);
Graphics graphicsTilesetsModified = tilesetsModified.getGraphics();
graphicsTilesetsModified.setColor(Color.BLACK);
graphicsTilesetsModified.fillRect(0, 0, tilesetsModified.getWidth(), tilesetsModified.getHeight());
// Draw in a BufferedImage for characters recuperation
graphicsTilesetsModified.drawImage(characterBackgroundColorModified, 0, 0, this);
for(int i = 0; i < 256; i++){
int x = (i%16)*characterSize.width;
int y = (i/16)*characterSize.height;
character[i] = new BufferedImage(characterSize.width, characterSize.height, BufferedImage.TYPE_INT_ARGB);
character[i].getGraphics().drawImage(tilesetsModified, 0, 0, characterSize.width, characterSize.height, x, y, x+characterSize.width, y+characterSize.height, this);
}
}
catch (IOException ex) {
Logger.getLogger(AsciiTerminal.class.getName()).log(Level.SEVERE, null, ex);
}
this.setLayout(null);
}
public void write(int positionX, int positionY, char character, Color characterColor){
this.write(positionX, positionY, character, characterColor, defaultCharacterBackgroundColor);
}
public void write(int positionX, int positionY, AsciiTerminalDataCell character){
this.write(positionX, positionY, character.data, character.dataColor, character.backgroundColor);
}
public void write(int positionX, int positionY, char character, Color characterColor, Color characterBackgroundColor){
if(positionX < 0 || positionX > size.width - 1){
throw new IllegalArgumentException("X position between [0 and "+size.width+"]");
}
if(positionY < 0 || positionY > size.height - 1){
throw new IllegalArgumentException("Y position between [0 and "+size.height+"]");
}
terminal[positionY][positionX].data = character;
terminal[positionY][positionX].dataColor = characterColor;
terminal[positionY][positionX].backgroundColor = characterBackgroundColor;
}
public void writeString(int positionX, int positionY, String string, Color characterColor){
writeString(positionX, positionY, string, characterColor, defaultCharacterBackgroundColor);
}
public void writeString(int positionX, int positionY, String string, Color characterColor, Color characterBackgroundColor){
for(char c : string.toCharArray()){
this.write(positionX, positionY, c, characterColor, characterBackgroundColor);
positionX++;
}
}
public AsciiTerminalDataCell readCurrent(int x, int y){
return this.oldTerminal[y][x];
}
public AsciiTerminalDataCell readNext(int x, int y){
return this.terminal[y][x];
}
public void clear(){
clear(0, 0, size.width, size.height);
}
public void clear(int x, int y, int width, int height){
if(x < 0 || x > size.width - 1){
throw new IllegalArgumentException("X position between [0 and "+(size.width-1)+"]");
}
if(y < 0 || y > size.height - 1){
throw new IllegalArgumentException("Y position between [0 and "+(size.height-1)+"]");
}
if(width < 1){
throw new IllegalArgumentException("Width under 1");
}
if(height < 1){
throw new IllegalArgumentException("Height under 1");
}
if(width+x > size.width || height+y > size.height){
throw new IllegalArgumentException("Clear over the terminal");
}
for(int i = y; i < y + height; i++){
for(int j = x; j < x + width; j++) {
write(j, i, (char)0, defaultCharacterColor, defaultCharacterBackgroundColor);
}
}
}
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
if(image == null) {
image = this.createImage(this.getPreferredSize().width, this.getPreferredSize().height);
graphics = (Graphics2D)image.getGraphics();
graphics.setColor(defaultCharacterBackgroundColor);
graphics.fillRect(0, 0, this.getWidth(), this.getHeight());
}
for(Component component : getComponents()) {
component.paint(graphics);
}
for(int i = 0; i < size.height; i++){
for(int j = 0; j < size.width; j++){
if( terminal[i][j].data == oldTerminal[i][j].data &&
terminal[i][j].dataColor.equals(oldTerminal[i][j].dataColor) &&
terminal[i][j].backgroundColor.equals(oldTerminal[i][j].backgroundColor)) {
continue;
}
LookupOp lookupOp = setColorCharacter(terminal[i][j].backgroundColor, terminal[i][j].dataColor);
graphics.drawImage(lookupOp.filter(character[terminal[i][j].data], null), j*characterSize.width*scale, i*characterSize.height*scale, characterSize.width*scale, characterSize.height*scale, this);
oldTerminal[i][j].data = terminal[i][j].data;
oldTerminal[i][j].dataColor = terminal[i][j].dataColor;
oldTerminal[i][j].backgroundColor = terminal[i][j].backgroundColor;
}
}
g.drawImage(image, 0, 0, this);
}
private LookupOp setColorCharacter(Color bgColor, Color fgColor){
short[] red = new short[256];
short[] green = new short[256];
short[] blue = new short[256];
short[] alpha = new short[256];
// Recuperation of compound colors of foreground character color
short dcr = (short) fgColor.getRed();
short dcg = (short) fgColor.getGreen();
short dcb = (short) fgColor.getBlue();
// Recuperation of compound colors of background character color
short bgr = (short) bgColor.getRed();
short bgg = (short) bgColor.getGreen();
short bgb = (short) bgColor.getBlue();
for(short j = 0; j < 256; j++){
// if is foreground color
if(j != 0){
/**
* Calculation of j*255/dcr .
* Cross product
* dcr = 180 255
* j = ? X
* Distribute the requested color [0 to 255] on the character color [0 to X]
*/
// Red
if(dcr != 0){
red[j] = (short)(j*dcr/255);
}
else{
red[j] = 0;
}
// green
if(dcg != 0){
green[j] = (short)(j*dcg/255);
}
else{
green[j] = 0;
}
// Blue
if( dcb != 0){
blue[j] = (short)(j*dcb/255);
}
else{
blue[j] = 0;
}
// Alpha
alpha[j] = 255;
}
// else is background color
else {
red[j] = bgr;
green[j] = bgg;
blue[j] = bgb;
alpha[j] = 255;
}
}
short[][] data = new short[][]{red, green, blue, alpha};
LookupTable lookupTable = new ShortLookupTable(0, data);
LookupOp lookupOp = new LookupOp(lookupTable, null);
return lookupOp;
}
public Color getDefaultCharacterColor(){
return this.defaultCharacterColor;
}
public void setDefaultCharacterColor(Color color){
this.defaultCharacterColor = color;
}
public Color getDefaultCharacterBackgroundColor() {
return defaultCharacterBackgroundColor;
}
public void setDefaultCharacterBackgroundColor(Color defaultCharacterBackgroundColor) {
this.defaultCharacterBackgroundColor = defaultCharacterBackgroundColor;
}
public Dimension getCharacterSize() {
return characterSize;
}
public int getScale() {
return scale;
}
public AsciiTerminalDataCell getCell(int x, int y) {
return terminal[y][x];
}
}
ChiptuneTracker main class :
package main;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics2D;
import java.awt.event.KeyEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.image.BufferedImage;
import java.io.File;
import java.net.URISyntaxException;
import javax.imageio.ImageIO;
import javax.swing.JOptionPane;
import ui.AsciiPanel;
import ui.AsciiTerminalDataCell;
import ui.CustomAsciiTerminal;
public class ChiptuneTracker {
public static final String TITLE = "ChiptuneTracker";
public static final int WINDOW_WIDTH = 29;
public static final int WINDOW_HEIGHT = 18;
public static final String TILESET_FILE = "/assets/wanderlust.png";
public static final String ICON_FILE = "/assets/icon.png";
public static final int CHARACTER_WIDTH = 12;
public static final int CHARACTER_HEIGHT = 12;
public static final int TARGET_FPS = 60;
public static final long OPTIMAL_TIME = 1000000000 / TARGET_FPS;
public static final int SCALE = 3;
public static final boolean CUSTOM_WINDOW = true;
private static ChiptuneTracker instance = new ChiptuneTracker();
private CustomAsciiTerminal asciiTerminal;
private AsciiPanel asciiPanel;
private boolean initSampleView = true;
private boolean initPatternView = true;
private Data data = new Data();
private DataManager dataManager;
private boolean changeData = false;
private Chanels chanels = new Chanels();
private View currentView;
private MenuView menuView;
private SampleView sampleView;
private PatternView patternView;
private ChiptuneTracker() {
asciiTerminal = new CustomAsciiTerminal(TITLE, new Dimension(WINDOW_WIDTH, WINDOW_HEIGHT), TILESET_FILE, CHARACTER_WIDTH, CHARACTER_HEIGHT, SCALE, ICON_FILE, CUSTOM_WINDOW);
asciiPanel = asciiTerminal.getAsciiPanel();
asciiPanel.setDefaultCharacterBackgroundColor(Color.DARK_GRAY);
asciiPanel.setDefaultCharacterColor(Color.WHITE);
dataManager = new DataManager();
}
public void init() {
menuView = new MenuView(this);
sampleView = new SampleView(this);
patternView = new PatternView(this);
changeView(sampleView);
}
public void run() {
long lastLoopTime = System.nanoTime();
boolean stopRender = true;
while(true) {
long now = System.nanoTime();
double updateLength = now - lastLoopTime;
lastLoopTime = now;
double delta = updateLength / ChiptuneTracker.OPTIMAL_TIME;
// Screenshot
KeyEvent event = asciiTerminal.getEvent();
if(event != null) {
if(event.getKeyCode() == KeyEvent.VK_F12) {
try {
BufferedImage image = new BufferedImage(WINDOW_WIDTH * CHARACTER_WIDTH * SCALE, WINDOW_HEIGHT * CHARACTER_HEIGHT * SCALE, BufferedImage.TYPE_INT_RGB);
Graphics2D graphics = image.createGraphics();
asciiPanel.paint(graphics);
boolean save = false;
int i = 0;
do {
File file = new File("screenshot-" + i + ".png");
if(!file.exists()) {
ImageIO.write(image, "PNG", file);
save = true;
}
i++;
} while(!save);
} catch (Exception e) {
JOptionPane.showMessageDialog(ChiptuneTracker.getInstance().getAsciiTerminal(), e.getMessage(), "Error", JOptionPane.ERROR_MESSAGE);
}
finally {
asciiTerminal.setEvent(null);
}
}
}
// Update
currentView.update(delta);
chanels.update();
// Paint
asciiPanel.clear();
currentView.paint();
asciiTerminal.revalidate();
asciiTerminal.repaint();
try {
long value = (lastLoopTime - System.nanoTime() + ChiptuneTracker.OPTIMAL_TIME) / 1000000;
if(value > 0) {
Thread.sleep(value);
}
else {
Thread.sleep(5);
}
} catch (InterruptedException e) {
}
}
}
public void changeView(View nextView) {
if(currentView != null) {
currentView.quit();
}
currentView = nextView;
asciiPanel.clear();
currentView.init();
}
public static ChiptuneTracker getInstance() {
return instance;
}
public CustomAsciiTerminal getAsciiTerminal() {
return asciiTerminal;
}
public AsciiPanel getAsciiPanel() {
return asciiPanel;
}
public boolean isInitPatternView() {
return initPatternView;
}
public boolean isInitSampleView() {
return initSampleView;
}
public void setInitPatternView(boolean initPatternView) {
this.initPatternView = initPatternView;
}
public void setInitSampleView(boolean initSampleView) {
this.initSampleView = initSampleView;
}
public Data getData() {
return data;
}
public void setData(Data data) {
this.data = data;
}
public DataManager getDataManager() {
return dataManager;
}
public boolean isChangeData() {
return changeData;
}
public void setChangeData(boolean changeData) {
this.changeData = changeData;
}
public Chanels getChanels() {
return chanels;
}
public MenuView getMenuView() {
return menuView;
}
public SampleView getSampleView() {
return sampleView;
}
public PatternView getPatternView() {
return patternView;
}
public static void main(String[] args) {
ChiptuneTracker chiptuneTracker = ChiptuneTracker.getInstance();
chiptuneTracker.init();
chiptuneTracker.run();
}
}
Is there any solution to resolve that?
Thanks!

Related

Inconsistent movement in JFrame

I am trying to built psuedo-galaga and I want consistent movement of my JComponents. They are laid out in a null layout, custom JPanel within a custom JFrame container. When I move my character, the speed of the bullets changes - by using the timer I am trying to limit the frame rate so that they move consistently but that has not worked.
Why does my code slow down when the user is moving? I feel like it is a focus subsystem issue or that I should maybe be using multiple threads?
import java.awt.Color;
import java.awt.Dimension;
import java.awt.event.WindowEvent;
import javax.swing.JFrame;
import javax.swing.JPanel;
#SuppressWarnings("serial")
public class Frame extends JFrame {
private Dimension dimension;
private final int WIDTH, HEIGHT;
private JPanel screen;
public Frame(int width, int height) {
WIDTH = width;
HEIGHT = height;
dimension = new Dimension(WIDTH, HEIGHT);
this.setPreferredSize(dimension);
this.setResizable(false);
this.setMinimumSize(dimension);
this.setMaximumSize(dimension);
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
this.pack();
this.setVisible(true);
this.setTitle("Galaga");
this.setBackground(Color.black);
this.setForeground(Color.white);
screen = new LevelOneScreen(dimension);
this.getContentPane().add(screen);
screen.requestFocus();
screen.requestFocusInWindow();
}
public void display() {
this.pack();
this.setVisible(true);
this.repaint();
if(screen instanceof LevelOneScreen && ((LevelOneScreen) screen).isDone()) {
this.dispatchEvent(new WindowEvent(this, WindowEvent.WINDOW_CLOSING));
}
}
}
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import javax.imageio.ImageIO;
import javax.swing.JPanel;
#SuppressWarnings("serial")
public class LevelOneScreen extends JPanel {
public static ArrayList<LevelOneBullet> enemyBullets;
private ArrayList<LevelOneEnemy> enemies;
private boolean direction;
private Dimension dimension;
private User user;
private int score;
private boolean isDone;
public LevelOneScreen(Dimension dimension) {
this.dimension = dimension;
isDone = false;
enemies = new ArrayList<LevelOneEnemy>();
enemyBullets = new ArrayList<LevelOneBullet>();
direction = true;
this.setLayout(null);
setBackground(Color.BLACK);
createEnemies();
createUser();
user.requestFocusInWindow();
user.requestFocus();
score = 0;
this.setSize(dimension);
this.setVisible(true);
}
private void createUser() {
user = new User((int) (dimension.getWidth() / 2), (int) (dimension.getHeight() - 100));
user.setVisible(true);
this.add(user);
}
private void createEnemies() {
BufferedImage img = null;
try {
img = ImageIO.read(new File("Enemy1.png"));
} catch (IOException e) {
System.out.println("Error Reading \"Enemy1.png\"");
}
// dimension.width
for (int i = 0; i < 15; i++) {
enemies.add(new LevelOneEnemy(i * 40 + 5, 30, img));
enemies.get(i).setVisible(true);
}
for (LevelOneEnemy e : enemies) {
this.add(e);
}
}
private void paintEnemies(Graphics g) {
for (LevelOneEnemy e : enemies) {
e.paint(g);
if (!direction)
e.setLocation(e.getX() - 1, e.getY());
else
e.setLocation(e.getX() + 1, e.getY());
}
if (enemies.get(enemies.size() - 1).getX() + 45 > dimension.getWidth() && direction) {
direction = false;
} else if (enemies.get(0).getX() < 5 && !direction) {
direction = true;
}
}
private void paintCollisionObjects(Graphics g) {
if (!enemies.isEmpty()) {
paintEnemies(g);
// check for bullet collision
if (!user.getBullets().isEmpty()) {
for (int i = enemies.size() - 1; i >= 0; i--) {
for (int j = user.getBullets().size() - 1; j >= 0; j--) {
if (enemies.get(i).getBounds().intersects(user.getBullets().get(j).getBounds())) {
this.remove(enemies.get(i));
enemies.remove(i);
user.getBullets().remove(j);
score += 100;
// To prevent ArrayOutOfBoundsException when
// Enemies are destroyed faster than they're removed
if (enemies.size() == 0)
break;
}
}
}
}
// check for user collision
if (!enemies.isEmpty()) {
for (int i = enemies.size() - 1; i >= 0; i--) {
if (enemies.get(i).getBounds().intersects(user.getBounds())) {
enemies.get(i).setLocation(0, getParent().getHeight() + 100);
this.remove(enemies.get(i));
enemies.remove(i);
user.decrementHealth();
score += 100;
}
}
}
if (!enemyBullets.isEmpty()) {
for (int i = enemyBullets.size() - 1; i >= 0; i--) {
enemyBullets.get(i).paint(g);
if (enemyBullets.get(i).getY() > getParent().getHeight() + 50) {
enemyBullets.remove(i);
} else if (enemyBullets.get(i).getBounds().intersects(user.getBounds())) {
enemyBullets.remove(i);
user.decrementHealth();
}
}
}
}
}
public void paintComponent(Graphics g) {
this.requestFocusInWindow();
super.paintComponent(g);
user.paintComponent(g);
paintCollisionObjects(g);
if(!isDone && enemies.isEmpty())
isDone = true;
}
public boolean isDone() {
return isDone;
}
public boolean isDead() {
return user.healthPercent() < .1;
}
public int getScore() {
return score;
}
public double getHealth() {
return user.healthPercent();
}
public String toString() {
String s = "Level One Screen\n";
for (int i = 0; i < this.getComponentCount(); i++) {
s = s + this.getComponent(i) + "\n";
}
return s;
}
}
import java.awt.Graphics;
import java.awt.Image;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import javax.imageio.ImageIO;
import javax.swing.JComponent;
#SuppressWarnings("serial")
public class LevelOneBullet extends JComponent {
private Image img;
private int dy;
public LevelOneBullet(int x, int y, boolean isEnemy) {
BufferedImage img = null;
try {
if(isEnemy)
img = ImageIO.read(new File("EnemyLaserShot.png"));
else
img = ImageIO.read(new File("UserLaserShot.png"));
} catch (IOException e) {
if(isEnemy)
System.out.println("Error Reading \"EnemyLaserShot.png\"");
else
System.out.println("Error Reading \"UserLaserShot.png\"");
}
this.img = img;
super.setLocation(x, y);
this.setVisible(true);
this.setBounds(x, y, 16, 30);
dy = isEnemy ? 3 : -3;
}
public void paint(Graphics g) {
g.drawImage(img, super.getX(), super.getY(), 16, 30, null);
//super.setLocation(super.getX(), super.getY() + dy);
this.setBounds(super.getX(), super.getY()+dy, 16, 30);
}
public String toString() {
return "LevelOneBullet: #" + super.getX() + ", " +super.getY();
}
}
import java.awt.Graphics;
import java.awt.Image;
import java.util.Random;
import javax.swing.JComponent;
#SuppressWarnings("serial")
public class LevelOneEnemy extends JComponent {
private Image im;
private int health;
private int shootSeed;
private long time;
private long lastTimeFired;
public LevelOneEnemy(int x, int y, Image im, int health) {
Random rand = new Random();
super.setBounds(x, y, 30, 30);
super.setLocation(x, y);
this.im = im;
this.health = health;
shootSeed = rand.nextInt(1000)+5000;
time = System.currentTimeMillis();
lastTimeFired = 0;
}
public LevelOneEnemy(int x, int y, Image im) {
Random rand = new Random();
super.setBounds(x, y, 30, 30);
super.setLocation(x, y);
this.im = im;
this.health = 100;
shootSeed = rand.nextInt(1000)+6000;
time = System.currentTimeMillis();
lastTimeFired = 0;
}
public LevelOneEnemy(int x, int y, Image im, boolean isLevelOne) {
Random rand = new Random();
super.setBounds(x, y, 30, 30);
super.setLocation(x, y);
this.im = im;
this.health = 100;
shootSeed = rand.nextInt(1000)+6000;
time = System.currentTimeMillis();
lastTimeFired = 0;
}
public void paint(Graphics g) {
if((System.currentTimeMillis()-time) % shootSeed < (shootSeed/30) &&
System.currentTimeMillis() - lastTimeFired > 5000) {
LevelOneScreen.enemyBullets.add(new LevelOneBullet(this.getX()+15, this.getY()+10, true));
lastTimeFired = System.currentTimeMillis();
}
g.drawImage(im, super.getX(), super.getY(), 30, 30, null);
}
public int getHealth() {
return health;
}
public String toString() {
return "LevelOneEnemy #(" + this.getX() + ", " + this.getY() + ")";
}
}
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.Timer;
public class Runner {
public final static int SCREENHEIGHT = 1000;
public final static int SCREENWIDTH = 800;
private static Frame frame;
public static void main(String[] args) {
frame = new Frame(SCREENWIDTH, SCREENHEIGHT);
FrameRateListener listen = new FrameRateListener();
Timer timer = new Timer(34, listen);
timer.start();
}
private static class FrameRateListener implements ActionListener{
#Override
public void actionPerformed(ActionEvent e) {
frame.display();
}
}
}
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.util.ArrayList;
public class UserKeyboardListener implements KeyListener {
private int dx, dy;
private int shoot;
private int speed;
private ArrayList<Integer> keysPressed;
public UserKeyboardListener() {
keysPressed = new ArrayList<Integer>();
shoot = 0;
speed = 1;
}
#Override
public void keyTyped(KeyEvent e) {
}
public int getdx() {
return dx;
}
public int getdy() {
return dy;
}
public int getShoot() {
return shoot;
}
public boolean decrementShoot() {
if (shoot - 1 < 0) {
return false;
} else {
shoot = shoot - 1;
return true;
}
}
#Override
public void keyPressed(KeyEvent e) {
int key = e.getKeyCode();
if (key == KeyEvent.VK_SPACE) {
} else {
if (!keysPressed.contains(key))
keysPressed.add(key);
if(keysPressed.contains(KeyEvent.VK_C))
speed = 2;
else
speed = 1;
if (keysPressed.contains(KeyEvent.VK_RIGHT) && keysPressed.contains(KeyEvent.VK_LEFT)) {
dx = 0;
} else if (keysPressed.contains(KeyEvent.VK_RIGHT)) {
dx = 1*speed;
} else if (keysPressed.contains(KeyEvent.VK_LEFT)) {
dx = -1*speed;
} else {
dx = 0;
}
if (keysPressed.contains(KeyEvent.VK_UP) && keysPressed.contains(KeyEvent.VK_DOWN)) {
dy = 0;
} else if (keysPressed.contains(KeyEvent.VK_UP)) {
dy = -1*speed;
} else if (keysPressed.contains(KeyEvent.VK_DOWN)) {
dy = 1*speed;
} else {
dy = 0;
}
}
}
#Override
public void keyReleased(KeyEvent e) {
int key = e.getKeyCode();
if (key == KeyEvent.VK_SPACE) {
shoot++;
} else {
if (keysPressed.contains(key))
keysPressed.remove(keysPressed.indexOf(key));
if(keysPressed.contains(KeyEvent.VK_C))
speed = 2;
else
speed = 1;
if (keysPressed.contains(KeyEvent.VK_RIGHT) && keysPressed.contains(KeyEvent.VK_LEFT)) {
dx = 0;
} else if (keysPressed.contains(KeyEvent.VK_RIGHT)) {
dx = 1*speed;
} else if (keysPressed.contains(KeyEvent.VK_LEFT)) {
dx = -1*speed;
} else {
dx = 0;
}
if (keysPressed.contains(KeyEvent.VK_UP) && keysPressed.contains(KeyEvent.VK_DOWN)) {
dy = 0;
} else if (keysPressed.contains(KeyEvent.VK_UP)) {
dy = -1*speed;
} else if (keysPressed.contains(KeyEvent.VK_DOWN)) {
dy = 1*speed;
} else {
dy = 0;
}
}
}
public String toString() {
return "UserKeyListener: (" + dx + ", " + dy + ")";
}
}
import java.awt.Graphics;
import java.awt.Image;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import javax.imageio.ImageIO;
import javax.swing.JComponent;
#SuppressWarnings("serial")
public class User extends JComponent {
private Image im;
private double health;
private double initialHealth;
private double healthDecrement;
private double stamina;
private int staminaDecrement;
private long lastBulletFired;
private ArrayList<LevelOneBullet> bullets;
public User(int x, int y, double health) {
BufferedImage img = null;
try {
img = ImageIO.read(new File("UserShip.png"));
} catch (IOException e) {
System.out.println("Error Reading \"UserShip.png\"");
}
bullets = new ArrayList<LevelOneBullet>();
super.setLocation(x, y);
super.setBounds(x, y, 50, 50);
this.addKeyListener(new UserKeyboardListener());
this.im = img;
this.health = 100 * health;
this.initialHealth = 100;
this.healthDecrement = 100/5.0;
this.stamina = 100;
this.staminaDecrement = 10;
this.setFocusable(true);
}
public User(int x, int y) {
BufferedImage img = null;
try {
img = ImageIO.read(new File("UserShip.png"));
} catch (IOException e) {
System.out.println("Error Reading \"UserShip.png\"");
}
bullets = new ArrayList<LevelOneBullet>();
super.setLocation(x, y);
super.setBounds(x, y, 50, 50);
this.addKeyListener(new UserKeyboardListener());
this.im = img;
this.health = 100;
this.initialHealth = 100;
this.healthDecrement = health/5.0;
this.stamina = 100;
this.staminaDecrement = 10;
this.setFocusable(true);
}
public void paintComponent(Graphics g) {
this.requestFocus();
this.requestFocusInWindow();
if (this.getKeyListeners().length > 0 &&
this.getKeyListeners()[0] instanceof UserKeyboardListener) {
UserKeyboardListener listen = (UserKeyboardListener) this.getKeyListeners()[0];
if(listen.getdx() != 0) {
if(this.getX()+listen.getdx() + this.getWidth() < this.getParent().getWidth() &&
this.getX()+listen.getdx() > 5)
this.setLocation(this.getX() + listen.getdx(), this.getY());
if(this.getX() < 10) {
this.setLocation(10, this.getY());
}
else if(this.getX()+this.getWidth() > this.getParent().getWidth() - 10)
this.setLocation(this.getParent().getWidth()-10-this.getWidth(), this.getY());
}
if(listen.getdy() != 0) {
if(this.getY() + listen.getdy() > 30 &&
this.getY()+this.getHeight()+listen.getdy() < this.getParent().getHeight()-10)
this.setLocation(this.getX(), this.getY()+listen.getdy());
if(this.getY() < 30) {
this.setLocation(30, this.getY());
}
else if(this.getY()+this.getHeight() > this.getParent().getHeight()-10)
this.setLocation(this.getX(), this.getParent().getHeight()-10-this.getHeight());
}
if (listen.getShoot() > 0 && stamina > 10) {
bullets.add(new LevelOneBullet(super.getX() + 17, super.getY() - 5, false));
decrementStamina();
listen.decrementShoot();
lastBulletFired = System.currentTimeMillis();
}
}
for (int i = bullets.size() - 1; i >= 0; i--) {
bullets.get(i).paint(g);
if (bullets.get(i).getY() < -50)
bullets.remove(i);
}
if(System.currentTimeMillis() - lastBulletFired > 1000 && stamina < 100) {
stamina += .5;
}
g.drawImage(im, super.getX(), super.getY(), this.getWidth(), this.getHeight(), null);
}
public double healthPercent() {
return health/initialHealth;
}
public void decrementHealth() {
health -= healthDecrement;
}
public double staminaPercent() {
return stamina / 100.0;
}
public void decrementStamina() {
stamina -= staminaDecrement;
}
public String toString() {
return "User: " + "(" + super.getX() + ", " + super.getY() + ")";
}
public ArrayList<LevelOneBullet> getBullets() {
return bullets;
}
}
The issue came from what #VGR said, updating components position in the paintComponents(..) method. This should be done from another method that is called when the timer's action occurs. This then updates the positions of the JComponents based off of when they should be refreshed from the timer rather than when paintComponents(..) is called - which we do not have control over.

marquee (ticker,text scroll) with java2d or javafx stutters

I'm trying to write a smooth ticker (text running from rigth to left on the screen).
It is almost as I want it, but there are still some stutters. I would like it to be as smooth as cloud moving in the sky. 30 years ago I managed with a few lines of assembler code, but in Java I fail.
It get's worse if I increase the speed (number of pixels I move the text at once).
Is there some kind of synchronization to the screen refresh missing?
EDIT
I updated my code according to #camickr remark to launch the window in an exclusive fullscreen windows, which lead to a sligth improvement.
Other things I tried:
Added ExtendedBufferCapabilities which is supposed to consider vsync
Toolkit.getDefaultToolkit().sync();
enabled opengl
tried a gaming loop
added some debugging info
When I use 30 fps and move the text only one pixel, on a 4k display it looks quite good but is also very slow. As soon as I increase to speed to 2 pixels it's starts to stutter.
I'm starting to think that it is simply not possible to achieve my goal with java2d and I have to move to some opengl-library.
Here is my code:
package scrolling;
import java.awt.AWTException;
import java.awt.BufferCapabilities;
import java.awt.Color;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics2D;
import java.awt.GraphicsConfiguration;
import java.awt.GraphicsDevice;
import java.awt.GraphicsEnvironment;
import java.awt.ImageCapabilities;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.font.FontRenderContext;
import java.awt.font.GlyphVector;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
import javax.swing.JFrame;
import javax.swing.Timer;
import sun.java2d.pipe.hw.ExtendedBufferCapabilities;
/**
* A smooth scroll with green background to be used with a video cutter.
*
* sun.java2d.pipe.hw.ExtendedBufferCapabilities is restricted https://stackoverflow.com/questions/25222811/access-restriction-the-type-application-is-not-api-restriction-on-required-l
*
*/
public class MyScroll extends JFrame implements ActionListener {
private int targetFps = 30;
private boolean isOpenGl = false;
private boolean isVsync = true;
private boolean useGamingLoop = false;
private int speed = 1;
private String message;
private int fontSize = 120;
private Font theFont;
private transient int leftEdge; // Offset from window's right edge to left edge
private Color bgColor;
private Color fgColor;
private int winWidth;
private int winHeight;
private double position = 0.77;
private FontMetrics fontMetrics;
private int yPositionScroll;
private boolean isFullScreen;
private long lastTimerStart = 0;
private BufferedImage img;
private Graphics2D graphicsScroll;
private GraphicsDevice currentScreenDevice = null;
private int msgWidth = 0;
private Timer scrollTimer;
private boolean isRunning;
/* gaming loop variables */
private static final long NANO_IN_MILLI = 1000000L;
// num of iterations with a sleep delay of 0ms before
// game loop yields to other threads.
private static final int NO_DELAYS_PER_YIELD = 16;
// max num of renderings that can be skipped in one game loop,
// game's internal state is updated but not rendered on screen.
private static int MAX_RENDER_SKIPS = 5;
// private long prevStatsTime;
private long gameStartTime;
private long curRenderTime;
private long rendersSkipped = 0L;
private long period; // period between rendering in nanosecs
private long fps;
private long frameCounter;
private long lastFpsTime;
public void init() {
fontSize = getWidth() / 17;
if (getGraphicsConfiguration().getBufferCapabilities().isPageFlipping()) {
try { // no pageflipping available with opengl
BufferCapabilities cap = new BufferCapabilities(new ImageCapabilities(true), new ImageCapabilities(true), BufferCapabilities.FlipContents.BACKGROUND);
// ExtendedBufferCapabilities is supposed to do a vsync
ExtendedBufferCapabilities ebc = new ExtendedBufferCapabilities(cap, ExtendedBufferCapabilities.VSyncType.VSYNC_ON);
createBufferStrategy(2, ebc);
} catch (AWTException e) {
e.printStackTrace();
}
} else {
createBufferStrategy(2);
}
System.out.println(getDeviceConfigurationString(getGraphicsConfiguration()));
message = "This is a test. ";
leftEdge = 0;
theFont = new Font("Helvetica", Font.PLAIN, fontSize);
bgColor = getBackground();
fgColor = getForeground();
winWidth = getSize().width - 1;
winHeight = getSize().height;
yPositionScroll = (int) (winHeight * position);
initScrollImage();
}
/**
* Draw the entire text to a buffered image to copy it to the screen later.
*/
private void initScrollImage() {
Graphics2D og = (Graphics2D) getBufferStrategy().getDrawGraphics();
fontMetrics = og.getFontMetrics(theFont);
Rectangle2D rect = fontMetrics.getStringBounds(message, og);
img = new BufferedImage((int) rect.getWidth(), (int) rect.getHeight(), BufferedImage.TYPE_INT_ARGB);
// At each frame, we get a reference on the rendering buffer graphics2d.
// To handle concurrency, we 'cut' it into graphics context for each cube.
graphicsScroll = img.createGraphics();
graphicsScroll.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
graphicsScroll.setBackground(Color.BLACK);
graphicsScroll.setFont(theFont);
graphicsScroll.setColor(bgColor);
graphicsScroll.fillRect(0, 0, img.getWidth(), img.getHeight()); // clear offScreen Image.
graphicsScroll.setColor(fgColor);
msgWidth = fontMetrics.stringWidth(message);
graphicsScroll.setColor(Color.white);
graphicsScroll.drawString(message, 1, img.getHeight() - 10);
// for better readability in front of an image draw an outline arround the text
graphicsScroll.setColor(Color.black);
graphicsScroll.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
Font font = new Font("Helvetica", Font.PLAIN, fontSize);
graphicsScroll.translate(1, img.getHeight() - 10);
FontRenderContext frc = graphicsScroll.getFontRenderContext();
GlyphVector gv = font.createGlyphVector(frc, message);
graphicsScroll.draw(gv.getOutline());
}
public void start() {
scrollTimer = new Timer(1000 / targetFps, this);
scrollTimer.setRepeats(true);
scrollTimer.setCoalesce(true);
scrollTimer.start();
}
public void startGamingloop() {
// loop initialization
long beforeTime, afterTime, timeDiff, sleepTime;
long overSleepTime = 0L;
int noDelays = 0;
long excess = 0L;
gameStartTime = System.nanoTime();
// prevStatsTime = gameStartTime;
beforeTime = gameStartTime;
period = (1000L * NANO_IN_MILLI) / targetFps; // rendering FPS (nanosecs/targetFPS)
System.out.println("FPS: " + targetFps + ", vsync=");
System.out.println("FPS period: " + period);
// gaming loop http://www.javagaming.org/index.php/topic,19971.0.html
while (true) {
// **2) execute physics
updateLeftEdge();
// **1) execute drawing
drawScroll();
// Synchronise with the display hardware.
// Flip the buffer
if (!getBufferStrategy().contentsLost()) {
getBufferStrategy().show();
}
if (isVsync) {
Toolkit.getDefaultToolkit().sync();
}
afterTime = System.nanoTime();
curRenderTime = afterTime;
calculateFramesPerSecond();
timeDiff = afterTime - beforeTime;
sleepTime = (period - timeDiff) - overSleepTime;
if (sleepTime > 0) { // time left in cycle
// System.out.println("sleepTime: " + (sleepTime/NANO_IN_MILLI));
try {
Thread.sleep(sleepTime / NANO_IN_MILLI);// nano->ms
} catch (InterruptedException ex) {
}
overSleepTime = (System.nanoTime() - afterTime) - sleepTime;
} else { // sleepTime <= 0;
System.out.println("Rendering too slow");
// this cycle took longer than period
excess -= sleepTime;
// store excess time value
overSleepTime = 0L;
if (++noDelays >= NO_DELAYS_PER_YIELD) {
Thread.yield();
// give another thread a chance to run
noDelays = 0;
}
}
beforeTime = System.nanoTime();
/*
* If the rendering is taking too long, then update the game state without rendering it, to get the UPS nearer to the required frame rate.
*/
int skips = 0;
while ((excess > period) && (skips < MAX_RENDER_SKIPS)) {
// update state but don’t render
System.out.println("Skip renderFPS, run updateFPS");
excess -= period;
updateLeftEdge();
skips++;
}
rendersSkipped += skips;
}
}
private void calculateFramesPerSecond() {
if (curRenderTime - lastFpsTime >= NANO_IN_MILLI * 1000) {
fps = frameCounter;
frameCounter = 0;
lastFpsTime = curRenderTime;
}
frameCounter++;
}
public void setRunning(boolean isRunning) {
this.isRunning = isRunning;
}
#Override
public void actionPerformed(ActionEvent e) {
render();
}
private void render() {
if (!isFullScreen) {
repaint(0, yPositionScroll, winWidth, yPositionScroll + fontMetrics.getAscent());
} else {
getBufferStrategy().show();
}
if (isVsync) {
Toolkit.getDefaultToolkit().sync();
}
updateLeftEdge();
drawScroll();
}
/**
* Draws (part) of the prerendered image with text to a position in the screen defined by an increasing
* variable "leftEdge".
*
* #return time drawing took.
*/
private long drawScroll() {
long beforeDrawText = System.nanoTime();
if (winWidth - leftEdge + msgWidth < 0) { // Current scroll entirely off-screen.
leftEdge = 0;
}
int x = winWidth - leftEdge;
int sourceWidth = Math.min(leftEdge, img.getWidth());
Graphics2D og = (Graphics2D) getBufferStrategy().getDrawGraphics();
try { // copy the pre drawn scroll to the screen
og.drawImage(img.getSubimage(0, 0, sourceWidth, img.getHeight()), x, yPositionScroll, null);
} catch (Exception e) {
System.out.println(e.getMessage() + " " + x + " " + sourceWidth);
}
long afterDrawText = System.nanoTime();
return afterDrawText - beforeDrawText;
}
public static void main(String[] args) {
MyScroll scroll = new MyScroll();
System.setProperty("sun.java2d.opengl", String.valueOf(scroll.isOpenGl())); // enable opengl
System.setProperty("sun.java2d.renderer.verbose", "true");
String renderer = "undefined";
try {
renderer = sun.java2d.pipe.RenderingEngine.getInstance().getClass().getName();
System.out.println("Renderer " + renderer);
} catch (Throwable th) {
// may fail with JDK9 jigsaw (jake)
if (false) {
System.err.println("Unable to get RenderingEngine.getInstance()");
th.printStackTrace();
}
}
scroll.setBackground(Color.green);
scroll.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
GraphicsEnvironment env = GraphicsEnvironment.getLocalGraphicsEnvironment();
GraphicsDevice[] screens = env.getScreenDevices();
// I want the external monitor attached to my notebook
GraphicsDevice device = screens[screens.length - 1];
boolean isFullScreenSupported = device.isFullScreenSupported();
scroll.setFullScreen(isFullScreenSupported);
scroll.setUndecorated(isFullScreenSupported);
scroll.setResizable(!isFullScreenSupported);
if (isFullScreenSupported) {
device.setFullScreenWindow(scroll);
scroll.setIgnoreRepaint(true);
scroll.validate();
} else {
// Windowed mode
Rectangle r = GraphicsEnvironment.getLocalGraphicsEnvironment().getMaximumWindowBounds();
scroll.setSize(r.width, r.height);
scroll.pack();
scroll.setExtendedState(JFrame.MAXIMIZED_BOTH);
scroll.setVisible(true);
}
// exit on pressing escape
scroll.addKeyListener(new KeyAdapter() {
#Override
public void keyPressed(KeyEvent e) {
if (e.getKeyCode() == KeyEvent.VK_ESCAPE) {
scroll.setRunning(false);
if(scroll.getScrollTimer() != null) {
scroll.getScrollTimer().stop();
}
System.exit(0);
}
}
});
scroll.setVisible(true);
scroll.init();
if (scroll.isUseGamingLoop()) {
scroll.startGamingloop();
} else {
scroll.start();
}
}
private void updateLeftEdge() {
leftEdge += speed;
}
public Timer getScrollTimer() {
return scrollTimer;
}
public void setFullScreen(boolean isFullScreen) {
this.isFullScreen = isFullScreen;
}
public void setTargetFps(int targetFps) {
this.targetFps = targetFps;
}
public void setOpenGl(boolean isOpenGl) {
this.isOpenGl = isOpenGl;
}
public void setVsync(boolean isVsync) {
this.isVsync = isVsync;
}
public void setUseGamingLoop(boolean useGamingLoop) {
this.useGamingLoop = useGamingLoop;
}
public void setSpeed(int speed) {
this.speed = speed;
}
public void setMessage(String message) {
this.message = message;
}
private String getDeviceConfigurationString(GraphicsConfiguration gc){
return "Bounds: " + gc.getBounds() + "\n" +
"Buffer Capabilities: " + gc.getBufferCapabilities() + "\n" +
" Back Buffer Capabilities: " + gc.getBufferCapabilities().getBackBufferCapabilities() + "\n" +
" Accelerated: " + gc.getBufferCapabilities().getBackBufferCapabilities().isAccelerated() + "\n" +
" True Volatile: " + gc.getBufferCapabilities().getBackBufferCapabilities().isTrueVolatile() + "\n" +
" Flip Contents: " + gc.getBufferCapabilities().getFlipContents() + "\n" +
" Front Buffer Capabilities: " + gc.getBufferCapabilities().getFrontBufferCapabilities() + "\n" +
" Accelerated: " + gc.getBufferCapabilities().getFrontBufferCapabilities().isAccelerated() + "\n" +
" True Volatile: " + gc.getBufferCapabilities().getFrontBufferCapabilities().isTrueVolatile() + "\n" +
" Is Full Screen Required: " + gc.getBufferCapabilities().isFullScreenRequired() + "\n" +
" Is MultiBuffer Available: " + gc.getBufferCapabilities().isMultiBufferAvailable() + "\n" +
" Is Page Flipping: " + gc.getBufferCapabilities().isPageFlipping() + "\n" +
"Device: " + gc.getDevice() + "\n" +
" Available Accelerated Memory: " + gc.getDevice().getAvailableAcceleratedMemory() + "\n" +
" ID String: " + gc.getDevice().getIDstring() + "\n" +
" Type: " + gc.getDevice().getType() + "\n" +
" Display Mode: " + gc.getDevice().getDisplayMode() + "\n" +
"Image Capabilities: " + gc.getImageCapabilities() + "\n" +
" Accelerated: " + gc.getImageCapabilities().isAccelerated() + "\n" +
" True Volatile: " + gc.getImageCapabilities().isTrueVolatile() + "\n";
}
public boolean isOpenGl() {
return isOpenGl;
}
public boolean isUseGamingLoop() {
return useGamingLoop;
}
}
Output of graphics capabilities:
Renderer sun.java2d.pisces.PiscesRenderingEngine
Bounds: java.awt.Rectangle[x=3839,y=0,width=3840,height=2160]
Buffer Capabilities: sun.awt.X11GraphicsConfig$XDBECapabilities#68de145
Back Buffer Capabilities: java.awt.ImageCapabilities#27fa135a
Accelerated: false
True Volatile: false
Flip Contents: undefined
Front Buffer Capabilities: java.awt.ImageCapabilities#27fa135a
Accelerated: false
True Volatile: false
Is Full Screen Required: false
Is MultiBuffer Available: false
Is Page Flipping: true
Device: X11GraphicsDevice[screen=1]
Available Accelerated Memory: -1
ID String: :0.1
Type: 0
Display Mode: java.awt.DisplayMode#1769
Image Capabilities: java.awt.ImageCapabilities#27fa135a
Accelerated: false
True Volatile: false
EDIT 2:
I wrote the same in javafx now, same result, it is stuttering as soon as I move more than 3 pixels at once.
I'm running on an Intel i9 9900K, Nvidia GeForce RTX 2060 Mobile, Ubuntu, OpenJdk 14.
package scrolling;
import java.awt.Color;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.font.FontRenderContext;
import java.awt.font.GlyphVector;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
import javax.swing.Timer;
import javafx.animation.AnimationTimer;
import javafx.application.Application;
import javafx.embed.swing.SwingFXUtils;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.stage.Screen;
import javafx.stage.Stage;
/**
* A smooth scroll with green background to be used with a video cutter.
*
* https://stackoverflow.com/questions/51478675/error-javafx-runtime-components-are-missing-and-are-required-to-run-this-appli
* https://stackoverflow.com/questions/18547362/javafx-and-openjdk
*
*/
public class MyScroll extends Application {
private boolean useGamingLoop = false;
private int speed = 3;
private String message;
private transient double leftEdge; // Offset from window's right edge to left edge
private Color bgColor;
private Color fgColor;
private double winWidth;
private int winHeight;
private double position = 0.77;
private int yPositionScroll;
private Image img;
private int msgWidth = 0;
private Timer scrollTimer;
private boolean isRunning;
private ImageView imageView;
long lastUpdateTime;
long lastIntervall;
long nextIntervall;
String ADAPTIVE_PULSE_PROP = "com.sun.scenario.animation.adaptivepulse";
int frame = 0;
long timeOfLastFrameSwitch = 0;
#Override
public void start(final Stage stage) {
message = "This is a test. ";
leftEdge = 0;
bgColor = Color.green;
fgColor = Color.white;
winWidth = (int)Screen.getPrimary().getBounds().getWidth();
winHeight = (int)Screen.getPrimary().getBounds().getHeight();
yPositionScroll = (int) (winHeight * position);
initScrollImage(stage);
stage.setFullScreenExitHint("");
stage.setAlwaysOnTop(true);
new AnimationTimer() {
#Override
public void handle(long now) {
nextIntervall = now - lastUpdateTime;
System.out.println(lastIntervall - nextIntervall);
lastUpdateTime = System.nanoTime();
drawScroll(stage);
lastIntervall = nextIntervall;
}
}.start();
//Creating a Group object
Group root = new Group(imageView);
//Creating a scene object
Scene scene = new Scene(root);
//Adding scene to the stage
stage.setScene(scene);
stage.show();
stage.setFullScreen(true);
}
/**
* Draw the entire text to an imageview and add to the scene.
*/
private void initScrollImage(Stage stage) {
int fontSize = (int)winWidth / 17;
Font theFont = new Font("Helvetica", Font.PLAIN, fontSize);
BufferedImage tempImg = new BufferedImage((int)winWidth, winHeight, BufferedImage.TYPE_INT_ARGB);
FontMetrics fontMetrics = tempImg.getGraphics().getFontMetrics(theFont);
Rectangle2D rect = fontMetrics.getStringBounds(message, tempImg.getGraphics());
msgWidth = fontMetrics.stringWidth(message);
BufferedImage bufferedImage = new BufferedImage((int) rect.getWidth(), (int) rect.getHeight(), BufferedImage.TYPE_INT_ARGB);
Graphics2D graphicsScroll = bufferedImage.createGraphics();
graphicsScroll.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
graphicsScroll.setBackground(Color.BLACK);
graphicsScroll.setFont(theFont);
graphicsScroll.setColor(bgColor);
graphicsScroll.fillRect(0, 0, bufferedImage.getWidth(), bufferedImage.getHeight()); // set background color
graphicsScroll.setColor(fgColor);
graphicsScroll.drawString(message, 1, bufferedImage.getHeight() - 10);
// for better readability in front of an image draw an outline arround the text
graphicsScroll.setColor(Color.black);
graphicsScroll.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
Font font = new Font("Helvetica", Font.PLAIN, fontSize);
graphicsScroll.translate(1, bufferedImage.getHeight() - 10);
FontRenderContext frc = graphicsScroll.getFontRenderContext();
GlyphVector gv = font.createGlyphVector(frc, message);
graphicsScroll.draw(gv.getOutline());
img = SwingFXUtils.toFXImage(bufferedImage, null);
imageView = new ImageView(img);
imageView.setSmooth(false);
imageView.setCache(true);
//Setting the preserve ratio of the image view
imageView.setPreserveRatio(true);
tempImg.flush();
}
/**
* Draws (part) of the prerendered image with text to a position in the screen defined by an increasing
* variable "leftEdge".
*
* #return time drawing took.
*/
private void drawScroll(Stage stage) {
leftEdge += speed;
if (winWidth - leftEdge + msgWidth < 0) { // Current scroll entirely off-screen.
leftEdge = 0;
}
// imageView.relocate(winWidth - leftEdge, yPositionScroll);
imageView.setX(winWidth - leftEdge);
}
public static void main(String[] args) {
// System.setProperty("sun.java2d.opengl", "true");
System.setProperty("prism.vsync", "true");
// System.setProperty("com.sun.scenario.animation.adaptivepulse", "true");
System.setProperty("com.sun.scenario.animation.vsync", "true");
launch(args);
}
public Timer getScrollTimer() {
return scrollTimer;
}
public void setSpeed(int speed) {
this.speed = speed;
}
public void setMessage(String message) {
this.message = message;
}
public boolean isUseGamingLoop() {
return useGamingLoop;
}
public void setRunning(boolean isRunning) {
this.isRunning = isRunning;
}
}
I'm running this with Windows 10. I don't think it will work well on a Unix system.
...
The code consists of 9 classes in 5 packages. The package name is in the code.
Marquee Class
package com.ggl.marquee;
import javax.swing.SwingUtilities;
import com.ggl.marquee.model.MarqueeModel;
import com.ggl.marquee.view.MarqueeFrame;
public class Marquee implements Runnable {
#Override
public void run() {
new MarqueeFrame(new MarqueeModel());
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Marquee());
}
}
CreateMarqueeActionListener Class
package com.ggl.marquee.controller;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JTextField;
import com.ggl.marquee.model.MarqueeModel;
import com.ggl.marquee.view.MarqueeFrame;
public class CreateMarqueeActionListener implements ActionListener {
private JTextField field;
private MarqueeFrame frame;
private MarqueeModel model;
public CreateMarqueeActionListener(MarqueeFrame frame, MarqueeModel model,
JTextField field) {
this.frame = frame;
this.model = model;
this.field = field;
}
#Override
public void actionPerformed(ActionEvent event) {
model.stopDtpRunnable();
model.resetPixels();
String s = field.getText().trim();
if (s.equals("")) {
frame.repaintMarqueePanel();
return;
}
s = " " + s + " ";
model.setTextPixels(model.getDefaultFont().getTextPixels(s));
frame.repaintMarqueePanel();
}
}
FontSelectionListener Class
package com.ggl.marquee.controller;
import javax.swing.DefaultListSelectionModel;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import com.ggl.marquee.model.MarqueeFont;
import com.ggl.marquee.model.MarqueeModel;
public class FontSelectionListener implements ListSelectionListener {
private MarqueeModel model;
public FontSelectionListener(MarqueeModel model) {
this.model = model;
}
#Override
public void valueChanged(ListSelectionEvent event) {
DefaultListSelectionModel selectionModel = (DefaultListSelectionModel) event
.getSource();
if (!event.getValueIsAdjusting()) {
int index = selectionModel.getMinSelectionIndex();
if (index >= 0) {
MarqueeFont font = model.getDefaultListModel().get(index);
model.setDefaultFont(font);
}
}
}
}
FontGenerator Class
package com.ggl.marquee.model;
import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.image.BufferedImage;
import java.awt.image.DataBufferInt;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class FontGenerator {
private static final boolean DEBUG = false;
private Font font;
private FontHeights fontHeights;
private Map<Character, MarqueeCharacter> characterMap;
public FontGenerator(Font font) {
this.font = font;
this.characterMap = new HashMap<Character, MarqueeCharacter>();
}
public void execute() {
int width = 50;
BufferedImage bi = generateCharacterImage(width, "B");
int[] result1 = getCharacterHeight(bi);
bi = generateCharacterImage(width, "g");
int[] result2 = getCharacterHeight(bi);
fontHeights = new FontHeights(result1[0], result1[1], result2[1]);
if (DEBUG) System.out.println(fontHeights.getAscender() + ", "
+ fontHeights.getBaseline() + ", "
+ fontHeights.getDescender());
for (int x = 32; x < 127; x++) {
char c = (char) x;
StringBuilder builder = new StringBuilder(3);
builder.append('H');
builder.append(c);
builder.append('H');
bi = generateCharacterImage(width, builder.toString());
int[][] pixels = convertTo2D(bi);
MarqueeCharacter mc = getCharacterPixels(pixels);
if (DEBUG) {
System.out.println(builder.toString() + " " +
mc.getWidth() + "x" + mc.getHeight());
}
characterMap.put(c, mc);
}
}
private BufferedImage generateCharacterImage(int width, String string) {
BufferedImage bi = new BufferedImage(
width, width, BufferedImage.TYPE_INT_RGB);
Graphics g = bi.getGraphics();
g.setFont(font);
g.setColor(Color.WHITE);
g.fillRect(0, 0, width, width);
g.setColor(Color.BLACK);
g.drawString(string, 0, width / 2);
return bi;
}
private int[] getCharacterHeight(BufferedImage bi) {
int[][] pixels = convertTo2D(bi);
int minHeight = bi.getHeight();
int maxHeight = 0;
for (int i = 0; i < pixels.length; i++) {
for (int j = 0; j < pixels[i].length; j++) {
if (pixels[i][j] < -1) {
minHeight = Math.min(i, minHeight);
maxHeight = Math.max(i, maxHeight);
}
}
}
int[] result = new int[2];
result[0] = minHeight;
result[1] = maxHeight;
return result;
}
private MarqueeCharacter getCharacterPixels(int[][] pixels) {
List<Boolean[]> list = new ArrayList<Boolean[]>();
int startRow = fontHeights.getAscender();
int endRow = fontHeights.getDescender();
int height = fontHeights.getCharacterHeight();
int startColumn = getCharacterColumnStart(pixels);
int endColumn = getCharacterColumnEnd(pixels);
for (int i = startColumn; i <= endColumn; i++) {
Boolean[] characterColumn = new Boolean[height];
int k = 0;
for (int j = startRow; j <= endRow; j++) {
if (pixels[j][i] < -1) characterColumn[k] = true;
else characterColumn[k] = false;
k++;
}
list.add(characterColumn);
}
MarqueeCharacter mc = new MarqueeCharacter(list.size(), height);
for (int i = 0; i < list.size(); i++) {
Boolean[] characterColumn = list.get(i);
mc.setColumn(characterColumn);
}
return mc;
}
private int getCharacterColumnStart(int[][] pixels) {
int start = fontHeights.getAscender();
int end = fontHeights.getBaseline();
int letterEndFlag = 0;
int column = 1;
while (letterEndFlag < 1) {
boolean pixelDetected = false;
for (int i = start; i <= end; i++) {
if (pixels[i][column] < -1) {
pixelDetected = true;
}
}
column++;
// End of first letter
if ((letterEndFlag == 0) && !pixelDetected) letterEndFlag = 1;
}
return column;
}
private int getCharacterColumnEnd(int[][] pixels) {
int start = fontHeights.getAscender();
int end = fontHeights.getBaseline();
int height = fontHeights.getCharacterHeight2();
int letterEndFlag = 0;
int column = pixels.length - 1;
while (letterEndFlag < 4) {
int pixelCount = 0;
for (int i = start; i <= end; i++) {
if (pixels[i][column] < -1) {
pixelCount++;
}
}
column--;
// End of first letter
if (pixelCount >= height) letterEndFlag++;
// Start of first letter
// if ((letterEndFlag == 0) && (pixelCount > 0)) letterEndFlag = 1;
}
return column;
}
private int[][] convertTo2D(BufferedImage image) {
final int[] pixels = ((DataBufferInt) image.getRaster().getDataBuffer())
.getData();
final int width = image.getWidth();
final int height = image.getHeight();
int[][] result = new int[height][width];
for (int pixel = 0, row = 0, col = 0; pixel < pixels.length; pixel++) {
result[row][col] = pixels[pixel];
col++;
if (col == width) {
col = 0;
row++;
}
}
return result;
}
public MarqueeCharacter getCharacter(Character c) {
MarqueeCharacter mc = characterMap.get(c);
return (mc == null) ? characterMap.get('?') : mc;
}
public int getCharacterHeight() {
return fontHeights.getCharacterHeight();
}
}
FontHeights Class
package com.ggl.marquee.model;
public class FontHeights {
private final int ascender;
private final int baseline;
private final int descender;
public FontHeights(int ascender, int baseline, int descender) {
this.ascender = ascender;
this.baseline = baseline;
this.descender = descender;
}
public int getCharacterHeight() {
return descender - ascender + 1;
}
public int getCharacterHeight2() {
return baseline - ascender + 1;
}
public int getAscender() {
return ascender;
}
public int getBaseline() {
return baseline;
}
public int getDescender() {
return descender;
}
}
MarqueeCharacter Class
package com.ggl.marquee.model;
import java.security.InvalidParameterException;
public class MarqueeCharacter {
private static int columnCount;
private int height;
private int width;
private boolean[][] pixels;
public MarqueeCharacter(int width, int height) {
this.width = width;
this.height = height;
this.pixels = new boolean[width][height];
columnCount = 0;
}
public void setColumn(Boolean[] value) {
int height = value.length;
if (this.height != height) {
String s = "The number of values must equal the column height - "
+ this.height;
throw new InvalidParameterException(s);
}
for (int i = 0; i < height; i++) {
pixels[columnCount][i] = value[i];
}
columnCount++;
}
public boolean[][] getPixels() {
return pixels;
}
public boolean isComplete() {
return (width == columnCount);
}
public int getHeight() {
return height;
}
public int getWidth() {
return width;
}
}
MarqueeFont Class
package com.ggl.marquee.model;
import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.font.FontRenderContext;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
import java.awt.image.DataBufferInt;
public class MarqueeFont {
private static final boolean DEBUG = false;
private int fontHeight;
private Font font;
public MarqueeFont(Font font) {
this.font = font;
FontRenderContext frc = new FontRenderContext(null, true, true);
Rectangle2D r2D = font.getStringBounds("HgH", frc);
this.fontHeight = (int) Math.round(r2D.getHeight());
if (DEBUG) {
System.out.println(font.getFamily() + " " + fontHeight + " pixels");
}
}
public boolean[][] getTextPixels(String s) {
FontRenderContext frc = new FontRenderContext(null, true, true);
Rectangle2D r2D = font.getStringBounds(s, frc);
int rWidth = (int) Math.round(r2D.getWidth());
int rHeight = (int) Math.round(r2D.getHeight());
int rX = (int) Math.round(r2D.getX());
int rY = (int) Math.round(r2D.getY());
if (DEBUG) {
System.out.print(s);
System.out.print(", rWidth = " + rWidth);
System.out.print(", rHeight = " + rHeight);
System.out.println(", rX = " + rX + ", rY = " + rY);
}
BufferedImage bi = generateCharacterImage(rX, -rY, rWidth, rHeight, s);
int[][] pixels = convertTo2D(bi);
if (DEBUG) {
displayPixels(pixels);
}
return createTextPixels(pixels);
}
private BufferedImage generateCharacterImage(int x, int y, int width,
int height, String string) {
BufferedImage bi = new BufferedImage(width, height,
BufferedImage.TYPE_INT_RGB);
Graphics g = bi.getGraphics();
g.setFont(font);
g.setColor(Color.WHITE);
g.fillRect(0, 0, width, height);
g.setColor(Color.BLACK);
g.drawString(string, x, y);
return bi;
}
private int[][] convertTo2D(BufferedImage image) {
final int[] pixels = ((DataBufferInt) image.getRaster().getDataBuffer())
.getData();
final int width = image.getWidth();
final int height = image.getHeight();
int[][] result = new int[height][width];
int row = 0;
int col = 0;
for (int pixel = 0; pixel < pixels.length; pixel++) {
result[row][col] = pixels[pixel];
col++;
if (col == width) {
col = 0;
row++;
}
}
return result;
}
private void displayPixels(int[][] pixels) {
for (int i = 0; i < pixels.length; i++) {
String s = String.format("%03d", (i + 1));
System.out.print(s + ". ");
for (int j = 0; j < pixels[i].length; j++) {
if (pixels[i][j] == -1) {
System.out.print(" ");
} else {
System.out.print("X ");
}
}
System.out.println("");
}
}
private boolean[][] createTextPixels(int[][] pixels) {
// The int array pixels is in column, row order.
// We have to flip the array and produce the output
// in row, column order.
if (DEBUG) {
System.out.println(pixels[0].length + "x" + pixels.length);
}
boolean[][] textPixels = new boolean[pixels[0].length][pixels.length];
for (int i = 0; i < pixels.length; i++) {
for (int j = 0; j < pixels[i].length; j++) {
if (pixels[i][j] == -1) {
textPixels[j][i] = false;
} else {
textPixels[j][i] = true;
}
}
}
return textPixels;
}
public Font getFont() {
return font;
}
public int getFontHeight() {
return fontHeight;
}
#Override
public String toString() {
StringBuilder builder = new StringBuilder();
builder.append(font.getFamily());
builder.append(", ");
builder.append(getStyleText());
builder.append(", ");
builder.append(font.getSize());
builder.append(" pixels");
return builder.toString();
}
private StringBuilder getStyleText() {
StringBuilder builder = new StringBuilder();
int style = font.getStyle();
if (style == Font.PLAIN) {
builder.append("normal");
} else if (style == Font.BOLD) {
builder.append("bold");
} else if (style == Font.ITALIC) {
builder.append("italic");
} else if (style == (Font.BOLD + Font.ITALIC)) {
builder.append("bold italic");
} else {
builder.append("unknown style");
}
return builder;
}
}
MarqueeFontFactory Class
package com.ggl.marquee.model;
import java.awt.Font;
import javax.swing.DefaultListModel;
public class MarqueeFontFactory {
private DefaultListModel<MarqueeFont> fontList;
private MarqueeFont defaultFont;
public MarqueeFontFactory() {
this.fontList = new DefaultListModel<MarqueeFont>();
addElements();
}
private void addElements() {
this.defaultFont = new MarqueeFont(new Font("Arial", Font.BOLD, 16));
fontList.addElement(defaultFont);
fontList.addElement(new MarqueeFont(new Font("Cambria", Font.BOLD, 16)));
fontList.addElement(new MarqueeFont(new Font("Courier New", Font.BOLD,
16)));
fontList.addElement(new MarqueeFont(new Font("Georgia", Font.BOLD, 16)));
fontList.addElement(new MarqueeFont(new Font("Lucida Calligraphy",
Font.BOLD, 16)));
fontList.addElement(new MarqueeFont(new Font("Times New Roman",
Font.BOLD, 16)));
fontList.addElement(new MarqueeFont(new Font("Verdana", Font.BOLD, 16)));
}
public DefaultListModel<MarqueeFont> getFontList() {
return fontList;
}
public void setDefaultFont(MarqueeFont defaultFont) {
this.defaultFont = defaultFont;
}
public MarqueeFont getDefaultFont() {
return defaultFont;
}
public int getCharacterHeight() {
int maxHeight = 0;
for (int i = 0; i < fontList.getSize(); i++) {
MarqueeFont font = fontList.get(i);
int height = font.getFontHeight();
maxHeight = Math.max(height, maxHeight);
}
return maxHeight;
}
}
MarqueeModel Class
package com.ggl.marquee.model;
import javax.swing.DefaultListModel;
import com.ggl.marquee.runnable.DisplayTextPixelsRunnable;
import com.ggl.marquee.view.MarqueeFrame;
public class MarqueeModel {
private static final int marqueeWidth = 120;
private boolean[][] marqueePixels;
private boolean[][] textPixels;
private DisplayTextPixelsRunnable dtpRunnable;
private MarqueeFontFactory fonts;
private MarqueeFrame frame;
public MarqueeModel() {
this.fonts = new MarqueeFontFactory();
this.marqueePixels = new boolean[marqueeWidth][getMarqueeHeight()];
}
public void setFrame(MarqueeFrame frame) {
this.frame = frame;
}
public MarqueeFontFactory getFonts() {
return fonts;
}
public DefaultListModel<MarqueeFont> getDefaultListModel() {
return fonts.getFontList();
}
public MarqueeFont getDefaultFont() {
return fonts.getDefaultFont();
}
public void setDefaultFont(MarqueeFont defaultFont) {
fonts.setDefaultFont(defaultFont);
}
public boolean[][] getMarqueePixels() {
return marqueePixels;
}
public boolean getMarqueePixel(int width, int height) {
return marqueePixels[width][height];
}
public int getMarqueeWidth() {
return marqueeWidth;
}
public int getMarqueeHeight() {
return fonts.getCharacterHeight();
}
public boolean[][] getTextPixels() {
return textPixels;
}
public int getTextPixelWidth() {
return textPixels.length;
}
private void startDtpRunnable() {
dtpRunnable = new DisplayTextPixelsRunnable(frame, this);
new Thread(dtpRunnable).start();
}
public void stopDtpRunnable() {
if (dtpRunnable != null) {
dtpRunnable.stopDisplayTextPixelsRunnable();
dtpRunnable = null;
}
}
public void setTextPixels(boolean[][] textPixels) {
this.textPixels = textPixels;
if (textPixels.length < getMarqueeWidth()) {
this.marqueePixels = copyCharacterPixels(0, textPixels,
marqueePixels);
} else {
startDtpRunnable();
}
}
public void resetPixels() {
for (int i = 0; i < getMarqueeWidth(); i++) {
for (int j = 0; j < getMarqueeHeight(); j++) {
marqueePixels[i][j] = false;
}
}
}
public void setAllPixels() {
for (int i = 0; i < getMarqueeWidth(); i++) {
for (int j = 0; j < getMarqueeHeight(); j++) {
marqueePixels[i][j] = true;
}
}
}
public boolean[][] copyCharacterPixels(int position,
boolean[][] characterPixels, boolean[][] textPixels) {
for (int i = 0; i < characterPixels.length; i++) {
for (int j = 0; j < characterPixels[i].length; j++) {
textPixels[i + position][j] = characterPixels[i][j];
}
}
return textPixels;
}
public void copyTextPixels(int position) {
for (int i = 0; i < marqueePixels.length; i++) {
int k = i + position;
k %= textPixels.length;
for (int j = 0; j < textPixels[i].length; j++) {
marqueePixels[i][j] = textPixels[k][j];
}
}
}
}
DisplayAllPixelsRunnable Class
package com.ggl.marquee.runnable;
import javax.swing.SwingUtilities;
import com.ggl.marquee.model.MarqueeModel;
import com.ggl.marquee.view.MarqueeFrame;
public class DisplayAllPixelsRunnable implements Runnable {
private MarqueeFrame frame;
private MarqueeModel model;
public DisplayAllPixelsRunnable(MarqueeFrame frame, MarqueeModel model) {
this.frame = frame;
this.model = model;
}
#Override
public void run() {
model.setAllPixels();
repaint();
try {
Thread.sleep(3000L);
} catch (InterruptedException e) {
}
model.resetPixels();
repaint();
}
private void repaint() {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
frame.repaintMarqueePanel();
}
});
}
}
DisplayTextPixelsRunnable Class
package com.ggl.marquee.runnable;
import javax.swing.SwingUtilities;
import com.ggl.marquee.model.MarqueeModel;
import com.ggl.marquee.view.MarqueeFrame;
public class DisplayTextPixelsRunnable implements Runnable {
private static int textPixelPosition;
private volatile boolean running;
private MarqueeFrame frame;
private MarqueeModel model;
public DisplayTextPixelsRunnable(MarqueeFrame frame, MarqueeModel model) {
this.frame = frame;
this.model = model;
textPixelPosition = 0;
}
#Override
public void run() {
this.running = true;
while (running) {
model.copyTextPixels(textPixelPosition);
repaint();
sleep();
textPixelPosition++;
textPixelPosition %= model.getTextPixelWidth();
}
}
private void repaint() {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
frame.repaintMarqueePanel();
}
});
}
private void sleep() {
try {
Thread.sleep(50L);
} catch (InterruptedException e) {
}
}
public synchronized void stopDisplayTextPixelsRunnable() {
this.running = false;
}
}
ControlPanel Class
package com.ggl.marquee.view;
import java.awt.BorderLayout;
import javax.swing.BoxLayout;
import javax.swing.JButton;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextField;
import javax.swing.ListSelectionModel;
import com.ggl.marquee.controller.CreateMarqueeActionListener;
import com.ggl.marquee.controller.FontSelectionListener;
import com.ggl.marquee.model.MarqueeFont;
import com.ggl.marquee.model.MarqueeModel;
public class ControlPanel {
private JButton submitButton;
private JPanel panel;
private MarqueeFrame frame;
private MarqueeModel model;
public ControlPanel(MarqueeFrame frame, MarqueeModel model) {
this.frame = frame;
this.model = model;
createPartControl();
}
private void createPartControl() {
panel = new JPanel();
panel.setLayout(new BoxLayout(panel, BoxLayout.PAGE_AXIS));
JPanel fontPanel = new JPanel();
fontPanel.setLayout(new BorderLayout());
JLabel fontLabel = new JLabel("Fonts");
fontPanel.add(fontLabel, BorderLayout.NORTH);
JList<MarqueeFont> fontList = new JList<MarqueeFont>(
model.getDefaultListModel());
fontList.setSelectedValue(model.getDefaultFont(), true);
fontList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
fontList.setVisibleRowCount(3);
ListSelectionModel listSelectionModel = fontList.getSelectionModel();
listSelectionModel.addListSelectionListener(new FontSelectionListener(
model));
JScrollPane fontScrollPane = new JScrollPane(fontList);
fontPanel.add(fontScrollPane, BorderLayout.CENTER);
panel.add(fontPanel);
JPanel fieldPanel = new JPanel();
JLabel fieldLabel = new JLabel("Marquee Text: ");
fieldPanel.add(fieldLabel);
JTextField field = new JTextField(30);
fieldPanel.add(field);
panel.add(fieldPanel);
JPanel buttonPanel = new JPanel();
submitButton = new JButton("Submit");
submitButton.addActionListener(new CreateMarqueeActionListener(frame,
model, field));
buttonPanel.add(submitButton);
panel.add(buttonPanel);
}
public JPanel getPanel() {
return panel;
}
public JButton getSubmitButton() {
return submitButton;
}
}
MarqueeFrame Class
package com.ggl.marquee.view;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import javax.swing.BoxLayout;
import javax.swing.JFrame;
import javax.swing.JPanel;
import com.ggl.marquee.model.MarqueeModel;
import com.ggl.marquee.runnable.DisplayAllPixelsRunnable;
public class MarqueeFrame {
private ControlPanel controlPanel;
private DisplayAllPixelsRunnable dapRunnable;
private JFrame frame;
private MarqueeModel model;
private MarqueePanel marqueePanel;
public MarqueeFrame(MarqueeModel model) {
this.model = model;
model.setFrame(this);
createPartControl();
}
private void createPartControl() {
frame = new JFrame();
// frame.setIconImage(getFrameImage());
frame.setTitle("Marquee");
frame.setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
frame.addWindowListener(new WindowAdapter() {
#Override
public void windowClosing(WindowEvent event) {
exitProcedure();
}
});
JPanel mainPanel = new JPanel();
mainPanel.setLayout(new BoxLayout(mainPanel, BoxLayout.PAGE_AXIS));
marqueePanel = new MarqueePanel(model);
mainPanel.add(marqueePanel);
controlPanel = new ControlPanel(this, model);
mainPanel.add(controlPanel.getPanel());
frame.add(mainPanel);
frame.pack();
frame.setLocationByPlatform(true);
frame.getRootPane().setDefaultButton(controlPanel.getSubmitButton());
frame.setVisible(true);
dapRunnable = new DisplayAllPixelsRunnable(this, model);
new Thread(dapRunnable).start();
}
private void exitProcedure() {
frame.dispose();
System.exit(0);
}
public void repaintMarqueePanel() {
marqueePanel.repaint();
}
}
MarqueePanel Class
package com.ggl.marquee.view;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import javax.swing.JPanel;
import com.ggl.marquee.model.MarqueeModel;
public class MarqueePanel extends JPanel {
private static final long serialVersionUID = -1677343084333836763L;
private static final int pixelWidth = 4;
private static final int gapWidth = 2;
private static final int totalWidth = pixelWidth + gapWidth;
private static final int yStart = gapWidth + totalWidth + totalWidth;
private MarqueeModel model;
public MarqueePanel(MarqueeModel model) {
this.model = model;
int width = model.getMarqueeWidth() * totalWidth + gapWidth;
int height = model.getMarqueeHeight() * totalWidth + yStart + yStart;
setPreferredSize(new Dimension(width, height));
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
g.setColor(Color.BLACK);
g.fillRect(0, 0, getWidth(), getHeight());
int x = gapWidth;
int y = yStart;
for (int i = 0; i < model.getMarqueeWidth(); i++) {
for (int j = 0; j < model.getMarqueeHeight(); j++) {
if (model.getMarqueePixel(i, j)) {
g.setColor(Color.PINK);
} else {
g.setColor(Color.BLACK);
}
g.fillRect(x, y, pixelWidth, pixelWidth);
y += totalWidth;
}
y = yStart;
x += totalWidth;
}
}
}
I did a few changes upon the same example (Marquee) posted by #Gilbert Le Blanc.
First of all, in the Marquee example the Thread.sleep() method was being used instead of a more modern approach with a TimerTask. I cannot stress enough about how much difference there is between Thread.sleep() and TimerTask.
Thread.sleep() is inaccurate. How inaccurate depends on the underlying operating system and its timers and schedulers. I've experienced that garbage collection going on in parallel can lead to excessive sleep.
Source: src
This led to an important problem
The painting function is called inconsistently instead of the expected fixed time for 30fps which is roughly 40ms, this generates some of the shuttering problem.
Source: me
which is solved partially by the TimerTask approach
On at least one major operating system (Windows), blocking on a timer has a much better precision and reliability than sleeping.
Source: GameDev.net
I have noticed a huge improvement when i rewrote the same example but using timerTask and it's surely more fluid than using the example you provided (2D Swing);
I compared it as following, the speed of the Marquee example is more or less the same speed of your 2d swing test put at 10, so try running your test with int speed = 10; and the one provided above and see if shutter more or less.
Alternatively, you can try to run the native Marquee Example and the new one with TimerTask and you should see an important major difference.
I think implementing a buffer strategy with this one is the way to go for achieving a truly fluid scroll experience...otherwise there is always OpenGL
The classes i changed:
DisplayTextPixelsRunnable is now DisplayTextPixelsTimerTask
package com.ggl.marquee.runnable;
import javax.swing.SwingUtilities;
import com.ggl.marquee.model.MarqueeModel;
import com.ggl.marquee.view.MarqueeFrame;
import java.util.TimerTask;
public class DisplayTextPixelsTimerTask extends TimerTask {
private static int textPixelPosition;
private final MarqueeFrame frame;
private final MarqueeModel model;
public DisplayTextPixelsTimerTask(MarqueeFrame frame, MarqueeModel model) {
this.frame = frame;
this.model = model;
textPixelPosition = 0;
}
#Override
public void run() {
model.copyTextPixels(textPixelPosition);
repaint();
textPixelPosition++;
textPixelPosition %= model.getTextPixelWidth();
}
private void repaint() {
SwingUtilities.invokeLater(frame::repaintMarqueePanel);
}
}
MarqueeModel
package com.ggl.marquee.model;
import javax.swing.DefaultListModel;
import com.ggl.marquee.runnable.DisplayTextPixelsTimerTask;
import com.ggl.marquee.view.MarqueeFrame;
import java.util.Timer;
import java.util.TimerTask;
public class MarqueeModel {
private static final int marqueeWidth = 120;
private static final long FPS_TARGET = 30;
private static final long DELAY_TIME = (long) (1000.0d / FPS_TARGET);
private boolean[][] marqueePixels;
private boolean[][] textPixels;
private TimerTask dtpRunnable;
private MarqueeFontFactory fonts;
private MarqueeFrame frame;
public MarqueeModel() {
this.fonts = new MarqueeFontFactory();
this.marqueePixels = new boolean[marqueeWidth][getMarqueeHeight()];
}
public void setFrame(MarqueeFrame frame) {
this.frame = frame;
}
public MarqueeFontFactory getFonts() {
return fonts;
}
public DefaultListModel<MarqueeFont> getDefaultListModel() {
return fonts.getFontList();
}
public MarqueeFont getDefaultFont() {
return fonts.getDefaultFont();
}
public void setDefaultFont(MarqueeFont defaultFont) {
fonts.setDefaultFont(defaultFont);
}
public boolean[][] getMarqueePixels() {
return marqueePixels;
}
public boolean getMarqueePixel(int width, int height) {
return marqueePixels[width][height];
}
public int getMarqueeWidth() {
return marqueeWidth;
}
public int getMarqueeHeight() {
return fonts.getCharacterHeight();
}
public boolean[][] getTextPixels() {
return textPixels;
}
public int getTextPixelWidth() {
return textPixels.length;
}
private void startDtpRunnable() {
dtpRunnable = new DisplayTextPixelsTimerTask(frame, this);
//running timer task as daemon thread
Timer timer = new Timer(true);
timer.scheduleAtFixedRate(dtpRunnable, 0, DELAY_TIME);
}
public void stopDtpRunnable() {
if (dtpRunnable != null) {
dtpRunnable.cancel();
dtpRunnable = null;
}
}
public void setTextPixels(boolean[][] textPixels) {
this.textPixels = textPixels;
if (textPixels.length < getMarqueeWidth()) {
this.marqueePixels = copyCharacterPixels(0, textPixels,
marqueePixels);
} else {
startDtpRunnable();
}
}
public void resetPixels() {
for (int i = 0; i < getMarqueeWidth(); i++) {
for (int j = 0; j < getMarqueeHeight(); j++) {
marqueePixels[i][j] = false;
}
}
}
public void setAllPixels() {
for (int i = 0; i < getMarqueeWidth(); i++) {
for (int j = 0; j < getMarqueeHeight(); j++) {
marqueePixels[i][j] = true;
}
}
}
public boolean[][] copyCharacterPixels(int position,
boolean[][] characterPixels, boolean[][] textPixels) {
for (int i = 0; i < characterPixels.length; i++) {
for (int j = 0; j < characterPixels[i].length; j++) {
textPixels[i + position][j] = characterPixels[i][j];
}
}
return textPixels;
}
public void copyTextPixels(int position) {
for (int i = 0; i < marqueePixels.length; i++) {
int k = i + position;
k %= textPixels.length;
for (int j = 0; j < textPixels[i].length; j++) {
marqueePixels[i][j] = textPixels[k][j];
}
}
}
}
I don't think it's possible to do more than that using only Java.
To be honest, the Marquee example is the smoothest i saw.

Keep getting Error: The method getImg() is undefined for the type javax.swing.ImageIcon

My friend code this brick breaker game rip off, but he keep getting this error
"Error: The method getImg() is undefined for the type javax.swing.ImageIcon" and I don't know how to fix it. Can someone help?
here the code:
public class Ball extends Sprite implements iFace{
private int xDir;
private int yDir;
public Ball(){
xDir = 1;
yDir = -1;
String path = "MAJOR_JAVA_ASSESSMENT/ball.png";
ImageIcon i = new ImageIcon(this.getClass().getResource(path));
img = i.getImg();
iWidth = img.getWidth(null);
iHeight = img.getHeight(null);
reset(); }
public void move()
{
x += xDir;
y += yDir;
if(x == 0)
{
setXDir(1);
}
if(x == width - iWidth)
{
setXDir(-1);
}
if (y == 0)
{
setYDir(1);
} }
private void reset()
{
x = initBallX;
y = initBallY;
}
public void setXDir(int x)
{
xDir = x;
}
public void setYDir(int y)
{
yDir = y;
}
public int getYDir()
{
return yDir;
}
}
here another code for you:
import java.awt.Color;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.RenderingHints;
import java.awt.Toolkit;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.util.Timer;
import java.util.TimerTask;
import javax.swing.JPanel;
public class Board extends JPanel implements iFace
{
private Timer timer;
private String message = "You lose";
private Ball ball;
private Paddle paddle;
private Brick bricks[];
private boolean inGame = true;
public Board()
{
initBoard();
}
private void initBoard()
{
addKeyListener(new timeAdapter());
setFocusable(true);
bricks = new Brick[numOfBricks];
setDoubleBuffered(true);
timer = new Timer();
timer.scheduleAtFixedRate(new ScheduleTask(), delay, period);
}
#Override
public void addNotify()
{
super.addNotify();
gameInit();
}
private void gameInit()
{
ball = new Ball();
paddle = new Paddle();
int k = 0;
for(int i = 0; i < 5; i++)
{
for(int j = 0; j < 6; j++)
{
bricks[k] = new Brick(j * 40 + 30, i * 10 + 50);
k++;
}
}
}
#Override
public void paintComponent(Graphics g)
{
super.paintComponent(g);
Graphics2D graphics2D = (Graphics2D) g;
graphics2D.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
graphics2D.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
if (inGame)
{
drawObj(graphics2D);
}
else
{
gameDone(graphics2D);
}
Toolkit.getDefaultToolkit().sync();
}
private void drawObj(Graphics graphics2D)
{
graphics2D.drawImage(ball.getImg(), ball.getX(), ball.getY(), ball.getWidth(), ball.getHeight(), this);
graphics2D.drawImage(paddle.getImg(), paddle.getX(), paddle.getY(), paddle.getWidth(), paddle.getHeight(), this);
for (int i = 0; i < numOfBricks; i++)
{
if(!bricks[i].isDead())
{
graphics2D.drawImage(bricks[i].getImg(), bricks[i].getX(), bricks[i].getY(), bricks[i].getWidth(), bricks[i].getHeight(), this);
}
}
}
private void gameDone(Graphics2D graphics2D)
{
Font f = new Font("TimesRoman", Font.BOLD, 20);
FontMetrics fm = this.getFontMetrics(f);
graphics2D.setColor(Color.RED);
graphics2D.setFont(f);
graphics2D.drawString(message, (iFace.width - fm.stringWidth(message)) / 2, iFace.width / 2);
}
private class timeAdapter extends KeyAdapter
{
#Override
public void keyReleased(KeyEvent p)
{
keyReleased(p);
}
#Override
public void keyPressed(KeyEvent p)
{
keyPressed(p);
}
}
private class ScheduleTask extends TimerTask
{
#Override
public void run()
{
ball.move();
paddle.move();
checkCollision();
repaint();
}
}
private void stopGame()
{
inGame = false;
timer.cancel();
}
private void checkCollision()
{
if(ball.getRectangle().getMaxY() > iFace.bottom)
{
stopGame();
}
for(int i = 0, j = 0; i < numOfBricks; i++)
{
if(bricks[i].isDead())
{
j++;
}
if(j == numOfBricks)
{
message = "GG YOU WIN";
stopGame();
}
}
if ((ball.getRectangle()).intersects(paddle.getRectangle()))
{
int paddlePos = (int) paddle.getRectangle().getMinX();
int ballPos = (int) ball.getRectangle().getMinX();
int first = paddlePos + 8;
int second = paddlePos + 16;
int third = paddlePos + 24;
int fourth = paddlePos + 32;
if (ballPos < first)
{
ball.setXDir(-1);
ball.setYDir(-1);
}
if (ballPos >= first && ballPos < second)
{
ball.setXDir(-1);
ball.setYDir(-1 + ball.getYDir());
}
if (ballPos >= second && ballPos < third)
{
ball.setXDir(0);
ball.setYDir(-1);
}
if (ballPos >= third && ballPos < fourth)
{
ball.setXDir(1);
ball.setYDir(-1);
}
}
for (int i = 0; i < numOfBricks; i++)
{
if ((ball.getRectangle()).intersects(bricks[i].getRectangle()))
{
int ballLeft = (int) ball.getRectangle().getMinX();
int ballHeight = (int) ball.getRectangle().getHeight();
int ballWidth = (int) ball.getRectangle().getWidth();
int ballTop = (int) ball.getRectangle().getMinY();
Point pR = new Point(ballLeft + ballWidth + 1, ballTop);
Point pL = new Point(ballLeft - 1, ballTop);
Point pT = new Point(ballLeft, ballTop - 1);
Point pB = new Point(ballLeft, ballTop + ballHeight + 1);
if (!bricks[i].isDead())
{
if (bricks[i].getRectangle().contains(pR))
{
ball.setXDir(-1);
}
else if
(bricks[i].getRectangle().contains(pL))
{
ball.setXDir(1);
}
if (bricks[i].getRectangle().contains(pT))
{
ball.setYDir(1);
}
else if (bricks[i].getRectangle().contains(pB))
{
ball.setYDir(-1);
}
bricks[i].setDeath(true);
}
}
}
}
}
i only give you the ball code as the two codes that have problems are basically have the same problem, so if someone can fix this one then, then we can the rest.
Also give you board as it the biggest and most important code, so this code may be the problem?
Thank you for reading this, and I hope you can help my friend.
getImg does not exist in the ImageIcon API, I think you'll find that it's ImageIcon#getImage instead
Having said that, I'd recommend using the ImageIO API instead

Changing the name of an output file corresponding to an integer

So, I am currently making a program in java, but I would like to make the program so that it changes the file output name corresponding to an integer in my class:
if(height > 10){
And here is my whole code
import java.awt.*;
public class imageReader {
private BufferedImage img, imageOut;
private int imageHeight, imageWidth;
private int deepbkg;
public imageReader() {
initializeSet();
readImage();
ProcessImage();
createOutImage();
saveProcFile();
}
public static void main(String[] args) {
#SuppressWarnings("unused")
imageReader iR= new imageReader();
}
public void initializeSet() {
Color cold = new Color(250, 100, 200);
deepbkg = cold.getRGB();
}
public void readImage(){
try{
img = ImageIO.read(new File("C:\\Users\\David_tmp\\Desktop\\ProjectImages\\LongIslandforPrint.jpg"));
}
catch (IOException e) {
}
}
public void ProcessImage(){
imageHeight = img.getHeight();
imageWidth = img.getWidth();
System.out.println(imageHeight+ " "+imageWidth );
}
public void createOutImage(){
imageOut = new BufferedImage(imageWidth, imageHeight, 1);
for (int imageX = 0; imageX < imageWidth; imageX++) {
for (int imageY = 0; imageY < imageHeight; imageY++){
int redbluevalue = img.getRGB(imageX, imageY);
double height = getHeight(redbluevalue);
// *****IMAGE HEIGHT****
if(height > 10){
//***END OF HEIGHT****
imageOut.setRGB(imageX, imageY, redbluevalue);
}
else{
imageOut.setRGB(imageX, imageY, deepbkg);
}
}
}
}
public void saveProcFile() {
try {
File outputfile = new File("C:\\Users\\David_tmp\\Desktop\\ProjectImages\\LongIslandforrrrPrint.jpg");
ImageIO.write(imageOut, "jpg", outputfile);
}
catch(IOException e) {
}
}
public double getHeight(int RGB) {
double heightX =0.;
Color tcol = new Color(RGB);
int Red = tcol.getRed();
int Blue = tcol.getBlue();
int Green = tcol.getGreen();
if( Red >248) {
heightX = 81.+ 99 * ((double)Red/250.);
}
if( Red <= 7 ) {
if(Green >= 249 ) // using blue value to calculate height
// range 23 -81
{
heightX = 23. + 58. *((double)255 - Blue)/255.;
}
else //using green to calculate height
{
heightX = 24.* ((double)Green-20.)/230.;
}
}
return heightX;
}
}
so I would like
LongIslandforrrrPrint.jpg
To change on the integer here:
if(height > 10){
So the file output would change corresponding to the integer
so the file name output would be like this
Heightmap[Integer].jpg
Something like this?
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import javax.imageio.ImageIO;
public class imageReader {
private BufferedImage img, imageOut;
private int imageHeight, imageWidth;
private int deepbkg;
public imageReader() {
initializeSet();
readImage();
ProcessImage();
createOutImage();
saveProcFile();
}
public static void main(String[] args) {
#SuppressWarnings("unused")
imageReader iR= new imageReader();
}
public void initializeSet() {
Color cold = new Color(250, 100, 200);
deepbkg = cold.getRGB();
}
public void readImage(){
try{
img =
ImageIO.read(new File("C:\\Users\\David_tmp\\Desktop\\ProjectImages\\LongIslandforPrint.jpg"));
}
catch (IOException e) {
}
}
public void ProcessImage(){
imageHeight = img.getHeight();
imageWidth = img.getWidth();
System.out.println(imageHeight+ " "+imageWidth );
}
public double myNum = 0;
public void createOutImage(){
imageOut = new BufferedImage(imageWidth, imageHeight, 1);
for (int imageX = 0; imageX < imageWidth; imageX++) {
for (int imageY = 0; imageY < imageHeight; imageY++){
int redbluevalue = img.getRGB(imageX, imageY);
double height = getHeight(redbluevalue);
// *****IMAGE HEIGHT****
if(height > 10){
//***END OF HEIGHT****
myNum = height;
imageOut.setRGB(imageX, imageY, redbluevalue);
}
else{
myNum = height;
imageOut.setRGB(imageX, imageY, deepbkg);
}
}
}
}
public void saveProcFile(){
try{
File outputfile = new File("C:\\Users\\David_tmp\\Desktop\\ProjectImages\\Heightmap["+ myNum + "].jpg");
ImageIO.write(imageOut, "jpg", outputfile);
}
catch(IOException e) {
}
}
public double getHeight(int RGB){
double heightX =0.;
Color tcol = new Color(RGB);
int Red = tcol.getRed();
int Blue = tcol.getBlue();
int Green = tcol.getGreen();
if( Red >248) {
heightX = 81.+ 99 * ((double)Red/250.);
}
if( Red <= 7 ){
if(Green >= 249 ) // using blue value to calculate height
// range 23 -81
{
heightX = 23. + 58. *((double)255 - Blue)/255.;
}
else //using green to calculate height
{
heightX = 24.* ((double)Green-20.)/230.;
}
}
return heightX;
}
}

The following code will not render

I believe the code will speak for itself, but in general the point of the code is the have a Map class that will take in an array of BufferedImages, x values, and y values, to compose a map of many layers (first layer being the BufferedImage array at 0, starting at the x value at 0 and the y value at 0, and so on). The main job of the map class, is to take each pixel of each image and convert them to Block Objects, which are just simply rectangles with a color (Includes a BufferedImage, because after it works, I will replace the color with the Image. Also includes an integer to specify which layer (1 being index 0) its allowed on with 0 meaning it can exist among all layers). In the end, when I call Render() on a Map object, the map object should do all the work in rendering the blocks into the correct positions. The largest problem with all of this is that I get no sytax or compiler errors, so my logic is what is messed up and I can not figure it out!
Thanks in advance, and if the question is confusing please tell me!
The Map Class:
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import java.util.ArrayList;
public class Map {
private int width;
private int height;
public int getWidth() { return width; }
public int getHeight() { return height; }
private int xPos;
private int yPos;
public int getX(int i)
{
return xPos;
}
public int getY(int i)
{
return yPos;
}
public void setPosition(int x, int y) { xPos = x; yPos = y; }
private int[] xStarts;
private int[] yStarts;
private ArrayList<BufferedImage> layersList = new ArrayList<BufferedImage>();
public void addLayer(BufferedImage image) { layersList.add(image); }
public void setLayer(int i, BufferedImage image) { layersList.set(i, image); }
private Block[][][] blocksArray;
private boolean beenInitialized = false;
public Map(BufferedImage[] images, int[] x, int[] y){
for (BufferedImage image : images){
layersList.add(image);
xStarts = x;
yStarts = y;
}
}
public void initialize(){
int widthMax = 0;
int heightMax = 0;
for (BufferedImage image : layersList){
if (image.getHeight() > heightMax) { heightMax = image.getHeight(); }
if (image.getWidth() > widthMax) { widthMax = image.getWidth(); }
}
width = widthMax;
height = heightMax;
blocksArray = new Block[layersList.size()][width][height];
for (int i = 0; i < layersList.size(); i++){
int currentLayer = i;
for (int y = 0; y < layersList.get(i).getHeight(); y++){
for (int x = 0; x < layersList.get(i).getWidth(); x++){
int colorCode = layersList.get(i).getRGB(x, y);
boolean error = true;
Block b = null;
for (int c = 0; c < Block.BLOCKS.size(); c++){
if (Block.BLOCKS.get(i).getColorCode() == colorCode && (Block.BLOCKS.get(i).getLayerCode() == currentLayer || Block.BLOCKS.get(i).getLayerCode() == 0)){
b = Block.BLOCKS.get(c);
error = false;
}
}
if (!error){
blocksArray[currentLayer][x][y] = b;
} else {
Block bb = new Block(false, colorCode);
bb.initialize();
blocksArray[currentLayer][x][y] = bb;
}
}
}
}
beenInitialized = true;
}
public void render(Graphics2D g2d){
if (beenInitialized){
for (int i = 0; i < layersList.size(); i++){
for (int y = yStarts[i]; y < layersList.get(i).getHeight() + yStarts[i]; y += Block.SIZE){
int currentY = 0;
for (int x = xStarts[i]; x < layersList.get(i).getWidth() + xStarts[i]; x += Block.SIZE){
int currentX = 0;
blocksArray[i][currentX][currentY].setPosition(x, y);
blocksArray[i][currentX][currentY].render(g2d);
currentX ++;
}
currentY++;
}
}
}
}
public void updatePosition(int x, int y){
xPos += x;
yPos += y;
}
}
The Block Class:
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.image.BufferedImage;
import java.util.ArrayList;
public class Block {
public static final int SIZE = 32;
public static final boolean DEBUG = true;
public static ArrayList<Block> BLOCKS = new ArrayList<Block>();
private Color debugColor;
public Color getColor() { return debugColor; }
public void setColor(Color color) { debugColor = color; }
private BufferedImage blockIcon;
public BufferedImage getIcon() { return blockIcon; }
public void setIcon(BufferedImage icon) { blockIcon = icon; }
private int xPos;
private int yPos;
public int getX() { return xPos; }
public int getY() { return yPos; }
public void setPosition(int x, int y) { xPos = x; yPos = y; }
private Rectangle blockShape;
public Rectangle getShape() { return blockShape; }
private int colorCode;
public int getColorCode() { return colorCode; }
private boolean colides;
public boolean doesColide() { return colides; }
private int layerCode;
public int getLayerCode() { return layerCode; }
private boolean beenInitialized = false;
public Block(boolean colides, int layerCode){
this.colides = colides;
this.layerCode = layerCode;
}
public void initialize(){
blockShape = new Rectangle(xPos, yPos, SIZE, SIZE);
int r = (colorCode >> 16) & 0x000000FF;
int g = (colorCode >> 8) & 0x000000FF;
int b = (colorCode) & 0x000000FF;
debugColor = new Color(r, g, b);
BLOCKS.add(this);
beenInitialized = true;
}
public void render(Graphics2D g2d){
if (beenInitialized){
if (DEBUG){
g2d.setColor(debugColor);
if (colides){
g2d.fill(blockShape);
} else {
g2d.draw(blockShape);
}
} else{
}
}
}
}
And finally the Game Class (I threw this together JUST to show a window for testing):
import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import javax.imageio.ImageIO;
import javax.swing.JFrame;
import javax.swing.JPanel;
public class Game extends JFrame{
public Game(){
super("Test");
try{
layer1 = ImageIO.read(getClass().getResourceAsStream("/layer1.png"));
layer2 = ImageIO.read(getClass().getResourceAsStream("/layer2.png"));
} catch (Exception ex) { ex.printStackTrace(); }
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setResizable(false);
setLayout(new BorderLayout());
add(new panel(), BorderLayout.CENTER);
pack();
setLocationRelativeTo(null);
setVisible(true);
}
public static void main(String[] args){
new Game();
}
private int[] xStartPositions = {0, 0};
private int[] yStartPositions = {0, 0};
private BufferedImage layer1;
private BufferedImage layer2;
private BufferedImage[] imageArray = {layer1, layer2};
private Map map;
public class panel extends JPanel{
public panel(){
setMinimumSize( new Dimension(1200, 675));
setMaximumSize( new Dimension(1200, 675));
setPreferredSize( new Dimension(1200, 675));
setVisible(true);
map = new Map(imageArray, xStartPositions, yStartPositions);
}
public void paint(Graphics g){
Graphics2D g2d = (Graphics2D) g;
map.render(g2d);
}
}
}
The initialize method of Map is never called, therefore Map will never render...
Some feedback...
Don't ever override paint, use paintComponent instead (it's very rare that you would need to override paint...
Make sure you are calling super.paintXxx - there's a lot of important working going on in the background that you don't want to miss or replicate...
Instead of extending from a top level container like JFrame, start by extending from JPanel and add this to a frame you create instead
Beware of static variables, this might cause you more problems ;)
You may also want to have a read through Initial Threads

Categories

Resources