Image Quality Loss When Drawing One BufferedImage to Another Using Graphics2D - java

I'm creating a program that displays animated gifs. Because some animated gif files only store the pixels that changed from the previous frame, before each frame is displayed, it's being drawn to a master BufferedImage object, named master, then that BufferedImage is being drawn. The problem is that drawing the frames (stored as BufferedImage objects themselves) to the master reduces their quality.
I know it's not a problem with the frames themselves, if I just draw the frames individually without drawing them to master then they look fine. It's also not a problem with the fact that there's lots of frames being layered on top of each other, even the first frame shows quality reduction. I've tried setting every RenderingHint to every possible value, but it changes nothing.
Below is my code, with unnecessary parts for solving this problem omitted:
import java.awt.image.BufferedImage;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Iterator;
import javax.activation.MimetypesFileTypeMap;
import javax.imageio.IIOImage;
import javax.imageio.ImageIO;
import javax.imageio.ImageReader;
import javax.imageio.metadata.IIOMetadata;
import javax.imageio.metadata.IIOMetadataNode;
import javax.imageio.stream.FileImageInputStream;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
import javax.swing.Timer;
#SuppressWarnings("serial")
class A extends javax.swing.JPanel{
public static final String PATH = "C:/Users/Owner/Desktop/test.gif";
public B i;
public A() throws java.io.IOException{
i = new B(new java.io.File(PATH));
i.registerComponent(this);
}
#Override
public java.awt.Dimension preferredSize(){
return i.getSize();
}
#Override
public void paintComponent(java.awt.Graphics g){
i.draw(g);
}
public static void main(String[] args){
javax.swing.SwingUtilities.invokeLater(new Runnable(){
public void run(){
javax.swing.JFrame f = new javax.swing.JFrame();
f.setDefaultCloseOperation(javax.swing.JFrame.EXIT_ON_CLOSE);
try{
f.add(new A());
}catch(Exception e){
}
f.pack();
f.setVisible(true);
}
});
}
}
class B{
private final static String META_FORMAT = "javax_imageio_gif_image_1.0";
// instance variables
private final BufferedImage[] frames;
private BufferedImage master;// Because Gif images can store only the changing
// pixels, the first frame is drawn to this image, then the next one *on top of it*, etc.
private final short[] frameDurations; // in 100ths of a second
private final short[] xOffsets;
private final short[] yOffsets;
private int frame = 0;
private final Dimension size;// the size of the gif (calculated in findSize)
private final Timer animationTimer;
// constructor from a File (checked to be a gif)
public B(File src) throws IOException{
if (!(new MimetypesFileTypeMap().getContentType(src.getPath()).equals("image/gif"))){
throw new IOException("File is not a gif. It's Mime Type is: " +
new MimetypesFileTypeMap().getContentType(src.getAbsolutePath()));
}
FileImageInputStream stream = new FileImageInputStream(src);
Iterator<ImageReader> readers = ImageIO.getImageReaders(stream);
ImageReader reader = null;
// loop through the availible ImageReaders, find one for .gif
while (readers.hasNext()){
reader = readers.next();
String metaFormat = reader.getOriginatingProvider().getNativeImageMetadataFormatName();
// if it's a gif
if ("gif".equalsIgnoreCase(reader.getFormatName()) && META_FORMAT.equals(metaFormat)){
break;
}else{
reader = null;
continue;
}
}// while (readers.hasNext())
// if no reader for gifs was found
if (reader == null){
throw new IOException("File could not be read as a gif");
}
reader.setInput(stream, false, false);
// Lists to be converted to arrays and set as the instance variables
ArrayList<BufferedImage> listFrames = new ArrayList<BufferedImage>();
ArrayList<Short> listFrameDurs = new ArrayList<Short>();
ArrayList<Short> listXs = new ArrayList<Short>();
ArrayList<Short> listYs = new ArrayList<Short>();
boolean unknownMeta = false;// asume that the metadata can be read until proven otherwise
// loop until there are no more frames (since that isn't known, break needs to be used)
for (int i = 0;true;i++){// equivalent of while(true) with a counter
IIOImage frame = null;
try{
frame = reader.readAll(i, null);
}catch(IndexOutOfBoundsException e){
break;// this means theres no more frames
}
listFrames.add((BufferedImage)frame.getRenderedImage());
if (unknownMeta){// if the metadata has already proven to be unreadable
continue;
}
IIOMetadata metadata = frame.getMetadata();
IIOMetadataNode rootNode = null;
try{
rootNode = (IIOMetadataNode) metadata.getAsTree(META_FORMAT);
}catch(IllegalArgumentException e){
// means that the metadata can't be read, it's in an unknown format
unknownMeta = true;
continue;
}
// get the duration of the current frame
IIOMetadataNode graphicControlExt = (IIOMetadataNode)rootNode.getElementsByTagName("GraphicControlExtension").item(0);
listFrameDurs.add(Short.parseShort(graphicControlExt.getAttribute("delayTime")));
// get the x and y offsets
try{
IIOMetadataNode imageDescrip = (IIOMetadataNode)rootNode.getElementsByTagName("ImageDescriptor").item(0);
listXs.add(Short.parseShort(imageDescrip.getAttribute("imageLeftPosition")));
listYs.add(Short.parseShort(imageDescrip.getAttribute("imageTopPosition")));
}catch(IndexOutOfBoundsException e){
e.printStackTrace();
listXs.add((short) 0);
listYs.add((short) 0);
}
}// for loop
reader.dispose();
// put the values in the lists into the instance variable arrays
frames = listFrames.toArray(new BufferedImage[0]);
// looping must be used because the ArrayList can't contian primitives
frameDurations = new short[listFrameDurs.size()];
for (int i = 0;i < frameDurations.length;i++){
frameDurations[i] = (short)(listFrameDurs.get(i) * 10);
}
xOffsets = new short[listXs.size()];
for (int i = 0;i < xOffsets.length;i++){
xOffsets[i] = listXs.get(i);
}
yOffsets = new short[listYs.size()];
for (int i = 0;i < yOffsets.length;i++){
yOffsets[i] = listYs.get(i);
}
size = findSize();
animationTimer = new Timer(frameDurations[0], null);
clearLayers();
}
// finds the size of the image in constructors
private final Dimension findSize(){
int greatestX = -1;
int greatestY = -1;
// loop through the frames and offsets, finding the greatest combination of the two
for (int i = 0;i < frames.length;i++){
if (greatestX < frames[i].getWidth() + xOffsets[i]){
greatestX = frames[i].getWidth() + xOffsets[i];
}
if (greatestY < frames[i].getHeight() + yOffsets[i]){
greatestY = frames[i].getHeight() + yOffsets[i];
}
}// loop
return new Dimension(greatestX, greatestY);
}// findSize
private BufferedImage getFrame(){
/* returning frames[frame] gives a perfect rendering of each frame (but only changed
* pixels), but when master is returned, even the first frame shows quality reduction
* (seen by slowing down the framerate). The issue is with drawing images to master
*/
Graphics2D g2d = master.createGraphics();
g2d.drawImage(frames[frame], xOffsets[frame], yOffsets[frame], null);
g2d.dispose();
return master;
}
public Dimension getSize(){
return size;
}
// adds a FrameChangeListener associated with a component to the Timer
public void registerComponent(Component c){
FrameChangeListener l = new FrameChangeListener(c);
animationTimer.addActionListener(l);
if (!animationTimer.isRunning()){
animationTimer.start();
}
}
// draws the image to the given Graphics context (registerComponent must be used for the image
// to animate properly)
public void draw(Graphics g){
g.drawImage(getFrame(), 0, 0, null);
}
// resets master
private void clearLayers(){
master = new BufferedImage((int)size.getWidth(), (int)size.getHeight(), frames[0].getType());
}
// class that listens for the Swing Timer.
private class FrameChangeListener implements ActionListener{
private final Component repaintComponent;
// the Components repaint method will be invoked whenever the animation changes frame
protected FrameChangeListener(Component c){
repaintComponent = c;
}
public void actionPerformed(ActionEvent e){
frame++;
int delay;
try{
delay = frameDurations[frame] * 10;
}catch(ArrayIndexOutOfBoundsException x){
frame = 0;
clearLayers();
delay = frameDurations[frame] * 10;
}
animationTimer.setDelay(delay);
repaintComponent.repaint();
}// actionPerformed
}// FrameChangeListener
}
And here is the image file I've been using to test:
And here is how it displays:
It would be much appreciated if anyone could help me solve this issue

