View Javadoc

1   /*
2   
3       dsh-piccolo-sprite  Piccolo2D sprite nodes and supporting classes.
4       Copyright (c) 2006-2013 held jointly by the individual authors.
5   
6       This library is free software; you can redistribute it and/or modify it
7       under the terms of the GNU Lesser General Public License as published
8       by the Free Software Foundation; either version 3 of the License, or (at
9       your option) any later version.
10  
11      This library is distributed in the hope that it will be useful, but WITHOUT
12      ANY WARRANTY; with out even the implied warranty of MERCHANTABILITY or
13      FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public
14      License for more details.
15  
16      You should have received a copy of the GNU Lesser General Public License
17      along with this library;  if not, write to the Free Software Foundation,
18      Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307  USA.
19  
20      > http://www.fsf.org/licensing/licenses/lgpl.html
21      > http://www.opensource.org/licenses/lgpl-license.php
22  
23  */
24  package org.dishevelled.piccolo.sprite;
25  
26  import java.awt.Graphics2D;
27  import java.awt.Image;
28  import java.awt.RenderingHints;
29  
30  import java.awt.image.BufferedImage;
31  
32  import java.awt.geom.AffineTransform;
33  
34  import java.io.File;
35  import java.io.InputStream;
36  import java.io.IOException;
37  
38  import java.net.MalformedURLException;
39  import java.net.URL;
40  
41  import java.util.ArrayList;
42  import java.util.Collections;
43  import java.util.List;
44  
45  import javax.imageio.ImageIO;
46  
47  /**
48   * Static utility methods for creating animations.
49   *
50   * @author  Michael Heuer
51   * @version $Revision$ $Date$
52   */
53  public final class Animations
54  {
55  
56      /**
57       * Private no-arg constructor.
58       */
59      private Animations()
60      {
61          // empty
62      }
63  
64  
65      // single frame animations
66  
67      /**
68       * Create and return a new single frame animation from the specified image input stream.
69       *
70       * @param image image input stream
71       * @return a new single frame animation from the specified image
72       * @throws IOException if an IO error occurs
73       */
74      public static SingleFrameAnimation createAnimation(final InputStream image)
75          throws IOException
76      {
77          BufferedImage bufferedImage = ImageIO.read(image);
78          return createAnimation(bufferedImage);
79      }
80  
81      /**
82       * Create and return a new single frame animation from the specified image file.
83       *
84       * @param image image file
85       * @return a new single frame animation from the specified image
86       * @throws IOException if an IO error occurs
87       */
88      public static SingleFrameAnimation createAnimation(final File image)
89          throws IOException
90      {
91          BufferedImage bufferedImage = ImageIO.read(image);
92          return createAnimation(bufferedImage);
93      }
94  
95      /**
96       * Create and return a new single frame animation from the specified image URL.
97       *
98       * @param image image URL
99       * @return a new single frame animation from the specified image
100      * @throws IOException if an IO error occurs
101      */
102     public static SingleFrameAnimation createAnimation(final URL image)
103         throws IOException
104     {
105         BufferedImage bufferedImage = ImageIO.read(image);
106         return createAnimation(bufferedImage);
107     }
108 
109     /**
110      * Create and return a new single frame animation from the specified image.
111      *
112      * @param image image
113      * @return a new single frame animation from the specified image
114      */
115     public static SingleFrameAnimation createAnimation(final Image image)
116     {
117         return new SingleFrameAnimation(image);
118     }
119 
120 
121     // multiple frame animations from base image
122 
123     /**
124      * Create and return a new multiple frames animation containing all the frame images
125      * specified from <code>baseImage</code>.
126      *
127      * @param baseImage base image file or URL name
128      * @param suffix image suffix
129      * @param frames number of frames
130      * @return a new multiple frames animation containing all the frame images
131      *    specified from <code>baseImage</code>
132      * @throws IOException if an IO error occurs
133      */
134     public static MultipleFramesAnimation createAnimation(final String baseImage, final String suffix, final int frames)
135         throws IOException
136     {
137         return new MultipleFramesAnimation(createFrameList(baseImage, suffix, frames));
138     }
139 
140     /**
141      * Create and return a new multiple frames animation containing all the frame images
142      * specified from <code>baseImage</code>.
143      *
144      * @param baseImage base image file
145      * @param suffix image suffix
146      * @param frames number of frames
147      * @return a new multiple frames animation containing all the frame images
148      *    specified from <code>baseImage</code>
149      * @throws IOException if an IO error occurs
150      */
151     public static MultipleFramesAnimation createAnimation(final File baseImage, final String suffix, final int frames)
152         throws IOException
153     {
154         return new MultipleFramesAnimation(createFrameList(baseImage, suffix, frames));
155     }
156 
157     /**
158      * Create and return a new multiple frames animation containing all the frame images
159      * specified from <code>baseImage</code>.
160      *
161      * @param baseImage base image URL
162      * @param suffix image suffix
163      * @param frames number of frames
164      * @return a new multiple frames animation containing all the frame images
165      *    specified from <code>baseImage</code>
166      * @throws IOException if an IO error occurs
167      */
168     public static MultipleFramesAnimation createAnimation(final URL baseImage, final String suffix, final int frames)
169         throws IOException
170     {
171         return new MultipleFramesAnimation(createFrameList(baseImage, suffix, frames));
172     }
173 
174 
175     // looped frame animations from base image
176 
177     /**
178      * Create and return a new looped frames animation containing all the frame images
179      * specified from <code>baseImage</code>.
180      *
181      * @param baseImage base image file or URL name
182      * @param suffix image suffix
183      * @param frames number of frames
184      * @return a new looped frames animation containing all the frame images
185      *    specified from <code>baseImage</code>
186      * @throws IOException if an IO error occurs
187      */
188     public static LoopedFramesAnimation createLoopedAnimation(final String baseImage,
189                                                               final String suffix,
190                                                               final int frames)
191         throws IOException
192     {
193         return new LoopedFramesAnimation(createFrameList(baseImage, suffix, frames));
194     }
195 
196     /**
197      * Create and return a new looped frames animation containing all the frame images
198      * specified from <code>baseImage</code>.
199      *
200      * @param baseImage base image file
201      * @param suffix image suffix
202      * @param frames number of frames
203      * @return a new looped frames animation containing all the frame images
204      *    specified from <code>baseImage</code>
205      * @throws IOException if an IO error occurs
206      */
207     public static LoopedFramesAnimation createLoopedAnimation(final File baseImage,
208                                                               final String suffix,
209                                                               final int frames)
210         throws IOException
211     {
212         return new LoopedFramesAnimation(createFrameList(baseImage, suffix, frames));
213     }
214 
215     /**
216      * Create and return a new looped frames animation containing all the frame images
217      * specified from <code>baseImage</code>.
218      *
219      * @param baseImage base image URL
220      * @param suffix image suffix
221      * @param frames number of frames
222      * @return a new looped frames animation containing all the frame images
223      *    specified from <code>baseImage</code>
224      * @throws IOException if an IO error occurs
225      */
226     public static LoopedFramesAnimation createLoopedAnimation(final URL baseImage,
227                                                               final String suffix,
228                                                               final int frames)
229         throws IOException
230     {
231         return new LoopedFramesAnimation(createFrameList(baseImage, suffix, frames));
232     }
233 
234 
235     // multiple frame animations from sprite sheet
236 
237     /**
238      * Create and return a new multiple frames animation containing all the frame images
239      * from <code>spriteSheet</code> as specified by the starting location <code>(x, y)</code>
240      * and read horizontally the specified number of frames.
241      *
242      * @param spriteSheet sprite sheet input stream
243      * @param x starting location x
244      * @param y starting location y
245      * @param width frame width
246      * @param height frame height
247      * @param frames number of frames
248      * @return a new multiple frames animation containing all the frame images
249      *    from <code>spriteSheet</code> as specified by the starting location <code>(x, y)</code>
250      *    and read horizontally the specified number of frames
251      * @throws IOException if an IO error occurs
252      */
253     public static MultipleFramesAnimation createAnimation(final InputStream spriteSheet, final int x, final int y,
254                                                           final int width, final int height, final int frames)
255         throws IOException
256     {
257         return new MultipleFramesAnimation(createFrameList(spriteSheet, x, y, width, height, frames));
258     }
259 
260     /**
261      * Create and return a new multiple frames animation containing all the frame images
262      * from <code>spriteSheet</code> as specified by the starting location <code>(x, y)</code>
263      * and read horizontally the specified number of frames.
264      *
265      * @param spriteSheet sprite sheet file
266      * @param x starting location x
267      * @param y starting location y
268      * @param width frame width
269      * @param height frame height
270      * @param frames number of frames
271      * @return a new multiple frames animation containing all the frame images
272      *    from <code>spriteSheet</code> as specified by the starting location <code>(x, y)</code>
273      *    and read horizontally the specified number of frames
274      * @throws IOException if an IO error occurs
275      */
276     public static MultipleFramesAnimation createAnimation(final File spriteSheet, final int x, final int y,
277                                                         final int width, final int height, final int frames)
278         throws IOException
279     {
280         return new MultipleFramesAnimation(createFrameList(spriteSheet, x, y, width, height, frames));
281     }
282 
283     /**
284      * Create and return a new multiple frames animation containing all the frame images
285      * from <code>spriteSheet</code> as specified by the starting location <code>(x, y)</code>
286      * and read horizontally the specified number of frames.
287      *
288      * @param spriteSheet sprite sheet URL
289      * @param x starting location x
290      * @param y starting location y
291      * @param width frame width
292      * @param height frame height
293      * @param frames number of frames
294      * @return a new multiple frames animation containing all the frame images
295      *    from <code>spriteSheet</code> as specified by the starting location <code>(x, y)</code>
296      *    and read horizontally the specified number of frames
297      * @throws IOException if an IO error occurs
298      */
299     public static MultipleFramesAnimation createAnimation(final URL spriteSheet, final int x, final int y,
300                                                         final int width, final int height, final int frames)
301         throws IOException
302     {
303         return new MultipleFramesAnimation(createFrameList(spriteSheet, x, y, width, height, frames));
304     }
305 
306     /**
307      * Create and return a new multiple frames animation containing all the frame images
308      * from <code>spriteSheet</code> as specified by the starting location <code>(x, y)</code>
309      * and read horizontally the specified number of frames.
310      *
311      * @param spriteSheet sprite sheet image
312      * @param x starting location x
313      * @param y starting location y
314      * @param width frame width
315      * @param height frame height
316      * @param frames number of frames
317      * @return a new multiple frames animation containing all the frame images
318      *    from <code>spriteSheet</code> as specified by the starting location <code>(x, y)</code>
319      *    and read horizontally the specified number of frames
320      */
321     public static MultipleFramesAnimation createAnimation(final BufferedImage spriteSheet, final int x, final int y,
322                                                         final int width, final int height, final int frames)
323     {
324         return new MultipleFramesAnimation(createFrameList(spriteSheet, x, y, width, height, frames));
325     }
326 
327 
328     // looped frame animations from sprite sheet
329 
330     /**
331      * Create and return a new looped frames animation containing all the frame images
332      * from <code>spriteSheet</code> as specified by the starting location <code>(x, y)</code>
333      * and read horizontally the specified number of frames.
334      *
335      * @param spriteSheet sprite sheet input stream
336      * @param x starting location x
337      * @param y starting location y
338      * @param width frame width
339      * @param height frame height
340      * @param frames number of frames
341      * @return a new looped frames animation containing all the frame images
342      *    from <code>spriteSheet</code> as specified by the starting location <code>(x, y)</code>
343      *    and read horizontally the specified number of frames
344      * @throws IOException if an IO error occurs
345      */
346     public static LoopedFramesAnimation createLoopedAnimation(final InputStream spriteSheet, final int x, final int y,
347                                                         final int width, final int height, final int frames)
348         throws IOException
349     {
350         return new LoopedFramesAnimation(createFrameList(spriteSheet, x, y, width, height, frames));
351     }
352 
353     /**
354      * Create and return a new looped frames animation containing all the frame images
355      * from <code>spriteSheet</code> as specified by the starting location <code>(x, y)</code>
356      * and read horizontally the specified number of frames.
357      *
358      * @param spriteSheet sprite sheet file
359      * @param x starting location x
360      * @param y starting location y
361      * @param width frame width
362      * @param height frame height
363      * @param frames number of frames
364      * @return a new looped frames animation containing all the frame images
365      *    from <code>spriteSheet</code> as specified by the starting location <code>(x, y)</code>
366      *    and read horizontally the specified number of frames
367      * @throws IOException if an IO error occurs
368      */
369     public static LoopedFramesAnimation createLoopedAnimation(final File spriteSheet, final int x, final int y,
370                                                         final int width, final int height, final int frames)
371         throws IOException
372     {
373         return new LoopedFramesAnimation(createFrameList(spriteSheet, x, y, width, height, frames));
374     }
375 
376     /**
377      * Create and return a new looped frames animation containing all the frame images
378      * from <code>spriteSheet</code> as specified by the starting location <code>(x, y)</code>
379      * and read horizontally the specified number of frames.
380      *
381      * @param spriteSheet sprite sheet URL
382      * @param x starting location x
383      * @param y starting location y
384      * @param width frame width
385      * @param height frame height
386      * @param frames number of frames
387      * @return a new looped frames animation containing all the frame images
388      *    from <code>spriteSheet</code> as specified by the starting location <code>(x, y)</code>
389      *    and read horizontally the specified number of frames
390      * @throws IOException if an IO error occurs
391      */
392     public static LoopedFramesAnimation createLoopedAnimation(final URL spriteSheet, final int x, final int y,
393                                                         final int width, final int height, final int frames)
394         throws IOException
395     {
396         return new LoopedFramesAnimation(createFrameList(spriteSheet, x, y, width, height, frames));
397     }
398 
399     /**
400      * Create and return a new looped frames animation containing all the frame images
401      * from <code>spriteSheet</code> as specified by the starting location <code>(x, y)</code>
402      * and read horizontally the specified number of frames.
403      *
404      * @param spriteSheet sprite sheet image
405      * @param x starting location x
406      * @param y starting location y
407      * @param width frame width
408      * @param height frame height
409      * @param frames number of frames
410      * @return a new looped frames animation containing all the frame images
411      *    from <code>spriteSheet</code> as specified by the starting location <code>(x, y)</code>
412      *    and read horizontally the specified number of frames
413      */
414     public static LoopedFramesAnimation createLoopedAnimation(final BufferedImage spriteSheet, final int x, final int y,
415                                                         final int width, final int height, final int frames)
416     {
417         return new LoopedFramesAnimation(createFrameList(spriteSheet, x, y, width, height, frames));
418     }
419 
420 
421     // multiple frame animations from frame list
422 
423     /**
424      * Create and return a new multiple frames animation containing the specified frame images.
425      *
426      * @param images list of frame images, must not be null
427      * @return a new multiple frames animation containing the specified frame images
428      */
429     public static MultipleFramesAnimation createAnimation(final List<Image> images)
430     {
431         return new MultipleFramesAnimation(images);
432     }
433 
434 
435     // looped frame animations from frame list
436 
437     /**
438      * Create and return a new looped frames animation containing the specified frame images.
439      *
440      * @param images list of frame images, must not be null
441      * @return a new looped frames animation containing the specified frame images
442      */
443     public static LoopedFramesAnimation createLoopedAnimation(final List<Image> images)
444     {
445         return new LoopedFramesAnimation(images);
446     }
447 
448 
449     // create frame lists
450 
451     /**
452      * Create and return an unmodifiable list of images containing all the frame images
453      * specified from <code>baseImage</code>.
454      *
455      * @param baseImage base image file or URL name
456      * @param suffix image suffix
457      * @param frames number of frames
458      * @return a new unmodifiable list of images containing all the frame images
459      *    specified from <code>baseImage</code>
460      * @throws IOException if an IO error occurs
461      */
462     public static List<Image> createFrameList(final String baseImage, final String suffix, final int frames)
463         throws IOException
464     {
465         if (baseImage == null)
466         {
467             throw new IllegalArgumentException("baseImage must not be null");
468         }
469         List<Image> frameList = null;
470         try
471         {
472             frameList = createFrameList(new URL(baseImage), suffix, frames);
473         }
474         catch (MalformedURLException e)
475         {
476             frameList = createFrameList(new File(baseImage), suffix, frames);
477         }
478         return frameList;
479     }
480 
481     /**
482      * Create and return an unmodifiable list of images containing all the frame images
483      * specified from <code>baseImage</code>.
484      *
485      * @param baseImage base image file
486      * @param suffix image suffix
487      * @param frames number of frames
488      * @return a new unmodifiable list of images containing all the frame images
489      *    specified from <code>baseImage</code>
490      * @throws IOException if an IO error occurs
491      */
492     public static List<Image> createFrameList(final File baseImage, final String suffix, final int frames)
493         throws IOException
494     {
495         if (baseImage == null)
496         {
497             throw new IllegalArgumentException("baseImage must not be null");
498         }
499         int leadingZeros = (int) (frames / 10) + 1; // is this math correct?
500         String format = "%s%0" + leadingZeros + "d%s";
501         List<Image> images = new ArrayList<Image>(frames);
502         for (int frame = 0; frame < frames; frame++)
503         {
504             File file = new File(String.format(format, new Object[] { baseImage.getPath(), frame, suffix }));
505             Image image = ImageIO.read(file);
506             images.add(image);
507         }
508         return Collections.unmodifiableList(images);
509     }
510 
511     /**
512      * Create and return an unmodifiable list of images containing all the frame images
513      * specified from <code>baseImage</code>.
514      *
515      * @param baseImage base image URL
516      * @param suffix image suffix
517      * @param frames number of frames
518      * @return a new unmodifiable list of images containing all the frame images
519      *    specified from <code>baseImage</code>
520      * @throws IOException if an IO error occurs
521      */
522     public static List<Image> createFrameList(final URL baseImage, final String suffix, final int frames)
523         throws IOException
524     {
525         if (baseImage == null)
526         {
527             throw new IllegalArgumentException("baseImage must not be null");
528         }
529         int leadingZeros = (int) (frames / 10) + 1; // is this math correct?
530         String format = "%s%0" + leadingZeros + "d%s";
531         List<Image> images = new ArrayList<Image>(frames);
532         for (int frame = 0; frame < frames; frame++)
533         {
534             URL url = new URL(String.format(format, new Object[] { baseImage.getPath(), frame, suffix }));
535             Image image = ImageIO.read(url);
536             images.add(image);
537         }
538         return Collections.unmodifiableList(images);
539     }
540 
541     /**
542      * Create and return an unmodifiable list of frame images containing all the frame images
543      * from <code>spriteSheet</code> as specified by the starting location <code>(x, y)</code>
544      * and read horizontally the specified number of frames.
545      *
546      * @param spriteSheet sprite sheet input stream
547      * @param x starting location x
548      * @param y starting location y
549      * @param width frame width
550      * @param height frame height
551      * @param frames number of frames
552      * @return an unmodifiable list of frame images containing all the frame images
553      *    from <code>spriteSheet</code> as specified by the starting location <code>(x, y)</code>
554      *    and read horizontally the specified number of frames
555      * @throws IOException if an IO error occurs
556      */
557     public static List<Image> createFrameList(final InputStream spriteSheet, final int x, final int y,
558                                               final int width, final int height, final int frames)
559         throws IOException
560     {
561         BufferedImage bufferedImage = ImageIO.read(spriteSheet);
562         return createFrameList(bufferedImage, x, y, width, height, frames);
563     }
564 
565     /**
566      * Create and return an unmodifiable list of frame images containing all the frame images
567      * from <code>spriteSheet</code> as specified by the starting location <code>(x, y)</code>
568      * and read horizontally the specified number of frames.
569      *
570      * @param spriteSheet sprite sheet file
571      * @param x starting location x
572      * @param y starting location y
573      * @param width frame width
574      * @param height frame height
575      * @param frames number of frames
576      * @return an unmodifiable list of frame images containing all the frame images
577      *    from <code>spriteSheet</code> as specified by the starting location <code>(x, y)</code>
578      *    and read horizontally the specified number of frames
579      * @throws IOException if an IO error occurs
580      */
581     public static List<Image> createFrameList(final File spriteSheet, final int x, final int y,
582                                               final int width, final int height, final int frames)
583         throws IOException
584     {
585         BufferedImage bufferedImage = ImageIO.read(spriteSheet);
586         return createFrameList(bufferedImage, x, y, width, height, frames);
587     }
588 
589     /**
590      * Create and return an unmodifiable list of frame images containing all the frame images
591      * from <code>spriteSheet</code> as specified by the starting location <code>(x, y)</code>
592      * and read horizontally the specified number of frames.
593      *
594      * @param spriteSheet sprite sheet file
595      * @param x starting location x
596      * @param y starting location y
597      * @param width frame width
598      * @param height frame height
599      * @param frames number of frames
600      * @return an unmodifiable list of frame images containing all the frame images
601      *    from <code>spriteSheet</code> as specified by the starting location <code>(x, y)</code>
602      *    and read horizontally the specified number of frames
603      * @throws IOException if an IO error occurs
604      */
605     public static List<Image> createFrameList(final URL spriteSheet, final int x, final int y,
606                                               final int width, final int height, final int frames)
607         throws IOException
608     {
609         BufferedImage bufferedImage = ImageIO.read(spriteSheet);
610         return createFrameList(bufferedImage, x, y, width, height, frames);
611     }
612 
613     /**
614      * Create and return an unmodifiable list of frame images containing all the frame images
615      * from <code>spriteSheet</code> as specified by the starting location <code>(x, y)</code>
616      * and read horizontally the specified number of frames.
617      *
618      * @param spriteSheet sprite sheet image
619      * @param x starting location x
620      * @param y starting location y
621      * @param width frame width
622      * @param height frame height
623      * @param frames number of frames
624      * @return an unmodifiable list of frame images containing all the frame images
625      *    from <code>spriteSheet</code> as specified by the starting location <code>(x, y)</code>
626      *    and read horizontally the specified number of frames
627      */
628     public static List<Image> createFrameList(final BufferedImage spriteSheet, final int x, final int y,
629                                               final int width, final int height, final int frames)
630     {
631         if (spriteSheet == null)
632         {
633             throw new IllegalArgumentException("spriteSheet must not be null");
634         }
635         List<Image> images = new ArrayList<Image>(frames);
636         for (int frame = 0; frame < frames; frame++)
637         {
638             Image subimage = spriteSheet.getSubimage(x + (frame * width), y, width, height);
639             images.add(subimage);
640         }
641         return Collections.unmodifiableList(images);
642     }
643 
644 
645     // sprite sheet utility methods
646 
647     /**
648      * Create and return a new sprite sheet image containing all of the specified frame images
649      * assembled horizontally.
650      *
651      * @param frameImages frame images, must not be null
652      * @return a new sprite sheet image containing all of the specified frame images
653      *    assembled horizontally
654      */
655     public static BufferedImage createSpriteSheet(final List<Image> frameImages)
656     {
657         if (frameImages == null)
658         {
659             throw new IllegalArgumentException("frameImages must not be null");
660         }
661         int width = 0;
662         int height = 0;
663         for (Image image : frameImages)
664         {
665             if (image.getWidth(null) > width)
666             {
667                 width = image.getWidth(null);
668             }
669             if (image.getHeight(null) > height)
670             {
671                 height = image.getHeight(null);
672             }
673         }
674         BufferedImage spriteSheet = new BufferedImage(width * frameImages.size(), height, BufferedImage.TYPE_INT_ARGB);
675         Graphics2D graphics = spriteSheet.createGraphics();
676         graphics.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
677         for (int i = 0, size = frameImages.size(); i < size; i++)
678         {
679             Image image = frameImages.get(i);
680             int x = width * i + (width / 2) - (image.getWidth(null) / 2);
681             int y = (height / 2) - (image.getHeight(null) / 2);
682             graphics.drawImage(image, x, y, null);
683         }
684         graphics.dispose();
685         return spriteSheet;
686     }
687 
688     /**
689      * Create and return a new sprite sheet image containing all of the specified frame images
690      * specified from <code>baseImage</code> assembled horizontally.
691      *
692      * @param baseImage base image file or URL name
693      * @param suffix image suffix
694      * @param frames number of frames
695      * @return a new sprite sheet image containing all of the specified frame images
696      *    specified from <code>baseImage</code> assembled horizontally
697      * @throws IOException if an IO error occurs
698      */
699     public static BufferedImage createSpriteSheet(final String baseImage, final String suffix, final int frames)
700         throws IOException
701     {
702         return createSpriteSheet(createFrameList(baseImage, suffix, frames));
703     }
704 
705     /**
706      * Create and return a new sprite sheet image containing all of the specified frame images
707      * specified from <code>baseImage</code> assembled horizontally.
708      *
709      * @param baseImage base image file
710      * @param suffix image suffix
711      * @param frames number of frames
712      * @return a new sprite sheet image containing all of the specified frame images
713      *    specified from <code>baseImage</code> assembled horizontally
714      * @throws IOException if an IO error occurs
715      */
716     public static BufferedImage createSpriteSheet(final File baseImage, final String suffix, final int frames)
717         throws IOException
718     {
719         return createSpriteSheet(createFrameList(baseImage, suffix, frames));
720     }
721 
722     /**
723      * Create and return a new sprite sheet image containing all of the specified frame images
724      * specified from <code>baseImage</code> assembled horizontally.
725      *
726      * @param baseImage base image URL
727      * @param suffix image suffix
728      * @param frames number of frames
729      * @return a new sprite sheet image containing all of the specified frame images
730      *    specified from <code>baseImage</code> assembled horizontally
731      * @throws IOException if an IO error occurs
732      */
733     public static BufferedImage createSpriteSheet(final URL baseImage, final String suffix, final int frames)
734         throws IOException
735     {
736         return createSpriteSheet(createFrameList(baseImage, suffix, frames));
737     }
738 
739     /**
740      * Create and return an unmodifiable list of frame images containing all the frame images
741      * from <code>frameImages</code> in the same order flipped horizontally.
742      *
743      * @param frameImages list of frame images, must not be null
744      * @return an unmodifiable list of frame images containing all the frame images
745      *   from <code>frameImages</code> in the same order flipped horizontally
746      */
747     public static List<Image> flipHorizontally(final List<Image> frameImages)
748     {
749         if (frameImages == null)
750         {
751             throw new IllegalArgumentException("frameImages must not be null");
752         }
753         int width = 0;
754         int height = 0;
755         for (Image image : frameImages)
756         {
757             if (image.getWidth(null) > width)
758             {
759                 width = image.getWidth(null);
760             }
761             if (image.getHeight(null) > height)
762             {
763                 height = image.getHeight(null);
764             }
765         }
766         BufferedImage spriteSheet = new BufferedImage(width * frameImages.size(), height, BufferedImage.TYPE_INT_ARGB);
767         Graphics2D graphics = spriteSheet.createGraphics();
768         graphics.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
769         for (int i = 0, size = frameImages.size(); i < size; i++)
770         {
771             Image image = frameImages.get(i);
772             double cx = image.getWidth(null) / 2.0d;
773             double cy = image.getHeight(null) / 2.0d;
774             double x = width * i + (width / 2.0d) - cx;
775             double y = (height / 2.0d) - cy;
776 
777             AffineTransform rotate = new AffineTransform();
778             rotate.translate(cx, cy);
779             rotate.concatenate(new AffineTransform(new double[] { 1.0d, 0.0d, 0.0d, -1.0d }));
780             rotate.translate(-cx, -cy);
781             graphics.setTransform(rotate);
782             graphics.drawImage(image, (int) x, (int) y, null);
783         }
784         graphics.dispose();
785         return createFrameList(spriteSheet, 0, 0, width, height, frameImages.size());
786     }
787 
788     /**
789      * Create and return an unmodifiable list of frame images containing all the frame images
790      * from <code>frameImages</code> in the same order flipped vertically.
791      *
792      * @param frameImages list of frame images, must not be null
793      * @return an unmodifiable list of frame images containing all the frame images
794      *   from <code>frameImages</code> in the same order flipped vertically
795      */
796     public static List<Image> flipVertically(final List<Image> frameImages)
797     {
798         if (frameImages == null)
799         {
800             throw new IllegalArgumentException("frameImages must not be null");
801         }
802         int width = 0;
803         int height = 0;
804         for (Image image : frameImages)
805         {
806             if (image.getWidth(null) > width)
807             {
808                 width = image.getWidth(null);
809             }
810             if (image.getHeight(null) > height)
811             {
812                 height = image.getHeight(null);
813             }
814         }
815         BufferedImage spriteSheet = new BufferedImage(width * frameImages.size(), height, BufferedImage.TYPE_INT_ARGB);
816         Graphics2D graphics = spriteSheet.createGraphics();
817         graphics.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
818         for (int i = 0, size = frameImages.size(); i < size; i++)
819         {
820             Image image = frameImages.get(i);
821             double cx = image.getWidth(null) / 2.0d;
822             double cy = image.getHeight(null) / 2.0d;
823             double x = width * i + (width / 2.0d) - cx;
824             double y = (height / 2.0d) - cy;
825 
826             AffineTransform rotate = new AffineTransform();
827             rotate.translate(cx, cy);
828             rotate.concatenate(new AffineTransform(new double[] { -1.0d, 0.0d, 0.0d, 1.0d }));
829             rotate.translate(-cx, -cy);
830             graphics.setTransform(rotate);
831             graphics.drawImage(image, (int) x, (int) y, null);
832         }
833         graphics.dispose();
834         return createFrameList(spriteSheet, 0, 0, width, height, frameImages.size());
835     }
836 
837     /**
838      * Create and return an unmodifiable list of frame images created by
839      * rotating around the center of specified image 2&#960;/steps times.
840      *
841      * @param image image to rotate, must not be null
842      * @param steps number of steps
843      * @return an unmodifiable list of frame images created by
844      *   rotating around the center of specified image 2&#960;/steps times
845      */
846     public static List<Image> rotate(final BufferedImage image, final int steps)
847     {
848         if (image == null)
849         {
850             throw new IllegalArgumentException("image must not be null");
851         }
852         int width = image.getWidth(null);
853         int height = image.getHeight(null);
854         int size = Math.max(width, height);
855         BufferedImage spriteSheet = new BufferedImage(size * steps, size, BufferedImage.TYPE_INT_ARGB);
856         Graphics2D graphics = spriteSheet.createGraphics();
857         graphics.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
858         double x = width / 2.0d;
859         double y = height / 2.0d;
860         double r = (2.0d / (double) steps) * Math.PI;
861         for (int i = 0; i < steps; i++)
862         {
863             AffineTransform rotate = new AffineTransform();
864             rotate.translate(size * i, 0.0d);
865             rotate.rotate(i * r, x, y);
866             graphics.drawRenderedImage(image, rotate);
867         }
868         graphics.dispose();
869         return createFrameList(spriteSheet, 0, 0, size, size, steps);
870     }
871 }