io-xpm.c 9.76 KB
Newer Older
1
2
/* GdkPixbuf library - JPEG image loader
 *
3
 * Copyright (C) 1999 Mark Crichton
4
5
6
7
 * Copyright (C) 1999 The Free Software Foundation
 *
 * Authors: Mark Crichton <crichton@gimp.org>
 *          Federico Mena-Quintero <federico@gimp.org>
8
9
10
11
12
13
14
15
16
17
18
19
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public
20
21
22
 * License along with this library; if not, write to the
 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
 * Boston, MA 02111-1307, USA.
23
24
25
26
27
28
29
 */

#include <config.h>
#include <stdio.h>
#include <string.h>
#include <glib.h>
#include <gdk/gdk.h>
30
#include "gdk-pixbuf.h"
31
32


33
34
35
36
37
38
39
40
41

/* I have must have done something to deserve this.
 * XPM is such a crappy format to handle.
 * This code is an ugly hybred from gdkpixmap.c
 * modified to respect transparent colors.
 * It's still a mess, though.
 */

enum buf_op {
42
43
44
	op_header,
	op_cmap,
	op_body
45
46
47
};

typedef struct {
48
49
50
	gchar *color_string;
	GdkColor color;
	gint transparent;
51
52
53
} _XPMColor;

struct file_handle {
54
55
56
	FILE *infile;
	gchar *buffer;
	guint buffer_size;
57
};
58
59

struct mem_handle {
60
	const gchar **data;
61
	int offset;
62
};
63
64

static gint
65
xpm_seek_string (FILE *infile, const gchar *str, gint skip_comments)
66
{
67
	char instr[1024];
68

69
70
71
72
73
74
75
	while (!feof (infile)) {
		fscanf (infile, "%1023s", instr);
		if (skip_comments == TRUE && strcmp (instr, "/*") == 0) {
			fscanf (infile, "%1023s", instr);
			while (!feof (infile) && strcmp (instr, "*/") != 0)
				fscanf (infile, "%1023s", instr);
			fscanf (infile, "%1023s", instr);
76
		}
77
78

		if (strcmp (instr, str) == 0)
79
			return TRUE;
80
81
	}

82
	return FALSE;
83
84
85
}

static gint
86
xpm_seek_char (FILE *infile, gchar c)
87
{
88
89
	gint b, oldb;

90
	while ((b = getc (infile)) != EOF) {
91
		if (c != b && b == '/') {
92
			b = getc (infile);
93
94
			if (b == EOF)
				return FALSE;
95

96
97
98
99
			else if (b == '*') {	/* we have a comment */
				b = -1;
				do {
					oldb = b;
100
					b = getc (infile);
101
102
					if (b == EOF)
						return FALSE;
103
				} while (!(oldb == '*' && b == '/'));
104
105
106
107
			}
		} else if (c == b)
			return TRUE;
	}
108

109
	return FALSE;
110
111
112
}

static gint
113
xpm_read_string (FILE *infile, gchar **buffer, guint *buffer_size)
114
{
115
116
117
118
119
120
121
	gint c;
	guint cnt = 0, bufsiz, ret = FALSE;
	gchar *buf;

	buf = *buffer;
	bufsiz = *buffer_size;
	if (buf == NULL) {
122
123
		bufsiz = 10 * sizeof (gchar);
		buf = g_new (gchar, bufsiz);
124
	}
125
126

	do {
127
		c = getc (infile);
128
129
	} while (c != EOF && c != '"');

130
	if (c != '"')
131
132
		goto out;

133
	while ((c = getc (infile)) != EOF) {
134
135
		if (cnt == bufsiz) {
			guint new_size = bufsiz * 2;
136

137
138
139
140
141
			if (new_size > bufsiz)
				bufsiz = new_size;
			else
				goto out;

142
			buf = g_realloc (buf, bufsiz);
143
144
			buf[bufsiz - 1] = '\0';
		}
145

146
147
148
149
150
151
152
		if (c != '"')
			buf[cnt++] = c;
		else {
			buf[cnt] = 0;
			ret = TRUE;
			break;
		}
153
154
	}

155
156
157
158
159
 out:
	buf[bufsiz - 1] = '\0';	/* ensure null termination for errors */
	*buffer = buf;
	*buffer_size = bufsiz;
	return ret;
160
161
}

162
163
static const gchar *
xpm_skip_whitespaces (const gchar *buffer)
164
{
165
	gint32 index = 0;
166

167
168
	while (buffer[index] != 0 && (buffer[index] == 0x20 || buffer[index] == 0x09))
		index++;
169

170
	return &buffer[index];
171
172
}

173
174
static const gchar *
xpm_skip_string (const gchar *buffer)
175
{
176
	gint32 index = 0;
177

178
179
	while (buffer[index] != 0 && buffer[index] != 0x20 && buffer[index] != 0x09)
		index++;
180

181
	return &buffer[index];
182
183
184
185
186
187
}

