Because we have a mix of both icons for core items and .png icons for custom items, I've developed an easy way to theme them (alter the color) all of them easily that will work across all browsers.
Prior to this, I tried CSS and SVG options and they just weren't as simple as this option (but probably less overhead of course). I have to call this after load because of course can't start any canvas work until the items actually hit the DOM and they're parsing through via PHP. There's about 50 of these icons max, more like 25 of them to iterate thought on 90% of users.
Any suggestions on how to streamline this or possibly simplify it? (削除) Also I would have posted a test (fiddle or bin) but canvas has a strict cross domain policy. (削除ここまで)
Update: Found an image on the jsfiddle shell I can use for an example that adheres to the cross-domain policy.
JSFiddle: http://jsfiddle.net/d3c0y/6BSsy/
function changeColor(imgObject, imgColor) {
// create hidden canvas
var canvas = document.createElement("canvas");
canvas.width = 40;
canvas.height = 40;
var ctx = canvas.getContext("2d");
ctx.drawImage(imgObject, 0, 0, 40, 40);
var map = ctx.getImageData(0, 0, 40, 40);
var imdata = map.data;
// convert image to grayscale first
var r, g, b, avg;
for (var p = 0, len = imdata.length; p < len; p += 4) {
r = imdata[p]
g = imdata[p + 1];
b = imdata[p + 2];
avg = Math.floor((r + g + b) / 3);
imdata[p] = imdata[p + 1] = imdata[p + 2] = avg;
}
ctx.putImageData(map, 0, 0);
// overlay using source-atop to follow transparency
ctx.globalCompositeOperation = "source-atop"
ctx.globalAlpha = 0.3;
ctx.fillStyle = imgColor;
ctx.fillRect(0, 0, 40, 40);
// replace image source with canvas data
return canvas.toDataURL("image/png", 1);
}
jQuery(window).load(function () {
$('.custom-module').each($.proxy(function () {
$(this).attr('src', changeColor(this, '#33CC33'));
}), this).promise().done(function () {
$('.module-li').removeClass('hidden');
});
});
-
1\$\begingroup\$ Can you provide a fiddle? So that we can see it in action? \$\endgroup\$konijn– konijn2014年03月22日 02:22:49 +00:00Commented Mar 22, 2014 at 2:22
1 Answer 1
Pretty nice overall. You're missing a semi-colon or two, but otherwise it's a nice solution.
The only real criticism I have are the "magic numbers" in there, like the 40x40 size that's hardcoded everywhere. Technically, you could derive the canvas dimension from the image, or pass the width/height as arguments. (Incidentally, setting a canvas's size will also clear its contents, which is helpful if there's one shared canvas that needs to be cleared between uses.) In either case, though, you'd have to get rid of the hardcoded "40".
Optimization-wise, the only addition I've made is to simply skip pixels with a zero alpha component; if they're transparent anyway, there's no need to bother with them.
Other than that, I've just broken the code into a couple of functions; nothing special (Note, haven't tested this yet.)
var changeIconColor = (function () {
var canvas = document.createElement("canvas"), // shared instance
context = canvas.getContext("2d");
// only place the dimensions are hardcoded
// everything else just references canvas.width/canvas.height
canvas.width = 40;
canvas.height = 40;
function desaturate() {
var imageData = context.getImageData(0, 0, canvas.width, canvas.height),
pixels = imageData.data,
i, l, r, g, b, a, average;
for(i = 0, l = pixels.length ; i < l ; i += 4) {
a = pixels[i + 3];
if( a === 0 ) { continue; } // skip if pixel is transparent
r = pixels[i];
g = pixels[i + 1];
b = pixels[i + 2];
average = (r + g + b) / 3 >>> 0; // quick floor
pixels[i] = pixels[i + 1] = pixels[i + 2] = average;
}
context.putImageData(imageData, 0, 0);
}
function colorize(color) {
context.globalCompositeOperation = "source-atop";
context.globalAlpha = 0.3; // you may want to make this an argument
context.fillStyle = color;
context.fillRect(0, 0, canvas.width, canvas.height);
// reset
context.globalCompositeOperation = "source-over";
context.globalAlpha = 1.0;
}
return function (iconElement, color) {
context.clearRect(0, 0, canvas.width, canvas.height);
context.drawImage(iconElement, 0, 0, canvas.width, canvas.height);
desaturate();
colorize(color);
return canvas.toDataURL("image/png", 1);
};
}());
As for invoking it, I don't think you need the $.proxy
and promise
stuff. I would think it'd be enough to just do
$('.custom-module').each(function () {
this.src = changeIconColor(this, '#33CC33');
});
$('.module-li').removeClass('hidden');
-
-
\$\begingroup\$ @d3c0y Neat, glad it worked \$\endgroup\$Flambino– Flambino2014年03月24日 00:19:05 +00:00Commented Mar 24, 2014 at 0:19