
/******************************************************************************
* MODULE     : effect.hpp
* DESCRIPTION: Graphical effects
* COPYRIGHT  : (C) 2013  Joris van der Hoeven
*******************************************************************************
* This software falls under the GNU general public license version 3 or later.
* It comes WITHOUT ANY WARRANTY WHATSOEVER. For details, see the file LICENSE
* in the root directory or <http://www.gnu.org/licenses/gpl-3.0.html>.
******************************************************************************/

#include "effect.hpp"
#include "true_color.hpp"
#include "gui.hpp"
#include "matrix.hpp"

/******************************************************************************
* Default implementations of virtual routines
******************************************************************************/

static rectangle
join (array<rectangle> rs, composition_mode mode) {
  if (N(rs) == 0) return rectangle (0, 0, 0, 0);
  rectangle ret= rs[0];
  for (int i=1; i<N(rs); i++) {
    rectangle aux= rs[i];
    switch (composition_type (mode)) {
    case 0:
      ret= rectangle (max (ret->x1, aux->x1), max (ret->y1, aux->y1),
                      min (ret->x2, aux->x2), min (ret->y2, aux->y2));
      break;
    case 1:
      ret= aux;
      break;
    case 3:
      ret= rectangle (min (ret->x1, aux->x1), min (ret->y1, aux->y1),
                      max (ret->x2, aux->x2), max (ret->y2, aux->y2));
      break;
    }
  }
  ret->x1= min (ret->x1, ret->x2);
  ret->y1= min (ret->y1, ret->y2);
  return ret;
}

rectangle
effect_rep::get_logical_extents (array<rectangle> rs) {
  return join (rs, compose_source_over);
}

/******************************************************************************
* Unmodified argument
******************************************************************************/

class argument_effect_rep: public effect_rep {
  int nr;
public:
  argument_effect_rep (int nr2): nr (nr2) {}
  rectangle get_logical_extents (array<rectangle> rs) { return rs[nr]; }
  rectangle get_extents (array<rectangle> rs) { return rs[nr]; }
  picture apply (array<picture> pics, SI pixel) {
    (void) pixel; return pics[nr]; }
};

effect argument_effect (int nr) {
  return tm_new<argument_effect_rep> (nr); }

/******************************************************************************
* Turbulence effect
******************************************************************************/

class turbulence_effect_rep: public effect_rep {
  effect eff;
  long seed;
  double w, h;
  int octaves;
  bool frac_sum;
public:
  turbulence_effect_rep (effect e, long s, double w2, double h2, int o, bool f):
    eff (e), seed (s), w (w2), h (h2), octaves (o), frac_sum (f) {}
  rectangle get_logical_extents (array<rectangle> rs) {
    return eff->get_logical_extents (rs); }
  rectangle get_extents (array<rectangle> rs) {
    return eff->get_extents (rs); }
  picture apply (array<picture> pics, SI pixel) {
    if (frac_sum)
      return fractal_noise (eff->apply (pics, pixel), seed,
                            w / pixel, h / pixel, octaves);
    else
      return turbulence (eff->apply (pics, pixel), seed,
                         w / pixel, h / pixel, octaves); }
};

effect turbulence (effect e, long s, double w, double h, int oct) {
  return tm_new<turbulence_effect_rep> (e, s, w, h, oct, false); }
effect fractal_noise (effect e, long s, double w, double h, int oct) {
  return tm_new<turbulence_effect_rep> (e, s, w, h, oct, true); }

/******************************************************************************
* Coordinate transformations
******************************************************************************/

class move_effect_rep: public effect_rep {
  effect eff;
  double dx, dy;
public:
  move_effect_rep (effect eff2, double dx2, double dy2):
    eff (eff2), dx (dx2), dy (dy2) {}
  rectangle get_logical_extents (array<rectangle> rs) {
    rectangle r= eff->get_logical_extents (rs);
    return rectangle ((SI) round (r->x1 + dx), (SI) round (r->y1 + dy),
                      (SI) round (r->x2 + dx), (SI) round (r->y2 + dy)); }
  rectangle get_extents (array<rectangle> rs) {
    rectangle r= eff->get_extents (rs);
    return rectangle ((SI) floor (r->x1 + dx), (SI) floor (r->y1 + dy),
                      (SI) ceil  (r->x2 + dx), (SI) ceil  (r->y2 + dy)); }
  picture apply (array<picture> pics, SI pixel) {
    return shift (eff->apply (pics, pixel), dx / pixel, dy / pixel); }
};

