mirror of
https://github.com/UberGames/GtkRadiant.git
synced 2024-11-29 23:22:23 +00:00
1266 lines
24 KiB
C
1266 lines
24 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 "qdata.h"
|
|
#include "inout.h"
|
|
|
|
byte *soundtrack;
|
|
char 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;
|
|
|
|
|
|
int samplecounts[0x10000];
|
|
|
|
wavinfo_t 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;
|
|
|
|
soundtrack = NULL;
|
|
sprintf( name, "%svideo/%s/%s.wav", gamedir, base, base );
|
|
printf( "%s\n", name );
|
|
f = fopen( name, "rb" );
|
|
if ( !f ) {
|
|
printf( "no soundtrack for %s\n", base );
|
|
return;
|
|
}
|
|
len = Q_filelength( f );
|
|
soundtrack = malloc( len );
|
|
fread( soundtrack, 1, len, f );
|
|
fclose( f );
|
|
|
|
wavinfo = GetWavinfo( name, soundtrack, len );
|
|
|
|
// count samples for compression
|
|
memset( samplecounts, 0, sizeof( samplecounts ) );
|
|
|
|
j = wavinfo.samples / 2;
|
|
for ( i = 0 ; i < j ; i++ )
|
|
{
|
|
val = ( (unsigned short *)( soundtrack + wavinfo.dataofs ) )[i];
|
|
samplecounts[val]++;
|
|
}
|
|
val = 0;
|
|
for ( i = 0 ; i < 0x10000 ; i++ )
|
|
if ( 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 = wavinfo.width * wavinfo.channels;
|
|
|
|
start = frame * wavinfo.rate / 14;
|
|
end = ( frame + 1 ) * wavinfo.rate / 14;
|
|
count = end - start;
|
|
|
|
for ( i = 0 ; i < count ; i++ )
|
|
{
|
|
sample = start + i;
|
|
if ( sample > wavinfo.samples || !soundtrack ) {
|
|
fwrite( &empty, 1, width, output );
|
|
}
|
|
else{
|
|
fwrite( soundtrack + wavinfo.dataofs + sample * width, 1, width,output );
|
|
}
|
|
}
|
|
}
|
|
|
|
//==========================================================================
|
|
|
|
/*
|
|
==================
|
|
MTF
|
|
==================
|
|
*/
|
|
cblock_t MTF( cblock_t in ){
|
|
int i, j, b, code;
|
|
byte *out_p;
|
|
int index[256];
|
|
cblock_t out;
|
|
|
|
out_p = out.data = malloc( in.count + 4 );
|
|
|
|
// write count
|
|
*out_p++ = in.count & 255;
|
|
*out_p++ = ( in.count >> 8 ) & 255;
|
|
*out_p++ = ( in.count >> 16 ) & 255;
|
|
*out_p++ = ( in.count >> 24 ) & 255;
|
|
|
|
for ( i = 0 ; i < 256 ; i++ )
|
|
index[i] = i;
|
|
|
|
for ( i = 0 ; i < in.count ; i++ )
|
|
{
|
|
b = in.data[i];
|
|
code = index[b];
|
|
*out_p++ = code;
|
|
|
|
// shuffle b indexes to 0
|
|
for ( j = 0 ; j < 256 ; j++ )
|
|
if ( index[j] < code ) {
|
|
index[j]++;
|
|
}
|
|
index[b] = 0;
|
|
}
|
|
|
|
out.count = out_p - out.data;
|
|
|
|
return out;
|
|
}
|
|
|
|
|
|
//==========================================================================
|
|
|
|
int bwt_size;
|
|
byte *bwt_data;
|
|
|
|
int bwtCompare( const void *elem1, const void *elem2 ){
|
|
int i;
|
|
int i1, i2;
|
|
int b1, b2;
|
|
|
|
i1 = *(int *)elem1;
|
|
i2 = *(int *)elem2;
|
|
|
|
for ( i = 0 ; i < bwt_size ; i++ )
|
|
{
|
|
b1 = bwt_data[i1];
|
|
b2 = bwt_data[i2];
|
|
if ( b1 < b2 ) {
|
|
return -1;
|
|
}
|
|
if ( b1 > b2 ) {
|
|
return 1;
|
|
}
|
|
if ( ++i1 == bwt_size ) {
|
|
i1 = 0;
|
|
}
|
|
if ( ++i2 == bwt_size ) {
|
|
i2 = 0;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
==================
|
|
BWT
|
|
==================
|
|
*/
|
|
cblock_t BWT( cblock_t in ){
|
|
int *sorted;
|
|
int i;
|
|
byte *out_p;
|
|
cblock_t out;
|
|
|
|
bwt_size = in.count;
|
|
bwt_data = in.data;
|
|
|
|
sorted = malloc( in.count * sizeof( *sorted ) );
|
|
for ( i = 0 ; i < in.count ; i++ )
|
|
sorted[i] = i;
|
|
qsort( sorted, in.count, sizeof( *sorted ), bwtCompare );
|
|
|
|
out_p = out.data = malloc( in.count + 8 );
|
|
|
|
// write count
|
|
*out_p++ = in.count & 255;
|
|
*out_p++ = ( in.count >> 8 ) & 255;
|
|
*out_p++ = ( in.count >> 16 ) & 255;
|
|
*out_p++ = ( in.count >> 24 ) & 255;
|
|
|
|
// write head index
|
|
for ( i = 0 ; i < in.count ; i++ )
|
|
if ( sorted[i] == 0 ) {
|
|
break;
|
|
}
|
|
*out_p++ = i & 255;
|
|
*out_p++ = ( i >> 8 ) & 255;
|
|
*out_p++ = ( i >> 16 ) & 255;
|
|
*out_p++ = ( i >> 24 ) & 255;
|
|
|
|
// write the L column
|
|
for ( i = 0 ; i < in.count ; i++ )
|
|
*out_p++ = in.data[( sorted[i] + in.count - 1 ) % in.count];
|
|
|
|
free( sorted );
|
|
|
|
out.count = out_p - out.data;
|
|
|
|
return out;
|
|
}
|
|
|
|
//==========================================================================
|
|
|
|
typedef struct hnode_s
|
|
{
|
|
int count;
|
|
qboolean used;
|
|
int children[2];
|
|
} hnode_t;
|
|
|
|
int numhnodes;
|
|
hnode_t hnodes[512];
|
|
unsigned charbits[256];
|
|
int charbitscount[256];
|
|
|
|
int SmallestNode( void ){
|
|
int i;
|
|
int best, bestnode;
|
|
|
|
best = 99999999;
|
|
bestnode = -1;
|
|
for ( i = 0 ; i < numhnodes ; i++ )
|
|
{
|
|
if ( hnodes[i].used ) {
|
|
continue;
|
|
}
|
|
if ( !hnodes[i].count ) {
|
|
continue;
|
|
}
|
|
if ( hnodes[i].count < best ) {
|
|
best = hnodes[i].count;
|
|
bestnode = i;
|
|
}
|
|
}
|
|
|
|
if ( bestnode == -1 ) {
|
|
return -1;
|
|
}
|
|
|
|
hnodes[bestnode].used = true;
|
|
return bestnode;
|
|
}
|
|
|
|
void BuildChars( int nodenum, unsigned bits, int bitcount ){
|
|
hnode_t *node;
|
|
|
|
if ( nodenum < 256 ) {
|
|
if ( bitcount > 32 ) {
|
|
Error( "bitcount > 32" );
|
|
}
|
|
charbits[nodenum] = bits;
|
|
charbitscount[nodenum] = bitcount;
|
|
return;
|
|
}
|
|
|
|
node = &hnodes[nodenum];
|
|
bits <<= 1;
|
|
BuildChars( node->children[0], bits, bitcount + 1 );
|
|
bits |= 1;
|
|
BuildChars( node->children[1], bits, bitcount + 1 );
|
|
}
|
|
|
|
|
|
/*
|
|
==================
|
|
Huffman
|
|
==================
|
|
*/
|
|
cblock_t Huffman( cblock_t in ){
|
|
int i;
|
|
hnode_t *node;
|
|
int outbits, c;
|
|
unsigned bits;
|
|
byte *out_p;
|
|
cblock_t out;
|
|
int max, maxchar;
|
|
|
|
// count
|
|
memset( hnodes, 0, sizeof( hnodes ) );
|
|
for ( i = 0 ; i < in.count ; i++ )
|
|
hnodes[in.data[i]].count++;
|
|
|
|
// normalize counts
|
|
max = 0;
|
|
maxchar = 0;
|
|
for ( i = 0 ; i < 256 ; i++ )
|
|
{
|
|
if ( hnodes[i].count > max ) {
|
|
max = hnodes[i].count;
|
|
maxchar = i;
|
|
}
|
|
}
|
|
if ( max == 0 ) {
|
|
Error( "Huffman: max == 0" );
|
|
}
|
|
|
|
for ( i = 0 ; i < 256 ; i++ )
|
|
{
|
|
hnodes[i].count = ( hnodes[i].count * 255 + max - 1 ) / max;
|
|
}
|
|
|
|
// build the nodes
|
|
numhnodes = 256;
|
|
while ( numhnodes != 511 )
|
|
{
|
|
node = &hnodes[numhnodes];
|
|
|
|
// pick two lowest counts
|
|
node->children[0] = SmallestNode();
|
|
if ( node->children[0] == -1 ) {
|
|
break; // no more
|
|
|
|
}
|
|
node->children[1] = SmallestNode();
|
|
if ( node->children[1] == -1 ) {
|
|
if ( node->children[0] != numhnodes - 1 ) {
|
|
Error( "Bad smallestnode" );
|
|
}
|
|
break;
|
|
}
|
|
node->count = hnodes[node->children[0]].count +
|
|
hnodes[node->children[1]].count;
|
|
numhnodes++;
|
|
}
|
|
|
|
BuildChars( numhnodes - 1, 0, 0 );
|
|
|
|
out_p = out.data = malloc( in.count * 2 + 1024 );
|
|
memset( out_p, 0, in.count * 2 + 1024 );
|
|
|
|
// write count
|
|
*out_p++ = in.count & 255;
|
|
*out_p++ = ( in.count >> 8 ) & 255;
|
|
*out_p++ = ( in.count >> 16 ) & 255;
|
|
*out_p++ = ( in.count >> 24 ) & 255;
|
|
|
|
// save out the 256 normalized counts so the tree can be recreated
|
|
for ( i = 0 ; i < 256 ; i++ )
|
|
*out_p++ = hnodes[i].count;
|
|
|
|
// write bits
|
|
outbits = 0;
|
|
for ( i = 0 ; i < in.count ; i++ )
|
|
{
|
|
c = charbitscount[in.data[i]];
|
|
bits = charbits[in.data[i]];
|
|
while ( c )
|
|
{
|
|
c--;
|
|
if ( bits & ( 1 << c ) ) {
|
|
out_p[outbits >> 3] |= 1 << ( outbits & 7 );
|
|
}
|
|
outbits++;
|
|
}
|
|
}
|
|
|
|
out_p += ( outbits + 7 ) >> 3;
|
|
|
|
out.count = out_p - out.data;
|
|
|
|
return out;
|
|
}
|
|
|
|
//==========================================================================
|
|
|
|
/*
|
|
==================
|
|
RLE
|
|
==================
|
|
*/
|
|
#define RLE_CODE 0xe8
|
|
#define RLE_TRIPPLE 0xe9
|
|
|
|
int rle_counts[256];
|
|
int rle_bytes[256];
|
|
|
|
cblock_t RLE( cblock_t in ){
|
|
int i;
|
|
byte *out_p;
|
|
int val;
|
|
int repeat;
|
|
cblock_t out;
|
|
|
|
out_p = out.data = malloc( in.count * 2 );
|
|
|
|
// write count
|
|
*out_p++ = in.count & 255;
|
|
*out_p++ = ( in.count >> 8 ) & 255;
|
|
*out_p++ = ( in.count >> 16 ) & 255;
|
|
*out_p++ = ( in.count >> 24 ) & 255;
|
|
|
|
for ( i = 0 ; i < in.count ; )
|
|
{
|
|
val = in.data[i];
|
|
rle_bytes[val]++;
|
|
repeat = 1;
|
|
i++;
|
|
while ( i < in.count && repeat < 255 && in.data[i] == val )
|
|
{
|
|
repeat++;
|
|
i++;
|
|
}
|
|
if ( repeat < 256 ) {
|
|
rle_counts[repeat]++;
|
|
}
|
|
if ( repeat > 3 || val == RLE_CODE ) {
|
|
*out_p++ = RLE_CODE;
|
|
*out_p++ = val;
|
|
*out_p++ = repeat;
|
|
}
|
|
else
|
|
{
|
|
while ( repeat-- )
|
|
*out_p++ = val;
|
|
}
|
|
}
|
|
|
|
out.count = out_p - out.data;
|
|
return out;
|
|
}
|
|
|
|
//==========================================================================
|
|
|
|
unsigned lzss_head[256];
|
|
unsigned lzss_next[0x20000];
|
|
|
|
/*
|
|
==================
|
|
LZSS
|
|
==================
|
|
*/
|
|
#define BACK_WINDOW 0x10000
|
|
#define BACK_BITS 16
|
|
#define FRONT_WINDOW 16
|
|
#define FRONT_BITS 4
|
|
cblock_t LZSS( cblock_t in ){
|
|
int i;
|
|
byte *out_p;
|
|
cblock_t out;
|
|
int val;
|
|
int j, start, max;
|
|
int bestlength, beststart;
|
|
int outbits;
|
|
|
|
if ( in.count >= sizeof( lzss_next ) / 4 ) {
|
|
Error( "LZSS: too big" );
|
|
}
|
|
|
|
memset( lzss_head, -1, sizeof( lzss_head ) );
|
|
|
|
out_p = out.data = malloc( in.count * 2 );
|
|
memset( out.data, 0, in.count * 2 );
|
|
|
|
// write count
|
|
*out_p++ = in.count & 255;
|
|
*out_p++ = ( in.count >> 8 ) & 255;
|
|
*out_p++ = ( in.count >> 16 ) & 255;
|
|
*out_p++ = ( in.count >> 24 ) & 255;
|
|
|
|
outbits = 0;
|
|
for ( i = 0 ; i < in.count ; )
|
|
{
|
|
val = in.data[i];
|
|
#if 1
|
|
// chained search
|
|
bestlength = 0;
|
|
beststart = 0;
|
|
|
|
max = FRONT_WINDOW;
|
|
if ( i + max > in.count ) {
|
|
max = in.count - i;
|
|
}
|
|
|
|
start = lzss_head[val];
|
|
while ( start != -1 && start >= i - BACK_WINDOW )
|
|
{
|
|
// count match length
|
|
for ( j = 0 ; j < max ; j++ )
|
|
if ( in.data[start + j] != in.data[i + j] ) {
|
|
break;
|
|
}
|
|
if ( j > bestlength ) {
|
|
bestlength = j;
|
|
beststart = start;
|
|
}
|
|
start = lzss_next[start];
|
|
}
|
|
|
|
#else
|
|
// slow simple search
|
|
// search for a match
|
|
max = FRONT_WINDOW;
|
|
if ( i + max > in.count ) {
|
|
max = in.count - i;
|
|
}
|
|
|
|
start = i - BACK_WINDOW;
|
|
if ( start < 0 ) {
|
|
start = 0;
|
|
}
|
|
bestlength = 0;
|
|
beststart = 0;
|
|
for ( ; start < i ; start++ )
|
|
{
|
|
if ( in.data[start] != val ) {
|
|
continue;
|
|
}
|
|
// count match length
|
|
for ( j = 0 ; j < max ; j++ )
|
|
if ( in.data[start + j] != in.data[i + j] ) {
|
|
break;
|
|
}
|
|
if ( j > bestlength ) {
|
|
bestlength = j;
|
|
beststart = start;
|
|
}
|
|
}
|
|
#endif
|
|
beststart = BACK_WINDOW - ( i - beststart );
|
|
|
|
if ( bestlength < 3 ) { // output a single char
|
|
bestlength = 1;
|
|
|
|
out_p[outbits >> 3] |= 1 << ( outbits & 7 ); // set bit to mark char
|
|
outbits++;
|
|
for ( j = 0 ; j < 8 ; j++, outbits++ )
|
|
if ( val & ( 1 << j ) ) {
|
|
out_p[outbits >> 3] |= 1 << ( outbits & 7 );
|
|
}
|
|
}
|
|
else
|
|
{ // output a phrase
|
|
outbits++; // leave a 0 bit to mark phrase
|
|
for ( j = 0 ; j < BACK_BITS ; j++, outbits++ )
|
|
if ( beststart & ( 1 << j ) ) {
|
|
out_p[outbits >> 3] |= 1 << ( outbits & 7 );
|
|
}
|
|
for ( j = 0 ; j < FRONT_BITS ; j++, outbits++ )
|
|
if ( bestlength & ( 1 << j ) ) {
|
|
out_p[outbits >> 3] |= 1 << ( outbits & 7 );
|
|
}
|
|
}
|
|
|
|
while ( bestlength-- )
|
|
{
|
|
val = in.data[i];
|
|
lzss_next[i] = lzss_head[val];
|
|
lzss_head[val] = i;
|
|
i++;
|
|
}
|
|
}
|
|
|
|
out_p += ( outbits + 7 ) >> 3;
|
|
out.count = out_p - out.data;
|
|
return out;
|
|
}
|
|
|
|
//==========================================================================
|
|
|
|
#define MIN_REPT 15
|
|
#define MAX_REPT 0
|
|
#define HUF_TOKENS ( 256 + MAX_REPT )
|
|
|
|
unsigned charbits1[256][HUF_TOKENS];
|
|
int charbitscount1[256][HUF_TOKENS];
|
|
|
|
hnode_t hnodes1[256][HUF_TOKENS * 2];
|
|
int numhnodes1[256];
|
|
|
|
int order0counts[256];
|
|
|
|
/*
|
|
==================
|
|
SmallestNode1
|
|
==================
|
|
*/
|
|
int SmallestNode1( hnode_t *hnodes, int numhnodes ){
|
|
int i;
|
|
int best, bestnode;
|
|
|
|
best = 99999999;
|
|
bestnode = -1;
|
|
for ( i = 0 ; i < numhnodes ; i++ )
|
|
{
|
|
if ( hnodes[i].used ) {
|
|
continue;
|
|
}
|
|
if ( !hnodes[i].count ) {
|
|
continue;
|
|
}
|
|
if ( hnodes[i].count < best ) {
|
|
best = hnodes[i].count;
|
|
bestnode = i;
|
|
}
|
|
}
|
|
|
|
if ( bestnode == -1 ) {
|
|
return -1;
|
|
}
|
|
|
|
hnodes[bestnode].used = true;
|
|
return bestnode;
|
|
}
|
|
|
|
|
|
/*
|
|
==================
|
|
BuildChars1
|
|
==================
|
|
*/
|
|
void BuildChars1( int prev, int nodenum, unsigned bits, int bitcount ){
|
|
hnode_t *node;
|
|
|
|
if ( nodenum < HUF_TOKENS ) {
|
|
if ( bitcount > 32 ) {
|
|
Error( "bitcount > 32" );
|
|
}
|
|
charbits1[prev][nodenum] = bits;
|
|
charbitscount1[prev][nodenum] = bitcount;
|
|
return;
|
|
}
|
|
|
|
node = &hnodes1[prev][nodenum];
|
|
bits <<= 1;
|
|
BuildChars1( prev, node->children[0], bits, bitcount + 1 );
|
|
bits |= 1;
|
|
BuildChars1( prev, node->children[1], bits, bitcount + 1 );
|
|
}
|
|
|
|
|
|
/*
|
|
==================
|
|
BuildTree1
|
|
==================
|
|
*/
|
|
void BuildTree1( int prev ){
|
|
hnode_t *node, *nodebase;
|
|
int numhnodes;
|
|
|
|
// build the nodes
|
|
numhnodes = HUF_TOKENS;
|
|
nodebase = hnodes1[prev];
|
|
while ( 1 )
|
|
{
|
|
node = &nodebase[numhnodes];
|
|
|
|
// pick two lowest counts
|
|
node->children[0] = SmallestNode1( nodebase, numhnodes );
|
|
if ( node->children[0] == -1 ) {
|
|
break; // no more
|
|
|
|
}
|
|
node->children[1] = SmallestNode1( nodebase, numhnodes );
|
|
if ( node->children[1] == -1 ) {
|
|
break;
|
|
}
|
|
|
|
node->count = nodebase[node->children[0]].count +
|
|
nodebase[node->children[1]].count;
|
|
numhnodes++;
|
|
}
|
|
numhnodes1[prev] = numhnodes - 1;
|
|
BuildChars1( prev, numhnodes - 1, 0, 0 );
|
|
}
|
|
|
|
|
|
/*
|
|
==================
|
|
Huffman1_Count
|
|
==================
|
|
*/
|
|
void Huffman1_Count( cblock_t in ){
|
|
int i;
|
|
int prev;
|
|
int v;
|
|
int rept;
|
|
|
|
prev = 0;
|
|
for ( i = 0 ; i < in.count ; i++ )
|
|
{
|
|
v = in.data[i];
|
|
order0counts[v]++;
|
|
hnodes1[prev][v].count++;
|
|
prev = v;
|
|
#if 1
|
|
for ( rept = 1 ; i + rept < in.count && rept < MAX_REPT ; rept++ )
|
|
if ( in.data[i + rept] != v ) {
|
|
break;
|
|
}
|
|
if ( rept > MIN_REPT ) {
|
|
hnodes1[prev][255 + rept].count++;
|
|
i += rept - 1;
|
|
}
|
|
#endif
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
==================
|
|
Huffman1_Build
|
|
==================
|
|
*/
|
|
byte scaled[256][HUF_TOKENS];
|
|
void Huffman1_Build( FILE *f ){
|
|
int i, j, v;
|
|
int max;
|
|
int total;
|
|
|
|
for ( i = 0 ; i < 256 ; i++ )
|
|
{
|
|
// normalize and save the counts
|
|
max = 0;
|
|
for ( j = 0 ; j < HUF_TOKENS ; j++ )
|
|
{
|
|
if ( hnodes1[i][j].count > max ) {
|
|
max = hnodes1[i][j].count;
|
|
}
|
|
}
|
|
if ( max == 0 ) {
|
|
max = 1;
|
|
}
|
|
total = 0;
|
|
for ( j = 0 ; j < HUF_TOKENS ; j++ )
|
|
{ // easy to overflow 32 bits here!
|
|
v = ( hnodes1[i][j].count * (double)255 + max - 1 ) / max;
|
|
if ( v > 255 ) {
|
|
Error( "v > 255" );
|
|
}
|
|
scaled[i][j] = hnodes1[i][j].count = v;
|
|
if ( v ) {
|
|
total++;
|
|
}
|
|
}
|
|
if ( total == 1 ) { // must have two tokens
|
|
if ( !scaled[i][0] ) {
|
|
scaled[i][0] = hnodes1[i][0].count = 1;
|
|
}
|
|
else{
|
|
scaled[i][1] = hnodes1[i][1].count = 1;
|
|
}
|
|
}
|
|
|
|
BuildTree1( i );
|
|
}
|
|
|
|
#if 0
|
|
// count up the total bits
|
|
total = 0;
|
|
for ( i = 0 ; i < 256 ; i++ )
|
|
for ( j = 0 ; j < 256 ; j++ )
|
|
total += charbitscount1[i][j] * hnodes1[i][j].count;
|
|
|
|
total = ( total + 7 ) / 8;
|
|
printf( "%i bytes huffman1 compressed\n", total );
|
|
#endif
|
|
|
|
fwrite( scaled, 1, sizeof( scaled ), f );
|
|
}
|
|
|
|
/*
|
|
==================
|
|
Huffman1
|
|
|
|
Order 1 compression with pre-built table
|
|
==================
|
|
*/
|
|
cblock_t Huffman1( cblock_t in ){
|
|
int i;
|
|
int outbits, c;
|
|
unsigned bits;
|
|
byte *out_p;
|
|
cblock_t out;
|
|
int prev;
|
|
int v;
|
|
int rept;
|
|
|
|
out_p = out.data = malloc( in.count * 2 + 1024 );
|
|
memset( out_p, 0, in.count * 2 + 1024 );
|
|
|
|
// write count
|
|
*out_p++ = in.count & 255;
|
|
*out_p++ = ( in.count >> 8 ) & 255;
|
|
*out_p++ = ( in.count >> 16 ) & 255;
|
|
*out_p++ = ( in.count >> 24 ) & 255;
|
|
|
|
// write bits
|
|
outbits = 0;
|
|
prev = 0;
|
|
for ( i = 0 ; i < in.count ; i++ )
|
|
{
|
|
v = in.data[i];
|
|
|
|
c = charbitscount1[prev][v];
|
|
bits = charbits1[prev][v];
|
|
if ( !c ) {
|
|
Error( "!bits" );
|
|
}
|
|
while ( c )
|
|
{
|
|
c--;
|
|
if ( bits & ( 1 << c ) ) {
|
|
out_p[outbits >> 3] |= 1 << ( outbits & 7 );
|
|
}
|
|
outbits++;
|
|
}
|
|
|
|
prev = v;
|
|
#if 1
|
|
// check for repeat encodes
|
|
for ( rept = 1 ; i + rept < in.count && rept < MAX_REPT ; rept++ )
|
|
if ( in.data[i + rept] != v ) {
|
|
break;
|
|
}
|
|
if ( rept > MIN_REPT ) {
|
|
c = charbitscount1[prev][255 + rept];
|
|
bits = charbits1[prev][255 + rept];
|
|
if ( !c ) {
|
|
Error( "!bits" );
|
|
}
|
|
while ( c )
|
|
{
|
|
c--;
|
|
if ( bits & ( 1 << c ) ) {
|
|
out_p[outbits >> 3] |= 1 << ( outbits & 7 );
|
|
}
|
|
outbits++;
|
|
}
|
|
i += rept - 1;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
out_p += ( outbits + 7 ) >> 3;
|
|
|
|
out.count = out_p - out.data;
|
|
|
|
return out;
|
|
}
|
|
|
|
//==========================================================================
|
|
|
|
|
|
/*
|
|
===================
|
|
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.pcx", gamedir, base, base, ten3, ten2, ten1, ten0 );
|
|
}
|
|
else{
|
|
sprintf( name, "%svideo/%s/%s%i%i%i.pcx", gamedir, base, base, ten2, ten1, ten0 );
|
|
}
|
|
|
|
f = fopen( name, "rb" );
|
|
if ( !f ) {
|
|
in.data = NULL;
|
|
return in;
|
|
}
|
|
fclose( f );
|
|
|
|
printf( "%s\n", name );
|
|
Load256Image( name, &in.data, palette, &width, &height );
|
|
in.count = width * 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 ){
|
|
char savename[1024];
|
|
char name[1024];
|
|
FILE *output;
|
|
int startframe, frame;
|
|
byte *palette;
|
|
int width, height;
|
|
byte current_palette[768];
|
|
int command;
|
|
int i;
|
|
int digits;
|
|
cblock_t in, huffman;
|
|
int swap;
|
|
|
|
|
|
GetToken( false );
|
|
strcpy( base, token );
|
|
if ( g_release ) {
|
|
// sprintf (savename, "video/%s.cin", token);
|
|
// ReleaseFile (savename);
|
|
return;
|
|
}
|
|
|
|
GetToken( false );
|
|
digits = atoi( token );
|
|
|
|
// optionally skip frames
|
|
if ( TokenAvailable() ) {
|
|
GetToken( false );
|
|
startframe = atoi( token );
|
|
}
|
|
else{
|
|
startframe = 0;
|
|
}
|
|
|
|
sprintf( savename, "%svideo/%s.cin", gamedir, base );
|
|
|
|
|
|
// clear stuff
|
|
memset( charbits1, 0, sizeof( charbits1 ) );
|
|
memset( charbitscount1, 0, sizeof( charbitscount1 ) );
|
|
memset( hnodes1, 0, sizeof( hnodes1 ) );
|
|
memset( numhnodes1, 0, sizeof( numhnodes1 ) );
|
|
memset( order0counts, 0, sizeof( order0counts ) );
|
|
|
|
|
|
// load the entire sound wav file if present
|
|
LoadSoundtrack();
|
|
|
|
if ( digits == 4 ) {
|
|
sprintf( name, "%svideo/%s/%s0000.pcx", gamedir, base, base );
|
|
}
|
|
else{
|
|
sprintf( name, "%svideo/%s/%s000.pcx", gamedir, base, base );
|
|
}
|
|
|
|
printf( "%s\n", name );
|
|
Load256Image( name, NULL, &palette, &width, &height );
|
|
|
|
output = fopen( savename, "wb" );
|
|
if ( !output ) {
|
|
Error( "Can't open %s", savename );
|
|
}
|
|
|
|
// write header info
|
|
i = LittleLong( width );
|
|
fwrite( &i, 4, 1, output );
|
|
i = LittleLong( height );
|
|
fwrite( &i, 4, 1, output );
|
|
i = LittleLong( wavinfo.rate );
|
|
fwrite( &i, 4, 1, output );
|
|
i = LittleLong( wavinfo.width );
|
|
fwrite( &i, 4, 1, output );
|
|
i = LittleLong( wavinfo.channels );
|
|
fwrite( &i, 4, 1, output );
|
|
|
|
// build the dictionary
|
|
for ( frame = startframe ; ; frame++ )
|
|
{
|
|
printf( "counting ", frame );
|
|
in = LoadFrame( base, frame, digits, &palette );
|
|
if ( !in.data ) {
|
|
break;
|
|
}
|
|
Huffman1_Count( in );
|
|
free( in.data );
|
|
}
|
|
printf( "\n" );
|
|
|
|
// build nodes and write counts
|
|
Huffman1_Build( output );
|
|
|
|
|
|
memset( current_palette, 0, sizeof( current_palette ) );
|
|
|
|
// compress it with the dictionary
|
|
for ( frame = startframe ; ; frame++ )
|
|
{
|
|
printf( "packing ", frame );
|
|
in = LoadFrame( base, frame, digits, &palette );
|
|
if ( !in.data ) {
|
|
break;
|
|
}
|
|
|
|
// see if the palette has changed
|
|
for ( i = 0 ; i < 768 ; i++ )
|
|
if ( palette[i] != current_palette[i] ) {
|
|
// write a palette change
|
|
memcpy( current_palette, palette, sizeof( current_palette ) );
|
|
command = LittleLong( 1 );
|
|
fwrite( &command, 1, 4, output );
|
|
fwrite( current_palette, 1, sizeof( current_palette ), output );
|
|
break;
|
|
}
|
|
if ( i == 768 ) {
|
|
command = 0; // no palette change
|
|
fwrite( &command, 1, 4, output );
|
|
}
|
|
|
|
// save the image
|
|
huffman = Huffman1( in );
|
|
printf( "%5i bytes after huffman1\n", huffman.count );
|
|
|
|
swap = LittleLong( huffman.count );
|
|
fwrite( &swap, 1, sizeof( swap ), output );
|
|
|
|
fwrite( huffman.data, 1, huffman.count, output );
|
|
|
|
// save some sound samples
|
|
WriteSound( output, frame );
|
|
|
|
free( palette );
|
|
free( in.data );
|
|
free( huffman.data );
|
|
}
|
|
printf( "\n" );
|
|
|
|
// write end-of-file command
|
|
command = 2;
|
|
fwrite( &command, 1, 4, output );
|
|
|
|
printf( "Total size: %i\n", ftell( output ) );
|
|
|
|
fclose( output );
|
|
|
|
if ( soundtrack ) {
|
|
free( soundtrack );
|
|
}
|
|
}
|