(function () { var ns = $.namespace('pskl.utils'); var colorCache = {}; ns.FrameUtils = { merge : function (frames) { var merged = null; if (frames.length) { merged = frames[0].clone(); var w = merged.getWidth(), h = merged.getHeight(); for (var i = 1 ; i < frames.length ; i++) { pskl.utils.FrameUtils.mergeFrames_(merged, frames[i]); } } return merged; }, mergeFrames_ : function (frameA, frameB) { frameB.forEachPixel(function (p, col, row) { if (p != Constants.TRANSPARENT_COLOR) { frameA.setPixel(col, row, p); } }); }, /** * Alpha compositing using porter duff algorithm : * http://en.wikipedia.org/wiki/Alpha_compositing * http://keithp.com/~keithp/porterduff/p253-porter.pdf * @param {String} strColor1 color over * @param {String} strColor2 color under * @return {String} the composite color */ mergePixels : function (strColor1, strColor2, globalOpacity1) { var col1 = pskl.utils.FrameUtils.toRgba(strColor1); var col2 = pskl.utils.FrameUtils.toRgba(strColor2); if (typeof globalOpacity1 == 'number') { col1 = JSON.parse(JSON.stringify(col1)); col1.a = globalOpacity1 * col1.a; } var a = col1.a + col2.a * (1 - col1.a); var r = ((col1.r * col1.a + col2.r * col2.a * (1 - col1.a)) / a)|0; var g = ((col1.g * col1.a + col2.g * col2.a * (1 - col1.a)) / a)|0; var b = ((col1.b * col1.a + col2.b * col2.a * (1 - col1.a)) / a)|0; return 'rgba('+r+','+g+','+b+','+a+')'; }, /** * Convert a color defined as a string (hex, rgba, rgb, 'TRANSPARENT') to an Object with r,g,b,a properties. * r, g and b are integers between 0 and 255, a is a float between 0 and 1 * @param {String} c color as a string * @return {Object} {r:Number,g:Number,b:Number,a:Number} */ toRgba : function (c) { if (colorCache[c]) { return colorCache[c]; } var color, matches; if (c === 'TRANSPARENT') { color = { r : 0, g : 0, b : 0, a : 0 }; } else if (c.indexOf('rgba(') != -1) { matches = /rgba\((\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*,\s*(1|0\.\d+)\s*\)/.exec(c); color = { r : parseInt(matches[1],10), g : parseInt(matches[2],10), b : parseInt(matches[3],10), a : parseFloat(matches[4]) }; } else if (c.indexOf('rgb(') != -1) { matches = /rgb\((\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\)/.exec(c); color = { r : parseInt(matches[1],10), g : parseInt(matches[2],10), b : parseInt(matches[3],10), a : 1 }; } else { matches = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(c); color = { r : parseInt(matches[1], 16), g : parseInt(matches[2], 16), b : parseInt(matches[3], 16), a : 1 }; } colorCache[c] = color; return color; }, /* * Create a pskl.model.Frame from an Image object. * Transparent pixels will either be converted to completely opaque or completely transparent pixels. * @param {Image} image source image * @return {pskl.model.Frame} corresponding frame */ createFromImage : function (image) { var w = image.width, h = image.height; var canvas = pskl.CanvasUtils.createCanvas(w, h); var context = canvas.getContext('2d'); context.drawImage(image, 0,0,w,h,0,0,w,h); var imgData = context.getImageData(0,0,w,h).data; return pskl.utils.FrameUtils.createFromImageData(imgData, w, h); }, createFromImageData : function (imageData, width, height) { // Draw the zoomed-up pixels to a different canvas context var grid = []; for (var x = 0 ; x < width ; x++){ grid[x] = []; for (var y = 0 ; y < height ; y++){ // Find the starting index in the one-dimensional image data var i = (y * width + x)*4; var r = imageData[i ]; var g = imageData[i+1]; var b = imageData[i+2]; var a = imageData[i+3]; if (a < 125) { grid[x][y] = Constants.TRANSPARENT_COLOR; } else { grid[x][y] = pskl.utils.FrameUtils.rgbToHex(r,g,b); } } } return pskl.model.Frame.fromPixelGrid(grid); }, /** * Convert a rgb(Number, Number, Number) color to hexadecimal representation * @param {Number} r red value, between 0 and 255 * @param {Number} g green value, between 0 and 255 * @param {Number} b blue value, between 0 and 255 * @return {String} hex representation of the color '#ABCDEF' */ rgbToHex : function (r, g, b) { return "#" + this.componentToHex(r) + this.componentToHex(g) + this.componentToHex(b); }, /** * Convert a color component (as a Number between 0 and 255) to its string hexa representation * @param {Number} c component value, between 0 and 255 * @return {String} eg. '0A' */ componentToHex : function (c) { var hex = c.toString(16); return hex.length == 1 ? "0" + hex : hex; } }; })();