/*
 * rbcWinImage.c --
 *
 *      This module implements image processing procedures for the rbc
 *      toolkit.
 *
 * Copyright (c) 2009 Samuel Green, Nicholas Hudson, Stanton Sievers, Jarrod Stormo
 * All rights reserved.
 *
 * See "license.terms" for details.
 */

#include "rbcInt.h"
#include "rbcImage.h"
#include <X11/Xutil.h>

#define CLAMP(c)	((((c) < 0.0) ? 0.0 : ((c) > 255.0) ? 255.0 : (c)))

#define GetBit(x, y) \
   srcBits[(srcBytesPerRow * (srcHeight - y - 1)) + (x>>3)] & (0x80 >> (x&7))
#define SetBit(x, y) \
   destBits[(destBytesPerRow * (destHeight - y - 1)) + (x>>3)] |= (0x80 >>(x&7))

/*
 *----------------------------------------------------------------------
 *
 * Rbc_ColorImageToPixmap --
 *
 *      Converts a color image into a pixmap.
 *      Right now this only handles TrueColor visuals.
 *
 * Results:
 *      The new pixmap is returned.
 *
 * Side effects:
 *      TODO: Side Effects
 *
 *----------------------------------------------------------------------
 */
Pixmap
Rbc_ColorImageToPixmap(interp, tkwin, image, colorTablePtr)
    Tcl_Interp *interp;
    Tk_Window tkwin;
    Rbc_ColorImage image;
    ColorTable *colorTablePtr; /* Points to array of colormap indices */
{
    HDC pixmapDC;
    TkWinDCState state;
    Display *display;
    int width, height, depth;
    Pixmap pixmap;
    register int x, y;
    register Pix32 *srcPtr;
    COLORREF rgb;

    *colorTablePtr = NULL;
    width = Rbc_ColorImageWidth(image);
    height = Rbc_ColorImageHeight(image);
    display = Tk_Display(tkwin);
    depth = Tk_Depth(tkwin);

    pixmap = Tk_GetPixmap(display, Tk_WindowId(tkwin), width, height, depth);
    pixmapDC = TkWinGetDrawableDC(display, pixmap, &state);

    srcPtr = Rbc_ColorImageBits(image);
    for (y = 0; y < height; y++) {
	for (x = 0; x < width; x++) {
	    rgb = PALETTERGB(srcPtr->Red, srcPtr->Green, srcPtr->Blue);
	    SetPixelV(pixmapDC, x, y, rgb);
	    srcPtr++;
	}
    }
    TkWinReleaseDrawableDC(pixmap, pixmapDC, &state);
    return pixmap;
}

/*
 *----------------------------------------------------------------------
 *
 * Rbc_ColorImageToPixmap2 --
 *
 *      Converts a color image into a pixmap.
 *      Right now this only handles TrueColor visuals.
 *
 * Results:
 *      The new pixmap is returned.
 *
 * Side effects:
 *      TODO: Side Effects
 *
 *----------------------------------------------------------------------
 */
Pixmap
Rbc_ColorImageToPixmap2(display, depth, image, colorTablePtr)
    Display *display;
    int depth;
    Rbc_ColorImage image;
    ColorTable *colorTablePtr; /* Points to array of colormap indices */
{
    BITMAP bm;
    HBITMAP hBitmap;
    TkWinBitmap *twdPtr;
    int width, height;
    register Pix32 *srcPtr;
    register int x, y;
    register unsigned char *destPtr;
    unsigned char *bits;

    *colorTablePtr = NULL;
    width = Rbc_ColorImageWidth(image);
    height = Rbc_ColorImageHeight(image);

    /*
     * Copy the color image RGB data into the DIB. The DIB scanlines
     * are stored bottom-to-top and the order of the RGB color
     * components is BGR. Who says Win32 GDI programming isn't
     * backwards?
     */
    bits = (unsigned char *)ckalloc(width * height * sizeof(unsigned char));
    assert(bits);
    srcPtr = Rbc_ColorImageBits(image);
    for (y = height - 1; y >= 0; y--) {
	destPtr = bits + (y * width);
	for (x = 0; x < width; x++) {
	    *destPtr++ = srcPtr->Blue;
	    *destPtr++ = srcPtr->Green;
	    *destPtr++ = srcPtr->Red;
	    *destPtr++ = (unsigned char)-1;
	    srcPtr++;
	}
    }
    bm.bmType = 0;
    bm.bmWidth = width;
    bm.bmHeight = height;
    bm.bmWidthBytes = width;
    bm.bmPlanes = 1;
    bm.bmBitsPixel = 32;
    bm.bmBits = bits;
    hBitmap = CreateBitmapIndirect(&bm);

    /* Create a windows version of a drawable. */
    twdPtr = (TkWinBitmap *)ckalloc(sizeof(TkWinBitmap));
    assert(twdPtr);
    twdPtr->type = TWD_BITMAP;
    twdPtr->handle = hBitmap;
    twdPtr->depth = depth;
    twdPtr->colormap = DefaultColormap(display, DefaultScreen(display));
    return (Pixmap)twdPtr;
}

/*
 *----------------------------------------------------------------------
 *
 * Rbc_DrawableToColorImage --
 *
 *      Takes a snapshot of an X drawable (pixmap or window) and
 *      converts it to a color image.
 *
 * Results:
 *      Returns a color image of the drawable.  If an error occurred,
 *      NULL is returned.
 *
 * Side effects:
 *      TODO: Side Effects
 *
 *----------------------------------------------------------------------
 */
