Permalink
Browse files

Merge pull request #365 from nicomlas/master

Crop filter
  • Loading branch information...
2 parents 348d1b5 + d81219f commit 7888ebe24dd52b237d4081cd5ade2f15b4d2007b @ericdrowell committed Apr 4, 2013
Showing with 242 additions and 2 deletions.
  1. +1 −1 Thorfile
  2. +200 −0 src/filters/Crop.js
  3. BIN tests/assets/bamoon.jpg
  4. +2 −1 tests/assets/unitDataUrls.js
  5. +39 −0 tests/js/unit/shapes/imageTests.js
View
@@ -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
@@ -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
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.

Large diffs are not rendered by default.

Oops, something went wrong.
@@ -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';
}
};

0 comments on commit 7888ebe

Please sign in to comment.