mirror of
https://git.do.srb2.org/STJr/SRB2.git
synced 2024-12-23 03:00:56 +00:00
d669a4e84a
Used instead of pLocalPalette when attempting to determine objective truths, such as "the colours of this gif without color profile modification" and "what indicies should this colormap remap to". Also, made f_wipe.c's paldiv only get calculated once.
639 lines
15 KiB
C
639 lines
15 KiB
C
// SONIC ROBO BLAST 2
|
||
//-----------------------------------------------------------------------------
|
||
// Copyright (C) 2013-2016 by Matthew "Inuyasha" Walsh.
|
||
// Copyright (C) 2013 by "Ninji".
|
||
// Copyright (C) 2013-2016 by Sonic Team Junior.
|
||
//
|
||
// This program is free software distributed under the
|
||
// terms of the GNU General Public License, version 2.
|
||
// See the 'LICENSE' file for more details.
|
||
//-----------------------------------------------------------------------------
|
||
/// \file m_anigif.c
|
||
/// \brief Animated GIF creation movie mode.
|
||
/// Uses an implementation of Lempel–Ziv–Welch (LZW) compression,
|
||
/// which by-the-way: the patents have expired for over ten years ago.
|
||
|
||
#include "m_anigif.h"
|
||
#include "d_main.h"
|
||
#include "z_zone.h"
|
||
#include "v_video.h"
|
||
#include "i_video.h"
|
||
#include "m_misc.h"
|
||
|
||
// GIFs are always little-endian
|
||
#include "byteptr.h"
|
||
|
||
consvar_t cv_gif_optimize = {"gif_optimize", "On", CV_SAVE, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL};
|
||
consvar_t cv_gif_downscale = {"gif_downscale", "On", CV_SAVE, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL};
|
||
|
||
#ifdef HAVE_ANIGIF
|
||
static boolean gif_optimize = false; // So nobody can do something dumb
|
||
static boolean gif_downscale = false; // like changing cvars mid output
|
||
|
||
static FILE *gif_out = NULL;
|
||
static INT32 gif_frames = 0;
|
||
static UINT8 gif_writeover = 0;
|
||
|
||
|
||
|
||
// OPTIMIZE gif output
|
||
// ---
|
||
|
||
//
|
||
// GIF_optimizecmprow
|
||
// checks a row for modification, and if any is detected, what parts
|
||
// modified input 'last': returns current row if modification detected
|
||
// modified input 'left': returns leftmost known changed pixel
|
||
// modified input 'right': returns rightmost known changed pixel
|
||
//
|
||
static UINT8 GIF_optimizecmprow(const UINT8 *dst, const UINT8 *src, INT32 row,
|
||
INT32 *last, INT32 *left, INT32 *right)
|
||
{
|
||
const UINT8 *dp = dst + (vid.width * row);
|
||
const UINT8 *sp = src + (vid.width * row);
|
||
const UINT8 *dtmp, *stmp;
|
||
UINT8 doleft = 1, doright = 1;
|
||
INT32 i = 0;
|
||
|
||
if (!memcmp(sp, dp, vid.width))
|
||
return 0; // unchanged.
|
||
|
||
*last = row;
|
||
|
||
// left side
|
||
i = 0;
|
||
if (*left == 0) // edge reached
|
||
doleft = 0;
|
||
else if (*left > 0) // left set, nonzero
|
||
{
|
||
if (!memcmp(sp, dp, *left))
|
||
doleft = 0; // left side not changed
|
||
}
|
||
while (doleft)
|
||
{
|
||
dtmp = dp + i;
|
||
stmp = sp + i;
|
||
if (*dtmp != *stmp)
|
||
{
|
||
doleft = 0;
|
||
*left = i;
|
||
}
|
||
++i;
|
||
}
|
||
|
||
// right side
|
||
i = vid.width - 1;
|
||
if (*right == vid.width - 1) // edge reached
|
||
doright = 0;
|
||
else if (*right >= 0) // right set, non-end-of-width
|
||
{
|
||
dtmp = dp + *right + 1;
|
||
stmp = sp + *right + 1;
|
||
if (!memcmp(stmp, dtmp, vid.width - (*right + 1)))
|
||
doright = 0; // right side not changed
|
||
}
|
||
while (doright)
|
||
{
|
||
dtmp = dp + i;
|
||
stmp = sp + i;
|
||
if (*dtmp != *stmp)
|
||
{
|
||
doright = 0;
|
||
*right = i;
|
||
}
|
||
--i;
|
||
}
|
||
return 1;
|
||
}
|
||
|
||
//
|
||
// GIF_optimizeregion
|
||
// attempts to optimize a GIF as it's being written by giving a region
|
||
// containing all of the changed pixels instead of rewriting
|
||
// the entire screen buffer to the GIF file every frame
|
||
// modified input 'x': returns optimal starting x coordinate
|
||
// modified input 'y': returns optimal starting y coordinate
|
||
// modified input 'w': returns optimal width
|
||
// modified input 'h': returns optimal height
|
||
//
|
||
static void GIF_optimizeregion(const UINT8 *dst, const UINT8 *src,
|
||
INT32 *x, INT32 *y, INT32 *w, INT32 *h)
|
||
{
|
||
INT32 st = 0, sb = vid.height - 1; // work from both directions
|
||
INT32 firstchg_t = -1, firstchg_b = -1; // store first changed row.
|
||
INT32 lastchg_t = -1, lastchg_b = -1; // Store last row... just in case
|
||
INT32 lmpix = -1, rmpix = -1; // store left and rightmost change
|
||
UINT8 stopt = 0, stopb = 0;
|
||
|
||
while ((!stopt || !stopb) && st < sb)
|
||
{
|
||
if (!stopt)
|
||
{
|
||
if (GIF_optimizecmprow(dst, src, st++, &lastchg_t, &lmpix, &rmpix)
|
||
&& lmpix == 0 && rmpix == vid.width - 1)
|
||
stopt = 1;
|
||
if (firstchg_t < 0 && lastchg_t >= 0)
|
||
firstchg_t = lastchg_t;
|
||
}
|
||
if (!stopb)
|
||
{
|
||
if (GIF_optimizecmprow(dst, src, sb--, &lastchg_b, &lmpix, &rmpix)
|
||
&& lmpix == 0 && rmpix == vid.width - 1)
|
||
stopb = 1;
|
||
if (firstchg_b < 0 && lastchg_b >= 0)
|
||
firstchg_b = lastchg_b;
|
||
}
|
||
}
|
||
|
||
if (lmpix < 0) // NO CHANGE.
|
||
{
|
||
// hack: we don't attempt to go back and rewrite the previous
|
||
// frame's delay, we just make this frame have only a single
|
||
// pixel so it contains minimal data
|
||
*x = *y = 0;
|
||
*w = *h = 1;
|
||
return;
|
||
}
|
||
|
||
*x = lmpix;
|
||
*y = (firstchg_t < 0 && lastchg_b >= 0) ? lastchg_b : firstchg_t;
|
||
*w = rmpix + 1;
|
||
*h = ((firstchg_b < 0 && lastchg_t >= 0) ? lastchg_t : firstchg_b) + 1;
|
||
|
||
*w -= *x;
|
||
*h -= *y;
|
||
}
|
||
|
||
|
||
|
||
// GIF Bit WRiter
|
||
// ---
|
||
static UINT8 *gifbwr_buf = NULL;
|
||
static UINT8 *gifbwr_cur;
|
||
static UINT8 gifbwr_bufsize = 0;
|
||
|
||
static UINT32 gifbwr_bits_buf = 0;
|
||
static INT32 gifbwr_bits_num = 0;
|
||
static UINT8 gifbwr_bits_min = 9;
|
||
|
||
//
|
||
// GIF_bwr_flush
|
||
// flushes any bits remaining in the buffer.
|
||
//
|
||
static void GIF_bwrflush(void)
|
||
{
|
||
if (gifbwr_bits_num > 0) // will be between 1 and 7
|
||
{
|
||
WRITEUINT8(gifbwr_cur, (UINT8)(gifbwr_bits_buf&0xFF));
|
||
++gifbwr_bufsize;
|
||
}
|
||
gifbwr_bits_buf = gifbwr_bits_num = 0;
|
||
}
|
||
|
||
//
|
||
// GIF_bwr_write
|
||
// writes bits into bit buffer,
|
||
// writes into buffer when whole bytes obtained
|
||
//
|
||
static void GIF_bwrwrite(UINT32 idata)
|
||
{
|
||
gifbwr_bits_buf |= (idata << gifbwr_bits_num);
|
||
gifbwr_bits_num += gifbwr_bits_min;
|
||
while (gifbwr_bits_num >= 8)
|
||
{
|
||
WRITEUINT8(gifbwr_cur, (UINT8)(gifbwr_bits_buf&0xFF));
|
||
gifbwr_bits_buf >>= 8;
|
||
gifbwr_bits_num -= 8;
|
||
++gifbwr_bufsize;
|
||
}
|
||
}
|
||
|
||
|
||
|
||
// SCReen BUFfer (obviously)
|
||
// ---
|
||
static UINT8 *scrbuf_pos;
|
||
static UINT8 *scrbuf_linebegin;
|
||
static UINT8 *scrbuf_lineend;
|
||
static UINT8 *scrbuf_writeend;
|
||
static INT16 scrbuf_downscaleamt = 1;
|
||
|
||
|
||
|
||
// GIF LZW algorithm
|
||
// ---
|
||
#define GIFLZW_TABLECLR 0x100
|
||
#define GIFLZW_DATAEND 0x101
|
||
#define GIFLZW_DICTSTART 0x102
|
||
#define GIFLZW_MAXCODE 4096
|
||
|
||
static UINT16 giflzw_workingCode;
|
||
static UINT16 giflzw_nextCodeToAssign;
|
||
static UINT32 *giflzw_hashTable = NULL; // 16384 required
|
||
|
||
//
|
||
// GIF_prepareLZW
|
||
// prepatres the LZW hash table for use
|
||
//
|
||
static void GIF_prepareLZW(void)
|
||
{
|
||
gifbwr_bits_min = 9;
|
||
giflzw_nextCodeToAssign = GIFLZW_DICTSTART;
|
||
|
||
if (!giflzw_hashTable)
|
||
giflzw_hashTable = Z_Malloc(16384*sizeof(UINT32), PU_STATIC, NULL);
|
||
memset(giflzw_hashTable, 0, 16384*sizeof(UINT32));
|
||
}
|
||
|
||
//
|
||
// GIF_searchHash
|
||
// searches the LZW hash table for a match
|
||
//
|
||
static char GIF_searchHash(UINT32 key, UINT32 *pOutput)
|
||
{
|
||
UINT32 entry, position = (key >> 6) & 0x3FFF;
|
||
|
||
while (giflzw_hashTable[position] != 0)
|
||
{
|
||
entry = giflzw_hashTable[position];
|
||
if ((entry >> 12) == key)
|
||
{
|
||
*pOutput = (entry & 0xFFF);
|
||
return 1;
|
||
}
|
||
|
||
position = (position + 1) & 0x3FFF;
|
||
}
|
||
|
||
return 0;
|
||
}
|
||
|
||
//
|
||
// GIF_addHash
|
||
// stores a hash in the hash table
|
||
//
|
||
static void GIF_addHash(UINT32 key, UINT32 value)
|
||
{
|
||
UINT32 position = (key >> 6) & 0x3FFF;
|
||
|
||
for (;;)
|
||
{
|
||
if (giflzw_hashTable[position] == 0)
|
||
{
|
||
giflzw_hashTable[position] = (key << 12) | (value & 0xFFF);
|
||
return;
|
||
}
|
||
|
||
position = (position + 1) & 0x3FFF;
|
||
}
|
||
}
|
||
|
||
//
|
||
// GIF_feedByte
|
||
// feeds bytes into the working code,
|
||
// and to the hash table or output from there.
|
||
//
|
||
static void GIF_feedByte(UINT8 pbyte)
|
||
{
|
||
UINT32 key, hashOutput = 0;
|
||
|
||
// Prepare a code with this byte if we have none
|
||
if (giflzw_workingCode == UINT16_MAX)
|
||
{
|
||
giflzw_workingCode = pbyte;
|
||
return;
|
||
}
|
||
|
||
// If we're here, this means we have a code in progress
|
||
// Is this string already in the dictionary?
|
||
key = (giflzw_workingCode << 8) | pbyte;
|
||
|
||
if (0 == GIF_searchHash(key, &hashOutput))
|
||
{
|
||
// It wasn't found.
|
||
// That means we can output what we already had, and
|
||
// create a new dictionary entry containing that
|
||
// plus our new byte.
|
||
if (giflzw_nextCodeToAssign > (1 << gifbwr_bits_min))
|
||
++gifbwr_bits_min; // out of room, extend minbits
|
||
|
||
GIF_bwrwrite(giflzw_workingCode);
|
||
GIF_addHash(key, giflzw_nextCodeToAssign);
|
||
++giflzw_nextCodeToAssign;
|
||
|
||
// Seed the working code with this byte, for the next
|
||
// round
|
||
giflzw_workingCode = pbyte;
|
||
return;
|
||
}
|
||
|
||
// This string is in there, so update our working code!
|
||
giflzw_workingCode = hashOutput;
|
||
}
|
||
|
||
//
|
||
// GIF_lzw
|
||
// polls the hashtable, does writing, etc
|
||
//
|
||
static void GIF_lzw(void)
|
||
{
|
||
while (scrbuf_pos <= scrbuf_writeend)
|
||
{
|
||
GIF_feedByte(*scrbuf_pos);
|
||
if (giflzw_nextCodeToAssign >= GIFLZW_MAXCODE)
|
||
{
|
||
GIF_bwrwrite(GIFLZW_TABLECLR);
|
||
GIF_prepareLZW();
|
||
}
|
||
if ((scrbuf_pos += scrbuf_downscaleamt) >= scrbuf_lineend)
|
||
{
|
||
scrbuf_lineend += (vid.width * scrbuf_downscaleamt);
|
||
scrbuf_linebegin += (vid.width * scrbuf_downscaleamt);
|
||
scrbuf_pos = scrbuf_linebegin;
|
||
}
|
||
// Just a bit of overflow prevention
|
||
if (gifbwr_bufsize >= 248)
|
||
break;
|
||
}
|
||
if (scrbuf_pos > scrbuf_writeend)
|
||
{
|
||
// 4.15.14 - I failed to account for the possibility that
|
||
// these two writes could possibly cause minbits increases.
|
||
// Luckily, we have a guarantee that the first byte CANNOT exceed
|
||
// the maximum possible code. So, we do a minbits check here...
|
||
if (giflzw_nextCodeToAssign++ > (1 << gifbwr_bits_min))
|
||
++gifbwr_bits_min; // out of room, extend minbits
|
||
GIF_bwrwrite(giflzw_workingCode);
|
||
|
||
// And luckily once more, if the data marker somehow IS at
|
||
// MAXCODE it doesn't matter, because it still marks the
|
||
// end of the stream and thus no extending will happen!
|
||
// But still, we need to check minbits again...
|
||
if (giflzw_nextCodeToAssign++ > (1 << gifbwr_bits_min))
|
||
++gifbwr_bits_min; // out of room, extend minbits
|
||
GIF_bwrwrite(GIFLZW_DATAEND);
|
||
|
||
// Okay, the flush is safe at least.
|
||
GIF_bwrflush();
|
||
gif_writeover = 1;
|
||
}
|
||
}
|
||
|
||
|
||
|
||
// GIF HEADer (okay yeah)
|
||
// ---
|
||
const UINT8 gifhead_base[6] = {0x47,0x49,0x46,0x38,0x39,0x61}; // GIF89a
|
||
const UINT8 gifhead_nsid[19] = {0x21,0xFF,0x0B, // extension block + size
|
||
0x4E,0x45,0x54,0x53,0x43,0x41,0x50,0x45,0x32,0x2E,0x30, // NETSCAPE2.0
|
||
0x03,0x01,0xFF,0xFF,0x00}; // sub-block, repetitions
|
||
|
||
//
|
||
// GIF_headwrite
|
||
// writes the gif header to the currently open output file.
|
||
// NOTE that this code does not accomodate for palette changes.
|
||
//
|
||
static void GIF_headwrite(void)
|
||
{
|
||
UINT8 *gifhead = Z_Malloc(800, PU_STATIC, NULL);
|
||
UINT8 *p = gifhead;
|
||
INT32 i;
|
||
UINT16 rwidth, rheight;
|
||
|
||
if (!gif_out)
|
||
return;
|
||
|
||
WRITEMEM(p, gifhead_base, sizeof(gifhead_base));
|
||
|
||
// Image width/height
|
||
if (gif_downscale)
|
||
{
|
||
scrbuf_downscaleamt = vid.dupx;
|
||
rwidth = (vid.width / scrbuf_downscaleamt);
|
||
rheight = (vid.height / scrbuf_downscaleamt);
|
||
}
|
||
else
|
||
{
|
||
scrbuf_downscaleamt = 1;
|
||
rwidth = vid.width;
|
||
rheight = vid.height;
|
||
}
|
||
WRITEUINT16(p, rwidth);
|
||
WRITEUINT16(p, rheight);
|
||
|
||
// colors, aspect, etc
|
||
WRITEUINT8(p, 0xF7);
|
||
WRITEUINT8(p, 0x00);
|
||
WRITEUINT8(p, 0x00);
|
||
|
||
// write color table
|
||
{
|
||
RGBA_t *pal = ((cv_screenshot_colorprofile.value)
|
||
? pLocalPalette
|
||
: pMasterPalette);
|
||
|
||
for (i = 0; i < 256; i++)
|
||
{
|
||
WRITEUINT8(p, pal[i].s.red);
|
||
WRITEUINT8(p, pal[i].s.green);
|
||
WRITEUINT8(p, pal[i].s.blue);
|
||
}
|
||
}
|
||
|
||
// write extension block
|
||
WRITEMEM(p, gifhead_nsid, sizeof(gifhead_nsid));
|
||
|
||
// write to file and be done with it!
|
||
fwrite(gifhead, 1, 800, gif_out);
|
||
Z_Free(gifhead);
|
||
}
|
||
|
||
|
||
|
||
// GIF FRAME (surprise!)
|
||
// ---
|
||
const UINT8 gifframe_gchead[4] = {0x21,0xF9,0x04,0x04}; // GCE, bytes, packed byte (no trans = 0 | no input = 0 | don't remove = 4)
|
||
|
||
static UINT8 *gifframe_data = NULL;
|
||
static size_t gifframe_size = 8192;
|
||
|
||
//
|
||
// GIF_framewrite
|
||
// writes a frame into the file.
|
||
//
|
||
static void GIF_framewrite(void)
|
||
{
|
||
UINT8 *p;
|
||
UINT8 *movie_screen = screens[2];
|
||
INT32 blitx, blity, blitw, blith;
|
||
|
||
if (!gifframe_data)
|
||
gifframe_data = Z_Malloc(gifframe_size, PU_STATIC, NULL);
|
||
p = gifframe_data;
|
||
|
||
if (!gif_out)
|
||
return;
|
||
|
||
// Compare image data (for optimizing GIF)
|
||
if (gif_optimize && gif_frames > 0)
|
||
{
|
||
// before blit movie_screen points to last frame, cur_screen points to this frame
|
||
UINT8 *cur_screen = screens[0];
|
||
GIF_optimizeregion(cur_screen, movie_screen, &blitx, &blity, &blitw, &blith);
|
||
|
||
// blit to temp screen
|
||
I_ReadScreen(movie_screen);
|
||
}
|
||
else
|
||
{
|
||
blitx = blity = 0;
|
||
blitw = vid.width;
|
||
blith = vid.height;
|
||
|
||
if (gif_frames == 0)
|
||
I_ReadScreen(movie_screen);
|
||
movie_screen = screens[0];
|
||
}
|
||
|
||
// screen regions are handled in GIF_lzw
|
||
{
|
||
UINT16 delay = 3; // todo
|
||
INT32 startline;
|
||
|
||
WRITEMEM(p, gifframe_gchead, 4);
|
||
|
||
WRITEUINT16(p, delay);
|
||
WRITEUINT8(p, 0);
|
||
WRITEUINT8(p, 0); // end of GCE
|
||
|
||
if (scrbuf_downscaleamt > 1)
|
||
{
|
||
// Ensure our downscaled blitx/y starts and ends on a pixel.
|
||
blitx -= (blitx % scrbuf_downscaleamt);
|
||
blity -= (blity % scrbuf_downscaleamt);
|
||
blitw = ((blitw + (scrbuf_downscaleamt - 1)) / scrbuf_downscaleamt) * scrbuf_downscaleamt;
|
||
blith = ((blith + (scrbuf_downscaleamt - 1)) / scrbuf_downscaleamt) * scrbuf_downscaleamt;
|
||
}
|
||
|
||
WRITEUINT8(p, 0x2C);
|
||
WRITEUINT16(p, (UINT16)(blitx / scrbuf_downscaleamt));
|
||
WRITEUINT16(p, (UINT16)(blity / scrbuf_downscaleamt));
|
||
WRITEUINT16(p, (UINT16)(blitw / scrbuf_downscaleamt));
|
||
WRITEUINT16(p, (UINT16)(blith / scrbuf_downscaleamt));
|
||
WRITEUINT8(p, 0); // no local table of colors
|
||
|
||
scrbuf_pos = movie_screen + blitx + (blity * vid.width);
|
||
scrbuf_writeend = scrbuf_pos + (blitw - 1) + ((blith - 1) * vid.width);
|
||
|
||
if (!gifbwr_buf)
|
||
gifbwr_buf = Z_Malloc(256, PU_STATIC, NULL);
|
||
gifbwr_cur = gifbwr_buf;
|
||
|
||
GIF_prepareLZW();
|
||
giflzw_workingCode = UINT16_MAX;
|
||
WRITEUINT8(p, gifbwr_bits_min - 1);
|
||
|
||
startline = (scrbuf_pos - movie_screen) / vid.width;
|
||
scrbuf_linebegin = movie_screen + (startline * vid.width) + blitx;
|
||
scrbuf_lineend = scrbuf_linebegin + blitw;
|
||
|
||
//prewrite a table clear
|
||
GIF_bwrwrite(GIFLZW_TABLECLR);
|
||
|
||
gif_writeover = 0;
|
||
while (!gif_writeover)
|
||
{
|
||
GIF_lzw(); // main lzw packing loop
|
||
|
||
if ((size_t)(p - gifframe_data) + gifbwr_bufsize + 1 >= gifframe_size)
|
||
{
|
||
INT32 temppos = p - gifframe_data;
|
||
gifframe_data = Z_Realloc(gifframe_data, (gifframe_size *= 2), PU_STATIC, NULL);
|
||
p = gifframe_data + temppos; // realloc moves gifframe_data, so p is now invalid
|
||
}
|
||
|
||
// reset after writing to read
|
||
gifbwr_cur = gifbwr_buf;
|
||
WRITEUINT8(p, gifbwr_bufsize);
|
||
WRITEMEM(p, gifbwr_cur, gifbwr_bufsize);
|
||
|
||
gifbwr_bufsize = 0;
|
||
gifbwr_cur = gifbwr_buf;
|
||
}
|
||
WRITEUINT8(p, 0); //terminator
|
||
}
|
||
fwrite(gifframe_data, 1, (p - gifframe_data), gif_out);
|
||
++gif_frames;
|
||
}
|
||
|
||
|
||
|
||
// ========================
|
||
// !!! PUBLIC FUNCTIONS !!!
|
||
// ========================
|
||
|
||
//
|
||
// GIF_open
|
||
// opens a new file for writing.
|
||
//
|
||
INT32 GIF_open(const char *filename)
|
||
{
|
||
#ifdef HWRENDER
|
||
if (rendermode != render_soft)
|
||
{
|
||
CONS_Alert(CONS_WARNING, M_GetText("GIFs cannot be taken in non-software modes!\n"));
|
||
return 0;
|
||
}
|
||
#endif
|
||
|
||
gif_out = fopen(filename, "wb");
|
||
if (!gif_out)
|
||
return 0;
|
||
|
||
gif_optimize = (!!cv_gif_optimize.value);
|
||
gif_downscale = (!!cv_gif_downscale.value);
|
||
GIF_headwrite();
|
||
gif_frames = 0;
|
||
return 1;
|
||
}
|
||
|
||
//
|
||
// GIF_frame
|
||
// writes a frame into the output gif
|
||
//
|
||
void GIF_frame(void)
|
||
{
|
||
// there's not much actually needed here, is there.
|
||
GIF_framewrite();
|
||
}
|
||
|
||
//
|
||
// GIF_close
|
||
// closes output GIF
|
||
//
|
||
INT32 GIF_close(void)
|
||
{
|
||
if (!gif_out)
|
||
return 0;
|
||
|
||
// final terminator.
|
||
fwrite(";", 1, 1, gif_out);
|
||
fclose(gif_out);
|
||
gif_out = NULL;
|
||
|
||
if (gifbwr_buf)
|
||
Z_Free(gifbwr_buf);
|
||
gifbwr_buf = gifbwr_cur = NULL;
|
||
|
||
if (gifframe_data)
|
||
Z_Free(gifframe_data);
|
||
gifframe_data = NULL;
|
||
|
||
if (giflzw_hashTable)
|
||
Z_Free(giflzw_hashTable);
|
||
giflzw_hashTable = NULL;
|
||
|
||
CONS_Printf(M_GetText("Animated gif closed; wrote %d frames\n"), gif_frames);
|
||
return 1;
|
||
}
|
||
#endif //ifdef HAVE_ANIGIF
|