Swing Graphics object not drawing over image in JLabel - java

I am creating a Java program that receives and plots GPS coordinates over a TCP socket connection on a 2D image of a map. The constructor creates the JFrame and graphical components and then starts off a SwingWorker thread to handle getting coordinates from the socket and then drawing an oval to represent the fix on the map.
I am able to always receive data over the connection but the program does not reliably draw points over the image.
Any suggestions and thank you!
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Image;
import java.io.File;
import java.util.List;
import javax.imageio.ImageIO;
import javax.swing.ImageIcon;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.SwingWorker;
import connectivity.TelitSocketServer;
import calculations.PlottingMath;
import objects.ElephantFix;
#SuppressWarnings("serial")
public class Gui
{
protected JFrame mainWindow;
protected JPanel mapArea = new JPanel();
private ReceiveFix rfix;
public Gui()
{
// TODO Auto-generated constructor stub
mainWindow = new JFrame();
// Load map image
Image map = null;
try
{
File mapImage = new File("map_images/AWE_PLOT.JPG");
map = ImageIO.read(mapImage);
} catch (Exception e)
{
System.out.println(e.toString());
}
JLabel label = new JLabel(new ImageIcon(map));
mapArea.add(label);
// Map Image Dimensions
mainWindow.getContentPane().add(mapArea, "Center");
mainWindow.setSize(471, 670);
mainWindow.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
mainWindow.setTitle("ElePlotter");
mainWindow.setVisible(true);
rfix = new ReceiveFix();
rfix.execute();
}
public static void main(String args[])
{
new Gui();
}
private class ReceiveFix extends SwingWorker<Void, ElephantFix>
{
#Override
protected Void doInBackground()
{
// Start the server
String fix = "";
TelitSocketServer currentConnection = new TelitSocketServer();
try
{
// Wait for client to connect
currentConnection.intializeConnection();
while (true)
{
// Parse and convert received GPS fix into arc radians
fix = currentConnection.readLine();
String[] split = fix.split(" ");
double latWholeDegrees = Double.parseDouble(split[0]
.substring(0, 3));
double longWholeDegrees = Double.parseDouble(split[1]
.substring(0, 3));
double latMinutes = Double.parseDouble(split[0]
.substring(3)) * .166667;
double longMinutes = Double.parseDouble(split[1]
.substring(3)) * .166667;
double lat = latWholeDegrees - latMinutes / 10;
double lon = longWholeDegrees + longMinutes / 10;
publish(new ElephantFix(lat, lon));
}
} catch (Exception e)
{
e.printStackTrace();
}
// Return null if somehow unable to publish node data
return null;
}
#Override
protected void process(List<ElephantFix> fixes)
{
int x, y;
// Get the most recently published node
ElephantFix aFix = fixes.get(fixes.size() - 1);
// Translate lat/long into map X/Y pixel
x = PlottingMath.getCurrentPixelX(aFix.getLatitude(),
aFix.getLongitude());
y = PlottingMath.getCurrentPixelY(aFix.getLatitude(),
aFix.getLongitude());
// Plot on image
Graphics g = mapArea.getGraphics();
g.setColor(Color.RED);
g.fillOval(x, y, 15, 15);
// mapArea.validate();
//
// mapArea.repaint();
//
// Gui.this.repaint();
}
}
}