class magnify_effect_rep: public effect_rep {
  effect eff;
  double sx, sy;
public:
  magnify_effect_rep (effect eff2, double sx2, double sy2):
    eff (eff2), sx (sx2), sy (sy2) {}
  rectangle get_logical_extents (array<rectangle> rs) {
    rectangle r= eff->get_logical_extents (rs);
    return rectangle ((SI) round (sx * r->x1), (SI) round (sy * r->y1),
                      (SI) round (sx * r->x2), (SI) round (sy * r->y2)); }
  rectangle get_extents (array<rectangle> rs) {
    rectangle r= eff->get_extents (rs);
    return rectangle ((SI) floor (sx * r->x1), (SI) floor (sy * r->y1),
                      (SI) ceil  (sx * r->x2), (SI) ceil  (sy * r->y2)); }
  picture apply (array<picture> pics, SI pixel) {
    return magnify (eff->apply (pics, pixel), sx, sy); }
};

class bubble_effect_rep: public effect_rep {
  effect eff;
  double r, a;
public:
  bubble_effect_rep (effect eff2, double r2, double a2):
    eff (eff2), r (r2), a (a2) {}
  rectangle get_extents (array<rectangle> rs) {
    rectangle rect= eff->get_extents (rs);
    double delta= r * (1 - a);
    return rectangle ((SI) floor (rect->x1 - delta),
                      (SI) floor (rect->y1 - delta),
                      (SI) ceil  (rect->x2 + delta),
                      (SI) ceil  (rect->y2 + delta)); }
  picture apply (array<picture> pics, SI pixel) {
    return bubble (eff->apply (pics, pixel), r / pixel, a); }
};

class crop_effect_rep: public effect_rep {
  effect eff;
  double cx1, cy1, cx2, cy2;
public:
  crop_effect_rep (effect eff2, double x1, double y1, double x2, double y2):
    eff (eff2), cx1 (x1), cy1 (y1), cx2 (x2), cy2 (y2) {}
  rectangle get_logical_extents (array<rectangle> rs) {
    rectangle r= eff->get_logical_extents (rs);
    SI x1= (SI) round (r->x1 + cx1 * (r->x2 - r->x1));
    SI y1= (SI) round (r->y1 + cy1 * (r->y2 - r->y1));
    SI x2= (SI) round (r->x1 + cx2 * (r->x2 - r->x1));
    SI y2= (SI) round (r->y1 + cy2 * (r->y2 - r->y1));
    return rectangle (0, 0, x2 - x1, y2 - y1); }
  rectangle get_extents (array<rectangle> rs) {
    rectangle r= eff->get_extents (rs);
    SI x1= (SI) floor (r->x1 + cx1 * (r->x2 - r->x1));
    SI y1= (SI) floor (r->y1 + cy1 * (r->y2 - r->y1));
    SI x2= (SI) ceil  (r->x1 + cx2 * (r->x2 - r->x1));
    SI y2= (SI) ceil  (r->y1 + cy2 * (r->y2 - r->y1));
    return rectangle (0, 0, x2 - x1, y2 - y1); }
  picture apply (array<picture> pics, SI pixel) {
    return crop (eff->apply (pics, pixel), cx1, cy1, cx2, cy2); }
};

effect move (effect eff, double dx, double dy) {
  return tm_new<move_effect_rep> (eff, dx, dy); }
effect magnify (effect eff, double sx, double sy) {
  return tm_new<move_effect_rep> (eff, sx, sy); }
effect bubble (effect eff, double r, double a) {
  return tm_new<bubble_effect_rep> (eff, r, a); }
effect crop (effect eff, double cx1, double cy1, double cx2, double cy2) {
  cx1= max (cx1, 0.0); cy1= max (cy1, 0.0);
  cx2= min (cx2, 1.0); cy2= min (cy2, 1.0);
  return tm_new<crop_effect_rep> (eff, cx1, cy1, cx2, cy2); }

