/*

  Heffer - Lewpen's 2D graphics lib

  Heffer.CPP

  Deep Red/Lewpen

*/



#include "../Main/ERisk.h"



/*---- COMMENTS --------------------------------------------------------------//

TODOs
-

Make alpha so that 0 is opaque and 255 is transparent - this means that to
plot a full pixel you can just use (0xrrggbb)



NOTES
-
  
The blits onto ABGR haven't been tested - I just made up the way to combine
source and destination alpha.  The rule is that alpha blitting should be
assocative - ie. (a+b)+c = a+(b+c) where a b and c are images with alpha.



TYPES
-

HEFFER_IMAGE

HEFFER_FONT
  A font is an 8-bit greyscale image containing the characters 0-255 and an
  array of x-co-ordinates and widths for extracting the characters.
  These can be generated from an image of the characters.



FUNCTIONS
-

BOOL Heffer_Startup();
BOOL Heffer_Shutdown();
  Must be called at start & end of program.  Return TRUE on success.

HEFFER_IMAGE *Heffer_LoadBitmapResource(HINSTANCE hInst, char *name);
HEFFER_IMAGE *Heffer_LoadTargaResource(HINSTANCE hInst, char *name);
  Loads images from a executable's resource file.
  hInst - the process handle of the executable
  name - the ASCII name of the resource
  returns a pointer to the created image
  BITMAP images are loaded as 24-bit BGR (HEFFER_FORMAT_BGR24)
  TARGA images are loaded as 32-bit ABGR (HEFFER_FORMAT_ABGR32)

HEFFER_IMAGE *Heffer_CreateImage(int w, int h, char format);
  Creates a blank image with the given dimentions and format

BOOL Heffer_DeleteImage(HEFFER_IMAGE *im);
  Deletes an image and any data associated with it.

void Heffer_Image_Display(HDC dc, HEFFER_IMAGE *im, int x, int y);
BOOL Heffer_GetImage(HEFFER_IMAGE *imdest, int dx, int dy, int w, int h, HDC dc, int sx, int sy, int flags);
void Heffer_Blit(HEFFER_IMAGE *imsrc, int dx, int dy, int w, int h, HEFFER_IMAGE *imdest, int sx, int sy, int flags);
void Heffer_AlphaBlit(HEFFER_IMAGE *imsrc, int dx, int dy, int w, int h, HEFFER_IMAGE *imdest, int sx, int sy, unsigned char alpha, int flags);

void Heffer_AlphaRect(HEFFER_IMAGE *imdest, int dx, int dy, int w, int h, int col);

void Heffer_DarkenRect(HEFFER_IMAGE *im, int x, int y, int w, int h);
void Heffer_BlurRect(HEFFER_IMAGE *im, int x, int y, int w, int h);

void Heffer_DrawDot(HEFFER_IMAGE *im, int x, int y, int col);
void Heffer_PutPixel(HEFFER_IMAGE *im, int x, int y, int col);
int Heffer_GetPixel(HEFFER_IMAGE *im, int x, int y);
void Heffer_DrawLine(HEFFER_IMAGE *im, int x1, int y1, int x2, int y2, int col);
void Heffer_DrawRing(HEFFER_IMAGE *im, float x1, float y1, float x2, float y2, int col);

HRGN Heffer_RegionFromImage(HEFFER_IMAGE *im, int col);
  Creates a Windows HRGN object from an image and a transparent colour.
  im - image to create region from
  col - solid colour to use as transparent (0x00bbggrr)
  Returns an HRGN which can be used in functions such as SetWindowRgn for
  changing the shape of a window.  Returns NULL on error.

HEFFER_IMAGE *Heffer_CreateImage_AlphaMask(HEFFER_IMAGE *imsrc, int col);
  Creates a mask of an image in the specified colour - replaces all colour with
  the specified colour but leaves the transparency.  This is the first step to
  creating layer effects such as drop shadow and outer glow.

HEFFER_IMAGE *Heffer_CreateImage_OuterGlow(HEFFER_IMAGE *imsrc, int col, double blur, double intens);
  Creates an outer glow layer from the source image
  The source image has to be in an alpha format (ie. HEFFER_FORMAT_ABGR32)

HEFFER_FONT *Heffer_LoadFontResource(HINSTANCE hInst, char *name);
  Loads a font stored in a resource in the Heffer font format.

void Heffer_TextOut(HEFFER_IMAGE *im, int x, int y, HEFFER_FONT *font, char *text, int col, int flags);



TECHNICAL NOTES
-

Colour ordering is Blue Green Red because this is the way GDI does it.
In memory each scan line of a bitmap (DIB) has to be a multiple of 4 bytes.



//---- PRIVATE FUNCTION PROTOS -----------------------------------------------*/


//---- PRIVATE TYPES ---------------------------------------------------------//


//---- GLOBALS ---------------------------------------------------------------//

#define talloc(a) ((a*)calloc(1, sizeof(a)))

int heffer_error = 0;

BOOL heffer_startup = FALSE;



//  Alpha-blend lookup tables - [modifier][value]

unsigned char heffer_bacon[256][256];
unsigned char heffer_prawn[256][256];

//  Intensity lookup table - [intens][alpha]

unsigned char heffer_intens[256][256];

//  256-colour dithering lookups

unsigned char heffer_lookup_b_g[256][256];
unsigned char heffer_lookup_r_bg[256][256];
unsigned char heffer_lookup_dither[16][256];

//  256-colour palette

HPALETTE heffer_hpal;

typedef struct HEFFER_TAG_LOGPALETTE256 {
  WORD         palVersion;
  WORD         palNumEntries;
  PALETTEENTRY palPalEntry[256];
} HEFFER_LOGPALETTE256;

typedef struct HEFFER_TAG_BITMAPINFO256 {
  BITMAPINFOHEADER bmiHeader;
  WORD bmiColors[256];
} HEFFER_BITMAPINFO256;

HEFFER_LOGPALETTE256 heffer_lp256;
HEFFER_BITMAPINFO256 heffer_bmi;



//---- Heffer_Startup --------------------------------------------------------//

//  Initialises the library

BOOL Heffer_Startup()
{
  //- Create alpha-blend lookup tables

  int i, j;

  for(j=0; j<256; j++) for(i=0; i<256; i++) {
    heffer_bacon[j][i] = (j*i)/255;
    heffer_prawn[j][i] = ((255-j)*i)/255;
  }

  //- Create intensity lookup

  for(j=0; j<256; j++) for(i=0; i<256; i++) {
    int k = (i*(j*3+256))/256;
    if(k>255) k = 255;
    heffer_intens[j][i] = k;
  }

  //- Create 256-colour dither lookups

  //  Blue & Green combination lookup

  for(j=0; j<256; j++) {            //  Blue value
    for(i=0; i<256; i++) {          //  Green value
      heffer_lookup_b_g[j][i] = ((i*6)>>8)*36 + ((j*6)>>8)*6;
    }
  }

  //  Red with Blue/Green combination lookup
  
  for(j=0; j<256; j++) {            //  Red value
    for(i=0; i<256; i++) {          //  B/G combined using previous table
      heffer_lookup_r_bg[j][i] = ((j*6)>>8) + i;
    }
  }

  //  Dither lookup tables

  for(j=0; j<16; j++) {
    for(i=0; i<256; i++) {
      if(i<128) { heffer_lookup_dither[j][i] = 0; } else { heffer_lookup_dither[j][i] = 255; }
    }
  }

  for(i=0; i<213; i++) {
    j = ((int)(i/6))*5+43;
    heffer_lookup_dither[ 0][i+ 2] = j;
    heffer_lookup_dither[ 1][i+ 5] = j;
    heffer_lookup_dither[ 2][i+ 8] = j;
    heffer_lookup_dither[ 3][i+10] = j;
    heffer_lookup_dither[ 4][i+13] = j;
    heffer_lookup_dither[ 5][i+16] = j;
    heffer_lookup_dither[ 6][i+18] = j;
    heffer_lookup_dither[ 7][i+21] = j;
    heffer_lookup_dither[ 8][i+24] = j;
    heffer_lookup_dither[ 9][i+26] = j;
    heffer_lookup_dither[10][i+29] = j;
    heffer_lookup_dither[11][i+32] = j;
    heffer_lookup_dither[12][i+34] = j;
    heffer_lookup_dither[13][i+37] = j;
    heffer_lookup_dither[14][i+40] = j;
    heffer_lookup_dither[15][i+42] = j;
  }

  //  Create palette

  int r, g, b;

  heffer_lp256.palVersion = 0x300;
  heffer_lp256.palNumEntries = 216;

  for(b=0; b<6; b++) for(g=0; g<6; g++) for(r=0; r<6; r++) {
    heffer_lp256.palPalEntry[r+6*g+36*b].peRed   = r*255/5;
    heffer_lp256.palPalEntry[r+6*g+36*b].peGreen = g*255/5;
    heffer_lp256.palPalEntry[r+6*g+36*b].peBlue  = b*255/5;
    heffer_lp256.palPalEntry[r+6*g+36*b].peFlags = 0;
  }

  heffer_hpal = CreatePalette((LOGPALETTE *)&heffer_lp256);

  //- Done

  heffer_startup = TRUE;

  return TRUE;
}



//---- Heffer_Shutdown -------------------------------------------------------//

BOOL Heffer_Shutdown()
{
  DeleteObject((HGDIOBJ)heffer_hpal);

  return TRUE;
}



//---- Heffer_LoadBitmapResource ---------------------------------------------//

HEFFER_IMAGE *Heffer_LoadBitmapResource(HINSTANCE hInst, char *name)
{
  //- Create object

  HEFFER_IMAGE *im = talloc(HEFFER_IMAGE);

  if(!im) return NULL;

  //  Get bitmap bits into buffer

  BITMAPINFO bmi;
  HDC dc, memDC;
  HBITMAP hbmp;
  BITMAP bmp;

  hbmp = LoadBitmap(hInst, name);
  if(!hbmp) {
    heffer_error = 77001001;
    free(im);
    return NULL;
  }
  GetObject(hbmp, sizeof(BITMAP), (LPVOID)&bmp);

  int w = bmp.bmWidth, h = bmp.bmHeight;
  int bufw = (w*3+3)&0xFFFFFFFC;

  im->buf = (unsigned char *)malloc(bufw*h);
  if(!im->buf) {
    heffer_error = 77001002;
    free(im);
    DeleteObject(hbmp);
    return NULL;
  }

  bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
  bmi.bmiHeader.biWidth = w;
  bmi.bmiHeader.biHeight = -h;
  bmi.bmiHeader.biPlanes = 1;
  bmi.bmiHeader.biBitCount = 24;
  bmi.bmiHeader.biCompression = BI_RGB;
  bmi.bmiHeader.biSizeImage = 0;
  bmi.bmiHeader.biXPelsPerMeter = 1000;
  bmi.bmiHeader.biYPelsPerMeter = 1000;
  bmi.bmiHeader.biClrUsed = 0;
  bmi.bmiHeader.biClrImportant = 0;

  dc = GetDC(GetDesktopWindow());
  memDC = CreateCompatibleDC(dc);

  int i = GetDIBits(memDC, hbmp, 0,h, (LPVOID)im->buf, (LPBITMAPINFO)&bmi, DIB_RGB_COLORS);

  DeleteDC(memDC);
  ReleaseDC(GetDesktopWindow(), dc);

  DeleteObject(hbmp);

  //- Fill in object info

  im->w  = w; im->h  = h; im->bufw = bufw;
  im->x1 = 0; im->y1 = 0; im->x2 = w; im->y2 = h;
  im->ox = 0; im->oy = 0;
  im->size = bufw*h;
  im->bpp = 3;

  im->format = HEFFER_FORMAT_BGR24;
  im->dirty = NULL; im->dirty_size = 0;

  return im;
}



