/*
 * tkWinImage.c --
 *
 *	This file contains routines for manipulation full-color images.
 *
 * Copyright © 1995 Sun Microsystems, Inc.
 *
 * See the file "license.terms" for information on usage and redistribution
 * of this file, and for a DISCLAIMER OF ALL WARRANTIES.
 */

#include "tkWinInt.h"

static int		DestroyImage(XImage* data);
static unsigned long	ImageGetPixel(XImage *image, int x, int y);
static int		PutPixel(XImage *image, int x, int y,
			    unsigned long pixel);

/*
 *----------------------------------------------------------------------
 *
 * DestroyImage --
 *
 *	This is a trivial wrapper around ckfree to make it possible to pass
 *	ckfree as a pointer.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Deallocates the image.
 *
 *----------------------------------------------------------------------
 */

static int
DestroyImage(
    XImage *imagePtr)		/* Image to free. */
{
    if (imagePtr) {
	if (imagePtr->data) {
	    ckfree(imagePtr->data);
	}
	ckfree(imagePtr);
    }
    return 0;
}

/*
 *----------------------------------------------------------------------
 *
 * ImageGetPixel --
 *
 *	Get a single pixel from an image.
 *
 * Results:
 *	Returns the 32 bit pixel value.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

static unsigned long
ImageGetPixel(
    XImage *image,
    int x, int y)
{
    unsigned long pixel = 0;
    unsigned char *srcPtr = (unsigned char *) &(image->data[(y * image->bytes_per_line)
	    + ((x * image->bits_per_pixel) / NBBY)]);

    switch (image->bits_per_pixel) {
    case 32:
    case 24:
	pixel = RGB(srcPtr[2], srcPtr[1], srcPtr[0]);
	break;
    case 16:
	pixel = RGB(((((WORD*)srcPtr)[0]) >> 7) & 0xf8,
		((((WORD*)srcPtr)[0]) >> 2) & 0xf8,
		((((WORD*)srcPtr)[0]) << 3) & 0xf8);
	break;
    case 8:
	pixel = srcPtr[0];
	break;
    case 4:
	pixel = ((x%2) ? (*srcPtr) : ((*srcPtr) >> 4)) & 0x0f;
	break;
    case 1:
	pixel = ((*srcPtr) & (0x80 >> (x%8))) ? 1 : 0;
	break;
    }
    return pixel;
}

/*
 *----------------------------------------------------------------------
 *
 * PutPixel --
 *
 *	Set a single pixel in an image.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

static int
PutPixel(
    XImage *image,
    int x, int y,
    unsigned long pixel)
{
    unsigned char *destPtr = (unsigned char *) &(image->data[(y * image->bytes_per_line)
	    + ((x * image->bits_per_pixel) / NBBY)]);

    switch  (image->bits_per_pixel) {
    case 32:
	/*
	 * Pixel is DWORD: 0x00BBGGRR
	 */

	destPtr[3] = 0;
	/* FALLTHRU */
    case 24:
	/*
	 * Pixel is triplet: 0xBBGGRR.
	 */

	destPtr[0] = (unsigned char) GetBValue(pixel);
	destPtr[1] = (unsigned char) GetGValue(pixel);
	destPtr[2] = (unsigned char) GetRValue(pixel);
	break;
    case 16:
	/*
	 * Pixel is WORD: 5-5-5 (R-G-B)
	 */

	(*(WORD*)destPtr) = ((GetRValue(pixel) & 0xf8) << 7)
		| ((GetGValue(pixel) & 0xf8) <<2)
		| ((GetBValue(pixel) & 0xf8) >> 3);
	break;
    case 8:
	/*
	 * Pixel is 8-bit index into color table.
	 */

	(*destPtr) = (unsigned char) pixel;
	break;
    case 4:
	/*
	 * Pixel is 4-bit index in MSBFirst order.
	 */

	if (x%2) {
	    (*destPtr) = (unsigned char) (((*destPtr) & 0xf0)
		    | (pixel & 0x0f));
	} else {
	    (*destPtr) = (unsigned char) (((*destPtr) & 0x0f)
		    | ((pixel << 4) & 0xf0));
	}
	break;
    case 1: {
	/*
	 * Pixel is bit in MSBFirst order.
	 */

	int mask = (0x80 >> (x%8));

	if (pixel) {
	    (*destPtr) |= mask;
	} else {
	    (*destPtr) &= ~mask;
	}
	break;
    }
    }
    return 0;
}

