[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. http://jpage-images.1.xpdev-hosted.com/iprocess.dll%|%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 /* 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; } ====== [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 [http://code.google.com/p/libsquish/] [http://www.sjbrown.co.uk/squish/]. I first mentioned this project at [http://wiki.tcl.tk/22182#pagetocc00b9d79]. ---- [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 ? <> Category Image Processing