function imagegen(options) {
  var overlayOpts = { mask: { color: '#000',
                              loadSpeed: 200,
			      opacity: 0.6 },
                      closeOnClick: false  };

  var SPINNER_WIDTH = 100;
  var SPINNER_HEIGHT = 100;

  // Canvas
  var toolbar;
  var canvas = new function() {
    var photo = $("#ig-photo");
    var fg = $("#ig-fg");
    var bg = $("#ig-bg");
    var canvas = $("#ig-canvas");
    var spinner = $("#ig-canvas-spinner");
    var self = this;

    this.setBusy = function(ena) {
      if (ena) {
	var w = canvas.width();
	var h = canvas.height();
	spinner.css({left: w/2-SPINNER_WIDTH/2,
		     top: h/2-SPINNER_HEIGHT/2});
	spinner.show();
      } else {
	spinner.hide();
      }
    };

    this.getPhotoPosition = function() {
      return {
	left: photo.position().left,
	top: photo.position().top,
	width: photo.width()
      };
    };

    this.setup = function(data) {
      options.data = data;
      var completeCount = 0;

      function imgComplete(e) {
	completeCount--;
	if (! completeCount) {
	  canvas.css({ width: data.canvas.width + "px",
		       height: data.canvas.height + "px" });
	  fg.attr("src", data.fg.img);
	  fg.css({width: data.canvas.width + "px",
  		  height: data.canvas.height + "px"});
	  bg.css({"background": data.bg.color,
		  width: data.canvas.width + "px",
  		  height: data.canvas.height + "px"});
	  photo.attr("src", data.photo.img_fx);
	  toolbar.zoomToFit();
	  self.setBusy(false);
	}
      }
      completeCount = 2;
      $.cacheImage([data.photo.img_fx],
		   { complete: function(e) {
		       var elt = e.currentTarget || e.srcElement;
		       data.photo.width = elt.width;
		       data.photo.height = elt.height;
		       imgComplete();  } });
      $.cacheImage([ data.fg.img],
		   { complete: function() { imgComplete();  } });
    };
  };

  // Webcam Dialog
  (function() {
     var camOn = false;
     var canvas = $("#ig-webcam-dialog .canvas");
     $("#ig-cam-snap").click(function() {
			       webcam.capture();
			       webcam.save("/money/"); // FIXME
			       return false;
			     });
     $("#ig-cam-on").click(function() {
			     if (camOn) {
			       camOn = false;
			       $("#XwebcamXobjectX").remove();
			     } else {
			       camOn = true;
			       canvas.webcam({
					       width: 320,
					       height: 240,
					       mode: "save",
					       swffile: "/media/jquery/webcam/jscam.swf",
					       onTick: function() {},
					       onSave: function() {},
					       onCapture: function() {},
					       debug: function(type,s) {},
					       onLoad: function() {}
					     });
			     }
			     return false;
			   });

   })();


  // Upload Dialog
  (function() {
     var dlg = $("#ig-upload-dialog");
     var face = $("#ig-upload-face-rect");
     var photo = $("#ig-upload-photo");
     var preview = $("#ig-upload-preview");
     var spinner = $(".spinner", dlg);
     var form = $("form", dlg);
     var btn = $("#ig-upload");

     function beforeOpen() {
       photo.attr("src", options.data.photo.img_fx);
       var w = options.data.photo.width;
       var h = options.data.photo.height;
       var mw = 300;
       var mh = 300;
       var nw = Math.round(mw);
       var nh = Math.round(nw*h/w);
       if (nh > mh) {
	 nh = mh;
	 nw = Math.round(nh*w/h);
       }
       var f = nw / w;
       face.css({ left: Math.ceil(f*options.data.photo.face[0])+"px",
		  top: Math.ceil(f*options.data.photo.face[1])+"px",
		  width: Math.ceil(f*options.data.photo.face[2]) +"px",
		 height: Math.ceil(f*options.data.photo.face[3]) + "px"});
       photo.css({ width: nw+"px",
		   height: nh+"px"});
     }

     btn.overlay($.extend({},
                          overlayOpts,
			  { onBeforeLoad: beforeOpen }));

     form.ajaxForm({beforeSubmit: function(arr,$form,options) {
		      spinner.css({left: (preview.width()-SPINNER_WIDTH)/2,
		                   top: (preview.height()-SPINNER_HEIGHT)/2});
		      spinner.show();
		    },
		    success: function(response, status, xhr, form) {
		      spinner.hide();
		      var startMarker = "<!--json-->";
		      var endMarker = "<!--nosj-->";
		      var i = response.indexOf(startMarker);
		      var j = response.indexOf(endMarker);
		      if (i < 0 || j < 0) {
			alert("Error: "+response);
		      }
		      var data = $.parseJSON(response.substring(i+startMarker.length, j));
		      canvas.setup(data);
		      btn.data("overlay").close();
		    }
		   });
   })();

  // Facebook
  (function() {
     var shareDlg = $("#ig-share-dialog");
     var shareBtn = $("#ig-share");
     var shareForm = $("#ig-share-form");
     var cancelBtn = $(".dlg-close", shareForm);
     var requestPending = false;
     var messageInput = $("textarea[name=message]", shareForm);
     var busy = $(".dlg-busy", shareDlg);
     var postBtn = $("#ig-share-post");

     function enableForm(ena) {
       var inputs = $("input, button, textarea", shareForm);
       var close = $(".close", shareDlg);
       if (ena) {
	 //postBtn.text("Post");
	 requestPending = false;
	 close.css({opacity: 1});
	 inputs.attr("disabled", false);
	 busy.hide();
       } else {
	 //postBtn.text("Posting...");
	 requestPending = true;
	 close.css({opacity: .4});
	 inputs.attr("disabled", true);
	 busy.show();
       }
     }

     function doClose() {
       shareBtn.data("overlay").close();
     }

     function beforeOpen() {
       requestPending = false;
       enableForm(true);
       messageInput
	 .val("Do you like my " + options.data.name + "?");
       shareForm.validator({position: 'top left',
	offset: [-12, 0],
	message: '<div><em/></div>'})
	 .bind("submit.share", function(e) {
		   if (! e.isDefaultPrevented() && ! requestPending) {
		     enableForm(false);
		     if ($("input[name=network]:checked").val() == 'twitter') {
		       postToTwitter();
		     } else {
		       postToFacebook();
		     }
		   }
		   e.preventDefault();
		 });
       $(window).bind("twitterAuthComplete.twitterShare",
		      function() { twitterAuthComplete(); });
       $(window).bind("twitterAuthFailed.twitterShare",
		      function() { twitterAuthFailed(); });

     }
     function beforeClose(e) {
       shareForm.unbind(".share");
       $(window).unbind(".twitterShare");
       shareForm.data("validator").destroy();
     }

     function afterClose() {
     }

     function twitterAuthFailed() {
       genr.showMessage("Sorry, your photo was not posted...");
       doClose();
     }

     function twitterAuthComplete() {
       genr.showMessage("Please wait while we post your photo...");
       var pos = canvas.getPhotoPosition();
       $.ajax({url:".",
	       type: "POST",
	       data: {
		 tpl: options.data.id,
		 finish: 1,
		 message: messageInput.val(),
		 photo_left:Math.round(pos.left),
		 photo_top:Math.round(pos.top),
		 photo_width:Math.round(pos.width),
		 twitter_access_token: 1
	       },
	       complete: function() {
		 doClose();
	       },
	       error: function() {
		 genr.showMessage(msgError);
	       },
	       success: function() {
		 genr.showMessage("Your photo has been posted!");
	       }

	      });
    }


     function postToTwitter() {
       var w =	window.open(options.urls.shareTwitter,
		  'Festisite Share',
		    'width=800,height=500,menubar=no,status=no, location=yes,toolbar=no,scrollbars=yes');
	w.focus();
     }

     function postToFacebook() {
       var msgError = "Sorry, your photo was not posted...";
       FB.init({appId: options.fbAppId, status: true, cookie: true, xfbml: true});
       FB.login(function(response) {
		  if (! response.session) {
		    genr.showMessage(msgError);
		    doClose();
		    return;
		  }
		  genr.showMessage("Please wait while we post your photo...");
		  var pos = canvas.getPhotoPosition();
		  $.ajax({url:".",
			  type: "POST",
			  data: {
			    tpl: options.data.id,
			    finish: 1,
			    message: messageInput.val(),
			    photo_left:Math.round(pos.left),
			    photo_top:Math.round(pos.top),
			    photo_width:Math.round(pos.width),
			    fb_access_token: response.session.access_token
			  },
			  complete: function() {
			    doClose();
			  },
			  error: function() {
			    genr.showMessage(msgError);
			  },
			  success: function() {
			    genr.showMessage("Your photo has been posted!");
			  }

			 });
		}, { perms: 'publish_stream'} );

     }

     shareBtn.overlay($.extend({},
                          overlayOpts,
				     { onBeforeLoad: beforeOpen,
				       onBeforeClose: beforeClose,
				       onClose: afterClose }));
     $("#ig-facebook").click(function() {
			       fb();
			     });
     cancelBtn.click(function() {
		       doClose();
		       return false;
		     });
   })();

  // Toolbar zoom/pos
  toolbar = new (function() {

     var positioning = false;

     function fit() {
       var e = document.getElementById("ig-photo");
       if (false) {
	 var p = canvas.getPhotoPosition();
	 var s= p.width / options.data.photo.width;
	 var fx = p.left + s*options.data.photo.face[0];
	 var fy = p.top + s*options.data.photo.face[1];
	 var fw = s*options.data.photo.face[2]
	 console.log(fx);
	 console.log(fy);
	 console.log(fw);
       }
       if (options.data.window.face) {
         var win = options.data.window.face || options.data.window.rect;
         var pho = options.data.photo;
         var fw = pho.face[2];
         var s = win.width / fw;
         var iw = pho.width * s;
         var ix = win.left - pho.face[0] * s;
         var iy = win.top - pho.face[1] * s;
         set_position(e, ix, iy, iw);
       } else {
	 var od = options.data;
         var nw = od.window.rect.width;
         var nh = od.photo.height*nw/od.photo.width;
         if (nh < od.window.rect.height) {
           nh = od.window.rect.height;
           nw = od.photo.width*nh/od.photo.height;
         }
         var cx = od.window.rect.left + od.window.rect.width/2;
         var cy = od.window.rect.top + od.window.rect.height/2;
         var nx = cx - nw/2;
         var ny = cy - nh/2;
         set_position(e, nx, ny, nw);
       }
       return false;
     }
    this.zoomToFit = fit;

     function set_position(e, l, t, w) {
       var h = w*options.data.photo.height/options.data.photo.width;
       e.style.width = "" + w + "px";
       e.style.height = "" + h + "px";
       e.style.left = "" + l + "px";
       e.style.top = "" + t + "px";
     }

     function do_position(dx, dy, dz) {
       var e = document.getElementById("ig-photo");
       var nw = e.width;
       var nh = nw*options.data.photo.height/options.data.photo.width;
       var cx = options.data.window.rect.left + options.data.window.rect.width/2;
       var cy = options.data.window.rect.top + options.data.window.rect.height/2;
       if (dz) {
         var fcx = ((cx - e.offsetLeft) * options.data.photo.width) / e.width;
         var fcy = ((cy - e.offsetTop) * options.data.photo.height) / e.height;
         nw = e.width + dz;
         nh = nw*options.data.photo.height/options.data.photo.width;
         var fcx2 = ((cx - e.offsetLeft) * options.data.photo.width) / nw;
         var fcy2 = ((cy - e.offsetTop) * options.data.photo.height) / nh;
         dx = ((fcx2-fcx) * nw) / options.data.photo.width ;
         dy = ((fcy2-fcy) * nh) / options.data.photo.height ;
       }
       var nx = e.offsetLeft+dx;
       var ny = e.offsetTop+dy;
       if (dx > 0 && nx > cx+options.data.window.rect.width/2) { return false; }
       else if (dx < 0 && nx < cx-options.data.window.rect.width/2-nw) { return false; }
       if (dy > 0 && ny > cy+options.data.window.rect.height/2) { return false; }
       else if (dy < 0 && ny < cy-options.data.window.rect.height/2-nh) { return false; }
       if (dz < 0 && nw < options.data.window.rect.width/2) { return false; }
       else if (dz > 0 && nw > 5*options.data.photo.width) { return false; }

       sx =  "" + (nx) + "px";
       sy = "" + (ny) + "px";
       sz = "" + (nw) + "px";
       sh = "" + (nh) + "px";
       e.style.left =sx;
       e.style.top = sy;
       if (dz) { e.style.width = sz; e.style.height = sh; }
       return false;
     }

     function position(dx, dy, dz) {
       function p() {
         if (! positioning) return;
         do_position(dx,dy,dz);
         dx *= 1.1;
         dy *= 1.1;
         dz *= 1.1;
         if (positioning) { setTimeout(p, 100); }
       }
       positioning = true;
       p();
     }

     function end_position() {
       positioning = false;
     }

     function buttonUI(b) {
       b.bind('mousedown', function(evt){
		$(this).addClass('pressed');
	      }).bind('mouseup mouseout', function(evt){
		  $(this).removeClass('pressed'); });
     }


     $.each([{ element: "#ig-pos-up", delta: [0,-1,0] },
    	     { element: "#ig-pos-down", delta: [0,1,0] },
    	     { element: "#ig-pos-left", delta: [-1,0,0] },
    	     { element: "#ig-pos-right", delta: [1,0,0] },
    	     { element: "#ig-zoom-in", delta: [0,0,1] },
    	     { element: "#ig-zoom-out", delta: [0,0,-1] }
    	    ], function(i, d) {
	      buttonUI($(d.element));
    	      $(d.element).bind("click", function() {
    				  return false;
    				});
    	      $(d.element).bind("mousedown", function() {
    				  position(d.delta[0], d.delta[1], d.delta[2]);
    				  return false;
    				});
    	      $(d.element).bind("mouseout mouseup", function() {
    				  end_position();
    				  return false;
    				});
    	    });
     buttonUI($("#ig-zoom-fit"));
     $("#ig-zoom-fit").click(function() {
    			       fit();
    			       return false;
    			     });
		 })();

  // Switching generator
  (function() {
     $(window).bind("genr_generator_selected", function(e, data) {
		      canvas.setBusy(true);
		      // FIXME: Move into success
		      $("input[name=tpl]").val(data.generator);
		      $.ajax({ type: "POST",
			       url: document.location.href,
			       data: { "tpl": data.generator },
			       success: function(data, textStatus, xhr) {
				 canvas.setup(data);
			       }
			     });
		    });
   })();

  // Download
  (function(){
     var form = $("#ig-download-form");
     function posToForm() {
       var pos = canvas.getPhotoPosition();
       $("input[name='photo_left']").val(Math.round(pos.left));
       $("input[name='photo_top']").val(Math.round(pos.top));
       $("input[name='photo_width']").val(Math.round(pos.width));
     }
     $("#ig-download-form").submit(function() {
				     posToForm();
				     return true;
				   });
   })();

  // Init
  canvas.setup(options.data);

//  $("#ig-webcam").overlay($.extend({},
//				   overlayOpts,
//				   { onBeforeLoad: function () {
//				     }}));
}


