/**********************************************************************
 *
 * ibitmapm.c - basic bitmap operation interface
 *
 * there are three bitmap operate for software bitmap interface:
 *
 * create  - create a new bitmap and return the struct address
 * release - free the bitmap 
 * blit    - copy the speciafied rectangle from one bitmap to another
 *
 * the history of this file:
 * Feb. 7 2003    skywind    created including create/release/blit
 *
 * aim to platform independence, all of the sources are writen in ansi 
 * c but you can replace the default blitter by ibitmap_funcset() to 
 * optimized the benchmark of ibitmap_blit in the speciafied platform
 *
 **********************************************************************/


#include "ibitmapm.h"

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>


/**********************************************************************
 * MEMORY ALLOCATOR FUNCTIONS
 **********************************************************************/
void*(*icmalloc)(size_t size) = malloc;
void (*icfree)(void *ptr) = free;


/**********************************************************************
 * BASIC BITMAP FUNCTIONS
 **********************************************************************/

/*
 * ibitmap_create - create bitmap and return the pointer to struct IBITMAP
 * width  - bitmap width
 * height - bitmap height
 * bpp    - bitmap color depth (8, 16, 24, 32)
 */
struct IBITMAP *ibitmap_create(int w, int h, int bpp)
{
    struct IBITMAP *bmp;
    unsigned char*line;
    int pixelbyte = (bpp + 7) >> 3;
    int i;

    /* check for invalid parametes */
    assert(bpp >= 8 && bpp <=32 && w > 0 && h > 0);

    /* allocate memory for the IBITMAP struct */
    bmp = (struct IBITMAP*)icmalloc(sizeof(struct IBITMAP));
    if (bmp == NULL) return NULL;

    /* setup bmp struct */
    bmp->w = w;
    bmp->h= h;
    bmp->bpp = bpp;
    bmp->pitch = (((long)w * pixelbyte + 3) >> 2) << 2;
    bmp->code = 0;

    /* allocate memory for pixel data */
    bmp->pixel = (char*)icmalloc(bmp->pitch * h);

    if (bmp->pixel == NULL) {
        icfree(bmp);
        return NULL;
    }

    bmp->mode = 0;
    bmp->mask = 0;
    bmp->extra = NULL;

    /* caculate the offset of each scanline */
    bmp->line = (void**)icmalloc(sizeof(void*) * h);
    if (bmp->line == NULL) {
        icfree(bmp->pixel);
        icfree(bmp);
        return NULL;
    }

    line = (unsigned char*)bmp->pixel;
    for (i = 0; i < h; i++, line += bmp->pitch) 
        bmp->line[i] = line;

    return bmp;
}

/*
 * ibitmap_release - release bitmap
 * bmp - pointer to the IBITMAP struct
 */
void ibitmap_release(struct IBITMAP *bmp)
{
    assert(bmp);
    if (bmp->pixel) icfree(bmp->pixel);
    if (bmp->line) icfree(bmp->line);
    if (bmp->extra) icfree(bmp->extra);
    icfree(bmp);
}

/*
 * ibitmap_clip - clip the rectangle from the src rectangle and 
 * caculate a new rectangle which is shared between dst and src rectangle
 * return non-zero for successful, return zero if there is no shared part
 */