mapArea.getGraphics(); is not how custom painting in Swing is done.
Start by taking a look at Performing Custom Painting for more details.
Adding a component to a container ensures that the child component will always be painted over the container...
JLabel label = new JLabel(new ImageIcon(map));
mapArea.add(label);
You have that backwards. It might be better to add both the label and mapArea to the same container
The next problem you will need to overcome is the fact that you will need to get your painting component to reside over the top of your label.
To this end you could...
Use a glass pane
Use a OverlayLayout
Use a GridBagLayout
Update with example
There are any number of ways to achieve this, you could use multiple components which act as layers onto which you could add other components or perform custom painting.
This is not as easy it as it sounds. Most layout managers don't consider two components sharing the same space. You also have to manage the connection between the layers, so if the map is centered within the lowest layer, but the available space is larger or smaller than the map, offsets begin to drift...messy...
A simpler solution would be to use a single component and render all the content onto it, for example...
import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import javax.imageio.ImageIO;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
public class TestMap {
public static void main(String[] args) {
new TestMap();
}
public TestMap() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
}
MapPane pane = new MapPane();
pane.dropPinAt(174, 147);
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLayout(new BorderLayout());
frame.add(pane);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class MapPane extends JPanel {
private BufferedImage img;
private BufferedImage pin;
private List<Point> pins;
public MapPane() {
pins = new ArrayList<>(25);
try {
img = ImageIO.read(getClass().getResource("/Map.jpg"));
pin = ImageIO.read(getClass().getResource("/Pin.png"));
} catch (IOException ex) {
ex.printStackTrace();
}
}
#Override
public Dimension getPreferredSize() {
return img == null ? new Dimension(200, 200) : new Dimension(img.getWidth(), img.getHeight());
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
if (img != null) {
Graphics2D g2d = (Graphics2D) g.create();
int x = (getWidth() - img.getWidth()) / 2;
int y = (getHeight() - img.getHeight()) / 2;
g2d.drawImage(img, x, y, this);
for (Point p : pins) {
// Offset the pin x/y
// to allow the point to land on the desired location
p.x -= 6;
p.y -= 64;
System.out.println(p);
g2d.drawImage(pin, p.x, p.y, this);
}
g2d.dispose();
}
}
protected void dropPinAt(int x, int y) {
pins.add(new Point(x, y));
}
}
}

You should not be drawing with a Graphics object obtained by calling getGraphics() on a Component. Doing this will give you a Graphics object that doesn't persist and an image that doesn't persist. Instead either draw on a BufferedImage with its Graphics object and display it in the GUI, or iterate through the data collection in the JPanel's paintComponent method and draw with the data obtained.

Related

Moving an image to the position of the cursor where is clicked on a JFrame

