b655bf2004-06-30Martin Stjernholm // This is a roxen module. Copyright © 1999 - 2004, Roxen IS.
2e0a351999-12-09Martin Nilsson //
37bdda1999-05-18Per Hedbor #include <module.h> inherit "module";
3b85e91999-11-15Per Hedbor constant thread_safe=1;
37bdda1999-05-18Per Hedbor  roxen.ImageCache the_cache;
3439822008-10-29 Erik Dahl constant cvs_version = "$Id: cimg.pike,v 1.76 2008/10/28 23:22:02 erikd Exp $";
b3281f2000-09-10Martin Nilsson constant module_type = MODULE_TAG;
bc0fa02001-03-08Per Hedbor constant module_name = "Graphics: Image converter";
7390582000-06-01Martin Nilsson constant module_doc = "Provides the tag <tt>&lt;cimg&gt;</tt> that can be used " "to convert images between different image formats.";
2fafd22000-05-18Kenneth Johansson 
4b3fc02001-03-12Johan Sundström mapping tagdocumentation() {
7390582000-06-01Martin Nilsson  Stdio.File file=Stdio.File(); if(!file->open(__FILE__,"r")) return 0;
b996582004-06-21Martin Stjernholm  mapping doc = compile_string("#define manual\n"+file->read(), __FILE__)->tagdoc;
4b3fc02001-03-12Johan Sundström  foreach(({ "cimg", "cimg-url" }), string tag) doc[tag] += the_cache->documentation(tag + " src='/internal-roxen-testimage'");
7390582000-06-01Martin Nilsson  return doc; }
2fafd22000-05-18Kenneth Johansson 
7390582000-06-01Martin Nilsson #ifdef manual
9b03652001-03-07Kenneth Johansson constant tagdoc=(["cimg":#"<desc tag='tag'><p><short> Manipulates and converts images between different image formats.</short> Provides the tag <tag>cimg</tag> that makes it is possible to convert, resize, crop and in other ways transform images.</p> </desc>
2fafd22000-05-18Kenneth Johansson 
4b3fc02001-03-12Johan Sundström <attr name='src' value='url' required='required'><p>
9b03652001-03-07Kenneth Johansson  The path to the indata file.</p>
2fafd22000-05-18Kenneth Johansson 
4b3fc02001-03-12Johan Sundström <ex><cimg src='/internal-roxen-testimage'/></ex>
2fafd22000-05-18Kenneth Johansson </attr>
3439822008-10-29 Erik Dahl <attr name='filename' value='string'><p> Append the filename value to the path. Recommended is not to append file suffix to the filename since there are settings for handling that automatically through this module settings. </p><p>This is usefull if you want to have images indexed, since many search engines uses the filename as a description of the image.</p> <ex><cimg-url src='/internal-roxen-testimage' filename='Roxen Test Image'/></ex> </attr>
9b03652001-03-07Kenneth Johansson <attr name='data' value='imagedata'><p>
2fafd22000-05-18Kenneth Johansson  Insert images from other sources, e.g. databases through entities or
9b03652001-03-07Kenneth Johansson  variables.</p>
cba1fa2001-08-30Johan Sundström <ex-box><emit source='sql' query='select imagedata from images where id=37'>
79f3f12003-09-08Anders Johansson <cimg data='&sql.imagedata:none;'/>
cba1fa2001-08-30Johan Sundström </emit></ex-box>
ce011f2003-09-19Jonas Wallden </attr> <attr name='process-all-layers'><p>Set this flag to make all image layers visible regardless of their original state.</p> </attr> <attr name='include-layers' value='layer-glob-list'><p>Comma-separated list of glob expressions which is matched against layer names. All matching layers are made visible regardless of their original state.</p> </attr> <attr name='exclude-layers' value='layer-glob-list'><p>Comma-separated list of glob expressions which is matched against layer names. All matching layers are hidden regardless of their original state.</p>
922ef62005-10-06Anders Johansson </attr> <attr name='exclude-invisible-layers'><p>Set this flag to automatically exclude layers that are not shown in the original image. This is only useful in combination with the 'process-all-layers' attribute.</p> </attr> ",
2fafd22000-05-18Kenneth Johansson 
9b03652001-03-07Kenneth Johansson "cimg-url":#"<desc tag='tag'><p><short>
4b3fc02001-03-12Johan Sundström  This tag generates an URL to the manipulated picture.</short>
9b03652001-03-07Kenneth Johansson  <tag>cimg-url</tag> takes the same attributes as <xref href='cimg.tag' />, including the image cache attributes. The use for
4b3fc02001-03-12Johan Sundström  the tag is to insert image-URLs into various places, e.g. a
9b03652001-03-07Kenneth Johansson  submit-box.</p>
2fafd22000-05-18Kenneth Johansson </desc>
4b3fc02001-03-12Johan Sundström <attr name='src' value='url' required='required'><p>
9b03652001-03-07Kenneth Johansson  The path to the indata file.</p>
2fafd22000-05-18Kenneth Johansson 
4b3fc02001-03-12Johan Sundström <ex><cimg-url src='/internal-roxen-testimage'/></ex>
2fafd22000-05-18Kenneth Johansson </attr>
9b03652001-03-07Kenneth Johansson <attr name='data' value='imagedata'><p>
2fafd22000-05-18Kenneth Johansson  Insert images from other sources, e.g. databases through entities or
9b03652001-03-07Kenneth Johansson  variables.</p>
cba1fa2001-08-30Johan Sundström <ex-box><emit source='sql' query='select imagedata from images where id=37'> <cimg-url data='&sql.imagedata;'/> </emit></ex-box>
7390582000-06-01Martin Nilsson </attr>",
0783ea2000-08-29Kenneth Johansson 
ce8fb02001-09-21Johan Sundström "emit#cimg":({ #"<desc type='plugin'><p><short>
9b03652001-03-07Kenneth Johansson  Entitybased version of <xref href='../graphics/cimg.tag' />.</short> Takes the same attributes as <tag>cimg</tag>.</p>
a0909c2004-10-27Jonas Wallden </desc> <attr name='nodata' value='yes | no'><p> Controls suppression of <ent>_.data</ent> in the output. Useful for reducing memory consumption in cached emit tags. The default value
c0f5522005-01-27Anders Johansson  is 'no'.</p> </attr>",
0783ea2000-08-29Kenneth Johansson  ([
ce8fb02001-09-21Johan Sundström "&_.type;":#"<desc type='entity'><p>
9b03652001-03-07Kenneth Johansson  Returns the image's content-type.</p>
0783ea2000-08-29Kenneth Johansson </desc>",
ce8fb02001-09-21Johan Sundström "&_.src;":#"<desc type='entity'><p>
9b03652001-03-07Kenneth Johansson  Returns the path to the indata file.</p>
0783ea2000-08-29Kenneth Johansson </desc>",
ce8fb02001-09-21Johan Sundström "&_.file-size;":#"<desc type='entity'><p>
9b03652001-03-07Kenneth Johansson  Returns the image's file size.</p>
0783ea2000-08-29Kenneth Johansson </desc>",
ce8fb02001-09-21Johan Sundström "&_.xsize;":#"<desc type='entity'><p>
9b03652001-03-07Kenneth Johansson  Returns the width of the image.</p>
0783ea2000-08-29Kenneth Johansson </desc>",
ce8fb02001-09-21Johan Sundström "&_.ysize;":#"<desc type='entity'><p>
9b03652001-03-07Kenneth Johansson  Returns the height of the image.</p>
0783ea2000-08-29Kenneth Johansson </desc>",
ce8fb02001-09-21Johan Sundström "&_.data;":#"<desc type='entity'><p>
08b5672000-09-19Kenneth Johansson  Returns the imagedata given through other sources, like databases
9b03652001-03-07Kenneth Johansson  through entities.</p>
0783ea2000-08-29Kenneth Johansson </desc>" ]) }),
7390582000-06-01Martin Nilsson ]);
2e0a351999-12-09Martin Nilsson #endif
4afee22001-04-03Per Hedbor int do_ext;
fe32ad2001-03-30Johan Sundström void create() { defvar("ext", Variable.Flag(0, VAR_MORE, "Append format to generated images", "Append the image format (.gif, .png, " ".jpg, etc) to the generated images. " "This is not necessary, but might seem " "nicer, especially to people who try " "to mirror your site.")); }
7390582000-06-01Martin Nilsson 
37bdda1999-05-18Per Hedbor void start() {
bd29c22007-01-17Jonas Wallden  // Reuse previous cache object if possible if (the_cache) { // Update reference to callback function in case we've been reloaded the_cache->set_draw_function(generate_image); } else { the_cache = roxen.ImageCache( "cimg", generate_image ); }
4afee22001-04-03Per Hedbor  do_ext = query("ext");
37bdda1999-05-18Per Hedbor }
bd29c22007-01-17Jonas Wallden void stop() { // Force cache object to be recreated in start() the_cache = 0; }
2fa8d52000-06-02Martin Nilsson mapping(string:function) query_action_buttons() {
da43042005-12-16Jonas Wallden  return ([ "Clear Cache":flush_cache ]);
5f13d52000-06-03Martin Nilsson } void flush_cache() { the_cache->flush();
3cca1c2004-05-21Jonas Wallden  // It's possible that user code contains a number of stale URLs in // e.g. <cache> blocks so we can just as well flush the RAM cache to // reduce the risk of broken images. cache.flush_memory_cache();
2fa8d52000-06-02Martin Nilsson } string status() { array s=the_cache->status();
482ba72000-12-30Per Hedbor  return sprintf("<b>Images in cache:</b> %d images<br />\n" "<b>Cache size:</b> %s", s[0], Roxen.sizetostring(s[1]));
2fa8d52000-06-02Martin Nilsson }
4047f82004-09-21Martin Stjernholm void image_error (string fmt, mixed... args) { if (RXML_CONTEXT) // generate_image can be called from within the cimg tag if // id->misc->generate_images is set. RXML.run_error (fmt, @args); else error (fmt, @args); }
6a613a2002-06-17Anders Johansson array(Image.Layer)|mapping generate_image( mapping args, RequestID id )
37bdda1999-05-18Per Hedbor {
689f3a2000-11-21Per Hedbor  array layers;
13b35c2003-07-04Jonas Wallden  // Photoshop layers: don't let individual layers expand the image // beyond the bounds of the overall image. mapping opts = ([ "crop_to_bounds" : 1 ]);
689f3a2000-11-21Per Hedbor  if( args["process-all-layers"] ) opts->draw_all_layers = 1;
bd2fd02001-08-15Per Hedbor  if( args["jpeg-shrink" ] ) { opts->scale_denom = (int)args["jpeg-shrink" ]; opts->scale_num = 1; }
34a9b81999-08-06Peter Bortas  if( args->data )
689f3a2000-11-21Per Hedbor  layers = roxen.decode_layers( args->data, opts );
34a9b81999-08-06Peter Bortas  else
6a613a2002-06-17Anders Johansson  {
5ae0a32002-10-24Jonas Wallden  mixed tmp; #if constant(Sitebuilder) // Let SiteBuilder get a chance to decode its argument data if (Sitebuilder.sb_start_use_imagecache) { Sitebuilder.sb_start_use_imagecache(args, id); tmp = roxen.load_layers(args->src, id, opts); Sitebuilder.sb_end_use_imagecache(args, id); } else #endif { tmp = roxen.load_layers(args->src, id, opts); }
d86e712002-07-01Anders Johansson  if (mappingp(tmp)) {
02d09b2004-05-05Anders Johansson  if (tmp->error == Protocols.HTTP.HTTP_UNAUTH)
d86e712002-07-01Anders Johansson  return tmp; else layers = 0; }
6a613a2002-06-17Anders Johansson  else layers = tmp; }
689f3a2000-11-21Per Hedbor 
7b56752001-06-25Marcus Wellhardh  if(!layers)
bd2fd02001-08-15Per Hedbor  { if( args->data )
4047f82004-09-21Martin Stjernholm  image_error("Failed to decode specified data\n");
bd2fd02001-08-15Per Hedbor  else
4047f82004-09-21Martin Stjernholm  image_error("Failed to load specified image [%O]\n", args->src);
bd2fd02001-08-15Per Hedbor  }
d86e712002-07-01Anders Johansson 
02d09b2004-05-05Anders Johansson  if (!sizeof(filter(layers->image(), objectp)))
4047f82004-09-21Martin Stjernholm  image_error("Failed to decode layers in specified image [%O]\n", args->src);
5715f82005-10-05Marcus Wellhardh  if(!args["exclude-invisible-layers"]) layers->set_misc_value( "visible",1 );
930b632000-12-10Per Hedbor  foreach( layers, Image.Layer lay ) if( !lay->get_misc_value( "name" ) ) lay->set_misc_value( "name", "Background" );
689f3a2000-11-21Per Hedbor  if( args["exclude-layers"] ) { foreach( args["exclude-layers"] / ",", string match ) foreach( layers, Image.Layer lay ) if( glob( match, lay->get_misc_value( "name" ) ) ) lay->set_misc_value( "visible", 0 ); } if( args["include-layers"] ) { foreach( args["include-layers"] / ",", string match ) foreach( layers, Image.Layer lay ) if( glob( match, lay->get_misc_value( "name" ) ) ) lay->set_misc_value( "visible", 1 ); } array res = ({}); foreach( layers, Image.Layer l ) { if( l->get_misc_value( "visible" ) ) res += ({ l }); } return res;
37bdda1999-05-18Per Hedbor } mapping find_internal( string f, RequestID id ) {
4afee22001-04-03Per Hedbor  // It's not enough to // 1. Only do this check when the ext flag is set, old URLs might // live in caches // // 2. Check str[-4] for '.', consider .jpeg .tiff etc. //
3439822008-10-29 Erik Dahl  // 3. Also handle / if filename attribute is used in either tag //
4afee22001-04-03Per Hedbor  // However, . is not a valid character in the ID, so just cutting at
3439822008-10-29 Erik Dahl  // the first one works as well as also cutting at the first /. sscanf (f, "%[^./]", f); return the_cache->http_file_answer( f, id );
37bdda1999-05-18Per Hedbor }
2fa8d52000-06-02Martin Nilsson mapping get_my_args( mapping args, RequestID id )
37bdda1999-05-18Per Hedbor {
36eceb2000-01-30Per Hedbor  mapping a= ([
37bdda1999-05-18Per Hedbor  "quant":args->quant,
77926b2000-04-11Per Hedbor  "crop":args->crop,
c526921999-05-18Per Hedbor  "format":args->format, "maxwidth":args->maxwidth, "maxheight":args->maxheight, "scale":args->scale, "dither":args->dither,
b941aa1999-06-25Per Hedbor  "gamma":args->gamma,
34a9b81999-08-06Peter Bortas  "data":args->data,
37bdda1999-05-18Per Hedbor  ]);
77926b2000-04-11Per Hedbor 
7c4b6b2003-09-15Jonas Wallden  if( args->src ) { mixed err = catch
1c10ca2000-07-05Per Hedbor  {
a6f9562000-09-19Per Hedbor  a->src = Roxen.fix_relative( args->src, id );
7424772002-11-05Anders Johansson  array(int)|Stat st = (id->conf->try_stat_file(a->src, id) || file_stat(a->src));
689f3a2000-11-21Per Hedbor  if (st) { string fn = id->conf->real_file( a->src, id ); if( fn ) Roxen.add_cache_stat_callback( id, fn, st[ST_MTIME] ); a->mtime = (string) (a->stat = st[ST_MTIME]);
1133a72000-07-07Henrik Grubbström (Grubba)  a->filesize = (string) st[ST_SIZE];
5ae0a32002-10-24Jonas Wallden  #if constant(Sitebuilder)
5d41d12002-11-06Anders Johansson  // The file we called try_stat_file() on above may be a SiteBuilder
5ae0a32002-10-24Jonas Wallden  // file. If so we need to extend the argument data with e.g. // current language fork. if (Sitebuilder.sb_prepare_imagecache)
2007802002-10-24Jonas Wallden  a = Sitebuilder.sb_prepare_imagecache(a, a->src, id);
5ae0a32002-10-24Jonas Wallden #endif
1133a72000-07-07Henrik Grubbström (Grubba)  }
1c10ca2000-07-05Per Hedbor  };
7c4b6b2003-09-15Jonas Wallden #ifdef DEBUG if (err) report_error("<cimg> or <emit#cimg>: error in get_my_args(): %s\n", describe_backtrace(err)); #endif }
2fa8d52000-06-02Martin Nilsson 
1c10ca2000-07-05Per Hedbor  a["background-color"] = id->misc->defines->bgcolor || "#eeeeee";
77926b2000-04-11Per Hedbor 
c526921999-05-18Per Hedbor  foreach( glob( "*-*", indices(args)), string n ) a[n] = args[n];
77926b2000-04-11Per Hedbor 
34a9b81999-08-06Peter Bortas  return a; }
a49dae2000-08-17Per Hedbor mapping check_args( mapping args ) { if( !args->format ) args->format = "png"; if( !(args->src || args->data) ) RXML.parse_error("Required attribute 'src' or 'data' missing\n");
314e012006-10-05Jonas Wallden  if (args->src == "") RXML.parse_error("Attribute 'src' cannot be empty\n");
a49dae2000-08-17Per Hedbor  if( args->src && args->data ) RXML.parse_error("Only one of 'src' and 'data' may be specified\n"); return args; } class TagCimgplugin { inherit RXML.Tag; constant name = "emit"; constant plugin_name = "cimg"; array get_dataset( mapping args, RequestID id ) { mapping res = ([ ]); mapping a = get_my_args( check_args( args ), id ); string data;
7c4b6b2003-09-15Jonas Wallden  mixed err = catch // This code will fail if the image does not exist.
eac60f2001-01-26Per Hedbor  {
21bd602004-06-16Anders Johansson  // Store misc->cacheable since the image cache can raise it and // disable protocol cache. int cacheable = id->misc->cacheable; int no_proto_cache = id->misc->no_proto_cache; #ifdef DEBUG_CACHEABLE report_debug("%s:%d saved cacheable flags\n", __FILE__, __LINE__); #endif
eac60f2001-01-26Per Hedbor  res->src=(query_absolute_internal_location(id)+the_cache->store( a,id ));
3439822008-10-29 Erik Dahl  if(args->filename && sizeof(args->filename)) res->src += "/" + args->filename;
4afee22001-04-03Per Hedbor  if(do_ext)
fe32ad2001-03-30Johan Sundström  res->src += "." + (a->format || "gif");
3439822008-10-29 Erik Dahl  res->src = Roxen.http_encode_invalids(res->src);
eac60f2001-01-26Per Hedbor  data = the_cache->data( a, id , 0 ); res["file-size"] = strlen(data); res["file-size-kb"] = strlen(data)/1024;
a0909c2004-10-27Jonas Wallden  if (lower_case(args->nodata || "no") == "no") res["data"] = data;
eac60f2001-01-26Per Hedbor  res |= the_cache->metadata( a, id, 0 ); // enforce generation
21bd602004-06-16Anders Johansson #ifdef DEBUG_CACHEABLE report_debug("%s:%d restored cacheable flags\n", __FILE__, __LINE__); #endif id->misc->cacheable = cacheable; id->misc->no_proto_cache = no_proto_cache;
eac60f2001-01-26Per Hedbor  return ({ res }); };
7c4b6b2003-09-15Jonas Wallden #ifdef DEBUG report_error("<emit#cimg> error in get_dataset(): %s\n", describe_backtrace(err)); #endif
f61ece2001-08-11Martin Stjernholm  RXML.parse_error( "Illegal arguments or image\n" );
eac60f2001-01-26Per Hedbor  return ({});
a49dae2000-08-17Per Hedbor  } } class TagCImg {
2fa8d52000-06-02Martin Nilsson  inherit RXML.Tag; constant name = "cimg";
1b2d742001-10-08Anders Johansson  constant flags = RXML.FLAG_EMPTY_ELEMENT;
2fa8d52000-06-02Martin Nilsson 
fe32ad2001-03-30Johan Sundström  class Frame
a49dae2000-08-17Per Hedbor  {
2fa8d52000-06-02Martin Nilsson  inherit RXML.Frame;
a49dae2000-08-17Per Hedbor  array do_return(RequestID id) { mapping a = get_my_args( check_args( args ), id );
2fa8d52000-06-02Martin Nilsson  args -= a;
fe32ad2001-03-30Johan Sundström  string ext = "";
3439822008-10-29 Erik Dahl  string filename = ""; if(args->filename && sizeof(args->filename)) filename += "/" + m_delete(args, "filename");
4afee22001-04-03Per Hedbor  if(do_ext)
fe32ad2001-03-30Johan Sundström  ext = "." + (a->format || "gif");
3439822008-10-29 Erik Dahl  args->src = Roxen.http_encode_invalids( query_absolute_internal_location( id ) + the_cache->store( a, id ) + filename + ext );
5701ac2003-10-17Anders Johansson  int no_draw = !id->misc->generate_images;
cf5b112003-10-16Anders Johansson  if( mapping size = the_cache->metadata( a, id, no_draw ) )
2fa8d52000-06-02Martin Nilsson  {
cf5b112003-10-16Anders Johansson  // image in cache (no_draw above prevents generation on-the-fly)
2fa8d52000-06-02Martin Nilsson  args->width = size->xsize; args->height = size->ysize; }
0023a62000-08-22Martin Nilsson  int xml=!args->noxml; m_delete(args, "noxml"); result = Roxen.make_tag( "img", args, xml );
2fa8d52000-06-02Martin Nilsson  return 0; }
37bdda1999-05-18Per Hedbor  } }
698c121999-05-19Peter Bortas 
2fa8d52000-06-02Martin Nilsson class TagCImgURL { inherit RXML.Tag; constant name = "cimg-url";
1b2d742001-10-08Anders Johansson  constant flags = RXML.FLAG_EMPTY_ELEMENT;
2fa8d52000-06-02Martin Nilsson 
a49dae2000-08-17Per Hedbor  class Frame {
2fa8d52000-06-02Martin Nilsson  inherit RXML.Frame;
4b3fc02001-03-12Johan Sundström  array do_return(RequestID id)
a49dae2000-08-17Per Hedbor  {
3439822008-10-29 Erik Dahl  string filename = ""; if(args->filename && sizeof(args->filename)) filename = "/" + m_delete(args, "filename"); result = Roxen.http_encode_invalids( query_absolute_internal_location(id) + the_cache->store(get_my_args(check_args( args ), id ), id) + filename + (do_ext ? "." + (args->format || "gif") : "") );
2fa8d52000-06-02Martin Nilsson  return 0; } }
698c121999-05-19Peter Bortas }