]> Pileus Git - ~andy/gtk/blob - gdk-pixbuf/io-xpm.c
0.5.0 - Federico
[~andy/gtk] / gdk-pixbuf / io-xpm.c
1 /* GdkPixbuf library - JPEG image loader
2  *
3  * Copyright (C) 1999 Mark Crichton
4  * Copyright (C) 1999 The Free Software Foundation
5  *
6  * Authors: Mark Crichton <crichton@gimp.org>
7  *          Federico Mena-Quintero <federico@gimp.org>
8  *
9  * This library is free software; you can redistribute it and/or
10  * modify it under the terms of the GNU Library General Public
11  * License as published by the Free Software Foundation; either
12  * version 2 of the License, or (at your option) any later version.
13  *
14  * This library is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
17  * Library General Public License for more details.
18  *
19  * You should have received a copy of the GNU Library General Public
20  * License along with this library; if not, write to the
21  * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
22  * Boston, MA 02111-1307, USA.
23  */
24
25 #include <config.h>
26 #include <stdio.h>
27 #include <string.h>
28 #include <glib.h>
29 #include <gdk/gdk.h>
30 #include "gdk-pixbuf.h"
31
32 \f
33
34 /* I have must have done something to deserve this.
35  * XPM is such a crappy format to handle.
36  * This code is an ugly hybred from gdkpixmap.c
37  * modified to respect transparent colors.
38  * It's still a mess, though.
39  */
40
41 enum buf_op {
42         op_header,
43         op_cmap,
44         op_body
45 };
46
47 typedef struct {
48         gchar *color_string;
49         GdkColor color;
50         gint transparent;
51 } _XPMColor;
52
53 struct file_handle {
54         FILE *infile;
55         gchar *buffer;
56         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         while (!feof (infile)) {
70                 fscanf (infile, "%1023s", instr);
71                 if (skip_comments == TRUE && strcmp (instr, "/*") == 0) {
72                         fscanf (infile, "%1023s", instr);
73                         while (!feof (infile) && strcmp (instr, "*/") != 0)
74                                 fscanf (infile, "%1023s", instr);
75                         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         gint b, oldb;
89
90         while ((b = getc (infile)) != EOF) {
91                 if (c != b && b == '/') {
92                         b = getc (infile);
93                         if (b == EOF)
94                                 return FALSE;
95
96                         else if (b == '*') {    /* we have a comment */
97                                 b = -1;
98                                 do {
99                                         oldb = b;
100                                         b = getc (infile);
101                                         if (b == EOF)
102                                                 return FALSE;
103                                 } while (!(oldb == '*' && b == '/'));
104                         }
105                 } else if (c == b)
106                         return TRUE;
107         }
108
109         return FALSE;
110 }
111
112 static gint
113 xpm_read_string (FILE *infile, gchar **buffer, guint *buffer_size)
114 {
115         gint c;
116         guint cnt = 0, bufsiz, ret = FALSE;
117         gchar *buf;
118
119         buf = *buffer;
120         bufsiz = *buffer_size;
121         if (buf == NULL) {
122                 bufsiz = 10 * sizeof (gchar);
123                 buf = g_new (gchar, bufsiz);
124         }
125
126         do {
127                 c = getc (infile);
128         } while (c != EOF && c != '"');
129
130         if (c != '"')
131                 goto out;
132
133         while ((c = getc (infile)) != EOF) {
134                 if (cnt == bufsiz) {
135                         guint new_size = bufsiz * 2;
136
137                         if (new_size > bufsiz)
138                                 bufsiz = new_size;
139                         else
140                                 goto out;
141
142                         buf = g_realloc (buf, bufsiz);
143                         buf[bufsiz - 1] = '\0';
144                 }
145
146                 if (c != '"')
147                         buf[cnt++] = c;
148                 else {
149                         buf[cnt] = 0;
150                         ret = TRUE;
151                         break;
152                 }
153         }
154
155  out:
156         buf[bufsiz - 1] = '\0'; /* ensure null termination for errors */
157         *buffer = buf;
158         *buffer_size = bufsiz;
159         return ret;
160 }
161
162 static const gchar *
163 xpm_skip_whitespaces (const gchar *buffer)
164 {
165         gint32 index = 0;
166
167         while (buffer[index] != 0 && (buffer[index] == 0x20 || buffer[index] == 0x09))
168                 index++;
169
170         return &buffer[index];
171 }
172
173 static const gchar *
174 xpm_skip_string (const gchar *buffer)
175 {
176         gint32 index = 0;
177
178         while (buffer[index] != 0 && buffer[index] != 0x20 && buffer[index] != 0x09)
179                 index++;
180
181         return &buffer[index];
182 }
183
184 /* Xlib crashed once at a color name lengths around 125 */
185 #define MAX_COLOR_LEN 120
186
187 static gchar *
188 xpm_extract_color (const gchar *buffer)
189 {
190         gint counter, numnames;
191         const gchar *ptr = NULL;
192         gchar ch, temp[128];
193         gchar color[MAX_COLOR_LEN], *retcol;
194         gint space;
195
196         counter = 0;
197         while (ptr == NULL) {
198                 if (buffer[counter] == 'c') {
199                         ch = buffer[counter + 1];
200                         if (ch == 0x20 || ch == 0x09)
201                                 ptr = &buffer[counter + 1];
202                 } else if (buffer[counter] == 0)
203                         return NULL;
204
205                 counter++;
206         }
207         ptr = xpm_skip_whitespaces (ptr);
208
209         if (ptr[0] == 0)
210                 return NULL;
211         else if (ptr[0] == '#') {
212                 counter = 1;
213                 while (ptr[counter] != 0 &&
214                        ((ptr[counter] >= '0' && ptr[counter] <= '9') ||
215                         (ptr[counter] >= 'a' && ptr[counter] <= 'f') ||
216                         (ptr[counter] >= 'A' && ptr[counter] <= 'F')))
217                         counter++;
218                 retcol = g_new (gchar, counter + 1);
219                 strncpy (retcol, ptr, counter);
220
221                 retcol[counter] = 0;
222
223                 return retcol;
224         }
225         color[0] = 0;
226         numnames = 0;
227
228         space = MAX_COLOR_LEN - 1;
229         while (space > 0) {
230                 sscanf (ptr, "%127s", temp);
231
232                 if (((gint) ptr[0] == 0) ||
233                     (strcmp ("s", temp) == 0) || (strcmp ("m", temp) == 0) ||
234                     (strcmp ("g", temp) == 0) || (strcmp ("g4", temp) == 0))
235                         break;
236                 else {
237                         if (numnames > 0) {
238                                 space -= 1;
239                                 strcat (color, " ");
240                         }
241
242                         strncat (color, temp, space);
243                         space -= MIN (space, strlen (temp));
244                         ptr = xpm_skip_string (ptr);
245                         ptr = xpm_skip_whitespaces (ptr);
246                         numnames++;
247                 }
248         }
249
250         retcol = g_strdup (color);
251         return retcol;
252 }
253
254 /* (almost) direct copy from gdkpixmap.c... loads an XPM from a file */
255
256 static const gchar *
257 file_buffer (enum buf_op op, gpointer handle)
258 {
259         struct file_handle *h = handle;
260
261         switch (op) {
262         case op_header:
263                 if (xpm_seek_string (h->infile, "XPM", FALSE) != TRUE)
264                         break;
265
266                 if (xpm_seek_char (h->infile, '{') != TRUE)
267                         break;
268                 /* Fall through to the next xpm_seek_char. */
269
270         case op_cmap:
271                 xpm_seek_char (h->infile, '"');
272                 fseek (h->infile, -1, SEEK_CUR);
273                 /* Fall through to the xpm_read_string. */
274
275         case op_body:
276                 xpm_read_string (h->infile, &h->buffer, &h->buffer_size);
277                 return h->buffer;
278
279         default:
280                 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         struct mem_handle *h = handle;
291         switch (op) {
292         case op_header:
293         case op_cmap:
294         case op_body:
295                 if (h->data[h->offset]) {
296                         const gchar* retval;
297
298                         retval = h->data[h->offset];
299                         h->offset += 1;
300                         return retval;
301                 }
302                 break;
303
304         default:
305                 g_assert_not_reached ();
306                 break;
307         }
308
309         return NULL;
310 }
311
312 /* Destroy notification function for the libart pixbuf */
313 static void
314 free_buffer (gpointer user_data, gpointer data)
315 {
316         free (data);
317 }
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         gint w, h, n_col, cpp;
324         gint cnt, xcnt, ycnt, wbytes, n, ns;
325         gint is_trans = FALSE;
326         const gchar *buffer;
327         gchar *name_buf;
328         gchar pixel_str[32];
329         GHashTable *color_hash;
330         _XPMColor *colors, *color, *fallbackcolor;
331         guchar *pixels, *pixtmp;
332
333         fallbackcolor = NULL;
334
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         name_buf = g_new (gchar, n_col * (cpp + 1));
350         colors = g_new (_XPMColor, n_col);
351
352         for (cnt = 0; cnt < n_col; cnt++) {
353                 gchar *color_name;
354
355                 buffer = (*get_buf) (op_cmap, handle);
356                 if (!buffer) {
357                         g_warning ("Can't load XPM colormap");
358                         g_hash_table_destroy (color_hash);
359                         g_free (name_buf);
360                         g_free (colors);
361                         return NULL;
362                 }
363
364                 color = &colors[cnt];
365                 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                 color->transparent = FALSE;
370
371                 color_name = xpm_extract_color (buffer);
372
373                 if ((color_name == NULL) || (g_strcasecmp (color_name, "None") == 0)
374                     || (gdk_color_parse (color_name, &color->color) == FALSE)) {
375                         color->transparent = TRUE;
376                         is_trans = TRUE;
377                 }
378
379                 g_free (color_name);
380                 g_hash_table_insert (color_hash, color->color_string, color);
381
382                 if (cnt == 0)
383                         fallbackcolor = color;
384         }
385
386         if (is_trans)
387                 pixels = malloc (w * h * 4);
388         else
389                 pixels = malloc (w * h * 3);
390
391         if (!pixels) {
392                 g_hash_table_destroy (color_hash);
393                 g_free (colors);
394                 g_free (name_buf);
395                 return NULL;
396         }
397
398         wbytes = w * cpp;
399         pixtmp = pixels;
400
401         for (ycnt = 0; ycnt < h; ycnt++) {
402                 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                         pixel_str[cpp] = 0;
409                         ns = 0;
410
411                         color = g_hash_table_lookup (color_hash, pixel_str);
412
413                         /* Bad XPM...punt */
414                         if (!color)
415                                 color = fallbackcolor;
416
417                         *pixtmp++ = color->color.red >> 8;
418                         *pixtmp++ = color->color.green >> 8;
419                         *pixtmp++ = color->color.blue >> 8;
420
421                         if (is_trans && color->transparent)
422                                 *pixtmp++ = 0;
423                         else if (is_trans)
424                                 *pixtmp++ = 0xFF;
425                 }
426         }
427
428         g_hash_table_destroy (color_hash);
429         g_free (colors);
430         g_free (name_buf);
431
432         return gdk_pixbuf_new_from_data (pixels, ART_PIX_RGB, is_trans,
433                                          w, h, is_trans ? (w * 4) : (w * 3),
434                                          free_buffer, NULL);
435 }
436
437 /* Shared library entry point for file loading */
438 GdkPixbuf *
439 image_load (FILE *f)
440 {
441         GdkPixbuf *pixbuf;
442         struct file_handle h;
443
444         memset (&h, 0, sizeof (h));
445         h.infile = f;
446         pixbuf = pixbuf_create_from_xpm (file_buffer, &h);
447         g_free (h.buffer);
448
449         return pixbuf;
450 }
451
452 /* Shared library entry point for memory loading */
453 GdkPixbuf *
454 image_load_xpm_data (const gchar **data)
455 {
456         GdkPixbuf *pixbuf;
457         struct mem_handle h;
458
459         h.data = data;
460         h.offset = 0;
461         
462         pixbuf = pixbuf_create_from_xpm (mem_buffer, &h);
463         
464         return pixbuf;
465 }