/*  Bitmap functions

    27.06.2001: tk, initial implementation.

    Copyright (c)2000 by Thomas Kindler, thomas.kindler@gmx.de

    This program is free software; you can redistribute it and/or
    modify it under the terms of the GNU General Public License as
    published by the Free Software Foundation; either version 2 of
    the License, or (at your option) any later version. Read the
    full License at http://www.gnu.org/copyleft for more details.
*/

// include files -----
//
#include "bitmap.h"
#include <assert.h>
#include <stdlib.h>
#include <stdio.h>
#include <mem.h>


/**
 * Function pointers to blitter functions
 */
static BM_BlitFunc *blitFuncs[] = {
  NULL,           BM_Blit_Nor,   BM_Blit_NotAnd,  BM_Blit_NotCopy,
  BM_Blit_AndNot, NULL,          BM_Blit_Xor,     BM_Blit_Nand,
  BM_Blit_And,    BM_Blit_Nexor, NULL,            BM_Blit_NotOr,
  BM_Blit_Copy,   BM_Blit_OrNot, BM_Blit_Or,      NULL
};


/**
 * Allocate a Bitmap structure.
 *
 * \param   w, h  width and height of bitmap
 * \return  Pointer to bitmap structure or NULL
 *          if out of memory.
 *
 * \note    The bitmap is not cleared for performance
 *          reasons. Do this by using BM_Clear().
 */
Bitmap *BM_Alloc(int w, int h)
{
  unsigned size = w*((h+7)>>3);
  size += size & 1;   // make size a multiple of two.

  Bitmap *bm = (Bitmap*)malloc(sizeof(Bitmap)+size);
  if (!bm)
    return NULL;

  bm->width  = w;
  bm->height = h;
  bm->bmBitsSize = size;
  return bm;
}


/**
 * Copy a Bitmap.
 * \param    bm  pointer to bitmap to copy.
 * \returns  pointer to copied bitmap, or NULL if
 *           out of memory.
 */
Bitmap *BM_Copy(Bitmap *bm)
{
  Bitmap *copy = BM_Alloc(bm->width, bm->height);
  if (!copy)
    return NULL;

  memcpy(copy->bmBits, bm->bmBits, copy->bmBitsSize);
  return copy;
}



/**
 * Free a Bitmap structure.
 * BM_Free frees a Bitmap (and its bmBits) previously
 * allocated by BM_Alloc.
 *
 * \param bm  pointer to a Bitmap structure.
 * \note  bmBits are only freed, if the pointer is
 *        non-null.
 */
void BM_Free(Bitmap *bm)
{
  if (bm) {
    bm->width  = 0;
    bm->height = 0;
    bm->bmBitsSize = 0;
    free(bm);
  }
}


/**
 * Clear a Bitmap's bmBits.
 *
 * \param bm       pointer to a Bitmap structure.
 * \param pattern  byte pattern to use.
 */
void BM_Clear(Bitmap *bm, char pattern)
{
  memset(bm->bmBits, pattern, bm->bmBitsSize);
}


/**
 * Invert all pixel of a Bitmap.
 *
 * \param bm       pointer to a Bitmap structure.
 */
void BM_Invert(Bitmap *bm)
{
  char     *bits = bm->bmBits;
  unsigned  size = bm->bmBitsSize;

  asm {
    pushf
    push  ax
    push  cx
    push  si 
    push  di
    push  ds
    push  es
    cld

    lds   si, bits
    les   di, bits

    mov   cx, size
    shr   cx, 1
l1: lodsw
    not   ax
    stosw
    loop  l1

    pop   es
    pop   ds
    pop   di
    pop   si
    pop   cx
    pop   ax
    popf
  }
}


/**
 * Scroll bitmap contents.
 *
 * \param bm       pointer to a Bitmap structure.
 * \param dx, dy   shift by (dx, dy) pixels.
 *
 * \todo
 *   re-implement this function as an in-place move!
 *
 */
void BM_Scroll(Bitmap *bm, int dx, int dy)
{
  Bitmap *tmp = BM_Copy(bm);
  assert(tmp != NULL);

  BM_Clear(bm);
  BM_Blit(bm, -dx, -dy, tmp);
  BM_Free(tmp);
}