//---- Heffer_LoadTargaResource ----------------------------------------------//

HEFFER_IMAGE *Heffer_LoadTargaResource(HINSTANCE hInst, char *name)
{
  //- Create object

  HEFFER_IMAGE *im = talloc(HEFFER_IMAGE);

  if(!im) return NULL;

  //- Load resource into memory

  HGLOBAL hResource = LoadResource(hInst, FindResource(hInst, name, "TARGA"));
  if(!hResource) {
    heffer_error = 77002006;
    return FALSE;
  }
  unsigned char *buf = (unsigned char *)LockResource(hResource);
  if(!buf) {
    heffer_error = 77002007;
    return FALSE;
  }

  //- Read TGA data

  BOOL compressed;

  //  Read over imageid and colormap fields
	unsigned char imageid = *buf++;
	unsigned char colormap = *buf++;

  //  Check that the source data is in the correct format
  if((int)*buf != 2) if((int)*buf !=10) {
    heffer_error = 77002001;
    free(im);
    return NULL;
  }

	compressed = *buf == 10;

	//  Skip past the image type and colour map specifiaction
  buf+=6;

  //  Read image metrics
  im->ox = *(short*)buf; buf+=2;
  im->oy = *(short*)buf; buf+=2;
  im->w = *(short*)buf; buf+=2;
  im->h = *(short*)buf; buf+=2;
	im->bpp = (*buf)>>3; buf++;

  //  Bypass the image descriptor
  buf++;

  if(im->bpp != 4) {
    heffer_error = 77002002;
    free(im);
		return NULL;
	}

  //  Skip over imageid and colormap data
  buf+=imageid;
	buf+=colormap;

	//  Allocate memory for the image

  im->bufw = (im->w*4+3)&0xFFFFFFFC;
  im->size = im->bufw*im->h;

  im->buf = (unsigned char*)malloc(im->size);
	if(!im->buf) {
    heffer_error = 77002003;
    free(im);
		return NULL;
	}

  if(compressed) {
//		DecompressImageData(image->data, buf, image->w*image->h);
    heffer_error = 77002666;
    free(im);
    return NULL;
	}	else {

    int bufw = im->bufw, h = im->h;

    for(int y=0; y<im->h; y++) {
      int o = (h-y-1)*im->bufw;
      for(int x=0; x<im->w; x++) {
        im->buf[o+1] = *buf++;
        im->buf[o+2] = *buf++;
        im->buf[o+3] = *buf++;
        im->buf[o+0] = *buf++;
        o += 4;
      }
    }
	}

  //- Done

  FreeResource(hResource);

  //- Fill in object info

  im->x1 = 0; im->y1 = 0; im->x2 = im->w; im->y2 = im->h;
  im->format = HEFFER_FORMAT_ABGR32;
  im->dirty = NULL; im->dirty_size = 0;

  return im;
}



//---- Heffer_CreateImage ----------------------------------------------------//

HEFFER_IMAGE *Heffer_CreateImage(int w, int h, char format)
{
  HEFFER_IMAGE *im;
  char bpp;

  switch(format) {
    case HEFFER_FORMAT_BGR24:  bpp = 3; break;
    case HEFFER_FORMAT_ABGR32: bpp = 4; break;
    default: return NULL;
  }

  //- Create image

  im = talloc(HEFFER_IMAGE);
  if(!im) return NULL;

  //- Allocate buffer

  int bufw = (w*bpp+3)&0xFFFFFFFC;

  im->buf = (unsigned char *)malloc(bufw*h);
  if(!im->buf) { free(im); return NULL; }

  //- Fill in object info

  im->w  = w; im->h  = h; im->bufw = bufw;
  im->x1 = 0; im->y1 = 0; im->x2 = w; im->y2 = h;
  im->ox = 0; im->oy = 0;
  im->size = bufw*h;
  im->bpp = bpp;

  im->format = format;
  im->dirty = NULL; im->dirty_size = 0;

  //- Clear image

  Heffer_FillImage(im, 0x00000000);

  return im;
}



//---- Heffer_DuplicateImage -------------------------------------------------//

HEFFER_IMAGE *Heffer_DuplicateImage(HEFFER_IMAGE *imsrc)
{
  //- Create image

  HEFFER_IMAGE *im = talloc(HEFFER_IMAGE);
  if(!im) return NULL;

  memcpy(im, imsrc, sizeof(HEFFER_IMAGE));

  //- Allocate buffer

  im->buf = (unsigned char *)malloc(im->bufw*im->h);
  if(!im->buf) { free(im); return NULL; }

  //- Copy image

  memcpy(im->buf, imsrc->buf, im->bufw*im->h);

  return im;
}



//---- Heffer_GetImage -------------------------------------------------------//

//  Gets a portion of a bitmap from a DC

BOOL Heffer_GetImage(HEFFER_IMAGE *im, int dx, int dy, int w, int h, HDC dc, int sx, int sy, int flags)
{
  if(!im) return NULL;

  //  Blit flags

  if(flags & HEFFER_BLIT_FULLSIZE) {
    w = im->w;
    h = im->h;
  }

  //  Get bitmap bits into buffer

  BITMAPINFO bmi;
  HDC memDC;
  HBITMAP hbmp, oldbmp;

  memDC = CreateCompatibleDC(dc);

  hbmp = CreateCompatibleBitmap(dc, w, h);
  if(!hbmp) {
    heffer_error = 77003001;
    free(im);
    return FALSE;
  }

  int bufw = (w*3+3)&0xFFFFFFFC;

  im->buf = (unsigned char *)malloc(bufw*h);
  if(!im->buf) {
    heffer_error = 77003002;
    free(im);
    DeleteObject(hbmp);
    return FALSE;
  }

  bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
  bmi.bmiHeader.biWidth = w;
  bmi.bmiHeader.biHeight = -h;
  bmi.bmiHeader.biPlanes = 1;
  bmi.bmiHeader.biBitCount = 24;
  bmi.bmiHeader.biCompression = BI_RGB;
  bmi.bmiHeader.biSizeImage = 0;
  bmi.bmiHeader.biXPelsPerMeter = 1000;
  bmi.bmiHeader.biYPelsPerMeter = 1000;
  bmi.bmiHeader.biClrUsed = 0;
  bmi.bmiHeader.biClrImportant = 0;

  dc = GetDC(GetDesktopWindow());
  memDC = CreateCompatibleDC(dc);

  oldbmp = (HBITMAP)SelectObject(memDC, hbmp);

  BitBlt(memDC, 0,0, w,h, dc, sx,sy, SRCCOPY);
  GetDIBits(memDC, hbmp, 0,h, (LPVOID)im->buf, (LPBITMAPINFO)&bmi, DIB_RGB_COLORS);

  SelectObject(memDC, oldbmp);
  DeleteDC(memDC);

  DeleteObject(hbmp);

  //- Fill in object info

  im->w  = w; im->h  = h; im->bufw = bufw;
  im->x1 = 0; im->y1 = 0; im->x2 = w; im->y2 = h;
  im->ox = 0; im->oy = 0;
  im->size = bufw*h;
  im->bpp = 3;

  im->format = HEFFER_FORMAT_BGR24;
  im->dirty = NULL; im->dirty_size = 0;

  return TRUE;
}



//---- Heffer_Blit -----------------------------------------------------------//

void Heffer_Blit(HEFFER_IMAGE *imdest, int dx, int dy, int w, int h, HEFFER_IMAGE *imsrc, int sx, int sy, int flags)
{
  int i, j;

  if(!imdest) return;
  if(!imsrc) return;

  //  Addresses of buffers

  unsigned char *sbuf = imsrc->buf;
  unsigned char *dbuf = imdest->buf;

  //  Blit flags

  if(flags & HEFFER_BLIT_FULLSIZE) {
    w = imsrc->w;
    h = imsrc->h;
  }

  //  Clip

  if(sx < 0) { dx -= sx; w += sx; sx = 0; }
  if(sy < 0) { dy -= sy; h += sy; sy = 0; }
  if(sx+w > imsrc->w) { w = imsrc->w-sx; }
  if(sy+h > imsrc->h) { h = imsrc->h-sy; }

  if(dx+w > imdest->x2) { w = imdest->x2-dx; }
  if(dy+h > imdest->y2) { h = imdest->y2-dy; }
  if(dx < imdest->x1) { w += dx-imdest->x1; sx -= dx-imdest->x1; dx = imdest->x1; }
  if(dy < imdest->y1) { h += dy-imdest->y1; sy -= dy-imdest->y1; dy = imdest->y1; }

  if(w<0 || h<0) return;

  //  Calculate offsets

  sbuf += sx*imsrc ->bpp + sy*imsrc ->bufw;
  dbuf += dx*imdest->bpp + dy*imdest->bufw;

  //  Copy data

  if(imsrc->format == HEFFER_FORMAT_BGR24) {
    if(imdest->format == HEFFER_FORMAT_BGR24) {

      //  24-bit BGR to 24-bit BGR blit
      for(j=0; j<h; j++) {
        for(i=0; i<w; i++) {
          *dbuf++ = *sbuf++;
          *dbuf++ = *sbuf++;
          *dbuf++ = *sbuf++;
        }
        sbuf += imsrc-> bufw - w*3;
        dbuf += imdest->bufw - w*3;
      }

    } else if(imdest->format == HEFFER_FORMAT_ABGR32) {

      //  24-bit BGR to 32-bit ABGR blit
      for(j=0; j<h; j++) {
        for(i=0; i<w; i++) {
          *dbuf++ = 255;
          *dbuf++ = *sbuf++;
          *dbuf++ = *sbuf++;
          *dbuf++ = *sbuf++;
        }
        sbuf += imsrc-> bufw - w*3;
        dbuf += imdest->bufw - w*4;
      }

    }
  } else if(imsrc->format == HEFFER_FORMAT_ABGR32) {
    if(imdest->format == HEFFER_FORMAT_BGR24) {

      //  32-bit ABGR to 24-bit BGR blit
      for(j=0; j<h; j++) {
        for(i=0; i<w; i++) {
          unsigned char a = *sbuf++;
          unsigned char c1, c2;
          (c1 = *dbuf, c2 = *sbuf++, *dbuf++ = ( (int)(c1)*(int)(255-a) + (int)(c2)*(int)(a) )/255);
          (c1 = *dbuf, c2 = *sbuf++, *dbuf++ = ( (int)(c1)*(int)(255-a) + (int)(c2)*(int)(a) )/255);
          (c1 = *dbuf, c2 = *sbuf++, *dbuf++ = ( (int)(c1)*(int)(255-a) + (int)(c2)*(int)(a) )/255);
        }
        sbuf += imsrc-> bufw - w*4;
        dbuf += imdest->bufw - w*3;
      }

    } else if(imdest->format == HEFFER_FORMAT_ABGR32) {

      //  32-bit ABGR to 32-bit ABGR blit
      for(j=0; j<h; j++) {
        for(i=0; i<w; i++) {
          unsigned char a = *sbuf++;
          *dbuf++ = (*dbuf*(255-a) + 255*a)/255;
          unsigned char c1, c2;
          (c1 = *dbuf, c2 = *sbuf++, *dbuf++ = ( (int)(c1)*(int)(255-a) + (int)(c2)*(int)(a) )/255);
          (c1 = *dbuf, c2 = *sbuf++, *dbuf++ = ( (int)(c1)*(int)(255-a) + (int)(c2)*(int)(a) )/255);
          (c1 = *dbuf, c2 = *sbuf++, *dbuf++ = ( (int)(c1)*(int)(255-a) + (int)(c2)*(int)(a) )/255);
        }
        sbuf += imsrc-> bufw - w*4;
        dbuf += imdest->bufw - w*4;
      }

    }
  }

  //  TODO: Update dirty region

}



