
org.monte.media.anim.ANIMOutputStream Maven / Gradle / Ivy
The newest version!
package org.monte.media.anim;
import java.util.Map;
import java.util.HashMap;
import org.monte.media.image.BitmapImage;
import org.monte.media.io.SeekableByteArrayOutputStream;
import org.monte.media.iff.IFFOutputStream;
import java.awt.image.IndexColorModel;
import java.io.File;
import java.io.IOException;
import java.util.Arrays;
import java.util.Collections;
import javax.imageio.stream.FileImageOutputStream;
import static java.lang.Math.*;
public class ANIMOutputStream {
public final static int MONITOR_ID_MASK = 0xffff1000;
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 MULTISCAN_MONITOR_ID = 0x00031000;
public final static int A2024_MONITOR_ID = 0x00041000;
public final static int PROTO_MONITOR_ID = 0x00051000;
public final static int EURO72_MONITOR_ID = 0x00061000;
public final static int EURO36_MONITOR_ID = 0x00071000;
public final static int SUPER72_MONITOR_ID = 0x00081000;
public final static int DBLNTSC_MONITOR_ID = 0x00091000;
public final static int DBLPAL_MONITOR_ID = 0x00001000;
public final static int MODE_MASK = 0x00000880;
public final static int HAM_MODE = 0x00000800;
public final static int EHB_MODE = 0x00000080;
private int jiffies = 60;
private int camg;
private boolean debug = false;
private IFFOutputStream out = null;
protected int frameCount = 0;
protected int absTime = 0;
private BitmapImage oddPrev;
private BitmapImage evenPrev;
private BitmapImage firstFrame;
private int firstWrapupDuration = 1;
private int secondWrapupDuration = 1;
private long numberOfFramesOffset = -1;
private static enum States {
REALIZED, STARTED, FINISHED, CLOSED;
}
private States state = States.REALIZED;
public ANIMOutputStream(File file) throws IOException {
out = new IFFOutputStream(new FileImageOutputStream(file));
}
public void setJiffies(int newValue) {
this.jiffies = newValue;
}
public int getJiffies() {
return this.jiffies;
}
public void setCAMG(int newValue) {
this.camg = newValue;
AmigaDisplayInfo info=AmigaDisplayInfo.getInfo(newValue);
if (info!=null)this.jiffies=info.fps;
}
public int getCAMG() {
return this.camg;
}
private void ensureOpen() throws IOException {
if (state == States.CLOSED) {
throw new IOException("Stream closed");
}
}
private void ensureStarted() throws IOException {
ensureOpen();
if (state == States.FINISHED) {
throw new IOException("Can not write into finished movie.");
}
if (state != States.STARTED) {
writeProlog();
state = States.STARTED;
}
}
public void finish() throws IOException {
ensureOpen();
if (state != States.FINISHED) {
writeEpilog();
out.finish();
state = States.FINISHED;
}
}
public void close() throws IOException {
try {
if (state == States.STARTED) {
finish();
}
} finally {
if (state != States.CLOSED) {
out.close();
state = States.CLOSED;
}
}
}
private void writeProlog() throws IOException {
out.pushCompositeChunk("FORM", "ANIM");
}
private void writeEpilog() throws IOException {
if (frameCount > 0) {
writeDeltaFrame(firstFrame, firstWrapupDuration);
writeDeltaFrame(firstFrame, secondWrapupDuration);
}
out.popChunk();
if (numberOfFramesOffset!=-1) {
long pos=out.getStreamPosition();
out.seek(numberOfFramesOffset);
out.writeUWORD(max(0,frameCount-2));
out.seek(pos);
}
}
public void writeFrame(BitmapImage image, int duration) throws IOException {
ensureStarted();
if (frameCount == 0) {
writeFirstFrame(image, duration);
} else {
writeDeltaFrame(image, duration);
}
}
private void writeFirstFrame(BitmapImage img, int duration) throws IOException {
out.pushCompositeChunk("FORM", "ILBM");
writeBMHD(out, img);
writeCMAP(out, img);
writeANHD(out, img.getWidth(), img.getHeight(), 0, absTime, duration);
writeCAMG(out, camg);
writeBODY(out, img);
out.popChunk();
firstFrame = new BitmapImage(img.getWidth(), img.getHeight(), img.getDepth(), img.getPlanarColorModel());
oddPrev = new BitmapImage(img.getWidth(), img.getHeight(), img.getDepth(), img.getPlanarColorModel());
evenPrev = new BitmapImage(img.getWidth(), img.getHeight(), img.getDepth(), img.getPlanarColorModel());
System.arraycopy(img.getBitmap(), 0, firstFrame.getBitmap(), 0, img.getBitmap().length);
System.arraycopy(img.getBitmap(), 0, oddPrev.getBitmap(), 0, img.getBitmap().length);
System.arraycopy(img.getBitmap(), 0, evenPrev.getBitmap(), 0, img.getBitmap().length);
absTime += duration;
firstWrapupDuration = secondWrapupDuration = duration;
frameCount++;
}
private void writeDeltaFrame(BitmapImage img, int duration) throws IOException {
BitmapImage prev = (frameCount & 1) == 0 ? evenPrev : oddPrev;
BitmapImage immPrev = (frameCount & 1) == 0 ? oddPrev : evenPrev;
out.pushCompositeChunk("FORM", "ILBM");
writeANHD(out, img.getWidth(), img.getHeight(), 0x5, absTime, duration);
writeCMAP(out, img, immPrev);
writeDLTA(out, img, prev);
out.popChunk();
System.arraycopy(img.getBitmap(), 0, prev.getBitmap(), 0, prev.getBitmap().length);
prev.setPlanarColorModel(img.getPlanarColorModel());
absTime += duration;
firstWrapupDuration = secondWrapupDuration = duration;
frameCount++;
}
public long getMovieTime() {return absTime;}
private void writeBMHD(IFFOutputStream out, BitmapImage img) throws IOException {
AmigaDisplayInfo info=AmigaDisplayInfo.getInfo(camg);
if (info==null)info=AmigaDisplayInfo.getInfo(AmigaDisplayInfo.DEFAULT_MONITOR_ID);
out.pushDataChunk("BMHD");
out.writeUWORD(img.getWidth());
out.writeUWORD(img.getHeight());
out.writeWORD(0);
out.writeWORD(0);
out.writeUBYTE(img.getDepth());
out.writeUBYTE(0);
out.writeUBYTE(1);
out.writeUBYTE(0);
out.writeUWORD(0);
out.writeUBYTE(info.resolutionX);
out.writeUBYTE(info.resolutionY);
out.writeUWORD(img.getWidth());
out.writeUWORD(img.getHeight());
out.popChunk();
}
private void writeCMAP(IFFOutputStream out, BitmapImage img) throws IOException {
out.pushDataChunk("CMAP");
IndexColorModel cm = (IndexColorModel) img.getPlanarColorModel();
for (int i = 0, n = cm.getMapSize(); i < n; ++i) {
out.writeUBYTE(cm.getRed(i));
out.writeUBYTE(cm.getGreen(i));
out.writeUBYTE(cm.getBlue(i));
}
out.popChunk();
}
private void writeCMAP(IFFOutputStream out, BitmapImage img, BitmapImage prev) throws IOException {
IndexColorModel cm = (IndexColorModel) img.getPlanarColorModel();
IndexColorModel prevCm = (IndexColorModel) prev.getPlanarColorModel();
boolean equals = true;
for (int i = 0, n = cm.getMapSize(); i < n; ++i) {
if (cm.getRGB(i) != prevCm.getRGB(i)) {
equals = false;
break;
}
}
if (!equals) {
writeCMAP(out, img);
}
}
private void writeCAMG(IFFOutputStream out, int camg) throws IOException {
out.pushDataChunk("CAMG");
out.writeLONG(camg);
out.popChunk();
}
private void writeDPAN(IFFOutputStream out) throws IOException {
out.pushDataChunk("DPAN");
out.writeUWORD(4);
numberOfFramesOffset = out.getStreamPosition();
out.writeUWORD(-1);
out.writeULONG(0);
out.popChunk();
}
private void writeBODY(IFFOutputStream out, BitmapImage img) throws IOException {
out.pushDataChunk("BODY");
int widthInBytes = (img.getWidth() + 7) / 8;
int ss = img.getScanlineStride();
int bs = img.getBitplaneStride();
int offset = 0;
byte[] data = img.getBitmap();
for (int y = 0, h = img.getHeight(); y < h; y++) {
for (int p = 0, d = img.getDepth(); p < d; p++) {
out.writeByteRun1(data, offset + bs * p, widthInBytes);
}
offset += ss;
}
out.popChunk();
}
private void writeDLTA(IFFOutputStream out, BitmapImage img, BitmapImage prev) throws IOException {
out.pushDataChunk("DLTA");
int height = img.getHeight();
int widthInBytes = (img.getWidth() + 7) / 8;
int ss = img.getScanlineStride();
int bs = img.getBitplaneStride();
int offset = 0;
byte[] data = img.getBitmap();
byte[] prevData = prev.getBitmap();
SeekableByteArrayOutputStream buf = new SeekableByteArrayOutputStream();
byte[][] planes = new byte[16][0];
int depth = img.getDepth();
for (int p = 0; p < depth; ++p) {
buf.reset();
for (int column = 0; column < widthInBytes; ++column) {
writeByteVertical(buf, data, prevData, bs * p + column, height, ss);
}
planes[p] = buf.toByteArray();
if (planes[p].length == widthInBytes) {
planes[p] = new byte[0];
}
}
int[] pPointers = new int[16];
for (int p = 0; p < depth; ++p) {
if (planes[p].length == 0) {
pPointers[p] = 0;
} else {
pPointers[p] = 16 * 4;
for (int q = 0; q < p; ++q) {
if (Arrays.equals(planes[q], planes[p])) {
pPointers[p] = pPointers[q];
planes[p] = new byte[0];
break;
}
pPointers[p] += planes[q].length;
}
}
}
for (int p = 0; p < pPointers.length; ++p) {
out.writeULONG(pPointers[p]);
}
for (int p = 0; p
< planes.length;
++p) {
out.write(planes[p]);
}
out.popChunk();
}
private void writeByteVertical(SeekableByteArrayOutputStream out, byte[] data, byte[] prev, int offset, int length, int step) throws IOException {
int opCount = 0;
long opCountPos = out.getStreamPosition();
out.write(0);
int literalOffset = 0;
int i;
for (i = 0; i < length; i++) {
int skipCount = i;
for (; skipCount < length; skipCount++) {
if (data[offset + skipCount * step] != prev[offset + skipCount * step]) {
break;
}
}
skipCount = skipCount - i;
if (skipCount + i == length) {
break;
}
if (skipCount > 0 && literalOffset == i
|| skipCount > 1) {
if (literalOffset < i) {
opCount++;
out.write(0x80 | (i - literalOffset));
for (int j = literalOffset; j < i; j++) {
out.write(data[offset + j * step]);
}
}
i += skipCount - 1;
literalOffset = i + 1;
for (; skipCount > 127; skipCount -= 127) {
opCount++;
out.write(127);
}
opCount++;
out.write(skipCount);
} else {
byte b = data[offset + i * step];
int repeatCount = i + 1;
for (; repeatCount < length; repeatCount++) {
if (data[offset + repeatCount * step] != b) {
break;
}
}
repeatCount = repeatCount - i;
if (repeatCount == 1) {
if (i - literalOffset > 126) {
opCount++;
out.write(0x80 | (i - literalOffset));
for (int j = literalOffset; j < i; j++) {
out.write(data[offset + j * step]);
}
literalOffset = i;
}
} else if (repeatCount < 4
&& literalOffset < i && i - literalOffset < 126) {
i++;
} else {
if (literalOffset < i) {
opCount++;
out.write(0x80 | (i - literalOffset));
for (int j = literalOffset; j < i; j++) {
out.write(data[offset + j * step]);
}
}
i += repeatCount - 1;
literalOffset = i + 1;
for (; repeatCount
> 255; repeatCount -= 255) {
opCount++;
out.write(0);
out.write(255);
out.write(b);
}
opCount++;
out.write(0);
out.write(repeatCount);
out.write(b);
}
}
}
if (literalOffset < i) {
opCount++;
out.write(0x80 | (i - literalOffset));
for (int j = literalOffset; j < i; j++) {
out.write(data[offset + j * step]);
}
}
long pos = out.getStreamPosition();
out.seek(opCountPos);
out.write(opCount);
out.seek(pos);
}
private void writeANHD(IFFOutputStream out, int width, int height, int compressionMode, int absTime, int relTime) throws IOException {
out.pushDataChunk("ANHD");
out.writeUBYTE(compressionMode);
out.writeUBYTE(0);
out.writeUWORD(width);
out.writeUWORD(height);
out.writeUWORD(0);
out.writeUWORD(0);
out.writeULONG(absTime);
out.writeULONG(relTime);
out.writeUBYTE(0);
out.writeUBYTE(0);
out.writeULONG(0); // bits
out.writeULONG(0); // pad
out.writeULONG(0);
out.writeULONG(0);
out.writeULONG(0);
out.popChunk();
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy