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