first pass at integrating filters into new caching mechanism. added group filter test. Removed blurX and blurY filters because I don't think they'll be needed in their current state. Commented out half filter blur test because it's not a common use case. other filter tests have been disabled for now. Working on enabling them with future commits

This commit is contained in:
Eric Rowell
2013-12-31 13:04:05 -08:00
parent 1166154c1b
commit 12e7b06978
19 changed files with 331 additions and 619 deletions

View File

@@ -241,26 +241,41 @@
drawScene: function(can) {
var layer = this.getLayer(),
canvas = can || (layer && layer.getCanvas()),
context = canvas && canvas.getContext(),
cachedCanvas = this._cache.canvas,
cachedSceneCanvas = cachedCanvas && cachedCanvas.scene;
if (this.isVisible()) {
this._draw(canvas, cachedSceneCanvas, 'drawScene');
if (cachedSceneCanvas) {
this._drawCachedSceneCanvas(context);
}
else {
this._drawChildren(canvas, 'drawScene');
}
}
return this;
},
drawHit: function(can) {
var layer = this.getLayer(),
canvas = can || (layer && layer.hitCanvas),
context = canvas && canvas.getContext(),
cachedCanvas = this._cache.canvas,
cachedHitCanvas = cachedCanvas && cachedCanvas.hit;
if (this.shouldDrawHit()) {
this._draw(canvas, cachedHitCanvas, 'drawHit');
if (cachedHitCanvas) {
context.save();
context._applyTransform(this);
context.drawImage(cachedHitCanvas._canvas, 0, 0);
context.restore();
}
else {
this._drawChildren(canvas, 'drawHit');
}
}
return this;
},
_draw: function(canvas, cachedCanvas, drawMethod) {
_drawChildren: function(canvas, drawMethod) {
var context = canvas && canvas.getContext(),
clipWidth = this.getClipWidth(),
clipHeight = this.getClipHeight(),
@@ -279,17 +294,9 @@
context.reset();
}
if (cachedCanvas) {
context.save();
context._applyTransform(this);
context.drawImage(cachedCanvas._canvas, 0, 0);
context.restore();
}
else {
this.children.each(function(child) {
child[drawMethod](canvas);
});
}
this.children.each(function(child) {
child[drawMethod](canvas);
});
if (hasClip) {
context.restore();

View File

@@ -111,11 +111,17 @@
this.addColorComponentSetter(constructor, attr, G);
this.addColorComponentSetter(constructor, attr, B);
// overloaders
this.addOverloadedGetterSetter(constructor, attr + RGB);
this.addOverloadedGetterSetter(constructor, attr + UPPER_R);
this.addOverloadedGetterSetter(constructor, attr + UPPER_G);
this.addOverloadedGetterSetter(constructor, attr + UPPER_B);
},
addFilterGetterSetter: function(constructor, attr, def) {
this.addGetter(constructor, attr, def);
this.addFilterSetter(constructor, attr);
this.addOverloadedGetterSetter(constructor, attr);
},
// getter adders
addColorRGBGetter: function(constructor, attr) {
@@ -237,6 +243,15 @@
return this;
};
},
addFilterSetter: function(constructor, attr) {
var method = SET + Kinetic.Util._capitalize(attr);
constructor.prototype[method] = function(val) {
this._setAttr(attr, val);
this._filterUpToDate = false;
return this;
};
},
addPointSetter: function(constructor, attr) {
var that = this,
baseMethod = SET + Kinetic.Util._capitalize(attr);

View File

@@ -43,6 +43,7 @@
this.eventListeners = {};
this.attrs = {};
this._cache = {};
this._filterUpToDate = false;
this.setAttrs(config);
// event bindings for cache handling
@@ -112,18 +113,25 @@
* @example
* node.cache();
*/
cache: function(box) {
var x = box.x || 0,
cache: function(b) {
var box = b || {},
x = box.x || 0,
y = box.y || 0,
width = box.width || this.width(),
height = box.height || this.height(),
sceneCanvasCache = new Kinetic.SceneCanvas({
pixelRatio: 1,
width: box.width,
height: box.height
width: width,
height: height
}),
filterCanvasCache = new Kinetic.SceneCanvas({
pixelRatio: 1,
width: width,
height: height
}),
hitCanvasCache = new Kinetic.HitCanvas({
pixelRatio: 1,
width: box.width,
height: box.height
width: width,
height: height
}),
origTransEnabled = this.transformsEnabled(),
origX = this.x(),
@@ -142,6 +150,7 @@
this._cache.canvas = {
scene: sceneCanvasCache,
filter: filterCanvasCache,
hit: hitCanvasCache,
x: x,
y: y
@@ -149,6 +158,48 @@
return this;
},
_drawCachedSceneCanvas: function(context) {
var filters = this.filters(),
cachedCanvas = this._cache.canvas,
sceneCanvas = cachedCanvas.scene,
filterCanvas = cachedCanvas.filter,
filterContext = filterCanvas.getContext(),
len, imageData, n, filter;
context.save();
context._applyTransform(this);
if (filters) {
if (!this._filterUpToDate) {
try {
len = filters.length;
filterContext.clear();
// copy cached canvas onto filter context
filterContext.drawImage(sceneCanvas._canvas, 0, 0);
imageData = filterContext.getImageData(0, 0, filterCanvas.getWidth(), filterCanvas.getHeight());
// apply filters to filter context
for (n=0; n<len; n++) {
filter = filters[n];
filter.call(this, imageData);
filterContext.putImageData(imageData, 0, 0);
}
}
catch(e) {
Kinetic.Util.warn('Unable to apply filter. ' + e.message);
}
this._filterUpToDate = true;
}
context.drawImage(filterCanvas._canvas, 0, 0);
}
else {
context.drawImage(sceneCanvas._canvas, 0, 0);
}
context.restore();
},
/*
* the default isDraggable method returns false.
* if the DragAndDrop module is included, this is overwritten
@@ -1753,6 +1804,23 @@
* @returns {Boolean|String}
*/
Kinetic.Factory.addGetterSetter(Kinetic.Node, 'filters');
/**
* get/set filters
* @name filters
* @method
* @memberof Kinetic.Node.prototype
* @param {Array} filters array of filters
* @returns {Array}
* @example
* // set a single filter<br>
* node.filters([Kinetic.Filters.Blur]);<br><br>
*
* // get filters<br>
* var filters = node.filters();
*/
Kinetic.Factory.addGetterSetter(Kinetic.Node, 'visible', 'inherit');
/**

View File

@@ -236,19 +236,16 @@
var canvas = can || this.getLayer().getCanvas(),
context = canvas.getContext(),
cachedCanvas = this._cache.canvas,
cachedSceneCanvas = cachedCanvas && cachedCanvas.scene,
drawFunc = this.getDrawFunc(),
hasShadow = this.hasShadow(),
stage, bufferCanvas, bufferContext;
if(this.isVisible()) {
context.save();
if (cachedSceneCanvas) {
context._applyTransform(this);
context.drawImage(cachedSceneCanvas._canvas, 0, 0);
if (cachedCanvas) {
this._drawCachedSceneCanvas(context);
}
else if (drawFunc) {
context.save();
// if buffer canvas is needed
if (this._useBufferCanvas()) {
stage = this.getStage();
@@ -286,10 +283,9 @@
context._applyOpacity(this);
drawFunc.call(this, context);
}
}
context.restore();
}
context.restore();
}
return this;
@@ -302,18 +298,22 @@
cachedHitCanvas = cachedCanvas && cachedCanvas.hit;
if(this.shouldDrawHit()) {
context.save();
if (cachedHitCanvas) {
context.save();
context._applyTransform(this);
context.drawImage(cachedHitCanvas._canvas, 0, 0);
context.restore();
}
else if (drawFunc) {
context.save();
context._applyLineJoin(this);
context._applyTransform(this);
drawFunc.call(this, context);
context.restore();
}
context.restore();
}
return this;

View File

@@ -352,24 +352,19 @@
Blur(src, dst||src, opt );
}else{
Blur.call(this, src, dst||src, opt || {
filterRadius: this.getFilterRadius()
filterRadius: this.blurRadius()
});
}
};
Kinetic.Factory.addFilterGetterSetter(Kinetic.Image, 'filterRadius', 0);
Kinetic.Factory.addFilterGetterSetter(Kinetic.Node, 'blurRadius', 0);
/**
* get filter radius. Returns the radius for Gaussian blur filter.
* @name getFilterRadius
* @method
* @memberof Kinetic.Image.prototype
*/
/**
* get filter radius. Set the radius for Gaussian blur filter.
* @name setFilterRadius
* get/set blur radius
* @name blurRadius
* @method
* @memberof Kinetic.Image.prototype
* @param {Integer} radius
* @returns {Integer}
*/
})();

View File

@@ -18,7 +18,7 @@
}
};
Kinetic.Factory.addFilterGetterSetter(Kinetic.Image, 'filterBrightness', 0);
Kinetic.Factory.addFilterGetterSetter(Kinetic.Node, 'brightness', 0);
/**
* get filter brightness. The brightness is a number between -255 and 255.&nbsp; Positive values
* increase the brightness and negative values decrease the brightness, making the image darker

View File

@@ -70,9 +70,9 @@
};
Kinetic.Factory.addFilterGetterSetter(Kinetic.Image, 'filterHue', 0);
Kinetic.Factory.addFilterGetterSetter(Kinetic.Image, 'filterSaturation', 1);
Kinetic.Factory.addFilterGetterSetter(Kinetic.Image, 'filterValue', 1);
Kinetic.Factory.addFilterGetterSetter(Kinetic.Node, 'hue', 0);
Kinetic.Factory.addFilterGetterSetter(Kinetic.Node, 'saturation', 1);
Kinetic.Factory.addFilterGetterSetter(Kinetic.Node, 'value', 1);
Kinetic.Filters.HSV = function(src,dst,opt){
if( this === Kinetic.Filters ){
@@ -97,7 +97,7 @@
}
};
Kinetic.Factory.addFilterGetterSetter(Kinetic.Image, 'filterHueShiftDeg', 0);
Kinetic.Factory.addFilterGetterSetter(Kinetic.Node, 'hueShiftDeg', 0);
/**
* get filter hue. Returns the hue shift for the HSV filter.
@@ -184,7 +184,7 @@
}
};
Kinetic.Factory.addFilterGetterSetter(Kinetic.Image, 'filterColorizeColor', [255,0,0] );
Kinetic.Factory.addFilterGetterSetter(Kinetic.Node, 'colorizeColor', [255,0,0] );
/**
* Gets the colorizing color.
* @name getFilterColorizeColor

View File

@@ -68,7 +68,7 @@
return kernel;
};
Kinetic.Factory.addFilterGetterSetter(Kinetic.Image, 'filterAmount', 50);
Kinetic.Factory.addFilterGetterSetter(Kinetic.Node, 'convoltion', 50);
/**
* get the current filter amount
* @name getFilterAmount

View File

@@ -37,7 +37,7 @@
}
};
Kinetic.Factory.addFilterGetterSetter(Kinetic.Image, 'quantizationLevels', 4);
Kinetic.Factory.addFilterGetterSetter(Kinetic.Node, 'quantizationLevels', 4);
/**
* get quantization levels. Returns the number of unique levels for each color

View File

@@ -191,7 +191,7 @@
return idata;
};
Kinetic.Factory.addFilterGetterSetter(Kinetic.Image, 'filterThreshold', 0);
Kinetic.Factory.addFilterGetterSetter(Kinetic.Node, 'threshold', 0);
//threshold The RGB euclidian distance threshold (default : 10)

View File

@@ -40,7 +40,7 @@
}
};
Kinetic.Factory.addFilterGetterSetter(Kinetic.Image, 'noiseAmount', 32);
Kinetic.Factory.addFilterGetterSetter(Kinetic.Node, 'noise', 32);
Kinetic.Filters.Noise = function(src,dst,opt){
if( this === Kinetic.Filters ){

View File

@@ -86,16 +86,15 @@
};
Kinetic.Factory.addFilterGetterSetter(Kinetic.Image, 'pixelWidth', 8);
Kinetic.Factory.addFilterGetterSetter(Kinetic.Image, 'pixelHeight', 8);
Kinetic.Factory.addFilterGetterSetter(Kinetic.Node, 'pixelationSize', 8);
Kinetic.Filters.Pixelate = function(src,dst,opt){
if( this === Kinetic.Filters ){
Pixelate(src, dst||src, opt );
}else{
Pixelate.call(this, src, dst||src, opt || {
pixelWidth: this.getPixelWidth(),
pixelHeight: this.getPixelHeight()
pixelWidth: this.pixelationSize(),
pixelHeight: this.pixelationSize()
});
}
};

View File

@@ -1,198 +0,0 @@
(function () {
/**
* BlurX Filter. Blurs the image in the X direction (horizontally). It
* performs w*h pixel reads, and w*h pixel writes. It is faster than a
* Gaussian blur but does not look as nice. Use Kinetic.Filters.Blur for
* a Gaussian blur.
* @function
* @author ippo615
* @memberof Kinetic.Filters
* @param {ImageData} src, the source image data (what will be transformed)
* @param {ImageData} dst, the destination image data (where it will be saved)
* @param {Object} opt
* @param {Number} [opt.blurWidth] how many neighboring pixels to will affect the
* blurred pixel, default: 5
*/
var BlurX = function(src,dst,opt){
var srcPixels = src.data,
dstPixels = dst.data,
xSize = src.width,
ySize = src.height,
i, m, x, y, k, tmp, r=0,g=0,b=0,a=0;
// DONT USE: kSize = opt.blurWidth || 5;
// HINT: consider when (opt.blurWidth = 0)
var kSize = 5;
if( opt.hasOwnProperty('blurWidth') ){
kSize = Math.round( Math.abs(opt.blurWidth) )+1;
}
var kMid = Math.floor(kSize/2);
//console.info('Blur Width: '+kSize);
//console.info('Blur Middle: '+kMid);
var xEnd = xSize - kMid;
for (y = 0; y < ySize; y += 1) {
r=0;g=0;b=0;a=0;
for (x=-kMid; x<kMid; x+=1 ){
// Add the new
//if( y === 0 ){ console.info('Loading pixel at: '+((x+xSize)%xSize) ); }
i = (y * xSize + (x+xSize)%xSize ) * 4;
r += srcPixels[i+0];
g += srcPixels[i+1];
b += srcPixels[i+2];
a += srcPixels[i+3];
}
for (x = 0; x < xSize; x += 1) {
//if( y === 0 ){ console.info('Added pixel at: '+(x+kMid)); }
//if( y === 0 ){ console.info('Recorded pixel at: '+x); }
//if( y === 0 ){ console.info('Removed pixel at: '+((x-kMid+xSize)%xSize) ); }
// Add the new
i = (y * xSize + (x+kMid)%xSize ) * 4;
r += srcPixels[i+0];
g += srcPixels[i+1];
b += srcPixels[i+2];
a += srcPixels[i+3];
// Store the result
i = (y * xSize + x) * 4;
dstPixels[i+0] = r/kSize;
dstPixels[i+1] = g/kSize;
dstPixels[i+2] = b/kSize;
dstPixels[i+3] = a/kSize;
// Subtract the old
i = (y * xSize + (x-kMid+xSize)%xSize ) * 4;
r -= srcPixels[i+0];
g -= srcPixels[i+1];
b -= srcPixels[i+2];
a -= srcPixels[i+3];
}
}
};
/**
* BlurY Filter. Blurs the image in the Y direction (vertically). It
* performs w*h pixel reads, and w*h pixel writes. It is faster than a
* Gaussian blur but does not look as nice. Use Kinetic.Filters.Blur for
* a Gaussian blur.
* @function
* @author ippo615
* @memberof Kinetic.Filters
* @param {ImageData} src, the source image data (what will be transformed)
* @param {ImageData} dst, the destination image data (where it will be saved)
* @param {Object} opt
* @param {Number} [opt.blurHeight] how many neighboring pixels to will affect the
* blurred pixel, default: 5
*/
var BlurY = function(src,dst,opt){
var srcPixels = src.data,
dstPixels = dst.data,
xSize = src.width,
ySize = src.height,
i, m, x, y, k, tmp, r=0,g=0,b=0,a=0;
var kSize = 5;
if( opt.hasOwnProperty('blurHeight') ){
kSize = Math.round( Math.abs(opt.blurHeight) )+1;
}
var kMid = Math.floor(kSize/2);
var yEnd = ySize - kMid;
for (x = 0; x < xSize; x += 1) {
r=0;g=0;b=0;a=0;
for (y=-kMid; y<kMid; y+=1 ){
// Add the new
i = ((y+ySize)%ySize * xSize + x ) * 4;
r += srcPixels[i+0];
g += srcPixels[i+1];
b += srcPixels[i+2];
a += srcPixels[i+3];
}
for (y = 0; y < ySize; y += 1) {
// Add the new
i = ((y+kMid+ySize)%ySize * xSize + x ) * 4;
r += srcPixels[i+0];
g += srcPixels[i+1];
b += srcPixels[i+2];
a += srcPixels[i+3];
// Store the result
i = (y * xSize + x) * 4;
dstPixels[i+0] = r/kSize;
dstPixels[i+1] = g/kSize;
dstPixels[i+2] = b/kSize;
dstPixels[i+3] = a/kSize;
// Subtract the old
i = ((y-kMid+ySize)%ySize * xSize + x ) * 4;
r -= srcPixels[i+0];
g -= srcPixels[i+1];
b -= srcPixels[i+2];
a -= srcPixels[i+3];
}
}
};
Kinetic.Factory.addFilterGetterSetter(Kinetic.Image, 'blurWidth', 5);
Kinetic.Factory.addFilterGetterSetter(Kinetic.Image, 'blurHeight', 5);
Kinetic.Filters.BlurX = function(src,dst,opt){
if( this === Kinetic.Filters ){
BlurX(src, dst||src, opt );
}else{
BlurX.call(this, src, dst||src, opt || {
blurWidth: this.getBlurWidth()
});
}
};
Kinetic.Filters.BlurY = function(src,dst,opt){
if( this === Kinetic.Filters ){
BlurY(src, dst||src, opt );
}else{
BlurY.call(this, src, dst||src, opt || {
blurHeight: this.getBlurHeight()
});
}
};
/**
* get filter blur width. Returns the width of a blurred pixel. Must be
* an integer greater than 0.
* @name getBlurWidth
* @method
* @memberof Kinetic.Image.prototype
*/
/**
* set filter blur width.
* @name setBlurWidth
* @method
* @memberof Kinetic.Image.prototype
*/
/**
* get filter blur height. Returns the height of a blurred pixel. Must be
* an integer greater than 0.
* @name getBlurHeight
* @method
* @memberof Kinetic.Image.prototype
*/
/**
* set filter blur height.
* @name setBlurHeight
* @method
* @memberof Kinetic.Image.prototype
*/
})();