Rbc_ColorImage
Rbc_DrawableToColorImage(tkwin, drawable, x, y, width, height, inputGamma)
    Tk_Window tkwin;
    Drawable drawable;
    int x;
    int y;
    int width;
    int height; /* Dimension of the drawable. */
    double inputGamma;
{
    void *data;
    BITMAPINFO info;
    DIBSECTION ds;
    HBITMAP hBitmap, oldBitmap;
    HPALETTE hPalette;
    HDC memDC;
    unsigned char *srcArr;
    register unsigned char *srcPtr;
    HDC hDC;
    TkWinDCState state;
    register Pix32 *destPtr;
    Rbc_ColorImage image;
    unsigned char lut[256];

    hDC = TkWinGetDrawableDC(Tk_Display(tkwin), drawable, &state);

    /* Create the intermediate drawing surface at window resolution. */
    ZeroMemory(&info, sizeof(info));
    info.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
    info.bmiHeader.biWidth = width;
    info.bmiHeader.biHeight = height;
    info.bmiHeader.biPlanes = 1;
    info.bmiHeader.biBitCount = 32;
    info.bmiHeader.biCompression = BI_RGB;
    hBitmap = CreateDIBSection(hDC, &info, DIB_RGB_COLORS, &data, NULL, 0);
    memDC = CreateCompatibleDC(hDC);
    oldBitmap = SelectBitmap(memDC, hBitmap);

    hPalette = Rbc_GetSystemPalette();
    if (hPalette != NULL) {
	SelectPalette(hDC, hPalette, FALSE);
	RealizePalette(hDC);
	SelectPalette(memDC, hPalette, FALSE);
	RealizePalette(memDC);
    }
    image = NULL;
    /* Copy the window contents to the memory surface. */
    if (!BitBlt(memDC, 0, 0, width, height, hDC, x, y, SRCCOPY)) {
#ifdef notdef
	PurifyPrintf("can't blit: %s\n", Rbc_LastError());
#endif
	goto done;
    }
    if (GetObject(hBitmap, sizeof(DIBSECTION), &ds) == 0) {
#ifdef notdef
	PurifyPrintf("can't get object: %s\n", Rbc_LastError());
#endif
	goto done;
    }
    srcArr = (unsigned char *)ds.dsBm.bmBits;
    image = Rbc_CreateColorImage(width, height);
    destPtr = Rbc_ColorImageBits(image);

    {
	register int i;
	double value;

	for (i = 0; i < 256; i++) {
	    value = pow(i / 255.0, inputGamma) * 255.0 + 0.5;
	    lut[i] = (unsigned char)CLAMP(value);
	}
    }

    /*
     * Copy the DIB RGB data into the color image. The DIB scanlines
     * are stored bottom-to-top and the order of the RGB color
     * components is BGR. Who says Win32 GDI programming isn't
     * backwards?
     */

    for (y = height - 1; y >= 0; y--) {
	srcPtr = srcArr + (y * ds.dsBm.bmWidthBytes);
	for (x = 0; x < width; x++) {
	    destPtr->Blue = lut[*srcPtr++];
	    destPtr->Green = lut[*srcPtr++];
	    destPtr->Red = lut[*srcPtr++];
	    destPtr->Alpha = (unsigned char)-1;
	    destPtr++;
	    srcPtr++;
	}
    }
done:
    DeleteBitmap(SelectBitmap(memDC, oldBitmap));
    DeleteDC(memDC);
    TkWinReleaseDrawableDC(drawable, hDC, &state);
    if (hPalette != NULL) {
	DeletePalette(hPalette);
    }
    return image;
}

/*
 *--------------------------------------------------------------
 *
 * Rbc_PhotoImageMask --
 *
 *      TODO: Description
 *
 * Results:
 *      TODO: Results
 *
 * Side effects:
 *      TODO: Side Effects
 *
 *--------------------------------------------------------------
 */
Pixmap
Rbc_PhotoImageMask(
    Tk_Window tkwin,
    Tk_PhotoImageBlock src)
{
    TkWinBitmap *twdPtr;
    int offset, count;
    register int x, y;
    unsigned char *srcPtr;
    int destBytesPerRow;
    int destHeight;
    unsigned char *destBits;

    destBytesPerRow = ((src.width + 31) & ~31) / 8;
    destBits = RbcCalloc(src.height, destBytesPerRow);
    destHeight = src.height;

    offset = count = 0;
    /* FIXME: figure out why this is so! */
    for (y = src.height - 1; y >= 0; y--) {
	srcPtr = src.pixelPtr + offset;
	for (x = 0; x < src.width; x++) {
	    if (srcPtr[src.offset[3]] == 0x00) {
		SetBit(x, y);
		count++;
	    }
	    srcPtr += src.pixelSize;
	}
	offset += src.pitch;
    }
    if (count > 0) {
	HBITMAP hBitmap;
	BITMAP bm;

	bm.bmType = 0;
	bm.bmWidth = src.width;
	bm.bmHeight = src.height;
	bm.bmWidthBytes = destBytesPerRow;
	bm.bmPlanes = 1;
	bm.bmBitsPixel = 1;
	bm.bmBits = destBits;
	hBitmap = CreateBitmapIndirect(&bm);

	twdPtr = (TkWinBitmap *)ckalloc(sizeof(TkWinBitmap));
	assert(twdPtr);
	twdPtr->type = TWD_BITMAP;
	twdPtr->handle = hBitmap;
	twdPtr->depth = 1;
	if (Tk_WindowId(tkwin) == None) {
	    twdPtr->colormap = DefaultColormap(Tk_Display(tkwin),
					       DefaultScreen(Tk_Display(tkwin)));
	} else {
	    twdPtr->colormap = Tk_Colormap(tkwin);
	}
    } else {
	twdPtr = NULL;
    }
    if (destBits != NULL) {
	ckfree((char *)destBits);
    }
    return (Pixmap)twdPtr;
}

/*
 *--------------------------------------------------------------
 *
 * Rbc_ColorImageMask --
 *
 *      TODO: Description
 *
 * Results:
 *      TODO: Results
 *
 * Side effects:
 *      TODO: Side Effects
 *
 *--------------------------------------------------------------
 */
Pixmap
Rbc_ColorImageMask(
    Tk_Window tkwin,
    Rbc_ColorImage image)
{
    TkWinBitmap *twdPtr;
    int count;
    register int x, y;
    Pix32 *srcPtr;
    int destBytesPerRow;
    int destWidth, destHeight;
    unsigned char *destBits;

    destWidth = Rbc_ColorImageWidth(image);
    destHeight = Rbc_ColorImageHeight(image);
    destBytesPerRow = ((destWidth + 31) & ~31) / 8;
    destBits = RbcCalloc(destHeight, destBytesPerRow);
    count = 0;
    srcPtr = Rbc_ColorImageBits(image);
    for (y = 0; y < destHeight; y++) {
	for (x = 0; x < destWidth; x++) {
	    if (srcPtr->Alpha == 0x00) {
		SetBit(x, y);
		count++;
	    }
	    srcPtr++;
	}
    }
    if (count > 0) {
	HBITMAP hBitmap;
	BITMAP bm;

	bm.bmType = 0;
	bm.bmWidth = Rbc_ColorImageWidth(image);
	bm.bmHeight = Rbc_ColorImageHeight(image);
	bm.bmWidthBytes = destBytesPerRow;
	bm.bmPlanes = 1;
	bm.bmBitsPixel = 1;
	bm.bmBits = destBits;
	hBitmap = CreateBitmapIndirect(&bm);

	twdPtr = (TkWinBitmap *)ckalloc(sizeof(TkWinBitmap));
	assert(twdPtr);
	twdPtr->type = TWD_BITMAP;
	twdPtr->handle = hBitmap;
	twdPtr->depth = 1;
	if (Tk_WindowId(tkwin) == None) {
	    twdPtr->colormap = DefaultColormap(Tk_Display(tkwin),
					       DefaultScreen(Tk_Display(tkwin)));
	} else {
	    twdPtr->colormap = Tk_Colormap(tkwin);
	}
    } else {
	twdPtr = NULL;
    }
    if (destBits != NULL) {
	ckfree((char *)destBits);
    }
    return (Pixmap)twdPtr;
}

/*
 *--------------------------------------------------------------
 *
 * Rbc_RotateBitmap --
 *
 *      Creates a new bitmap containing the rotated image
 *      of the given bitmap.  We also need a special GC of
 *      depth 1, so that we do not need to rotate more than
 *      one plane of the bitmap.
 *
 *      Note that under Windows, monochrome bitmaps are
 *      stored bottom-to-top.  This is why the right angle
 *      rotations 0/180 and 90/270 look reversed.
 *
 * Results:
 *      Returns a new bitmap containing the rotated image.
 *
 * Side effects:
 *      TODO: Side Effects
 *
 *--------------------------------------------------------------
 */
