|
| 1 | +package org.approvaltests.awt; |
| 2 | + |
| 3 | +// GifSequenceWriter.java |
| 4 | +// |
| 5 | +// Created by Elliot Kroo on 2009-04-25. |
| 6 | +// |
| 7 | +// This work is licensed under the Creative Commons Attribution 3.0 Unported |
| 8 | +// License. To view a copy of this license, visit |
| 9 | +// http://creativecommons.org/licenses/by/3.0/ or send a letter to Creative |
| 10 | +// Commons, 171 Second Street, Suite 300, San Francisco, California, 94105, USA. |
| 11 | + |
| 12 | + |
| 13 | +import javax.imageio.*; |
| 14 | +import javax.imageio.metadata.*; |
| 15 | +import javax.imageio.stream.*; |
| 16 | +import java.awt.image.*; |
| 17 | +import java.io.*; |
| 18 | +import java.util.Iterator; |
| 19 | + |
| 20 | +public class GifSequenceWriter implements AutoCloseable{ |
| 21 | + protected ImageWriter gifWriter; |
| 22 | + protected ImageWriteParam imageWriteParam; |
| 23 | + protected IIOMetadata imageMetaData; |
| 24 | + |
| 25 | + /** |
| 26 | + * Creates a new GifSequenceWriter |
| 27 | + * |
| 28 | + * @param outputStream the ImageOutputStream to be written to |
| 29 | + * @param imageType one of the imageTypes specified in BufferedImage |
| 30 | + * @param timeBetweenFramesMS the time between frames in miliseconds |
| 31 | + * @param loopContinuously wether the gif should loop repeatedly |
| 32 | + * @throws IIOException if no gif ImageWriters are found |
| 33 | + * |
| 34 | + * @author Elliot Kroo (elliot[at]kroo[dot]net) |
| 35 | + */ |
| 36 | + public GifSequenceWriter( |
| 37 | + ImageOutputStream outputStream, |
| 38 | + int imageType, |
| 39 | + int timeBetweenFramesMS, |
| 40 | + boolean loopContinuously) throws IIOException, IOException { |
| 41 | + // my method to create a writer |
| 42 | + gifWriter = getWriter(); |
| 43 | + imageWriteParam = gifWriter.getDefaultWriteParam(); |
| 44 | + ImageTypeSpecifier imageTypeSpecifier = |
| 45 | + ImageTypeSpecifier.createFromBufferedImageType(imageType); |
| 46 | + |
| 47 | + imageMetaData = |
| 48 | + gifWriter.getDefaultImageMetadata(imageTypeSpecifier, |
| 49 | + imageWriteParam); |
| 50 | + |
| 51 | + String metaFormatName = imageMetaData.getNativeMetadataFormatName(); |
| 52 | + |
| 53 | + IIOMetadataNode root = (IIOMetadataNode) |
| 54 | + imageMetaData.getAsTree(metaFormatName); |
| 55 | + |
| 56 | + IIOMetadataNode graphicsControlExtensionNode = getNode( |
| 57 | + root, |
| 58 | + "GraphicControlExtension"); |
| 59 | + |
| 60 | + graphicsControlExtensionNode.setAttribute("disposalMethod", "none"); |
| 61 | + graphicsControlExtensionNode.setAttribute("userInputFlag", "FALSE"); |
| 62 | + graphicsControlExtensionNode.setAttribute( |
| 63 | + "transparentColorFlag", |
| 64 | + "FALSE"); |
| 65 | + graphicsControlExtensionNode.setAttribute( |
| 66 | + "delayTime", |
| 67 | + Integer.toString(timeBetweenFramesMS / 10)); |
| 68 | + graphicsControlExtensionNode.setAttribute( |
| 69 | + "transparentColorIndex", |
| 70 | + "0"); |
| 71 | + |
| 72 | + IIOMetadataNode commentsNode = getNode(root, "CommentExtensions"); |
| 73 | + commentsNode.setAttribute("CommentExtension", "Created by MAH"); |
| 74 | + |
| 75 | + IIOMetadataNode appEntensionsNode = getNode( |
| 76 | + root, |
| 77 | + "ApplicationExtensions"); |
| 78 | + |
| 79 | + IIOMetadataNode child = new IIOMetadataNode("ApplicationExtension"); |
| 80 | + |
| 81 | + child.setAttribute("applicationID", "NETSCAPE"); |
| 82 | + child.setAttribute("authenticationCode", "2.0"); |
| 83 | + |
| 84 | + int loop = loopContinuously ? 0 : 1; |
| 85 | + |
| 86 | + child.setUserObject(new byte[]{ 0x1, (byte) (loop & 0xFF), (byte) |
| 87 | + ((loop >> 8) & 0xFF)}); |
| 88 | + appEntensionsNode.appendChild(child); |
| 89 | + |
| 90 | + imageMetaData.setFromTree(metaFormatName, root); |
| 91 | + |
| 92 | + gifWriter.setOutput(outputStream); |
| 93 | + |
| 94 | + gifWriter.prepareWriteSequence(null); |
| 95 | + } |
| 96 | + |
| 97 | + public void writeToSequence(RenderedImage img) throws IOException { |
| 98 | + gifWriter.writeToSequence( |
| 99 | + new IIOImage( |
| 100 | + img, |
| 101 | + null, |
| 102 | + imageMetaData), |
| 103 | + imageWriteParam); |
| 104 | + } |
| 105 | + |
| 106 | + /** |
| 107 | + * Close this GifSequenceWriter object. This does not close the underlying |
| 108 | + * stream, just finishes off the GIF. |
| 109 | + */ |
| 110 | + public void close() throws IOException { |
| 111 | + gifWriter.endWriteSequence(); |
| 112 | + } |
| 113 | + |
| 114 | + /** |
| 115 | + * Returns the first available GIF ImageWriter using |
| 116 | + * ImageIO.getImageWritersBySuffix("gif"). |
| 117 | + * |
| 118 | + * @return a GIF ImageWriter object |
| 119 | + * @throws IIOException if no GIF image writers are returned |
| 120 | + */ |
| 121 | + private static ImageWriter getWriter() throws IIOException { |
| 122 | + Iterator<ImageWriter> iter = ImageIO.getImageWritersBySuffix("gif"); |
| 123 | + if(!iter.hasNext()) { |
| 124 | + throw new IIOException("No GIF Image Writers Exist"); |
| 125 | + } else { |
| 126 | + return iter.next(); |
| 127 | + } |
| 128 | + } |
| 129 | + |
| 130 | + /** |
| 131 | + * Returns an existing child node, or creates and returns a new child node (if |
| 132 | + * the requested node does not exist). |
| 133 | + * |
| 134 | + * @param rootNode the <tt>IIOMetadataNode</tt> to search for the child node. |
| 135 | + * @param nodeName the name of the child node. |
| 136 | + * |
| 137 | + * @return the child node, if found or a new node created with the given name. |
| 138 | + */ |
| 139 | + private static IIOMetadataNode getNode( |
| 140 | + IIOMetadataNode rootNode, |
| 141 | + String nodeName) { |
| 142 | + int nNodes = rootNode.getLength(); |
| 143 | + for (int i = 0; i < nNodes; i++) { |
| 144 | + if (rootNode.item(i).getNodeName().compareToIgnoreCase(nodeName) |
| 145 | + == 0) { |
| 146 | + return((IIOMetadataNode) rootNode.item(i)); |
| 147 | + } |
| 148 | + } |
| 149 | + IIOMetadataNode node = new IIOMetadataNode(nodeName); |
| 150 | + rootNode.appendChild(node); |
| 151 | + return(node); |
| 152 | + } |
| 153 | + |
| 154 | + /** |
| 155 | + public GifSequenceWriter( |
| 156 | + BufferedOutputStream outputStream, |
| 157 | + int imageType, |
| 158 | + int timeBetweenFramesMS, |
| 159 | + boolean loopContinuously) { |
| 160 | + */ |
| 161 | + |
| 162 | + public static void main(String[] args) throws Exception { |
| 163 | + if (args.length > 1) { |
| 164 | + // grab the output image type from the first image in the sequence |
| 165 | + BufferedImage firstImage = ImageIO.read(new File(args[0])); |
| 166 | + |
| 167 | + // create a new BufferedOutputStream with the last argument |
| 168 | + ImageOutputStream output = |
| 169 | + new FileImageOutputStream(new File(args[args.length - 1])); |
| 170 | + |
| 171 | + // create a gif sequence with the type of the first image, 1 second |
| 172 | + // between frames, which loops continuously |
| 173 | + GifSequenceWriter writer = |
| 174 | + new GifSequenceWriter(output, firstImage.getType(), 1, false); |
| 175 | + |
| 176 | + // write out the first image to our sequence... |
| 177 | + writer.writeToSequence(firstImage); |
| 178 | + for(int i=1; i<args.length-1; i++) { |
| 179 | + BufferedImage nextImage = ImageIO.read(new File(args[i])); |
| 180 | + writer.writeToSequence(nextImage); |
| 181 | + } |
| 182 | + |
| 183 | + writer.close(); |
| 184 | + output.close(); |
| 185 | + } else { |
| 186 | + System.out.println( |
| 187 | + "Usage: java GifSequenceWriter [list of gif files] [output file]"); |
| 188 | + } |
| 189 | + } |
| 190 | +} |
0 commit comments