/*
 * imgtools.c --
 *
 *      main file for imgtools. Contents: mostly creation 
 *      of the Tcl commands, but also gradients and some general
 *      tools.
 *      The rest of the functionality is implemented in these files:
 *          transparency.c      everything concerning transparency
 *          interpolatedOps.c   interpolation and tools that use inter-
 *                              polation (rotate, scale)
 *          imgtools-test.c     some functions used for testing
 *          cputime.c           our own little profiling tool 
 *
 * Copyright (c) 2016-2017 by Simon Bachmann <simonbachmann@bluewin.ch>
 * All rights reserved.
 *
 * Permission is hereby granted, without written agreement and without
 * license or royalty fees, to use, copy, modify, and distribute this
 * software and its documentation for any purpose, provided that the
 * above copyright notice and the following two paragraphs appear in
 * all copies of this software.
 * 
 */

#include <tcl.h>
#include <tk.h> 
#include <string.h>
#include <stdio.h>
#include <math.h>
#include <limits.h>

#ifdef _WINDOWS
   #include <windows.h>
#endif

#include "imgtoolsDecl.h"
#include "transparency.h"
#include "imgtools-test.h"
#include "interpolatedOps.h"
#include "debugmsg.h"

#include <assert.h>

/*
 * === Constants ===
 */
const char *const optionNames[] = {
    "-compositingrule",
    "-shrink",
    "-interpolation",
    "-background",
    "-alpha",
    "-from",
    "-to",
    "-orient",
    "-clipping",
    "-followpixel",
    NULL
};   

// Filter names are definded in iterpolatedOps.c

/* 
 * === Prototypes ===
 */

static int Imgtools_Gradient(ClientData clientData, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]);

/*
 * === Gradients ===
 */

/*
 *--------------------------------------------------------------
 *
 * Imgtools_Gradient --
 *
 *      Create a rectangular color gradient. Called by the user via
 *      the corresponding command (::imgtools::transparency)
 *
 * Results:
 *      Standard Tcl result.
 *
 * Side effects:
 *      writes the gradient to destImg. 
 *      destImg must exist, its contents will be partially or  
 *      completely overwritten. If the -width or -height options 
 *      for the dest image were set, these sizes are respected.
 *
 *--------------------------------------------------------------
 */
static int Imgtools_Gradient(clientData, interp, objc, objv) 
ClientData clientData;  /* not used */
Tcl_Interp *interp;     /* current interpreter, used for error messages */
int objc;               /* argument count */
Tcl_Obj *const objv[];  /* argumetn values */
{
    int result = TCL_OK;
    
    Imgtools_Image destImgData;
    destImgData.memOwner = 0;   /* Needed for early return on errors */
    
    Imgtools_CommonOpts commonOpts;
    int allowedOpts;
        
    unsigned char fromColor[3], toColor[3];
    
    /*
     * Parse arguments
     */
    Tk_PhotoHandle destimg;
    Tcl_Obj *destImgName = NULL;
    
    if (objc < 4) {
        Tcl_WrongNumArgs(interp, 1, objv, "fromColor toColor ?options? destImg");
        Tcl_SetErrorCode(interp, "IMGTOOLS", "OPTIONS", "WRONG_NUM_ARGS", NULL);
        result = TCL_ERROR;
        goto endOfGradient;
    }
    if ((result = Imgtools_FindPhoto(interp, objv[objc-1], &destimg)) != TCL_OK) {
        goto endOfGradient;
    }
    destImgName = objv[objc-1];
    
    if ((result = Imgtools_GetColor(interp, objv[1], fromColor)) != TCL_OK) {
        Tcl_SetErrorCode(interp, "IMGTOOLS", "OPTIONS", "BAD_VALUE", NULL);
        goto endOfGradient;
    }
    if ((result = Imgtools_GetColor(interp, objv[2], toColor)) != TCL_OK) {
        Tcl_SetErrorCode(interp, "IMGTOOLS", "OPTIONS", "BAD_VALUE", NULL);
        goto endOfGradient;
    }

    allowedOpts = OPT_COMPOSITE | OPT_SHRINK | OPT_TO | OPT_ORIENT;
    
    if ((result = Imgtools_ParseCommonOpts(interp, objc-4, objv+3, allowedOpts, 
            &commonOpts)) != TCL_OK) {
        goto endOfGradient;
    }
            
    /*
     * Check and "clean" the coordinates
     */
    Imgtools_IntCoords destSize;
    Tk_PhotoGetSize(destimg, &destSize.x, &destSize.y);
    Imgtools_IntCoords start, end;
    if (Imgtools_GetRegion(interp, destSize, commonOpts.toCorners, ALLOW_LARGER,
            SIZE_IS_MAX, &start, &end) != TCL_OK) {
        Tcl_SetErrorCode(interp, "IMGTOOLS", "OPTIONS", "BAD_VALUE", NULL);
        result = TCL_ERROR;
        goto endOfGradient;
    }
    debugmsg(interp, DEBUG_PARAMETERS, "Gradient: -to corners:\n");
    debugmsg(interp, DEBUG_PARAMETERS, "\tstart.x: %d, start.y: %d\n",
             start.x, start.y);
    debugmsg(interp, DEBUG_PARAMETERS, "\tend.x: %d, end.y: %d\n",
             end.x, end.y);

    /*
     * Calculate the parameters for the gradient
     */
    int gradientWidth = 0, gradientHeight = 0;
    float aCoefficient[3], bCoefficient[3];
    if (commonOpts.orient == VERTICAL) {
        gradientHeight = end.y - start.y;
        gradientWidth = end.x - start.x;
        destImgData.width = gradientWidth;
        destImgData.height = gradientHeight;
    } else {
        gradientHeight = end.x - start.x;
        gradientWidth = end.y - start.y;
        destImgData.width = gradientHeight;
        destImgData.height = gradientWidth;
    }
    for (int i = 0; i < 3; i++) {
        aCoefficient[i] = (float)(toColor[i] - fromColor[i]) / (gradientHeight - 1);
        bCoefficient[i] = fromColor[i];
    }
    
    /*
     * Prepare destData and calculate gradient
     */
    unsigned char curColor[3];
    unsigned char *curPixelPtr;
    destImgData.pixelSize = 4;
    destImgData.pitch = destImgData.width * destImgData.pixelSize;
    destImgData.pixelPtr = (unsigned char *)Tcl_Alloc(sizeof(unsigned char) * 
        destImgData.width * destImgData.height * destImgData.pixelSize);
    destImgData.startPtr = destImgData.pixelPtr;
    destImgData.memOwner = 1;
    
    for (int gradientRow = gradientHeight; gradientRow--; ) {
        curColor[RED] = roundf(gradientRow * aCoefficient[RED] + bCoefficient[RED]);
        curColor[GREEN] = roundf(gradientRow * aCoefficient[GREEN] + bCoefficient[GREEN]);
        curColor[BLUE] = roundf(gradientRow * aCoefficient[BLUE] + bCoefficient[BLUE]);
        for (int gradientCol = gradientWidth; gradientCol--; ) {
            if (commonOpts.orient == VERTICAL) {
                curPixelPtr = destImgData.pixelPtr + gradientRow*destImgData.pitch + gradientCol*destImgData.pixelSize;
            } else {
                curPixelPtr = destImgData.pixelPtr + gradientRow*destImgData.pixelSize + gradientCol*destImgData.pitch;
            }
            memcpy(curPixelPtr, curColor, sizeof(unsigned char)*3);
            curPixelPtr[ALPHA] = UCHAR_MAX;
        }
    }
    
    /*
     * Put data to destImg
     */
    Imgtools_IntCoords putSize;
    Imgtools_GetSizeFromCorners(start, end, &putSize);
    if (Imgtools_PutImage(interp, commonOpts.composite, commonOpts.shrink, &destImgData, 
            start, putSize, Tcl_GetString(destImgName), destimg) != TCL_OK) {
        Tcl_SetObjResult(interp, Tcl_NewStringObj("Failed to put data to destination image.", -1));
        Tcl_SetErrorCode(interp, "IMGTOOLS", "DATA", "EXPORT_FAILED", NULL);
        result = TCL_ERROR;
        goto endOfGradient;
    }
    
    Tcl_SetObjResult(interp, destImgName);
    result = TCL_OK;
    
endOfGradient:
    Imgtools_FreeImage(&destImgData);
    
    return result;
}


