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