/**
* Copyright: t3am_C9 (Alexander Schäffer, Johannes Ebersold, Sebastian Geib, Thomas Kisiel)
*
* This file is part of GifToApngConverter
*
* GifToApngConverter is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* GifToApngConverter 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with GifToApngConverter. If not, see <a href="http://www.gnu.org/licenses/">here</a>
*/
package gif;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
/**
* Reads information from the Gif-file which can't be accessed with
* the integrated image reader. It's a bit complex... but whatever.
* It could evolve to a real gifreader xD
*/
public class GifInfo
{
// a few needed constants to identifiy blocks
private final int IMAGEDESCRIPTOR = 0x2c;
private final int TRAILER = 0x3b;
private final int EXTENSIONBLOCK = 0x21;
private final int APPLICATIONEXTENSIONLABEL = 0xFF;
private final int COMMENTEXTENSIONBLOCK = 0xFE;
private final int PLAINTEXTEXTENSION = 0x01;
private final int GRAPHICCONTROLEXTENSION = 0xF9;
private final int MARKLIMIT = 1024;
// member
protected int mLoops = 1; // 0 = forever
private StringBuilder mComment; // holds a Comment String
/**
* Reads information from a file. Right now, it will only read the
* netscape extension information, because there is no other way to get it.
* @param file File
* @throws IOException
*/
public GifInfo(File file) throws IOException
{
// create a buffered stream. Error handling will be done outside)
BufferedInputStream bis = new BufferedInputStream(new FileInputStream(file));
// load the string builder
mComment = new StringBuilder();
// make sure that we have the mark/reset feature because I need it
if (!bis.markSupported())
{
throw new IOException("Error: Mark/Read not supported.");
}
readHeader(bis);
readBlocks(bis);
bis.close();
}
/**
* Moves through all blocks in the gif file and reads the needed information
* @param bis BufferedInputStream
*/
private void readBlocks(BufferedInputStream bis) throws IOException
{
int id = 0;
do
{
// get block id and leave fpointer where it is
bis.mark(MARKLIMIT);
id = bis.read();
bis.reset();
if (id <= 0) throw new IOException("GifInfo: Something went wrong or I reached EOF."); // quit loop
// decide what to do with the blocks
if (id != EXTENSIONBLOCK)
{
// handle gif blocks here. They MUST be processed
switch (id)
{
case IMAGEDESCRIPTOR:
readImage(bis);
break;
case TRAILER:
return;
default:
throw new IOException("Error: GifInfo encountered an unknown block type: "+ id);
}
}
else
{
// handle extension blocks here
int label = 0;
bis.mark(MARKLIMIT);
safeSkip(bis,1);
label = bis.read();
bis.reset();
switch (label)
{
case APPLICATIONEXTENSIONLABEL:
readNetscapeExtensionBlock(bis);
break;
case COMMENTEXTENSIONBLOCK:
readComment(bis);
break;
case PLAINTEXTEXTENSION:
//TODO is this even implementable in the apng???
skipExtensionBlock(bis);
break;
case GRAPHICCONTROLEXTENSION:
skipExtensionBlock(bis);
break;
default:
skipExtensionBlock(bis);
}
}
} while (true);
}
/**
* Moves the filepointer over a filetable. Function needs the packed fields value and
* it has to be called after the end of the preceding block
* @param bis BufferedStreamReader
* @param packedFieldsValue int
* @throws IOException
*/
private void skipColorTable(BufferedInputStream bis, int packedFieldsValue) throws IOException
{
if ((packedFieldsValue & 128) > 0) // checks flag 8
{
int tableSize = packedFieldsValue & 7; // get 3 bit value
tableSize = 3 * ((int) Math.pow(2, (tableSize +1 )));
safeSkip(bis,tableSize);
}
}
/**
* Right now just skip over the header. We just need the palette information to
* know how far we have to skip. This method moves the filepointer.
* @param bis BufferedInputStream
* @throws IOException
*/
private void readHeader(BufferedInputStream bis) throws IOException
{
int value = 0;
// skipp header information. Check for global table and it's size if available
safeSkip(bis,6); // jump over header
// try to read the header information
safeSkip(bis,4);
value = bis.read(); // packed fields
safeSkip(bis,2);
// handle a possible global color table
skipColorTable(bis, value);
}
/**
* Will skip over the imageblock. This method moves the filepointer.
* @param bis BufferedInputStream
* @throws IOException
*/
private void readImage(BufferedInputStream bis) throws IOException
{
// skip some data
safeSkip(bis,9);
int value = bis.read();
// skip local table if available
skipColorTable(bis, value);
// skip the lzw info
safeSkip(bis,1);
skipSubBlocks(bis);
// pointer is now at the beginning of the next block
}
private void readComment(BufferedInputStream bis) throws IOException
{
// skip first two bytes because I am sue that i am correct
safeSkip(bis,2);
// now read in the data
int length;
int value;
do
{
length = bis.read();
if(length>0 && mComment.length()>0){
mComment.append(' '); //space between comment-blocks
}
for (int i = 0; i < length;i++)
{
value = bis.read();
mComment.append((char)value);
}
} while (length > 0);
// pointer will be at the beginning of the next block
}
/**
* Moves the filepointer over an extensionblock
* This method moves the filepointer!
* @param bis BufferedInputStream
*/
private void skipExtensionBlock(BufferedInputStream bis) throws IOException
{
safeSkip(bis,2); // skip ident and label
int length = bis.read();
// skip data
safeSkip(bis,length);
// skip data block with subblocks
skipSubBlocks(bis);
}
/**
* Skips over subblocks which appear in a Datablock
* @param bis
* @throws IOException
*/
private void skipSubBlocks(BufferedInputStream bis) throws IOException
{
// skip all subblocks
int value = 0;
do
{
value = bis.read();
safeSkip(bis, value);
} while (value != 0);
}
/**
* Reads the Netscape extension. If this isn't the netscape extension, we skip it.
* @param bis BufferedInputStream
* @throws IOException
*/
private void readNetscapeExtensionBlock(BufferedInputStream bis) throws IOException
{
bis.mark(MARKLIMIT); // safe position in case we need to skip this blo
safeSkip(bis,3); // skip identification and blocksize
// we just check 1 letter for now
if (bis.read() == 'N')
{
safeSkip(bis,12); // skip useless data for now
// now read the looping count... finally =)
int value = 0;
value = bis.read();
value += 256 * bis.read();
if (value > 0) value++;
mLoops = value; // yay :D
safeSkip(bis,1);
}
else
{
bis.reset();
skipExtensionBlock(bis);
}
// we should be now at the beginning of the next block
}
/**
* Returns the number of iterations for the gif
* @return int
*/
public int getLoops()
{
return mLoops;
}
/**
* Returns a comment in a String
* @return String
*/
public String getComment()
{
return mComment.toString();
}
/**
* Will skip for 100%.
* @param bis
* @param count
* @throws IOException
*/
private void safeSkip(BufferedInputStream bis, long count) throws IOException
{
do
{
count = count - bis.skip(count);
// if count > 0 check for EOF
if (count >= bis.available())
throw new IOException("Error: End of File reached (file might be corrupt)");
} while (count > 0);
}
}