Pixmap
Rbc_RotateBitmap(tkwin, srcBitmap, srcWidth, srcHeight, theta, destWidthPtr, destHeightPtr)
    Tk_Window tkwin;
    Pixmap srcBitmap; /* Source bitmap to be rotated */
    int srcWidth;
    int srcHeight; /* Width and height of the source bitmap */
    double theta; /* Right angle rotation to perform */
    int *destWidthPtr;
    int *destHeightPtr;
{
    Display *display;		/* X display */
    Window root;		/* Root window drawable */
    Pixmap destBitmap;
    double rotWidth, rotHeight;
    HDC hDC;
    TkWinDCState state;
    register int x, y;		/* Destination bitmap coordinates */
    register int sx, sy;	/* Source bitmap coordinates */
    unsigned long pixel;
    HBITMAP hBitmap;
    int result;
    struct MonoBitmap {
	BITMAPINFOHEADER bi;
	RGBQUAD colors[2];
    } mb;
    int srcBytesPerRow, destBytesPerRow;
    int destWidth, destHeight;
    unsigned char *srcBits, *destBits;

    display = Tk_Display(tkwin);
    root = RootWindow(Tk_Display(tkwin), Tk_ScreenNumber(tkwin));
    Rbc_GetBoundingBox(srcWidth, srcHeight, theta, &rotWidth, &rotHeight,
		       (Point2D *)NULL);

    destWidth = (int)ceil(rotWidth);
    destHeight = (int)ceil(rotHeight);
    destBitmap = Tk_GetPixmap(display, root, destWidth, destHeight, 1);
    if (destBitmap == None) {
	return None;		/* Can't allocate pixmap. */
    }
    srcBits = Rbc_GetBitmapData(display, srcBitmap, srcWidth, srcHeight,
				&srcBytesPerRow);
    if (srcBits == NULL) {
	OutputDebugStringA("Rbc_GetBitmapData failed");
	return None;
    }
    destBytesPerRow = ((destWidth + 31) & ~31) / 8;
    destBits = RbcCalloc(destHeight, destBytesPerRow);

    theta = FMOD(theta, 360.0);
    if (FMOD(theta, (double)90.0) == 0.0) {
	int quadrant;

	/* Handle right-angle rotations specially. */

	quadrant = (int)(theta / 90.0);
	switch (quadrant) {
	    case ROTATE_270:	/* 270 degrees */
		for (y = 0; y < destHeight; y++) {
		    sx = y;
		    for (x = 0; x < destWidth; x++) {
			sy = destWidth - x - 1;
			pixel = GetBit(sx, sy);
			if (pixel) {
			    SetBit(x, y);
			}
		    }
		}
		break;

	    case ROTATE_180:		/* 180 degrees */
		for (y = 0; y < destHeight; y++) {
		    sy = destHeight - y - 1;
		    for (x = 0; x < destWidth; x++) {
			sx = destWidth - x - 1;
			pixel = GetBit(sx, sy);
			if (pixel) {
			    SetBit(x, y);
			}
		    }
		}
		break;

	    case ROTATE_90:		/* 90 degrees */
		for (y = 0; y < destHeight; y++) {
		    sx = destHeight - y - 1;
		    for (x = 0; x < destWidth; x++) {
			sy = x;
			pixel = GetBit(sx, sy);
			if (pixel) {
			    SetBit(x, y);
			}
		    }
		}
		break;

	    case ROTATE_0:		/* 0 degrees */
		for (y = 0; y < destHeight; y++) {
		    for (x = 0; x < destWidth; x++) {
			pixel = GetBit(x, y);
			if (pixel) {
			    SetBit(x, y);
			}
		    }
		}
		break;

	    default:
		/* The calling routine should never let this happen. */
		break;
	}
    } else {
	double radians, sinTheta, cosTheta;
	double srcCX, srcCY;	/* Center of source rectangle */
	double destCX, destCY;	/* Center of destination rectangle */
	double tx, ty;
	double rx, ry;		/* Angle of rotation for x and y coordinates */

	radians = (theta / 180.0) * M_PI;
	sinTheta = sin(radians), cosTheta = cos(radians);

	/*
	 * Coordinates of the centers of the source and destination rectangles
	 */
	srcCX = srcWidth * 0.5;
	srcCY = srcHeight * 0.5;
	destCX = destWidth * 0.5;
	destCY = destHeight * 0.5;

	/* Rotate each pixel of dest image, placing results in source image */

	for (y = 0; y < destHeight; y++) {
	    ty = y - destCY;
	    for (x = 0; x < destWidth; x++) {

		/* Translate origin to center of destination image */
		tx = x - destCX;

		/* Rotate the coordinates about the origin */
		rx = (tx * cosTheta) - (ty * sinTheta);
		ry = (tx * sinTheta) + (ty * cosTheta);

		/* Translate back to the center of the source image */
		rx += srcCX;
		ry += srcCY;

		sx = ROUND(rx);
		sy = ROUND(ry);

		/*
		 * Verify the coordinates, since the destination image can be
		 * bigger than the source
		 */

		if ((sx >= srcWidth) || (sx < 0) || (sy >= srcHeight) ||
			(sy < 0)) {
		    continue;
		}
		pixel = GetBit(sx, sy);
		if (pixel) {
		    SetBit(x, y);
		}
	    }
	}
    }
    hBitmap = ((TkWinDrawable *)destBitmap)->bitmap.handle;
    ZeroMemory(&mb, sizeof(mb));
    mb.bi.biSize = sizeof(BITMAPINFOHEADER);
    mb.bi.biPlanes = 1;
    mb.bi.biBitCount = 1;
    mb.bi.biCompression = BI_RGB;
    mb.bi.biWidth = destWidth;
    mb.bi.biHeight = destHeight;
    mb.bi.biSizeImage = destBytesPerRow * destHeight;
    mb.colors[0].rgbBlue = mb.colors[0].rgbRed = mb.colors[0].rgbGreen = 0x0;
    mb.colors[1].rgbBlue = mb.colors[1].rgbRed = mb.colors[1].rgbGreen = 0xFF;
    hDC = TkWinGetDrawableDC(display, destBitmap, &state);
    result = SetDIBits(hDC, hBitmap, 0, destHeight, (LPVOID)destBits,
		       (BITMAPINFO *)&mb, DIB_RGB_COLORS);
    TkWinReleaseDrawableDC(destBitmap, hDC, &state);
    if (!result) {
#if WINDEBUG
	PurifyPrintf("can't setDIBits: %s\n", Rbc_LastError());
#endif
	destBitmap = None;
    }
    if (destBits != NULL) {
	ckfree((char *)destBits);
    }
    if (srcBits != NULL) {
	ckfree((char *)srcBits);
    }

    *destWidthPtr = destWidth;
    *destHeightPtr = destHeight;
    return destBitmap;
}

/*
 * -----------------------------------------------------------------------
 *
 * Rbc_ScaleBitmap --
 *
 *      Creates a new scaled bitmap from another bitmap.
 *
 * Results:
 *      The new scaled bitmap is returned.
 *
 * Side Effects:
 *      A new pixmap is allocated. The caller must release this.
 *
 * -----------------------------------------------------------------------
 */
Pixmap
Rbc_ScaleBitmap(tkwin, srcBitmap, srcWidth, srcHeight, destWidth, destHeight)
    Tk_Window tkwin;
    Pixmap srcBitmap;
    int srcWidth;
    int srcHeight;
    int destWidth;
    int destHeight;
{
    TkWinDCState srcState, destState;
    HDC src, dest;
    Pixmap destBitmap;
    Window root;
    Display *display;

    /* Create a new bitmap the size of the region and clear it */

    display = Tk_Display(tkwin);
    root = RootWindow(Tk_Display(tkwin), Tk_ScreenNumber(tkwin));
    destBitmap = Tk_GetPixmap(display, root, destWidth, destHeight, 1);
    if (destBitmap == None) {
	return None;
    }
    src = TkWinGetDrawableDC(display, srcBitmap, &srcState);
    dest = TkWinGetDrawableDC(display, destBitmap, &destState);

    StretchBlt(dest, 0, 0, destWidth, destHeight, src, 0, 0,
	       srcWidth, srcHeight, SRCCOPY);

    TkWinReleaseDrawableDC(srcBitmap, src, &srcState);
    TkWinReleaseDrawableDC(destBitmap, dest, &destState);
    return destBitmap;
}

/*
 * -----------------------------------------------------------------------
 *
 * Rbc_ScaleRotateBitmapRegion --
 *
 *      Creates a scaled and rotated bitmap from a given bitmap.  The
 *      caller also provides (offsets and dimensions) the region of
 *      interest in the destination bitmap.  This saves having to
 *      process the entire destination bitmap is only part of it is
 *      showing in the viewport.
 *
 *      This uses a simple rotation/scaling of each pixel in the
 *      destination image.  For each pixel, the corresponding
 *      pixel in the source bitmap is used.  This means that
 *      destination coordinates are first scaled to the size of
 *      the rotated source bitmap.  These coordinates are then
 *      rotated back to their original orientation in the source.
 *
 * Results:
 *      The new rotated and scaled bitmap is returned.
 *
 * Side Effects:
 *      A new pixmap is allocated. The caller must release this.
 *
 * -----------------------------------------------------------------------
 */
