Loading GIF files/animations...

Previous topic - Next topic

dreamerman

We can load jpg, png and bmp files, but whole gifs world was beyond our reach, until now.
From long time I was thinking about making gif importer, but LZW compression looked complicated, it still look strange ;d
But after long searches for most understandable code, I found some source in Java, and translation to GLB began..
Here it is, GIF loading code, with examples.

NOTE: I only 'converted' that module to GLBasic, real thanks should go to Jitsu Wan for his Java source.

GIF module:
Code (glbasic) Select
// --------------------------------- //
// Project: gif_loader

// No copyright asserted on this source code. May be used for
// any purpose, however, refer TO the Unisys LZW patent FOR any additional
// restrictions. I am NOT responsible IF something will not work properly.

// GLBasic GIF load port by dreamerman[at]wp.pl
// heavily based on gif loader for Java ME by Jitsu Wan with reference to work done by Kevin Weiner, FM Software; LZW decoder adapted from John Cristy's ImageMagick.
// includes c++ inline
// any other info in code
//
// --- HOW TO USE ---
// - first initialize gif vars by calling -
// Init_Gif_loader()
// - next load gif file as sprite -
// Load_gif_file("some.gif", sprite_id%, frame_count%, frame_width%, frame_height%)
// DrawSprite sprite_id%, 0, 0
// - or load directly as animation for use with DrawAnim ... -
// Load_gif_as_anim("some.gif", anim_id%, frame_count%, frame_width%, frame_height%)
// DrawAnim anim_id%, current_frame%, 0, 0
// - that's all... -

//ver: public 2
//Changelog:
//- public 2: added Redim's to clear image buffers



//two importants variables, set max sprite size - lower for compatibility with legacy gpu/mobile devices
GLOBAL gif_max_sprite_width% = 2048, gif_max_sprite_height% = 2048
//other gif loader variables
GLOBAL gif_curr_posx%, gif_curr_posy%, gif_currxlen%
GLOBAL gif_bytes%[], gif_file_size%, gif_error%, gif_rposition%, gif_tpixels%[], gif_framecount%
GLOBAL gif_width%, gif_height%, gif_gct_flag%, gif_gct_size%, gif_pixelaspect%, gif_bgindex%, gif_bgcolor%, gif_lastbgcolor%
GLOBAL gif_lctFlag%, gif_interlace%, gif_lct_size%, gif_islastimg%
GLOBAL gif_gctab%[], gif_lctab%[], gif_blockb%[], gif_block_size%
GLOBAL gif_delay%, gif_transparency%, gif_transIndex%, gif_dispose%, gif_lastDispose%
GLOBAL gif_MaxStackSize% = 4096, gif_prefix%[], gif_suffix%[], gif_pixelStack%[]
GLOBAL gif_ix%, gif_iy%, gif_iw%, gif_ih%, gif_done%, gif_loopcount%
GLOBAL gif_lastImage_id%, gif_lastRect%[], gif_image%[], gif_lastimage%[], gif_wholesprite%[]

//init function
FUNCTION Init_Gif_loader:

DIM gif_bytes%[2]
DIM gif_gctab%[256]
DIM gif_lctab%[256]
DIM gif_blockb%[256]
DIM gif_tpixels%[256]
DIM gif_prefix%[gif_MaxStackSize%]
DIM gif_suffix%[gif_MaxStackSize%]
DIM gif_pixelStack%[gif_MaxStackSize%+1]
DIM gif_lastRect%[4]
DIM gif_image%[256]
DIM gif_lastimage%[256]
DIM gif_wholesprite%[256]

ENDFUNCTION

//gif loading errors:
//1 - no such file
//2 - error with opening file
//3 - not a gif file - if it's bmp/jpg/png it will be loaded
//4 - ???
//5 - to small sprite size - to many gif frames
FUNCTION Load_gif_file: filename$, sprite_id%, BYREF frame_count%, BYREF frame_width%, BYREF frame_height%
LOCAL i1%, i2%, i3%, wp%, czs1$

