summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorFreeyorp <TheFreeYorp@NOSPAM.G.m.a.i.l.replace>2013-06-26 02:58:35 +1200
committerFreeyorp <TheFreeYorp@NOSPAM.G.m.a.i.l.replace>2013-06-26 02:58:35 +1200
commitda89573679309ad3c8ed11b93a806ea6384ba6fb (patch)
tree8b68ef02da4b8b1742e9c7ff49e1698f96e9edde
parent2cf8ba330dc60fa4c269c4fdb16716a1d9ad1e90 (diff)
downloadmanaportal-da89573679309ad3c8ed11b93a806ea6384ba6fb.tar.gz
manaportal-da89573679309ad3c8ed11b93a806ea6384ba6fb.tar.bz2
manaportal-da89573679309ad3c8ed11b93a806ea6384ba6fb.tar.xz
manaportal-da89573679309ad3c8ed11b93a806ea6384ba6fb.zip
Implement dyeImage
Split loadImage into a common resource.js Add a compatability check for both canvas-node and browser functionality Note that the generated dyed image is off-by-one to the tested TMWW image. The algorithm needs verification and possibly correction. Either way, it's close enough by the eye.
-rw-r--r--public/js/mp/dye.js39
-rw-r--r--public/js/mp/resource.js36
-rw-r--r--public/playground.html32
-rw-r--r--test/load.js9
-rw-r--r--test/mp/dye.js2
-rw-r--r--test/mp/future.js64
6 files changed, 153 insertions, 29 deletions
diff --git a/public/js/mp/dye.js b/public/js/mp/dye.js
index 530b6c4..7cf8da5 100644
--- a/public/js/mp/dye.js
+++ b/public/js/mp/dye.js
@@ -28,11 +28,50 @@ var mp = function(mp) {
return { channel: channel[idx], intensity: max };
}
+ /*
+ * Return a dye specification from a dye string.
+ */
function parseDyeString(dyeString) {
/* TODO */
}
+ /*
+ * Dye the internal image data based on the specification provided by dyeData.
+ * The specification can be generated from a dyeString by parseDyeString.
+ * The array passed in will be modified.
+ */
function dyeImage(imageData, dyeData) {
+ for (var p = 0; p < imageData.length; p += 4) {
+ var pixel = [imageData[p], imageData[p + 1], imageData[p + 2]];
+ var alpha = imageData[p + 3];
+ if (!alpha) {
+ continue;
+ }
+
+ var channel = getChannel(pixel);
+ var channelId = channel.channel;
+
+ if (!channelId || !(channelId in dyeData) || !dyeData[channelId].length) {
+ continue;
+ }
+
+ var intensity = channel.intensity;
+ var val = intensity * dyeData[channelId].length
+ var i = Math.floor(val / 255);
+ var t = val - i * 255;
+ if (!t) {
+ --i;
+ imageData[p ] = dyeData[channelId][i][0];
+ imageData[p + 1] = dyeData[channelId][i][1];
+ imageData[p + 2] = dyeData[channelId][i][2];
+ continue;
+ }
+
+ imageData[p ] = ((255 - t) * (i && dyeData[channelId][i - 1][0]) + t * dyeData[channelId][i][0]) / 255;
+ imageData[p + 1] = ((255 - t) * (i && dyeData[channelId][i - 1][1]) + t * dyeData[channelId][i][1]) / 255;
+ imageData[p + 2] = ((255 - t) * (i && dyeData[channelId][i - 1][2]) + t * dyeData[channelId][i][2]) / 255;
+ }
/* TODO */
+ return imageData;
}
return mp;
}(mp || {});
diff --git a/public/js/mp/resource.js b/public/js/mp/resource.js
new file mode 100644
index 0000000..f348a89
--- /dev/null
+++ b/public/js/mp/resource.js
@@ -0,0 +1,36 @@
+"use strict";
+var mp = function(mp) {
+ mp.resource = {
+ loadImage: loadImage
+ };
+
+ /*
+ * Quick compatability workaround
+ * node testing environment needs new Canvas() and won't tolerate document.createElement("canvas")
+ * A createCanvas method is therefore provided in its sandbox which can be quickly checked to determine the method needed
+ */
+ var createCanvas = "createCanvas" in document ? document.createCanvas : function() { return document.createElement("canvas"); };
+
+ var canvas = createCanvas();
+ var context = canvas.getContext("2d");
+
+ /*
+ * Load in an image given a URL.
+ * The provided callback will fire when loading is complete.
+ * The parameters will be false and the the imageData if successful, and false and the error otherwise.
+ */
+ function loadImage(url, callback) {
+ var image = new Image();
+ image.onload = function() {
+ canvas.width = image.width;
+ canvas.height = image.height;
+ context.drawImage(image, 0, 0);
+ callback(false, context.getImageData(0, 0, image.width, image.height));
+ };
+ image.onerror = function(err) {
+ callback(true, err);
+ };
+ image.src = url;
+ }
+ return mp;
+}(mp || {});
diff --git a/public/playground.html b/public/playground.html
new file mode 100644
index 0000000..3099b83
--- /dev/null
+++ b/public/playground.html
@@ -0,0 +1,32 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<html>
+<head>
+</head>
+<body>
+<canvas id="original" width="32" height="32"></canvas>
+<canvas id="dyed" width="32" height="32"></canvas>
+</body>
+
+<script src="js/mp/dye.js"></script>
+<script src="js/mp/resource.js"></script>
+<script>
+var dyeData = {
+ "R": [
+ [0xed, 0xe5, 0xb2],
+ [0xff, 0xf7, 0xbf]
+ ],
+ "G": [
+ [0xcc, 0xcc, 0xcc],
+ [0xff, 0xff, 0xff]
+ ]
+};
+mp.resource.loadImage("bigcake.png", function(err, imageData) {
+ var oContext = document.getElementById("original").getContext("2d");
+ var dContext = document.getElementById("dyed").getContext("2d");
+ oContext.putImageData(imageData, 0, 0);
+ mp.dye.dyeImage(imageData.data, dyeData);
+ dContext.putImageData(imageData, 0, 0);
+});
+</script>
+</html>
diff --git a/test/load.js b/test/load.js
index 97bee69..57e838e 100644
--- a/test/load.js
+++ b/test/load.js
@@ -2,7 +2,8 @@
process.env.TZ = "UTC";
var smash = require("smash"),
- jsdom = require("jsdom");
+ jsdom = require("jsdom"),
+ Canvas = require("canvas");
module.exports = function() {
var files = [].slice.call(arguments).map(function(d) { return "public/js/" + d; }),
@@ -40,10 +41,16 @@ module.exports = function() {
};
};
+ document.createCanvas = function() {
+ return new Canvas();
+ }
+
sandbox = {
console: console,
document: document,
window: document.createWindow(),
+ Canvas: Canvas,
+ Image: Canvas.Image
};
return topic;
diff --git a/test/mp/dye.js b/test/mp/dye.js
index 36acfa3..26b33c2 100644
--- a/test/mp/dye.js
+++ b/test/mp/dye.js
@@ -7,7 +7,7 @@ var suite = vows.describe("mp.dye");
suite.addBatch({
"The manaportal dye": {
- topic: load("mp/dye").expression("mp.dye"),
+ topic: load("mp/dye").expression("mp.dye").document(),
"getChannel": {
topic: function(dye) { return dye.getChannel; },
"returns null given pure black": function(f) {
diff --git a/test/mp/future.js b/test/mp/future.js
index 6f491ae..cf0e68a 100644
--- a/test/mp/future.js
+++ b/test/mp/future.js
@@ -2,15 +2,10 @@
var vows = require("vows"),
load = require("../load"),
assert = require("assert"),
- jsdom = require("jsdom"),
- Canvas = require("canvas"),
- Image = Canvas.Image;
+ jsdom = require("jsdom");
var suite = vows.describe("mp.dye");
-var canvas = new Canvas(32,32);
-var context = canvas.getContext("2d");
-
var dyeString = "R:#ede5b2,fff7bf;G:#cccccc,ffffff";
var dyeData = {
"R": [
@@ -23,40 +18,55 @@ var dyeData = {
]
};
-function loadImage(url, tests) {
+function assertImageDataEqual(input, expected, actual, width) {
+ assert.equal(actual.length, expected.length, "expected same " + expected.length + " pixel components, found " + actual.length);
+ for (var i = 0; i != actual.length; i += 4) {
+ var p = i / 4;
+ var y = Math.floor(p / width);
+ var x = p - y * width;
+ var msg = "At (" + x + "," + y + "): "
+ + "Input rgba(" + input [i ] + "," + input [i + 1] + "," + input [i + 2] + "," + input [i + 3] + ") "
+ + "should dye to rgba(" + expected[i ] + "," + expected[i + 1] + "," + expected[i + 2] + "," + expected[i + 3] + "); "
+ + "found rgba(" + actual [i ] + "," + actual [i + 1] + "," + actual [i + 2] + "," + actual [i + 3] + ")";
+ assert.equal(actual[i ], expected[i ], msg);
+ assert.equal(actual[i + 1], expected[i + 1], msg);
+ assert.equal(actual[i + 2], expected[i + 2], msg);
+ assert.equal(actual[i + 3], expected[i + 3], msg);
+ }
+}
+
+function unshiftLoadImageBind(url, tests) {
tests.topic = function() {
- var image = new Image;
+ var mp = arguments[arguments.length - 1];
var tester = this;
- var callback = this.callback;
var args = arguments;
- image.onload = function() {
- canvas.width = image.width;
- canvas.height = image.height;
- context.drawImage(image, 0, 0);
- callback = callback.bind(tester, false, context.getImageData(0, 0, image.width, image.height));
- callback.apply(tester, args);
- }
- image.onerror = function(err) {
- throw new Error("Error loading '" + url + "': " + err);
- }
- image.src = url;
+ mp.resource.loadImage(url, function(err, data) {
+ if (err) {
+ throw new Error("Error loading '" + url + "': " + data);
+ }
+ tester.callback.bind(tester, err, data).apply(tester, args);
+ });
};
return tests;
}
suite.addBatch({
"The manaportal dye": {
- topic: load("mp/dye").expression("mp.dye").document(),
+ topic: load("mp/dye", "mp/resource").expression("mp").document(),
"parseDyeString": {
- "Extracts a the dye channel data from the dyestring": function(dye) {
- assert.equal(dye.parseDyeString(dyeString), dyeData);
+ "Extracts the dye channel data from the dyestring": function(mp) {
+ assert.equal(mp.dye.parseDyeString(dyeString), dyeData);
}
},
"dyeImage": {
- "with the big recolorable cake": loadImage("test/data/bigcake.png", {
- "to the big white cake": loadImage("test/data/whitecake.png", {
- "dyes correctly when given the correct dye data": function(err1, whiteCake, dyeableCake, dye) {
- assert.deepEqual(dye.dyeImage(dyeableCake.data, dyeData), whiteCake.data);
+ "with the big recolorable cake": unshiftLoadImageBind("test/data/bigcake.png", {
+ "to the big white cake": unshiftLoadImageBind("test/data/whitecake.png", {
+ "dyes correctly when given the correct dye data": function(err, whiteCake, dyeableCake, mp) {
+ var input = dyeableCake.data;
+ var expected = whiteCake.data;
+ var actual = new Uint8ClampedArray(input);
+ mp.dye.dyeImage(actual, dyeData);
+ assertImageDataEqual(input, expected, actual, whiteCake.width);
}
})
})