/*
 * imgWindow.c --
 *
 * A photo image file handler to put the content of a window in a photo.
 *
 * Author : Jan Nijtmans
 *
 * $Id: window.c 397 2016-04-03 18:43:14Z obermeier $
 *
 */

/*
 * Generic initialization code, parameterized via CPACKAGE and PACKAGE.
 */

#include "init.c"

#include "X11/Xutil.h"
#ifndef __WIN32__
#  ifndef MAC_OSX_TK
#   include "X11/Xproto.h"
#  else
#   include "X11/Xlib.h"
#   include "X11/Xfuncproto.h"
#  endif
#else
#   include "X11/Xlib.h"
#   include "tkInt.h"
#   include "tkWinInt.h"
#   include "X11/Xfuncproto.h"
#endif

#ifndef X_GetImage
#   define X_GetImage 73
#endif

/*
 * The format record for the Win data format:
 */

static int xerrorhandler(ClientData clientData, XErrorEvent *e);

typedef struct ColormapData {   /* Hold color information for a window */
    int separated;              /* Whether to use separate color bands */
    int color;                  /* Whether window is color or black/white */
    int ncolors;                /* Number of color values stored */
    XColor *colors;             /* Pixel value -> RGB mappings */
    int red_mask, green_mask, blue_mask;        /* Masks and shifts for each */
    int red_shift, green_shift, blue_shift;     /* color band */
} ColormapData;

/*
 * Prototypes for local procedures defined in this file:
 */

#define UCHAR(c) ((unsigned char) (c))
/*
 *--------------------------------------------------------------
 *
 * xerrorhandler --
 *
 *      This is a dummy function to catch X11 errors during an
 *      attempt to convert a window to a photo image.
 *
 * Results:
 *      None.
 *
 * Side effects:
 *      None.
 *
 *--------------------------------------------------------------
 */

static int
xerrorhandler(clientData, e)
    ClientData clientData;
    XErrorEvent *e;
{
    return 0;
}

/* OPA TODO: Must be a better way to specify non-existing format functions. */
static int
ChnRead (interp, chan, fileName, format, imageHandle,
         destX, destY, width, height, srcX, srcY)
    Tcl_Interp *interp;
    Tcl_Channel chan;
    const char *fileName;
    Tcl_Obj *format;
    Tk_PhotoHandle imageHandle;
    int destX, destY;
    int width, height;
    int srcX, srcY;
{
    return 0;
}

static int ChnWrite(
    Tcl_Interp *interp,
    const char *filename,
    Tcl_Obj *format,
    Tk_PhotoImageBlock *blockPtr
) {
    return 0;
}

static int StringWrite(
    Tcl_Interp *interp,
    Tcl_Obj *format,
    Tk_PhotoImageBlock *blockPtr
) {
    return 0;
}

/*
 *----------------------------------------------------------------------
 *
 * ChnMatch --
 *
 *      This procedure is invoked by the photo image type to see if
 *      a file contains image data in WINDOW format.
 *
 * Results:
 *      The return value is always 0, because a window cannot be
 *      read from a file.
 *
 * Side effects:
 *      None.
 *
 *----------------------------------------------------------------------
 */

static int ChnMatch(
    Tcl_Channel chan,
    const char *filename,
    Tcl_Obj *format,
    int *widthPtr,
    int *heightPtr,
    Tcl_Interp *interp
) {
    return 0;
}

/*
 *----------------------------------------------------------------------
 *
 * ObjMatch --
 *
 *  This procedure is invoked by the photo image type to see if
 *  an object contains image data which can be read from a window.
 *
 * Results:
 *  The return value is 1 if data contains a valid window name.
 *
 * Side effects:
 *  the size of the image is placed in widthPtr and heightPtr.
 *
 *----------------------------------------------------------------------
 */

static int ObjMatch(
    Tcl_Obj *data,
    Tcl_Obj *format,
    int *widthPtr,
    int *heightPtr,
    Tcl_Interp *interp
) {
    Tk_Window tkwin;
    const char *name;

    name = tkimg_GetStringFromObj(data, NULL);

    if (interp && name && (name[0] == '.') &&
        ((name[1] == 0) || islower(UCHAR(name[1])))) {
        tkwin = Tk_MainWindow(interp);
        if (tkwin == NULL) {
            return 0;
        }
        tkwin = Tk_NameToWindow(interp, name, tkwin);
        if (tkwin == NULL) {
            *widthPtr = *heightPtr = 0;
            return 1;
        }
        *widthPtr =  Tk_Width(tkwin);
        *heightPtr = Tk_Height(tkwin);
        return 1;
    }
    return 0;
}