gif_file_size% = GETFILESIZE(filename$); gif_error% = 0
IF (gif_file_size% = 0)
DEBUG "GIF load error: wrong file size\n"
gif_error = 1; RETURN 0
ENDIF //no file size?
// DEBUG "reading start: " + GETTIMERALL() + "\n"
wp% = GENFILE()
IF (OPENFILE(wp%, filename$, 1))
REDIM gif_bytes%[gif_file_size%]
READSTR wp%, czs1$, gif_file_size%
FOR i1% = 0 TO gif_file_size%-1
gif_bytes%[i1%] = ASC(MID$(czs1$, i1%, 1))
NEXT
CLOSEFILE wp%
gif_rposition% = 6
ELSE //some error with loading file
gif_error = 2; RETURN 0
ENDIF

// DEBUG "reading end: " + GETTIMERALL() + ", file size: " + gif_file_size% + "\n"

gif_curr_posx% = 0; gif_curr_posy% = 0; gif_framecount% = 0
czs1$ = CHR$(gif_bytes%[0]) + CHR$(gif_bytes%[1]) + CHR$(gif_bytes%[2]) + CHR$(gif_bytes%[3]) + CHR$(gif_bytes%[4]) + CHR$(gif_bytes%[5])
IF (czs1$ = "GIF87a" OR czs1$ = "GIF89a")
// DEBUG "it's a gif file..\n"
//clear last image buffer
REDIM gif_image%[0]; REDIM gif_wholesprite%[0]; REDIM gif_lastimage%[0]
gif_readLSD()
IF (gif_gct_flag%); gif_readColorTable(1); ENDIF
//redim sprite array
i1% = INTEGER(gif_max_sprite_width% / gif_width%)
gif_currxlen% = i1% * gif_width%
REDIM gif_wholesprite%[gif_currxlen% * gif_max_sprite_height%]

gif_readContents()

// DEBUG "loading end: " + GETTIMERALL() + "\n"
// DEBUG "array x-size: " + gif_currxlen% + ", loop count: " + gif_loopcount% + ", delay: " + gif_delay% + " \n"
// DEBUG "frame count: " + gif_framecount% + ", in-sprite pos: " + gif_curr_posx% + "x" + gif_curr_posy% + "\n"
IF (gif_error% = 5); DEC gif_curr_posy%, gif_height%; DEC gif_framecount%; ENDIF
REDIM gif_wholesprite%[gif_currxlen% * (gif_curr_posy%+gif_height%)] //cut of unnecessary data
//create sprite from array
MEM2SPRITE(gif_wholesprite%[], sprite_id%, gif_currxlen%, gif_curr_posy%+gif_height%)

ELSE //check if it's bmp, jpg  or png for normal loading
IF (gif_bytes%[0] = 66 AND gif_bytes%[1] = 77) OR (gif_bytes%[0] = 255 AND gif_bytes%[1] = 216) OR (gif_bytes%[0] = 137 AND gif_bytes%[1] = 80 AND gif_bytes%[2] = 78 AND gif_bytes%[3] = 71)
LOADSPRITE filename$, sprite_id%
GETSPRITESIZE sprite_id%, gif_width%, gif_height%
IF (gif_width% > 0 AND gif_height% > 0)
gif_framecount% = 1
ELSE
gif_error = 3; RETURN 0
ENDIF
ELSE
gif_error = 3; RETURN 0
ENDIF
ENDIF
frame_count% = gif_framecount%; frame_width% = gif_width%; frame_height% = gif_height%
REDIM gif_bytes%[1]
REDIM gif_wholesprite%[1]

RETURN 1
ENDFUNCTION

//equivalent for LoadAnim
FUNCTION Load_gif_as_anim: filename$, anim_id%, BYREF frame_count%, BYREF frame_width%, BYREF frame_height%
LOCAL ws% = GENSPRITE(), temp%, tcount%, twidth%, theight%
temp% = Load_gif_file(filename$, anim_id%, tcount%, twidth%, theight%)
SETSPRITEANIM anim_id%, twidth%, theight%
frame_count% = tcount%; frame_width% = twidth%; frame_height% = theight%
RETURN temp%
ENDFUNCTION

