Version 3 of rphoto

Updated 2010-08-23 22:10:13 by AK

Richard Suchenwirth 2010-03-23 - I needed a fast photo image rotation quite badly at work, which would rotate fairly large images (say 2000 * 3000 pixels) by 90 degrees, in place. So after a long while, I did some C coding again, and some hours later, there it was: implementing a single command

 rphoto imname ccw

where imname is the name of a photo image, and ccw is 1 for counter-clockwise, 0 for clockwise. This is a standalone extension, but come to think, wouldn't it be nice to have this functionality as an option for image copy ? :^)

/* rphoto.c - extension to make Tk photo images rotatable by 90 degrees */
/* $Id: rphoto.c,v 1.2 2010/03/23 15:07:30 suchenwi Exp $ */
#include <tk.h>

#define RPHOTO_VERSION "1.0"

#ifdef __WIN32__
#   include <windows.h>
#   ifndef DECLSPEC_EXPORT
#      define DECLSPEC_EXPORT __declspec(dllexport)
#   endif /* DECLSPEC_EXPORT */

BOOL APIENTRY DllMain(HANDLE hModule, DWORD dwReason, LPVOID lpReserved) {
  return TRUE;
}
#else
#   define DECLSPEC_EXPORT
#endif
/*------------------------------------------------------------- Worker function*/

static int rphoto(Tk_PhotoHandle imgin, int ccw) {
  /* Rotate a photo image by 90 degrees in place.
     ccw: 1 for counter-clockwise, 0 for clockwise */
  Tk_PhotoImageBlock inblock;
  int rc;
  int width, height;
  int i, j, k, ii, jj;
  unsigned char *cp, aByte;
  Tk_PhotoImageBlock pixel;
  unsigned char *buf;
    
  rc = Tk_PhotoGetImage(imgin, &inblock);
  if(rc != 1) return TCL_ERROR;
  width  = inblock.width;
  height = inblock.height;
  buf = malloc(width * height * 4);
  if(buf == NULL) return TCL_ERROR;
  pixel.pixelPtr = buf;
  pixel.width  = height;
  pixel.height = width;
  pixel.pitch  = height * 4;
  pixel.pixelSize = 4;
  pixel.offset0 = 0;
  pixel.offset1 = 1;
  pixel.offset2 = 2;
  pixel.offset3 = 3;
  
  for(i = 0; i < height; i++) {
    ii = (ccw? i: height-i-1);
    for (j = 0; j < width; j++) {
      jj = (ccw? width-j-1: j);
      for (k = 0; k < inblock.pixelSize; k++) {
        aByte = *(inblock.pixelPtr
                  + ii * inblock.pitch
                  + jj * inblock.pixelSize
                  + inblock.offsetk);
        cp = pixel.pixelPtr
          + j * pixel.pitch
          + i * pixel.pixelSize
          + pixel.offsetk;
        *cp = aByte;
      }
    }
  }
  Tk_PhotoSetSize(imgin, height, width); /* swapped */
  Tk_PhotoPutBlock(imgin, &pixel, 0, 0, height, width, TK_PHOTO_COMPOSITE_SET);
  free(buf);
  Tk_PhotoSetSize(imgin, 0, 0); /* allow resizing by others */
  return TCL_OK;
}
/*------------------------------------------------------------ Command wrapper */
static int rphotoCmd(ClientData clientdata, Tcl_Interp *interp,
                     int objc, Tcl_Obj *CONST objv) {
  Tk_PhotoHandle imgin;
  int ccw;
  int rc;
  Tcl_Obj *resultPtr = Tcl_GetObjResult(interp);

  if(objc != 3) {
    Tcl_WrongNumArgs(interp, 1, objv, "imgin imgout ccw");
    return TCL_ERROR;
  }
  imgin = Tk_FindPhoto(interp,Tcl_GetStringFromObj(objv1, NULL));
  if (imgin == NULL) {
    Tcl_SetStringObj(resultPtr, "input image does not exist",-1);
    return TCL_ERROR;
  }
  rc = Tcl_GetIntFromObj(interp,objv2,&ccw);
  if(rc != TCL_OK) return TCL_ERROR;

  rc = rphoto(imgin, ccw);
  if(rc != TCL_OK) {
    Tcl_SetStringObj(resultPtr, "out of memory",-1);
    return TCL_ERROR;
  }
  return TCL_OK;
}
/* --------------------------------------------- Package & command registration */
EXTERN_C int DECLSPEC_EXPORT Rphoto_Init(Tcl_Interp* interp) {
  int r;
  const char *tkversion;
  
#ifdef USE_TCL_STUBS
  Tcl_InitStubs(interp, "8.3", 0);
#endif
#ifdef USE_TK_STUBS
  Tk_InitStubs(interp, "8.3", 0);
#endif

  tkversion = Tcl_PkgRequire(interp, "Tk", "8.4", 0);
  if (tkversion == NULL) return TCL_ERROR;

  r = Tcl_PkgProvide(interp, "rphoto", RPHOTO_VERSION);
  
  Tcl_CreateObjCommand(interp, "rphoto", (Tcl_ObjCmdProc *)rphotoCmd,
                       (ClientData)NULL, (Tcl_CmdDeleteProc *)NULL);
  return r;
}

EXTERN_C int DECLSPEC_EXPORT Rphoto_SafeInit(Tcl_Interp* interp) {
  /* We don't need to be specially safe so... */
  return Rphoto_Init(interp);
}