]> git.imager.perl.org - imager.git/blob - ICO/imicon.c
00b516a242e66a16f42815c2dfee07c8ef750b69
[imager.git] / ICO / imicon.c
1 #include "imext.h"
2 #include "imicon.h"
3 #include "msicon.h"
4 #include <string.h>
5
6 static void
7 ico_push_error(int error) {
8   char error_buf[ICO_MAX_MESSAGE];
9
10   ico_error_message(error, error_buf, sizeof(error_buf));
11   i_push_error(error, error_buf);
12 }
13
14 static
15 i_img *
16 read_one_icon(ico_reader_t *file, int index) {
17   ico_image_t *image;
18   int error;
19   i_img *result;
20
21   image = ico_image_read(file, index, &error);
22   if (!image) {
23     ico_push_error(error);
24     i_push_error(0, "error reading ICO/CUR image");
25     return NULL;
26   }
27
28   if (image->direct) {
29     int x, y;
30     i_color *line_buf;
31     i_color *outp;
32     ico_color_t *inp = image->image_data;
33
34     if (!i_int_check_image_file_limits(image->width, image->height, 4, 1)) {
35       ico_image_release(image);
36       return NULL;
37     }
38
39     result = i_img_8_new(image->width, image->height, 4);
40     if (!result) {
41       ico_image_release(image);
42       return NULL;
43     }
44
45     line_buf = mymalloc(image->width * sizeof(i_color));
46
47     for (y = 0; y < image->height; ++y) {
48       outp = line_buf;
49       for (x = 0; x < image->width; ++x) {
50         outp->rgba.r = inp->r;
51         outp->rgba.g = inp->g;
52         outp->rgba.b = inp->b;
53         outp->rgba.a = inp->a;
54         ++outp;
55         ++inp;
56       }
57       i_plin(result, 0, image->width, y, line_buf);
58     }
59
60     myfree(line_buf);
61   }
62   else {
63     int pal_index;
64     int y;
65     unsigned char *image_data;
66
67     if (!i_int_check_image_file_limits(image->width, image->height, 3, 1)) {
68       ico_image_release(image);
69       return NULL;
70     }
71
72     result = i_img_pal_new(image->width, image->height, 3, 256);
73     if (!result) {
74       ico_image_release(image);
75       return NULL;
76     }
77     
78     /* fill in the palette */
79     for (pal_index = 0; pal_index < image->palette_size; ++pal_index) {
80       i_color c;
81       c.rgba.r = image->palette[pal_index].r;
82       c.rgba.g = image->palette[pal_index].g;
83       c.rgba.b = image->palette[pal_index].b;
84       c.rgba.a = 255; /* so as to not confuse some code */
85
86       if (i_addcolors(result, &c, 1) < 0) {
87         i_push_error(0, "could not add color to palette");
88         ico_image_release(image);
89         i_img_destroy(result);
90         return NULL;
91       }
92     }
93
94     /* fill in the image data */
95     image_data = image->image_data;
96     for (y = 0; y < image->height; ++y) {
97       i_ppal(result, 0, image->width, y, image_data);
98       image_data += image->width;
99     }
100   }
101
102   {
103     unsigned char *inp = image->mask_data;
104     char *outp;
105     int x, y;
106     char *mask;
107     /* fill in the mask tag */
108     /* space for " .\n", width + 1 chars per line and NUL */
109     mask = mymalloc(3 + (image->width + 1) * image->height + 1);
110
111     outp = mask;
112     *outp++ = '.';
113     *outp++ = '*';
114     *outp++ = '\n';
115     for (y = 0; y < image->height; ++y) {
116       for (x = 0; x < image->width; ++x) {
117         *outp++ = *inp++ ? '*' : '.';
118       }
119       if (y != image->height - 1) /* not on the last line */
120         *outp++ = '\n';
121     }
122     *outp++ = '\0';
123
124     if (ico_type(file) == ICON_ICON)
125       i_tags_set(&result->tags, "ico_mask", mask, (outp-mask)-1);
126     else
127       i_tags_set(&result->tags, "cur_mask", mask, (outp-mask)-1);
128     
129     myfree(mask);
130   }
131   if (ico_type(file) == ICON_ICON) {
132     i_tags_setn(&result->tags, "ico_bits", image->bit_count);
133     i_tags_set(&result->tags, "i_format", "ico", 3);
134   }
135   else {
136     i_tags_setn(&result->tags, "cur_bits", image->bit_count);
137     i_tags_set(&result->tags, "i_format", "cur", 3);
138     i_tags_setn(&result->tags, "cur_hotspotx", image->hotspot_x);
139     i_tags_setn(&result->tags, "cur_hotspoty", image->hotspot_y);
140   }
141
142   ico_image_release(image);
143
144   return result;
145 }
146
147 i_img *
148 i_readico_single(io_glue *ig, int index) {
149   ico_reader_t *file;
150   i_img *result;
151   int error;
152
153   i_clear_error();
154
155   file = ico_reader_open(ig, &error);
156   if (!file) {
157     ico_push_error(error);
158     i_push_error(0, "error opening ICO/CUR file");
159     return NULL;
160   }
161
162   /* the index is range checked by msicon.c - don't duplicate it here */
163
164   result = read_one_icon(file, index);
165   ico_reader_close(file);
166
167   return result;
168 }
169
170 i_img **
171 i_readico_multi(io_glue *ig, int *count) {
172   ico_reader_t *file;
173   int index;
174   int error;
175   i_img **imgs;
176
177   i_clear_error();
178
179   file = ico_reader_open(ig, &error);
180   if (!file) {
181     ico_push_error(error);
182     i_push_error(0, "error opening ICO/CUR file");
183     return NULL;
184   }
185
186   imgs = mymalloc(sizeof(i_img *) * ico_image_count(file));
187
188   *count = 0;
189   for (index = 0; index < ico_image_count(file); ++index) {
190     i_img *im = read_one_icon(file, index);
191     if (!im)
192       break;
193
194     imgs[(*count)++] = im;
195   }
196
197   ico_reader_close(file);
198
199   if (*count == 0) {
200     myfree(imgs);
201     return NULL;
202   }
203
204   return imgs;
205 }
206
207 static int
208 validate_image(i_img *im) {
209   if (im->xsize > 255 || im->ysize > 255) {
210     i_push_error(0, "image too large for ico file");
211     return 0;
212   }
213   if (im->channels < 1 || im->channels > 4) {
214     /* this shouldn't happen, but check anyway */
215     i_push_error(0, "invalid channels");
216     return 0;
217   }
218
219   return 1;
220 }
221
222 static int
223 translate_mask(i_img *im, unsigned char *out, const char *in) {
224   int x, y;
225   int one, zero;
226   int len = strlen(in);
227   int pos;
228   int newline; /* set to the first newline type we see */
229   int notnewline; /* set to whatever in ( "\n\r" newline isn't ) */
230
231   if (len < 3)
232     return 0;
233
234   zero = in[0];
235   one = in[1];
236   if (in[2] == '\n' || in[2] == '\r') {
237     newline = in[2];
238     notnewline = '\n' + '\r' - newline;
239   }
240   else {
241     return 0;
242   }
243
244   pos = 3;
245   y = 0;
246   while (y < im->ysize && pos < len) {
247     x = 0;
248     while (x < im->xsize && pos < len) {
249       if (in[pos] == newline) {
250         /* don't process it, we look for it later */
251         break;
252       }
253       else if (in[pos] == notnewline) {
254         ++pos; /* just drop it */
255       }
256       else if (in[pos] == one) {
257         *out++ = 1;
258         ++x;
259         ++pos;
260       }
261       else if (in[pos] == zero) {
262         *out++ = 0;
263         ++x;
264         ++pos;
265       }
266       else if (in[pos] == ' ' || in[pos] == '\t') {
267         /* just ignore whitespace */
268         ++pos;
269       }
270       else {
271         return 0;
272       }
273     }
274     while (x++ < im->xsize) {
275       *out++ = 0;
276     }
277     while (pos < len && in[pos] != newline)
278       ++pos;
279     if (pos < len && in[pos] == newline)
280       ++pos; /* actually skip the newline */
281
282     ++y;
283   }
284   while (y++ < im->ysize) {
285     for (x = 0; x < im->xsize; ++x)
286       *out++ = 0;
287   }
288
289   return 1;
290 }
291
292 static void 
293 derive_mask(i_img *im, ico_image_t *ico) {
294
295   if (im->channels == 1 || im->channels == 3) {
296     /* msicon.c's default mask is what we want */
297     myfree(ico->mask_data);
298     ico->mask_data = NULL;
299   }
300   else {
301     int channel = im->channels - 1;
302     i_sample_t *linebuf = mymalloc(sizeof(i_sample_t) * im->xsize);
303     int x, y;
304     unsigned char *out = ico->mask_data;
305
306     for (y = 0; y < im->ysize; ++y) {
307       i_gsamp(im, 0, im->xsize, y, linebuf, &channel, 1);
308       for (x = 0; x < im->xsize; ++x) {
309         *out++ = linebuf[x] == 255 ? 0 : 1;
310       }
311     }
312     myfree(linebuf);
313   }
314 }
315
316 static void
317 fill_image_base(i_img *im, ico_image_t *ico, const char *mask_name) {
318   int x, y;
319
320   ico->width = im->xsize;
321   ico->height = im->ysize;
322   ico->direct = im->type == i_direct_type;
323   if (ico->direct) {
324     int channels[4];
325     int set_alpha = 0;
326     ico_color_t *out;
327     i_sample_t *in;
328     unsigned char *linebuf = mymalloc(ico->width * 4);
329     ico->image_data = mymalloc(sizeof(ico_color_t) * ico->width * ico->height);
330     
331     switch (im->channels) {
332     case 1:
333       channels[0] = channels[1] = channels[2] = channels[3] = 0;
334       ++set_alpha;
335       break;
336
337     case 2:
338       channels[0] = channels[1] = channels[2] = 0;
339       channels[3] = 1;
340       break;
341
342     case 3:
343       channels[0] = 0;
344       channels[1] = 1;
345       channels[2] = 2;
346       channels[3] = 2;
347       ++set_alpha;
348       break;
349
350     case 4:
351       channels[0] = 0;
352       channels[1] = 1;
353       channels[2] = 2;
354       channels[3] = 3;
355       break;
356     }
357     
358     out = ico->image_data;
359     for (y = 0; y < im->ysize; ++y) {
360       i_gsamp(im, 0, im->xsize, y, linebuf, channels, 4);
361       in = linebuf;
362       for (x = 0; x < im->xsize; ++x) {
363         out->r = *in++;
364         out->g = *in++;
365         out->b = *in++;
366         out->a = set_alpha ? 255 : *in;
367         in++;
368         ++out;
369       }
370     }
371     myfree(linebuf);
372     ico->palette = NULL;
373   }
374   else {
375     unsigned char *out;
376     i_color *colors;
377     int i;
378     i_palidx *in;
379     i_palidx *linebuf = mymalloc(sizeof(i_palidx) * ico->width);
380
381     ico->image_data = mymalloc(sizeof(ico_color_t) * ico->width * ico->height);
382
383     out = ico->image_data;
384     for (y = 0; y < im->ysize; ++y) {
385       i_gpal(im, 0, im->xsize, y, linebuf);
386       in = linebuf;
387       for (x = 0; x < im->xsize; ++x) {
388         *out++ = *in++;
389       }
390     }
391     myfree(linebuf);
392
393     ico->palette_size = i_colorcount(im);
394     ico->palette = mymalloc(sizeof(ico_color_t) * ico->palette_size);
395     colors = mymalloc(sizeof(i_color) * ico->palette_size);
396     i_getcolors(im, 0, colors, ico->palette_size);
397     for (i = 0; i < ico->palette_size; ++i) {
398       if (im->channels == 1 || im->channels == 2) {
399         ico->palette[i].r = ico->palette[i].g =
400           ico->palette[i].b = colors[i].rgba.r;
401       }
402       else {
403         ico->palette[i].r = colors[i].rgba.r;
404         ico->palette[i].g = colors[i].rgba.g;
405         ico->palette[i].b = colors[i].rgba.b;
406       }
407     }
408     myfree(colors);
409   }
410
411   {
412     /* build the mask */
413     int mask_index;
414
415     ico->mask_data = mymalloc(im->xsize * im->ysize);
416
417     if (!i_tags_find(&im->tags, mask_name, 0, &mask_index)
418         || !im->tags.tags[mask_index].data
419         || !translate_mask(im, ico->mask_data, 
420                            im->tags.tags[mask_index].data)) {
421       derive_mask(im, ico);
422     }
423   }
424 }
425
426 static void
427 unfill_image(ico_image_t *ico) {
428   myfree(ico->image_data);
429   if (ico->palette)
430     myfree(ico->palette);
431   if (ico->mask_data)
432     myfree(ico->mask_data);
433 }
434
435 static void
436 fill_image_icon(i_img *im, ico_image_t *ico) {
437   fill_image_base(im, ico, "ico_mask");
438   ico->hotspot_x = ico->hotspot_y = 0;
439 }
440
441 int
442 i_writeico_wiol(i_io_glue_t *ig, i_img *im) {
443   ico_image_t ico;
444   int error;
445
446   i_clear_error();
447
448   if (!validate_image(im))
449     return 0;
450
451   fill_image_icon(im, &ico);
452
453   if (!ico_write(ig, &ico, 1, ICON_ICON, &error)) {
454     ico_push_error(error);
455     unfill_image(&ico);
456     return 0;
457   }
458
459   unfill_image(&ico);
460
461   if (i_io_close(ig) < 0) {
462     i_push_error(0, "error closing output");
463     return 0;
464   }
465
466   return 1;
467 }
468
469 int
470 i_writeico_multi_wiol(i_io_glue_t *ig, i_img **ims, int count) {
471   ico_image_t *icons;
472   int error;
473   int i;
474
475   i_clear_error();
476
477   if (count > 0xFFFF) {
478     i_push_error(0, "too many images for ico files");
479     return 0;
480   }
481
482   for (i = 0; i < count; ++i)
483     if (!validate_image(ims[i]))
484       return 0;
485
486   icons = mymalloc(sizeof(ico_image_t) * count);
487
488   for (i = 0; i < count; ++i)
489     fill_image_icon(ims[i], icons + i);
490
491   if (!ico_write(ig, icons, count, ICON_ICON, &error)) {
492     ico_push_error(error);
493     for (i = 0; i < count; ++i)
494       unfill_image(icons + i);
495     myfree(icons);
496     return 0;
497   }
498
499   for (i = 0; i < count; ++i)
500     unfill_image(icons + i);
501   myfree(icons);
502
503   if (i_io_close(ig) < 0) {
504     i_push_error(0, "error closing output");
505     return 0;
506   }
507
508   return 1;
509 }
510
511 void
512 fill_image_cursor(i_img *im, ico_image_t *ico) {
513   int hotx, hoty;
514   fill_image_base(im, ico, "ico_mask");
515
516   if (!i_tags_get_int(&im->tags, "cur_hotspotx", 0, &hotx))
517     hotx = 0;
518   if (!i_tags_get_int(&im->tags, "cur_hotspoty", 0, &hoty))
519     hoty = 0;
520
521   if (hotx < 0)
522     hotx = 0;
523   else if (hotx >= im->xsize)
524     hotx = im->xsize - 1;
525
526   if (hoty < 0)
527     hoty = 0;
528   else if (hoty >= im->ysize)
529     hoty = im->ysize - 1;
530   
531   ico->hotspot_x = hotx;
532   ico->hotspot_y = hoty;
533 }
534
535 int
536 i_writecur_wiol(i_io_glue_t *ig, i_img *im) {
537   ico_image_t ico;
538   int error;
539
540   i_clear_error();
541
542   if (!validate_image(im))
543     return 0;
544
545   fill_image_cursor(im, &ico);
546
547   if (!ico_write(ig, &ico, 1, ICON_CURSOR, &error)) {
548     ico_push_error(error);
549     unfill_image(&ico);
550     return 0;
551   }
552
553   unfill_image(&ico);
554
555   if (i_io_close(ig) < 0) {
556     i_push_error(0, "error closing output");
557     return 0;
558   }
559
560   return 1;
561 }
562
563 int
564 i_writecur_multi_wiol(i_io_glue_t *ig, i_img **ims, int count) {
565   ico_image_t *icons;
566   int error;
567   int i;
568
569   i_clear_error();
570
571   if (count > 0xFFFF) {
572     i_push_error(0, "too many images for ico files");
573     return 0;
574   }
575
576   for (i = 0; i < count; ++i)
577     if (!validate_image(ims[i]))
578       return 0;
579
580   icons = mymalloc(sizeof(ico_image_t) * count);
581
582   for (i = 0; i < count; ++i)
583     fill_image_cursor(ims[i], icons + i);
584
585   if (!ico_write(ig, icons, count, ICON_CURSOR, &error)) {
586     ico_push_error(error);
587     for (i = 0; i < count; ++i)
588       unfill_image(icons + i);
589     myfree(icons);
590     return 0;
591   }
592
593   for (i = 0; i < count; ++i)
594     unfill_image(icons + i);
595   myfree(icons);
596
597   if (i_io_close(ig) < 0) {
598     i_push_error(0, "error closing output");
599     return 0;
600   }
601
602   return 1;
603 }
604