Displaying+animated+GIFs

//by Richard Russell, November 2007//

The normal methods of displaying a GIF image in a //BBC BASIC for Windows// program (using the code listed in the [|Help documentation] or in the article Displaying a JPEG or GIF in a picture box) will not display an **animated GIF** - only the first frame of the animation will be shown.

One straightforward method of displaying an animated GIF is to use an ATL container control. Assuming a suitable player is available (which there will be if **Internet Explorer** is installed on the PC) this will open and display the animation continuously until the ATL window is closed. An alternative method is to use Direct Show which gives a greater degree of control, for example you can pause and restart the animation. However it is still rather limiting, for example there is no straightforward way of controlling the animation speed.

When you need more control, an alternative method of displaying animated GIFs is to use the **GDI Plus** subsystem, installed as standard on **Windows XP** and **Windows Vista** and available as a redistributable file for earlier versions (**Windows 98** onwards). See the Help documentation under [|Library Routines... Antialiased graphics] for more information on GDI+.

To use this method you must first perform some initialisation, as follows:

code format="bb4w" INSTALL @lib$+"GDIPLIB" PROC_gdipinit ON CLOSE PROC_gdipexit : QUIT ON ERROR PROC_gdipexit : SYS "MessageBox", @hwnd%, REPORT$, 0, 48 : QUIT SYS "LoadLibrary", "GDIPLUS.DLL" TO gdip% SYS "GetProcAddress", gdip%, "GdipLoadImageFromFile"           TO `GdipLoadImageFromFile` SYS "GetProcAddress", gdip%, "GdipImageGetFrameDimensionsCount" TO `GdipImageGetFrameDimensionsCount` SYS "GetProcAddress", gdip%, "GdipImageGetFrameDimensionsList" TO `GdipImageGetFrameDimensionsList` SYS "GetProcAddress", gdip%, "GdipImageGetFrameCount"          TO `GdipImageGetFrameCount` SYS "GetProcAddress", gdip%, "GdipImageSelectActiveFrame"      TO `GdipImageSelectActiveFrame` SYS "GetProcAddress", gdip%, "GdipGetPropertyItemSize"         TO `GdipGetPropertyItemSize` SYS "GetProcAddress", gdip%, "GdipGetPropertyItem"             TO `GdipGetPropertyItem` SYS "GetProcAddress", gdip%, "GdipGetImageWidth"               TO `GdipGetImageWidth` SYS "GetProcAddress", gdip%, "GdipGetImageHeight"              TO `GdipGetImageHeight` SYS "GetProcAddress", gdip%, "GdipDrawImageRectI"              TO `GdipDrawImageRectI` SYS "GetProcAddress", gdip%, "GdipDisposeImage"                TO `GdipDisposeImage` code You may of course put this code in a procedure or library if you don't want to clutter your main program. If you need to perform your own 'cleanup' operations (on an error or when the window is closed), call your own replacement routine in the ON CLOSE and ON ERROR statements, and move the **PROC_gdipexit** there.

Once you have carried out the initialisation you can simply display the animated GIF as follows:

code format="bb4w" filename$ = "C:\www\bbcwin\bbctile3.gif" PROCanimatedgif(filename$, 500) code The second parameter of **PROCanimatedgif** is the time for which you want the animation to display, in centiseconds (one-hundredths of a second).

Once you have finished with the GDI+ subsystem (typically on exit from your program) don't forget to call **PROC_gdipexit**:

code format="bb4w" PROC_gdipexit END code Here is the all-important **PROCanimatedgif** routine:

code format="bb4w" DEF PROCanimatedgif(filename$, duration%) LOCAL wfile%, image%, ndims%, nframes%, frame%, psize%, xsize%, ysize% LOCAL xpos%, ypos%, endtime%, DimensionIDs{}, PropItem%, rc{}, delay% DIM wfile% LOCAL LEN(filename$)*2+1 SYS "MultiByteToWideChar", 0, 0, filename$, -1, wfile%, LEN(filename$)+1 SYS `GdipLoadImageFromFile`, wfile%, ^image% IF image% = 0 ERROR 100, "File not found" SYS `GdipImageGetFrameDimensionsCount`, image%, ^ndims% DIM DimensionIDs{(ndims%) a%,b%,c%,d%} SYS `GdipImageGetFrameDimensionsList`, image%, DimensionIDs{(0)}, ndims% SYS `GdipImageGetFrameCount`, image%, DimensionIDs{(0)}, ^nframes% PropertyTagFrameDelay% = &5100 SYS `GdipGetPropertyItemSize`, image%, PropertyTagFrameDelay%, ^psize% DIM PropItem% LOCAL psize% SYS `GdipGetPropertyItem`, image%, PropertyTagFrameDelay%, psize%, PropItem% SYS `GdipGetImageWidth`, image%, ^xsize% SYS `GdipGetImageHeight`, image%, ^ysize% DIM rc{l%,t%,r%,b%} SYS "GetClientRect", @hwnd%, rc{} IF xsize%>rc.r% ysize% *= rc.r% / xsize% : xsize% = rc.r%     IF ysize%>rc.b% xsize% *= rc.b% / ysize% : ysize% = rc.b%      xpos% = (rc.r%-xsize%)/2 ypos% = (rc.b%-ysize%)/2 rc.l% = xpos% : rc.r% = xpos% + xsize% rc.t% = ypos% : rc.b% = ypos% + ysize% endtime% = TIME + duration% REPEAT SYS `GdipImageSelectActiveFrame`, image%, DimensionIDs{(0)}, frame% SYS `GdipDrawImageRectI`, FN_gdipg, image%, xpos%, ypos%, xsize%, ysize% SYS "InvalidateRect", @hwnd%, rc{}, 0 *REFRESH delay% = PropItem%!(16+4*frame%) IF delay% WAIT delay% ELSE WAIT 8 frame% = (frame%+1) MOD nframes% UNTIL TIME>endtime% SYS `GdipDisposeImage`, image% ENDPROC code If the image is a 'transparent GIF' (i.e. it has a transparent background colour) the code must be modified to clear the background before each frame is displayed. One way is to alter the main display loop as shown below, which clears the background to the current Text Background Colour (as would be used by CLS):

code format="bb4w" endtime% = TIME + duration% SYS "CreateSolidBrush", @vdu%?71 + &1000000 TO brush% REPEAT SYS `GdipImageSelectActiveFrame`, image%, DimensionIDs{(0)}, frame% SYS "FillRect", @memhdc%, rc{}, brush% SYS `GdipDrawImageRectI`, FN_gdipg, image%, xpos%, ypos%, xsize%, ysize% SYS "InvalidateRect", @hwnd%, rc{}, 0 *REFRESH delay% = PropItem%!(16+4*frame%) IF delay% WAIT delay% ELSE WAIT 8 frame% = (frame%+1) MOD nframes% UNTIL TIME>endtime% SYS "DeleteObject", brush% code I'm sure you can see how you could modify this code if you wanted, for example, to display the animation for a certain number of loops or a certain number of frames, rather than for a certain time. The routine centers the GIF in the window; it could easily be modified to display it in a different position or at a different size.