Pixmap
Rbc_ScaleRotateBitmapRegion(tkwin, srcBitmap, srcWidth, srcHeight, regionX, regionY, regionWidth, regionHeight, virtWidth, virtHeight, theta)
    Tk_Window tkwin;
    Pixmap srcBitmap; /* Source bitmap. */
    unsigned int srcWidth;
    unsigned int srcHeight; /* Size of source bitmap */
    int regionX;
    int regionY; /* Offset of region in virtual
				  * destination bitmap. */
    unsigned int regionWidth;
    unsigned int regionHeight; /* Desire size of bitmap region. */
    unsigned int virtWidth;
    unsigned int virtHeight; /* Virtual size of destination bitmap. */
    double theta; /* Angle to rotate bitmap. */
{
    Display *display;		/* X display */
    HBITMAP hBitmap;
    HDC hDC;
    Pixmap destBitmap;
    TkWinDCState state;
    Window root;		/* Root window drawable */
    double rotWidth, rotHeight;
    double xScale, yScale;
    int srcBytesPerRow, destBytesPerRow;
    int destHeight;
    int result;
    register int sx, sy;	/* Source bitmap coordinates */
    register int x, y;		/* Destination bitmap coordinates */
    unsigned char *srcBits, *destBits;
    unsigned long pixel;
    struct MonoBitmap {
	BITMAPINFOHEADER bi;
	RGBQUAD colors[2];
    } mb;

    display = Tk_Display(tkwin);
    root = RootWindow(Tk_Display(tkwin), Tk_ScreenNumber(tkwin));

    /* Create a bitmap and image big enough to contain the rotated text */
    destBitmap = Tk_GetPixmap(display, root, regionWidth, regionHeight, 1);
    if (destBitmap == None) {
	return None;		/* Can't allocate pixmap. */
    }
    srcBits = Rbc_GetBitmapData(display, srcBitmap, srcWidth, srcHeight,
				&srcBytesPerRow);
    if (srcBits == NULL) {
	OutputDebugStringA("Rbc_GetBitmapData failed");
	return None;
    }
    destBytesPerRow = ((regionWidth + 31) & ~31) / 8;
    destBits = RbcCalloc(regionHeight, destBytesPerRow);
    destHeight = regionHeight;

    theta = FMOD(theta, 360.0);
    Rbc_GetBoundingBox(srcWidth, srcHeight, theta, &rotWidth, &rotHeight,
		       (Point2D *)NULL);
    xScale = rotWidth / (double)virtWidth;
    yScale = rotHeight / (double)virtHeight;

    if (FMOD(theta, (double)90.0) == 0.0) {
	int quadrant;

	/* Handle right-angle rotations specifically */

	quadrant = (int)(theta / 90.0);
	switch (quadrant) {
	    case ROTATE_270:	/* 270 degrees */
		for (y = 0; y < (int)regionHeight; y++) {
		    sx = (int)(yScale * (double)(y+regionY));
		    for (x = 0; x < (int)regionWidth; x++) {
			sy = (int)(xScale *(double)(virtWidth - (x+regionX) - 1));
			pixel = GetBit(sx, sy);
			if (pixel) {
			    SetBit(x, y);
			}
		    }
		}
		break;

	    case ROTATE_180:	/* 180 degrees */
		for (y = 0; y < (int)regionHeight; y++) {
		    sy = (int)(yScale * (double)(virtHeight - (y + regionY) - 1));
		    for (x = 0; x < (int)regionWidth; x++) {
			sx = (int)(xScale *(double)(virtWidth - (x+regionX) - 1));
			pixel = GetBit(sx, sy);
			if (pixel) {
			    SetBit(x, y);
			}
		    }
		}
		break;

	    case ROTATE_90:		/* 90 degrees */
		for (y = 0; y < (int)regionHeight; y++) {
		    sx = (int)(yScale * (double)(virtHeight - (y + regionY) - 1));
		    for (x = 0; x < (int)regionWidth; x++) {
			sy = (int)(xScale * (double)(x + regionX));
			pixel = GetBit(sx, sy);
			if (pixel) {
			    SetBit(x, y);
			}
		    }
		}
		break;

	    case ROTATE_0:		/* 0 degrees */
		for (y = 0; y < (int)regionHeight; y++) {
		    sy = (int)(yScale * (double)(y + regionY));
		    for (x = 0; x < (int)regionWidth; x++) {
			sx = (int)(xScale * (double)(x + regionX));
			pixel = GetBit(sx, sy);
			if (pixel) {
			    SetBit(x, y);
			}
		    }
		}
		break;

	    default:
		/* The calling routine should never let this happen. */
		break;
	}
    } else {
	double radians, sinTheta, cosTheta;
	double scx, scy; 	/* Offset from the center of the
				 * source rectangle. */
	double rcx, rcy; 	/* Offset to the center of the
				 * rotated rectangle. */
	double tx, ty;		/* Translated coordinates from center */
	double rx, ry;		/* Angle of rotation for x and y coordinates */

	radians = (theta / 180.0) * M_PI;
	sinTheta = sin(radians), cosTheta = cos(radians);

	/*
	 * Coordinates of the centers of the source and destination rectangles
	 */
	scx = srcWidth * 0.5;
	scy = srcHeight * 0.5;
	rcx = rotWidth * 0.5;
	rcy = rotHeight * 0.5;

	/* For each pixel of the destination image, transform back to the
	 * associated pixel in the source image. */

	for (y = 0; y < (int)regionHeight; y++) {
	    ty = (yScale * (double)(y + regionY)) - rcy;
	    for (x = 0; x < (int)regionWidth; x++) {

		/* Translate origin to center of destination image. */
		tx = (xScale * (double)(x + regionX)) - rcx;

		/* Rotate the coordinates about the origin. */
		rx = (tx * cosTheta) - (ty * sinTheta);
		ry = (tx * sinTheta) + (ty * cosTheta);

		/* Translate back to the center of the source image. */
		rx += scx;
		ry += scy;

		sx = ROUND(rx);
		sy = ROUND(ry);

		/*
		 * Verify the coordinates, since the destination image can be
		 * bigger than the source.
		 */

		if ((sx >= (int)srcWidth) || (sx < 0) ||
			(sy >= (int)srcHeight) || (sy < 0)) {
		    continue;
		}
		pixel = GetBit(sx, sy);
		if (pixel) {
		    SetBit(x, y);
		}
	    }
	}
    }
    /* Write the rotated image into the destination bitmap. */
    hBitmap = ((TkWinDrawable *)destBitmap)->bitmap.handle;
    ZeroMemory(&mb, sizeof(mb));
    mb.bi.biSize = sizeof(BITMAPINFOHEADER);
    mb.bi.biPlanes = 1;
    mb.bi.biBitCount = 1;
    mb.bi.biCompression = BI_RGB;
    mb.bi.biWidth = regionWidth;
    mb.bi.biHeight = regionHeight;
    mb.bi.biSizeImage = destBytesPerRow * regionHeight;
    mb.colors[0].rgbBlue = mb.colors[0].rgbRed = mb.colors[0].rgbGreen = 0x0;
    mb.colors[1].rgbBlue = mb.colors[1].rgbRed = mb.colors[1].rgbGreen = 0xFF;
    hDC = TkWinGetDrawableDC(display, destBitmap, &state);
    result = SetDIBits(hDC, hBitmap, 0, regionHeight, (LPVOID)destBits,
		       (BITMAPINFO *)&mb, DIB_RGB_COLORS);
    TkWinReleaseDrawableDC(destBitmap, hDC, &state);
    if (!result) {
#if WINDEBUG
	PurifyPrintf("can't setDIBits: %s\n", Rbc_LastError());
#endif
	destBitmap = None;
    }
    if (destBits != NULL) {
	ckfree((char *)destBits);
    }
    if (srcBits != NULL) {
	ckfree((char *)srcBits);
    }
    return destBitmap;
}

