1 /*
2 * File: texture.c
3 *
4 * Description: funcs for creating and adding textures.
5 *
6 * This source code is part of kludge3d, and is released under the
7 * GNU General Public License.
8 *
9 *
10 */
11
12 #include <stdio.h>
13 #include <stdlib.h>
14 #include <unistd.h>
15 #include <string.h> /* for memset (wtf?) */
16
17 #include "texture.h"
18 #include "globals.h"
19 #include "prefs.h"
20 #include "tex_pcx.h"
21 #include "tex_sgi.h"
22 #include "tex_gdkpixbuf.h"
23
24 #ifdef MEMWATCH
25 #include "memwatch.h"
26 #endif
27
28
29 #define TEX_DEFAULT_SEARCH_PATH ":.:./textures:./images:..:../textures:../images"
30
31
32 /* PROTOTYPES ***********************************************************/
33
34 void tex_remove_alpha( Texture *ntex ) ;
35 void vertical_flip( tex_data * td );
36 void tex_make_mipmaps( Texture * t ) ;
37 void tex_flip( Texture * t ) ;
38 int tex_examine_extension( const gchar *filename ) ;
39 gchar *tex_resolve_filename( const gchar *filename ) ;
40 char *tex_find_relative_filename( char *texname, char *modelname ) ;
41 void tex_populate_pixbuf( Texture * t ) ;
42
43 /* STRUCTS **************************************************************/
44
45 struct texture_file_fmt *tex_fmt_list[] = {
46 &tex_fmt_pcx,
47 &tex_fmt_sgi,
48 &tex_fmt_rgb,
49
50 &tex_fmt_gdkpixbuf, /* this should probably be listed last */
51 NULL
52 };
53
54
55 /* FUNCS ****************************************************************/
56
tex_new(void)57 Texture *tex_new( void ) {
58 Texture * newt;
59
60 newt = ( Texture * ) malloc( sizeof( Texture ) );
61 memset( newt, '\0', sizeof( Texture ) );
62
63 return newt;
64 }
65
tex_delete(Texture * tt)66 void tex_delete( Texture * tt ) {
67
68 int i;
69
70 if( tt == NULL ) return;
71
72 if( tt->pixbuf )
73 g_object_unref( tt->pixbuf );
74
75 if ( tt->filename )
76 free( tt->filename );
77
78 if ( tt->original.data )
79 free( tt->original.data );
80
81 for( i = 0; i < TEXTURE_NUM_MIP_LEVELS; i++ ) {
82 if ( tt->mip[i].data )
83 free( tt->mip[i].data );
84 }
85
86 free( tt );
87 }
88
89
90
vertical_flip(tex_data * td)91 void vertical_flip( tex_data * td ) {
92 unsigned char * tmp_buff;
93 int c, d, off, off2;
94 int width, height, depth;
95
96 if( td == NULL ) return;
97
98 width = td->width;
99 height = td->height;
100 depth = td->depth;
101
102 d = width * depth;
103
104 tmp_buff = ( unsigned char * ) malloc( width * height * depth );
105
106 for ( c = height - 1, off = 0, off2 = width * ( height - 1 ) * depth;
107 c >= 0; c--, off += d, off2 -= d )
108 memcpy( tmp_buff + off, td->data + off2, d );
109
110 free( td->data );
111 td->data = tmp_buff;
112 }
113
114
tex_scale(tex_data * td_src,tex_data * td_dest,int width,int height)115 void tex_scale( tex_data *td_src, tex_data *td_dest, int width, int height ) {
116
117 // this takes the info in td_src, scales it, and puts it in td_dest.
118 // I couldn't find any algos online for image scaling (granted, I didn't
119 // look very hard) so I wrote my own. It's icky, but it seems to work.
120
121 unsigned char * tmp_buff;
122 int x, y;
123 int depth;
124 int src_current_row;
125 int src_current_col;
126 int dest_offset = 0;
127
128 if( td_src == NULL || td_src->data == NULL || td_dest == NULL )
129 return;
130
131 depth = td_src->depth;
132
133 tmp_buff = ( unsigned char * ) malloc( width * height * depth );
134
135 for( y = 0; y < height; y++ ) {
136
137 src_current_row = (int)( (float)td_src->height * ( (float)y / (float)height ) ) * ( td_src->width * depth );
138 // array index of the first element of the current row
139
140 for( x = 0; x < width; x++ ) {
141
142 src_current_col = (int)( (float)td_src->width * ( (float)x / (float)width ) ) * depth;
143
144 tmp_buff[ dest_offset ] = td_src->data[ src_current_row + src_current_col ];
145 tmp_buff[ dest_offset +1 ] = td_src->data[ src_current_row + src_current_col +1 ];
146 tmp_buff[ dest_offset +2 ] = td_src->data[ src_current_row + src_current_col +2 ];
147
148 dest_offset += depth;
149
150 }
151
152 }
153
154 if( td_dest->data != NULL )
155 free( td_dest->data );
156
157 td_dest->data = tmp_buff;
158 td_dest->width = width;
159 td_dest->height = height;
160 td_dest->depth = td_src->depth;
161 }
162
163
164
165
tex_make_mipmaps(Texture * t)166 void tex_make_mipmaps( Texture * t ) {
167
168 // generate mipmaps for this texture!
169
170 g_return_if_fail( t != NULL );
171 g_return_if_fail( t->original.data != NULL );
172
173 // fixme - minor kludge - i want to use a loop here, but i can't,
174 // because TEXTURE_MIP_LEVEL_*_SIZE are individual defs, not in
175 // a nice convenient array
176
177 tex_scale( &(t->original), &(t->mip[TEXTURE_MIP_LEVEL_SMALLEST]),
178 TEXTURE_MIP_LEVEL_SMALLEST_SIZE, TEXTURE_MIP_LEVEL_SMALLEST_SIZE );
179
180 tex_scale( &(t->original), &(t->mip[TEXTURE_MIP_LEVEL_MEDIUM]),
181 TEXTURE_MIP_LEVEL_MEDIUM_SIZE, TEXTURE_MIP_LEVEL_MEDIUM_SIZE );
182
183 tex_scale( &(t->original), &(t->mip[TEXTURE_MIP_LEVEL_LARGE]),
184 TEXTURE_MIP_LEVEL_LARGE_SIZE, TEXTURE_MIP_LEVEL_LARGE_SIZE );
185
186 tex_scale( &(t->original), &(t->mip[TEXTURE_MIP_LEVEL_OPENGL]),
187 TEXTURE_MIP_LEVEL_OPENGL_SIZE, TEXTURE_MIP_LEVEL_OPENGL_SIZE );
188 }
189
190
tex_flip(Texture * t)191 void tex_flip( Texture * t ) {
192
193 /* flips texture as needed */
194
195 /* opengl's textures use a different origin than that of
196 x11/gtk/etc. If the gtk textures need to be flipped, the ogl one
197 will not. If the gtk textures don't need to be flipped, the ogl one
198 will. */
199 if( t->upside_down ) {
200 vertical_flip( &(t->mip[TEXTURE_MIP_LEVEL_SMALLEST]) );
201 vertical_flip( &(t->mip[TEXTURE_MIP_LEVEL_MEDIUM]) );
202 vertical_flip( &(t->mip[TEXTURE_MIP_LEVEL_LARGE]) );
203 /* don't flip the opengl one... */
204 } else {
205 /* don't flip the gtk ones... */
206 vertical_flip( &(t->mip[TEXTURE_MIP_LEVEL_OPENGL]) );
207 }
208 }
209
210
tex_load(const gchar * file_name,int fmt_idx,char * model_fname)211 Texture * tex_load( const gchar *file_name, int fmt_idx, char *model_fname ) {
212
213 Texture *tex = NULL;
214 FILE *fp = NULL;
215 int status;
216 char *tex_name = NULL;
217 char *old_pwd = NULL;
218
219 g_return_val_if_fail(file_name != NULL, NULL);
220 g_return_val_if_fail(fmt_idx >= TEXTURE_FORMAT_UNKNOWN, NULL);
221
222 /* remember the current working directory; change directory to the
223 model's directory. This is important, because the texture names are
224 relative to the model's path. */
225 if( model_fname ) {
226 char *model_dir;
227 old_pwd = g_get_current_dir();
228 printf( "%s - pwd is %s\n", __FUNCTION__, old_pwd );
229 model_dir = g_path_get_dirname( model_fname );
230 chdir( model_dir );
231 g_free( model_dir );
232 }
233
234 if( fmt_idx == TEXTURE_FORMAT_UNKNOWN )
235 fmt_idx = tex_examine_extension( file_name );
236 if( fmt_idx == TEXTURE_FORMAT_UNKNOWN )
237 goto quit;
238
239 if( g_path_is_absolute( file_name ) ) {
240 tex_name = g_strdup( file_name );
241 } else {
242 tex_name = tex_resolve_filename( file_name );
243 }
244
245 if( tex_name == NULL )
246 goto quit;
247
248 fp = fopen( tex_name, "r" );
249 if( fp == NULL )
250 goto quit;
251
252 tex = tex_new();
253
254 if( g_path_is_absolute( file_name ) ) {
255 char *rel_name =
256 tex_find_relative_filename( tex_name, model_fname );
257 tex->filename = strdup( rel_name );
258 free( rel_name );
259 } else {
260 tex->filename = strdup( tex_name );
261 }
262
263 status = (*(tex_fmt_list[fmt_idx]->load_tex))( fp, tex );
264
265 fclose( fp );
266
267 if( status == TRUE ) {
268 tex_delete( tex );
269 tex = NULL;
270 goto quit;
271 }
272
273 tex_remove_alpha( tex );
274 tex_make_mipmaps( tex );
275 tex_flip( tex );
276
277 if( tex->pixbuf == NULL ) {
278 tex_populate_pixbuf( tex );
279 }
280
281 quit:
282 if( tex_name )
283 g_free( tex_name );
284 if( old_pwd ) {
285 chdir( old_pwd );
286 g_free( old_pwd );
287 }
288
289 return tex;
290 }
291
292
293
tex_examine_extension(const gchar * filename)294 int tex_examine_extension( const gchar *filename ) {
295 gint i, j, l, m;
296 int result = TEXTURE_FORMAT_UNKNOWN;
297
298 m = strlen(filename);
299 for(i = 0; (tex_fmt_list[i] != NULL); i++)
300 {
301 if( tex_fmt_list[i]->load_tex == NULL )
302 continue;
303
304 /* NULL means wildcard */
305 if( tex_fmt_list[i]->extension == NULL ) {
306 /* We've encountered an entry in the tex_fmt_list that claims
307 to be able to load multiple file formats. We'll remember that,
308 in case we don't/didn't find a more suitable loader. */
309 result = i;
310 continue;
311 }
312
313 l = strlen(tex_fmt_list[i]->extension);
314 if(l >= m)
315 continue;
316
317 for(j = 0; j < l; j++) {
318 if(tex_fmt_list[i]->extension[l - j] != filename[m - j])
319 break;
320 }
321
322 if((j == l) && (filename[m-l - 1] == '.'))
323 return i;
324 }
325 return result;
326 }
327
328
tex_resolve_filename(const gchar * filename)329 gchar *tex_resolve_filename( const gchar *filename ) {
330 char *pathstring, *ptr, buf[1024] = {'\0'};
331
332 if( g_file_test( filename, G_FILE_TEST_EXISTS ) ) {
333 return( g_strdup( filename ) );
334 }
335
336 pathstring = strdup( pref_get_string(
337 "Textures::Texture Search Path (colon-separated)",
338 TEX_DEFAULT_SEARCH_PATH ) );
339 ptr = strtok( pathstring, ":" );
340 while( ptr ) {
341 snprintf( buf, 1023, "%s/%s", ptr, filename );
342 if( g_file_test( buf, G_FILE_TEST_EXISTS ) )
343 break;
344 ptr = strtok( NULL, ":" );
345 }
346
347 free( pathstring );
348 return( ptr ? g_strdup( buf ) : NULL );
349 }
350
351
tex_find_relative_filename(char * texname,char * modelname)352 char *tex_find_relative_filename( char *texname, char *modelname ) {
353 char *mstr, buf[1024] = {'\0'};
354 int i, depth = 0;
355 int len_mstr, len_texname, first_diff;
356
357 #if 0
358 /* if modelname is not valid, use pwd (make sure pwd is '/'-terminated) */
359 if( modelname == NULL ) {
360 mstr = g_get_current_dir();
361 printf( "%s - pwd is %s\n", __FUNCTION__, mstr );
362 } else {
363 mstr = g_strdup( modelname );
364 }
365 #else
366 /* if modelname is not valid, use full (absolute) texname */
367 if( modelname == NULL ) {
368 return strdup( texname );
369 } else {
370 mstr = g_strdup( modelname );
371 }
372 #endif
373
374 /* examine both until a difference is found */
375 len_mstr = strlen( mstr );
376 len_texname = strlen( texname );
377 for( i = 0; i < len_mstr && i < len_texname; i++ ) {
378 if( mstr[i] != texname[i] ) {
379 break;
380 }
381 }
382
383 first_diff = i;
384
385 /* at first different char in mstr, start counting '/'s; store as 'depth' */
386 for( i = first_diff; i < len_mstr; i++ ) {
387 if( mstr[i] == '/' )
388 depth++;
389 }
390
391 /* rewind texname until we hit a '/', and (if we do hit a '/') take one
392 step forward */
393 while( first_diff >= 0 ) {
394 if( texname[first_diff] == '/' || first_diff == 0 )
395 break;
396 else
397 first_diff--;
398 }
399 if( texname[first_diff] == '/' )
400 first_diff++;
401
402 /* relative file name for texname is:
403 ('../' * depth) + ptr-to-first-diff-char-in-texname */
404 for( i = 0; i < depth; i++ ) {
405 strcat( buf, "../" );
406 }
407 strcat( buf, texname + first_diff );
408
409 g_free( mstr );
410 return strdup( buf );
411 }
412
413
tex_remove_alpha(Texture * ntex)414 void tex_remove_alpha( Texture *ntex ) {
415
416 unsigned char * tmp_buff;
417 char *off_src, *off_dest;
418 int i;
419 int width, height, depth3, depth4;
420
421 if( ntex == NULL )
422 return;
423
424 width = ntex->original.width;
425 height = ntex->original.height;
426 depth4 = ntex->original.depth;
427 depth3 = 3;
428
429 if( depth4 != 4 )
430 return;
431
432 tmp_buff = ( unsigned char * ) malloc( width * height * depth3 );
433
434 off_src = ntex->original.data;
435 off_dest = tmp_buff;
436
437 for ( i = width * height; i >= 0; i-- ) {
438
439 off_dest[0] = off_src[0];
440 off_dest[1] = off_src[1];
441 off_dest[2] = off_src[2];
442
443 off_src += depth4;
444 off_dest += depth3;
445 }
446
447 free( ntex->original.data );
448 ntex->original.data = tmp_buff;
449 ntex->original.depth = 3;
450 }
451
452
tex_populate_pixbuf(Texture * t)453 void tex_populate_pixbuf( Texture * t ) {
454
455 /* if the texture doesn't have a pixbuf containing a copy of the
456 image, then create one */
457
458 tex_data *orig;
459
460 if( t == NULL || t->pixbuf ) return;
461
462 orig = &(t->original);
463
464 t->pixbuf = gdk_pixbuf_new_from_data(
465 orig->data, GDK_COLORSPACE_RGB,
466 (orig->depth == 4), 8,
467 orig->width, orig->height,
468 orig->width * orig->depth, /* rowstride - dist in bytes between rows */
469 NULL, NULL /* no destructor func is specified for the data that
470 we've passed in; we will take care of that in
471 tex_delete */
472 );
473
474 if( t->upside_down ) {
475 GdkPixbuf *copy;
476 int pb_width, pb_height;
477
478 copy = gdk_pixbuf_copy( t->pixbuf );
479 pb_width = gdk_pixbuf_get_width( copy );
480 pb_height = gdk_pixbuf_get_height( copy );
481 gdk_pixbuf_scale(
482 copy, t->pixbuf,
483 0, 0,
484 pb_width,
485 pb_height,
486 0.0,
487 (double)pb_height,
488 1.0, -1.0,
489 GDK_INTERP_HYPER );
490
491 g_object_unref( copy );
492 }
493 }
494
495
496
497