i need some help here with an exercise of my Java class. What i'm trying to do is that when i click on any part of the JFrame, an image moves closer to the pointer. Right now i have done the part that once i click on the JFrame the image is in the same position as the pointer but it does like it's "teleporting" and i'm trying to make it more like a constant movement to the pointer position.
So far this is my code i have atm:
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
public class ControlaRaton extends MouseAdapter {
JLabel label;
public ControlaRaton(JLabel label){
this.label = label;
}
public void mouseClicked(MouseEvent evt){
Point pos = evt.getPoint();
System.out.println(pos.x+" "+pos.y);
//System.out.println("Boton: "+evt.getButton());
label.setLocation(pos.x-20,pos.y-50);
}
}
Any ideas on how to do it that way? I was thinking maybe using a Thread but i don't know exactly how to implement it here :s
This is pretty simple approach, basically all this does is uses a Swing Timer to move an entity towards the last known click point
Have a look at
Concurrency in Swing
How to use Swing Timers
How to Write a Mouse Listener
for more details
Swing is NOT thread safe, when performing these types of operations, it's important to take this into consideration. The Swing API provides several ways to work threads, in this case, I've used a simple Timer as it generates updates at a regular interval and triggers updates from within the EDT, making it safe to update the UI from within
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.RenderingHints;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.geom.AffineTransform;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import javax.imageio.ImageIO;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.Timer;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
public class MoveTowards {
public static void main(String[] args) {
new MoveTowards();
}
public MoveTowards() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
ex.printStackTrace();
}
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(new MoveTowardsPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
class MoveTowardsPane extends JPanel {
private final BufferedImage image;
private Point imagePosition = new Point(150, 150);
private Point mousePoint;
private double imageAngleRad = 0;
public MoveTowardsPane() {
BufferedImage i = null;
try {
i = ImageIO.read(getClass().getResource("/sprite.png"));
} catch (IOException e) {
e.printStackTrace();
}
image = i;
addMouseListener(new MouseAdapter() {
#Override
public void mouseClicked(MouseEvent e) {
mousePoint = e.getPoint();
double dx = e.getX() - imagePosition.getX();
double dy = e.getY() - imagePosition.getY();
imageAngleRad = Math.atan2(dy, dx);
repaint();
}
});
Timer timer = new Timer(40, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
if (mousePoint != null) {
int centerX = imagePosition.x + (image.getWidth() / 2);
int centerY = imagePosition.y + (image.getHeight() / 2);
if (mousePoint.x != centerX) {
imagePosition.x += mousePoint.x < centerX ? -1 : 1;
}
if (mousePoint.y != centerY) {
imagePosition.y += mousePoint.y < centerY ? -1 : 1;
}
repaint();
}
}
});
timer.start();
}
#Override
public Dimension getPreferredSize() {
return new Dimension(400, 400);
}
#Override
protected void paintComponent(Graphics gr) {
super.paintComponent(gr);
Graphics2D g = (Graphics2D) gr.create();
g.setRenderingHint(
RenderingHints.KEY_RENDERING,
RenderingHints.VALUE_RENDER_QUALITY);
int cx = image.getWidth() / 2;
int cy = image.getHeight() / 2;
AffineTransform oldAT = g.getTransform();
g.translate(cx + imagePosition.x, cy + imagePosition.y);
g.rotate(imageAngleRad);
g.translate(-cx, -cy);
g.drawImage(image, 0, 0, null);
g.setTransform(oldAT);
g.dispose();
}
}
}
Why doesn't this use a JLabel? A lot of reasons, JLabel isn't really well fitted for the task, as it needs to take into account a lot of other information. This example also "turns" the sprite towards the click point, something which isn't easily achieved with a JLabel.
In principle, the theory is still the same for moving a component.
See
Painting in AWT and Swing
Performing Custom Painting
for more details about how this approach works
Redraw your circle on mouse move
void mouseMoved(MouseEvent evt){
Point pos = evt.getPoint();
System.out.println(pos.x+" "+pos.y);
//System.out.println("Boton: "+evt.getButton());
label.setLocation(pos.x-20,pos.y-50);
}
});
If you want to move the label just a bit with each click you could do the following:
public void mouseClicked(MouseEvent evt){
Point clickedPos = evt.getPoint();
Point newPosForLabel = calculateNewPos(clickedPos, labelPos);
label.setLocation(newPosForLabel);
}
private Point calculateNewPos(Point clickedPos, Point labelPos) {
// calculate newPos based on labelPos and clickedPos
return newPos;
}
else use the timer from Hannes or from the link given by MadProgrammer:

PaintComponent is not being called-Java

