1 #include "imext.h"
2 #include "imicon.h"
3 #include "msicon.h"
4 #include <string.h>
5 
6 static void
ico_push_error(int error)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 *
read_one_icon(ico_reader_t * file,int index,int masked,int alpha_masked)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 *
i_readico_single(io_glue * ig,int index,int masked,int alpha_masked)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 **
i_readico_multi(io_glue * ig,int * count,int masked,int alpha_masked)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
validate_image(i_img * im)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
translate_mask(i_img * im,unsigned char * out,const char * in)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
derive_mask(i_img * im,ico_image_t * ico)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
fill_image_base(i_img * im,ico_image_t * ico,const char * mask_name)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
unfill_image(ico_image_t * ico)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
fill_image_icon(i_img * im,ico_image_t * ico)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
i_writeico_wiol(i_io_glue_t * ig,i_img * im)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
i_writeico_multi_wiol(i_io_glue_t * ig,i_img ** ims,int count)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
fill_image_cursor(i_img * im,ico_image_t * ico)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
i_writecur_wiol(i_io_glue_t * ig,i_img * im)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
i_writecur_multi_wiol(i_io_glue_t * ig,i_img ** ims,int count)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 
655