ru.sbtqa.monte.media.anim.ANIMDecoder Maven / Gradle / Ivy
/* @(#)ANIMDecoder.java
* Copyright © 1999-2011 Werner Randelshofer, Switzerland.
* You may only use this software in accordance with the license terms.
*/
package ru.sbtqa.monte.media.anim;
import java.applet.AudioClip;
import java.awt.image.*;
import java.io.*;
import java.net.URL;
import java.util.*;
import ru.sbtqa.monte.media.AbortException;
import ru.sbtqa.monte.media.ParseException;
import ru.sbtqa.monte.media.eightsvx.EightSVXDecoder;
import ru.sbtqa.monte.media.iff.IFFChunk;
import ru.sbtqa.monte.media.iff.IFFParser;
import ru.sbtqa.monte.media.iff.IFFVisitor;
import ru.sbtqa.monte.media.iff.MC68000InputStream;
import ru.sbtqa.monte.media.ilbm.CRNGColorCycle;
import ru.sbtqa.monte.media.ilbm.ColorCycle;
import ru.sbtqa.monte.media.ilbm.DRNGColorCycle;
import ru.sbtqa.monte.media.ilbm.HAMColorModel;
/**
* 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.
*
2.1 2010-04-11 Adds support for CCRT color cycling.
*
2.1 2010-01-22 Adds support for CRNG color cycling.
*
2.0 2009-12-25 Treat an ILBM file as an animation with a single frame.
*
1.3 2009-12-24 Added support for CRNG color cycling.
*
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.
*
1.2 2003-04-21 Decode ANFI revised.
*
1.0 2003-04-03 Support for ANIM+SLA (Animations with Statically Loaded
* Audio) files added.
*
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;
/**
* 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) {
this.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.
* @throws java.io.IOException TODO
*/
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).
*
*
* 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;
*
*/
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.
*
*
* typedef struct {
* UBYTE red, green, blue; // color intesnities 0..255
* } ColorRegister; // size = 3 bytes
*
* typedef ColorRegister ColorMap[n]; // size = 3n bytes
*
*/
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).
*
*
* 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;
*
*
* @param chunk TODO
* @param track TODO
* @throws ru.sbtqa.monte.media.ParseException TODO
*/
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).
*
*
* #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;
*
*
* @param chunk TODO
* @param track TODO
* @throws ru.sbtqa.monte.media.ParseException TODO
*/
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)
*
* The RNG_ACTIVE flag is set when the range is cyclable. A range should
* only have the RNG _ACTIVE if it:
*
* contains at least one color register
* has a defined rate
* has more than one color and/or color register
*
*
* 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;
*
*
* @param chunk TODO
* @param track TODO
* @throws ru.sbtqa.monte.media.ParseException TODO
*/
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.
*
* @param ccrtChunks TODO
* @param track TODO
* @param crngChunks TODO
* @param drngChunks TODO
* @throws ru.sbtqa.monte.media.ParseException TODO
*/
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).
*
*
* 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;
*
*/
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).
*
*
* 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;
*
*/
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).
*
*
* 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;
*
*
*/
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;
*/
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy