hypnotoad writes: While pure-script image scaling is "nice" for one-off projects, many of us need something that is fast, and capable of scaling images into arbitrary scale factors. Below is a routine that is based in C that we use in production to scale ship templates to fit on a canvas for tracing. As users need different levels of detail, it had to scale and line up pixel-for pixel for what was being overlaid on the canvas.
/* * save as "imgscale.c", compile with: * $ gcc -shared -o imgscale.so -I/usr/include/tcl8.5 imgscale.c -DUSE_TCL_STUBS -DUSE_TK_STUBS -ltclstub8.5 -ltkstub8.5 */ /* ** Scale an image using grid sampling ** Please note this implementation will crash if the ** destination image was not already created ** ** Arguments: srcimg newwidth newheight destimg alpha ** srcimg: A photo object created with "image create photo" ** newwidth: The new width of the image in pixels ** newheight: The new height of the image in pixels ** destimage: Name for the resulting image ** alpha: Alpha to apply to the image (1 - opaque, 0-clear) */ #include <tcl.h> #include <tk.h> static int irmScaleImgCmd( void *pArg, Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[] ){ char *srcName, *destName; Tk_PhotoImageBlock srcBlock, destBlock; Tk_PhotoHandle srcImage, destImage; int di, dj; double scalex, scaley, sx2, sy2, newalpha; int returnCode; int width, height, newwid, newhgt; if (objc != 5 && objc != 6) { Tcl_WrongNumArgs(interp, 1, objv, "srcimg newwid newhgt destimg ?alpha?"); return TCL_ERROR; } srcName = Tcl_GetString(objv[1]); if (Tcl_GetIntFromObj(interp, objv[2], &newwid)) [] return TCL_ERROR; if (Tcl_GetIntFromObj(interp, objv[3], &newhgt)) return TCL_ERROR; destName=Tcl_GetString(objv[4]); if (objc == 6) { if (Tcl_GetDoubleFromObj(interp, objv[5], &newalpha)) return TCL_ERROR; } else { newalpha=1.0; } if (newalpha>1.0) { newalpha=1.0; } srcImage = Tk_FindPhoto(interp, srcName); if (!srcImage) return TCL_ERROR; Tk_PhotoGetSize(srcImage, &width, &height); Tk_PhotoGetImage(srcImage, &srcBlock); if (!srcBlock.pixelPtr) { /* Empty image. Do nothing */ return TCL_OK; } if (srcBlock.pixelSize != 4 && srcBlock.pixelSize!=3) { Tcl_AppendResult(interp, "I can't make heads or tails from this image, the bitfield is neither 3 nor 4", NULL); return TCL_ERROR; } destImage = Tk_FindPhoto(interp, destName); if (!destImage) return TCL_ERROR; Tk_PhotoBlank(destImage); Tk_PhotoSetSize(interp, destImage, newwid, newhgt); destBlock.width = newwid; destBlock.height = newhgt; scalex = srcBlock.width / (double) newwid; scaley = srcBlock.height / (double) newhgt; sx2 = scalex / 2.0; sy2 = scaley / 2.0; destBlock.pixelSize = 4; destBlock.pitch = newwid * 4; destBlock.offset[0] = 0; destBlock.offset[1] = 1; destBlock.offset[2] = 2; destBlock.offset[3] = 3; destBlock.pixelPtr = (unsigned char *) Tcl_Alloc(destBlock.width * destBlock.height * 4); /* Loop through and scale */ for (dj=0 ; dj<destBlock.height ; dj++) { for (di=0 ; di<destBlock.width ; di++) { int si, sj; int cx = (int)(di * scalex); int cy = (int)(dj * scaley); int points = 1; double red = 0.0, green = 0.0, blue = 0.0, alpha = 0.0; int newoff = destBlock.pitch*dj + destBlock.pixelSize*di; int startoff = srcBlock.pitch*cy + srcBlock.pixelSize*cx; red = (double) srcBlock.pixelPtr[startoff + srcBlock.offset[0]]; green = (double) srcBlock.pixelPtr[startoff + srcBlock.offset[1]]; blue = (double) srcBlock.pixelPtr[startoff + srcBlock.offset[2]]; if (srcBlock.pixelSize == 4) { alpha = (double) srcBlock.pixelPtr[startoff + srcBlock.offset[3]]; } else { alpha += 255; } for (sj=(int)cy-sy2 ; sj<(int)cy+sy2 ; sj++) { if (sj < 0) continue; if (sj > srcBlock.height) continue; for (si=(int)cx-sx2 ; si<(int)cx+sx2 ; si++) { int offset = srcBlock.pitch*sj + srcBlock.pixelSize*si; if (si < 0) continue; if (si > srcBlock.width) continue; points++; red += (double) srcBlock.pixelPtr[offset + srcBlock.offset[0]]; green += (double) srcBlock.pixelPtr[offset + srcBlock.offset[1]]; blue += (double) srcBlock.pixelPtr[offset + srcBlock.offset[2]]; if (srcBlock.pixelSize == 4) { alpha += (double) srcBlock.pixelPtr[offset + srcBlock.offset[3]]; } else { alpha += 255; } } } destBlock.pixelPtr[newoff + destBlock.offset[0]] = (unsigned char)(red / points); destBlock.pixelPtr[newoff + destBlock.offset[1]] = (unsigned char)(green / points); destBlock.pixelPtr[newoff + destBlock.offset[2]] = (unsigned char)(blue / points); destBlock.pixelPtr[newoff + destBlock.offset[3]] = (unsigned char)(alpha*newalpha / points); } } returnCode = Tk_PhotoPutBlock(interp, destImage, &destBlock, 0, 0, destBlock.width, destBlock.height, TK_PHOTO_COMPOSITE_SET); return returnCode; } /* ** The following is the only public symbol in this source file. . */ int DLLEXPORT Imgscale_Init(Tcl_Interp *interp){ if(Tcl_InitStubs(interp, TCL_VERSION, 0) == NULL) { return TCL_ERROR; } if(Tk_InitStubs(interp, TK_VERSION, 0) == NULL) { return TCL_ERROR; } Tcl_CreateObjCommand(interp, "image_scale", irmScaleImgCmd, 0, 0); return TCL_OK; }
2014-06-06
arw: I wrapped the above code in an Imagescale TEA package and placed it on github.com . This might help some of you to compile it under Windows, OSX and Linux.
RZ - 2010-02-04 10:34:03
Any chance of including this in the tk [image] command?
aspect: stubsified and added build instructions with help from the irc channel.
EG: Added a check for an empty source image, which leads to a segmentation fault.
ralfixx Note: it seems TK 8.5 is required for this. I get SEGVs with 8.3.3
hypnotoad The problem is pre 8.5 Tk core's don't do Alpha blending. While I was experimenting, I did come up with an alternate algorithm that de-saturated the image. Here's that version:
/* ** Scale an image using grid sampling ** Please note this implementation will crash if the ** destination image was not already created */ #include <tcl.h> #include <tk.h> static int irmScaleImgNoAlphaCmd( void *pArg, Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[] ){ char *srcName, *destName; Tk_PhotoImageBlock srcBlock, destBlock; Tk_PhotoHandle srcImage, destImage; int di,dj; double scalex, scaley, sx2, sy2, newalpha; int returnCode; int width, height, newwid, newhgt; if (objc != 5 && objc !=6){ Tcl_WrongNumArgs(interp, 1, objv, "srcimg newwid newhgt destimg ?alpha?"); return TCL_ERROR; } srcName=Tcl_GetString(objv[1]); if (Tcl_GetIntFromObj(interp,objv[2], &newwid)) return TCL_ERROR; if (Tcl_GetIntFromObj(interp,objv[3], &newhgt)) return TCL_ERROR; destName = Tcl_GetString(objv[4]); if (objc==6) { if (Tcl_GetDoubleFromObj(interp,objv[5], &newalpha)) return TCL_ERROR; } else { newalpha = 1.0; } if (newalpha > 1.0) { newalpha = 1.0; } srcImage = Tk_FindPhoto(interp, srcName); if (!srcImage) return TCL_ERROR; Tk_PhotoGetSize(srcImage, &width, &height); Tk_PhotoGetImage(srcImage, &srcBlock); if (srcBlock.pixelSize != 4 && srcBlock.pixelSize!=3) { Tcl_AppendResult(interp, "I can't make heads or tails from this image, the bitfield is neither 3 nor 4", NULL); return TCL_ERROR; } destImage = Tk_FindPhoto(interp, destName); if (!destImage) return TCL_ERROR; Tk_PhotoBlank(destImage); Tk_PhotoSetSize(interp, destImage, newwid, newhgt); destBlock.width = newwid; destBlock.height = newhgt; scalex = srcBlock.width / (double)newwid; scaley = srcBlock.height / (double)newhgt; sx2 = scalex / 2.0; sy2 = scaley / 2.0; destBlock.pixelSize = 4; destBlock.pitch = newwid * 4; destBlock.offset[0] = 0; destBlock.offset[1] = 1; destBlock.offset[2] = 2; destBlock.offset[3] = 3; destBlock.pixelPtr = (unsigned char *) Tcl_Alloc(destBlock.width * destBlock.height * 4); /* Loop through and scale */ for (dj=0 ; dj<destBlock.height ; dj++) { for (di=0 ; di<destBlock.width ; di++) { int si, sj; int cx = (int)(di*scalex); int cy = (int)(dj*scaley); int points = 1; double red=0.0, green=0.0, blue=0.0, alpha=0.0; int newoff = destBlock.pitch*dj+destBlock.pixelSize*di; int startoff = srcBlock.pitch*cy+srcBlock.pixelSize*cx; red = (double) srcBlock.pixelPtr[startoff + srcBlock.offset[0]]; green = (double) srcBlock.pixelPtr[startoff + srcBlock.offset[1]]; blue = (double) srcBlock.pixelPtr[startoff + srcBlock.offset[2]]; if (srcBlock.pixelSize == 4) { alpha = (double) srcBlock.pixelPtr[startoff + srcBlock.offset[3]]; } else { alpha = 255; } for (sj=(int)cy-sy2 ; sj<(int)cy+sy2 ; sj++) { if (sj < 0) continue; if (sj > srcBlock.height) continue; for (si=(int)cx-sx2 ; si<(int)cx+sx2 ; si++) { int offset = srcBlock.pitch*sj + srcBlock.pixelSize*si; if (si < 0) continue; if (si > srcBlock.width) continue; points++; red += (double) srcBlock.pixelPtr[offset + srcBlock.offset[0]]; green += (double) srcBlock.pixelPtr[offset + srcBlock.offset[1]]; blue += (double) srcBlock.pixelPtr[offset + srcBlock.offset[2]]; if (srcBlock.pixelSize == 4) { alpha += (double) srcBlock.pixelPtr[offset + srcBlock.offset[3]]; } else { alpha += 255; } } } /* ** As an experiment, instead of alpha blending we will desaturate ** the image */ { int padding = 1.0-newalpha*255; double range = (255.0-padding)/255.0; destBlock.pixelPtr[newoff+destBlock.offset[0]] = (unsigned char)(red/points*range+padding); destBlock.pixelPtr[newoff+destBlock.offset[1]] = (unsigned char)(green/points*range+padding); destBlock.pixelPtr[newoff+destBlock.offset[2]] = (unsigned char)(blue/points*range+padding); /*destBlock.pixelPtr[newoff+destBlock.offset[3]]=255;*/ } } } returnCode = Tk_PhotoPutBlock(interp, destImage, &destBlock, 0, 0, destBlock.width, destBlock.height, TK_PHOTO_COMPOSITE_SET); return returnCode; }
RLE (2012-11-09): imgscale.c did not compile for me until I added
#include <tcl.h> #include <tk.h>
above the function definition. I made the changes in the code above.