blob: bc62c809ccfbe5a9ef3030d3dd2b9f9f6d0c19b0 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @author Oleg V. Khaschansky
*
*/
#include "gifdecoder.h"
#include "hycomp.h"
/*
* Class: org_apache_harmony_awt_gl_image_GifDecoder
* Method: initIDs
* Signature: ()V
*/
JNIEXPORT void JNICALL Java_org_apache_harmony_awt_gl_image_GifDecoder_initIDs
(JNIEnv *env, jclass cls) {
jclass gifDataStreamClass = (*env)->FindClass(env, gifDataStreamClassName);
jclass colorTableClass = (*env)->FindClass(env, colorTableClassName);
jclass logicalScreenClass = (*env)->FindClass(env, logicalScreenClassName);
jclass graphicBlockClass = (*env)->FindClass(env, graphicBlockClassName);
// Gif decoder
img_GIF_forceRGBID = (*env)->GetFieldID(env, cls, "forceRGB", "Z");
img_GIF_hNativeDecoderID = (*env)->GetFieldID(env, cls, "hNativeDecoder", "J");
img_GIF_bytesConsumedID = (*env)->GetFieldID(env, cls, "bytesConsumed", "I");
img_GIF_setCommentID = (*env)->GetMethodID(env, cls, "setComment", "(Ljava/lang/String;)V");
// Gif data stream
img_GIF_ds_logicalScreenID = (*env)->GetFieldID(env, gifDataStreamClass, "logicalScreen", logicalScreenClassSignature);
img_GIF_ds_loopCountID = (*env)->GetFieldID(env, gifDataStreamClass, "loopCount", "I");
img_GIF_ds_completedID = (*env)->GetFieldID(env, gifDataStreamClass, "completed", "Z");
// Gif logical screen
img_GIF_ls_logicalScreenWidthID = (*env)->GetFieldID(env, logicalScreenClass, "logicalScreenWidth", "I");
img_GIF_ls_logicalScreenHeightID = (*env)->GetFieldID(env, logicalScreenClass, "logicalScreenHeight", "I");
img_GIF_ls_backgroundColorID = (*env)->GetFieldID(env, logicalScreenClass, "backgroundColor", "I");
img_GIF_ls_globalColorTableID = (*env)->GetFieldID(env, logicalScreenClass, "globalColorTable", colorTableClassSignature);
img_GIF_ls_completedID = (*env)->GetFieldID(env, logicalScreenClass, "completed", "Z");
// Gif color table
img_GIF_ct_colorsID = (*env)->GetFieldID(env, colorTableClass, "colors", "[B");
img_GIF_ct_sizeID = (*env)->GetFieldID(env, colorTableClass, "size", "I");
img_GIF_ct_completedID = (*env)->GetFieldID(env, colorTableClass, "completed", "Z");
// Gif graphic block
img_GIF_gb_imageLeftID = (*env)->GetFieldID(env, graphicBlockClass, "imageLeft", "I");
img_GIF_gb_imageTopID = (*env)->GetFieldID(env, graphicBlockClass, "imageTop", "I");
img_GIF_gb_imageWidthID = (*env)->GetFieldID(env, graphicBlockClass, "imageWidth", "I");
img_GIF_gb_imageHeightID = (*env)->GetFieldID(env, graphicBlockClass, "imageHeight", "I");
img_GIF_gb_disposalMethodID = (*env)->GetFieldID(env, graphicBlockClass, "disposalMethod", "I");
img_GIF_gb_delayTimeID = (*env)->GetFieldID(env, graphicBlockClass, "delayTime", "I");
img_GIF_gb_transparentColorID = (*env)->GetFieldID(env, graphicBlockClass, "transparentColor", "I");
img_GIF_gb_interlaceID = (*env)->GetFieldID(env, graphicBlockClass, "interlace", "Z");
img_GIF_gb_imageDataID = (*env)->GetFieldID(env, graphicBlockClass, "imageData", "[B");
img_GIF_gb_rgbImageDataID = (*env)->GetFieldID(env, graphicBlockClass, "rgbImageData", "[I");
img_GIF_gb_currYID = (*env)->GetFieldID(env, graphicBlockClass, "currY", "I");
img_GIF_gb_completedID = (*env)->GetFieldID(env, graphicBlockClass, "completed", "Z");
}
/*
* Class: org_apache_harmony_awt_gl_image_GifDecoder
* Method: releaseNativeDecoder
* Signature: (J)V
*/
JNIEXPORT void JNICALL Java_org_apache_harmony_awt_gl_image_GifDecoder_releaseNativeDecoder
(JNIEnv *env, jclass cls, jlong hDecoder) {
// Cleanup if image was truncated
if(hDecoder)
free((GifDecoder *) ((IDATA)hDecoder));
}
/*
* Class: org_apache_harmony_awt_gl_image_GifDecoder
* Method: decode
* Signature: ([BIJLorg/apache/harmony/awt/gl/image/GifDecoder$GifDataStream;Lorg/apache/harmony/awt/gl/image/GifDecoder$GifGraphicBlock;)I
*/
JNIEXPORT jint JNICALL Java_org_apache_harmony_awt_gl_image_GifDecoder_decode
(JNIEnv *env,
jobject obj,
jbyteArray jInput,
jint bytesInBuffer,
jlong hDecoder,
jobject dataStream,
jobject currBlock) {
GIF_RETVAL retval = STATUS_OK;
GifDecoder *decoder = getDecoder(env, obj, dataStream, (GifDecoder*) ((IDATA)hDecoder));
int scanlinesDecoded;
decoder->input = decoder->inputPtr =
(*env)->GetPrimitiveArrayCritical(env, jInput, 0);
decoder->bytesInBuffer += bytesInBuffer;
bytesInBuffer = decoder->bytesInBuffer;
while(retval == STATUS_OK && decoder->bytesInBuffer > 0) {
switch(decoder->state) {
case STATE_INIT: {
retval = readHeader(env, decoder);
break;
}
case STATE_AT_GLOBAL_COLOR_TABLE: {
retval = loadColorTable(env, decoder->jGlobalColorTable, decoder);
break;
}
case STATE_AT_LOCAL_COLOR_TABLE: {
retval = loadColorTable(env, NULL, decoder);
break;
}
case STATE_BLOCK_BEGINNING: {
unsigned char blockLabel = *(decoder->inputPtr);
switch(blockLabel) {
case EXTENSION_INTRODUCER:
retval = readExtension(env, decoder, currBlock);
break;
case IMAGE_SEPARATOR:
retval = readImageDescriptor(env, currBlock, decoder);
break;
case GIF_TRAILER:
retval = STATUS_EOF;
break;
}
break;
}
case STATE_STARTING_DECOMPRESSION: {
retval = initDecompression(env, decoder, currBlock);
break;
}
case STATE_DECOMPRESSING: {
if(!decoder->interlace)
retval = decompress(env, currBlock, decoder);
else
retval = decompressInterlaced(env, currBlock, decoder);
break;
}
case STATE_READING_COMMENT:{
retval = readComment(env, decoder);
break;
}
case STATE_SKIPPING_BLOCKS: {
retval = skipData(decoder);
break;
}
default:
// Should never execute this!
break;
}
}
// Copy unconsumed data to the start of the input buffer
if(decoder->bytesInBuffer > 0) {
memmove(decoder->input, decoder->inputPtr, decoder->bytesInBuffer);
}
(*env)->ReleasePrimitiveArrayCritical(env, jInput, decoder->input, 0);
(*env)->SetIntField(
env,
obj,
img_GIF_bytesConsumedID,
bytesInBuffer - decoder->bytesInBuffer
);
if(decoder->stateVars.imageDataStarted) {
if(!decoder->interlace) {
scanlinesDecoded = decoder->pixelsDecoded / decoder->currentWidth -
decoder->oldPixelsDecoded / decoder->currentWidth;
decoder->oldPixelsDecoded = decoder->pixelsDecoded;
} else {
if(retval == STATUS_LINE_COMPLETED && decoder->pass < MAX_PASS) {
scanlinesDecoded = 1;
if(decoder->currScanline >= 0)
(*env)->SetIntField(env, currBlock, img_GIF_gb_currYID, decoder->currScanline);
decoder->scanlineOffset = 0;
} else {
scanlinesDecoded = 0;
}
}
} else {
scanlinesDecoded = 0;
}
if(retval == STATUS_FRAME_COMPLETED) {
decoder->oldPixelsDecoded = decoder->pixelsDecoded = 0;
}
// Free the decoder if decoding is finished
if(retval == STATUS_EOF) {
free(decoder);
decoder = NULL;
}
(*env)->SetLongField(env, obj, img_GIF_hNativeDecoderID, (jlong) ((IDATA)decoder));
return scanlinesDecoded;
}
// Utility function to perform fast buffer format conversion
static void toIntARGB(unsigned char *in, unsigned int *out, int lengthInPixels) {
// Go in backward direction
unsigned char *inCurrPtr = in + lengthInPixels*3 - 1; // end of the input buffer
// end of the output buffer
unsigned int *outCurrPtr = out + lengthInPixels - 1;
unsigned int val;
while (inCurrPtr > in) {
val = *(inCurrPtr--); // B
val |= (*(inCurrPtr--))<<8; // G
val |= (*(inCurrPtr--))<<16; // R
val |= 0xFF000000; // Alpha = 1
*(outCurrPtr--) = val;
}
}
/*
* Class: org_apache_harmony_awt_gl_image_GifDecoder
* Method: toRGB
* Signature: ([B[BI)[I
*/
JNIEXPORT jintArray JNICALL Java_org_apache_harmony_awt_gl_image_GifDecoder_toRGB
(JNIEnv *env, jclass cls, jbyteArray jSrc, jbyteArray jColormap, jint transparentColor) {
unsigned int numPixels = (*env)->GetArrayLength(env, jSrc);
unsigned int cmapSize = (*env)->GetArrayLength(env, jColormap);
// Create INT_ARGB colormap
unsigned int *intARGBColormap = malloc(cmapSize*sizeof(int));
jintArray jDst = (*env)->NewIntArray(env, numPixels);
unsigned char *src = (*env)->GetPrimitiveArrayCritical(env, jSrc, 0);
unsigned char *colormap = (*env)->GetPrimitiveArrayCritical(env, jColormap, 0);
unsigned int *dst = (*env)->GetPrimitiveArrayCritical(env, jDst, 0);
unsigned int *dstPtr = dst;
unsigned char *srcPtr = src;
// Fill it
toIntARGB(colormap, intARGBColormap, cmapSize);
if(transparentColor != IMPOSSIBLE_VALUE)
intARGBColormap[transparentColor] &= 0x00FFFFFF;
// Convert pixels
while(numPixels--) {
*(dstPtr++) = intARGBColormap[*(srcPtr++)];
}
(*env)->ReleasePrimitiveArrayCritical(env, jDst, dst, 0);
(*env)->ReleasePrimitiveArrayCritical(env, jSrc, src, JNI_ABORT); /* We does not change this array */
(*env)->ReleasePrimitiveArrayCritical(env, jColormap, colormap, JNI_ABORT); /* We does not change this array */
free(intARGBColormap);
return jDst;
}
GifDecoder* getDecoder(JNIEnv *env, jobject obj, jobject dataStream, GifDecoder* decoder) {
if(!decoder) {
decoder = malloc(sizeof(GifDecoder));
decoder->stateVars.commentString = NULL;
decoder->stateVars.forceRGB = FALSE;
decoder->stateVars.imageDataStarted = FALSE;
decoder->state = STATE_INIT;
decoder->bytesInBuffer = 0;
decoder->transparentColorIndex = IMPOSSIBLE_VALUE;
decoder->useLocalColorTable = FALSE;
decoder->stateVars.backgroundConverted = FALSE;
decoder->comment = NULL;
decoder->commentLength = 0;
(*env)->SetLongField(env, obj, img_GIF_hNativeDecoderID, 0);
}
decoder->jDataStream = dataStream;
decoder->jDecoder = obj;
return decoder;
}
GIF_RETVAL readHeader(JNIEnv *env, GifDecoder *decoder) {
if(decoder->bytesInBuffer >= SIZE_HEADER) {
unsigned char colorTableSize;
unsigned char flags;
decoder->jLogicalScreen = (*env)->GetObjectField(env, decoder->jDataStream, img_GIF_ds_logicalScreenID);
decoder->jGlobalColorTable = (*env)->GetObjectField(env, decoder->jLogicalScreen, img_GIF_ls_globalColorTableID);
decoder->inputPtr += 6; // Skip header - already checked in java
// Get width/height
decoder->logicalScreenWidth = *((unsigned short *) decoder->inputPtr);
decoder->inputPtr += 2;
(*env)->SetIntField(env, decoder->jLogicalScreen, img_GIF_ls_logicalScreenWidthID, decoder->logicalScreenWidth);
decoder->logicalScreenHeight = *((unsigned short *) decoder->inputPtr);
decoder->inputPtr += 2;
(*env)->SetIntField(env, decoder->jLogicalScreen, img_GIF_ls_logicalScreenHeightID, decoder->logicalScreenHeight);
// Get flags
flags = *(decoder->inputPtr++);
decoder->hasGlobalColorTable = flags & LS_GLOBAL_COLOR_TABLE_MASK;
if(decoder->hasGlobalColorTable) {
colorTableSize = flags & LS_GLOBAL_COLOR_TABLE_SIZE_MASK;
decoder->globalColorTable.size = 1 << (colorTableSize+1); // 2^(CR + 1)
(*env)->SetIntField(env, decoder->jGlobalColorTable, img_GIF_ct_sizeID, decoder->globalColorTable.size);
// Get the background color
decoder->backgroundColor = *(decoder->inputPtr++);
(*env)->SetIntField(env, decoder->jLogicalScreen, img_GIF_ls_backgroundColorID, decoder->backgroundColor);
} else { // Skip background color
decoder->backgroundColor = 0;
decoder->inputPtr++;
}
decoder->inputPtr++; // Skip pixel aspect ratio
decoder->bytesInBuffer -= SIZE_HEADER;
// Logical screen is loaded
// Set completed flag in java
(*env)->SetBooleanField(env, decoder->jLogicalScreen, img_GIF_ls_completedID, TRUE);
if(decoder->hasGlobalColorTable) {
decoder->state = STATE_AT_GLOBAL_COLOR_TABLE;
} else { // No global color table, force RGB, set completed flag in java
(*env)->SetBooleanField(env, decoder->jGlobalColorTable, img_GIF_ct_completedID, TRUE);
decoder->stateVars.forceRGB = TRUE;
(*env)->SetBooleanField(env, decoder->jDecoder, img_GIF_forceRGBID, TRUE);
decoder->state = STATE_BLOCK_BEGINNING;
}
return STATUS_OK;
}
return STATUS_BUFFER_EMPTY;
}
GIF_RETVAL loadColorTable(JNIEnv *env, jobject jColorTable, GifDecoder *decoder) {
int tableSizeInBytes, tableSize;
jbyte *colors;
jbyteArray jColors;
ColorTable *cTable;
boolean global;
if(jColorTable != NULL) { // Loading a global color table
cTable = &decoder->globalColorTable;
global = TRUE;
} else { // Loading a local color table
cTable = &decoder->localColorTable;
global = FALSE;
}
tableSize = cTable->size;
tableSizeInBytes = tableSize * 3;
if(decoder->bytesInBuffer < tableSizeInBytes)
return STATUS_BUFFER_EMPTY; // Not enough bytes in buffer
if(jColorTable != NULL) {
jColors = (*env)->GetObjectField(env, jColorTable, img_GIF_ct_colorsID);
colors = (*env)->GetPrimitiveArrayCritical(env, jColors, 0);
memcpy(colors, decoder->inputPtr, tableSizeInBytes);
(*env)->ReleasePrimitiveArrayCritical(env, jColors, colors, 0);
(*env)->SetBooleanField(env, jColorTable, img_GIF_ct_completedID, TRUE);
}
// Convert to int RGB format always. Could be optimized easily if it worth it.
toIntARGB(decoder->inputPtr, cTable->rgbTable, tableSize);
decoder->bytesInBuffer -= tableSizeInBytes;
decoder->inputPtr += tableSizeInBytes;
if(global)
decoder->state = STATE_BLOCK_BEGINNING;
else {
decoder->stateVars.imageDataStarted = TRUE; // OK to set it here - already passed GCE
decoder->state = STATE_STARTING_DECOMPRESSION;
}
return STATUS_OK;
}
/*
This function skips sequence of sub-blocks with input data organized according
to GIF spec. Required for skipping unrecognized extensions.
*/
GIF_RETVAL skipData(GifDecoder *decoder) {
int dataSize;
while((dataSize = *(decoder->inputPtr))) {
if(dataSize + 1 > decoder->bytesInBuffer) // Need to load more data, suspending
return STATUS_BUFFER_EMPTY;
// Advance to next data block
decoder->bytesInBuffer -= dataSize + 1; // One byte is for size of block itself
decoder->inputPtr += dataSize + 1;
}
decoder->inputPtr++; // Skip zero size block
decoder->bytesInBuffer--;
decoder->state = STATE_BLOCK_BEGINNING;
return STATUS_OK;
}
/*
This function saves comments from the sequence of sub-blocks organized according
to GIF spec into the character string.
*/
GIF_RETVAL readComment(JNIEnv *env, GifDecoder *decoder) {
int dataSize;
while((dataSize = *(decoder->inputPtr))) {
if(dataSize + 1 > decoder->bytesInBuffer) // Need to load more data, suspending
return STATUS_BUFFER_EMPTY;
decoder->inputPtr++; // Skip size of the block
decoder->commentLength += dataSize;
decoder->comment = realloc(decoder->comment, decoder->commentLength);
memcpy(
decoder->comment + decoder->commentLength - dataSize,
decoder->inputPtr,
dataSize
);
// Advance to next data block
decoder->bytesInBuffer -= dataSize + 1; // One byte is for size of block itself
decoder->inputPtr += dataSize;
}
decoder->inputPtr++; // Skip zero size block
decoder->bytesInBuffer--;
sendCommentToJava(env, decoder);
decoder->state = STATE_BLOCK_BEGINNING;
return STATUS_OK;
}
void readGraphicControl(JNIEnv *env, GifDecoder *decoder, jobject currBlock) {
unsigned char transparentColorIndex;
int disposalMethod;
boolean transparentColorFlag = FALSE;
unsigned char flags = *(decoder->inputPtr++); // Read packed fields
unsigned int delayTime = (*((unsigned short *)decoder->inputPtr))*10;
decoder->inputPtr += 2;
transparentColorIndex = *(decoder->inputPtr);
decoder->inputPtr += 2; // Skip block terminator also;
disposalMethod = (flags & GC_DISPOSAL_METHOD_MASK) >> GC_DISPOSAL_METHOD_BIT_OFFSET;
transparentColorFlag = flags & GC_TRANSPARENT_COLOR_MASK;
(*env)->SetIntField(env, currBlock, img_GIF_gb_delayTimeID, delayTime);
(*env)->SetIntField(env, currBlock, img_GIF_gb_disposalMethodID, disposalMethod);
// Postpone setting of transparent color in java until state STATE_STARTING_DECOMPRESSION
// since we don't know if local color table presents
if(transparentColorFlag) {
decoder->transparentColorIndex = transparentColorIndex;
if(decoder->stateVars.imageDataStarted) {
(*env)->SetBooleanField(env, decoder->jDecoder, img_GIF_forceRGBID, TRUE);
decoder->stateVars.forceRGB = TRUE;
}
} else {
decoder->transparentColorIndex = IMPOSSIBLE_VALUE;
}
decoder->bytesInBuffer -= SIZE_GRAPHIC_CONTROL;
}
GIF_RETVAL readExtension(JNIEnv *env, GifDecoder *decoder, jobject currBlock) {
unsigned char extensionSize = *(decoder->inputPtr+2);
unsigned char extensionType;
if(decoder->bytesInBuffer < extensionSize + 3) { // Stop, we need more data
return STATUS_BUFFER_EMPTY;
}
decoder->inputPtr++; // Skip extension introducer
extensionType = *(decoder->inputPtr);
decoder->inputPtr += 2; // Skip size, already got it
decoder->bytesInBuffer -= 3; // Decrease buffer size as we advanced
switch(extensionType) {
case GRAPHIC_CONTROL_EXTENSION:
readGraphicControl(env, decoder, currBlock);
break;
case COMMENT_EXTENSION: {
decoder->inputPtr -= 1; // No extension size, usual sub-blocks, go back
decoder->bytesInBuffer += 1;
decoder->state = STATE_READING_COMMENT;
return readComment(env, decoder);
}
case APPLICATION_EXTENSION:
if(extensionSize == SIZE_NETSCAPE_EXT &&
!strncmp(decoder->inputPtr, "NETSCAPE2.0", SIZE_NETSCAPE_EXT)) {
if(*(decoder->inputPtr + extensionSize) == 3) { // Magic size of the sub-block in netscape ext
unsigned short loopCount;
decoder->inputPtr += extensionSize;
decoder->bytesInBuffer -= extensionSize;
loopCount = *((unsigned short *) (decoder->inputPtr+2));
(*env)->SetIntField(env, decoder->jDataStream, img_GIF_ds_loopCountID, loopCount);
return skipData(decoder);
} // If the extension is invalid proceed to default
}
case PLAIN_TEXT_EXTENSION:
default:
decoder->inputPtr += extensionSize;
decoder->bytesInBuffer -= extensionSize;
if(!(*(decoder->inputPtr))) { // No additional data
decoder->inputPtr++;
decoder->bytesInBuffer--;
decoder->state = STATE_BLOCK_BEGINNING;
} else {
decoder->state = STATE_SKIPPING_BLOCKS;
return skipData(decoder);
}
}
return STATUS_OK;
}
GIF_RETVAL readImageDescriptor(JNIEnv *env, jobject currBlock, GifDecoder *decoder) {
unsigned short *tmp;
unsigned char flags;
boolean interlace;
if(decoder->bytesInBuffer < SIZE_IMAGE_DESCRIPTOR)
return STATUS_BUFFER_EMPTY;
decoder->inputPtr++; // Skip image separator
tmp = (unsigned short *) decoder->inputPtr;
(*env)->SetIntField(env, currBlock, img_GIF_gb_imageLeftID, *(tmp++));
(*env)->SetIntField(env, currBlock, img_GIF_gb_imageTopID, *(tmp++));
decoder->currentWidth = *(tmp++);
(*env)->SetIntField(env, currBlock, img_GIF_gb_imageWidthID, decoder->currentWidth);
decoder->currentHeight = *(tmp++);
(*env)->SetIntField(env, currBlock, img_GIF_gb_imageHeightID, decoder->currentHeight);
decoder->inputPtr += 8;
flags = *(decoder->inputPtr++);
decoder->bytesInBuffer -= SIZE_IMAGE_DESCRIPTOR; // Image descriptor passed
interlace = (flags & ID_INTERLACE_MASK) ? 1 : 0;
(*env)->SetBooleanField(env, currBlock, img_GIF_gb_interlaceID, interlace);
decoder->interlace = interlace;
if(flags & ID_LOCAL_COLOR_TABLE_MASK) { // Local color table present
decoder->stateVars.forceRGB = TRUE;
(*env)->SetBooleanField(env, decoder->jDecoder, img_GIF_forceRGBID, TRUE);
decoder->localColorTable.size = 1 << ((flags & ID_LOCAL_COLOR_TABLE_SIZE_MASK) + 1);
decoder->useLocalColorTable = TRUE;
decoder->state = STATE_AT_LOCAL_COLOR_TABLE;
} else {
decoder->useLocalColorTable = FALSE;
decoder->stateVars.imageDataStarted = TRUE; // OK to set it here - already passed GCE
decoder->state = STATE_STARTING_DECOMPRESSION;
}
// Greate image data array in java
if(decoder->stateVars.forceRGB) {
decoder->jIntOut = (*env)->NewIntArray(env, decoder->currentWidth * decoder->currentHeight);
(*env)->SetObjectField(env, currBlock, img_GIF_gb_rgbImageDataID, decoder->jIntOut);
} else {
decoder->jByteOut = (*env)->NewByteArray(env, decoder->currentWidth * decoder->currentHeight);
(*env)->SetObjectField(env, currBlock, img_GIF_gb_imageDataID, decoder->jByteOut);
}
return STATUS_OK;
}
void resetDecompressorState(GifDecoder *decoder) {
decoder->decompress.currCodeSize = decoder->decompress.initCodeSize;
decoder->decompress.maxCodeMinusOne = (1 << decoder->decompress.initCodeSize) - 2;
// This is a running code, it will be incremented by 2 before actual processing,
// so we make it here equal to "FIRST AVAILABLE CODE" - 2 which is clear code
decoder->decompress.currKnownCode = decoder->decompress.clearCode;
decoder->decompress.lastCode = IMPOSSIBLE_CODE;
// Fill prefix with IMPOSSIBLE_CODE
memset(decoder->decompress.prefix, 0xFF, (MAX_CODE+1)*sizeof(unsigned int));
}
void removeTransparencyFromTables(GifDecoder *decoder) {
int i;
for(i=0; i<255; i++) {
decoder->localColorTable.rgbTable[i] |= 0xFF000000;
decoder->globalColorTable.rgbTable[i] |= 0xFF000000;
}
}
GIF_RETVAL initDecompression(JNIEnv *env, GifDecoder *decoder, jobject currBlock) {
DecompressInfo *di = &decoder->decompress;
unsigned int transparentColor;
// Now we know all color tables info, so it's possible to set transparent color in java
if(decoder->stateVars.forceRGB) {
// If we switched in ARGB mode we need to have ARGB background
convertBackgroundColor(env, decoder);
// Fix the value in the color table - make it transparent
removeTransparencyFromTables(decoder);
if(decoder->transparentColorIndex != IMPOSSIBLE_VALUE) {
decoder->localColorTable.rgbTable[decoder->transparentColorIndex] &= 0x00FFFFFF;
decoder->globalColorTable.rgbTable[decoder->transparentColorIndex] &= 0x00FFFFFF;
transparentColor = decoder->useLocalColorTable ?
decoder->localColorTable.rgbTable[decoder->transparentColorIndex] :
decoder->globalColorTable.rgbTable[decoder->transparentColorIndex];
} else {
transparentColor = IMPOSSIBLE_VALUE;
}
} else {
transparentColor = decoder->transparentColorIndex;
}
(*env)->SetIntField(env, currBlock, img_GIF_gb_transparentColorID, transparentColor);
decoder->outputBuffer = NULL;
di->shiftState = 0;
decoder->pixelsDecoded = 0;
decoder->oldPixelsDecoded = 0;
di->initCodeSize = *(decoder->inputPtr++) + 1;
decoder->bytesInBuffer--;
di->clearCode = 1 << (di->initCodeSize - 1);
di->eoiCode = di->clearCode + 1;
//di->currCode = di->clearCode + 2;
resetDecompressorState(decoder);
di->stackPointer = 0;
di->accumbits = 0;
decoder->doProcessing = TRUE;
decoder->state = STATE_DECOMPRESSING;
// Init variables needed to support interlacing
if(decoder->interlace)
initInterlaceVars(decoder);
return STATUS_OK;
}
void intFromIndexModel(
unsigned char *in,
unsigned int *out,
int lengthInPixels,
GifDecoder *decoder
) {
unsigned int *rgbTable;
int i;
if(lengthInPixels == 0) // Nothing to do
return;
if(decoder->useLocalColorTable) {
rgbTable = decoder->localColorTable.rgbTable;
} else {
rgbTable = decoder->globalColorTable.rgbTable;
}
for(i = 0; i < lengthInPixels; i++) {
out[i] = rgbTable[in[i]];
}
}
unsigned int getPrefix(unsigned int code, DecompressInfo *di) {
int i = MAX_CODE; // Limits the number of iterations in case if something wrong
while (code > di->clearCode && i--)
code = di->prefix[code];
return code;
}
void pushStack(DecompressInfo *di) {
int i = MAX_CODE; // Limits the number of iterations in case if something wrong
while(di->currPrefix > di->clearCode && di->currPrefix < MAX_CODE && i--) {
di->stack[di->stackPointer++] = di->suffix[di->currPrefix];
di->currPrefix = di->prefix[di->currPrefix];
}
di->stack[di->stackPointer++] = di->currPrefix;
}
GIF_RETVAL decompress(JNIEnv *env, jobject currBlock, GifDecoder *decoder) {
unsigned char *output, *outPtr;
unsigned int i = 0; // Counter for decoded pixels
GIF_RETVAL retval = STATUS_OK;
if(decoder->stateVars.forceRGB) {
output = malloc(decoder->currentHeight*decoder->currentWidth);
} else {
decoder->jByteOut = (*env)->GetObjectField(env, currBlock, img_GIF_gb_imageDataID);
output = (*env)->GetPrimitiveArrayCritical(env, decoder->jByteOut, 0);
}
outPtr = output + decoder->pixelsDecoded;
for(;;) {
int savedBlockSize = *(decoder->inputPtr);
int blockSize = savedBlockSize;
DecompressInfo *di = &decoder->decompress;
if(decoder->bytesInBuffer < savedBlockSize + 1) { // Cannot read next block
retval = STATUS_BUFFER_EMPTY;
break;
}
decoder->inputPtr++;
decoder->bytesInBuffer--;
if(savedBlockSize == 0) { // Frame completed
decoder->state = STATE_BLOCK_BEGINNING;
(*env)->SetBooleanField(env, currBlock, img_GIF_gb_completedID, TRUE);
// Pass control to java code - need to call imageComplete and create new GifGraphicBlock
retval = STATUS_FRAME_COMPLETED;
break;
}
if(decoder->doProcessing) {
unsigned char *savedPtr = decoder->inputPtr;
for(;;) {
if(di->shiftState < di->currCodeSize) {
// Need to read at least one more byte
di->accumbits |= (*(decoder->inputPtr++) << di->shiftState);
di->shiftState += 8;
blockSize--;
} else {
di->currCode = di->accumbits & codeMasks[di->currCodeSize];
di->accumbits >>= di->currCodeSize;
di->shiftState -= di->currCodeSize;
// Check if we need to increase code size
if(++di->currKnownCode > di->maxCodeMinusOne && di->currCodeSize < MAX_BITS) {
unsigned int maxCodePlusOne = di->maxCodeMinusOne + 2;
di->currCodeSize++;
di->maxCodeMinusOne = (maxCodePlusOne << 1) - 2;
}
// Now process the obtained code
if (di->currCode == di->eoiCode) { // EOI encountered, stop processing
decoder->inputPtr += blockSize;
// Ensure that no more data will be decoded
di->shiftState = 0;
blockSize = 0;
decoder->doProcessing = FALSE;
} else if(di->currCode == di->clearCode) { // Clear code - reset code size
resetDecompressorState(decoder);
} else { // Regular code
if(di->currCode < di->clearCode) { // It is just a scalar pixel
outPtr[i++] = di->currCode;
} else { // Search for prefix for this code
// Code not added yet, corresponding prefix is the last code,
// suffix is the first character of the last code
if(di->prefix[di->currCode] == IMPOSSIBLE_CODE) {
di->currPrefix = di->lastCode;
di->suffix[di->currKnownCode] =
di->stack[di->stackPointer++] = getPrefix(di->lastCode, di);
} else { // Set prefix to current code
di->currPrefix = di->currCode;
}
pushStack(di);
// Now pop the stack to get the output
while (di->stackPointer)
outPtr[i++] = di->stack[--(di->stackPointer)];
}
if(di->lastCode != IMPOSSIBLE_CODE) {
// Set prefix to last code and update suffix
di->prefix[di->currKnownCode] = di->lastCode;
// Suffix depends on wether it is a special case or not
if(di->currCode == di->currKnownCode) {
di->suffix[di->currKnownCode] = getPrefix(di->lastCode, di);
} else {
di->suffix[di->currKnownCode] = getPrefix(di->currCode, di);
}
}
// Save the last code
di->lastCode = di->currCode;
}
}
// Break if no more bytes to load in this block
if(!blockSize && di->shiftState < di->currCodeSize)
break;
} // while
} else {
decoder->inputPtr += savedBlockSize;
}
decoder->bytesInBuffer -= savedBlockSize;
}
decoder->pixelsDecoded = decoder->oldPixelsDecoded + i;
if(decoder->stateVars.forceRGB) { // Need to convert to RGB from index color model
unsigned int *intOut, *intSrcPtr;
decoder->jIntOut = (*env)->GetObjectField(env, currBlock, img_GIF_gb_rgbImageDataID);
intOut = (*env)->GetPrimitiveArrayCritical(env, decoder->jIntOut, 0);
intSrcPtr = intOut + decoder->oldPixelsDecoded;
intFromIndexModel(
outPtr,
intSrcPtr,
decoder->pixelsDecoded - decoder->oldPixelsDecoded,
decoder);
(*env)->ReleasePrimitiveArrayCritical(env, decoder->jIntOut, intOut, 0);
free(output);
} else {
(*env)->ReleasePrimitiveArrayCritical(env, decoder->jByteOut, output, 0);
}
return retval;
}
void convertBackgroundColor(JNIEnv *env, GifDecoder *decoder) {
if(!decoder->stateVars.backgroundConverted) {
unsigned int backgroundColor;
decoder->jLogicalScreen = (*env)->GetObjectField(env, decoder->jDataStream, img_GIF_ds_logicalScreenID);
backgroundColor =
(*env)->GetIntField(env, decoder->jLogicalScreen, img_GIF_ls_backgroundColorID);
if(backgroundColor != IMPOSSIBLE_VALUE)
backgroundColor = decoder->useLocalColorTable ?
decoder->localColorTable.rgbTable[backgroundColor] :
decoder->globalColorTable.rgbTable[backgroundColor];
(*env)->SetIntField(env, decoder->jLogicalScreen, img_GIF_ls_backgroundColorID, backgroundColor);
}
decoder->stateVars.backgroundConverted = TRUE;
}
void updateScanline(GifDecoder *decoder) {
if(decoder->scanlineWritten) { // Else complete scanline first
decoder->currScanline += decoder->increment;
if(decoder->currScanline >= decoder->currentHeight) {
decoder->pass++;
decoder->currScanline = startingScanline[decoder->pass];
decoder->increment = scanlineIncrement[decoder->pass];
}
decoder->scanlineWritten = FALSE;
}
}
GIF_RETVAL decompressInterlaced(JNIEnv *env, jobject currBlock, GifDecoder *decoder) {
unsigned char *output, *outPtr;
GIF_RETVAL retval = STATUS_OK;
unsigned int savedScanlineOffset = decoder->scanlineOffset;
if(decoder->stateVars.forceRGB) {
outPtr = output = malloc(decoder->currentWidth);
} else {
decoder->jByteOut = (*env)->GetObjectField(env, currBlock, img_GIF_gb_imageDataID);
outPtr = output = (*env)->GetPrimitiveArrayCritical(env, decoder->jByteOut, 0);
}
for(;;) {
int savedBlockSize, blockSize;
DecompressInfo *di = &decoder->decompress;
if(decoder->leftInBlock == 0) {
savedBlockSize = *(decoder->inputPtr);
blockSize = savedBlockSize;
} else {
savedBlockSize = decoder->leftInBlock;
blockSize = savedBlockSize;
}
if(decoder->bytesInBuffer < savedBlockSize + 1) { // Cannot read next block
retval = STATUS_BUFFER_EMPTY;
break;
}
if(decoder->leftInBlock == 0) { // Skip size itself
decoder->inputPtr++;
decoder->bytesInBuffer--;
}
if(savedBlockSize == 0) { // Frame completed
decoder->state = STATE_BLOCK_BEGINNING;
(*env)->SetBooleanField(env, currBlock, img_GIF_gb_completedID, TRUE);
// Pass control to java code - need to call imageComplete and create new GifGraphicBlock
retval = STATUS_FRAME_COMPLETED;
break;
}
if(decoder->doProcessing) {
updateScanline(decoder); // Set output scanline
if(decoder->pass > 3) {
decoder->inputPtr += blockSize;
blockSize = 0;
decoder->leftInBlock = 0;
decoder->doProcessing = FALSE;
decoder->bytesInBuffer -= savedBlockSize;
continue;
}
if(decoder->stateVars.forceRGB) {
outPtr = output;
} else {
outPtr = output + decoder->currentWidth * decoder->currScanline;
}
// pop what's left in the stack
while (di->stackPointer && decoder->scanlineOffset < decoder->currentWidth)
outPtr[decoder->scanlineOffset++] = di->stack[--(di->stackPointer)];
while(decoder->scanlineOffset < decoder->currentWidth) { // Decode one scanline
if(di->shiftState < di->currCodeSize) {
// Need to read at least one more byte
di->accumbits |= (*(decoder->inputPtr++) << di->shiftState);
di->shiftState += 8;
blockSize--;
} else {
di->currCode = di->accumbits & codeMasks[di->currCodeSize];
di->accumbits >>= di->currCodeSize;
di->shiftState -= di->currCodeSize;
// Check if we need to increase code size
if(++di->currKnownCode > di->maxCodeMinusOne && di->currCodeSize < MAX_BITS) {
unsigned int maxCodePlusOne = di->maxCodeMinusOne + 2;
di->currCodeSize++;
di->maxCodeMinusOne = (maxCodePlusOne << 1) - 2;
}
// Now process the obtained code
if (di->currCode == di->eoiCode) { // EOI encountered, stop processing
decoder->inputPtr += blockSize;
decoder->leftInBlock = 0;
// Ensure that no more data will be decoded
di->shiftState = 0;
blockSize = 0;
decoder->doProcessing = FALSE;
} else if(di->currCode == di->clearCode) { // Clear code - reset code size
resetDecompressorState(decoder);
} else { // Regular code
if(di->currCode < di->clearCode) { // It is just a scalar pixel
outPtr[decoder->scanlineOffset++] = di->currCode;
} else { // Search for prefix for this code
// Code not added yet, corresponding prefix is the last code,
// suffix is the first character of the last code
if(di->prefix[di->currCode] == IMPOSSIBLE_CODE) {
di->currPrefix = di->lastCode;
di->suffix[di->currKnownCode] =
di->stack[di->stackPointer++] = getPrefix(di->lastCode, di);
} else { // Set prefix to current code
di->currPrefix = di->currCode;
}
pushStack(di);
// Now pop the stack to get the output
while (di->stackPointer && decoder->scanlineOffset < decoder->currentWidth)
outPtr[decoder->scanlineOffset++] = di->stack[--(di->stackPointer)];
}
if(di->lastCode != IMPOSSIBLE_CODE) {
// Set prefix to last code and update suffix
di->prefix[di->currKnownCode] = di->lastCode;
// Suffix depends on wether it is a special case or not
if(di->currCode == di->currKnownCode) {
di->suffix[di->currKnownCode] = getPrefix(di->lastCode, di);
} else {
di->suffix[di->currKnownCode] = getPrefix(di->currCode, di);
}
}
// Save the last code
di->lastCode = di->currCode;
}
}
// Break if no more bytes to load in this block
if(!blockSize && di->shiftState < di->currCodeSize)
break;
} // while
} else {
decoder->inputPtr += savedBlockSize;
}
decoder->leftInBlock = blockSize;
decoder->bytesInBuffer -= savedBlockSize - blockSize;
if(decoder->scanlineOffset == decoder->currentWidth) {
retval = STATUS_LINE_COMPLETED;
decoder->scanlineWritten = TRUE;
break;
}
}
if(decoder->stateVars.forceRGB) { // Need to convert to RGB from index color model
if(decoder->currScanline >= 0 && decoder->scanlineOffset != 0 && decoder->pass < MAX_PASS) {
unsigned int *intOut, *intSrcPtr;
decoder->jIntOut = (*env)->GetObjectField(env, currBlock, img_GIF_gb_rgbImageDataID);
intOut = (*env)->GetPrimitiveArrayCritical(env, decoder->jIntOut, 0);
intSrcPtr = intOut + decoder->currScanline * decoder->currentWidth + savedScanlineOffset;
intFromIndexModel(
outPtr + savedScanlineOffset,
intSrcPtr,
decoder->scanlineOffset - savedScanlineOffset,
decoder);
(*env)->ReleasePrimitiveArrayCritical(env, decoder->jIntOut, intOut, 0);
}
free(output);
} else {
(*env)->ReleasePrimitiveArrayCritical(env, decoder->jByteOut, output, 0);
}
decoder->scanlineOffset %= decoder->currentWidth;
return retval;
}
void initInterlaceVars(GifDecoder *decoder) {
decoder->increment = 8;
decoder->pass = 0;
decoder->scanlineOffset= 0;
decoder->currScanline = -8;
decoder->leftInBlock = 0;
decoder->scanlineWritten = TRUE;
}
void sendCommentToJava(JNIEnv *env, GifDecoder *decoder) {
jstring jStr;
decoder->comment = realloc(decoder->comment, decoder->commentLength+1);
*(decoder->comment + decoder->commentLength) = 0; // Set the last character to zero
jStr = (*env)->NewStringUTF(env, decoder->comment);
free(decoder->comment);
decoder->comment = NULL;
decoder->commentLength = 0;
(*env)->CallVoidMethod(env, decoder->jDecoder, img_GIF_setCommentID, jStr);
}