#ifdef notdef
/*
 *----------------------------------------------------------------------
 *
 * Rbc_BlendColorImage --
 *
 *      Takes a snapshot of an X drawable (pixmap or window) and
 *      converts it to a color image.
 *
 * Results:
 *      Returns a color image of the drawable.  If an error occurred,
 *      NULL is returned.
 *
 * Side effects:
 *      TODO: Side Effects
 *
 *----------------------------------------------------------------------
 */
void
Rbc_BlendColorImage(
    Tk_Window tkwin,
    Drawable drawable,
    int width, int height, /* Dimension of the drawable. */
    Region2D *regionPtr) /* Region to be snapped. */
{
    void *data;
    BITMAPINFO info;
    DIBSECTION ds;
    HBITMAP hBitmap, oldBitmap;
    HPALETTE hPalette;
    HDC memDC;
    unsigned char *srcArr;
    register unsigned char *srcPtr;
    HDC hDC;
    TkWinDCState state;
    register Pix32 *destPtr;
    Rbc_ColorImage image;
    register int x, y;

    if (regionPtr == NULL) {
	regionPtr = Rbc_SetRegion(0, 0, ColorImageWidth(image),
				  ColorImageHeight(image), &region);
    }
    if (regionPtr->left < 0) {
	regionPtr->left = 0;
    }
    if (regionPtr->right >= destWidth) {
	regionPtr->right = destWidth - 1;
    }
    if (regionPtr->top < 0) {
	regionPtr->top = 0;
    }
    if (regionPtr->bottom >= destHeight) {
	regionPtr->bottom = destHeight - 1;
    }
    width = RegionWidth(regionPtr);
    height = RegionHeight(regionPtr);

    hDC = TkWinGetDrawableDC(display, drawable, &state);

    /* Create the intermediate drawing surface at window resolution. */
    ZeroMemory(&info, sizeof(info));
    info.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
    info.bmiHeader.biWidth = width;
    info.bmiHeader.biHeight = height;
    info.bmiHeader.biPlanes = 1;
    info.bmiHeader.biBitCount = 32;
    info.bmiHeader.biCompression = BI_RGB;
    hBitmap = CreateDIBSection(hDC, &info, DIB_RGB_COLORS, &data, NULL, 0);
    memDC = CreateCompatibleDC(hDC);
    oldBitmap = SelectBitmap(memDC, hBitmap);

    hPalette = Rbc_GetSystemPalette();
    if (hPalette != NULL) {
	SelectPalette(hDC, hPalette, FALSE);
	RealizePalette(hDC);
	SelectPalette(memDC, hPalette, FALSE);
	RealizePalette(memDC);
    }
    image = NULL;
    /* Copy the window contents to the memory surface. */
    if (!BitBlt(memDC, 0, 0, width, height, hDC, regionPtr->left,
		regionPtr->top, SRCCOPY)) {
#ifdef notdef
	PurifyPrintf("can't blit: %s\n", Rbc_LastError());
#endif
	goto done;
    }
    if (GetObject(hBitmap, sizeof(DIBSECTION), &ds) == 0) {
#ifdef notdef
	PurifyPrintf("can't get object: %s\n", Rbc_LastError());
#endif
	goto done;
    }
    srcArr = (unsigned char *)ds.dsBm.bmBits;
    image = Rbc_CreateColorImage(width, height);
    destPtr = Rbc_ColorImageBits(image);

    /*
     * Copy the DIB RGB data into the color image. The DIB scanlines
     * are stored bottom-to-top and the order of the RGBA color
     * components is BGRA. Who says Win32 GDI programming isn't
     * backwards?
     */
    for (y = height - 1; y >= 0; y--) {
	srcPtr = srcArr + (y * ds.dsBm.bmWidthBytes);
	for (x = 0; x < width; x++) {
	    if (destPtr->Alpha > 0) {
		/* Blend colorimage with background. */
		destPtr->Blue = *srcPtr++;
		destPtr->Green = *srcPtr++;
		destPtr->Red = *srcPtr++;
		destPtr->Alpha = (unsigned char)-1;
		srcPtr++;
	    }
	    destPtr++;
	}
    }
done:
    DeleteBitmap(SelectBitmap(memDC, oldBitmap));
    DeleteDC(memDC);
    TkWinReleaseDrawableDC(drawable, hDC, &state);
    if (hPalette != NULL) {
	DeletePalette(hPalette);
    }
    return image;
}
#endif