View File

@@ -33,7 +33,7 @@
}
};
Kinetic.Factory.addFilterGetterSetter(Kinetic.Image, 'thresholdLevel', 128);
Kinetic.Factory.addFilterGetterSetter(Kinetic.Node, 'threshold', 128);
Kinetic.Filters.Threshold = function(src,dst,opt){
if( this === Kinetic.Filters ){

View File

@@ -45,36 +45,18 @@
_drawFunc: function(context) {
var width = this.getWidth(),
height = this.getHeight(),
crop, cropWidth, cropHeight,
params,
image;
image = this.getImage(),
crop, cropWidth, cropHeight, params;
//TODO: this logic needs to hook int othe new caching system
// if a filter is set, and the filter needs to be updated, reapply
if (this.getFilter() && this._applyFilter) {
this.applyFilter();
this._applyFilter = false;
}
// NOTE: this.filterCanvas may be set by the above code block
// In that case, cropping is already applied.
if (this.filterCanvas) {
image = this.filterCanvas._canvas;
params = [image, 0, 0, width, height];
}
else {
image = this.getImage();
if (image) {
crop = this.getCrop(),
cropWidth = crop.width;
cropHeight = crop.height;
if (cropWidth && cropHeight) {
params = [image, crop.x, crop.y, cropWidth, cropHeight, 0, 0, width, height];
} else {
params = [image, 0, 0, width, height];
}
if (image) {
crop = this.getCrop(),
cropWidth = crop.width;
cropHeight = crop.height;
if (cropWidth && cropHeight) {
params = [image, crop.x, crop.y, cropWidth, cropHeight, 0, 0, width, height];
}
else {
params = [image, 0, 0, width, height];
}
}
@@ -106,59 +88,6 @@
context.fillStrokeShape(this);
}
},
applyFilter: function() {
var image = this.getImage(),
width = this.getWidth(),
height = this.getHeight(),
filter = this.getFilter(),
crop = this.getCrop(),
filterCanvas, context, imageData;
// Determine the region we are cropping
crop.x = crop.x;
crop.y = crop.y;
crop.width = crop.width || width - crop.x;
crop.height = crop.height || height - crop.y;
// Make a filterCanvas the same size as the cropped image
if (this.filterCanvas &&
this.filterCanvas.getWidth() === crop.width &&
this.filterCanvas.getHeight() === crop.height) {
filterCanvas = this.filterCanvas;
filterCanvas.getContext().clear();
}
else {
filterCanvas = this.filterCanvas = new Kinetic.SceneCanvas({
width: crop.width,
height: crop.height,
pixelRatio: 1
});
}
context = filterCanvas.getContext();
try {
// Crop the image onto the filterCanvas then apply
// the filter to the filterCanvas
context.drawImage(image, crop.x, crop.y, crop.width, crop.height, 0,0,crop.width, crop.height);
imageData = context.getImageData(0, 0, crop.width, crop.height);
filter.call(this, imageData);
context.putImageData(imageData, 0, 0);
}
catch(e) {
this.clearFilter();
Kinetic.Util.warn('Unable to apply filter. ' + e.message);
}
},
/**
* clear filter
* @method
* @memberof Kinetic.Image.prototype
*/
clearFilter: function() {
this.filterCanvas = null;
this._applyFilter = false;
},
/**
* create image hit region which enables more accurate hit detection mapping of the image
* by avoiding event detections for transparent pixels
@@ -227,31 +156,10 @@
getHeight: function() {
var image = this.getImage();
return this.attrs.height || (image ? image.height : 0);
},
destroy: function(){
Kinetic.Shape.prototype.destroy.call(this);
delete this.filterCanvas;
delete this.attrs;
return this;
}
};
Kinetic.Util.extend(Kinetic.Image, Kinetic.Shape);
Kinetic.Factory.addFilterGetterSetter = function(constructor, attr, def) {
this.addGetter(constructor, attr, def);
this.addFilterSetter(constructor, attr);
};
Kinetic.Factory.addFilterSetter = function(constructor, attr) {
var method = SET + Kinetic.Util._capitalize(attr);
constructor.prototype[method] = function(val) {
this._setAttr(attr, val);
this._applyFilter = true;
};
};
// add getters setters
Kinetic.Factory.addGetterSetter(Kinetic.Image, 'image');
@@ -363,22 +271,4 @@
* @memberof Kinetic.Image.prototype
* @returns {Number}
*/
Kinetic.Factory.addFilterGetterSetter(Kinetic.Image, 'filter');
/**
* set filter
* @name setFilter
* @method
* @memberof Kinetic.Image.prototype
* @param {Function} filter
*/
/**
* get filter
* @name getFilter
* @method
* @memberof Kinetic.Image.prototype
* @returns {Function}
*/
})();