#pike __REAL_VERSION__


//! @appears Image.PSD
//! PhotoShop Document image format.

inherit Image._PSD;

//!
class Layer
{

  string mode, name;
  //!

  int opacity;
  //!

  object image;
  //!

  object alpha;
  //!

  int flags;
  //!

  int xoffset, yoffset;
  //!

  int width, height;
  //!

  int mask_flags;
  //!

  int mask_xoffset, mask_yoffset;
  //!

  int mask_width, mask_height;
  //!

  int mask_default_color;
  //!
}

Layer decode_layer(mapping layer, mapping i)
{
//   int stt = gethrtime();
  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;
  l->image = Image.Image( l->width, l->height );
  l->mode = layer->mode;
  l->flags = layer->flags;
  l->name = layer->name;
  l->mask_flags = layer->mask_flags;
  l->mask_default_color = layer->mask_default_color;
 
  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;

  if(l->mask_flags & 1) // pos relative to layer
  {
    l->mask_xoffset += l->xoffset;
    l->mask_yoffset += l->yoffset;
  }
  array colors;
  int inverted;

  switch(i->mode)
  {
   case RGB:
     array lays = ({});
     foreach( layer->channels, mapping c )
     {
       string mode;
       switch( (int)c->id )
       {
        case 0:
          mode = "red"; 
          break;
        case 1:
          mode = "green"; 
          break;
        case 2:
          mode = "blue"; 
          break;
       }
       if( mode )
       {
//          int st = gethrtime();
         if( !sizeof(lays) )
           lays += ({ 
             Image.Layer(___decode_image_channel(l->width,
						 l->height,
                                                 c->data))
           });
         else
           lays += (({ Image.Layer( ([
             "image":___decode_image_channel(l->width, l->height, c->data),
//             "alpha_value":1.0,
             "mode":mode, 
	   ]) )
           }));
//          werror(mode+" took %4.5f seconds\n", (gethrtime()-st)/1000000.0 );
         c->data = 0;
       }
     }
//      int st = gethrtime();
     l->image = Image.lay( lays )->image();
//      werror("combine took %4.5f seconds\n", (gethrtime()-st)/1000000.0 );
     break;
    case CMYK:
     inverted = 1;
     colors = ({ ({255,0,0,}),
                 ({0,255,0,}),
                 ({0,0,255,}),
               }) + ({ 255,255,255 }) * 24;
     l->image = Image.Image( l->width, l->height, 255, 255, 255);
     break;

   case Indexed:
     use_cmap = 1;
     break;
   default:
     werror("Unsupported layer format mode ("+i->mode+"), using greyscale\n");
   case Greyscale:
     colors = ({
       255,255,255
     })*24;
     break;
  }
//   int st = gethrtime();
  foreach(layer->channels, mapping c)
  {
    object tmp;

    if( !colors && (c->id >= 0 ))
      continue;

    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);

    switch( c->id )
    {
     default:
       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;
       }
       break;
     case -1: /* alpha */
       if(!l->alpha)
         l->alpha = tmp;
       else
         l->alpha *= tmp;
       break;
     case -2: /* user mask */
       if(!(l->mask_flags & 2)) /* layer mask disabled */
       {
	 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);
	 
         if(l->mask_flags & 4) /* invert mask */
           tmp = tmp->invert();
	 
         if(!l->alpha)
           l->alpha = tmp;
         else
           l->alpha *= tmp;
         break;
       }
    }
    c->data = 0;
  }
//   werror(" mode %s image %O alpha %O\n",
// 	 l->mode, l->image, l->alpha );
//   werror("alpha/mask took %4.5f seconds\n", (gethrtime()-st)/1000000.0 );
//   werror("TOTAL took %4.5f seconds\n\n", (gethrtime()-stt)/1000000.0 );
  return l;
}

//! @decl mapping __decode(string|mapping data)
//! 
//! 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"
//!     Additional image data. See mappping below.
//!   @member array(Layer) "layers"
//!     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
//!   @member mapping(string:int) "resinfo"
//!     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
//!
//! The layer members:
//! @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
mapping __decode( mapping|string what, mapping|void options )
{
  mapping data;
  if(mappingp(what))
    data = what;
  else 
    data = ___decode( what );
  what=0;
  array rl = ({});
  foreach( data->layers, mapping l )
    rl += ({ decode_layer( l, data ) });

  data->layers = rl;
  return data;
}

array(object) decode_background( mapping data )
{
  object img;

  if( data->image_data )
    img = ___decode_image_data(data->width,       data->height, 
                               data->channels,    data->mode,
                               data->compression, data->image_data,

                               data->color_data);
  return ({ img, 0 });
}