//main loop
FUNCTION gif_readContents:
LOCAL i1%, code%, czs1$

gif_done% = 0
WHILE (gif_done% = 0)
code% = gif_read()
SELECT code%
CASE 44 //0x2C - image separator
gif_readImage()
// DEBUG "new image: " + gif_framecount% + "\n"
CASE 33 //0x21 - extension
code% = gif_read()
// DEBUG "extension: " + "\n"
SELECT code%
CASE 0xf9 //graphic control extension
gif_readGraphicControlExt()
// DEBUG "graphic ext: dispose: " + gif_dispose% + " \n"
CASE 0xff //application extension
gif_readBlock()
// DEBUG "netscape ext \n"
czs1$ = ""
FOR i1%=0 TO 10; INC czs1$, CHR$(gif_blockb%[i1%]); NEXT
IF (czs1$ = "NETSCAPE2.0")
gif_readNetscapeExt()
ELSE
gif_skipblock()
ENDIF
DEFAULT
gif_skipblock()
ENDSELECT
CASE 59 //0x3b - terminator
gif_done% = 1
CASE 0 //0x00 - bad byte
//continue
DEFAULT
//raise error - format error?
ENDSELECT
WEND

ENDFUNCTION


// original comment
// Decodes LZW image data into pixel array.
// Adapted from John Cristy's ImageMagick.
FUNCTION gif_decodeImageData:
LOCAL NullCode% = -1, npix% = gif_iw% * gif_ih%
LOCAL available%, clear%, code_mask%, code_size%, end_of_information%, in_code%, old_code%, bits%, code%, count%, i%, datum%, data_size%, first%, top%, bi%, pi%

IF (LEN(gif_tpixels%[]) < npix); REDIM gif_tpixels%[npix]; ENDIF

data_size% = gif_read() //  Initialize GIF data stream decoder.
clear% = ASL(1, data_size%)
end_of_information% = clear% + 1
available% = clear% + 2
old_code% = NullCode%
code_size% = data_size% + 1
code_mask% = ASL(1, code_size) - 1
FOR code% = 0 TO clear%-1
gif_prefix%[code%] = 0
gif_suffix%[code%] = code%
NEXT

INLINE
         //  Decode GIF pixel stream.
         datum = bits = count = first = top = pi = bi = 0;

         while (i < npix)
         {
             if (top == 0)
             {
                 if (bits < code_size)
                 {
                     //  Load bytes until there are enough bits for a code.
                     if (count == 0)
                     {
                         // Read a new data block.
                         count = gif_readBlock();
                         if (count <= 0) {
                 break;
             }
                         bi = 0;
                     }
                     datum += ((gif_blockb(bi)) & 0xff) << bits; //(((int) gif_blockb(bi)) & 0xff) << bits;
                     bits += 8;
                     bi++;
                     count--;
                     continue;
                 }

                 //  Get the next code.

                 code = datum & code_mask;
                 datum >>= code_size;
                 bits -= code_size;

                 //  Interpret the code

                 if ((code > available) || (code == end_of_information)) {
                     break;
                 }
                 if (code == clear)
                 {
                     //  Reset decoder.
                     code_size = data_size + 1;
                     code_mask = (1 << code_size) - 1;
                     available = clear + 2;
                     old_code = NullCode;
                     continue;
                 }
                 if (old_code == NullCode)
                 {
                     gif_pixelStack(top++) = gif_suffix(code);
                     old_code = code;
                     first = code;
                     continue;
                 }
                 in_code = code;
                 if (code == available)
                 {
                     gif_pixelStack(top++) = first; //(byte) first
                     code = old_code;
                 }
                 while (code > clear)
                 {
                     gif_pixelStack(top++) = gif_suffix(code);
                     code = gif_prefix(code);
                 }
                 first = (gif_suffix(code)) & 0xff; //((int) gif_suffix(code)) & 0xff;

                 //  Add a new string to the string table,

                 if (available >= gif_MaxStackSize) {
                     break;
                 }
                 gif_pixelStack(top++) = first; //(byte) first
                 gif_prefix(available) = old_code; //(short) old_code
                 gif_suffix(available) = first; //(byte) first
                 available++;
                 if (((available & code_mask) == 0) && (available < gif_MaxStackSize))
                 {
                     code_size++;
                     code_mask += available;
                 }
                 old_code = in_code;
             }

             //  Pop a pixel off the pixel stack.

             top--;
             gif_tpixels(pi++) = gif_pixelStack(top);
             i++;
         }

         for (i = pi; i < npix; i++)
         {
             gif_tpixels(i) = 0; // clear missing pixels
         }
