/* * @(#)ANIMDecoder.java 2.3 2011-07-21 * * Copyright (c) 1999-2011 Werner Randelshofer, Goldau, Switzerland. * All rights reserved. * * You may not use, copy or modify this file, except in compliance with the * license agreement you entered into with Werner Randelshofer. * For details see accompanying license terms. */ package org.monte.media.anim; import org.monte.media.AbortException; import org.monte.media.ParseException; import org.monte.media.iff.*; import org.monte.media.ilbm.HAMColorModel; import org.monte.media.eightsvx.EightSVXDecoder; import org.monte.media.ilbm.CRNGColorCycle; import org.monte.media.ilbm.ColorCycle; import org.monte.media.ilbm.DRNGColorCycle; import java.io.*; import java.util.*; import java.awt.image.*; import java.net.URL; import java.applet.AudioClip; /** * Decodes IFF files and adds the data to an ANIMMovieTrack. * * @author Werner Randelshofer, Hausmatt 10, CH-6405 Goldau, Switzerland * @version 2.3 2011-07-21 Treats CMAP specially if OCS chip set is detected. * <br>2.1 2010-04-11 Adds support for CCRT color cycling. * <br>2.1 2010-01-22 Adds support for CRNG color cycling. * <br>2.0 2009-12-25 Treat an ILBM file as an animation with a single * frame. * <br>1.3 2009-12-24 Added support for CRNG color cycling. * <br>1.2.1 2006-09-30 Decode CMAP even if it is too big or too small * for the number of bitplanes used of the animation. * <br>1.2 2003-04-21 Decode ANFI revised. * <br>1.0 2003-04-03 Support for ANIM+SLA (Animations with Statically * Loaded Audio) files added. * <br>1.0 1999-10-19 */ public class ANIMDecoder implements IFFVisitor { private final static int ILBM_ID = IFFParser.stringToID("ILBM"); private final static int BMHD_ID = IFFParser.stringToID("BMHD"); private final static int CMAP_ID = IFFParser.stringToID("CMAP"); private final static int CAMG_ID = IFFParser.stringToID("CAMG"); private final static int CCRT_ID = IFFParser.stringToID("CCRT"); private final static int CRNG_ID = IFFParser.stringToID("CRNG"); private final static int DRNG_ID = IFFParser.stringToID("DRNG"); private final static int BODY_ID = IFFParser.stringToID("BODY"); private final static int ANHD_ID = IFFParser.stringToID("ANHD"); private final static int DLTA_ID = IFFParser.stringToID("DLTA"); private final static int ANIM_ID = IFFParser.stringToID("ANIM"); private final static int COPYRIGHT_ID = IFFParser.stringToID("(c) "); private final static int AUTH_ID = IFFParser.stringToID("AUTH"); private final static int ANNO_ID = IFFParser.stringToID("ANNO"); private final static int ANFI_ID = IFFParser.stringToID("ANFI"); private final static int SCTL_ID = IFFParser.stringToID("SCTL"); /** CAMG monitor ID mask. */ public final static int MONITOR_ID_MASK = 0xffff1000; /** Default ID chooses a system dependent screen mode. We always fall back * to NTSC OCS with 60fps. * * The default monitor ID triggers OCS mode! * OCS stands for "Original Chip Set". The OCS chip set only had 4 bits per color register. * All later chip sets hat 8 bits per color register. */ public final static int DEFAULT_MONITOR_ID = 0x00000000; /** NTSC, 60fps, 44:52. */ public final static int NTSC_MONITOR_ID = 0x00011000; /** PAL, 50fps, 44:44. */ public final static int PAL_MONITOR_ID = 0x00021000; /** MULTISCAN (VGA), 58fps, 44:44. */ public final static int MULTISCAN_MONITOR_ID = 0x00031000; /** A2024, 60fps (I don't know the real value). */ public final static int A2024_MONITOR_ID = 0x00041000; /** PROTO, 60fps (I don't know the real value). */ public final static int PROTO_MONITOR_ID = 0x00051000; /** EURO72, 69fps, 44:44. */ public final static int EURO72_MONITOR_ID = 0x00061000; /** EURO36, 73fps, 44:44. */ public final static int EURO36_MONITOR_ID = 0x00071000; /** SUPER72, 71fps, 34:40. */ public final static int SUPER72_MONITOR_ID = 0x00081000; /** DBLNTSC, 58fps, 44:52. */ public final static int DBLNTSC_MONITOR_ID = 0x00091000; /** DBLPAL, 48fps, 44:44. */ public final static int DBLPAL_MONITOR_ID = 0x000a1000; protected final static int MODE_MASK = 0x00000880; protected final static int HAM_MODE = 0x00000800; protected final static int EHB_MODE = 0x00000080; /** Instance variables */ private InputStream inputStream_; private URL location_; private Vector sources_; /** Properties. */ private Hashtable properties_; /** CMAP data. */ private ColorModel cmapColorModel; /** MovieTrack */ private ANIMMovieTrack track; /** Number of ANIM Chunks found. */ private int animCount; /** Index of ANIM Chunk to load. */ private int index; /** 8SVX Decoder */ private EightSVXDecoder eightSVXDecoder; /** * Data of previously decoded CMAP. */ private byte[] previousCMAPdata_; /** * Count the number of color maps encountered. */ /** Flag if within ANIM form. */ private boolean isInANIM; /** Flag if within ILBM form. */ private boolean isInILBM; /** The camg. */ private int camg = NTSC_MONITOR_ID; /* Constructors */ public ANIMDecoder(InputStream inputStream) { inputStream_ = inputStream; } public ANIMDecoder(URL location) { location_ = location; } /** * Decodes the stream and produces animation frames into the specified movie * track. * Reads the n-th ANIM chunk out of the IFF-file. * * @param track The decoded data is stored in this track. * @param n The index of the ANIM FORM to be read out of the IFF-File * @param loadAudio If this is set to false, audio data will be skipped. */ public void produce(ANIMMovieTrack track, int n, boolean loadAudio) throws IOException { InputStream in = null; this.track = track; index = n; animCount = 0; if (inputStream_ != null) { in = inputStream_; } else { in = location_.openStream(); } try { IFFParser iff = new IFFParser(); registerChunks(iff, loadAudio); if (loadAudio) { eightSVXDecoder = new EightSVXDecoder() { @Override public void addAudioClip(AudioClip clip) { super.addAudioClip(clip); ANIMDecoder.this.track.addAudioClip(clip); } }; eightSVXDecoder.registerChunks(iff); } iff.parse(in, this); } catch (ParseException e) { //System.out.println(e1); throw new IOException(e.getMessage()); } catch (AbortException e) { //System.out.println(e); throw new IOException(e.getMessage()); } finally { in.close(); } } public void registerChunks(IFFParser iff, boolean loadAudio) { iff.declarePropertyChunk(ILBM_ID, BMHD_ID); iff.declarePropertyChunk(ILBM_ID, CMAP_ID); iff.declarePropertyChunk(ILBM_ID, CAMG_ID); iff.declarePropertyChunk(ILBM_ID, ANHD_ID); iff.declareCollectionChunk(ILBM_ID, CCRT_ID); iff.declareCollectionChunk(ILBM_ID, CRNG_ID); iff.declareCollectionChunk(ILBM_ID, DRNG_ID); if (loadAudio) { iff.declarePropertyChunk(ILBM_ID, ANFI_ID); iff.declareCollectionChunk(ILBM_ID, SCTL_ID); } iff.declareGroupChunk(ANIM_ID, IFFParser.ID_FORM); iff.declareGroupChunk(ILBM_ID, IFFParser.ID_FORM); iff.declareDataChunk(ILBM_ID, BODY_ID); iff.declareDataChunk(ILBM_ID, DLTA_ID); iff.declareCollectionChunk(ILBM_ID, AUTH_ID); iff.declareCollectionChunk(ILBM_ID, ANNO_ID); iff.declareCollectionChunk(ILBM_ID, COPYRIGHT_ID); } public void enterGroup(IFFChunk chunk) { // Process chunks only when within ANIM Form or within ILBM Form. if (chunk.getType() == ANIM_ID) { if (animCount++ == index) { isInANIM = true; } } else if (chunk.getType() == ILBM_ID) { isInILBM = true; } // Decode 8SVX Sound data if (isInANIM && eightSVXDecoder != null) { eightSVXDecoder.enterGroup(chunk); } } public void leaveGroup(IFFChunk chunk) { // Decode 8SVX Sound data if (isInANIM && eightSVXDecoder != null) { eightSVXDecoder.leaveGroup(chunk); } // Process chunks only when within ANIM Form or within ILBM Form if (chunk.getType() == ANIM_ID) { isInANIM = false; } if (chunk.getType() == ILBM_ID) { isInILBM = false; } } public void visitChunk(IFFChunk group, IFFChunk chunk) throws ParseException, AbortException { if (Thread.currentThread().isInterrupted()) { throw new AbortException(); } if (isInANIM) { // Decode 8SVX data if (eightSVXDecoder != null) { eightSVXDecoder.visitChunk(group, chunk); } // Decode ANIM data if (group.getType() == ILBM_ID) { // Init track if not initialized. if (track.getWidth() == 0) { decodeBMHD(group.getPropertyChunk(BMHD_ID), track); decodeCAMG(group.getPropertyChunk(CAMG_ID), track); decodeColorCycling(// group.getCollectionChunks(CCRT_ID), group.getCollectionChunks(CRNG_ID),// group.getCollectionChunks(DRNG_ID),// track); decodeAUTH(group.getCollectionChunks(AUTH_ID), track); decodeANNO(group.getCollectionChunks(ANNO_ID), track); decodeCOPYRIGHT(group.getCollectionChunks(COPYRIGHT_ID), track); } boolean is4BitsPerChannel = (camg & MONITOR_ID_MASK) == DEFAULT_MONITOR_ID; ColorModel cm = decodeCMAP(group.getPropertyChunk(CMAP_ID), track, is4BitsPerChannel); if (cm != null) { cmapColorModel = cm; } if (chunk.getID() == BODY_ID) { decodeBODY(cmapColorModel, group, chunk, track); } else if (chunk.getID() == DLTA_ID) { decodeDLTA(cmapColorModel, group, chunk, track); } } } else if (isInILBM) { // Decode an ILBM image, which is outside of an ANIM Form as // a movie with a single video frame // Init track if not initialized. if (track.getWidth() == 0) { decodeBMHD(group.getPropertyChunk(BMHD_ID), track); decodeCAMG(group.getPropertyChunk(CAMG_ID), track); decodeColorCycling(// group.getCollectionChunks(CCRT_ID),// group.getCollectionChunks(CRNG_ID),// group.getCollectionChunks(DRNG_ID),// track); decodeAUTH(group.getCollectionChunks(AUTH_ID), track); decodeANNO(group.getCollectionChunks(ANNO_ID), track); decodeCOPYRIGHT(group.getCollectionChunks(COPYRIGHT_ID), track); } track.setPlayWrapupFrames(true); boolean is4BitsPerChannel = (camg & MONITOR_ID_MASK) == DEFAULT_MONITOR_ID; ColorModel cm = decodeCMAP(group.getPropertyChunk(CMAP_ID), track, is4BitsPerChannel); if (cm != null) { cmapColorModel = cm; } if (chunk.getID() == BODY_ID) { decodeBODY(cmapColorModel, group, chunk, track); } } } /** * Decodes the bitmap header (ILBM BMHD). * * <pre> * typedef UBYTE Masking; // Choice of masking technique * * #define mskNone 0 * #define mskHasMask 1 * #define mskHasTransparentColor 2 * #define mskLasso 3 * * typedef UBYTE Compression; // Choice of compression algorithm * // applied to the rows of all source and mask planes. * // "cmpByteRun1" is the byte run encoding. Do not compress * // accross rows! * #define cmpNone 0 * #define cmpByteRun1 1 * * typedef struct { * UWORD w, h; // raster width & height in pixels * WORD x, y; // pixel position for this image * UBYTE nbPlanes; // # source bitplanes * Masking masking; * Compression compression; * UBYTE pad1; // unused; ignore on read, write as 0 * UWORD transparentColor; // transparent "color number" (sort of) * UBYTE xAspect, yAspect; // pixel aspect, a ratio width : height * WORD pageWidth, pageHeight; // source "page" size in pixels * } BitmapHeader; * </pre> */ private void decodeBMHD(IFFChunk chunk, ANIMMovieTrack track) throws ParseException { try { MC68000InputStream in = new MC68000InputStream(new ByteArrayInputStream(chunk.getData())); track.setWidth(in.readUWORD()); track.setHeight(in.readUWORD()); track.setXPosition(in.readWORD()); track.setYPosition(in.readWORD()); track.setNbPlanes(in.readUBYTE()); track.setMasking(in.readUBYTE()); track.setCompression(in.readUBYTE()); in.skip(1); track.setTransparentColor(in.readUWORD()); track.setXAspect(in.readUBYTE()); track.setYAspect(in.readUBYTE()); track.setPageWidth(in.readWORD()); track.setPageHeight(in.readWORD()); in.close(); } catch (IOException e) { throw new ParseException(e.toString()); } } /** * Decodes the CAMG Chunk. * The required information from the BMHD chunk must be provided * by the ANIMMovieTrack. */ private void decodeCAMG(IFFChunk chunk, ANIMMovieTrack track) throws ParseException { if (chunk != null) { try { MC68000InputStream in = new MC68000InputStream(new ByteArrayInputStream(chunk.getData())); camg = in.readLONG(); in.close(); } catch (IOException e) { throw new ParseException(e.toString()); } } // Extract color mode bits switch (camg & (MODE_MASK | MODE_MASK)) { case EHB_MODE: track.setScreenMode(ANIMMovieTrack.MODE_EHB); break; case HAM_MODE: if (track.getNbPlanes() == 6) { track.setScreenMode(ANIMMovieTrack.MODE_HAM6); } else if (track.getNbPlanes() == 8) { track.setScreenMode(ANIMMovieTrack.MODE_HAM8); } else { throw new ParseException("unsupported Ham Mode with " + track.getNbPlanes() + " bitplanes"); } break; default: if (track.getNbPlanes() <= 8) { track.setScreenMode(ANIMMovieTrack.MODE_INDEXED_COLORS); } else { track.setScreenMode(ANIMMovieTrack.MODE_DIRECT_COLORS); } } // Extract monitor id bits int camgJiffies; switch (camg & MONITOR_ID_MASK) { case DEFAULT_MONITOR_ID: camgJiffies = 60; break; case NTSC_MONITOR_ID: camgJiffies = 60; break; case PAL_MONITOR_ID: camgJiffies = 50; break; case MULTISCAN_MONITOR_ID: camgJiffies = 58; break; case A2024_MONITOR_ID: camgJiffies = 60; break; // I don't know the real value case PROTO_MONITOR_ID: camgJiffies = 60; break; // I don't know the real value case EURO72_MONITOR_ID: camgJiffies = 69; break; case EURO36_MONITOR_ID: camgJiffies = 73; break; case DBLNTSC_MONITOR_ID: camgJiffies = 58; break; case DBLPAL_MONITOR_ID: camgJiffies = 48; break; case SUPER72_MONITOR_ID: camgJiffies = 71; break; default: camgJiffies = 60; break; } track.setJiffies(camgJiffies); } /** * Decodes the color map (ILBM CMAP). * The required information from the BMHD chunk and the CAMG chunk * must be provided by the ANIMMovieTrack. * * <pre> * typedef struct { * UBYTE red, green, blue; // color intesnities 0..255 * } ColorRegister; // size = 3 bytes * * typedef ColorRegister ColorMap[n]; // size = 3n bytes * </pre> */ private ColorModel decodeCMAP(IFFChunk chunk, ANIMMovieTrack track, boolean is4BitsPerChannel) throws ParseException { byte[] red; byte[] green; byte[] blue; int size = 0; int colorsToRead = 0; if (chunk == null) { return null; } byte[] cmapData = chunk.getData(); if (previousCMAPdata_ != null && Arrays.equals(cmapData, previousCMAPdata_)) { return null; } else { previousCMAPdata_ = cmapData; } switch (track.getScreenMode()) { case ANIMMovieTrack.MODE_EHB: size = 64; colorsToRead = Math.min(32, (int) chunk.getSize() / 3); break; case ANIMMovieTrack.MODE_HAM6: case ANIMMovieTrack.MODE_HAM8: size = 1 << (track.getNbPlanes() - 2); colorsToRead = Math.min(size, (int) chunk.getSize() / 3); break; case ANIMMovieTrack.MODE_INDEXED_COLORS: size = 1 << (track.getNbPlanes()); colorsToRead = Math.min(size, (int) chunk.getSize() / 3); break; case ANIMMovieTrack.MODE_DIRECT_COLORS: return new DirectColorModel(24, 0xFF0000, 0x00FF00, 0x0000FF); } red = new byte[size]; green = new byte[size]; blue = new byte[size]; byte[] data = chunk.getData(); int j = 0; if (is4BitsPerChannel) { for (int i = 0; i < colorsToRead; i++) { red[i] = (byte) (data[j] & 0xf0 | ((data[j] & 0xf0) >>> 4)); green[i] = (byte) (data[j + 1] & 0xf0 | ((data[j + 1] & 0xf0) >>> 4)); blue[i] = (byte) (data[j + 2] & 0xf0 | ((data[j + 2] & 0xf0) >>> 4)); j += 3; } } else { for (int i = 0; i < colorsToRead; i++) { red[i] = data[j++]; green[i] = data[j++]; blue[i] = data[j++]; } } switch (track.getScreenMode()) { case ANIMMovieTrack.MODE_EHB: j = 32; for (int i = 0; i < 32; i++, j++) { red[j] = (byte) ((red[i] & 255) / 2); green[j] = (byte) ((green[i] & 255) / 2); blue[j] = (byte) ((blue[i] & 255) / 2); } // Should return the effective number of planes, but // runs on more Java VM's when allways returning 8. return new IndexColorModel(8, 64, red, green, blue, -1); //return new IndexColorModel(track.getNbPlanes(),64,red,green,blue,-1); case ANIMMovieTrack.MODE_HAM6: return new HAMColorModel(HAMColorModel.HAM6, 16, red, green, blue, false); case ANIMMovieTrack.MODE_HAM8: return new HAMColorModel(HAMColorModel.HAM8, 64, red, green, blue, false); case ANIMMovieTrack.MODE_INDEXED_COLORS: // Should return the effective number of planes, but // runs on more Java VM's when allways returning 8. //return new IndexColorModel(8,(int)chunk.getSize() / 3,red,green,blue,-1); return new IndexColorModel(8, Math.min(red.length, (int) chunk.getSize() / 3), red, green, blue, -1); //return new IndexColorModel(track.getNbPlanes(),(int)chunk.getSize() / 3,red,green,blue); default: { throw new ParseException("ScreenMode not supported:" + track.getScreenMode()); } } } /** * Decodes the color cycling range and timing chunk (ILBM CCRT). * * <pre> * enum { * dontCycle = 0, forward = 1, backwards = -1 * } ccrtDirection; * typedef struct { * WORD enum ccrtDirection direction; // 0=don't cycle, 1=forward, -1=backwards * UBYTE start; // range lower * UBYTE end; // range upper * ULONG seconds; // seconds between cycling * ULONG microseconds; // msecs between cycling * WORD pad; // future exp - store 0 here * } ilbmColorCyclingRangeAndTimingChunk; * </pre> */ protected void decodeCCRT(IFFChunk chunk, ANIMMovieTrack track) throws ParseException { ColorCycle cc; try { MC68000InputStream in = new MC68000InputStream(new ByteArrayInputStream(chunk.getData())); int direction = in.readWORD(); int start = in.readUBYTE(); int end = in.readUBYTE(); long seconds = in.readULONG(); long microseconds = in.readULONG(); int pad = in.readWORD(); cc = new CRNGColorCycle(1000000 / (int) (seconds * 1000 + microseconds / 1000), 1000, start, end,// direction == 1 || direction == -1, // direction == 1, track.getScreenMode() == ANIMMovieTrack.MODE_EHB); in.close(); } catch (IOException e) { throw new ParseException(e.toString()); } if (cc.isActive()) { track.addColorCycle(cc); } } /** * Decodes the color range cycling (ILBM CRNG). * * <pre> * #define RNG_NORATE 36 // Dpaint uses this rate to mean non-active * set { * active = 1, reverse = 2 * } crngActive; * * // A CRange is store in a CRNG chunk. * typedef struct { * WORD pad1; // reserved for future use; store 0 here * * WORD rate; // 60/sec=16384, 30/sec=8192, 1/sec=16384/60=273 * WORD set crngActive flags; // bit0 set = active, bit 1 set = reverse * UBYTE low; UBYTE high; // lower and upper color registers selected * } ilbmColorRegisterRangeChunk; * </pre> */ protected void decodeCRNG(IFFChunk chunk, ANIMMovieTrack track) throws ParseException { try { ColorCycle cc; MC68000InputStream in = new MC68000InputStream(new ByteArrayInputStream(chunk.getData())); int pad1 = in.readUWORD(); int rate = in.readUWORD(); int flags = in.readUWORD(); int low = in.readUBYTE(); int high = in.readUBYTE(); //System.out.println("CRNG pad1:"+pad1+" rate:"+rate+" flags:"+flags+" low:"+low+" high:"+high); cc = new CRNGColorCycle(rate, 273, // low, high, // (flags & 1) != 0 && rate > 36 && high > low, // (flags & 2) != 0, // track.getScreenMode() == ANIMMovieTrack.MODE_EHB); if (cc.isActive()) { track.addColorCycle(cc); } in.close(); } catch (IOException e) { throw new ParseException(e.toString()); } } /** * Decodes the DPaint IV enhanced color cycle chunk (ILBM DRNG) * <p> * The RNG_ACTIVE flag is set when the range is cyclable. A range should * only have the RNG _ACTIVE if it: * <ol> * <li>contains at least one color register</li> * <li>has a defined rate</li> * <li>has more than one color and/or color register</li> * </ol> * <pre> * ILBM DRNG DPaint IV enhanced color cycle chunk * -------------------------------------------- * * set { * RNG_ACTIVE=1,RNG_DP_RESERVED=4 * } drngFlags; * * /* True color cell * / * typedef struct { * UBYTE cell; * UBYTE r; * UBYTE g; * UBYTE b; * } ilbmDRNGDColor; * * /* Color register cell * / * typedef struct { * UBYTE cell; * UBYTE index; * } ilbmDRNGDIndex; * * /* DRNG chunk. * / * typedef struct { * UBYTE min; /* min cell value * / * UBYTE max; /* max cell value * / * UWORD rate; /* color cycling rate, 16384 = 60 steps/second * / * UWORD set drngFlags flags; /* 1=RNG_ACTIVE, 4=RNG_DP_RESERVED * / * UBYTE ntrue; /* number of DColorCell structs to follow * / * UBYTE ntregs; /* number of DIndexCell structs to follow * / * ilbmDRNGDColor[ntrue] trueColorCells; * ilbmDRNGDIndex[ntregs] colorRegisterCells; * } ilbmDRangeChunk; * </pre> */ protected void decodeDRNG(IFFChunk chunk, ANIMMovieTrack track) throws ParseException { ColorCycle cc; try { MC68000InputStream in = new MC68000InputStream(new ByteArrayInputStream(chunk.getData())); int min = in.readUBYTE(); int max = in.readUBYTE(); int rate = in.readUWORD(); int flags = in.readUWORD(); int ntrue = in.readUBYTE(); int nregs = in.readUBYTE(); DRNGColorCycle.Cell[] cells = new DRNGColorCycle.Cell[ntrue + nregs]; for (int i = 0; i < ntrue; i++) { int cell = in.readUBYTE(); int rgb = (in.readUBYTE() << 16) | (in.readUBYTE() << 8) | in.readUBYTE(); cells[i] = new DRNGColorCycle.DColorCell(cell, rgb); } for (int i = 0; i < nregs; i++) { int cell = in.readUBYTE(); int index = in.readUBYTE(); cells[i + ntrue] = new DRNGColorCycle.DIndexCell(cell, index); } //System.out.println("DRNG min:"+min+" max:"+max+" rate:"+rate+" flags:"+flags+" ntrue:"+ntrue+" nregs:"+nregs); cc = new DRNGColorCycle(rate, 273, min, max, // (flags & 1) != 0 && rate > 36 && min <= max && ntrue + nregs > 1,// track.getScreenMode() == ANIMMovieTrack.MODE_EHB, cells); if (cc.isActive()) { track.addColorCycle(cc); } in.close(); } catch (IOException e) { throw new ParseException(e.toString()); } } /** * Process CRNG and DRNG chunks in the sequence of their * location in the file. */ protected void decodeColorCycling(IFFChunk[] ccrtChunks, IFFChunk[] crngChunks, IFFChunk[] drngChunks, ANIMMovieTrack track) throws ParseException { int activeCycles = 0; int j = 0, k = 0, l = 0; for (int i = 0, n = ccrtChunks.length + crngChunks.length + drngChunks.length; i < n; i++) { if (j < crngChunks.length // && (k >= drngChunks.length || crngChunks[j].getScan() < drngChunks[k].getScan())// && (l >= ccrtChunks.length || crngChunks[j].getScan() < ccrtChunks[l].getScan())) { decodeCRNG(crngChunks[j], track); j++; } else if (k < drngChunks.length // && (l >= ccrtChunks.length || drngChunks[k].getScan() < ccrtChunks[l].getScan())) { decodeDRNG(drngChunks[k], track); k++; } else { decodeCCRT(ccrtChunks[l], track); l++; } } track.setProperty("colorCycling", track.getColorCyclesCount()); } private void decodeBODY(ColorModel colorModel, IFFChunk group, IFFChunk body, ANIMMovieTrack track) throws ParseException { ANIMKeyFrame frame = new ANIMKeyFrame(); frame.setColorModel(colorModel); decodeANHD(group.getPropertyChunk(ANHD_ID), frame); if (group.getPropertyChunk(ANFI_ID) != null) { decodeANFI(group.getPropertyChunk(ANFI_ID), frame, track); } IFFChunk[] sctlChunks = group.getCollectionChunks(SCTL_ID); for (int i = 0; i < sctlChunks.length; i++) { decodeSCTL(sctlChunks[i], frame, track); } frame.cleanUpAudioCommands(); frame.setData(body.getData()); frame.setCompression(track.getCompression()); // This is not good, because subsequent body frames may be compressed differently. track.addFrame(frame); } private void decodeDLTA(ColorModel colorModel, IFFChunk group, IFFChunk dlta, ANIMMovieTrack track) throws ParseException { ANIMDeltaFrame frame = new ANIMDeltaFrame(); frame.setColorModel(colorModel); decodeANHD(group.getPropertyChunk(ANHD_ID), frame); if (group.getPropertyChunk(ANFI_ID) != null) { decodeANFI(group.getPropertyChunk(ANFI_ID), frame, track); } IFFChunk[] sctlChunks = group.getCollectionChunks(SCTL_ID); for (int i = 0; i < sctlChunks.length; i++) { decodeSCTL(sctlChunks[i], frame, track); } frame.cleanUpAudioCommands(); frame.setData(dlta.getData()); track.addFrame(frame); } /** * Decodes the anim header (ILBM ANHD). * * <pre> * typedef UBYTE Operation; // Choice of compression algorithm. * * #define opDirect 0 // set directly (normal ILBM BODY) * #define opXOR 1 // XOR ILBM mode * #define opLongDelta 2 // Long Delta mode * #define opShortDelta 3 // Short Delta Mode * #define opGeneralDelta 4 // Generalized short/long Delta mode * #define opByteVertical 5 // Byte Vertical Delta mode * #define opStereoDelta 6 // Stereo op 5 (third party) * #define opVertical7 7 // Short/Long Vertical Delta mode (opcodes and data stored separately) * #define opVertical8 8 // Short/Long Vertical Delta mode (opcodes and data combined) * #define opJ 74 // (ascii 'J') reserved for Eric Graham's compression technique * * typedef struct { * Operation operation; // The compression method. * UBYTE mask; // XOR mode only - plane mask where each * // bit is set =1 if there is data and =0 * // if not. * UWORD w,h; // XOR mode only - width and height of the * // area represented by the BODY to eliminate * // unnecessary un-changed data. * UWORD x,y; // XOR mode only - position of rectangular * // area represented by the BODY. * ULONG abstime; // currently unused - timing for a frame * // relative to the time the first frame * // was displayed - in jiffies (1/60 sec). * ULONG reltime; // timing for frame relative to time * // previous frame was displayed - in * // jiffies (1/60 sec). * UBYTE interleave;// unused so far - indicates how many frames * // back this data is to modify. =0 defaults * // to indicate two frames back (for double * // buffering). =n indicates n frames back. * // The main intent here is to allow values * // of =1 for special applications where * // frame data would modify the immediately * // previous frame. * UBYTE pad0; // Pad byte, not used at present. * ULONG bits; // 32 option bits used by opGeneralDelta, * // opByteVertical, opVertical7 and opVertical8. * // At present only 6 are identified, but the * // rest are set =0 tso they can be used to * // implement future ideas. These are defined * // for opGeneralData only at this point. It is * // recommended that all bits be set =0 for * // opByteVertical and that any bit settings used in * // the future (such as for XOR mode) be compatible * // with the opGeneralData settings. Player code * // should check undefined bits in opGeneralData and * // opByteVertical to assure they are zero. * // * // The six bits for current use are: * // * // bit # set =0 set =1 * // ======================================= * // 0 short data long data * // 1 set XOR * // 2 separate info one info list * // for each plane for all planes * // 3 not RLC RLC (run length coded) * // 4 horizontal vertical * // 5 short info offsets long info offsets * * UBYTE pad[16]; // This is a pad for future use for future * // compression modes. * } AnimHeader; * </pre> */ private void decodeANHD(IFFChunk chunk, ANIMFrame frame) throws ParseException { if (chunk != null) { try { MC68000InputStream in = new MC68000InputStream(new ByteArrayInputStream(chunk.getData())); frame.setOperation(in.readUBYTE()); frame.setMask(in.readUBYTE()); frame.setWidth(in.readUWORD()); frame.setHeight(in.readUWORD()); frame.setY(in.readUWORD()); frame.setX(in.readUWORD()); frame.setAbsTime(in.readULONG()); frame.setRelTime(in.readULONG()); frame.setInterleave(in.readUBYTE()); in.skip(1); frame.setBits((int) in.readULONG()); // in.skip(16); in.close(); } catch (IOException e) { throw new ParseException(e.toString()); } } } /** * Decodes the anim frame info (ILBM ANFI). * * <pre> * enum { * play = 0x28, * doNothing = 0x0 * } anfiCommand; * * typedef struct { * USHORT enum anfiCommand command; // What to do, see above * USHORT frequencyDivider; // frequency divider (Amiga's audio.device) * UBYTE sound; // Sound Number (starting at 1). * UBYTE channel; // Channel number (1 to 4, 0=invalid) * UBYTE repeats; // repeat count (0..2 = play once, 3..? = play 2 or more times) * UBYTE volume; // volume 00 to 40 (max should be 3F weird, perhaps volum 01 is 00 and 00 is channel off) * } anfiCommandInfo; * * typedef struct { * anfiCommandInfo[4] commandInfo; * UBYTE[4] pad4; // For future use * } animANFIChunk; * </pre> */ private void decodeANFI(IFFChunk chunk, ANIMFrame frame, ANIMMovieTrack track) throws ParseException { try { MC68000InputStream in = new MC68000InputStream(new ByteArrayInputStream(chunk.getData())); for (int i = 0; i < 4; i++) { int command = in.readUWORD(); int frequency = in.readUWORD(); int sound = in.readUBYTE(); int channel = in.readUBYTE(); int repeats = in.readUBYTE(); if (repeats > 2) { repeats -= 1; } else { repeats = 1; } int volume = in.readUBYTE(); //if (command == 0x28 || command == 0x81f) { if (command != 0) { ANIMAudioCommand audioCommand = new ANIMAudioCommand(ANIMAudioCommand.COMMAND_PLAY_SOUND, volume - 1, sound, repeats, 1 << channel, 0 /*frequency*/, 0); frame.addAudioCommand(audioCommand); audioCommand.prepare(track); } } //int pad = in.readULONG(); in.close(); } catch (IOException e) { throw new ParseException(e.toString()); } /* if (chunk != null) { try { MC68000InputStream in = new MC68000InputStream( new ByteArrayInputStream( chunk.getData() ) ) ; for (int i = 0; i < 4; i ++) { //in.skip(4); // skip pad bytes long pad1a = in.readWORD(); long pad1b = in.readWORD(); int audioclip = in.readUBYTE(); //in.skip(3); // skip pad bytes int repeatCount = in.readUBYTE(); int pad3 = in.readWORD(); if (audioclip != 0) { frame.setAudioClip(audioclip - 1, i, repeatCount); // System.out.println("channel:"+i+" clip:"+audioclip+ " pad1a:"+pad1a+" pad1b:"+pad1b +" repeat:"+repeatCount+" pad3:"+pad3); } } } catch (IOException e) { throw new ParseException(e.toString()); } }*/ } /** * Decodes the ANIM+SLA Sound Control collection chunk (ILBM SCTL). * * <pre> * typedef UBYTE Command; // Choice of commands * #define cmdPlaySound 1 // Start playing a sound * #define cmdStopSound 2 // Stop the sound in a given channel * #define cmdSetFreqvol 3 // Change frequency/volume for a channel * * typedef USHORT Flags; // Choice of flags * #define flagNoInterrupt 1 // Play the sound, but only if * // the channel isn't in use * * typedef struct { * Command command; // What to do, see above * UBYTE volume; // Volume 0..64 * UWORD sound, // Sound number (one based) * repeats, // Number of times to play the sound * channel, // Channel(s) to use for playing (bit mask) * frequency; // If non-zero, overrides the VHDR value * Flags flags; // Flags, see above * UBYTE pad[4]; // For future use * } SoundControl; * * </pre> */ private void decodeSCTL(IFFChunk chunk, ANIMFrame frame, ANIMMovieTrack track) throws ParseException { try { MC68000InputStream in = new MC68000InputStream(new ByteArrayInputStream(chunk.getData())); int command = in.readUBYTE(); int volume = in.readUBYTE(); int sound = in.readUWORD(); int repeats = in.readUWORD(); int channel = in.readUWORD(); int frequency = in.readUWORD(); int flags = in.readUWORD(); //int pad = in.readULONG(); in.close(); ANIMAudioCommand audioCommand = new ANIMAudioCommand(command, volume, sound, repeats, channel, frequency, flags); frame.addAudioCommand(audioCommand); audioCommand.prepare(track); } catch (IOException e) { throw new ParseException(e.toString()); } } protected void decodeCOPYRIGHT(IFFChunk[] chunks, ANIMMovieTrack track) throws ParseException { for (int i = 0; i < chunks.length; i++) { String copyright = new String(chunks[i].getData()); appendProperty("copyright", copyright); appendProperty("comment", "� " + copyright); } } protected void decodeAUTH(IFFChunk[] chunks, ANIMMovieTrack track) throws ParseException { for (int i = 0; i < chunks.length; i++) { String author = new String(chunks[i].getData()); appendProperty("author", author); appendProperty("comment", "Author " + author); } } protected void decodeANNO(IFFChunk[] chunks, ANIMMovieTrack track) throws ParseException { for (int i = 0; i < chunks.length; i++) { String anno = new String(chunks[i].getData()); appendProperty("annotation", anno); appendProperty("comment", anno); } } private void appendProperty(String name, String value) { String oldValue = (String) track.getProperty(name); if (oldValue == null) { track.setProperty(name, value); } else { track.setProperty(name, oldValue + "\n" + value); } } /* /* Normal identifiers. * / public final static int DEFAULT_MONITOR_ID = 0x00000000; public final static int NTSC_MONITOR_ID = 0x00011000; public final static int PAL_MONITOR_ID = 0x00021000; public final static int LORES_MASK = 0x00000000; public final static int LACE_MASK = 0x00000004; public final static int HIRES_MASK = 0x00008000; public final static int SUPER_MASK = 0x00008020; public final static int MODE_MASK = 0x00000800; public final static int DPF_MASK = 0x00000400; public final static int DPF2_MASK = 0x00000440; public final static int MODE_MASK = 0x00000080; /* The following 20 composite keys are for Modes on the default Monitor. NTSC & PAL "flavours" of these particular keys may be made by or'ing the NTSC or PAL MONITOR_ID with the desired MODE_KEY. * / public final static int LORES_KEY = 0x00000000; // NTSC:320*200,44x52 PAL:320x256,44x44 public final static int HIRES_KEY = 0x00008000; // NTSC:640*200,22x52 PAL:640*256,22x44 public final static int SUPER_KEY = 0x00008020; // NTSC:1280*200,11x52 PAL:1280x256,11x44 public final static int HAM_MODE = 0x00000800; // NTSC:320*200,44x52 PAL:320x256,44x44 public final static int LORESLACE_KEY = 0x00000004; // NTSC:320*400,44x26 PAL:320x512,44x22 public final static int HIRESLACE_KEY = 0x00008004; // NTSC:640*400,22x26 PAL:640x512,22x22 public final static int SUPERLACE_KEY = 0x00008024; // NTSC:1280*400,11x26 PAL:1280x512,11x22 public final static int HAMLACE_KEY = 0x00000804; // NTSC:320*400,44x26 PAL:320x512,44x22 public final static int LORESDPF_KEY = 0x00000400; // 320*240,256 public final static int HIRESDPF_KEY = 0x00008400; // 640*240,256 public final static int SUPERDPF_KEY = 0x00008420; // 1280*240,256 public final static int LORESLACEDPF_KEY = 0x00000404; // 320*480,512 public final static int HIRESLACEDPF_KEY = 0x00008404; // 640*480,512 public final static int SUPERLACEDPF_KEY = 0x00008424; // 1280*480,512 public final static int LORESDPF2_KEY = 0x00000440; // 320*240,256 public final static int HIRESDPF2_KEY = 0x00008440; // 640*240,256 public final static int SUPERDPF2_KEY = 0x00008460; // 1280*240,256 public final static int LORESLACEDPF2_KEY = 0x00000444; // 320*480,512 public final static int HIRESLACEDPF2_KEY = 0x00008444; // 640*480,512 public final static int SUPERLACEDPF2_KEY = 0x00008464; // 1280*480,512 public final static int EHB_MODE = 0x00000080; // NTSC:320*200,44x52 PAL:320*256,44x44 public final static int EXTRAHALFBRITELACE_KEY = 0x00000084; // NTSC:320*400,44x26 PAL:320*512,44x22 /* VGA identifiers. * / public final static int MULTISCAN_MONITOR_ID = 0x00031000; public final static int VGALACE_MASK = 0x00000001; public final static int VGALORES_MASK = 0x00008000; public final static int VGAEXTRALORES_KEY = 0x00031004; // 160*480 v 88x22 public final static int VGALORES_KEY = 0x00039004; // 320*480 v 44x22 public final static int VGAPRODUCT_KEY = 0x00039024; // 640*480 v 22x22 public final static int VGAHAM_KEY = 0x00031804; // public final static int VGAEXTRALORESLACE_KEY = 0x00031005; // 160*960 v 88x11 public final static int VGALORESLACE_KEY = 0x00039005; // 320*960 v 44x11 public final static int VGAPRODUCTLACE_KEY = 0x00039025; // 640*960 v 22x11 public final static int VGAHAMLACE_KEY = 0x00031805; // public final static int VGAEXTRALORESDPF_KEY = 0x00031404; // public final static int VGALORESDPF_KEY = 0x00039404; public final static int VGAPRODUCTDPF_KEY = 0x00039424; public final static int VGAEXTRALORESLACEDPF_KEY = 0x00031405; public final static int VGALORESLACEDPF_KEY = 0x00039405; public final static int VGAPRODUCTLACEDPF_KEY = 0x00039425; // public final static int VGAEXTRALORESDPF2_KEY = 0x00031444; public final static int VGALORESDPF2_KEY = 0x00039444; public final static int VGAPRODUCTDPF2_KEY = 0x00039464; public final static int VGAEXTRALORESLACEDPF2_KEY = 0x00031445; public final static int VGALORESLACEDPF2_KEY = 0x00039445; public final static int VGAPRODUCTLACEDPF2_KEY = 0x00039465; // 640*960 public final static int VGAEXTRAHALFBRITE_KEY = 0x00031084; public final static int VGAEXTRAHALFBRITELACE_KEY = 0x00031085; /* A2024 identifiers. * / public final static int A2024_MONITOR_ID = 0x00041000; public final static int A2024TENHERTZ_KEY = 0x00041000; public final static int A2024FIFTEENHERTZ_KEY = 0x00049000; /* Proto identifiers. * / public final static int PROTO_MONITOR_ID = 0x00051000; /* Euro identifiers * / public final static int EURO36_MONITOR_ID = 0x00071000; public final static int EURO36EXTRAHALFBRITE_KEY = 0x00071080; // 320*200 v 44x44 public final static int EURO36EXTRAHALFBRITELACE_KEY = 0x00071084; // 320*400 v 44x22 public final static int EURO36HAM_KEY = 0x00071800; // 320*200 v 44x44 public final static int EURO36HAMLACE_KEY = 0x00071804; // 320*400 v 44x22 public final static int EURO36HIRES_KEY = 0x00079000; // 640*200 v 22x44 public final static int EURO36HIRESLACE_KEY = 0x00079004; // 640*400 v 22x22 public final static int EURO36LORES_KEY = 0x00071000; // 320*200 v 44x44 public final static int EURO36LORESLACE_KEY = 0x00071004; // 320*400 v 44x22 public final static int EURO36SUPERHIRES_KEY = 0x00079020; // 1280*200 v 11x44 public final static int EURO36SUPERHIRESLACE_KEY = 0x00079024; // 1280*400 v 11x44 public final static int EURO72ECS_KEY = 0x00069004; // 320*400 v 44x22 public final static int EURO72ECSLACE_KEY = 0x00069005; // 320*800 v 44x11 public final static int EURO72PRODUCT_KEY = 0x00069024; // 640*400 v 22x22 public final static int EURO72PRODUCTLACE_KEY = 0x00069025; // 640*800 v 22x11 public final static int EURO_KEY = 0x00071000; */ }