//
// MJPBCodec.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.codec;
import java.io.*;
//import java.util.Arrays;
import loci.formats.*;
/**
* Methods for compressing and decompressing QuickTime Motion JPEG-B data.
*
* <dl><dt><b>Source code:</b></dt>
* <dd><a href="https://skyking.microscopy.wisc.edu/trac/java/browser/trunk/loci/formats/codec/MJPBCodec.java">Trac</a>,
* <a href="https://skyking.microscopy.wisc.edu/svn/java/trunk/loci/formats/codec/MJPBCodec.java">SVN</a></dd></dl>
*/
public class MJPBCodec extends BaseCodec implements Codec {
// -- Constants --
private static final byte[] HEADER = new byte[] {
(byte) 0xff, (byte) 0xd8, 0, 16, 0x4a, 0x46, 0x49, 0x46, 0,
1, 1, 0, 0x48, 0x48, 0, 0
};
// -- Codec API methods --
/* @see Codec#compress(byte[], int, int, int[], Object) */
public byte[] compress(byte[] data, int x, int y, int[] dims, Object options)
throws FormatException
{
throw new FormatException("Motion JPEG-B compression not supported.");
}
/* @see Codec#decompress(byte[], Object) */
public byte[] decompress(byte[] data, Object options) throws FormatException {
if (options == null || !(options instanceof int[])) return null;
int[] o = (int[]) options;
int x = o[0];
int y = o[1];
int bits = o[2];
boolean interlaced = o[3] == 1;
byte[] raw = null;
byte[] raw2 = null;
try {
RandomAccessStream ras = new RandomAccessStream(data);
ras.order(false);
ras.skipBytes(20);
byte[] lumDcBits = null, lumAcBits = null, lumDc = null, lumAc = null;
byte[] quant = null;
byte[] a = new byte[4];
ras.read(a);
String s1 = new String(a);
ras.seek(ras.getFilePointer() - 20);
ras.read(a);
String s2 = new String(a);
ras.skipBytes(12);
if (s1.equals("mjpg") || s2.equals("mjpg")) {
int extra = 16;
if (s2.startsWith("m")) {
extra = 0;
ras.seek(4);
}
ras.skipBytes(12);
int offset = ras.readInt() + extra;
int quantOffset = ras.readInt() + extra;
int huffmanOffset = ras.readInt() + extra;
int sof = ras.readInt() + extra;
int sos = ras.readInt() + extra;
int sod = ras.readInt() + extra;
if (quantOffset != 0) {
ras.seek(quantOffset);
int len = ras.readShort();
ras.skipBytes(1);
quant = new byte[64];
ras.read(quant);
}
if (huffmanOffset != 0) {
ras.seek(huffmanOffset);
int len = ras.readShort();
ras.skipBytes(1);
lumDcBits = new byte[16];
ras.read(lumDcBits);
lumDc = new byte[12];
ras.read(lumDc);
ras.skipBytes(1);
lumAcBits = new byte[16];
ras.read(lumAcBits);
int sum = 0;
for (int i=0; i<lumAcBits.length; i++) {
sum += lumAcBits[i] & 0xff;
}
lumAc = new byte[sum];
ras.read(lumAc);
/*
if (sum == 162) ras.read(lumAc);
else {
byte[] tmp = new byte[162];
ras.read(tmp);
ByteVector v = new ByteVector(sum);
int[] count = new int[lumAcBits.length];
Arrays.fill(count, (byte) 0);
for (int i=0; i<tmp.length; i++) {
int size = 0;
int val = tmp[i] & 0xff;
while (Math.pow(2, size) < val) size++;
if (count[size] < lumAcBits[size]) {
v.add(tmp[i]);
count[size]++;
}
else if (size == 8) {
for (int j=size+1; j<lumAcBits.length; j++) {
if (count[j] < lumAcBits[j]) {
v.add(tmp[i]);
count[j]++;
}
}
}
}
lumAc = v.toByteArray();
}
*/
}
ras.seek(sof + 7);
int channels = ras.read();
int[] sampling = new int[channels];
for (int i=0; i<channels; i++) {
ras.skipBytes(1);
sampling[i] = ras.read();
ras.skipBytes(1);
}
ras.seek(sos + 3);
int[] tables = new int[channels];
for (int i=0; i<channels; i++) {
ras.skipBytes(1);
tables[i] = ras.read();
}
ras.seek(sod);
int numBytes = (int) (offset - ras.getFilePointer());
if (offset == 0) numBytes = (int) (ras.length() - ras.getFilePointer());
raw = new byte[numBytes];
ras.read(raw);
if (offset != 0) {
ras.seek(offset + 36);
int n = ras.readInt();
ras.skipBytes(n);
ras.seek(ras.getFilePointer() - 40);
numBytes = (int) (ras.length() - ras.getFilePointer());
raw2 = new byte[numBytes];
ras.read(raw2);
}
}
if (raw == null) raw = data;
// insert zero after each byte equal to 0xff
ByteVector b = new ByteVector();
for (int i=0; i<raw.length; i++) {
b.add((byte) raw[i]);
if (raw[i] == (byte) 0xff) {
b.add((byte) 0);
}
}
if (raw2 == null) raw2 = new byte[0];
ByteVector b2 = new ByteVector();
for (int i=0; i<raw2.length; i++) {
b2.add((byte) raw2[i]);
if (raw2[i] == (byte) 0xff) {
b2.add((byte) 0);
}
}
// assemble fake JPEG plane
ByteVector v = new ByteVector(1000);
v.add(HEADER);
v.add(new byte[] {(byte) 0xff, (byte) 0xdb});
int length = 4 + quant.length*2;
v.add((byte) ((length >>> 8) & 0xff));
v.add((byte) (length & 0xff));
v.add((byte) 0);
v.add(quant);
v.add((byte) 1);
v.add(quant);
v.add(new byte[] {(byte) 0xff, (byte) 0xc4});
length = (lumDcBits.length + lumDc.length + lumAcBits.length +
lumAc.length)*2 + 6;
v.add((byte) ((length >>> 8) & 0xff));
v.add((byte) (length & 0xff));
v.add((byte) 0);
v.add(lumDcBits);
v.add(lumDc);
v.add((byte) 1);
v.add(lumDcBits);
v.add(lumDc);
v.add((byte) 16);
v.add(lumAcBits);
v.add(lumAc);
v.add((byte) 17);
v.add(lumAcBits);
v.add(lumAc);
v.add((byte) 0xff);
v.add((byte) 0xc0);
length = (bits >= 40) ? 11 : 17;
v.add((byte) ((length >>> 8) & 0xff));
v.add((byte) (length & 0xff));
int fieldHeight = y;
if (interlaced) fieldHeight /= 2;
if (y % 2 == 1) fieldHeight++;
int c = bits == 24 ? 3 : (bits == 32 ? 4 : 1);
v.add(bits >= 40 ? (byte) (bits - 32) : (byte) (bits / c));
v.add((byte) ((fieldHeight >>> 8) & 0xff));
v.add((byte) (fieldHeight & 0xff));
v.add((byte) ((x >>> 8) & 0xff));
v.add((byte) (x & 0xff));
v.add((bits >= 40) ? (byte) 1 : (byte) 3);
v.add((byte) 1);
v.add((byte) 33);
v.add((byte) 0);
if (bits < 40) {
v.add((byte) 2);
v.add((byte) 17);
v.add((byte) 1);
v.add((byte) 3);
v.add((byte) 17);
v.add((byte) 1);
}
v.add((byte) 0xff);
v.add((byte) 0xda);
length = (bits >= 40) ? 8 : 12;
v.add((byte) ((length >>> 8) & 0xff));
v.add((byte) (length & 0xff));
v.add((bits >= 40) ? (byte) 1 : (byte) 3);
v.add((byte) 1);
v.add((byte) 0);
if (bits < 40) {
v.add((byte) 2);
v.add((byte) 1);
v.add((byte) 3);
v.add((byte) 1);
}
v.add((byte) 0);
v.add((byte) 0x3f);
v.add((byte) 0);
if (interlaced) {
ByteVector v2 = new ByteVector(v.size());
v2.add(v.toByteArray());
v.add(b.toByteArray());
v.add((byte) 0xff);
v.add((byte) 0xd9);
v2.add(b2.toByteArray());
v2.add((byte) 0xff);
v2.add((byte) 0xd9);
JPEGCodec jpeg = new JPEGCodec();
byte[] top = jpeg.decompress(v.toByteArray());
byte[] bottom = jpeg.decompress(v2.toByteArray());
int bpp = bits < 40 ? bits / 8 : (bits - 32) / 8;
int ch = bits < 40 ? 3 : 1;
byte[] result = new byte[x * y * bpp * ch];
int topNdx = 0;
int bottomNdx = 0;
for (int yy=0; yy<y; yy++) {
if (yy % 2 == 0) {
System.arraycopy(top, topNdx*x*bpp, result, yy*x*bpp, x*bpp);
topNdx++;
}
else {
System.arraycopy(bottom, bottomNdx*x*bpp, result, yy*x*bpp, x*bpp);
bottomNdx++;
}
}
return result;
}
else {
v.add(b.toByteArray());
v.add((byte) 0xff);
v.add((byte) 0xd9);
return new JPEGCodec().decompress(v.toByteArray());
}
}
catch (IOException e) {
throw new FormatException(e);
}
}
}