/******************************************************************************
* Pens
******************************************************************************/

class gaussian_pen_effect_rep: public effect_rep {
  double rx, ry, phi;
public:
  gaussian_pen_effect_rep (double rx2, double ry2, double phi2):
    rx (rx2), ry (ry2), phi (phi2) {}
  rectangle get_extents (array<rectangle> rs) { (void) rs;
    SI R= (SI) ceil (2.5 * max (rx, ry));
    return rectangle (-R, -R, R, R); }
  picture apply (array<picture> pics, SI pixel) { (void) pics;
    return gaussian_pen_picture (rx / pixel, ry / pixel, phi); }
};

class oval_pen_effect_rep: public effect_rep {
  double rx, ry, phi;
public:
  oval_pen_effect_rep (double rx2, double ry2, double phi2):
    rx (rx2), ry (ry2), phi (phi2) {}
  rectangle get_extents (array<rectangle> rs) { (void) rs;
    SI R= (SI) max (rx, ry);
    return rectangle (-R, -R, R, R); }
  picture apply (array<picture> pics, SI pixel) { (void) pics;
    return oval_pen_picture (rx / pixel, ry / pixel, phi); }
};

class rectangular_pen_effect_rep: public effect_rep {
  double rx, ry, phi;
public:
  rectangular_pen_effect_rep (double rx2, double ry2, double p2):
    rx (rx2), ry (ry2), phi (p2) {}
  rectangle get_extents (array<rectangle> rs) { (void) rs;
    SI R= (SI) sqrt (rx * rx + ry * ry);
    return rectangle (-R, -R, R, R); }
  picture apply (array<picture> pics, SI pixel) { (void) pics;
    return rectangular_pen_picture (rx / pixel, ry / pixel, phi); }
};

class motion_pen_effect_rep: public effect_rep {
  double dx, dy;
public:
  motion_pen_effect_rep (double dx2, double dy2):
    dx (dx2), dy (dy2) {}
  rectangle get_extents (array<rectangle> rs) { (void) rs;
    return rectangle ((SI) min (dx, 0.0), (SI) min (dy, 0.0),
                      (SI) max (dx, 0.0), (SI) max (dy, 0.0)); }
  picture apply (array<picture> pics, SI pixel) { (void) pics;
    return motion_pen_picture (dx / pixel, dy / pixel); }
};

effect gaussian_pen_effect (double r) {
  return tm_new<gaussian_pen_effect_rep> (r, r, 0.0); }
effect oval_pen_effect (double r) {
  return tm_new<oval_pen_effect_rep> (r, r, 0.0); }
effect rectangular_pen_effect (double r) {
  return tm_new<rectangular_pen_effect_rep> (r, r, 0.0); }

effect gaussian_pen_effect (double rx, double ry, double phi) {
  return tm_new<gaussian_pen_effect_rep> (rx, ry, phi); }
effect oval_pen_effect (double rx, double ry, double phi) {
  return tm_new<oval_pen_effect_rep> (rx, ry, phi); }
effect rectangular_pen_effect (double rx, double ry, double phi) {
  return tm_new<rectangular_pen_effect_rep> (rx, ry, phi); }

effect motion_pen_effect (double dx, double dy) {
  return tm_new<motion_pen_effect_rep> (dx, dy); }

/******************************************************************************
* Special effects, taking a pen as parameter
******************************************************************************/

class blur_effect_rep: public effect_rep {
  effect eff, pen;
public:
  blur_effect_rep (effect eff2, effect pen2):
    eff (eff2), pen (pen2) {}
  rectangle get_extents (array<rectangle> rs) {
    rectangle r1= eff->get_extents (rs);
    rectangle r2= pen->get_extents (rs);
    return rectangle (r1->x1 + r2->x1, r1->y1 + r2->y1,
                      r1->x2 + r2->x2, r1->y2 + r2->y2); }
  picture apply (array<picture> pics, SI pixel) {
    picture p1= eff->apply (pics, pixel);
    picture p2= pen->apply (pics, pixel);
    return blur (p1, p2); }
};