The problem is this line from the clearLayers() method:
master = new BufferedImage((int)size.getWidth(), (int)size.getHeight(), frames[0].getType());
As the GIF uses a palette, the BufferedImage type will be TYPE_BYTE_INDEXED. However, if you pass this parameter to the BufferedImage constructor, it will use a default IndexColorModel (a built-in, fixed 256 color palette), not the palette from your GIF. Thus, the frames from the GIF will have to be dithered into the destination, as the colors doesn't match.
Instead, use TYPE_INT_RGB/TYPE_INT_ARGB for type, or use the constructor that also takes an IndexColorModel parameter and pass the IndexColorModel from the frames of the GIF.
In code:
master = new BufferedImage((int)size.getWidth(), (int)size.getHeight(), BufferedImage.TYPE_INT_ARGB);
Alternatively, the following should also work if all frames of the GIF uses the same palette (not necessarily the case):
master = new BufferedImage((int)size.getWidth(), (int)size.getHeight(), frames[0].getType(), (IndexColorModel) frames[0].getColorModel());
However, as the OP reports back the latter option doesn't work for him, the first option is probably safer. :-)

Related

Running out of ways to add a sound file in java

The trouble I'm having now is that I want to insert a sound within the explosion class, specifically in the run function. I have tried a number of ways to get the .WAV sound file to play but all of them failed.
Explosion:
import javax.swing.ImageIcon;
import javax.swing.JLabel;
public class Explosion implements Runnable {
ColoredBallPanel myPanel;
private JLabel img_label;
private ImageIcon imageIcon; // The explosion GIF
private int x_position; // The x position of the GIF.
private int y_position; // The Y position of the GIF.
private final int img_display_time = 650;
public Explosion(ColoredBallPanel panel, int x, int y) {
super();
myPanel = panel;
x_position = x;
y_position = y;
my_GIF();
}
/**
* Adds the GIF to the panel relative to where the balls have collided.
*/
public void my_GIF() {
imageIcon = new ImageIcon(
"C:\\Users\\Oscar\\Desktop\\Bangor Uni Y2 S1\\ICP-2150-0 Advanced Java Programming 201819\\Lab9\\explo.gif"); // sets
// the
// image
// icon
img_label = new JLabel();
img_label.setBounds(x_position, y_position, 200, 200);// Add image at position x_position, y_position
img_label.setIcon(imageIcon);
myPanel.add(img_label);
}
/**
* runs the thread on completion deletes the image
*/
#Override
public void run() {
try {
Thread.sleep(img_display_time); // Sleeps for a set amount of time.
} catch (InterruptedException e) {
}
img_label.setIcon(null); // Deletes the image.
img_label = null;
}
}
Try this:
Clip clip = AudioSystem.getClip();
AudioInputStream inputStream = AudioSystem.getAudioInputStream(
Explosion.class.getResourceAsStream("/path/to/sounds/" + url));
clip.open(inputStream);
clip.start();
There is a pretty good explanation here: https://stackoverflow.com/tags/javasound/info