int ibitmap_clip(int dw, int dh, int sw, int sh, int *x1, int *y1, 
             int *x2, int *y2, int *r2, int *b2)
{
    int rectw, recth, n;
    int x = *x1, y = *y1;
    int rectx = *x2;
    int recty = *y2;
    int rectr = *r2;
    int rectb = *b2;

    /* check edge first */
    if (rectx >= rectr || recty >= rectb) return -1;
    if (rectr <= 0 || rectb <= 0 || rectx >= sw || recty >= sh) return -2;

    /* modify src rectangle position */ 
    if (rectx < 0) x -= rectx, rectx = 0; 
    if (recty < 0) y -= recty, recty = 0; 

    /* modify src rectangle size */
    if (rectr > sw) rectr = sw;
    if (rectb > sh) rectb = sh;

    /* caculate new size for the src rectangle */
    rectw = rectr - rectx;
    recth = rectb - recty;

    /* check whether the rectangle is out of area */
    if (x >= dw || y >= dh || x + rectw <= 0 || y + recth <= 0) return -3;

    if (x < 0) rectw += x, rectx -= x, x = 0; 
    if (y < 0) recth += y, recty -= y, y = 0; 

    n = x + rectw - dw; 
    if (n > 0) rectr -= n;
    
    n = y + recth - dh; 
    if (n > 0) rectb -= n;
    
    /* save the result */
    *x2 = rectx;     
    *y2 = recty;
    *r2 = rectr;       
    *b2 = rectb;
    *x1 = x; 
    *y1 = y;

    return 0;
}


/**********************************************************************
 * BLITTER DEFINITION
 **********************************************************************/
int ibitmap_blitnc(char *dst, long pitch1, const char *src, int w, int h, 
    long pitch2, int pixsize, long linesize);
int ibitmap_blitmc(char *dst, long pitch1, const char *src, int w, int h, 
    long pitch2, int pixsize, long linesize, unsigned long mask);
int ibitmap_blitfc(char *dst, long pitch1, const char *src, int w, int h,
    long pitch2, int pixsize, long linesize, unsigned long mask, int flag);


/* set blitters interface to default blitters (c version) */
ibitmap_blitter_norm ibitmap_blitn = ibitmap_blitnc;
ibitmap_blitter_mask ibitmap_blitm = ibitmap_blitmc;
ibitmap_blitter_flip ibitmap_blitf = ibitmap_blitfc;



/**********************************************************************
 * BLITTER INTERFACE - CORE OPERATION
 **********************************************************************/

/*
 * ibitmap_blit - blit from source bitmap to dest bitmap
 * returns zero for successful, others for error    
 * dst       - dest bitmap to draw on
 * x, y      - target position of dest bitmap to draw on
 * src       - source bitmap 
 * sx, sy    - source rectangle position in source bitmap
 * w, h      - source rectangle width and height in source bitmap
 * mask      - coloekey of source bitmap, ignored if no IBLIT_MASK
 * mode      - blit flag bits (IBLIT_CLIP, IBLIT_MASK)
 */
int ibitmap_blit(struct IBITMAP *dst, int x, int y, struct IBITMAP *src, 
          int sx, int sy, int w, int h, unsigned long mask, int mode)
{
    long pitch1, pitch2;
    long linesize, d1, d2;
    int m = sx + w;
    int n = sy + h;
    int pixsize, r;
    char *pixel1;
    char *pixel2;

    /* check whether parametes is error */
    assert(src && dst);
    assert(src->bpp == dst->bpp);

    /* check whether need to clip rectangle */
    if (mode & IBLIT_CLIP) {
        r = ibitmap_clip(dst->w, dst->h, src->w, src->h, 
            &x, &y, &sx, &sy, &m, &n);
        if (r != 0) return 0;
    }

    /* get the size of one pixel */
    pixsize = (src->bpp + 7) >> 3;
    pitch1 = dst->pitch;
    pitch2 = src->pitch;
    w = m - sx;
    h = n - sy;

    /* choose linear offset */
    switch (pixsize) {
    case 1: 
        linesize = w; 
        d1 = x; 
        d2 = sx; 
        break;
    case 2: 
        linesize = (w << 1); 
        d1 = (x << 1); 
        d2 = (sx << 1); 
        break;
    case 3:
        linesize = (w << 1) + w;
        d1 = (x << 1) + x;
        d2 = (sx << 1) + sx;
        break;
    case 4:
        linesize = (w << 2);
        d1 = (x << 2);
        d2 = (sx << 2);
        break;
    default:
        linesize = w * pixsize;
        d1 = x * pixsize;
        d2 = sx * pixsize;
        break;
    }

    /* get the first scanlines of two bitmaps */
    pixel1 = (char*)dst->line[y] + d1;
    pixel2 = (char*)src->line[sy] + d2;

    /* detect whether blit with flip */
    if ((mode & (IBLIT_VFLIP | IBLIT_HFLIP)) != 0) {
        if (mode & IBLIT_VFLIP) {
            pixel2 = (char*)src->line[sy + h - 1] + d2;
        }
        r = ibitmap_blitf(pixel1, pitch1, pixel2, w, h, 
            pitch2, pixsize, linesize, mask, mode);
        if (r) ibitmap_blitfc(pixel1, pitch1, pixel2, w, h,
            pitch2, pixsize, linesize, mask, mode);
        return 0;
    }

    /* check to use the normal blitter or the transparent blitter */
    if ((mode & IBLIT_MASK) == 0) {
        r = ibitmap_blitn(pixel1, pitch1, pixel2, w, h, 
            pitch2, pixsize, linesize);
        if (r) ibitmap_blitnc(pixel1, pitch1, pixel2, w, h, 
            pitch2, pixsize, linesize);
    }   else {
        r = ibitmap_blitm(pixel1, pitch1, pixel2, w, h, 
            pitch2, pixsize, linesize, mask);
        if (r) ibitmap_blitmc(pixel1, pitch1, pixel2, w, h, 
            pitch2, pixsize, linesize, mask);
    }

    return 0;
}


/**********************************************************************
 * ibitmap_blitnc - default blitter for normal blit (no transparent)
 **********************************************************************/
int ibitmap_blitnc(char *dst, long pitch1, const char *src, int w, int h, 
    long pitch2, int pixelbyte, long linesize)
{
    char *ss = (char*)src;
    char *sd = (char*)dst;

    /* avoid never use warnings */
    if (pixelbyte == 1) linesize = w;

    /* copy each line */
    for (; h; h--) 
    {
        memcpy(sd, ss, linesize);
        sd += pitch1;
        ss += pitch2;
    }

    return 0;
}


/**********************************************************************
 * ibitmap_blitmc - default blitter for transparent blit
 **********************************************************************/
int ibitmap_blitmc(char *dst, long pitch1, const char *src, int w, int h, 
    long pitch2, int pixelbyte, long linesize, unsigned long mask)
{
    static unsigned long endian = 0x11223344;
    unsigned char *p1, *p2;
    unsigned long key24, c;
    unsigned char key08;
    unsigned short key16;
    long inc1, inc2, i;

    /* caculate the inc offset of two array */
    inc1 = pitch1 - linesize;
    inc2 = pitch2 - linesize;

    /* copying pixel for all color depth */
    switch (pixelbyte)
    {
    /* for 8 bits colors depth */
    case 1:
        key08 = (unsigned char)mask;
        for (; h; h--) {                 /* copy each scanline */
            for (i = w; i; i--) {        /* copy each pixel */
                if (*(unsigned char*)src - key08) *dst = *src;
                src++;
                dst++;
            }
            dst += inc1;
            src += inc2;
        }
        break;
    
    /* for 15/16 bits colors depth */
    case 2:        
        key16 = (unsigned short)mask;
        for (; h; h--) {                 /* copy each scanline */
            for (i = w; i; i--)       {  /* copy each pixel */
                if (*(unsigned short*)src - key16) 
                    *(unsigned short*)dst = *(unsigned short*)src;
                src += 2;
                dst += 2;
            }
            dst += inc1;
            src += inc2;
        }
        break;

    /* for 24 bits colors depth */
    case 3:
        if (((unsigned char*)&endian)[0] != 0x44) key24 = mask;
        else key24 = ((mask & 0xFFFF) << 8) | ((mask >> 16) & 0xFF);
        p1 = (unsigned char*)dst;
        p2 = (unsigned char*)src;

        for (; h; h--) {                 /* copy each scanline */
            for (i = w; i; i--)       {  /* copy each pixel */
                c = (((unsigned long)(*(unsigned short*)p2)) << 8) | p2[2];
                if (c - key24) {
                    p1[0] = p2[0];
                    p1[1] = p2[1];
                    p1[2] = p2[2];
                }
                p1 += 3;
                p2 += 3;
            }
            p1 += inc1;
            p2 += inc2;
        }
        break;

    /* for 32 bits colors depth */
    case 4:        
        for (; h; h--) {                 /* copy each scanline */
            for (i = w; i; i--)       {  /* copy each pixel */
                if (*(unsigned long*)src - mask) 
                    *(unsigned long*)dst = *(unsigned long*)src;
                src += 4;
                dst += 4;
            }
            dst += inc1;
            src += inc2;
        }
        break;
    }

    return 0;
}