class outline_effect_rep: public effect_rep {
  effect eff, pen;
public:
  outline_effect_rep (effect eff2, effect pen2):
    eff (eff2), pen (pen2) {}
  rectangle get_extents (array<rectangle> rs) {
    rectangle r1= eff->get_extents (rs);
    rectangle r2= pen->get_extents (rs);
    return rectangle (r1->x1 + r2->x1, r1->y1 + r2->y1,
                      r1->x2 + r2->x2, r1->y2 + r2->y2); }
  picture apply (array<picture> pics, SI pixel) {
    picture p1= eff->apply (pics, pixel);
    picture p2= pen->apply (pics, pixel);
    return outline (p1, p2); }
};

class thicken_effect_rep: public effect_rep {
  effect eff, pen;
public:
  thicken_effect_rep (effect eff2, effect pen2):
    eff (eff2), pen (pen2) {}
  rectangle get_extents (array<rectangle> rs) {
    rectangle r1= eff->get_extents (rs);
    rectangle r2= pen->get_extents (rs);
    return rectangle (r1->x1 + r2->x1, r1->y1 + r2->y1,
                      r1->x2 + r2->x2, r1->y2 + r2->y2); }
  picture apply (array<picture> pics, SI pixel) {
    picture p1= eff->apply (pics, pixel);
    picture p2= pen->apply (pics, pixel);
    return thicken (p1, p2); }
};

class erode_effect_rep: public effect_rep {
  effect eff, pen;
public:
  erode_effect_rep (effect eff2, effect pen2):
    eff (eff2), pen (pen2) {}
  rectangle get_extents (array<rectangle> rs) {
    rectangle r1= eff->get_extents (rs);
    rectangle r2= pen->get_extents (rs);
    return rectangle (r1->x1 + r2->x1, r1->y1 + r2->y1,
                      r1->x2 + r2->x2, r1->y2 + r2->y2); }
  picture apply (array<picture> pics, SI pixel) {
    picture p1= eff->apply (pics, pixel);
    picture p2= pen->apply (pics, pixel);
    return erode (p1, p2); }
};

effect blur (effect eff, effect pen) {
  return tm_new<blur_effect_rep> (eff, pen); }
effect outline (effect eff, effect pen) {
  return tm_new<outline_effect_rep> (eff, pen); }
effect thicken (effect eff, effect pen) {
  return tm_new<thicken_effect_rep> (eff, pen); }
effect erode (effect eff, effect pen) {
  return tm_new<erode_effect_rep> (eff, pen); }

/******************************************************************************
* Effects that depend on randomization
******************************************************************************/

class degrade_effect_rep: public effect_rep {
  effect eff;
  double wx, wy, th, sh;
public:
  degrade_effect_rep (effect e2, double Wx, double Wy, double Th, double Sh):
    eff (e2), wx (Wx), wy (Wy), th (Th), sh (Sh) {}
  rectangle get_extents (array<rectangle> rs) {
    return eff->get_extents (rs); }
  picture apply (array<picture> pics, SI pixel) {
    return degrade (eff->apply (pics, pixel),
                    wx / pixel, wy / pixel, th, sh); }
};

effect degrade (effect eff, double wx, double wy, double th, double sh) {
  return tm_new<degrade_effect_rep> (eff, wx, wy, th, sh); }

class distort_effect_rep: public effect_rep {
  effect eff;
  double wx, wy, rx, ry;
public:
  distort_effect_rep (effect e2, double Wx, double Wy, double Rx, double Ry):
    eff (e2), wx (Wx), wy (Wy), rx (Rx), ry (Ry) {}
  rectangle get_extents (array<rectangle> rs) {
    return eff->get_extents (rs); }
  picture apply (array<picture> pics, SI pixel) {
    return distort (eff->apply (pics, pixel),
                    wx / pixel, wy / pixel, rx / pixel, ry / pixel); }
};

effect distort (effect eff, double wx, double wy, double rx, double ry) {
  return tm_new<distort_effect_rep> (eff, wx, wy, rx, ry); }