Stopping a window from displaying till it is fully drawn?

I am working on a Java program that takes in a large amount of files (3000 max) with an associated array of 1/0's. Currently I have a visualization of the array where there is a grid where each box is filled black for 1 or white for 0. When drawn it runs well but takes around a minute to fully load (and potentially locks the computer up in the meantime.) Is there a way I can: 1, not display the window till it is done
(i.e JFrame create,
//draw window
frame.setVisible(true))
and 2, track the progress of the process so that I can use a progress bar with it?
edit: Can I run a thread to draw it and then simply make a while loop to only display it once the thread is completed?
In the example below, a SwingWorker sets pixels in a BufferedImage based on the data read from a random file. Note that Thread.sleep() is used to simulate latency; it is otherwise not required. You can add a JProgressBar as shown here.
Is there a better way to get simple colored boxes?
Yes. In the example below, each pixel represents one cell. For larger boxes, return a multiple of the image size, e.g.
#Override
public Dimension getPreferredSize() {
return new Dimension(2 * N, 2 * N);
}
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import java.io.BufferedInputStream;
import java.io.DataInputStream;
import java.io.FileInputStream;
import java.util.List;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingWorker;
/**
* #see https://stackoverflow.com/a/25043676/230513
*/
public class WorkerTest {
private static final int N = 256;
private final BooleanPanel panel = new BooleanPanel();
private class BooleanPanel extends JPanel {
private BufferedImage image;
public void setImage(BufferedImage bi) {
this.image = bi;
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g;
g2d.drawImage(image, 0, 0, getWidth(), getHeight(), null);
}
#Override
public Dimension getPreferredSize() {
return new Dimension(N, N);
}
}
private class BufferedImageWorker extends SwingWorker<BufferedImage, BufferedImage> {
#Override
protected BufferedImage doInBackground() throws Exception {
BufferedImage image = new BufferedImage(N, N, BufferedImage.TYPE_INT_ARGB);
try (DataInputStream dis = new DataInputStream(
new BufferedInputStream(new FileInputStream("/dev/random")))) {
for (int row = 0; row < N; row++) {
for (int col = 0; col < N; col++) {
image.setRGB(col, row, dis.readByte() < 0 ? 0xffffffff : 0xff000000);
}
Thread.sleep(40); // ~25 Hz
publish(image);
}
return image;
}
}
#Override
protected void process(List<BufferedImage> list) {
for (BufferedImage bi : list) {
panel.setImage(bi);
panel.repaint();
}
}
}
private void display() {
JFrame f = new JFrame("WorkerTest");
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.add(panel);
f.pack();
f.setLocationRelativeTo(null);
f.setVisible(true);
new BufferedImageWorker().execute();
}
public static void main(String[] args) {
EventQueue.invokeLater(() -> {
new WorkerTest().display();
});
}
}
I would definitely use a SwingWorker in this case. Basically, maybe something along these lines (I'm not sure what type of object your 'visualization' is, so for simplicity, I'll just say it's an Image). You can add this at the bottom of your class. You'll obviously have to edit it to make it work for you.
protected class DrawGridTask extends SwingWorker<Image, Object> {
ObjectToPutImageOn imageObject;
public DrawGridTask(ObjectToPutImageOn obj) {
this.imageObject = obj;
}
protected Image doInBackground() {
// generate your Image or graphic or whatever here
return Image;
}
protected void done() {
imageObject.drawThisCompletedImage(get());
}
}
To call this method, you would run (new DrawGridTask(objectToPutImageOn)).execute();
All the code in doInBackground() will run on it's own worker thread. Done() runs on the event dispatch thread, and gets the reference doInBackground() returns when it calls get().
There is more information here, including how to do progress updates at: http://docs.oracle.com/javase/tutorial/uiswing/concurrency/worker.html
Since I mentioned Images, if you do work with them, you might also want to take a look at the MediaTracker class, this can be very useful for blocking until an image is ready.

Java slideshow image delay using paintComponent

I am putting together a slideshow program that will measure a user's time spent on each slide. The slideshow goes through several different magic tricks. Each trick is shown twice. Interim images are shown between the repetition. Transition images are shown between each trick.
On the first repetition of a trick the JPanel color flashes on the screen after a click before the next image is shown. This doesn't happen during the second repetition of the same trick. It's possible that the image is taking too long to load.
Is there an easy way to pre-load the images so that there isn't a delay the first time through?
NOTE: Original code deleted.
EDIT 1/10/2013: This code now works on slower machines. trashgod's second addendum helped the most. The mouseClick control structure periodically asks SwingWorker classes to load 40 images or less of the current trick while also setting the used images to null. I have simplified my code down for this to just two Image[]s and added a main method so it stands alone. Images are still required to run though. This is now pretty bare bones code, and if you're trying to make a slideshow with a lot of images I think it would be a good place to start.
NOTE: I think I figured out how to properly implement SwingWorker while still using multiple Image[]s. trashgod and kleopatra is this implementation in-line with what you were suggesting? I didn't end up using publish and process since I couldn't figure out how to get that to work appropriately with an indexed array, but because the StringWorker doesn't load all images in the array (only 40), and the code calls StringWorker every 20 images, there should be a pretty good buffer.
EDIT 1/10/2013 Changed out MouseListener by instead extending MouseAdapter on my Mouse class. Also fixed my paintComponent method to include a call to super.paintComponent(g).
Added publish/process methods to my SwingWorker class ImageWorker. Added a wrapper class, ArrayWrapper to allow passing imageArray[i] and its corresponding index int i with publish to process.
package slideshow3;
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.MouseEvent;
import java.awt.event.MouseAdapter;
import java.io.File;
import java.io.IOException;
import javax.imageio.ImageIO;
import java.util.List;
public class SlideShow3 extends JFrame
{
//screenImage will be replaced with each new slide
private Image screenImage;
private int width;
private int height;
//Create panel for displaying images using paintComponent()
private SlideShow3.PaintPanel mainImagePanel;
//Used for keybinding
private Action escapeAction;
//Image array variables for each trick
private Image[] handCuffs; //h
private Image[] cups; //c
//Used to step through the trick arrays one image at a time
private int h = 0;
private int c = 0;
//Used by timeStamp() for documenting time per slide
private long time0 = 0;
private long time1;
public SlideShow3()
{
super();
//Create instance of each Image array
handCuffs = new Image[50];
cups = new Image[176];
//start(handCuffsString);
start("handCuffs");
try
{
screenImage = ImageIO.read(new File("images/begin1.jpg"));
}
catch (IOException nm)
{
System.out.println("begin");
System.out.println(nm.getMessage());
System.exit(0);
}
/******************************************
* Removes window framing. The next line sets fullscreen mode.
* Once fullscreen is set width and height are determined for the window
******************************************/
this.setUndecorated(true);
GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice().setFullScreenWindow(this);
width = this.getWidth();
height = this.getHeight();
//Mouse click binding to slide advance control structure
addMouseListener(new Mouse());
//Create panel so that I can use key binding which requires JComponent
mainImagePanel = new PaintPanel();
add(mainImagePanel);
/******************************************
* Key Binding
* ESC will exit the slideshow
******************************************/
// Key bound AbstractAction items
escapeAction = new EscapeAction();
// Gets the mainImagePanel InputMap and pairs the key to the action
mainImagePanel.getInputMap().put(KeyStroke.getKeyStroke("ESCAPE"), "doEscapeAction");
// This line pairs the AbstractAction enterAction to the action "doEnterAction"
mainImagePanel.getActionMap().put("doEscapeAction", escapeAction);
/******************************************
* End Key Binding
******************************************/
}
public static void main(String[] args)
{
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run()
{
SlideShow3 show = new SlideShow3();
show.setVisible(true);
}
});
}
//This method executes a specific SwingWorker class to preload images
public void start(String e)
{
if(e.equals("handCuffs"))
{
new ImageWorker(handCuffs.length, h, e).execute();
}
else if(e.equals("cups"))
{
new ImageWorker(cups.length, c, e).execute();
}
}
//Stretches and displays images in fullscreen window
private class PaintPanel extends JPanel
{
#Override
public void paintComponent(Graphics g)
{
if(screenImage != null)
{
super.paintComponent(g);
g.drawImage(screenImage, 0, 0, width, height, this);
}
}
}
/******************************************
* The following SwingWorker class Pre-loads all necessary images.
******************************************/
private class ArrayWrapper
{
private int i;
private Image image;
public ArrayWrapper(Image image, int i)
{
this.i = i;
this.image = image;
}
public int getIndex()
{
return i;
}
public Image getImage()
{
return image;
}
}
private class ImageWorker extends SwingWorker<Image[], ArrayWrapper>
{
private int currentPosition;
private int arraySize;
private String trickName;
private Image[] imageArray;
public ImageWorker(int arraySize, int currentPosition, String trick)
{
super();
this.currentPosition = currentPosition;
this.arraySize = arraySize;
this.trickName = trick;
}
#Override
public Image[] doInBackground()
{
imageArray = new Image[arraySize];
for(int i = currentPosition; i < currentPosition+40 && i < arraySize; i++)
{
try
{
imageArray[i] = ImageIO.read(new File("images/" + trickName + (i+1) + ".jpg"));
ArrayWrapper wrapArray = new ArrayWrapper(imageArray[i], i);
publish(wrapArray);
}
catch (IOException e)
{
System.out.println(trickName);
System.out.println(e.getMessage());
System.exit(0);
}
}
return imageArray;
}
#Override
public void process(List<ArrayWrapper> chunks)
{
for(ArrayWrapper element: chunks)
{
if(trickName.equals("handCuffs"))
{
handCuffs[element.getIndex()] = element.getImage();
}
else if(trickName.equals("cups"))
{
cups[element.getIndex()] = element.getImage();
}
}
}
#Override
public void done()
{
try
{
if(trickName.equals("handCuffs"))
{
handCuffs = get();
}
else if(trickName.equals("cups"))
{
cups = get();
}
}
catch(InterruptedException ignore){}
catch(java.util.concurrent.ExecutionException e)
{
String why = null;
Throwable cause = e.getCause();
if(cause != null)
{
why = cause.getMessage();
}
else
{
why = e.getMessage();
}
System.err.println("Error retrieving file: " + why);
}
}
}
/******************************************
* End SwingWorker Pre-Loading Classes
******************************************/
//Prints out time spent on each slide
public void timeStamp()
{
time1 = System.currentTimeMillis();
if(time0 != 0)
{
System.out.println(time1 - time0);
}
time0 = System.currentTimeMillis();
}
/******************************************
* User Input Classes for Key Binding Actions and Mouse Click Actions
******************************************/
private class EscapeAction extends AbstractAction
{
#Override
public void actionPerformed(ActionEvent e)
{
System.exit(0);
}
}
public class Mouse extends MouseAdapter
{
#Override
public void mouseClicked(MouseEvent e)
{
if(!(h<handCuffs.length) && !(c<cups.length))
{
timeStamp();
System.exit(0);
}
else if(h<handCuffs.length)
{
timeStamp();
screenImage = handCuffs[h];
repaint();
System.out.print("handCuffs[" + (h+1) + "]\t");
h++;
//purge used slides and refresh slide buffer
if(h == 20 || h == 40)
{
for(int i = 0; i < h; i++)
{
handCuffs[i] = null;
}
start("handCuffs");
}
if(h == 45)
{
start("cups");
}
}
else if(c<cups.length)
{
timeStamp();
screenImage = cups[c];
repaint();
System.out.print("cups[" + (c+1) + "]\t");
c++;
//purge used slides and refresh slide buffer
if(c == 20 || c == 40 || c == 60 || c == 80 || c == 100 || c == 120 || c == 140 || c == 160)
{
for(int i = 0; i < c; i++)
{
cups[i] = null;
}
start("cups");
}
}
}
}
/******************************************
* End User Input Classes for Key Binding Actions and Mouse Click Actions
******************************************/
}
This example uses a List<ImageIcon> as a cache of images returned by getImage(). Using getResource(), the delay is imperceptible. The next and previous buttons are bound to the Space key by default.
Addendum: You can control navigation by conditioning a button's setEnabled() state using an instance of javax.swing.Timer, for example.
Addendum: Your second example waits until the mouse is clicked to begin reading an image, an indeterminate process that may return a copy immediately or may not complete until after repaint(). Instead, begin reading the images in the background using ImageIO.read(), as shown here. You can process() your List<Image> and show progress, as seen here. The SwingWorker can be launched from the initial thread, running while you subsequently build your GUI on the EDT. You can display the first image as soon as it is processed.