ENDINLINE

ENDFUNCTION


FUNCTION gif_readImage:

gif_ix% = gif_readShort()
gif_iy% = gif_readShort()
gif_iw% = gif_readShort()
gif_ih% = gif_readShort()

LOCAL packed% = gif_read(), save%
gif_lctFlag% = bAND(packed%, 0x80) //1 - local color table flag
gif_interlace% = bAND(packed%, 0x40) //2 - interlace flag
//3 - sort flag, 4-5 - reserved
gif_lct_size% = ASL(2, bAND(packed%, 7)) //6-8 - local color table size
IF (gif_lctFlag%) //use local table
gif_readColorTable(0)
ELSE
FOR i1% = 0 TO gif_gct_size%-1
gif_lctab%[i1%] = gif_gctab%[i1%]
NEXT
IF (gif_bgindex% = gif_transIndex%); gif_bgcolor% = 0; ENDIF
ENDIF

IF (gif_transparency%)
save% = gif_lctab%[gif_transIndex%]
gif_lctab%[gif_transIndex%] = 0
ENDIF

gif_decodeImageData()
gif_skipblock()

INC gif_framecount%
//check for space
IF (gif_curr_posy% + gif_height% >= gif_max_sprite_height%)
gif_error% = 5; gif_done% = 1
ENDIF
IF (gif_error% = 0)
gif_setPixels() // transfer pixel data to image
IF (gif_transparency%); gif_lctab%[gif_transIndex%] = save%; ENDIF
gif_resetFrame()
ENDIF

//calculate new position in our sprite
INC gif_curr_posx%, gif_width%
IF (gif_curr_posx% >= gif_currxlen%)
gif_curr_posx% = 0
INC gif_curr_posy%, gif_height%
IF (gif_curr_posy% >= gif_max_sprite_height%)
gif_error% = 5
gif_done% = 1
ENDIF
ENDIF
// DEBUG "new position in sprite: " + gif_curr_posx% + "x" + gif_curr_posy% + "\n"

ENDFUNCTION


FUNCTION gif_setPixels:
LOCAL n%, i1%, i2%, i3%, i4%, i5%, i6%, dlug% = gif_width% * gif_height%, ccolor%

IF (gif_lastDispose% > 0)
IF (gif_lastDispose% = 3)
n% = gif_framecount% - 2 //use image before last
IF (n% > 0)
i1% = (n - 1) * gif_width%; i2% = 0 //x & y pos of that frame on sprite array
WHILE (i1% > gif_currxlen%)
DEC i1%, gif_currxlen%; INC i2%, gif_height%
WEND

FOR i4% = i2% TO i2% + gif_height%
i5% = gif_height% * i4%
FOR i3% = i1% TO i1% + gif_width%
gif_image%[i6%] = gif_wholesprite%[i5% + i3%]
INC i6%
NEXT
NEXT
ENDIF
ELSEIF (gif_lastDispose% = 2)
IF (gif_transparency%)
ccolor% = INTEGER(0x00000000)
ELSE
ccolor% = gif_lastbgcolor%
ENDIF
FOR i1% = 0 TO dlug%-1
gif_image%[i1%] = ccolor%
NEXT
//should be: g.fillRect(lastRect[0],lastRect[1],lastRect[2],lastRect[3]);
ELSE
FOR i1% = 0 TO dlug% - 1
gif_image%[i1%] = gif_lastimage%[i1%]
NEXT
ENDIF
ENDIF


