Version 7 of C compiled image processing on an interactive Bwise canvas

Updated 2004-03-02 07:52:52

by Theo Verelst


FOSDEM update!

When presenting BWise at fosdem, I as last demonstration had prepared this example, and of course Murphy had to come along and spoil it all..

I short there seemed to be no way to get this bwise/C program/image view block chain to work without error also when I tried it again later in belgium. A few days later I tried again, and found that the

   exec sine.exe

in essence fails to operate under tcl (wish) 8.4.4 from activestate, it generates a for me as yet untraceble 'out of bounds' error of some kind.

This page now contains a quick fix for the problem, which could be caused by cygwin or by some stack/heap space inside a process problem, consisting of adding a -mno-cygwin flag to the compile command. Then all runs smooth and well (even pretty fast) again.

I also changed a loop condition in the C program to let max represent the actual maximum of the sine number (small change).

(March 2, 2004) I've upgraded cygwin to the latest version, and the problem hasn't repeated itself, without changing anything else. Well well.

GPS Hi Theo. I have found such memory problems and it has often been an off by 1 error in my C code. The Electric Fence tool helps for debugging heap memory usage errors. There is also another tool that works with Linux called Valgrind. Some of the Tcl developers use it and IIRC there is a target in the Makefile for running Tcl with Valgrind.


I'm starting this page as a sketch of the idea that a c compiler can be run as part of a bwise canvas, where compilation is automatic, and followed by automatic execution of the resulting program, and in the first example showing of the esulting image on-canvas.

Sketch doesn't mean the method doesn't in principle work without (much) adaptation in general, but I'm not done doing a streamlined XP/linux automatic version, a few things require hand actions.

http://82.168.209.239/Wiki/srcsine1.png

