mirror of
https://github.com/UberGames/GtkRadiant.git
synced 2024-11-29 07:02:26 +00:00
1127 lines
27 KiB
C
1127 lines
27 KiB
C
/*
|
|
Copyright (C) 1999-2007 id Software, Inc. and contributors.
|
|
For a list of contributors, see the accompanying CONTRIBUTORS file.
|
|
|
|
This file is part of GtkRadiant.
|
|
|
|
GtkRadiant is free software; you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation; either version 2 of the License, or
|
|
(at your option) any later version.
|
|
|
|
GtkRadiant is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with GtkRadiant; if not, write to the Free Software
|
|
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
|
*/
|
|
|
|
#include <assert.h>
|
|
#include "q3data.h"
|
|
|
|
static int s_resample_width = 256;
|
|
static int s_resample_height = 256;
|
|
|
|
#define OUTPUT_TGAS 1
|
|
|
|
#define UNCOMPRESSED 0
|
|
#define BTC_COMPRESSION 1
|
|
|
|
static int s_compression_method = BTC_COMPRESSION;
|
|
|
|
static const char *CIN_EXTENSION = "cn2";
|
|
static const int CIN_SIGNATURE = ( 'C' << 24 ) | ( 'I' << 16 ) | ( 'N' << 8 ) | ( '2' );
|
|
|
|
static byte *s_soundtrack;
|
|
static char s_base[32];
|
|
static char s_output_base[32];
|
|
|
|
/*
|
|
===============================================================================
|
|
|
|
WAV loading
|
|
|
|
===============================================================================
|
|
*/
|
|
|
|
typedef struct
|
|
{
|
|
int rate;
|
|
int width;
|
|
int channels;
|
|
int loopstart;
|
|
int samples;
|
|
int dataofs; // chunk starts this many bytes from file start
|
|
} wavinfo_t;
|
|
|
|
|
|
byte *data_p;
|
|
byte *iff_end;
|
|
byte *last_chunk;
|
|
byte *iff_data;
|
|
int iff_chunk_len;
|
|
|
|
|
|
static int s_samplecounts[0x10000];
|
|
static wavinfo_t s_wavinfo;
|
|
|
|
short GetLittleShort( void ){
|
|
short val = 0;
|
|
val = *data_p;
|
|
val = val + ( *( data_p + 1 ) << 8 );
|
|
data_p += 2;
|
|
return val;
|
|
}
|
|
|
|
int GetLittleLong( void ){
|
|
int val = 0;
|
|
val = *data_p;
|
|
val = val + ( *( data_p + 1 ) << 8 );
|
|
val = val + ( *( data_p + 2 ) << 16 );
|
|
val = val + ( *( data_p + 3 ) << 24 );
|
|
data_p += 4;
|
|
return val;
|
|
}
|
|
|
|
void FindNextChunk( char *name ){
|
|
while ( 1 )
|
|
{
|
|
data_p = last_chunk;
|
|
|
|
if ( data_p >= iff_end ) { // didn't find the chunk
|
|
data_p = NULL;
|
|
return;
|
|
}
|
|
|
|
data_p += 4;
|
|
iff_chunk_len = GetLittleLong();
|
|
if ( iff_chunk_len < 0 ) {
|
|
data_p = NULL;
|
|
return;
|
|
}
|
|
// if (iff_chunk_len > 1024*1024)
|
|
// Sys_Error ("FindNextChunk: %i length is past the 1 meg sanity limit", iff_chunk_len);
|
|
data_p -= 8;
|
|
last_chunk = data_p + 8 + ( ( iff_chunk_len + 1 ) & ~1 );
|
|
if ( !strncmp( data_p, name, 4 ) ) {
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
void FindChunk( char *name ){
|
|
last_chunk = iff_data;
|
|
FindNextChunk( name );
|
|
}
|
|
|
|
|
|
void DumpChunks( void ){
|
|
char str[5];
|
|
|
|
str[4] = 0;
|
|
data_p = iff_data;
|
|
do
|
|
{
|
|
memcpy( str, data_p, 4 );
|
|
data_p += 4;
|
|
iff_chunk_len = GetLittleLong();
|
|
printf( "0x%x : %s (%d)\n", (int)( data_p - 4 ), str, iff_chunk_len );
|
|
data_p += ( iff_chunk_len + 1 ) & ~1;
|
|
} while ( data_p < iff_end );
|
|
}
|
|
|
|
/*
|
|
============
|
|
GetWavinfo
|
|
============
|
|
*/
|
|
wavinfo_t GetWavinfo( char *name, byte *wav, int wavlength ){
|
|
wavinfo_t info;
|
|
int i;
|
|
int format;
|
|
int samples;
|
|
|
|
memset( &info, 0, sizeof( info ) );
|
|
|
|
if ( !wav ) {
|
|
return info;
|
|
}
|
|
|
|
iff_data = wav;
|
|
iff_end = wav + wavlength;
|
|
|
|
// find "RIFF" chunk
|
|
FindChunk( "RIFF" );
|
|
if ( !( data_p && !strncmp( data_p + 8, "WAVE", 4 ) ) ) {
|
|
printf( "Missing RIFF/WAVE chunks\n" );
|
|
return info;
|
|
}
|
|
|
|
// get "fmt " chunk
|
|
iff_data = data_p + 12;
|
|
// DumpChunks ();
|
|
|
|
FindChunk( "fmt " );
|
|
if ( !data_p ) {
|
|
printf( "Missing fmt chunk\n" );
|
|
return info;
|
|
}
|
|
data_p += 8;
|
|
format = GetLittleShort();
|
|
if ( format != 1 ) {
|
|
printf( "Microsoft PCM format only\n" );
|
|
return info;
|
|
}
|
|
|
|
info.channels = GetLittleShort();
|
|
info.rate = GetLittleLong();
|
|
data_p += 4 + 2;
|
|
info.width = GetLittleShort() / 8;
|
|
|
|
// get cue chunk
|
|
FindChunk( "cue " );
|
|
if ( data_p ) {
|
|
data_p += 32;
|
|
info.loopstart = GetLittleLong();
|
|
// Com_Printf("loopstart=%d\n", sfx->loopstart);
|
|
|
|
// if the next chunk is a LIST chunk, look for a cue length marker
|
|
FindNextChunk( "LIST" );
|
|
if ( data_p ) {
|
|
if ( !strncmp( data_p + 28, "mark", 4 ) ) { // this is not a proper parse, but it works with cooledit...
|
|
data_p += 24;
|
|
i = GetLittleLong(); // samples in loop
|
|
info.samples = info.loopstart + i;
|
|
}
|
|
}
|
|
}
|
|
else{
|
|
info.loopstart = -1;
|
|
}
|
|
|
|
// find data chunk
|
|
FindChunk( "data" );
|
|
if ( !data_p ) {
|
|
printf( "Missing data chunk\n" );
|
|
return info;
|
|
}
|
|
|
|
data_p += 4;
|
|
samples = GetLittleLong();
|
|
|
|
if ( info.samples ) {
|
|
if ( samples < info.samples ) {
|
|
Error( "Sound %s has a bad loop length", name );
|
|
}
|
|
}
|
|
else{
|
|
info.samples = samples;
|
|
}
|
|
|
|
info.dataofs = data_p - wav;
|
|
|
|
return info;
|
|
}
|
|
|
|
//=====================================================================
|
|
|
|
/*
|
|
==============
|
|
LoadSoundtrack
|
|
==============
|
|
*/
|
|
void LoadSoundtrack( void ){
|
|
char name[1024];
|
|
FILE *f;
|
|
int len;
|
|
int i, val, j;
|
|
|
|
s_soundtrack = NULL;
|
|
sprintf( name, "%svideo/%s/%s.wav", gamedir, s_base, s_base );
|
|
printf( "WAV: %s\n", name );
|
|
f = fopen( name, "rb" );
|
|
if ( !f ) {
|
|
printf( "no soundtrack for %s\n", s_base );
|
|
return;
|
|
}
|
|
len = Q_filelength( f );
|
|
s_soundtrack = malloc( len );
|
|
fread( s_soundtrack, 1, len, f );
|
|
fclose( f );
|
|
|
|
s_wavinfo = GetWavinfo( name, s_soundtrack, len );
|
|
|
|
// count samples for compression
|
|
memset( s_samplecounts, 0, sizeof( s_samplecounts ) );
|
|
|
|
j = s_wavinfo.samples / 2;
|
|
for ( i = 0 ; i < j ; i++ )
|
|
{
|
|
val = ( (unsigned short *)( s_soundtrack + s_wavinfo.dataofs ) )[i];
|
|
s_samplecounts[val]++;
|
|
}
|
|
val = 0;
|
|
for ( i = 0 ; i < 0x10000 ; i++ )
|
|
if ( s_samplecounts[i] ) {
|
|
val++;
|
|
}
|
|
|
|
printf( "%i unique sample values\n", val );
|
|
}
|
|
|
|
/*
|
|
==================
|
|
WriteSound
|
|
==================
|
|
*/
|
|
void WriteSound( FILE *output, int frame ){
|
|
int start, end;
|
|
int count;
|
|
int empty = 0;
|
|
int i;
|
|
int sample;
|
|
int width;
|
|
|
|
width = s_wavinfo.width * s_wavinfo.channels;
|
|
|
|
start = frame * s_wavinfo.rate / 14;
|
|
end = ( frame + 1 ) * s_wavinfo.rate / 14;
|
|
count = end - start;
|
|
|
|
for ( i = 0 ; i < count ; i++ )
|
|
{
|
|
sample = start + i;
|
|
if ( sample > s_wavinfo.samples || !s_soundtrack ) {
|
|
fwrite( &empty, 1, width, output );
|
|
}
|
|
else{
|
|
fwrite( s_soundtrack + s_wavinfo.dataofs + sample * width, 1, width,output );
|
|
}
|
|
}
|
|
}
|
|
|
|
//==========================================================================
|
|
|
|
static float s_resampleXRatio;
|
|
static float s_resampleYRatio;
|
|
|
|
static void BoxFilterHorizontalElements( unsigned char *dst, unsigned char *src, float s0, float s1 ){
|
|
float w;
|
|
float rSum = 0, gSum = 0, bSum = 0;
|
|
float x = s0;
|
|
float sumWeight = 0;
|
|
|
|
for ( x = s0; x < s1; x++, src += 4 )
|
|
{
|
|
if ( x == s0 ) {
|
|
w = ( int ) ( s0 + 1 ) - x;
|
|
}
|
|
else if ( x + 1 >= s1 ) {
|
|
w = s1 - ( int ) x;
|
|
}
|
|
else
|
|
{
|
|
w = 1.0f;
|
|
}
|
|
|
|
rSum += src[0] * w;
|
|
gSum += src[1] * w;
|
|
bSum += src[2] * w;
|
|
sumWeight += w;
|
|
}
|
|
|
|
rSum /= sumWeight;
|
|
gSum /= sumWeight;
|
|
bSum /= sumWeight;
|
|
|
|
dst[0] = ( unsigned char ) ( rSum + 0.5 );
|
|
dst[1] = ( unsigned char ) ( gSum + 0.5 );
|
|
dst[2] = ( unsigned char ) ( bSum + 0.5 );
|
|
}
|
|
|
|
static void BoxFilterVerticalElements( unsigned char *dst, // destination of the filter process
|
|
unsigned char *src, // source pixels
|
|
int srcStep, // stride of the source pixels
|
|
float s0, float s1 ){
|
|
float w;
|
|
float rSum = 0, gSum = 0, bSum = 0;
|
|
float y = s0;
|
|
float sumWeight = 0;
|
|
|
|
for ( y = s0; y < ( int ) ( s1 + 1 ) ; y++, src += srcStep )
|
|
{
|
|
if ( y == s0 ) {
|
|
w = ( int ) ( s0 + 1 ) - y;
|
|
}
|
|
else if ( y + 1 >= s1 ) {
|
|
w = s1 - ( int ) y;
|
|
}
|
|
else
|
|
{
|
|
w = 1.0f;
|
|
}
|
|
|
|
rSum += src[0] * w;
|
|
gSum += src[1] * w;
|
|
bSum += src[2] * w;
|
|
sumWeight += w;
|
|
}
|
|
|
|
rSum /= sumWeight;
|
|
gSum /= sumWeight;
|
|
bSum /= sumWeight;
|
|
|
|
dst[0] = ( unsigned char ) ( rSum + 0.5 );
|
|
dst[1] = ( unsigned char ) ( gSum + 0.5 );
|
|
dst[2] = ( unsigned char ) ( bSum + 0.5 );
|
|
dst[3] = 0xff;
|
|
|
|
}
|
|
|
|
static void BoxFilterRow( unsigned char *dstStart, cblock_t *in, int dstRow, int rowWidth ){
|
|
int i;
|
|
unsigned char *indata = ( unsigned char * ) in->data;
|
|
|
|
indata += 4 * dstRow * in->width;
|
|
|
|
for ( i = 0; i < rowWidth; i++ )
|
|
{
|
|
float c0 = i * s_resampleXRatio;
|
|
float c1 = ( i + 1 ) * s_resampleXRatio;
|
|
|
|
BoxFilterHorizontalElements( &dstStart[i * 4], &indata[( ( int ) c0 ) * 4], c0, c1 );
|
|
}
|
|
}
|
|
|
|
static void BoxFilterColumn( unsigned char *dstStart, unsigned char *srcStart, int dstCol, int dstRowWidth, int dstColHeight, int srcRowWidthInPels ){
|
|
float c0, c1;
|
|
int i;
|
|
|
|
for ( i = 0; i < dstColHeight; i++ )
|
|
{
|
|
c0 = i * s_resampleYRatio;
|
|
c1 = ( i + 1 ) * s_resampleYRatio;
|
|
|
|
BoxFilterVerticalElements( &dstStart[i * 4 * dstRowWidth], &srcStart[(int)c0 * srcRowWidthInPels * 4], srcRowWidthInPels * 4, c0, c1 );
|
|
}
|
|
}
|
|
|
|
#define DROP_SAMPLE 0
|
|
#define BOX_FILTER 1
|
|
|
|
static void ResampleFrame( cblock_t *in, unsigned char *out, int method, int outWidth, int outHeight ){
|
|
int row, column;
|
|
unsigned char *indata = ( unsigned char * ) in->data;
|
|
|
|
s_resampleXRatio = in->width / ( float ) outWidth;
|
|
s_resampleYRatio = in->height / ( float ) outHeight;
|
|
|
|
if ( method == DROP_SAMPLE ) {
|
|
for ( row = 0; row < outHeight; row++ )
|
|
{
|
|
int r = ( int ) ( row * s_resampleYRatio );
|
|
|
|
for ( column = 0; column < outWidth; column++ )
|
|
{
|
|
int c = ( int ) ( column * s_resampleXRatio );
|
|
|
|
out[( row * outWidth + column ) * 4 + 0] = indata[( r * in->width + c ) * 4 + 0];
|
|
out[( row * outWidth + column ) * 4 + 1] = indata[( r * in->width + c ) * 4 + 1];
|
|
out[( row * outWidth + column ) * 4 + 2] = indata[( r * in->width + c ) * 4 + 2];
|
|
out[( row * outWidth + column ) * 4 + 3] = 0xff;
|
|
}
|
|
}
|
|
}
|
|
else if ( method == BOX_FILTER ) {
|
|
unsigned char intermediate[1024 * 1024 * 4];
|
|
|
|
assert( in->height <= 1024 );
|
|
assert( in->width <= 1024 );
|
|
|
|
//
|
|
// filter our M x N source image into a RESAMPLE_WIDTH x N horizontally filtered image
|
|
//
|
|
for ( row = 0; row < in->height; row++ )
|
|
{
|
|
BoxFilterRow( &intermediate[row * 4 * outWidth], in, row, outWidth );
|
|
}
|
|
|
|
//
|
|
// filter our RESAMPLE_WIDTH x N horizontally filtered image into a RESAMPLE_WIDTH x RESAMPLE_HEIGHT filtered image
|
|
//
|
|
for ( column = 0; column < outWidth; column++ )
|
|
{
|
|
BoxFilterColumn( &out[column * 4], &intermediate[column * 4], column, outWidth, outHeight, s_resample_width );
|
|
}
|
|
}
|
|
}
|
|
|
|
static float BTCDistanceSquared( float a[3], float b[3] ){
|
|
return ( b[0] - a[0] ) * ( b[0] - a[0] ) +
|
|
( b[1] - a[1] ) * ( b[1] - a[1] ) +
|
|
( b[2] - a[2] ) * ( b[2] - a[2] );
|
|
}
|
|
|
|
static void BTCFindEndpoints( float inBlock[4][4][3], unsigned int endPoints[2][2] ){
|
|
float longestDistance = -1;
|
|
|
|
int bX, bY;
|
|
|
|
//
|
|
// find the two points farthest from each other
|
|
//
|
|
for ( bY = 0; bY < 4; bY++ )
|
|
{
|
|
for ( bX = 0; bX < 4; bX++ )
|
|
{
|
|
int cX, cY;
|
|
float d;
|
|
|
|
//
|
|
// check the rest of the current row
|
|
//
|
|
for ( cX = bX + 1; cX < 4; cX++ )
|
|
{
|
|
if ( ( d = BTCDistanceSquared( inBlock[bY][bX], inBlock[bY][cX] ) ) > longestDistance ) {
|
|
longestDistance = d;
|
|
endPoints[0][0] = bX;
|
|
endPoints[0][1] = bY;
|
|
endPoints[1][0] = cX;
|
|
endPoints[1][1] = bY;
|
|
}
|
|
}
|
|
|
|
//
|
|
// check remaining rows and columns
|
|
//
|
|
for ( cY = bY + 1; cY < 4; cY++ )
|
|
{
|
|
for ( cX = 0; cX < 4; cX++ )
|
|
{
|
|
if ( ( d = BTCDistanceSquared( inBlock[bY][bX], inBlock[cY][cX] ) ) > longestDistance ) {
|
|
longestDistance = d;
|
|
endPoints[0][0] = bX;
|
|
endPoints[0][1] = bY;
|
|
endPoints[1][0] = cX;
|
|
endPoints[1][1] = cY;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static float BTCQuantizeBlock( float inBlock[4][4][3], unsigned long endPoints[2][2], int btcQuantizedBlock[4][4], float bestError ){
|
|
int i;
|
|
int blockY, blockX;
|
|
float dR, dG, dB;
|
|
float R, G, B;
|
|
float error = 0;
|
|
float colorLine[4][3];
|
|
|
|
//
|
|
// build the color line
|
|
//
|
|
dR = inBlock[endPoints[1][1]][endPoints[1][0]][0] -
|
|
inBlock[endPoints[0][1]][endPoints[0][0]][0];
|
|
dG = inBlock[endPoints[1][1]][endPoints[1][0]][1] -
|
|
inBlock[endPoints[0][1]][endPoints[0][0]][1];
|
|
dB = inBlock[endPoints[1][1]][endPoints[1][0]][2] -
|
|
inBlock[endPoints[0][1]][endPoints[0][0]][2];
|
|
|
|
dR *= 0.33f;
|
|
dG *= 0.33f;
|
|
dB *= 0.33f;
|
|
|
|
R = inBlock[endPoints[0][1]][endPoints[0][0]][0];
|
|
G = inBlock[endPoints[0][1]][endPoints[0][0]][1];
|
|
B = inBlock[endPoints[0][1]][endPoints[0][0]][2];
|
|
|
|
for ( i = 0; i < 4; i++ )
|
|
{
|
|
colorLine[i][0] = R;
|
|
colorLine[i][1] = G;
|
|
colorLine[i][2] = B;
|
|
|
|
R += dR;
|
|
G += dG;
|
|
B += dB;
|
|
}
|
|
|
|
//
|
|
// quantize each pixel into the appropriate range
|
|
//
|
|
for ( blockY = 0; blockY < 4; blockY++ )
|
|
{
|
|
for ( blockX = 0; blockX < 4; blockX++ )
|
|
{
|
|
float distance = 10000000000;
|
|
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;
|
|
}
|
|
}
|