2

I have a Tcl procedure which magnifies an image.

proc ShowWindow {wtitle zfactor imgdata} {

puts stdout "Now in the Tcl procedure ShowWindow!";

image create photo idata -data $imgdata;    # create a photo image from the input data
image create photo the_window;      # final window

the_window copy idata -zoom $zfactor $zfactor;  # copy the original and magnify

wm title . $wtitle;         # window title
wm resizable . false false;     # no resizing in both x and y directions

catch {destroy .dwindow};       # since this procedure will be called multiple times
                # we need to suppress the 'window name "dwindow" already exists in parent' message

label .dwindow -image the_window;   # create a label to display the image
pack .dwindow;          # display the image

}

I would like to call this Tcl procedure from C++.

I think that "imgdata" is a ByteArray. Is this correct?

The pertinent code fragment is shown below:

// ...Tcl/Tk initialization omitted...

unsigned char *img; // PPM image data
int num_bytes;      // PPM image file size

// ...routines to initialize img and num_bytes omitted...

Tcl_Obj *tcl_raw;

// transfer the PPM image data into Tcl_Obj

if (!(tcl_raw = Tcl_NewByteArrayObj(img, num_bytes))) {
  cerr << "Tcl_NewByteArrayObj() failed!" << endl;
  cerr << "Exiting..." << endl;
  return 1;
} else {
  cerr << "Tcl_NewByteArrayObj() succeeded!" << endl;
}

Tcl_IncrRefCount(tcl_raw);  // is this really necessary?

// set the Tcl variable "imgdata" to the Tcl_Obj

if (Tcl_SetVar2Ex(tcl_interpreter, "imgdata", "", tcl_raw, TCL_LEAVE_ERR_MSG) == NULL) {
  cerr << "Tcl_SetVar2Ex() failed!" << endl;
  cerr << "Exiting..." << endl;
  return 1;
} else {
  cerr << "Tcl_SetVar2Ex() succeeded!" << endl;
}

// execute the Tcl procedure

if (Tcl_Eval(tcl_interpreter, "ShowWindow TheImage 8 $imgdata") != TCL_OK) {
  cerr << "Tcl_Eval() failed!" << endl;
  cerr << "Tcl_GetStringResult() = " << Tcl_GetStringResult(tcl_interpreter) << endl;
  cerr << "Exiting..." << endl;
  return 1;
} else {
  cerr << "Tcl_Eval() succeeded!" << endl;
}

The program fails at Tcl_Eval(). The program output is:

...
Tcl_NewByteArrayObj() succeeded!
Tcl_SetVar2Ex() succeeded!
Tcl_Eval() failed!
Tcl_GetStringResult() = can't read "imgdata": variable is array
Exiting...

What is the recommended way of doing this?

1 Answers1

1

You can treat the Tcl ByteArrayObj type as a buffer by using Tcl_ByteArraySetLength or Tcl_GetByteArrayFromObj which both give you access to the data part of the Tcl object.

Tcl_Obj *dataObj = Tcl_NewObj();
char *dataPtr = Tcl_SetByteArrayLength(dataObj, 1024);

Now you can use dataPtr to set the bytes in the object. The Tcl_SetByteArrayLength function will make this object become a ByteArray type.

However, you might also want to look at imgscale which I use for stretching Tk images and this uses various interpolation modes eg:

image create photo thumbnail
::imgscale::bilinear $myphoto 64 64 thumbnail 1.0

to downscale some photo into a thumbnail image.

patthoyts
  • 32,320
  • 3
  • 62
  • 93
  • Thanks for the quick response @patthoyts! If I understand correctly, Tcl_NewByteArrayObj(bytes, num_bytes) creates a new ByteArray object and "set(s) the object's internal representation to a copy of the array of bytes given by bytes". If so, why use Tcl_NewObj() and the method you outlined? Please note that I am not trying to create a new Tcl command from C++, but rather trying to call a Tcl procedure from C++. Thanks again! – user1882115 Dec 06 '12 at 13:09
  • 1
    @user Tcl and C++ have _very_ different ideas about what an object is; don't bother trying to copy terminology across. `Tcl_NewObj` allocates a new raw value (initially an empty value) and `Tcl_SetByteArrayLength` configures an existing value to be an array of bytes of a particular length and conveniently returns a pointer to those bytes. You can then fill that array up as you see fit before sending the `Tcl_Obj` value holding it into a suitable routine. – Donal Fellows Dec 06 '12 at 15:17
  • Thanks all for your hints/tips! The only error in the posted code was in: `Tcl_SetVar2Ex(tcl_interpreter, "imgdata", "", tcl_raw, TCL_LEAVE_ERR_MSG)`. The empty double quotes was *not* NULL and thus Tcl interpreted "imgdata" to be an array instead of a binary string. The "correct" call should be: `Tcl_SetVar2Ex(tcl_interpreter, "imgdata", NULL, tcl_raw, TCL_LEAVE_ERR_MSG)` to indicate a scalar variable. In summary: (1) use the C ByteArrayObj APIs (2) use Tcl_SetVar2Ex() to create and set the Tcl variable – user1882115 Dec 07 '12 at 01:30