Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Add the Crop filter

  • Loading branch information...
commit d81219f7f1405422ab950592c448cd87e324c68a 1 parent c74171c
Nicolas COSME authored
View
2  Thorfile
@@ -5,7 +5,7 @@ class Build < Thor
# This is the list of files to concatenate. The first file will appear at the top of the final file. All files are relative to the lib directory.
FILES = [
"src/Global.js", "src/util/Type.js", "src/Canvas.js", "src/util/Tween.js", "src/util/Transform.js", "src/util/Collection.js",
- "src/filters/Grayscale.js", "src/filters/Brighten.js", "src/filters/Invert.js", "src/filters/Blur.js",
+ "src/filters/Grayscale.js", "src/filters/Brighten.js", "src/filters/Invert.js", "src/filters/Blur.js", "src/filters/Crop.js",
"src/Node.js", "src/Animation.js", "src/DragAndDrop.js", "src/Transition.js", "src/Container.js", "src/Shape.js", "src/Stage.js", "src/Layer.js", "src/Group.js",
"src/shapes/Rect.js", "src/shapes/Circle.js", "src/shapes/Wedge.js", "src/shapes/Ellipse.js", "src/shapes/Image.js", "src/shapes/Polygon.js", "src/shapes/Text.js", "src/shapes/Line.js", "src/shapes/Spline.js", "src/shapes/Blob.js", "src/shapes/Sprite.js",
"src/plugins/Path.js", "src/plugins/TextPath.js", "src/plugins/RegularPolygon.js", "src/plugins/Star.js", "src/plugins/Label.js"
View
200 src/filters/Crop.js
@@ -0,0 +1,200 @@
+(function(Kinetic) {
+
+ function pixelAt(idata, x, y) {
+ var idx = (y * idata.width + x) * 4;
+ var d = [];
+ d.push(idata.data[idx++], idata.data[idx++], idata.data[idx++], idata.data[idx++]);
+ return d;
+ };
+
+ function rgbDistance(p1, p2) {
+ return Math.sqrt(Math.pow(p1[0] - p2[0], 2) + Math.pow(p1[1] - p2[1], 2) + Math.pow(p1[2] - p2[2], 2));
+ };
+
+ function rgbMean(pTab) {
+ var m = [0, 0, 0];
+
+ for (var i = 0; i < pTab.length; i++) {
+ m[0] += pTab[i][0];
+ m[1] += pTab[i][1];
+ m[2] += pTab[i][2];
+ }
+
+ m[0] /= pTab.length;
+ m[1] /= pTab.length;
+ m[2] /= pTab.length;
+
+ return m;
+ };
+
+ function backgroundMask(idata, config) {
+ var rgbv_no = pixelAt(idata, 0, 0);
+ var rgbv_ne = pixelAt(idata, idata.width - 1, 0);
+ var rgbv_so = pixelAt(idata, 0, idata.height - 1);
+ var rgbv_se = pixelAt(idata, idata.width - 1, idata.height - 1);
+
+
+ var thres = (config && config.threshold) ? config.threshold : 10;
+ if (rgbDistance(rgbv_no, rgbv_ne) < thres && rgbDistance(rgbv_ne, rgbv_se) < thres && rgbDistance(rgbv_se, rgbv_so) < thres && rgbDistance(rgbv_so, rgbv_no) < thres) {
+
+ // Mean color
+ var mean = rgbMean([rgbv_ne, rgbv_no, rgbv_se, rgbv_so]);
+
+ // Mask based on color distance
+ var mask = [];
+ for (var i = 0; i < idata.width * idata.height; i++) {
+ var d = rgbDistance(mean, [idata.data[i * 4], idata.data[i * 4 + 1], idata.data[i * 4 + 2]]);
+ mask[i] = (d < thres) ? 0 : 255;
+ }
+
+ return mask;
+ }
+ };
+
+ function applyMask(idata, mask) {
+ for (var i = 0; i < idata.width * idata.height; i++) {
+ idata.data[4 * i + 3] = mask[i];
+ }
+ };
+
+ function erodeMask(mask, sw, sh) {
+
+ var weights = [1, 1, 1, 1, 0, 1, 1, 1, 1];
+ var side = Math.round(Math.sqrt(weights.length));
+ var halfSide = Math.floor(side / 2);
+
+ var maskResult = [];
+ for (var y = 0; y < sh; y++) {
+ for (var x = 0; x < sw; x++) {
+
+ var so = y * sw + x;
+ var a = 0;
+ for (var cy = 0; cy < side; cy++) {
+ for (var cx = 0; cx < side; cx++) {
+ var scy = y + cy - halfSide;
+ var scx = x + cx - halfSide;
+
+ if (scy >= 0 && scy < sh && scx >= 0 && scx < sw) {
+
+ var srcOff = scy * sw + scx;
+ var wt = weights[cy * side + cx];
+
+ a += mask[srcOff] * wt;
+ }
+ }
+ }
+
+ maskResult[so] = (a === 255 * 8) ? 255 : 0;
+ }
+ }
+
+ return maskResult;
+ };
+
+ function dilateMask(mask, sw, sh) {
+
+ var weights = [1, 1, 1, 1, 1, 1, 1, 1, 1];
+ var side = Math.round(Math.sqrt(weights.length));
+ var halfSide = Math.floor(side / 2);
+
+ var maskResult = [];
+ for (var y = 0; y < sh; y++) {
+ for (var x = 0; x < sw; x++) {
+
+ var so = y * sw + x;
+ var a = 0;
+ for (var cy = 0; cy < side; cy++) {
+ for (var cx = 0; cx < side; cx++) {
+ var scy = y + cy - halfSide;
+ var scx = x + cx - halfSide;
+
+ if (scy >= 0 && scy < sh && scx >= 0 && scx < sw) {
+
+ var srcOff = scy * sw + scx;
+ var wt = weights[cy * side + cx];
+
+ a += mask[srcOff] * wt;
+ }
+ }
+ }
+
+ maskResult[so] = (a >= 255 * 4) ? 255 : 0;
+ }
+ }
+
+ return maskResult;
+ };
+
+ function smoothEdgeMask(mask, sw, sh) {
+
+ var weights = [1 / 9, 1 / 9, 1 / 9, 1 / 9, 1 / 9, 1 / 9, 1 / 9, 1 / 9, 1 / 9];
+ var side = Math.round(Math.sqrt(weights.length));
+ var halfSide = Math.floor(side / 2);
+
+ var maskResult = [];
+ for (var y = 0; y < sh; y++) {
+ for (var x = 0; x < sw; x++) {
+
+ var so = y * sw + x;
+ var a = 0;
+ for (var cy = 0; cy < side; cy++) {
+ for (var cx = 0; cx < side; cx++) {
+ var scy = y + cy - halfSide;
+ var scx = x + cx - halfSide;
+
+ if (scy >= 0 && scy < sh && scx >= 0 && scx < sw) {
+
+ var srcOff = scy * sw + scx;
+ var wt = weights[cy * side + cx];
+
+ a += mask[srcOff] * wt;
+ }
+ }
+ }
+
+ maskResult[so] = a;
+ }
+ }
+
+ return maskResult;
+ }
+
+ Kinetic = Kinetic || {};
+ Kinetic.Filters = Kinetic.Filters || {};
+
+ /**
+ * Crop Filter
+ *
+ * Only crop unicolor background images for instance
+ *
+ * @function
+ * @memberOf Kinetic.Filters
+ * @param {Object} imageData
+ * @param {Object} config
+ * @param {Integer} config.threshold The RGB euclidian distance threshold (default : 10)
+ */
+ Kinetic.Filters.Crop = function(idata, config) {
+ // Detect pixels close to the background color
+ var mask = backgroundMask(idata, config);
+ if (mask) {
+ // Erode
+ mask = erodeMask(mask, idata.width, idata.height);
+
+ // Dilate
+ mask = dilateMask(mask, idata.width, idata.height);
+
+ // Gradient
+ mask = smoothEdgeMask(mask, idata.width, idata.height);
+
+ // Apply mask
+ applyMask(idata, mask);
+
+ // todo : Update hit region function according to mask
+ }
+
+ return idata;
+ };
+
+ window['Kinetic'] = Kinetic;
+
+})(Kinetic);
View
BIN  tests/assets/bamoon.jpg
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
View
3  tests/assets/unitDataUrls.js
2 additions, 1 deletion not shown
View
39 tests/js/unit/shapes/imageTests.js
@@ -407,5 +407,44 @@ Test.Modules.IMAGE = {
imageObj.src = '../assets/lion.png';
showHit(layer);
+ },
+ 'crop unicolor background filter': function(containerId) {
+ var imageObj = new Image();
+ imageObj.onload = function() {
+ var stage = new Kinetic.Stage({
+ container: containerId,
+ width: 600,
+ height: 200
+ });
+ var layer = new Kinetic.Layer({
+ throttle: 999
+ });
+ var bamoon = new Kinetic.Image({
+ x: 0,
+ y: 0,
+ image: imageObj,
+ draggable: true
+ }),
+ filtered = new Kinetic.Image({
+ x: 300,
+ y: 0,
+ image: imageObj,
+ draggable: true
+ });
+
+ layer.add(bamoon);
+ layer.add(filtered);
+ stage.add(layer);
+
+ filtered.applyFilter(Kinetic.Filters.Crop, {
+ threshold: 10
+ }, function() {
+ layer.draw();
+ var dataUrl = layer.toDataURL();
+ //console.log(dataUrl);
+ testDataUrl(dataUrl, 'crop filter', 'problem with Crop filter.');
+ });
+ };
+ imageObj.src = '../assets/bamoon.jpg';
}
};
Please sign in to comment.
Something went wrong with that request. Please try again.