My code is supposed to draw a random sized image three times in random locations. For some reason, when I run this code using BlueJ, all that shows up is a gray screen. I think it is because PaintComponent isn't being called, but I am not quite sure. What went wrong with my code and how can I fix it?
class PanelHolder extends JPanel{//class PanelHolder extends JPanel
//variables
public boolean threesharks;
public int xcoord;
public int ycoord;
public int ratio;
public Image i;
public int w;
public int h;
public boolean background=true;
Color water = new Color(136, 180, 231);
public PanelHolder(){//constructor
i = Toolkit.getDefaultToolkit().getImage("$harkz.png");
}
public void randomxy(){
for(int x=0;x<3;x++){
threesharks=true;
ycoord=(int)(Math.random()*300+200);//use math.random to figure out coordinates and sizing
xcoord=(int)(Math.random()*1000+0);//make a loop
ratio=(int)(Math.random()*5+1);
w=ratio*523;
h=ratio*195;
repaint();
System.out.println("I'm in randomxy");
//call repaint() each time
//after three times, make threesharks=false
}
threesharks=false;
}
public void paintComponent(Graphics g){
if(threesharks){
setBackground(water);
System.out.print("hi!");
if(background){
super.paintComponent(g);//set backgroun
background=false;
}
g.drawImage(i, xcoord, ycoord, w, h, this);
}
}
}
You seem to have a misunderstanding with how painting works in Swing. Swing will call your paintComponent when ever it thinks your component needs to be repainted, this might occur for many different reasons, many of which you don't have control over.
Painting in Swing is destructive, that is, each time your paintComponent method is called, you are expected to repaint the entire state of the component from scratch, painting is not accumalitive.
This means that you need to store the state of things you want to paint in some meaningful manner and re-use these values are needed.
Have a look at Painting in AWT and Swing and Performing Custom Painting for more details about how painting works in Swing
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Image;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import javax.imageio.ImageIO;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
public class Test {
public static void main(String[] args) {
new Test();
}
public Test() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
ex.printStackTrace();
}
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(new PanelHolder());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
class PanelHolder extends JPanel {//class PanelHolder extends JPanel
//variables
public boolean threesharks;
public BufferedImage i;
public boolean background = true;
Color water = new Color(136, 180, 231);
private Point[] points;
private Image[] images;
public PanelHolder() {
//constructor
try {
i = ImageIO.read(...);
} catch (IOException ex) {
ex.printStackTrace();
}
randomxy();
}
#Override
public Dimension getPreferredSize() {
return new Dimension(800, 800);
}
public void randomxy() {
points = new Point[3];
images = new Image[3];
for (int x = 0; x < 3; x++) {
points[x] = new Point();
double ratio = (Math.random() * 6d) + 0.1d;
int width = (int) (ratio * i.getWidth());
int height = (int) (ratio * i.getHeight());
points[x].y = Math.max(0, (int) (Math.random() * 800) - height);//use math.random to figure out coordinates and sizing
points[x].x = Math.max(0, (int) (Math.random() * 800) - width);//make a loop
images[x] = i.getScaledInstance(width, height, Image.SCALE_SMOOTH);
}
}
public void paintComponent(Graphics g) {
super.paintComponent(g);//set backgroun
g.setColor(water);
g.fillRect(0, 0, getWidth(), getHeight());
if (points != null && images != null) {
for (int index = 0; index < points.length; index++) {
g.drawImage(images[index], points[index].x, points[index].y, this);
}
}
}
}
}
This is a rough example, which uses Image#getScaledInstance which is not generally recommended, but works for the example.
Have a look at The Perils of Image.getScaledInstance() for more details
Have a look at Quality of Image after resize very low -- Java and Java: maintaining aspect ratio of JPanel background image for possible (scaling) alternatives
I'd also have a look at Reading/Loading an Image for a better mechanism for loading images

Drawing an image on top of an image in a JComponent erases part of the bottom image

