//
// AVIReader.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.in;
import java.io.*;
import java.util.Vector;
import loci.formats.*;
import loci.formats.codec.BitBuffer;
import loci.formats.codec.*;
/**
* AVIReader is the file format reader for AVI files.
*
* Much of this code was adapted from Wayne Rasband's AVI Movie Reader
* plugin for ImageJ (available at http://rsb.info.nih.gov/ij).
*
* <dl><dt><b>Source code:</b></dt>
* <dd><a href="https://skyking.microscopy.wisc.edu/trac/java/browser/trunk/loci/formats/in/AVIReader.java">Trac</a>,
* <a href="https://skyking.microscopy.wisc.edu/svn/java/trunk/loci/formats/in/AVIReader.java">SVN</a></dd></dl>
*/
public class AVIReader extends FormatReader {
// -- Constants --
/** Supported compression types. */
private static final int MSRLE = 1;
private static final int MS_VIDEO = 1296126531;
// -- Fields --
/** Offset to each plane. */
private Vector offsets;
/** Number of bytes in each plane. */
private Vector lengths;
private String type = "error";
private String fcc = "error";
private int size = -1;
private long pos;
// Stream Format chunk fields
private int bmpColorsUsed, bmpWidth;
private int bmpCompression, bmpScanLineSize;
private short bmpBitsPerPixel;
private byte[][] lut = null;
private byte[] lastImage;
// -- Constructor --
/** Constructs a new AVI reader. */
public AVIReader() { super("Audio Video Interleave", "avi"); }
// -- IFormatReader API methods --
/* @see loci.formats.IFormatReader#isThisType(byte[]) */
public boolean isThisType(byte[] block) {
return new String(block).startsWith("RIFF");
}
/* @see loci.formats.IFormatReader#get8BitLookupTable() */
public byte[][] get8BitLookupTable() throws FormatException, IOException {
FormatTools.assertId(currentId, true, 1);
return lut;
}
/* @see loci.formats.IFormatReader#openBytes(int, byte[]) */
public byte[] openBytes(int no, byte[] buf)
throws FormatException, IOException
{
FormatTools.assertId(currentId, true, 1);
FormatTools.checkPlaneNumber(this, no);
FormatTools.checkBufferSize(this, buf.length);
int bytes = FormatTools.getBytesPerPixel(core.pixelType[0]);
double p = ((double) bmpScanLineSize) / bmpBitsPerPixel;
int effectiveWidth = (int) (bmpScanLineSize / p);
if (effectiveWidth == 0 || effectiveWidth < core.sizeX[0]) {
effectiveWidth = core.sizeX[0];
}
long fileOff = ((Long) offsets.get(no)).longValue();
in.seek(fileOff);
if (bmpCompression != 0) return uncompress(no, buf);
if (bmpBitsPerPixel < 8) {
int rawSize = bytes * core.sizeY[0] * effectiveWidth * core.sizeC[0];
rawSize /= (8 / bmpBitsPerPixel);
byte[] b = new byte[rawSize];
int len = rawSize / core.sizeY[0];
for (int y=0; y<core.sizeY[0]; y++) {
in.read(b, (core.sizeY[0] - y - 1) * len, len);
}
BitBuffer bb = new BitBuffer(b);
for (int i=0; i<buf.length; i++) {
buf[i] = (byte) bb.getBits(bmpBitsPerPixel);
}
return buf;
}
int pad = bmpScanLineSize - core.sizeX[0]*(bmpBitsPerPixel / 8);
int scanline = core.sizeX[0] * (bmpBitsPerPixel / 8);
for (int i=core.sizeY[0] - 1; i>=0; i--) {
in.read(buf, i*scanline, scanline);
if (bmpBitsPerPixel == 24) {
for (int j=0; j<core.sizeX[0]; j++) {
byte r = buf[i*scanline + j*3 + 2];
buf[i*scanline + j*3 + 2] = buf[i*scanline + j*3];
buf[i*scanline + j*3] = r;
}
}
in.skipBytes(pad * (bmpBitsPerPixel / 8));
}
if (bmpBitsPerPixel == 16) {
// channels are separated, need to swap them
byte[] r = new byte[core.sizeX[0] * core.sizeY[0] * 2];
System.arraycopy(buf, 2 * (buf.length / 3), r, 0, r.length);
System.arraycopy(buf, 0, buf, 2 * (buf.length / 3), r.length);
System.arraycopy(r, 0, buf, 0, r.length);
}
return buf;
}
// -- Internal FormatReader API methods --
/* @see loci.formats.FormatReader#initFile(String) */
protected void initFile(String id) throws FormatException, IOException {
if (debug) debug("AVIReader.initFile(" + id + ")");
super.initFile(id);
in = new RandomAccessStream(id);
in.order(true);
status("Verifying AVI format");
offsets = new Vector();
lengths = new Vector();
String listString;
type = in.readString(4);
size = in.readInt();
fcc = in.readString(4);
if (type.equals("RIFF")) {
if (!fcc.equals("AVI ")) {
throw new FormatException("Sorry, AVI RIFF format not found.");
}
}
else throw new FormatException("Not an AVI file");
pos = in.getFilePointer();
long spos = pos;
status("Searching for image data");
while ((in.length() - in.getFilePointer()) > 4) {
listString = in.readString(4);
in.seek(pos);
if (listString.equals(" JUN")) {
in.skipBytes(1);
pos++;
}
if (listString.equals("JUNK")) {
type = in.readString(4);
size = in.readInt();
if (type.equals("JUNK")) {
in.skipBytes(size);
}
}
else if (listString.equals("LIST")) {
spos = in.getFilePointer();
type = in.readString(4);
size = in.readInt();
fcc = in.readString(4);
in.seek(spos);
if (fcc.equals("hdrl")) {
type = in.readString(4);
size = in.readInt();
fcc = in.readString(4);
if (type.equals("LIST")) {
if (fcc.equals("hdrl")) {
type = in.readString(4);
size = in.readInt();
if (type.equals("avih")) {
spos = in.getFilePointer();
addMeta("Microseconds per frame", new Integer(in.readInt()));
addMeta("Max. bytes per second", new Integer(in.readInt()));
in.skipBytes(8);
addMeta("Total frames", new Integer(in.readInt()));
addMeta("Initial frames", new Integer(in.readInt()));
in.skipBytes(8);
core.sizeX[0] = in.readInt();
addMeta("Frame height", new Integer(in.readInt()));
addMeta("Scale factor", new Integer(in.readInt()));
addMeta("Frame rate", new Integer(in.readInt()));
addMeta("Start time", new Integer(in.readInt()));
addMeta("Length", new Integer(in.readInt()));
addMeta("Frame width", new Integer(core.sizeX[0]));
if (spos + size <= in.length()) {
in.seek(spos + size);
}
}
}
}
}
else if (fcc.equals("strl")) {
long startPos = in.getFilePointer();
long streamSize = size;
type = in.readString(4);
size = in.readInt();
fcc = in.readString(4);
if (type.equals("LIST")) {
if (fcc.equals("strl")) {
type = in.readString(4);
size = in.readInt();
if (type.equals("strh")) {
spos = in.getFilePointer();
in.skipBytes(40);
addMeta("Stream quality", new Integer(in.readInt()));
addMeta("Stream sample size", new Integer(in.readInt()));
if (spos + size <= in.length()) {
in.seek(spos + size);
}
}
type = in.readString(4);
size = in.readInt();
if (type.equals("strf")) {
spos = in.getFilePointer();
in.skipBytes(4);
bmpWidth = in.readInt();
core.sizeY[0] = in.readInt();
in.skipBytes(2);
bmpBitsPerPixel = in.readShort();
bmpCompression = in.readInt();
in.skipBytes(4);
addMeta("Horizontal resolution", new Integer(in.readInt()));
addMeta("Vertical resolution", new Integer(in.readInt()));
bmpColorsUsed = in.readInt();
in.skipBytes(4);
addMeta("Bitmap compression value",
new Integer(bmpCompression));
addMeta("Number of colors used", new Integer(bmpColorsUsed));
addMeta("Bits per pixel", new Integer(bmpBitsPerPixel));
// scan line is padded with zeros to be a multiple of 4 bytes
int npad = bmpWidth % 4;
if (npad > 0) npad = 4 - npad;
bmpScanLineSize = (bmpWidth + npad) * (bmpBitsPerPixel / 8);
int bmpActualColorsUsed = 0;
if (bmpColorsUsed != 0) {
bmpActualColorsUsed = bmpColorsUsed;
}
else if (bmpBitsPerPixel < 16) {
// a value of 0 means we determine this based on the
// bits per pixel
bmpActualColorsUsed = 1 << bmpBitsPerPixel;
}
if (bmpCompression != MSRLE && bmpCompression != 0 &&
bmpCompression != MS_VIDEO)
{
throw new FormatException("Unsupported compression type " +
bmpCompression);
}
if (!(bmpBitsPerPixel == 4 || bmpBitsPerPixel == 8 ||
bmpBitsPerPixel == 24 || bmpBitsPerPixel == 16 ||
bmpBitsPerPixel == 32))
{
throw new FormatException(bmpBitsPerPixel +
" bits per pixel not supported");
}
if (bmpActualColorsUsed != 0) {
// read the palette
lut = new byte[3][bmpColorsUsed];
for (int i=0; i<bmpColorsUsed; i++) {
lut[2][i] = in.readByte();
lut[1][i] = in.readByte();
lut[0][i] = in.readByte();
in.skipBytes(1);
}
}
in.seek(spos + size);
}
}
spos = in.getFilePointer();
type = in.readString(4);
size = in.readInt();
if (type.equals("strd")) {
in.skipBytes(size);
}
else {
in.seek(spos);
}
spos = in.getFilePointer();
type = in.readString(4);
size = in.readInt();
if (type.equals("strn")) {
in.skipBytes(size);
}
else {
in.seek(spos);
}
}
if (startPos + streamSize + 8 <= in.length()) {
in.seek(startPos + 8 + streamSize);
}
}
else if (fcc.equals("movi")) {
type = in.readString(4);
size = in.readInt();
fcc = in.readString(4);
if (type.equals("LIST")) {
if (fcc.equals("movi")) {
spos = in.getFilePointer();
if (spos >= in.length() - 12) break;
type = in.readString(4);
size = in.readInt();
fcc = in.readString(4);
if (!(type.equals("LIST") && fcc.equals("rec "))) {
in.seek(spos);
}
spos = in.getFilePointer();
type = in.readString(4);
size = in.readInt();
while (type.substring(2).equals("db") ||
type.substring(2).equals("dc") ||
type.substring(2).equals("wb"))
{
if (type.substring(2).equals("db") ||
type.substring(2).equals("dc"))
{
offsets.add(new Long(in.getFilePointer()));
lengths.add(new Long(size));
in.skipBytes(size);
}
spos = in.getFilePointer();
type = in.readString(4);
size = in.readInt();
if (type.equals("JUNK")) {
in.skipBytes(size);
spos = in.getFilePointer();
type = in.readString(4);
size = in.readInt();
}
}
in.seek(spos);
}
}
}
else {
// skipping unknown block
try {
in.skipBytes(8 + size);
}
catch (IllegalArgumentException iae) { }
}
}
else {
// skipping unknown block
type = in.readString(4);
if (in.getFilePointer() + size + 4 <= in.length()) {
size = in.readInt();
in.skipBytes(size);
}
}
pos = in.getFilePointer();
}
status("Populating metadata");
core.imageCount[0] = offsets.size();
core.rgb[0] = bmpBitsPerPixel > 8 || (bmpCompression != 0);
core.sizeZ[0] = 1;
core.sizeC[0] = core.rgb[0] ? 3 : 1;
core.sizeT[0] = core.imageCount[0];
core.currentOrder[0] = core.sizeC[0] == 3 ? "XYCTZ" : "XYTCZ";
core.littleEndian[0] = true;
core.interleaved[0] = bmpBitsPerPixel != 16;
core.indexed[0] = bmpBitsPerPixel == 8 && bmpCompression != 0;
core.falseColor[0] = false;
core.metadataComplete[0] = true;
if (bmpBitsPerPixel <= 8) core.pixelType[0] = FormatTools.UINT8;
else if (bmpBitsPerPixel == 16) core.pixelType[0] = FormatTools.UINT16;
else if (bmpBitsPerPixel == 32) core.pixelType[0] = FormatTools.UINT32;
else if (bmpBitsPerPixel == 24) core.pixelType[0] = FormatTools.UINT8;
else {
throw new FormatException(
"Unknown matching for pixel bit width of: " + bmpBitsPerPixel);
}
if (bmpCompression != 0) core.pixelType[0] = FormatTools.UINT8;
MetadataStore store = getMetadataStore();
store.setImage(currentId, null, null, null);
FormatTools.populatePixels(store, this);
for (int i=0; i<core.sizeC[0]; i++) {
store.setLogicalChannel(i, null, null, null, null, null, null, null, null,
null, null, null, null, null,
null, null, null, null, null, null, null, null, null, null, null);
}
}
// -- Helper methods --
private byte[] uncompress(int no, byte[] buf)
throws FormatException, IOException
{
byte[] b = new byte[(int) ((Long) lengths.get(no)).longValue()];
in.read(b);
if (bmpCompression == MSRLE) {
Object[] options = new Object[2];
options[1] = lastImage;
options[0] = new int[] {core.sizeX[0], core.sizeY[0]};
MSRLECodec codec = new MSRLECodec();
buf = codec.decompress(b, options);
lastImage = buf;
if (no == core.imageCount[0] - 1) lastImage = null;
return buf;
}
else if (bmpCompression == MS_VIDEO) {
Object[] options = new Object[4];
options[0] = new Integer(bmpBitsPerPixel);
options[1] = new Integer(core.sizeX[0]);
options[2] = new Integer(core.sizeY[0]);
options[3] = lastImage;
MSVideoCodec codec = new MSVideoCodec();
buf = codec.decompress(b, options);
lastImage = buf;
if (no == core.imageCount[0] - 1) lastImage = null;
return buf;
}
throw new FormatException("Unsupported compression : " + bmpCompression);
}
}