Multiple instances of the same animated GIF in a Canvas (Java)

So I'm making a game where you can put bombs on the location of your character. Each bomb is associated with a GIF image when the bomb is displayed and eventually go BOOM (think about Bomberman).
The problem was, when i tried to paint more than one bomb on the screen, it was painted from the last frame of the GIF. Investigating, I found the method image.flush() to reset the GIF cicle but now the problem is that every time I paint a second bomb on the screen, the GIF cycle is reset for all previously bombs on screen.
Here is my constructor for each bomb:
public Tnt(int x, int y){
this.x = x;
this.y = y;
ImageIcon ii = new ImageIcon("src/main/resources/modelObjects/tnt.gif");
image = ii.getImage();
image.flush();
}
Every bomb i create enters an ArrayList (listTnt) and is removed after 6 secs, so i only paint the bombs already active.
Here is my method for drawing:
public void draw(Graphics2D g2d, JPanel board){
for(Tnt tnt: listTnt){
g2d.drawImage(tnt.getImage(), tnt.getX(), tnt.getY(), board);
}
}
EDIT: Seems that the problem was ImageIcon, since it reuses the image using Toolkit.getImage. Instead, Toolkit.createImage create a not reusable image.
Here is my new constructor for Tnt that worked perfectly:
public Tnt(int x, int y){
this.x = x;
this.y = y;
Toolkit t = Toolkit.getDefaultToolkit ();
image = t.createImage("src/main/resources/modelObjects/tnt.gif");
}
I dont even need image.flush() now. Thank you all.
The underlying Image is being reused amongst each ImageIcon.
Judging by the OpenJDK source code, it appears to be due to the fact that each simply requests the Image via Toolkit.getImage.
This method has a nifty caveat, however, which explains the issue at hand:
The underlying toolkit attempts to resolve multiple requests with the same filename to the same returned Image.
Instead, you should skip the ImageIcon step completely (since it's inappropriate to be using a Swing class unnecessarily in the first place), and instead call Toolkit.createImage, which states in the documentation:
The returned Image is a new object which will not be shared with any other caller of this method or its getImage variant.
Good luck.
As I did not know how to solve this, I tried #super_ solution and it works quite nicely. I share the code for anyone who wants an example. +1 to him
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.net.MalformedURLException;
import java.net.URL;
import javax.swing.ImageIcon;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
import javax.swing.Timer;
public class TestAnimatedGif {
private static final int IMAGE_COUNT = 9;
protected void initUI() {
JFrame frame = new JFrame(TestAnimatedGif.class.getSimpleName());
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
final JPanel panel = new JPanel();
frame.add(panel);
frame.setSize(600, 400);
frame.setVisible(true);
final Timer t = new Timer(1000, null);
t.addActionListener(new ActionListener() {
int count = 0;
#Override
public void actionPerformed(ActionEvent e) {
if (count < IMAGE_COUNT) {
try {
JLabel image = new JLabel(new ImageIcon(Toolkit.getDefaultToolkit().createImage(
new URL("http://www.sitevip.net/gifs/bomba/BOMB-B_animado.gif"))));
panel.add(image);
count++;
panel.revalidate();
panel.repaint();
System.err.println("image added");
} catch (MalformedURLException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
} else {
t.stop();
}
}
});
t.start();
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
new TestAnimatedGif().initUI();
}
});
}
}

