he unpack code for ICO/CUR file handling could extend 32-bit unsigned values to 64...
[imager.git] / ICO / imicon.c
CommitLineData
b00687b2
TC
1#include "imext.h"
2#include "imicon.h"
3#include "msicon.h"
2b405c9e 4#include <string.h>
b00687b2
TC
5
6static void
7ico_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
14static
15i_img *
f17a0a60 16read_one_icon(ico_reader_t *file, int index, int masked, int alpha_masked) {
b00687b2
TC
17 ico_image_t *image;
18 int error;
19 i_img *result;
20
21 image = ico_image_read(file, index, &error);
22 if (!image) {
b00687b2 23 ico_push_error(error);
2b405c9e 24 i_push_error(0, "error reading ICO/CUR image");
b00687b2
TC
25 return NULL;
26 }
27
f17a0a60 28 if (masked && (image->bit_count != 32 || alpha_masked)) {
413dc198
TC
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
b00687b2
TC
44 if (image->direct) {
45 int x, y;
2b405c9e 46 i_color *line_buf;
b00687b2
TC
47 i_color *outp;
48 ico_color_t *inp = image->image_data;
c668f2cb 49 int channels = masked || image->bit_count == 32 ? 4 : 3;
b00687b2 50
c668f2cb 51 if (!i_int_check_image_file_limits(image->width, image->height, channels, 1)) {
2b405c9e
TC
52 ico_image_release(image);
53 return NULL;
54 }
55
c668f2cb
TC
56
57 result = i_img_8_new(image->width, image->height, channels);
b00687b2
TC
58 if (!result) {
59 ico_image_release(image);
b00687b2
TC
60 return NULL;
61 }
62
2b405c9e
TC
63 line_buf = mymalloc(image->width * sizeof(i_color));
64
b00687b2
TC
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;
413dc198 84 int channels = masked ? 4 : 3;
b00687b2 85
413dc198 86 if (!i_int_check_image_file_limits(image->width, image->height, channels, 1)) {
2b405c9e
TC
87 ico_image_release(image);
88 return NULL;
89 }
90
413dc198 91 result = i_img_pal_new(image->width, image->height, channels, 256);
b00687b2
TC
92 if (!result) {
93 ico_image_release(image);
b00687b2
TC
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;
413dc198 103 c.rgba.a = 255;
b00687b2
TC
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);
b00687b2
TC
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
2b405c9e
TC
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);
b00687b2
TC
147
148 myfree(mask);
149 }
413dc198
TC
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;
919e0000
TC
161 int first = 0;
162 int last = 0;
163
413dc198
TC
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 }
2b405c9e
TC
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 }
b00687b2
TC
191
192 ico_image_release(image);
193
194 return result;
195}
196
197i_img *
f17a0a60 198i_readico_single(io_glue *ig, int index, int masked, int alpha_masked) {
b00687b2
TC
199 ico_reader_t *file;
200 i_img *result;
201 int error;
202
2b405c9e
TC
203 i_clear_error();
204
b00687b2
TC
205 file = ico_reader_open(ig, &error);
206 if (!file) {
207 ico_push_error(error);
2b405c9e 208 i_push_error(0, "error opening ICO/CUR file");
b00687b2
TC
209 return NULL;
210 }
211
2b405c9e 212 /* the index is range checked by msicon.c - don't duplicate it here */
b00687b2 213
f17a0a60 214 result = read_one_icon(file, index, masked, alpha_masked);
b00687b2
TC
215 ico_reader_close(file);
216
217 return result;
218}
219
220i_img **
f17a0a60 221i_readico_multi(io_glue *ig, int *count, int masked, int alpha_masked) {
b00687b2
TC
222 ico_reader_t *file;
223 int index;
224 int error;
225 i_img **imgs;
226
2b405c9e
TC
227 i_clear_error();
228
b00687b2
TC
229 file = ico_reader_open(ig, &error);
230 if (!file) {
231 ico_push_error(error);
2b405c9e 232 i_push_error(0, "error opening ICO/CUR file");
b00687b2
TC
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) {
f17a0a60 240 i_img *im = read_one_icon(file, index, masked, alpha_masked);
2b405c9e 241 if (!im)
b00687b2
TC
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}
2b405c9e
TC
256
257static int
258validate_image(i_img *im) {
2fe1e4bc 259 if (im->xsize > 256 || im->ysize > 256) {
2b405c9e
TC
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
272static int
273translate_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
342static void
343derive_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
366static void
367fill_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
476static void
477unfill_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
485static void
486fill_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
491int
492i_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
519int
520i_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
561void
562fill_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
585int
586i_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
613int
614i_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