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