/*
 * === Image Data operations ==
 */

/*
 *--------------------------------------------------------------
 *
 * Imgtools_PutImage --
 *
 *      Puts image data from a Imgtools_Image to Tk_PhotoHandle.
 *      Takes care to handle the -shrink and -compositingurle options
 *      correctly.
 *      Source image data must be normalized!
 *
 * Results:
 *      Standard Tcl result
 *
 * Side effects:
 *      Image in destHandle is changed.
 * 
 *--------------------------------------------------------------
 */

int Imgtools_PutImage (interp, compositing, shrink, sourceData, startPos, putSize, destName, destHandle)
Tcl_Interp *interp;                 /* current interpreter */
const int compositing;              /* setting for compositing */
const int shrink;                   /* setting for shrinking. Does not work */
const Imgtools_Image *sourceData;   /* get data from here */
const Imgtools_IntCoords startPos;  /* Starting position in dest for copying image data */
const Imgtools_IntCoords putSize;   /* size of the region in destImg to use for data */
const char *destName;               /* name of destination image */
Tk_PhotoHandle destHandle;          /* put data to this image handle */
{
    int putResult;
    Imgtools_IntCoords userSize, shrinkSize;
    Tcl_Obj *command;
    Tk_PhotoImageBlock tkSourceData;
    
    /*
     * The limitations of Tk's C interface make it a bit complicated
     * to implement the -shrink option correctly...
     */
    command = Tcl_ObjPrintf("%s cget -width", destName);
    Tcl_EvalObjEx(interp, command, 0);
    Tcl_GetIntFromObj(interp, Tcl_GetObjResult(interp), &userSize.x);
    command = Tcl_ObjPrintf("%s cget -height", destName);
    Tcl_EvalObjEx(interp, command, 0);
    Tcl_GetIntFromObj(interp, Tcl_GetObjResult(interp), &userSize.y);

    if (userSize.x == 0) {
        shrinkSize.x = putSize.x;
    } else {
        shrinkSize.x = userSize.x;
    }
    if (userSize.y == 0) {
        shrinkSize.y = putSize.y;
    } else {
        shrinkSize.y = userSize.y;
    }
    if (shrink) {
        Tk_PhotoSetSize(interp, destHandle, shrinkSize.x, shrinkSize.y);
    }
    
    /*
     * Transfer data from custom to Tk structure 
     */
    tkSourceData.width = sourceData->width;
    tkSourceData.height = sourceData->height;
    tkSourceData.pixelSize = sourceData->pixelSize;
    tkSourceData.pitch = sourceData->pitch;
    tkSourceData.pixelPtr = sourceData->startPtr;
    for (int i = RED; i <= ALPHA; i++) {
        tkSourceData.offset[i] = i;
    }
    
    /*
     * Write result 
     */
    debugmsg(interp, DEBUG_PARAMETERS, "PutImage: Tk data struct\n");
    debugmsg(interp, DEBUG_PARAMETERS, "\twidth: %d, height: %d\n", 
             tkSourceData.width, tkSourceData.height);
    debugmsg(interp, DEBUG_PARAMETERS, "\tpixelSize: %d, pitch: %d\n",
             tkSourceData.pixelSize, tkSourceData.pitch);
    debugmsg(interp, DEBUG_PARAMETERS, "PutImage: -to parameters:\n");
    debugmsg(interp, DEBUG_PARAMETERS, "\tstartPos.x: %d, startPos.y: %d\n",
             startPos.x, startPos.y);
    debugmsg(interp, DEBUG_PARAMETERS, "\tputSize.x: %d, putSize.y: %d\n",
             putSize.x, putSize.y);
    
    putResult = Tk_PhotoPutBlock(interp, destHandle, &tkSourceData, 
        startPos.x, startPos.y, putSize.x, putSize.y, compositing);
    
    if (shrink) {
        Tk_PhotoSetSize(interp, destHandle, userSize.x, userSize.y);
    }
    
    return putResult;
}

/*
 *--------------------------------------------------------------
 *
 * Imgtools_GetImage --
 *
 *      Get Image data from a Tk_PhotoHandle into a Imgtools_Image
 *
 * Results:
 *      Standard Tcl result.
 *      May fail if Tk gives us an image in a exotic format - e.g.
 *      less than 4 channels or strange offsets.
 *      Tk 8.6 doesn't do this.
 *      In case it should happen anyway, an error is left in interp
 *      and TCL_ERROR is returned.
 *
 * Side effects:
 *      Imgtools_Image is modified to hold the image data
 * 
 *--------------------------------------------------------------
 */
int Imgtools_GetImage (interp, sourceHandle, destPtr)
    Tcl_Interp *interp;             /* put errors here */
    Tk_PhotoHandle sourceHandle;    /* Get data from here */
    Imgtools_Image *destPtr;        /* Put data here */
{
    Tk_PhotoImageBlock tmp;
    
    Tk_PhotoGetImage(sourceHandle, &tmp);
    /* 
     * Tk should always provide an image with all 4 channels and
     * "standard" offsets.
     * just to be sure, we check anyway 
     */
    if (tmp.pixelSize != 4 
            || tmp.offset[RED] != RED 
            || tmp.offset[GREEN] != GREEN
            || tmp.offset[BLUE] != BLUE
            || tmp.offset[ALPHA] != ALPHA ) {
        // We cowardly refuse to handle such an image
        Tcl_SetObjResult(interp, Tcl_ObjPrintf("Got an image with unsupported layout. "
            "Cannot handle that"));
        Tcl_SetErrorCode(interp, "IMGTOOLS", "IMAGE", "UNKNOWN_FORMAT", NULL);
        return TCL_ERROR;
    }
    
    destPtr->pixelPtr = destPtr->startPtr = tmp.pixelPtr;
    destPtr->width = tmp.width;
    destPtr->height = tmp.height;
    destPtr->pitch = tmp.pitch;
    destPtr->pixelSize = tmp.pixelSize;
    destPtr->memOwner = 0;

    return TCL_OK;
}

/*
 *--------------------------------------------------------------
 *
 * Imgtools_ApplySubregion --
 *
 *      Reduces an image to a subregion. This is done by setting
 *      the startPtr, width and height fields accordingly. 
 *      Original data is kept, to avoid copying around data.
 *      If the subregion is smaller than the original, the image 
 *      will no longer be "normal": pitch will no longer be equal 
 *      to width * pixelSize.
 *      Works also on images that are already non-normal.
 *
 * Results:
 *      standard Tcl result. Error if subregion is not within 
 *      the image.
 *
 * Side effects:
 *      Changes the Image structure. While the data is still present, 
 *      the original image is lost, as the information about its 
 *      size and start position is overwritten.
 *      In case of errors, leaves an error message in interp.
 * 
 *--------------------------------------------------------------
 */
int Imgtools_ApplySubregion (interp, regionCorners, img)
Tcl_Interp *interp;                         /* error messages are left here */
const Imgtools_IntCoords regionCorners[2];  /* the two corners defining the region */
Imgtools_Image *img;                        /* the image to modify */
{
    Imgtools_IntCoords start, end, size;
    int result = TCL_OK;
    
    size.x = img->width;
    size.y = img->height;
    if((result = Imgtools_GetRegion(interp, size, regionCorners, WITHIN_IMAGE,
            SIZE_IS_MAX, &start, &end)) != TCL_OK) {
        return result;
    }
    Imgtools_GetSizeFromCorners(start, end, &size);
    
    img->startPtr += start.x*img->pixelSize + start.y*img->pitch;
    img->width = size.x;
    img->height = size.y;
    
    return result;
}

/*
 *--------------------------------------------------------------
 *
 * Imgtools_FreeImage --
 *
 *      Frees the memory allocated for pixelPtr of an image,
 *      but only if we own that memory, i.e. we allocated it and 
 *      therefore can safely free it as well.
 *
 * Results:
 *      none
 *
 * Side effects:
 *      Frees memory of image data, sets pointers in struct to NULL
 * 
 *--------------------------------------------------------------
 */
void Imgtools_FreeImage (img) 
Imgtools_Image *img; /* pointer to the image to free */
{
    if (img->memOwner) {
        Tcl_Free((char*)img->pixelPtr);
        img->pixelPtr = img->startPtr = NULL;
        img->memOwner = 0;
    }
    return;
}

/* UNTESTED, NOT USED
 *--------------------------------------------------------------
 *
 * Imgtools_NormalizeImage --
 *
 *      Copy image data from a Imgtools_Image to another.
 *      The destination imageBlock will be "normalized" i.e. 
 *      its pixelSize will be 4, pitch will be width*pixelSize.
 *      Needed as a workaround for a Tk bug.
 *      It does not, however, make sure that startPtr points 
 *      to pixelPtr.
 *
 * Results:
 *      Standard Tcl result.
 *
 * Side effects:
 *      Changes the contents of the image. Allocates memory 
 *      for image data and sets memOwner field accordingly (if needed).
 *      Frees memory of original block.
 * 
 *--------------------------------------------------------------
 */
int Imgtools_NormalizeImage (imgPtr)
    Imgtools_Image *imgPtr; /* the image to normalize */
{
    Imgtools_Image orig;
    orig = *imgPtr;
    
    imgPtr->pixelSize = 4;
    imgPtr->pitch = imgPtr->width * imgPtr->pixelSize;
    
    if (imgPtr->pixelSize != orig.pixelSize
        || imgPtr->pitch != orig.pitch)
    {
        /* 
         * FIXME This branch of the if might not be needed
         */
        imgPtr->pixelPtr = (unsigned char *)Tcl_Alloc( imgPtr->width 
            * imgPtr->height * imgPtr->pixelSize * sizeof(unsigned char));
        imgPtr->startPtr = imgPtr->pixelPtr;
        imgPtr->memOwner = 1;
    } else {
        /*
         * if we keep the data from the original image, 
         * we're done.
         */
        return TCL_OK;
    }
    
    unsigned char *newRow, *newPixel;
    int origOffset;
    for (int y = imgPtr->height; y--; ) {
        newRow = imgPtr->startPtr + y*imgPtr->pitch;
        for (int x = imgPtr->width; x--; ) {
            newPixel = newRow + x*imgPtr->pixelSize;
            origOffset = x*orig.pixelSize + y*orig.pitch;
            newPixel[RED] = orig.startPtr[origOffset + RED];
            newPixel[GREEN] = orig.startPtr[origOffset + GREEN];
            newPixel[BLUE] = orig.startPtr[origOffset + BLUE];
            newPixel[ALPHA] = orig.startPtr[origOffset + ALPHA];
        }
    }
    
    return TCL_OK;
}

/* 
 * === Other utilities, Init proc
 */

/*
 *--------------------------------------------------------------
 * 
 * Imgtools_ParseCommonOpts --
 * 
 *      Parse options 
 *      The name is kinda misleading: it is a legacy of an older
 *      approach where the commands would parse part of the options
 *      themselves. It turned out simpler to do everything here.
 *      ==> the function pares all options. <==
 * 
 * Results:
 *      Standard Tcl Result
 * 
 * Side effects:
 *      fills the commonOpts structure - either with 
 *      the values provided with the corresponding arguments
 *      or with the defaults.
 *      Leaves an error message _AND_ sets error Codes in interp in 
 *      case of errors (invalid or not allowed opts, invalid values).
 * 
 * Notes:
 *      For the -from and -to options, returns an error if negagive values were specified. 
 *      -> a negative value in commonOptsResult means the value was not specified.
 * 
 * -------------------------------------------------------------
*/
int Imgtools_ParseCommonOpts (interp, objc, objv, allowedOpts, commonOptsResult)
Tcl_Interp *interp;     /* Used for error messages */
int objc;               /* Arguments count, should not include any trailing "fixed" arguments */
                        /* (e.g. destImage) */
Tcl_Obj *const objv[];  /* Arguments, should point to the beginning of the options */
                        /* (no subcommands, arguents, etc) */