I am making a 2d game and I need to draw an image on top of another. After I draw the first image(the larger one, jpg), the second image(the smaller one,png) erases from where the second image is to the lower right hand corner. Like this:
I have looked into this a bit, and it was suggested that I use buffered images, so I did that with both images and the problem remains. Here is one post I looked at: How to draw an image over another image?. I have also seen some people suggesting graphics2d, though I did not really understand the reason to use them or how to use them. I am new to java graphics and images, so it is probably a silly mistake.
Here is my code. Thank you.
import java.awt.*;
import java.awt.event.*;
import java.net.*;
import javax.swing.*;
import java.util.ArrayList;
import java.awt.image.BufferedImage;
import javax.imageio.ImageIO;
import java.io.IOException;
public class DisplayExample extends JComponent
{
private BufferedImage backgroundImage;
private String backgroundName;
private BufferedImage image; //image to draw
private int imageX; //position of left edge of image
private int imageY; //position of top edge of image
private JFrame frame;
public static void main(String[] args)
{
DisplayExample example = new DisplayExample();
example.run();
}
public DisplayExample()
{
imageX = 200;
imageY = 200;
backgroundName = "backgroundShip.jpg";
URL backgroundURL = getClass().getResource(backgroundName);
if (backgroundURL == null)
throw new RuntimeException("Unable to load: " + backgroundName);
try{backgroundImage = ImageIO.read(backgroundURL);}catch(IOException ioe){}
//load image
String fileName = "explosion.png";
URL url = getClass().getResource(fileName);
if (url == null)
throw new RuntimeException("Unable to load: " + fileName);
//image = new ImageIcon(url).getImage();
try{image = ImageIO.read(url);}catch(IOException ioe){}
System.out.println(image instanceof BufferedImage);
setPreferredSize(new Dimension(1040,500)); //set size of drawing region
//need for keyboard input
setFocusable(true); //indicates that WorldDisp can process key presses
frame = new JFrame();
frame.getContentPane().add(this);
frame.pack();
frame.setVisible(true);
}
public void paintComponent(Graphics g)
{
super.paintComponent(g);
if(backgroundImage != null)
g.drawImage(backgroundImage,0,0,getWidth(), getHeight(), null);
g.drawImage(image, imageX, imageY, this);
}
public void run()
{
while(true)
{
imageY+=1;
repaint();
try{Thread.sleep(100);}catch(Exception e){}
}
}
}
So I took your code, added my own images and it runs fine for me.
Having said that, there are some areas you can improve:
You're running the risk of either blocking the Event Dispatching Thread or introducing a thread race condition into your code with your run method. You should consider using a Swing Timer instead. See How to use Swing Timers for more details. This allows you to schedule regular callbacks which are called within the context of the EDT, making it safer to update the context of the UI
You should only ever create or modify the state of the UI from within the context of the EDT, Swing is not thread safe. See Initial Threads for more details. Swing has been known to have "issues" when the UI is not initialised within the EDT
Scaling an image is expensive, you should avoid doing so from within the paint methods, instead, scale the image and keep a reference to the result and use it when you need to paint it.
You should consider using the key bindings API over KeyListener, it will solve many of the issues associated with using KeyListener. See How to Use Key Bindings for more details.
For example...
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import javax.imageio.ImageIO;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.Timer;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
public class DisplayExample extends JComponent {
private BufferedImage backgroundImage;
private String backgroundName;
private BufferedImage image; //image to draw
private int imageX; //position of left edge of image
private int imageY; //position of top edge of image
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
ex.printStackTrace();
}
DisplayExample example = new DisplayExample();
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(example);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public DisplayExample() {
imageX = 200;
imageY = 200;
try {
backgroundImage = ImageIO.read(new File("..."));
} catch (IOException ioe) {
ioe.printStackTrace();
}
//load image
try {
image = ImageIO.read(new File("..."));
} catch (IOException ioe) {
ioe.printStackTrace();
}
//need for keyboard input
//setFocusable(true); //indicates that WorldDisp can process key presses
// Use the key bindings API instead, causes less issues
Timer timer = new Timer(100, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
imageY += 1;
repaint();
}
});
timer.start();
}
#Override
public Dimension getPreferredSize() {
return backgroundImage == null ? new Dimension(200, 200) : new Dimension(backgroundImage.getWidth(), backgroundImage.getHeight());
}
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
if (backgroundImage != null) {
// Scaling is expensive, don't do it here
int x = (getWidth() - backgroundImage.getWidth()) / 2;
int y = (getHeight() - backgroundImage.getHeight()) / 2;
g2d.drawImage(backgroundImage, x, y, this);
}
g2d.drawImage(image, imageX, imageY, this);
g2d.dispose();
}
}

Graphics2D and Jpanel Query: Easier Way?

