Subversion Repositories planix.SVN

Rev

Blame | Last modification | View Log | RSS feed

#include <u.h>
#include <libc.h>
#include <bio.h>
#include <draw.h>

enum
{
        FileHdrLen=     6,
        IconDescrLen=   16,
        IconHdrLen=     40,
};

typedef struct Icon Icon;
struct Icon
{
        Icon    *next;
        char    *file;

        uchar   w;              /* icon width */
        uchar   h;              /* icon height */
        ushort  ncolor;         /* number of colors */
        ushort  nplane;         /* number of bit planes */
        ushort  bits;           /* bits per pixel */
        ulong   len;            /* length of data */
        ulong   offset;         /* file offset to data */
        uchar   map[4*256];     /* color map */

        Image   *img;

        uchar   *xor;
        int     xorlen;
        uchar   *and;
        int     andlen;
};

typedef struct Header Header;
struct Header
{
        uint    n;
        Icon    *first;
        Icon    *last;
};

void
Bputs(Biobuf *b, ushort x)
{
        Bputc(b, x&0xff);
        Bputc(b, x>>8);
}

void
Bputl(Biobuf *b, ulong x)
{
        Bputs(b, x&0xffff);
        Bputs(b, x>>16);
}

Header h;

void*   emalloc(int);
void    mk8bit(Icon*, int);
void    mkxorand(Icon*, int);
void    readicon(char*);

void
main(int argc, char **argv)
{
        int i;
        Biobuf *b, out;
        Icon *icon;
        ulong offset;
        ulong len;

        ARGBEGIN{
        }ARGEND;

        /* read in all the images */
        display = initdisplay(nil, nil, nil);
        if(argc < 1){
                readicon("/fd/0");
        } else {
                for(i = 0; i < argc; i++)
                        readicon(argv[i]);
        }

        /* create the .ico file */
        b = &out;
        Binit(b, 1, OWRITE);

        /* offset to first icon */
        offset = FileHdrLen + h.n*IconDescrLen;

        /* file header is */
        Bputs(b, 0);
        Bputs(b, 1);
        Bputs(b, h.n);

        /* icon description */
        for(icon = h.first; icon != nil; icon = icon->next){
                Bputc(b, icon->w);
                Bputc(b, icon->h);
                Bputc(b, icon->ncolor);
                Bputc(b, 0);
                Bputs(b, icon->nplane);
                Bputs(b, icon->bits);
                len = IconHdrLen + icon->ncolor*4 + icon->xorlen + icon->andlen;
                Bputl(b, len);
                Bputl(b, offset);
                offset += len;
        }

        /* icons */
        for(icon = h.first; icon != nil; icon = icon->next){
                /* icon header (BMP like) */
                Bputl(b, IconHdrLen);
                Bputl(b, icon->w);
                Bputl(b, 2*icon->h);
                Bputs(b, icon->nplane);
                Bputs(b, icon->bits);
                Bputl(b, 0);    /* compression info */
                Bputl(b, 0);
                Bputl(b, 0);
                Bputl(b, 0);
                Bputl(b, 0);
                Bputl(b, 0);

                /* color map */
                if(Bwrite(b, icon->map, 4*icon->ncolor) < 0)
                        sysfatal("writing color map: %r");

                /* xor bits */
                if(Bwrite(b, icon->xor, icon->xorlen) < 0)
                        sysfatal("writing xor bits: %r");

                /* and bits */
                if(Bwrite(b, icon->and, icon->andlen) < 0)
                        sysfatal("writing and bits: %r");
        }

        Bterm(b);
        exits(0);
}

void
readicon(char *file)
{
        int fd;
        Icon *icon;

        fd = open(file, OREAD);
        if(fd < 0)
                sysfatal("opening %s: %r", file);
        icon = emalloc(sizeof(Icon));
        icon->img = readimage(display, fd, 0);
        if(icon->img == nil)
                sysfatal("reading image %s: %r", file);
        close(fd);

        if(h.first)
                h.last->next = icon;
        else
                h.first = icon;
        h.last = icon;
        h.n++;

        icon->h = Dy(icon->img->r);
        icon->w = Dx(icon->img->r);
        icon->bits = 1<<icon->img->depth;
        icon->nplane = 1;

        /* convert to 8 bits per pixel */
        switch(icon->img->chan){
        case GREY8:
        case CMAP8:
                break;
        case GREY1:
        case GREY2:
        case GREY4:
                mk8bit(icon, 1);
                break;
        default:
                mk8bit(icon, 0);
                break;
        }
        icon->bits = 8;
        icon->file = file;

        /* create xor/and masks, minimizing bits per pixel */
        mkxorand(icon, icon->img->chan == GREY8);
}