#ifdef HAVE_IJL_H

#include <ijl.h>

/*
 *--------------------------------------------------------------
 *
 * Rbc_JPEGToColorImage --
 *
 *      TODO: Description
 *
 * Results:
 *      TODO: Results
 *
 * Side effects:
 *      TODO: Side Effects
 *
 *--------------------------------------------------------------
 */
Rbc_ColorImage
Rbc_JPEGToColorImage(interp, fileName)
    Tcl_Interp *interp;
    char *fileName;
{
    JPEG_CORE_PROPERTIES jpgProps;
    Rbc_ColorImage image;

    ZeroMemory(&jpgProps, sizeof(JPEG_CORE_PROPERTIES));
    if (ijlInit(&jpgProps) != IJL_OK) {
	Tcl_AppendResult(interp, "can't initialize Intel JPEG library",
			 (char *)NULL);
	return NULL;
    }
    jpgProps.JPGFile = fileName;
    if (ijlRead(&jpgProps, IJL_JFILE_READPARAMS) != IJL_OK) {
	Tcl_AppendResult(interp, "can't read JPEG file header from \"",
			 fileName, "\" file.", (char *)NULL);
	goto error;
    }

    // !dudnik: to fix bug case 584680, [OT:287A305B]
    // Set the JPG color space ... this will always be
    // somewhat of an educated guess at best because JPEG
    // is "color blind" (i.e., nothing in the bit stream
    // tells you what color space the data was encoded from).
    // However, in this example we assume that we are
    // reading JFIF files which means that 3 channel images
    // are in the YCbCr color space and 1 channel images are
    // in the Y color space.
    switch (jpgProps.JPGChannels) {
	case 1:
	    jpgProps.JPGColor = IJL_G;
	    jpgProps.DIBChannels = 4;
	    jpgProps.DIBColor = IJL_RGBA_FPX;
	    break;

	case 3:
	    jpgProps.JPGColor = IJL_YCBCR;
	    jpgProps.DIBChannels = 4;
	    jpgProps.DIBColor = IJL_RGBA_FPX;
	    break;

	case 4:
	    jpgProps.JPGColor = IJL_YCBCRA_FPX;
	    jpgProps.DIBChannels = 4;
	    jpgProps.DIBColor = IJL_RGBA_FPX;
	    break;

	default:
	    /* This catches everything else, but no color twist will be
		   performed by the IJL. */
	    jpgProps.DIBColor = (IJL_COLOR)IJL_OTHER;
	    jpgProps.JPGColor = (IJL_COLOR)IJL_OTHER;
	    jpgProps.DIBChannels = jpgProps.JPGChannels;
	    break;
    }

    jpgProps.DIBWidth    = jpgProps.JPGWidth;
    jpgProps.DIBHeight   = jpgProps.JPGHeight;
    jpgProps.DIBPadBytes = IJL_DIB_PAD_BYTES(jpgProps.DIBWidth,
			   jpgProps.DIBChannels);

    image = Rbc_CreateColorImage(jpgProps.JPGWidth, jpgProps.JPGHeight);

    jpgProps.DIBBytes = (BYTE *)Rbc_ColorImageBits(image);
    if (ijlRead(&jpgProps, IJL_JFILE_READWHOLEIMAGE) != IJL_OK) {
	Tcl_AppendResult(interp, "can't read image data from \"", fileName,
			 "\"", (char *)NULL);
	goto error;
    }
    if (ijlFree(&jpgProps) != IJL_OK) {
	fprintf(stderr, "can't free Intel(R) JPEG library\n");
    }
    return image;

error:
    ijlFree(&jpgProps);
    if (image != NULL) {
	Rbc_FreeColorImage(image);
    }
    ijlFree(&jpgProps);
    return NULL;
}

#else

#ifdef HAVE_JPEGLIB_H

#undef HAVE_STDLIB_H
#undef EXTERN
#ifdef WIN32
#define XMD_H	1
#endif
#include "jpeglib.h"
#include <setjmp.h>

typedef struct {
    struct jpeg_error_mgr pub;	/* "public" fields */
    jmp_buf jmpBuf;
    Tcl_DString dString;
} ReaderHandler;

static void ErrorProc (j_common_ptr jpegInfo);
static void MessageProc (j_common_ptr jpegInfo);

/*
 *--------------------------------------------------------------
 *
 * ErrorProc --
 *
 *      Here's the routine that will replace the standard
 *      error_exit method
 *
 * Results:
 *      TODO: Results
 *
 * Side effects:
 *      TODO: Side Effects
 *
 *--------------------------------------------------------------
 */
static void
ErrorProc(jpgPtr)
    j_common_ptr jpgPtr;
{
    ReaderHandler *handlerPtr = (ReaderHandler *)jpgPtr->err;

    (*handlerPtr->pub.output_message) (jpgPtr);
    longjmp(handlerPtr->jmpBuf, 1);
}