#ifdef  __WIN32__
    typedef struct _BITMAPCAPTURE {
        HBITMAP hbm;
        LPDWORD pixels;
        INT     width;
        INT     height;
    } BITMAPCAPTURE;

    static BOOL CaptureScreen (BITMAPCAPTURE *bmpCapture)
    {
        BOOL bResult = FALSE;
        if (!bmpCapture) {
            return bResult;
        }
         
        ZeroMemory (bmpCapture, sizeof(BITMAPCAPTURE));
         
        HDC hdcScreen  = GetDC(NULL);
        HDC hdcCapture = CreateCompatibleDC(NULL);
        int nWidth     = GetSystemMetrics(SM_CXVIRTUALSCREEN),
            nHeight    = GetSystemMetrics(SM_CYVIRTUALSCREEN);
         
        LPBYTE lpCapture;
        BITMAPINFO bmiCapture = { {
            sizeof(BITMAPINFOHEADER), nWidth, -nHeight, 1, 32, BI_RGB, 0, 0, 0, 0, 0,
        } };

        bmpCapture->hbm = CreateDIBSection (hdcScreen, &bmiCapture,
            DIB_RGB_COLORS, (LPVOID *)&lpCapture, NULL, 0);
        if (bmpCapture->hbm) {
            HBITMAP hbmOld = (HBITMAP)SelectObject(hdcCapture, bmpCapture->hbm);
            BitBlt(hdcCapture, 0, 0, nWidth, nHeight, hdcScreen, 0, 0, SRCCOPY);
            SelectObject(hdcCapture, hbmOld);
            bmpCapture->pixels = (LPDWORD)lpCapture;
            bmpCapture->width  = nWidth;
            bmpCapture->height = nHeight;
            bResult = TRUE;
        }
        DeleteDC(hdcCapture);
        DeleteDC(hdcScreen);
        return bResult;
    }

#endif /* __WIN32__ */

/*
 *----------------------------------------------------------------------
 *
 * ObjRead --
 *
 *      This procedure is called by the photo image type to read
 *      the contents of a window and give it to the photo image.
 *
 * Results:
 *      A standard TCL completion code.  If TCL_ERROR is returned
 *      then an error message is left in interp->result.
 *
 * Side effects:
 *      new data is added to the image given by imageHandle.
 *
 *----------------------------------------------------------------------
 */
