package ij.plugin;
import ij.*;
import ij.process.*;
import ij.gui.*;
import ij.io.*;
import ij.plugin.Animator;
import java.awt.*;
import java.awt.image.*;
import java.io.*;
import java.util.*;
import javax.imageio.ImageIO;
public class AVI_Reader extends VirtualStack implements PlugIn {
private final static int FOURCC_RIFF = 0x46464952; private final static int FOURCC_AVI = 0x20495641; private final static int FOURCC_AVIX = 0x58495641; private final static int FOURCC_ix00 = 0x30307869; private final static int FOURCC_indx = 0x78646e69; private final static int FOURCC_idx1 = 0x31786469; private final static int FOURCC_LIST = 0x5453494c; private final static int FOURCC_hdrl = 0x6c726468; private final static int FOURCC_avih = 0x68697661; private final static int FOURCC_strl = 0x6c727473; private final static int FOURCC_strh = 0x68727473; private final static int FOURCC_strf = 0x66727473; private final static int FOURCC_movi = 0x69766f6d; private final static int FOURCC_rec = 0x20636572; private final static int FOURCC_JUNK = 0x4b4e554a; private final static int FOURCC_vids = 0x73646976; private final static int FOURCC_00db = 0x62643030; private final static int FOURCC_00dc = 0x63643030;
private final static int NO_COMPRESSION = 0; private final static int NO_COMPRESSION_RGB= 0x20424752; private final static int NO_COMPRESSION_RAW= 0x20574152; private final static int NO_COMPRESSION_Y800=0x30303859; private final static int NO_COMPRESSION_Y8 = 0x20203859; private final static int NO_COMPRESSION_GREY=0x59455247; private final static int NO_COMPRESSION_Y16= 0x20363159; private final static int NO_COMPRESSION_MIL= 0x204c494d; private final static int AYUV_COMPRESSION = 0x56555941; private final static int UYVY_COMPRESSION = 0x59565955; private final static int Y422_COMPRESSION = 0x564E5955; private final static int UYNV_COMPRESSION = 0x32323459; private final static int CYUV_COMPRESSION = 0x76757963; private final static int V422_COMPRESSION = 0x32323456; private final static int YUY2_COMPRESSION = 0x32595559; private final static int YUNV_COMPRESSION = 0x564E5559; private final static int YUYV_COMPRESSION = 0x56595559; private final static int YVYU_COMPRESSION = 0x55595659;
private final static int I420_COMPRESSION = 0x30323449; private final static int IYUV_COMPRESSION = 0x56555949; private final static int YV12_COMPRESSION = 0x32315659; private final static int NV12_COMPRESSION = 0x3231564E; private final static int NV21_COMPRESSION = 0x3132564E;
private final static int JPEG_COMPRESSION = 0x6765706a; private final static int JPEG_COMPRESSION2 = 0x4745504a; private final static int JPEG_COMPRESSION3 = 0x04; private final static int MJPG_COMPRESSION = 0x47504a4d; private final static int PNG_COMPRESSION = 0x20676e70; private final static int PNG_COMPRESSION2 = 0x20474e50; private final static int PNG_COMPRESSION3 = 0x05;
private final static int BITMASK24 = 0x10000; private final static long SIZE_MASK = 0xffffffffL; private final static long FOUR_GB = 0x100000000L;
private final static int AVIF_HASINDEX = 0x00000010; private final static int AVIF_MUSTUSEINDEX = 0x00000020; private final static int AVIF_ISINTERLEAVED= 0x00000100;
private final static byte AVI_INDEX_OF_CHUNKS=0x01; private final static byte AVI_INDEX_OF_INDEXES=0x00;
private static boolean staticConvertToGray;
private static boolean staticFlipVertical;
private static boolean staticIsVirtual = true;
private int firstFrame = 1; private int lastFrame = 0; private boolean convertToGray; private boolean flipVertical; private boolean isVirtual; private RandomAccessFile raFile;
private String raFilePath;
private boolean headerOK = false; private int streamNumber; private int type0xdb, type0xdc; private long fileSize; private long aviSize; private long headerPositionEnd; private long indexPosition; private long indexPositionEnd; private long moviPosition; private int paddingGranularity = 2; private int frameNumber = 1; private int lastFrameToRead = Integer.MAX_VALUE;
private int totalFramesFromIndex; private boolean indexForCountingOnly; private boolean isOversizedAvi1; private int dataCompression; private boolean isPlanarFormat; private int scanLineSize;
private boolean dataTopDown; private ColorModel cm;
private boolean variableLength; private Vector<long[]> frameInfos; private ImageStack stack;
private ImagePlus imp;
private boolean verbose = IJ.debugMode;
private long startTime;
private boolean aborting;
private boolean displayDialog = true;
private String errorText;
private int dwMicroSecPerFrame;
private int dwMaxBytesPerSec;
private int dwReserved1;
private int dwFlags;
private int dwTotalFrames; private int dwInitialFrames;
private int dwStreams;
private int dwSuggestedBufferSize;
private int dwWidth;
private int dwHeight;
private int fccStreamHandler;
private int dwStreamFlags;
private int dwPriorityLanguage; private int dwStreamInitialFrames;
private int dwStreamScale;
private int dwStreamRate;
private int dwStreamStart;
private int dwStreamLength;
private int dwStreamSuggestedBufferSize;
private int dwStreamQuality;
private int dwStreamSampleSize;
private int biSize; private int biWidth;
private int biHeight;
private short biPlanes; private short biBitCount; private int biCompression;
private int biSizeImage; private int biXPelsPerMeter; private int biYPelsPerMeter; private int biClrUsed; private int biClrImportant;
public void run (String arg) {
String options = IJ.isMacro()?Macro.getOptions():null;
if (options!=null && options.contains("select=") && !options.contains("open="))
Macro.setOptions(options.replaceAll("select=", "open="));
OpenDialog od = new OpenDialog("Open AVI File", arg);
String fileName = od.getFileName();
if (fileName == null) return;
String fileDir = od.getDirectory();
String path = fileDir + fileName;
try {
openAndReadHeader(path); } catch (Exception e) {
error(exceptionMessage(e));
return;
} finally {
closeFile(raFile);
}
if (displayDialog && !showDialog(fileName)) return;
errorText = null;
ImageStack stack = makeStack(path, firstFrame, lastFrame, isVirtual, convertToGray, flipVertical); if (aborting)
return; if (stack==null || stack.getSize() == 0 || stack.getProcessor(1)==null) { if (errorText != null)
error(errorText);
else {
String rangeText = "";
if (firstFrame > 1 || (lastFrame != 0 && lastFrame != dwTotalFrames))
rangeText = "\nin Range "+firstFrame+
(lastFrame>0 ? " - "+lastFrame : " - end");
error("Error: No Frames Found"+rangeText);
}
return;
} else if (errorText != null)
IJ.showMessage("AVI Reader", errorText); imp = new ImagePlus(WindowManager.makeUniqueName(fileName), stack);
if (imp.getBitDepth()==16)
imp.getProcessor().resetMinAndMax();
setFramesPerSecond(imp);
FileInfo fi = new FileInfo();
fi.fileName = fileName;
fi.directory = fileDir;
imp.setFileInfo(fi);
if (arg.equals(""))
imp.show();
IJ.showTime(imp, startTime, "Read AVI in ", stack.getSize());
}
public ImagePlus getImagePlus() {
return imp;
}
public static ImagePlus openVirtual(String path) {
return open(path, true);
}
public static ImagePlus open(String path, boolean virtual) {
AVI_Reader reader = new AVI_Reader();
ImageStack stack = reader.makeStack (path, 1, 0, virtual, false, false);
if (stack!=null)
return new ImagePlus((new File(path)).getName(), stack);
else
return null;
}
public ImageStack makeStack (String path, int firstFrame, int lastFrame,
boolean isVirtual, boolean convertToGray, boolean flipVertical) {
this.firstFrame = firstFrame;
this.lastFrame = lastFrame;
this.isVirtual = isVirtual;
this.convertToGray = convertToGray;
this.flipVertical = flipVertical;
IJ.showProgress(.001);
try {
readAVI(path);
} catch (OutOfMemoryError e) {
stack.trim();
errorText = "Out of memory. " + stack.getSize() + " of " + dwTotalFrames + " frames will be opened.";
} catch (Exception e) {
errorText = exceptionMessage(e);
if (isVirtual || stack==null || stack.getSize()==0) return null;
} finally {
closeFile(raFile);
if (verbose)
IJ.log("File closed.");
IJ.showProgress(1.0);
}
if (isVirtual && frameInfos != null)
stack = this;
if (stack!=null && cm!=null)
stack.setColorModel(cm);
return stack;
}
public String getErrorText() {
return errorText;
}
public synchronized ImageProcessor getProcessor(int n) {
if (frameInfos==null || frameInfos.size()==0 || raFilePath==null)
return null;
if (n<1 || n>frameInfos.size())
throw new IllegalArgumentException("Argument out of range: "+n);
Object pixels = null;
RandomAccessFile rFile = null;
try {
rFile = new RandomAccessFile(new File(raFilePath), "r");
long[] frameInfo = (long[])(frameInfos.get(n-1));
pixels = readFrame(rFile, frameInfo[0], (int)frameInfo[1]);
} catch (Exception e) {
error(exceptionMessage(e));
return null;
} finally {
closeFile(rFile);
}
if (pixels == null) return null; if (pixels instanceof byte[])
return new ByteProcessor(dwWidth, biHeight, (byte[])pixels, cm);
else if (pixels instanceof short[])
return new ShortProcessor(dwWidth, biHeight, (short[])pixels, cm);
else
return new ColorProcessor(dwWidth, biHeight, (int[])pixels);
}
public int getWidth() {
return dwWidth;
}
public int getHeight() {
return biHeight;
}
public int getSize() {
if (frameInfos == null) return 0;
else return frameInfos.size();
}
public String getSliceLabel(int n) {
if (frameInfos==null || n<1 || n>frameInfos.size())
throw new IllegalArgumentException("No Virtual Stack or argument out of range: "+n);
return frameLabel(((long[])(frameInfos.get(n-1)))[2]);
}
public void deleteSlice(int n) {
if (frameInfos==null || frameInfos.size()==0) return;
if (n<1 || n>frameInfos.size())
throw new IllegalArgumentException("Argument out of range: "+n);
frameInfos.removeElementAt(n-1);
}
private boolean showDialog (String fileName) {
if (lastFrame!=-1)
lastFrame = dwTotalFrames;
if (!IJ.isMacro()) {
convertToGray = staticConvertToGray;
flipVertical = staticFlipVertical;
isVirtual = staticIsVirtual;
}
GenericDialog gd = new GenericDialog("AVI Reader");
gd.addNumericField("First Frame: ", firstFrame, 0);
gd.addNumericField("Last Frame: ", lastFrame, 0, 6, "");
gd.addCheckbox("Use Virtual Stack", isVirtual);
gd.addCheckbox("Convert to Grayscale", convertToGray);
gd.addCheckbox("Flip Vertical", flipVertical);
gd.setSmartRecording(true);
gd.showDialog();
if (gd.wasCanceled()) return false;
firstFrame = (int)gd.getNextNumber();
lastFrame = (int)gd.getNextNumber();
isVirtual = gd.getNextBoolean();
convertToGray = gd.getNextBoolean();
flipVertical = gd.getNextBoolean();
if (!IJ.isMacro()) {
staticConvertToGray = convertToGray;
staticFlipVertical = flipVertical;
staticIsVirtual = isVirtual;
}
IJ.register(this.getClass());
return true;
}
private void readAVI(String path) throws Exception, IOException {
if (!headerOK) openAndReadHeader(path);
else {
File file = new File(path); raFile = new RandomAccessFile(file, "r");
}
startTime += System.currentTimeMillis();
if (lastFrame > 0) lastFrameToRead = lastFrame;
if (lastFrame < 0 && dwTotalFrames > 0) lastFrameToRead = dwTotalFrames+lastFrame;
if (lastFrameToRead < firstFrame) return;
boolean hasIndex = (dwFlags & AVIF_HASINDEX) != 0;
if (isVirtual || firstFrame>1) { frameInfos = new Vector<long[]>(100); long nextPosition = -1;
if (indexPosition > 0) { raFile.seek(indexPosition);
nextPosition = findFourccAndRead(FOURCC_indx, false, indexPositionEnd, false);
}
if (hasIndex && (frameInfos==null ||frameInfos.size()==0)) { raFile.seek(headerPositionEnd);
moviPosition = findFourccAndSkip(FOURCC_movi, true, fileSize); if (moviPosition<0)
throw new Exception("AVI File has no movie data");
long positionBehindMovie = raFile.getFilePointer();
while (positionBehindMovie < fileSize-8) {
if (verbose)
IJ.log("searching for 'idx1' at 0x"+Long.toHexString(positionBehindMovie));
raFile.seek(positionBehindMovie);
if (positionBehindMovie > FOUR_GB)
isOversizedAvi1 = true;
nextPosition = findFourccAndRead(FOURCC_idx1, false, fileSize, false);
if (nextPosition >= 0) break;
positionBehindMovie += FOUR_GB; }
}
if (verbose)
IJ.log("'frameInfos' has "+frameInfos.size()+" entries");
}
if (isVirtual && frameInfos.size()>0) return;
raFile.seek(headerPositionEnd);
if (firstFrame>1 && frameInfos.size()>0) {
long[] frameInfo = (long[])frameInfos.get(0);
raFile.seek(frameInfo[0]-8); frameNumber = firstFrame;
if (verbose)
IJ.log("directly go to frame "+firstFrame+" @ 0x"+Long.toHexString(frameInfo[0]-8));
readMovieData(fileSize);
} else {
frameNumber = 1;
findFourccAndRead(FOURCC_movi, true, fileSize, true);
}
long pos = raFile.getFilePointer();
while (pos>0 && pos<fileSize && (frameNumber<lastFrameToRead+1))
pos = findFourccAndRead(FOURCC_RIFF, false, fileSize, false);
return;
}
private void openAndReadHeader (String path) throws Exception, IOException {
startTime = System.currentTimeMillis();
if (verbose)
IJ.log("OPEN AND READ AVI FILE HEADER "+timeString());
File file = new File(path); raFile = new RandomAccessFile(file, "r");
raFilePath = path;
fileSize = raFile.length();
int fileType = readInt(); if (verbose)
IJ.log("File header: File type='"+fourccString(fileType)+"' (should be 'RIFF')"+timeString());
if (fileType != FOURCC_RIFF)
throw new Exception("Not an AVI file.");
aviSize = readInt() & SIZE_MASK; int riffType = readInt();
if (verbose)
IJ.log("File header: RIFF type='"+fourccString(riffType)+"' (should be 'AVI ')");
if (riffType != FOURCC_AVI)
throw new Exception("Not an AVI file.");
findFourccAndRead(FOURCC_hdrl, true, fileSize, true);
startTime -= System.currentTimeMillis(); headerOK = true;
}
private void readAVIX(long endPosition) throws Exception, IOException {
if (verbose)
IJ.log("Trying to read AVIX"+timeString());
int riffType = readInt();
if (verbose)
IJ.log("File header: RIFF type='"+fourccString(riffType)+"' (should be 'AVIX')");
if (riffType != FOURCC_AVIX)
throw new Exception("Not an AVI file.");
findFourccAndRead(FOURCC_movi, true, fileSize, true); }
private long findFourccAndRead(int fourcc, boolean isList, long endPosition,
boolean throwNotFoundException) throws Exception, IOException {
long nextPos;
boolean contentOk = false;
do {
int type = readType(endPosition);
if (type == 0) { if (throwNotFoundException)
throw new Exception("Required item '"+fourccString(fourcc)+"' not found");
else
return -1;
}
long size = readInt() & SIZE_MASK;
nextPos = raFile.getFilePointer() + size;
if (nextPos>endPosition || nextPos>fileSize) {
errorText = "AVI File Error: '"+fourccString(type)+"' @ 0x"+Long.toHexString(raFile.getFilePointer()-8)+" has invalid length. File damaged/truncated?";
IJ.log(errorText); if (fourcc == FOURCC_movi)
nextPos = fileSize; else
return -1; }
if (isList && type == FOURCC_LIST)
type = readInt();
if (verbose)
IJ.log("Search for '"+fourccString(fourcc)+"', found "+fourccString(type)+"' data "+posSizeString(nextPos-size, size));
if (type==fourcc) {
contentOk = readContents(fourcc, nextPos);
} else if (verbose)
IJ.log("'"+fourccString(type)+"', ignored");
raFile.seek(nextPos);
if (contentOk)
return nextPos; } while (!contentOk);
return nextPos;
}
private long findFourccAndSkip(int fourcc, boolean isList, long endPosition) throws IOException {
while (true) {
int type = readType(endPosition);
if (type == 0) return -1;
long size = readInt() & SIZE_MASK;
long chunkPos = raFile.getFilePointer();
long nextPos = chunkPos + size; if (isList && type == FOURCC_LIST)
type = readInt();
if (verbose)
IJ.log("Searching for (to skip) '"+fourccString(fourcc)+"', found "+fourccString(type)+
"' data "+posSizeString(chunkPos, size));
raFile.seek(nextPos);
if (type == fourcc)
return chunkPos; }
}
private boolean readContents (int fourcc, long endPosition) throws Exception, IOException {
switch (fourcc) {
case FOURCC_hdrl:
headerPositionEnd = endPosition;
findFourccAndRead(FOURCC_avih, false, endPosition, true);
findFourccAndRead(FOURCC_strl, true, endPosition, true);
return true;
case FOURCC_avih:
readAviHeader();
return true;
case FOURCC_strl:
long nextPosition = findFourccAndRead(FOURCC_strh, false, endPosition, false);
if (nextPosition<0) return false;
indexPosition = findFourccAndRead(FOURCC_strf, false, endPosition, true);
indexPositionEnd= endPosition;
indexForCountingOnly = true; totalFramesFromIndex = 0;
nextPosition = findFourccAndRead(FOURCC_indx, false, endPosition, false);
if (nextPosition > 0 && totalFramesFromIndex > dwTotalFrames)
dwTotalFrames = totalFramesFromIndex;
indexForCountingOnly = false;
return true;
case FOURCC_strh:
int streamType = readInt();
if (streamType != FOURCC_vids) {
if (verbose)
IJ.log("Non-video Stream '"+fourccString(streamType)+" skipped");
streamNumber++;
return false;
}
readStreamHeader();
return true;
case FOURCC_strf:
readBitMapInfo(endPosition);
return true;
case FOURCC_indx:
case FOURCC_ix00:
readAvi2Index(endPosition);
return true;
case FOURCC_idx1:
readOldFrameIndex(endPosition);
return true;
case FOURCC_RIFF:
readAVIX(endPosition);
return true;
case FOURCC_movi:
readMovieData(endPosition);
return true;
}
throw new Exception("Program error, type "+fourccString(fourcc));
}
void readAviHeader() throws Exception, IOException { dwMicroSecPerFrame = readInt();
dwMaxBytesPerSec = readInt();
dwReserved1 = readInt(); dwFlags = readInt();
dwTotalFrames = readInt();
dwInitialFrames = readInt();
dwStreams = readInt();
dwSuggestedBufferSize = readInt();
dwWidth = readInt();
dwHeight = readInt();
if (verbose) {
IJ.log("AVI HEADER (avih):"+timeString());
IJ.log(" dwMicroSecPerFrame=" + dwMicroSecPerFrame);
IJ.log(" dwMaxBytesPerSec=" + dwMaxBytesPerSec);
IJ.log(" dwReserved1=" + dwReserved1);
IJ.log(" dwFlags=" + dwFlags);
IJ.log(" dwTotalFrames=" + dwTotalFrames);
IJ.log(" dwInitialFrames=" + dwInitialFrames);
IJ.log(" dwStreams=" + dwStreams);
IJ.log(" dwSuggestedBufferSize=" + dwSuggestedBufferSize);
IJ.log(" dwWidth=" + dwWidth);
IJ.log(" dwHeight=" + dwHeight);
}
}
void readStreamHeader() throws Exception, IOException { fccStreamHandler = readInt();
dwStreamFlags = readInt();
dwPriorityLanguage = readInt();
dwStreamInitialFrames = readInt();
dwStreamScale = readInt();
dwStreamRate = readInt();
dwStreamStart = readInt();
dwStreamLength = readInt();
dwStreamSuggestedBufferSize = readInt();
dwStreamQuality = readInt();
dwStreamSampleSize = readInt();
if (verbose) {
IJ.log("VIDEO STREAM HEADER (strh):");
IJ.log(" fccStreamHandler='" + fourccString(fccStreamHandler)+"'");
IJ.log(" dwStreamFlags=" + dwStreamFlags);
IJ.log(" wPriority,wLanguage=" + dwPriorityLanguage);
IJ.log(" dwStreamInitialFrames=" + dwStreamInitialFrames);
IJ.log(" dwStreamScale=" + dwStreamScale);
IJ.log(" dwStreamRate=" + dwStreamRate);
IJ.log(" dwStreamStart=" + dwStreamStart);
IJ.log(" dwStreamLength=" + dwStreamLength);
IJ.log(" dwStreamSuggestedBufferSize=" + dwStreamSuggestedBufferSize);
IJ.log(" dwStreamQuality=" + dwStreamQuality);
IJ.log(" dwStreamSampleSize=" + dwStreamSampleSize);
}
if (dwStreamSampleSize > 1)
throw new Exception("Video stream with "+dwStreamSampleSize+" (more than 1) frames/chunk not supported");
type0xdb = FOURCC_00db + (streamNumber<<8); type0xdc = FOURCC_00dc + (streamNumber<<8); }
private void readAvi2Index(long endPosition) throws Exception, IOException {
short wLongsPerEntry = readShort();
byte bIndexSubType = raFile.readByte();
byte bIndexType = raFile.readByte();
int nEntriesInUse = readInt();
int dwChunkId = readInt();
long qwBaseOffset = readLong();
readInt(); if (verbose) {
String bIndexString = bIndexType == AVI_INDEX_OF_CHUNKS ? ": AVI_INDEX_OF_CHUNKS" :
bIndexType == AVI_INDEX_OF_INDEXES ? ": AVI_INDEX_OF_INDEXES" : ": UNSUPPORTED";
IJ.log("AVI 2 INDEX:");
IJ.log(" wLongsPerEntry=" + wLongsPerEntry);
IJ.log(" bIndexSubType=" + bIndexSubType);
IJ.log(" bIndexType=" + bIndexType + bIndexString);
IJ.log(" nEntriesInUse=" + nEntriesInUse);
IJ.log(" dwChunkId='" + fourccString(dwChunkId)+"'");
if (bIndexType == AVI_INDEX_OF_CHUNKS)
IJ.log(" qwBaseOffset=" + "0x"+Long.toHexString(qwBaseOffset));
}
if (bIndexType == AVI_INDEX_OF_INDEXES) { if (wLongsPerEntry != 4) return; for (int i=0;i<nEntriesInUse;i++) { long qwOffset = readLong();
int dwSize = readInt();
int dwDuration = readInt(); if (verbose)
IJ.log(" indx entry: '" +fourccString(dwChunkId)+"' incl header "+posSizeString(qwOffset,dwSize)+timeString());
long nextIndxEntryPointer = raFile.getFilePointer();
raFile.seek(qwOffset); findFourccAndRead(FOURCC_ix00, false, qwOffset+dwSize, true);
raFile.seek(nextIndxEntryPointer);
if (frameNumber>lastFrameToRead) break;
}
} else if (bIndexType == AVI_INDEX_OF_CHUNKS) {
if (verbose) {
IJ.log("readAvi2Index frameNumber="+frameNumber+" firstFrame="+firstFrame);
if (indexForCountingOnly) IJ.log("<just counting frames, not interpreting index now>");
}
if (wLongsPerEntry != 2) return; if (dwChunkId != type0xdb && dwChunkId != type0xdc) { if (verbose)
IJ.log("INDEX ERROR: SKIPPED ix00, wrong stream number or type, should be "+
fourccString(type0xdb)+" or "+fourccString(type0xdc));
return;
}
if (indexForCountingOnly) { totalFramesFromIndex += nEntriesInUse;
return;
}
for (int i=0;i<nEntriesInUse;i++) {
long dwOffset = readInt() & 0xffffffffL;
long pos=qwBaseOffset+dwOffset;
int dwSize = readInt();
if (isVirtual) IJ.showProgress((double)frameNumber/lastFrameToRead);
if (frameNumber >= firstFrame && dwSize>0) { frameInfos.add(new long[] {pos, dwSize, (long) frameNumber*dwMicroSecPerFrame});
if (verbose)
IJ.log("movie data "+frameNumber+" '"+fourccString(dwChunkId)+"' "+posSizeString(pos,dwSize)+timeString());
}
frameNumber++;
if (frameNumber>lastFrameToRead) break;
}
if (verbose)
IJ.log("Index read up to frame "+(frameNumber-1));
}
}
private void readOldFrameIndex(long endPosition) throws Exception, IOException {
int offset = -1; int[] offsetsToTry = new int[] {0, (int)moviPosition}; long lastFramePos = 0;
while (true) {
if ((raFile.getFilePointer()+16) >endPosition) break;
int dwChunkId = readInt();
int dwFlags = readInt();
int dwOffset = readInt();
int dwSize = readInt();
if ((dwChunkId==type0xdb || dwChunkId==type0xdc) && dwSize>0) {
if (offset < 0) { long temp = raFile.getFilePointer();
for (int i=0; i<offsetsToTry.length; i++) {
long pos = (dwOffset + offsetsToTry[i]) & SIZE_MASK;
if (pos < moviPosition) continue; raFile.seek(pos);
int chunkIdAtPos = readInt(); if (chunkIdAtPos == dwChunkId) {
offset = offsetsToTry[i];
break;
}
}
if (verbose)
IJ.log("idx1: dwOffsets are w.r.t. 0x"+(offset<0 ? " UNKONWN??" : Long.toHexString(offset)));
raFile.seek(temp);
if (offset < 0) return; }
long framePos = (dwOffset & SIZE_MASK) + offset;
if (isOversizedAvi1)
while (framePos < lastFramePos) framePos += FOUR_GB; lastFramePos = framePos;
if (frameNumber >= firstFrame) {
frameInfos.add(new long[]{framePos+8, dwSize, (long)frameNumber*dwMicroSecPerFrame});
if (verbose)
IJ.log("idx1 movie data '"+fourccString(dwChunkId)+"' "+posSizeString(framePos,dwSize)+timeString());
}
frameNumber++;
if (frameNumber>lastFrameToRead) break;
} } if (verbose)
IJ.log("Index read up to frame "+(frameNumber-1));
}
void readBitMapInfo(long endPosition) throws Exception, IOException {
biSize = readInt();
biWidth = readInt();
biHeight = readInt();
biPlanes = readShort();
biBitCount = readShort();
biCompression = readInt();
biSizeImage = readInt();
biXPelsPerMeter = readInt();
biYPelsPerMeter = readInt();
biClrUsed = readInt();
biClrImportant = readInt();
if (verbose) {
IJ.log(" biSize=" + biSize);
IJ.log(" biWidth=" + biWidth);
IJ.log(" biHeight=" + biHeight);
IJ.log(" biPlanes=" + biPlanes);
IJ.log(" biBitCount=" + biBitCount);
IJ.log(" biCompression=0x" + Integer.toHexString(biCompression)+" '"+fourccString(biCompression)+"'");
IJ.log(" biSizeImage=" + biSizeImage);
IJ.log(" biXPelsPerMeter=" + biXPelsPerMeter);
IJ.log(" biYPelsPerMeter=" + biYPelsPerMeter);
IJ.log(" biClrUsed=" + biClrUsed);
IJ.log(" biClrImportant=" + biClrImportant);
}
int allowedBitCount = 0;
boolean readPalette = false;
switch (biCompression) {
case NO_COMPRESSION:
case NO_COMPRESSION_RGB:
case NO_COMPRESSION_RAW:
dataCompression = NO_COMPRESSION;
dataTopDown = biHeight<0; allowedBitCount = 8 | BITMASK24 | 32; readPalette = biBitCount <= 8;
break;
case NO_COMPRESSION_Y8:
case NO_COMPRESSION_GREY:
case NO_COMPRESSION_Y800:
dataTopDown = true;
dataCompression = NO_COMPRESSION;
allowedBitCount = 8;
break;
case NO_COMPRESSION_Y16:
case NO_COMPRESSION_MIL:
dataCompression = NO_COMPRESSION;
allowedBitCount = 16;
break;
case AYUV_COMPRESSION:
dataCompression = AYUV_COMPRESSION;
allowedBitCount = 32;
break;
case UYVY_COMPRESSION:
case UYNV_COMPRESSION:
dataTopDown = true;
case CYUV_COMPRESSION: case V422_COMPRESSION:
dataCompression = UYVY_COMPRESSION;
allowedBitCount = 16;
break;
case YUY2_COMPRESSION:
case YUNV_COMPRESSION:
case YUYV_COMPRESSION:
dataTopDown = true;
dataCompression = YUY2_COMPRESSION;
allowedBitCount = 16;
break;
case YVYU_COMPRESSION:
dataTopDown = true;
dataCompression = YVYU_COMPRESSION;
allowedBitCount = 16;
break;
case IYUV_COMPRESSION:
case I420_COMPRESSION:
case YV12_COMPRESSION:
case NV12_COMPRESSION:
case NV21_COMPRESSION:
dataCompression = (dataCompression==IYUV_COMPRESSION) ?
I420_COMPRESSION : biCompression;
dataTopDown = biHeight>0;
isPlanarFormat = true;
allowedBitCount = 12;
break;
case JPEG_COMPRESSION:
case JPEG_COMPRESSION2:
case JPEG_COMPRESSION3:
case MJPG_COMPRESSION:
dataCompression = JPEG_COMPRESSION;
variableLength = true;
break;
case PNG_COMPRESSION:
case PNG_COMPRESSION2:
case PNG_COMPRESSION3:
variableLength = true;
dataCompression = PNG_COMPRESSION;
break;
default:
throw new Exception("Unsupported compression: "+Integer.toHexString(biCompression)+
(biCompression>=0x20202020 ? " '" + fourccString(biCompression)+"'" : ""));
}
int bitCountTest = (biBitCount==24) ? BITMASK24 : biBitCount; if (allowedBitCount!=0 && (bitCountTest & allowedBitCount)==0)
throw new Exception("Unsupported: "+biBitCount+" bits/pixel for compression '"+
fourccString(biCompression)+"'");
if (biHeight < 0) biHeight = -biHeight;
if (isPlanarFormat && ((biWidth&1)!=0 || (biHeight&1)!=0))
throw new Exception("Odd size ("+biWidth+"x"+biHeight+") unsupported with "+fourccString(biCompression)+" compression");
scanLineSize = isPlanarFormat ?
(biWidth * biBitCount) / 8 : ((biWidth * biBitCount + 31) / 32) * 4;
long spaceForPalette = endPosition-raFile.getFilePointer();
if (readPalette && biClrUsed==0 && spaceForPalette!=0)
biClrUsed = 1 << biBitCount;
if (verbose) {
IJ.log(" > data compression=0x" + Integer.toHexString(dataCompression)+" '"+fourccString(dataCompression)+"'");
IJ.log(" > palette colors=" + biClrUsed);
IJ.log(" > scan line size=" + scanLineSize);
IJ.log(" > data top down=" + dataTopDown);
}
if (readPalette && biClrUsed > 0) {
if (verbose)
IJ.log(" Reading "+biClrUsed+" Palette colors: " + posSizeString(spaceForPalette));
if (spaceForPalette < biClrUsed*4)
throw new Exception("Not enough data ("+spaceForPalette+") for palette of size "+(biClrUsed*4));
byte[] pr = new byte[biClrUsed];
byte[] pg = new byte[biClrUsed];
byte[] pb = new byte[biClrUsed];
for (int i = 0; i < biClrUsed; i++) {
pb[i] = raFile.readByte();
pg[i] = raFile.readByte();
pr[i] = raFile.readByte();
raFile.readByte();
}
cm = new IndexColorModel(biBitCount, biClrUsed, pr, pg, pb);
}
}
void readMovieData(long endPosition) throws Exception, IOException {
if (verbose)
IJ.log("MOVIE DATA "+posSizeString(endPosition-raFile.getFilePointer())+timeString()+
"\nSearching for stream "+streamNumber+": '"+
fourccString(type0xdb)+"' or '"+fourccString(type0xdc)+"' chunks");
if (isVirtual) {
if (frameInfos==null) frameInfos = new Vector<long[]>(lastFrameToRead); } else if (stack==null)
stack = new ImageStack(dwWidth, biHeight);
while (true) { int type = readType(endPosition);
if (type==0) break; long size = readInt() & SIZE_MASK;
long pos = raFile.getFilePointer();
long nextPos = pos + size;
if (nextPos > endPosition && nextPos < fileSize-8 && fileSize > FOUR_GB) {
endPosition = fileSize; }
if ((type==type0xdb || type==type0xdc) && size>0) {
IJ.showProgress((double)frameNumber /lastFrameToRead);
if (verbose)
IJ.log(frameNumber+" movie data '"+fourccString(type)+"' "+posSizeString(size)+timeString());
if (frameNumber >= firstFrame) {
if (isVirtual)
frameInfos.add(new long[]{pos, size, frameNumber*dwMicroSecPerFrame});
else { Object pixels = readFrame(raFile, pos, (int)size);
String label = frameLabel(frameNumber*dwMicroSecPerFrame);
stack.addSlice(label, pixels);
}
}
frameNumber++;
if (frameNumber>lastFrameToRead) break;
} else if (verbose)
IJ.log("skipped '"+fourccString(type)+"' "+posSizeString(size));
if (nextPos > endPosition) break;
raFile.seek(nextPos);
}
}
private Object readFrame (RandomAccessFile rFile, long filePos, int size)
throws Exception, IOException {
rFile.seek(filePos);
if (variableLength) return readCompressedFrame(rFile, size);
else
return readFixedLengthFrame(rFile, size);
}
private Object readCompressedFrame (RandomAccessFile rFile, int size)
throws Exception, IOException {
InputStream inputStream = new raInputStream(rFile, size, biCompression==MJPG_COMPRESSION);
BufferedImage bi = ImageIO.read(inputStream);
if (bi==null) throw new Exception("can't read frame, ImageIO returns null");
int type = bi.getType();
ImageProcessor ip = null;
if (type==BufferedImage.TYPE_BYTE_GRAY) {
ip = new ByteProcessor(bi);
} else if (type==bi.TYPE_BYTE_INDEXED) {
cm = bi.getColorModel();
ip = new ByteProcessor((Image)bi);
} else
ip = new ColorProcessor(bi);
if (convertToGray)
ip = ip.convertToByte(false);
if (flipVertical)
ip.flipVertical();
if (ip.getWidth()!=dwWidth || ip.getHeight()!=biHeight)
ip = ip.resize(dwWidth, biHeight);
return ip.getPixels();
}
private Object readFixedLengthFrame (RandomAccessFile rFile, int size) throws Exception, IOException {
if (size < scanLineSize*biHeight) throw new Exception("Data chunk size "+size+" too short ("+(scanLineSize*biHeight)+" required)");
byte[] rawData = new byte[size];
int n = rFile.read(rawData, 0, size);
if (n < rawData.length)
throw new Exception("Frame ended prematurely after " + n + " bytes");
boolean topDown = flipVertical ? !dataTopDown : dataTopDown;
Object pixels = null;
byte[] bPixels = null;
int[] cPixels = null;
short[] sPixels = null;
if (biBitCount <=8 || convertToGray) {
bPixels = new byte[dwWidth * biHeight];
pixels = bPixels;
} else if (biBitCount == 16 && dataCompression == NO_COMPRESSION) {
sPixels = new short[dwWidth * biHeight];
pixels = sPixels;
} else {
cPixels = new int[dwWidth * biHeight];
pixels = cPixels;
}
if (isPlanarFormat && !convertToGray)
unpackPlanarImage(rawData, cPixels, topDown);
else {
int offset = topDown ? 0 : (biHeight-1)*dwWidth;
int rawOffset = 0;
for (int i = biHeight - 1; i >= 0; i--) { if (biBitCount <=8 || isPlanarFormat)
unpack8bit(rawData, rawOffset, bPixels, offset, dwWidth);
else if (convertToGray)
unpackGray(rawData, rawOffset, bPixels, offset, dwWidth);
else if (biBitCount==16 && dataCompression == NO_COMPRESSION)
unpackShort(rawData, rawOffset, sPixels, offset, dwWidth);
else
unpack(rawData, rawOffset, cPixels, offset, dwWidth);
rawOffset += isPlanarFormat ? dwWidth : scanLineSize;
offset += topDown ? dwWidth : -dwWidth;
}
}
return pixels;
}
void unpack8bit(byte[] rawData, int rawOffset, byte[] pixels, int byteOffset, int w) {
for (int i = 0; i < w; i++)
pixels[byteOffset + i] = rawData[rawOffset + i];
}
void unpackGray(byte[] rawData, int rawOffset, byte[] pixels, int byteOffset, int w) {
int j = byteOffset;
int k = rawOffset;
if (dataCompression == 0) {
for (int i = 0; i < w; i++) {
int b0 = (((int) (rawData[k++])) & 0xff);
int b1 = (((int) (rawData[k++])) & 0xff);
int b2 = (((int) (rawData[k++])) & 0xff);
if (biBitCount==32) k++; pixels[j++] = (byte)((b0*934 + b1*4809 + b2*2449 + 4096)>>13); }
} else {
if (dataCompression==UYVY_COMPRESSION || dataCompression==AYUV_COMPRESSION)
k++; int step = dataCompression==AYUV_COMPRESSION ? 4 : 2;
for (int i = 0; i < w; i++) {
pixels[j++] = rawData[k]; k+=step;
}
}
}
void unpackShort(byte[] rawData, int rawOffset, short[] pixels, int shortOffset, int w) {
int j = shortOffset;
int k = rawOffset;
for (int i = 0; i < w; i++) {
pixels[j++] = (short) ((int)(rawData[k++] & 0xFF)| (((int)(rawData[k++] & 0xFF))<<8));
}
}
void unpack(byte[] rawData, int rawOffset, int[] pixels, int intOffset, int w) {
int j = intOffset;
int k = rawOffset;
switch (dataCompression) {
case NO_COMPRESSION:
for (int i = 0; i < w; i++) {
int b0 = (((int) (rawData[k++])) & 0xff);
int b1 = (((int) (rawData[k++])) & 0xff) << 8;
int b2 = (((int) (rawData[k++])) & 0xff) << 16;
if (biBitCount==32) k++; pixels[j++] = 0xff000000 | b0 | b1 | b2;
}
break;
case YUY2_COMPRESSION:
for (int i = 0; i < w/2; i++) {
int y0 = rawData[k++] & 0xff;
int u = rawData[k++] ^ 0xffffff80; int y1 = rawData[k++] & 0xff;
int v = rawData[k++] ^ 0xffffff80;
writeRGBfromYUV(y0, u, v, pixels, j++);
writeRGBfromYUV(y1, u, v, pixels, j++);
}
break;
case UYVY_COMPRESSION:
for (int i = 0; i < w/2; i++) {
int u = rawData[k++] ^ 0xffffff80;
int y0 = rawData[k++] & 0xff;
int v = rawData[k++] ^ 0xffffff80;
int y1 = rawData[k++] & 0xff;
writeRGBfromYUV(y0, u, v, pixels, j++);
writeRGBfromYUV(y1, u, v, pixels, j++);
}
break;
case YVYU_COMPRESSION:
for (int i = 0; i < w/2; i++) {
int y0 = rawData[k++] & 0xff;
int v = rawData[k++] ^ 0xffffff80;
int y1 = rawData[k++] & 0xff;
int u = rawData[k++] ^ 0xffffff80;
writeRGBfromYUV(y0, u, v, pixels, j++);
writeRGBfromYUV(y1, u, v, pixels, j++);
}
break;
case AYUV_COMPRESSION:
for (int i = 0; i < w; i++) {
k++; int y = rawData[k++] & 0xff;
int v = rawData[k++] ^ 0xffffff80;
int u = rawData[k++] ^ 0xffffff80;
writeRGBfromYUV(y, u, v, pixels, j++);
}
break;
}
}
void unpackPlanarImage(byte[] rawData, int[] cPixels, boolean topDown) {
int w = dwWidth, h = dwHeight;
int uP = w*h, vP = w*h; int uvInc = (dataCompression==NV12_COMPRESSION || dataCompression==NV21_COMPRESSION) ?
2 : 1; if (dataCompression == YV12_COMPRESSION) uP += w*h/4; else if (dataCompression == I420_COMPRESSION)
vP += w*h/4; else if (dataCompression == NV12_COMPRESSION)
vP++; else uP++;
int lineOutInc = topDown ? w : -w;
for (int line=0; line<h; line+=2) {
int pRaw0 = line*w;
int pRawEnd = pRaw0 + w;
int pOut = topDown ? line*w : (h-line-1)*w;
for (int pRaw = pRaw0; pRaw < pRawEnd; ) {
int u = rawData[uP] ^ 0xffffff80; int v = rawData[vP] ^ 0xffffff80;
writeRGBfromYUV(rawData[pRaw] & 0xff, u, v, cPixels, pOut);
writeRGBfromYUV(rawData[pRaw+w] & 0xff, u, v, cPixels, pOut+lineOutInc);
pRaw++; pOut++;
writeRGBfromYUV(rawData[pRaw] & 0xff, u, v, cPixels, pOut);
writeRGBfromYUV(rawData[pRaw+w] & 0xff, u, v, cPixels, pOut+lineOutInc);
pRaw++; pOut++;
uP+=uvInc; vP+=uvInc;
}
}
}
final void writeRGBfromYUV(int y, int u, int v, int[]pixels, int intArrayIndex) {
int r = (9535*y + 13074*v -148464) >> 13;
int g = (9535*y - 6660*v - 3203*u -148464) >> 13;
int b = (9535*y + 16531*u -148464) >> 13;
if (r>255) r=255; if (r<0) r=0;
if (g>255) g=255; if (g<0) g=0;
if (b>255) b=255; if (b<0) b=0;
pixels[intArrayIndex] = 0xff000000 | (r<<16) | (g<<8) | b;
}
final long readLong() throws IOException {
long low = readInt() & 0x00000000FFFFFFFFL;
long high = readInt() & 0x00000000FFFFFFFFL;
long result = high <<32 | low;
return (long) result; }
final int readInt() throws IOException {
int result = 0;
for (int shiftBy = 0; shiftBy < 32; shiftBy += 8)
result |= (raFile.readByte() & 0xff) << shiftBy;
return result;
}
final short readShort() throws IOException {
int low = raFile.readByte() & 0xff;
int high = raFile.readByte() & 0xff;
return (short) (high << 8 | low);
}
private int readType(long endPosition) throws IOException {
while (true) {
long pos = raFile.getFilePointer();
if (pos%paddingGranularity!=0) {
pos = (pos/paddingGranularity+1)*paddingGranularity;
raFile.seek(pos); }
if (pos >= endPosition) return 0;
int type = readInt();
if (type != FOURCC_JUNK)
return type;
long size = readInt()&SIZE_MASK;
if (verbose)
IJ.log("Skip JUNK: "+posSizeString(size));
raFile.seek(raFile.getFilePointer()+size); }
}
private void setFramesPerSecond (ImagePlus imp) {
if (dwMicroSecPerFrame<1000 && dwStreamRate>0) dwMicroSecPerFrame = (int)(dwStreamScale*1e6/dwStreamRate);
if (dwMicroSecPerFrame>=1000)
imp.getCalibration().fps = 1e6 / dwMicroSecPerFrame;
}
private String frameLabel(long timeMicroSec) {
return IJ.d2s(timeMicroSec/1.e6)+" s";
}
private String posSizeString(long size) throws IOException {
return posSizeString(raFile.getFilePointer(), size);
}
private String posSizeString(long pos, long size) {
return "0x"+Long.toHexString(pos)+"-0x"+Long.toHexString(pos+size-1)+" ("+size+" Bytes)";
}
private String timeString() {
return " (t="+(System.currentTimeMillis()-startTime)+" ms)";
}
private String fourccString(int fourcc) {
String s = "";
for (int i=0; i<4; i++) {
int c = fourcc&0xff;
s += Character.toString((char)c);
fourcc >>= 8;
}
return s;
}
private void closeFile(RandomAccessFile rFile) {
if (rFile != null) try {
rFile.close();
} catch (Exception e) {}
}
private void error(String msg) {
aborting = true;
IJ.error("AVI Reader", msg);
}
private String exceptionMessage (Exception e) {
String msg;
if (e.getClass() == Exception.class) msg = e.getMessage();
else
msg = e + "\n" + e.getStackTrace()[0]+"\n"+e.getStackTrace()[1];
return "An error occurred reading the AVI file.\n \n" + msg;
}
final private static int BUFFERSIZE = 4096; final private static byte[] HUFFMAN_TABLES = new byte[] { (byte)0xFF,(byte)0xC4,0x01,(byte)0xA2, 0x00,0x00,0x01,0x05,0x01,0x01,0x01,0x01,0x01,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08,0x09,0x0A,0x0B,0x01,0x00,0x03,0x01,0x01,0x01,0x01,
0x01,0x01,0x01,0x01,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x07,
0x08,0x09,0x0A,0x0B,0x10,0x00,0x02,0x01,0x03,0x03,0x02,0x04,0x03,0x05,0x05,0x04,0x04,0x00,
0x00,0x01,0x7D,0x01,0x02,0x03,0x00,0x04,0x11,0x05,0x12,0x21,0x31,0x41,0x06,0x13,0x51,0x61,
0x07,0x22,0x71,0x14,0x32,(byte)0x81,(byte)0x91,(byte)0xA1,0x08,0x23,0x42,
(byte)0xB1,(byte)0xC1,0x15,0x52,(byte)0xD1,(byte)0xF0,0x24,
0x33,0x62,0x72,(byte)0x82,0x09,0x0A,0x16,0x17,0x18,0x19,0x1A,0x25,0x26,0x27,0x28,0x29,0x2A,0x34,
0x35,0x36,0x37,0x38,0x39,0x3A,0x43,0x44,0x45,0x46,0x47,0x48,0x49,0x4A,0x53,0x54,0x55,0x56,
0x57,0x58,0x59,0x5A,0x63,0x64,0x65,0x66,0x67,0x68,0x69,0x6A,0x73,0x74,0x75,0x76,0x77,0x78,
0x79,0x7A,(byte)0x83,(byte)0x84,(byte)0x85,(byte)0x86,(byte)0x87,(byte)0x88,(byte)0x89,
(byte)0x8A,(byte)0x92,(byte)0x93,(byte)0x94,(byte)0x95,(byte)0x96,(byte)0x97,(byte)0x98,(byte)0x99,
(byte)0x9A,(byte)0xA2,(byte)0xA3,(byte)0xA4,(byte)0xA5,(byte)0xA6,(byte)0xA7,(byte)0xA8,(byte)0xA9,
(byte)0xAA,(byte)0xB2,(byte)0xB3,(byte)0xB4,(byte)0xB5,(byte)0xB6,(byte)0xB7,(byte)0xB8,(byte)0xB9,
(byte)0xBA,(byte)0xC2,(byte)0xC3,(byte)0xC4,(byte)0xC5,(byte)0xC6,(byte)0xC7,(byte)0xC8,(byte)0xC9,
(byte)0xCA,(byte)0xD2,(byte)0xD3,(byte)0xD4,(byte)0xD5,(byte)0xD6,(byte)0xD7,(byte)0xD8,(byte)0xD9,
(byte)0xDA,(byte)0xE1,(byte)0xE2,(byte)0xE3,(byte)0xE4,(byte)0xE5,(byte)0xE6,(byte)0xE7,(byte)0xE8,
(byte)0xE9,(byte)0xEA,(byte)0xF1,(byte)0xF2,(byte)0xF3,(byte)0xF4,(byte)0xF5,(byte)0xF6,(byte)0xF7,
(byte)0xF8,(byte)0xF9,(byte)0xFA,0x11,0x00,0x02,0x01,0x02,0x04,0x04,0x03,0x04,0x07,0x05,0x04,0x04,0x00,0x01,
0x02,0x77,0x00,0x01,0x02,0x03,0x11,0x04,0x05,0x21,0x31,0x06,0x12,0x41,0x51,0x07,0x61,0x71,
0x13,0x22,0x32,(byte)0x81,0x08,0x14,0x42,(byte)0x91,(byte)0xA1,(byte)0xB1,(byte)0xC1,0x09,0x23,0x33,
0x52,(byte)0xF0,0x15,0x62,
0x72,(byte)0xD1,0x0A,0x16,0x24,0x34,(byte)0xE1,0x25,(byte)0xF1,0x17,0x18,0x19,0x1A,0x26,0x27,0x28,0x29,0x2A,
0x35,0x36,0x37,0x38,0x39,0x3A,0x43,0x44,0x45,0x46,0x47,0x48,0x49,0x4A,0x53,0x54,0x55,0x56,
0x57,0x58,0x59,0x5A,0x63,0x64,0x65,0x66,0x67,0x68,0x69,0x6A,0x73,0x74,0x75,0x76,0x77,0x78,
0x79,0x7A,(byte)0x82,(byte)0x83,(byte)0x84,(byte)0x85,(byte)0x86,(byte)0x87,(byte)0x88,(byte)0x89,
(byte)0x8A,(byte)0x92,(byte)0x93,(byte)0x94,(byte)0x95,(byte)0x96,(byte)0x97,(byte)0x98,
(byte)0x99,(byte)0x9A,(byte)0xA2,(byte)0xA3,(byte)0xA4,(byte)0xA5,(byte)0xA6,(byte)0xA7,(byte)0xA8,
(byte)0xA9,(byte)0xAA,(byte)0xB2,(byte)0xB3,(byte)0xB4,(byte)0xB5,(byte)0xB6,(byte)0xB7,(byte)0xB8,
(byte)0xB9,(byte)0xBA,(byte)0xC2,(byte)0xC3,(byte)0xC4,(byte)0xC5,(byte)0xC6,(byte)0xC7,(byte)0xC8,
(byte)0xC9,(byte)0xCA,(byte)0xD2,(byte)0xD3,(byte)0xD4,(byte)0xD5,(byte)0xD6,(byte)0xD7,(byte)0xD8,
(byte)0xD9,(byte)0xDA,(byte)0xE2,(byte)0xE3,(byte)0xE4,(byte)0xE5,(byte)0xE6,(byte)0xE7,(byte)0xE8,
(byte)0xE9,(byte)0xEA,(byte)0xF2,(byte)0xF3,(byte)0xF4,(byte)0xF5,(byte)0xF6,(byte)0xF7,(byte)0xF8,
(byte)0xF9,(byte)0xFA };
final private static int HUFFMAN_LENGTH = 420;
class raInputStream extends InputStream {
RandomAccessFile rFile; int readableSize; boolean fixMJPG; byte[] buffer; int bufferPointer; int bufferLength;
raInputStream (RandomAccessFile rFile, int readableSize, boolean fixMJPG) throws IOException {
this.rFile = rFile;
this.readableSize = readableSize;
this.fixMJPG = fixMJPG;
if (fixMJPG) {
buffer = new byte[BUFFERSIZE];
bufferLength = Math.min(BUFFERSIZE-HUFFMAN_LENGTH, readableSize);
bufferLength = rFile.read(buffer, 0, bufferLength);
addHuffmanTables();
}
}
public int available () {
return readableSize;
}
public int read () throws IOException {
readableSize--;
if (fixMJPG) {
int result = buffer[bufferPointer] & 0xff;
bufferPointer++;
if (bufferPointer >= bufferLength) fixMJPG = false; return result;
} else
return rFile.read();
}
public int read (byte[] b, int off, int len) throws IOException {
int nBytes;
if (fixMJPG) {
nBytes = Math.min(len, bufferLength-bufferPointer);
System.arraycopy(buffer, bufferPointer, b, off, nBytes);
bufferPointer += nBytes;
if (bufferPointer >= bufferLength) {
fixMJPG = false;
if (len-nBytes > 0)
nBytes += rFile.read(b, off+nBytes, len-nBytes);
}
} else
nBytes = rFile.read(b, off, len);
readableSize -= nBytes;
return nBytes;
}
private void addHuffmanTables() {
if (readShort(0)!=0xffd8 || bufferLength<6) return; int offset = 2;
int segmentLength = 0;
do {
int code = readShort(offset); if (code==0xffc4) return;
else if (code==0xffda || code==0xffd9) { insertHuffmanTables(offset);
return; }
offset += 2;
segmentLength = readShort(offset); offset += segmentLength; } while (offset<bufferLength-4 && segmentLength>=0);
}
private int readShort(int offset) {
return ((buffer[offset]&0xff)<<8) | (buffer[offset+1]&0xff);
}
private void insertHuffmanTables(int position) {
System.arraycopy(buffer, position, buffer, position+HUFFMAN_LENGTH, bufferLength-position);
System.arraycopy(HUFFMAN_TABLES, 0, buffer, position, HUFFMAN_LENGTH);
bufferLength += HUFFMAN_LENGTH;
readableSize += HUFFMAN_LENGTH;
}
}
public void displayDialog(boolean displayDialog) {
this.displayDialog = displayDialog;
}
}