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