int allowedOpts;        /* bitfield of accepted options. */
Imgtools_CommonOpts *commonOptsResult;  /* Values for opts (or defaults) */
                                        /* are written to this struct */
{
    int result = TCL_OK;
    int index, filterIndex;
    int level;
    
    Tcl_Obj *allowedOptsList = NULL;    /* Pointer is DecrRefCount'd on early return */
    
    /*
     * preset defaults
     */
    commonOptsResult->options = 0;
    commonOptsResult->composite = TK_PHOTO_COMPOSITE_OVERLAY;
    commonOptsResult->shrink = 0;
    commonOptsResult->filter = CATROM;
    commonOptsResult->background[0] = 0;
    commonOptsResult->background[1] = 0;
    commonOptsResult->background[2] = 0;
    commonOptsResult->alpha = 0;
    commonOptsResult->fromCorners[0].x = -1;
    commonOptsResult->fromCorners[0].y = -1;
    commonOptsResult->fromCorners[1].x = -1;
    commonOptsResult->fromCorners[1].y = -1;
    commonOptsResult->toCorners[0].x = -1;
    commonOptsResult->toCorners[0].y = -1;
    commonOptsResult->toCorners[1].x = -1;
    commonOptsResult->toCorners[1].y = -1;
    commonOptsResult->orient = VERTICAL;
    commonOptsResult->followPixel.x = -1;
    commonOptsResult->followPixel.y = -1;
    commonOptsResult->clipping = NONE;
    
    allowedOptsList = Imgtools_GetAllowedOptsList(allowedOpts, optionNames);
    
    for (int i = 0; i < objc; i++) {
        
        if ((result = Tcl_GetIndexFromObj(NULL, objv[i], optionNames, "option", 
                TCL_EXACT, &index)) != TCL_OK) {
            /*
             * The option is not known. We need to override the error message
             * left ba GetIndexFromObj, as it uses all options, not just the allowed ones
             */
            Tcl_SetObjResult(interp, Tcl_ObjPrintf("bad option \"%s\": must be %s",
                Tcl_GetString(objv[i]), Tcl_GetString(allowedOptsList)));
            Tcl_SetErrorCode(interp, "IMGTOOLS", "OPTIONS", "BAD_OPTION", NULL);
            goto freeAndBye;
        }
        
        if ( ! ((1 << index) & allowedOpts)) {
            /*
             * The option is known, but not allowed
             */
            Tcl_SetObjResult(interp, Tcl_ObjPrintf("bad option \"%s\": must be %s",
                optionNames[index], Tcl_GetString(allowedOptsList)));
            Tcl_SetErrorCode(interp, "IMGTOOLS", "OPTIONS", "BAD_OPTION", NULL);
            result = TCL_ERROR;
            goto freeAndBye;
        }
        
        switch (1 << index) {
            case OPT_COMPOSITE: 
                if (i + 1 >= objc) {
                    Tcl_SetObjResult(interp, Tcl_ObjPrintf("option \"%s\" requires an argument",
                        optionNames[index]));
                    Tcl_SetErrorCode(interp, "IMGTOOLS", "OPTIONS", "MISSING_VALUE", NULL);
                    result = TCL_ERROR;
                    goto freeAndBye;
                }
                
                if (Tcl_StringMatch(Tcl_GetString(objv[i+1]), "overlay") == 1) {
                    commonOptsResult->composite = TK_PHOTO_COMPOSITE_OVERLAY;
                    commonOptsResult->options |= OPT_COMPOSITE;
                } else if (Tcl_StringMatch(Tcl_GetString(objv[i+1]), "set") == 1) {
                    commonOptsResult->composite = TK_PHOTO_COMPOSITE_SET;
                    commonOptsResult->options |= OPT_COMPOSITE;
                } else {
                    Tcl_SetObjResult(interp, Tcl_NewStringObj("compositing rule "
                        "must be one of \"overlay\" or \"set\"", -1));
                    Tcl_SetErrorCode(interp, "IMGTOOLS", "OPTIONS", "BAD_VALUE", NULL);
                    result = TCL_ERROR;
                    goto freeAndBye;
                }
                i++;
                break;
            
            case OPT_SHRINK: 
                commonOptsResult->shrink = 1;
                commonOptsResult->options |= OPT_SHRINK;
                break;
            
            case OPT_FILTER: 
                if (i + 1 >= objc) {
                    Tcl_SetObjResult(interp, Tcl_ObjPrintf("option \"%s\" requires "
                        "an argument", optionNames[index]));
                    Tcl_SetErrorCode(interp, "IMGTOOLS", "OPTIONS", "MISSING_VALUE", NULL);
                    result = TCL_ERROR;
                    goto freeAndBye;
                }
                if ((result = Tcl_GetIndexFromObj(interp, objv[i+1], filterNames, 
                        "interpolation method", TCL_EXACT, &filterIndex))!= TCL_OK) {
                    Tcl_SetErrorCode(interp, "IMGTOOLS", "OPTIONS", "BAD_VALUE", NULL);
                    goto freeAndBye;
                }
                commonOptsResult->filter = filterIndex;
                commonOptsResult->options |= OPT_FILTER;
                ++i;
                break;
            
            case OPT_BACKGROUND: 
                if (i + 1 >= objc) {
                    Tcl_SetObjResult(interp, Tcl_ObjPrintf("option %s "
                        "requires an argument", optionNames[index]));
                    Tcl_SetErrorCode(interp, "IMGTOOLS", "OPTIONS", "MISSING_VALUE", NULL);
                    result = TCL_ERROR;
                    goto freeAndBye;
                }
                
                if ((result = Imgtools_GetColor(interp, objv[i+1], 
                        commonOptsResult->background)) != TCL_OK) {
                    Tcl_SetErrorCode(interp, "IMGTOOLS", "OPTIONS", "BAD_VALUE", NULL);
                    goto freeAndBye;
                }
                commonOptsResult->options |= OPT_BACKGROUND;
                ++i;
                break;                
            
            case OPT_ALPHA: 
                if (i + 1 >= objc) {
                    Tcl_SetObjResult(interp, Tcl_ObjPrintf(
                        "option %s requires an argument", optionNames[index]));
                    Tcl_SetErrorCode(interp, "IMGTOOLS", "OPTIONS", "MISSING_VALUE", NULL);
                    result = TCL_ERROR;
                    goto freeAndBye;
                }
                
                double tmpAlpha;
                if ((result = Imgtools_GetAlphaVal(interp, objv[i+1], "option -alpha", 
                        &tmpAlpha)) != TCL_OK) {
                    Tcl_SetErrorCode(interp, "IMGTOOLS", "OPTIONS", "BAD_VALUE", NULL);
                    goto freeAndBye;
                }
                commonOptsResult->alpha = tmpAlpha;
                commonOptsResult->options |= OPT_ALPHA;
                ++i;
                break;
                
            case OPT_FROM:
                if (i + 1 >= objc) {
                    Tcl_SetObjResult(interp, Tcl_ObjPrintf(
                        "option %s requires an argument", optionNames[index]));
                    Tcl_SetErrorCode(interp, "IMGTOOLS", "OPTIONS", "MISSING_VALUE", NULL);
                    result = TCL_ERROR;
                    goto freeAndBye;
                }
                int fromCoords[4] = {-1, -1, -1, -1};
                for (int j = 0; j < 4; j++) {
                    if (i+1 >= objc || Tcl_GetString(objv[i+1])[0] == '-') {
                        break;
                    }
                    if ((result = Tcl_GetIntFromObj(interp, objv[i+1], 
                            fromCoords+j)) != TCL_OK) {
                        Tcl_SetErrorCode(interp, "IMGTOOLS", "OPTIONS", "BAD_VALUE", NULL);
                        goto freeAndBye;
                    }
                    if (fromCoords[j] < 0) {
                        //since a '-' terminates the loop, we should never get 
                        //negative values anyway
                        assert(0);
                        Tcl_SetObjResult(interp, Tcl_ObjPrintf("value for the %s option "
                        "must be non-negative", optionNames[index]));
                        Tcl_SetErrorCode(interp, "IMGTOOLS", "OPTIONS", "BAD_VALUE", NULL);
                        result = TCL_ERROR;
                        goto freeAndBye;
                    }
                    ++i;
                }
                commonOptsResult->options |= OPT_FROM;                    
                commonOptsResult->fromCorners[0].x = fromCoords[0];
                commonOptsResult->fromCorners[0].y = fromCoords[1];
                commonOptsResult->fromCorners[1].x = fromCoords[2];
                commonOptsResult->fromCorners[1].y = fromCoords[3];
                break;
                
            case OPT_TO:
                if (i + 1 >= objc) {
                    Tcl_SetObjResult(interp, Tcl_ObjPrintf(
                        "option %s requires an argument", optionNames[index]));
                    Tcl_SetErrorCode(interp, "IMGTOOLS", "OPTIONS", "MISSING_VALUE", NULL);
                    result = TCL_ERROR;
                    goto freeAndBye;
                }
                int toCoords[4] = {-1, -1, -1, -1};
                for (int j = 0; j < 4; j++) {
                    if (i+1 >= objc || Tcl_GetString(objv[i+1])[0] == '-') {
                        break;
                    }
                    if ((result = Tcl_GetIntFromObj(interp, objv[i+1], 
                            toCoords+j)) != TCL_OK) {
                        Tcl_SetErrorCode(interp, "IMGTOOLS", "OPTIONS", "BAD_VALUE", NULL);
                        goto freeAndBye;
                    }
                    if (toCoords[j] < 0) {
                        // since a '-' terminates the loop, we should never get 
                        // negative values anyway
                        assert(0);
                        Tcl_SetObjResult(interp, Tcl_ObjPrintf("value for the %s option "
                            "must be non-negative", optionNames[index]));
                        Tcl_SetErrorCode(interp, "IMGTOOLS", "OPTIONS", "BAD_VALUE", NULL);
                        result = TCL_ERROR;
                        goto freeAndBye;
                    }
                    ++i;
                }
                commonOptsResult->options |= OPT_TO;                    
                commonOptsResult->toCorners[0].x = toCoords[0];
                commonOptsResult->toCorners[0].y = toCoords[1];
                commonOptsResult->toCorners[1].x = toCoords[2];
                commonOptsResult->toCorners[1].y = toCoords[3];
                break;
                    
            case OPT_ORIENT:
                if (i + 1 >= objc) {
                    Tcl_SetObjResult(interp, Tcl_ObjPrintf(
                        "option %s requires an argument", optionNames[index]));
                    Tcl_SetErrorCode(interp, "IMGTOOLS", "OPTIONS", "MISSING_VALUE", NULL);
                    result = TCL_ERROR;
                    goto freeAndBye;
                }
                if (Tcl_StringMatch(Tcl_GetString(objv[i+1]), "vertical") == 1) {
                    commonOptsResult->orient = VERTICAL;
                    commonOptsResult->options |= OPT_ORIENT;
                } else if (Tcl_StringMatch(Tcl_GetString(objv[i+1]), "horizontal") == 1) {
                    commonOptsResult->orient = HORIZONTAL;
                    commonOptsResult->options |= OPT_ORIENT;
                } else {
                    Tcl_SetObjResult(interp, Tcl_ObjPrintf(
                        "unknown orientation \"%s\": must be horizontal or vertical",
                        Tcl_GetString(objv[i+1])));
                    Tcl_SetErrorCode(interp, "IMGTOOLS", "OPTIONS", "BAD_VALUE", NULL);
                    result = TCL_ERROR;
                    goto freeAndBye;
                }
                ++i;
                break;
            
            case OPT_CLIPPING:
                if (i + 1 >= objc) {
                    Tcl_SetObjResult(interp, Tcl_ObjPrintf(
                        "option %s requires an argument", optionNames[index]));
                    Tcl_SetErrorCode(interp, "IMGTOOLS", "OPTIONS", "MISSING_VALUE", NULL);
                    result = TCL_ERROR;
                    goto freeAndBye;
                }
                if (Tcl_StringMatch(Tcl_GetString(objv[i+1]), "noblanks") == 1) {
                    commonOptsResult->clipping = ALL;
                    commonOptsResult->options |= OPT_CLIPPING;
                } else if (Tcl_StringMatch(Tcl_GetString(objv[i+1]), "none") == 1) {
                    commonOptsResult->clipping = NONE;
                    commonOptsResult->options |= OPT_CLIPPING;
                } else if (Tcl_StringMatch(Tcl_GetString(objv[i+1]), "keepsize") == 1) {
                    commonOptsResult->clipping = KEEP;
                    commonOptsResult->options |= OPT_CLIPPING;
                } else {
                    Tcl_SetObjResult(interp, Tcl_ObjPrintf("unknown clipping mode "
                        "\"%s\": must be none, keepsize or noblanks", 
                        Tcl_GetString(objv[i+1])));
                    Tcl_SetErrorCode(interp, "IMGTOOLS", "OPTIONS", "BAD_VALUE", NULL);
                    result = TCL_ERROR;
                    goto freeAndBye;
                }
                ++i;
                break;
                
            case OPT_FOLLOWPIXEL:
                if (i + 2 >= objc) {
                    Tcl_SetObjResult(interp, Tcl_ObjPrintf(
                        "option %s requires two arguments", optionNames[index]));
                    Tcl_SetErrorCode(interp, "IMGTOOLS", "OPTIONS", "MISSING_VALUE", NULL);
                    result = TCL_ERROR;
                    goto freeAndBye;
                }
                if ((result = Tcl_GetIntFromObj(interp, objv[i+1], 
                        &(commonOptsResult->followPixel.x))) != TCL_OK) {
                    Tcl_SetErrorCode(interp, "IMGTOOLS", "OPTIONS", "BAD_VALUE", NULL);
                    goto freeAndBye;
                }
                if ((result = Tcl_GetIntFromObj(interp, objv[i+2], 
                        &(commonOptsResult->followPixel.y))) != TCL_OK) {
                    Tcl_SetErrorCode(interp, "IMGTOOLS", "OPTIONS", "BAD_VALUE", NULL);
                    goto freeAndBye;
                }
                commonOptsResult->options |= OPT_FOLLOWPIXEL;
                /*
                 * Set the 'f' debug level
                 */
                level = Imgtools_GetDebugLevel(interp);
                level |= DEBUG_FOLLOW;
                Imgtools_SetDebugLevel(interp, level);
                i += 2;
                break;
            
            default:
                Tcl_Panic("We've got a serious problem here: no bit for index %d", index);
                
        }
    }
    
freeAndBye:
    debugmsg(interp, DEBUG_PROGRESS, "ParseCommonOpts: reached freeAndBye\n");
    if(allowedOptsList != NULL) {
        Tcl_DecrRefCount(allowedOptsList);
    }
    debugmsg_printopts(interp, commonOptsResult);
    
    return result;
}