//! Convert a photoshop mode name to pike @[Image.lay] mode names
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";
   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.
    case "smud":     
     return "difference";

     // Soft light. Not really supported yet. For now, use hardlight with lower
     // opacity. Gives a rather good aproximation.
    case "sLit":     
     return "hardlight";

   default:
     werror("WARNING: PSD: Unsupported mode: "+mode+". Skipping layer\n");
     return 0;
  }
}

//! @decl array(Image.Layer) decode_layers( string data, mapping|void options )
//!
//! Decodes a PSD image to an array of Image.Layer objects
//!
//! The layer object have the following extra variables (to be queried
//! using @[Image.Layer()->get_misc_value]):
//!
//! @string
//!   @value "image_guides"
//!     Returns array containing guide definitions.
//!   @value "name"
//!     Returns string containing the name of the layer.
//!   @value "visible"
//!     Is 1 of the layer is visible and 0 if it is hidden.
//! @endstring
//!
//! Accepts these options:
//! @mapping
//! @member bool "draw_all_layers"
//!   If included, all layers will be decoded, even the non-visible ones.
//! @member bool "crop_to_bounds"
//!   Remove areas that are outside the image boundaries in all layers
//! @member Image.Color "background"
//!   If included, include a solid background layer with the given color
//! @endmapping
array decode_layers( string|mapping what, mapping|void opts )
{
  if(!opts) opts = ([]);

  if(!mappingp( what ) )
    what = __decode( what );
  
  mapping lopts = ([ "tiled":1, ]);

  if( opts->background )
  {
    lopts->image = Image.Image( 32, 32, opts->background );
    lopts->alpha = Image.Image( 32, 32, Image.Color.white );
    lopts->alpha_value = 1.0;
  }

  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;
    }
  }
  array layers;
  Image.Layer lay;
  if( lopts->image )
  {
    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 );
  }
  else
    layers = ({});

  foreach(reverse(what->layers), object l)
  {
    if( !(l->flags & LAYER_FLAG_INVISIBLE) || opts->draw_all_layers )
    {
      if( string m = translate_mode( l->mode ) )
      {
	lay = Image.Layer( l->image, l->alpha, m );

        lay->set_misc_value( "visible", !(l->flags & LAYER_FLAG_INVISIBLE) );
        lay->set_misc_value( "name",      l->name );
        lay->set_misc_value( "image_guides", what->resources->guides );
	if( l->mode == "sLit" )
	  l->opacity /= 3;

	l->image = 0; l->alpha = 0;
        
	if( l->opacity != 255 )
        {
	  float lo =  l->opacity / 255.0;
          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)));
        }

	if (opts->crop_to_bounds && lay->image()) {
	  //  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 );

	layers += ({ lay });
      }
    }
  }
//   werror("%O\n", layers );
  layers->set_misc_value( "size", ({ what->width, what->height }) );
  return layers;
}

//! @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
//! a mapping (as received from @[__decode])
//!
//! @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}).
//!
//!   @member bool "draw_all_layers"
//!     Draw invisible layers as well.
//!
//!   @member bool "draw_guides"
//!     Draw the guides.
//!
//!   @member bool "draw_selection"
//!     Mark the selection using an overlay.
//!
//!   @member bool "ignore_unknown_layer_modes"
//!     Do not asume 'Normal' for unknown layer modes.
//!
//!   @member bool "mark_layers"
//!     Draw an outline around all (drawn) layers.
//!
//!   @member Image.Font "mark_layer_names"
//!     Write the name of all layers using the font object,
//!
//!   @member bool "mark_active_layer"
//!     Draw an outline around the active layer
//! @endmapping
//!
//! @returns
//! @mapping
//!   @member Image.Image "image"
//!     The image object.
//!   @member Image.Image "alpha"
//!     The alpha channel image object.
//! @endmapping
//!
//! @note
//!   Throws upon error in data. For more information, see @[__decode]
mapping _decode( string|mapping what, mapping|void opts )
{
  mapping data;
  if(!opts) opts = ([]);
  if(mappingp(what))
    data = what;
  else 
    data = __decode( what );
  what=0;

  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();

  return 
  ([
    "image":img,
    "alpha":alpha,
    "type":"application/x-photoshop",
  ]);
}

//! @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].
Image.Image decode( string|mapping what, mapping|void opts )
{
  mapping data;
  if(!opts) opts = ([]);
  if(mappingp(what))
    data = what;
  else 
    data = __decode( what );
  what=0;

  Image.Layer res = Image.lay(decode_layers( data, opts ),
                              0,0,data->width,data->height );
  return res->image();
}