/*
 *----------------------------------------------------------------------
 *
 * XCreateImage --
 *
 *	Allocates storage for a new XImage.
 *
 * Results:
 *	Returns a newly allocated XImage.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

XImage *
XCreateImage(
    TCL_UNUSED(Display *),
    TCL_UNUSED(Visual *),
    unsigned int depth,
    int format,
    int offset,
    char *data,
    unsigned int width,
    unsigned int height,
    int bitmap_pad,
    int bytes_per_line)
{
    XImage* imagePtr = (XImage*)ckalloc(sizeof(XImage));

    imagePtr->width = width;
    imagePtr->height = height;
    imagePtr->xoffset = offset;
    imagePtr->format = format;
    imagePtr->data = data;
    imagePtr->byte_order = LSBFirst;
    imagePtr->bitmap_unit = 8;
    imagePtr->bitmap_bit_order = LSBFirst;
    imagePtr->bitmap_pad = bitmap_pad;
    imagePtr->bits_per_pixel = depth;
    imagePtr->depth = depth;

    /*
     * Under Windows, bitmap_pad must be on an LONG data-type boundary.
     */

#define LONGBITS    (sizeof(LONG) * 8)

    bitmap_pad = (bitmap_pad + LONGBITS - 1) / LONGBITS * LONGBITS;

    /*
     * Round to the nearest bitmap_pad boundary.
     */

    if (bytes_per_line) {
	imagePtr->bytes_per_line = bytes_per_line;
    } else {
	imagePtr->bytes_per_line = (((depth * width)
		+ (bitmap_pad - 1)) >> 3) & ~((bitmap_pad >> 3) - 1);
    }

    imagePtr->red_mask = 0;
    imagePtr->green_mask = 0;
    imagePtr->blue_mask = 0;

    imagePtr->f.put_pixel = PutPixel;
    imagePtr->f.get_pixel = ImageGetPixel;
    imagePtr->f.destroy_image = DestroyImage;
    imagePtr->f.create_image = NULL;
    imagePtr->f.sub_image = NULL;
    imagePtr->f.add_pixel = NULL;

    return imagePtr;
}

/*
 *----------------------------------------------------------------------
 *
 * XGetImageZPixmap --
 *
 *	This function copies data from a pixmap or window into an XImage. This
 *	handles the ZPixmap case only.
 *
 * Results:
 *	Returns a newly allocated image containing the data from the given
 *	rectangle of the given drawable.
 *
 * Side effects:
 *	None.
 *
 * This procedure is adapted from the XGetImage implementation in TkNT. That
 * code is Copyright © 1994 Software Research Associates, Inc.
 *
 *----------------------------------------------------------------------
 */