/* Xlib crashed once at a color name lengths around 125 */
#define MAX_COLOR_LEN 120

static gchar *
188
xpm_extract_color (const gchar *buffer)
189
{
190
	gint counter, numnames;
191
192
	const gchar *ptr = NULL;
        gchar ch, temp[128];
193
194
	gchar color[MAX_COLOR_LEN], *retcol;
	gint space;
195

196
197
	counter = 0;
	while (ptr == NULL) {
198
		if ((buffer[counter] == 'c') || (buffer[counter] == 'g')) {
199
200
201
202
203
204
205
206
			ch = buffer[counter + 1];
			if (ch == 0x20 || ch == 0x09)
				ptr = &buffer[counter + 1];
		} else if (buffer[counter] == 0)
			return NULL;

		counter++;
	}
207
	ptr = xpm_skip_whitespaces (ptr);
208
209
210
211
212
213
214
215
216
217

	if (ptr[0] == 0)
		return NULL;
	else if (ptr[0] == '#') {
		counter = 1;
		while (ptr[counter] != 0 &&
		       ((ptr[counter] >= '0' && ptr[counter] <= '9') ||
			(ptr[counter] >= 'a' && ptr[counter] <= 'f') ||
			(ptr[counter] >= 'A' && ptr[counter] <= 'F')))
			counter++;
218
219
		retcol = g_new (gchar, counter + 1);
		strncpy (retcol, ptr, counter);
220
221
222
223
224
225
226
227
228
229

		retcol[counter] = 0;

		return retcol;
	}
	color[0] = 0;
	numnames = 0;

	space = MAX_COLOR_LEN - 1;
	while (space > 0) {
230
		sscanf (ptr, "%127s", temp);
231
232

		if (((gint) ptr[0] == 0) ||
233
234
		    (strcmp ("s", temp) == 0) || (strcmp ("m", temp) == 0) ||
		    (strcmp ("g", temp) == 0) || (strcmp ("g4", temp) == 0))
235
			break;
236
		else {
237
238
			if (numnames > 0) {
				space -= 1;
239
				strcat (color, " ");
240
			}
241
242
243
244
245

			strncat (color, temp, space);
			space -= MIN (space, strlen (temp));
			ptr = xpm_skip_string (ptr);
			ptr = xpm_skip_whitespaces (ptr);
246
247
			numnames++;
		}
248
249
	}

250
	retcol = g_strdup (color);
251
	return retcol;
252
253
254
255
}

/* (almost) direct copy from gdkpixmap.c... loads an XPM from a file */

256
static const gchar *
257
file_buffer (enum buf_op op, gpointer handle)
258
{
259
260
261
262
	struct file_handle *h = handle;

	switch (op) {
	case op_header:
263
		if (xpm_seek_string (h->infile, "XPM", FALSE) != TRUE)
264
265
			break;

266
		if (xpm_seek_char (h->infile, '{') != TRUE)
267
268
269
270
			break;
		/* Fall through to the next xpm_seek_char. */

	case op_cmap:
271
272
		xpm_seek_char (h->infile, '"');
		fseek (h->infile, -1, SEEK_CUR);
273
274
275
		/* Fall through to the xpm_read_string. */

	case op_body:
276
		xpm_read_string (h->infile, &h->buffer, &h->buffer_size);
277
		return h->buffer;
278
279
280

	default:
		g_assert_not_reached ();
281
	}
282
283

	return NULL;
284
285
286
}

/* This reads from memory */
287
static const gchar *
288
mem_buffer (enum buf_op op, gpointer handle)
289
{
290
291
292
293
294
	struct mem_handle *h = handle;
	switch (op) {
	case op_header:
	case op_cmap:
	case op_body:
295
296
297
298
299
300
301
302
                if (h->data[h->offset]) {
                        const gchar* retval;

                        retval = h->data[h->offset];
                        h->offset += 1;
                        return retval;
                }
                break;
303
304
305

	default:
		g_assert_not_reached ();
306
                break;
307
	}
308

309
	return NULL;
310
311
}

312
313
314
315
316
317
/* Destroy notification function for the libart pixbuf */
static void
free_buffer (gpointer user_data, gpointer data)
{
	free (data);
}
318

