From 5c7f7accf2bf9d1231464a27ff3712bbb4a8460d Mon Sep 17 00:00:00 2001
From: David Bau
Date: Fri, 8 Aug 2014 01:51:23 -0400
Subject: [PATCH 1/4] Add .clip() method for sprites.
Also makes sprites not-perfectly-transparent by default.
Also refactors and fixes some small issues with the new hull-computing
code (thanks for the code alex), and adds a test.
---
jquery-turtle.js | 130 ++++++++++++++++++++++++++++++++++-------------
test/clip.html | 45 ++++++++++++++++
2 files changed, 141 insertions(+), 34 deletions(-)
create mode 100644 test/clip.html
diff --git a/jquery-turtle.js b/jquery-turtle.js
index 3613692..38912ab 100644
--- a/jquery-turtle.js
+++ b/jquery-turtle.js
@@ -1366,6 +1366,7 @@ function convexHull(points) {
function parseTurtleHull(text) {
if (!text) return null;
+ if ($.isArray(text)) return text;
var nums = $.map(text.trim().split(/\s+/), parseFloat), points = [], j = 0;
while (j + 1 < nums.length) {
points.push({ pageX: nums[j], pageY: nums[j + 1] });
@@ -2656,18 +2657,13 @@ function applyLoadedImage(loaded, elem, css) {
sel.css(css);
}
var newOrigin = readTransformOrigin(elem);
- if(loaded){
- var hull = cutTransparent(loaded);
- var turtleHull = '';
- for(var i = 0; i < hull.length; i++){
- if(i > 0) turtleHull += ' ';
- // Scale coordinates to the size of elem
- hull[i].pageX = Math.floor(hull[i].pageX * sel.css('height').slice(0, -2) / loaded.height);
- hull[i].pageY = Math.floor(hull[i].pageY * sel.css('width').slice(0, -2) / loaded.width);
- turtleHull += (hull[i].pageX - newOrigin[0]) + ' ' + (hull[i].pageY - newOrigin[1]);
- }
- sel.css('turtleHull', turtleHull);
- console.log(turtleHull);
+ if (loaded && !css.turtleHull) {
+ var hull = transparentHull(loaded);
+ scalePolygon(hull,
+ parseFloat(sel.css('width')) / loaded.width,
+ parseFloat(sel.css('height')) / loaded.height,
+ -newOrigin[0], -newOrigin[1]);
+ sel.css('turtleHull', hull);
}
// If there was a change, then translate the element to keep the origin
// in the same location on the screen.
@@ -5115,7 +5111,9 @@ function gatherelts(args) {
}
// Gather elements passed as arguments.
for (j = 0; j < argcount; ++j) {
- if (args[j].constructor === $) {
+ if (!args[j]) {
+ continue; // Skip null args.
+ } else if (args[j].constructor === $) {
elts.push.apply(elts, args[j].toArray()); // Unpack jQuery.
} else if ($.isArray(args[j])) {
elts.push.apply(elts, args[j]); // Accept an array.
@@ -6154,6 +6152,29 @@ var turtlefn = {
function pf() {
return this.pen('path', continuationArg(arguments, 0));
},
+ clip: wrapcommand('clip', 1,
+ ["Clips tranparent bits out of the image of the sprite, " +
+ "and sets the hit region."],
+ function clip(cc, threshold) {
+ if (threshold == null) {
+ threshold = 0.125;
+ }
+ return this.plan(function(j, elem) {
+ cc.appear(j);
+ if (elem.tagName == 'CANVAS') {
+ var hull = transparentHull(elem, threshold),
+ sel = $(elem),
+ origin = readTransformOrigin(elem);
+ eraseOutsideHull(elem, hull);
+ scalePolygon(hull,
+ parseFloat(sel.css('width')) / elem.width,
+ parseFloat(sel.css('height')) / elem.height,
+ -origin[0], -origin[1]);
+ sel.css('turtleHull', hull);
+ }
+ cc.resolve(j);
+ });
+ }),
play: wrapcommand('play', 1,
["play(notes) Play notes. Notes are specified in " +
"" +
@@ -7796,7 +7817,10 @@ function createRectangleShape(width, height, subpixels) {
return (function(color) {
var c = getOffscreenCanvas(width, height);
var ctx = c.getContext('2d');
- if (color && color != 'transparent') {
+ if (!color) {
+ color = "rgba(128,128,128,0.125)";
+ }
+ if (color != 'transparent') {
ctx.fillStyle = color;
ctx.fillRect(0, 0, width, height);
}
@@ -9413,39 +9437,77 @@ function tryinitpanel() {
eval("scope('jquery-turtle', " + seejs + ", this)");
-function cutTransparent(image){
- // We transform image into a 2D array of 0s and 1s, with 0 and 1 representing
- // transparent and non-transparent pixels in image, respectively.
+function transparentHull(image, threshold) {
var c = document.createElement('canvas');
+ if (!threshold) threshold = 0;
c.width = image.width;
c.height = image.height;
ctx = c.getContext('2d');
ctx.drawImage(image, 0, 0);
- data = ctx.getImageData(0, 0, c.width, c.height).data;
- alphaData = [];
- for(var i = 0; i < data.length; i += 4){
- var row = Math.floor(i / 4 / c.width);
- var col = (i / 4) - row * c.width;
- if(!alphaData[row]) alphaData[row] = [];
- alphaData[row][col] = (data[i + 3] == 0 ? 0 : 1);
- }
+ return transparentCanvasHull(c, threshold);
+}
+
+function transparentCanvasHull(canvas, threshold) {
+ var ctx = canvas.getContext('2d');
+ data = ctx.getImageData(0, 0, canvas.width, canvas.height).data;
var hull = [];
- for(var i = 0; i < c.height; i++){
+ var intthresh = 256 * threshold;
+ var first, last, prevfirst = Infinity, prevlast = -1;
+ for (var row = 0; row < canvas.height; ++row) {
// We only take the first/last hull in a row to reduce the number of
// possible points from O(n^2) to O(n).
- var first = -1, last = -1;
- for(var j = 0; j < c.width; j++){
- if(alphaData[i][j] == 1){
- if(first < 0) first = j;
- last = j;
+ first = Infinity;
+ last = -1;
+ for (var col = 0; col < canvas.width; ++col) {
+ if (data[row * 4 * canvas.width + col * 4 + 3] > intthresh) {
+ if (last < 0) first = col;
+ last = col;
}
}
- if(first >= 0){
- hull.push({ pageX: first, pageY: i});
- hull.push({ pageX: last, pageY: i});
+ if (last >= 0 || prevlast >= 0) {
+ hull.push({ pageX: Math.min(first, prevfirst), pageY: row});
+ hull.push({ pageX: Math.max(last, prevlast), pageY: row});
}
+ prevfirst = first;
+ prevlast = last;
+ }
+ if (prevlast >= 0) {
+ hull.push({ pageX: prevfirst, pageY: canvas.height});
+ hull.push({ pageX: prevlast, pageY: canvas.height});
}
return convexHull(hull);
}
+function eraseOutsideHull(canvas, hull) {
+ var ctx = canvas.getContext('2d'),
+ w = canvas.width,
+ h = canvas.height
+ j = 0;
+ ctx.save();
+ // Erase everything outside clipping region.
+ ctx.beginPath();
+ ctx.moveTo(0, 0);
+ ctx.lineTo(w, 0);
+ ctx.lineTo(w, h);
+ ctx.lineTo(0, h);
+ ctx.closePath();
+ if (hull.length) {
+ ctx.moveTo(hull[0].pageX, hull[0].pageY);
+ for (; j < hull.length; j += 1) {
+ ctx.lineTo(hull[j].pageX, hull[j].pageY);
+ }
+ }
+ ctx.closePath();
+ ctx.clip();
+ ctx.clearRect(0, 0, w, h);
+ ctx.restore();
+}
+
+function scalePolygon(poly, sx, sy, tx, ty) {
+ for (var i = 0; i < poly.length; i++){
+ poly[i].pageX = poly[i].pageX * sx + tx;
+ poly[i].pageY = poly[i].pageY * sy + ty;
+ }
+}
+
})(jQuery);
diff --git a/test/clip.html b/test/clip.html
new file mode 100644
index 0000000..c5b2271
--- /dev/null
+++ b/test/clip.html
@@ -0,0 +1,45 @@
+
+
+
+
+
+
+
+
+
+
From 2b818acd68dadf3d025ddeda8ca934ad6ece723a Mon Sep 17 00:00:00 2001
From: David Bau
Date: Fri, 8 Aug 2014 19:41:11 -0400
Subject: [PATCH 2/4] Deal with cross-origin restrictions on bit access, and
remove openicon urls.
---
jquery-turtle.js | 57 ++++++++++++------------------------------------
1 file changed, 14 insertions(+), 43 deletions(-)
diff --git a/jquery-turtle.js b/jquery-turtle.js
index 38912ab..3841875 100644
--- a/jquery-turtle.js
+++ b/jquery-turtle.js
@@ -2658,12 +2658,16 @@ function applyLoadedImage(loaded, elem, css) {
}
var newOrigin = readTransformOrigin(elem);
if (loaded && !css.turtleHull) {
- var hull = transparentHull(loaded);
- scalePolygon(hull,
- parseFloat(sel.css('width')) / loaded.width,
- parseFloat(sel.css('height')) / loaded.height,
- -newOrigin[0], -newOrigin[1]);
- sel.css('turtleHull', hull);
+ try {
+ var hull = transparentHull(loaded);
+ scalePolygon(hull,
+ parseFloat(sel.css('width')) / loaded.width,
+ parseFloat(sel.css('height')) / loaded.height,
+ -newOrigin[0], -newOrigin[1]);
+ sel.css('turtleHull', hull);
+ } catch (e) {
+ // Do not do this if the image can't be loaded.
+ }
}
// If there was a change, then translate the element to keep the origin
// in the same location on the screen.
@@ -7907,45 +7911,12 @@ function nameToImg(name, defaultshape) {
if (shape) {
return shape(color);
}
- // Parse openicon patterns.
- // TODO: add more built-in shapes and remove this.
- var openicon =
- /^openicon:\/?\/?([^@\/][^@]*)(?:@(?:(\d+):)?(\d+))?$/.exec(name);
- if (openicon) {
- var openiconName = openicon[1],
- sourceSize = parseInt(openicon[3]),
- targetSize = parseInt(openicon[2]),
- dotloc = openiconName.lastIndexOf('.'),
- openiconType = 'png';
- if (openiconName.indexOf('/') == -1) {
- openiconName = 'others/' + openiconName;
- }
- if (dotloc > 0 && dotloc <= openiconName.length - 4 &&
- dotloc >= openiconName.length - 5) {
- openiconType = openiconName.substring(dotloc + 1);
- openiconName = openiconName.substring(0, dotloc);
- }
- if (!targetSize) {
- targetSize = sourceSize || 24;
- }
- if (!sourceSize) {
- sourceSize = 48;
- }
- return {
- url: 'http://openiconlibrary.sourceforge.net/gallery2/' +
- 'open_icon_library-full/icons/' + openiconType + '/' +
- sourceSize + 'x' + sourceSize + '/' +
- openiconName + '.' + openiconType,
- css: {
- width: targetSize,
- height: targetSize,
- transformOrigin: '50% 50%',
- opacity: 1
- }
- }
- }
// Parse URLs.
if (/^(?:(?:https?|data):)?\//i.exec(name)) {
+ if (/^https?:/i.test(name) && !/^https?:\/\/[^/]*pencilcode.net/.test(name)
+ && /(?:^|\.)pencilcode\.net$/.test(window.location.hostname)) {
+ name = '/proxy/' + name;
+ }
return {
url: name,
css: {
From f8b82cd8e59c78df53e70fec8bdcd07dec09306a Mon Sep 17 00:00:00 2001
From: David Bau
Date: Fri, 8 Aug 2014 19:57:18 -0400
Subject: [PATCH 3/4] Slight optimization.
---
jquery-turtle.js | 14 ++++++++------
1 file changed, 8 insertions(+), 6 deletions(-)
diff --git a/jquery-turtle.js b/jquery-turtle.js
index 3841875..81dc0f5 100644
--- a/jquery-turtle.js
+++ b/jquery-turtle.js
@@ -427,13 +427,15 @@ if (!transform || !hasGetBoundingClientRect()) {
// and that first value will be interpreted as defaultProp:value1.
// Some rudimentary quoting can be done, e.g., value:"prop", etc.
function parseOptionString(str, defaultProp) {
- if (str == null) {
- return {};
- }
- if ($.isPlainObject(str)) {
- return str;
+ if (typeof(str) != 'string') {
+ if (str == null) {
+ return {};
+ }
+ if ($.isPlainObject(str)) {
+ return str;
+ }
+ str = '' + str;
}
- str = '' + str;
// Each token is an identifier, a quoted or parenthesized string,
// a run of whitespace, or any other non-matching character.
var token = str.match(/[-a-zA-Z_][-\w]*|"[^"]*"|'[^']'|\([^()]*\)|\s+|./g),
From 8273770e066b733dcdf1ea69349fa0df3461a067 Mon Sep 17 00:00:00 2001
From: David Bau
Date: Mon, 11 Aug 2014 12:10:54 -0400
Subject: [PATCH 4/4] Fix clip off-by-one error.
---
jquery-turtle.js | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/jquery-turtle.js b/jquery-turtle.js
index 81dc0f5..2c58de9 100644
--- a/jquery-turtle.js
+++ b/jquery-turtle.js
@@ -9439,14 +9439,14 @@ function transparentCanvasHull(canvas, threshold) {
}
if (last >= 0 || prevlast >= 0) {
hull.push({ pageX: Math.min(first, prevfirst), pageY: row});
- hull.push({ pageX: Math.max(last, prevlast), pageY: row});
+ hull.push({ pageX: Math.max(last, prevlast) + 1, pageY: row});
}
prevfirst = first;
prevlast = last;
}
if (prevlast >= 0) {
hull.push({ pageX: prevfirst, pageY: canvas.height});
- hull.push({ pageX: prevlast, pageY: canvas.height});
+ hull.push({ pageX: prevlast + 1, pageY: canvas.height});
}
return convexHull(hull);
}