I'm currently in the process of writing a paint program in java, designed to have flexible and comprehensive functionalities. It stemmed from my final project, that I wrote overnight the day before. Because of that, it's got tons and tons of bugs, which I've been tackling one by one (e.g. I can only save files that will be empty, my rectangles don't draw right but my circles do...).
This time, I've been trying to add undo/redo functionality to my program. However, I can't "undo" something that I have done. Therefore, I got an idea to save copies of my BufferedImage each time a mouseReleased event was fired. However, with some of the images going to 1920x1080 resolution, I figured that this wouldn't be efficient: storing them would probably take gigabytes of memory.
The reason for why I can't simply paint the same thing with the background colour to undo is because I have many different brushes, which paint based on Math.random(), and because there are many different layers (in a single layer).
Then, I've considered cloning the Graphics objects that I use to paint to the BufferedImage. Like this:
ArrayList<Graphics> revisions = new ArrayList<Graphics>();
#Override
public void mouseReleased(MouseEvent event) {
Graphics g = image.createGraphics();
revisions.add(g);
}
I haven't done this before, so I have a couple questions:
Would I still be wasting pointless memory by doing this, like cloning my BufferedImages?
Is there necessarily a different way I can do this?
No, storing a Graphics object is usually a bad idea. :-)
Here's why: Normally, Graphics instances are short-lived and is used to paint or draw onto some kind of surface (typically a (J)Component or a BufferedImage). It holds the state of these drawing operations, like colors, stroke, scale, rotation etc. However, it does not hold the result of the drawing operations or the pixels.
Because of this, it won't help you achieve undo-functionality. The pixels belongs to the component or image. So, rolling back to a "previous" Graphics object will not modify the pixels back to the previous state.
Here's some approaches I know works:
Use a "chain" of commands (command pattern) to modify the image. Command pattern works very nice with undo/redo (and is implemented in Swing/AWT in Action). Render all commands in sequence, starting from the original. Pro: The state in each command is usually not so large, allowing you to have many steps of undo-buffer in memory. Con: After a lot of operations, it becomes slow...
For every operation, store the entire BufferedImage (as you originally did). Pro: Easy to implement. Con: You'll run out of memory fast. Tip: You could serialize the images, making undo/redo taking less memory, at the cost of more processing time.
A combination of the above, using command pattern/chain idea, but optimizing the rendering with "snapshots" (as BufferedImages) when reasonable. Meaning you won't need to render everything from the beginning for each new operation (faster). Also flush/serialize these snapshots to disk, to avoid running out of memory (but keep them in memory if you can, for speed). You could also serialize the commands to disk, for virtually unlimited undo. Pro: Works great when done right. Con: Will take some time to get right.
PS: For all of the above, you need to use a background thread (like SwingWorker or similar) to update the displayed image, store commands/images to disk etc in the background, to keep a responsive UI.
Good luck! :-)
Idea #1, storing the Graphics objects simply wouldn't work. The Graphics should not be considered as "holding" some display memory, but rather as a handle to access an area of display memory. In the case of BufferedImage, each Graphics object will be always the handle to the same given image memory buffer, so they all will represent the same image. Even more importantly, you can't actually do anything with the stored Graphics: As they do not store anything, there is no way whatsoever they could "re-store" anything.
Idea #2, cloning the BufferedImages is a much better idea, but you'll indeed be wasting memory, and quickly run out of it. It helps only to store those parts of the image affected by the draw, for example using rectangular areas, but it still costs a lot of memory. Buffering those undo images to disk could help, but it will make your UI slow and unresponsive, and that's bad; furthermore, it makes you application more complex and error-prone.
My alternative would be to store store the image modifications in a list, rendered from first to last on top of the image. An undo operation then simply consists of removing the modification from the list.
This requires you to "reify" the image modifications, i.e. create a class that implements a single modification, by providing a void draw(Graphics gfx) method which performs the actual drawing.
As you said, random modifications pose an additional problem. However, the key problem is your use of Math.random() to create random numbers. Instead, perform each random modification with a Random created from a fixed seed value, so that the (pseudo-)random number sequences are the same on each invocation of draw(), i.e., each draw has exactly the same effects. (That's why they are called "pseudo-random" -- the generated numbers look random, but they are just as deterministic as any other function.)
In contrast to the image storing technique, which has memory problems, the problem with this technique is that many modifications may make the GUI slow, especially if the modifications are computationally intensive. To prevent this, the simplest way would be to fix an appropriate maximum size of the list of undoable modifications. If this limit would be exceeded by adding a new modification, remove the oldest modification the list and apply it to the backing BufferedImage itself.
The following simple demo application shows that (and how) this all works together. It also includes a nice "redo" feature for redoing undone actions.
package stackoverflow;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.image.BufferedImage;
import java.util.LinkedList;
import java.util.Random;
import javax.swing.*;
public final class UndoableDrawDemo
implements Runnable
{
public static void main(String[] args) {
EventQueue.invokeLater(new UndoableDrawDemo()); // execute on EDT
}
// holds the list of drawn modifications, rendered back to front
private final LinkedList<ImageModification> undoable = new LinkedList<>();
// holds the list of undone modifications for redo, last undone at end
private final LinkedList<ImageModification> undone = new LinkedList<>();
// maximum # of undoable modifications
private static final int MAX_UNDO_COUNT = 4;
private BufferedImage image;
public UndoableDrawDemo() {
image = new BufferedImage(600, 600, BufferedImage.TYPE_INT_RGB);
}
public void run() {
// create display area
final JPanel drawPanel = new JPanel() {
#Override
public void paintComponent(Graphics gfx) {
super.paintComponent(gfx);
// display backing image
gfx.drawImage(image, 0, 0, null);
// and render all undoable modification
for (ImageModification action: undoable) {
action.draw(gfx, image.getWidth(), image.getHeight());
}
}
#Override
public Dimension getPreferredSize() {
return new Dimension(image.getWidth(), image.getHeight());
}
};
// create buttons for drawing new stuff, undoing and redoing it
JButton drawButton = new JButton("Draw");
JButton undoButton = new JButton("Undo");
JButton redoButton = new JButton("Redo");
drawButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
// maximum number of undo's reached?
if (undoable.size() == MAX_UNDO_COUNT) {
// remove oldest undoable action and apply it to backing image
ImageModification first = undoable.removeFirst();
Graphics imageGfx = image.getGraphics();
first.draw(imageGfx, image.getWidth(), image.getHeight());
imageGfx.dispose();
}
// add new modification
undoable.addLast(new ExampleRandomModification());
// we shouldn't "redo" the undone actions
undone.clear();
drawPanel.repaint();
}
});
undoButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
if (!undoable.isEmpty()) {
// remove last drawn modification, and append it to undone list
ImageModification lastDrawn = undoable.removeLast();
undone.addLast(lastDrawn);
drawPanel.repaint();
}
}
});
redoButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
if (!undone.isEmpty()) {
// remove last undone modification, and append it to drawn list again
ImageModification lastUndone = undone.removeLast();
undoable.addLast(lastUndone);
drawPanel.repaint();
}
}
});
JPanel buttonPanel = new JPanel(new FlowLayout());
buttonPanel.add(drawButton);
buttonPanel.add(undoButton);
buttonPanel.add(redoButton);
// create frame, add all content, and open it
JFrame frame = new JFrame("Undoable Draw Demo");
frame.getContentPane().add(drawPanel);
frame.getContentPane().add(buttonPanel, BorderLayout.NORTH);
frame.pack();
frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
//--- draw actions ---
// provides the seeds for the random modifications -- not for drawing itself
private static final Random SEEDS = new Random();
// interface for draw modifications
private interface ImageModification
{
void draw(Graphics gfx, int width, int height);
}
// example random modification, draws bunch of random lines in random color
private static class ExampleRandomModification implements ImageModification
{
private final long seed;
public ExampleRandomModification() {
// create some random seed for this modification
this.seed = SEEDS.nextLong();
}
#Override
public void draw(Graphics gfx, int width, int height) {
// create a new pseudo-random number generator with our seed...
Random random = new Random(seed);
// so that the random numbers generated are the same each time.
gfx.setColor(new Color(
random.nextInt(256), random.nextInt(256), random.nextInt(256)));
for (int i = 0; i < 16; i++) {
gfx.drawLine(
random.nextInt(width), random.nextInt(height),
random.nextInt(width), random.nextInt(height));
}
}
}
}
Most games(or programs) saves only the necessary parts and thats what you should do.
a rectangle can be represented by width, height, background color, stroke, outline etc. So you can just save these parameters instead of the actual rectangle. "rectangle color:red width: 100 height 100"
for the random aspects of your program( random color on brushes) you can either save the seed or save the result. "random seed: 1023920"
if the program allows user to import images then you should copy and save the images.
fillters and effects(zoom/transformation/glow) can all be represented by parameters just like shapes. eg. "zoom scale: 2" "rotate angle: 30"
so you save all these parameters in a list and when then you need to undo you can mark the parameters as deleted( but dont actually delete them since you want to be able to redo as well). Then you can erase the whole canvas and recreate the image based on the parameters minus those that were marked as deleted.
*for stuff like lines you can just store their locations in a list.
You are going to want to try to compress your images (using PNG is a good start, it has some nice filters along with zlib compression that really help). I think the best way to do this is to
make a copy of the image before you modify it
modify it
compare the copy with the new modified image
for every pixel you didn't change, make that pixel a black, transparent pixel.
That should compress really, really well in PNG. Try black and white and see if there is a difference (I don't think there will be, but make sure you set the rgb values to the same thing, not just the alpha value, so it will compress better).
You might get even better performance out of cropping the image to the part that was changed, but I'm not sure how much you gain from that, considering the compression (and the fact that you will have to now save and remember the offset).
Then, since you have an alpha channel, if they undo, you can just put the undo image back on top of the current image and you're set.
Related
Hello I am an inexperienced programmer and this is my first question on Stack Overflow!
I am attempting to implement 'fog of war' in my Java game. This means most of my map begins off black and then as one of my characters moves around parts of the map will be revealed. I have searched around including here and found a few suggestions and tried tweaking them myself. Each of my approaches works, however I run into significant runtime issues with each. For comparison, before any of my fog of war attempts I was getting 250-300 FPS.
Here is my basic approach:
Render my background and all objects on my JPanel
Create a black BufferedImage (fogofwarBI)
Work out which areas of my map need to be visible
Set the relevant pixels on my fogofwarBI to be fully transparent
Render my fogofwarBI, thus covering parts of the screen with black and in transparent sections allowing the background and objects to be seen.
For initialising the buffered image I have done the following in my FogOfWar() class:
private BufferedImage blackBI = loader.loadImage("/map_black_2160x1620.png");
private BufferedImage fogofwarBI = new BufferedImage(blackBI.getWidth(), blackBI.getHeight(), BufferedImage.TYPE_INT_ARGB);
public FogOfWar() {
fogofwarBI.getGraphics().drawImage(blackBI,0,0,null);
}
In each of my attempts I start the character in a middle of 'visible' terrain, ie. in a section of my map which has no fog (where my fogofwarBI will have fully transparent pixels).
Attempt 1: setRGB
First I find the 'new' coordinates in my character's field of vision if it has moved. ie. not every pixel within the character's range of sight, but just the pixels at the edge of his range of vision in the direction he is moving. This is done with a for loop, and will go through up to 400 or so pixels.
I feed each of these x and y coordinates into my FogOfWar class.
I check if these x,y coordinates are already visible (in which case I don't bother doing anything to them to save time). I do this check by maintaining a Set of Lists. Where each List contains two elements: an x and y value. And the Set is a unique set of the coordinate Lists. The Set begins empty, and I will add x,y coordinates to represent transparent pixels. I use the Set to keep the collection unique and because I understand the List.contains function is a fast way of doing this check. And I store the coordinates in a List to avoid mixing up x and y.
If a given x,y position on my fogofwarBI is not currently visible I add set the RBG to be transparent using .setRGB, and add it to my transparentPoints Set so that coordinate will not be edited again in future.
Set<List<Integer>> transparentPoints = new HashSet<List<Integer>>();
public void editFog(int x, int y) {
if (transparentPoints.contains(Arrays.asList(x,y)) == false){
fogofwarBI.setRGB(x,y,0); // 0 is transparent in ARGB
transparentPoints.add(Arrays.asList(x,y));
}
}
I then render it using
public void render(Graphics g, Camera camera) {
g.drawImage(fogofwarBI, 0, 0, Game.v_WIDTH, Game.v_HEIGHT,
camera.getX()-Game.v_WIDTH/2, camera.getY()-Game.v_HEIGHT/2,
camera.getX()+Game.v_WIDTH/2, camera.getY()+Game.v_HEIGHT/2, null);
}
Where I am basically applying the correct part of my fogofwarBI to my JPanel (800*600) based on where my game camera is.
Results:
Works correctly.
FPS of 20-30 when moving through fog, otherwise normal (250-300).
This method is slow due to the .setRGB function, being run up to 400 times each time my game 'ticks'.
Attempt 2: Raster
In this attempt I create a raster of my fogofwarBI to play with the pixels directly in an array format.
private BufferedImage blackBI = loader.loadImage("/map_black_2160x1620.png");
private BufferedImage fogofwarBI = new BufferedImage(blackBI.getWidth(), blackBI.getHeight(), BufferedImage.TYPE_INT_ARGB);
WritableRaster raster = fogofwarBI.getRaster();
DataBufferInt dataBuffer = (DataBufferInt)raster.getDataBuffer();
int[] pixels = dataBuffer.getData();
public FogOfWar() {
fogofwarBI.getGraphics().drawImage(blackBI,0,0,null);
}
My editFog method then looks like this:
public void editFog(int x, int y) {
if (transparentPoints.contains(Arrays.asList(x,y)) == false){
pixels[(x)+((y)*Game.m_WIDTH)] = 0; // 0 is transparent in ARGB
transparentPoints.add(Arrays.asList(x,y));
}
}
My understanding is that the raster is in (constant?) communication with the pixels array, and so I render the BI in the same way as in attempt 1.
Results:
Works correctly.
A constant FPS of around 15.
I believe it is constantly this slow (regardless of whether my character is moving through fog or not) because whilst manipulating the pixels array is quick, the raster is constantly working.
Attempt 3: Smaller Raster
This is a variation on attempt 2.
I read somewhere that constantly resizing a BufferedImage using the 10 input version of .drawImage is slow. I also thought that having a raster for a 2160*1620 BufferedImage might be slow.
Therefore I tried having my 'fog layer' only equal to the size of my view (800*600), and updating every pixel using a for loop, based on whether the current pixel should be black or visible from my standard transparentPoints Set and based on my camera position.
So now my editFog Class just updates the Set of invisible pixels and my render class looks like this:
public void render(Graphics g, Camera camera) {
int xOffset = camera.getX() - Game.v_WIDTH/2;
int yOffset = camera.getY() - Game.v_HEIGHT/2;
for (int i = 0; i<Game.v_WIDTH; i++) {
for (int j = 0; j<Game.v_HEIGHT; j++) {
if ( transparentPoints.contains(Arrays.asList(i+xOffset,j+yOffset)) ) {
pixels[i+j*Game.v_WIDTH] = 0;
} else {
pixels[i+j*Game.v_WIDTH] = myBlackARGB;
}
}
}
g.drawImage(fogofwarBI, 0, 0, null);
}
So I am no longer resizing my fogofwarBI on the fly, but I am updating every single pixel every time.
Result:
Works correctly.
FPS: Constantly 1 FPS - worst result yet!
I guess that any savings of not resizing my fogofwarBI and having it smaller are massively outweighed by updating 800*600 pixels in the raster rather than around 400.
I have run out of ideas and none of my internet searching is getting me any further in trying to do this in a better way. I think there must be a way to do fog of war effectively, but perhaps I am not yet familiar enough with Java or the available tools.
And pointers as to whether my current attempts could be improved or whether I should be trying something else altogether would be very much appreciated.
Thanks!
This is a good question. I am not familar with the awt/swing type rendering, so I can only try to explain a possible solution for the problem.
From a performance standpoint I think it is a better choice to chunk/raster the FOW in bigger sections of the map rather than using a pixelbased system. That will reduce the amount of checks per tick and updating it will also take less resources, as only a small portion of the window/map needs to update. The larger the grid, the less checks, but there is a visual penalty the bigger you go.
Leaving it like that would make the FOW look blocky/pixelated, but its not something you can't fix.
For the direct surrounding of a player, you can add a circle texture with the player at its center. You can than use blending (I believe the term in awt/swing is composite) to 'override' the alpha where the circle overlaps the FOW texture. This way the pixel-based updating is done by the renderingAPI which usually uses hardware enhanced methods to achieve these things. (for custom pixel-based rendering, something like 'shader scripts' are often used if supported by the rendering API)
This is enough if you only need temporary vission in the FOW (if you don't need to 'remember' the map), you don't even need a texture grid for the FOW than, but I suspect you do want to 'remember' the map. So in that case:
The blocky/pixelated look can be fixed like they do with grid-based terain. Basically add a small additional textures/shapes based on the surroundings to make things look nice. The link below provides good examples and a detailed explanation on how to do the 'terrain-transitions' as they are called.
https://www.gamedev.net/articles/programming/general-and-gameplay-programming/tilemap-based-game-techniques-handling-terrai-r934/
I hope this gives a better result. If you cannot get a better result, I would advise switching over to something like OpenGL for the render engine as it is meant for games, while the awt/swing API is primarely used for UI/application rendering.
I need a code, more specifically a game, in which meteorites fall down and the user has to dodge them with the arrow keys.
Moving the player is not my problem. I will do this at the end.
I figured out how to create an animated object falling down, from top to bottom.
How can I create a loop which creates one new falling element every 2 seconds? The position of the element should be randomized.
This is my code at the moment:
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import javax.swing.JFrame;
import javax.swing.JPanel;
#SuppressWarnings("serial")
public class Game extends JPanel {
int x = 135;
int y = 0;
private void moveBall() {
y = y + 1;
}
#Override
public void paint(Graphics g) {
super.paint(g);
Graphics2D g2d = (Graphics2D) g;
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2d.fillOval(x, y, 30, 30);
}
public static void main(String[] args) throws InterruptedException {
JFrame frame = new JFrame("Meteorites");
Game game = new Game();
frame.add(game);
frame.setSize(300, 400);
frame.setVisible(true);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
while (true) {
game.moveBall();
game.repaint();
Thread.sleep(10);
}
}
}
You will need a game loop (gl) first of all. The gl updates the state and position of every object on the screen and other state related objects not seen on the screen.
It should be easy for objects to register themselves to the gl. The gl is not responsible for updating the state of every object, but it does call back every registered object to update themselves (or update other objects that it is responsible for, for example the collision detection object will update any objects on the screen that have collided).
The gl passes the current time in nano-seconds (typically) to all the objects that have registered with the gl. For example check out the AnimationTimer in Javafx. In JavaFX, by creating an AnimationTimer, and calling the start method on it, JavaFX will call your handle method on every frame... essentially registering it to JavaFX's main thread (or game loop).
So you will need to create a gl. Register objects to it, maybe create an interface or abstract class similar to JavaFX's AnimationTimer so that objects can register themselves to your gl. The gl should not have any Thread.sleeps in it, but if it does it should be very short and I imagine you would add it in only if your gl is going to fast, because maybe there is not a lot of stuff being calculated (hence no use crippling the CPU), but you can adjust this. I know that working on JavaFX games I can tell you that we work very hard to keep any delays in he gl down to a minimum, hence no Thread sleeps, as this will halt the smooth flow of your animation.
The objects that get updated will track the passage of time and can execute events based on how much time has passed by. For example you can have an object that registers itself to the gl and is responsible for putting new falling animated objects on the screen. It can have a field for how long to wait before dropping the next item. Since it is called every frame by the gl, it can see how much time is passing by, and when x seconds pass by, it can create and drop the next object, then reset its timer.
The animated objects falling would also be called by the gl so they can smoothly drop down. At every frame these falling objects will calculate their new position. Typically this is done using an interpolator (its a good idea to look into this). Interpolators make it easy to abstract out where the position of an animating object should be relative to a fraction of time passing by. For example if I am moving an item linearly over X distance and Y time, an interpolater will tell you how far along X distance the object should be after a percentage of Y time has passed. Interpolators are very powerful because you can create an interpolator that moves your object not just linearly but can slow it up or speed it up (like gravity effect), it can even add bouncing effects to your objects. So you'll want to get a firm understanding of this. JavaFX comes with interpolators. There are also online tools I have seen in the past that let you construct your own JavaFX interpolators based on the kind of animation effect you are trying to get.
So to put it all together, have a gl, have an object that registers to the gl that is responsible for putting new falling objects on the screen, lets call this object ObjectDropper. The ObjectDropper will implement something like AnimationTimer so it can get called every frame by your gl. It will decide when to drop objects and can also decide the random point from where that object will drop. You will have objects that will drop, they will be given information like where their starting point is, where their ending point is, and how long it will take to drop, and maybe even an interpolator. These objects will also be registered to the gl and be called every frame so that they can adjust their position according to their interpolator.
This is all a very broad generic outline that tends to be true for most animated games. There might be tools and libraries that make this all easily implemented in Swing. As you can see JavaFX was already designed to handle many of these concepts.
I have a JPanel to which I'd like to add JPEG and PNG images that I generate on the fly.
All the examples I've seen so far in the Swing Tutorials, specially in the Swing examples use ImageIcons.
I'm generating these images as byte arrays, and they are usually larger than the common icon they use in the examples, at 640x480.
Is there any (performance or other) problem in using the ImageIcon class to display an image that size in a JPanel?
What's the usual way of doing it?
How to add an image to a JPanel without using the ImageIcon class?
Edit: A more careful examination of the tutorials and the API shows that you cannot add an ImageIcon directly to a JPanel. Instead, they achieve the same effect by setting the image as an icon of a JLabel. This just doesn't feel right...
If you are using JPanels, then are probably working with Swing. Try this:
BufferedImage myPicture = ImageIO.read(new File("path-to-file"));
JLabel picLabel = new JLabel(new ImageIcon(myPicture));
add(picLabel);
The image is now a swing component. It becomes subject to layout conditions like any other component.
Here's how I do it (with a little more info on how to load an image):
import java.awt.Graphics;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.imageio.ImageIO;
import javax.swing.JPanel;
public class ImagePanel extends JPanel{
private BufferedImage image;
public ImagePanel() {
try {
image = ImageIO.read(new File("image name and path"));
} catch (IOException ex) {
// handle exception...
}
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
g.drawImage(image, 0, 0, this); // see javadoc for more info on the parameters
}
}
Fred Haslam's way works fine. I had trouble with the filepath though, since I want to reference an image within my jar. To do this, I used:
BufferedImage wPic = ImageIO.read(this.getClass().getResource("snow.png"));
JLabel wIcon = new JLabel(new ImageIcon(wPic));
Since I only have a finite number (about 10) images that I need to load using this method, it works quite well. It gets file without having to have the correct relative filepath.
I think there is no need to subclass of anything. Just use a Jlabel. You can set an image into a Jlabel. So, resize the Jlabel then fill it with an image. Its OK. This is the way I do.
You can avoid rolling your own Component subclass completely by using the JXImagePanel class from the free SwingX libraries.
Download
JLabel imgLabel = new JLabel(new ImageIcon("path_to_image.png"));
You can subclass JPanel - here is an extract from my ImagePanel, which puts an image in any one of 5 locations, top/left, top/right, middle/middle, bottom/left or bottom/right:
protected void paintComponent(Graphics gc) {
super.paintComponent(gc);
Dimension cs=getSize(); // component size
gc=gc.create();
gc.clipRect(insets.left,insets.top,(cs.width-insets.left-insets.right),(cs.height-insets.top-insets.bottom));
if(mmImage!=null) { gc.drawImage(mmImage,(((cs.width-mmSize.width)/2) +mmHrzShift),(((cs.height-mmSize.height)/2) +mmVrtShift),null); }
if(tlImage!=null) { gc.drawImage(tlImage,(insets.left +tlHrzShift),(insets.top +tlVrtShift),null); }
if(trImage!=null) { gc.drawImage(trImage,(cs.width-insets.right-trSize.width+trHrzShift),(insets.top +trVrtShift),null); }
if(blImage!=null) { gc.drawImage(blImage,(insets.left +blHrzShift),(cs.height-insets.bottom-blSize.height+blVrtShift),null); }
if(brImage!=null) { gc.drawImage(brImage,(cs.width-insets.right-brSize.width+brHrzShift),(cs.height-insets.bottom-brSize.height+brVrtShift),null); }
}
There shouldn't be any problem (other than any general problems you might have with very large images).
If you're talking about adding multiple images to a single panel, I would use ImageIcons. For a single image, I would think about making a custom subclass of JPanel and overriding its paintComponent method to draw the image.
(see 2)
JPanel is almost always the wrong class to subclass. Why wouldn't you subclass JComponent?
There is a slight problem with ImageIcon in that the constructor blocks reading the image. Not really a problem when loading from the application jar, but maybe if you're potentially reading over a network connection. There's plenty of AWT-era examples of using MediaTracker, ImageObserver and friends, even in the JDK demos.
I'm doing something very similar in a private project I'm working on. Thus far I've generated images up to 1024x1024 without any problems (except memory) and can display them very quickly and without any performance problems.
Overriding the paint method of JPanel subclass is overkill and requires more work than you need to do.
The way I do it is:
Class MapIcon implements Icon {...}
OR
Class MapIcon extends ImageIcon {...}
The code you use to generate the image will be in this class. I use a BufferedImage to draw onto then when the paintIcon() is called, use g.drawImvge(bufferedImage); This reduces the amount of flashing done while you generate your images, and you can thread it.
Next I extend JLabel:
Class MapLabel extends Scrollable, MouseMotionListener {...}
This is because I want to put my image on a scroll pane, I.e. display part of the image and have the user scroll around as needed.
So then I use a JScrollPane to hold the MapLabel, which contains only the MapIcon.
MapIcon map = new MapIcon ();
MapLabel mapLabel = new MapLabel (map);
JScrollPane scrollPane = new JScrollPane();
scrollPane.getViewport ().add (mapLabel);
But for your scenario (just show the whole image every time). You need to add the MapLabel to the top JPanel, and make sure to size them all to the full size of the image (by overriding the GetPreferredSize()).
This answer is a complement to #shawalli's answer...
I wanted to reference an image within my jar too, but instead of having a BufferedImage, I simple did this:
JPanel jPanel = new JPanel();
jPanel.add(new JLabel(new ImageIcon(getClass().getClassLoader().getResource("resource/images/polygon.jpg"))));
Create a source folder in your project directory, in this case I called it Images.
JFrame snakeFrame = new JFrame();
snakeFrame.setBounds(100, 200, 800, 800);
snakeFrame.setVisible(true);
snakeFrame.add(new JLabel(new ImageIcon("Images/Snake.png")));
snakeFrame.pack();
You can avoid using own Components and SwingX library and ImageIO class:
File f = new File("hello.jpg");
JLabel imgLabel = new JLabel(new ImageIcon(file.getName()));
I can see many answers, not really addressing the three questions of the OP.
1) A word on performance: byte arrays are likely unefficient unless you can use an exact pixel byte ordering which matches to your display adapters current resolution and color depth.
To achieve the best drawing performance, simply convert your image to a BufferedImage which is generated with a type corresponding to your current graphics configuration. See createCompatibleImage at https://docs.oracle.com/javase/tutorial/2d/images/drawonimage.html
These images will be automatically cached on the display card memory after drawing a few times without any programming effort (this is standard in Swing since Java 6), and therefore the actual drawing will take negligible amount of time - if you did not change the image.
Altering the image will come with an additional memory transfer between main memory and GPU memory - which is slow. Avoid "redrawing" the image into a BufferedImage therefore, avoid doing getPixel and setPixel at all means.
For example, if you are developing a game, instead of drawing all the game actors to a BufferedImage and then to a JPanel, it is a lot faster to load all actors as smaller BufferedImages, and draw them one by one in your JPanel code at their proper position - this way there is no additional data transfer between the main memory and GPU memory except of the initial transfer of the images for caching.
ImageIcon will use a BufferedImage under the hood - but basically allocating a BufferedImage with the proper graphics mode is the key, and there is no effort to do this right.
2) The usual way of doing this is to draw a BufferedImage in an overridden paintComponent method of the JPanel. Although Java supports a good amount of additional goodies such as buffer chains controlling VolatileImages cached in the GPU memory, there is no need to use any of these since Java 6 which does a reasonably good job without exposing all of these details of GPU acceleration.
Note that GPU acceleration may not work for certain operations, such as stretching translucent images.
3) Do not add. Just paint it as mentioned above:
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
g.drawImage(image, 0, 0, this);
}
"Adding" makes sense if the image is part of the layout. If you need this as a background or foreground image filling the JPanel, just draw in paintComponent. If you prefer brewing a generic Swing component which can show your image, then it is the same story (you may use a JComponent and override its paintComponent method) - and then add this to your layout of GUI components.
4) How to convert the array to a Bufferedimage
Converting your byte arrays to PNG, then loading it is quite resource intensive. A better way is to convert your existing byte array to a BufferedImage.
For that: do not use for loops and copy pixels. That is very very slow. Instead:
learn the preferred byte structure of the BufferedImage (nowadays it is safe to assume RGB or RGBA, which is 4 bytes per pixel)
learn the scanline and scansize in use (e.g. you might have a 142 pixels wide image - but in the real life that will be stored as a 256 pixel wide byte array since it is faster to process that and mask the unused pixes by the GPU hardware)
then once you have an array build according to these principles, the setRGB array method of the BufferedImage can copy your array to the BufferedImage.
I am implementing a board game where each player has several units and each unit is shown to a user using a common image which I read from a file using the following method. I read this image at application startup and going to use it later.
private static BufferedImage readBufferedImage (String imagePath) {
try {
InputStream is = IconManager.class.getClassLoader().getResourceAsStream(imagePath);
BufferedImage bimage = ImageIO.read(is);
is.close();
return bimage;
} catch (Exception e) {
return null;
}
}
Units differ with various colorful tokens located on the top of that common image.
Before I just add several commonImages to JPanel and tokens were implemented using JLabels which were floating on the top of commonImage
//at startup
ImageIcon commonImage = new ImageIcon(readBufferedImage("image.png"));
...
JPanel panel = new JPanel();
panel.add(commonImage);
panel.add(commonImage);
//located JLabels with token on the top of each commonImage
However, now I want to use JScrollPane instead of JPanel, so I think it is a better approach to drawString() and drawImage() to each commonImage before I show it to a user.
I estimate roughly a number of units as 20. So now every turn for every unit I would need to generate on-the-fly separate BufferedImage with various tokens configuration.
The question is whether I should cache already generated BufferedImages depending on token configuration to extract from cache if the image has been generated before with same configuration?
The question you ask depends on a few factors:
How long it takes to generate the image from scratch
How long it takes to check if the image was generated before
How large each image will be
The probability of the image being reused
So without having a good knowledge of your application, no one is going to be able to give you an answer accurate for every situation.
In general, if you're only moving tokens around, it should be quick to implement an update and the paintComponent method of the panel your drawing. If you save your base image, token image and the current resulting image (basically what you proposed), I wouldn't anticipate a performance problem. The key is to do your updating of the image in your custom update method, and always draw the current resulting image in the paintComponent method.
Update
For example, you can cache the current display using code similar to this:
private BufferedImage cachedImage;
...
#Override
public void paintComponent(Graphics g){
//If the image needs to be refreshed draw it to the cache first
if(cachedImage == null){
cachedImage = new BufferedImage(getWidth(), getHeight(), BufferedImage.TYPE_4BYTE_ABGR);
super.paintComponent(cachedImage.getGraphics());
}
//Draw the image from cache
g.drawImage(cachedImage, 0, 0, this);
}
When you want to clear the cache, in your method that does the update, you'd set cachedImage = null;
But in your situation,
Any performance problems at the scale of 200x200 you experience would have to do with the number of drawings you're doing. Since I'm guessing you're not concerned about a frames/second rate, I doubt you'll have performance problems (you can collect timings to see exactly how long it takes to draw - I'd guess well under 100 ms)
The easiest thing to do would be cache the images of the tokens and their strings (I'm assuming they rarely change), but still dynamically create the layout of the tokens. (Any other performance gains in your situation aren't going to be so easy).
Caching previous rendering of the screen will essentially break the swing display model (not much point in using it - just do the layout yourself). This actually applies to pretty much any caching of a container.
How can I draw something in JPanel that will stay the same and not be repainted, I am doing a traffic simulation program and I want the road to be drawn once because It will not change.
Thanks
I'm not sure you actually want your road to never be repainted - repaint events fire (for example) when your window is resized, or when it becomes visible following another window obstructing it. If your panel never repaints then it'll look peculiar.
As far as I remember, Swing will only fire appropriate paint events for these circumstances, so you should be OK following the usual method of subclassing JPanel with a suitable override:
public class RoadPanel extends JPanel {
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
// your drawing code here
}
}
If you cache your road into an image or another graphics format (to save calculating the display data multiple times) once drawn once, this might save you some time on subsequent paints.
To my knowledge, no, unless there is a trick with transparent overlays.
Most graphical applications I saw (and did) just re-draw the whole panel on each repaint. Now, you can do that once, in a graphic buffer, and then just paint the whole background at once, quickly, by copying the graphic buffer to the JPanel. It should be faster than calling all graphical primitives to draw the road.
Or, the way some 2D games do, perhaps paint it once and update the moving parts, like sprites: it needs to erase the old place used by the sprites (restore the background there) and re-draw the sprites at the new place. So you still have a copy of the road in a graphic buffer but instead of re-drawing it whole each time, you update only some small parts. Can be slightly faster.
The component will need to be repainted every time that the panel is obscured (ie frame minimized/another window put on top). Therefore drawing something only once will not work as you want it to. To make parts that do not change be drawn more efficiently you can draw them once to a 'buffer' image, and then just draw this buffer each time that the panel or component needs to be redrawn.
// Field that stores the image so it is always accessible
private Image roadImage = null;
// ...
// ...
// Override paintComponent Method
public void paintComponent(Graphics g){
if (roadImage == null) {
// Create the road image if it doesn't exist
roadImage = createImage(width, height);
// draw the roads to the image
Graphics roadG = roadImage.getGraphics();
// Use roadG like you would any other graphics
// object to draw the roads to an image
} else {
// If the buffer image exists, you just need to draw it.
// Draw the road buffer image
g.drawImage(roadImage, 0, 0, null);
}
// Draw everything else ...
// g.draw...
}
What I do is set a boolean value to whether or not a certain part needs to be redrawn. Then, in the paintComponent() method I can check the value and redraw the certain thing, or not.
protected void paintComponent(Graphics g){
super.paintComponent(g);
if (drawRoad) {
drawRoadMethod(g);
}
drawTheRest(g);
}
Kinda like that.