319
/* This function does all the work. */
320
static GdkPixbuf *
321
pixbuf_create_from_xpm (const gchar * (*get_buf) (enum buf_op op, gpointer handle), gpointer handle)
322
{
323
324
325
	gint w, h, n_col, cpp;
	gint cnt, xcnt, ycnt, wbytes, n, ns;
	gint is_trans = FALSE;
326
327
	const gchar *buffer;
        gchar *name_buf;
328
329
330
	gchar pixel_str[32];
	GHashTable *color_hash;
	_XPMColor *colors, *color, *fallbackcolor;
331
	guchar *pixels, *pixtmp;
332

Arturo Espinosa's avatar
Arturo Espinosa committed
333
334
	fallbackcolor = NULL;

335
	buffer = (*get_buf) (op_header, handle);
336
	if (!buffer) {
337
		g_warning ("No XPM header found");
338
		return NULL;
339
	}
340
	sscanf (buffer, "%d %d %d %d", &w, &h, &n_col, &cpp);
341
	if (cpp >= 32) {
342
		g_warning ("XPM has more than 31 chars per pixel.");
343
		return NULL;
344
	}
345

346
	/* The hash is used for fast lookups of color from chars */
347
	color_hash = g_hash_table_new (g_str_hash, g_str_equal);
348

349
350
	name_buf = g_new (gchar, n_col * (cpp + 1));
	colors = g_new (_XPMColor, n_col);
351
352
353
354
355
356

	for (cnt = 0; cnt < n_col; cnt++) {
		gchar *color_name;

		buffer = (*get_buf) (op_cmap, handle);
		if (!buffer) {
357
358
359
360
			g_warning ("Can't load XPM colormap");
			g_hash_table_destroy (color_hash);
			g_free (name_buf);
			g_free (colors);
361
362
			return NULL;
		}
363

364
365
		color = &colors[cnt];
		color->color_string = &name_buf[cnt * (cpp + 1)];
366
		strncpy (color->color_string, buffer, cpp);
367
		color->color_string[cpp] = 0;
368
		buffer += strlen (color->color_string);
369
370
		color->transparent = FALSE;

371
		color_name = xpm_extract_color (buffer);
372

373
374
		if ((color_name == NULL) || (g_strcasecmp (color_name, "None") == 0)
		    || (gdk_color_parse (color_name, &color->color) == FALSE)) {
375
376
377
			color->transparent = TRUE;
			is_trans = TRUE;
		}
Mark Crichton's avatar
Mark Crichton committed
378

379
380
		g_free (color_name);
		g_hash_table_insert (color_hash, color->color_string, color);
381

382
383
384
		if (cnt == 0)
			fallbackcolor = color;
	}
385

386
	if (is_trans)
387
		pixels = malloc (w * h * 4);
388
	else
389
		pixels = malloc (w * h * 3);
390
391

	if (!pixels) {
392
393
394
		g_hash_table_destroy (color_hash);
		g_free (colors);
		g_free (name_buf);
395
396
		return NULL;
	}
397

398
399
400
401
402
	wbytes = w * cpp;
	pixtmp = pixels;

	for (ycnt = 0; ycnt < h; ycnt++) {
		buffer = (*get_buf) (op_body, handle);
403
		if ((!buffer) || (strlen (buffer) < wbytes))
404
			continue;
405

406
		for (n = 0, cnt = 0, xcnt = 0; n < wbytes; n += cpp, xcnt++) {
407
			strncpy (pixel_str, &buffer[n], cpp);
408
409
410
			pixel_str[cpp] = 0;
			ns = 0;

411
			color = g_hash_table_lookup (color_hash, pixel_str);
412
413
414
415
416

			/* Bad XPM...punt */
			if (!color)
				color = fallbackcolor;

417
418
419
			*pixtmp++ = color->color.red >> 8;
			*pixtmp++ = color->color.green >> 8;
			*pixtmp++ = color->color.blue >> 8;
420

421
			if (is_trans && color->transparent)
422
				*pixtmp++ = 0;
423
			else if (is_trans)
424
425
426
				*pixtmp++ = 0xFF;
		}
	}
Mark Crichton's avatar
Mark Crichton committed
427

428
429
430
	g_hash_table_destroy (color_hash);
	g_free (colors);
	g_free (name_buf);
431

432
433
434
	return gdk_pixbuf_new_from_data (pixels, ART_PIX_RGB, is_trans,
					 w, h, is_trans ? (w * 4) : (w * 3),
					 free_buffer, NULL);
435
436
437
}

/* Shared library entry point for file loading */
438
439
GdkPixbuf *
image_load (FILE *f)
440
{
441
442
	GdkPixbuf *pixbuf;
	struct file_handle h;
443

444
	memset (&h, 0, sizeof (h));
445
	h.infile = f;
446
447
	pixbuf = pixbuf_create_from_xpm (file_buffer, &h);
	g_free (h.buffer);
448

449
	return pixbuf;
450
}
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465

/* Shared library entry point for memory loading */
GdkPixbuf *
image_load_xpm_data (const gchar **data)
{
        GdkPixbuf *pixbuf;
        struct mem_handle h;

        h.data = data;
        h.offset = 0;
        
	pixbuf = pixbuf_create_from_xpm (mem_buffer, &h);
        
	return pixbuf;
}