2014-03-15 16:59:03 +00:00
// SONIC ROBO BLAST 2
//-----------------------------------------------------------------------------
2020-01-06 22:16:27 +00:00
// Copyright (C) 2013-2016 by Matthew "Kaito Sinclaire" Walsh.
2016-05-18 00:42:11 +00:00
// Copyright (C) 2013 by "Ninji".
2024-09-22 00:35:01 +00:00
// Copyright (C) 2013-2024 by Sonic Team Junior.
2014-03-15 16:59:03 +00:00
//
// 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"
2020-11-07 09:32:25 +00:00
# include "i_system.h" // I_GetPreciseTime
2017-04-20 17:59:27 +00:00
# include "m_misc.h"
2020-01-29 16:47:55 +00:00
# include "st_stuff.h" // st_palette
2021-09-13 21:17:34 +00:00
# include "doomstat.h" // singletics
2014-03-15 16:59:03 +00:00
2019-11-10 03:04:11 +00:00
# ifdef HWRENDER
# include "hardware/hw_main.h"
# endif
2014-03-15 16:59:03 +00:00
// GIFs are always little-endian
# include "byteptr.h"
2020-11-07 09:01:15 +00:00
CV_PossibleValue_t gif_dynamicdelay_cons_t [ ] = {
{ 0 , " Off " } ,
{ 1 , " On " } ,
{ 2 , " Accurate, experimental " } ,
{ 0 , NULL } } ;
2020-10-07 06:04:23 +00:00
consvar_t cv_gif_optimize = CVAR_INIT ( " gif_optimize " , " On " , CV_SAVE , CV_OnOff , NULL ) ;
consvar_t cv_gif_downscale = CVAR_INIT ( " gif_downscale " , " On " , CV_SAVE , CV_OnOff , NULL ) ;
2020-11-07 09:01:15 +00:00
consvar_t cv_gif_dynamicdelay = CVAR_INIT ( " gif_dynamicdelay " , " On " , CV_SAVE , gif_dynamicdelay_cons_t , NULL ) ;
2020-10-07 06:04:23 +00:00
consvar_t cv_gif_localcolortable = CVAR_INIT ( " gif_localcolortable " , " On " , CV_SAVE , CV_OnOff , NULL ) ;
2014-03-15 16:59:03 +00:00
# ifdef HAVE_ANIGIF
static boolean gif_optimize = false ; // So nobody can do something dumb
static boolean gif_downscale = false ; // like changing cvars mid output
2020-11-07 09:49:21 +00:00
static UINT8 gif_dynamicdelay = ( UINT8 ) 0 ; // and messing something up
2020-01-29 20:31:27 +00:00
// Palette handling
2020-01-29 16:47:55 +00:00
static boolean gif_localcolortable = false ;
2020-01-29 20:31:27 +00:00
static boolean gif_colorprofile = false ;
static RGBA_t * gif_headerpalette = NULL ;
static RGBA_t * gif_framepalette = NULL ;
2014-03-15 16:59:03 +00:00
static FILE * gif_out = NULL ;
static INT32 gif_frames = 0 ;
2020-11-07 09:32:25 +00:00
static precise_t gif_prevframetime = 0 ;
static UINT32 gif_delayus = 0 ; // "us" is microseconds
2014-03-15 16:59:03 +00:00
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
2014-04-19 17:41:29 +00:00
if ( gifbwr_bufsize > = 248 )
2014-03-15 16:59:03 +00:00
break ;
}
2014-04-14 05:14:58 +00:00
if ( scrbuf_pos > scrbuf_writeend )
2014-03-15 16:59:03 +00:00
{
2014-04-19 17:41:29 +00:00
// 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
2014-03-15 16:59:03 +00:00
GIF_bwrwrite ( giflzw_workingCode ) ;
2014-04-19 17:41:29 +00:00
// 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
2014-03-15 16:59:03 +00:00
GIF_bwrwrite ( GIFLZW_DATAEND ) ;
2014-04-19 17:41:29 +00:00
// Okay, the flush is safe at least.
2014-03-15 16:59:03 +00:00
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
2020-01-29 16:47:55 +00:00
//
2020-01-29 20:31:27 +00:00
// GIF_getpalette
// determine the palette for the current frame.
2020-01-29 16:47:55 +00:00
//
2020-01-29 20:31:27 +00:00
static RGBA_t * GIF_getpalette ( size_t palnum )
2020-01-29 16:47:55 +00:00
{
2020-02-03 04:52:43 +00:00
// In hardware mode, always returns the local palette
2020-01-29 16:47:55 +00:00
# ifdef HWRENDER
2020-02-03 04:52:43 +00:00
if ( rendermode = = render_opengl )
return pLocalPalette ;
else
2020-01-29 16:47:55 +00:00
# endif
2020-02-03 04:52:43 +00:00
return ( gif_colorprofile ? & pLocalPalette [ palnum * 256 ] : & pMasterPalette [ palnum * 256 ] ) ;
2020-01-29 16:47:55 +00:00
}
//
// GIF_palwrite
// writes the gif palette.
2020-01-29 20:31:27 +00:00
// used both for the header and local color tables.
2020-01-29 16:47:55 +00:00
//
2020-01-29 20:31:27 +00:00
static UINT8 * GIF_palwrite ( UINT8 * p , RGBA_t * pal )
2020-01-29 16:47:55 +00:00
{
INT32 i ;
for ( i = 0 ; i < 256 ; i + + )
{
WRITEUINT8 ( p , pal [ i ] . s . red ) ;
WRITEUINT8 ( p , pal [ i ] . s . green ) ;
WRITEUINT8 ( p , pal [ i ] . s . blue ) ;
}
return p ;
}
2014-03-15 16:59:03 +00:00
//
// GIF_headwrite
// writes the gif header to the currently open output file.
//
static void GIF_headwrite ( void )
{
UINT8 * gifhead = Z_Malloc ( 800 , PU_STATIC , NULL ) ;
UINT8 * p = gifhead ;
UINT16 rwidth , rheight ;
if ( ! gif_out )
return ;
WRITEMEM ( p , gifhead_base , sizeof ( gifhead_base ) ) ;
// Image width/height
if ( gif_downscale )
{
2023-10-30 04:38:51 +00:00
scrbuf_downscaleamt = vid . dup ;
2014-03-15 16:59:03 +00:00
rwidth = ( vid . width / scrbuf_downscaleamt ) ;
rheight = ( vid . height / scrbuf_downscaleamt ) ;
}
else
{
scrbuf_downscaleamt = 1 ;
rwidth = vid . width ;
rheight = vid . height ;
}
2020-01-29 16:47:55 +00:00
2014-03-15 16:59:03 +00:00
WRITEUINT16 ( p , rwidth ) ;
WRITEUINT16 ( p , rheight ) ;
// colors, aspect, etc
2020-01-29 20:31:27 +00:00
WRITEUINT8 ( p , 0xF7 ) ; // (0xF7 = 1111 0111)
2014-03-15 16:59:03 +00:00
WRITEUINT8 ( p , 0x00 ) ;
WRITEUINT8 ( p , 0x00 ) ;
// write color table
2020-01-29 20:31:27 +00:00
p = GIF_palwrite ( p , gif_headerpalette ) ;
2014-03-15 16:59:03 +00:00
// write extension block
WRITEMEM ( p , gifhead_nsid , sizeof ( gifhead_nsid ) ) ;
// write to file and be done with it!
2020-01-29 20:31:27 +00:00
fwrite ( gifhead , 1 , 800 , gif_out ) ;
2014-03-15 16:59:03 +00:00
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 ;
2020-05-09 19:59:09 +00:00
//
// GIF_rgbconvert
// converts an RGB frame to a frame with a palette.
//
2019-11-10 03:04:11 +00:00
# ifdef HWRENDER
2020-09-10 05:10:31 +00:00
static colorlookup_t gif_colorlookup ;
2020-05-09 19:59:09 +00:00
static void GIF_rgbconvert ( UINT8 * linear , UINT8 * scr )
2019-11-10 03:04:11 +00:00
{
UINT8 r , g , b ;
2020-05-09 19:59:09 +00:00
size_t src = 0 , dest = 0 ;
size_t size = ( vid . width * vid . height * 3 ) ;
2019-11-10 03:04:11 +00:00
2020-10-15 04:14:16 +00:00
InitColorLUT ( & gif_colorlookup , ( gif_localcolortable ) ? gif_framepalette : gif_headerpalette , true ) ;
2019-11-10 03:04:11 +00:00
2020-05-09 19:59:09 +00:00
while ( src < size )
2019-11-10 03:04:11 +00:00
{
2020-05-09 19:59:09 +00:00
r = ( UINT8 ) linear [ src ] ;
g = ( UINT8 ) linear [ src + 1 ] ;
b = ( UINT8 ) linear [ src + 2 ] ;
2020-09-10 05:10:31 +00:00
scr [ dest ] = GetColorLUTDirect ( & gif_colorlookup , r , g , b ) ;
2020-05-09 19:59:09 +00:00
src + = ( 3 * scrbuf_downscaleamt ) ;
dest + = scrbuf_downscaleamt ;
2019-11-10 03:04:11 +00:00
}
}
# endif
2014-03-15 16:59:03 +00:00
//
// 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 ;
2020-01-29 20:31:27 +00:00
boolean palchanged ;
2014-03-15 16:59:03 +00:00
if ( ! gifframe_data )
gifframe_data = Z_Malloc ( gifframe_size , PU_STATIC , NULL ) ;
p = gifframe_data ;
if ( ! gif_out )
return ;
2020-01-29 20:31:27 +00:00
// Lactozilla: Compare the header's palette with the current frame's palette and see if it changed.
if ( gif_localcolortable )
{
2020-02-03 05:24:22 +00:00
gif_framepalette = GIF_getpalette ( max ( st_palette , 0 ) ) ;
2020-01-29 20:31:27 +00:00
palchanged = memcmp ( gif_headerpalette , gif_framepalette , sizeof ( RGBA_t ) * 256 ) ;
}
else
palchanged = false ;
2014-03-15 16:59:03 +00:00
// Compare image data (for optimizing GIF)
2020-01-29 20:31:27 +00:00
// If the palette has changed, the entire frame is considered to be different.
if ( gif_optimize & & gif_frames > 0 & & ( ! palchanged ) )
2014-03-15 16:59:03 +00:00
{
// 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
2019-11-10 03:04:11 +00:00
if ( rendermode = = render_soft )
I_ReadScreen ( movie_screen ) ;
# ifdef HWRENDER
else if ( rendermode = = render_opengl )
2020-05-09 19:59:09 +00:00
{
UINT8 * linear = HWR_GetScreenshot ( ) ;
GIF_rgbconvert ( linear , movie_screen ) ;
free ( linear ) ;
}
2019-11-10 03:04:11 +00:00
# endif
2014-03-15 16:59:03 +00:00
}
else
{
blitx = blity = 0 ;
blitw = vid . width ;
blith = vid . height ;
2019-11-10 03:04:11 +00:00
# ifdef HWRENDER
2020-05-09 19:59:09 +00:00
// Copy the current OpenGL frame into the base screen
if ( rendermode = = render_opengl )
{
UINT8 * linear = HWR_GetScreenshot ( ) ;
GIF_rgbconvert ( linear , screens [ 0 ] ) ;
free ( linear ) ;
2019-11-10 03:04:11 +00:00
}
2020-05-09 19:59:09 +00:00
# endif
// Copy the first frame into the movie screen
// OpenGL already does the same above.
if ( gif_frames = = 0 & & rendermode = = render_soft )
I_ReadScreen ( movie_screen ) ;
2019-11-10 03:04:11 +00:00
2014-03-15 16:59:03 +00:00
movie_screen = screens [ 0 ] ;
}
// screen regions are handled in GIF_lzw
{
2020-10-16 22:06:13 +00:00
UINT16 delay = 0 ;
2014-03-15 16:59:03 +00:00
INT32 startline ;
2021-09-13 21:17:34 +00:00
if ( gif_dynamicdelay = = ( UINT8 ) 2 & & ! singletics )
2020-11-07 09:01:15 +00:00
{
2020-06-27 15:56:15 +00:00
// golden's attempt at creating a "dynamic delay"
2020-10-16 22:06:13 +00:00
UINT16 mingifdelay = 10 ; // minimum gif delay in milliseconds (keep at 10 because gifs can't get more precise).
2022-05-01 05:32:46 +00:00
gif_delayus + = ( I_GetPreciseTime ( ) - gif_prevframetime ) / ( I_GetPrecisePrecision ( ) / 1000000 ) ; // increase delay by how much time was spent between last measurement
2020-06-27 15:56:15 +00:00
2020-10-16 22:06:13 +00:00
if ( gif_delayus / 1000 > = mingifdelay ) // delay is big enough to be able to effect gif frame delay?
{
int frames = ( gif_delayus / 1000 ) / mingifdelay ; // get amount of frames to delay.
delay = frames ; // set the delay to delay that amount of frames.
gif_delayus - = frames * ( mingifdelay * 1000 ) ; // remove frames by the amount of milliseconds they take. don't reset to 0, the microseconds help consistency.
}
2020-06-27 15:56:15 +00:00
}
2021-09-13 21:17:34 +00:00
else if ( gif_dynamicdelay = = ( UINT8 ) 1 & & ! singletics )
2020-11-07 09:01:15 +00:00
{
float delayf = ceil ( 100.0f / NEWTICRATE ) ;
2022-05-01 05:32:46 +00:00
delay = ( UINT16 ) ( ( I_GetPreciseTime ( ) - gif_prevframetime ) ) / ( I_GetPrecisePrecision ( ) / 1000000 ) / 10 / 1000 ;
2020-11-07 09:01:15 +00:00
if ( delay < ( UINT16 ) ( delayf ) )
delay = ( UINT16 ) ( delayf ) ;
}
2020-06-27 15:56:15 +00:00
else
{
// the original code
int d1 = ( int ) ( ( 100.0f / NEWTICRATE ) * ( gif_frames + 1 ) ) ;
int d2 = ( int ) ( ( 100.0f / NEWTICRATE ) * ( gif_frames ) ) ;
delay = d1 - d2 ;
}
2014-03-15 16:59:03 +00:00
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 ) ) ;
2020-01-29 16:47:55 +00:00
if ( ! gif_localcolortable )
WRITEUINT8 ( p , 0 ) ; // no local table of colors
else
{
2020-01-29 20:31:27 +00:00
if ( palchanged )
{
// The palettes are different, so write the Local Color Table!
WRITEUINT8 ( p , 0x87 ) ; // (0x87 = 1000 0111)
p = GIF_palwrite ( p , gif_framepalette ) ;
}
else
WRITEUINT8 ( p , 0 ) ; // They are equal, no Local Color Table needed.
2020-01-29 16:47:55 +00:00
}
2014-03-15 16:59:03 +00:00
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
2014-04-19 17:41:29 +00:00
if ( ( size_t ) ( p - gifframe_data ) + gifbwr_bufsize + 1 > = gifframe_size )
2014-03-15 16:59:03 +00:00
{
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 ;
2020-11-07 09:32:25 +00:00
gif_prevframetime = I_GetPreciseTime ( ) ;
2014-03-15 16:59:03 +00:00
}
// ========================
// !!! PUBLIC FUNCTIONS !!!
// ========================
//
// GIF_open
// opens a new file for writing.
//
INT32 GIF_open ( const char * filename )
{
gif_out = fopen ( filename , " wb " ) ;
if ( ! gif_out )
return 0 ;
gif_optimize = ( ! ! cv_gif_optimize . value ) ;
gif_downscale = ( ! ! cv_gif_downscale . value ) ;
2020-11-07 09:49:21 +00:00
gif_dynamicdelay = ( UINT8 ) cv_gif_dynamicdelay . value ;
2020-01-29 16:47:55 +00:00
gif_localcolortable = ( ! ! cv_gif_localcolortable . value ) ;
2020-01-29 20:31:27 +00:00
gif_colorprofile = ( ! ! cv_screenshot_colorprofile . value ) ;
gif_headerpalette = GIF_getpalette ( 0 ) ;
2019-12-17 18:37:43 +00:00
2014-03-15 16:59:03 +00:00
GIF_headwrite ( ) ;
gif_frames = 0 ;
2020-11-07 09:32:25 +00:00
gif_prevframetime = I_GetPreciseTime ( ) ;
2020-10-16 22:06:13 +00:00
gif_delayus = 0 ;
2014-03-15 16:59:03 +00:00
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