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:
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.