1 //-----------------------------------------------------------------------------
2 //
3 // ImageLib Sources
4 // Copyright (C) 2000-2009 by Denton Woods
5 // Last modified: 01/04/2009
6 //
7 // Filename: src-IL/src/il_xpm.c
8 //
9 // Description: Reads from an .xpm file.
10 //
11 //-----------------------------------------------------------------------------
12 
13 
14 #include "il_internal.h"
15 #ifndef IL_NO_XPM
16 #include <ctype.h>
17 
18 
19 //If this is defined, only xpm files with 1 char/pixel
20 //can be loaded. They load somewhat faster then, though
21 //(not much).
22 //#define XPM_DONT_USE_HASHTABLE
23 
24 ILboolean iIsValidXpm(void);
25 ILboolean iLoadXpmInternal(void);
26 ILint XpmGetsInternal(ILubyte *Buffer, ILint MaxLen);
27 
28 //! Checks if the file specified in FileName is a valid XPM file.
ilIsValidXpm(ILconst_string FileName)29 ILboolean ilIsValidXpm(ILconst_string FileName)
30 {
31 	ILHANDLE	XpmFile;
32 	ILboolean	bXpm = IL_FALSE;
33 
34 	if (!iCheckExtension(FileName, IL_TEXT("xpm"))) {
35 		ilSetError(IL_INVALID_EXTENSION);
36 		return bXpm;
37 	}
38 
39 	XpmFile = iopenr(FileName);
40 	if (XpmFile == NULL) {
41 		ilSetError(IL_COULD_NOT_OPEN_FILE);
42 		return bXpm;
43 	}
44 
45 	bXpm = ilIsValidXpmF(XpmFile);
46 	icloser(XpmFile);
47 
48 	return bXpm;
49 }
50 
51 
52 //! Checks if the ILHANDLE contains a valid XPM file at the current position.
ilIsValidXpmF(ILHANDLE File)53 ILboolean ilIsValidXpmF(ILHANDLE File)
54 {
55 	ILuint		FirstPos;
56 	ILboolean	bRet;
57 
58 	iSetInputFile(File);
59 	FirstPos = itell();
60 	bRet = iIsValidXpm();
61 	iseek(FirstPos, IL_SEEK_SET);
62 
63 	return bRet;
64 }
65 
66 
67 //! Checks if Lump is a valid XPM lump.
ilIsValidXpmL(const void * Lump,ILuint Size)68 ILboolean ilIsValidXpmL(const void *Lump, ILuint Size)
69 {
70 	iSetInputLump(Lump, Size);
71 	return iIsValidXpm();
72 }
73 
74 
75 // Internal function to get the header and check it.
iIsValidXpm(void)76 ILboolean iIsValidXpm(void)
77 {
78 	ILubyte	Buffer[10];
79 	ILuint	Pos = itell();
80 
81 	XpmGetsInternal(Buffer, 10);
82 	iseek(Pos, IL_SEEK_SET);  // Restore position
83 
84 	if (strncmp("/* XPM */", (char*)Buffer, strlen("/* XPM */")))
85 		return IL_FALSE;
86 	return IL_TRUE;
87 }
88 
89 
90 // Reads an .xpm file
ilLoadXpm(ILconst_string FileName)91 ILboolean ilLoadXpm(ILconst_string FileName)
92 {
93 	ILHANDLE	XpmFile;
94 	ILboolean	bXpm = IL_FALSE;
95 
96 	XpmFile = iopenr(FileName);
97 	if (XpmFile == NULL) {
98 		ilSetError(IL_COULD_NOT_OPEN_FILE);
99 		return bXpm;
100 	}
101 
102 	iSetInputFile(XpmFile);
103 	bXpm = ilLoadXpmF(XpmFile);
104 	icloser(XpmFile);
105 
106 	return bXpm;
107 }
108 
109 
110 //! Reads an already-opened .xpm file
ilLoadXpmF(ILHANDLE File)111 ILboolean ilLoadXpmF(ILHANDLE File)
112 {
113 	ILuint		FirstPos;
114 	ILboolean	bRet;
115 
116 	iSetInputFile(File);
117 	FirstPos = itell();
118 	bRet = iLoadXpmInternal();
119 	iseek(FirstPos, IL_SEEK_SET);
120 
121 	return bRet;
122 }
123 
124 
125 //! Reads from a memory "lump" that contains an .xpm
ilLoadXpmL(const void * Lump,ILuint Size)126 ILboolean ilLoadXpmL(const void *Lump, ILuint Size)
127  {
128 	iSetInputLump(Lump, Size);
129 	return iLoadXpmInternal();
130 }
131 
132 
133 typedef ILubyte XpmPixel[4];
134 
135 #define XPM_MAX_CHAR_PER_PIXEL 2
136 
137 
138 #ifndef XPM_DONT_USE_HASHTABLE
139 
140 //The following hash table code was inspired by the xpm
141 //loading code of xv, one of the best image viewers of X11
142 
143 //For xpm files with more than one character/pixel, it is
144 //impractical to use a simple lookup table for the
145 //character-to-color mapping (because the table requires
146 //2^(chars/pixel) entries, this is quite big).
147 //Because of that, a hash table is used for the mapping.
148 //The hash table has 257 entries, and collisions are
149 //resolved by chaining.
150 
151 //257 is the smallest prime > 256
152 #define XPM_HASH_LEN 257
153 
154 typedef struct XPMHASHENTRY
155 {
156 	ILubyte ColourName[XPM_MAX_CHAR_PER_PIXEL];
157 	XpmPixel ColourValue;
158 	struct XPMHASHENTRY *Next;
159 } XPMHASHENTRY;
160 
161 
XpmHash(const ILubyte * name,int len)162 static ILuint XpmHash(const ILubyte* name, int len)
163 {
164 	ILint i, sum;
165 	for (sum = i = 0; i < len; ++i)
166 		sum += name[i];
167 	return sum % XPM_HASH_LEN;
168 }
169 
170 
XpmCreateHashTable()171 XPMHASHENTRY** XpmCreateHashTable()
172 {
173 	XPMHASHENTRY** Table =
174 		(XPMHASHENTRY**)ialloc(XPM_HASH_LEN*sizeof(XPMHASHENTRY*));
175 	if (Table != NULL)
176 		memset(Table, 0, XPM_HASH_LEN*sizeof(XPMHASHENTRY*));
177 	return Table;
178 }
179 
180 
XpmDestroyHashTable(XPMHASHENTRY ** Table)181 void XpmDestroyHashTable(XPMHASHENTRY **Table)
182 {
183 	ILint i;
184 	XPMHASHENTRY* Entry;
185 
186 	for (i = 0; i < XPM_HASH_LEN; ++i) {
187 		while (Table[i] != NULL) {
188 			Entry = Table[i]->Next;
189 			ifree(Table[i]);
190 			Table[i] = Entry;
191 		}
192 	}
193 
194 	ifree(Table);
195 }
196 
197 
XpmInsertEntry(XPMHASHENTRY ** Table,const ILubyte * Name,int Len,XpmPixel Colour)198 void XpmInsertEntry(XPMHASHENTRY **Table, const ILubyte* Name, int Len, XpmPixel Colour)
199 {
200 	XPMHASHENTRY* NewEntry;
201 	ILuint Index;
202 	Index = XpmHash(Name, Len);
203 
204 	NewEntry = (XPMHASHENTRY*)ialloc(sizeof(XPMHASHENTRY));
205 	if (NewEntry != NULL) {
206 		NewEntry->Next = Table[Index];
207 		memcpy(NewEntry->ColourName, Name, Len);
208 		memcpy(NewEntry->ColourValue, Colour, sizeof(Colour));
209 		Table[Index] = NewEntry;
210 	}
211 }
212 
213 
XpmGetEntry(XPMHASHENTRY ** Table,const ILubyte * Name,int Len,XpmPixel Colour)214 void XpmGetEntry(XPMHASHENTRY **Table, const ILubyte* Name, int Len, XpmPixel Colour)
215 {
216 	XPMHASHENTRY* Entry;
217 	ILuint Index;
218 
219 	Index = XpmHash(Name, Len);
220 	Entry = Table[Index];
221 	while (Entry != NULL && strncmp((char*)(Entry->ColourName), (char*)Name, Len) != 0)
222 		Entry = Entry->Next;
223 
224 	if (Entry != NULL)
225 		memcpy(Colour, Entry->ColourValue, sizeof(Colour));
226 }
227 
228 #endif //XPM_DONT_USE_HASHTABLE
229 
230 
XpmGetsInternal(ILubyte * Buffer,ILint MaxLen)231 ILint XpmGetsInternal(ILubyte *Buffer, ILint MaxLen)
232 {
233 	ILint	i = 0, Current;
234 
235 	if (ieof())
236 		return IL_EOF;
237 
238 	while ((Current = igetc()) != IL_EOF && i < MaxLen - 1) {
239 		if (Current == IL_EOF)
240 			return 0;
241 		if (Current == '\n') //unix line ending
242 			break;
243 
244 		if (Current == '\r') { //dos/mac line ending
245 			Current = igetc();
246 			if (Current == '\n') //dos line ending
247 				break;
248 
249 			if (Current == IL_EOF)
250 				break;
251 
252 			Buffer[i++] = (ILubyte)Current;
253 			continue;
254 		}
255 		Buffer[i++] = (ILubyte)Current;
256 	}
257 
258 	Buffer[i++] = 0;
259 
260 	return i;
261 }
262 
263 
XpmGets(ILubyte * Buffer,ILint MaxLen)264 ILint XpmGets(ILubyte *Buffer, ILint MaxLen)
265 {
266 	ILint		Size, i, j;
267 	ILboolean	NotComment = IL_FALSE, InsideComment = IL_FALSE;
268 
269 	do {
270 		Size = XpmGetsInternal(Buffer, MaxLen);
271 		if (Size == IL_EOF)
272 			return IL_EOF;
273 
274 		//skip leading whitespace (sometimes there's whitespace
275 		//before a comment or before the pixel data)
276 
277 		for(i = 0; i < Size && isspace(Buffer[i]); ++i) ;
278 		Size = Size - i;
279 		for(j = 0; j < Size; ++j)
280 			Buffer[j] = Buffer[j + i];
281 
282 		if (Size == 0)
283 			continue;
284 
285 		if (Buffer[0] == '/' && Buffer[1] == '*') {
286 			for (i = 2; i < Size; i++) {
287 				if (Buffer[i] == '*' && Buffer[i+1] == '/') {
288 					break;
289 				}
290 			}
291 			if (i >= Size)
292 				InsideComment = IL_TRUE;
293 		}
294 		else if (InsideComment) {
295 			for (i = 0; i < Size; i++) {
296 				if (Buffer[i] == '*' && Buffer[i+1] == '/') {
297 					break;
298 				}
299 			}
300 			if (i < Size)
301 				InsideComment = IL_FALSE;
302 		}
303 		else {
304 			NotComment = IL_TRUE;
305 		}
306 	} while (!NotComment);
307 
308 	return Size;
309 }
310 
311 
XpmGetInt(ILubyte * Buffer,ILint Size,ILint * Position)312 ILint XpmGetInt(ILubyte *Buffer, ILint Size, ILint *Position)
313 {
314 	char		Buff[1024];
315 	ILint		i, j;
316 	ILboolean	IsInNum = IL_FALSE;
317 
318 	for (i = *Position, j = 0; i < Size; i++) {
319 		if (isdigit(Buffer[i])) {
320 			IsInNum = IL_TRUE;
321 			Buff[j++] = Buffer[i];
322 		}
323 		else {
324 			if (IsInNum) {
325 				Buff[j] = 0;
326 				*Position = i;
327 				return atoi(Buff);
328 			}
329 		}
330 	}
331 
332 	return -1;
333 }
334 
335 
XpmPredefCol(char * Buff,XpmPixel * Colour)336 ILboolean XpmPredefCol(char *Buff, XpmPixel *Colour)
337 {
338 	ILint len;
339 	ILint val = 128;
340 
341 	if (!stricmp(Buff, "none")) {
342 		(*Colour)[0] = 0;
343 		(*Colour)[1] = 0;
344 		(*Colour)[2] = 0;
345 		(*Colour)[3] = 0;
346 		return IL_TRUE;
347 	}
348 
349 	(*Colour)[3] = 255;
350 
351 	if (!stricmp(Buff, "black")) {
352 		(*Colour)[0] = 0;
353 		(*Colour)[1] = 0;
354 		(*Colour)[2] = 0;
355 		return IL_TRUE;
356 	}
357 	if (!stricmp(Buff, "white")) {
358 		(*Colour)[0] = 255;
359 		(*Colour)[1] = 255;
360 		(*Colour)[2] = 255;
361 		return IL_TRUE;
362 	}
363 	if (!stricmp(Buff, "red")) {
364 		(*Colour)[0] = 255;
365 		(*Colour)[1] = 0;
366 		(*Colour)[2] = 0;
367 		return IL_TRUE;
368 	}
369 	if (!stricmp(Buff, "green")) {
370 		(*Colour)[0] = 0;
371 		(*Colour)[1] = 255;
372 		(*Colour)[2] = 0;
373 		return IL_TRUE;
374 	}
375 	if (!stricmp(Buff, "blue")) {
376 		(*Colour)[0] = 0;
377 		(*Colour)[1] = 0;
378 		(*Colour)[2] = 255;
379 		return IL_TRUE;
380 	}
381 	if (!stricmp(Buff, "yellow")) {
382 		(*Colour)[0] = 255;
383 		(*Colour)[1] = 255;
384 		(*Colour)[2] = 0;
385 		return IL_TRUE;
386 	}
387 	if (!stricmp(Buff, "cyan")) {
388 		(*Colour)[0] = 0;
389 		(*Colour)[1] = 255;
390 		(*Colour)[2] = 255;
391 		return IL_TRUE;
392 	}
393 	if (!stricmp(Buff, "gray")) {
394 		(*Colour)[0] = 128;
395 		(*Colour)[1] = 128;
396 		(*Colour)[2] = 128;
397 		return IL_TRUE;
398 	}
399 
400 	//check for grayXXX codes (added 20040218)
401 	len = ilCharStrLen(Buff);
402 	if (len >= 4) {
403 		if (Buff[0] == 'g' || Buff[0] == 'G'
404 			|| Buff[1] == 'r' || Buff[1] == 'R'
405 			|| Buff[2] == 'a' || Buff[2] == 'A'
406 			|| Buff[3] == 'y' || Buff[3] == 'Y') {
407 			if (isdigit(Buff[4])) { // isdigit returns false on '\0'
408 				val = Buff[4] - '0';
409 				if (isdigit(Buff[5])) {
410 					val = val*10 + Buff[5] - '0';
411 					if (isdigit(Buff[6]))
412 						val = val*10 + Buff[6] - '0';
413 				}
414 				val = (255*val)/100;
415 			}
416 			(*Colour)[0] = (ILubyte)val;
417 			(*Colour)[1] = (ILubyte)val;
418 			(*Colour)[2] = (ILubyte)val;
419 			return IL_TRUE;
420 		}
421 	}
422 
423 
424 	// Unknown colour string, so use black
425 	// (changed 20040218)
426 	(*Colour)[0] = 0;
427 	(*Colour)[1] = 0;
428 	(*Colour)[2] = 0;
429 
430 	return IL_FALSE;
431 }
432 
433 
434 #ifndef XPM_DONT_USE_HASHTABLE
XpmGetColour(ILubyte * Buffer,ILint Size,int Len,XPMHASHENTRY ** Table)435 ILboolean XpmGetColour(ILubyte *Buffer, ILint Size, int Len, XPMHASHENTRY **Table)
436 #else
437 ILboolean XpmGetColour(ILubyte *Buffer, ILint Size, int Len, XpmPixel* Colours)
438 #endif
439 {
440 	ILint		i = 0, j, strLen = 0;
441 	ILubyte		ColBuff[3];
442 	char		Buff[1024];
443 
444 	XpmPixel	Colour;
445 	ILubyte		Name[XPM_MAX_CHAR_PER_PIXEL];
446 
447 	for ( ; i < Size; i++) {
448 		if (Buffer[i] == '\"')
449 			break;
450 	}
451 	i++;  // Skip the quotes.
452 
453 	if (i >= Size)
454 		return IL_FALSE;
455 
456 	// Get the characters.
457 	for (j = 0; j < Len; ++j) {
458 		Name[j] = Buffer[i++];
459 	}
460 
461 	// Skip to the colour definition.
462 	for ( ; i < Size; i++) {
463 		if (Buffer[i] == 'c')
464 			break;
465 	}
466 	i++;  // Skip the 'c'.
467 
468 	if (i >= Size || Buffer[i] != ' ') { // no 'c' found...assume black
469 #ifndef XPM_DONT_USE_HASHTABLE
470 		memset(Colour, 0, sizeof(Colour));
471 		Colour[3] = 255;
472 		XpmInsertEntry(Table, Name, Len, Colour);
473 #else
474 		memset(Colours[Name[0]], 0, sizeof(Colour));
475 		Colours[Name[0]][3] = 255;
476 #endif
477 		return IL_TRUE;
478 	}
479 
480 	for ( ; i < Size; i++) {
481 		if (Buffer[i] != ' ')
482 			break;
483 	}
484 
485 	if (i >= Size)
486 		return IL_FALSE;
487 
488 	if (Buffer[i] == '#') {
489 		// colour string may 4 digits/color or 1 digit/color
490 		// (added 20040218) TODO: is isxdigit() ANSI???
491 		++i;
492 		while (i + strLen < Size && isxdigit(Buffer[i + strLen]))
493 			++strLen;
494 
495 		for (j = 0; j < 3; j++) {
496 			if (strLen >= 10) { // 4 digits
497 				ColBuff[0] = Buffer[i + j*4];
498 				ColBuff[1] = Buffer[i + j*4 + 1];
499 			}
500 			else if (strLen >= 8) { // 3 digits
501 				ColBuff[0] = Buffer[i + j*3];
502 				ColBuff[1] = Buffer[i + j*3 + 1];
503 			}
504 			else if (strLen >= 6) { // 2 digits
505 				ColBuff[0] = Buffer[i + j*2];
506 				ColBuff[1] = Buffer[i + j*2 + 1];
507 			}
508 			else if(j < strLen) { // 1 digit, strLen >= 1
509 				ColBuff[0] = Buffer[i + j];
510 				ColBuff[1] = 0;
511 			}
512 
513 			ColBuff[2] = 0; // add terminating '\0' char
514 			Colour[j] = (ILubyte)strtol((char*)ColBuff, NULL, 16);
515 		}
516 		Colour[3] = 255;  // Full alpha.
517 	}
518 	else {
519 		for (j = 0; i < Size; i++) {
520 			if (!isalnum(Buffer[i]))
521 				break;
522 			Buff[j++] = Buffer[i];
523 		}
524 		Buff[j] = 0;
525 
526 		if (i >= Size)
527 			return IL_FALSE;
528 
529 		if (!XpmPredefCol(Buff, &Colour))
530 
531 			return IL_FALSE;
532 	}
533 
534 
535 #ifndef XPM_DONT_USE_HASHTABLE
536 	XpmInsertEntry(Table, Name, Len, Colour);
537 #else
538 	memcpy(Colours[Name[0]], Colour, sizeof(Colour));
539 #endif
540 	return IL_TRUE;
541 }
542 
543 
iLoadXpmInternal()544 ILboolean iLoadXpmInternal()
545 {
546 #define BUFFER_SIZE 2000
547 	ILubyte			Buffer[BUFFER_SIZE], *Data;
548 	ILint			Size, Pos, Width, Height, NumColours, i, x, y;
549 
550 	ILint			CharsPerPixel;
551 
552 #ifndef XPM_DONT_USE_HASHTABLE
553 	XPMHASHENTRY	**HashTable;
554 #else
555 	XpmPixel	*Colours;
556 	ILint		Offset;
557 #endif
558 
559 	Size = XpmGetsInternal(Buffer, BUFFER_SIZE);
560 	if (strncmp("/* XPM */", (char*)Buffer, strlen("/* XPM */"))) {
561 		ilSetError(IL_INVALID_FILE_HEADER);
562 		return IL_FALSE;
563 	}
564 
565 	Size = XpmGets(Buffer, BUFFER_SIZE);
566 	// @TODO:  Actually check the variable name here.
567 
568 	Size = XpmGets(Buffer, BUFFER_SIZE);
569 	Pos = 0;
570 	Width = XpmGetInt(Buffer, Size, &Pos);
571 	Height = XpmGetInt(Buffer, Size, &Pos);
572 	NumColours = XpmGetInt(Buffer, Size, &Pos);
573 
574 	CharsPerPixel = XpmGetInt(Buffer, Size, &Pos);
575 
576 #ifdef XPM_DONT_USE_HASHTABLE
577 	if (CharsPerPixel != 1) {
578 		ilSetError(IL_FORMAT_NOT_SUPPORTED);
579 		return IL_FALSE;
580 	}
581 #endif
582 
583 	if (CharsPerPixel > XPM_MAX_CHAR_PER_PIXEL
584 		|| Width*CharsPerPixel > BUFFER_SIZE) {
585 		ilSetError(IL_FORMAT_NOT_SUPPORTED);
586 		return IL_FALSE;
587 	}
588 
589 #ifndef XPM_DONT_USE_HASHTABLE
590 	HashTable = XpmCreateHashTable();
591 	if (HashTable == NULL)
592 		return IL_FALSE;
593 #else
594 	Colours = ialloc(256 * sizeof(XpmPixel));
595 	if (Colours == NULL)
596 		return IL_FALSE;
597 #endif
598 
599 	for (i = 0; i < NumColours; i++) {
600 		Size = XpmGets(Buffer, BUFFER_SIZE);
601 #ifndef XPM_DONT_USE_HASHTABLE
602 		if (!XpmGetColour(Buffer, Size, CharsPerPixel, HashTable)) {
603 			XpmDestroyHashTable(HashTable);
604 #else
605 		if (!XpmGetColour(Buffer, Size, CharsPerPixel, Colours)) {
606 			ifree(Colours);
607 #endif
608 			return IL_FALSE;
609 		}
610 	}
611 
612 	if (!ilTexImage(Width, Height, 1, 4, IL_RGBA, IL_UNSIGNED_BYTE, NULL)) {
613 #ifndef XPM_DONT_USE_HASHTABLE
614 		XpmDestroyHashTable(HashTable);
615 #else
616 		ifree(Colours);
617 #endif
618 		return IL_FALSE;
619 	}
620 
621 	Data = iCurImage->Data;
622 
623 	for (y = 0; y < Height; y++) {
624 		Size = XpmGets(Buffer, BUFFER_SIZE);
625 		for (x = 0; x < Width; x++) {
626 #ifndef XPM_DONT_USE_HASHTABLE
627 			XpmGetEntry(HashTable, &Buffer[1 + x*CharsPerPixel], CharsPerPixel, &Data[(x << 2)]);
628 #else
629 			Offset = (x << 2);
630 			Data[Offset + 0] = Colours[Buffer[x + 1]][0];
631 			Data[Offset + 1] = Colours[Buffer[x + 1]][1];
632 			Data[Offset + 2] = Colours[Buffer[x + 1]][2];
633 			Data[Offset + 3] = Colours[Buffer[x + 1]][3];
634 #endif
635 		}
636 
637 		Data += iCurImage->Bps;
638 	}
639 
640 	//added 20040218
641 	iCurImage->Origin = IL_ORIGIN_UPPER_LEFT;
642 
643 
644 #ifndef XPM_DONT_USE_HASHTABLE
645 	XpmDestroyHashTable(HashTable);
646 #else
647 	ifree(Colours);
648 #endif
649 	return IL_TRUE;
650 
651 #undef BUFFER_SIZE
652 }
653 
654 #endif//IL_NO_XPM
655 
656