Offscreen patcher capture

    Feb 19 2009 | 3:36 pm
    I wonder if there's a way with the new API to export a patcher or a portion of it as an image file (a la jit.desktop but without having the patcher as the frontmost window).
    Not sure it's very clear, my goal is to export easily & quickly the waveform drawn by the waveform~ object, but more generally, this could be useful (without having to use jit.desktop or render a buffer~ in a matrix).
    The best way would be to have an exportimage method in the waveform object.
    Has anyone any idea where to start ? I see jsurface has an "jgraphics_image_surface_writepng" method but how can I obtain the t_jsurface in question ? Can I for example attach my object to a waveform~ and obtain a t_jsurface ?

    • Feb 20 2009 | 1:46 pm
      Here is how you can do it. Be warned though that this is undocumented and is not guaranteed to keep working in future versions of max. Below is some loosely sketched code that could work.
      The patcherview object has an A_CANT method called "getimage" with the following prototype:
      t_jsurface* patcherview_getimage(t_patcherview *pv, t_rect *rect);
      So, you could use the jpatcher_get_firstview() function to get a patcherview object from the patcher. Then, do:
      t_rect rect; // fill in the rect with the coords you want // You could call jbox_get_rect_for_view to // get the rect for a given box in a given view. // This would take into account the difference between // patching and presentation modes. surface = object_method(patcherview, gensym("getimage"), &rect);
      Then you could use jgraphics_image_surface_writepng() to write the surface to a file. Or you could use the surface to draw to a jgraphics context later in a UI object's paint method. I'm sure you could have lots of fun with that.
      When you are done make sure to call jgraphics_surface_destroy() to free the surface.
    • Feb 22 2009 | 9:51 pm
      Thanks a lot,
      I'll give it a try tomorrow and let you know
      Thanks again
    • Feb 23 2009 | 10:09 am
      Hi Rob,
      I'm trying to implement what you said, but there's a problem with the jgraphics_image_surface_writepng function in the link :
      Undefined symbols: "_jgraphics_image_surface_writepng", referenced from: _capture_bang in capture.o ld: symbol(s) not found collect2: ld returned 1 exit status
      Can you reproduce this ?
      See code below :
      #include "ext.h"
      #include "ext_obex.h"
      #include "jpatcher_api.h"
      #include "jgraphics.h"
      #include "ext_path.h"
      ////////////////////////// object struct
      typedef struct _capture
      	t_object a_ob;
      } t_capture;
      ///////////////////////// function prototypes
      //// standard set
      void *capture_new(t_symbol *s, long argc, t_atom *argv);
      void capture_free(t_capture *x);
      void capture_assist(t_capture *x, void *b, long m, long a, char *s);
      void capture_bang(t_capture *x); // incoming bang message
      void capture_export(t_capture *x, t_symbol *s, long argc, t_atom *argv);
      void capture_version(t_capture *x);
      //////////////////////// global class pointer variable
      void *capture_class;
      int main(void)
      	t_class *c;
      	c = class_new("capture", (method)capture_new, (method)capture_free, (long)sizeof(t_capture), 0L, A_GIMME, 0);
          class_addmethod(c, (method)capture_bang, "bang", 0);
          class_addmethod(c, (method)capture_assist,	"assist", A_CANT, 0);
      	class_addmethod(c, (method)capture_export, "export", A_GIMME, 0);
      	class_addmethod(c, (method)capture_version, "version",	0);
      	class_register(CLASS_BOX, c);
      	capture_class = c;
      	post("Copyright © 2009 capture Leopold Frey");
      	post("tBuild LF - %s %s",__DATE__,__TIME__);
      	return 0;
      void capture_assist(t_capture *x, void *b, long m, long a, char *s)
      	if (m == ASSIST_INLET) { //inlet
      		sprintf(s, "I am inlet %ld", a);
      	else {	// outlet
      		sprintf(s, "I am outlet %ld", a);
      void capture_export(t_capture *x, t_symbol *s, long argc, t_atom *argv)
      void capture_bang(t_capture *x)
      	t_object *jp;
      	t_object *pv;
      	t_rect rect;
      	t_jsurface *surface;
      	char *filename;
      	short path_id;
      	long dpi;
      	t_max_err err;
      	// get the object's parent patcher
      	err = object_obex_lookup(x, gensym("#P"), (t_object **)&jp);
      	if (err != MAX_ERR_NONE)
      	pv = jpatcher_get_firstview(gensym("#P")->s_thing);
      	filename = "test";
      	path_id = path_getdefault(); // todo : use path_frompathname
      	dpi = 72;
      	if(pv) {
      		rect.x = 0;
      		rect.y = 0;
      		rect.width = 100;
      		rect.height = 100;
      		surface = object_method(pv, gensym("getimage"), &rect); // =? patcherview_getimage(pv, &rect);
      			err = jgraphics_image_surface_writepng(surface, filename, path_id, dpi);
      			if (err != MAX_ERR_NONE)
      				object_post((t_object *)x, "Error while writing image");
      void capture_free(t_capture *x)
      void capture_version(t_capture *x)
      	object_post((t_object *)x,"Copyright © 2009 capture Leopold Frey");
      	object_post((t_object *)x,"tBuild LF - %s %s",__DATE__,__TIME__);
      void *capture_new(t_symbol *s, long argc, t_atom *argv)
      	t_capture *x = NULL;
      	if (x = (t_capture *)object_alloc(capture_class)) {
      	return (x);
    • Feb 24 2009 | 3:12 pm
      Hi there,
      I've found a workaround to this link problem. I'm now using jgraphics_write_image_surface_to_filedata instead of jgraphics_image_surface_writepng (still don't know where this problem comes from).
      Now, jgraphics_write_image_surface_to_filedata should, according to the sdk (jgraphics.h line 344), be able to write done JPEG files as well.
      But it crashes the app systematically on a mac, and doesn't do anything on a pc while The PNG export is fine (and fun) on both platforms.
      Attached crash report and a simple example.
      I'm also working on using the surface in an UI object's paint method as Rob suggested but I'm also having trouble there (based on the scripto example in the sdk). I'll provide a simple example ASAP.
    • Feb 25 2009 | 6:25 am
      Just in case anyone is (or will be) interested in this thread, I've made a simple example of painting a patcherview inside another ui object.
      See example attached.
      I've also made an external which combines both methods (rendering a patcherview or a portion of it in a file or in an ui object). Rendering a waveform to an image has never been easier ! I'll release it soon.
      Questions : in the attached example, I'm building a patcher window and placing a nobox ui object in it (like the scripto example of the sdk). Is it possible to prevent this patcher from being edited or switched to presentation mode (like the file browser for example) ? I assume there are just a few attributes to add to the following code, but which of them ? Is this part of a private API ?
      sprintf(parsebuf,"@defrect 0 0 100 100 @title capture @enablehscroll 0 @enablevscroll 0 @presentation 0 @toolbarid """);
      patcher = (t_object *)object_new_typed(CLASS_NOBOX,gensym("jpatcher"),1, &a);
      What about JPEG rendering (see previous post) ?
      By the way, this new API is great, and porting to windows is easy : there's nothing to do !
      Thanks Cycling Folks.
    • Feb 25 2009 | 7:29 pm
      Hi Leo,
      As you found, jgraphics_image_surface_writepng is not exported. Perhaps we can export that in max/msp 5.0.7.
      Meanwhile I'm glad jgraphics_write_image_surface_to_filedata is mostly working for you. We'll have to look into the JPEG writing crash when we get a chance. It is quite possible that that has never been tested.
    • Aug 02 2014 | 5:09 pm
      Hi Léopold,
      I know this thread is a bit old, but is your patcher image rendering object (still) available somewhere?
      Thanks, bb