/*
 * GraphApp - Cross-Platform Graphics Programming Library.
 *
 * File: bitmaps.c -- functions for creating a detroying bitmaps.
 * Platform: Windows  Version: 2.40  Date: 1998/05/05
 *
 * Version: 1.00  Changes: Original version by Lachlan Patrick.
 * Version: 2.00  Changes: New object class system implemented.
 * Version: 2.15  Changes: Improved bitmap data formats.
 * Version: 2.30  Changes: Now uses bitmap_base.
 * Version: 2.40  Changes: Fast image to bitmap conversion.
 */

/* Copyright (C) 1993-1998 Lachlan Patrick

   This file is part of GraphApp, a cross-platform C graphics library.

   GraphApp is free software; you can redistribute it and/or modify it
   under the terms of the GNU Library General Public License.
   GraphApp is distributed in the hope that it will be useful, but
   WITHOUT ANY WARRANTY.

   See the file COPYLIB.TXT for details.
*/

#include "internal.h"

/*
 *  Internal bitmap deletion function.
 */
static void private_delbitmap(bitmap obj)
{
    if (obj->handle)
	DeleteObject((HBITMAP) obj->handle);
}

/*
 *  Create/return the base bitmap object.
 */
static object get_bitmap_base(void)
{
    static object bitmap_base = NULL;

    if (! bitmap_base)
	bitmap_base = new_object(BaseObject, 0, NULL);
    return bitmap_base;
}

/*
 *  Create a white bitmap.
 */
bitmap newbitmap(int width, int height, int depth)
{
    BITMAP bm;
    HBITMAP hb, old;
    HDC dc, screendc;

    screendc = GetDC(0); /* get screen dc */

    if (depth == 1) /* monochrome bitmap required */
    {
	hb = CreateBitmap(width, height, 1, 1, 0);
    }
    else /* colour bitmap required */
    {
	hb = CreateCompatibleBitmap(screendc, width, height);
    }

    if (! hb) {
	ReleaseDC(0, screendc);
	return NULL;
    }

    dc = CreateCompatibleDC(screendc);
    old = SelectObject(dc, hb);
    PatBlt(dc, 0, 0, width, height, WHITENESS);
    SelectObject(dc, old);
    DeleteDC(dc);

    ReleaseDC(0, screendc);

    GetObject(hb, sizeof(BITMAP), (LPSTR) &bm);

    bitmap obj = new_object(BitmapObject, hb, get_bitmap_base());
    if (! obj) {
	DeleteObject(hb);
	return NULL;
    }
    obj->rect = rect(0,0,width,height);
    obj->depth = (bm.bmPlanes * bm.bmBitsPixel);
    obj->die = private_delbitmap;

    return obj;
}

/*
 *  Check an image to see if there are any transparent pixels.
 *  Return 1 as soon as a transparent pixel is found, 0 if none are.
 */
PROTECTED
int has_transparent_pixels(image img)
{
    int i, width, height, total;
    rgb *palette;
    GAbyte *pixel8;
    rgb *pixel32;
    rgb col;

    if (! img)
	return 0;

    width = getwidth(img);
    height = getheight(img);
    total = width * height;

    palette = getpalette(img);

    pixel8 = getpixels(img);
    pixel32 = (rgb *) pixel8;

    if (getdepth(img) == 8) {
	for (i=0; i < total; i++) {
	    col = pixel8[i];
	    col = palette[col];
	    if (getalpha(col) > 0x7F)
		return 1;
	}
    }
    else {
	for (i=0; i < total; i++) {
	    col = pixel32[i];
	    if (getalpha(col) > 0x7F)
		return 1;
	}
    }

    return 0;
}

/*
 *  Create a bitmap version of an image.
 */