//---- Heffer_AlphaBlit ------------------------------------------------------//

void Heffer_AlphaBlit(HEFFER_IMAGE *imdest, int dx, int dy, int w, int h, HEFFER_IMAGE *imsrc, int sx, int sy, unsigned char alpha, int flags)
{
  int i, j;

  if(!imdest) return;
  if(!imsrc) return;

  //  Addresses of buffers

  unsigned char *sbuf = imsrc->buf;
  unsigned char *dbuf = imdest->buf;

  //  Blit flags

  if(flags & HEFFER_BLIT_FULLSIZE) {
    w = imsrc->w;
    h = imsrc->h;
  }

  //  Clip

  if(dx+w > imdest->x2) { w = imdest->x2-dx; }
  if(dy+h > imdest->y2) { h = imdest->y2-dy; }
  if(dx < imdest->x1) { w += dx-imdest->x1; sx -= dx-imdest->x1; dx = imdest->x1; }
  if(dy < imdest->y1) { h += dy-imdest->y1; sy -= dy-imdest->y1; dy = imdest->y1; }

  if(w<0 || h<0) return;

  //  Calculate offsets

  sbuf += sx*imsrc ->bpp + sy*imsrc ->bufw;
  dbuf += dx*imdest->bpp + dy*imdest->bufw;

  //  Copy data

  if(imsrc->format == HEFFER_FORMAT_BGR24) {
    if(imdest->format == HEFFER_FORMAT_BGR24) {

      //  24-bit BGR to 24-bit BGR blit
      for(j=0; j<h; j++) {
        for(i=0; i<w; i++) {
          unsigned char c1, c2;
          (c1 = *dbuf, c2 = *sbuf++, *dbuf++ = ( (int)(c1)*(int)(255-alpha) + (int)(c2)*(int)(alpha) )/255);
          (c1 = *dbuf, c2 = *sbuf++, *dbuf++ = ( (int)(c1)*(int)(255-alpha) + (int)(c2)*(int)(alpha) )/255);
          (c1 = *dbuf, c2 = *sbuf++, *dbuf++ = ( (int)(c1)*(int)(255-alpha) + (int)(c2)*(int)(alpha) )/255);
        }
        sbuf += imsrc-> bufw - w*3;
        dbuf += imdest->bufw - w*3;
      }

    } else if(imdest->format == HEFFER_FORMAT_ABGR32) {

      //  24-bit BGR to 32-bit ABGR blit
      for(j=0; j<h; j++) {
        for(i=0; i<w; i++) {
          unsigned char a = alpha;
          unsigned char c1, c2;
          *dbuf++ = ( (int)(*dbuf)*(255-a) + (int)(255    )*(a) )>>8;
          (c1 = *dbuf, c2 = *sbuf++, *dbuf++ = ( (int)(c1)*(int)(255-a) + (int)(c2)*(int)(a) )/255);
          (c1 = *dbuf, c2 = *sbuf++, *dbuf++ = ( (int)(c1)*(int)(255-a) + (int)(c2)*(int)(a) )/255);
          (c1 = *dbuf, c2 = *sbuf++, *dbuf++ = ( (int)(c1)*(int)(255-a) + (int)(c2)*(int)(a) )/255);

        }
        sbuf += imsrc-> bufw - w*3;
        dbuf += imdest->bufw - w*4;
      }

    }
  } else if(imsrc->format == HEFFER_FORMAT_ABGR32) {
    if(imdest->format == HEFFER_FORMAT_BGR24) {

      //  32-bit ABGR to 24-bit BGR blit
      for(j=0; j<h; j++) {
        for(i=0; i<w; i++) {
          unsigned char a = ((*sbuf++)*alpha)/255;
          unsigned char c1, c2;
          (c1 = *dbuf, c2 = *sbuf++, *dbuf++ = ( (int)(c1)*(int)(255-a) + (int)(c2)*(int)(a) )/255);
          (c1 = *dbuf, c2 = *sbuf++, *dbuf++ = ( (int)(c1)*(int)(255-a) + (int)(c2)*(int)(a) )/255);
          (c1 = *dbuf, c2 = *sbuf++, *dbuf++ = ( (int)(c1)*(int)(255-a) + (int)(c2)*(int)(a) )/255);
        }
        sbuf += imsrc-> bufw - w*4;
        dbuf += imdest->bufw - w*3;
      }

    } else if(imdest->format == HEFFER_FORMAT_ABGR32) {

      //  32-bit ABGR to 32-bit ABGR blit
      for(j=0; j<h; j++) {
        for(i=0; i<w; i++) {
          unsigned char a = ((*sbuf++)*alpha)/255;
          *dbuf++ = ( (int)(*dbuf)*(255-a) + (int)(255    )*(a) )>>8;
          *dbuf++ = ( (int)(*dbuf)*(255-a) + (int)(*sbuf++)*(a) )>>8;
          *dbuf++ = ( (int)(*dbuf)*(255-a) + (int)(*sbuf++)*(a) )>>8;
          *dbuf++ = ( (int)(*dbuf)*(255-a) + (int)(*sbuf++)*(a) )>>8;
        }
        sbuf += imsrc-> bufw - w*4;
        dbuf += imdest->bufw - w*4;
      }

    }
  }

  //  TODO: Update dirty region

}



//---- Heffer_SoftLightBlit --------------------------------------------------//

void Heffer_SoftLightBlit(HEFFER_IMAGE *imdest, int dx, int dy, int w, int h, HEFFER_IMAGE *imsrc, int sx, int sy, unsigned char alpha, int flags)
{
  int i, j;

  if(!imdest) return;
  if(!imsrc) return;

  //  Addresses of buffers

  unsigned char *sbuf = imsrc->buf;
  unsigned char *dbuf = imdest->buf;

  //  Blit flags

  if(flags & HEFFER_BLIT_FULLSIZE) {
    w = imsrc->w;
    h = imsrc->h;
  }

  //  Clip

  if(dx+w > imdest->x2) { w = imdest->x2-dx; }
  if(dy+h > imdest->y2) { h = imdest->y2-dy; }
  if(dx < imdest->x1) { w += dx-imdest->x1; sx -= dx-imdest->x1; dx = imdest->x1; }
  if(dy < imdest->y1) { h += dy-imdest->y1; sy -= dy-imdest->y1; dy = imdest->y1; }

  if(w<0 || h<0) return;

  //  Calculate offsets

  sbuf += sx*imsrc ->bpp + sy*imsrc ->bufw;
  dbuf += dx*imdest->bpp + dy*imdest->bufw;

  //  Copy data

  if(imsrc->format == HEFFER_FORMAT_BGR24) {
    if(imdest->format == HEFFER_FORMAT_BGR24) {

      //  24-bit BGR to 24-bit BGR blit
      for(j=0; j<h; j++) {
        for(i=0; i<w; i++) {
          unsigned char c1, c2;
          (c1 = *dbuf, c2 = *sbuf++, *dbuf++ = ( (int)(c1)*(int)(255-alpha) + (int)(c2)*(int)(alpha) )/255);
          (c1 = *dbuf, c2 = *sbuf++, *dbuf++ = ( (int)(c1)*(int)(255-alpha) + (int)(c2)*(int)(alpha) )/255);
          (c1 = *dbuf, c2 = *sbuf++, *dbuf++ = ( (int)(c1)*(int)(255-alpha) + (int)(c2)*(int)(alpha) )/255);
        }
        sbuf += imsrc-> bufw - w*3;
        dbuf += imdest->bufw - w*3;
      }

    } else if(imdest->format == HEFFER_FORMAT_ABGR32) {

      //  24-bit BGR to 32-bit ABGR blit (only one that's done)
      for(j=0; j<h; j++) {
        for(i=0; i<w; i++) {
          unsigned char a = alpha;
          unsigned char c1, c2;
          *dbuf++;
          (c1 = *dbuf, c2 = *sbuf++, *dbuf++ = ( (int)(c1)*(int)(255-a) + (int)(c2)*(int)(a) )/255);
          (c1 = *dbuf, c2 = *sbuf++, *dbuf++ = ( (int)(c1)*(int)(255-a) + (int)(c2)*(int)(a) )/255);
          (c1 = *dbuf, c2 = *sbuf++, *dbuf++ = ( (int)(c1)*(int)(255-a) + (int)(c2)*(int)(a) )/255);

        }
        sbuf += imsrc-> bufw - w*3;
        dbuf += imdest->bufw - w*4;
      }

    }
  } else if(imsrc->format == HEFFER_FORMAT_ABGR32) {
    if(imdest->format == HEFFER_FORMAT_BGR24) {

      //  32-bit ABGR to 24-bit BGR blit
      for(j=0; j<h; j++) {
        for(i=0; i<w; i++) {
          unsigned char a = ((*sbuf++)*alpha)/255;
          unsigned char c1, c2;
          (c1 = *dbuf, c2 = *sbuf++, *dbuf++ = ( (int)(c1)*(int)(255-a) + (int)(c2)*(int)(a) )/255);
          (c1 = *dbuf, c2 = *sbuf++, *dbuf++ = ( (int)(c1)*(int)(255-a) + (int)(c2)*(int)(a) )/255);
          (c1 = *dbuf, c2 = *sbuf++, *dbuf++ = ( (int)(c1)*(int)(255-a) + (int)(c2)*(int)(a) )/255);
        }
        sbuf += imsrc-> bufw - w*4;
        dbuf += imdest->bufw - w*3;
      }

    } else if(imdest->format == HEFFER_FORMAT_ABGR32) {

      //  32-bit ABGR to 32-bit ABGR blit
      for(j=0; j<h; j++) {
        for(i=0; i<w; i++) {
          unsigned char a = ((*sbuf++)*alpha)/255;
          *dbuf++;
          *dbuf++ = ( (int)(*dbuf)*(255-a) + (int)(*sbuf++)*(a) )>>8;
          *dbuf++ = ( (int)(*dbuf)*(255-a) + (int)(*sbuf++)*(a) )>>8;
          *dbuf++ = ( (int)(*dbuf)*(255-a) + (int)(*sbuf++)*(a) )>>8;
        }
        sbuf += imsrc-> bufw - w*4;
        dbuf += imdest->bufw - w*4;
      }

    }
  }

  //  TODO: Update dirty region

}