/**********************************************************************
 * ibitmap_blitfc - default blitter for flip blit
 **********************************************************************/
int ibitmap_blitfc(char *dst, long pitch1, const char *src, int w, int h, 
    long pitch2, int pixsize, long linesize, unsigned long mask, int flag)
{
    static unsigned long endian = 0x11223344;
    unsigned char *p1, *p2;
    unsigned long key24, c;
    unsigned char key08;
    unsigned short key16;
    long inc1, inc2, i;

    /* flip vertical without mask */
    if ((flag & (IBLIT_MASK | IBLIT_HFLIP)) == 0) {
        for (; h; h--) {
            memcpy(dst, src, linesize);
            dst += pitch1;
            src -= pitch2;
        }
        return 0;
    }
    /* flip vertical with mask */
    if ((flag & IBLIT_MASK) != 0 && (flag & IBLIT_HFLIP) == 0) {
        for (; h; h--) {
            ibitmap_blitmc(dst, pitch1, src, w, 1, pitch2, pixsize, 
                linesize, mask);
            dst += pitch1;
            src -= pitch2;
        }
        return 0;
    }

    /* flip horizon */
    src += linesize - pixsize;
    inc1 = pitch1 - linesize;

    /* horizon & vertical or horizon only */
    if (flag & IBLIT_VFLIP) inc2 = - (pitch2 - linesize);
    else inc2 = pitch2 + linesize;

    /* copying pixel for all color depth */
    switch (pixsize)
    {
    /* flip in 8 bits colors depth */
    case 1: 
        key08 = (unsigned char)mask;
        for (; h; h--) {
            if (flag & IBLIT_MASK) {
                for (i = w; i; i--) {      /* copy each pixel with mask */
                    if (*(unsigned char*)src - key08) *dst = *src;
                    dst++;
                    src--;
                }
            }    else {
                for (i = w; i; i--) {      /* copy pixels without mask  */
                    *dst = *src;
                    dst++;
                    src--;
                }
            }
            dst += inc1;
            src += inc2;
        }
        break;

    /* flip in 15/16 bits colors depth */
    case 2:
        key16 = (unsigned short)mask;
        for (; h; h--) {
            if (flag & IBLIT_MASK) {
                for (i = w; i; i--) {      /* copy each pixel with mask */
                    if (*(unsigned short*)src - key16) 
                        *(unsigned short*)dst = *(unsigned short*)src;
                    dst += 2;
                    src -= 2;
                }
            }    else {
                for (i = w; i; i--) {      /* copy pixels without mask  */
                    *(unsigned short*)dst = *(unsigned short*)src;
                    dst += 2;
                    src -= 2;
                }
            }
            dst += inc1;
            src += inc2;
        }
        break;

    /* flip in 24 bits colors depth */
    case 3:
        if (((unsigned char*)&endian)[0] != 0x44) key24 = mask;
        else key24 = ((mask & 0xFFFF) << 8) | ((mask >> 16) & 0xFF);
        p1 = (unsigned char*)dst;
        p2 = (unsigned char*)src;
        for (; h; h--) {                   /* copy each scanline */
            if (flag & IBLIT_MASK) {
                for (i = w; i; i--) {      /* copy each pixel with mask */
                    c = (((unsigned long)(*(unsigned short*)p2)) << 8);
                    if ((c | p2[2]) - key24) {
                        p1[0] = p2[0];
                        p1[1] = p2[1];
                        p1[2] = p2[2];
                    }
                    p1 += 3;
                    p2 -= 3;
                }
            }    else {                     /* copy pixels without mask */
                for (i = w; i; i--) {
                    p1[0] = p2[0];
                    p1[1] = p2[1];
                    p1[2] = p2[2];
                    p1 += 3;
                    p2 -= 3;
                }
            }
            p1 += inc1;
            p2 += inc2;
        }
        break;

    /* flip in 32 bits colors depth */
    case 4:
        for (; h; h--) {
            if (flag & IBLIT_MASK) {
                for (i = w; i; i--) {      /* copy each pixel with mask */
                    if (*(unsigned long*)src - mask) 
                        *(unsigned long*)dst = *(unsigned long*)src;
                    dst += 4;
                    src -= 4;
                }
            }    else {
                for (i = w; i; i--) {      /* copy pixels without mask  */
                    *(unsigned long*)dst = *(unsigned long*)src;
                    dst += 4;
                    src -= 4;
                }
            }
            dst += inc1;
            src += inc2;
        }
        break;
    }

    return 0;
}



