package chatty.util.gif; import chatty.util.gif.GifDecoder.GifImage; import java.awt.MediaTracker; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.net.URL; import java.security.MessageDigest; import java.util.Arrays; import java.util.HashSet; import java.util.Set; import java.util.logging.Logger; import javax.imageio.ImageIO; import javax.imageio.stream.ImageOutputStream; import javax.swing.ImageIcon; /** * * @author tduva */ public class GifUtil { private static final Logger LOGGER = Logger.getLogger(GifUtil.class.getName()); /** * Loads a GIF from the given URL, fixing FPS, or just creates an ImageIcon * directly if it's not a valid GIF. * * @param url The URL (local or internet) to get the image data from * @return The created ImageIcon, or null if an error occured creating the * image * @throws Exception When an error occured loading the image */ public static ImageIcon getGifFromUrl(URL url) throws Exception { try (InputStream input = url.openStream()) { // Use readAllBytes() because GifDecoder doesn't handle streams well byte[] imageData = readAllBytes(input); ImageIcon image = null; try { //System.out.println(hash(imageData)+" "+url); image = fixGifFps(imageData); } catch (Exception ex) { /** * If not a GIF, or another error occured, just create the image * normally. */ image = new ImageIcon(imageData); } if (image.getImageLoadStatus() == MediaTracker.ERRORED) { return null; } return image; } } /** * Decodes and re-writes the animated GIF with modified FPS. Any delay * smaller than 10ms is set to 100ms. Yes, this is kind of ugly, but it * worked best with the GIF emotes I tested. * * @param imageData * @return * @throws IOException */ private static ImageIcon fixGifFps(byte[] imageData) throws IOException { GifImage gif = GifDecoder.read(imageData); if (shouldPreferRegular(imageData)) { ImageIcon icon = new ImageIcon(imageData); icon.setDescription("GIF (Exception)"); return icon; } if (DONT_FIX.contains(hash(imageData))) { /** * Don't try to fix some GIFs, which look better originally. This is * obviously not ideal, but until a method is found that works well * for ALL GIFs, this must do. */ ImageIcon icon = new ImageIcon(imageData); icon.setDescription("GIF (Dont fix)"); return icon; } ByteArrayOutputStream bos = new ByteArrayOutputStream(); try (ImageOutputStream output = ImageIO.createImageOutputStream(bos)) { GifSequenceWriter w = new GifSequenceWriter(output, gif.getFrame(0).getType(), true); for (int i = 0; i < gif.getFrameCount(); i++) { int delay = gif.getDelay(i) * 10; if (delay <= 10) { delay = 100; } w.writeToSequence(gif.getFrame(i), delay); } w.close(); } ImageIcon icon = new ImageIcon(bos.toByteArray()); icon.setDescription("GIF"); return icon; } private static boolean shouldPreferRegular(byte[] imageData) { try { GifDecoderE.GifImage gif = GifDecoderE.read(imageData); if (invalidDelay(gif)) { System.out.println("Invalid delay"); return false; } for (int i = 0; i < gif.getFrameCount(); i++) { gif.getFrame(i); } } catch (Exception ex) { System.out.println("Exception "+ex); return true; } return false; } private static boolean invalidDelay(GifDecoderE.GifImage gif) { for (int i = 0; i < gif.getFrameCount(); i++) { if (gif.getDelay(i) == 0) { return true; } } return false; } private static final Set<String> DONT_FIX = new HashSet<>(Arrays.asList(new String[]{ "0571f6a1a125404f5e6f75ae206381e381c76304", // SourPls "52ac1713a09fe3df1b8794d47a326e80c23f06dd", // SourPls })); /** * Read all bytes from the stream into a byte array. * * @param input * @return * @throws IOException */ private static byte[] readAllBytes(InputStream input) throws IOException { ByteArrayOutputStream result = new ByteArrayOutputStream(); byte[] buffer = new byte[1024]; int length; while ((length = input.read(buffer, 0, buffer.length)) != -1) { result.write(buffer, 0, length); } return result.toByteArray(); } private static String hash(byte[] input) { try { MessageDigest md = MessageDigest.getInstance("SHA-1"); return byteArrayToHexString(md.digest(input)); } catch (Exception ex) { // Fail silently } return null; } private static String byteArrayToHexString(byte[] b) { String result = ""; for (int i = 0; i < b.length; i++) { result += Integer.toString((b[i] & 0xff) + 0x100, 16).substring(1); } return result; } public static class GifDecoderException extends RuntimeException { } }