stitch images together in java

I'm trying to stitch some images together using java. I have a bunch of images I'd like to stitch together and they are all the same dimensions so it's really just a question of lining them up next to each other I suppose. I have it working but it's very slow and probably very memory intensive. I'm wondering if there's an easier way:
public static void main(String[] args) throws IOException
{
int dim = 256;
BufferedImage merged = null;
for(int y = 0; y<10;y++)
{
for(int x = 0; x<10;x++)
{
URL url = new URL(someURL);
BufferedImage nextImage = ImageIO.read(url);
if(merged==null)
merged=nextImage;
else
{
BufferedImage tempMerged;
tempMerged = new BufferedImage(10*dim,10*dim,merged.getType());
//Write first image
for(int xx=0;xx<merged.getWidth();xx++)
for(int yy=0;yy<merged.getHeight();yy++)
tempMerged.setRGB(xx,yy,merged.getRGB(xx,yy));
//Write img2
for(int xx=0;xx<dim;xx++)
{
for(int yy=0;yy<dim;yy++)
{
int destX = (x*dim)+xx;
int destY = (y*dim)+yy;
tempMerged.setRGB(destX,destY,nextImage.getRGB(xx,yy));
}
}
merged=tempMerged;
}
System.out.println("Stitched image at "+x+","+y);
}
}
ImageIO.write(merged, "png", new File("merged.png"));
}
#Thomas: You'd have to create a new image of twice the size of the source images (e.g. for 2x 512x512 the new image should be 512x1024 or 1024x512). Then you'd render the source images to the respective area/rectangle of the target image
E.G. TiledImageWrite.java
import java.awt.image.BufferedImage;
import java.awt.*;
import javax.swing.*;
import java.net.URL;
import java.io.File;
import javax.imageio.ImageIO;
class TiledImageWrite {
public static void main(String[] args) throws Exception {
URL dayStromloUrl = new URL("https://i.stack.imgur.com/OVOg3.jpg");
URL nightStromloUrl = new URL("https://i.stack.imgur.com/lxthA.jpg");
final BufferedImage dayStromloImage = ImageIO.read(dayStromloUrl);
final BufferedImage nightStromloImage = ImageIO.read(nightStromloUrl);
final int width = dayStromloImage.getWidth();
final int height = dayStromloImage.getHeight();;
final BufferedImage columnImage =
new BufferedImage(width,2*height,BufferedImage.TYPE_INT_RGB);
final BufferedImage rowImage =
new BufferedImage(2*width,height,BufferedImage.TYPE_INT_RGB);
SwingUtilities.invokeLater( new Runnable() {
public void run() {
JPanel gui = new JPanel(new BorderLayout(3,3));
Graphics2D g2dColumn = columnImage.createGraphics();
g2dColumn.drawImage(dayStromloImage,0,0, null);
// start this one at 'height' down the final image
g2dColumn.drawImage(nightStromloImage,0,height, null);
Graphics2D g2dRow = rowImage.createGraphics();
g2dRow.drawImage(dayStromloImage,0,0, null);
// start this one at 'width' across the final image
g2dRow.drawImage(nightStromloImage,width,0, null);
gui.add(new JLabel(new ImageIcon(columnImage)),BorderLayout.CENTER);
gui.add(new JLabel(new ImageIcon(rowImage)),BorderLayout.SOUTH);
JOptionPane.showMessageDialog(null, gui);
}
} );
ImageIO.write(columnImage, "png", new File("column.png"));
ImageIO.write(rowImage, "png", new File("row.png"));
}
}
column.png
AFAIK what you're doing here is to write layers to a image. However, the png format doesn't support this.
You'd have to create a new image of twice the size of the source images (e.g. for 2x 512x512 the new image should be 512x1024 or 1024x512). Then you'd render the source images to the respective area/rectangle of the target image.
I figured out why it was going slow. In reality, I didn't want to merge images together, but rather stitch together a bunch of images. What I was doing was rewriting the original image everything when all I really want to do is add to it. Much faster now!

Categories

Resources