Is there an easier way to code my program such that I can draw my tile-based map onto a Panel (of some sort), such that the map wont redraw each time I resize the window (with resizable off)? I realize that that is great for debugging and testing my mapDrawing function, but, I also don't think I'm doing it ideally, or even in a smart way at all.
My code is as follows.. if you need my subclasses for some reason, I can edit those in too.
import java.awt.*;
import javax.swing.*;
public class AhnkorMyst extends JPanel { // main game class
static final int screenWidth = 760;
static final int screenHeight = 760;
public void paintComponent(Graphics g) {
super.paintComponent(g); // paint background
setBackground(Color.BLACK);
Graphics2D g2d = (Graphics2D) g;
Map newMap = new Map(g2d, screenWidth, screenHeight);
newMap.generateBaseMap();
newMap.populateSurroundings();
newMap.quadSmoothingIteration ();
int i, j;
for (j = 0; j < (newMap.mapHeight / 20); j++) {
for (i = 0; i < (newMap.mapWidth / 20); i++) {
newMap.mainMap[i][j].paint();
}
}
}
public static void main (String[] args) {
AhnkorMyst game = new AhnkorMyst();
JFrame frame = new JFrame("Ahnkor Myst");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(game);
frame.setSize(screenWidth + 10, screenHeight + 30);
frame.setLocationRelativeTo(null);
frame.setVisible(true);
frame.setResizable(false);
}
}
edit** my Map is randomly generated with the generateBaseMap () function.
This is "very" basic example of the concept. Basically, this re-builds the BufferedImage which represents the basic "view" of the map every time the JPanel is invalidated.
You should note, that I simple randomise the map each time it is built, presumably, you will be using some kind of virtual structure which defines the map itself and would use this to build the map instead...
import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.util.Random;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.imageio.ImageIO;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
public class TestTiles {
public static void main(String[] args) {
new TestTiles();
}
public TestTiles() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
}
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLayout(new BorderLayout());
frame.add(new TileMap());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class TileMap extends JPanel {
private int tileColumns = 8;
private int tileRows = 8;
private BufferedImage tileSheet;
private BufferedImage tileMap;
public TileMap() {
try {
tileSheet = ImageIO.read(getClass().getResource("/TileSet.png"));
} catch (IOException ex) {
ex.printStackTrace();
}
}
#Override
public Dimension getPreferredSize() {
return new Dimension(200, 200);
}
#Override
public void invalidate() {
tileMap = null;
super.invalidate();
}
protected void buildMap() {
tileMap = new BufferedImage(getWidth(), getHeight(), BufferedImage.TYPE_INT_RGB);
Graphics2D g2d = tileMap.createGraphics();
int tileWidth = tileSheet.getWidth() / tileColumns;
int tileHeight = tileSheet.getHeight() / tileRows;
Random random = new Random();
for (int x = 0; x < getWidth(); x += tileWidth) {
for (int y = 0; y < getHeight(); y += tileHeight) {
int xCell = random.nextInt(tileColumns - 1) * tileWidth;
int yCell = random.nextInt(tileRows - 1) * tileHeight;
BufferedImage tile = tileSheet.getSubimage(xCell, yCell, tileWidth, tileHeight);
g2d.drawImage(tile, x, y, this);
}
}
g2d.dispose();
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
if (tileSheet != null) {
Graphics2D g2d = (Graphics2D) g.create();
if (tileMap == null) {
buildMap();
}
g2d.drawImage(tileMap, 0, 0, this);
g2d.dispose();
}
}
}
}
You could take this concept further and pre-generate the entire world into a single BufferedImage and use getSubImage to grab a smaller portion which what you want to display. This starts to form the basic concept of scrolling, as you could maintain a virtual position in the world and calculate what portion of the map would need to be shown to represent it...
Avoid lengthy calculations and instantiations in your implementation of paintComponent(). You can get an idea of the available rendering budget on your target platform using the approach shown in this AnimationTest. Instead, pre-compute as much as possible. In this tile example, the ground map is entirely static, and the rendering is handled by paintIcon(). A related example is examined here.

