]> de.git.xonotic.org Git - xonotic/netradiant.git/blob - tools/quake3/q3data/video.c
eol style
[xonotic/netradiant.git] / tools / quake3 / q3data / video.c
1 #include <assert.h>
2 #include "q3data.h"
3
4 static int s_resample_width = 256;
5 static int s_resample_height = 256;
6
7 #define OUTPUT_TGAS                     1
8
9 #define UNCOMPRESSED            0
10 #define BTC_COMPRESSION         1
11
12 static int s_compression_method = BTC_COMPRESSION;
13
14 static const char *CIN_EXTENSION = "cn2";
15 static const int CIN_SIGNATURE = ( 'C' << 24 ) | ( 'I' << 16 ) | ( 'N' << 8 ) | ( '2' );
16
17 static byte     *s_soundtrack;
18 static char     s_base[32];
19 static char     s_output_base[32];
20
21 /*
22 ===============================================================================
23
24 WAV loading
25
26 ===============================================================================
27 */
28
29 typedef struct
30 {
31         int                     rate;
32         int                     width;
33         int                     channels;
34         int                     loopstart;
35         int                     samples;
36         int                     dataofs;                // chunk starts this many bytes from file start
37 } wavinfo_t;
38
39
40 byte    *data_p;
41 byte    *iff_end;
42 byte    *last_chunk;
43 byte    *iff_data;
44 int     iff_chunk_len;
45
46
47 static int                      s_samplecounts[0x10000];
48 static wavinfo_t        s_wavinfo;
49
50 short GetLittleShort(void)
51 {
52         short val = 0;
53         val = *data_p;
54         val = val + (*(data_p+1)<<8);
55         data_p += 2;
56         return val;
57 }
58
59 int GetLittleLong(void)
60 {
61         int val = 0;
62         val = *data_p;
63         val = val + (*(data_p+1)<<8);
64         val = val + (*(data_p+2)<<16);
65         val = val + (*(data_p+3)<<24);
66         data_p += 4;
67         return val;
68 }
69
70 void FindNextChunk(char *name)
71 {
72         while (1)
73         {
74                 data_p=last_chunk;
75
76                 if (data_p >= iff_end)
77                 {       // didn't find the chunk
78                         data_p = NULL;
79                         return;
80                 }
81                 
82                 data_p += 4;
83                 iff_chunk_len = GetLittleLong();
84                 if (iff_chunk_len < 0)
85                 {
86                         data_p = NULL;
87                         return;
88                 }
89 //              if (iff_chunk_len > 1024*1024)
90 //                      Sys_Error ("FindNextChunk: %i length is past the 1 meg sanity limit", iff_chunk_len);
91                 data_p -= 8;
92                 last_chunk = data_p + 8 + ( (iff_chunk_len + 1) & ~1 );
93                 if (!strncmp(data_p, name, 4))
94                         return;
95         }
96 }
97
98 void FindChunk(char *name)
99 {
100         last_chunk = iff_data;
101         FindNextChunk (name);
102 }
103
104
105 void DumpChunks(void)
106 {
107         char    str[5];
108         
109         str[4] = 0;
110         data_p=iff_data;
111         do
112         {
113                 memcpy (str, data_p, 4);
114                 data_p += 4;
115                 iff_chunk_len = GetLittleLong();
116                 printf ("0x%x : %s (%d)\n", (int)(data_p - 4), str, iff_chunk_len);
117                 data_p += (iff_chunk_len + 1) & ~1;
118         } while (data_p < iff_end);
119 }
120
121 /*
122 ============
123 GetWavinfo
124 ============
125 */
126 wavinfo_t GetWavinfo (char *name, byte *wav, int wavlength)
127 {
128         wavinfo_t       info;
129         int     i;
130         int     format;
131         int             samples;
132
133         memset (&info, 0, sizeof(info));
134
135         if (!wav)
136                 return info;
137                 
138         iff_data = wav;
139         iff_end = wav + wavlength;
140
141 // find "RIFF" chunk
142         FindChunk("RIFF");
143         if (!(data_p && !strncmp(data_p+8, "WAVE", 4)))
144         {
145                 printf("Missing RIFF/WAVE chunks\n");
146                 return info;
147         }
148
149 // get "fmt " chunk
150         iff_data = data_p + 12;
151 // DumpChunks ();
152
153         FindChunk("fmt ");
154         if (!data_p)
155         {
156                 printf("Missing fmt chunk\n");
157                 return info;
158         }
159         data_p += 8;
160         format = GetLittleShort();
161         if (format != 1)
162         {
163                 printf("Microsoft PCM format only\n");
164                 return info;
165         }
166
167         info.channels = GetLittleShort();
168         info.rate = GetLittleLong();
169         data_p += 4+2;
170         info.width = GetLittleShort() / 8;
171
172 // get cue chunk
173         FindChunk("cue ");
174         if (data_p)
175         {
176                 data_p += 32;
177                 info.loopstart = GetLittleLong();
178 //              Com_Printf("loopstart=%d\n", sfx->loopstart);
179
180         // if the next chunk is a LIST chunk, look for a cue length marker
181                 FindNextChunk ("LIST");
182                 if (data_p)
183                 {
184                         if (!strncmp (data_p + 28, "mark", 4))
185                         {       // this is not a proper parse, but it works with cooledit...
186                                 data_p += 24;
187                                 i = GetLittleLong ();   // samples in loop
188                                 info.samples = info.loopstart + i;
189                         }
190                 }
191         }
192         else
193                 info.loopstart = -1;
194
195 // find data chunk
196         FindChunk("data");
197         if (!data_p)
198         {
199                 printf("Missing data chunk\n");
200                 return info;
201         }
202
203         data_p += 4;
204         samples = GetLittleLong ();
205
206         if (info.samples)
207         {
208                 if (samples < info.samples)
209                         Error ("Sound %s has a bad loop length", name);
210         }
211         else
212                 info.samples = samples;
213
214         info.dataofs = data_p - wav;
215
216         return info;
217 }
218
219 //=====================================================================
220
221 /*
222 ==============
223 LoadSoundtrack
224 ==============
225 */
226 void LoadSoundtrack (void)
227 {
228         char    name[1024];
229         FILE    *f;
230         int             len;
231         int     i, val, j;
232
233         s_soundtrack = NULL;
234         sprintf (name, "%svideo/%s/%s.wav", gamedir, s_base, s_base);
235         printf ("WAV: %s\n", name);
236         f = fopen (name, "rb");
237         if (!f)
238         {
239                 printf ("no soundtrack for %s\n", s_base);
240                 return;
241         }
242         len = Q_filelength(f);
243         s_soundtrack = malloc(len);
244         fread (s_soundtrack, 1, len, f);
245         fclose (f);
246
247         s_wavinfo = GetWavinfo (name, s_soundtrack, len);
248
249         // count samples for compression
250         memset (s_samplecounts, 0, sizeof(s_samplecounts));
251
252         j = s_wavinfo.samples/2;
253         for (i=0 ; i<j ; i++)
254         {
255                 val = ((unsigned short *)( s_soundtrack + s_wavinfo.dataofs))[i];
256                 s_samplecounts[val]++;
257         }
258         val = 0;
259         for (i=0 ; i<0x10000 ; i++)
260                 if (s_samplecounts[i])
261                         val++;
262
263         printf ("%i unique sample values\n", val);
264 }
265
266 /*
267 ==================
268 WriteSound
269 ==================
270 */
271 void WriteSound (FILE *output, int frame)
272 {
273         int             start, end;
274         int             count;
275         int             empty = 0;
276         int             i;
277         int             sample;
278         int             width;
279
280         width = s_wavinfo.width * s_wavinfo.channels;
281
282         start = frame*s_wavinfo.rate/14;
283         end = (frame+1)*s_wavinfo.rate/14;
284         count = end - start;
285
286         for (i=0 ; i<count ; i++)
287         {
288                 sample = start+i;
289                 if (sample > s_wavinfo.samples || !s_soundtrack)
290                         fwrite (&empty, 1, width, output);
291                 else
292                         fwrite (s_soundtrack + s_wavinfo.dataofs + sample*width, 1, width,output);
293         }
294 }
295
296 //==========================================================================
297
298 static float s_resampleXRatio;
299 static float s_resampleYRatio;
300
301 static void BoxFilterHorizontalElements( unsigned char *dst, unsigned char *src, float s0, float s1 )
302 {
303         float w;
304         float rSum = 0, gSum = 0, bSum = 0;
305         float x = s0;
306         float sumWeight = 0;
307
308         for ( x = s0; x < s1; x++, src += 4 )
309         {
310                 if ( x == s0 )
311                 {
312                         w = ( int ) ( s0 + 1 ) - x;
313                 }
314                 else if ( x + 1 >= s1 )
315                 {
316                         w = s1 - ( int ) x;
317                 }
318                 else
319                 {
320                         w = 1.0f;
321                 }
322
323                 rSum += src[0] * w;
324                 gSum += src[1] * w;
325                 bSum += src[2] * w;
326                 sumWeight += w;
327         }
328
329         rSum /= sumWeight;
330         gSum /= sumWeight;
331         bSum /= sumWeight;
332
333         dst[0] = ( unsigned char ) ( rSum + 0.5 );
334         dst[1] = ( unsigned char ) ( gSum + 0.5 );
335         dst[2] = ( unsigned char ) ( bSum + 0.5 );
336 }
337
338 static void BoxFilterVerticalElements( unsigned char *dst, // destination of the filter process
339                                                                            unsigned char *src, // source pixels
340                                                                            int srcStep,            // stride of the source pixels
341                                                                            float s0, float s1 )
342 {
343         float w;
344         float rSum = 0, gSum = 0, bSum = 0;
345         float y = s0;
346         float sumWeight = 0;
347
348         for ( y = s0; y < ( int ) ( s1 + 1 ) ; y++, src += srcStep )
349         {
350                 if ( y == s0 )
351                 {
352                         w = ( int ) ( s0 + 1 ) - y;
353                 }
354                 else if ( y + 1 >= s1 )
355                 {
356                         w = s1 - ( int ) y;
357                 }
358                 else
359                 {
360                         w = 1.0f;
361                 }
362
363                 rSum += src[0] * w;
364                 gSum += src[1] * w;
365                 bSum += src[2] * w;
366                 sumWeight += w;
367         }
368
369         rSum /= sumWeight;
370         gSum /= sumWeight;
371         bSum /= sumWeight;
372
373         dst[0] = ( unsigned char ) ( rSum + 0.5 );
374         dst[1] = ( unsigned char ) ( gSum + 0.5 );
375         dst[2] = ( unsigned char ) ( bSum + 0.5 );
376         dst[3] = 0xff;
377
378 }
379
380 static void BoxFilterRow( unsigned char *dstStart, cblock_t *in, int dstRow, int rowWidth )
381 {
382         int i;
383         unsigned char *indata = ( unsigned char * ) in->data;
384
385         indata += 4 * dstRow * in->width;
386
387         for ( i = 0; i < rowWidth; i++ )
388         {
389                 float c0 = i * s_resampleXRatio;
390                 float c1 = ( i + 1 ) * s_resampleXRatio;
391
392                 BoxFilterHorizontalElements( &dstStart[i*4], &indata[( ( int ) c0 ) * 4], c0, c1 );
393         }
394 }
395
396 static void BoxFilterColumn( unsigned char *dstStart, unsigned char *srcStart, int dstCol, int dstRowWidth, int dstColHeight, int srcRowWidthInPels )
397 {
398         float c0, c1;
399         int i;
400
401         for ( i = 0; i < dstColHeight; i++ )
402         {
403                 c0 = i * s_resampleYRatio;
404                 c1 = ( i + 1 ) * s_resampleYRatio;
405
406                 BoxFilterVerticalElements( &dstStart[i*4*dstRowWidth], &srcStart[(int)c0*srcRowWidthInPels*4], srcRowWidthInPels*4, c0, c1 );
407         }
408 }
409
410 #define DROP_SAMPLE             0
411 #define BOX_FILTER              1
412
413 static void ResampleFrame( cblock_t *in, unsigned char *out, int method, int outWidth, int outHeight )
414 {
415         int row, column;
416         unsigned char *indata = ( unsigned char * ) in->data;
417
418         s_resampleXRatio = in->width / ( float ) outWidth;
419         s_resampleYRatio = in->height / ( float ) outHeight;
420
421         if ( method == DROP_SAMPLE )
422         {
423                 for ( row = 0; row < outHeight; row++ )
424                 {
425                         int r = ( int ) ( row * s_resampleYRatio );
426
427                         for ( column = 0; column < outWidth; column++ )
428                         {
429                                 int c = ( int ) ( column * s_resampleXRatio );
430
431                                 out[(row*outWidth+column)*4+0] = indata[(r*in->width+c)*4+0];
432                                 out[(row*outWidth+column)*4+1] = indata[(r*in->width+c)*4+1];
433                                 out[(row*outWidth+column)*4+2] = indata[(r*in->width+c)*4+2];
434                                 out[(row*outWidth+column)*4+3] = 0xff;
435                         }
436                 }
437         }
438         else if ( method == BOX_FILTER )
439         {
440                 unsigned char intermediate[1024*1024*4];
441
442                 assert( in->height <= 1024 );
443                 assert( in->width <= 1024 );
444
445                 //
446                 // filter our M x N source image into a RESAMPLE_WIDTH x N horizontally filtered image
447                 //
448                 for ( row = 0; row < in->height; row++ )
449                 {
450                         BoxFilterRow( &intermediate[row*4*outWidth], in, row, outWidth );
451                 }
452
453                 //
454                 // filter our RESAMPLE_WIDTH x N horizontally filtered image into a RESAMPLE_WIDTH x RESAMPLE_HEIGHT filtered image
455                 //
456                 for ( column = 0; column < outWidth; column++ )
457                 {
458                         BoxFilterColumn( &out[column*4], &intermediate[column*4], column, outWidth, outHeight, s_resample_width );
459                 }
460         }
461 }
462
463 static float BTCDistanceSquared( float a[3], float b[3] )
464 {
465         return ( b[0] - a[0] ) * ( b[0] - a[0] ) + 
466                    ( b[1] - a[1] ) * ( b[1] - a[1] ) +
467                    ( b[2] - a[2] ) * ( b[2] - a[2] );
468 }
469
470 static void BTCFindEndpoints( float inBlock[4][4][3], unsigned int endPoints[2][2] )
471 {
472         float longestDistance = -1;
473
474         int bX, bY;
475
476         //
477         // find the two points farthest from each other
478         //
479         for ( bY = 0; bY < 4; bY++ )
480         {
481                 for ( bX = 0; bX < 4; bX++ )
482                 {
483                         int cX, cY;
484                         float d;
485
486                         //
487                         // check the rest of the current row
488                         //
489                         for ( cX = bX + 1; cX < 4; cX++ )
490                         {
491                                 if ( ( d = BTCDistanceSquared( inBlock[bY][bX], inBlock[bY][cX] ) ) > longestDistance )
492                                 {
493                                         longestDistance = d;
494                                         endPoints[0][0] = bX;
495                                         endPoints[0][1] = bY;
496                                         endPoints[1][0] = cX;
497                                         endPoints[1][1] = bY;
498                                 }
499                         }
500
501                         //
502                         // check remaining rows and columns
503                         //
504                         for ( cY = bY+1; cY < 4; cY++ )
505                         {
506                                 for ( cX = 0; cX < 4; cX++ )
507                                 {
508                                         if ( ( d = BTCDistanceSquared( inBlock[bY][bX], inBlock[cY][cX] ) ) > longestDistance )
509                                         {
510                                                 longestDistance = d;
511                                                 endPoints[0][0] = bX;
512                                                 endPoints[0][1] = bY;
513                                                 endPoints[1][0] = cX;
514                                                 endPoints[1][1] = cY;
515                                         }
516                                 }
517                         }
518                 }
519         }
520 }
521
522 static float BTCQuantizeBlock( float inBlock[4][4][3], unsigned long endPoints[2][2], int btcQuantizedBlock[4][4], float bestError )
523 {
524         int i;
525         int blockY, blockX;
526         float dR, dG, dB;
527         float R, G, B;
528         float error = 0;
529         float colorLine[4][3];
530
531         //
532         // build the color line
533         //
534         dR = inBlock[endPoints[1][1]][endPoints[1][0]][0] -
535                  inBlock[endPoints[0][1]][endPoints[0][0]][0];
536         dG = inBlock[endPoints[1][1]][endPoints[1][0]][1] -
537                  inBlock[endPoints[0][1]][endPoints[0][0]][1];
538         dB = inBlock[endPoints[1][1]][endPoints[1][0]][2] -
539                  inBlock[endPoints[0][1]][endPoints[0][0]][2];
540
541         dR *= 0.33f;
542         dG *= 0.33f;
543         dB *= 0.33f;
544
545         R = inBlock[endPoints[0][1]][endPoints[0][0]][0];
546         G = inBlock[endPoints[0][1]][endPoints[0][0]][1];
547         B = inBlock[endPoints[0][1]][endPoints[0][0]][2];
548
549         for ( i = 0; i < 4; i++ )
550         {
551                 colorLine[i][0] = R;
552                 colorLine[i][1] = G;
553                 colorLine[i][2] = B;
554
555                 R += dR;
556                 G += dG;
557                 B += dB;
558         }
559
560         //
561         // quantize each pixel into the appropriate range
562         //
563         for ( blockY = 0; blockY < 4; blockY++ )
564         {
565                 for ( blockX = 0; blockX < 4; blockX++ )
566                 {
567                         float distance = 10000000000;
568                         int shortest = -1;
569
570                         for ( i = 0; i < 4; i++ )
571                         {
572                                 float d;
573
574                                 if ( ( d = BTCDistanceSquared( inBlock[blockY][blockX], colorLine[i] ) ) < distance )
575                                 {
576                                         distance = d;
577                                         shortest = i;
578                                 }
579                         }
580
581                         error += distance;
582
583                         //
584                         // if bestError is not -1 then that means this is a speculative quantization
585                         //
586                         if ( bestError != -1 )
587                         {
588                                 if ( error > bestError )
589                                         return error;
590                         }
591
592                         btcQuantizedBlock[blockY][blockX] = shortest;
593                 }
594         }
595
596         return error;
597 }
598
599 /*
600 ** float BTCCompressBlock
601 */
602 static float BTCCompressBlock( float inBlock[4][4][3], unsigned long out[2] )
603 {
604         int i;
605         int btcQuantizedBlock[4][4];    // values should be [0..3]
606         unsigned long encodedEndPoints, encodedBitmap;
607         unsigned int endPoints[2][2];           // endPoints[0] = color start, endPoints[1] = color end
608         int blockY, blockX;
609         float error = 0;
610         float bestError = 10000000000;
611         unsigned int bestEndPoints[2][2];
612
613 #if 0
614         //
615         // find the "ideal" end points for the color vector 
616         //
617         BTCFindEndpoints( inBlock, endPoints );
618         error = BTCQuantizeBlock( inBlock, endPoints, btcQuantizedBlock );
619         memcpy( bestEndPoints, endPoints, sizeof( bestEndPoints ) );
620 #else
621         for ( blockY = 0; blockY < 4; blockY++ )
622         {
623                 for ( blockX = 0; blockX < 4; blockX++ )
624                 {
625                         int x2, y2;
626
627                         for ( y2 = 0; y2 < 4; y2++ )
628                         {
629                                 for ( x2 = 0; x2 < 4; x2++ )
630                                 {
631                                         if ( ( x2 == blockX ) && ( y2 == blockY ) )
632                                                 continue;
633
634                                         endPoints[0][0] = blockX;
635                                         endPoints[0][1] = blockY;
636                                         endPoints[1][0] = x2;
637                                         endPoints[1][1] = y2;
638
639                                         error = BTCQuantizeBlock( inBlock, endPoints, btcQuantizedBlock, -1 ); //bestError );
640
641                                         if ( error < bestError )
642                                         {
643                                                 bestError = error;
644                                                 memcpy( bestEndPoints, endPoints, sizeof( bestEndPoints ) );
645                                         }
646                                 }
647                         }
648                 }
649         }
650
651         error = BTCQuantizeBlock( inBlock, bestEndPoints, btcQuantizedBlock, -1.0f );
652 #endif
653
654         //
655         // encode the results
656         //
657         encodedBitmap = 0;
658         for ( blockY = 0; blockY < 4; blockY++ )
659         {
660                 for ( blockX = 0; blockX < 4; blockX++ )
661                 {
662                         int shift = ( blockX + blockY * 4 ) * 2;
663                         encodedBitmap |= btcQuantizedBlock[blockY][blockX] << shift;
664                 }
665         }
666
667         //
668         // encode endpoints
669         //
670         encodedEndPoints = 0;
671         for ( i = 0; i < 2; i++ )
672         {
673                 int iR, iG, iB;
674
675                 iR = ( ( int ) inBlock[bestEndPoints[i][1]][bestEndPoints[i][0]][0] );
676                 if ( iR > 255 ) 
677                         iR = 255;
678                 else if ( iR < 0 ) 
679                         iR = 0;
680                 iR >>= 3;
681
682                 iG = ( ( int ) inBlock[bestEndPoints[i][1]][bestEndPoints[i][0]][1] );
683                 if ( iG > 255 )
684                         iG = 255;
685                 else if ( iG < 0 )
686                         iG = 0;
687                 iG >>= 2;
688
689                 iB = ( ( int ) inBlock[bestEndPoints[i][1]][bestEndPoints[i][0]][2] );
690                 if ( iB > 255 )
691                         iB = 255;
692                 else if ( iB < 0 )
693                         iB = 0;
694                 iB >>= 3;
695
696
697                 encodedEndPoints |= ( ( ( iR << 11 ) | ( iG << 5 ) | ( iB ) ) << ( i * 16 ) );
698         }
699
700         //
701         // store 
702         //
703         out[0] = encodedBitmap;
704         out[1] = encodedEndPoints;
705
706         return error;
707 }
708
709 /*
710 ** void BTCDecompressFrame
711 */
712 static void BTCDecompressFrame( unsigned long *src, unsigned char *dst )
713 {
714         int x, y;
715         int iR, iG, iB;
716         int dstX, dstY;
717         float colorStart[3], colorEnd[3];
718         unsigned char colorRampABGR[4][4];
719         unsigned encoded;
720
721         memset( colorRampABGR, 0xff, sizeof( colorRampABGR ) );
722
723         for ( y = 0; y < s_resample_height / 4; y++ )
724         {
725                 for ( x = 0; x < s_resample_width / 4; x++ )
726                 {
727                         unsigned colorStartPacked = src[(y*s_resample_width/4 + x)*2 + 1] & 0xffff;
728                         unsigned colorEndPacked = src[(y*s_resample_width/4 + x)*2 + 1] >> 16;
729
730                         //
731                         // grab the end points
732                         //   0 = color start
733                         //   1 = color end
734                         //
735                         iR = ( ( colorStartPacked >> 11 ) & ( ( 1 << 5 ) - 1 ) );
736                         iR = ( iR << 3 ) | ( iR >> 2 );
737                         iG = ( ( colorStartPacked >> 5 ) & ( ( 1 << 6 )  - 1 ) );
738                         iG = ( iG << 2 ) | ( iG >> 4 );
739                         iB = ( ( colorStartPacked ) & ( ( 1 << 5  ) - 1 ) );
740                         iB = ( iB << 3 ) | ( iB >> 2 );
741
742                         colorStart[0] = iR;
743                         colorStart[1] = iG;
744                         colorStart[2] = iB;
745                         colorRampABGR[0][0] = iR;
746                         colorRampABGR[0][1] = iG;
747                         colorRampABGR[0][2] = iB;
748
749                         iR = ( ( colorEndPacked >> 11 ) & ( ( 1 << 5 ) - 1 ) );
750                         iR = ( iR << 3 ) | ( iR >> 2 );
751                         iG = ( ( colorEndPacked >> 5 ) & ( ( 1 << 6 )  - 1 ) );
752                         iG = ( iG << 2 ) | ( iG >> 4 );
753                         iB = ( colorEndPacked & ( ( 1 << 5  ) - 1 ) );
754                         iB = ( iB << 3 ) | ( iB >> 2 );
755
756                         colorEnd[0] = iR;
757                         colorEnd[1] = iG;
758                         colorEnd[2] = iB;
759                         colorRampABGR[3][0] = iR;
760                         colorRampABGR[3][1] = iG;
761                         colorRampABGR[3][2] = iB;
762                         
763                         //
764                         // compute this block's color ramp
765                         // FIXME: This needs to be reversed on big-endian machines
766                         //
767                         
768                         colorRampABGR[1][0] = colorStart[0] * 0.66f + colorEnd[0] * 0.33f;
769                         colorRampABGR[1][1] = colorStart[1] * 0.66f + colorEnd[1] * 0.33f;
770                         colorRampABGR[1][2] = colorStart[2] * 0.66f + colorEnd[2] * 0.33f;
771
772                         colorRampABGR[2][0] = colorStart[0] * 0.33f + colorEnd[0] * 0.66f;
773                         colorRampABGR[2][1] = colorStart[1] * 0.33f + colorEnd[1] * 0.66f;
774                         colorRampABGR[2][2] = colorStart[2] * 0.33f + colorEnd[2] * 0.66f;
775
776                         //
777                         // decode the color data
778                         // information is encoded in 2-bit pixels, with low order bits corresponding
779                         // to upper left pixels.  These 2-bit values are indexed into the block's
780                         // computer color ramp.
781                         //
782                         encoded = src[(y*s_resample_width/4 + x)*2 + 0];
783
784                         for ( dstY = 0; dstY < 4; dstY++ )
785                         {
786                                 for ( dstX = 0; dstX < 4; dstX++ )
787                                 {
788                                         memcpy( &dst[(y*4+dstY)*s_resample_width*4+x*4*4+dstX*4], colorRampABGR[encoded&3], sizeof( colorRampABGR[0] ) );
789                                         encoded >>= 2;
790                                 }
791                         }
792                 }
793         }
794 }
795
796 /*
797 ** BTCCompressFrame
798 **
799 ** Perform a BTC compression using a 2-bit encoding at each pixel.  This
800 ** compression method is performed by decomposing the incoming image into
801 ** a sequence of 4x4 blocks.  At each block two color values are computed
802 ** that define the endpoints of a vector in color space that represent
803 ** the two colors "farthest apart".
804 */
805 static float BTCCompressFrame( unsigned char *src, unsigned long *dst )
806 {
807         int x, y;
808         int bX, bY;
809         float btcBlock[4][4][3];
810
811         float error = 0;
812         
813         for ( y = 0; y < s_resample_height / 4; y++ )
814         {
815                 for ( x = 0; x < s_resample_width / 4; x++ )
816                 {
817                         //
818                         // fill in the BTC block with raw values
819                         //
820                         for ( bY = 0; bY < 4; bY++ )
821                         {
822                                 for ( bX = 0; bX < 4; bX++ )
823                                 {
824                                         btcBlock[bY][bX][0] = src[(y*4+bY)*s_resample_width*4 + (x*4+bX)*4 + 0];
825                                         btcBlock[bY][bX][1] = src[(y*4+bY)*s_resample_width*4 + (x*4+bX)*4 + 1];
826                                         btcBlock[bY][bX][2] = src[(y*4+bY)*s_resample_width*4 + (x*4+bX)*4 + 2];
827                                 }
828                         }
829
830                         error += BTCCompressBlock( btcBlock, &dst[(y*s_resample_width/4+x)*2] );
831                 }
832         }
833
834         return error / ( ( s_resample_width / 4 ) * ( s_resample_height / 4 ) );
835 }
836
837 /*
838 ===================
839 LoadFrame
840 ===================
841 */
842 cblock_t LoadFrame (char *base, int frame, int digits, byte **palette)
843 {
844         int                     ten3, ten2, ten1, ten0;
845         cblock_t        in;
846         int                     width, height;
847         char            name[1024];
848         FILE            *f;
849
850         in.data = NULL;
851         in.count = -1;
852
853         ten3 = frame/1000;
854         ten2 = (frame-ten3*1000)/100;
855         ten1 = (frame-ten3*1000-ten2*100)/10;
856         ten0 = frame%10;
857
858         if (digits == 4)
859                 sprintf (name, "%svideo/%s/%s%i%i%i%i.tga", gamedir, base, base, ten3, ten2, ten1, ten0);
860         else
861                 sprintf (name, "%svideo/%s/%s%i%i%i.tga", gamedir, base, base, ten2, ten1, ten0);
862
863         f = fopen(name, "rb");
864         if (!f)
865         {
866                 in.data = NULL;
867                 return in;
868         }
869         fclose (f);
870
871         printf ("%s", name);
872         LoadTGA( name, ( unsigned char ** ) &in.data, &width, &height );
873         if ( palette )
874                 *palette = 0;
875 //      Load256Image (name, &in.data, palette, &width, &height);
876         in.count = width*height;
877         in.width = width;
878         in.height = height;
879 // FIXME: map 0 and 255!
880
881 #if 0
882         // rle compress
883         rle = RLE(in);
884         free (in.data);
885
886         return rle;
887 #endif
888
889         return in;
890 }
891
892 /*
893 ===============
894 Cmd_Video
895
896 video <directory> <framedigits>
897 ===============
898 */
899 void Cmd_Video (void)
900 {
901         float sumError = 0, error = 0, maxError = 0;
902         char    savename[1024];
903         char    name[1024];
904         FILE    *output;
905         int             startframe, frame;
906         int             width, height;
907         int             i;
908         int             digits;
909         int             minutes;
910         float   fseconds;
911         int             remSeconds;
912         cblock_t        in;
913         unsigned char *resampled;
914         unsigned long *compressed;
915         clock_t start, stop;
916
917         GetToken (qfalse);
918         strcpy (s_base, token);
919         if (g_release)
920         {
921 //              sprintf (savename, "video/%s.cin", token);
922 //              ReleaseFile (savename);
923                 return;
924         }
925
926         GetToken( qfalse );
927         strcpy( s_output_base, token );
928
929         GetToken (qfalse);
930         digits = atoi(token);
931
932         GetToken( qfalse );
933
934         if ( !strcmp( token, "btc" ) )
935         {
936                 s_compression_method = BTC_COMPRESSION;
937                 printf( "Compression: BTC\n" );
938         }
939         else if ( !strcmp( token, "uc" ) )
940         {
941                 s_compression_method = UNCOMPRESSED;
942                 printf( "Compression: none\n" );
943         }
944         else
945         {
946                 Error( "Uknown compression method '%s'\n", token );
947         }
948
949         GetToken( qfalse );
950         s_resample_width = atoi( token );
951
952         GetToken( qfalse );
953         s_resample_height = atoi( token );
954
955         resampled = malloc( sizeof( unsigned char ) * 4 * s_resample_width * s_resample_height );
956         compressed = malloc( sizeof( long ) * 2 * ( s_resample_width / 4 ) * ( s_resample_height / 4 ) );
957
958         printf( "Resample width: %d\n", s_resample_width );
959         printf( "Resample height: %d\n", s_resample_height );
960
961         // optionally skip frames
962         if (TokenAvailable ())
963         {
964                 GetToken (qfalse);
965                 startframe = atoi(token);
966         }
967         else
968                 startframe=0;
969
970         sprintf (savename, "%svideo/%s.%s", writedir, s_output_base, CIN_EXTENSION );
971
972         // load the entire sound wav file if present
973         LoadSoundtrack ();
974
975         if (digits == 4)
976                 sprintf (name, "%svideo/%s/%s0000.tga", gamedir, s_base, s_base);
977         else
978                 sprintf (name, "%svideo/%s/%s000.tga", gamedir, s_base, s_base);
979
980         printf ("%s\n", name);
981         LoadTGA( name, NULL, &width, &height);
982
983         output = fopen (savename, "wb");
984         if (!output)
985                 Error ("Can't open %s", savename);
986
987         // write header info
988         i = LittleLong( CIN_SIGNATURE );
989         fwrite (&i, 4, 1, output );
990         i = LittleLong (s_resample_width);
991         fwrite (&i, 4, 1, output);
992         i = LittleLong (s_resample_height);
993         fwrite (&i, 4, 1, output);
994         i = LittleLong (s_wavinfo.rate);
995         fwrite (&i, 4, 1, output);
996         i = LittleLong (s_wavinfo.width);
997         fwrite (&i, 4, 1, output);
998         i = LittleLong (s_wavinfo.channels);
999         fwrite (&i, 4, 1, output);
1000         i = LittleLong ( s_compression_method );
1001         fwrite (&i, 4, 1, output );
1002
1003         start = clock();
1004
1005         // perform compression on a per frame basis
1006         for ( frame=startframe ;  ; frame++)
1007         {
1008                 printf ("%02d: ", frame);
1009                 in = LoadFrame (s_base, frame, digits, 0 );
1010                 if (!in.data)
1011                         break;
1012
1013                 ResampleFrame( &in, ( unsigned char * ) resampled, BOX_FILTER, s_resample_width, s_resample_height );
1014
1015                 if ( s_compression_method == UNCOMPRESSED )
1016                 {
1017                         printf( "\n" );
1018                         fwrite( resampled, 1, sizeof( unsigned char ) * s_resample_width * s_resample_height * 4, output );
1019
1020 #if OUTPUT_TGAS
1021                         {
1022                                 int x, y;
1023                                 char buffer[1000];
1024
1025                                 for ( y = 0; y < s_resample_height/2; y++ )
1026                                 {
1027                                         for ( x = 0; x < s_resample_width; x++ )
1028                                         {
1029                                                 unsigned char tmp[4];
1030
1031                                                 tmp[0] = resampled[(s_resample_height-1-y)*s_resample_width*4 + x*4 + 0];
1032                                                 tmp[1] = resampled[(s_resample_height-1-y)*s_resample_width*4 + x*4 + 1];
1033                                                 tmp[2] = resampled[(s_resample_height-1-y)*s_resample_width*4 + x*4 + 2];
1034                                                 tmp[3] = resampled[(s_resample_height-1-y)*s_resample_width*4 + x*4 + 3];
1035
1036                                                 resampled[(s_resample_height-1-y)*s_resample_width*4 + x*4 + 0] = resampled[y*s_resample_width*4 + x*4 + 0];
1037                                                 resampled[(s_resample_height-1-y)*s_resample_width*4 + x*4 + 1] = resampled[y*s_resample_width*4 + x*4 + 1];
1038                                                 resampled[(s_resample_height-1-y)*s_resample_width*4 + x*4 + 2] = resampled[y*s_resample_width*4 + x*4 + 2];
1039                                                 resampled[(s_resample_height-1-y)*s_resample_width*4 + x*4 + 3] = resampled[y*s_resample_width*4 + x*4 + 3];
1040
1041                                                 resampled[y*s_resample_width*4 + x*4 + 0] = tmp[0];
1042                                                 resampled[y*s_resample_width*4 + x*4 + 1] = tmp[1];
1043                                                 resampled[y*s_resample_width*4 + x*4 + 2] = tmp[2];
1044                                                 resampled[y*s_resample_width*4 + x*4 + 3] = tmp[3];
1045                                         }
1046                                 }
1047
1048                                 sprintf( buffer, "%svideo/%s/uc%04d.tga", gamedir, s_base, frame );
1049                                 WriteTGA( buffer, resampled, s_resample_width, s_resample_height );
1050                         }
1051 #endif
1052                 }
1053                 else if ( s_compression_method == BTC_COMPRESSION )
1054                 {
1055                         error = BTCCompressFrame( resampled, compressed );
1056
1057                         sumError += error;
1058
1059                         if ( error > maxError ) 
1060                                 maxError = error;
1061
1062                         printf( " (error = %f)\n", error );
1063                         fwrite( compressed, 1, 2 * sizeof( long ) * ( s_resample_width / 4 ) * ( s_resample_height / 4 ), output );
1064
1065 #if OUTPUT_TGAS
1066                         {
1067                                 int x, y;
1068                                 unsigned char *uncompressed;
1069                                 char buffer[1000];
1070
1071                                 uncompressed = malloc( sizeof( unsigned char ) * 4 * s_resample_width * s_resample_height );
1072                                 BTCDecompressFrame( compressed, uncompressed );
1073
1074                                 for ( y = 0; y < s_resample_height/2; y++ )
1075                                 {
1076                                         for ( x = 0; x < s_resample_width; x++ )
1077                                         {
1078                                                 unsigned char tmp[4];
1079
1080                                                 tmp[0] = uncompressed[(s_resample_height-1-y)*s_resample_width*4 + x*4 + 0];
1081                                                 tmp[1] = uncompressed[(s_resample_height-1-y)*s_resample_width*4 + x*4 + 1];
1082                                                 tmp[2] = uncompressed[(s_resample_height-1-y)*s_resample_width*4 + x*4 + 2];
1083                                                 tmp[3] = uncompressed[(s_resample_height-1-y)*s_resample_width*4 + x*4 + 3];
1084
1085                                                 uncompressed[(s_resample_height-1-y)*s_resample_width*4 + x*4 + 0] = uncompressed[y*s_resample_width*4 + x*4 + 0];
1086                                                 uncompressed[(s_resample_height-1-y)*s_resample_width*4 + x*4 + 1] = uncompressed[y*s_resample_width*4 + x*4 + 1];
1087                                                 uncompressed[(s_resample_height-1-y)*s_resample_width*4 + x*4 + 2] = uncompressed[y*s_resample_width*4 + x*4 + 2];
1088                                                 uncompressed[(s_resample_height-1-y)*s_resample_width*4 + x*4 + 3] = uncompressed[y*s_resample_width*4 + x*4 + 3];
1089
1090                                                 uncompressed[y*s_resample_width*4 + x*4 + 0] = tmp[0];
1091                                                 uncompressed[y*s_resample_width*4 + x*4 + 1] = tmp[1];
1092                                                 uncompressed[y*s_resample_width*4 + x*4 + 2] = tmp[2];
1093                                                 uncompressed[y*s_resample_width*4 + x*4 + 3] = tmp[3];
1094                                         }
1095                                 }
1096
1097
1098                                 sprintf( buffer, "%svideo/%s/btc%04d.tga", gamedir, s_base, frame );
1099                                 WriteTGA( buffer, uncompressed, s_resample_width, s_resample_height );
1100
1101                                 free( uncompressed );
1102                         }
1103 #endif
1104                 }
1105
1106                 WriteSound( output, frame );
1107
1108                 free (in.data);
1109         }
1110         stop = clock();
1111
1112         printf ("\n");
1113
1114         printf ("Total size: %i\n", ftell( output ) );
1115         printf ("Average error: %f\n", sumError / ( frame - startframe ) );
1116         printf ("Max error: %f\n", maxError );
1117
1118         fseconds = ( stop - start ) / 1000.0f;
1119         minutes = fseconds / 60;
1120         remSeconds = fseconds - minutes * 60;
1121
1122         printf ("Total time: %d s (%d m %d s)\n", ( int ) fseconds, minutes, remSeconds );
1123         printf ("Time/frame: %.2f seconds\n", fseconds / ( frame - startframe ) );
1124
1125         fclose (output);
1126
1127         if ( s_soundtrack )
1128         {
1129                 free( s_soundtrack );
1130                 s_soundtrack = 0;
1131         }
1132 }