//---- Heffer_AlphaRect ------------------------------------------------------//

void Heffer_AlphaRect(HEFFER_IMAGE *imdest, int dx, int dy, int w, int h, int col)
{
  unsigned char r, g, b, a;
  int i, j;

  if(!imdest) return;

  r = (col>> 0)&0xFF;
  g = (col>> 8)&0xFF;
  b = (col>>16)&0xFF;
  a = (col>>24)&0xFF;

  if(a == 0xFF) {
    Heffer_FillRect(imdest, dx, dy, w, h, col);
    return;
  }

  //  Addresses of buffers

  unsigned char *dbuf = imdest->buf;

  //  Clip

  if(dx+w > imdest->x2) { w = imdest->x2-dx; }
  if(dy+h > imdest->y2) { h = imdest->y2-dy; }
  if(dx < imdest->x1) { w += dx-imdest->x1; dx = imdest->x1; }
  if(dy < imdest->y1) { h += dy-imdest->y1; dy = imdest->y1; }

  if(w<0 || h<0) return;

  //  Calculate offsets

  dbuf += dx*imdest->bpp + dy*imdest->bufw;

  //  Copy data

  if(imdest->format == HEFFER_FORMAT_BGR24) {

    //  24-bit BGR blit
    for(j=0; j<h; j++) {
      for(i=0; i<w; i++) {
        unsigned char c1, c2;
        (c1 = *dbuf, c2 = b, *dbuf++ = ( (int)(c1)*(int)(255-a) + (int)(c2)*(int)(a) )/255);
        (c1 = *dbuf, c2 = g, *dbuf++ = ( (int)(c1)*(int)(255-a) + (int)(c2)*(int)(a) )/255);
        (c1 = *dbuf, c2 = r, *dbuf++ = ( (int)(c1)*(int)(255-a) + (int)(c2)*(int)(a) )/255);
      }
      dbuf += imdest->bufw - w*3;
    }

  } else if(imdest->format == HEFFER_FORMAT_ABGR32) {

    //  32-bit ABGR blit
    for(j=0; j<h; j++) {
      for(i=0; i<w; i++) {
        unsigned char c1, c2;
        *dbuf++ = ( (int)(*dbuf)*(255-a) + (int)(255    )*(a) )>>8;
        (c1 = *dbuf, c2 = b, *dbuf++ = ( (int)(c1)*(int)(255-a) + (int)(c2)*(int)(a) )/255);
        (c1 = *dbuf, c2 = g, *dbuf++ = ( (int)(c1)*(int)(255-a) + (int)(c2)*(int)(a) )/255);
        (c1 = *dbuf, c2 = r, *dbuf++ = ( (int)(c1)*(int)(255-a) + (int)(c2)*(int)(a) )/255);
      }
      dbuf += imdest->bufw - w*4;
    }

  }

  //  TODO: Update dirty region

}



//---- Heffer_FillRect -------------------------------------------------------//

//  DOING:  Rewrite in asm so it's nice and fast :)
//          At the moment it's bobbins.

void Heffer_FillRect(HEFFER_IMAGE *im, int x, int y, int w, int h, int col)
{
  unsigned char r, g, b, a;
  int i, j;

  r = (col>> 0)&0xFF;
  g = (col>> 8)&0xFF;
  b = (col>>16)&0xFF;
  a = (col>>24)&0xFF;

  //  Address of buffer

  unsigned char *buf = im->buf;
  int bufw = im->bufw;

  //  Calculate offsets

  if(x < im->x1) { w += x-im->x1; x = im->x1; }
  if(y < im->y1) { h += y-im->y1; y = im->y1; }
  if(x+w > im->x2) { w = im->x2-x; }
  if(y+h > im->y2) { h = im->y2-y; }

  if(w <= 0 || h <= 0) return;

  buf += x*im->bpp + y*im->bufw;

  //  Do stuff

  if(im->bpp == 3) {

    _asm {

      mov ah, b
      mov bh, g
      mov dh, r
      mov al, r
      mov bl, b
      mov dl, g
      rol eax, 16
      rol ebx, 16
      rol edx, 16
      mov ah, g
      mov bh, r
      mov dh, b
      mov al, b
      mov bl, g
      mov dl, r

      mov edi, buf

      mov esi, h
    yloop:

      push edi

      mov ecx, w

    xloop_test16:
      cmp ecx, 16
      jl xloop_no16
      mov [edi+4* 0], eax
      mov [edi+4* 1], ebx
      mov [edi+4* 2], edx
      mov [edi+4* 3], eax
      mov [edi+4* 4], ebx
      mov [edi+4* 5], edx
      mov [edi+4* 6], eax
      mov [edi+4* 7], ebx
      mov [edi+4* 8], edx
      mov [edi+4* 9], eax
      mov [edi+4*10], ebx
      mov [edi+4*11], edx
      add edi, 4*12
      sub ecx, 16
      jmp xloop_test16
    xloop_no16:

    xloop_test4:
      cmp ecx, 4
      jl xloop_no4
      mov [edi+4* 0], eax
      mov [edi+4* 1], ebx
      mov [edi+4* 2], edx
      add edi, 4*3
      sub ecx, 4
      jmp xloop_test4
    xloop_no4:

    xloop_test1:
      cmp ecx, 1
      jl xloop_no1
      mov [edi+0], al
      mov [edi+1], bl
      mov [edi+2], dl
      add edi, 3
      sub ecx, 1
      jmp xloop_test1
    xloop_no1:

      pop edi
      add edi, bufw

      dec esi
      jnz yloop
    }

/*
    for(j=0; j<h; j++) {
      for(i=0; i<w; i++) { *buf++ = b; *buf++ = g; *buf++ = r; }
      buf += im->bufw - w*3;
    }
*/

  } else if(im->bpp == 4) {

    for(j=0; j<h; j++) {
      for(i=0; i<w; i++) { *buf++ = b; *buf++ = g; *buf++ = r; *buf++ = a; }
      buf += im->bufw - w*4;
    }

  }

  //  TODO: Update dirty region


}



//---- Heffer_FillImage ------------------------------------------------------//

void Heffer_FillImage(HEFFER_IMAGE *im, int col)
{
  if(!im) return;
  Heffer_FillRect(im, 0, 0, im->w, im->h, col);
}



//---- Heffer_DarkenRect -----------------------------------------------------//

void Heffer_DarkenRect(HEFFER_IMAGE *im, int x, int y, int w, int h)
{
  int i, j;

  //  Addresses of buffers

  unsigned char *buf = im->buf;

  //  Calculate offsets

  if(x < im->x1) { w += x-im->x1; x = im->x1; }
  if(y < im->y1) { h += y-im->y1; y = im->y1; }
  if(x+w > im->x2) { w = im->x2-x; }
  if(y+h > im->y2) { h = im->y2-y; }

  if(w<0 || h<0) return;

  buf += x*3 + y*im->bufw;

  //  Copy data

  for(j=0; j<h; j++) {
    for(i=0; i<w; i++) {
      *buf++ >>= 1;
      *buf++ >>= 1;
      *buf++ >>= 1;
    }
    buf += im->bufw - w*3;
  }

  //  TODO: Update dirty region


}



//---- Heffer_BlurRect -------------------------------------------------------//

void Heffer_BlurRect(HEFFER_IMAGE *im, int x, int y, int w, int h)
{
  int i, j, v;

  //  Addresses of buffers

  unsigned char *buf = im->buf;
  int bufw = im->bufw;

  //  Calculate offsets

  if(x < im->x1) { w += x-im->x1; x = im->x1; }
  if(y < im->y1) { h += y-im->y1; y = im->y1; }
  if(x+w > im->x2) { w = im->x2-x; }
  if(y+h > im->y2) { h = im->y2-y; }

  if(w<0 || h<0) return;

  buf += x*3 + y*im->bufw;

  //  Copy data

  for(j=0; j<h-1; j++) {
    for(i=0; i<w-1; i++) {
      v = buf[0]; v += buf[3]; v += buf[bufw]; v += buf[bufw+3]; v >>= 2;
      *buf++ = v;
      v = buf[0]; v += buf[3]; v += buf[bufw]; v += buf[bufw+3]; v >>= 2;
      *buf++ = v;
      v = buf[0]; v += buf[3]; v += buf[bufw]; v += buf[bufw+3]; v >>= 2;
      *buf++ = v;
    }
    buf += im->bufw - (w+1)*3;
  }

  //  TODO: Update dirty region


}



//---- Heffer_DrawDot ---------------------------------------------------------//

void Heffer_DrawDot(HEFFER_IMAGE *im, int x, int y, int col)
{
  Heffer_FillRect(im, x-2, y-2, 5, 5, col^0xFFFFFF);
  Heffer_FillRect(im, x-1, y-1, 3, 3, col);
}



//---- Heffer_PutPixel --------------------------------------------------------//

void Heffer_PutPixel(HEFFER_IMAGE *im, int x, int y, int col)
{
  unsigned char r, g, b, a;

  r = (col>> 0)&0xFF;
  g = (col>> 8)&0xFF;
  b = (col>>16)&0xFF;
  a = (col>>24)&0xFF;

  //  Clip

  if(x<im->x1 || y<im->y1 || x >= im->x2 || y >= im->y2) return;

  unsigned char *buf = im->buf;
  buf += x*im->bpp + y*im->bufw;

  //  Plot pixel

  if(im->bpp == 3) {
    *buf++ = b; *buf++ = g; *buf++ = r;
  } else if(im->bpp == 4) {
    *buf++ = b; *buf++ = g; *buf++ = r; *buf++ = a;
  }

  //- TODO: Set dirty region

}



