Closed darrensw closed 11 years ago
There is certainly a lot to learn from Aviary's implementation. From what I can gather, the way it works is:
Most of this can be deduced from checking out the snippet of code at the end of this post, which is from Avary's web editor.
So, there are definitely some interesting ideas to play around with based on this info, but the main difference between Aviary's editor and CamanJS is that Aviary was designed for it's own editor interface while CamanJS is more open-ended in terms of UX implementation. CamanJS is also available in NodeJS where a lot of Aviary's editing process wouldn't apply or make sense.
CamanJS also always works with the full image instead of generating a smaller preview image, which is a cool feature that could be built into CamanJS as an option. This would greatly help online-based CamanJS editors become more responsive for the user.
AV.PaintWidget.prototype.module["saturation"] = function() {
var _paintWidget;
var _satchange = 0;
var _origBacking, _origBackingPixels, _newCanvasData, _oldLayers, _flatten;
var _dirty;
var api = {};
var _saturationRun = function(dest, src, val) {
var i, r, g, b, rNew, gNew, bNew;
var v = val;
var R = .213 * (1 - v);
var G = .715 * (1 - v);
var B = .072 * (1 - v);
for (i = 0; 4 * i < src.length; i++) {
r = src[4 * i];
g = src[4 * i + 1];
b = src[4 * i + 2];
rNew = (R + v) * r + G * g + B * b + .5 | 0;
gNew = R * r + (G + v) * g + B * b + .5 | 0;
bNew = R * r + G * g + (B + v) * b + .5 | 0;
r = rNew > 255 ? 255 : rNew < 0 ? 0 : rNew;
g = gNew > 255 ? 255 : gNew < 0 ? 0 : gNew;
b = bNew > 255 ? 255 : bNew < 0 ? 0 : bNew;
dest[4 * i] = r;
dest[4 * i + 1] = g;
dest[4 * i + 2] = b;
dest[4 * i + 3] = src[4 * i + 3]
}
};
var _saturation = function(layerName, val, flatten) {
var layer = _paintWidget.getLayerByName(layerName);
if (layer.canvas == null) {
return
}
var i;
var cc;
cc = layer.canvas.getContext("2d");
if (_origBacking == null) {
if (flatten) {
_oldLayers = _paintWidget.duplicateAllLayers();
_paintWidget.flattenAllLayers();
layer = _paintWidget.getLayerByName(layerName);
cc = layer.canvas.getContext("2d")
}
_origBackingPixels = AV.cnvs.getCanvasPixelData(layer.canvas);
_origBacking = AV.cnvs.copyCanvas(layer.canvas);
_newCanvasData = cc.createImageData(layer.canvas.width, layer.canvas.height)
}
_satchange = val;
_flatten = flatten;
_saturationRun(_newCanvasData.data, _origBackingPixels.data, val);
cc.putImageData(_newCanvasData, 0, 0);
_paintWidget.recomposite()
};
var _pushState = function() {
var layerIndex = _paintWidget.currentLayerIndex;
var layer = _paintWidget.layers[layerIndex];
_paintWidget.actions.push([_saturationUndo, this, [layer.name, _origBacking, _oldLayers]], [_saturationRedo, this, [layer.name, _satchange, _flatten]], {action: "saturation",value: _satchange,flatten: _flatten});
_paintWidget.actions.redoFake()
};
var _saturationUndo = function(layerName, backing, oldLayers) {
if (oldLayers) {
_paintWidget.duplicateAllLayersFrom(oldLayers)
} else {
var layer = _paintWidget.getLayerByName(layerName);
var c = layer.canvas.getContext("2d");
c.globalCompositeOperation = "copy";
c.drawImage(backing, 0, 0);
c.globalCompositeOperation = "source-over"
}
_paintWidget.recomposite()
};
var _saturationRedo = function(layerName, val, flatten) {
_saturation(layerName, val, flatten);
_origBacking = null;
_oldLayers = null;
_dirty = true
};
api.applyPreview = function(val, flatten) {
var layerIndex = _paintWidget.currentLayerIndex;
var layer = _paintWidget.layers[layerIndex];
AV.util.nextFrame(function() {
if (!_dirty) {
_paintWidget.actions.undoFake()
} else {
_paintWidget.actions.undo();
_dirty = false
}
_saturation(layer.name, val, flatten);
_pushState()
})
};
api.activate = function(paintWidget) {
_paintWidget = paintWidget;
_origBacking = _origBackingPixels = null;
_newCanvasData = null;
_oldLayers = null;
_satchange = 0;
_flatten = false;
_dirty = false
};
api.deactivate = function() {
api.reset()
};
api.reset = function() {
_origBacking = null;
_origBackingPixels = null;
_newCanvasData = null;
_oldLayers = null
};
api.readAction = function(data, next) {
if (data && data.value !== undefined) {
var layerIndex = _paintWidget.currentLayerIndex;
var layer = _paintWidget.layers[layerIndex];
_saturation(layer.name, data.value, data.flatten);
_pushState()
}
if (next) {
next.call(this)
}
};
return api
}();
Oh, and to answer your question about why filters are faster: I'm not 100% on this, but it's probably because each filter is applied in a single pass. While this gives the benefit of speed, it requires custom code for each filter and loses simplicity. In CamanJS, a filter is a series of adjustments that are applied one-by-one. This means it takes longer, but makes the code incredibly simple.
One really cool idea that I've been toying with in my head for awhile would be "pre-compiled" filters. Not 100% sure on how that implementation would work right now, but it would be amazing if done correctly.
Not so much an issue as a question... How does Aviary achieve almost instant speed on adjustments e.g. brightness, contrast with sliders or 'effects' (filters e.g. lomo) as they call them?
Can Camanjs match that somehow?
I'm about to launch a site using Caman which is a very Social Media centric site but would hate to be seen as a slower version of something. Don't like the Aviary branding but does an end user actually care?
Is it the code? the Process? Very curious.