class gnaw_effect_rep: public effect_rep {
  effect eff;
  double wx, wy, rx, ry;
public:
  gnaw_effect_rep (effect e2, double Wx, double Wy, double Rx, double Ry):
    eff (e2), wx (Wx), wy (Wy), rx (Rx), ry (Ry) {}
  rectangle get_extents (array<rectangle> rs) {
    return eff->get_extents (rs); }
  picture apply (array<picture> pics, SI pixel) {
    return gnaw (eff->apply (pics, pixel),
                 wx / pixel, wy / pixel, rx / pixel, ry / pixel); }
};

effect gnaw (effect eff, double wx, double wy, double rx, double ry) {
  return tm_new<gnaw_effect_rep> (eff, wx, wy, rx, ry); }

/******************************************************************************
* Composing various effects
******************************************************************************/

class compose_effect_rep: public effect_rep {
  array<effect> effs;
  composition_mode mode;
public:
  compose_effect_rep (array<effect> e, composition_mode m):
    effs (e), mode (m) {
      ASSERT (N (effs) > 0, "at least one effect expected"); }
  rectangle get_logical_extents (array<rectangle> rs) {
    array<rectangle> xrs (N(effs));
    for (int i=0; i<N(effs); i++)
      xrs[i]= effs[i]->get_logical_extents (rs);
    return join (xrs, mode); }
  rectangle get_extents (array<rectangle> rs) {
    array<rectangle> xrs (N(effs));
    for (int i=0; i<N(effs); i++)
      xrs[i]= effs[i]->get_extents (rs);
    return join (xrs, mode); }
  picture apply (array<picture> pics, SI pixel) {
    array<picture> args (N(effs));
    for (int i=0; i<N(effs); i++)
      args[i]= effs[i]->apply (pics, pixel);
    return compose (args, mode); }
};

effect compose (array<effect> effs, composition_mode mode) {
  return tm_new<compose_effect_rep> (effs, mode); }
effect superpose (array<effect> effs) {
  return compose (effs, compose_source_over); }
effect add (array<effect> effs) {
  return compose (effs, compose_add); }
effect sub (array<effect> effs) {
  return compose (effs, compose_sub); }
effect mul (array<effect> effs) {
  return compose (effs, compose_mul); }
effect min (array<effect> effs) {
  return compose (effs, compose_min); }
effect max (array<effect> effs) {
  return compose (effs, compose_max); }

class mix_effect_rep: public effect_rep {
  effect eff1, eff2;
  double a1, a2;
public:
  mix_effect_rep (effect eff1b, double a1b, effect eff2b, double a2b):
    eff1 (eff1b), eff2 (eff2b), a1 (a1b), a2 (a2b) {}
  rectangle get_extents (array<rectangle> rs) {
    array<rectangle> xrs (2);
    xrs[0]= eff1->get_extents (rs);
    xrs[1]= eff2->get_extents (rs);
    return join (xrs, compose_add); }
  picture apply (array<picture> pics, SI pixel) {
    picture pic1= eff1->apply (pics, pixel);
    picture pic2= eff2->apply (pics, pixel);
    return mix (pic1, a1, pic2, a2); }
};

effect mix (effect eff1, double a1, effect eff2, double a2) {
  return tm_new<mix_effect_rep> (eff1, a1, eff2, a2); }

/******************************************************************************
* Color effects
******************************************************************************/

class normalize_effect_rep: public effect_rep {
  effect eff;
public:
  normalize_effect_rep (effect eff2):
    eff (eff2) {}
  rectangle get_extents (array<rectangle> rs) {
    return eff->get_extents (rs); }
  picture apply (array<picture> pics, SI pixel) {
    return normalize (eff->apply (pics, pixel)); }
};

class color_matrix_effect_rep: public effect_rep {
  effect eff;
  array<double> m;
public:
  color_matrix_effect_rep (effect eff2, array<double> m2):
    eff (eff2), m (m2) {}
  rectangle get_extents (array<rectangle> rs) {
    return eff->get_extents (rs); }
  picture apply (array<picture> pics, SI pixel) {
    return color_matrix (eff->apply (pics, pixel), m); }
};

class make_transparent_effect_rep: public effect_rep {
  effect eff;
  color bgc;
public:
  make_transparent_effect_rep (effect eff2, color bgc2):
    eff (eff2), bgc (bgc2) {}
  rectangle get_extents (array<rectangle> rs) {
    return eff->get_extents (rs); }
  picture apply (array<picture> pics, SI pixel) {
    return make_transparent (eff->apply (pics, pixel), bgc); }
};

