]> git.lizzy.rs Git - plan9front.git/blob - sys/src/cmd/jpg/ico.c
ico: fix interpretation of 0 widths/heights
[plan9front.git] / sys / src / cmd / jpg / ico.c
1 #include <u.h>
2 #include <libc.h>
3 #include <bio.h>
4 #include <draw.h>
5 #include <memdraw.h>
6 #include <event.h>
7 #include <cursor.h>
8
9 #include "imagefile.h"
10
11 typedef struct Icon Icon;
12 struct Icon
13 {
14         Icon    *next;
15
16         ushort  w;              /* icon width */
17         ushort  h;              /* icon height */
18         ushort  ncolor;         /* number of colors */
19         ushort  nplane;         /* number of bit planes */
20         ushort  bits;           /* bits per pixel */
21         ulong   len;            /* length of data */
22         ulong   offset;         /* file offset to data */
23
24         Memimage        *img;
25         Memimage        *mask;
26
27         Rectangle r;            /* relative */
28         Rectangle sr;           /* abs */
29 };
30
31 typedef struct Header Header;
32 struct Header
33 {
34         uint    n;
35         Icon    *first;
36         Icon    *last;
37 };
38
39 int debug;
40 int cflag;
41 Mouse mouse;
42 Header h;
43 Image *background;
44
45 ushort
46 gets(uchar *p)
47 {
48         return p[0] | (p[1]<<8);
49 }
50
51 ulong
52 getl(uchar *p)
53 {
54         return p[0] | (p[1]<<8) | (p[2]<<16) | (p[3]<<24);
55 }
56
57 int
58 Bgetheader(Biobuf *b, Header *h)
59 {
60         uchar buf[40];
61         Icon *icon;
62         int i;
63
64         memset(h, 0, sizeof(*h));
65         if(Bread(b, buf, 6) != 6)
66                 goto eof;
67         if(gets(&buf[0]) != 0)
68                 goto header;
69         if(gets(&buf[2]) != 1)
70                 goto header;
71         h->n = gets(&buf[4]);
72         for(i = 0; i < h->n; i++){
73                 icon = mallocz(sizeof(*icon), 1);
74                 if(icon == nil)
75                         sysfatal("malloc: %r");
76                 if(Bread(b, buf, 16) != 16)
77                         goto eof;
78                 icon->w = buf[0] == 0 ? 256 : buf[0];
79                 icon->h = buf[1] == 0 ? 256 : buf[1];
80                 icon->ncolor = buf[2] == 0 ? 256 : buf[2];
81                 icon->nplane = gets(&buf[4]);
82                 icon->bits = gets(&buf[6]);
83                 icon->len = getl(&buf[8]);
84                 icon->offset = getl(&buf[12]);
85                 if(i == 0)
86                         h->first = icon;
87                 else
88                         h->last->next = icon;
89                 h->last = icon;
90         }
91         return 0;
92
93 eof:
94         werrstr("unexpected EOF");
95         return -1;
96 header:
97         werrstr("unknown header format");
98         return -1;
99 }
100
101 uchar*
102 transcmap(Icon *icon, int ncolor, uchar *map)
103 {
104         uchar *m, *p;
105         int i;
106
107         p = m = mallocz(sizeof(int)*(1<<icon->bits), 1);
108         if(m == nil)
109                 sysfatal("malloc: %r");
110         for(i = 0; i < ncolor; i++){
111                 *p++ = rgb2cmap(map[2], map[1], map[0]);
112                 map += 4;
113         }
114         return m;
115 }
116
117 Memimage*
118 xor2img(Icon *icon, long chan, uchar *xor, uchar *map)
119 {
120         uchar *data;
121         Memimage *img;
122         int inxlen;
123         uchar *from, *to;
124         int s, byte, mask;
125         int x, y;
126
127         inxlen = 4*((icon->bits*icon->w+31)/32);
128         img = allocmemimage(Rect(0,0,icon->w,icon->h), chan);
129         if(img == nil)
130                 return nil;
131
132         if(chan != CMAP8){
133                 from = xor + icon->h*inxlen;
134                 for(y = 0; y < icon->h; y++){
135                         from -= inxlen;
136                         loadmemimage(img, Rect(0,y,icon->w,y+1), from, inxlen);
137                 }
138                 return img;
139         }
140
141         to = data = malloc(icon->w*icon->h);
142         if(data == nil){
143                 freememimage(img);
144                 return nil;
145         }
146
147         /* rotate around the y axis, go to 8 bits, and convert color */
148         mask = (1<<icon->bits)-1;
149         for(y = 0; y < icon->h; y++){
150                 s = -1;
151                 byte = 0;
152                 from = xor + (icon->h - 1 - y)*inxlen;
153                 for(x = 0; x < icon->w; x++){
154                         if(s < 0){
155                                 byte = *from++;
156                                 s = 8-icon->bits;
157                         }
158                         *to++ = map[(byte>>s) & mask];
159                         s -= icon->bits;
160                 }
161         }
162         /* stick in an image */
163         loadmemimage(img, Rect(0,0,icon->w,icon->h), data, icon->h*icon->w);
164         free(data);
165         return img;
166 }
167
168 Memimage*
169 and2img(Icon *icon, uchar *and)
170 {
171         uchar *data;
172         Memimage *img;
173         int inxlen;
174         int outxlen;
175         uchar *from, *to;
176         int x, y;
177
178         inxlen = 4*((icon->w+31)/32);
179         to = data = malloc(inxlen*icon->h);
180         if(data == nil)
181                 return nil;
182
183         /* rotate around the y axis and invert bits */
184         outxlen = (icon->w+7)/8;
185         for(y = 0; y < icon->h; y++){
186                 from = and + (icon->h - 1 - y)*inxlen;
187                 for(x = 0; x < outxlen; x++)
188                         *to++ = ~(*from++);
189         }
190
191         /* stick in an image */
192         if(img = allocmemimage(Rect(0,0,icon->w,icon->h), GREY1))
193                 loadmemimage(img, Rect(0,0,icon->w,icon->h), data, icon->h*outxlen);
194
195         free(data);
196         return img;
197 }
198
199 int
200 Bgeticon(Biobuf *b, Icon *icon)
201 {
202         uchar *end;
203         uchar *xor;
204         uchar *and;
205         uchar *cm;
206         uchar *buf;
207         uchar *map2map;
208         Memimage *img;
209         uchar magic[4];
210         int ncolor;
211         long chan;
212
213         Bseek(b, icon->offset, 0);
214         if(Bread(b, magic, 4) != 4){
215                 werrstr("unexpected EOF");
216                 return -1;
217         }
218         if(magic[0] == 137 && memcmp(magic+1, "PNG", 3) == 0){
219                 Rawimage **png;
220
221                 Bseek(b, -4, 1);
222                 png = Breadpng(b, CRGB);
223                 if(png == nil || png[0] == nil)
224                         return -1;
225                 switch(png[0]->chandesc){
226                 case CY:
227                         chan = GREY8;
228                         break;
229                 case CYA16:
230                         chan = CHAN2(CGrey, 8, CAlpha, 8);
231                         break;
232                 case CRGB24:
233                         chan = RGB24;
234                         break;
235                 case CRGBA32:
236                         chan = RGBA32;
237                         break;
238                 default:
239                         werrstr("bad icon png channel descriptor");
240                         return -1;
241                 }
242                 icon->mask = nil;
243                 icon->img = allocmemimage(png[0]->r, chan);
244                 loadmemimage(icon->img, icon->img->r, png[0]->chans[0], png[0]->chanlen);
245                 return 0;
246         }
247
248         if(getl(magic) != 40){
249                 werrstr("bad icon bmp header");
250                 return -1;
251         }
252         if(icon->len < 40){
253                 werrstr("bad icon bmp header length");
254                 return -1;
255         }
256         buf = malloc(icon->len);
257         if(buf == nil)
258                 return -1;
259         memmove(buf, magic, 4);
260         if(Bread(b, buf+4, icon->len-4) != icon->len-4){
261                 werrstr("unexpected EOF");
262                 return -1;
263         }
264
265         /* this header's info takes precedence over previous one */
266         ncolor = 0;
267         icon->w = getl(buf+4);
268         icon->h = getl(buf+8)>>1;
269         icon->nplane = gets(buf+12);
270         icon->bits = gets(buf+14);
271
272         if(icon->w == 0)
273                 icon->w = 256;
274         if(icon->h == 0)
275                 icon->h = 256;
276
277         /* limit what we handle */
278         switch(icon->bits){
279         case 1:
280         case 2:
281         case 4:
282         case 8:
283                 ncolor = icon->ncolor;
284                 if(ncolor > (1<<icon->bits))
285                         ncolor = 1<<icon->bits;
286                 chan = CMAP8;
287                 break;
288         case 15:
289         case 16:
290                 chan = RGB16;
291                 break;
292         case 24:
293                 chan = RGB24;
294                 break;
295         case 32:
296                 chan = ARGB32;
297                 break;
298         default:
299                 werrstr("don't support %d bit pixels", icon->bits);
300                 return -1;
301         }
302         if(icon->nplane != 1){
303                 werrstr("don't support %d planes", icon->nplane);
304                 return -1;
305         }
306
307         xor = cm = buf + 40;
308         if(chan == CMAP8)
309                 xor += 4*ncolor;
310         end = xor + icon->h*4*((icon->bits*icon->w+31)/32);
311         if(end < buf || end > buf+icon->len){
312                 werrstr("bad icon length %zux != %lux", end - buf, icon->len);
313                 return -1;
314         }
315
316         /* translate the color map to a plan 9 one */
317         map2map = nil;
318         if(chan == CMAP8)
319                 map2map = transcmap(icon, ncolor, cm);
320
321         /* convert the images */
322         icon->img = xor2img(icon, chan, xor, map2map);
323         if(icon->img == nil){
324                 werrstr("xor2img: %r");
325                 return -1;
326         }
327         icon->mask = nil;
328
329         /* check for and mask */
330         and = end;
331         end += icon->h*4*((icon->w+31)/32);
332         if(end <= buf+icon->len)
333                 icon->mask = and2img(icon, and);
334
335         /* so that we save an image with a white background */
336         if(img = allocmemimage(icon->img->r, icon->img->chan)){
337                 memfillcolor(img, DWhite);
338                 memimagedraw(img, icon->img->r, icon->img, ZP, icon->mask, ZP, SoverD);
339                 freememimage(icon->img);
340                 icon->img = img;
341         }
342
343         free(buf);
344         free(map2map);
345         return 0;
346 }
347
348 void
349 usage(void)
350 {
351         fprint(2, "usage: %s [ -c ] [ file ]\n", argv0);
352         exits("usage");
353 }
354
355 enum
356 {
357         Mimage,
358         Mmask,
359         Mexit,
360
361         Up= 1,
362         Down= 0,
363 };
364
365 char    *menu3str[] = {
366         [Mimage]        "write image",
367         [Mmask]         "write mask",
368         [Mexit]         "exit",
369         0,
370 };
371
372 Menu    menu3 = {
373         menu3str
374 };
375
376 Cursor sight = {
377         {-7, -7},
378         {0x1F, 0xF8, 0x3F, 0xFC, 0x7F, 0xFE, 0xFB, 0xDF,
379          0xF3, 0xCF, 0xE3, 0xC7, 0xFF, 0xFF, 0xFF, 0xFF,
380          0xFF, 0xFF, 0xFF, 0xFF, 0xE3, 0xC7, 0xF3, 0xCF,
381          0x7B, 0xDF, 0x7F, 0xFE, 0x3F, 0xFC, 0x1F, 0xF8,},
382         {0x00, 0x00, 0x0F, 0xF0, 0x31, 0x8C, 0x21, 0x84,
383          0x41, 0x82, 0x41, 0x82, 0x41, 0x82, 0x7F, 0xFE,
384          0x7F, 0xFE, 0x41, 0x82, 0x41, 0x82, 0x41, 0x82,
385          0x21, 0x84, 0x31, 0x8C, 0x0F, 0xF0, 0x00, 0x00,}
386 };
387
388 void
389 buttons(int ud)
390 {
391         while((mouse.buttons==0) != ud)
392                 mouse = emouse();
393 }
394
395 void
396 mesg(char *fmt, ...)
397 {
398         va_list arg;
399         char buf[1024];
400         static char obuf[1024];
401
402         va_start(arg, fmt);
403         vseprint(buf, buf+sizeof(buf), fmt, arg);
404         va_end(arg);
405         string(screen, screen->r.min, background, ZP, font, obuf);
406         string(screen, screen->r.min, display->white, ZP, font, buf);
407         strcpy(obuf, buf);
408 }
409
410 void
411 doimage(Icon *icon)
412 {
413         int rv;
414         char file[256];
415         int fd;
416
417         rv = -1;
418         snprint(file, sizeof(file), "%dx%d.img", icon->w, icon->h);
419         fd = create(file, OWRITE, 0664);
420         if(fd >= 0){
421                 rv = writememimage(fd, icon->img);
422                 close(fd);
423         }
424         if(rv < 0)
425                 mesg("error writing %s: %r", file);
426         else
427                 mesg("created %s", file);
428 }
429
430 void
431 domask(Icon *icon)
432 {
433         int rv;
434         char file[64];
435         int fd;
436
437         if(icon->mask == nil)
438                 return;
439
440         rv = -1;
441         snprint(file, sizeof(file), "%dx%d.mask", icon->w, icon->h);
442         fd = create(file, OWRITE, 0664);
443         if(fd >= 0){
444                 rv = writememimage(fd, icon->mask);
445                 close(fd);
446         }
447         if(rv < 0)
448                 mesg("error writing %s: %r", file);
449         else
450                 mesg("created %s", file);
451 }
452
453 void
454 apply(void (*f)(Icon*))
455 {
456         Icon *icon;
457
458         esetcursor(&sight);
459         buttons(Down);
460         if(mouse.buttons == 4)
461                 for(icon = h.first; icon; icon = icon->next)
462                         if(ptinrect(mouse.xy, icon->sr)){
463                                 buttons(Up);
464                                 f(icon);
465                                 break;
466                         }
467         buttons(Up);
468         esetcursor(0);
469 }
470
471 void
472 menu(void)
473 {
474         int sel;
475
476         sel = emenuhit(3, &mouse, &menu3);
477         switch(sel){
478         case Mimage:
479                 apply(doimage);
480                 break;
481         case Mmask:
482                 apply(domask);
483                 break;
484         case Mexit:
485                 exits(0);
486                 break;
487         }
488 }
489
490 void
491 mousemoved(void)
492 {
493         Icon *icon;
494
495         for(icon = h.first; icon; icon = icon->next)
496                 if(ptinrect(mouse.xy, icon->sr)){
497                         mesg("%dx%d", icon->w, icon->h);
498                         return;
499                 }
500         mesg("");
501 }
502
503 enum
504 {
505         BORDER= 1,
506 };
507
508 Image*
509 screenimage(Memimage *m)
510 {
511         Rectangle r;
512         Image *i;
513
514         if(i = allocimage(display, m->r, m->chan, 0, DNofill)){
515                 r = m->r;
516                 while(r.min.y < m->r.max.y){
517                         r.max.y = r.min.y+1;
518                         loadimage(i, r, byteaddr(m, r.min), bytesperline(r, m->depth));
519                         r.min.y++;
520                 }
521         }
522         return i;
523 }
524
525 void
526 eresized(int new)
527 {
528         Icon *icon;
529         Image *i;
530         Rectangle r;
531
532         if(new && getwindow(display, Refnone) < 0)
533                 sysfatal("can't reattach to window");
534         draw(screen, screen->clipr, background, nil, ZP);
535         r.max.x = screen->r.min.x;
536         r.min.y = screen->r.min.y + font->height + 2*BORDER;
537         for(icon = h.first; icon != nil; icon = icon->next){
538                 r.min.x = r.max.x + BORDER;
539                 r.max.x = r.min.x + Dx(icon->img->r);
540                 r.max.y = r.min.y + Dy(icon->img->r);
541                 if(i = screenimage(icon->img)){
542                         draw(screen, r, i, nil, ZP);
543                         freeimage(i);
544                 }
545                 border(screen, r, -BORDER, display->black, ZP);
546                 icon->sr = r;
547         }
548         flushimage(display, 1);
549 }
550
551 void
552 main(int argc, char **argv)
553 {
554         Biobuf in;
555         Icon *icon;
556         int num, fd;
557         Rectangle r;
558         Event e;
559
560         ARGBEGIN{
561         case 'd':
562                 debug = 1;
563                 break;
564         case 'c':
565                 cflag = 1;
566                 break;
567         default:
568                 usage();
569         }ARGEND;
570
571         fd = -1;
572         switch(argc){
573         case 0:
574                 fd = 0;
575                 break;
576         case 1:
577                 fd = open(argv[0], OREAD);
578                 if(fd < 0)
579                         sysfatal("opening: %r");
580                 break;
581         default:
582                 usage();
583                 break;
584         }
585
586         memimageinit();
587         Binit(&in, fd, OREAD);
588
589         if(Bgetheader(&in, &h) < 0)
590                 sysfatal("reading header: %r");
591
592         num = 0;
593         r.min = Pt(4, 4);
594         for(icon = h.first; icon != nil; icon = icon->next){
595                 if(Bgeticon(&in, icon) < 0){
596                         fprint(2, "%s: read fail: %r\n", argv0);
597                         continue;
598                 }
599                 if(debug)
600                         fprint(2, "w %ud h %ud ncolor %ud bits %ud len %lud offset %lud\n",
601                            icon->w, icon->h, icon->ncolor, icon->bits, icon->len, icon->offset);
602                 r.max = addpt(r.min, Pt(icon->w, icon->h));
603                 icon->r = r;
604                 if(cflag){
605                         writememimage(1, icon->img);
606                         exits(0);
607                 }
608                 r.min.x += r.max.x;
609                 num++;
610         }
611
612         if(num == 0 || cflag)
613                 sysfatal("no images");
614
615         initdraw(nil, nil, "ico");
616         background = allocimage(display, Rect(0, 0, 1, 1), screen->chan, 1, 0x808080FF);
617         eresized(0);
618         einit(Emouse|Ekeyboard);
619         for(;;)
620                 switch(event(&e)){
621                 case Ekeyboard:
622                         break;
623                 case Emouse:
624                         mouse = e.mouse;
625                         if(mouse.buttons & 4)
626                                 menu();
627                         else
628                                 mousemoved();
629                         break;
630                 }
631         /* not reached */
632 }