C Image Processing

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 repetitive 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;
}

HJG 2009-09-20 The download-link does not work. jdp 2009-09-21 Just tested it, and it does seem to work. I'll probably post an updated version soon anyway, that DLL has a couple issues.


AMG: I recently wrote some similar code for scripted processing of infrared- and visible-spectrum photoimagery. It only works on square images with power-of-two dimensions and four 8-bit interleaved channels. The images are stored in simple headerless binary strings. The dimensions are inferred from the string length. There are commands to blur images, "spread" images, average image pairs, "overlay" image pairs, resample images, etc. (The words in quotes are taken from GIMP.) The commands for blurring and spreading (and any other operation that works on groups of neighboring pixels) allow you to specify neighboring images from a tiled mosaic, so that individual image tiles can be seamlessly reassembled even after processing.

I split the code into two separate packages. One does all the image processing, and it has no dependency on Tk. The other only has a command for converting to a photo.

Oh yeah, there are also commands for decompressing from and compressing to S3TC/DXT textures. They're just simple wrappers around the Squish library. No, not Squish/Tk, but rather libsquish [L1 ] [L2 ].

I first mentioned this project at GSoC Idea: Tk - Factor Photo Image Handling.

Update: See Critcl image processing for similar, free code.


jdp 2009-11-13: If anyone's wondering what happened to the fixed version that was coming, along with the IIR Gaussian filter, here's my excuse: "my homework ate it". I started some courses at the community college in September and I'm not really getting any coding time anymore. I'll try to have another go at finishing it over the Christmas break. In the meanwhile, does anyone want me to post the current source somewhere so that they can have a go at it?

AK - 2009-11-13 18:55:38

Yes please.

Note: I will not have time to have a go myself, other however might. It is also a bit of redundancy against a system crash and other accidents.

Maybe the Half Bakery if your code is just a tarball. Or do you have it already under version control ?

jdp 2009-11-14 21:41 UTC - Uploaded to [L3 ]. Everything actually currently compiles except the IIR filter, so that's disabled by default. And there's still not much parameter checking, so passing certain values can produce "interesting" results.