Image animation not getting displayed

I have the following code.I want to display the images in my chick array on the screen after some time delay.The coordinates have to be shifted by 10px each x direction when the new
image is loaded.I would like to know what additional code snippets can i add in this code to have an animation on my Frame with the two images i have .Below is my code:
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Frame;
import java.awt.Graphics;
import java.awt.Image;
import java.awt.Toolkit;
public class Chicken extends Frame implements Runnable{
Thread animation;
long frameDelay = 3000;
Image chick[] = new Image[2];
int numFrames = chick.length;
Toolkit tk = getToolkit();
public Chicken()
{
setSize(new Dimension(300,300));
setVisible(true);
setBackground(Color.BLACK);
animation = new Thread(this);
chick[0] = tk.createImage("stand.png");
chick[1] = tk.createImage("walk.png");
animation.start();
//setVisible(false);
}
public void paint(Graphics g)
{
g.drawImage(chick[0],100,100,null );
}
#Override
public void run() {
// TODO Auto-generated method stub
repaint();
try {
Thread.sleep(frameDelay);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
public static void main(String args[])
{
Chicken instance = new Chicken();
}
}
So, the first problem you have is a resource issue.
Resources stored "within" the application (typically known as embedded resources) can't be loaded like external resources.
chick[0] = tk.createImage("stand.png");
Is expecting a file "./stand.png" which doesn't exist. Instead, you need to load the resource through the Class#getResource API...
chick[0] = tk.createImage(getClass().getResource("/stand.png"));
The second problem you will face is the fact that you are overriding paint of a top level container. This really shouldn't be done. Let's start with the fact it's not double buffered and end with the fact that frames have decorations which sit inside the viewable area. This means that the decorations will overlap what ever you paint to the surface...not pretty...
The third problem is you are not telling the image where it should move to. It's static.
You need some kind of x/y value that tells the image where it should be painted. You would modify these values by a given x/y delta within you thread before you called repaint...
The forth problem you might have is the fact that you are using AWT...which is kind of dated. Swing would solve your double buffering issue for you...IMHO, would make a better choice - there's a lot more documentation and examples on Swing laying around now days ;)
While I'm on my hobble horse...I would, personally, recommend ImageIO over Toolkit#createImage or ImageIcon, mostly because it supports more formats, but also because it will throw an Exception when the image can't be read for some reason...
I have a simple example if Swing, but I won't post it, because I'll get in trouble for running of topic...let me know if you would like to see it
Updated with Swing example
This uses a embedded image in the default package...
import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.imageio.ImageIO;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.Timer;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
public class ChickenDance {
public static void main(String[] args) {
new ChickenDance();
}
public ChickenDance() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
}
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLayout(new BorderLayout());
frame.add(new TestPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class TestPane extends JPanel {
private BufferedImage chicken;
private int xPos;
private int yPos;
private int xDelta = 4;
public TestPane() {
try {
chicken = ImageIO.read(getClass().getResource("/Chicken.png"));
} catch (IOException ex) {
Logger.getLogger(ChickenDance.class.getName()).log(Level.SEVERE, null, ex);
}
Timer timer = new Timer(40, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
xPos += xDelta;
if (xPos + chicken.getWidth() > getWidth()) {
xPos = getWidth() - chicken.getWidth();
xDelta *= -1;
} else if (xPos < 0) {
xPos = 0;
xDelta *= -1;
}
yPos = (getHeight() - chicken.getHeight()) / 2;
repaint();
}
});
if (chicken != null) {
timer.start();
}
}
#Override
public Dimension getPreferredSize() {
return new Dimension(400, 400);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
if (chicken != null) {
Graphics2D g2d = (Graphics2D) g.create();
g2d.drawImage(chicken, xPos, yPos, this);
g2d.dispose();
}
}
}
}

Categories

Resources