//---- Heffer_GetPixel --------------------------------------------------------//

int Heffer_GetPixel(HEFFER_IMAGE *im, int x, int y)
{
  unsigned char r, g, b, a=0;

  if(x<im->x1 || y<im->y1 || x >= im->x2 || y >= im->y2) return -1;

  unsigned char *buf = im->buf;
  buf += x*im->bpp + y*im->bufw;

  //  Get pixel

  if(im->bpp == 3) {
    b = *buf++; g = *buf++; r = *buf++;
  } else if(im->bpp == 4) {
    b = *buf++; g = *buf++; r = *buf++; a = *buf++;
  }

  return (a<<24)+(b<<16)+(g<<8)+(r);
}



//---- Heffer_DrawLine --------------------------------------------------------//

void Heffer_DrawLine(HEFFER_IMAGE *im, int x1, int y1, int x2, int y2, int col)
{
  int x, y, dx, dy;
  int i, di;

  dx = x2-x1; if(dx < 0) dx =- dx;
  dy = y2-y1; if(dy < 0) dy =- dy;

  di = dx; if(dy > dx) di = dy;

  if(!di) return;

  for(i=0; i<=di; i++) {
    x = x1 + (x2-x1)*i/di;
    y = y1 + (y2-y1)*i/di;
//    Heffer_PutPixel(im, x, y, col);

    Heffer_PutPixel(im, x  , y-1, col);
    Heffer_PutPixel(im, x+1, y-1, col);
    Heffer_PutPixel(im, x-1, y  , col);
    Heffer_PutPixel(im, x  , y  , col);
    Heffer_PutPixel(im, x+1, y  , col);
    Heffer_PutPixel(im, x+2, y  , col);
    Heffer_PutPixel(im, x-1, y+1, col);
    Heffer_PutPixel(im, x  , y+1, col);
    Heffer_PutPixel(im, x+1, y+1, col);
    Heffer_PutPixel(im, x+2, y+1, col);
    Heffer_PutPixel(im, x  , y+2, col);
    Heffer_PutPixel(im, x+1, y+2, col);
  }
}



//---- Heffer_DrawRing --------------------------------------------------------//

void Heffer_DrawRing(HEFFER_IMAGE *im, float x1, float y1, float x2, float y2, int col)
{
  double cx = 0.5*(x1+x2), cy = 0.5*(y1+y2);
  double dx = (x2-x1), dy = (y2-y1);
  double d = atan(dy/dx);
  double r, a, rx, ry, rm, rd, rr, ratio;
  double xa=0, ya=0, xb, yb;
  int i;

  rx = 40+0.5*sqrt(dx*dx+dy*dy);
  ry = 30+sqrt(rx-40);

  ratio = 0.7;
  if(rx > 120) {
    rx = 120;
  }

  rm = ry;
  rd = rx-ry;

  for(i=0; i<128; i++) {
    a = (1.2*(i/64.0)-0.1)*3.1415926;
    rr = 0.5-0.5*cos(a*2);
    rr = (rr*rr)*ratio+rr*(1-ratio);
    r = rm + rd * rr;

    if(i<32) r += 0.4*ry*((32-i)/32.0);
    if(i>96) r += 0.4*ry*((i-96)/32.0);

    xb = xa; yb = ya;
    xa = cx - r*sin(a-d); ya = cy - r*cos(a-d);

    if(i) Heffer_DrawLine(im, (int)xa, (int)ya, (int)xb, (int)yb, col);
    Heffer_DrawLine(im, 128+i, 480, 128+i, (int)(480-r), col);
  }

}



//---- Heffer_Tri ------------------------------------------------------------//

void Heffer_Tri(HEFFER_IMAGE *im, double x1, double y1, double x2, double y2, double x3, double y3, int col)
{
  double t;
  int x, y, ya, yb, xa, xb;
  
  unsigned char r, g, b, a;

  r = (col>> 0)&0xFF;
  g = (col>> 8)&0xFF;
  b = (col>>16)&0xFF;
  a = (col>>24)&0xFF;

  //  Sort points

  if(y1 > y2) { t=y2; y2=y1; y1=t; t=x2; x2=x1; x1=t; }
  if(y2 > y3) { t=y3; y3=y2; y2=t; t=x3; x3=x2; x2=t; }
  if(y1 > y2) { t=y2; y2=y1; y1=t; t=x2; x2=x1; x1=t; }

  //  Clip

  ya = y1; yb = y3;

  if(yb <= im->y1) return;
  if(ya >= im->y2) return;

  if(ya < im->y1) ya = im->y1-1;
  if(yb > im->y2) yb = im->y2;

  //  Draw

  for(y=ya+1; y<yb; y++) {

    if(y < y2) { xa = x1 + (x3-x1)*(y-y1)/(y3-y1); xb = x1 + (x2-x1)*(y-y1)/(y2-y1); } else
    if(y < y3) { xa = x1 + (x3-x1)*(y-y1)/(y3-y1); xb = x2 + (x3-x2)*(y-y2)/(y3-y2); }

    if(xa > xb) { t=xa; xa=xb; xb=t; }

    if(xa < im->x1) xa = im->x1;
    if(xb > im->x2) xb = im->x2;

    char *dbuf = (char *)im->buf + xa*im->bpp + y*im->bufw;

    if(im->format == HEFFER_FORMAT_BGR24) {

      for(x=xa; x<xb; x++) {
        unsigned char c1, c2;
        (c1 = *dbuf, c2 = b, *dbuf++ = ( (int)(c1)*(int)(255-a) + (int)(c2)*(int)(a) )/255);
        (c1 = *dbuf, c2 = g, *dbuf++ = ( (int)(c1)*(int)(255-a) + (int)(c2)*(int)(a) )/255);
        (c1 = *dbuf, c2 = r, *dbuf++ = ( (int)(c1)*(int)(255-a) + (int)(c2)*(int)(a) )/255);
      }

    } else if(im->format == HEFFER_FORMAT_ABGR32) {

      for(x=xa; x<xb; x++) {
        unsigned char c1, c2;
        *dbuf++ = ( (int)(*dbuf)*(255-a) + (int)(255    )*(a) )>>8;
        (c1 = *dbuf, c2 = b, *dbuf++ = ( (int)(c1)*(int)(255-a) + (int)(c2)*(int)(a) )/255);
        (c1 = *dbuf, c2 = g, *dbuf++ = ( (int)(c1)*(int)(255-a) + (int)(c2)*(int)(a) )/255);
        (c1 = *dbuf, c2 = r, *dbuf++ = ( (int)(c1)*(int)(255-a) + (int)(c2)*(int)(a) )/255);
      }
    }

  }

}



//---- Heffer_AATri ----------------------------------------------------------//

void Heffer_AATri(HEFFER_IMAGE *im, double x1, double y1, double x2, double y2, double x3, double y3, int col)
{
  int i, j;

  for(j=0; j<4; j++) for(i=0; i<4; i++) {

    Heffer_Tri(
      im,
      x1+i*0.25, y1+j*0.25,
      x2+i*0.25, y2+j*0.25,
      x3+i*0.25, y3+j*0.25,
      col
    );

  }

}



//---- Heffer_LewpensMagicFunction -------------------------------------------//

