Version 2 of C Image Processing

Updated 2009-09-19 19:36:38 by jdp

jdp 2009-09-18 - Some C code for image processing. Known to work on Windows, but it should be portable. Not particularly clean or well-commented, but it works. Here is a Windows binary, 10KB, upx-packed (because I hate it when someone posts some C code and I just want to use it without the struggle of compiling on Windows).

The command-line for compiling was tcc -IC:\Tcl\include -DUSE_TCL_STUBS -DUSE_TK_STUBS -v -shared -o iprocess.dll iprocess.c tclStubLib.c tkStubLib.c

Includes procedures (some of which are arguably misnamed) for:

  • image::avg - Getting the average brightness for the area of an image
    • image::avg image x y width height
  • image::split - Splitting an image into red, green and blue channels
    • image::split image red green blue
    • The images for red, green and blue must exist already and must be the same size as image
  • image::join - Joining red, green, and blue channels into a single image
    • image::join red green blue image
  • image::hsv - Converting an image to HSV (hue is stored in the red channel, saturation in green, and value in blue)
    • image::hsv image
  • effect::dither - Converts the image to horizontal black-and-white bars which vary in width to create the image
    • effect::dither image step
    • step is the maximum width of the white bars
  • effect::supersaturate - If the value for the red channel is > than 50%, it's set to 100%, else 0%. Repeat for all channels.
    • effect::supersaturate image
  • effect::blur
    • effect::blur image coeff
    • coeff should be between 0 and 1 (but you can get some interesting results by ignoring this)
  • effect::crisp
    • effect::crisp image coeff
    • Again, coeff should be <1 and >0 (but you can get some interesting results by ignoring this)
  • effect::shade - Similar to supersaturate, only it has more than two ways to go
    • effect::shade image shades
    • shades is the number of different shades to allow for each channel
  • effect::emboss
    • effect::emboss image
  • effect::invert
    • effect::invert image
  • effect::grey / effect::gray - convert the image to greyscale. (At the C-level, they refer to the same function)
    • effect::grey image
  • combine::diff - Takes two images; the resulting image is the difference between them
    • combine::diff image1 image2 out
    • image1, image2 and out should all be the same sound and already exist
  • combine::mix - Mixes two images with the specified weight.
    • `combine::mix image1 image2 out weight
    • See combine::diff. Weight is 0.5 for equal mixing; 1 for all image1, and 0 for all image2. I haven't tried making this less than 0 or greater than 1. I am not liable if you try and your computer blows up. :)
  • combine::alpha - Takes two images, and uses the red channel of the second for the alpha channel of the first.
    • combine::alpha image alpha out
    • All arguments must have the same dimensions and preexist.

And now for the code (comments and fixes etc. welcome):

/* iprocess.c */

#include <tk.h>

/* Macros from getting ints and doubles from Tcl_Objs */
#define GET_INT(n,v) if ( Tcl_GetIntFromObj( interp, objv[n], &v ) == TCL_ERROR ) return TCL_ERROR
#define GET_DOUBLE(n,v) if ( Tcl_GetDoubleFromObj( interp, objv[n], &v ) == TCL_ERROR ) return TCL_ERROR

/* Math macros */
#define MIN(a,b) ( (a) < (b) ? (a) : (b) )
#define MAX(a,b) ( (a) > (b) ? (a) : (b) )
#define THRESHOLD(n,t) ( (n) > (t) ? 255 : 0 )

/* Macros for repetative tasks */
#define WRITE_OUT(h,b) ( Tk_PhotoPutBlock( interp, (h), &(b), 0, 0, (b).width, (b).height, TK_PHOTO_COMPOSITE_SET ) )
#define CMD_ARGS ClientData clientData, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]
#define CHECK_ARGS(n,m) if (objc != (n) + 1) { Tcl_WrongNumArgs( interp, 1, objv, m ); return TCL_ERROR; }

/* Macros for getting values from an image block... */

/* ... based x and y */
#define RED(p,x,y)  ((p)->pixelPtr[(y)*(p)->pitch + (x)*(p)->pixelSize + (p)->offset[0]] )
#define GREEN(p,x,y) ((p)->pixelPtr[(y)*(p)->pitch + (x)*(p)->pixelSize + (p)->offset[1]])
#define BLUE(p,x,y)  ((p)->pixelPtr[(y)*(p)->pitch + (x)*(p)->pixelSize + (p)->offset[2]])

/* ... based on pixel number (faster) */
#define R(i,n) ((i).pixelPtr[(n)+(i).offset[0]])
#define G(i,n) ((i).pixelPtr[(n)+(i).offset[1]])
#define B(i,n) ((i).pixelPtr[(n)+(i).offset[2]])
#define A(i,n) ((i).pixelPtr[(n)+(i).offset[3]])

/* ... based on pixel number and bounds-checked (S stands for safe) */
#define SR(i,n) ((n)<0||(n)>(i).width*(i).height*(i).pixelSize?0:(i).pixelPtr[(n)+(i).offset[0]])
#define SG(i,n) ((n)<0||(n)>(i).width*(i).height*(i).pixelSize?0:(i).pixelPtr[(n)+(i).offset[1]])
#define SB(i,n) ((n)<0||(n)>(i).width*(i).height*(i).pixelSize?0:(i).pixelPtr[(n)+(i).offset[2]])
#define SA(i,n) ((n)<0||(n)>(i).width*(i).height*(i).pixelSize?0:(i).pixelPtr[(n)+(i).offset[3]])

int Diff_Cmd(CMD_ARGS);
int Gray_Cmd(CMD_ARGS);
int Avg_Cmd(CMD_ARGS);
int Split_Cmd(CMD_ARGS);
int Join_Cmd(CMD_ARGS);
int Dither_Cmd(CMD_ARGS);
int Sat_Cmd(CMD_ARGS);
int Blur_Cmd(CMD_ARGS);
int Crisp_Cmd(CMD_ARGS);
int HSV_Cmd(CMD_ARGS);
int Shade_Cmd(CMD_ARGS);
int Mix_Cmd(CMD_ARGS);
int Invert_Cmd(CMD_ARGS);
int Emboss_Cmd(CMD_ARGS);
int AddAlpha_Cmd(CMD_ARGS);

__declspec(dllexport) int Iprocess_Init(Tcl_Interp *interp)
{
        /* initialize stubs */
        if ( Tcl_InitStubs( interp, "8.1", 0 ) == NULL ) return TCL_ERROR;
        if ( Tk_InitStubs( interp, "8.1", 0 ) == NULL ) return TCL_ERROR;
        
        /* create commands */
        
        /* misc image operations */
        Tcl_CreateObjCommand( interp, "image::avg", Avg_Cmd, NULL, NULL );
        Tcl_CreateObjCommand( interp, "image::split", Split_Cmd, NULL, NULL );
        Tcl_CreateObjCommand( interp, "image::join", Join_Cmd, NULL, NULL );
        Tcl_CreateObjCommand( interp, "image::hsv", HSV_Cmd, NULL, NULL );
        
        /* effects */
        Tcl_CreateObjCommand( interp, "effect::dither", Dither_Cmd, NULL, NULL );
        Tcl_CreateObjCommand( interp, "effect::supersaturate", Sat_Cmd, NULL, NULL );
        Tcl_CreateObjCommand( interp, "effect::blur", Blur_Cmd, NULL, NULL );
        Tcl_CreateObjCommand( interp, "effect::crisp", Crisp_Cmd, NULL, NULL );
        Tcl_CreateObjCommand( interp, "effect::shade", Shade_Cmd, NULL, NULL );
        Tcl_CreateObjCommand( interp, "effect::emboss", Emboss_Cmd, NULL, NULL );
        Tcl_CreateObjCommand( interp, "effect::invert", Invert_Cmd, NULL, NULL );
        Tcl_CreateObjCommand( interp, "effect::gray", Gray_Cmd, NULL, NULL );
        Tcl_CreateObjCommand( interp, "effect::grey", Gray_Cmd, NULL, NULL );
        
        /* combinations */
        Tcl_CreateObjCommand( interp, "combine::diff", Diff_Cmd, NULL, NULL );
        Tcl_CreateObjCommand( interp, "combine::mix", Mix_Cmd, NULL, NULL );
        Tcl_CreateObjCommand( interp, "combine::alpha", AddAlpha_Cmd, NULL, NULL );
        
        Tcl_PkgProvide( interp, "iprocess", "0.1" );
        
        return TCL_OK;
}

int Split_Cmd(CMD_ARGS)
{
        Tk_PhotoHandle hred, hgreen, hblue;
        Tk_PhotoImageBlock image, red, green, blue;
        int isize, k;
        
        CHECK_ARGS( 4, "image red green blue" );
        
        Tk_PhotoGetImage( Tk_FindPhoto( interp, Tcl_GetString( objv[1] ) ), &image );
        
        hred = Tk_FindPhoto( interp, Tcl_GetString( objv[2] ) );
        hgreen = Tk_FindPhoto( interp, Tcl_GetString( objv[3] ) );
        hblue = Tk_FindPhoto( interp, Tcl_GetString( objv[4] ) );
        Tk_PhotoGetImage( hred, &red );
        Tk_PhotoGetImage( hgreen, &green );
        Tk_PhotoGetImage( hblue, &blue );
        
        /* at some point add a helpful error message */
        if (!( image.width     == red.width     && image.width     == green.width     && image.width     == blue.width     &&
               image.height    == red.height    && image.height    == green.height    && image.height    == blue.height    &&
                   image.pixelSize == red.pixelSize && image.pixelSize == green.pixelSize && image.pixelSize == blue.pixelSize ))
        {
                return TCL_ERROR;
        }
        
        isize = image.width * image.height * image.pixelSize;
        
        k = 0;
        while ( k < isize ) {
                R( red, k ) = R( image, k );
                G( red, k ) = R( image, k );
                B( red, k ) = R( image, k );
                
                R( green, k ) = G( image, k );
                G( green, k ) = G( image, k );
                B( green, k ) = G( image, k );
                
                R( blue, k ) = B( image, k );
                G( blue, k ) = B( image, k );
                B( blue, k ) = B( image, k );
                
                k += image.pixelSize;
        }
        
        WRITE_OUT( hred, red );
        WRITE_OUT( hgreen, green );
        WRITE_OUT( hblue, blue );
        
        return TCL_OK;
}

int Join_Cmd(CMD_ARGS)
{
        Tk_PhotoHandle himage, hred, hgreen, hblue;
        Tk_PhotoImageBlock image, red, green, blue;
        int isize, k;
        
        CHECK_ARGS( 4, "red green blue image" );
        
        himage = Tk_FindPhoto( interp, Tcl_GetString( objv[4] ) );
        hred = Tk_FindPhoto( interp, Tcl_GetString( objv[1] ) );
        hgreen = Tk_FindPhoto( interp, Tcl_GetString( objv[2] ) );
        hblue = Tk_FindPhoto( interp, Tcl_GetString( objv[3] ) );
        
        Tk_PhotoGetImage( himage, &image );
        Tk_PhotoGetImage( hred, &red );
        Tk_PhotoGetImage( hgreen, &green );
        Tk_PhotoGetImage( hblue, &blue );
        
        /* at some point add a helpful error message */
        if (!( image.width     == red.width     && image.width     == green.width     && image.width     == blue.width     &&
               image.height    == red.height    && image.height    == green.height    && image.height    == blue.height    &&
                   image.pixelSize == red.pixelSize && image.pixelSize == green.pixelSize && image.pixelSize == blue.pixelSize ))
        {
                return TCL_ERROR;
        }
        
        isize = image.width * image.height * image.pixelSize;
        
        k = 0;
        while ( k < isize ) {
                R( image, k ) = R( red, k );
                G( image, k ) = G( green, k );
                B( image, k ) = B( blue, k );
                
                k += image.pixelSize;
        }
        
        WRITE_OUT( himage, image );
        
        return TCL_OK;
}

int Invert_Cmd( CMD_ARGS )
{
        Tk_PhotoHandle himage;
        Tk_PhotoImageBlock image;
        int isize, k;

        CHECK_ARGS( 1, "image" );
        
        himage = Tk_FindPhoto( interp, Tcl_GetString( objv[1] ) );
        Tk_PhotoGetImage( himage, &image );
        
        isize = image.width * image.height * image.pixelSize;
        
        k = 0;
        while ( k < isize ) {
                R( image, k ) = 255 - R( image, k );
                G( image, k ) = 255 - G( image, k );
                B( image, k ) = 255 - B( image, k );
                k += image.pixelSize;
        }
        
        WRITE_OUT( himage, image );
        
        return TCL_OK;
}

int Gray_Cmd(CMD_ARGS)
{
        Tk_PhotoHandle imagehandle;
        Tk_PhotoImageBlock image, out;
        int isize, ki, ko;
        unsigned short avg;
        
        CHECK_ARGS( 1, "image" );
        
        imagehandle = Tk_FindPhoto( interp, Tcl_GetString( objv[1] ) );
        Tk_PhotoGetImage( imagehandle, &image );
        
        out.width = image.width;
        out.height = image.height;
        out.pitch = out.width;
        out.pixelSize = 1;
        out.offset[0] = 0;
        out.offset[1] = 0;
        out.offset[2] = 0;
        
        if ( ( out.pixelPtr = (unsigned char*) attemptckalloc( out.width * out.height * sizeof( unsigned char ) ) ) == NULL ) {
                Tcl_SetObjResult( interp, Tcl_NewStringObj( "Memory allocation error", 24 ) );
                return TCL_ERROR;
        }
        
        isize = image.width * image.height * image.pixelSize;
        
        ki = 0;
        ko = 0;
        while ( ki < isize ) {
                avg = (int)( 0.3 * R( image, ki ) + 0.59 * G( image, ki ) + 0.11 * B( image, ki ) );
                R( out, ko ) = avg;
                ki += image.pixelSize;
                ko += out.pixelSize;
        }
        
        WRITE_OUT( imagehandle, out );
        ckfree( out.pixelPtr );
        
        return TCL_OK;
}

int Avg_Cmd(CMD_ARGS)
{
        Tk_PhotoHandle imagehandle;
        Tk_PhotoImageBlock image;
        int isize, k, pixels, x, y, width, height, cx, cy;
        unsigned long sum = 0;
        Tcl_Obj *result;
        
        CHECK_ARGS( 5, "image x y width height" );
        
        GET_INT( 2, x );
        GET_INT( 3, y );
        GET_INT( 4, width );
        GET_INT( 5, height );
        
        imagehandle = Tk_FindPhoto( interp, Tcl_GetString( objv[1] ) );
        Tk_PhotoGetImage( imagehandle, &image );
        
        MIN( width, image.width - x );
        MIN( height, image.height - y );
        
        isize = (height + y) * image.pitch + (width + x) * image.pixelSize;
        
        cx = 0;
        k = y * image.pitch + x * image.pixelSize;
        pixels = width * height;
        while ( k < isize ) {
                sum += R( image, k );
                if (cx >= width) {
                        k += image.pitch - width * image.pixelSize;
                        cx = 0;
                } else {
                        k += image.pixelSize;
                        cx++;
                }
        }
        
        result = Tcl_NewIntObj( (sum / pixels) % 256 );
        Tcl_SetObjResult( interp, result );
        
        return TCL_OK;
}



int Sat_Cmd(CMD_ARGS)
{
        Tk_PhotoHandle imagehandle;
        Tk_PhotoImageBlock image;
        int isize, k;
        
        CHECK_ARGS( 1, "image" );
        
        imagehandle = Tk_FindPhoto( interp, Tcl_GetString( objv[1] ) );
        Tk_PhotoGetImage( imagehandle, &image );
        
        isize = image.width * image.pixelSize + image.height * image.pitch;
        
        k = 0;
        while ( k < isize ) {
                R( image, k ) = THRESHOLD( R( image, k ), 128 );
                G( image, k ) = THRESHOLD( G( image, k ), 128 );
                B( image, k ) = THRESHOLD( B( image, k ), 128 );
                k += image.pixelSize;
        }
        
        WRITE_OUT( imagehandle, image );
        
        return TCL_OK;
}

int HSV_Cmd(CMD_ARGS)
{
        Tk_PhotoHandle imagehandle;
        Tk_PhotoImageBlock image;
        int isize, k;
        double r, g, b, max, min, h, s, v;
        
        CHECK_ARGS( 1, "image" );
        
        imagehandle = Tk_FindPhoto( interp, Tcl_GetString( objv[1] ) );
        Tk_PhotoGetImage( imagehandle, &image );
        
        isize = image.width * image.pixelSize + image.height * image.pitch;
        
        k = 0;
        while ( k < isize ) {
                r = R( image, k ) / 255.0;
                g = G( image, k ) / 255.0;
                b = B( image, k ) / 255.0;
                max = MAX( MAX( r, g ), b );
                min = MIN( MIN( r, g ), b );
                
                if (max == min) {
                        h = 0;
                } else if (max == r) {
                        h = 60 * ( g - b ) / ( max - min ) + 360;
                        while (h >= 360) {
                                h -= 360;
                        }
                } else if (max == g) {
                        h = 60 * ( b - r ) / ( max - min ) + 120;
                } else {
                        h = 60 * ( r - g ) / ( max - min ) + 240;
                }
                
                if (max == 0) {
                        s = 0;
                } else {
                        s = ( max - min ) / max;
                }
                
                R( image, k ) = (int)( ( h / 360.0 ) * 255 );
                G( image, k ) = (int)( s * 255 );
                B( image, k ) = (int)( max * 255 );
                
                k += image.pixelSize;
        }
        
        WRITE_OUT( imagehandle, image );
        
        return TCL_OK;
}

int Emboss_Cmd(CMD_ARGS)
{        
        Tk_PhotoHandle himage;
        Tk_PhotoImageBlock image, out;
        int isize, k, c, rval, gval, bval;
        int r[9], g[9], b[9];
        
        CHECK_ARGS( 1, "image" );
        
        himage = Tk_FindPhoto( interp, Tcl_GetString( objv[1] ) );
        Tk_PhotoGetImage( himage, &image );
        
        out.width = image.width;
        out.height = image.height;
        out.pitch = image.pitch;
        out.pixelSize = image.pixelSize;
        
        for ( c = 0 ; c < sizeof( image.offset ) / sizeof( int ) ; c++ ) out.offset[c] = image.offset[c];
        
        if ( ( out.pixelPtr = (unsigned char*) attemptckalloc( image.width * image.height * image.pixelSize * (sizeof(unsigned char) + 1) ) ) == NULL ) {
                exit(1);
        }
        
        isize = image.width * image.pixelSize * image.height;
        
        k = 0;
        while ( k < isize ) {
                r[0] = SR( image, k - image.pitch - image.pixelSize );
                g[0] = SG( image, k - image.pitch - image.pixelSize );
                b[0] = SB( image, k - image.pitch - image.pixelSize );
                r[1] = SR( image, k - image.pitch );
                g[1] = SG( image, k - image.pitch );
                b[1] = SB( image, k - image.pitch );
                r[2] = SR( image, k - image.pitch + image.pixelSize );
                g[2] = SG( image, k - image.pitch + image.pixelSize );
                b[2] = SB( image, k - image.pitch + image.pixelSize );
                r[3] = SR( image, k - image.pixelSize );
                g[3] = SG( image, k - image.pixelSize );
                b[3] = SB( image, k - image.pixelSize );
                r[4] = R( image, k );
                g[4] = G( image, k );
                b[4] = B( image, k );
                r[5] = SR( image, k + image.pixelSize );
                g[5] = SG( image, k + image.pixelSize );
                b[5] = SB( image, k + image.pixelSize );
                r[6] = SR( image, k + image.pitch - image.pixelSize );
                g[6] = SG( image, k + image.pitch - image.pixelSize );
                b[6] = SB( image, k + image.pitch - image.pixelSize );
                r[7] = SR( image, k + image.pitch );
                g[7] = SG( image, k + image.pitch );
                b[7] = SB( image, k + image.pitch );
                r[8] = SR( image, k + image.pitch + image.pixelSize );
                g[8] = SG( image, k + image.pitch + image.pixelSize );
                b[8] = SB( image, k + image.pitch + image.pixelSize );
                
                rval = 128 + (-r[0] - r[1] + r[2] - r[3] - r[4] + r[5] + r[6] + r[7] + r[8]);
                gval = 128 + (-g[0] - g[1] + g[2] - g[3] - g[4] + g[5] + g[6] + g[7] + g[8]);
                bval = 128 + (-b[0] - b[1] + b[2] - b[3] - b[4] + b[5] + b[6] + b[7] + b[8]);
                rval = MAX( rval, 0 );
                gval = MAX( gval, 0 );
                bval = MAX( bval, 0 );
                rval = MIN( rval, 255 );
                gval = MIN( gval, 255 );
                bval = MIN( bval, 255 );
                c = 0.3 * rval + 0.59 * gval + 0.11 * bval;
                
                R( out, k ) = c;
                G( out, k ) = c;
                B( out, k ) = c; 
                A( out, k ) = 255;
                
                k += image.pixelSize;
        }

        WRITE_OUT( himage, out );
        ckfree( out.pixelPtr );
        
        return TCL_OK;
}

int Dither_Cmd(CMD_ARGS)
{
        /* Image handles and blocks */
        Tk_PhotoHandle todith;
        Tk_PhotoImageBlock blorig, blnew;
        int step, i, ct, k, isize, osize, dx, c;
        int *ko, *ki;
        double intens;
        
        /* Check args, get args */
        CHECK_ARGS( 2, "image step" );
        GET_INT( 2, step );
        todith = Tk_FindPhoto ( interp, Tcl_GetString( objv[1] ) );

        Tk_PhotoGetImage ( todith, &blorig );

        /* fill in blnew */
        blnew.width  = blorig.width;
        blnew.height = blorig.height;
        blnew.pitch  = blnew.width;
        blnew.pixelSize = 1;
        blnew.offset[0] = 0;
        blnew.offset[1] = 0;
        blnew.offset[2] = 0;
        
        /* set isize (image size) and osize (output size) in bytes */
        isize = blorig.height * blorig.pitch + blorig.width * blorig.pixelSize;
        osize = blnew.height * blnew.pitch + blnew.width * blnew.pixelSize;
        
        /* Allocate memory for new image */
        if ( ( blnew.pixelPtr = (unsigned char*) calloc( osize, sizeof( unsigned char ) ) ) == NULL ) {
                Tcl_SetObjResult( interp, Tcl_NewStringObj( "Memory allocation error", 24 ) );
                return TCL_ERROR;
        }
        
        /* Allocate memory for position markers */
        if ( ( ki = (int *) calloc( step, sizeof( int ) ) ) == NULL ) { Tcl_SetObjResult( interp, Tcl_NewStringObj( "Memory allocation error", 24 ) ); return TCL_ERROR; }
        if ( ( ko = (int *) calloc( step, sizeof( int ) ) ) == NULL ) { Tcl_SetObjResult( interp, Tcl_NewStringObj( "Memory allocation error", 24 ) ); return TCL_ERROR; }
        
        /* Initialize position markers */
        for ( c = 0 ; c < step ; c++ ) {
                ki[c] = c * blorig.pitch;
                ko[c] = c * blnew.pitch;
        }
        
        /* ki[c] and ko[c] are the corresponding pixels in the in and out images */
        
        while ( ko[step - 1] < osize && ki[step - 1] < isize ) {
                for ( dx = 0 ; dx < blorig.width ; dx++ ) {
                        
                        /* calculate the intensity of greyness on a scale of 0.0 to 1.0 */
                        for ( intens = 0.0, c = 0 ; c < step ; c++ )
                                intens += 0.3 * R( blorig, ki[c] ) + 0.59 * G( blorig, ki[c] ) + 0.11 * B( blorig, ki[c] );
                        
                        intens /= 255.0 * step;
                        
                        i = (int)( intens * (step - 2) ) + 2;
                        
                        ct = ( step - i ) / 2;
                        
                        for ( c = ct ; c < ct + i ; c++ ) {
                                R( blnew, ko[c] ) = 255;
                        } 
                        for ( c = 0 ; c < step ; c++ ) {
                                ki[c] += blorig.pixelSize;
                                ko[c] += blnew.pixelSize;
                        }
                }
                for ( c = 0 ; c < step ; c++ ) {
                        ki[c] += blorig.pitch * step;
                        ko[c] += blnew.pitch * step;
                }
        }

        
        
        WRITE_OUT( todith, blnew );
        free( blnew.pixelPtr );
        
        free( ki );
        free( ko );
        
        return TCL_OK;
}

int Shade_Cmd(CMD_ARGS)
{
        Tk_PhotoHandle imagehandle;
        Tk_PhotoImageBlock image;
        int isize, k, c, rval, gval, bval, shades, shinterval;
        
        CHECK_ARGS( 2, "image shades" );
        
        imagehandle = Tk_FindPhoto( interp, Tcl_GetString( objv[1] ) );
        Tk_PhotoGetImage( imagehandle, &image );
        
        GET_INT( 2, shades );
        shinterval = 255 / shades;
        
        isize = image.width * image.pixelSize * image.height;
        
        k = 0;
        while ( k < isize ) {
                rval = R( image, k );
                gval = G( image, k );
                bval = B( image, k );
                
                rval = (int)(rval / shinterval) * shinterval;
                gval = (int)(gval / shinterval) * shinterval;
                bval = (int)(bval / shinterval) * shinterval;
                
                R( image, k ) = rval;
                G( image, k ) = gval;
                B( image, k ) = bval;
                
                k += image.pixelSize;
        }
        
        WRITE_OUT( imagehandle, image );
        
        return TCL_OK;
}

int Blur_Cmd(CMD_ARGS)
{
        Tk_PhotoHandle imagehandle;
        Tk_PhotoImageBlock image, out;
        int isize, k, c, rval, gval, bval, aval;
        int r[9], g[9], b[9], a[9];
        double coeff;
        
        CHECK_ARGS( 2, "image coeff" );
        
        imagehandle = Tk_FindPhoto( interp, Tcl_GetString( objv[1] ) );
        Tk_PhotoGetImage( imagehandle, &image );
        
        GET_DOUBLE( 2, coeff );
        coeff = MAX( 0, coeff );
        coeff = MIN( 255, coeff );
        
        out.width = image.width;
        out.height = image.height;
        out.pitch = image.pitch;
        out.pixelSize = image.pixelSize;
        
        for ( c = 0 ; c < sizeof( image.offset ) / sizeof( int ) ; c++ ) out.offset[c] = image.offset[c];
        
        if ( ( out.pixelPtr = (unsigned char*) attemptckalloc( image.width * image.height * image.pixelSize * (sizeof(unsigned char) + 1) ) ) == NULL ) {
                Tcl_SetObjResult( interp, Tcl_NewStringObj( "Memory allocation error", 24 ) );
                return TCL_ERROR;
        }
        
        isize = image.width * image.pixelSize * image.height;
        
        k = 0;
        while ( k < isize ) {
                for ( c = 0 ; c < 9 ; c++ ) {
                        r[c] = 0;
                        g[c] = 0;
                        b[c] = 0;
                }
                
                r[0] = SR( image, k - image.pitch * 2 - image.pixelSize * 2 );
                g[0] = SG( image, k - image.pitch * 2 - image.pixelSize * 2 );
                b[0] = SB( image, k - image.pitch * 2 - image.pixelSize * 2 );
                a[0] = SA( image, k - image.pitch * 2 - image.pixelSize * 2 );
                r[1] = SR( image, k - image.pitch );
                g[1] = SG( image, k - image.pitch );
                b[1] = SB( image, k - image.pitch );
                a[1] = SA( image, k - image.pitch );
                r[2] = SR( image, k - image.pitch * 2 + image.pixelSize * 2 );
                g[2] = SG( image, k - image.pitch * 2 + image.pixelSize * 2 );
                b[2] = SB( image, k - image.pitch * 2 + image.pixelSize * 2 );
                a[2] = SA( image, k - image.pitch * 2 + image.pixelSize * 2 );
                r[3] = SR( image, k - image.pixelSize );
                g[3] = SG( image, k - image.pixelSize );
                b[3] = SB( image, k - image.pixelSize );
                a[3] = SA( image, k - image.pixelSize );
                r[4] = R( image, k );
                g[4] = G( image, k );
                b[4] = B( image, k );
                a[4] = A( image, k );
                r[5] = SR( image, k + image.pixelSize );
                g[5] = SG( image, k + image.pixelSize );
                b[5] = SB( image, k + image.pixelSize );
                a[5] = SA( image, k + image.pixelSize );
                r[6] = SR( image, k + image.pitch * 2 - image.pixelSize * 2 );
                g[6] = SG( image, k + image.pitch * 2 - image.pixelSize * 2 );
                b[6] = SB( image, k + image.pitch * 2 - image.pixelSize * 2 );
                a[6] = SA( image, k + image.pitch * 2 - image.pixelSize * 2 );
                r[7] = SR( image, k + image.pitch );
                g[7] = SG( image, k + image.pitch );
                b[7] = SB( image, k + image.pitch );
                a[7] = SA( image, k + image.pitch );
                r[8] = SR( image, k + image.pitch * 2 + image.pixelSize * 2 );
                g[8] = SG( image, k + image.pitch * 2 + image.pixelSize * 2 );
                b[8] = SB( image, k + image.pitch * 2 + image.pixelSize * 2 );
                a[8] = SA( image, k + image.pitch * 2 + image.pixelSize * 2 );
                
                rval = (1 - coeff) * r[4] + coeff/8 * (r[0] + r[1] + r[2] + r[3] + r[5] + r[6] + r[7] + r[8]);
                gval = (1 - coeff) * g[4] + coeff/8 * (g[0] + g[1] + g[2] + g[3] + g[5] + g[6] + g[7] + g[8]);
                bval = (1 - coeff) * b[4] + coeff/8 * (b[0] + b[1] + b[2] + b[3] + b[5] + b[6] + b[7] + b[8]);
                aval = (1 - coeff) * a[4] + coeff/8 * (a[0] + a[1] + a[2] + a[3] + a[5] + a[6] + a[7] + a[8]);
                rval = MAX( rval, 0 );
                gval = MAX( gval, 0 );
                bval = MAX( bval, 0 );
                aval = MAX( aval, 0 );
                rval = MIN( rval, 255 );
                gval = MIN( gval, 255 );
                bval = MIN( bval, 255 );
                aval = MIN( aval, 255 );
                
                R( out, k ) = rval;
                G( out, k ) = gval;
                B( out, k ) = bval; 
                A( out, k ) = aval;
                
                k += image.pixelSize;
        }
        
        WRITE_OUT( imagehandle, out );
        ckfree( out.pixelPtr );
        
        return TCL_OK;
}

int Crisp_Cmd(CMD_ARGS)
{
        Tk_PhotoHandle imagehandle;
        Tk_PhotoImageBlock image, out;
        int isize, k, c, rval, gval, bval, aval;
        int r[9], g[9], b[9], a[9];
        double coeff;
        
        CHECK_ARGS( 2, "image coeff" );
        
        imagehandle = Tk_FindPhoto( interp, Tcl_GetString( objv[1] ) );
        Tk_PhotoGetImage( imagehandle, &image );
        
        GET_DOUBLE( 2, coeff );
        coeff = MAX( 0, coeff );
        coeff = MIN( 255, coeff );
        
        out.width = image.width;
        out.height = image.height;
        out.pitch = image.pitch;
        out.pixelSize = image.pixelSize;
        
        for ( c = 0 ; c < sizeof( image.offset ) / sizeof( int ) ; c++ ) out.offset[c] = image.offset[c];
        
        if ( ( out.pixelPtr = (unsigned char*) attemptckalloc( image.width * image.height * image.pixelSize * (sizeof(unsigned char) + 1) ) ) == NULL ) {
                Tcl_SetObjResult( interp, Tcl_NewStringObj( "Memory allocation error", 24 ) );
                return TCL_ERROR;
        }
        
        isize = image.width * image.pixelSize * image.height;
        
        k = 0;
        while ( k < isize ) {
                for ( c = 0 ; c < 9 ; c++ ) {
                        r[c] = 0;
                        g[c] = 0;
                        b[c] = 0;
                }
                
                r[0] = SR( image, k - image.pitch - image.pixelSize );
                g[0] = SG( image, k - image.pitch - image.pixelSize );
                b[0] = SB( image, k - image.pitch - image.pixelSize );
                a[0] = SA( image, k - image.pitch - image.pixelSize );
                r[1] = SR( image, k - image.pitch );
                g[1] = SG( image, k - image.pitch );
                b[1] = SB( image, k - image.pitch );
                a[1] = SA( image, k - image.pitch );
                r[2] = SR( image, k - image.pitch + image.pixelSize );
                g[2] = SG( image, k - image.pitch + image.pixelSize );
                b[2] = SB( image, k - image.pitch + image.pixelSize );
                a[2] = SA( image, k - image.pitch + image.pixelSize );
                r[3] = SR( image, k - image.pixelSize );
                g[3] = SG( image, k - image.pixelSize );
                b[3] = SB( image, k - image.pixelSize );
                a[3] = SA( image, k - image.pixelSize );
                r[4] = R( image, k );
                g[4] = G( image, k );
                b[4] = B( image, k );
                a[4] = A( image, k );
                r[5] = SR( image, k + image.pixelSize );
                g[5] = SG( image, k + image.pixelSize );
                b[5] = SB( image, k + image.pixelSize );
                a[5] = SA( image, k + image.pixelSize );
                r[6] = SR( image, k + image.pitch - image.pixelSize );
                g[6] = SG( image, k + image.pitch - image.pixelSize );
                b[6] = SB( image, k + image.pitch - image.pixelSize );
                a[6] = SA( image, k + image.pitch - image.pixelSize );
                r[7] = SR( image, k + image.pitch );
                g[7] = SG( image, k + image.pitch );
                b[7] = SB( image, k + image.pitch );
                a[7] = SA( image, k + image.pitch );
                r[8] = SR( image, k + image.pitch + image.pixelSize );
                g[8] = SG( image, k + image.pitch + image.pixelSize );
                b[8] = SB( image, k + image.pitch + image.pixelSize );
                a[8] = SA( image, k + image.pitch + image.pixelSize );
                
                rval = coeff * r[4] - (1 - coeff)/8 * (r[0] + r[1] + r[2] + r[3] + r[5] + r[6] + r[7] + r[8]);
                gval = coeff * g[4] - (1 - coeff)/8 * (g[0] + g[1] + g[2] + g[3] + g[5] + g[6] + g[7] + g[8]);
                bval = coeff * b[4] - (1 - coeff)/8 * (b[0] + b[1] + b[2] + b[3] + b[5] + b[6] + b[7] + b[8]);
                aval = coeff * a[4] - (1 - coeff)/8 * (a[0] + a[1] + a[2] + a[3] + a[5] + a[6] + a[7] + a[8]);
                rval = MAX( rval, 0 );
                gval = MAX( gval, 0 );
                bval = MAX( bval, 0 );
                aval = MAX( aval, 0 );
                rval = MIN( rval, 255 );
                gval = MIN( gval, 255 );
                bval = MIN( bval, 255 );
                aval = MIN( aval, 255 );
                
                R( out, k ) = rval;
                G( out, k ) = gval;
                B( out, k ) = bval; 
                A( out, k ) = aval;
                
                k += image.pixelSize;
        }
        
        WRITE_OUT( imagehandle, out );
        ckfree( out.pixelPtr );
        
        return TCL_OK;
}

int Diff_Cmd(CMD_ARGS)
{
        Tk_PhotoHandle outhandle;
        Tk_PhotoImageBlock image1, image2, out;
        int isize, k;
        
        CHECK_ARGS ( 3, "image1 image2 out" );
        
        Tk_PhotoGetImage( Tk_FindPhoto( interp, Tcl_GetString( objv[1] ) ), &image1 );
        Tk_PhotoGetImage( Tk_FindPhoto( interp, Tcl_GetString( objv[2] ) ), &image2 );
        
        outhandle = Tk_FindPhoto( interp, Tcl_GetString( objv[3] ) );
        Tk_PhotoGetImage( outhandle, &out );
        
        /* at some point add a helpful error message */
        if (!( image1.width     == image2.width     && image1.width     == out.width     &&
               image1.height    == image2.height    && image1.height    == out.height    &&
                   image1.pixelSize == image2.pixelSize && image1.pixelSize == out.pixelSize ))
        {
                return TCL_ERROR;
        }
        
        isize = image1.width * image1.height * image1.pixelSize;
        
        k = 0;
        while ( k < isize ) {
                R( out, k ) = abs( R( image1, k ) - R( image2, k ) );
                G( out, k ) = abs( G( image1, k ) - G( image2, k ) );
                B( out, k ) = abs( B( image1, k ) - B( image2, k ) );
                k += image1.pixelSize;
        }
        
        WRITE_OUT( outhandle, out );
        
        return TCL_OK;
}

int Mix_Cmd(CMD_ARGS)
{
        Tk_PhotoHandle outhandle;
        Tk_PhotoImageBlock image1, image2, out;
        int isize, k;
        double weight, rweight;
        
        CHECK_ARGS ( 4, "image1 image2 out weight" );
        
        Tk_PhotoGetImage( Tk_FindPhoto( interp, Tcl_GetString( objv[1] ) ), &image1 );
        Tk_PhotoGetImage( Tk_FindPhoto( interp, Tcl_GetString( objv[2] ) ), &image2 );
        
        outhandle = Tk_FindPhoto( interp, Tcl_GetString( objv[3] ) );
        Tk_PhotoGetImage( outhandle, &out );
        
        GET_DOUBLE( 4, weight );
        weight = MAX( 0, weight );
        weight = MIN( 1, weight );
        rweight = 1 - weight;
        
        /* at some point add a helpful error message */
        if (!( image1.width     == image2.width     && image1.width     == out.width     &&
               image1.height    == image2.height    && image1.height    == out.height    &&
                   image1.pixelSize == image2.pixelSize && image1.pixelSize == out.pixelSize ))
        {
                return TCL_ERROR;
        }
        
        isize = image1.width * image1.height * image1.pixelSize;
        
        k = 0;
        while ( k < isize ) {
                R( out, k ) = R( image1, k ) * weight + R( image2, k ) * rweight;
                G( out, k ) = G( image1, k ) * weight + G( image2, k ) * rweight;
                B( out, k ) = B( image1, k ) * weight + B( image2, k ) * rweight;
                k += image1.pixelSize;
        }
        
        WRITE_OUT( outhandle, out );
        
        return TCL_OK;
}

int AddAlpha_Cmd(CMD_ARGS)
{
        Tk_PhotoHandle outhandle;
        Tk_PhotoImageBlock image, alpha, out;
        int isize, k;
        
        CHECK_ARGS ( 3, "image alpha out" );
        
        Tk_PhotoGetImage( Tk_FindPhoto( interp, Tcl_GetString( objv[1] ) ), &image );
        Tk_PhotoGetImage( Tk_FindPhoto( interp, Tcl_GetString( objv[2] ) ), &alpha );
        
        outhandle = Tk_FindPhoto( interp, Tcl_GetString( objv[3] ) );
        
        /* at some point add a helpful error message */
        if (!( image.width == alpha.width && image.height == alpha.height && image.pixelSize == alpha.pixelSize ))
        {
                return TCL_ERROR;
        }
        
        out.width = image.width;
        out.height = image.height;
        out.pitch = out.width * 4;
        out.pixelSize = 4;
        out.offset[0] = 0;
        out.offset[1] = 1;
        out.offset[2] = 2;
        out.offset[3] = 3;
        
        /* Allocate memory for new image */
        if ( ( out.pixelPtr = (unsigned char*) calloc( out.width * 4 + out.height * out.pitch, sizeof( unsigned char ) ) ) == NULL ) {
                Tcl_SetObjResult( interp, Tcl_NewStringObj( "Memory allocation error", 24 ) );
                return TCL_ERROR;
        }
        
        isize = image.width * image.height * image.pixelSize;
        
        k = 0;
        while ( k < isize ) {
                R( out, k ) = R( image, k );
                G( out, k ) = G( image, k );
                B( out, k ) = B( image, k );
                A( out, k ) = R( alpha, k );
                k += image.pixelSize;
        }
        
        WRITE_OUT( outhandle, out );
        
        return TCL_OK;
}