1 | /* -*- mode: C; c-file-style: "linux" -*- */ |
2 | /* GdkPixbuf library - Windows Icon/Cursor image loader |
3 | * |
4 | * Copyright (C) 1999 The Free Software Foundation |
5 | * |
6 | * Authors: Arjan van de Ven <arjan@fenrus.demon.nl> |
7 | * Federico Mena-Quintero <federico@gimp.org> |
8 | * |
9 | * Based on io-bmp.c |
10 | * |
11 | * This library is free software; you can redistribute it and/or |
12 | * modify it under the terms of the GNU Lesser General Public |
13 | * License as published by the Free Software Foundation; either |
14 | * version 2 of the License, or (at your option) any later version. |
15 | * |
16 | * This library is distributed in the hope that it will be useful, |
17 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
18 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
19 | * Lesser General Public License for more details. |
20 | * |
21 | * You should have received a copy of the GNU Lesser General Public |
22 | * License along with this library; if not, see <http://www.gnu.org/licenses/>. |
23 | */ |
24 | |
25 | #undef DUMPBIH |
26 | #define DEBUG(s) |
27 | |
28 | #define 40 |
29 | |
30 | /* |
31 | |
32 | Icons are just like BMP's, except for the header. |
33 | |
34 | Known bugs: |
35 | * bi-tonal files aren't tested |
36 | |
37 | */ |
38 | |
39 | #include "config.h" |
40 | #include <stdio.h> |
41 | #include <stdlib.h> |
42 | #ifdef HAVE_UNISTD_H |
43 | #include <unistd.h> |
44 | #endif |
45 | #include <string.h> |
46 | #include <errno.h> |
47 | #include <glib/gi18n-lib.h> |
48 | #include "gdk-pixbuf-io.h" |
49 | |
50 | |
51 | |
52 | /* |
53 | |
54 | These structures are actually dummies. These are according to |
55 | the "Windows API reference guide volume II" as written by |
56 | Borland International, but GCC fiddles with the alignment of |
57 | the internal members. |
58 | |
59 | */ |
60 | |
61 | struct { |
62 | gushort ; |
63 | guint ; |
64 | guint ; |
65 | guint ; |
66 | }; |
67 | |
68 | struct { |
69 | guint ; |
70 | guint ; |
71 | guint ; |
72 | gushort ; |
73 | gushort ; |
74 | guint ; |
75 | guint ; |
76 | guint ; |
77 | guint ; |
78 | guint ; |
79 | guint ; |
80 | }; |
81 | |
82 | #ifdef DUMPBIH |
83 | /* |
84 | |
85 | DumpBIH printf's the values in a BitmapInfoHeader to the screen, for |
86 | debugging purposes. |
87 | |
88 | */ |
89 | static void DumpBIH(unsigned char *BIH) |
90 | { |
91 | printf("biSize = %i \n" , |
92 | (int)(BIH[3] << 24) + (BIH[2] << 16) + (BIH[1] << 8) + (BIH[0])); |
93 | printf("biWidth = %i \n" , |
94 | (int)(BIH[7] << 24) + (BIH[6] << 16) + (BIH[5] << 8) + (BIH[4])); |
95 | printf("biHeight = %i \n" , |
96 | (int)(BIH[11] << 24) + (BIH[10] << 16) + (BIH[9] << 8) + |
97 | (BIH[8])); |
98 | printf("biPlanes = %i \n" , (int)(BIH[13] << 8) + (BIH[12])); |
99 | printf("biBitCount = %i \n" , (int)(BIH[15] << 8) + (BIH[14])); |
100 | printf("biCompress = %i \n" , |
101 | (int)(BIH[19] << 24) + (BIH[18] << 16) + (BIH[17] << 8) + |
102 | (BIH[16])); |
103 | printf("biSizeImage = %i \n" , |
104 | (int)(BIH[23] << 24) + (BIH[22] << 16) + (BIH[21] << 8) + |
105 | (BIH[20])); |
106 | printf("biXPels = %i \n" , |
107 | (int)(BIH[27] << 24) + (BIH[26] << 16) + (BIH[25] << 8) + |
108 | (BIH[24])); |
109 | printf("biYPels = %i \n" , |
110 | (int)(BIH[31] << 24) + (BIH[30] << 16) + (BIH[29] << 8) + |
111 | (BIH[28])); |
112 | printf("biClrUsed = %i \n" , |
113 | (int)(BIH[35] << 24) + (BIH[34] << 16) + (BIH[33] << 8) + |
114 | (BIH[32])); |
115 | printf("biClrImprtnt= %i \n" , |
116 | (int)(BIH[39] << 24) + (BIH[38] << 16) + (BIH[37] << 8) + |
117 | (BIH[36])); |
118 | } |
119 | #endif |
120 | |
121 | /* Progressive loading */ |
122 | struct { |
123 | gint ; |
124 | gint ; |
125 | guint ; |
126 | guint ; /* Negative = 1 -> top down BMP, |
127 | Negative = 0 -> bottom up BMP */ |
128 | }; |
129 | |
130 | /* Score the various parts of the icon */ |
131 | struct ico_direntry_data { |
132 | gint ImageScore; |
133 | gint width; |
134 | gint height; |
135 | guint DIBoffset; |
136 | gint x_hot; |
137 | gint y_hot; |
138 | }; |
139 | |
140 | struct ico_progressive_state { |
141 | GdkPixbufModuleSizeFunc size_func; |
142 | GdkPixbufModulePreparedFunc prepared_func; |
143 | GdkPixbufModuleUpdatedFunc updated_func; |
144 | gpointer user_data; |
145 | |
146 | gint ; /* The size of the header-part (incl colormap) */ |
147 | guchar *; /* The buffer for the header (incl colormap) */ |
148 | gint ; /* The size of the allocated HeaderBuf */ |
149 | gint ; /* The nr of bytes actually in HeaderBuf */ |
150 | |
151 | gint LineWidth; /* The width of a line in bytes */ |
152 | guchar *LineBuf; /* Buffer for 1 line */ |
153 | gint LineDone; /* # of bytes in LineBuf */ |
154 | gint Lines; /* # of finished lines */ |
155 | |
156 | gint Type; /* |
157 | 32 = RGBA |
158 | 24 = RGB |
159 | 16 = 555 RGB |
160 | 8 = 8 bit colormapped |
161 | 4 = 4 bpp colormapped |
162 | 1 = 1 bit bitonal |
163 | */ |
164 | gboolean cursor; |
165 | gint x_hot; |
166 | gint y_hot; |
167 | |
168 | struct headerpair ; /* Decoded (BE->CPU) header */ |
169 | GList *entries; |
170 | guint DIBoffset; |
171 | |
172 | GdkPixbuf *pixbuf; /* Our "target" */ |
173 | }; |
174 | |
175 | static gpointer |
176 | gdk_pixbuf__ico_image_begin_load(GdkPixbufModuleSizeFunc size_func, |
177 | GdkPixbufModulePreparedFunc prepared_func, |
178 | GdkPixbufModuleUpdatedFunc updated_func, |
179 | gpointer user_data, |
180 | GError **error); |
181 | static gboolean gdk_pixbuf__ico_image_stop_load(gpointer data, GError **error); |
182 | static gboolean gdk_pixbuf__ico_image_load_increment(gpointer data, |
183 | const guchar * buf, guint size, |
184 | GError **error); |
185 | |
186 | static void |
187 | context_free (struct ico_progressive_state *context) |
188 | { |
189 | g_free (mem: context->LineBuf); |
190 | context->LineBuf = NULL; |
191 | g_free (mem: context->HeaderBuf); |
192 | g_list_free_full (list: context->entries, free_func: g_free); |
193 | if (context->pixbuf) |
194 | g_object_unref (object: context->pixbuf); |
195 | |
196 | g_free (mem: context); |
197 | } |
198 | |
199 | static gint |
200 | compare_direntry_scores (gconstpointer a, |
201 | gconstpointer b) |
202 | { |
203 | const struct ico_direntry_data *ia = a; |
204 | const struct ico_direntry_data *ib = b; |
205 | |
206 | /* Backwards, so largest first */ |
207 | if (ib->ImageScore < ia->ImageScore) |
208 | return -1; |
209 | else if (ib->ImageScore > ia->ImageScore) |
210 | return 1; |
211 | return 0; |
212 | } |
213 | |
214 | static void (guchar *Data, gint Bytes, |
215 | struct ico_progressive_state *State, |
216 | GError **error) |
217 | { |
218 | /* For ICO's we have to be very clever. There are multiple images possible |
219 | in an .ICO. As a simple heuristic, we select the image which is the largest |
220 | in pixels. |
221 | */ |
222 | struct ico_direntry_data *entry; |
223 | gint IconCount = 0; /* The number of icon-versions in the file */ |
224 | guchar *BIH; /* The DIB for the used icon */ |
225 | guchar *Ptr; |
226 | gint I; |
227 | guint16 imgtype; /* 1 = icon, 2 = cursor */ |
228 | GList *l; |
229 | gboolean = FALSE; |
230 | |
231 | /* Step 1: The ICO header */ |
232 | |
233 | /* First word should be 0 according to specs */ |
234 | if (((Data[1] << 8) + Data[0]) != 0) { |
235 | g_set_error (err: error, |
236 | GDK_PIXBUF_ERROR, |
237 | code: GDK_PIXBUF_ERROR_CORRUPT_IMAGE, |
238 | _("Invalid header in icon (%s)" ), "first word" ); |
239 | return; |
240 | |
241 | } |
242 | |
243 | imgtype = (Data[3] << 8) + Data[2]; |
244 | |
245 | State->cursor = (imgtype == 2) ? TRUE : FALSE; |
246 | |
247 | /* If it is not a cursor make sure it is actually an icon */ |
248 | if (!State->cursor && imgtype != 1) { |
249 | g_set_error (err: error, |
250 | GDK_PIXBUF_ERROR, |
251 | code: GDK_PIXBUF_ERROR_CORRUPT_IMAGE, |
252 | _("Invalid header in icon (%s)" ), "image type" ); |
253 | return; |
254 | } |
255 | |
256 | IconCount = (Data[5] << 8) + (Data[4]); |
257 | |
258 | State->HeaderSize = 6 + IconCount*16; |
259 | |
260 | DEBUG(g_print ("Image type: %d (%s)\nImage count: %d\n" , imgtype, imgtype == 2 ? "cursor" : "icon" , IconCount)); |
261 | |
262 | if (State->HeaderSize>State->BytesInHeaderBuf) { |
263 | guchar *tmp=g_try_realloc(mem: State->HeaderBuf,n_bytes: State->HeaderSize); |
264 | if (!tmp) { |
265 | g_set_error_literal (err: error, |
266 | GDK_PIXBUF_ERROR, |
267 | code: GDK_PIXBUF_ERROR_INSUFFICIENT_MEMORY, |
268 | _("Not enough memory to load icon" )); |
269 | return; |
270 | } |
271 | State->HeaderBuf = tmp; |
272 | State->BytesInHeaderBuf = State->HeaderSize; |
273 | } |
274 | if (Bytes < State->HeaderSize) { |
275 | return; |
276 | } |
277 | |
278 | /* Now iterate through the ICONDIRENTRY structures, and sort them by |
279 | * which one we think is "best" (essentially the largest) */ |
280 | g_list_free_full (list: State->entries, free_func: g_free); |
281 | State->entries = 0; |
282 | Ptr = Data + 6; |
283 | for (I=0;I<IconCount;I++) { |
284 | int width; |
285 | int height; |
286 | int depth; |
287 | int x_hot; |
288 | int y_hot; |
289 | guint data_size G_GNUC_UNUSED; |
290 | guint data_offset; |
291 | |
292 | width = Ptr[0]; |
293 | height = Ptr[1]; |
294 | depth = Ptr[2]; |
295 | x_hot = (Ptr[5] << 8) + Ptr[4]; |
296 | y_hot = (Ptr[7] << 8) + Ptr[6]; |
297 | data_size = ((guint) (Ptr[11]) << 24) + (Ptr[10] << 16) + (Ptr[9] << 8) + (Ptr[8]); |
298 | data_offset = ((guint) (Ptr[15]) << 24) + (Ptr[14] << 16) + (Ptr[13] << 8) + (Ptr[12]); |
299 | DEBUG(g_print ("Image %d: %d x %d\n\tDepth: %d\n" , I, width, height, depth); |
300 | if (imgtype == 2) |
301 | g_print ("\tHotspot: %d x %d\n" , x_hot, y_hot); |
302 | else |
303 | g_print ("\tColor planes: %d\n\tBits per pixel: %d\n" , x_hot, y_hot); |
304 | g_print ("\tSize: %d\n\tOffset: %d\n" , data_size, data_offset);) |
305 | |
306 | if (depth == 0) |
307 | depth = 32; |
308 | else if (depth <= 2) |
309 | depth = 1; |
310 | else if (depth <= 16) |
311 | depth = 4; |
312 | else if (depth <= 256) |
313 | depth = 8; |
314 | |
315 | /* We check whether the HeaderSize (int) would overflow */ |
316 | if (data_offset > INT_MAX - INFOHEADER_SIZE) { |
317 | got_broken_header = TRUE; |
318 | continue; |
319 | } |
320 | |
321 | entry = g_new0 (struct ico_direntry_data, 1); |
322 | entry->width = width ? width : 256; |
323 | entry->height = height ? height : 256; |
324 | entry->ImageScore = entry->width * entry->height * depth; |
325 | entry->x_hot = x_hot; |
326 | entry->y_hot = y_hot; |
327 | entry->DIBoffset = data_offset; |
328 | State->entries = g_list_insert_sorted (list: State->entries, data: entry, func: compare_direntry_scores); |
329 | Ptr += 16; |
330 | } |
331 | |
332 | /* Now go through and find one we can parse */ |
333 | entry = NULL; |
334 | for (l = State->entries; l != NULL; l = g_list_next (l)) { |
335 | entry = l->data; |
336 | |
337 | /* Avoid invoking undefined behavior in the State->HeaderSize calculation below */ |
338 | if (entry->DIBoffset > G_MAXINT - INFOHEADER_SIZE) { |
339 | g_set_error (err: error, |
340 | GDK_PIXBUF_ERROR, |
341 | code: GDK_PIXBUF_ERROR_CORRUPT_IMAGE, |
342 | _("Invalid header in icon (%s)" ), "header size" ); |
343 | return; |
344 | } |
345 | |
346 | /* We know how many bytes are in the "header" part. */ |
347 | State->HeaderSize = entry->DIBoffset + INFOHEADER_SIZE; |
348 | |
349 | if (State->HeaderSize>State->BytesInHeaderBuf) { |
350 | guchar *tmp=g_try_realloc(mem: State->HeaderBuf,n_bytes: State->HeaderSize); |
351 | if (!tmp) { |
352 | g_set_error_literal (err: error, |
353 | GDK_PIXBUF_ERROR, |
354 | code: GDK_PIXBUF_ERROR_INSUFFICIENT_MEMORY, |
355 | _("Not enough memory to load icon" )); |
356 | return; |
357 | } |
358 | State->HeaderBuf = tmp; |
359 | State->BytesInHeaderBuf = State->HeaderSize; |
360 | } |
361 | if (Bytes<State->HeaderSize) |
362 | return; |
363 | |
364 | BIH = Data+entry->DIBoffset; |
365 | |
366 | /* A compressed icon, try the next one */ |
367 | if ((BIH[16] != 0) || (BIH[17] != 0) || (BIH[18] != 0) |
368 | || (BIH[19] != 0)) { |
369 | DEBUG(g_print("Skipping icon with score %d, as it is compressed\n" , entry->ImageScore)); |
370 | continue; |
371 | } |
372 | |
373 | DEBUG(g_print("Selecting icon with score %d\n" , entry->ImageScore)); |
374 | |
375 | /* If we made it to here then we have selected a BIH structure |
376 | * in a format that we can parse */ |
377 | break; |
378 | } |
379 | |
380 | /* No valid icon found, because all are compressed? */ |
381 | if (l == NULL) { |
382 | g_set_error_literal (err: error, |
383 | GDK_PIXBUF_ERROR, |
384 | code: GDK_PIXBUF_ERROR_CORRUPT_IMAGE, |
385 | message: got_broken_header ? |
386 | _("Invalid header in icon" ) : |
387 | _("Compressed icons are not supported" )); |
388 | return; |
389 | } |
390 | |
391 | /* This is the one we're going with */ |
392 | State->DIBoffset = entry->DIBoffset; |
393 | State->x_hot = entry->x_hot; |
394 | State->y_hot = entry->y_hot; |
395 | |
396 | #ifdef DUMPBIH |
397 | DumpBIH(BIH); |
398 | #endif |
399 | /* Add the palette to the headersize */ |
400 | |
401 | State->Header.width = |
402 | (int)(BIH[7] << 24) + (BIH[6] << 16) + (BIH[5] << 8) + (BIH[4]); |
403 | if (State->Header.width == 0) |
404 | State->Header.width = 256; |
405 | |
406 | State->Header.height = |
407 | (int)((BIH[11] << 24) + (BIH[10] << 16) + (BIH[9] << 8) + (BIH[8]))/2; |
408 | /* /2 because the BIH height includes the transparency mask */ |
409 | if (State->Header.height == 0) |
410 | State->Header.height = 256; |
411 | |
412 | /* Negative heights mean top-down pixel-order */ |
413 | if (State->Header.height < 0) { |
414 | State->Header.height = -State->Header.height; |
415 | State->Header.Negative = 1; |
416 | } |
417 | if (State->Header.width < 0) { |
418 | State->Header.width = -State->Header.width; |
419 | } |
420 | |
421 | if (State->Header.width != entry->width || |
422 | State->Header.height != entry->height) { |
423 | g_set_error (err: error, |
424 | GDK_PIXBUF_ERROR, |
425 | code: GDK_PIXBUF_ERROR_CORRUPT_IMAGE, |
426 | _("Invalid header in icon (%s)" ), "image size" ); |
427 | return; |
428 | } |
429 | |
430 | State->Header.depth = (BIH[15] << 8) + (BIH[14]); |
431 | State->Type = State->Header.depth; |
432 | |
433 | /* Determine the palette size. If the header indicates 0, it |
434 | is actually the maximum for the bpp. You have to love the |
435 | guys who made the spec. */ |
436 | I = (int)(BIH[35] << 24) + (BIH[34] << 16) + (BIH[33] << 8) + (BIH[32]); |
437 | I = I*4; |
438 | if ((I==0)&&(State->Type==1)) |
439 | I = 2*4; |
440 | if ((I==0)&&(State->Type==4)) |
441 | I = 16*4; |
442 | if ((I==0)&&(State->Type==8)) |
443 | I = 256*4; |
444 | |
445 | State->HeaderSize+=I; |
446 | |
447 | if (State->HeaderSize < 0) { |
448 | g_set_error (err: error, |
449 | GDK_PIXBUF_ERROR, |
450 | code: GDK_PIXBUF_ERROR_CORRUPT_IMAGE, |
451 | _("Invalid header in icon (%s)" ), "palette size" ); |
452 | return; |
453 | } |
454 | |
455 | if (State->HeaderSize>State->BytesInHeaderBuf) { |
456 | guchar *tmp=g_try_realloc(mem: State->HeaderBuf,n_bytes: State->HeaderSize); |
457 | if (!tmp) { |
458 | g_set_error_literal (err: error, |
459 | GDK_PIXBUF_ERROR, |
460 | code: GDK_PIXBUF_ERROR_INSUFFICIENT_MEMORY, |
461 | _("Not enough memory to load icon" )); |
462 | return; |
463 | } |
464 | State->HeaderBuf = tmp; |
465 | State->BytesInHeaderBuf = State->HeaderSize; |
466 | } |
467 | if (Bytes < State->HeaderSize) { |
468 | return; |
469 | } |
470 | |
471 | if (State->Type == 32) |
472 | State->LineWidth = State->Header.width * 4; |
473 | else if (State->Type == 24) |
474 | State->LineWidth = State->Header.width * 3; |
475 | else if (State->Type == 16) |
476 | State->LineWidth = State->Header.width * 2; |
477 | else if (State->Type == 8) |
478 | State->LineWidth = State->Header.width * 1; |
479 | else if (State->Type == 4) |
480 | State->LineWidth = (State->Header.width+1)/2; |
481 | else if (State->Type == 1) { |
482 | State->LineWidth = State->Header.width / 8; |
483 | if ((State->Header.width & 7) != 0) |
484 | State->LineWidth++; |
485 | } else { |
486 | g_set_error_literal (err: error, |
487 | GDK_PIXBUF_ERROR, |
488 | code: GDK_PIXBUF_ERROR_CORRUPT_IMAGE, |
489 | _("Unsupported icon type" )); |
490 | return; |
491 | } |
492 | |
493 | /* Pad to a 32 bit boundary */ |
494 | if (((State->LineWidth % 4) > 0)) |
495 | State->LineWidth = (State->LineWidth / 4) * 4 + 4; |
496 | |
497 | |
498 | if (State->LineBuf == NULL) { |
499 | State->LineBuf = g_try_malloc(n_bytes: State->LineWidth); |
500 | if (!State->LineBuf) { |
501 | g_set_error_literal (err: error, |
502 | GDK_PIXBUF_ERROR, |
503 | code: GDK_PIXBUF_ERROR_INSUFFICIENT_MEMORY, |
504 | _("Not enough memory to load icon" )); |
505 | return; |
506 | } |
507 | } |
508 | |
509 | g_assert(State->LineBuf != NULL); |
510 | |
511 | |
512 | if (State->pixbuf == NULL) { |
513 | { |
514 | gint width = State->Header.width; |
515 | gint height = State->Header.height; |
516 | |
517 | (*State->size_func) (&width, &height, State->user_data); |
518 | if (width == 0 || height == 0) { |
519 | State->LineWidth = 0; |
520 | return; |
521 | } |
522 | } |
523 | |
524 | State->pixbuf = |
525 | gdk_pixbuf_new(colorspace: GDK_COLORSPACE_RGB, TRUE, bits_per_sample: 8, |
526 | width: State->Header.width, |
527 | height: State->Header.height); |
528 | if (!State->pixbuf) { |
529 | g_set_error_literal (err: error, |
530 | GDK_PIXBUF_ERROR, |
531 | code: GDK_PIXBUF_ERROR_INSUFFICIENT_MEMORY, |
532 | _("Not enough memory to load icon" )); |
533 | return; |
534 | } |
535 | if (State->cursor) { |
536 | gchar hot[10]; |
537 | g_snprintf (string: hot, n: 10, format: "%d" , State->x_hot); |
538 | gdk_pixbuf_set_option (pixbuf: State->pixbuf, key: "x_hot" , value: hot); |
539 | g_snprintf (string: hot, n: 10, format: "%d" , State->y_hot); |
540 | gdk_pixbuf_set_option (pixbuf: State->pixbuf, key: "y_hot" , value: hot); |
541 | } |
542 | |
543 | /* Notify the client that we are ready to go */ |
544 | (*State->prepared_func) (State->pixbuf, |
545 | NULL, |
546 | State->user_data); |
547 | } |
548 | |
549 | } |
550 | |
551 | /* |
552 | * func - called when we have pixmap created (but no image data) |
553 | * user_data - passed as arg 1 to func |
554 | * return context (opaque to user) |
555 | */ |
556 | |
557 | static gpointer |
558 | gdk_pixbuf__ico_image_begin_load(GdkPixbufModuleSizeFunc size_func, |
559 | GdkPixbufModulePreparedFunc prepared_func, |
560 | GdkPixbufModuleUpdatedFunc updated_func, |
561 | gpointer user_data, |
562 | GError **error) |
563 | { |
564 | struct ico_progressive_state *context; |
565 | |
566 | g_assert (size_func != NULL); |
567 | g_assert (prepared_func != NULL); |
568 | g_assert (updated_func != NULL); |
569 | |
570 | context = g_new0(struct ico_progressive_state, 1); |
571 | context->size_func = size_func; |
572 | context->prepared_func = prepared_func; |
573 | context->updated_func = updated_func; |
574 | context->user_data = user_data; |
575 | |
576 | context->HeaderSize = 54; |
577 | context->HeaderBuf = g_try_malloc(n_bytes: 14 + INFOHEADER_SIZE + 4*256 + 512); |
578 | if (!context->HeaderBuf) { |
579 | g_free (mem: context); |
580 | g_set_error_literal (err: error, |
581 | GDK_PIXBUF_ERROR, |
582 | code: GDK_PIXBUF_ERROR_INSUFFICIENT_MEMORY, |
583 | _("Not enough memory to load ICO file" )); |
584 | return NULL; |
585 | } |
586 | /* 4*256 for the colormap */ |
587 | context->BytesInHeaderBuf = 14 + INFOHEADER_SIZE + 4*256 + 512 ; |
588 | context->HeaderDone = 0; |
589 | |
590 | context->LineWidth = 0; |
591 | context->LineBuf = NULL; |
592 | context->LineDone = 0; |
593 | context->Lines = 0; |
594 | |
595 | context->Type = 0; |
596 | |
597 | memset(s: &context->Header, c: 0, n: sizeof(struct headerpair)); |
598 | |
599 | |
600 | context->pixbuf = NULL; |
601 | |
602 | |
603 | return (gpointer) context; |
604 | } |
605 | |
606 | /* |
607 | * context - returned from image_begin_load |
608 | * |
609 | * free context, unref gdk_pixbuf |
610 | */ |
611 | static gboolean |
612 | gdk_pixbuf__ico_image_stop_load(gpointer data, |
613 | GError **error) |
614 | { |
615 | struct ico_progressive_state *context = |
616 | (struct ico_progressive_state *) data; |
617 | gboolean ret = TRUE; |
618 | |
619 | /* FIXME this thing needs to report errors if |
620 | * we have unused image data |
621 | */ |
622 | |
623 | g_return_val_if_fail(context != NULL, TRUE); |
624 | |
625 | if (context->HeaderDone < context->HeaderSize) { |
626 | g_set_error_literal (err: error, |
627 | GDK_PIXBUF_ERROR, |
628 | code: GDK_PIXBUF_ERROR_CORRUPT_IMAGE, |
629 | _("ICO image was truncated or incomplete." )); |
630 | ret = FALSE; |
631 | } |
632 | |
633 | context_free (context); |
634 | return ret; |
635 | } |
636 | |
637 | static void |
638 | OneLine32 (struct ico_progressive_state *context) |
639 | { |
640 | gint X; |
641 | guchar *Pixels; |
642 | gsize rowstride = gdk_pixbuf_get_rowstride (pixbuf: context->pixbuf); |
643 | |
644 | X = 0; |
645 | if (context->Header.Negative == 0) |
646 | Pixels = (gdk_pixbuf_get_pixels (pixbuf: context->pixbuf) + |
647 | rowstride * (context->Header.height - context->Lines - 1)); |
648 | else |
649 | Pixels = (gdk_pixbuf_get_pixels (pixbuf: context->pixbuf) + |
650 | rowstride * context->Lines); |
651 | while (X < context->Header.width) { |
652 | /* BGRA */ |
653 | Pixels[X * 4 + 0] = context->LineBuf[X * 4 + 2]; |
654 | Pixels[X * 4 + 1] = context->LineBuf[X * 4 + 1]; |
655 | Pixels[X * 4 + 2] = context->LineBuf[X * 4 + 0]; |
656 | Pixels[X * 4 + 3] = context->LineBuf[X * 4 + 3]; |
657 | X++; |
658 | } |
659 | } |
660 | |
661 | static void OneLine24(struct ico_progressive_state *context) |
662 | { |
663 | gint X; |
664 | guchar *Pixels; |
665 | gsize rowstride = gdk_pixbuf_get_rowstride (pixbuf: context->pixbuf); |
666 | |
667 | X = 0; |
668 | if (context->Header.Negative == 0) |
669 | Pixels = (gdk_pixbuf_get_pixels (pixbuf: context->pixbuf) + |
670 | rowstride * (context->Header.height - context->Lines - 1)); |
671 | else |
672 | Pixels = (gdk_pixbuf_get_pixels (pixbuf: context->pixbuf) + |
673 | rowstride * context->Lines); |
674 | while (X < context->Header.width) { |
675 | Pixels[X * 4 + 0] = context->LineBuf[X * 3 + 2]; |
676 | Pixels[X * 4 + 1] = context->LineBuf[X * 3 + 1]; |
677 | Pixels[X * 4 + 2] = context->LineBuf[X * 3 + 0]; |
678 | Pixels[X * 4 + 3] = 0xff; |
679 | X++; |
680 | } |
681 | |
682 | } |
683 | |
684 | static void |
685 | OneLine16 (struct ico_progressive_state *context) |
686 | { |
687 | int i; |
688 | guchar *pixels; |
689 | guchar *src; |
690 | gsize rowstride = gdk_pixbuf_get_rowstride (pixbuf: context->pixbuf); |
691 | |
692 | if (context->Header.Negative == 0) |
693 | pixels = (gdk_pixbuf_get_pixels (pixbuf: context->pixbuf) + |
694 | rowstride * (context->Header.height - context->Lines - 1)); |
695 | else |
696 | pixels = (gdk_pixbuf_get_pixels (pixbuf: context->pixbuf) + |
697 | rowstride * context->Lines); |
698 | |
699 | src = context->LineBuf; |
700 | |
701 | for (i = 0; i < context->Header.width; i++) { |
702 | int v, r, g, b; |
703 | |
704 | v = (int) src[0] | ((int) src[1] << 8); |
705 | src += 2; |
706 | |
707 | /* Extract 5-bit RGB values */ |
708 | |
709 | r = (v >> 10) & 0x1f; |
710 | g = (v >> 5) & 0x1f; |
711 | b = v & 0x1f; |
712 | |
713 | /* Fill the rightmost bits to form 8-bit values */ |
714 | |
715 | *pixels++ = (r << 3) | (r >> 2); |
716 | *pixels++ = (g << 3) | (g >> 2); |
717 | *pixels++ = (b << 3) | (b >> 2); |
718 | *pixels++ = 0xff; |
719 | } |
720 | } |
721 | |
722 | |
723 | static void OneLine8(struct ico_progressive_state *context) |
724 | { |
725 | gint X; |
726 | guchar *Pixels; |
727 | gsize rowstride = gdk_pixbuf_get_rowstride (pixbuf: context->pixbuf); |
728 | |
729 | X = 0; |
730 | if (context->Header.Negative == 0) |
731 | Pixels = (gdk_pixbuf_get_pixels (pixbuf: context->pixbuf) + |
732 | rowstride * (context->Header.height - context->Lines - 1)); |
733 | else |
734 | Pixels = (gdk_pixbuf_get_pixels (pixbuf: context->pixbuf) + |
735 | rowstride * context->Lines); |
736 | while (X < context->Header.width) { |
737 | /* The joys of having a BGR byteorder */ |
738 | Pixels[X * 4 + 0] = |
739 | context->HeaderBuf[4 * context->LineBuf[X] + INFOHEADER_SIZE + 2 + context->DIBoffset]; |
740 | Pixels[X * 4 + 1] = |
741 | context->HeaderBuf[4 * context->LineBuf[X] + INFOHEADER_SIZE + 1 +context->DIBoffset]; |
742 | Pixels[X * 4 + 2] = |
743 | context->HeaderBuf[4 * context->LineBuf[X] + INFOHEADER_SIZE +context->DIBoffset]; |
744 | Pixels[X * 4 + 3] = 0xff; |
745 | X++; |
746 | } |
747 | } |
748 | static void OneLine4(struct ico_progressive_state *context) |
749 | { |
750 | gint X; |
751 | guchar *Pixels; |
752 | gsize rowstride = gdk_pixbuf_get_rowstride (pixbuf: context->pixbuf); |
753 | |
754 | X = 0; |
755 | if (context->Header.Negative == 0) |
756 | Pixels = (gdk_pixbuf_get_pixels (pixbuf: context->pixbuf) + |
757 | rowstride * (context->Header.height - context->Lines - 1)); |
758 | else |
759 | Pixels = (gdk_pixbuf_get_pixels (pixbuf: context->pixbuf) + |
760 | rowstride * context->Lines); |
761 | |
762 | while (X < context->Header.width) { |
763 | guchar Pix; |
764 | |
765 | Pix = context->LineBuf[X/2]; |
766 | |
767 | Pixels[X * 4 + 0] = |
768 | context->HeaderBuf[4 * (Pix>>4) + INFOHEADER_SIZE + 2 + context->DIBoffset]; |
769 | Pixels[X * 4 + 1] = |
770 | context->HeaderBuf[4 * (Pix>>4) + INFOHEADER_SIZE + 1 +context->DIBoffset]; |
771 | Pixels[X * 4 + 2] = |
772 | context->HeaderBuf[4 * (Pix>>4) + INFOHEADER_SIZE + context->DIBoffset]; |
773 | Pixels[X * 4 + 3] = 0xff; |
774 | X++; |
775 | if (X<context->Header.width) { |
776 | /* Handle the other 4 bit pixel only when there is one */ |
777 | Pixels[X * 4 + 0] = |
778 | context->HeaderBuf[4 * (Pix&15) + INFOHEADER_SIZE + 2 + context->DIBoffset]; |
779 | Pixels[X * 4 + 1] = |
780 | context->HeaderBuf[4 * (Pix&15) + INFOHEADER_SIZE + 1 + context->DIBoffset]; |
781 | Pixels[X * 4 + 2] = |
782 | context->HeaderBuf[4 * (Pix&15) + INFOHEADER_SIZE + context->DIBoffset]; |
783 | Pixels[X * 4 + 3] = 0xff; |
784 | X++; |
785 | } |
786 | } |
787 | |
788 | } |
789 | |
790 | static void OneLine1(struct ico_progressive_state *context) |
791 | { |
792 | gint X; |
793 | guchar *Pixels; |
794 | gsize rowstride = gdk_pixbuf_get_rowstride (pixbuf: context->pixbuf); |
795 | |
796 | X = 0; |
797 | if (context->Header.Negative == 0) |
798 | Pixels = (gdk_pixbuf_get_pixels (pixbuf: context->pixbuf) + |
799 | rowstride * (context->Header.height - context->Lines - 1)); |
800 | else |
801 | Pixels = (gdk_pixbuf_get_pixels (pixbuf: context->pixbuf) + |
802 | rowstride * context->Lines); |
803 | while (X < context->Header.width) { |
804 | int Bit; |
805 | |
806 | Bit = (context->LineBuf[X / 8]) >> (7 - (X & 7)); |
807 | Bit = Bit & 1; |
808 | /* The joys of having a BGR byteorder */ |
809 | Pixels[X * 4 + 0] = Bit*255; |
810 | Pixels[X * 4 + 1] = Bit*255; |
811 | Pixels[X * 4 + 2] = Bit*255; |
812 | Pixels[X * 4 + 3] = 0xff; |
813 | X++; |
814 | } |
815 | } |
816 | |
817 | static void OneLineTransp(struct ico_progressive_state *context) |
818 | { |
819 | gint X; |
820 | guchar *Pixels; |
821 | gsize rowstride = gdk_pixbuf_get_rowstride (pixbuf: context->pixbuf); |
822 | |
823 | /* Ignore the XOR mask for XP style 32-bpp icons with alpha */ |
824 | if (context->Header.depth == 32) |
825 | return; |
826 | |
827 | X = 0; |
828 | if (context->Header.Negative == 0) |
829 | Pixels = (gdk_pixbuf_get_pixels (pixbuf: context->pixbuf) + |
830 | rowstride * (2*context->Header.height - context->Lines - 1)); |
831 | else |
832 | Pixels = (gdk_pixbuf_get_pixels (pixbuf: context->pixbuf) + |
833 | rowstride * (context->Lines-context->Header.height)); |
834 | while (X < context->Header.width) { |
835 | int Bit; |
836 | |
837 | Bit = (context->LineBuf[X / 8]) >> (7 - (X & 7)); |
838 | Bit = Bit & 1; |
839 | /* The joys of having a BGR byteorder */ |
840 | Pixels[X * 4 + 3] = 255-Bit*255; |
841 | #if 0 |
842 | if (Bit){ |
843 | Pixels[X*4+0] = 255; |
844 | Pixels[X*4+1] = 255; |
845 | } else { |
846 | Pixels[X*4+0] = 0; |
847 | Pixels[X*4+1] = 0; |
848 | } |
849 | #endif |
850 | X++; |
851 | } |
852 | } |
853 | |
854 | |
855 | static void OneLine(struct ico_progressive_state *context) |
856 | { |
857 | context->LineDone = 0; |
858 | |
859 | if (context->Lines >= context->Header.height*2) { |
860 | return; |
861 | } |
862 | |
863 | if (context->Lines <context->Header.height) { |
864 | if (context->Type == 32) |
865 | OneLine32 (context); |
866 | else if (context->Type == 24) |
867 | OneLine24(context); |
868 | else if (context->Type == 16) |
869 | OneLine16 (context); |
870 | else if (context->Type == 8) |
871 | OneLine8(context); |
872 | else if (context->Type == 4) |
873 | OneLine4(context); |
874 | else if (context->Type == 1) |
875 | OneLine1(context); |
876 | else |
877 | g_assert_not_reached (); |
878 | } else |
879 | OneLineTransp(context); |
880 | |
881 | context->Lines++; |
882 | if (context->Lines>=context->Header.height) { |
883 | context->Type = 1; |
884 | context->LineWidth = context->Header.width / 8; |
885 | if ((context->Header.width & 7) != 0) |
886 | context->LineWidth++; |
887 | /* Pad to a 32 bit boundary */ |
888 | if (((context->LineWidth % 4) > 0)) |
889 | context->LineWidth = (context->LineWidth / 4) * 4 + 4; |
890 | } |
891 | |
892 | { |
893 | int y; |
894 | |
895 | y = context->Lines % context->Header.height; |
896 | if (context->Header.Negative == 0 && |
897 | context->Lines < context->Header.height) |
898 | y = context->Header.height - y; |
899 | (*context->updated_func) (context->pixbuf, |
900 | 0, |
901 | y, |
902 | context->Header.width, |
903 | 1, |
904 | context->user_data); |
905 | |
906 | } |
907 | } |
908 | |
909 | /* |
910 | * context - from image_begin_load |
911 | * buf - new image data |
912 | * size - length of new image data |
913 | * |
914 | * append image data onto inrecrementally built output image |
915 | */ |
916 | static gboolean |
917 | gdk_pixbuf__ico_image_load_increment(gpointer data, |
918 | const guchar * buf, |
919 | guint size, |
920 | GError **error) |
921 | { |
922 | struct ico_progressive_state *context = |
923 | (struct ico_progressive_state *) data; |
924 | |
925 | gint BytesToCopy; |
926 | |
927 | while (size > 0) { |
928 | g_assert(context->LineDone >= 0); |
929 | if (context->HeaderDone < context->HeaderSize) { /* We still |
930 | have headerbytes to do */ |
931 | BytesToCopy = |
932 | context->HeaderSize - context->HeaderDone; |
933 | if (BytesToCopy > size) |
934 | BytesToCopy = size; |
935 | |
936 | memmove(dest: context->HeaderBuf + context->HeaderDone, |
937 | src: buf, n: BytesToCopy); |
938 | |
939 | size -= BytesToCopy; |
940 | buf += BytesToCopy; |
941 | context->HeaderDone += BytesToCopy; |
942 | } |
943 | else { |
944 | BytesToCopy = |
945 | context->LineWidth - context->LineDone; |
946 | if (BytesToCopy > size) |
947 | BytesToCopy = size; |
948 | |
949 | if (BytesToCopy > 0) { |
950 | /* Should be non-NULL once the header is decoded, as below. */ |
951 | g_assert (context->LineBuf != NULL); |
952 | |
953 | memmove(dest: context->LineBuf + |
954 | context->LineDone, src: buf, |
955 | n: BytesToCopy); |
956 | |
957 | size -= BytesToCopy; |
958 | buf += BytesToCopy; |
959 | context->LineDone += BytesToCopy; |
960 | } |
961 | if ((context->LineDone >= context->LineWidth) && (context->LineWidth > 0)) { |
962 | /* By this point, DecodeHeader() will have been called, and should have returned successfully |
963 | * or set a #GError, as its only return-FALSE-without-setting-a-GError paths are when |
964 | * (context->HeaderDone < context->HeaderSize) or (context->LineWidth == 0). |
965 | * If it’s returned a #GError, we will have bailed already; otherwise, pixbuf will be set. */ |
966 | g_assert (context->pixbuf != NULL); |
967 | OneLine(context); |
968 | } |
969 | |
970 | |
971 | } |
972 | |
973 | if (context->HeaderDone >= 6 && context->pixbuf == NULL) { |
974 | GError *decode_err = NULL; |
975 | DecodeHeader(Data: context->HeaderBuf, |
976 | Bytes: context->HeaderDone, State: context, error: &decode_err); |
977 | if (context->LineBuf != NULL && context->LineWidth == 0) |
978 | return TRUE; |
979 | |
980 | if (decode_err) { |
981 | g_propagate_error (dest: error, src: decode_err); |
982 | return FALSE; |
983 | } |
984 | } |
985 | } |
986 | |
987 | return TRUE; |
988 | } |
989 | |
990 | /* saving ICOs */ |
991 | |
992 | static gint |
993 | write8 (FILE *f, |
994 | guint8 *data, |
995 | gint count) |
996 | { |
997 | gint bytes; |
998 | gint written; |
999 | |
1000 | written = 0; |
1001 | while (count > 0) |
1002 | { |
1003 | bytes = fwrite (ptr: (char*) data, size: sizeof (char), n: count, s: f); |
1004 | if (bytes <= 0) |
1005 | break; |
1006 | count -= bytes; |
1007 | data += bytes; |
1008 | written += bytes; |
1009 | } |
1010 | |
1011 | return written; |
1012 | } |
1013 | |
1014 | static gint |
1015 | write16 (FILE *f, |
1016 | guint16 *data, |
1017 | gint count) |
1018 | { |
1019 | gint i; |
1020 | |
1021 | for (i = 0; i < count; i++) |
1022 | data[i] = GUINT16_TO_LE (data[i]); |
1023 | |
1024 | return write8 (f, data: (guint8*) data, count: count * 2); |
1025 | } |
1026 | |
1027 | static gint |
1028 | write32 (FILE *f, |
1029 | guint32 *data, |
1030 | gint count) |
1031 | { |
1032 | gint i; |
1033 | |
1034 | for (i = 0; i < count; i++) |
1035 | data[i] = GUINT32_TO_LE (data[i]); |
1036 | |
1037 | return write8 (f, data: (guint8*) data, count: count * 4); |
1038 | } |
1039 | |
1040 | typedef struct _IconEntry IconEntry; |
1041 | struct _IconEntry { |
1042 | gint width; |
1043 | gint height; |
1044 | gint depth; |
1045 | gint hot_x; |
1046 | gint hot_y; |
1047 | |
1048 | guint8 n_colors; |
1049 | guint32 *colors; |
1050 | guint xor_rowstride; |
1051 | guint8 *xor; |
1052 | guint and_rowstride; |
1053 | guint8 *and; |
1054 | }; |
1055 | |
1056 | static gboolean |
1057 | fill_entry (IconEntry *icon, |
1058 | GdkPixbuf *pixbuf, |
1059 | gint hot_x, |
1060 | gint hot_y, |
1061 | GError **error) |
1062 | { |
1063 | guchar *p, *pixels, *and, *xor; |
1064 | gint n_channels, v, x, y; |
1065 | |
1066 | if (icon->width > 256 || icon->height > 256) { |
1067 | g_set_error_literal (err: error, |
1068 | GDK_PIXBUF_ERROR, |
1069 | code: GDK_PIXBUF_ERROR_BAD_OPTION, |
1070 | _("Image too large to be saved as ICO" )); |
1071 | return FALSE; |
1072 | } |
1073 | |
1074 | if (hot_x > -1 && hot_y > -1) { |
1075 | icon->hot_x = hot_x; |
1076 | icon->hot_y = hot_y; |
1077 | if (icon->hot_x >= icon->width || icon->hot_y >= icon->height) { |
1078 | g_set_error_literal (err: error, |
1079 | GDK_PIXBUF_ERROR, |
1080 | code: GDK_PIXBUF_ERROR_BAD_OPTION, |
1081 | _("Cursor hotspot outside image" )); |
1082 | return FALSE; |
1083 | } |
1084 | } |
1085 | else { |
1086 | icon->hot_x = -1; |
1087 | icon->hot_y = -1; |
1088 | } |
1089 | |
1090 | switch (icon->depth) { |
1091 | case 32: |
1092 | icon->xor_rowstride = icon->width * 4; |
1093 | break; |
1094 | case 24: |
1095 | icon->xor_rowstride = icon->width * 3; |
1096 | break; |
1097 | case 16: |
1098 | icon->xor_rowstride = icon->width * 2; |
1099 | break; |
1100 | default: |
1101 | g_set_error (err: error, |
1102 | GDK_PIXBUF_ERROR, |
1103 | code: GDK_PIXBUF_ERROR_BAD_OPTION, |
1104 | _("Unsupported depth for ICO file: %d" ), icon->depth); |
1105 | return FALSE; |
1106 | } |
1107 | |
1108 | if ((icon->xor_rowstride % 4) != 0) |
1109 | icon->xor_rowstride = 4 * ((icon->xor_rowstride / 4) + 1); |
1110 | icon->xor = g_new0 (guchar, icon->xor_rowstride * icon->height); |
1111 | |
1112 | icon->and_rowstride = (icon->width + 7) / 8; |
1113 | if ((icon->and_rowstride % 4) != 0) |
1114 | icon->and_rowstride = 4 * ((icon->and_rowstride / 4) + 1); |
1115 | icon->and = g_new0 (guchar, icon->and_rowstride * icon->height); |
1116 | |
1117 | pixels = gdk_pixbuf_get_pixels (pixbuf); |
1118 | n_channels = gdk_pixbuf_get_n_channels (pixbuf); |
1119 | for (y = 0; y < icon->height; y++) { |
1120 | p = pixels + (gsize) gdk_pixbuf_get_rowstride (pixbuf) * (icon->height - 1 - y); |
1121 | and = icon->and + icon->and_rowstride * y; |
1122 | xor = icon->xor + icon->xor_rowstride * y; |
1123 | for (x = 0; x < icon->width; x++) { |
1124 | switch (icon->depth) { |
1125 | case 32: |
1126 | xor[0] = p[2]; |
1127 | xor[1] = p[1]; |
1128 | xor[2] = p[0]; |
1129 | xor[3] = 0xff; |
1130 | if (n_channels == 4) { |
1131 | xor[3] = p[3]; |
1132 | if (p[3] < 0x80) |
1133 | *and |= 1 << (7 - x % 8); |
1134 | } |
1135 | xor += 4; |
1136 | break; |
1137 | case 24: |
1138 | xor[0] = p[2]; |
1139 | xor[1] = p[1]; |
1140 | xor[2] = p[0]; |
1141 | if (n_channels == 4 && p[3] < 0x80) |
1142 | *and |= 1 << (7 - x % 8); |
1143 | xor += 3; |
1144 | break; |
1145 | case 16: |
1146 | v = ((p[0] >> 3) << 10) | ((p[1] >> 3) << 5) | (p[2] >> 3); |
1147 | xor[0] = v & 0xff; |
1148 | xor[1] = v >> 8; |
1149 | if (n_channels == 4 && p[3] < 0x80) |
1150 | *and |= 1 << (7 - x % 8); |
1151 | xor += 2; |
1152 | break; |
1153 | } |
1154 | |
1155 | p += n_channels; |
1156 | if (x % 8 == 7) |
1157 | and++; |
1158 | } |
1159 | } |
1160 | |
1161 | return TRUE; |
1162 | } |
1163 | |
1164 | static void |
1165 | free_entry (IconEntry *icon) |
1166 | { |
1167 | g_free (mem: icon->colors); |
1168 | g_free (mem: icon->and); |
1169 | g_free (mem: icon->xor); |
1170 | g_free (mem: icon); |
1171 | } |
1172 | |
1173 | static void |
1174 | write_icon (FILE *f, GSList *entries) |
1175 | { |
1176 | IconEntry *icon; |
1177 | GSList *entry; |
1178 | guint8 bytes[4]; |
1179 | guint16 words[4]; |
1180 | guint32 dwords[6]; |
1181 | gint type; |
1182 | gint n_entries; |
1183 | gint offset; |
1184 | gint size; |
1185 | |
1186 | if (((IconEntry *)entries->data)->hot_x > -1) |
1187 | type = 2; |
1188 | else |
1189 | type = 1; |
1190 | n_entries = g_slist_length (list: entries); |
1191 | |
1192 | /* header */ |
1193 | words[0] = 0; |
1194 | words[1] = type; |
1195 | words[2] = n_entries; |
1196 | write16 (f, data: words, count: 3); |
1197 | |
1198 | offset = 6 + 16 * n_entries; |
1199 | |
1200 | for (entry = entries; entry; entry = entry->next) { |
1201 | icon = (IconEntry *)entry->data; |
1202 | size = INFOHEADER_SIZE + icon->height * (icon->and_rowstride + icon->xor_rowstride); |
1203 | |
1204 | /* directory entry */ |
1205 | if (icon->width == 256) |
1206 | bytes[0] = 0; |
1207 | else |
1208 | bytes[0] = icon->width; |
1209 | if (icon->height == 256) |
1210 | bytes[1] = 0; |
1211 | else |
1212 | bytes[1] = icon->height; |
1213 | bytes[2] = icon->n_colors; |
1214 | bytes[3] = 0; |
1215 | write8 (f, data: bytes, count: 4); |
1216 | if (type == 1) { |
1217 | words[0] = 1; |
1218 | words[1] = icon->depth; |
1219 | } |
1220 | else { |
1221 | words[0] = icon->hot_x; |
1222 | words[1] = icon->hot_y; |
1223 | } |
1224 | write16 (f, data: words, count: 2); |
1225 | dwords[0] = size; |
1226 | dwords[1] = offset; |
1227 | write32 (f, data: dwords, count: 2); |
1228 | |
1229 | offset += size; |
1230 | } |
1231 | |
1232 | for (entry = entries; entry; entry = entry->next) { |
1233 | icon = (IconEntry *)entry->data; |
1234 | |
1235 | /* bitmap header */ |
1236 | dwords[0] = INFOHEADER_SIZE; |
1237 | dwords[1] = icon->width; |
1238 | dwords[2] = icon->height * 2; |
1239 | write32 (f, data: dwords, count: 3); |
1240 | words[0] = 1; |
1241 | words[1] = icon->depth; |
1242 | write16 (f, data: words, count: 2); |
1243 | dwords[0] = 0; |
1244 | dwords[1] = 0; |
1245 | dwords[2] = 0; |
1246 | dwords[3] = 0; |
1247 | dwords[4] = 0; |
1248 | dwords[5] = 0; |
1249 | write32 (f, data: dwords, count: 6); |
1250 | |
1251 | /* image data */ |
1252 | write8 (f, data: icon->xor, count: icon->xor_rowstride * icon->height); |
1253 | write8 (f, data: icon->and, count: icon->and_rowstride * icon->height); |
1254 | } |
1255 | } |
1256 | |
1257 | /* Locale-independent signed integer string parser, base 10. |
1258 | * @minimum and @maximum are valid inclusively. */ |
1259 | static gboolean |
1260 | ascii_strtoll (const gchar *str, |
1261 | gint64 minimum, |
1262 | gint64 maximum, |
1263 | gint64 *out, |
1264 | GError **error) |
1265 | { |
1266 | gint64 retval; |
1267 | const gchar *end_ptr; |
1268 | |
1269 | errno = 0; |
1270 | retval = g_ascii_strtoll (nptr: str, endptr: (gchar **) &end_ptr, base: 10); |
1271 | |
1272 | if (errno != 0) { |
1273 | g_set_error_literal (err: error, |
1274 | G_IO_ERROR, code: G_IO_ERROR_INVALID_ARGUMENT, |
1275 | message: g_strerror (errno)); |
1276 | return FALSE; |
1277 | } else if (end_ptr == str || *end_ptr != '\0') { |
1278 | g_set_error (err: error, G_IO_ERROR, code: G_IO_ERROR_INVALID_ARGUMENT, |
1279 | format: "Argument is not an integer: %s" , str); |
1280 | return FALSE; |
1281 | } else if ((maximum < G_MAXINT64 && retval > maximum) || |
1282 | (minimum > G_MININT64 && retval < minimum)) { |
1283 | g_set_error (err: error, G_IO_ERROR, code: G_IO_ERROR_INVALID_ARGUMENT, |
1284 | format: "Argument should be in range [%" G_GINT64_FORMAT |
1285 | ", %" G_GINT64_FORMAT "]: %s" , |
1286 | minimum, maximum, str); |
1287 | return FALSE; |
1288 | } |
1289 | |
1290 | g_assert (retval >= minimum && retval <= maximum); |
1291 | |
1292 | if (out != NULL) |
1293 | *out = retval; |
1294 | |
1295 | return TRUE; |
1296 | } |
1297 | |
1298 | static gboolean |
1299 | gdk_pixbuf__ico_image_save (FILE *f, |
1300 | GdkPixbuf *pixbuf, |
1301 | gchar **keys, |
1302 | gchar **values, |
1303 | GError **error) |
1304 | { |
1305 | gint hot_x, hot_y; |
1306 | IconEntry *icon; |
1307 | GSList *entries = NULL; |
1308 | |
1309 | /* support only single-image ICOs for now */ |
1310 | icon = g_new0 (IconEntry, 1); |
1311 | icon->width = gdk_pixbuf_get_width (pixbuf); |
1312 | icon->height = gdk_pixbuf_get_height (pixbuf); |
1313 | icon->depth = gdk_pixbuf_get_has_alpha (pixbuf) ? 32 : 24; |
1314 | hot_x = -1; |
1315 | hot_y = -1; |
1316 | |
1317 | /* parse options */ |
1318 | if (keys && *keys) { |
1319 | gchar **kiter; |
1320 | gchar **viter; |
1321 | |
1322 | for (kiter = keys, viter = values; *kiter && *viter; kiter++, viter++) { |
1323 | gint64 out; |
1324 | if (strcmp (s1: *kiter, s2: "depth" ) == 0) { |
1325 | if (!ascii_strtoll (str: *viter, minimum: 1, maximum: 32, |
1326 | out: &out, error)) |
1327 | return FALSE; |
1328 | icon->depth = out; |
1329 | } |
1330 | else if (strcmp (s1: *kiter, s2: "x_hot" ) == 0) { |
1331 | if (!ascii_strtoll (str: *viter, G_MININT, G_MAXINT, |
1332 | out: &out, error)) |
1333 | return FALSE; |
1334 | hot_x = out; |
1335 | } |
1336 | else if (strcmp (s1: *kiter, s2: "y_hot" ) == 0) { |
1337 | if (!ascii_strtoll (str: *viter, G_MININT, G_MAXINT, |
1338 | out: &out, error)) |
1339 | return FALSE; |
1340 | hot_y = out; |
1341 | } |
1342 | |
1343 | } |
1344 | } |
1345 | |
1346 | if (!fill_entry (icon, pixbuf, hot_x, hot_y, error)) { |
1347 | free_entry (icon); |
1348 | return FALSE; |
1349 | } |
1350 | |
1351 | entries = g_slist_append (list: entries, data: icon); |
1352 | write_icon (f, entries); |
1353 | |
1354 | g_slist_foreach (list: entries, func: (GFunc)free_entry, NULL); |
1355 | g_slist_free (list: entries); |
1356 | |
1357 | return TRUE; |
1358 | } |
1359 | |
1360 | static gboolean |
1361 | gdk_pixbuf__ico_is_save_option_supported (const gchar *option_key) |
1362 | { |
1363 | if (g_strcmp0 (str1: option_key, str2: "depth" ) == 0 || |
1364 | g_strcmp0 (str1: option_key, str2: "x_hot" ) == 0 || |
1365 | g_strcmp0 (str1: option_key, str2: "y_hot" ) == 0) |
1366 | return TRUE; |
1367 | |
1368 | return FALSE; |
1369 | } |
1370 | |
1371 | #ifndef INCLUDE_ico |
1372 | #define MODULE_ENTRY(function) G_MODULE_EXPORT void function |
1373 | #else |
1374 | #define MODULE_ENTRY(function) void _gdk_pixbuf__ico_ ## function |
1375 | #endif |
1376 | |
1377 | MODULE_ENTRY (fill_vtable) (GdkPixbufModule *module) |
1378 | { |
1379 | module->begin_load = gdk_pixbuf__ico_image_begin_load; |
1380 | module->stop_load = gdk_pixbuf__ico_image_stop_load; |
1381 | module->load_increment = gdk_pixbuf__ico_image_load_increment; |
1382 | module->save = gdk_pixbuf__ico_image_save; |
1383 | module->is_save_option_supported = gdk_pixbuf__ico_is_save_option_supported; |
1384 | } |
1385 | |
1386 | MODULE_ENTRY (fill_info) (GdkPixbufFormat *info) |
1387 | { |
1388 | static const GdkPixbufModulePattern signature[] = { |
1389 | { " \x1 " , "zz znz" , 100 }, |
1390 | { " \x2 " , "zz znz" , 100 }, |
1391 | { NULL, NULL, 0 } |
1392 | }; |
1393 | static const gchar *mime_types[] = { |
1394 | "image/x-icon" , |
1395 | "image/x-ico" , |
1396 | "image/x-win-bitmap" , |
1397 | "image/vnd.microsoft.icon" , |
1398 | "application/ico" , |
1399 | "image/ico" , |
1400 | "image/icon" , |
1401 | "text/ico" , |
1402 | NULL |
1403 | }; |
1404 | static const gchar *extensions[] = { |
1405 | "ico" , |
1406 | "cur" , |
1407 | NULL |
1408 | }; |
1409 | |
1410 | info->name = "ico" ; |
1411 | info->signature = (GdkPixbufModulePattern *) signature; |
1412 | info->description = NC_("image format" , "Windows icon" ); |
1413 | info->mime_types = (gchar **) mime_types; |
1414 | info->extensions = (gchar **) extensions; |
1415 | info->flags = GDK_PIXBUF_FORMAT_WRITABLE | GDK_PIXBUF_FORMAT_THREADSAFE; |
1416 | info->license = "LGPL" ; |
1417 | } |
1418 | |
1419 | |
1420 | |
1421 | |
1422 | |