unsigned char *Heffer_LewpensMagicFunction(HEFFER_IMAGE *im)
{
  int w = im->w, h = im->h;
  int bufw = im->bufw;

  unsigned char *scr = (unsigned char *)malloc(w*h);
  unsigned char *buf = im->buf;

  _asm {

    mov esi, buf
    mov edi, scr

    xor eax, eax
    xor ebx, ebx

    mov edx, h
    shr dx, 2
  ss3d_rgb2pal_dither_yloop:
    push esi
    push edi
  
    mov ecx, w
    shr cx, 2
  ss3d_rgb2pal_dither_xloop:
    push esi
    push edi
  
      //  0  //
    mov bl, [esi]
    mov al, heffer_lookup_dither[ebx]
    mov bl, [esi+1]
    mov ah, heffer_lookup_dither[ebx]
    mov al, heffer_lookup_b_g[eax]
    mov bl, [esi+2]
    mov ah, heffer_lookup_dither[ebx]
    mov al, heffer_lookup_r_bg[eax]
    mov [edi], al
    add esi, 3
    inc edi

      //  8  //
    mov bl, [esi]
    mov al, heffer_lookup_dither[ebx+2048]
    mov bl, [esi+1]
    mov ah, heffer_lookup_dither[ebx+2048]
    mov al, heffer_lookup_b_g[eax]
    mov bl, [esi+2]
    mov ah, heffer_lookup_dither[ebx+2048]
    mov al, heffer_lookup_r_bg[eax]
    mov [edi], al
    add esi, 3
    inc edi

      //  2  //
    mov bl, [esi]
    mov al, heffer_lookup_dither[ebx+512]
    mov bl, [esi+1]
    mov ah, heffer_lookup_dither[ebx+512]
    mov al, heffer_lookup_b_g[eax]
    mov bl, [esi+2]
    mov ah, heffer_lookup_dither[ebx+512]
    mov al, heffer_lookup_r_bg[eax]
    mov [edi], al
    add esi, 3
    inc edi

      //  A  //
    mov bl, [esi]
    mov al, heffer_lookup_dither[ebx+2560]
    mov bl, [esi+1]
    mov ah, heffer_lookup_dither[ebx+2560]
    mov al, heffer_lookup_b_g[eax]
    mov bl, [esi+2]
    mov ah, heffer_lookup_dither[ebx+2560]
    mov al, heffer_lookup_r_bg[eax]
    mov [edi], al
    sub esi, 9
    sub edi, 3
    add esi, bufw
    add edi, w

  
      //  C  //
    mov bl, [esi]
    mov al, heffer_lookup_dither[ebx+3072]
    mov bl, [esi+1]
    mov ah, heffer_lookup_dither[ebx+3072]
    mov al, heffer_lookup_b_g[eax]
    mov bl, [esi+2]
    mov ah, heffer_lookup_dither[ebx+3072]
    mov al, heffer_lookup_r_bg[eax]
    mov [edi], al
    add esi, 3
    inc edi

      //  4  //
    mov bl, [esi]
    mov al, heffer_lookup_dither[ebx+1024]
    mov bl, [esi+1]
    mov ah, heffer_lookup_dither[ebx+1024]
    mov al, heffer_lookup_b_g[eax]
    mov bl, [esi+2]
    mov ah, heffer_lookup_dither[ebx+1024]
    mov al, heffer_lookup_r_bg[eax]
    mov [edi], al
    add esi, 3
    inc edi

      //  E  //
    mov bl, [esi]
    mov al, heffer_lookup_dither[ebx+3584]
    mov bl, [esi+1]
    mov ah, heffer_lookup_dither[ebx+3584]
    mov al, heffer_lookup_b_g[eax]
    mov bl, [esi+2]
    mov ah, heffer_lookup_dither[ebx+3584]
    mov al, heffer_lookup_r_bg[eax]
    mov [edi], al
    add esi, 3
    inc edi

      //  6  //
    mov bl, [esi]
    mov al, heffer_lookup_dither[ebx+1536]
    mov bl, [esi+1]
    mov ah, heffer_lookup_dither[ebx+1536]
    mov al, heffer_lookup_b_g[eax]
    mov bl, [esi+2]
    mov ah, heffer_lookup_dither[ebx+1536]
    mov al, heffer_lookup_r_bg[eax]
    mov [edi], al
    sub esi, 9
    sub edi, 3
    add esi, bufw
    add edi, w
  

      //  3  //
    mov bl, [esi]
    mov al, heffer_lookup_dither[ebx+768]
    mov bl, [esi+1]
    mov ah, heffer_lookup_dither[ebx+768]
    mov al, heffer_lookup_b_g[eax]
    mov bl, [esi+2]
    mov ah, heffer_lookup_dither[ebx+768]
    mov al, heffer_lookup_r_bg[eax]
    mov [edi], al
    add esi, 3
    inc edi

      //  B  //
    mov bl, [esi]
    mov al, heffer_lookup_dither[ebx+2816]
    mov bl, [esi+1]
    mov ah, heffer_lookup_dither[ebx+2816]
    mov al, heffer_lookup_b_g[eax]
    mov bl, [esi+2]
    mov ah, heffer_lookup_dither[ebx+2816]
    mov al, heffer_lookup_r_bg[eax]
    mov [edi], al
    add esi, 3
    inc edi

      //  1  //
    mov bl, [esi]
    mov al, heffer_lookup_dither[ebx+256]
    mov bl, [esi+1]
    mov ah, heffer_lookup_dither[ebx+256]
    mov al, heffer_lookup_b_g[eax]
    mov bl, [esi+2]
    mov ah, heffer_lookup_dither[ebx+256]
    mov al, heffer_lookup_r_bg[eax]
    mov [edi], al
    add esi, 3
    inc edi

      //  9  //
    mov bl, [esi]
    mov al, heffer_lookup_dither[ebx+2304]
    mov bl, [esi+1]
    mov ah, heffer_lookup_dither[ebx+2304]
    mov al, heffer_lookup_b_g[eax]
    mov bl, [esi+2]
    mov ah, heffer_lookup_dither[ebx+2304]
    mov al, heffer_lookup_r_bg[eax]
    mov [edi], al
    sub esi, 9
    sub edi, 3
    add esi, bufw
    add edi, w


      //  F  //
    mov bl, [esi]
    mov al, heffer_lookup_dither[ebx+3840]
    mov bl, [esi+1]
    mov ah, heffer_lookup_dither[ebx+3840]
    mov al, heffer_lookup_b_g[eax]
    mov bl, [esi+2]
    mov ah, heffer_lookup_dither[ebx+3840]
    mov al, heffer_lookup_r_bg[eax]
    mov [edi], al
    add esi, 3
    inc edi

      //  7  //
    mov bl, [esi]
    mov al, heffer_lookup_dither[ebx+1792]
    mov bl, [esi+1]
    mov ah, heffer_lookup_dither[ebx+1792]
    mov al, heffer_lookup_b_g[eax]
    mov bl, [esi+2]
    mov ah, heffer_lookup_dither[ebx+1792]
    mov al, heffer_lookup_r_bg[eax]
    mov [edi], al
    add esi, 3
    inc edi

      //  D  //
    mov bl, [esi]
    mov al, heffer_lookup_dither[ebx+3328]
    mov bl, [esi+1]
    mov ah, heffer_lookup_dither[ebx+3328]
    mov al, heffer_lookup_b_g[eax]
    mov bl, [esi+2]
    mov ah, heffer_lookup_dither[ebx+3328]
    mov al, heffer_lookup_r_bg[eax]
    mov [edi], al
    add esi, 3
    inc edi

      //  5  //
    mov bl, [esi]
    mov al, heffer_lookup_dither[ebx+1280]
    mov bl, [esi+1]
    mov ah, heffer_lookup_dither[ebx+1280]
    mov al, heffer_lookup_b_g[eax]
    mov bl, [esi+2]
    mov ah, heffer_lookup_dither[ebx+1280]
    mov al, heffer_lookup_r_bg[eax]
    mov [edi], al

    pop edi
    pop esi

    add esi, 12
    add edi, 4
    dec cx
    jnz ss3d_rgb2pal_dither_xloop

    pop edi
    pop esi

    add esi, bufw
    add edi, w
    add esi, bufw
    add edi, w
    add esi, bufw
    add edi, w
    add esi, bufw
    add edi, w

    dec dx
    jnz ss3d_rgb2pal_dither_yloop
  }

  return scr;
}



//---- Heffer_Image_Display --------------------------------------------------//

void Heffer_Image_Display(HDC dc, HEFFER_IMAGE *im, int x, int y, BOOL d)
{
  if(!im) return;

  heffer_bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
  heffer_bmi.bmiHeader.biWidth = im->w;
  heffer_bmi.bmiHeader.biHeight = -im->h;
  heffer_bmi.bmiHeader.biPlanes = 1;
  heffer_bmi.bmiHeader.biBitCount = 24;
  heffer_bmi.bmiHeader.biCompression = BI_RGB;
  heffer_bmi.bmiHeader.biSizeImage = 0;
  heffer_bmi.bmiHeader.biXPelsPerMeter = 1000;
  heffer_bmi.bmiHeader.biYPelsPerMeter = 1000;
  heffer_bmi.bmiHeader.biClrUsed = 0;
  heffer_bmi.bmiHeader.biClrImportant = 0;

  int dw = im->w, dh = im->h;

  if(d) {
    dw *= 2;
    dh *= 2;
  }

  //  TEMP: Show us exactly what's appearing

//  Heffer_AlphaRect(im, 0, 0, 1000, 1000, (GetTickCount()*8261782)&0x33FFFFFF);

  if(GetDeviceCaps(dc, BITSPIXEL) > 8) {

    //- Hi-colour or Truecolour blit

    StretchDIBits(
      dc,
      x, y, dw, dh,
      0, 0, im->w, im->h,
      (LPVOID)im->buf,
      (LPBITMAPINFO)&heffer_bmi,
      DIB_RGB_COLORS, SRCCOPY
    );

  } else {

    //- 256-colour blit

    unsigned char *buf = Heffer_LewpensMagicFunction(im);

    int i;

    for(i=0; i<256; i++) {
      buf[i] = i;
      buf[i+im->w] = i;
      buf[i+im->w*2] = i;
      buf[i+im->w*3] = i;
    }

    SelectPalette(dc, heffer_hpal, FALSE);
    RealizePalette(dc);

    heffer_bmi.bmiHeader.biBitCount = 8;
    heffer_bmi.bmiHeader.biClrUsed = 216;
    heffer_bmi.bmiHeader.biClrImportant = 216;

    for(i=0; i<216; i++) heffer_bmi.bmiColors[i] = i;

    StretchDIBits(
      dc,
      x, y, dw, dh,
      0, 0, im->w, im->h,
      (LPVOID)buf,
      (LPBITMAPINFO)&heffer_bmi,
      DIB_PAL_COLORS, SRCCOPY
    );

  }

}



//---- Heffer_Image_DisplayRect ----------------------------------------------//

void Heffer_Image_DisplayRect(HDC dc, int dx, int dy, int w, int h, HEFFER_IMAGE *im, int sx, int sy)
{
  if(!im) return;

  heffer_bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
  heffer_bmi.bmiHeader.biWidth = im->w;
  heffer_bmi.bmiHeader.biHeight = -im->h;
  heffer_bmi.bmiHeader.biPlanes = 1;
  heffer_bmi.bmiHeader.biBitCount = 24;
  heffer_bmi.bmiHeader.biCompression = BI_RGB;
  heffer_bmi.bmiHeader.biSizeImage = 0;
  heffer_bmi.bmiHeader.biXPelsPerMeter = 1000;
  heffer_bmi.bmiHeader.biYPelsPerMeter = 1000;
  heffer_bmi.bmiHeader.biClrUsed = 0;
  heffer_bmi.bmiHeader.biClrImportant = 0;

  //  TEMP: Show us exactly what's appearing

//  Heffer_AlphaRect(im, 0, 0, 1000, 1000, (GetTickCount()*8261782)&0x33FFFFFF);

  if(GetDeviceCaps(dc, BITSPIXEL) > 8) {

    //- Hi-colour or Truecolour blit

    StretchDIBits(
      dc,
      dx, dy, w, h,
      sx, im->h-sy-h, w, h,
      (LPVOID)im->buf,
      (LPBITMAPINFO)&heffer_bmi,
      DIB_RGB_COLORS, SRCCOPY
    );

  } else {

    //- 256-colour blit

    unsigned char *buf = Heffer_LewpensMagicFunction(im);

    int i;

    for(i=0; i<256; i++) {
      buf[i] = i;
      buf[i+im->w] = i;
      buf[i+im->w*2] = i;
      buf[i+im->w*3] = i;
    }

    SelectPalette(dc, heffer_hpal, FALSE);
    RealizePalette(dc);

    heffer_bmi.bmiHeader.biBitCount = 8;
    heffer_bmi.bmiHeader.biClrUsed = 216;
    heffer_bmi.bmiHeader.biClrImportant = 216;

    for(i=0; i<216; i++) heffer_bmi.bmiColors[i] = i;

    StretchDIBits(
      dc,
      dx, dy, w, h,
      sx, sy, w, h,
      (LPVOID)im->buf,
      (LPBITMAPINFO)&heffer_bmi,
      DIB_RGB_COLORS, SRCCOPY
    );

  }

}



//---- Heffer_RegionFromImage ------------------------------------------------//

HRGN Heffer_RegionFromImage(HEFFER_IMAGE *im, int col)
{
  unsigned char r, g, b;
  int x, y, x1;

  r = (col>> 0)&0xFF;
  g = (col>> 8)&0xFF;
  b = (col>>16)&0xFF;

  unsigned char *buf = im->buf;
  int bufw = im->bufw;

  HRGN rgn, rgn2;

  rgn = CreateRectRgn(0, 0, im->w, im->h);

  for(y=0; y<im->h; y++) {

    for(x=0; x<im->w; ) {

      //  Find transparent pixels

      x1 = x;
      while(buf[y*bufw+x*3  ] == b &&
            buf[y*bufw+x*3+1] == g &&
            buf[y*bufw+x*3+2] == r && x < im->w) x++;

      //  Remove from region

      rgn2 = CreateRectRgn(x1, y, x, y+1);
      CombineRgn(rgn, rgn, rgn2, RGN_DIFF);
      DeleteObject(rgn2);

      //  Skip over non-transparent pixel

      while((buf[y*bufw+x*3  ] != b ||
             buf[y*bufw+x*3+1] != g ||
             buf[y*bufw+x*3+2] != r) && x < im->w) x++;
    }
  }

  return rgn;
}