/*
 *--------------------------------------------------------------
 *
 * MessageProc --
 *
 *      TODO: Description
 *
 * Results:
 *      TODO: Results
 *
 * Side effects:
 *      TODO: Side Effects
 *
 *--------------------------------------------------------------
 */
static void
    MessageProc(jpgPtr)
j_common_ptr jpgPtr;
{
    ReaderHandler *handlerPtr = (ReaderHandler *)jpgPtr->err;
    char buffer[JMSG_LENGTH_MAX];

    /* Create the message and append it into the dynamic string. */
    (*handlerPtr->pub.format_message) (jpgPtr, buffer);
    Tcl_DStringAppend(&(handlerPtr->dString), " ", -1);
    Tcl_DStringAppend(&(handlerPtr->dString), buffer, -1);
}

/*
 *----------------------------------------------------------------------
 *
 * Rbc_JPEGToColorImage --
 *
 *      Reads a JPEG file and converts it into a color image.
 *
 * Results:
 *      The color image is returned.  If an error occured, such
 *      as the designated file could not be opened, NULL is returned.
 *
 * Side effects:
 *      TODO: Side Effects
 *
 *----------------------------------------------------------------------
 */
Rbc_ColorImage
Rbc_JPEGToColorImage(interp, fileName)
    Tcl_Interp *interp;
    char *fileName;
{
    struct jpeg_decompress_struct jpg;
    Rbc_ColorImage image;
    unsigned int imageWidth, imageHeight;
    register Pix32 *destPtr;
    ReaderHandler handler;
    FILE *f;
    JSAMPLE **readBuffer;
    int row_stride;
    register int i;
    register JSAMPLE *bufPtr;

    f = fopen(fileName, "rb");
    if (f == NULL) {
	Tcl_AppendResult(interp, "can't open \"", fileName, "\":",
			 Tcl_PosixError(interp), (char *)NULL);
	return NULL;
    }
    image = NULL;

    /* Step 1: allocate and initialize JPEG decompression object */

    /* We set up the normal JPEG error routines, then override error_exit. */
    jpg.dct_method = JDCT_IFAST;
    jpg.err = jpeg_std_error(&handler.pub);
    handler.pub.error_exit = ErrorProc;
    handler.pub.output_message = MessageProc;

    Tcl_DStringInit(&handler.dString);
    Tcl_DStringAppend(&handler.dString, "error reading \"", -1);
    Tcl_DStringAppend(&handler.dString, fileName, -1);
    Tcl_DStringAppend(&handler.dString, "\": ", -1);

    if (setjmp(handler.jmpBuf)) {
	jpeg_destroy_decompress(&jpg);
	fclose(f);
	Tcl_DStringResult(interp, &(handler.dString));
	return NULL;
    }
    jpeg_create_decompress(&jpg);
    jpeg_stdio_src(&jpg, f);

    jpeg_read_header(&jpg, TRUE);	/* Step 3: read file parameters */

    jpeg_start_decompress(&jpg);	/* Step 5: Start decompressor */
    imageWidth = jpg.output_width;
    imageHeight = jpg.output_height;
    if ((imageWidth < 1) || (imageHeight < 1)) {
	Tcl_AppendResult(interp, "bad JPEG image size", (char *)NULL);
	fclose(f);
	return NULL;
    }
    /* JSAMPLEs per row in output buffer */
    row_stride = imageWidth * jpg.output_components;

    /* Make a one-row-high sample array that will go away when done
     * with image */
    readBuffer = (*jpg.mem->alloc_sarray) ((j_common_ptr)&jpg, JPOOL_IMAGE,
					   row_stride, 1);
    image = Rbc_CreateColorImage(imageWidth, imageHeight);
    destPtr = Rbc_ColorImageBits(image);

    if (jpg.output_components == 1) {
	while (jpg.output_scanline < imageHeight) {
	    jpeg_read_scanlines(&jpg, readBuffer, 1);
	    bufPtr = readBuffer[0];
	    for (i = 0; i < (int)imageWidth; i++) {
		destPtr->Red = destPtr->Green = destPtr->Blue = *bufPtr++;
		destPtr->Alpha = (unsigned char)-1;
		destPtr++;
	    }
	}
    } else {
	while (jpg.output_scanline < imageHeight) {
	    jpeg_read_scanlines(&jpg, readBuffer, 1);
	    bufPtr = readBuffer[0];
	    for (i = 0; i < (int)imageWidth; i++) {
		destPtr->Red = *bufPtr++;
		destPtr->Green = *bufPtr++;
		destPtr->Blue = *bufPtr++;
		destPtr->Alpha = (unsigned char)-1;
		destPtr++;
	    }
	}
    }
    jpeg_finish_decompress(&jpg);	/* We can ignore the return value
					 * since suspension is not
					 * possible with the stdio data
					 * source.  */
    jpeg_destroy_decompress(&jpg);


    /*
     * After finish_decompress, we can close the input file.  Here we
     * postpone it until after no more JPEG errors are possible, so as
     * to simplify the setjmp error logic above.  (Actually, I don't
     * think that jpeg_destroy can do an error exit, but why assume
     * anything...)
     */
    fclose(f);

    /*
     * At this point you may want to check to see whether any corrupt-data
     * warnings occurred (test whether jerr.pub.num_warnings is nonzero).
     */
    if (handler.pub.num_warnings > 0) {
	Tcl_SetErrorCode(interp, "IMAGE", "JPEG",
			 Tcl_DStringValue(&(handler.dString)), (char *)NULL);
    } else {
	Tcl_SetErrorCode(interp, "NONE", (char *)NULL);
    }
    /*
     * We're ready to call the Tk_Photo routines. They'll take the RGB
     * array we've processed to build the Tk image of the JPEG.
     */
    Tcl_DStringFree(&(handler.dString));
    return image;
}

#endif /* HAVE_JPEGLIB_H */
#endif /* HAVE_IJL_H */

