meltingice / CamanJS

Javascript HTML5 (Ca)nvas (Man)ipulation
http://camanjs.com
BSD 3-Clause "New" or "Revised" License
3.55k stars 404 forks source link

using <img> instead of script and canvas? #34

Closed Pomax closed 12 years ago

Pomax commented 12 years ago

I love the idea of camanjs, but the fact that it requires a script and canvas element seems too developer-oriented, rather than every day user oriented. I would like to suggest using the img element and having caman.js auto-convert them to canvas elements with the relevant operations appied, with the instructions encoded in a data-* attribute, such as

<img src="myimage.jpg" data-caman-instructions="saturation(20) gamma(1.4) vintage contrast(5) exposure(15) vignette(300,60)">

or something similar along these lines, which caman then replaced on page with a canvas element that generates an overlay Image() object based on the canvas's .toDataURL so that "save as" still works.

This way, the usability becomes infinitely better: include camanjs, and just use image tags. Operations or canvas element not supported in a browser? Not a problem, the image element will simply show the original image, instead of being converted to a canvas element.

rwaldron commented 12 years ago

+1

meltingice commented 12 years ago

I too like this idea. I'll play around with it a bit.

Pomax commented 12 years ago

As a possible foothold, I have some code that does shimming that basically has a replaceAforB(a) { ... } function that will take an element and replace it in the dom with something else, with an dom ready event handler that basically does a document.querySelectorAll("tag[data-type=somevalue]").each(replaceAforB(this)) so that for static content, the replacement is immediate, with a replaceAforB that looks like:

replaceAforB = function(a) {
  var b = ...
  // ...construct replacement fragment...
  if (a.parentNode) {
    a.parentNode.replaceChild(b,a);
  }
  return b;
}

I also "hijack" document.createElement, to do the following (is quasi-JS):

(function() {
  var oldFn = document.createElement;
  document.createElement = function(tagname) {
    var element = oldFn(tagname);
    if(tagname==="img") {
      var oldSa = element.setAttribute;
      element.setAttribute = function(name, value) {
        if(name==="data-special-name") {
          var newElement = replaceAforB(element);
          handleInstructions(newElement);
        }
      }
    } return element;
  };
}());

This allows element creation using createElement("img") followed by img.setType("data-special-name", "...."), which then automatically triggers element replacement, and handling code.

For templating engines, you'd still need a special function that can get called whenever a new template fragment/partial gets injected that contains elements with canan instructions tied to it, but this should be relatively straight forward, basically calling the same code that gets called on dom ready for the initial replacement pass.

meltingice commented 12 years ago

One thing that needs to be brainstormed is: how to handle functions that take an object as a parameter? Simple functions like saturation(20) are easy to do, but when you have radialBlur({size: 20, center: {x: 100, y: 10}}) it gets more complicated.

Pomax commented 12 years ago

while technically still a form of eval, not all eval is bad (as long as we're safe about it). You could use:

try {
  var functionFactory = new Function(" return function() { " + attributeString + " }"); 
  try {
    instructionFunction = functionFactory();
    // pre-run callback hooks go here
    instructionFunction();
    // post-run callback hooks go here
  } catch (error) {
    console.log("runtime errors in the attribute string code...");
    // runtime error callback hooks go here
  }
} catch (error) {
  console.log("syntax errors in the attribute string...");
  // illegal argument callback hooks go here
}

So you make a Function (object) that acts as a factory for a function that calls all the Caman code. This can fail due to either compile or runtime errors, so we try/catch to make sure we know whether something failed, so that we can take appropriate actions.

And in order to make sure the code was really safe, we don't want to rely on automatic semi-color insertion. ASI is incredibly hard to predict, so you probably want to massage the attributeString content so that there are ";" after each function. If there are no nested functions, a RegExp replace for ")\s+(\w)" to "); $1" would be enough, if there ARE nested functions, things get trickier and you might want to preprocess with a simple tokeniser that counts nesting and only injects ";" when it's at depth 0.

meltingice commented 12 years ago

A solid implementation of this is now in master. There is still some work that can be done to improve it, such as making quotes around string arguments optional, but this should be a good first iteration. Thanks for the suggestion and ideas!