//---- Heffer_DeleteImage ----------------------------------------------------//

BOOL Heffer_DeleteImage(HEFFER_IMAGE *im)
{
  if(!im) return FALSE;

  if(im->buf) free(im->buf);
  free(im);

  return TRUE;
}



//---- CLIPPING REGION FUNCTIONS ---------------------------------------------//

//---- Heffer_Clip_Set -------------------------------------------------------//

void Heffer_Clip_Set(HEFFER_IMAGE *im, int x1, int y1, int x2, int y2, BOOL combine)
{
  //  Clip the region against the image size

  if(x1<0) x1 = 0;
  if(y1<0) y1 = 0;
  if(x2>im->w) x2 = im->w;
  if(y2>im->h) y2 = im->h;

  //  Clip against current region

  if(combine) {
    if(x1<im->x1) x1 = im->x1;
    if(y1<im->y1) y1 = im->y1;
    if(x2>im->x2) x2 = im->x2;
    if(y2>im->y2) y2 = im->y2;
  }

  //  Set clipping region

  im->x1 = x1;
  im->y1 = y1;
  im->x2 = x2;
  im->y2 = y2;
}



//---- Heffer_Clip_None ------------------------------------------------------//

void Heffer_Clip_None(HEFFER_IMAGE *im)
{
  im->x1 = 0;
  im->y1 = 0;
  im->x2 = im->w;
  im->y2 = im->h;
}



//---- Heffer_Clip_Get -------------------------------------------------------//

void Heffer_Clip_Get(HEFFER_IMAGE *im, int *x1, int *y1, int *x2, int *y2)
{
  *x1 = im->x1;
  *y1 = im->y1;
  *x2 = im->x2;
  *y2 = im->y2;
}



//---- IMAGE FILTER ROUTINES -------------------------------------------------//

//---- Heffer_Filter_Blur ----------------------------------------------------//

BOOL Heffer_Filter_Blur(HEFFER_IMAGE *im, int radius, int ox, int oy)
{
  heffer_error = 0;

  if(!im) {
    heffer_error = HEFFER_FILTER_BLUR | HEFFER_ERROR_NULLPTR;
    return FALSE;
  }

  if(im->format != HEFFER_FORMAT_ABGR32) {
    heffer_error = HEFFER_FILTER_BLUR | HEFFER_ERROR_FORMAT;
    return NULL;
  }

  int bufw = im->bufw;
  int w = im->w, h = im->h;

  //- Create destination buffer

  unsigned char *newbuf = (unsigned char *)malloc(bufw*h);

  if(!newbuf) {
    heffer_error = HEFFER_FILTER_BLUR | HEFFER_ERROR_MALLOC;
    return FALSE;
  }

  //- Blur into destination buffer

  unsigned char *sbuf = im->buf;
  unsigned char *dbuf = newbuf;

  int x, y, i, j;

  for(y=0; y<h; y++) {
    for(x=0; x<w; x++) {

      int total = 0;

      int a=0, r=0, g=0, b=0;

      for(j=-radius; j<=radius; j++) if(j+y-oy >= 0 && j+y-oy < h) {
        for(i=-radius; i<=radius; i++) if(i+x-ox >= 0 && i+x-ox < w) {
          unsigned char *sbuf2 = sbuf + (i+x-ox)*4 + (j+y-oy)*bufw;
          a += *sbuf2++;
          r += *sbuf2++;
          g += *sbuf2++;
          b += *sbuf2++;
          total++;
        }
      }

      if(total) {
        *dbuf++ = (unsigned char)(a/total);
        *dbuf++ = (unsigned char)(b/total);
        *dbuf++ = (unsigned char)(g/total);
        *dbuf++ = (unsigned char)(r/total);
      } else {
        *dbuf++ = (unsigned char)0;
        *dbuf++ = (unsigned char)0;
        *dbuf++ = (unsigned char)0;
        *dbuf++ = (unsigned char)0;
      }
    }
//    sbuf += bufw - w*4;
    dbuf += bufw - w*4;
  }

  free(im->buf);
  im->buf = newbuf;

  //- Done!

  return TRUE;
}



//---- IMAGE EFFECT ROUTINES -------------------------------------------------//

//---- Heffer_CreateImage_AlphaMask ------------------------------------------//

HEFFER_IMAGE *Heffer_CreateImage_AlphaMask(HEFFER_IMAGE *imsrc, int col)
{
  heffer_error = 0;

  if(!imsrc) {
    heffer_error = HEFFER_CREATEIMAGE_ALPHAMASK | HEFFER_ERROR_NULLPTR;
    return NULL;
  }

  if(imsrc->format != HEFFER_FORMAT_ABGR32) {
    heffer_error = HEFFER_CREATEIMAGE_ALPHAMASK | HEFFER_ERROR_FORMAT;
    return NULL;
  }

  //- Create destination image

  int w = imsrc->w, h = imsrc->h;

  HEFFER_IMAGE *imdest = Heffer_CreateImage(w, h, HEFFER_FORMAT_ABGR32);

  if(!imdest) {
    heffer_error = HEFFER_CREATEIMAGE_ALPHAMASK | HEFFER_ERROR_MALLOC;
    return NULL;
  }

  //- Make the mask

  unsigned char r, g, b, a;

  r = (col>> 0)&0xFF;
  g = (col>> 8)&0xFF;
  b = (col>>16)&0xFF;
  a = (col>>24)&0xFF;

  unsigned char *buf = imsrc->buf;
  int bufw = imsrc->bufw;

  unsigned char *sbuf = imsrc->buf;
  unsigned char *dbuf = imdest->buf;

  int x, y;

  for(y=0; y<h; y++) {
    for(x=0; x<w; x++) {
      unsigned char c1 = *sbuf;
      *dbuf++ = ((int)c1*(int)a)/255;
      *dbuf++ = b;
      *dbuf++ = g;
      *dbuf++ = r;
      sbuf+=4;
    }
    sbuf += imsrc-> bufw - w*4;
    dbuf += imdest->bufw - w*4;
  }

  return imdest;
}



//---- Heffer_CreateImage_DropShadow -----------------------------------------//

HEFFER_IMAGE *Heffer_CreateImage_DropShadow(HEFFER_IMAGE *imsrc, int col, int x, int y, int blur)
{
  //- Create alpha mask

  HEFFER_IMAGE *imdest = Heffer_CreateImage_AlphaMask(imsrc, col);

  if(!imdest) {
    return NULL;
  }

  //- Blur mask

  Heffer_Filter_Blur(imdest, blur, x, y);

  return imdest;
}



//---- Heffer_CreateImage_OuterGlow ------------------------------------------//

HEFFER_IMAGE *Heffer_CreateImage_OuterGlow(HEFFER_IMAGE *imsrc, int col, int blur, unsigned char intens)
{
  //- Create alpha mask

  HEFFER_IMAGE *imdest = Heffer_CreateImage_AlphaMask(imsrc, col);

  if(!imdest) {
    return NULL;
  }

  //- Blur mask

  Heffer_Filter_Blur(imdest, blur, 0, 0);

  //- Add intensity

  unsigned char *buf = imdest->buf;
  int bufw = imdest->bufw;

  int w = imdest->w, h = imdest->h;

  int x, y;

  for(y=0; y<h; y++) {
    for(x=0; x<w; x++) {
      *buf = heffer_intens[intens][*buf];
      buf+=4;
    }
    buf += imdest->bufw - w*4;
  }

  return imdest;
}



//---- FONT & TEXT ROUTINES --------------------------------------------------//

//---- Heffer_LoadFontResource -----------------------------------------------//

HEFFER_FONT *Heffer_LoadFontResource(HINSTANCE hInst, char *name)
{
  HGLOBAL hResource = LoadResource(hInst, FindResource(hInst, name, "RISKFONT"));
  if(!hResource) {
    heffer_error = HEFFER_LOADFONTRESOURCE | HEFFER_ERROR_LOADRESOURCE;
    return NULL;
  }

  HEFFER_FONT *font = (HEFFER_FONT *)LockResource(hResource);
  if(!font) {
    heffer_error = HEFFER_LOADFONTRESOURCE | HEFFER_ERROR_LOCKRESOURCE;
    return NULL;
  }

  //  Check magic

  if(font->magic != HEFFER_FONT_MAGIC) {
    FreeResource(hResource);
    heffer_error = HEFFER_LOADFONTRESOURCE | HEFFER_ERROR_MAGIC;
    return NULL;
  }

  return font;
}



//---- Heffer_DeleteFont -----------------------------------------------------//



//---- Heffer_FontFromImage --------------------------------------------------//

HEFFER_FONT *Heffer_FontFromImage(HEFFER_IMAGE *im)
{



  return NULL;
}



//---- Heffer_GetTextSize ----------------------------------------------------//

void Heffer_GetTextSize(HEFFER_FONT *font, char *t, int *wp, int *hp)
{
  unsigned char *text = (unsigned char *)t;

  //- Get length of string

  int len;

  for(len=0; text[len]; len++);
  if(!len) {
    *wp = 0;
    *hp = font->h;
    return;
  }

  //- Calculate size of string

  int w = 0, h = font->h;
  int index;
   
  for(index=0; index<len; index++)
    w += font->w[text[index]];

  *wp = w; *hp = h;
}



//---- Heffer_TextOut --------------------------------------------------------//

/*
  TODO: different dest formats
  TODO: Better clipping!
*/