// See also http://msdn.microsoft.com/en-us/library/dd183353%28v=VS.85%29.aspx
bitmap imagetobitmap(image img)
{
    unsigned row_bytes;
    unsigned size;

    if (!img) return NULL;

    /* remember some facts about the image */
    int width = getwidth(img), height = getheight(img);
    int depth = getdepth(img);
    rgb *palette = getpalette(img);
    int palsize = getpalettesize(img);
    GAbyte *pixel8 = getpixels(img);
    rgb *pixel32 = (rgb *) pixel8;

    /* create DIB info in memory */
    size = sizeof(BITMAPINFOHEADER) + 4; /* Why + 4 ? */
    size += (sizeof(RGBQUAD) * palsize);
    /* rows need to be whole (4-byte) words */
    if (depth == 8) row_bytes = ((width + 3) / 4) * 4;
    else if (depth == 24) row_bytes = (((width * 3) + 3) / 4) * 4;
    else row_bytes = 4 * width;
    size += (row_bytes * height);

    /* create the block, align on LONG boundary
       malloc will have done the alignment, which needs to 
       be for pointers.
    */
    GAbyte *block = array(size, GAbyte);
    BITMAPINFO *bmi = (BITMAPINFO *) block;
	

    /* assign header info */
    bmi->bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
    bmi->bmiHeader.biWidth = width;
    bmi->bmiHeader.biHeight = -height;  // use negative value to flip
    bmi->bmiHeader.biPlanes = 1;
    bmi->bmiHeader.biBitCount = depth;
    bmi->bmiHeader.biCompression = BI_RGB;
    bmi->bmiHeader.biClrUsed = palsize;

    /* assign colour table */
    for (int i = 0; i < palsize; i++) {
	rgb colour  = palette[i];
	GAbyte *data = (GAbyte *) (& bmi->bmiColors[i]);
	data[0] = getblue(colour);
	data[1] = getgreen(colour);
	data[2] = getred(colour);
	data[3] = 0;
    }

    /* assign the bitmap data itself */
    GAbyte *data = (GAbyte *) (&bmi->bmiColors[palsize]);

    if (depth == 8)
	for (unsigned y = 0; y < height; y++) {
	    // memcpy(data + y * row_bytes, pixel8 + y * width, width);
	    for (unsigned x = 0; x < width; x++)
		data[y * row_bytes + x] = pixel8[y * width + x];
	}
    else if (depth == 24)
	for (unsigned y = 0; y < height; y++) {
	    for (unsigned x = 0; x < width; x++) {
		rgb colour = pixel32[y * width + x];
		int i = y * row_bytes + 3*x;
		data[i]   = getblue(colour);
		data[i+1] = getgreen(colour);
		data[i+2] = getred(colour);
	    }
	}
    else if (height && width)
	memcpy(data, pixel32, 4*height*width);

    /* Create the bitmap from the DIB data.
       Could also use CreateDIBsection */

    HDC screendc = GetDC(0);
    HBITMAP hb = CreateDIBitmap(screendc, &bmi->bmiHeader, CBM_INIT,
				data, bmi, DIB_RGB_COLORS);
    ReleaseDC(0, screendc);

    /* tidy up */
    discard(block);

    /* create the bitmap */
    bitmap obj = new_object(BitmapObject, hb, get_bitmap_base());
    if (!obj) {
	DeleteObject(hb);
	return NULL;
    }
    obj->rect = rect(0, 0, width, height);
    obj->depth = depth;
    obj->die = private_delbitmap;

    return obj;
}

#ifdef UNUSED
/*
 *  The loadicon function returns a HBITMAP of an ICON from resources.
 */
static bitmap loadicon(const char *name)
{
    HICON i;
    HDC dc;
    HBITMAP old;
    bitmap obj;

    if (this_instance == 0)
	return NULL;
    if ((i = LoadIcon(this_instance, name)) == 0)
	return NULL;
    if ((obj = newbitmap(32, 32, 0)) == NULL)
	return NULL;

    dc = CreateCompatibleDC(0);
    old = SelectObject(dc, (HBITMAP)obj->handle);
    DrawIcon(dc, 0, 0, i);
    SelectObject(dc, old);
    DeleteDC(dc);
    DestroyIcon(i);

    return obj;
}

/*
 *  The loadpict function returns BITMAP resources.
 */
