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