static int ObjRead(interp, data, format, imageHandle,
                   destX, destY, width, height, srcX, srcY)
    Tcl_Interp *interp;
    Tcl_Obj *data;
    Tcl_Obj *format;
    Tk_PhotoHandle imageHandle;
    int destX, destY;
    int width, height;
    int srcX, srcY;
{
    Tk_PhotoImageBlock block;
    Tk_Window tkwin;
    int fileWidth, fileHeight, nBytes, x, y;
    int rootX = 0, rootY = 0;
    const char *name;
#ifndef __WIN32__
    XImage *ximage;
    ColormapData cdata;
    Colormap cmap;
    int i, ncolors;
    Visual *visual;
#else
    TkWinDCState DCi;
    HDC         ximage;
    BITMAPCAPTURE grab;
#endif
    unsigned char *p;
    Tk_ErrorHandler handle;
    int green, blue;
    int result = TCL_OK;

    name = tkimg_GetStringFromObj(data, NULL);

    tkwin = Tk_NameToWindow(interp, name, Tk_MainWindow(interp));

    if (!tkwin) {
        Tcl_AppendResult(interp, "Window \"", name,"\" doesn't exist", (char *) NULL);
        return TCL_ERROR;
    }

    if (!Tk_WindowId(tkwin)) {
        Tcl_AppendResult(interp, "Window \"", name,"\" is not mapped", (char *) NULL);
        return TCL_ERROR;
    }

    Tk_GetRootCoords(tkwin, &rootX, &rootY);

    fileWidth = Tk_Width(tkwin);
    fileHeight = Tk_Height(tkwin);

    if ((srcX + width) > fileWidth) {
        width = fileWidth - srcX;
    }
    if ((srcY + height) > fileHeight) {
        height = fileHeight - srcY;
    }
    if ((width <= 0) || (height <= 0)) {
        return TCL_OK;
    }

    /*
     * If the window is off the screen it will generate an BadMatch/XError
     * We catch any BadMatch errors here
     */

    handle = Tk_CreateErrorHandler(Tk_Display(tkwin), BadMatch,
            X_GetImage, -1, xerrorhandler, (ClientData) tkwin);

#ifndef __WIN32__
    /*
     * Generate an XImage from the window.  We can then read pixel
     * values out of the XImage.
     */

    ximage = XGetImage(Tk_Display(tkwin), Tk_WindowId(tkwin), srcX, srcY,
        width, height, AllPlanes, ZPixmap);

    Tk_DeleteErrorHandler(handle);

    if (ximage == (XImage*) NULL) {
        Tcl_AppendResult(interp, "Window \"", name,
                "\" cannot be transformed into a pixmap (possibly obscured?)",
                (char *) NULL);
        return TCL_ERROR;
    }
#else
    ximage = TkWinGetDrawableDC(Tk_Display(tkwin), Tk_WindowId(tkwin), &DCi);

    if ( ! CaptureScreen (&grab)) {
        Tcl_AppendResult(interp, "Window \"", name, "\" cannot be grabbed", (char *) NULL);
        return TCL_ERROR;
    }

    Tk_DeleteErrorHandler(handle);
#endif

    if (tkimg_PhotoExpand(interp, imageHandle, destX + width, destY + height) == TCL_ERROR) {
        return TCL_ERROR;
    }

#ifndef __WIN32__
    visual = Tk_Visual(tkwin);
    cmap = Tk_Colormap(tkwin);

    /*
     * Obtain information about the colormap, ie the mapping between
     * pixel values and RGB values. The code below should work
     * for all Visual types.
     */

    ncolors = visual->map_entries;
    cdata.colors = (XColor *) ckalloc(sizeof(XColor) * ncolors);
    cdata.ncolors = ncolors;
    if (visual->class == DirectColor || visual->class == TrueColor) {
        cdata.separated = 1;
        cdata.red_mask = visual->red_mask;
        cdata.green_mask = visual->green_mask;
        cdata.blue_mask = visual->blue_mask;
        cdata.red_shift = 0;
        cdata.green_shift = 0;
        cdata.blue_shift = 0;
        while ((0x0001 & (cdata.red_mask >> cdata.red_shift)) == 0)
            cdata.red_shift ++;
        while ((0x0001 & (cdata.green_mask >> cdata.green_shift)) == 0)
            cdata.green_shift ++;
        while ((0x0001 & (cdata.blue_mask >> cdata.blue_shift)) == 0)
            cdata.blue_shift ++;
        for (i = 0; i < ncolors; i ++)
            cdata.colors[i].pixel =
                    ((i << cdata.red_shift) & cdata.red_mask) |
                    ((i << cdata.green_shift) & cdata.green_mask) |
                    ((i << cdata.blue_shift) & cdata.blue_mask);
    } else {
        cdata.separated = 0;
        cdata.red_mask = 0;
        cdata.green_mask = 0;
        cdata.blue_mask = 0;
        cdata.red_shift = 0;
        cdata.green_shift = 0;
        cdata.blue_shift = 0;
        for (i = 0; i < ncolors; i ++) cdata.colors[i].pixel = i;
    }
    cdata.color = !(visual->class == StaticGray || visual->class == GrayScale);

    XQueryColors(Tk_Display(tkwin), cmap, cdata.colors, ncolors);
#endif

    block.offset[0] = 0;
    block.offset[3] = 0;
#ifndef __WIN32__
    if (cdata.color) {
#endif
        block.pixelSize = 3;
        block.offset[1] = green = 1;
        block.offset[2] = blue = 2;
#ifndef __WIN32__
    } else {
        block.pixelSize = 1;
        block.offset[1] = green = 0;
        block.offset[2] = blue = 0;
    }
#endif
    block.width = width;
    block.height = height;
    block.pitch = block.pixelSize * width;
    nBytes = block.pitch * height;
    block.pixelPtr = (unsigned char *) ckalloc((unsigned) nBytes);

    p = block.pixelPtr;
    for (y = 0; y<height; y++) {
        for (x = 0; x<width; x++) {
#ifndef __WIN32__
            unsigned long pixel = XGetPixel(ximage, x, y);
            if (cdata.separated) {
                int r = (pixel & cdata.red_mask) >> cdata.red_shift;
                p[0] = cdata.colors[r].red >> 8;
                if (cdata.color) {
                    int g = (pixel & cdata.green_mask) >> cdata.green_shift;
                    int b = (pixel & cdata.blue_mask) >> cdata.blue_shift;
                    p[1] = cdata.colors[g].green >> 8;
                    p[2] = cdata.colors[b].blue >> 8;
                }
            } else {
                p[0] = cdata.colors[pixel].red >> 8;
                if (cdata.color) {
                    p[1] = cdata.colors[pixel].green >> 8;
                    p[2] = cdata.colors[pixel].blue >> 8;
                }
            }
#else
            /* Bitmap has order ARGB. */
            #define BITMAP_PIXEL(b, x, y) ((b).pixels[(y) * (b).width + (x)])
            COLORREF pixel = BITMAP_PIXEL (grab, x+rootX, y+rootY);
            p[0] = (pixel & 0xFF0000) >> 16;
            p[1] = (pixel & 0xFF00)   >>  8;
            p[2] = (pixel & 0xFF);
#endif
            p += block.pixelSize;
        }
    }

    if (tkimg_PhotoPutBlock(interp, imageHandle, &block, destX, destY, width, height, TK_PHOTO_COMPOSITE_SET) == TCL_ERROR) {
        result = TCL_ERROR;
    }

#ifndef __WIN32__
    XDestroyImage(ximage);
    ckfree((char *) cdata.colors);
#else
    DeleteObject(grab.hbm);
    TkWinReleaseDrawableDC(Tk_WindowId(tkwin), ximage, &DCi);
#endif
    ckfree((char *) block.pixelPtr);
    return result;
}
