1 | /* -*- mode: C; c-file-style: "linux" -*- */ |
2 | /* GdkPixbuf library - XBM image loader |
3 | * |
4 | * Copyright (C) 1999 Mark Crichton |
5 | * Copyright (C) 1999 The Free Software Foundation |
6 | * Copyright (C) 2001 Eazel, Inc. |
7 | * |
8 | * Authors: Mark Crichton <crichton@gimp.org> |
9 | * Federico Mena-Quintero <federico@gimp.org> |
10 | * Jonathan Blandford <jrb@redhat.com> |
11 | * John Harper <jsh@eazel.com> |
12 | * |
13 | * This library is free software; you can redistribute it and/or |
14 | * modify it under the terms of the GNU Library General Public |
15 | * License as published by the Free Software Foundation; either |
16 | * version 2 of the License, or (at your option) any later version. |
17 | * |
18 | * This library is distributed in the hope that it will be useful, |
19 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
20 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
21 | * Library General Public License for more details. |
22 | * |
23 | * You should have received a copy of the GNU Library General Public |
24 | * License along with this library; if not, see <http://www.gnu.org/licenses/>. |
25 | */ |
26 | |
27 | /* Following code adapted from io-tiff.c, which was ``(almost) blatantly |
28 | ripped from Imlib'' */ |
29 | |
30 | #include "config.h" |
31 | #include <stdlib.h> |
32 | #include <string.h> |
33 | #ifdef HAVE_UNISTD_H |
34 | #include <unistd.h> |
35 | #endif |
36 | #include <stdio.h> |
37 | #include <errno.h> |
38 | #include <glib/gstdio.h> |
39 | #include <glib/gi18n-lib.h> |
40 | |
41 | #include "gdk-pixbuf-io.h" |
42 | |
43 | |
44 | |
45 | |
46 | typedef struct _XBMData XBMData; |
47 | struct _XBMData |
48 | { |
49 | GdkPixbufModulePreparedFunc prepared_func; |
50 | GdkPixbufModuleUpdatedFunc updated_func; |
51 | gpointer user_data; |
52 | |
53 | gchar *tempname; |
54 | FILE *file; |
55 | gboolean all_okay; |
56 | }; |
57 | |
58 | |
59 | /* xbm parser borrowed from xc/lib/X11/RdBitF.c */ |
60 | |
61 | #define MAX_SIZE 255 |
62 | |
63 | /* shared data for the image read/parse logic */ |
64 | static short hex_table[256]; /* conversion value */ |
65 | static gboolean initialized = FALSE; /* easier to fill in at run time */ |
66 | |
67 | |
68 | /* Table index for the hex values. Initialized once, first time. |
69 | * Used for translation value or delimiter significance lookup. |
70 | */ |
71 | static void |
72 | init_hex_table (void) |
73 | { |
74 | /* |
75 | * We build the table at run time for several reasons: |
76 | * |
77 | * 1. portable to non-ASCII machines. |
78 | * 2. still reentrant since we set the init flag after setting table. |
79 | * 3. easier to extend. |
80 | * 4. less prone to bugs. |
81 | */ |
82 | hex_table['0'] = 0; |
83 | hex_table['1'] = 1; |
84 | hex_table['2'] = 2; |
85 | hex_table['3'] = 3; |
86 | hex_table['4'] = 4; |
87 | hex_table['5'] = 5; |
88 | hex_table['6'] = 6; |
89 | hex_table['7'] = 7; |
90 | hex_table['8'] = 8; |
91 | hex_table['9'] = 9; |
92 | hex_table['A'] = 10; |
93 | hex_table['B'] = 11; |
94 | hex_table['C'] = 12; |
95 | hex_table['D'] = 13; |
96 | hex_table['E'] = 14; |
97 | hex_table['F'] = 15; |
98 | hex_table['a'] = 10; |
99 | hex_table['b'] = 11; |
100 | hex_table['c'] = 12; |
101 | hex_table['d'] = 13; |
102 | hex_table['e'] = 14; |
103 | hex_table['f'] = 15; |
104 | |
105 | /* delimiters of significance are flagged w/ negative value */ |
106 | hex_table[' '] = -1; |
107 | hex_table[','] = -1; |
108 | hex_table['}'] = -1; |
109 | hex_table['\n'] = -1; |
110 | hex_table['\t'] = -1; |
111 | |
112 | initialized = TRUE; |
113 | } |
114 | |
115 | /* Read next hex value in the input stream, return -1 if EOF */ |
116 | static int |
117 | next_int (FILE *fstream) |
118 | { |
119 | int ch; |
120 | int value = 0; |
121 | int gotone = 0; |
122 | int done = 0; |
123 | |
124 | /* loop, accumulate hex value until find delimiter |
125 | skip any initial delimiters found in read stream */ |
126 | |
127 | while (!done) { |
128 | ch = getc (stream: fstream); |
129 | if (ch == EOF) { |
130 | value = -1; |
131 | done++; |
132 | } else { |
133 | /* trim high bits, check type and accumulate */ |
134 | ch &= 0xff; |
135 | if (g_ascii_isxdigit (ch)) { |
136 | value = ((value & 0xf) << 4) + g_ascii_xdigit_value (c: ch); |
137 | gotone = 1; |
138 | } else if ((hex_table[ch]) < 0 && gotone) { |
139 | done++; |
140 | } |
141 | } |
142 | } |
143 | return value; |
144 | } |
145 | |
146 | static gboolean |
147 | read_bitmap_file_data (FILE *fstream, |
148 | guint *width, |
149 | guint *height, |
150 | guchar **data, |
151 | int *x_hot, |
152 | int *y_hot) |
153 | { |
154 | guchar *bits = NULL; /* working variable */ |
155 | char line[MAX_SIZE]; /* input line from file */ |
156 | guint size; /* number of bytes of data */ |
157 | char name_and_type[MAX_SIZE]; /* an input line */ |
158 | char *type; /* for parsing */ |
159 | int value; /* from an input line */ |
160 | int version10p; /* boolean, old format */ |
161 | int padding; /* to handle alignment */ |
162 | int bytes_per_line; /* per scanline of data */ |
163 | guint ww = 0; /* width */ |
164 | guint hh = 0; /* height */ |
165 | int hx = -1; /* x hotspot */ |
166 | int hy = -1; /* y hotspot */ |
167 | |
168 | /* first time initialization */ |
169 | if (!initialized) { |
170 | init_hex_table (); |
171 | } |
172 | |
173 | /* error cleanup and return macro */ |
174 | #define RETURN(code) { g_free (bits); return code; } |
175 | |
176 | while (fgets (s: line, MAX_SIZE, stream: fstream)) { |
177 | if (strlen (s: line) == MAX_SIZE-1) |
178 | RETURN (FALSE); |
179 | if (sscanf (s: line,format: "#define %s %d" ,name_and_type,&value) == 2) { |
180 | if (!(type = strrchr (s: name_and_type, c: '_'))) |
181 | type = name_and_type; |
182 | else { |
183 | type++; |
184 | } |
185 | |
186 | if (!strcmp (s1: "width" , s2: type)) { |
187 | if (value <= 0) |
188 | RETURN (FALSE); |
189 | ww = (unsigned int) value; |
190 | } |
191 | if (!strcmp (s1: "height" , s2: type)) { |
192 | if (value <= 0) |
193 | RETURN (FALSE); |
194 | hh = (unsigned int) value; |
195 | } |
196 | if (!strcmp (s1: "hot" , s2: type)) { |
197 | if (type-- == name_and_type |
198 | || type-- == name_and_type) |
199 | continue; |
200 | if (!strcmp (s1: "x_hot" , s2: type)) |
201 | hx = value; |
202 | if (!strcmp (s1: "y_hot" , s2: type)) |
203 | hy = value; |
204 | } |
205 | continue; |
206 | } |
207 | |
208 | if (sscanf (s: line, format: "static short %s = {" , name_and_type) == 1) |
209 | version10p = 1; |
210 | else if (sscanf (s: line,format: "static const unsigned char %s = {" ,name_and_type) == 1) |
211 | version10p = 0; |
212 | else if (sscanf (s: line,format: "static unsigned char %s = {" ,name_and_type) == 1) |
213 | version10p = 0; |
214 | else if (sscanf (s: line, format: "static const char %s = {" , name_and_type) == 1) |
215 | version10p = 0; |
216 | else if (sscanf (s: line, format: "static char %s = {" , name_and_type) == 1) |
217 | version10p = 0; |
218 | else |
219 | continue; |
220 | |
221 | if (!(type = strrchr (s: name_and_type, c: '_'))) |
222 | type = name_and_type; |
223 | else |
224 | type++; |
225 | |
226 | if (strcmp (s1: "bits[]" , s2: type)) |
227 | continue; |
228 | |
229 | if (!ww || !hh) |
230 | RETURN (FALSE); |
231 | |
232 | /* Choose @padding so @size is even if @version10p is %TRUE. |
233 | * If @version10p is %FALSE, @size could be even or odd. */ |
234 | if ((ww % 16) && ((ww % 16) < 9) && version10p) |
235 | padding = 1; |
236 | else |
237 | padding = 0; |
238 | |
239 | /* Check for overflow for the bytes_per_line calculation. */ |
240 | if (ww > G_MAXUINT - 7) |
241 | RETURN (FALSE); |
242 | |
243 | bytes_per_line = (ww+7)/8 + padding; |
244 | g_assert (!version10p || (bytes_per_line % 2) == 0); |
245 | |
246 | /* size = bytes_per_line * hh */ |
247 | if (!g_uint_checked_mul (&size, bytes_per_line, hh)) |
248 | RETURN (FALSE); |
249 | |
250 | bits = g_malloc (n_bytes: size); |
251 | |
252 | if (version10p) { |
253 | unsigned char *ptr; |
254 | guint bytes; |
255 | |
256 | /* @bytes is guaranteed not to overflow (which could |
257 | * happen if @size is the odd-valued %G_MAXUINT: @bytes would reach |
258 | * %G_MAXUINT-1 in the loop, then be incremented to %G_MAXUINT+1 on the |
259 | * next iteration) because @bytes_per_line is guaranteed to be even if |
260 | * @version10p is %TRUE (due to the selection of |
261 | * @padding in that case), so @size must be even too. */ |
262 | g_assert ((size % 2) == 0); |
263 | |
264 | for (bytes = 0, ptr = bits; bytes < size; (bytes += 2)) { |
265 | if ((value = next_int (fstream)) < 0) |
266 | RETURN (FALSE); |
267 | *(ptr++) = value; |
268 | if (!padding || ((bytes+2) % bytes_per_line)) |
269 | *(ptr++) = value >> 8; |
270 | } |
271 | } else { |
272 | unsigned char *ptr; |
273 | guint bytes; |
274 | |
275 | for (bytes = 0, ptr = bits; bytes < size; bytes++, ptr++) { |
276 | if ((value = next_int (fstream)) < 0) |
277 | RETURN (FALSE); |
278 | *ptr=value; |
279 | } |
280 | } |
281 | break; |
282 | } |
283 | |
284 | if (!bits) |
285 | RETURN (FALSE); |
286 | |
287 | *data = bits; |
288 | *width = ww; |
289 | *height = hh; |
290 | if (x_hot) |
291 | *x_hot = hx; |
292 | if (y_hot) |
293 | *y_hot = hy; |
294 | |
295 | return TRUE; |
296 | } |
297 | |
298 | |
299 | |
300 | static GdkPixbuf * |
301 | gdk_pixbuf__xbm_image_load_real (FILE *f, |
302 | XBMData *context, |
303 | GError **error) |
304 | { |
305 | guint w, h; |
306 | int x_hot, y_hot; |
307 | guchar *data = NULL, *ptr; |
308 | guchar *pixels; |
309 | guint row_stride; |
310 | int x, y; |
311 | int reg = 0; /* Quiet compiler */ |
312 | int bits; |
313 | |
314 | GdkPixbuf *pixbuf; |
315 | |
316 | if (!read_bitmap_file_data (fstream: f, width: &w, height: &h, data: &data, x_hot: &x_hot, y_hot: &y_hot)) { |
317 | g_set_error_literal (err: error, |
318 | GDK_PIXBUF_ERROR, |
319 | code: GDK_PIXBUF_ERROR_CORRUPT_IMAGE, |
320 | _("Invalid XBM file" )); |
321 | return NULL; |
322 | } |
323 | |
324 | pixbuf = gdk_pixbuf_new (colorspace: GDK_COLORSPACE_RGB, FALSE, bits_per_sample: 8, width: w, height: h); |
325 | |
326 | if (pixbuf == NULL) { |
327 | g_free (mem: data); |
328 | g_set_error_literal (err: error, |
329 | GDK_PIXBUF_ERROR, |
330 | code: GDK_PIXBUF_ERROR_INSUFFICIENT_MEMORY, |
331 | _("Insufficient memory to load XBM image file" )); |
332 | return NULL; |
333 | } |
334 | |
335 | if (x_hot != -1 && y_hot != -1) { |
336 | gchar hot[10]; |
337 | g_snprintf (string: hot, n: 10, format: "%d" , x_hot); |
338 | gdk_pixbuf_set_option (pixbuf, key: "x_hot" , value: hot); |
339 | g_snprintf (string: hot, n: 10, format: "%d" , y_hot); |
340 | gdk_pixbuf_set_option (pixbuf, key: "y_hot" , value: hot); |
341 | } |
342 | |
343 | pixels = gdk_pixbuf_get_pixels (pixbuf); |
344 | row_stride = gdk_pixbuf_get_rowstride (pixbuf); |
345 | |
346 | if (context) |
347 | (* context->prepared_func) (pixbuf, NULL, context->user_data); |
348 | |
349 | |
350 | /* Initialize PIXBUF */ |
351 | |
352 | ptr = data; |
353 | for (y = 0; y < h; y++) { |
354 | bits = 0; |
355 | for (x = 0; x < w; x++) { |
356 | guchar channel; |
357 | if (bits == 0) { |
358 | reg = *ptr++; |
359 | bits = 8; |
360 | } |
361 | |
362 | channel = (reg & 1) ? 0 : 255; |
363 | reg >>= 1; |
364 | bits--; |
365 | |
366 | pixels[x * 3 + 0] = channel; |
367 | pixels[x * 3 + 1] = channel; |
368 | pixels[x * 3 + 2] = channel; |
369 | } |
370 | pixels += row_stride; |
371 | } |
372 | g_free (mem: data); |
373 | |
374 | if (context) { |
375 | (* context->updated_func) (pixbuf, 0, 0, w, h, context->user_data); |
376 | } |
377 | |
378 | return pixbuf; |
379 | } |
380 | |
381 | |
382 | /* Static loader */ |
383 | |
384 | static GdkPixbuf * |
385 | gdk_pixbuf__xbm_image_load (FILE *f, |
386 | GError **error) |
387 | { |
388 | return gdk_pixbuf__xbm_image_load_real (f, NULL, error); |
389 | } |
390 | |
391 | |
392 | /* Progressive loader */ |
393 | |
394 | /* |
395 | * Proper XBM progressive loading isn't implemented. Instead we write |
396 | * it to a file, then load the file when it's done. It's not pretty. |
397 | */ |
398 | |
399 | static gpointer |
400 | gdk_pixbuf__xbm_image_begin_load (GdkPixbufModuleSizeFunc size_func, |
401 | GdkPixbufModulePreparedFunc prepared_func, |
402 | GdkPixbufModuleUpdatedFunc updated_func, |
403 | gpointer user_data, |
404 | GError **error) |
405 | { |
406 | XBMData *context; |
407 | gint fd; |
408 | |
409 | g_assert (size_func != NULL); |
410 | g_assert (prepared_func != NULL); |
411 | g_assert (updated_func != NULL); |
412 | |
413 | context = g_new (XBMData, 1); |
414 | context->prepared_func = prepared_func; |
415 | context->updated_func = updated_func; |
416 | context->user_data = user_data; |
417 | context->all_okay = TRUE; |
418 | fd = g_file_open_tmp (tmpl: "gdkpixbuf-xbm-tmp.XXXXXX" , |
419 | name_used: &context->tempname, |
420 | NULL); |
421 | if (fd < 0) { |
422 | g_free (mem: context); |
423 | return NULL; |
424 | } |
425 | |
426 | context->file = fdopen (fd: fd, modes: "w+" ); |
427 | if (context->file == NULL) { |
428 | g_free (mem: context->tempname); |
429 | g_free (mem: context); |
430 | return NULL; |
431 | } |
432 | |
433 | return context; |
434 | } |
435 | |
436 | static gboolean |
437 | gdk_pixbuf__xbm_image_stop_load (gpointer data, |
438 | GError **error) |
439 | { |
440 | XBMData *context = (XBMData*) data; |
441 | gboolean retval = TRUE; |
442 | |
443 | g_return_val_if_fail (data != NULL, TRUE); |
444 | |
445 | fflush (stream: context->file); |
446 | rewind (stream: context->file); |
447 | if (context->all_okay) { |
448 | GdkPixbuf *pixbuf; |
449 | pixbuf = gdk_pixbuf__xbm_image_load_real (f: context->file, |
450 | context, |
451 | error); |
452 | if (pixbuf == NULL) |
453 | retval = FALSE; |
454 | else |
455 | g_object_unref (object: pixbuf); |
456 | } |
457 | |
458 | fclose (stream: context->file); |
459 | g_unlink (filename: context->tempname); |
460 | g_free (mem: context->tempname); |
461 | g_free (mem: (XBMData *) context); |
462 | |
463 | return retval; |
464 | } |
465 | |
466 | static gboolean |
467 | gdk_pixbuf__xbm_image_load_increment (gpointer data, |
468 | const guchar *buf, |
469 | guint size, |
470 | GError **error) |
471 | { |
472 | XBMData *context = (XBMData *) data; |
473 | |
474 | g_return_val_if_fail (data != NULL, FALSE); |
475 | |
476 | if (fwrite (ptr: buf, size: sizeof (guchar), n: size, s: context->file) != size) { |
477 | gint save_errno = errno; |
478 | context->all_okay = FALSE; |
479 | g_set_error_literal (err: error, |
480 | G_FILE_ERROR, |
481 | code: g_file_error_from_errno (err_no: save_errno), |
482 | _("Failed to write to temporary file when loading XBM image" )); |
483 | return FALSE; |
484 | } |
485 | |
486 | return TRUE; |
487 | } |
488 | |
489 | #ifndef INCLUDE_xbm |
490 | #define MODULE_ENTRY(function) G_MODULE_EXPORT void function |
491 | #else |
492 | #define MODULE_ENTRY(function) void _gdk_pixbuf__xbm_ ## function |
493 | #endif |
494 | |
495 | MODULE_ENTRY (fill_vtable) (GdkPixbufModule *module) |
496 | { |
497 | module->load = gdk_pixbuf__xbm_image_load; |
498 | module->begin_load = gdk_pixbuf__xbm_image_begin_load; |
499 | module->stop_load = gdk_pixbuf__xbm_image_stop_load; |
500 | module->load_increment = gdk_pixbuf__xbm_image_load_increment; |
501 | } |
502 | |
503 | MODULE_ENTRY (fill_info) (GdkPixbufFormat *info) |
504 | { |
505 | static const GdkPixbufModulePattern signature[] = { |
506 | { "#define " , NULL, 100 }, |
507 | { "/*" , NULL, 50 }, |
508 | { NULL, NULL, 0 } |
509 | }; |
510 | static const gchar *mime_types[] = { |
511 | "image/x-xbitmap" , |
512 | NULL |
513 | }; |
514 | static const gchar *extensions[] = { |
515 | "xbm" , |
516 | NULL |
517 | }; |
518 | |
519 | info->name = "xbm" ; |
520 | info->signature = (GdkPixbufModulePattern *) signature; |
521 | info->description = NC_("image format" , "XBM" ); |
522 | info->mime_types = (gchar **) mime_types; |
523 | info->extensions = (gchar **) extensions; |
524 | info->flags = GDK_PIXBUF_FORMAT_THREADSAFE; |
525 | info->license = "LGPL" ; |
526 | } |
527 | |