static bitmap loadpict(const char *name)
{
    BITMAP bm;
    HBITMAP hb;
    bitmap obj;

    if (this_instance == 0)
	return NULL;
    hb = LoadBitmap(this_instance, name);
    GetObject(hb, sizeof(BITMAP), (LPSTR) &bm);

    obj = new_object(BitmapObject, hb, get_bitmap_base());
    if (obj) {
	obj->rect = rect(0, 0, bm.bmWidth, bm.bmHeight);
	obj->depth = (bm.bmPlanes * bm.bmBitsPixel);
	obj->die = private_delbitmap;
    }
    return obj;
}

/*
 *  The loadbitmap function finds and returns bitmaps from resources,
 *  or from an image file.
 */
bitmap loadbitmap(const char *name)
{
    bitmap obj;

    if ((obj = loadpict(name)) != NULL)
	return obj;
    if ((obj = loadicon(name)) != NULL)
	return obj;
    if ((obj = imagetobitmap(loadimage(name))) != NULL)
	return obj;
    return NULL;
}

/*
 *  The following functions are obsolete from version 2.4.
 */

void setbitmapdata(bitmap obj, unsigned char *data)
{
    rect r;
    int depth, size, row_bytes;
    int x, y;
    unsigned char *newdata = NULL;

    r = obj->rect;
    depth = obj->depth;

    /* Calculate source row bytes. */
    row_bytes = ((depth * r.width) + 7) / 8;
    /* Each row must be a multiple of 16 bits wide. */
    if (row_bytes % 2) {
	/* Odd number of bytes, must assign into new array. */
	size = (row_bytes+1) * r.height;
	newdata = array (size, GAbyte);
	if (! newdata)
	    return;
	for (y=0; y<r.height; y++) {
	    for (x=0; x<row_bytes; x++) {
		newdata[y*(row_bytes+1)+x] =
		    data[y*row_bytes+x];
	    }
	}
	SetBitmapBits((HBITMAP)obj->handle, size,
		      (LPSTR)newdata);
	discard(newdata);
    } else {
	/* Correct format already! */
	size = row_bytes * r.height;
	SetBitmapBits((HBITMAP)obj->handle, size, (LPSTR)data);
    }
}

void getbitmapdata(bitmap obj, unsigned char *data)
{
    rect r;
    int depth, size, row_bytes;
    int x, y;
    unsigned char *newdata = NULL;

    r = obj->rect;
    depth = obj->depth;

    /* Calculate destination row bytes. */
    row_bytes = ((depth * r.width) + 7) / 8;
    /* Each row must be a multiple of 16 bits wide. */
    if (row_bytes % 2) {
	/* Odd number of bytes, must assign into new array. */
	size = (row_bytes+1) * r.height;
	newdata = array (size, GAbyte);
	if (! newdata)
	    return;
	GetBitmapBits((HBITMAP)obj->handle, size,
		      (LPSTR)newdata);
	for (y=0; y<r.height; y++) {
	    for (x=0; x<row_bytes; x++) {
		data[y*row_bytes+x] =
		    newdata[y*(row_bytes+1)+x];
	    }
	}
	discard(newdata);
    } else {
	/* Correct format already! */
	size = row_bytes * r.height;
	GetBitmapBits((HBITMAP)obj->handle, size, (LPSTR)data);
    }
}

bitmap createbitmap(int width, int height, int depth, unsigned char *data)
{
    bitmap obj;

    if ((obj = newbitmap(width, height, depth)) == NULL)
	return NULL;
    if (data)
	setbitmapdata(obj, data);
    return obj;
}
#endif

void getbitmapdata2(bitmap obj, unsigned char **data)
{
    rect r = obj->rect;
    int depth = 32, size, ret;
    BITMAPINFO bmi;

    bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
    bmi.bmiHeader.biWidth = r.width;
    bmi.bmiHeader.biHeight = -r.height;
    bmi.bmiHeader.biPlanes = 1;
    bmi.bmiHeader.biBitCount = depth;
    bmi.bmiHeader.biCompression = BI_RGB;
    bmi.bmiHeader.biClrUsed = 0;

    size = 4 * r.width * r.height;
    *data = (unsigned char *) malloc(size);
    if(*data) {
	ret = GetDIBits(get_context(obj), (HBITMAP)obj->handle, 
			0, r.height, (LPSTR)*data, &bmi, DIB_RGB_COLORS);
	if(!ret) {
	    free(*data);
	    *data = NULL;
	}
    }
}