void*
emalloc(int len)
{
        void *x;

        x = mallocz(len, 1);
        if(x == nil)
                sysfatal("memory: %r");
        return x;
}

/* convert to 8 bit */
void
mk8bit(Icon *icon, int grey)
{
        Image *img;

        img = allocimage(display, icon->img->r, grey ? GREY8 : CMAP8, 0, DNofill);
        if(img == nil)
                sysfatal("can't allocimage: %r");
        draw(img, img->r, icon->img, nil, ZP);
        freeimage(icon->img);
        icon->img = img;
}

/* make xor and and mask */
void
mkxorand(Icon *icon, int grey)
{
        int i, x, y, s, sa;
        uchar xx[256];
        uchar *data, *p, *e;
        int ndata;
        uchar *mp;
        int ncolor;
        ulong color;
        int bits;
        uchar andbyte, xorbyte;
        uchar *ato, *xto;
        int xorrl, andrl;

        ndata = icon->h * icon->w;
        data = emalloc(ndata);
        if(unloadimage(icon->img, icon->img->r, data, ndata) < 0)
                sysfatal("can't unload %s: %r", icon->file);
        e = data + ndata;

        /* find colors used */
        memset(xx, 0, sizeof xx);
        for(p = data; p < e; p++)
                xx[*p]++;

        /* count the colors and create a mapping from plan 9 */
        mp = icon->map;
        ncolor = 0;
        for(i = 0; i < 256; i++){
                if(xx[i] == 0)
                        continue;
                if(grey){
                        *mp++ = i;
                        *mp++ = i;
                        *mp++ = i;
                        *mp++ = 0;
                } else {
                        color = cmap2rgb(i);
                        *mp++ = color;
                        *mp++ = color>>8;
                        *mp++ = color>>16;
                        *mp++ = 0;
                }
                xx[i] = ncolor;
                ncolor++;
        }

        /* get minimum number of pixels per bit (with a color map) */
        if(ncolor <= 2){
                ncolor = 2;
                bits = 1;
        } else if(ncolor <= 4){
                ncolor = 4;
                bits = 2;
        } else if(ncolor <= 16){
                ncolor = 16;
                bits = 4;
        } else {
                ncolor = 256;
                bits = 8;
        }
        icon->bits = bits;
        icon->ncolor = ncolor;

        /* the xor mask rows are justified to a 32 bit boundary */
        /* the and mask is 1 bit grey */
        xorrl = 4*((bits*icon->w + 31)/32);
        andrl = 4*((icon->w + 31)/32);
        icon->xor = emalloc(xorrl * icon->h);
        icon->and = emalloc(andrl * icon->h);
        icon->xorlen = xorrl*icon->h;
        icon->andlen = andrl*icon->h;

        /* make both masks.  they're upside down relative to plan9 ones */
        p = data;
        for(y = 0; y < icon->h; y++){
                andbyte = 0;
                xorbyte = 0;
                sa = s = 0;
                xto = icon->xor + (icon->h-1-y)*xorrl;
                ato = icon->and + (icon->h-1-y)*andrl;
                for(x = 0; x < icon->w; x++){
                        xorbyte <<= bits;
                        xorbyte |= xx[*p];
                        s += bits;
                        if(s == 8){
                                *xto++ = xorbyte;
                                xorbyte = 0;
                                s = 0;
                        }
                        andbyte <<= 1;
                        if(*p == 0xff)
                                andbyte |= 1;
                        sa++;
                        if(sa == 0){
                                *ato++ = andbyte;
                                sa = 0;
                                andbyte = 0;
                        }
                        p++;
                }
        }
        free(data);
}