//
// LegacyQTWriter.java
//
/*
LOCI Bio-Formats package for reading and converting biological file formats.
Copyright (C) 2005-@year@ Melissa Linkert, Curtis Rueden, Chris Allan,
Eric Kjellman and Brian Loranger.
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU Library General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Library General Public License for more details.
You should have received a copy of the GNU Library General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package loci.formats.out;
import java.awt.Image;
import java.awt.image.BufferedImage;
import java.io.*;
import loci.formats.*;
/**
* LegacyQTWriter is a file format writer for QuickTime movies. It uses the
* QuickTime for Java library, and allows the user to choose between a variety
* of common video codecs.
*
* Much of this code was based on the QuickTime Movie Writer for ImageJ
* (available at http://rsb.info.nih.gov/ij/plugins/movie-writer.html).
*
* <dl><dt><b>Source code:</b></dt>
* <dd><a href="https://skyking.microscopy.wisc.edu/trac/java/browser/trunk/loci/formats/out/LegacyQTWriter.java">Trac</a>,
* <a href="https://skyking.microscopy.wisc.edu/svn/java/trunk/loci/formats/out/LegacyQTWriter.java">SVN</a></dd></dl>
*/
public class LegacyQTWriter extends FormatWriter {
// -- Constants --
/** Time scale. */
private static final int TIME_SCALE = 600;
// -- Fields --
/** Instance of LegacyQTTools to handle QuickTime for Java detection. */
protected LegacyQTTools tools;
/** Reflection tool for QuickTime for Java calls. */
protected ReflectedUniverse r;
/** The codec to use. */
protected int codec = QTWriter.CODEC_RAW;
/** The quality to use. */
protected int quality = QTWriter.QUALITY_NORMAL;
/** Number of frames written. */
private int numWritten = 0;
/** Frame width. */
private int width;
/** Frame height. */
private int height;
private int[] pixels2 = null;
// -- Constructor --
public LegacyQTWriter() {
super("Legacy QuickTime", "mov");
}
// -- LegacyQTWriter API methods --
/**
* Sets the encoded movie's codec.
* @param codec Codec value:<ul>
* <li>QTWriter.CODEC_CINEPAK</li>
* <li>QTWriter.CODEC_ANIMATION</li>
* <li>QTWriter.CODEC_H_263</li>
* <li>QTWriter.CODEC_SORENSON</li>
* <li>QTWriter.CODEC_SORENSON_3</li>
* <li>QTWriter.CODEC_MPEG_4</li>
* <li>QTWriter.CODEC_RAW</li>
* </ul>
*/
public void setCodec(int codec) { this.codec = codec; }
/**
* Sets the quality of the encoded movie.
* @param quality Quality value:<ul>
* <li>QTWriter.QUALITY_LOW</li>
* <li>QTWriter.QUALITY_MEDIUM</li>
* <li>QTWriter.QUALITY_HIGH</li>
* <li>QTWriter.QUALITY_MAXIMUM</li>
* </ul>
*/
public void setQuality(int quality) { this.quality = quality; }
// -- IFormatWriter API methods --
/* @see loci.formats.IFormatWriter#saveImage(Image, boolean) */
public void saveImage(Image image, boolean last)
throws FormatException, IOException
{
if (tools == null) {
tools = new LegacyQTTools();
r = tools.getUniverse();
}
if (tools.isQTExpired()) {
throw new FormatException(LegacyQTTools.EXPIRED_QT_MSG);
}
if (!tools.canDoQT()) throw new FormatException(LegacyQTTools.NO_QT_MSG);
if (!initialized) {
initialized = true;
try {
r.exec("QTSession.open()");
BufferedImage img = ImageTools.makeBuffered(image);
width = img.getWidth();
height = img.getHeight();
File f = new File(currentId);
r.setVar("f", f);
r.setVar("width", (float) width);
r.setVar("height", (float) height);
r.exec("movFile = new QTFile(f)");
r.setVar("val", -2147483648 | 268435456);
r.setVar("kMoviePlayer", 1414942532);
r.exec("movie = Movie.createMovieFile(movFile, kMoviePlayer, val)");
int timeScale = TIME_SCALE;
r.setVar("timeScale", timeScale);
r.setVar("zero", 0);
r.setVar("zeroFloat", (float) 0);
r.exec("videoTrack = movie.addTrack(width, height, zeroFloat)");
r.exec("videoMedia = new VideoMedia(videoTrack, timeScale)");
r.exec("videoMedia.beginEdits()");
r.setVar("pixelFormat", 32);
r.exec("imgDesc2 = new ImageDescription(pixelFormat)");
r.setVar("width", width);
r.setVar("height", height);
r.exec("imgDesc2.setWidth(width)");
r.exec("imgDesc2.setHeight(height)");
r.exec("gw = new QDGraphics(imgDesc2, zero)");
r.exec("bounds = new QDRect(zero, zero, width, height)");
r.exec("pixMap = gw.getPixMap()");
r.exec("pixSize = pixMap.getPixelSize()");
r.setVar("codec", codec);
r.setVar("quality", quality);
int rawImageSize = width * height * 4;
r.setVar("rawImageSize", rawImageSize);
r.setVar("boolTrue", true);
r.exec("imageHandle = new QTHandle(rawImageSize, boolTrue)");
r.exec("imageHandle.lock()");
r.exec("compressedImage = RawEncodedImage.fromQTHandle(imageHandle)");
r.setVar("rate", 30);
r.exec("seq = new CSequence(gw, bounds, pixSize, codec, " +
"CodecComponent.bestFidelityCodec, quality, quality, rate, null, " +
"zero)");
r.exec("imgDesc = seq.getDescription()");
}
catch (ReflectException e) {
trace(e);
throw new FormatException("Legacy QuickTime writer failed", e);
}
}
numWritten++;
try {
r.exec("pixMap = gw.getPixMap()");
r.exec("pixelData = pixMap.getPixelData()");
r.exec("intsPerRow = pixelData.getRowBytes()");
int intsPerRow = ((Integer) r.getVar("intsPerRow")).intValue() / 4;
byte[][] px = ImageTools.getBytes(ImageTools.makeBuffered(image));
int[] pixels = new int[px[0].length];
for (int i=0; i<pixels.length; i++) {
byte[] b = new byte[4];
for (int j=0; j<px.length; j++) {
b[j] = px[j][i];
}
for (int j=px.length; j<4; j++) {
b[j] = px[j % px.length][i];
}
pixels[i] = DataTools.bytesToInt(b, true);
}
if (pixels2 == null) pixels2 = new int[intsPerRow * height];
r.exec("nativeLittle = EndianOrder.isNativeLittleEndian()");
boolean nativeLittle =
((Boolean) r.getVar("nativeLittle")).booleanValue();
if (nativeLittle) {
int offset1, offset2;
for (int y=0; y<height; y++) {
offset1 = y * width;
offset2 = y * intsPerRow;
for (int x=0; x<width; x++) {
r.setVar("thisByte", pixels[offset1++]);
r.exec("b = EndianOrder.flipBigEndianToNative32(thisByte)");
pixels2[offset2++] = ((Integer) r.getVar("b")).intValue();
}
}
}
else {
for (int i=0; i<height; i++) {
System.arraycopy(pixels, i*width, pixels2, i*intsPerRow, width);
}
}
r.setVar("pixels2", pixels2);
r.setVar("len", intsPerRow * height);
r.exec("pixelData.copyFromArray(zero, pixels2, zero, len)");
r.setVar("four", 4);
r.exec("cfInfo = seq.compressFrame(gw, bounds, four, compressedImage)");
// see developer.apple.com/qa/qtmcc/qtmcc20.html
r.exec("similarity = cfInfo.getSimilarity()");
int sim = ((Integer) r.getVar("similarity")).intValue();
r.setVar("syncSample", sim == 0);
r.exec("dataSize = cfInfo.getDataSize()");
r.setVar("fps", fps);
r.setVar("frameRate", 600);
r.setVar("rate", 600 / fps);
boolean sync = ((Boolean) r.getVar("syncSample")).booleanValue();
int syncSample = sync ? 0 : 1;
r.setVar("sync", syncSample);
r.setVar("one", 1);
r.exec("videoMedia.addSample(imageHandle, zero, dataSize, " +
"rate, imgDesc, one, sync)");
}
catch (ReflectException e) {
trace(e);
throw new FormatException("Legacy QuickTime writer failed", e);
}
if (last) {
try {
r.exec("videoMedia.endEdits()");
r.exec("duration = videoMedia.getDuration()");
r.setVar("floatOne", (float) 1.0);
r.exec("videoTrack.insertMedia(zero, zero, duration, floatOne)");
r.exec("omf = OpenMovieFile.asWrite(movFile)");
r.exec("name = movFile.getName()");
r.setVar("minusOne", -1);
r.exec("movie.addResource(omf, minusOne, name)");
r.exec("QTSession.close()");
}
catch (ReflectException e) {
trace(e);
throw new FormatException("Legacy QuickTime writer failed", e);
}
}
}
/* @see loci.formats.IFormatWriter#canDoStacks() */
public boolean canDoStacks() { return true; }
// -- IFormatHandler API methods --
/* @see loci.formats.IFormatHandler#close() */
public void close() throws IOException {
r = null;
numWritten = 0;
width = 0;
height = 0;
pixels2 = null;
currentId = null;
initialized = false;
}
}