/*
 *--------------------------------------------------------------
 * 
 * Imgtools_GetAllowedOptsList --
 * 
 *      Build a string listing the allwed options based on option names 
 *      list and bitfield. The resulting string is suitable for error messages
 * 
 * Results:
 *      Tcl_Obj listing the allowed options. Remember to decrRefCount 
 *      if no longer needed.
 * 
 * Side effects:
 *      none
 * 
 * -------------------------------------------------------------
*/
Tcl_Obj * Imgtools_GetAllowedOptsList(allowedOpts, optionNames)
int allowedOpts;                /* bitfield */
const char *const optionNames[];/* names of options, NULL terminated */
{
    /*
     * calculate maximum length
     */
    int len = 0;
    for (int i = 0; optionNames[i] != NULL; i++) {
        /*
         * since we need the bytelength, strlen is fine in this case.
         * even if optionNames should conain non-ASCII chars.
         */
        len += strlen(optionNames[i]) + 2;
    }
    len += 3; /* the "or" before the last opt" */
    
    Tcl_Obj *result;
    const char *tmp = NULL; 
    int first = 1;
    result = Tcl_NewStringObj("", -1);
    
    for (int i = 0; optionNames[i] != NULL; i++) {
        if ( ! ((1 << i) & allowedOpts)) {
            continue;
        }
        
        if (tmp == NULL) {
            tmp = optionNames[i];
            continue;
        }
        if ( ! first) {
            Tcl_AppendToObj(result, ", ", -1);
        } else {
            first = 0;
        }
        Tcl_AppendToObj(result, tmp, -1);
        tmp = optionNames[i];
    }
    if ( ! first) {
        assert(tmp != NULL);
        Tcl_AppendToObj(result, " or ", -1);
    }
    if (tmp != NULL) {
        Tcl_AppendToObj(result, tmp, -1);
    }
    
    return result;
}

/*
 *--------------------------------------------------------------
 * 
 * Imgtools_FindPhoto --
 * 
 *      Checks if an image exists. If so, set the Tk_PhotoHandle
 * 
 * Results:
 *      Standart Tcl result. 
 * 
 * Side effects:
 *      May write an error message to interp (image not found)
 *      Updates the value of handle to the found photo, null
 *      if no image by that name exists.
 * 
 * -------------------------------------------------------------
*/
int Imgtools_FindPhoto (interp, name, handle)
Tcl_Interp *interp;     /* write error message here */
Tcl_Obj *name;          /* name of the image */
Tk_PhotoHandle *handle; /* result goes here */
{
    int result = TCL_OK;
    
    if ((*handle = Tk_FindPhoto(interp, Tcl_GetString(name))) == NULL) {
        Tcl_SetObjResult(interp, Tcl_ObjPrintf("image \"%s\" not found",
            Tcl_GetString(name)));
        Tcl_SetErrorCode(interp, "IMGTOOLS", "OPTIONS", "BAD_VALUE", NULL);
        result = TCL_ERROR;
    }
    
    return result;
}

