/*
 * alphaplot.c
 *
 * Four different plot routines capable of dealing with the alpha channel.
 * #included by pngro.c four times with appropriate #defines.
 */

#ifdef DITHERED
#ifdef SCALED
void do_alpha_plot_scaled_dithered(Bitmap *bmp, osspriteop_header *dst, int x, int y, os_factors *fac)
#else
void do_alpha_plot_dithered(Bitmap *bmp, osspriteop_header *dst, int x, int y)
#endif
#else
#ifdef SCALED
void do_alpha_plot_scaled(Bitmap *bmp, osspriteop_header *dst, int x, int y, os_factors *fac)
#else
void do_alpha_plot(Bitmap * restrict bmp, osspriteop_header * restrict dst, int x, int y)
#endif
#endif
{
    osspriteop_header *src;
    int sw, sh, dw, dh, dd;
    int x0, x1, y0, y1;
    #ifdef DITHERED
    int nr, ng, nb;
    int xdir = 1;
    signed char *errorbuffer = NULL;
    signed char *thise, *nexte;
    #endif
    #ifdef SCALED
    int sx, sy;
    char *rp;
    #endif
    int i,j;
    char *sp, *dp;
    unsigned c;
    const char *cttable;

    /* Anchor dereferenced */
    src=(osspriteop_header *) ((char *) bmp->sprite + bmp->sprite->first);
    dd=bitmap_depth;
    dw=((dst->width+1)<<2)>>(dd-3);
    dh=dst->height+1;
    sw=src->width+1;
    sh=src->height+1;
    #ifdef SCALED
    sw=sw*fac->xmul/fac->xdiv;
    sh=sh*fac->ymul/fac->ydiv;
    #endif

    if (x+sw<=0 || x>=dw || y+sh<=0 || y>=dh)
        return;

    if (x<0) x0=-x; else x0=0;
    if (y<0) y0=-y; else y0=0;
    if (x+sw>dw) x1=dw-x; else x1=sw;
    if (y+sh>dh) y1=dh-y; else y1=sh;

    if (dd==3)
    {
        /* Flex anchor disturbed */
        cttable = get_inverse_table();
        #ifdef DITHERED
        /* Allow one extra pixel each end sloppiness. */
        errorbuffer = calloc((x1-x0+2)*2, 3);
        if (errorbuffer)
        {
            thise = errorbuffer + 3;
            nexte = errorbuffer + (x1-x0+1)*6;
        }
        #endif
    }

    #ifdef SCALED
    sy=(y0*fac->ydiv+(fac->ydiv>>1));
    #endif

    /* Anchor dereferenced */
    src=(osspriteop_header *) ((char *) bmp->sprite + bmp->sprite->first);

    for (j=y0; j<y1; j++)
    {
        dp = (char *)dst+dst->image+(y+j)*(dst->width+1)*4+((x+x0)<<(dd-3));
        #ifdef SCALED
        sx = (x0*fac->xdiv+(fac->xdiv>>1));
        rp = (char *)src+src->image+(sy/fac->ymul)*(src->width+1)*4;
        sy += fac->ydiv;
        #else
        sp = (char *)src+src->image+j*(src->width+1)*4+x0*4;
        #endif

        #ifdef DITHERED
        nr=ng=nb=0;

        for (i=x0; i!=x1; i+=xdir)
        #else
        for (i=x0; i<x1; i++)
        #endif
        {
            int dr,dg,db,sr,sg,sb,sa;
            #ifndef DITHERED
            switch (dd)
            {
                case 3:
            #endif
                    #ifdef DITHERED
                    c=*dp; dp+=xdir;
                    #else
                    c=*dp++;
                    #endif
                    dr=pal256[c][0];
                    dg=pal256[c][1];
                    db=pal256[c][2];
            #ifndef DITHERED
                    break;
                case 4:
                    c=*(unsigned *)dp;
                    dp+=2;
                    dr = (c & 0x001F) << 3; dr |= dr >> 5;
                    dg = (c & 0x03E0) >> 2; dg |= dg >> 5;
                    db = (c & 0x7C00) >> 7; db |= db >> 5;
                    break;
                case 5:
                    dr=*dp++;
                    dg=*dp++;
                    db=*dp; dp+=2;
                    break;
            }
            #endif
            #ifdef SCALED
            sp = rp+(sx/fac->xmul)*4;
            #ifdef DITHERED
            sx+=fac->xdiv*xdir;
            #else
            sx+=fac->xdiv;
            #endif
            #endif
            #ifdef DITHERED
            sr=sp[0];
            sg=sp[1];
            sb=sp[2];
            sa=sp[3];
            sp+=4*xdir;
            #else
            sr=*sp++;
            sg=*sp++;
            sb=*sp++;
            sa=*sp++;
            #endif
            if (sa==255)
            {
                dr=sr;
                dg=sg;
                db=sb;
            }
            else if (sa==0)
            {
                #ifndef DITHERED
                continue;
                #endif
            }
            else
            {
                sa = sa+(sa>>7);
                dr=screen_from_1[(screen_to_1[sr]*sa+screen_to_1[dr]*(256-sa))>>8];
                dg=screen_from_1[(screen_to_1[sg]*sa+screen_to_1[dg]*(256-sa))>>8];
                db=screen_from_1[(screen_to_1[sb]*sa+screen_to_1[db]*(256-sa))>>8];
            }
            #ifndef DITHERED
            switch (dd)
            {
                case 3:
            #endif
                    #ifdef DITHERED
                    if (errorbuffer)
                    {
                        int er, eg, eb;
                        int r, g, b;
                        /*
                         * There's got to be an easier way of doing this Floyd-Steinberg
                         * stuff. I don't know why I bother sometimes.
                         */
                        dr += *thise++ + nr; if (dr<0) dr=0; else if (dr>255) dr=255;
                        dg += *thise++ + ng; if (dg<0) dg=0; else if (dg>255) dg=255;
                        db += *thise++ + nb; if (db<0) db=0; else if (db>255) db=255;
                        c = cttable[INDEX15(dr, dg, db)];
                        //if (xdir == -1)
                          dp[-xdir] = c;
                        er = dr - pal256[c][0];
                        eg = dg - pal256[c][1];
                        eb = db - pal256[c][2];
                        nr = 7*er/16;
                        ng = 7*eg/16;
                        nb = 7*eb/16;
                        r = 3*er/16; if (r<-128) r=-128; else if (r>127) r=127;
                        g = 3*eg/16; if (g<-128) g=-128; else if (g>127) g=127;
                        b = 3*eb/16; if (b<-128) b=-128; else if (b>127) b=127;
                        nexte[-3] = r;
                        nexte[-2] = g;
                        nexte[-1] = b;
                        r = nexte[0] + 5*er/16; if (r<-128) r=-128; else if (r>127) r=127;
                        g = nexte[1] + 5*eg/16; if (g<-128) g=-128; else if (g>127) g=127;
                        b = nexte[2] + 5*eb/16; if (b<-128) b=-128; else if (b>127) b=127;
                        nexte[0] = r;
                        nexte[1] = g;
                        nexte[2] = b;
                        r = nexte[3] + er/16; if (r<-128) r=-128; else if (r>127) r=127;
                        g = nexte[4] + eg/16; if (g<-128) g=-128; else if (g>127) g=127;
                        b = nexte[5] + eb/16; if (b<-128) b=-128; else if (b>127) b=127;
                        nexte[3] = r;
                        nexte[4] = g;
                        nexte[5] = b;
                        nexte -= 3;
                    }
                    else
                    #endif
                    {
                        c = INDEX15(dr, dg, db);
                        dp[-1] = cttable[c];
                    }
            #ifndef DITHERED
                    break;
                case 4:
                    c = INDEX15(dr, dg, db);
                    ((unsigned short *)dp)[-1] = c;
                    break;
                case 5:
                    dp[-4]=dr;
                    dp[-3]=dg;
                    dp[-2]=db;
                    break;
            }
            #endif
        }

        #ifdef DITHERED
        if (errorbuffer)
        {
            int tmp2 = x1;
            x1 = x0 - xdir;
            x0 = tmp2 - xdir;
            if (xdir == +1)
            {
                xdir = -1;
                thise = errorbuffer + (x0-x1+3)*3;
                nexte = errorbuffer + (x0-x1)*3;
            }
            else
            {
                xdir = +1;
                thise = errorbuffer + 3;
                nexte = errorbuffer + (x1-x0+1)*6;
            }
            /* nexte gets cleared automatically, except the first pixel */
            nexte[0]=nexte[1]=nexte[2]=0;
        }
        #endif
    }

    /* Anchor dereference must stay valid to here */

    #ifdef DITHERED
    free(errorbuffer);
    #endif
}