/**********************************************************************
 * FILLER DEFINITION
 **********************************************************************/
int ibitmap_fillc(char *dst, long pitch, int w, int h, int pixsize, 
    unsigned long c);

/* set filler interface to default filler (c version) */
ibitmap_filler ibitmap_filling = ibitmap_fillc;


/* ibitmap_fill - fill the rectangle with given color 
 * returns zero for successful, others for error
 * dst     - dest bitmap to draw on
 * dx, dy  - target position of dest bitmap to draw on
 * w, h    - width and height of the rectangle to be filled
 * col     - indicate the color to fill the rectangle
 */
int ibitmap_fill(struct IBITMAP *dst, int dx, int dy, int w, int h,
    unsigned long col, int noclip)
{
    int pixsize, r;
    long delta;
    char *pixel;

    assert(dst);
    if (noclip == 0) {
        if (dx >= (long)dst->w || dx + w <= 0 || w < 0) return 0;
        if (dy >= (long)dst->h || dy + h <= 0 || h < 0) return 0;
        if (dx + w >= (long)dst->w) w = (long)dst->w - dx;
        if (dy + h >= (long)dst->h) h = (long)dst->h - dy;
    }

    /* get pixel size */
    pixsize = (dst->bpp + 7) >> 3;

    /* choose linear offset */
    switch (pixsize) {
    case 1: delta = dx; break;
    case 2: delta = (dx << 1);  break;
    case 3: delta = (dx << 1) + dx; break;
    case 4: delta = (dx << 2); break;
    default: delta = dx * pixsize; break;
    }

    /* get the first scanlines of the bitmap */
    pixel = (char*)dst->line[dy] + delta;

    r = ibitmap_filling(pixel, dst->pitch, w, h, pixsize, col);
    if (r) ibitmap_fillc(pixel, dst->pitch, w, h, pixsize, col);

    return 0;
}


/**********************************************************************
 * ibitmap_blitfc - default blitter for flip blit
 **********************************************************************/