/*
 *--------------------------------------------------------------
 *
 * Imgtools_GetSizesFromSpec --
 *
 *      Calculates the size of the scaled image according to the spec string.
 *      For the format of the spec string refer to the manpage.
 *
 * Results:
 *      Returns TCL_OK on success. If spec is malformed, leaves a message in 
 *      the result of the interpreter and returns TCL_ERROR.
 *      The dimensions of the scaled image are written to destSize.
 *
 * Side effects:
 *      None.
 *
 *--------------------------------------------------------------
 */
int Imgtools_GetSizesFromSpec(interp, spec, sourceSize, destSize)
    Tcl_Interp *interp;                     /* The interpreter. Used for the error message */
    Tcl_Obj *spec;                          /* Spec string giving a scale factor or absolute size(s) */
    const Imgtools_IntCoords sourceSize;    /* size of the source image */
    Imgtools_IntCoords *destSize;           /* calculated sizes are written to this struct */
{
    
    Tcl_Obj *xSpec, *ySpec, *curSpec;
    char * specStr;
    typedef enum {ABSOLUTE, RATIO} Type;
    Type type = ABSOLUTE;
    int result = TCL_OK;
    
    /*
     * Split specStr
     */
    specStr = Tcl_GetString(spec);
    xSpec = Tcl_NewStringObj("", -1);
    ySpec = Tcl_NewStringObj("", -1);
    curSpec = xSpec;
    for (int i = 0; i < Tcl_NumUtfChars(specStr, -1); i++) {
        if ((Tcl_UtfNcmp(Tcl_UtfAtIndex(specStr, i), "x", 1) == 0 ||
            Tcl_UtfNcmp(Tcl_UtfAtIndex(specStr, i), "X", 1) == 0 ) &&
            curSpec != ySpec ) {
            curSpec = ySpec;
        } else {
#if TCL_MAJOR_VERSION == 8 && TCL_MINOR_VERSION == 7
            /* Workaround for Tcl 8.7. */
	    char tmp[2];
	    tmp[0] = specStr[i];
	    tmp[1] = '\0';
            Tcl_AppendStringsToObj(curSpec, tmp, NULL);
#else
            Tcl_UniChar curChar;
            curChar = Tcl_GetUniChar(spec, i);
            Tcl_AppendUnicodeToObj(curSpec, &curChar, 1);
#endif
        }
    }

    debugmsg(interp, DEBUG_OPTIONPARSING, "xSpec: %s, ySpec: %s\n", Tcl_GetString(xSpec), Tcl_GetString(ySpec));
    
    /*
     * Relative or absolute?
     */
    int isRelative;
    if (Tcl_GetCharLength(xSpec) != 0) {
        Imgtools_RemovePercent(&xSpec, &isRelative);
        if (isRelative) {
            type = RATIO;
            Imgtools_RemovePercent(&ySpec, NULL);
        }
    } else {
        Imgtools_RemovePercent(&ySpec, &isRelative);
        if (isRelative) {
            type = RATIO;
        }
    }
    
    /*
     * Get the numbers
     */
    Imgtools_FloatCoords ratio;
    if (type == ABSOLUTE) {
        if (Tcl_GetCharLength(xSpec) > 0 && Tcl_GetIntFromObj(interp, xSpec, &(destSize->x)) != TCL_OK) {
            Tcl_SetErrorCode(interp, "IMGTOOLS", "OPTIONS", "BAD_VALUE", NULL);
            result = TCL_ERROR;
            goto cleanup;
        }
        if (Tcl_GetCharLength(ySpec) > 0 && Tcl_GetIntFromObj(interp, ySpec, &(destSize->y)) != TCL_OK) {
            Tcl_SetErrorCode(interp, "IMGTOOLS", "OPTIONS", "BAD_VALUE", NULL);
            result = TCL_ERROR;
            goto cleanup;
        }
    } else {
        if (Tcl_GetCharLength(xSpec) > 0 && Tcl_GetDoubleFromObj(interp, xSpec, &ratio.x) != TCL_OK) {
            Tcl_SetErrorCode(interp, "IMGTOOLS", "OPTIONS", "BAD_VALUE", NULL);
            result = TCL_ERROR;
            goto cleanup;
        }
        if (Tcl_GetCharLength(ySpec) > 0 && Tcl_GetDoubleFromObj(interp, ySpec, &ratio.y) != TCL_OK) {
            Tcl_SetErrorCode(interp, "IMGTOOLS", "OPTIONS", "BAD_VALUE", NULL);
            result = TCL_ERROR;
            goto cleanup;
        }
        destSize->x = round(sourceSize.x * ratio.x / 100);
        destSize->y = round(sourceSize.y * ratio.y / 100);
    }
    
    debugmsg(interp, DEBUG_OPTIONPARSING, "sizes from spec: %d, %d\n", destSize->x, destSize->y);
    
    /*
     * We might need to calculate one of the sizes
     * Test for validity
     */
    if (Tcl_GetCharLength(xSpec) == 0 && Tcl_GetCharLength(ySpec) == 0) {
        Tcl_SetObjResult(interp, Tcl_ObjPrintf("malformed specStr \"%s\": must be widthxheight", specStr));
        Tcl_SetErrorCode(interp, "IMGTOOLS", "OPTIONS", "BAD_VALUE", NULL);
        result = TCL_ERROR;
        goto cleanup;
    }
    double imgRatio;
    if (sourceSize.x <= 0 || sourceSize.y <= 0) {
        imgRatio = 1;
    } else {
        imgRatio = (double)sourceSize.x / sourceSize.y;
    }
    if (Tcl_GetCharLength(xSpec) == 0) {
        destSize->x = round(destSize->y * imgRatio);
    }
    if (Tcl_GetCharLength(ySpec) == 0) {
        destSize->y = round(destSize->x / imgRatio);
    }
    if (destSize->x < 0) {
        Tcl_SetObjResult(interp, Tcl_ObjPrintf("bad width \"%d\": must be positive", destSize->x));
        Tcl_SetErrorCode(interp, "IMGTOOLS", "OPTIONS", "BAD_VALUE", NULL);
        result = TCL_ERROR;
        goto cleanup;
    }
    if (destSize->y < 0) {
        Tcl_SetObjResult(interp, Tcl_ObjPrintf("bad height \"%d\": must be positive", destSize->y));
        Tcl_SetErrorCode(interp, "IMGTOOLS", "OPTIONS", "BAD_VALUE", NULL);
        result = TCL_ERROR;
        goto cleanup;
    }
    
    /*
     * Cleanup
     */
cleanup:
    Tcl_DecrRefCount(xSpec);
    Tcl_DecrRefCount(ySpec);
    
    return result;
}

/*
 *--------------------------------------------------------------
 * 
 * Imgtools_RemovePercent --
 * 
 *      Removes a single percent sign from the end of the string
 *      representation of an obj. If obj is a single percen sign,
 *      does nothing.
 * 
 * Results:
 *      none
 * 
 * Side effects:
 *      May create a new Obj to hold the changed string. in this
 *      case the old one is decrRefCount'd.
 *      If a percent sign was removed, changedPtr is assigned 1, 
 *      otherwise it is 0.
 * 
 * -------------------------------------------------------------
*/
void Imgtools_RemovePercent(objPtrPtr, changedPtr)
Tcl_Obj **objPtrPtr;/* The object to inspect and modify*/
int *changedPtr;    /* signal if anyting changed. NULL is allowed! */
{
    Tcl_Obj *tmp;
    char *objStr;
    int *cleanChangedPtr, dummy;
    int len = Tcl_GetCharLength(*objPtrPtr);
    objStr = Tcl_GetString(*objPtrPtr);
    if (changedPtr == NULL) {
        cleanChangedPtr = &dummy;
    } else {
        cleanChangedPtr = changedPtr;
    }
    
    if ( Tcl_UtfNcmp(Tcl_UtfAtIndex(objStr,  len-1), "%", 1) == 0 && len > 1) {
        *cleanChangedPtr = 1;
        tmp = Tcl_GetRange(*objPtrPtr, 0, len-2);
        Tcl_DecrRefCount(*objPtrPtr);
        *objPtrPtr = tmp;
    } else {
        *cleanChangedPtr = 0;
    }
    return;
}
    