/**
 * Load a bitmap from a bmp file.
 *
 * This function allocates and loads a bitmap from a windows
 * .bmp file. Only two color, uncompressed bitmaps are
 * supported.
 *
 * \param  fn  filename to load.
 * \return pointer to a Bitmap structure or NULL if an
 *         error occured.
 */
Bitmap *BM_Load(char *fn)
{
  printf("loading \"%s\".. ", fn);

  FILE *f = fopen( fn, "rb" );
  if (!f) {
    perror("");
    return NULL;
  }

  BitmapFileHeader  bmfh = {0};
  fread(&bmfh, 1, sizeof(bmfh), f);

  if (bmfh.bfType        != 0x4D42 ||
      bmfh.biPlanes      != 1      ||
      bmfh.biBitCount    != 1      ||
      bmfh.biCompression != 0       )
  {
    printf( "invalid format.\n" );
    fclose(f);
    return NULL;
  }

  fseek(f, bmfh.bfOffBits, SEEK_SET);

  Bitmap *bm = BM_Alloc(bmfh.biWidth, abs(bmfh.biHeight));
  if (!bm) {
    printf("can't alloc bitmap.\n");
    fclose(f);
    return NULL;
  }
  BM_Clear(bm);

  char pad[4];
  int  padBytes = (4-(bmfh.biWidth+7)>>3) & 3;

  for (int y=abs(bmfh.biHeight)-1; y>=0; y--) {
    unsigned char  *dst = bm->bmBits + (y>>3)*bm->width;
    unsigned char ymask = 1<<(y&7), xmask = 0, src;

    for (int x=0; x<bm->width; x++) {
      if (!xmask) {
        src   = ~fgetc(f);
        xmask = 0x80;
      }
      if (src & xmask)
        *dst |= ymask;
      xmask >>= 1;
      dst++;
    }
    fread(pad, 1, padBytes, f);
  }

  fclose(f);
  printf( "%dx%d (%u bytes).\n", bm->width,
          bm->height, bm->bmBitsSize);
  return bm;
}


/**
 * Save a bitmap into a bmp file.
 *
 * This function saves a bitmap into a two-color,
 * uncompressed windows .bmp file.
 *
 * \param  bm  Bitmap to save.
 * \param  fn  filename to save to.
 */
void  BM_Save(Bitmap *bm, char *fn)
{
  BitmapFileHeader  bmfh;

  int imagesize = (((bm->width+31)>>5)<<2) * bm->height;

  bmfh.bfType = 0x4D42;
  bmfh.bfSize = sizeof(bmfh) + imagesize;
  bmfh.bfReserved1 = 0;
  bmfh.bfReserved2 = 0;
  bmfh.bfOffBits = sizeof(bmfh);

  bmfh.biSize = 40;
  bmfh.biWidth = bm->width;
  bmfh.biHeight = bm->height;
  bmfh.biPlanes = 1;
  bmfh.biBitCount = 1;
  bmfh.biCompression = 0;
  bmfh.biSizeImage = imagesize;

  bmfh.biXPelsPerMeter = DPI2PPM(72);
  bmfh.biYPelsPerMeter = DPI2PPM(72);
  bmfh.biClrUsed = 2;
  bmfh.biClrImportant = 2;

  bmfh.color0 = BMP_BGCOLOR;
  bmfh.color1 = BMP_FGCOLOR;

  printf("saving \"%s\".. ", fn);
  FILE *f = fopen(fn, "wb");
  if (!f) {
    perror("");
    return;
  }

  fwrite(&bmfh, sizeof(bmfh), 1, f);

  char pad[4] = {0};
  int  padlen = (4-(bm->width+7)>>3) & 3;

  for (int y=bm->height-1; y>=0; y--) {
    unsigned char *src   = &bm->bmBits[(y>>3)*bm->width];
    unsigned char  ymask = 1<<(y&7), xmask = 0x80, c=0;

    for (int x=0; x<bm->width; x++) {
      if (*src++ & ymask)
        c |= xmask;
      xmask >>= 1;
      if (!xmask) {
        fputc(c, f);
        c = 0;
        xmask = 0x80;
      }
    }
    if (xmask != 0x80)
      fputc(c, f);

    fwrite(pad, padlen, 1, f);
  }

  fclose(f);
  printf("ok.\n");
}