INLINE
         // copy each source line to the appropriate place in the destination
         int pass = 1;
         int incr = 8;
         int iline = 0;
         for (int i = 0; i < gif_ih; i++) {
             int line = i;
             if (gif_interlace) {
                 if (iline >= gif_ih) {
                     pass++;
                     switch (pass) {
                         case 2 :
                             iline = 4;
                             break;
                         case 3 :
                             iline = 2;
                             incr = 4;
                             break;
                         case 4 :
                             iline = 1;
                             incr = 2;
                     }
                 }
                 line = iline;
                 iline += incr;
             }
             line += gif_iy;
             if (line < gif_height) {
                 int k = line * gif_width;
                 int dx = k + gif_ix; // start of line in dest
                 int dlim = dx + gif_iw; // end of dest line
                 if ((k + gif_width) < dlim) {
                     dlim = k + gif_width; // past dest edge
                 }
                 int sx = i * gif_iw; // start of line in source
                 while (dx < dlim) {
                     // map color and insert in destination
                     int index = (gif_tpixels(sx++)) & 0xff;
                     int c = gif_lctab(index);
                     if (c != 0) {
                         gif_image(dx) = c;
                     }
                     dx++;
                 }
             }
         }

ENDINLINE

//paste current frame to our sprite
i2% = 0; i3% = gif_curr_posy% * gif_currxlen% + gif_curr_posx%
FOR i1% = 0 TO gif_height% * gif_width% - 1
gif_wholesprite%[i3%+i2%] = gif_image%[i1%]
INC i2%
IF (i2%>=gif_width%); i2% = 0; INC i3%, gif_currxlen%; ENDIF
NEXT

ENDFUNCTION


FUNCTION gif_readGraphicControlExt:
gif_read() //block size
LOCAL packed% = gif_read() //packed fields
gif_dispose% = ASR(bAND(packed%, 0x1c), 2) //disposal method -> dispose = (packed & 0x1c) >> 2
IF (gif_dispose% = 0); gif_dispose% = 1; ENDIF
gif_transparency% = bAND(packed%, 1) //transparency = (packed & 1) != 0;
gif_delay% = gif_readShort() * 10 //delay in miliseconds
gif_transIndex% = gif_read() //transparent color index
gif_read() //block terminator
ENDFUNCTION

//how many loops/repeats - does we care about it? ;]
FUNCTION gif_readNetscapeExt:
LOCAL b1%, b2%
REPEAT
gif_readBlock()
IF (gif_blockb%[0] = 1)
b1% = bAND(gif_blockb%[1], 256)
b2% = bAND(gif_blockb%[2], 256)
gif_loopcount% = bOR(ASL(b2%, 8), b1%)
ENDIF
UNTIL (gif_block_size% = 0 OR gif_error% <> 0)
ENDFUNCTION

//read Logical Screen Descriptor
FUNCTION gif_readLSD:
gif_width% = gif_readShort()
gif_height% = gif_readShort()
LOCAL packed% = gif_read(), dlug% = gif_width% * gif_height%
gif_gct_flag% = bAND(packed%, 128) // = (packed & 0x80) != 0
gif_gct_size% = ASL(2, bAND(packed%, 7))
//bits:
// 1   : global color table flag
// 2-4 : color resolution
// 5   : gct sort flag
// 6-8 : gct size

gif_bgindex% = gif_read()
gif_pixelaspect% = gif_read()
//redim our image tables
REDIM gif_image%[dlug%]
REDIM gif_lastimage%[dlug%]

//DEBUG "info: " + gif_width% + "x" + gif_height% + ", colors count: " + gif_gct_size% + "\n"

ENDFUNCTION

//read one byte from gif file array
FUNCTION gif_read:
LOCAL i1% = 0
i1% = gif_bytes%[gif_rposition]
INC gif_rposition
RETURN i1%
ENDFUNCTION

FUNCTION gif_readShort:
RETURN gif_read() + ASL(gif_read(), 8)
ENDFUNCTION