void Heffer_TextOut(HEFFER_IMAGE *im, int x, int y, HEFFER_FONT *font, char *t, int col, int flags)
{
  int len;
  int index, i, j;
  unsigned char *text = (unsigned char *)t;

  unsigned char r, g, b;

  r = (col>> 0)&0xFF;
  g = (col>> 8)&0xFF;
  b = (col>>16)&0xFF;

  if(!im) return;
  if(!font) return;
  if(!text) return;

  unsigned char *buf = im->buf;
  int bufw = im->bufw;

  //- Get length of string

  for(len=0; text[len]; len++);
  if(!len) return;

  //- Calculate size of string

  int w = 0, h = font->h;
  
  for(index=0; index<len; index++)
    w += font->w[text[index]];

  //- Position based on justification

  if(flags & HEFFER_ALIGN_HCENTER) {
    x -= w>>1;
  } else if(flags & HEFFER_ALIGN_RIGHT) {
    x -= w;
  }

  if(flags & HEFFER_ALIGN_VCENTER) {
    y -= h>>1;
  } else if(flags & HEFFER_ALIGN_BOTTOM) {
    y -= h;
  }

  //- Put characters in image

  int sx, sw;

  if(im->format == HEFFER_FORMAT_BGR24) {

    for(index=0; index<len; index++) {
      sx = font->x[text[index]];
      sw = font->w[text[index]];

      if(x >= im->x1 && x+sw < im->x2 && y >= im->y1 && y+h < im->y2) {

        for(j=0; j<h; j++) {
          int o = x*3 + (j+y)*bufw;

          for(i=0; i<sw; i++) {
            unsigned char a = font->buf[(i+sx)+j*font->bufw];
            unsigned char c1, c2;
            (c1 = buf[o], c2 = b, buf[o++] = ( (int)(c1)*(int)(255-a) + (int)(c2)*(int)(a) )/255);
            (c1 = buf[o], c2 = g, buf[o++] = ( (int)(c1)*(int)(255-a) + (int)(c2)*(int)(a) )/255);
            (c1 = buf[o], c2 = r, buf[o++] = ( (int)(c1)*(int)(255-a) + (int)(c2)*(int)(a) )/255);
          }
        }

      } else if(x > im->x1-sw && x < im->x2 && y > im->y1-h && y < im->y2) {

        //  TODO: calculate clipped area of character

        for(j=0; j<h; j++) {
          int o = x*3 + (j+y)*bufw;

          for(i=0; i<sw; i++) {

            if(x+i >= im->x1 && x+i < im->x2 && y+j >= im->y1 && y+j < im->y2) {
              unsigned char a = font->buf[(i+sx)+j*font->bufw];
              unsigned char c1, c2;
              (c1 = buf[o+0], c2 = b, buf[o+0] = ( (int)(c1)*(int)(255-a) + (int)(c2)*(int)(a) )/255);
              (c1 = buf[o+1], c2 = g, buf[o+1] = ( (int)(c1)*(int)(255-a) + (int)(c2)*(int)(a) )/255);
              (c1 = buf[o+2], c2 = r, buf[o+2] = ( (int)(c1)*(int)(255-a) + (int)(c2)*(int)(a) )/255);
            }
            o+=3;
          }
        }

      }
      x += sw;
    }

  } else if(im->format == HEFFER_FORMAT_ABGR32) {

    for(index=0; index<len; index++) {
      sx = font->x[text[index]];
      sw = font->w[text[index]];

      if(x >= im->x1 && x+sw < im->x2 && y >= im->y1 && y+h < im->y2) for(j=0; j<h; j++) {
        int o = x*4 + (j+y)*bufw;

        for(i=0; i<sw; i++) {
          unsigned char a = font->buf[(i+sx)+j*font->bufw];
          //  TODO: Combine properly not just overwrite!
          buf[o++] = a;
          buf[o++] = b;
          buf[o++] = g;
          buf[o++] = r;
        }
      }
      x += sw;
    }

  }

}



//---- Heffer_DrawText -------------------------------------------------------//

char heffer_drawtext_text[1024];

void Heffer_DrawText(HEFFER_IMAGE *im, int x1, int y1, int x2, int y2, HEFFER_FONT *font, char *t, int col, int flags)
{
  int w = x2-x1;
  int h = y2-y1;

  int tw, th=0;
  int x=x1, y=y1;

  //- Find line breaks

  int lastspacei, lastspacej;
  int i=0, j=0;

  while(t[i] && y+th <= y2) {

    //- Get characters until width too big or we meet a newline

    tw = 0;
    j = 0;
    lastspacei = -1;
    lastspacej = -1;

    heffer_drawtext_text[0] = 0;

    while(tw <= w && t[i] && t[i] != '\n') {
      if(t[i] == ' ') {
        lastspacei = i;
        lastspacej = j;
      }
      heffer_drawtext_text[j++] = t[i];
      heffer_drawtext_text[j] = 0;
      Heffer_GetTextSize(font, heffer_drawtext_text, &tw, &th);
      i++;
    }

    //  Lastspace is the character we need to break at (last space or newline)

    if(tw > w) {

      if(lastspacej > -1) {
        heffer_drawtext_text[lastspacej] = 0;
        i = lastspacei+1;
      } else {
        heffer_drawtext_text[j] = 0;
        i--;
      }
      Heffer_TextOut(im, x, y, font, heffer_drawtext_text, col, flags);

    } else if(!t[i]) {

      Heffer_TextOut(im, x, y, font, heffer_drawtext_text, col, flags);

    } else if(t[i] == '\n') {

      Heffer_TextOut(im, x, y, font, heffer_drawtext_text, col, flags);
      i++;

    }

    y += th;
  }

}



//---- Heffer_AlphaMerge -----------------------------------------------------//

//  Merges 2 images together using alpha into a destination image.  Images must
// all the the same size - function not crash-proofed.
//  Also all formats must be BGR24

//  alpha=0 - all image 1, alpha=255 - all image 2.

void Heffer_AlphaMerge(HEFFER_IMAGE *imdest, HEFFER_IMAGE *imsrc1, HEFFER_IMAGE *imsrc2, int alpha)
{
  int i, j;

  if(alpha < 0) alpha = 0;
  if(alpha > 255) alpha = 255;

  //  Addresses of buffers

  unsigned char *sbuf1 = imsrc1->buf;
  unsigned char *sbuf2 = imsrc2->buf;
  unsigned char *dbuf = imdest->buf;

  int w = imdest->w, h = imdest->h;

  //  Copy data - BGR24 + BGR24 -> BGR24

  for(j=0; j<h; j++) {
    for(i=0; i<w; i++) {
      unsigned char c1, c2;
      (c1 = *sbuf1++, c2 = *sbuf2++, *dbuf++ = ( (int)(c1)*(int)(255-alpha) + (int)(c2)*(int)(alpha) )/255);
      (c1 = *sbuf1++, c2 = *sbuf2++, *dbuf++ = ( (int)(c1)*(int)(255-alpha) + (int)(c2)*(int)(alpha) )/255);
      (c1 = *sbuf1++, c2 = *sbuf2++, *dbuf++ = ( (int)(c1)*(int)(255-alpha) + (int)(c2)*(int)(alpha) )/255);
    }
    sbuf1 += imsrc1->bufw - w*3;
    sbuf2 += imsrc2->bufw - w*3;
    dbuf += imdest->bufw - w*3;
  }

}



//---- Heffer_DistortNAll ----------------------------------------------------//

//  Merges 2 images together using alpha into a destination image.  Images must
// all the the same size - function not crash-proofed.
//  Also all formats must be BGR24

//  alpha=0 - all image 1, alpha=255 - all image 2.

void Heffer_DistortNAll(HEFFER_IMAGE *imdest, HEFFER_IMAGE *imsrc1, HEFFER_IMAGE *imsrc2, unsigned char *distortx, unsigned char *distorty, int alpha)
{
  int i, j;

  if(alpha < 0) alpha = 0;
  if(alpha > 255) alpha = 255;

  int da1, da2;

  da1 = alpha/5;
  da2 = (255-alpha)/5;

  if(da1 < 0)   da1 = 0;
  if(da1 > 255) da1 = 255;
  if(da2 < 0)   da2 = 0;
  if(da2 > 255) da2 = 255;

  //  Addresses of buffers

  unsigned char *sbuf1 = imsrc1->buf;
  unsigned char *sbuf2 = imsrc2->buf;
  unsigned char *dbuf = imdest->buf;

  int w = imdest->w, h = imdest->h;

  //  Set up distortion

  int pattern_sin[256];
  for(i=0; i<256; i++) pattern_sin[i] = (int)(128+127*sin(i*3.1415926/128));
  int time = alpha/5;

  //  Copy data - BGR24 + BGR24 -> BGR24

  for(j=0; j<h; j++) {
    for(i=0; i<w; i++) {

      unsigned char x1, y1, x2, y2;

      int distortx = (
        pattern_sin[(time*3 + i*5 + j*4)&255] +
        pattern_sin[(time*2 + i*2 + j*5)&255] +
        pattern_sin[(time*1 + i*1 + j*3)&255] +
        pattern_sin[(time*2 + i*3 + j*1)&255]
      ) * w / 1024;
      int distorty = (
        pattern_sin[(time*3 - i*1 - j*3)&255] +
        pattern_sin[(time*1 - i*2 + j*1)&255] +
        pattern_sin[(time*2 + i*1 + j*2)&255] +
        pattern_sin[(time*1 + i*3 - j*2)&255]
      ) * h / 1024;

      x1 = (((int)(distortx))*((int)(da1)) + ((int)i)*((int)(255-da1)))/255;
      y1 = (((int)(distorty))*((int)(da1)) + ((int)j)*((int)(255-da1)))/255;
      x2 = (((int)(distortx))*((int)(da2)) + ((int)i)*((int)(255-da2)))/255;
      y2 = (((int)(distorty))*((int)(da2)) + ((int)j)*((int)(255-da2)))/255;

      int os1 = x1+y1*w;
      int os2 = x2+y2*w;

//      os1 = i+j*w;
//      os2 = i+j*w;

      os1 *= 3; os2 *= 3;

      unsigned char c1, c2;
      (c1 = sbuf1[os1+0], c2 = sbuf2[os2+0], *dbuf++ = ( (int)(c1)*(int)(255-alpha) + (int)(c2)*(int)(alpha) )/255);
      (c1 = sbuf1[os1+1], c2 = sbuf2[os2+1], *dbuf++ = ( (int)(c1)*(int)(255-alpha) + (int)(c2)*(int)(alpha) )/255);
      (c1 = sbuf1[os1+2], c2 = sbuf2[os2+2], *dbuf++ = ( (int)(c1)*(int)(255-alpha) + (int)(c2)*(int)(alpha) )/255);

      distortx ++;
      distorty ++;
    }
    sbuf1 += imsrc1->bufw - w*3;
    sbuf2 += imsrc2->bufw - w*3;
    dbuf += imdest->bufw - w*3;
  }

}



//---- Heffer_TextOutBorder --------------------------------------------------//

void Heffer_TextOutBorder(HEFFER_IMAGE *im, int x, int y, HEFFER_FONT *font, char *t, int col, int bcol, int flags)
{
  Heffer_TextOut(im, x+1, y, font, t, bcol, flags);
  Heffer_TextOut(im, x-1, y, font, t, bcol, flags);
  Heffer_TextOut(im, x, y+1, font, t, bcol, flags);
  Heffer_TextOut(im, x, y-1, font, t, bcol, flags);
  Heffer_TextOut(im, x, y, font, t, col, flags);
}


//---- END OF FILE -----------------------------------------------------------//
