In trying to make an animated GIF from an image with transparency this code is producing a ghosting effect from the earlier image frames.
Image of 3rd frame, which shows the first two frames beneath the final frame
How can the code be altered to produce a 'clean' animated image for all frames?
Note: The code uses two methods, written by GeoffTitmus & Maxideon, that were formerly on the Oracle forums site. The links I first saw referenced in the code (in the JavaDocs section) have long since disappeared, so were removed. Other than that, only a few JavaDoc warnings were corrected, otherwise they are exactly as found from around the internet or as used in my older code projects.
import java.awt.*;
import java.awt.image.*;
import javax.swing.*;
import javax.swing.border.EmptyBorder;
import java.io.*;
import javax.imageio.*;
import javax.imageio.metadata.IIOInvalidTreeException;
import javax.imageio.stream.*;
import javax.imageio.metadata.*;
import org.w3c.dom.Node;
import java.net.URL;
public class AnimatedGifWithTransparency {
private JComponent ui = null;
public final String PATH = "https://i.stack.imgur.com/P59NF.png";
private BufferedImage image;
AnimatedGifWithTransparency() {
try {
initUI();
} catch (IOException ex) {
ex.printStackTrace();
}
}
/**
* @author GeoffTitmus
* @param out OutputStream A stream in which to store the animation.
* @param frames BufferedImage[] Array of BufferedImages, the frames of the
* animation.
* @param delayTimes String[] Array of Strings, representing the frame delay
* times.
* @throws Exception
*/
public static void saveAnimate(OutputStream out, BufferedImage[] frames, String[] delayTimes) throws Exception {
ImageWriter iw = ImageIO.getImageWritersByFormatName("gif").next();
ImageOutputStream ios = ImageIO.createImageOutputStream(out);
iw.setOutput(ios);
iw.prepareWriteSequence(null);
for (int i = 0; i < frames.length; i++) {
BufferedImage src = frames[i];
ImageWriteParam iwp = iw.getDefaultWriteParam();
IIOMetadata metadata = iw.getDefaultImageMetadata(new ImageTypeSpecifier(src), iwp);
configure(metadata, delayTimes[i], i);
IIOImage ii = new IIOImage(src, null, metadata);
iw.writeToSequence(ii, null);
}
iw.endWriteSequence();
ios.close();
}
/**
* @author Maxideon
* @param meta
* @param delayTime String Frame delay for this frame.
* @param imageIndex
*/
public static void configure(IIOMetadata meta,
String delayTime,
int imageIndex) {
String metaFormat = meta.getNativeMetadataFormatName();
if (!"javax_imageio_gif_image_1.0".equals(metaFormat)) {
throw new IllegalArgumentException(
"Unfamiliar gif metadata format: " + metaFormat);
}
Node root = meta.getAsTree(metaFormat);
//find the GraphicControlExtension node
Node child = root.getFirstChild();
while (child != null) {
if ("GraphicControlExtension".equals(child.getNodeName())) {
break;
}
child = child.getNextSibling();
}
IIOMetadataNode gce = (IIOMetadataNode) child;
gce.setAttribute("userDelay", "FALSE");
gce.setAttribute("delayTime", delayTime);
//only the first node needs the ApplicationExtensions node
if (imageIndex == 0) {
IIOMetadataNode aes
= new IIOMetadataNode("ApplicationExtensions");
IIOMetadataNode ae
= new IIOMetadataNode("ApplicationExtension");
ae.setAttribute("applicationID", "NETSCAPE");
ae.setAttribute("authenticationCode", "2.0");
byte[] uo = new byte[]{
//last two bytes is an unsigned short (little endian) that
//indicates the the number of times to loop.
//0 means loop forever.
0x1, 0x0, 0x0
};
ae.setUserObject(uo);
aes.appendChild(ae);
root.appendChild(aes);
}
try {
meta.setFromTree(metaFormat, root);
} catch (IIOInvalidTreeException e) {
//shouldn't happen
throw new Error(e);
}
}
private BufferedImage getShiftedImage(int frame) {
int w = image.getWidth();
int h = image.getHeight();
BufferedImage bi = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB);
Graphics g = bi.getGraphics();
int xOffPos = frame*w/3;
int xOffNeg = xOffPos-w;
g.drawImage(image, xOffNeg, 0, ui);
g.drawImage(image, xOffPos, 0, ui);
g.dispose();
return bi;
}
public final void initUI() throws IOException {
if (ui != null) {
return;
}
image = ImageIO.read(new URL(PATH));
ui = new JPanel(new BorderLayout(4, 4));
ui.setBorder(new EmptyBorder(4, 4, 4, 4));
BufferedImage[] frames = {
getShiftedImage(0),
getShiftedImage(1),
getShiftedImage(2)
};
String[] delayTimes = {
"50","50","50"
};
ByteArrayOutputStream baos = new ByteArrayOutputStream();
try {
saveAnimate(baos, frames, delayTimes);
} catch (Exception ex) {
ex.printStackTrace();
return;
}
byte[] bytes = baos.toByteArray();
ui.add(new JLabel(new ImageIcon(bytes)));
}
public JComponent getUI() {
return ui;
}
public static void main(String[] args) {
Runnable r = () -> {
AnimatedGifWithTransparency o = new AnimatedGifWithTransparency();
JFrame f = new JFrame(o.getClass().getSimpleName());
f.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
f.setLocationByPlatform(true);
f.setContentPane(o.getUI());
f.pack();
f.setMinimumSize(f.getSize());
f.setVisible(true);
};
SwingUtilities.invokeLater(r);
}
}