Version 4 of tclepeg

Updated 2011-12-26 00:51:10 by dzach

epeg is a super-fast library for resizing JPEGs. It offers an improvement of at least an order of magnitude over other known libraries like ImageMagick. Here are some numbers indicative of its speed:

 $ time ./tclepeg test.jpeg test.jpg
 real    0m0.063s
 user    0m0.060s
 sys     0m0.000s

 $ time convert -resize 426x640 test.jpeg test.jpg 
 real    0m1.158s
 user    0m1.048s
 sys     0m0.052s

In the above example the improvement is 18 fold. test.jpeg is a 2592x3888 image that is resized to a 426x640 one using the epeg library. Convert is ImageMagick's convert utility. It took epeg 63 msec to do the conversion against 1158 msec for ImageMagick. These timings include reading/writing to disk. tclepeg takes about 1msec to do the conversion on the same system.

tclepeg is a first effort to bring epeg's functionality to TCL by means of a binary linux library that can be loaded into a TCL program. Size options are not yet parsed and the quality value is fixed, but this initial C code is posted here with the hope that more competent tclers may step in to improve it.


C code for the tclepeg library

The library provides a TCL command epeg that takes as an argument JPEG image data. In the code below the output is fixed to 320x240px, 75% JPEG quality. See epeg for more. Compiles on an Ubuntu Linux machine.

 /*
 * tclepeg.c -- A fast JPEG resize (reduction) Tcl C extension based on the epeg library
 * For a source repository of epeg look in : 
 *         http://maemo.gitorious.org/maemo-af/epeg
 *         http://svn.enlightenment.org/svn/e/OLD/epeg/
 * 
 * Adapted to tcl by dzach
 * 
 */
 #include <tcl.h>
 #include <stdlib.h>
 #include <string.h>
 #include "Epeg.h"

 static int 
 Tclepeg_Cmd(ClientData cdata, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[])
 {
         if(objc < 2) {
                 Tcl_SetObjResult(interp, Tcl_NewStringObj("wrong # args. Should be epeg ?option value ...? jpegdata", -1));
                 return TCL_ERROR;
         }
         int i, w=320, h=240, q=50, sizei, sizeo;
         unsigned char *imi, *imo, *opt, *val;
         Epeg_Image *im;

         // set user options
         for (i=1; i<objc-2; i++)
         {
                 opt = Tcl_GetString(objv[i]);
                 if (strncmp(opt, "-w", 2) == 0)
                 {
                         w = atof(Tcl_GetString(objv[++i]));
                 }
                 else if (strncmp(opt, "-h", 2) == 0)
                 {
                         h = atof(Tcl_GetString(objv[++i]));
                 }
                 else if (strncmp(opt, "-q", 2) == 0)
                 {
                         q = atof(Tcl_GetString(objv[++i]));
                 }
                 else
                 {
                         printf("option %s is errelevant\n",Tcl_GetString(objv[i]));
                 }
         }
         imi = Tcl_GetByteArrayFromObj(objv[objc-1], &sizei);
         if ((imo = (unsigned char *)malloc(sizei)) == NULL)
         {
                 Tcl_SetResult(interp,NULL,NULL);
                 return TCL_ERROR;
         }
         // now do image resize with epeg

         // recreate image in memory
         if ( (im = epeg_memory_open(imi,sizei)) == NULL)
         {
                 Tcl_SetResult(interp,NULL,NULL);
                 return TCL_ERROR;
         }
         epeg_decode_size_set           (im, w, h);
         epeg_quality_set               (im, q);
         epeg_thumbnail_comments_enable (im, 1);
         epeg_memory_output_set         (im, &imo, &sizeo);

         if (epeg_encode(im) != 0)
         {
                 Tcl_SetResult(interp,NULL,NULL);
                 return TCL_ERROR;
         }
         epeg_close                     (im);

         // resize allocated output image memory to free excessive memory
         if ((imo = realloc(imo, sizeo)) == NULL)
         {
                 Tcl_SetResult(interp,NULL,NULL);
                 return TCL_ERROR;
         }

         // prepare tcl command output
         Tcl_Obj *timo = Tcl_NewByteArrayObj(imo, sizeo);
         Tcl_SetObjResult(interp, timo);
         free(imo);
         return TCL_OK;
 }

 /*
 * Tclepeg_Init -- Called when Tcl loads the extension.
 */
 int DLLEXPORT
 Tclepeg_Init(Tcl_Interp *interp)
 {
         if (Tcl_InitStubs(interp, TCL_VERSION, 0) == NULL) {
                 return TCL_ERROR;
         }
         /* changed this to check for an error - GPS */
         if (Tcl_PkgProvide(interp, "tclepeg", "0.2") == TCL_ERROR) {
                 return TCL_ERROR;
         }
         Tcl_CreateObjCommand(interp, "epeg", Tclepeg_Cmd, NULL, NULL);
         return TCL_OK;
 }

Compile using:

 gcc -shared tclepeg.c libepeg.a -ljpeg -o tclepeg.so

libepeg.a and libjpeg have to be in the same directory with tclepeg.c or else their path should be changed so that the compiler knows where to find them.

Usage example:

 load ./tclepeg.so
 # read in a JPEG image
 set fd [open test.jpeg]
 fconfigure $fd -translation binary
 set img [read $fd]
 close $fd

 # write reduced image
 set fd [open out.jpeg w] 
 fconfigure $fd -translation binary 
 # let tclepeg do the work
 puts -nonewline $fd [epeg -width 320 -height 240 -quality 50 $img]
 close $fd

provided the tclepeg.so library is in the present working directory directory.

You are welcome to improve the above code and post it here.