package net.sf.freecol.common.io.sza; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.net.URL; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.zip.ZipEntry; import java.util.zip.ZipInputStream; import org.freecolandroid.repackaged.java.awt.Component; import org.freecolandroid.repackaged.java.awt.Image; import org.freecolandroid.repackaged.java.awt.image.BufferedImage; import org.freecolandroid.repackaged.javax.imageio.ImageIO; /** * An animation made from images stored in a zip-file. */ public final class SimpleZippedAnimation implements Iterable<AnimationEvent> { private static final String ANIMATION_DESCRIPTOR_FILE = "animation.txt"; private final List<AnimationEvent> events; private final int width; private final int height; /** * Creates a new animation from a stream generated by the * provided URL. * * @param url The URL to read a zip-file from. * @throws IOException if the file cannot be opened, or * is invalid. */ public SimpleZippedAnimation(final URL url) throws IOException { this(url.openStream()); } /** * Creates a new animation from a stream. * * @param zipStream An <code>InputStream</code> to a zip-file. * @throws IOException if the file cannot be opened, or * is invalid. */ public SimpleZippedAnimation(final InputStream zipStream) throws IOException { this(new ZipInputStream(zipStream)); } private SimpleZippedAnimation(final List<AnimationEvent> events, final int width, final int height) { this.events = events; this.width = width; this.height = height; } private SimpleZippedAnimation(final ZipInputStream zipStream) throws IOException { this.events = new ArrayList<AnimationEvent>(); /* * Preload all files from the archive since we cannot * use a ZipFile for reading (as we should support an * arbitrary stream). */ final Map<String, BufferedImage> loadingImages = new HashMap<String, BufferedImage>(); final List<String> loadingDescriptor = new LinkedList<String>(); try { ZipEntry entry; while ((entry = zipStream.getNextEntry()) != null) { if (entry.getName().equals(ANIMATION_DESCRIPTOR_FILE)) { final BufferedReader in = new BufferedReader(new InputStreamReader(zipStream)); String line; while ((line = in.readLine()) != null) { loadingDescriptor.add(line); } } else { loadingImages.put(entry.getName(), ImageIO.read(zipStream)); } zipStream.closeEntry(); } } finally { try { zipStream.close(); } catch (Exception e) {} } if (loadingDescriptor.size() == 0) { throw new IOException("animation.txt is missing from the SZA."); } int largestWidth = 0; int largestHeight = 0; for (String line : loadingDescriptor) { final int index = line.indexOf('('); final int index2 = line.indexOf("ms)"); if (index < 0 || index2 <= index) { throw new IOException("animation.txt should use the format: FILNAME (TIMEms)"); } final String imageName = line.substring(0, index).trim(); final int durationInMs = Integer.parseInt(line.substring(index+1, index2)); final BufferedImage image = loadingImages.get(imageName); if (image == null) { throw new IOException("Could not find referenced image: " + imageName); } events.add(new ImageAnimationEventImpl(image, durationInMs)); if (image.getWidth() > largestWidth) { largestWidth = image.getWidth(); } if (image.getHeight() > largestHeight) { largestHeight = image.getHeight(); } } this.width = largestWidth; this.height = largestHeight; } /** * Gets the width of the animation. * @return The largest width of all the frames in * this animation. */ public int getWidth() { return width; } /** * Gets the height of the animation. * @return The largest height of all the frames in * this animation. */ public int getHeight() { return height; } /** * Returns all of the animation events. * @return An <code>Iterator</code> with all the images * and other resources (support for sound may be * added later). */ public Iterator<AnimationEvent> iterator() { return Collections.unmodifiableList(events).iterator(); } /** * Creates a scaled animation based on this object. * * @param scale The scaling factor (with 1 being normal size, * 2 twice the size, 0.5 half the size etc). * @return The scaled animation. */ public SimpleZippedAnimation createScaledVersion(double scale) { final List<AnimationEvent> newEvents = new ArrayList<AnimationEvent>(); for (AnimationEvent event : events) { if (event instanceof ImageAnimationEvent) { newEvents.add(((ImageAnimationEventImpl) event).createScaledVersion(scale)); } else { newEvents.add(event); } } return new SimpleZippedAnimation(newEvents, (int) (width * scale), (int) (height * scale)); } private static final class ImageAnimationEventImpl implements ImageAnimationEvent { // private static final Component _c = new Component() {}; private final Image image; private final int durationInMs; private ImageAnimationEventImpl(final Image image, final int durationInMs) { this.image = image; this.durationInMs = durationInMs; } public Image getImage() { return image; } public int getDurationInMs() { return durationInMs; } private ImageAnimationEvent createScaledVersion(double scale) { System.out.println("ERROR!");new Exception().printStackTrace();throw new UnsupportedOperationException("Broken!"); // final int width = (int) (image.getWidth(null) * scale); // final int height = (int) (image.getHeight(null) * scale); // // MediaTracker mt = new MediaTracker(_c); // final Image scaledImage = image.getScaledInstance(width, height, Image.SCALE_SMOOTH); // mt.addImage(scaledImage, 0, width, height); // try { // mt.waitForID(0); // } catch (InterruptedException e) { // return null; // } // // return new ImageAnimationEventImpl(scaledImage, durationInMs); } } }