static XImage *
XGetImageZPixmap(
    Display *display,
    Drawable d,
    int x, int y,
    unsigned int width, unsigned int height,
    TCL_UNUSED(unsigned long),
    int	format)
{
    TkWinDrawable *twdPtr = (TkWinDrawable *)d;
    XImage *ret_image;
    HDC hdc, hdcMem;
    HBITMAP hbmp, hbmpPrev;
    BITMAPINFO *bmInfo = NULL;
    HPALETTE hPal, hPalPrev1 = 0, hPalPrev2 = 0;
    int size;
    unsigned int n;
    unsigned int depth;
    unsigned char *data;
    TkWinDCState state;
    BOOL ret;

    if (format != ZPixmap) {
	TkpDisplayWarning("Only ZPixmap types are implemented",
		"XGetImageZPixmap Failure");
	return NULL;
    }

    hdc = TkWinGetDrawableDC(display, d, &state);

    /*
     * Need to do a Blt operation to copy into a new bitmap.
     */

    hbmp = CreateCompatibleBitmap(hdc, (int) width, (int) height);
    hdcMem = CreateCompatibleDC(hdc);
    hbmpPrev = (HBITMAP)SelectObject(hdcMem, hbmp);
    hPal = state.palette;
    if (hPal) {
	hPalPrev1 = SelectPalette(hdcMem, hPal, FALSE);
	n = RealizePalette(hdcMem);
	if (n > 0) {
	    UpdateColors(hdcMem);
	}
	hPalPrev2 = SelectPalette(hdc, hPal, FALSE);
	n = RealizePalette(hdc);
	if (n > 0) {
	    UpdateColors(hdc);
	}
    }

    ret = BitBlt(hdcMem, 0, 0, (int) width, (int) height, hdc, x, y, SRCCOPY);
    if (hPal) {
	SelectPalette(hdc, hPalPrev2, FALSE);
    }
    SelectObject(hdcMem, hbmpPrev);
    TkWinReleaseDrawableDC(d, hdc, &state);
    if (ret == FALSE) {
	ret_image = NULL;
	goto cleanup;
    }
    if (twdPtr->type == TWD_WINDOW) {
	depth = Tk_Depth((Tk_Window) twdPtr->window.winPtr);
    } else {
	depth = twdPtr->bitmap.depth;
    }

    size = sizeof(BITMAPINFO);
    if (depth <= 8) {
	size += sizeof(unsigned short) << depth;
    }
    bmInfo = (BITMAPINFO *)ckalloc(size);

    bmInfo->bmiHeader.biSize		= sizeof(BITMAPINFOHEADER);
    bmInfo->bmiHeader.biWidth		= width;
    bmInfo->bmiHeader.biHeight		= -(int) height;
    bmInfo->bmiHeader.biPlanes		= 1;
    bmInfo->bmiHeader.biBitCount	= depth;
    bmInfo->bmiHeader.biCompression	= BI_RGB;
    bmInfo->bmiHeader.biSizeImage	= 0;
    bmInfo->bmiHeader.biXPelsPerMeter	= 0;
    bmInfo->bmiHeader.biYPelsPerMeter	= 0;
    bmInfo->bmiHeader.biClrUsed		= 0;
    bmInfo->bmiHeader.biClrImportant	= 0;

    if (depth == 1) {
	unsigned char *p, *pend;

	GetDIBits(hdcMem, hbmp, 0, height, NULL, bmInfo, DIB_PAL_COLORS);
	data = (unsigned char *)ckalloc(bmInfo->bmiHeader.biSizeImage);
	if (!data) {
	    /* printf("Failed to allocate data area for XImage.\n"); */
	    ret_image = NULL;
	    goto cleanup;
	}
	ret_image = XCreateImage(display, NULL, depth, ZPixmap, 0, (char *) data,
		width, height, 32, (int) ((width + 31) >> 3) & ~1);
	if (ret_image == NULL) {
	    ckfree(data);
	    goto cleanup;
	}

	/*
	 * Get the BITMAP info into the Image.
	 */

	if (GetDIBits(hdcMem, hbmp, 0, height, data, bmInfo,
		DIB_PAL_COLORS) == 0) {
	    ckfree(ret_image->data);
	    ckfree(ret_image);
	    ret_image = NULL;
	    goto cleanup;
	}
	p = data;
	pend = data + bmInfo->bmiHeader.biSizeImage;
	while (p < pend) {
	    *p = ~*p;
	    p++;
	}
    } else if (depth == 8) {
	unsigned short *palette;
	unsigned int i;
	unsigned char *p;

	GetDIBits(hdcMem, hbmp, 0, height, NULL, bmInfo, DIB_PAL_COLORS);
	data = (unsigned char *)ckalloc(bmInfo->bmiHeader.biSizeImage);
	if (!data) {
	    /* printf("Failed to allocate data area for XImage.\n"); */
	    ret_image = NULL;
	    goto cleanup;
	}
	ret_image = XCreateImage(display, NULL, 8, ZPixmap, 0, (char *) data,
		width, height, 8, (int) width);
	if (ret_image == NULL) {
	    ckfree(data);
	    goto cleanup;
	}

	/*
	 * Get the BITMAP info into the Image.
	 */

	if (GetDIBits(hdcMem, hbmp, 0, height, data, bmInfo,
		DIB_PAL_COLORS) == 0) {
	    ckfree(ret_image->data);
	    ckfree(ret_image);
	    ret_image = NULL;
	    goto cleanup;
	}
	p = data;
	palette = (unsigned short *) bmInfo->bmiColors;
	for (i = 0; i < bmInfo->bmiHeader.biSizeImage; i++, p++) {
	    *p = (unsigned char) palette[*p];
	}
    } else if (depth == 16) {
	GetDIBits(hdcMem, hbmp, 0, height, NULL, bmInfo, DIB_RGB_COLORS);
	data = (unsigned char *)ckalloc(bmInfo->bmiHeader.biSizeImage);
	if (!data) {
	    /* printf("Failed to allocate data area for XImage.\n"); */
	    ret_image = NULL;
	    goto cleanup;
	}
	ret_image = XCreateImage(display, NULL, 16, ZPixmap, 0, (char *) data,
		width, height, 16, 0 /* will be calc'ed from bitmap_pad */);
	if (ret_image == NULL) {
	    ckfree(data);
	    goto cleanup;
	}

	/*
	 * Get the BITMAP info directly into the Image.
	 */

	if (GetDIBits(hdcMem, hbmp, 0, height, ret_image->data, bmInfo,
		DIB_RGB_COLORS) == 0) {
	    ckfree(ret_image->data);
	    ckfree(ret_image);
	    ret_image = NULL;
	    goto cleanup;
	}
    } else {
	GetDIBits(hdcMem, hbmp, 0, height, NULL, bmInfo, DIB_RGB_COLORS);
	data = (unsigned char *)ckalloc(width * height * 4);
	if (!data) {
	    /* printf("Failed to allocate data area for XImage.\n"); */
	    ret_image = NULL;
	    goto cleanup;
	}
	ret_image = XCreateImage(display, NULL, 32, ZPixmap, 0, (char *) data,
		width, height, 0, (int) width * 4);
	if (ret_image == NULL) {
	    ckfree(data);
	    goto cleanup;
	}

	if (depth <= 24) {
	    /*
	     * This used to handle 16 and 24 bpp, but now just handles 24. It
	     * can likely be optimized for that. -- hobbs
	     */

	    unsigned char *smallBitData, *smallBitBase, *bigBitData;
	    unsigned int byte_width, h, w;

	    byte_width = ((width * 3 + 3) & ~(unsigned)3);
	    smallBitBase = (unsigned char *)ckalloc(byte_width * height);
	    if (!smallBitBase) {
		ckfree(ret_image->data);
		ckfree(ret_image);
		ret_image = NULL;
		goto cleanup;
	    }
	    smallBitData = smallBitBase;

	    /*
	     * Get the BITMAP info into the Image.
	     */

	    if (GetDIBits(hdcMem, hbmp, 0, height, smallBitData, bmInfo,
		    DIB_RGB_COLORS) == 0) {
		ckfree(ret_image->data);
		ckfree(ret_image);
		ckfree(smallBitBase);
		ret_image = NULL;
		goto cleanup;
	    }

	    /*
	     * Copy the 24 Bit Pixmap to a 32-Bit one.
	     */

	    for (h = 0; h < height; h++) {
		bigBitData   = (unsigned char *) ret_image->data + h * ret_image->bytes_per_line;
		smallBitData = smallBitBase + h * byte_width;

		for (w = 0; w < width; w++) {
		    *bigBitData++ = ((*smallBitData++));
		    *bigBitData++ = ((*smallBitData++));
		    *bigBitData++ = ((*smallBitData++));
		    *bigBitData++ = 0;
		}
	    }

	    /*
	     * Free the Device contexts, and the Bitmap.
	     */

	    ckfree(smallBitBase);
	} else {
	    /*
	     * Get the BITMAP info directly into the Image.
	     */

	    if (GetDIBits(hdcMem, hbmp, 0, height, ret_image->data, bmInfo,
		    DIB_RGB_COLORS) == 0) {
		ckfree(ret_image->data);
		ckfree(ret_image);
		ret_image = NULL;
		goto cleanup;
	    }
	}
    }

  cleanup:
    if (bmInfo) {
	ckfree(bmInfo);
    }
    if (hPal) {
	SelectPalette(hdcMem, hPalPrev1, FALSE);
    }
    DeleteDC(hdcMem);
    DeleteObject(hbmp);

    return ret_image;
}