int ibitmap_fillc(char *dst, long pitch, int w, int h, int pixsize, 
    unsigned long col)
{
    static unsigned long endian = 0x11223344;
    unsigned short col16;
    unsigned char col8, c1, c2, c3;
    unsigned long *p32;
    long inc, i;

    switch (pixsize)
    {
    /* fill for 8 bits color depth */
    case 1:
        col8 = (unsigned char)(col & 0xff);
        for (; h; h--) {
            memset(dst, col8, w);
            dst += pitch;
        }
        break;

    /* fill for 15/16 bits color depth */
    case 2:
        col16 = (unsigned short)(col & 0xffff);
        col = (col << 16) | (col & 0xffff);
        for (; h; h--) {
            p32 = (unsigned long*)dst;
            for (i = w >> 1; i; i--) *p32++ = col;
            if (w & 1) *(unsigned short*)p32 = col16;
            dst += pitch;
        }
        break;
    
    /* fill for 24 bits color depth */
    case 3:
        inc = pitch - ((w << 1) + w);
        if (((unsigned char*)&endian)[0] != 0x44) {
            c1 = (col >> 16) & 0xff;
            c2 = (col >> 8) & 0xff;
            c3 = col & 0xff;
        }    else {
            c1 = col & 0xff;
            c2 = (col >> 8) & 0xff;
            c3 = (col >> 16) & 0xff;
        }
        for (; h; h--) {
            for (i = w; i; i--) {
                *dst++ = c1;
                *dst++ = c2;
                *dst++ = c3;
            }
            dst += inc;
        }
        break;

    /* fill for 32 bits color depth */
    case 4:
        for (; h; h--) {
            p32 = (unsigned long*)dst;
            for (i = w; i; i--) *p32++ = col;
            dst += pitch;
        }
        break;
    }

    return 0;
}



/*
 * ibitmap_funcset - set basic bitmap functions, returns zero for successful
 * mode - function index, there are two blitters, (0/1 normal/transparent) 
 * proc - extra function pointer, set it to null to use default method
 * there are two blitters, normal blitter and transparent blitter, which 
 * ibitmap_blit will call to complete blit operation. see ibitmapm.c 
 */
int ibitmap_funcset(int functionid, const void *proc)
{
    typedef void*(*icmalloc_t)(size_t);
    typedef void (*icfree_t)(void*ptr);

    switch (functionid)
    {
    case IBITMAP_BLITER_NORM:
        ibitmap_blitn = (proc == NULL)? ibitmap_blitnc : 
                (ibitmap_blitter_norm)proc;
        break;
    case IBITMAP_BLITER_MASK:
        ibitmap_blitm = (proc == NULL)? ibitmap_blitmc : 
                (ibitmap_blitter_mask)proc;
        break;
    case IBITMAP_BLITER_FLIP:
        ibitmap_blitf = (proc == NULL)? ibitmap_blitfc : 
                (ibitmap_blitter_flip)proc;
        break;
    case IBITMAP_FILLER:
        ibitmap_filling = (proc == NULL)? ibitmap_fillc :
                (ibitmap_filler)proc;
        break;
    case IBITMAP_MALLOC:
        icmalloc = (proc == NULL)? malloc : (icmalloc_t)proc;
        break;
    case IBITMAP_FREE:
        icfree = (proc == NULL)? free : (icfree_t)proc;
        break;
    default:
        assert(0);
        break;
    }
    return 0;
}


/*
 * ibitmap_funcget - get the blitter function pointer, returns the address
 * of the blitter function, and returns NULL for error. for more information
 * please see the comment of ibitmap_funcset.
 */
void *ibitmap_funcget(int functionid, int version)
{
    void *proc = NULL;
    switch (functionid)
    {
    case IBITMAP_BLITER_NORM: 
        proc = (version)? ibitmap_blitnc : ibitmap_blitn; 
        break;
    case IBITMAP_BLITER_MASK: 
        proc = (version)? ibitmap_blitmc : ibitmap_blitm; 
        break;
    case IBITMAP_BLITER_FLIP: 
        proc = (version)? ibitmap_blitfc : ibitmap_blitf; 
        break;
    case IBITMAP_FILLER:
        proc = (version)? ibitmap_fillc : ibitmap_filling;
        break;
    }
    return proc;
}