/**
 * Copy a region between two Bitmaps (with clipping).
 *
 * \note   This function does clipping, and is therefore quite
 *         inefficient for copying small regions. Don't use this
 *         to copy text, or other small bitmaps. Do the clipping
 *         yourself, and use the fastblt functions instead.
 *
 * \param  dst   pointer to destination Bitmap
 * \param  x,y   destination coordinates
 * \param  w,h   width and height in pixels
 * \param  u,v   source coordinates
 * \param  mode  blitmode (BLT_COPY, BLT_OR..)
 *
 * \note   source width [height] is used, if w [h] is set to -1.
 * \note   implement overlapping region copies
 *         (by using a temporary bitmap).
 */
void BM_Blit( Bitmap *dst, int x, int y, Bitmap *src, int u, int v,
              int w, int h, int mode )
{
  if (w==-1)  w = src->width;
  if (h==-1)  h = src->height;

  // clip x/u coordinates
  //
  if (x<0) {
    u += -x;
    w -= -x;
    x  =  0;
  }
  if (x+w >= dst->width)
    w -= (x+w)-dst->width;

  if (u<0) {
    x += -u;
    w -= -u;
    u  =  0;
  }
  if (u+w >= src->width)
    w -= (u+w)-src->width;

  if (w<=0)
    return;

  // clip y/v coordinates
  //
  if (y<0)
    v += -y,  h -= -y,  y = 0;
  if (y+h >= dst->height)
    h -= (y+h)-dst->height;

  if (v<0)
    y += -v,  h -= -v,  v = 0;
  if (v+h >= src->height)
    h -= (v+h) - src->height;

  if (h<=0)
    return;

  BM_FastBlit(dst, x, y, src, u, v, w, h, mode);
}


/**
 * Copy a region between two Bitmaps (without clipping).
 *
 * \note   This function does not do clipping. You'll
 *         have to take care of that yourself.
 *
 * \param  dst  pointer to destination Bitmap
 * \param  x,y  destination coordinates
 * \param  w,h  width and height in pixels
 * \param  u,v  source coordinates
 * \param  op   one of the BLT_XXX blitmode constants
 *
 * \note   source width [height] is used, if w [h] is set to -1.
 * \note   This function doesn't handle overlapping source and
 *         destination regions (the blit does always start at
 *         the top-left corner).
 */
void BM_FastBlit(Bitmap *dst, int x, int y, Bitmap *src, int u, int v,
                 int w, int h, int mode)
{
  if (w==-1)  w = src->width;
  if (h==-1)  h = src->height;

  // calculate destination masks
  //
  unsigned char fbm, lbm;
  fbm = 0xff << (   y & 7);
  lbm = 0xff >> (-y-h & 7);

  // calculate number of pages
  //
  int pages = ((y+h+7)>>3) - (y>>3);
  if (pages == 1)
    fbm &= lbm;

  // calculate pixel shift
  //
  int shift = (v&7)-(y&7);
  if (shift < 0) {
    v     -= 8;
    shift &= 7;
  }

/*
  printf("dst: "); BM_Info(dst);
  printf("src: "); BM_Info(src);
  printf(
    "  %p, x=%d, y=%d, %p, u=%d, v=%d,\n"
    "  w=%d, pages=%d, shift=%d, fbm=%02x, lbm=%02x, mode=%d\n",
    dst, x, y, src, u, v, w, pages, shift, fbm, lbm, mode
  );
*/
  BM_BlitFunc *blitter = blitFuncs[mode & 15];
  if (blitter)
    blitter( &dst->bmBits[x+(y>>3)*dst->width], dst->width,
             &src->bmBits[u+(v>>3)*src->width], src->width,
             w, pages, shift, fbm, lbm );
}