/*
 *----------------------------------------------------------------------
 *
 * XGetImage --
 *
 *	This function copies data from a pixmap or window into an XImage.
 *
 * Results:
 *	Returns a newly allocated image containing the data from the given
 *	rectangle of the given drawable.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

XImage *
XGetImage(
    Display* display,
    Drawable d,
    int x, int y,
    unsigned int width, unsigned int height,
    unsigned long plane_mask,
    int	format)
{
    TkWinDrawable *twdPtr = (TkWinDrawable *)d;
    XImage *imagePtr;
    HDC dc;

    LastKnownRequestProcessed(display)++;

    if (twdPtr == NULL) {
	/*
	 * Avoid unmapped windows or bad drawables
	 */

	return NULL;
    }

    if (twdPtr->type != TWD_BITMAP) {
	/*
	 * This handles TWD_WINDOW or TWD_WINDC, always creating a 32bit
	 * image. If the window being copied isn't visible (unmapped or
	 * obscured), we quietly stop copying (no user error). The user will
	 * see black where the widget should be. This branch is likely
	 * followed in favor of XGetImageZPixmap as postscript printed widgets
	 * require RGB data.
	 */

	TkWinDCState state;
	unsigned int xx, yy, size;
	COLORREF pixel;

	dc = TkWinGetDrawableDC(display, d, &state);

	imagePtr = XCreateImage(display, NULL, 32, format, 0, NULL,
		width, height, 32, 0);
	size = imagePtr->bytes_per_line * imagePtr->height;
	imagePtr->data = (char *)ckalloc(size);
	memset(imagePtr->data, 0, size);

	for (yy = 0; yy < height; yy++) {
	    for (xx = 0; xx < width; xx++) {
		pixel = GetPixel(dc, x+(int)xx, y+(int)yy);
		if (pixel == CLR_INVALID) {
		    break;
		}
		PutPixel(imagePtr, (int) xx, (int) yy, pixel);
	    }
	}

	TkWinReleaseDrawableDC(d, dc, &state);
    } else if (format == ZPixmap) {
	/*
	 * This actually handles most TWD_WINDOW requests, but it varies from
	 * the above in that it really does a screen capture of an area, which
	 * is consistent with the Unix behavior, but does not appear to handle
	 * all bit depths correctly. -- hobbs
	 */

	imagePtr = XGetImageZPixmap(display, d, x, y,
		width, height, plane_mask, format);
    } else {
	const char *errMsg = NULL;
	char infoBuf[sizeof(BITMAPINFO) + sizeof(RGBQUAD)];
	BITMAPINFO *infoPtr = (BITMAPINFO*)infoBuf;

	if (twdPtr->bitmap.handle == NULL) {
	    errMsg = "XGetImage: not implemented for empty bitmap handles";
	} else if (format != XYPixmap) {
	    errMsg = "XGetImage: not implemented for format != XYPixmap";
	} else if (plane_mask != 1) {
	    errMsg = "XGetImage: not implemented for plane_mask != 1";
	}
	if (errMsg != NULL) {
	    /*
	     * Do a soft warning for the unsupported XGetImage types.
	     */

	    TkpDisplayWarning(errMsg, "XGetImage Failure");
	    return NULL;
	}

	imagePtr = XCreateImage(display, NULL, 1, XYBitmap, 0, NULL,
		width, height, 32, 0);
	imagePtr->data = (char *)ckalloc(imagePtr->bytes_per_line * imagePtr->height);

	dc = GetDC(NULL);

	GetDIBits(dc, twdPtr->bitmap.handle, 0, height, NULL,
		infoPtr, DIB_RGB_COLORS);

	infoPtr->bmiHeader.biSize		= sizeof(BITMAPINFOHEADER);
	infoPtr->bmiHeader.biWidth		= width;
	infoPtr->bmiHeader.biHeight		= -(LONG)height;
	infoPtr->bmiHeader.biPlanes		= 1;
	infoPtr->bmiHeader.biBitCount		= 1;
	infoPtr->bmiHeader.biCompression	= BI_RGB;
	infoPtr->bmiHeader.biSizeImage		= 0;
	infoPtr->bmiHeader.biXPelsPerMeter	= 0;
	infoPtr->bmiHeader.biYPelsPerMeter	= 0;
	infoPtr->bmiHeader.biClrUsed		= 0;
	infoPtr->bmiHeader.biClrImportant	= 0;

	GetDIBits(dc, twdPtr->bitmap.handle, 0, height, imagePtr->data,
		infoPtr, DIB_RGB_COLORS);
	ReleaseDC(NULL, dc);
    }

    return imagePtr;
}

/*
 * Local Variables:
 * mode: c
 * c-basic-offset: 4
 * fill-column: 78
 * End:
 */
