a580e12000-09-27Fredrik Hübinette (Hubbe) #pike __REAL_VERSION__
a20af62000-09-26Fredrik Hübinette (Hubbe) 
ca80da2001-12-15Peter Bortas  //! @appears Image.PSD
925a152013-07-08Henrik Grubbström (Grubba) //! PhotoShop Document image format.
ca80da2001-12-15Peter Bortas 
fb58ae2014-07-29Per Hedbor inherit Image._PSD;
db9f1c1999-04-15Per Hedbor 
fb58ae2014-07-29Per Hedbor //!
db9f1c1999-04-15Per Hedbor class Layer {
fb58ae2014-07-29Per Hedbor 
a8f8652000-11-16Per Hedbor  string mode, name;
fb58ae2014-07-29Per Hedbor  //!
db9f1c1999-04-15Per Hedbor  int opacity;
fb58ae2014-07-29Per Hedbor  //!
db9f1c1999-04-15Per Hedbor  object image;
fb58ae2014-07-29Per Hedbor  //!
db9f1c1999-04-15Per Hedbor  object alpha;
fb58ae2014-07-29Per Hedbor  //!
db9f1c1999-04-15Per Hedbor  int flags;
fb58ae2014-07-29Per Hedbor  //!
db9f1c1999-04-15Per Hedbor  int xoffset, yoffset;
fb58ae2014-07-29Per Hedbor  //!
db9f1c1999-04-15Per Hedbor  int width, height;
fb58ae2014-07-29Per Hedbor  //!
db9f1c1999-04-15Per Hedbor 
2767d51999-04-17Per Hedbor  int mask_flags;
fb58ae2014-07-29Per Hedbor  //!
2767d51999-04-17Per Hedbor  int mask_xoffset, mask_yoffset;
fb58ae2014-07-29Per Hedbor  //!
2767d51999-04-17Per Hedbor  int mask_width, mask_height;
fb58ae2014-07-29Per Hedbor  //!
8202d72003-09-24Jonas Wallden  int mask_default_color;
fb58ae2014-07-29Per Hedbor  //!
db9f1c1999-04-15Per Hedbor } Layer decode_layer(mapping layer, mapping i) {
2db2b62000-11-21Per Hedbor // int stt = gethrtime();
db9f1c1999-04-15Per Hedbor  Layer l = Layer(); int use_cmap; l->opacity = layer->opacity; l->width = layer->right-layer->left; l->height = layer->bottom-layer->top; l->xoffset = layer->left; l->yoffset = layer->top;
640db92005-11-14Martin Nilsson  l->image = Image.Image( l->width, l->height );
db9f1c1999-04-15Per Hedbor  l->mode = layer->mode; l->flags = layer->flags;
a8f8652000-11-16Per Hedbor  l->name = layer->name;
8202d72003-09-24Jonas Wallden  l->mask_flags = layer->mask_flags; l->mask_default_color = layer->mask_default_color;
3524712015-05-26Martin Nilsson 
2767d51999-04-17Per Hedbor  l->mask_width = layer->mask_right-layer->mask_left; l->mask_height = layer->mask_bottom-layer->mask_top; l->mask_xoffset = layer->mask_left; l->mask_yoffset = layer->mask_top;
8202d72003-09-24Jonas Wallden  if(l->mask_flags & 1) // pos relative to layer
2767d51999-04-17Per Hedbor  {
8202d72003-09-24Jonas Wallden  l->mask_xoffset += l->xoffset; l->mask_yoffset += l->yoffset;
2767d51999-04-17Per Hedbor  }
db9f1c1999-04-15Per Hedbor  array colors;
2767d51999-04-17Per Hedbor  int inverted;
9b68cc1999-07-16Per Hedbor 
db9f1c1999-04-15Per Hedbor  switch(i->mode) { case RGB:
9b68cc1999-07-16Per Hedbor  array lays = ({}); foreach( layer->channels, mapping c ) { string mode; switch( (int)c->id ) { case 0:
3524712015-05-26Martin Nilsson  mode = "red";
9b68cc1999-07-16Per Hedbor  break; case 1:
3524712015-05-26Martin Nilsson  mode = "green";
9b68cc1999-07-16Per Hedbor  break; case 2:
3524712015-05-26Martin Nilsson  mode = "blue";
9b68cc1999-07-16Per Hedbor  break; } if( mode ) {
2db2b62000-11-21Per Hedbor // int st = gethrtime();
9b68cc1999-07-16Per Hedbor  if( !sizeof(lays) )
3524712015-05-26Martin Nilsson  lays += ({
a8f8652000-11-16Per Hedbor  Image.Layer(___decode_image_channel(l->width, l->height,
9b68cc1999-07-16Per Hedbor  c->data)) }); else lays += (({ Image.Layer( ([ "image":___decode_image_channel(l->width, l->height, c->data),
b9e5cc2000-10-21Per Hedbor // "alpha_value":1.0,
3524712015-05-26Martin Nilsson  "mode":mode,
b9e5cc2000-10-21Per Hedbor  ]) )
9b68cc1999-07-16Per Hedbor  }));
2db2b62000-11-21Per Hedbor // werror(mode+" took %4.5f seconds\n", (gethrtime()-st)/1000000.0 );
9b68cc1999-07-16Per Hedbor  c->data = 0; } }
2db2b62000-11-21Per Hedbor // int st = gethrtime();
9b68cc1999-07-16Per Hedbor  l->image = Image.lay( lays )->image();
2db2b62000-11-21Per Hedbor // werror("combine took %4.5f seconds\n", (gethrtime()-st)/1000000.0 );
db9f1c1999-04-15Per Hedbor  break;
a8f8652000-11-16Per Hedbor  case CMYK:
2767d51999-04-17Per Hedbor  inverted = 1; colors = ({ ({255,0,0,}), ({0,255,0,}), ({0,0,255,}), }) + ({ 255,255,255 }) * 24;
640db92005-11-14Martin Nilsson  l->image = Image.Image( l->width, l->height, 255, 255, 255);
2767d51999-04-17Per Hedbor  break;
9b68cc1999-07-16Per Hedbor 
db9f1c1999-04-15Per Hedbor  case Indexed: use_cmap = 1; break; default:
2db2b62000-11-21Per Hedbor  werror("Unsupported layer format mode ("+i->mode+"), using greyscale\n");
9b68cc1999-07-16Per Hedbor  case Greyscale: colors = ({ 255,255,255 })*24;
db9f1c1999-04-15Per Hedbor  break; }
2db2b62000-11-21Per Hedbor // int st = gethrtime();
db9f1c1999-04-15Per Hedbor  foreach(layer->channels, mapping c) {
2767d51999-04-17Per Hedbor  object tmp;
9b68cc1999-07-16Per Hedbor  if( !colors && (c->id >= 0 )) continue;
2767d51999-04-17Per Hedbor  if( c->id != -2) tmp = ___decode_image_channel(l->width, l->height, c->data); else tmp = ___decode_image_channel(l->mask_width,l->mask_height,c->data);
8202d72003-09-24Jonas Wallden 
9b68cc1999-07-16Per Hedbor  switch( c->id )
db9f1c1999-04-15Per Hedbor  { default:
2767d51999-04-17Per Hedbor  if(!use_cmap) { if(inverted) l->image -= tmp*colors[c->id%sizeof(colors)]; else l->image += tmp*colors[c->id%sizeof(colors)]; } else { __apply_cmap( tmp, i->color_data ); l->image = tmp; }
db9f1c1999-04-15Per Hedbor  break; case -1: /* alpha */ if(!l->alpha) l->alpha = tmp; else l->alpha *= tmp; break;
2767d51999-04-17Per Hedbor  case -2: /* user mask */
8202d72003-09-24Jonas Wallden  if(!(l->mask_flags & 2)) /* layer mask disabled */
2767d51999-04-17Per Hedbor  {
8202d72003-09-24Jonas Wallden  array pad_color = ({ l->mask_default_color }) * 3; int x0 = l->xoffset - l->mask_xoffset; int y0 = l->yoffset - l->mask_yoffset; tmp = tmp->copy(x0, y0, x0 + l->image->xsize() - 1, y0 + l->image->ysize() - 1, @pad_color);
3524712015-05-26Martin Nilsson 
8202d72003-09-24Jonas Wallden  if(l->mask_flags & 4) /* invert mask */
2767d51999-04-17Per Hedbor  tmp = tmp->invert();
3524712015-05-26Martin Nilsson 
2767d51999-04-17Per Hedbor  if(!l->alpha) l->alpha = tmp; else l->alpha *= tmp; break; }
db9f1c1999-04-15Per Hedbor  }
9b68cc1999-07-16Per Hedbor  c->data = 0;
db9f1c1999-04-15Per Hedbor  }
f6444d2001-03-11Per Hedbor // werror(" mode %s image %O alpha %O\n", // l->mode, l->image, l->alpha );
2db2b62000-11-21Per Hedbor // werror("alpha/mask took %4.5f seconds\n", (gethrtime()-st)/1000000.0 ); // werror("TOTAL took %4.5f seconds\n\n", (gethrtime()-stt)/1000000.0 );
db9f1c1999-04-15Per Hedbor  return l; }
ca80da2001-12-15Peter Bortas //! @decl mapping __decode(string|mapping data)
3524712015-05-26Martin Nilsson //!
edd49e2003-07-23Martin Nilsson //! Decodes a PSD image to a mapping, defined as follows. //! //! @mapping //! @member int(1..24) "channels" //! The number of channels in the image, including any alpha channels. //! @member int(1..30000) "height" //! @member int(1..30000) "width" //! The image dimensions. //! @member int(0..1) "compression" //! 1 if the image is compressed, 0 if not. //! @member int(1..1)|int(8..8)|int(16..16) "depth" //! The number of bits per channel. //! @member int(0..4)|int(7..9) "mode" //! The color mode of the file. //! @int //! @value 0 //! Bitmap //! @value 1 //! Greyscale //! @value 2 //! Indexed //! @value 3 //! RGB //! @value 4 //! CMYK //! @value 7 //! Multichannel //! @value 8 //! Duotone //! @value 9 //! Lab //! @endint //! @member string "color_data" //! Raw color data. //! @member string "image_data" //! Ram image data. //! @member mapping(string|int:mixed) "resources"
8db6142011-08-03Chris Angelico //! Additional image data. See mappping below.
fb58ae2014-07-29Per Hedbor //! @member array(Layer) "layers"
edd49e2003-07-23Martin Nilsson //! An array with the layers of the image. See mapping below. //! @endmapping //! //! The resources mapping. Unknown resources will be identified //! by their ID number (as an int). //! @mapping //! @member string "caption" //! Image caption. //! @member string "url" //! Image associated URL. //! @member int "active_layer" //! Which layer is active. //! @member array(mapping(string:int)) "guides" //! An array with all guides stored in the image file. //! @mapping //! @member int "pos" //! The position of the guide. //! @member int(0..1) "vertical" //! 1 if the guide is vertical, 0 if it is horizontal. //! @endmapping
8db6142011-08-03Chris Angelico //! @member mapping(string:int) "resinfo"
edd49e2003-07-23Martin Nilsson //! Resolution information //! @mapping //! @member int "hres" //! @member int "hres_unit" //! @member int "width_unit" //! @member int "vres" //! @member int "vres_unit" //! @member int "height_unit" //! FIXME: Document these. //! @endmapping //! @endmapping //!
fb58ae2014-07-29Per Hedbor //! The layer members:
edd49e2003-07-23Martin Nilsson //! @mapping //! @member int "top" //! @member int "left" //! @member int "right" //! @member int "bottom" //! The rectangle containing the contents of the layer. //! @member int "mask_top" //! @member int "mask_left" //! @member int "mask_right" //! @member int "mask_bottom" //! @member int "mask_flags" //! FIXME: Document these //! @member int(0..255) "opacity" //! 0=transparent, 255=opaque. //! @member int "clipping" //! 0=base, 1=non-base. //! @member int "flags" //! bit 0=transparency protected //! bit 1=visible //! @member string "mode" //! Blend mode. //! @string //! @value "norm" //! Normal //! @value "dark" //! Darken //! @value "lite" //! Lighten //! @value "hue " //! Hue //! @value "sat " //! Saturation //! @value "colr" //! Color //! @value "lum " //! Luminosity //! @value "mul " //! Multiply //! @value "scrn" //! Screen //! @value "diss" //! Dissolve //! @value "over" //! Overlay //! @value "hLit" //! Hard light //! @value "sLit" //! Soft light //! @value "diff" //! Difference //! @endstring //! @member string "extra_data" //! Raw extra data. //! @member string "name" //! The name of the layer //! @member array(mapping(string:int|string)) "channels" //! The channels of the layer. Each array element is a //! mapping as follows //! @mapping //! @member int "id" //! The ID of the channel //! @int //! @value -2 //! User supplied layer mask //! @value -1 //! Transparency mask //! @value 0 //! Red //! @value 1 //! Green //! @value 2 //! Blue //! @endint //! @member string "data" //! The image data //! @endmapping //! @endmapping
db9f1c1999-04-15Per Hedbor mapping __decode( mapping|string what, mapping|void options ) { mapping data; if(mappingp(what)) data = what;
3524712015-05-26Martin Nilsson  else
db9f1c1999-04-15Per Hedbor  data = ___decode( what ); what=0; array rl = ({}); foreach( data->layers, mapping l )
9b68cc1999-07-16Per Hedbor  rl += ({ decode_layer( l, data ) });
db9f1c1999-04-15Per Hedbor  data->layers = rl; return data; }
92464e1999-11-02Per Hedbor array(object) decode_background( mapping data )
db9f1c1999-04-15Per Hedbor {
9b68cc1999-07-16Per Hedbor  object img;
92464e1999-11-02Per Hedbor 
2767d51999-04-17Per Hedbor  if( data->image_data )
3524712015-05-26Martin Nilsson  img = ___decode_image_data(data->width, data->height,
2767d51999-04-17Per Hedbor  data->channels, data->mode, data->compression, data->image_data,
92464e1999-11-02Per Hedbor 
2767d51999-04-17Per Hedbor  data->color_data);
9b68cc1999-07-16Per Hedbor  return ({ img, 0 }); }
fb58ae2014-07-29Per Hedbor //! Convert a photoshop mode name to pike @[Image.lay] mode names
9b68cc1999-07-16Per Hedbor string translate_mode( string mode ) { switch( mode ) { case "norm": return "normal"; case "mul ": return "multiply"; case "add ": return "add"; case "diff": return "difference"; case "sub ": return "subtract"; case "diss": return "dissolve"; case "scrn": return "screen"; case "over": return "overlay"; case "dark": return "min"; case "lite": return "max"; case "hue ": return "hue";
38d32e2001-03-28Per Hedbor  case "div ": return "idivide"; case "hLit": return "hardlight"; case "colr": return "color"; // Not 100% like photoshop. case "lum ": return "value"; // Not 100% like photoshop. case "sat ": return "saturation";// Not 100% like photoshop. // Colorburn, not really multiply, but the best aproximation soo far. case "idiv": return "multiply"; // Exclusion. Not really difference, but very close.
3524712015-05-26Martin Nilsson  case "smud":
38d32e2001-03-28Per Hedbor  return "difference"; // Soft light. Not really supported yet. For now, use hardlight with lower // opacity. Gives a rather good aproximation.
3524712015-05-26Martin Nilsson  case "sLit":
38d32e2001-03-28Per Hedbor  return "hardlight";
9b68cc1999-07-16Per Hedbor  default:
2db2b62000-11-21Per Hedbor  werror("WARNING: PSD: Unsupported mode: "+mode+". Skipping layer\n");
9b68cc1999-07-16Per Hedbor  return 0; } }
a59e562001-12-20Martin Nilsson //! @decl array(Image.Layer) decode_layers( string data, mapping|void options )
ca80da2001-12-15Peter Bortas //! //! Decodes a PSD image to an array of Image.Layer objects //! //! The layer object have the following extra variables (to be queried
a59e562001-12-20Martin Nilsson //! using @[Image.Layer()->get_misc_value]):
ca80da2001-12-15Peter Bortas //! //! @string //! @value "image_guides" //! Returns array containing guide definitions. //! @value "name" //! Returns string containing the name of the layer. //! @value "visible"
edd49e2003-07-23Martin Nilsson //! Is 1 of the layer is visible and 0 if it is hidden.
ca80da2001-12-15Peter Bortas //! @endstring
fb58ae2014-07-29Per Hedbor //! //! Accepts these options: //! @mapping
ab697a2014-08-02Henrik Grubbström (Grubba) //! @member bool "draw_all_layers"
fb58ae2014-07-29Per Hedbor //! If included, all layers will be decoded, even the non-visible ones.
ab697a2014-08-02Henrik Grubbström (Grubba) //! @member bool "crop_to_bounds"
fb58ae2014-07-29Per Hedbor //! Remove areas that are outside the image boundaries in all layers
ab697a2014-08-02Henrik Grubbström (Grubba) //! @member Image.Color "background"
fb58ae2014-07-29Per Hedbor //! If included, include a solid background layer with the given color //! @endmapping
9b68cc1999-07-16Per Hedbor array decode_layers( string|mapping what, mapping|void opts ) { if(!opts) opts = ([]); if(!mappingp( what ) ) what = __decode( what );
3524712015-05-26Martin Nilsson 
9b68cc1999-07-16Per Hedbor  mapping lopts = ([ "tiled":1, ]);
c776f31999-11-02Per Hedbor  if( opts->background )
9b68cc1999-07-16Per Hedbor  {
c776f31999-11-02Per Hedbor  lopts->image = Image.Image( 32, 32, opts->background ); lopts->alpha = Image.Image( 32, 32, Image.Color.white );
9b68cc1999-07-16Per Hedbor  lopts->alpha_value = 1.0; }
c776f31999-11-02Per Hedbor  object img, alpha; if( !what->layers || !sizeof(what->layers)) { [ img, alpha ] = decode_background( what ); if( img ) { lopts->image = img; if( alpha ) lopts->alpha = alpha; else lopts->alpha = 0; lopts->alpha_value = 1.0; } }
92464e1999-11-02Per Hedbor  array layers;
2db2b62000-11-21Per Hedbor  Image.Layer lay;
92464e1999-11-02Per Hedbor  if( lopts->image )
2db2b62000-11-21Per Hedbor  { layers = ({ (lay=Image.Layer( lopts )) }); lay->set_misc_value( "visible", 1 ); lay->set_misc_value( "name", "Background" ); lay->set_misc_value( "image_guides", what->resources->guides ); }
92464e1999-11-02Per Hedbor  else layers = ({});
9b68cc1999-07-16Per Hedbor  foreach(reverse(what->layers), object l) {
a8f8652000-11-16Per Hedbor  if( !(l->flags & LAYER_FLAG_INVISIBLE) || opts->draw_all_layers )
9b68cc1999-07-16Per Hedbor  {
a8f8652000-11-16Per Hedbor  if( string m = translate_mode( l->mode ) ) {
2db2b62000-11-21Per Hedbor  lay = Image.Layer( l->image, l->alpha, m );
a8f8652000-11-16Per Hedbor  lay->set_misc_value( "visible", !(l->flags & LAYER_FLAG_INVISIBLE) ); lay->set_misc_value( "name", l->name );
2db2b62000-11-21Per Hedbor  lay->set_misc_value( "image_guides", what->resources->guides );
38d32e2001-03-28Per Hedbor  if( l->mode == "sLit" ) l->opacity /= 3;
a8f8652000-11-16Per Hedbor  l->image = 0; l->alpha = 0;
3524712015-05-26Martin Nilsson 
a8f8652000-11-16Per Hedbor  if( l->opacity != 255 )
33364f2000-12-16Per Hedbor  {
38d32e2001-03-28Per Hedbor  float lo = l->opacity / 255.0;
33364f2000-12-16Per Hedbor  if( lay->alpha() ) lay->set_image( lay->image(), lay->alpha()*lo ); else lay->set_image( lay->image(), Image.Image( lay->xsize(), lay->yszize(), (int)(255*lo), (int)(255*lo), (int)(255*lo))); }
a721962003-07-04Jonas Wallden 
a0d6322005-10-06Henrik Grubbström (Grubba)  if (opts->crop_to_bounds && lay->image()) {
a721962003-07-04Jonas Wallden  // Crop/expand this layer so it matches the image bounds. // This will lose data which extends beyond the image bounds // but keeps the image dimensions consistent. int x0 = -l->xoffset, y0 = -l->yoffset; int x1 = x0 + what->width - 1, y1 = y0 + what->height - 1; Image.Image new_img = lay->image()->copy(x0, y0, x1, y1); Image.Image new_alpha = lay->alpha() && lay->alpha()->copy(x0, y0, x1, y1); lay->set_image(new_img, new_alpha); } else lay->set_offset( l->xoffset, l->yoffset );
a8f8652000-11-16Per Hedbor  layers += ({ lay }); }
9b68cc1999-07-16Per Hedbor  } }
38d32e2001-03-28Per Hedbor // werror("%O\n", layers );
879dc82002-05-29Per Hedbor  layers->set_misc_value( "size", ({ what->width, what->height }) );
9b68cc1999-07-16Per Hedbor  return layers;
db9f1c1999-04-15Per Hedbor }
ca80da2001-12-15Peter Bortas //! @decl mapping _decode(string|mapping data, mapping|void options) //! //! Decodes a PSD image to a mapping, with at least an //! 'image' and possibly an 'alpha' object. Data is either a PSD image, or
a59e562001-12-20Martin Nilsson //! a mapping (as received from @[__decode])
ca80da2001-12-15Peter Bortas //!
edd49e2003-07-23Martin Nilsson //! @param options //! @mapping //! @member array(int)|Image.Color "background" //! Sets the background to the given color. Arrays should be in //! the format ({r,g,b}).
ca80da2001-12-15Peter Bortas //!
fb58ae2014-07-29Per Hedbor //! @member bool "draw_all_layers"
edd49e2003-07-23Martin Nilsson //! Draw invisible layers as well.
ca80da2001-12-15Peter Bortas //!
fb58ae2014-07-29Per Hedbor //! @member bool "draw_guides"
edd49e2003-07-23Martin Nilsson //! Draw the guides.
ca80da2001-12-15Peter Bortas //!
fb58ae2014-07-29Per Hedbor //! @member bool "draw_selection"
edd49e2003-07-23Martin Nilsson //! Mark the selection using an overlay.
ca80da2001-12-15Peter Bortas //!
fb58ae2014-07-29Per Hedbor //! @member bool "ignore_unknown_layer_modes"
ca80da2001-12-15Peter Bortas //! Do not asume 'Normal' for unknown layer modes. //!
fb58ae2014-07-29Per Hedbor //! @member bool "mark_layers"
edd49e2003-07-23Martin Nilsson //! Draw an outline around all (drawn) layers.
ca80da2001-12-15Peter Bortas //!
edd49e2003-07-23Martin Nilsson //! @member Image.Font "mark_layer_names"
ca80da2001-12-15Peter Bortas //! Write the name of all layers using the font object, //!
fb58ae2014-07-29Per Hedbor //! @member bool "mark_active_layer"
ca80da2001-12-15Peter Bortas //! Draw an outline around the active layer
edd49e2003-07-23Martin Nilsson //! @endmapping //! //! @returns //! @mapping //! @member Image.Image "image" //! The image object. //! @member Image.Image "alpha" //! The alpha channel image object. //! @endmapping
ca80da2001-12-15Peter Bortas //! //! @note
a59e562001-12-20Martin Nilsson //! Throws upon error in data. For more information, see @[__decode]
db9f1c1999-04-15Per Hedbor mapping _decode( string|mapping what, mapping|void opts ) { mapping data; if(!opts) opts = ([]); if(mappingp(what)) data = what;
3524712015-05-26Martin Nilsson  else
db9f1c1999-04-15Per Hedbor  data = __decode( what ); what=0;
9b68cc1999-07-16Per Hedbor  Image.Layer res = Image.lay(decode_layers( data, opts ), 0,0,data->width,data->height ); Image.Image img = res->image(); Image.Image alpha = res->alpha();
db9f1c1999-04-15Per Hedbor 
3524712015-05-26Martin Nilsson  return
db9f1c1999-04-15Per Hedbor  ([ "image":img, "alpha":alpha,
43abb42003-12-03Martin Nilsson  "type":"application/x-photoshop",
db9f1c1999-04-15Per Hedbor  ]);
2db2b62000-11-21Per Hedbor }
ca80da2001-12-15Peter Bortas //! @decl Image.Image decode(string data) //! Decodes a PSD image to a single image object. //! //! @note //! Throws upon error in data. To get access to more information like //! alpha channels and layer names, see @[_decode] and @[__decode].
2db2b62000-11-21Per Hedbor Image.Image decode( string|mapping what, mapping|void opts ) { mapping data; if(!opts) opts = ([]); if(mappingp(what)) data = what;
3524712015-05-26Martin Nilsson  else
2db2b62000-11-21Per Hedbor  data = __decode( what ); what=0; Image.Layer res = Image.lay(decode_layers( data, opts ), 0,0,data->width,data->height ); return res->image();
db9f1c1999-04-15Per Hedbor }