Files
love-android/jni/devil-1.7.8/src-IL/src/il_gif.c
T

756 lines
17 KiB
C

//-----------------------------------------------------------------------------
//
// ImageLib Sources
// Copyright (C) 2000-2008 by Denton Woods
// Last modified: 03/07/2009
//
// Filename: src-IL/src/il_gif.c
//
// Description: Reads from a Graphics Interchange Format (.gif) file.
//
// The LZW decompression code is based on code released to the public domain
// by Javier Arevalo and can be found at
// http://www.programmersheaven.com/zone10/cat452
//
//-----------------------------------------------------------------------------
#include "il_internal.h"
#ifndef IL_NO_GIF
#include "il_gif.h"
ILenum GifType;
//! Checks if the file specified in FileName is a valid Gif file.
ILboolean ilIsValidGif(ILconst_string FileName)
{
ILHANDLE GifFile;
ILboolean bGif = IL_FALSE;
if (!iCheckExtension(FileName, IL_TEXT("gif"))) {
ilSetError(IL_INVALID_EXTENSION);
return bGif;
}
GifFile = iopenr(FileName);
if (GifFile == NULL) {
ilSetError(IL_COULD_NOT_OPEN_FILE);
return bGif;
}
bGif = ilIsValidGifF(GifFile);
icloser(GifFile);
return bGif;
}
//! Checks if the ILHANDLE contains a valid Gif file at the current position.
ILboolean ilIsValidGifF(ILHANDLE File)
{
ILuint FirstPos;
ILboolean bRet;
iSetInputFile(File);
FirstPos = itell();
bRet = iIsValidGif();
iseek(FirstPos, IL_SEEK_SET);
return bRet;
}
//! Checks if Lump is a valid Gif lump.
ILboolean ilIsValidGifL(const void *Lump, ILuint Size)
{
iSetInputLump(Lump, Size);
return iIsValidGif();
}
// Internal function to get the header and check it.
ILboolean iIsValidGif()
{
char Header[6];
if (iread(Header, 1, 6) != 6)
return IL_FALSE;
iseek(-6, IL_SEEK_CUR);
if (!strnicmp(Header, "GIF87A", 6))
return IL_TRUE;
if (!strnicmp(Header, "GIF89A", 6))
return IL_TRUE;
return IL_FALSE;
}
//! Reads a Gif file
ILboolean ilLoadGif(ILconst_string FileName)
{
ILHANDLE GifFile;
ILboolean bGif = IL_FALSE;
GifFile = iopenr(FileName);
if (GifFile == NULL) {
ilSetError(IL_COULD_NOT_OPEN_FILE);
return bGif;
}
bGif = ilLoadGifF(GifFile);
icloser(GifFile);
return bGif;
}
//! Reads an already-opened Gif file
ILboolean ilLoadGifF(ILHANDLE File)
{
ILuint FirstPos;
ILboolean bRet;
iSetInputFile(File);
FirstPos = itell();
bRet = iLoadGifInternal();
iseek(FirstPos, IL_SEEK_SET);
return bRet;
}
//! Reads from a memory "lump" that contains a Gif
ILboolean ilLoadGifL(const void *Lump, ILuint Size)
{
iSetInputLump(Lump, Size);
return iLoadGifInternal();
}
// Internal function used to load the Gif.
ILboolean iLoadGifInternal()
{
GIFHEAD Header;
ILpal GlobalPal;
if (iCurImage == NULL) {
ilSetError(IL_ILLEGAL_OPERATION);
return IL_FALSE;
}
GlobalPal.Palette = NULL;
GlobalPal.PalSize = 0;
//read header
iread(&Header.Sig, 1, 6);
Header.Width = GetLittleUShort();
Header.Height = GetLittleUShort();
Header.ColourInfo = igetc();
Header.Background = igetc();
Header.Aspect = igetc();
if (!strnicmp(Header.Sig, "GIF87A", 6)) {
GifType = GIF87A;
}
else if (!strnicmp(Header.Sig, "GIF89A", 6)) {
GifType = GIF89A;
}
else {
ilSetError(IL_INVALID_FILE_HEADER);
return IL_FALSE;
}
if (!ilTexImage(Header.Width, Header.Height, 1, 1, IL_COLOUR_INDEX, IL_UNSIGNED_BYTE, NULL))
return IL_FALSE;
iCurImage->Origin = IL_ORIGIN_UPPER_LEFT;
// Check for a global colour map.
if (Header.ColourInfo & (1 << 7)) {
if (!iGetPalette(Header.ColourInfo, &GlobalPal, IL_FALSE, NULL)) {
return IL_FALSE;
}
}
if (!GetImages(&GlobalPal, &Header))
return IL_FALSE;
if (GlobalPal.Palette && GlobalPal.PalSize)
ifree(GlobalPal.Palette);
GlobalPal.Palette = NULL;
GlobalPal.PalSize = 0;
return ilFixImage();
}
ILboolean iGetPalette(ILubyte Info, ILpal *Pal, ILboolean UsePrevPal, ILimage *PrevImage)
{
ILuint PalSize, PalOffset = 0;
// If we have a local palette and have to use the previous frame as well,
// we have to copy the palette from the previous frame, in addition
// to the data, which we copy in GetImages.
// The ld(palettes bpp - 1) is stored in the lower
// 3 bits of Info (weird gif format ... :) )
PalSize = (1 << ((Info & 0x7) + 1)) * 3;
if (UsePrevPal) {
if (PrevImage == NULL) { // Cannot use the previous palette if it does not exist.
ilSetError(IL_ILLEGAL_FILE_VALUE);
return IL_FALSE;
}
PalSize = PalSize + PrevImage->Pal.PalSize;
PalOffset = PrevImage->Pal.PalSize;
}
if (PalSize > 256 * 3) {
ilSetError(IL_ILLEGAL_FILE_VALUE);
return IL_FALSE;
}
Pal->PalSize = PalSize;
Pal->PalType = IL_PAL_RGB24;
//Pal->Palette = (ILubyte*)ialloc(Pal->PalSize);
Pal->Palette = (ILubyte*)ialloc(256 * 3);
if (Pal->Palette == NULL)
return IL_FALSE;
if (UsePrevPal)
memcpy(Pal->Palette, PrevImage->Pal.Palette, PrevImage->Pal.PalSize); // Copy the old palette over.
if (iread(Pal->Palette + PalOffset, 1, Pal->PalSize) != Pal->PalSize) { // Read the new palette.
ifree(Pal->Palette);
Pal->Palette = NULL;
return IL_FALSE;
}
return IL_TRUE;
}
ILboolean GetImages(ILpal *GlobalPal, GIFHEAD *GifHead)
{
IMAGEDESC ImageDesc, OldImageDesc;
GFXCONTROL Gfx;
ILboolean BaseImage = IL_TRUE;
ILimage *Image = iCurImage, *TempImage = NULL, *PrevImage = NULL;
ILuint NumImages = 0, i;
ILint input;
ILuint PalOffset;
OldImageDesc.ImageInfo = 0; // to initialize the data with an harmless value
Gfx.Used = IL_TRUE;
while (!ieof()) {
ILubyte DisposalMethod = 1;
i = itell();
if (!SkipExtensions(&Gfx))
goto error_clean;
i = itell();
if (!Gfx.Used)
DisposalMethod = (Gfx.Packed & 0x1C) >> 2;
//read image descriptor
ImageDesc.Separator = igetc();
if (ImageDesc.Separator != 0x2C) //end of image
break;
ImageDesc.OffX = GetLittleUShort();
ImageDesc.OffY = GetLittleUShort();
ImageDesc.Width = GetLittleUShort();
ImageDesc.Height = GetLittleUShort();
ImageDesc.ImageInfo = igetc();
if (ieof()) {
ilGetError(); // Gets rid of the IL_FILE_READ_ERROR that inevitably results.
break;
}
if (!BaseImage) {
NumImages++;
Image->Next = ilNewImage(iCurImage->Width, iCurImage->Height, 1, 1, 1);
if (Image->Next == NULL)
goto error_clean;
//20040612: DisposalMethod controls how the new images data is to be combined
//with the old image. 0 means that it doesn't matter how they are combined,
//1 means keep the old image, 2 means set to background color, 3 is
//load the image that was in place before the current (this is not implemented
//here! (TODO?))
if (DisposalMethod == 2 || DisposalMethod == 3)
//Note that this is actually wrong, too: If the image has a local
//color table, we should really search for the best fit of the
//background color table and use that index (?). Furthermore,
//we should only memset the part of the image that is not read
//later (if we are sure that no parts of the read image are transparent).
if (!Gfx.Used && Gfx.Packed & 0x1)
memset(Image->Next->Data, Gfx.Transparent, Image->SizeOfData);
else
memset(Image->Next->Data, GifHead->Background, Image->SizeOfData);
else if (DisposalMethod == 1 || DisposalMethod == 0)
memcpy(Image->Next->Data, Image->Data, Image->SizeOfData);
//Interlacing has to be removed after the image was copied (line above)
if (OldImageDesc.ImageInfo & (1 << 6)) { // Image is interlaced.
if (!RemoveInterlace(Image))
goto error_clean;
}
PrevImage = Image;
Image = Image->Next;
Image->Format = IL_COLOUR_INDEX;
Image->Origin = IL_ORIGIN_UPPER_LEFT;
} else {
BaseImage = IL_FALSE;
if (!Gfx.Used && Gfx.Packed & 0x1)
memset(Image->Data, Gfx.Transparent, Image->SizeOfData);
else
memset(Image->Data, GifHead->Background, Image->SizeOfData);
}
Image->OffX = ImageDesc.OffX;
Image->OffY = ImageDesc.OffY;
PalOffset = 0;
// Check to see if the image has its own palette.
if (ImageDesc.ImageInfo & (1 << 7)) {
ILboolean UsePrevPal = IL_FALSE;
if (DisposalMethod == 1 && NumImages != 0) { // Cannot be the first image for this.
PalOffset = PrevImage->Pal.PalSize;
UsePrevPal = IL_TRUE;
}
if (!iGetPalette(ImageDesc.ImageInfo, &Image->Pal, UsePrevPal, PrevImage)) {
goto error_clean;
}
} else {
if (!iCopyPalette(&Image->Pal, GlobalPal)) {
goto error_clean;
}
}
if (!GifGetData(Image, Image->Data + ImageDesc.OffX + ImageDesc.OffY*Image->Width, Image->SizeOfData,
ImageDesc.Width, ImageDesc.Height, Image->Width, PalOffset, &Gfx)) {
memset(Image->Data, 0, Image->SizeOfData); //@TODO: Remove this. For debugging purposes right now.
ilSetError(IL_ILLEGAL_FILE_VALUE);
goto error_clean;
}
// See if there was a valid graphics control extension.
if (!Gfx.Used) {
Gfx.Used = IL_TRUE;
Image->Duration = Gfx.Delay * 10; // We want it in milliseconds.
// See if a transparent colour is defined.
if (Gfx.Packed & 1) {
if (!ConvertTransparent(Image, Gfx.Transparent)) {
goto error_clean;
}
}
}
i = itell();
// Terminates each block.
if((input = igetc()) == IL_EOF)
goto error_clean;
if (input != 0x00)
iseek(-1, IL_SEEK_CUR);
// break;
OldImageDesc = ImageDesc;
}
//Deinterlace last image
if (OldImageDesc.ImageInfo & (1 << 6)) { // Image is interlaced.
if (!RemoveInterlace(Image))
goto error_clean;
}
if (BaseImage) // Was not able to load any images in...
return IL_FALSE;
return IL_TRUE;
error_clean:
Image = iCurImage->Next;
/* while (Image) {
TempImage = Image;
Image = Image->Next;
ilCloseImage(TempImage);
}*/
return IL_FALSE;
}
ILboolean SkipExtensions(GFXCONTROL *Gfx)
{
ILint Code;
ILint Label;
ILint Size;
// DW (06-03-2002): Apparently there can be...
//if (GifType == GIF87A)
// return IL_TRUE; // No extensions in the GIF87a format.
do {
if((Code = igetc()) == IL_EOF)
return IL_FALSE;
if (Code != 0x21) {
iseek(-1, IL_SEEK_CUR);
return IL_TRUE;
}
if((Label = igetc()) == IL_EOF)
return IL_FALSE;
switch (Label)
{
case 0xF9:
Gfx->Size = igetc();
Gfx->Packed = igetc();
Gfx->Delay = GetLittleUShort();
Gfx->Transparent = igetc();
Gfx->Terminator = igetc();
if (ieof())
return IL_FALSE;
Gfx->Used = IL_FALSE;
break;
/*case 0xFE:
break;
case 0x01:
break;*/
default:
do {
if((Size = igetc()) == IL_EOF)
return IL_FALSE;
iseek(Size, IL_SEEK_CUR);
} while (!ieof() && Size != 0);
}
// @TODO: Handle this better.
if (ieof()) {
ilSetError(IL_FILE_READ_ERROR);
return IL_FALSE;
}
} while (1);
return IL_TRUE;
}
#define MAX_CODES 4096
ILint curr_size, clear, ending, newcodes, top_slot, slot, navail_bytes = 0, nbits_left = 0;
ILubyte b1;
ILubyte byte_buff[257];
ILubyte *pbytes;
ILubyte *stack;
ILubyte *suffix;
ILshort *prefix;
ILboolean success;
ILuint code_mask[13] =
{
0L,
0x0001L, 0x0003L,
0x0007L, 0x000FL,
0x001FL, 0x003FL,
0x007FL, 0x00FFL,
0x01FFL, 0x03FFL,
0x07FFL, 0x0FFFL
};
ILint get_next_code(void) {
ILint i, t;
ILuint ret;
//20050102: Tests for IL_EOF were added because this function
//crashed sometimes if igetc() returned IL_EOF
//(for example "table-add-column-before-active.gif" included in the
//mozilla source package)
if (!nbits_left) {
if (navail_bytes <= 0) {
pbytes = byte_buff;
navail_bytes = igetc();
if(navail_bytes == IL_EOF) {
success = IL_FALSE;
return ending;
}
if (navail_bytes) {
for (i = 0; i < navail_bytes; i++) {
if((t = igetc()) == IL_EOF) {
success = IL_FALSE;
return ending;
}
byte_buff[i] = t;
}
}
}
b1 = *pbytes++;
nbits_left = 8;
navail_bytes--;
}
ret = b1 >> (8 - nbits_left);
while (curr_size > nbits_left) {
if (navail_bytes <= 0) {
pbytes = byte_buff;
navail_bytes = igetc();
if(navail_bytes == IL_EOF) {
success = IL_FALSE;
return ending;
}
if (navail_bytes) {
for (i = 0; i < navail_bytes; i++) {
if((t = igetc()) == IL_EOF) {
success = IL_FALSE;
return ending;
}
byte_buff[i] = t;
}
}
}
b1 = *pbytes++;
ret |= b1 << nbits_left;
nbits_left += 8;
navail_bytes--;
}
nbits_left -= curr_size;
return (ret & code_mask[curr_size]);
}
static void cleanUpGifLoadState()
{
ifree(stack);
ifree(suffix);
ifree(prefix);
}
ILboolean GifGetData(ILimage *Image, ILubyte *Data, ILuint ImageSize, ILuint Width, ILuint Height, ILuint Stride, ILuint PalOffset, GFXCONTROL *Gfx)
{
ILubyte *sp;
ILint code, fc, oc;
ILubyte DisposalMethod = 0;
ILint c, size;
ILuint i = 0, Read = 0, j = 0;
ILubyte *DataPtr = Data;
if (!Gfx->Used)
DisposalMethod = (Gfx->Packed & 0x1C) >> 2;
if((size = igetc()) == IL_EOF)
return IL_FALSE;
if (size < 2 || 9 < size) {
return IL_FALSE;
}
stack = (ILubyte*)ialloc(MAX_CODES + 1);
suffix = (ILubyte*)ialloc(MAX_CODES + 1);
prefix = (ILshort*)ialloc(sizeof(*prefix) * (MAX_CODES + 1));
if (!stack || !suffix || !prefix)
{
cleanUpGifLoadState();
return IL_FALSE;
}
curr_size = size + 1;
top_slot = 1 << curr_size;
clear = 1 << size;
ending = clear + 1;
slot = newcodes = ending + 1;
navail_bytes = nbits_left = 0;
oc = fc = 0;
sp = stack;
while ((c = get_next_code()) != ending && Read < Height) {
if (c == clear)
{
curr_size = size + 1;
slot = newcodes;
top_slot = 1 << curr_size;
while ((c = get_next_code()) == clear);
if (c == ending)
break;
if (c >= slot)
c = 0;
oc = fc = c;
if (DisposalMethod == 1 && !Gfx->Used && Gfx->Transparent == c && (Gfx->Packed & 0x1) != 0)
i++;
else if (i < Width)
DataPtr[i++] = c + PalOffset;
if (i == Width)
{
DataPtr += Stride;
i = 0;
Read += 1;
++j;
if (j >= Height) {
cleanUpGifLoadState();
return IL_FALSE;
}
}
}
else
{
code = c;
//BG-2007-01-10: several fixes for incomplete GIFs
if (code >= slot)
{
code = oc;
if (sp >= stack + MAX_CODES) {
cleanUpGifLoadState();
return IL_FALSE;
}
*sp++ = fc;
}
if (code >= MAX_CODES)
return IL_FALSE;
while (code >= newcodes)
{
if (sp >= stack + MAX_CODES)
{
cleanUpGifLoadState();
return IL_FALSE;
}
*sp++ = suffix[code];
code = prefix[code];
}
if (sp >= stack + MAX_CODES) {
cleanUpGifLoadState();
return IL_FALSE;
}
*sp++ = (ILbyte)code;
if (slot < top_slot)
{
fc = code;
suffix[slot] = fc;
prefix[slot++] = oc;
oc = c;
}
if (slot >= top_slot && curr_size < 12)
{
top_slot <<= 1;
curr_size++;
}
while (sp > stack)
{
sp--;
if (DisposalMethod == 1 && !Gfx->Used && Gfx->Transparent == *sp && (Gfx->Packed & 0x1) != 0)
i++;
else if (i < Width)
DataPtr[i++] = *sp + PalOffset;
if (i == Width)
{
i = 0;
Read += 1;
j = (j+1) % Height;
// Needs to start from Data, not Image->Data.
DataPtr = Data + j * Stride;
}
}
}
}
cleanUpGifLoadState();
return IL_TRUE;
}
/*From the GIF spec:
The rows of an Interlaced images are arranged in the following order:
Group 1 : Every 8th. row, starting with row 0. (Pass 1)
Group 2 : Every 8th. row, starting with row 4. (Pass 2)
Group 3 : Every 4th. row, starting with row 2. (Pass 3)
Group 4 : Every 2nd. row, starting with row 1. (Pass 4)
*/
ILboolean RemoveInterlace(ILimage *image)
{
ILubyte *NewData;
ILuint i, j = 0;
NewData = (ILubyte*)ialloc(image->SizeOfData);
if (NewData == NULL)
return IL_FALSE;
//changed 20041230: images with offsety != 0 were not
//deinterlaced correctly before...
for (i = 0; i < image->OffY; i++, j++) {
memcpy(&NewData[i * image->Bps], &image->Data[j * image->Bps], image->Bps);
}
for (i = 0 + image->OffY; i < image->Height; i += 8, j++) {
memcpy(&NewData[i * image->Bps], &image->Data[j * image->Bps], image->Bps);
}
for (i = 4 + image->OffY; i < image->Height; i += 8, j++) {
memcpy(&NewData[i * image->Bps], &image->Data[j * image->Bps], image->Bps);
}
for (i = 2 + image->OffY; i < image->Height; i += 4, j++) {
memcpy(&NewData[i * image->Bps], &image->Data[j * image->Bps], image->Bps);
}
for (i = 1 + image->OffY; i < image->Height; i += 2, j++) {
memcpy(&NewData[i * image->Bps], &image->Data[j * image->Bps], image->Bps);
}
ifree(image->Data);
image->Data = NewData;
return IL_TRUE;
}
// Uses the transparent colour index to make an alpha channel.
ILboolean ConvertTransparent(ILimage *Image, ILubyte TransColour)
{
ILubyte *Palette;
ILuint i, j;
if (!Image->Pal.Palette || !Image->Pal.PalSize) {
ilSetError(IL_INTERNAL_ERROR);
return IL_FALSE;
}
Palette = (ILubyte*)ialloc(Image->Pal.PalSize / 3 * 4);
if (Palette == NULL)
return IL_FALSE;
for (i = 0, j = 0; i < Image->Pal.PalSize; i += 3, j += 4) {
Palette[j ] = Image->Pal.Palette[i ];
Palette[j+1] = Image->Pal.Palette[i+1];
Palette[j+2] = Image->Pal.Palette[i+2];
if (j/4 == TransColour)
Palette[j+3] = 0x00;
else
Palette[j+3] = 0xFF;
}
ifree(Image->Pal.Palette);
Image->Pal.Palette = Palette;
Image->Pal.PalSize = Image->Pal.PalSize / 3 * 4;
Image->Pal.PalType = IL_PAL_RGBA32;
return IL_TRUE;
}
#endif //IL_NO_GIF