//block reading
FUNCTION gif_readBlock:
LOCAL i1%
gif_block_size% = gif_read()
IF (gif_block_size% > 0)
LOCAL count% = 0
FOR i2%=0 TO gif_block_size%-1
gif_blockb%[i2%] = gif_bytes%[gif_rposition+i2%]
NEXT
INC gif_rposition%, gif_block_size%
ENDIF
RETURN gif_block_size%
ENDFUNCTION

FUNCTION gif_skipblock:
REPEAT
gif_readBlock()
UNTIL (gif_block_size%=0 OR gif_error%<>0)
ENDFUNCTION

FUNCTION gif_readColorTable: is_global%
LOCAL i1%
IF (is_global% = 1)
FOR i1%=0 TO gif_gct_size%-1
gif_gctab%[i1%] = 0xff000000 + ASL(gif_bytes%[gif_rposition+2], 16) + ASL(gif_bytes%[gif_rposition+1], 8) + gif_bytes%[gif_rposition]
INC gif_rposition, 3
NEXT
ELSE
FOR i1%=0 TO gif_lct_size%-1
gif_lctab%[i1%] = 0xff000000 + ASL(gif_bytes%[gif_rposition+2], 16) + ASL(gif_bytes%[gif_rposition+1], 8) + gif_bytes%[gif_rposition]
INC gif_rposition, 3
NEXT
ENDIF
ENDFUNCTION

FUNCTION gif_resetFrame:
gif_lastDispose% = gif_dispose%
gif_lastRect%[0] = gif_ix%
gif_lastRect%[1] = gif_iy%
gif_lastRect%[2] = gif_iw%
gif_lastRect%[3] = gif_ih%

//lastImage = image
LOCAL dlug% = LEN(gif_image%[]), i1%
FOR i1% = 0 TO dlug%-1
gif_lastimage%[i1%] = gif_image%[i1%]
NEXT
gif_islastimg% = 1

gif_lastbgcolor% = gif_bgcolor%
gif_dispose% = 0
gif_transparency% = 0
//gif_delay% = 0
gif_lct_size% = 0

ENDFUNCTION


example - load as sprite:
Code (glbasic) Select

SETCURRENTDIR("Media") // go to media files
LOCAL t1%, t2%, t3%
Init_Gif_loader()
Load_gif_file("some_animation.gif", 1, t1%, t2%, t3%)
SAVESPRITE "test.png", 1
MOUSEWAIT
END


example - load as animation:
Code (glbasic) Select

SETCURRENTDIR("Media") // go to media files
LOCAL t1%, t2%, t3%, gif_cframe%, gif_frames%, done% = 1

Init_Gif_loader()
Load_gif_as_anim("earth.gif", 3, gif_frames%, t2%, t3%)

CLEARSCREEN RGB(100, 50, 50)
WHILE done%
IF KEY(01); done% = 0; ENDIF
INC gif_cframe%
IF (gif_cframe% >= gif_frames%); gif_cframe% = 0; ENDIF
SLEEP gif_delay% //var from gif loading module
IF (gif_frames%>0); DRAWANIM 3, gif_cframe%, 10, 10; ENDIF
SHOWSCREEN
WEND
END


:-)

Edit: little update, fixed issue with not clearing image buffers..
Check my source code editor for GLBasic - link Update: 20.04.2020

Ian Price

Excellent news. Well done to all :)
I came. I saw. I played.

mentalthink

Uhh this it´s very Important... thanks a lot  :nw: :nw: :nw:

erico

I wonder about the final data size of a sprite based game using GIF. Does is compress(losslesly) to a good measure when compared to PNG? Holding sheets of sprite or movie strip on a lower source (gif 256 colors) could be great.

Its a widespread format, I should mark this page :-[

Kitty Hello

Insane. That's so cool.

Schranz0r

I <3 DGArray's :D

PC:
AMD Ryzen 7 3800X 16@4.5GHz, 16GB Corsair Vengeance LPX DDR4-3200 RAM, ASUS Dual GeForce RTX™ 3060 OC Edition 12GB GDDR6, Windows 11 Pro 64Bit, MSi Tomahawk B350 Mainboard