/*
 *--------------------------------------------------------------
 * 
 * Imgtools_GetAlphaVal --
 * 
 *      Reads an alpha value from a Tcl_Obj. 
 *      i.e. a value between 0 and 1 or between 0 and 100%.
 * 
 * Results:
 *      Standard Tcl result
 * 
 * Side effects:
 *      Writes the value to alphaPtr.
 *      If the input is malformed, leaves a message in interp.
 * 
 * -------------------------------------------------------------
*/
int Imgtools_GetAlphaVal (interp, alphaStr, optName, alphaPtr)
Tcl_Interp *interp;     /* Interpreter to use for error messages */
Tcl_Obj *alphaStr;      /* STring to parse */
char *optName;          /* Name of option or subcommand. needed for errror message */
                        /* "$optName requires a value...." */
double *alphaPtr;       /* Result is written here */
{
    const char *valStr;
    int len = 0;
    
    valStr = Tcl_GetStringFromObj(alphaStr, &len);
    if (valStr[len-1] == '%') {
        if (Tcl_GetDoubleFromObj(interp, Tcl_NewStringObj(valStr, len-1), alphaPtr) != TCL_OK) {
            Tcl_SetErrorCode(interp, "IMGTOOLS", "OPTIONS", "BAD_VALUE", NULL);
            return TCL_ERROR;
        }
        if (*alphaPtr < 0 || *alphaPtr > 100) {
            Tcl_SetObjResult(interp, Tcl_ObjPrintf("%s requires a value between 0 and 100%%", optName));
            Tcl_SetErrorCode(interp, "IMGTOOLS", "OPTIONS", "BAD_VALUE", NULL);
            return TCL_ERROR;
        }
        *alphaPtr /= 100;
    } else {
        if (Tcl_GetDoubleFromObj(interp, alphaStr, alphaPtr) != TCL_OK) {
            Tcl_SetErrorCode(interp, "IMGTOOLS", "OPTIONS", "BAD_VALUE", NULL);
            return TCL_ERROR;
        }
        if (*alphaPtr < 0 || *alphaPtr > 1) {
            Tcl_SetObjResult(interp, Tcl_ObjPrintf("%s requires a value between 0 and 1", optName));
            Tcl_SetErrorCode(interp, "IMGTOOLS", "OPTIONS", "BAD_VALUE", NULL);
            return TCL_ERROR;
        }
    }
    return TCL_OK;
}

/*
 * -------------------------------------------------------------
 * 
 * Imgtools_ReduceAngle --
 * 
 *      Reduce an angle in the range [0, 360[
 *      take care to check for overflow
 * 
 * Result:
 *      standard tcl result
 * 
 * Side effects:
 *      writes reduced angle to resultPtr.
 *      if input angle is too big (currently the limit is INT_MAX)
 *      leaves an error message in interp
 *      The limit of INT_MAX is due to the fact that we use integer division
 *      for reducing angles.
 * 
 * -------------------------------------------------------------
 */
int Imgtools_ReduceAngle (interp, angle, resultPtr)
Tcl_Interp *interp;     /* interp for error message */
double angle;           /* input value */
double *resultPtr;      /* result is written here */
{
    if (angle > INT_MAX) {
        Tcl_SetObjResult(interp, Tcl_NewStringObj("value for angle is too big", -1));
        return TCL_ERROR;
    }
    if (angle < INT_MIN) {
        Tcl_SetObjResult(interp, Tcl_NewStringObj("value for angle is too small", -1));
        return TCL_ERROR;
    }
    *resultPtr = angle - 360 *((int)(angle / 360));
    if (*resultPtr < 0) {
        *resultPtr += 360;
    }
    assert(*resultPtr >= 0);
    assert(*resultPtr < 360);
    return TCL_OK;
}

/*
 * -------------------------------------------------------------
 * 
 * Imgtools_GetAngle --
 * 
 *      Parse a string and get the measure of an angle from it.
 *      Basically just Tcl_GetDoubleFromObj, but allow a trailing '°' char 
 * 
 * Result:
 *      standard tcl result
 * 
 * Side effects:
 *      writes angle to resultPtr.
 *      If input string cannot be converted to a double, leaves
 *      an appropriate error message in interp.
 * 
 * -------------------------------------------------------------
 */
int Imgtools_GetAngle(interp, angleObj, resultPtr)
Tcl_Interp *interp;     /* leave error messages here */
Tcl_Obj *angleObj;      /* the input */
double *resultPtr;      /* result is written here. undefined in case of errors */
{
    /*
     * Skip trailing whitespace
     */
    Tcl_Obj *trimCmd, *trimmedAngleObj;
    int result = TCL_OK;
    
    trimCmd = Tcl_NewObj();
    Tcl_IncrRefCount(trimCmd);
    Tcl_AppendToObj(trimCmd, "string trimright ", -1);
    Tcl_AppendObjToObj(trimCmd, angleObj);
    if (Tcl_EvalObjEx(interp, trimCmd, TCL_EVAL_DIRECT) != TCL_OK) {
        result = TCL_ERROR;
        trimmedAngleObj = NULL;
        goto cleanup;
    }
    trimmedAngleObj = Tcl_GetObjResult(interp);
    
    /*
     * Ignore a single '°' at the end
     */
    int endChar;
    endChar = Tcl_GetCharLength(trimmedAngleObj) - 1;
    if (Tcl_UtfNcmp(Tcl_UtfAtIndex(Tcl_GetString(trimmedAngleObj), endChar), "°", 1) == 0) {
        --endChar;
    }
    trimmedAngleObj = Tcl_GetRange(trimmedAngleObj, 0, endChar);
    
    /*
     * Get the number
     */
    if (Tcl_GetDoubleFromObj(interp, trimmedAngleObj, resultPtr) != TCL_OK) {
        result = TCL_ERROR;
        goto cleanup;
    }
    
cleanup:
    Tcl_DecrRefCount(trimCmd);
    if (trimmedAngleObj != NULL) {
        Tcl_DecrRefCount(trimmedAngleObj);
    }
    
    return result;
}

/*
 * -------------------------------------------------------------
 * 
 * Imgtools_GetColor --
 * 
 *      Get RGB values from a color string
 * 
 * Result:
 *      standard tcl result
 * 
 * Side effects:
 *      writes RGB values to result array.
 *      If colorStr is not an accepted form, returns TCL_ERROR
 *      and leaves an error message in interp
 * 
 * -------------------------------------------------------------
 */
int Imgtools_GetColor(interp, colorStr, result)
Tcl_Interp *interp;     /* leave error messages here */
Tcl_Obj *colorStr;      /* the color string to convert */
unsigned char *result;  /* the RGB values are written to this array */
                        /* must have be at least size 3 */
{
    XColor * color = NULL;
    if ((color = Tk_GetColor(interp, Tk_MainWindow(interp), Tcl_GetString(colorStr))) == NULL) {
        return TCL_ERROR;
    }
    result[0] = color->red >> 8;
    result[1] = color->green >> 8;
    result[2] = color->blue >> 8;
    
    Tk_FreeColor(color);
    return TCL_OK;
}

