Okay, so here's the deal: I'm trying to simply draw an object (in this case a tornado) onto my main canvas. I'm using JFrames, a canvas, a buffer strategy, and a buffered image to draw on. As far as I can figure I'm using the "game loop" correctly and from what I've been previously able to find my order of operations in the rendering is correct. Is there some thing I'm missing about the nature of using buffer strategies perhaps? I can't tell why i get just a grey screen.
Basically I'm trying to get this tornado program working using as many "proper" graphical and coding techniques as possible. Ultimately all I want is a little city that has a tornado moving through it, with each component existing as an object (the tornado, buildings, people, etc). However I find myself unable to continue util I can actually draw the blasted! The code below should show you whatever else you need to know, I'm fairly new to programming and this is a high school project, so any other pointers are welcome but mostly I want to know why the tornado wont draw!
I'm using eclipse, by the way. and so far I've followed these toutorials and posts to get where I am now:
http://www.gamedev.net/page/resources/_/technical/general-programming/java-games-active-rendering-r2418
http://examples.javacodegeeks.com/desktop-java/awt/image/drawing-on-a-buffered-image/
This is the main class:
import java.awt.Canvas;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GraphicsConfiguration;
import java.awt.GraphicsDevice;
import java.awt.GraphicsEnvironment;
import java.awt.Transparency;
import java.awt.image.BufferStrategy;
import java.awt.image.BufferedImage;
import javax.swing.*;
public class Main {
static BufferStrategy BuffStrat;
static Thread t1;
static BufferedImage backbuff;
static JFrame mainframe;
static Tornado tornado;
public static void main(String[] args) {
Simulation();
}
public static void Setup() {
mainframe = new JFrame("Tornado Ally");
mainframe.setIgnoreRepaint(true);
mainframe.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
Canvas maincanvas = new Canvas();
maincanvas.setIgnoreRepaint(true);
maincanvas.setSize(750, 600);
mainframe.add(maincanvas);
mainframe.pack();
mainframe.setVisible(true);
maincanvas.createBufferStrategy(2);
BuffStrat = maincanvas.getBufferStrategy();
GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
GraphicsDevice gd = ge.getDefaultScreenDevice();
GraphicsConfiguration gc = gd.getDefaultConfiguration();
backbuff = gc.createCompatibleImage(750, 600, Transparency.BITMASK);
tornado = new Tornado (0, 0, 0, 0, 0, backbuff);
}
public static void Simulation() {
Setup();
while (true) {
Render();
delay(10);
}
}
public static void Render() {
Graphics2D g = null;
g = backbuff.createGraphics();
g.setBackground(Color.LIGHT_GRAY);
g.clearRect(0, 0, 750, 600);
tornado.drawTornado(g);
Graphics gI = BuffStrat.getDrawGraphics();
gI.drawImage(backbuff, 0, 0, null);
BuffStrat.show();
gI.dispose();
g.dispose();
}
public static void delay(int ms) {
try {
Thread.sleep(ms);
} catch (InterruptedException e) {
}
}
}
This is the Tornado Object:
import java.awt.Dimension;
import java.awt.Graphics2D;
import java.awt.GraphicsConfiguration;
import java.awt.Image;
import java.awt.geom.Dimension2D;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import javax.imageio.ImageIO;
public class Tornado {
int x, y, z;
int mag;
double velocity;
Dimension2D hitbox;
GraphicsConfiguration gc;
Image t;
BufferedImage backbuff;
public Tornado(int x, int y, int z, int mag, double velocity, BufferedImage backbuff) {
this.x = x;
this.y = y;
this.z = z;
this.mag = mag;
this.velocity = velocity;
this.backbuff = backbuff;
{
try {
t = ImageIO.read(new File("Sprites.Tornado/TornadoFull.png"));
} catch (IOException e) {
}
}
}
public void drawTornado(Graphics2D g) {
g.drawImage(t, 0, 0, null);
}
}
When testing your code, it throw an exception because your code attempted to construct a BufferStrategy before the window had being resized. Because the main window is using a LayoutManager a better solution would be to use something like...
//maincanvas.setSize(750, 600);
maincanvas.setPreferredSize(new Dimension(750, 600));
When you call pack, the window will be resized to accommodate the preferred size of it's child components...
You are also ignoring your exceptions when you load you image, so you won't even know if it hadn't loaded for some reason. At the very least, you should be logging any exceptions...
For example...
try {
t = ImageIO.read(new File("TSprites.Tornado/TornadoFull.png"));
} catch (IOException e) {
e.printStackTrace();
}
This code suggests that the image is an external resource, if not (and it's built into the application), you would need to use getClass().getResource("/TSprites.Tornado/TornadoFull.png") instead of new File(...)...
You should also consider constructing your UI within the context of the Event Dispatching Thread, for details, take a look at Initial Threads.
For example, I changed your main method to look more like...
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
Simulation();
}
});
And updated your Simulation method to execute the main loop within the context of another thread, for example...
public static void Simulation() {
Setup();
new Thread(new Runnable() {
#Override
public void run() {
while (true) {
Render();
delay(10);
}
}
}).start();
}
You may also like to take a look at Code Conventions for the Java Programming Language as it will make you code easier to read by others
Also, beware of the over use of static. This can cause more problems then they solve if not used correctly...
Also, try and avoid the use of magic numbers, instead, use values you know, for example...
Instead of...
g.clearRect(0, 0, 750, 600);
Try using...
g.clearRect(0, 0, backbuff.getWidth(), backbuff.getHeight());
Related
when I call a repaint method its flickering, I was searching on the internet about doubleBuffered but its stil flickering all of objects on the same time,
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.io.*;
import java.util.ArrayList;
import java.util.Random;
public class GuiGame extends JFrame implements ActionListener {
private final Flower flower;
private final ArrayList<MyObjects> objekty;
private Image dbImage;
private Graphics dbGraphics;
public GuiGame() {
this.setFocusable(true);
Timer timer = new Timer(40, this);
timer.start();
this.setVisible(true);
/*
public void paint(Graphics g) {
this.dbImage = this.createImage(this.getWidth(), this.getHeight());
this.dbGraphics = this.dbImage.getGraphics();
this.paintComponents(g);
g.drawImage(this.dbImage, 0 ,0, this);
}
*/
public void paint(Graphics g) {
//when i use doubleBuffering this method was called paintComponents
g.drawImage(background.getImage(), 0, 0, this);
g.drawImage(flower.getImage(), flower.getPozX(), flower.getPozY(), this);
String skore = "Score: " + player.getScore() ;
g.drawString(skore, 20, 50);
this.paintObjects(g);
}
public void paintObjects(Graphics g) {
if (this.objekty != null) {
for (objekty o : this.objekty) {
o.move();
g.drawImage(o.getImage(), o.getPozX(), o.getPozY(), this);
}
}
}
when I used doubleBuffering I tried to slow down a timer e.g. to 1000, it was blank page for most of the time and my objects was there only for moment.
when I do not used it, only last objects which i draw were flickering.
how can I avoid?
I would recommend you start by taking a look at Painting in AWT and Swing and Performing Custom Painting.
Swing components, when used correctly, are double buffered by default.
Painting is very complex, there's a lot of work that goes into a paint pass. When you override a "paint" method, you either need to honour the existing workflow (ie, call the super.paintXxx method) or be prepared to take over the full responsibility of the method. Your code has circumvented whole sections of the paint workflow, this is never a good idea.
It's not generally recommend that you extend from top level containers like JFrame, you're not adding any new functionality and they are generally complex components to start with. JFrame for example, is actually a composite component, that is, it has a number of child components added to it that form it's core functionality
By overriding paint (of JFrame), you start competing with these components, and because a child component can be painted without the parent been involved, this can lead to any number of issues.
Instead, start with a JPanel and override it's paintComponent method, this guarantees that you will be operating in a double buffered workflow.
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;
public class Main {
public static void main(String[] args) {
new Main();
}
public Main() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
JFrame frame = new JFrame();
frame.add(new MainPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
} catch (IOException ex) {
Logger.getLogger(Main.class.getName()).log(Level.SEVERE, null, ex);
}
}
});
}
public class MainPane extends JPanel {
private int xDelta = 1;
private int xPos = 0;
private BufferedImage ufo;
public MainPane() throws IOException {
ufo = ImageIO.read(getClass().getResource("/images/ufo.png"));
Timer timer = new Timer(5, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
xPos += xDelta;
if (xPos + ufo.getWidth() > getWidth()) {
xPos = getWidth() - ufo.getWidth();
xDelta *= -1;
} else if (xPos < 0) {
xPos = 0;
xDelta *= -1;
}
repaint();
}
});
timer.start();
}
#Override
public Dimension getPreferredSize() {
return new Dimension(400, 400);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
paintUFO(g2d);
g2d.dispose();
}
protected void paintUFO(Graphics2D g2d) {
int y = (getHeight() - ufo.getHeight()) / 2;
g2d.drawImage(ufo, xPos, y, this);
}
}
}
So, for a personal project, I've been trying to create a program in Java that can make a rudimentary animation appear on a Swing application. As far as I know, I've done everything right, and as far as I know it's working, but when I run the application, it does not let me close the application without Task Manager, and when I force quit the app IntelliJ tells me "process finished with exit code 1". It's also not displaying my animation on the screen despite displaying normal Graphics things such as lines.
Here is my JFrame code:
package animtest;
import javax.swing.*;
public class AnimTest extends JFrame {
public void addComponents() {
AnimPanel panel = new AnimPanel();
setContentPane(panel);
}
public AnimTest(String string) {
super(string);
}
public static void main(String[] args) {
AnimTest frame = new AnimTest("Animation Test");
frame.addComponents();
frame.setExtendedState(JFrame.MAXIMIZED_BOTH);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
}
}
Here is my JPanel code:
package animtest;
import javax.imageio.ImageIO;
import javax.swing.*;
import java.awt.*;
import java.io.File;
import java.io.IOException;
import java.util.concurrent.TimeUnit;
public class AnimPanel extends JPanel {
Image[] marsExploding = new Image[3];
public AnimPanel() {
try {
marsExploding[0] = ImageIO.read(new File("res/MarsExploding.png"));
marsExploding[1] = ImageIO.read(new File("res/MarsExploding2.png"));
marsExploding[2] = ImageIO.read(new File("res/MarsExploding3.png"));
} catch (IOException ex) {
ex.printStackTrace();
}
}
#Override
public void paintComponent(Graphics g2) {
Graphics2D g = (Graphics2D) g2.create();
g.setColor(Color.white);
for (int i = 0; i < marsExploding.length; i++) {
g.drawImage(marsExploding[i], (getWidth() / 2) - 128, (getHeight() / 2) - 128, 256, 256, null);
try {
TimeUnit.SECONDS.sleep(500);
} catch (InterruptedException ex) {
Thread.currentThread().interrupt();
}
g.fillRect(0, 0, getWidth(), getHeight());
}
g.dispose();
repaint();
}
}
Any help is greatly appreciated, thank you!
EDIT 1
OK, so this is my new panel code, which should honor Swing's contract and concurrency:
package animtest;
import javax.imageio.ImageIO;
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.File;
import java.io.IOException;
public class AnimPanel extends JPanel {
Image[] marsExploding = new Image[3];
Graphics2D g;
int currentFrame = 0;
public AnimPanel() {
try {
marsExploding[0] = ImageIO.read(new File("res/MarsExploding.png"));
marsExploding[1] = ImageIO.read(new File("res/MarsExploding2.png"));
marsExploding[2] = ImageIO.read(new File("res/MarsExploding3.png"));
} catch (IOException ex) {
ex.printStackTrace();
}
Timer timer = new Timer(500, null);
timer.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
displayNewFrame(currentFrame);
if (currentFrame < 2) {
currentFrame++;
} else {
currentFrame = 0;
}
}
});
timer.start();
}
#Override
public void paintComponent(Graphics g2) {
super.paintComponent(g2);
g = (Graphics2D) g2.create();
}
public void displayNewFrame(int frame) {
g.fillRect(0, 0, getWidth(), getHeight());
g.drawImage(marsExploding[frame], (getWidth() / 2) - 128, (getHeight() / 2) - 128, 256, 256, null);
}
}
However this doesn't actually display anything to the screen.
Swing is a single threaded framework, this means, that any call which is long running or blocking made from within the context of the Event Dispatching Thread will prevent the UI from been updated or allow the user to interact with it, making your UI appear as if it's hung (because it essentially has).
See Concurrency in Swing for more details.
Instead of trying to use TimeUnit.SECONDS.sleep(500); inside a paint method, you should have some background thread which ticks at a regular interval and allows you to update the UI accordingly. The problem is, Swing is also not thread safe, meaning that you should never try and create or update the UI from outside the context of the Event Dispatching Thread.
For a basic solution, you can, however, use a Swing Timer, which will trigger a ActionListener on a regular bases within the context of the EDT, making it safe to use with the UI.
See How to use Swing Timers for more details.
Painting in Swing is performed by a series of chained method calls, custom painting requires you to insert your code within on of these links, paintComponent been the most preferred.
However, you are expected to honor the contract of these links by calling the super paint method you are overriding.
See Painting in AWT and Swing and Performing Custom Painting for more details
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();
}
}
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();
}
}
}
}
For my application I need to dynamically create thumbnails of websites. So far I have this code from SO:
public class CreateWebsiteThumbnail {
private static final int WIDTH = 128;
private static final int HEIGHT = 128;
private BufferedImage image = new BufferedImage(WIDTH, HEIGHT, BufferedImage.TYPE_INT_RGB);
public void capture(Component component) {
component.setSize(image.getWidth(), image.getHeight());
Graphics2D g = image.createGraphics();
try {
component.paint(g);
} finally {
g.dispose();
}
}
private BufferedImage getScaledImage(int width, int height) {
BufferedImage buffer = new BufferedImage(width, height,
BufferedImage.TYPE_INT_RGB);
Graphics2D g = buffer.createGraphics();
try {
g.drawImage(image, 0, 0, width, height, null);
} finally {
g.dispose();
}
return buffer;
}
public void save(File png, int width, int height) throws IOException {
ImageIO.write(getScaledImage(width, height), "png", png);
}
public static void main(String[] args) throws IOException {
Shell shell = new Shell();
Browser browser = new Browser(shell, SWT.EMBEDDED);
browser.setUrl("http://www.google.com");
CreateWebsiteThumbnail cap = new CreateWebsiteThumbnail();
cap.capture(What her?);
cap.save(new File("foo.png"), 64, 64);
}
}
But as you can see here, I don't know which part of the browser I should pass to my capture method. Any hints?
I don't see, how the code you provided could work. The Shell is not opened, it has no size, the Browser didn't get time to actually load anything, no event loop seems to be running to enable any drawing, ...
The following code does a screenshot of a page using SWT browser:
import java.io.IOException;
import org.eclipse.swt.SWT;
import org.eclipse.swt.browser.Browser;
import org.eclipse.swt.browser.ProgressEvent;
import org.eclipse.swt.browser.ProgressListener;
import org.eclipse.swt.graphics.GC;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.ImageData;
import org.eclipse.swt.graphics.ImageLoader;
import org.eclipse.swt.layout.FillLayout;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Shell;
public class CreateWebsiteThumbnail {
private static final int WIDTH = 800;
private static final int HEIGHT = 600;
public static void main( String[] args ) throws IOException {
final Display display = new Display();
final Shell shell = new Shell();
shell.setLayout(new FillLayout());
final Browser browser = new Browser(shell, SWT.EMBEDDED);
browser.addProgressListener(new ProgressListener() {
#Override
public void changed( ProgressEvent event ) {}
#Override
public void completed( ProgressEvent event ) {
shell.forceActive();
display.asyncExec(new Runnable() {
#Override
public void run() {
grab(display, shell, browser);
}
});
}
});
browser.setUrl("http://www.google.com");
shell.setSize(WIDTH, HEIGHT);
shell.open();
while ( !shell.isDisposed() ) {
if ( !display.readAndDispatch() ) display.sleep();
}
display.dispose();
}
private static void grab( final Display display, final Shell shell, final Browser browser ) {
final Image image = new Image(display, browser.getBounds());
GC gc = new GC(browser);
gc.copyArea(image, 0, 0);
gc.dispose();
ImageLoader loader = new ImageLoader();
loader.data = new ImageData[] { image.getImageData() };
loader.save("foo.png", SWT.IMAGE_PNG);
image.dispose();
shell.dispose();
}
}
But there are some serious caveats:
You cannot do this off-screen. SWT screenshots are just a copy of the current Display.
The window containing your browser must be on top, when taking the screenshot.
The page should be visible after onLoad (which is actually not the case with google.com, but works for me because of the asyncExec call anyway - if you get a white image, try another URL)
The result is dependant on your OS and its installed browsers
I'd go with a non Java-solution, in order to get off screen drawing. I believe the linked question might help you to get further.
As far as I know, when you use a Browser object, the webpage you load is rendered directly on the Composite object you pass to it through the constructor. In your case, it is rendered on your Shell item which is a window-style object. There is no method to render the webpage directly on, say, an Image object.
You can try, though, to instantiate your Browser on a Canvas object and save the image directly from there.
Unfortunately I am unable to test whether this works or not because I have neither Eclipse nor SWT installed; I am pretty sure though that, if what you want to do is doable, this is the only way.
I did some experimenting myself. My intuition was to try on JEditorPane just like mentioned in the answer your code is based on. I do not know of how much help it is going to be but it might help. I gives some results, but from obvious reasons, luck of css support etc in JEditorPane it all looks ugly.
This is my code:
import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.Graphics2D;
import java.awt.event.ActionEvent;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.net.URL;
import javax.imageio.ImageIO;
import javax.swing.*;
import javax.swing.text.html.HTMLEditorKit;
public class WebPageToImageThumbnail {
public static void main(String[] a) throws Exception {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
JFrame frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
final JEditorPane editorPane = new JEditorPane();
editorPane.setEditorKit(new HTMLEditorKit());
try {
editorPane.setPage(new URL("http://weblogs.java.net/blog/alex2d/archive/2008/12/jwebpane_projec.html"));
} catch (IOException ex) {
}
final JPanel panel = new JPanel(new BorderLayout());
panel.add(new JScrollPane(editorPane), BorderLayout.CENTER);
panel.add(new JButton(new AbstractAction("SAVE") {
#Override
public void actionPerformed(ActionEvent e) {
BufferedImage image = capture(editorPane);
try {
save(image, new File("foo.png"), 64, 64);
} catch (IOException ex) {
}
}
}), BorderLayout.SOUTH);
frame.setContentPane(panel);
frame.setSize(600, 400);
frame.setVisible(true);
}
});
}
public static BufferedImage capture(Component component) {
BufferedImage image = new BufferedImage(component.getWidth(), component.getHeight(), BufferedImage.TYPE_INT_RGB);//component.setSize(image.getWidth(), image.getHeight());
Graphics2D g = image.createGraphics();
try {
component.paint(g);
} finally {
g.dispose();
}
return image;
}
private static BufferedImage getScaledImage(BufferedImage image, int width, int height) {
BufferedImage buffer = new BufferedImage(width, height,
BufferedImage.TYPE_INT_RGB);
Graphics2D g = buffer.createGraphics();
try {
g.drawImage(image, 0, 0, width, height, null);
} finally {
g.dispose();
}
return buffer;
}
public static void save(BufferedImage image, File png, int width, int height) throws IOException {
ImageIO.write(getScaledImage(image, width, height), "png", png);
}
}
I also did some digging about the SWT I found some thinks that might be of use, but since I currently luck time, I cannot test. From what I read I have to agree with #Emanuele Bezzi (+1) that we are going to have to use the Shell somehow to get the content of the site, in which we are effectively only interested.
I found out that Shell has print method which takes GC object which can paint including to Image and other interesting to us stuff, the documentation says: "Class GC is where all of the drawing capabilities that are supported by SWT are located. Instances are used to draw on either an Image, a Control, or directly on a Display.".
Yet at this particular moment it is not clear to me how to exactly get it to do what I want. Anyway I am raising this point just to make you aware. I still need to look into it further.
Maybe you're better off rendering the page offscreen right from the start. For such a task you may for example use "flying saucer" (which is xhtml only, so you need to tidy up). There seem to be also some tools to interface Webkit directly from Java.