class make_opaque_effect_rep: public effect_rep {
  effect eff;
  color bgc;
public:
  make_opaque_effect_rep (effect eff2, color bgc2):
    eff (eff2), bgc (bgc2) {}
  rectangle get_extents (array<rectangle> rs) {
    return eff->get_extents (rs); }
  picture apply (array<picture> pics, SI pixel) {
    return make_opaque (eff->apply (pics, pixel), bgc); }
};

class recolor_effect_rep: public effect_rep {
  effect eff;
  color col;
public:
  recolor_effect_rep (effect eff2, color col2):
    eff (eff2), col (col2) {}
  rectangle get_extents (array<rectangle> rs) {
    return eff->get_extents (rs); }
  picture apply (array<picture> pics, SI pixel) {
    return recolor (eff->apply (pics, pixel), col); }
};

class skin_effect_rep: public effect_rep {
  effect eff;
  color col;
public:
  skin_effect_rep (effect eff2, color col2):
    eff (eff2), col (col2) {}
  rectangle get_extents (array<rectangle> rs) {
    return eff->get_extents (rs); }
  picture apply (array<picture> pics, SI pixel) {
    return apply_skin (eff->apply (pics, pixel), col); }
};

effect normalize (effect eff) {
  return tm_new<normalize_effect_rep> (eff); }
effect color_matrix (effect eff, array<double> m) {
  return tm_new<color_matrix_effect_rep> (eff, m); }
effect make_transparent (effect eff, color bgc) {
  return tm_new<make_transparent_effect_rep> (eff, bgc); }
effect make_opaque (effect eff, color bgc) {
  return tm_new<make_opaque_effect_rep> (eff, bgc); }
effect recolor (effect eff, color col) {
  return tm_new<recolor_effect_rep> (eff, col); }
effect apply_skin (effect eff, color col) {
  return tm_new<skin_effect_rep> (eff, col); }

/******************************************************************************
* Hatching
******************************************************************************/

class hatch_effect_rep: public effect_rep {
  effect eff;
  int sx, sy;
  double fp;
  double de;
public:
  hatch_effect_rep (effect e, int sx2, int sy2, double fp2, double de2):
    eff (e), sx (sx2), sy (sy2), fp (fp2), de (de2) {}
  rectangle get_logical_extents (array<rectangle> rs) {
    return eff->get_logical_extents (rs); }
  rectangle get_extents (array<rectangle> rs) {
    return eff->get_extents (rs); }
  picture apply (array<picture> pics, SI pixel) {
    return hatch (eff->apply (pics, pixel), sx, sy, fp, de); }
};

effect hatch (effect e, int sx, int sy, double fp, double de) {
  return tm_new<hatch_effect_rep> (e, sx, sy, fp, de); }

class dots_effect_rep: public effect_rep {
  effect eff;
  int a, b, c, d;
  double fp;
  double de;
public:
  dots_effect_rep (effect e, int a2, int b2, int c2, int d2,
                   double fp2, double de2):
    eff (e), a (a2), b (b2), c (c2), d (d2), fp (fp2), de (de2) {}
  rectangle get_logical_extents (array<rectangle> rs) {
    return eff->get_logical_extents (rs); }
  rectangle get_extents (array<rectangle> rs) {
    return eff->get_extents (rs); }
  picture apply (array<picture> pics, SI pixel) {
    return dots (eff->apply (pics, pixel), a, b, c, d, fp, de); }
};

effect dots (effect e, int a, int b, int c, int d, double fp, double de) {
  return tm_new<dots_effect_rep> (e, a, b, c, d, fp, de); }

/******************************************************************************
* Building effects from tree description
******************************************************************************/

array<effect>
build_effects (array<tree> a) {
  array<effect> effs (N(a));
  for (int i=0; i<N(a); i++)
    effs[i]= build_effect (a[i]);
  return effs;
}