/*
 * -------------------------------------------------------------
 * 
 * Imgtools_GetRegion --
 * 
 *      Calculate Coords of a rectangular subregion from two  
 *      opposite corners. 
 * 
 * Result:
 *      standard tcl result
 * 
 * Side effects:
 *      writes start coordinates to resStart, and end coords
 *      to resEnd.
 *      End coords are not part of the region.
 *      if the corner coords contain negative
 *      values (opt not specified), adjusts them to image size.
 *      If Resulting coords are outside the image and allowLarger
 *      is false, retruns an error.
 * 
 * -------------------------------------------------------------
 */
int Imgtools_GetRegion(interp, imgSize, corners, bounds, sizeMode, resStartPtr, resEndPtr)
Tcl_Interp *interp;                     /* leave error messages here */
const Imgtools_IntCoords imgSize;       /* size of the destination image */
const Imgtools_IntCoords corners[2];    /* the coords of the two corners */
const enum AllowLargerFlag bounds; /* if ALLOW_LARGER, do not raise an error */
                                        /* if the region expands outside the image */
const enum RegionSizeFlag sizeMode;     /* used when x2, y2 were not given. Specify if */
                                        /* the imgSize is absolute or an increment for */
                                        /* the missing coordinages */
Imgtools_IntCoords *resStartPtr, *resEndPtr; /* result is writte here */
{
    int result = TCL_OK;
    Imgtools_IntCoords corner1, corner2;
    
    corner1 = corners[0];
    corner2 = corners[1];
    
    debugmsg(interp, DEBUG_PARAMETERS, "GetRegion: input values\n");
    debugmsg(interp, DEBUG_PARAMETERS, "\tcorner1: %d, %d\n", 
             corner1.x, corner1.y);
    debugmsg(interp, DEBUG_PARAMETERS, "\tcorner2: %d, %d\n",
             corner2.x, corner2.y);
    debugmsg(interp, DEBUG_PARAMETERS, "\timgSize: %dx%d\n",
             imgSize.x, imgSize.y);
    
    if (corner1.x < 0) {
        corner1.x = 0;
        assert(corner1.y < 0);
        assert(corner2.x < 0);
        assert(corner2.y < 0);
    }
    if (corner1.y < 0) {
        corner1.y = corner1.x;
        assert(corner2.x < 0);
        assert(corner2.y < 0);
    }
    if (corner2.x < 0) {
        assert(corner2.y < 0);
        corner2.x = sizeMode == SIZE_IS_INCREMENT ? 
            corner1.x + imgSize.x : imgSize.x;
        corner2.y = sizeMode == SIZE_IS_INCREMENT ?
            corner1.y + imgSize.y : imgSize.y;
    } else {
        if (corner2.y < 0) {
            corner2.y = corner2.x;
        }
    }
    resStartPtr->x = MIN(corner1.x, corner2.x);
    resStartPtr->y = MIN(corner1.y, corner2.y);
    resEndPtr->x = MAX(corner1.x, corner2.x);
    resEndPtr->y = MAX(corner1.y, corner2.y);
    
    if ( bounds == WITHIN_IMAGE  && 
            (resStartPtr->x > imgSize.x 
            || resStartPtr->y > imgSize.y
            || resEndPtr->x > imgSize.x 
            || resEndPtr->y > imgSize.y)) {
        Tcl_SetObjResult(interp, Tcl_ObjPrintf("coordinates extend outside image"));
        result = TCL_ERROR;
    }
    
    debugmsg(interp, DEBUG_PARAMETERS, "GetRegion: output values\n");
    debugmsg(interp, DEBUG_PARAMETERS, "\tstart: %d, %d\n", 
             resStartPtr->x, resStartPtr->y);
    debugmsg(interp, DEBUG_PARAMETERS, "\tend: %d, %d\n",
             resEndPtr->x, resEndPtr->y);
    
    return result;
}

/*
 *--------------------------------------------------------------
 *
 * Imgtools_GetSizeFromCorners --
 *
 *      Calculates a size from the coors of the top left and bottom right
 *      corners of a region. (bottom right is not part of the region)
 *
 * Results:
 *      None.
 *
 * Side effects:
 *      Size is written to the given struct pointer
 *
 *--------------------------------------------------------------
 */
void Imgtools_GetSizeFromCorners(topLeftCorner, bottomRightCorner, sizePtr)
const Imgtools_IntCoords topLeftCorner;     /* the two corners */
const Imgtools_IntCoords bottomRightCorner; /* of the region */
Imgtools_IntCoords *sizePtr;                /* write result here */
{
    sizePtr->x = bottomRightCorner.x - topLeftCorner.x;
    sizePtr->y = bottomRightCorner.y - topLeftCorner.y;
    
    return;
}

/*
 *--------------------------------------------------------------
 * 
 * Imgtools_Init --
 * 
 *      Init proc, called when extension is loaded.
 * 
 * Results:
 *      None
 * 
 * Side effects:
 *      Creates a namespace ::imgtools
 *      Loads Tk
 *      Creates following commands: ::imgtools::scale, ::imgtools::rotate
 *      Provides the package "imgtools"
 * 
 * -------------------------------------------------------------
*/
int DLLEXPORT Imgtools_Init(interp)
Tcl_Interp *interp; /* The interpreter where the package is loaded */
{
    if(Tcl_InitStubs(interp, TCL_VERSION, 0) == NULL) {
        return TCL_ERROR;
    }
    if(Tk_InitStubs(interp, TK_VERSION, 0) == NULL) {
        return TCL_ERROR;
    }
    
    if(Tcl_PkgRequire(interp, "Tk", TK_VERSION, 0) == NULL) {
        return TCL_ERROR;
    }
    
    if(Tcl_CreateNamespace(interp, "::imgtools", NULL, NULL) == NULL) {
        /*
         * Chances are that the namespace already exists.
         * We don't want to pollute other people's namespace, 
         * so we just fail at this point.
         */
        return TCL_ERROR;
    }
    Tcl_CreateNamespace(interp, "::imgtools::debug", NULL, NULL);
    
    Tcl_CreateObjCommand(interp, "::imgtools::scale", Imgtools_Scale, NULL, NULL);
    Tcl_CreateObjCommand(interp, "::imgtools::rotate", Imgtools_Rotate, NULL, NULL);
    Tcl_CreateObjCommand(interp, "::imgtools::transparency", Imgtools_Transparency, NULL, NULL);
    Tcl_CreateObjCommand(interp, "::imgtools::gradient", Imgtools_Gradient, NULL, NULL);
    
#ifdef IMGTOOLS_DEBUG
    if (setvbuf(stdout, NULL, _IONBF, 0) != 0) {
        printf("Falied to set stdout to unbuffered!\n");
        fflush(stdout);
    }
    Imgtools_SetDebugLevel(interp, 0);
    Tcl_CreateObjCommand(interp, "::imgtools::debug::setlevel", Imgtools_SetDebugLevelCmd, NULL, NULL);
    Tcl_CreateObjCommand(interp, "::imgtools::debug::getlevel", Imgtools_GetDebugLevelCmd, NULL, NULL);
#endif

    Imgtools_Test_Init(interp);
    
    if(Tcl_PkgProvide(interp, "imgtools", PACKAGE_VERSION) == TCL_ERROR) {
        Tcl_SetObjResult(interp, Tcl_NewStringObj("Failed to provide package", -1)); 
        return TCL_ERROR;
    }
    return TCL_OK;
    
}