The following two files are needed in the current directory where bwise (one file version: [L1 ] must be running, under I guess any reasonable version of tcl/tk. You need cygwin on windows to have a good C compiler with unix interface, which I used in the example, or you can be running on Linux, where the current user must have access right to gcc. On windows, you may have to set (depending on your configuration):

 append env(PATH) ";c:\\cygwin\\bin"

I made this and some more examples first on linux (Redhad 9, compiled tk 8.4), while I made this page using a windows (XP with precompiled tcl8.4) running version from the same files and file system, except I had some versions, I think they should interchange without work. The 'newimage sine.ppm' command, which can generate a block with image in one stroke, isn't in the saved canvas, because then blocks are saved as individual parts, linked implicitly by tags under bwise, so I had to edit the saved canvas content file to prepare a block of size 512 x 512 because at first run, there isn't an sine.ppm image yet.

The first block should be 'funprop'-ed through the popup menu to get the chain going and produce the output image. Depending om machine speed that takes a number of seconds, also the second block, which runs after the compiler compiled the small program, which produces the image, takes a little while to run, depending on machine speed, I used fairly fast machines. Of course it also depends on the OS..


 # file: cansine6.tcl
 global bcount scopeindex wireindex shellindex drumindex entrycount moncount proccount seqcount stackcount termindex textcount
 set bcount 0
 set scopeindex 0
 set wireindex 5
 set shellindex 0
 set drumindex 0
 set entrycount 3
 set moncount 1
 set proccount 2
 #image create photo sine -file sine.ppm
 image create photo sine -width 512 -height 512
 .mw.c create rectangle 246.0 10.0 286.0 40.0 -activedash {} -activefill {} -activeoutline {} -activeoutlinestipple {} -activestipple {} -activewidth 0.0 -dash {} -dashoffset 0 -disableddash {} -disabledfill {} -disabledoutline {} -disabledoutlinestipple {} -disabledstipple {} -disabledwidth 0 -fill yellow -offset 0,0 -outline darkblue -outlineoffset 0,0 -outlinestipple {} -state {} -stipple {} -tags {Proc1 newblock block} -width 1.0
 .mw.c create text 266.0 40.0 -activefill {} -activestipple {} -anchor n -disabledfill {} -disabledstipple {} -fill darkblue -font {Helvetica -12} -justify left -offset 0,0 -state {} -stipple {} -tags {Proc1 crb label} -text Proc1 -width 0
 .mw.c create text 245.0 29.0 -activefill {} -activestipple {} -anchor se -disabledfill {} -disabledstipple {} -fill black -font {Helvetica -12} -justify left -offset 0,0 -state {} -stipple {} -tags {Proc1 crb pinname in} -text in -width 0
 .mw.c create line 226.0 30.0 246.0 30.0 -activedash {} -activefill {} -activestipple {} -activewidth 0.0 -arrow none -arrowshape {8 10 3} -capstyle butt -fill darkblue -dash {} -dashoffset 0 -disableddash {} -disabledfill {} -disabledstipple {} -disabledwidth 0.0 -joinstyle round -offset 0,0 -smooth 0 -splinesteps 12 -state {} -stipple {} -tags {Proc1 newblock pin in typein} -width 2.0
 .mw.c create text 287.0 29.0 -activefill {} -activestipple {} -anchor sw -disabledfill {} -disabledstipple {} -fill black -font {Helvetica -12} -justify left -offset 0,0 -state {} -stipple {} -tags {Proc1 crb pinname out} -text out -width 0
 .mw.c create line 306.0 30.0 286.0 30.0 -activedash {} -activefill {} -activestipple {} -activewidth 0.0 -arrow none -arrowshape {8 10 3} -capstyle butt -fill darkblue -dash {} -dashoffset 0 -disableddash {} -disabledfill {} -disabledstipple {} -disabledwidth 0.0 -joinstyle round -offset 0,0 -smooth 0 -splinesteps 12 -state {} -stipple {} -tags {Proc1 newblock pin out typeout} -width 2.0
 .mw.c create rectangle 351.0 10.0 391.0 40.0 -activedash {} -activefill {} -activeoutline {} -activeoutlinestipple {} -activestipple {} -activewidth 0.0 -dash {} -dashoffset 0 -disableddash {} -disabledfill {} -disabledoutline {} -disabledoutlinestipple {} -disabledstipple {} -disabledwidth 0 -fill yellow -offset 0,0 -outline darkblue -outlineoffset 0,0 -outlinestipple {} -state {} -stipple {} -tags {Proc2 newblock block} -width 1.0
 .mw.c create text 371.0 40.0 -activefill {} -activestipple {} -anchor n -disabledfill {} -disabledstipple {} -fill darkblue -font {Helvetica -12} -justify left -offset 0,0 -state {} -stipple {} -tags {Proc2 crb label} -text Proc2 -width 0
 .mw.c create text 350.0 29.0 -activefill {} -activestipple {} -anchor se -disabledfill {} -disabledstipple {} -fill black -font {Helvetica -12} -justify left -offset 0,0 -state {} -stipple {} -tags {Proc2 crb pinname in} -text in -width 0
 .mw.c create line 331.0 30.0 351.0 30.0 -activedash {} -activefill {} -activestipple {} -activewidth 0.0 -arrow none -arrowshape {8 10 3} -capstyle butt -fill darkblue -dash {} -dashoffset 0 -disableddash {} -disabledfill {} -disabledstipple {} -disabledwidth 0.0 -joinstyle round -offset 0,0 -smooth 0 -splinesteps 12 -state {} -stipple {} -tags {Proc2 newblock pin in typein} -width 2.0
 .mw.c create text 392.0 29.0 -activefill {} -activestipple {} -anchor sw -disabledfill {} -disabledstipple {} -fill black -font {Helvetica -12} -justify left -offset 0,0 -state {} -stipple {} -tags {Proc2 crb pinname out} -text out -width 0
 .mw.c create line 411.0 30.0 391.0 30.0 -activedash {} -activefill {} -activestipple {} -activewidth 0.0 -arrow none -arrowshape {8 10 3} -capstyle butt -fill darkblue -dash {} -dashoffset 0 -disableddash {} -disabledfill {} -disabledstipple {} -disabledwidth 0.0 -joinstyle round -offset 0,0 -smooth 0 -splinesteps 12 -state {} -stipple {} -tags {Proc2 newblock pin out typeout} -width 2.0
 .mw.c create line 306.0 30.0 331.0 30.0 -activedash {} -activefill {} -activestipple {} -activewidth 0.0 -arrow none -arrowshape {8 10 3} -capstyle butt -fill darkblue -dash {} -dashoffset 0 -disableddash {} -disabledfill {} -disabledstipple {} -disabledwidth 0.0 -joinstyle round -offset 0,0 -smooth 0 -splinesteps 12 -state {} -stipple {} -tags {wire1 connect wire Proc1 out Proc2 in} -width 1.0
 .mw.c create rectangle 53.0 72.0 565.0 584.0 -activedash {} -activefill {} -activeoutline {} -activeoutlinestipple {} -activestipple {} -activewidth 0.0 -dash {} -dashoffset 0 -disableddash {} -disabledfill {} -disabledoutline {} -disabledoutlinestipple {} -disabledstipple {} -disabledwidth 0 -fill yellow -offset 0,0 -outline darkblue -outlineoffset 0,0 -outlinestipple {} -state {} -stipple {} -tags {sine newblock block} -width 1.0
 .mw.c create text 309.0 584.0 -activefill {} -activestipple {} -anchor n -disabledfill {} -disabledstipple {} -fill darkblue -font {{MS Sans Serif} 8} -justify left -offset 0,0 -state {} -stipple {} -tags {sine crb label} -text sine -width 0
 .mw.c create text 52.0 91.0 -activefill {} -activestipple {} -anchor se -disabledfill {} -disabledstipple {} -fill black -font {{MS Sans Serif} 8} -justify left -offset 0,0 -state {} -stipple {} -tags {sine crb pinname in} -text in -width 0
 .mw.c create line 33.0 92.0 53.0 92.0 -activedash {} -activefill {} -activestipple {} -activewidth 0.0 -arrow none -arrowshape {8 10 3} -capstyle butt -fill darkblue -dash {} -dashoffset 0 -disableddash {} -disabledfill {} -disabledstipple {} -disabledwidth 0.0 -joinstyle round -offset 0,0 -smooth 0 -splinesteps 12 -state {} -stipple {} -tags {sine newblock pin in typein} -width 2.0
 .mw.c create text 566.0 91.0 -activefill {} -activestipple {} -anchor sw -disabledfill {} -disabledstipple {} -fill black -font {{MS Sans Serif} 8} -justify left -offset 0,0 -state {} -stipple {} -tags {sine crb pinname out} -text out -width 0
 .mw.c create line 585.0 92.0 565.0 92.0 -activedash {} -activefill {} -activestipple {} -activewidth 0.0 -arrow none -arrowshape {8 10 3} -capstyle butt -fill darkblue -dash {} -dashoffset 0 -disableddash {} -disabledfill {} -disabledstipple {} -disabledwidth 0.0 -joinstyle round -offset 0,0 -smooth 0 -splinesteps 12 -state {} -stipple {} -tags {sine newblock pin out typeout} -width 2.0
 .mw.c create image 53.0 72.0 -activeimage {} -anchor nw -disabledimage {} -image sine -state {} -tags {sine newimage block image}
 .mw.c create line 411.0 30.0 33.0 92.0 -activedash {} -activefill {} -activestipple {} -activewidth 0.0 -arrow none -arrowshape {8 10 3} -capstyle butt -fill darkblue -dash {} -dashoffset 0 -disableddash {} -disabledfill {} -disabledstipple {} -disabledwidth 0.0 -joinstyle round -offset 0,0 -smooth 0 -splinesteps 12 -state {} -stipple {} -tags {wire4 connect wire Proc2 out sine in} -width 1.0

 # now the block related variables\n 
 set Proc1.bfunc { if ![catch {exec gcc -mno-cygwin -O -o [file rootname ${Proc1.in}] ${Proc1.in} -lm} r] {set Proc1.out [file rootname ${Proc1.in}]} { set Proc1.out $r } }
 set Proc1.bfunc_init {}
 set Proc1.in {sine.c}
 set Proc1.out {sine}
 set Proc2.bfunc {if ![catch [exec ${Proc2.in} ] r2] {set Proc2.out sine.ppm} {} }
 set Proc2.bfunc_init {}
 set Proc2.in {sine}
 set Proc2.out {sine.ppm}
 set sine.bfunc {set sine.out [sine read ${sine.in} ]}
 set sine.bfunc_init {}
 set sine.in {sine.ppm}
 set sine.out {}

 /* File: sine.c */
 /* combine transparent sine graphs in a ppm image */
 /* Commercial rights reserved, Theo Verelst, free use otherwise, mentioning author */ 
 #include <stdio.h>
 #include <string.h>
 #include <malloc.h>
 #include <math.h>

 #define PI 3.1415926535

 unsigned char *im;
 int w,h;

 unsigned char * createim(w,h)
 int w,h;
 {
   unsigned char *d;
   int x,y;
   d = (unsigned char *) malloc(3*w*h);
   for (x=0; x<w; x++)
      for (y=0; y<h; y++) {
         d[(y*w+x)*3+0] = 0;
         d[(y*w+x)*3+1] = 0;
         d[(y*w+x)*3+2] = 0;
      }
   return d;
 }

 int writeppm(n,w,h)
 char n[];
 int w,h;
 {
   FILE *fp;
   fp = fopen(n,"wb");
   if ((void *) fp == NULL ) return(-1);

   fprintf(fp,"P6\n%d %d\n255\n",w,h);
   fwrite(im, 3, w*h,fp);
   fclose(fp);
   return(0);
 }


 void rectangle(x1,y1,x2,y2)
 int x1,x2,y1,y2;
 {
   int i,j;

   for (i=x1; i<x2; i++)
      for (j=y1; j<y2; j++) {
         im[(j*w+i)*3+0] = 0;
         im[(j*w+i)*3+1] = 0;
         im[(j*w+i)*3+2] = 0;
      }
 }



 #define point(mx,my,r,g,b) { im[((my)*w+(mx))*3+0] = (r); im[((my)*w+(mx))*3+1] = (g); im[((my)*w+(mx))*3+2] = (b); }

 #define point_trans(mx,my,r,g,b,a) { im[((my)*w+(mx))*3+0] += (r/a); im[((my)*w+(mx))*3+1] += (g/a); im[((my)*w+(mx))*3+2] += (b/a); }

 #define fpoint(x,y,r,g,b) im[(((int) (0.5+y))*w+((int) (0.5+x)))*3+0] = (r); im[(((int) (0.5+y))*w+((int) (0.5+x)))*3+1] = (g); im[(((int) (0.5+y))*w+((int) (0.5+x)))*3+2] = (b);

 drawsine()
 {
   int i,j,k,l, max;
   double d,e,x;

   max = 4;
   for (x=0; x<2*PI; x+=PI/256) {
      for (k=1; k<=max; k++)
         for (j=h; j>(256-sin(((double) k)*x)*256); j--)
            point_trans((int) (0.5 + (x*512.0)/(2.0*PI)), j, 0, 255, 255, max);
   }
 }

 main(argc,argv)
 int argc;
 char *argv[];
 {
   w = 512; h = 512;
   im = createim(w,h);
   if (im == NULL) { printf("Malloc error: quiting.\n"); exit(-1); }
   rectangle(0,0,w,h);
   drawsine();
   writeppm("sine.ppm", w,h);
 }

The idea of the C program is that a PPM formatted image (large but uncompressed 'true' colour) is written to a fixed file, of which the size can be set by globals h and w, which gets an image by a bar representation of a number (max in drawsine) sine waves of increasing frequency, alpha blended together.

The procedure blocks can be inspected for their *.bfunc associated variables by right clicking the procedure (yellow) block and choosing 'data' from the popup menu. In essence, proc1 compiles the C program, and proc2 runs it, while the image block (called sine) updates the image it shows on the canvas by (re-) reading the sine.ppm image file when it 'fires'.

Bwise lets you fire a block by 'eval' from the popup menu (either the middle or right mouse button), while 'funprop' fires all out-->in connected blocks in sequence as well. So one can also by hand 'eval' each block, and make sure its output value is 'transfer'-ed to the next before executing the next, because the wires do carry information, not just triggers. which in this case is the base name of the source file 'sine'. You may want to have versions for that (sine2), or possibly only for a part of the network. See the in and out pins of the procedures, and their variable content verifiable and editable in the data windows.

The block function for the program execution is simplest: it simply execs the sine(.exe) program we just compiled, basically by :

   exec sine

Except we take the name of the program from the input 'in' pin. The return value of the sine program is ignored (though it is in global variable r2 if we would want it), but the exec is wrapped in a catch to determine wether the program execution was succesfull or in error, for instance when we didn't 'cd' (in small or big console, or maybe you want to hardcode it in the bwise or cansine.tcl files) in tcl to the directory which contains the program file. The output value, stored in the variable associated with the 'out' pin is set to empty list when there is an error, or the the fixed expected sine.ppm filename which should contain the intended image after the C based sine program has completed successfully. So the whole block function, stored in the global variable Proc2.bfunc is (see the cansine.tcl file above, at the bottom):

   if ![catch 
      [
         exec ${Proc2.in} 
      ] r2
   ] {
      set Proc2.out sine.ppm
   } {
   }

The proc1 block function look a bit complicated at first (depending on who's looking), because I wanted it to do some automatic things straightforwardly. In essence it does:

   exec gcc -o sine sine.c -lm

Which is the cygnus or linux command to compile the C program, and creating a program file called sine. Maybe for windows you may want to call it sine.exe, on XP (SP-1) with bash 2.05b.0(9) gcc 3.2, exe isn't needed, except you may want to use it in contrast with a linux executable. The whole command for the above version is:

    if ![catch 
       {exec gcc -O -o [file rootname ${Proc1.in}] ${Proc1.in} -lm}
    r] {
       set Proc1.out [file rootname ${Proc1.in}]
    } {
       set Proc1.out $r 
    }    

The compile command is caught for errors and called with the Proc1.in variables' content as C source file, while the output file is the base name of that filename, that is without .c . When there is no error, the name of the executable is put on the Proc1.out pin, otherwise the return value (probably the error) from the compiler.

The final block called 'sine' has a simple to type, completely tcl function, that is no exec, and no conditions:

   set sine.out [ sine read ${sine.in} ]

The output pin will normally contain an emtpy list, which is fine because as it is it is the last block in the net. The Proc2.in pin variable contains the name of the image file (sine.ppm). The block assumes the existence of a Tk image called 'sine', which is called by name and given the subcommand 'read' with the image name as argument to update the image shown on top of the block.

Normally one could use

   newimage sine.ppm

to get the same block, and possibly add more arguments for instance to put the block in place, but we couldn't create an empty block on the canvas. Here, the canvas was saved, which in Bwise saves the image blocks, too, and the saved file adapted slightly such that it assumes an empty image of size 512x512 in the block, also when the sine.ppm file doesn't yet exist.