📄 video.c
字号:
int shortest = -1;
for ( i = 0; i < 4; i++ )
{
float d;
if ( ( d = BTCDistanceSquared( inBlock[blockY][blockX], colorLine[i] ) ) < distance )
{
distance = d;
shortest = i;
}
}
error += distance;
//
// if bestError is not -1 then that means this is a speculative quantization
//
if ( bestError != -1 )
{
if ( error > bestError )
return error;
}
btcQuantizedBlock[blockY][blockX] = shortest;
}
}
return error;
}
/*
** float BTCCompressBlock
*/
static float BTCCompressBlock( float inBlock[4][4][3], unsigned long out[2] )
{
int i;
int btcQuantizedBlock[4][4]; // values should be [0..3]
unsigned long encodedEndPoints, encodedBitmap;
unsigned int endPoints[2][2]; // endPoints[0] = color start, endPoints[1] = color end
int blockY, blockX;
float error = 0;
float bestError = 10000000000;
unsigned int bestEndPoints[2][2];
#if 0
//
// find the "ideal" end points for the color vector
//
BTCFindEndpoints( inBlock, endPoints );
error = BTCQuantizeBlock( inBlock, endPoints, btcQuantizedBlock );
memcpy( bestEndPoints, endPoints, sizeof( bestEndPoints ) );
#else
for ( blockY = 0; blockY < 4; blockY++ )
{
for ( blockX = 0; blockX < 4; blockX++ )
{
int x2, y2;
for ( y2 = 0; y2 < 4; y2++ )
{
for ( x2 = 0; x2 < 4; x2++ )
{
if ( ( x2 == blockX ) && ( y2 == blockY ) )
continue;
endPoints[0][0] = blockX;
endPoints[0][1] = blockY;
endPoints[1][0] = x2;
endPoints[1][1] = y2;
error = BTCQuantizeBlock( inBlock, endPoints, btcQuantizedBlock, -1 ); //bestError );
if ( error < bestError )
{
bestError = error;
memcpy( bestEndPoints, endPoints, sizeof( bestEndPoints ) );
}
}
}
}
}
error = BTCQuantizeBlock( inBlock, bestEndPoints, btcQuantizedBlock, -1.0f );
#endif
//
// encode the results
//
encodedBitmap = 0;
for ( blockY = 0; blockY < 4; blockY++ )
{
for ( blockX = 0; blockX < 4; blockX++ )
{
int shift = ( blockX + blockY * 4 ) * 2;
encodedBitmap |= btcQuantizedBlock[blockY][blockX] << shift;
}
}
//
// encode endpoints
//
encodedEndPoints = 0;
for ( i = 0; i < 2; i++ )
{
int iR, iG, iB;
iR = ( ( int ) inBlock[bestEndPoints[i][1]][bestEndPoints[i][0]][0] );
if ( iR > 255 )
iR = 255;
else if ( iR < 0 )
iR = 0;
iR >>= 3;
iG = ( ( int ) inBlock[bestEndPoints[i][1]][bestEndPoints[i][0]][1] );
if ( iG > 255 )
iG = 255;
else if ( iG < 0 )
iG = 0;
iG >>= 2;
iB = ( ( int ) inBlock[bestEndPoints[i][1]][bestEndPoints[i][0]][2] );
if ( iB > 255 )
iB = 255;
else if ( iB < 0 )
iB = 0;
iB >>= 3;
encodedEndPoints |= ( ( ( iR << 11 ) | ( iG << 5 ) | ( iB ) ) << ( i * 16 ) );
}
//
// store
//
out[0] = encodedBitmap;
out[1] = encodedEndPoints;
return error;
}
/*
** void BTCDecompressFrame
*/
static void BTCDecompressFrame( unsigned long *src, unsigned char *dst )
{
int x, y;
int iR, iG, iB;
int dstX, dstY;
float colorStart[3], colorEnd[3];
unsigned char colorRampABGR[4][4];
unsigned encoded;
memset( colorRampABGR, 0xff, sizeof( colorRampABGR ) );
for ( y = 0; y < s_resample_height / 4; y++ )
{
for ( x = 0; x < s_resample_width / 4; x++ )
{
unsigned colorStartPacked = src[(y*s_resample_width/4 + x)*2 + 1] & 0xffff;
unsigned colorEndPacked = src[(y*s_resample_width/4 + x)*2 + 1] >> 16;
//
// grab the end points
// 0 = color start
// 1 = color end
//
iR = ( ( colorStartPacked >> 11 ) & ( ( 1 << 5 ) - 1 ) );
iR = ( iR << 3 ) | ( iR >> 2 );
iG = ( ( colorStartPacked >> 5 ) & ( ( 1 << 6 ) - 1 ) );
iG = ( iG << 2 ) | ( iG >> 4 );
iB = ( ( colorStartPacked ) & ( ( 1 << 5 ) - 1 ) );
iB = ( iB << 3 ) | ( iB >> 2 );
colorStart[0] = iR;
colorStart[1] = iG;
colorStart[2] = iB;
colorRampABGR[0][0] = iR;
colorRampABGR[0][1] = iG;
colorRampABGR[0][2] = iB;
iR = ( ( colorEndPacked >> 11 ) & ( ( 1 << 5 ) - 1 ) );
iR = ( iR << 3 ) | ( iR >> 2 );
iG = ( ( colorEndPacked >> 5 ) & ( ( 1 << 6 ) - 1 ) );
iG = ( iG << 2 ) | ( iG >> 4 );
iB = ( colorEndPacked & ( ( 1 << 5 ) - 1 ) );
iB = ( iB << 3 ) | ( iB >> 2 );
colorEnd[0] = iR;
colorEnd[1] = iG;
colorEnd[2] = iB;
colorRampABGR[3][0] = iR;
colorRampABGR[3][1] = iG;
colorRampABGR[3][2] = iB;
//
// compute this block's color ramp
// FIXME: This needs to be reversed on big-endian machines
//
colorRampABGR[1][0] = colorStart[0] * 0.66f + colorEnd[0] * 0.33f;
colorRampABGR[1][1] = colorStart[1] * 0.66f + colorEnd[1] * 0.33f;
colorRampABGR[1][2] = colorStart[2] * 0.66f + colorEnd[2] * 0.33f;
colorRampABGR[2][0] = colorStart[0] * 0.33f + colorEnd[0] * 0.66f;
colorRampABGR[2][1] = colorStart[1] * 0.33f + colorEnd[1] * 0.66f;
colorRampABGR[2][2] = colorStart[2] * 0.33f + colorEnd[2] * 0.66f;
//
// decode the color data
// information is encoded in 2-bit pixels, with low order bits corresponding
// to upper left pixels. These 2-bit values are indexed into the block's
// computer color ramp.
//
encoded = src[(y*s_resample_width/4 + x)*2 + 0];
for ( dstY = 0; dstY < 4; dstY++ )
{
for ( dstX = 0; dstX < 4; dstX++ )
{
memcpy( &dst[(y*4+dstY)*s_resample_width*4+x*4*4+dstX*4], colorRampABGR[encoded&3], sizeof( colorRampABGR[0] ) );
encoded >>= 2;
}
}
}
}
}
/*
** BTCCompressFrame
**
** Perform a BTC compression using a 2-bit encoding at each pixel. This
** compression method is performed by decomposing the incoming image into
** a sequence of 4x4 blocks. At each block two color values are computed
** that define the endpoints of a vector in color space that represent
** the two colors "farthest apart".
*/
static float BTCCompressFrame( unsigned char *src, unsigned long *dst )
{
int x, y;
int bX, bY;
float btcBlock[4][4][3];
float error = 0;
for ( y = 0; y < s_resample_height / 4; y++ )
{
for ( x = 0; x < s_resample_width / 4; x++ )
{
//
// fill in the BTC block with raw values
//
for ( bY = 0; bY < 4; bY++ )
{
for ( bX = 0; bX < 4; bX++ )
{
btcBlock[bY][bX][0] = src[(y*4+bY)*s_resample_width*4 + (x*4+bX)*4 + 0];
btcBlock[bY][bX][1] = src[(y*4+bY)*s_resample_width*4 + (x*4+bX)*4 + 1];
btcBlock[bY][bX][2] = src[(y*4+bY)*s_resample_width*4 + (x*4+bX)*4 + 2];
}
}
error += BTCCompressBlock( btcBlock, &dst[(y*s_resample_width/4+x)*2] );
}
}
return error / ( ( s_resample_width / 4 ) * ( s_resample_height / 4 ) );
}
/*
===================
LoadFrame
===================
*/
cblock_t LoadFrame (char *base, int frame, int digits, byte **palette)
{
int ten3, ten2, ten1, ten0;
cblock_t in;
int width, height;
char name[1024];
FILE *f;
in.data = NULL;
in.count = -1;
ten3 = frame/1000;
ten2 = (frame-ten3*1000)/100;
ten1 = (frame-ten3*1000-ten2*100)/10;
ten0 = frame%10;
if (digits == 4)
sprintf (name, "%svideo/%s/%s%i%i%i%i.tga", gamedir, base, base, ten3, ten2, ten1, ten0);
else
sprintf (name, "%svideo/%s/%s%i%i%i.tga", gamedir, base, base, ten2, ten1, ten0);
f = fopen(name, "rb");
if (!f)
{
in.data = NULL;
return in;
}
fclose (f);
printf ("%s", name);
LoadTGA( name, ( unsigned char ** ) &in.data, &width, &height );
if ( palette )
*palette = 0;
// Load256Image (name, &in.data, palette, &width, &height);
in.count = width*height;
in.width = width;
in.height = height;
// FIXME: map 0 and 255!
#if 0
// rle compress
rle = RLE(in);
free (in.data);
return rle;
#endif
return in;
}
/*
===============
Cmd_Video
video <directory> <framedigits>
===============
*/
void Cmd_Video (void)
{
float sumError = 0, error = 0, maxError = 0;
char savename[1024];
char name[1024];
FILE *output;
int startframe, frame;
int width, height;
int i;
int digits;
int minutes;
float fseconds;
int remSeconds;
cblock_t in;
unsigned char *resampled;
unsigned long *compressed;
clock_t start, stop;
GetToken (qfalse);
strcpy (s_base, token);
if (g_release)
{
// sprintf (savename, "video/%s.cin", token);
// ReleaseFile (savename);
return;
}
GetToken( qfalse );
strcpy( s_output_base, token );
GetToken (qfalse);
digits = atoi(token);
GetToken( qfalse );
if ( !strcmp( token, "btc" ) )
{
s_compression_method = BTC_COMPRESSION;
printf( "Compression: BTC\n" );
}
else if ( !strcmp( token, "uc" ) )
{
s_compression_method = UNCOMPRESSED;
printf( "Compression: none\n" );
}
else
{
Error( "Uknown compression method '%s'\n", token );
}
GetToken( qfalse );
s_resample_width = atoi( token );
GetToken( qfalse );
s_resample_height = atoi( token );
resampled = malloc( sizeof( unsigned char ) * 4 * s_resample_width * s_resample_height );
compressed = malloc( sizeof( long ) * 2 * ( s_resample_width / 4 ) * ( s_resample_height / 4 ) );
printf( "Resample width: %d\n", s_resample_width );
printf( "Resample height: %d\n", s_resample_height );
// optionally skip frames
if (TokenAvailable ())
{
GetToken (qfalse);
startframe = atoi(token);
}
else
startframe=0;
sprintf (savename, "%svideo/%s.%s", writedir, s_output_base, CIN_EXTENSION );
// load the entire sound wav file if present
LoadSoundtrack ();
if (digits == 4)
sprintf (name, "%svideo/%s/%s0000.tga", gamedir, s_base, s_base);
else
sprintf (name, "%svideo/%s/%s000.tga", gamedir, s_base, s_base);
printf ("%s\n", name);
LoadTGA( name, NULL, &width, &height);
output = fopen (savename, "wb");
if (!output)
Error ("Can't open %s", savename);
// write header info
i = LittleLong( CIN_SIGNATURE );
fwrite (&i, 4, 1, output );
i = LittleLong (s_resample_width);
fwrite (&i, 4, 1, output);
i = LittleLong (s_resample_height);
fwrite (&i, 4, 1, output);
i = LittleLong (s_wavinfo.rate);
fwrite (&i, 4, 1, output);
i = LittleLong (s_wavinfo.width);
fwrite (&i, 4, 1, output);
i = LittleLong (s_wavinfo.channels);
fwrite (&i, 4, 1, output);
i = LittleLong ( s_compression_method );
fwrite (&i, 4, 1, output );
start = clock();
// perform compression on a per frame basis
for ( frame=startframe ; ; frame++)
{
printf ("%02d: ", frame);
in = LoadFrame (s_base, frame, digits, 0 );
if (!in.data)
break;
ResampleFrame( &in, ( unsigned char * ) resampled, BOX_FILTER, s_resample_width, s_resample_height );
if ( s_compression_method == UNCOMPRESSED )
{
printf( "\n" );
fwrite( resampled, 1, sizeof( unsigned char ) * s_resample_width * s_resample_height * 4, output );
#if OUTPUT_TGAS
{
int x, y;
char buffer[1000];
for ( y = 0; y < s_resample_height/2; y++ )
{
for ( x = 0; x < s_resample_width; x++ )
{
unsigned char tmp[4];
tmp[0] = resampled[(s_resample_height-1-y)*s_resample_width*4 + x*4 + 0];
tmp[1] = resampled[(s_resample_height-1-y)*s_resample_width*4 + x*4 + 1];
tmp[2] = resampled[(s_resample_height-1-y)*s_resample_width*4 + x*4 + 2];
tmp[3] = resampled[(s_resample_height-1-y)*s_resample_width*4 + x*4 + 3];
resampled[(s_resample_height-1-y)*s_resample_width*4 + x*4 + 0] = resampled[y*s_resample_width*4 + x*4 + 0];
resampled[(s_resample_height-1-y)*s_resample_width*4 + x*4 + 1] = resampled[y*s_resample_width*4 + x*4 + 1];
resampled[(s_resample_height-1-y)*s_resample_width*4 + x*4 + 2] = resampled[y*s_resample_width*4 + x*4 + 2];
resampled[(s_resample_height-1-y)*s_resample_width*4 + x*4 + 3] = resampled[y*s_resample_width*4 + x*4 + 3];
resampled[y*s_resample_width*4 + x*4 + 0] = tmp[0];
resampled[y*s_resample_width*4 + x*4 + 1] = tmp[1];
resampled[y*s_resample_width*4 + x*4 + 2] = tmp[2];
resampled[y*s_resample_width*4 + x*4 + 3] = tmp[3];
}
}
sprintf( buffer, "%svideo/%s/uc%04d.tga", gamedir, s_base, frame );
WriteTGA( buffer, resampled, s_resample_width, s_resample_height );
}
#endif
}
else if ( s_compression_method == BTC_COMPRESSION )
{
error = BTCCompressFrame( resampled, compressed );
sumError += error;
if ( error > maxError )
maxError = error;
printf( " (error = %f)\n", error );
fwrite( compressed, 1, 2 * sizeof( long ) * ( s_resample_width / 4 ) * ( s_resample_height / 4 ), output );
#if OUTPUT_TGAS
{
int x, y;
unsigned char *uncompressed;
char buffer[1000];
uncompressed = malloc( sizeof( unsigned char ) * 4 * s_resample_width * s_resample_height );
BTCDecompressFrame( compressed, uncompressed );
for ( y = 0; y < s_resample_height/2; y++ )
{
for ( x = 0; x < s_resample_width; x++ )
{
unsigned char tmp[4];
tmp[0] = uncompressed[(s_resample_height-1-y)*s_resample_width*4 + x*4 + 0];
tmp[1] = uncompressed[(s_resample_height-1-y)*s_resample_width*4 + x*4 + 1];
tmp[2] = uncompressed[(s_resample_height-1-y)*s_resample_width*4 + x*4 + 2];
tmp[3] = uncompressed[(s_resample_height-1-y)*s_resample_width*4 + x*4 + 3];
uncompressed[(s_resample_height-1-y)*s_resample_width*4 + x*4 + 0] = uncompressed[y*s_resample_width*4 + x*4 + 0];
uncompressed[(s_resample_height-1-y)*s_resample_width*4 + x*4 + 1] = uncompressed[y*s_resample_width*4 + x*4 + 1];
uncompressed[(s_resample_height-1-y)*s_resample_width*4 + x*4 + 2] = uncompressed[y*s_resample_width*4 + x*4 + 2];
uncompressed[(s_resample_height-1-y)*s_resample_width*4 + x*4 + 3] = uncompressed[y*s_resample_width*4 + x*4 + 3];
uncompressed[y*s_resample_width*4 + x*4 + 0] = tmp[0];
uncompressed[y*s_resample_width*4 + x*4 + 1] = tmp[1];
uncompressed[y*s_resample_width*4 + x*4 + 2] = tmp[2];
uncompressed[y*s_resample_width*4 + x*4 + 3] = tmp[3];
}
}
sprintf( buffer, "%svideo/%s/btc%04d.tga", gamedir, s_base, frame );
WriteTGA( buffer, uncompressed, s_resample_width, s_resample_height );
free( uncompressed );
}
#endif
}
WriteSound( output, frame );
free (in.data);
}
stop = clock();
printf ("\n");
printf ("Total size: %i\n", ftell( output ) );
printf ("Average error: %f\n", sumError / ( frame - startframe ) );
printf ("Max error: %f\n", maxError );
fseconds = ( stop - start ) / 1000.0f;
minutes = fseconds / 60;
remSeconds = fseconds - minutes * 60;
printf ("Total time: %d s (%d m %d s)\n", ( int ) fseconds, minutes, remSeconds );
printf ("Time/frame: %.2f seconds\n", fseconds / ( frame - startframe ) );
fclose (output);
if ( s_soundtrack )
{
free( s_soundtrack );
s_soundtrack = 0;
}
}
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -