001/** 002 * Copyright (c) 2011, The University of Southampton and the individual contributors. 003 * All rights reserved. 004 * 005 * Redistribution and use in source and binary forms, with or without modification, 006 * are permitted provided that the following conditions are met: 007 * 008 * * Redistributions of source code must retain the above copyright notice, 009 * this list of conditions and the following disclaimer. 010 * 011 * * Redistributions in binary form must reproduce the above copyright notice, 012 * this list of conditions and the following disclaimer in the documentation 013 * and/or other materials provided with the distribution. 014 * 015 * * Neither the name of the University of Southampton nor the names of its 016 * contributors may be used to endorse or promote products derived from this 017 * software without specific prior written permission. 018 * 019 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 020 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 021 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 022 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 023 * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 024 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 025 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 026 * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 027 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 028 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 029 */ 030package org.openimaj.demos.sandbox.image.gif; 031 032// 033// GifSequenceWriter.java 034// 035// Created by Elliot Kroo on 2009-04-25. 036// 037// This work is licensed under the Creative Commons Attribution 3.0 Unported 038// License. To view a copy of this license, visit 039// http://creativecommons.org/licenses/by/3.0/ or send a letter to Creative 040// Commons, 171 Second Street, Suite 300, San Francisco, California, 94105, USA. 041 042import java.awt.image.BufferedImage; 043import java.awt.image.RenderedImage; 044import java.io.File; 045import java.io.FileNotFoundException; 046import java.io.IOException; 047import java.io.OutputStream; 048import java.util.Iterator; 049import java.util.List; 050 051import javax.imageio.IIOException; 052import javax.imageio.IIOImage; 053import javax.imageio.ImageIO; 054import javax.imageio.ImageTypeSpecifier; 055import javax.imageio.ImageWriteParam; 056import javax.imageio.ImageWriter; 057import javax.imageio.metadata.IIOMetadata; 058import javax.imageio.metadata.IIOMetadataNode; 059import javax.imageio.stream.FileImageOutputStream; 060import javax.imageio.stream.ImageOutputStream; 061import javax.imageio.stream.MemoryCacheImageOutputStream; 062 063import org.openimaj.image.Image; 064import org.openimaj.image.ImageUtilities; 065 066/** 067 * A {@link GifSequenceWriter} uses java {@link ImageIO} to write animated gifs. 068 * Taken wholesale from: http://elliot.kroo.net/software/java/GifSequenceWriter/ 069 * and modified to play nicely with openimaj 070 * 071 * @author Elliot Kroo (elliot[at]kroo[dot]net), Sina Samangooei 072 * (ss@ecs.soton.ac.uk) 073 * 074 */ 075public class GifSequenceWriter { 076 protected ImageWriter gifWriter; 077 protected ImageWriteParam imageWriteParam; 078 protected IIOMetadata imageMetaData; 079 private boolean delayedInit; 080 private ImageOutputStream outputStream; 081 private int timeBetweenFramesMS; 082 private boolean loopContinuously; 083 084 /** 085 * Creates a new GifSequenceWriter 086 * 087 * @param outputStream 088 * the ImageOutputStream to be written to 089 * @param imageType 090 * one of the imageTypes specified in BufferedImage 091 * @param timeBetweenFramesMS 092 * the time between frames in miliseconds 093 * @param loopContinuously 094 * wether the gif should loop repeatedly 095 * @throws IIOException 096 * if no gif ImageWriters are found 097 * @throws IOException 098 */ 099 public GifSequenceWriter(ImageOutputStream outputStream, int imageType,int timeBetweenFramesMS, boolean loopContinuously) throws IIOException, IOException { 100 this.delayedInit = false; 101 init(outputStream,imageType,timeBetweenFramesMS,loopContinuously); 102 } 103 104 /** 105 * Creates a new GifSequenceWriter. Not specifying the imageType means that initialisation of the writer is delayed until the first image 106 * is written 107 * 108 * @param outputStream 109 * the ImageOutputStream to be written to 110 * @param timeBetweenFramesMS 111 * the time between frames in miliseconds 112 * @param loopContinuously 113 * whether the gif should loop repeatedly 114 * @throws IIOException 115 * if no gif ImageWriters are found 116 * @throws IOException 117 */ 118 public GifSequenceWriter(ImageOutputStream outputStream, int timeBetweenFramesMS, boolean loopContinuously) throws IIOException, IOException { 119 this.delayedInit = true; 120 this.outputStream = outputStream; 121 this.timeBetweenFramesMS = timeBetweenFramesMS; 122 this.loopContinuously = loopContinuously; 123 } 124 125 private void init(ImageOutputStream outputStream, int imageType,int timeBetweenFramesMS, boolean loopContinuously) throws IOException { 126 // my method to create a writer 127 gifWriter = getWriter(); 128 imageWriteParam = gifWriter.getDefaultWriteParam(); 129 ImageTypeSpecifier imageTypeSpecifier = ImageTypeSpecifier.createFromBufferedImageType(imageType); 130 131 imageMetaData = gifWriter.getDefaultImageMetadata(imageTypeSpecifier,imageWriteParam); 132 133 String metaFormatName = imageMetaData.getNativeMetadataFormatName(); 134 135 IIOMetadataNode root = (IIOMetadataNode) imageMetaData.getAsTree(metaFormatName); 136 137 IIOMetadataNode graphicsControlExtensionNode = getNode(root,"GraphicControlExtension"); 138 139 graphicsControlExtensionNode.setAttribute("disposalMethod", "none"); 140 graphicsControlExtensionNode.setAttribute("userInputFlag", "FALSE"); 141 graphicsControlExtensionNode.setAttribute("transparentColorFlag", 142 "FALSE"); 143 graphicsControlExtensionNode.setAttribute("delayTime", 144 Integer.toString(timeBetweenFramesMS / 10)); 145 graphicsControlExtensionNode.setAttribute("transparentColorIndex", "0"); 146 147 IIOMetadataNode commentsNode = getNode(root, "CommentExtensions"); 148 commentsNode.setAttribute("CommentExtension", "Created by MAH"); 149 150 IIOMetadataNode appEntensionsNode = getNode(root, 151 "ApplicationExtensions"); 152 153 IIOMetadataNode child = new IIOMetadataNode("ApplicationExtension"); 154 155 child.setAttribute("applicationID", "NETSCAPE"); 156 child.setAttribute("authenticationCode", "2.0"); 157 158 int loop = loopContinuously ? 0 : 1; 159 160 child.setUserObject(new byte[] { 0x1, (byte) (loop & 0xFF), 161 (byte) ((loop >> 8) & 0xFF) }); 162 appEntensionsNode.appendChild(child); 163 164 imageMetaData.setFromTree(metaFormatName, root); 165 166 gifWriter.setOutput(outputStream); 167 168 gifWriter.prepareWriteSequence(null); 169 delayedInit = false; 170 } 171 172 /** 173 * Write an image to the sequence 174 * 175 * @param img 176 * @throws IOException 177 */ 178 public void writeToSequence(Image<?, ?> img) throws IOException { 179 BufferedImage bimg = ImageUtilities.createBufferedImage(img); 180 if(delayedInit){ 181 init(outputStream, bimg.getType(), timeBetweenFramesMS, loopContinuously); 182 } 183 writeToSequence(bimg); 184 } 185 186 private void writeToSequence(RenderedImage img) throws IOException { 187 gifWriter.writeToSequence(new IIOImage(img, null, imageMetaData), 188 imageWriteParam); 189 } 190 191 /** 192 * Close this GifSequenceWriter object. This does not close the underlying 193 * stream, just finishes off the GIF. 194 * 195 * @throws IOException 196 */ 197 public void close() throws IOException { 198 gifWriter.endWriteSequence(); 199 } 200 201 /** 202 * Returns the first available GIF ImageWriter using 203 * ImageIO.getImageWritersBySuffix("gif"). 204 * 205 * @return a GIF ImageWriter object 206 * @throws IIOException 207 * if no GIF image writers are returned 208 */ 209 private static ImageWriter getWriter() throws IIOException { 210 Iterator<ImageWriter> iter = ImageIO.getImageWritersBySuffix("gif"); 211 if (!iter.hasNext()) { 212 throw new IIOException("No GIF Image Writers Exist"); 213 } else { 214 return iter.next(); 215 } 216 } 217 218 /** 219 * Returns an existing child node, or creates and returns a new child node 220 * (if the requested node does not exist). 221 * 222 * @param rootNode 223 * the <tt>IIOMetadataNode</tt> to search for the child node. 224 * @param nodeName 225 * the name of the child node. 226 * 227 * @return the child node, if found or a new node created with the given 228 * name. 229 */ 230 private static IIOMetadataNode getNode(IIOMetadataNode rootNode, 231 String nodeName) { 232 int nNodes = rootNode.getLength(); 233 for (int i = 0; i < nNodes; i++) { 234 if (rootNode.item(i).getNodeName().compareToIgnoreCase(nodeName) == 0) { 235 return ((IIOMetadataNode) rootNode.item(i)); 236 } 237 } 238 IIOMetadataNode node = new IIOMetadataNode(nodeName); 239 rootNode.appendChild(node); 240 return (node); 241 } 242 243 /** 244 * Write an array of images to a gif file. The type of the output gif is taken from the first frame. 245 * All future frames are assumed to be the same type. 246 * 247 * @param frames 248 * @param timeBetweenFramesMS 249 * @param loopContinuously 250 * @param file 251 * 252 * @throws IOException 253 */ 254 public static void writeGif(List<Image<?,?>> frames,int timeBetweenFramesMS, boolean loopContinuously,File file) throws IOException{ 255 ImageOutputStream output = new FileImageOutputStream(file); 256 try { 257 writeGif(frames,timeBetweenFramesMS,loopContinuously,output); 258 } finally{ 259 output.close(); 260 } 261 } 262 263 /** 264 * Construct a {@link GifSequenceWriter} which writes to a file 265 * @param timeBetweenFramesMS 266 * @param loopContinuously 267 * @param f 268 * @return {@link GifSequenceWriter} instance 269 * 270 * @throws IOException 271 * @throws FileNotFoundException 272 * @throws IIOException 273 */ 274 public static GifSequenceWriter createWriter(int timeBetweenFramesMS, boolean loopContinuously, File f) throws IIOException, FileNotFoundException, IOException { 275 return new GifSequenceWriter(new FileImageOutputStream(f),timeBetweenFramesMS,loopContinuously); 276 } 277 278 /** 279 * Write an array of images to a gif output stream 280 * 281 * @param frames 282 * @param timeBetweenFramesMS 283 * @param loopContinuously 284 * @param stream 285 * @throws IOException 286 */ 287 public static void writeGif(List<Image<?,?>> frames,int timeBetweenFramesMS, boolean loopContinuously,OutputStream stream) throws IOException{ 288 ImageOutputStream output = new MemoryCacheImageOutputStream(stream); 289 writeGif(frames,timeBetweenFramesMS,loopContinuously,output); 290 } 291 292 /** 293 * Write some frames into an {@link ImageOutputStream} 294 * @param frames 295 * @param timeBetweenFramesMS 296 * @param loopContinuously 297 * @param output 298 * @throws IOException 299 */ 300 public static void writeGif(List<Image<?, ?>> frames,int timeBetweenFramesMS, boolean loopContinuously,ImageOutputStream output) throws IOException { 301 if(frames.size() == 0){ 302 throw new IOException("No frames!"); 303 } 304 GifSequenceWriter writer = new GifSequenceWriter(output,timeBetweenFramesMS, loopContinuously); 305 for (Image<?, ?> image : frames) { 306 writer.writeToSequence(image); 307 } 308 writer.close(); 309 } 310}