/* * @(#)ANIMDeltaFrame.java 1.4.1 2010-12-25 * * Copyright (c) 1999-2010 Werner Randelshofer, Goldau, Switzerland. * All rights reserved. * * You may not use, copy or modify this file, except in compliance with the * license agreement you entered into with Werner Randelshofer. * For details see accompanying license terms. */ package org.monte.media.anim; import org.monte.media.image.BitmapImage; /** * @author Werner Randelshofer, Hausmatt 10, CH-6405 Goldau, Switzerland * @version 1.4.1 2010-12-25 Minor fixes. * <br>1.4 2010-01-10 Try to play Vertical7Long anims even if they * are broken. * <br>1.3 2009-11-19 Added support for BYTE_VERTICAL with XOR Op. * <br> 1.2 2006-10-01 Added support for DECODER_J. Removed "_" suffix from * instance variable names. * <br> 1.1 2003-03-30 Static OP codes are now public. * <br> 1.0 1999-10-19 */ public class ANIMDeltaFrame extends ANIMFrame { private int leftBound, topBound, rightBound, bottomBound; private final static int // ENCODING_BYTE_VERTICAL = 5, ENCODING_VERTICAL_7_SHORT = 6, ENCODING_VERTICAL_7_LONG = 7, ENCODING_VERTICAL_8_SHORT = 8, ENCODING_VERTICAL_8_LONG = 9, ENCODING_J = 74; public final static int // OP_Direct = 0, OP_XOR = 1, OP_LongDelta = 2, OP_ShortDelta = 3, OP_GeneralDelta = 4, OP_ByteVertical = 5, OP_StereoDelta = 6, OP_Vertical7 = 7, OP_Vertical8 = 8, OP_J = 74; /** Wether we already printed a warning about a broken encoding. */ private boolean isWarningPrinted = false; public ANIMDeltaFrame() { } private int getEncoding() { switch (getOperation()) { case OP_Direct: // Key Frame (Data stored in ILBM BODY Chunk) throw new InternalError("Key Frames not yet supported (Anim Op0)"); case OP_ByteVertical: if (getBits() == BIT_XOR) { // okay } else if ((getBits() & BadBitsOP_ByteVertical) != 0) { throw new InternalError("Unknown Bits for Anim Op5 in ANHD; Bits:" + getBits()); } return ENCODING_BYTE_VERTICAL; case OP_Vertical7: if ((getBits() & BIT_LongData) == 0) { return ENCODING_VERTICAL_7_SHORT; } else { return ENCODING_VERTICAL_7_LONG; } case OP_Vertical8: if ((getBits() & BIT_LongData) == 0) { return ENCODING_VERTICAL_8_SHORT; } else { return ENCODING_VERTICAL_8_LONG; } case OP_J: return ENCODING_J; default: throw new InternalError("ANIM Op" + getOperation() + " not supported."); } } @Override public void decode(BitmapImage bitmap, ANIMMovieTrack track) { switch (getEncoding()) { case ENCODING_BYTE_VERTICAL: decodeByteVertical(bitmap, track); break; case ENCODING_VERTICAL_7_SHORT: decodeVertical7Short(bitmap, track); break; case ENCODING_VERTICAL_7_LONG: decodeVertical7Long(bitmap, track); break; case ENCODING_VERTICAL_8_SHORT: decodeVertical8Short(bitmap, track); break; case ENCODING_VERTICAL_8_LONG: decodeVertical8Long(bitmap, track); break; case ENCODING_J: decodeJ(bitmap, track); break; default: throw new InternalError("Unsupported encoding." + getEncoding()); } } private void decodeByteVertical(BitmapImage bitmap, ANIMMovieTrack track) { int columns = 0; int iOp = 0; byte[] planeBytes = bitmap.getBitmap(); int iPl = 0; int widthInBytes = bitmap.getBitplaneStride(); int interleave = track.getNbPlanes() * widthInBytes; int opCode = 0; int opCount = 0; byte copyByte = 0; leftBound = widthInBytes; rightBound = 0; topBound = track.getHeight(); bottomBound = 0; int height = track.getHeight(); boolean isXOR = getBits() == BIT_XOR; // Repeat for each plane. for (int i = 0,n=track.getNbPlanes();i<n; ++i) { // iOp is the pointer (index) to the op-codes. iOp = ((data[i * 4] & 0xff) << 24) + ((data[i * 4 + 1] & 0xff) << 16) + ((data[i * 4 + 2] & 0xff) << 8) + (data[i * 4 + 3] & 0xff); try { if (iOp > 0) { // Each column of the plane is coded on its own. for (columns = 0; columns < widthInBytes; ++columns) { // Set iPl to the beginning of the column in the plane. iPl = columns + i * widthInBytes; opCount = data[iOp++] & 0xff; if (opCount > 0) { if (columns < leftBound) { leftBound = columns; } if (columns > rightBound) { rightBound = columns; } opCode = data[iOp]; if (opCode <= 0) { topBound = 0; } else { if (opCode < topBound) { topBound = opCode; } } if (isXOR) { for (; opCount > 0; opCount--) { opCode = data[iOp++]; if (opCode > 0) { // Skip ops iPl += opCode * interleave; } else if (opCode < 0) { // Uniq ops opCode &= 0x7f; while (opCode-- > 0) { planeBytes[iPl] ^= data[iOp++]; iPl += interleave; } } else { // Repeat ops opCode = data[iOp++] & 0xff; if (opCode == 0) { return; } //throw new InterpretException("Error in Delta Chunk: copy bytes with count 0."); copyByte = data[iOp++]; while (opCode-- > 0) { planeBytes[iPl] ^= copyByte; iPl += interleave; } } } } else { for (; opCount > 0; opCount--) { opCode = data[iOp++]; if (opCode > 0) { // Skip ops iPl += opCode * interleave; } else if (opCode < 0) { // Uniq ops opCode &= 0x7f; while (opCode-- > 0) { planeBytes[iPl] = data[iOp++]; iPl += interleave; } } else { // Repeat ops opCode = data[iOp++] & 0xff; if (opCode == 0) { return; } //throw new InterpretException("Error in Delta Chunk: copy bytes with count 0."); copyByte = data[iOp++]; while (opCode-- > 0) { planeBytes[iPl] = copyByte; iPl += interleave; } } } } if (opCode <= 0) { int bottom = (iPl - (columns + i * widthInBytes)) / interleave; if (bottom > bottomBound) { bottomBound = bottom; } } else { if (height - opCode > bottomBound) { bottomBound = height - opCode; } } } } } } catch (ArrayIndexOutOfBoundsException e) { e.printStackTrace(); } } if (leftBound <= rightBound) { leftBound *= 8; rightBound = rightBound * 8 + 8; } } private void decodeVertical8Short(BitmapImage bitmap, ANIMMovieTrack track) { int columns = 0; int iOp = 0; byte[] planeBytes = bitmap.getBitmap(); int iPl = 0; int widthInBytes = bitmap.getBitplaneStride(); int interleave = track.getNbPlanes() * widthInBytes; int opCode = 0; int opCount = 0; byte copyByte1 = 0; byte copyByte2 = 0; leftBound = widthInBytes; rightBound = 0; topBound = track.getHeight() - 1; bottomBound = 0; int height = track.getHeight() - 1; // Repeat for each plane. for (int i = 0; i < track.getNbPlanes(); i++) { // iOp points to the Op-Codes. iOp = ((data[i * 4] & 0xff) << 24) + ((data[i * 4 + 1] & 0xff) << 16) + ((data[i * 4 + 2] & 0xff) << 8) + (data[i * 4 + 3] & 0xff); if (iOp > 0) { // Each column has its own Op-codes. for (columns = 0; columns < widthInBytes; columns += 2) { // iPl points to the column in the bitmap. iPl = columns + i * widthInBytes; opCount = ((data[iOp++] & 0xff) << 8) | (data[iOp++] & 0xff); if (opCount > 0) { if (columns < leftBound) { leftBound = columns; } if (columns > rightBound) { rightBound = columns; } opCode = (data[iOp] << 8) | (data[iOp + 1] & 0xff); if (opCode <= 0) { topBound = 0; } else { if (opCode < topBound) { topBound = opCode; } } for (; opCount > 0; opCount--) { opCode = (data[iOp++] << 8) | (data[iOp++] & 0xff); if (opCode > 0) { // Skip ops iPl += opCode * interleave; } else if (opCode < 0) { // Uniq ops opCode &= 0x7fff; while (opCode-- > 0) { planeBytes[iPl] = data[iOp++]; planeBytes[iPl + 1] = data[iOp++]; iPl += interleave; } } else { // Repeat ops opCode = ((data[iOp++] << 8) | (data[iOp++] & 0xff)) & 0xffff; if (opCode == 0) { return; } //throw new InterpretException("Error in Delta Chunk: copy bytes with count 0."); copyByte1 = data[iOp++]; copyByte2 = data[iOp++]; while (opCode-- > 0) { planeBytes[iPl] = copyByte1; planeBytes[iPl + 1] = copyByte2; iPl += interleave; } } } if (opCode <= 0) { int bottom = (iPl - (columns + i * widthInBytes)) / interleave; if (bottom > bottomBound) { bottomBound = bottom; } } else { if (height - opCode > bottomBound) { bottomBound = height - opCode; } } } } } } if (leftBound <= rightBound) { leftBound *= 8; rightBound = rightBound * 8 + 16; } } private void decodeVertical8Long(BitmapImage bitmap, ANIMMovieTrack track) { int columns = 0; int iOp = 0; byte[] planeBytes = bitmap.getBitmap(); int iPl = 0; int widthInBytes = bitmap.getBitplaneStride(); int interleave = track.getNbPlanes() * widthInBytes; int opCode = 0; int opCount = 0; byte copyByte1 = 0; byte copyByte2 = 0; byte copyByte3 = 0; byte copyByte4 = 0; leftBound = widthInBytes; rightBound = 0; topBound = track.getHeight() - 1; bottomBound = 0; int height = track.getHeight() - 1; // Repeat for each plane for (int i = 0; i < track.getNbPlanes(); i++) { // iOp points to the op-codes. iOp = ((data[i * 4] & 0xff) << 24) + ((data[i * 4 + 1] & 0xff) << 16) + ((data[i * 4 + 2] & 0xff) << 8) + (data[i * 4 + 3] & 0xff); if (iOp > 0) { // Decode each column of the plane separately. for (columns = 0; columns < widthInBytes; columns += 4) { // iPl points to the column in the bitmap. iPl = columns + i * widthInBytes; opCount = ((data[iOp++] & 0xff) << 24) + ((data[iOp++] & 0xff) << 16) + ((data[iOp++] & 0xff) << 8) + (data[iOp++] & 0xff); if (opCount > 0) { if (columns < leftBound) { leftBound = columns; } if (columns > rightBound) { rightBound = columns; } opCode = ((data[iOp] & 0xff) << 24) + ((data[iOp + 1] & 0xff) << 16) + ((data[iOp + 2] & 0xff) << 8) + (data[iOp + 3] & 0xff); if (opCode <= 0) { topBound = 0; } else { if (opCode < topBound) { topBound = opCode; } } for (; opCount > 0; opCount--) { opCode = ((data[iOp++] & 0xff) << 24) + ((data[iOp++] & 0xff) << 16) + ((data[iOp++] & 0xff) << 8) + (data[iOp++] & 0xff); if (opCode > 0) { // Skip ops iPl += opCode * interleave; } else if (opCode < 0) { // Uniq ops opCode &= 0x7fffffff; while (opCode-- > 0) { planeBytes[iPl] = data[iOp++]; planeBytes[iPl + 1] = data[iOp++]; planeBytes[iPl + 2] = data[iOp++]; planeBytes[iPl + 3] = data[iOp++]; iPl += interleave; } } else { // Repeat ops opCode = ((data[iOp++] & 0xff) << 24) + ((data[iOp++] & 0xff) << 16) + ((data[iOp++] & 0xff) << 8) + (data[iOp++] & 0xff); if (opCode == 0) { return; } //throw new InterpretException("Error in Delta Chunk: copy bytes with count 0."); copyByte1 = data[iOp++]; copyByte2 = data[iOp++]; copyByte3 = data[iOp++]; copyByte4 = data[iOp++]; while (opCode-- > 0) { planeBytes[iPl] = copyByte1; planeBytes[iPl + 1] = copyByte2; planeBytes[iPl + 2] = copyByte3; planeBytes[iPl + 3] = copyByte4; iPl += interleave; } } } if (opCode <= 0) { int bottom = (iPl - (columns + i * widthInBytes)) / interleave; if (bottom > bottomBound) { bottomBound = bottom; } } else { if (height - opCode > bottomBound) { bottomBound = height - opCode; } } } } } } if (leftBound <= rightBound) { leftBound *= 8; rightBound = rightBound * 8 + 32; } } private void decodeVertical7Short(BitmapImage bitmap, ANIMMovieTrack track) { int columns = 0; int iOp = 0; int iData = 0; byte[] planeBytes = bitmap.getBitmap(); int iPl = 0; int widthInBytes = bitmap.getBitplaneStride(); int interleave = bitmap.getScanlineStride(); int opCode = 0; int opCount = 0; byte copyByte1 = 0; byte copyByte2 = 0; leftBound = widthInBytes; rightBound = 0; topBound = track.getHeight() - 1; bottomBound = 0; int height = track.getHeight() - 1; for (int i = 0; i < track.getNbPlanes(); i++) { iOp = ((data[i * 4] & 0xff) << 24) + ((data[i * 4 + 1] & 0xff) << 16) + ((data[i * 4 + 2] & 0xff) << 8) + (data[i * 4 + 3] & 0xff); iData = ((data[i * 4 + 32] & 0xff) << 24) + ((data[i * 4 + 33] & 0xff) << 16) + ((data[i * 4 + 34] & 0xff) << 8) + (data[i * 4 + 35] & 0xff); if (iOp > 0) { for (columns = 0; columns < widthInBytes; columns += 2) { iPl = columns + i * widthInBytes; opCount = data[iOp++] & 0xff; if (opCount > 0) { if (columns < leftBound) { leftBound = columns; } if (columns > rightBound) { rightBound = columns; } opCode = data[iOp]; if (opCode <= 0) { topBound = 0; } else { if (opCode < topBound) { topBound = opCode; } } for (; opCount > 0; opCount--) { opCode = data[iOp++]; if (opCode > 0) { // Skip ops iPl += opCode * interleave; } else if (opCode < 0) { // Uniq ops opCode &= 0x7f; while (opCode-- > 0) { planeBytes[iPl] = data[iData++]; planeBytes[iPl + 1] = data[iData++]; iPl += interleave; } } else { // Repeat ops opCode = data[iOp++] & 0xff; if (opCode == 0) { return; } //throw new InterpretException("Error in Delta Chunk: copy bytes with count 0."); copyByte1 = data[iData++]; copyByte2 = data[iData++]; while (opCode-- > 0) { planeBytes[iPl] = copyByte1; planeBytes[iPl + 1] = copyByte2; iPl += interleave; } } } if (opCode <= 0) { int bottom = (iPl - (columns + i * widthInBytes)) / interleave; if (bottom > bottomBound) { bottomBound = bottom; } } else { if (height - opCode > bottomBound) { bottomBound = height - opCode; } } } } } } if (leftBound <= rightBound) { leftBound *= 8; rightBound = rightBound * 8 + 32; } } private void decodeVertical7Long(BitmapImage bitmap, ANIMMovieTrack track) { int columns = 0; int iOp = 0; int iData = 0; byte[] planeBytes = bitmap.getBitmap(); int iPl = 0; int widthInBytes = bitmap.getBitplaneStride(); int interleave = track.getNbPlanes() * widthInBytes; int opCode = 0; int opCount = 0; byte copyByte1 = 0; byte copyByte2 = 0; byte copyByte3 = 0; byte copyByte4 = 0; leftBound = widthInBytes; rightBound = 0; topBound = track.getHeight() - 1; bottomBound = 0; int height = track.getHeight() - 1; for (int i = 0; i < track.getNbPlanes(); i++) { iOp = ((data[i * 4] & 0xff) << 24) + ((data[i * 4 + 1] & 0xff) << 16) + ((data[i * 4 + 2] & 0xff) << 8) + (data[i * 4 + 3] & 0xff); iData = ((data[i * 4 + 32] & 0xff) << 24) + ((data[i * 4 + 33] & 0xff) << 16) + ((data[i * 4 + 34] & 0xff) << 8) + (data[i * 4 + 35] & 0xff); if (iOp > 0) { for (columns = 0; columns < widthInBytes; columns += 4) { try { iPl = columns + i * widthInBytes; opCount = data[iOp++] & 0xff; if (opCount > 0) { if (columns < leftBound) { leftBound = columns; } if (columns > rightBound) { rightBound = columns; } opCode = data[iOp]; if (opCode <= 0) { topBound = 0; } else { if (opCode < topBound) { topBound = opCode; } } for (; opCount > 0; opCount--) { opCode = data[iOp++]; if (opCode > 0) { // Skip ops iPl += opCode * interleave; } else if (opCode < 0) { // Uniq ops opCode &= 0x7f; while (opCode-- > 0) { planeBytes[iPl] = data[iData++]; planeBytes[iPl + 1] = data[iData++]; planeBytes[iPl + 2] = data[iData++]; planeBytes[iPl + 3] = data[iData++]; iPl += interleave; } } else { // Repeat ops opCode = data[iOp++] & 0xff; if (opCode == 0) { return; } //throw new InterpretException("Error in Delta Chunk: copy bytes with count 0."); copyByte1 = data[iData++]; copyByte2 = data[iData++]; copyByte3 = data[iData++]; copyByte4 = data[iData++]; while (opCode-- > 0) { planeBytes[iPl] = copyByte1; planeBytes[iPl + 1] = copyByte2; planeBytes[iPl + 2] = copyByte3; planeBytes[iPl + 3] = copyByte4; iPl += interleave; } } } if (opCode <= 0) { int bottom = (iPl - (columns + i * widthInBytes)) / interleave; if (bottom > bottomBound) { bottomBound = bottom; } } else { if (height - opCode > bottomBound) { bottomBound = height - opCode; } } } } catch (IndexOutOfBoundsException e) { // Some delta frames write over the bounds // of the bitmap. if (!isWarningPrinted) { e.printStackTrace(); isWarningPrinted = true; } } } } } if (leftBound <= rightBound) { leftBound *= 8; rightBound = rightBound * 8 + 64; } } /** Decodes DLTA's in Eric Graham's Compresson mode "J". * * The following documentation has been taken from Steven Den Beste's * docu for his "unmovie" program. * * A DLTA appears to have three kinds * of items in it, with each type being indicated by the value of its first byte: * * * Type 0: indicates the end of the DLTA. Layout: * word: 0 * * Type 1: indicates a "wall": This is a section of the image which has full * Z-height, is 1 byte wide in X, and has a variable Y size. Layout: * word: 1 * word: 0=unidirectional (store value), 1=bidirectional (XOR value) * word: Y-size (number of pixels in Y direction) * word: number of blocks to follow: * per block: * word: offset in each bitplane (note: NOT in the total image!) * 1-6 bytes: full Z height for first Y * 1-6 bytes: full Z height for second Y * etc., extending DOWN. * * Type 2: indicates a "pile": This is a section of the image which has full * Z-height, and has both variable Y size and X size. Layout: * word: 2 * word: 0=unidirectional, 1=bidirectional * word: Y size * word: X size * word: number of blocks to follow: * per block: * word: offset in each bitplane (NOT in the total image) * successive bytes: a traversed 3D rectangle, with X varying within * Y within Z. (X moves right, Z moves up, Y moves down) * * The movie is double-buffered, but you don't have to know about that part. * (Anyway, it is described in the original documentation for "pilbm" if you're * curious. */ private void decodeJ(BitmapImage bitmap, ANIMMovieTrack track) { int nbPlanes = track.getNbPlanes(); int widthInBytes = bitmap.getBitplaneStride(); // Mark all pixels of the delta frame as being changed // XXX - Determine minimal bounds /* rightBound = track.getWidth(); leftBound = 0; bottomBound = track.getHeight(); topBound = 0;*/ leftBound = track.getWidth() - 1; rightBound = 0; topBound = track.getHeight() - 1; bottomBound = 0; // Current reading position; int pos = 0; // Output data goes here: byte[] planeBytes = bitmap.getBitmap(); // Change type, 16 bit short: 0=End of Delta, 1=Wall, 2=Pile. int changeType; try { decodingLoop: while (pos < data.length) { changeType = ((data[pos++] & 0xff) << 8) | ((data[pos++]) & 0xff); switch (changeType) { case 0: /* End of DELTA */ break decodingLoop; case 1: { /* Wall */ // Read wall header // struct { // short uni_flag; // short y_size; // short num_blocks; } wall; int uniFlag = ((data[pos++] & 0xff) << 8) | ((data[pos++]) & 0xff); int ySize = ((data[pos++] & 0xff) << 8) | ((data[pos++]) & 0xff); int numBlocks = ((data[pos++] & 0xff) << 8) | ((data[pos++]) & 0xff); // Decode wall data for (int b = 0; b < numBlocks; b++) { int offset = ((data[pos++] & 0xff) << 8) | ((data[pos++]) & 0xff); leftBound = Math.min(leftBound, (offset % widthInBytes) * 8); rightBound = Math.max(rightBound, (offset % widthInBytes) * 8 + 8); topBound = Math.min(topBound, (offset / widthInBytes)); bottomBound = Math.max(bottomBound, (offset / widthInBytes) + ySize); int realOffset = (offset / widthInBytes) * nbPlanes; realOffset *= widthInBytes; realOffset += offset % widthInBytes; if (uniFlag == 1) { for (int z = 0; z < nbPlanes; z++) { for (int y = 0; y < ySize; y++) { int dest = z * widthInBytes * ySize + y * widthInBytes + realOffset; planeBytes[dest] ^= data[pos++]; } } } else { for (int z = 0; z < nbPlanes; z++) { for (int y = 0; y < ySize; y++) { int dest = z * widthInBytes * ySize + y * widthInBytes + realOffset; planeBytes[dest] = data[pos++]; } } } // If we've stopped on an odd boundary, read and throw away // another byte. if (pos % 2 == 1) { pos++; } } break; } case 2: { /* Pile */ // Read Pile header // struct { // short uni_flag; // short y_size; // short x_size; // short num_blocks; } pile; int uniFlag = ((data[pos++] & 0xff) << 8) | ((data[pos++]) & 0xff); int ySize = ((data[pos++] & 0xff) << 8) | ((data[pos++]) & 0xff); int xSize = ((data[pos++] & 0xff) << 8) | ((data[pos++]) & 0xff); int numBlocks = ((data[pos++] & 0xff) << 8) | ((data[pos++]) & 0xff); // Decode Pile data for (int b = 0; b < numBlocks; b++) { int offset = ((data[pos++] & 0xff) << 8) | ((data[pos++]) & 0xff); leftBound = Math.min(leftBound, (offset % widthInBytes) * 8); rightBound = Math.max(rightBound, (offset % widthInBytes + xSize) * 8 + 8); topBound = Math.min(topBound, (offset / widthInBytes)); bottomBound = Math.max(bottomBound, (offset / widthInBytes) + ySize); int realOffset = (offset / widthInBytes) * nbPlanes; realOffset *= widthInBytes; realOffset += offset % widthInBytes; if (uniFlag == 1) { for (int z = 0; z < nbPlanes; z++) { for (int y = 0; y < ySize; y++) { for (int x = 0; x < xSize; x++) { int dest = z * widthInBytes * ySize + y * widthInBytes + realOffset + x; planeBytes[dest] ^= data[pos++]; } } } } else { for (int z = 0; z < nbPlanes; z++) { for (int y = 0; y < ySize; y++) { for (int x = 0; x < xSize; x++) { int dest = z * widthInBytes * ySize + y * widthInBytes + realOffset + x; planeBytes[dest] = data[pos++]; } } } } // If we've stopped on an odd boundary, read and throw away // another byte. if (pos % 2 == 1) { pos++; } } break; } default: System.out.println("Unsupported changeType in 'J' delta frame:" + changeType); break decodingLoop; //throw new InternalError("Unsupported changeType in 'J' delta frame:"+changeType); } } } catch (IndexOutOfBoundsException e) { // Some delta frames write over the bounds // of the bitmap. if (!isWarningPrinted) { e.printStackTrace(); isWarningPrinted = true; } } } @Override public int getTopBound(ANIMMovieTrack track) { return topBound; } @Override public int getBottomBound(ANIMMovieTrack track) { return bottomBound; } @Override public int getLeftBound(ANIMMovieTrack track) { return leftBound; } @Override public int getRightBound(ANIMMovieTrack track) { return rightBound; } /** Returns true if the frame can be decoded over both the previous frame * or the subsequent frame. Bidirectional frames can be used efficiently * for forward and backward playing a movie. * <p> * All key frames are bidirectional. Delta frames which use an XOR OP-mode * are bidirectional as well. */ @Override public boolean isBidirectional() { switch (getOperation()) { case OP_Direct: // Key Frame (Data stored in ILBM BODY Chunk) return true; case OP_ByteVertical: if (getBits() == BIT_XOR) { return true; } break; case OP_J: // All J-encoded frames seem to be bidirectional return true; default: break; } return false; } }