effect
build_effect (tree t) {
  if (t == "")
    return argument_effect (0);
  else if (is_int (t))
    return argument_effect (as_int (t));
  else if (is_func (t, EFF_MOVE, 3)) {
    effect eff= build_effect (t[0]);
    double dx = as_double (t[1]);
    double dy = as_double (t[2]);
    return move (eff, dx, dy);
  }
  else if (is_func (t, EFF_MAGNIFY, 3)) {
    effect eff= build_effect (t[0]);
    double sx = as_double (t[1]);
    double sy = as_double (t[2]);
    return magnify (eff, sx, sy);
  }
  else if (is_func (t, EFF_BUBBLE, 3)) {
    effect eff= build_effect (t[0]);
    double r  = as_double (t[1]);
    double a  = as_double (t[2]);
    return bubble (eff, r, a);
  }
  else if (is_func (t, EFF_GAUSSIAN, 1)) {
    double r= as_double (t[0]);
    return gaussian_pen_effect (r);
  }
  else if (is_func (t, EFF_TURBULENCE, 5)) {
    effect e= build_effect (t[0]);
    int    s= as_int (t[1]);
    double w= as_double (t[2]);
    double h= as_double (t[3]);
    int    o= as_int (t[4]);
    return turbulence (e, s, w, h, o);
  }
  else if (is_func (t, EFF_FRACTAL_NOISE, 5)) {
    effect e= build_effect (t[0]);
    int    s= as_int (t[1]);
    double w= as_double (t[2]);
    double h= as_double (t[3]);
    int    o= as_int (t[4]);
    return fractal_noise (e, s, w, h, o);
  }
  else if (is_compound (t, EFF_HATCH, 5)) {
    effect e = build_effect (t[0]);
    int    sx= as_int (t[1]);
    int    sy= as_int (t[2]);
    double fp= as_double (t[3]);
    double de= as_double (t[4]);
    return hatch (e, sx, sy, fp, de);
  }
  else if (is_compound (t, EFF_DOTS, 7)) {
    effect e = build_effect (t[0]);
    int    a = as_int (t[1]);
    int    b = as_int (t[2]);
    int    c = as_int (t[3]);
    int    d = as_int (t[4]);
    double fp= as_double (t[5]);
    double de= as_double (t[6]);
    return dots (e, a, b, c, d, fp, de);
  }
  else if (is_func (t, EFF_GAUSSIAN, 3)) {
    double rx= as_double (t[0]);
    double ry= as_double (t[1]);
    double a = as_double (t[2]);
    return gaussian_pen_effect (rx, ry, a);
  }
  else if (is_func (t, EFF_OVAL, 1)) {
    double r= as_double (t[0]);
    return oval_pen_effect (r);
  }
  else if (is_func (t, EFF_OVAL, 3)) {
    double rx= as_double (t[0]);
    double ry= as_double (t[1]);
    double a = as_double (t[2]);
    return oval_pen_effect (rx, ry, a);
  }
  else if (is_func (t, EFF_RECTANGULAR, 1)) {
    double r= as_double (t[0]);
    return rectangular_pen_effect (r);
  }
  else if (is_func (t, EFF_RECTANGULAR, 3)) {
    double rx= as_double (t[0]);
    double ry= as_double (t[1]);
    double a = as_double (t[2]);
    return rectangular_pen_effect (rx, ry, a);
  }
  else if (is_func (t, EFF_MOTION, 2)) {
    double dx= as_double (t[0]);
    double dy= as_double (t[1]);
    return motion_pen_effect (dx, dy);
  }
  else if (is_func (t, EFF_BLUR, 2)) {
    effect eff= build_effect (t[0]);
    effect pen= build_effect (t[1]);
    return blur (eff, pen);
  }
  else if (is_func (t, EFF_OUTLINE, 2)) {
    effect eff  = build_effect (t[0]);
    effect pen= build_effect (t[1]);
    return outline (eff, pen);
  }
  else if (is_func (t, EFF_THICKEN, 2)) {
    effect eff  = build_effect (t[0]);
    effect pen= build_effect (t[1]);
    return thicken (eff, pen);
  }
  else if (is_func (t, EFF_ERODE, 2)) {
    effect eff  = build_effect (t[0]);
    effect pen= build_effect (t[1]);
    return erode (eff, pen);
  }
  else if (is_func (t, EFF_DEGRADE, 5)) {
    effect eff= build_effect (t[0]);
    double wx = as_double (t[1]);
    double wy = as_double (t[2]);
    double th = as_double (t[3]);
    double sh = as_double (t[4]);
    return degrade (eff, wx, wy, th, sh);
  }
  else if (is_func (t, EFF_DISTORT, 5)) {
    effect eff= build_effect (t[0]);
    double wx = as_double (t[1]);
    double wy = as_double (t[2]);
    double rx = as_double (t[3]);
    double ry = as_double (t[4]);
    return distort (eff, wx, wy, rx, ry);
  }
  else if (is_func (t, EFF_GNAW, 5)) {
    effect eff= build_effect (t[0]);
    double wx = as_double (t[1]);
    double wy = as_double (t[2]);
    double rx = as_double (t[3]);
    double ry = as_double (t[4]);
    return gnaw (eff, wx, wy, rx, ry);
  }
  else if (is_func (t, EFF_SUPERPOSE)) {
    array<effect> effs= build_effects (A(t));
    return superpose (effs);
  }
  else if (is_func (t, EFF_ADD)) {
    array<effect> effs= build_effects (A(t));
    return add (effs);
  }
  else if (is_func (t, EFF_SUB)) {
    array<effect> effs= build_effects (A(t));
    return sub (effs);
  }
  else if (is_func (t, EFF_MUL)) {
    array<effect> effs= build_effects (A(t));
    return mul (effs);
  }
  else if (is_func (t, EFF_MIN)) {
    array<effect> effs= build_effects (A(t));
    return min (effs);
  }
  else if (is_func (t, EFF_MAX)) {
    array<effect> effs= build_effects (A(t));
    return max (effs);
  }
  else if (is_func (t, EFF_MIX, 4)) {
    effect eff1= build_effect (t[0]);
    double a1  = as_double (t[1]);
    effect eff2= build_effect (t[2]);
    double a2  = as_double (t[3]);
    return mix (eff1, a1, eff2, a2);
  }
  else if (is_func (t, EFF_NORMALIZE)) {
    effect eff= build_effect (t[0]);
    return normalize (eff);
  }
  else if (is_func (t, EFF_MONOCHROME, 3)) {
    effect eff= build_effect (t[0]);
    color  col= named_color (as_string (t[1]));
    double a  = as_double (t[2]);
    true_color c (col);
    array<double> m (20);
    for (int i=0; i<20; i++) m[i]= 0.0;
    m[0]= m[6]= m[12]= 1-a;
    m[4]= c.r; m[9]= c.g; m[14]= c.b;
    m[18]= 1-a + c.a * a;
    return color_matrix (eff, m);
  }
  else if (is_func (t, EFF_COLOR_MATRIX, 2)) {
    effect eff = build_effect (t[0]);
    matrix<double> m= as_matrix<double> (t[1]);
    if (NR (m) != 4 && NC (m) != 5) return argument_effect (0);
    array<double> v;
    for (int i=0; i<4; i++)
      for (int j=0; j<5; j++)
        v << m (i, j);
    return color_matrix (eff, v);
  }
  else if (is_func (t, EFF_MAKE_TRANSPARENT)) {
    effect eff= build_effect (t[0]);
    color  bgc= named_color (as_string (t[1]));
    return make_transparent (eff, bgc);
  }
  else if (is_func (t, EFF_MAKE_OPAQUE)) {
    effect eff= build_effect (t[0]);
    color  bgc= named_color (as_string (t[1]));
    return make_opaque (eff, bgc);
  }
  else if (is_func (t, EFF_RECOLOR)) {
    effect eff= build_effect (t[0]);
    color  col= named_color (as_string (t[1]));
    return recolor (eff, col);
  }
  else if (is_func (t, EFF_SKIN)) {
    effect eff= build_effect (t[0]);
    color  col= named_color (as_string (t[1]));
    return apply_skin (eff, col);
  }
  else if (is_compound (t, "eff-crop", 5)) {
    effect eff= build_effect (t[0]);
    double cx1= as_double (t[1]);
    double cy1= as_double (t[2]);
    double cx2= as_double (t[3]);
    double cy2= as_double (t[4]);
    return crop (eff, cx1, cy1, cx2, cy2);
  }
  else
    return argument_effect (0);
}
