Ox.load.Image = function(options, callback) { Ox.Image = function() { var self = {}, that = {}; self.callback = arguments[arguments.length - 1]; if (arguments.length == 2) { self.src = arguments[0]; self.image = new Image(); self.image.onload = init; self.image.src = self.src; } else { self.width = arguments[0]; self.height = arguments[1]; self.background = arguments.length == 4 ? arguments[2] : [0, 0, 0, 0]; init(); } function error(mode) { throw new RangeError('PNG codec can\'t ' + mode + ' ' + ( mode == 'encode' ? 'data' : 'image' )); } function getCapacity(bpb) { var capacity = 0; that.forEach(function(rgba) { capacity += rgba[3] == 255 ? bpb * 3/8 : 0; }); return capacity; } function getIndex() { if (arguments.length == 1) { xy = arguments[0]; } else { xy = [arguments[0], arguments[1]]; } return ( Ox.mod(xy[0], self.width) + Ox.mod(xy[1] * self.width, self.width * self.height) ) * 4; } function getXY(index) { index /= 4; return [index % self.width, Math.floor(index / self.width)]; } function init() { if (self.image) { self.width = self.image.width; self.height = self.image.height; } self.canvas = Ox.element('').attr({ width: self.width, height: self.height }); self.context = self.canvas[0].getContext('2d'); if (self.image) { self.context.drawImage(self.image, 0, 0); } else if (Ox.sum(self.background)) { that.fillStyle(self.background); that.fillRect(0, 0, self.width, self.height); } self.imageData = self.context.getImageData(0, 0, self.width, self.height); self.data = self.imageData.data; self.callback(that); } function setSL(sl, d) { var c = sl == 's' ? 1 : 2; return that.map(function(rgba) { var hsl = Ox.hsl([rgba[0], rgba[1], rgba[2]]), rgb; hsl[c] = d < 0 ? hsl[c] * (d + 1) : hsl[c] + (1 - hsl[c]) * d; rgb = Ox.rgb(hsl); return Ox.merge(rgb, rgba[3]); }); } that.blur = function(val) { return that.filter(val == 1 ? [ 0.0, 0.2, 0.0, 0.2, 0.2, 0.2, 0.0, 0.2, 0.0 ] : val == 2 ? [ 0.00, 1/21, 1/21, 1/21, 0.00, 1/21, 1/21, 1/21, 1/21, 1/21, 1/21, 1/21, 1/21, 1/21, 1/21, 1/21, 1/21, 1/21, 1/21, 1/21, 0.00, 1/21, 1/21, 1/21, 0.00 ] : [ 0.00, 0.00, 1/37, 1/37, 1/37, 0.00, 0.00, 0.00, 1/37, 1/37, 1/37, 1/37, 1/37, 0.00, 1/37, 1/37, 1/37, 1/37, 1/37, 1/37, 1/37, 1/37, 1/37, 1/37, 1/37, 1/37, 1/37, 1/37, 1/37, 1/37, 1/37, 1/37, 1/37, 1/37, 1/37, 0.00, 1/37, 1/37, 1/37, 1/37, 1/37, 0.00, 0.00, 0.00, 1/37, 1/37, 1/37, 0.00, 0.00 ]); }; that.channel = function(str) { str = str[0].toLowerCase(); return that.map(function(rgba) { var i = ['r', 'g', 'b', 'a'].indexOf(str), rgb, val; if (i > -1) { return Ox.map(rgba, function(v, c) { return str == 'a' ? (c < 3 ? rgba[3] : 255) : (c == i || c == 3 ? v : 0); }); } else { i = ['h', 's', 'l'].indexOf(str); val = Ox.hsl([rgba[0], rgba[1], rgba[2]])[i]; rgb = i == 0 ? Ox.rgb([val, 1, 0.5]) : Ox.map(Ox.range(3), function(v) { return parseInt(val * 255); }); return Ox.merge(rgb, rgba[3]); } }); } that.contour = function(val) { return that.filter([ +1, +1, +1, +1, -7, +1, +1, +1, +1 ]); }; that.edges = function(val) { return that.filter([ -1, -1, -1, -1, +8, -1, -1, -1, -1 ]).saturation(-1); }; that.emboss = function(val) { return that.filter([ -1, -1, +0, -1, +0, +1, +0, +1, +1 ], 128).saturation(-1); }; that.decode = function() { var callback = arguments[arguments.length - 1], deflate = Ox.isBoolean(arguments[0]) ? arguments[0] : Ox.isBoolean(arguments[1]) ? arguments[1] : false, mode = Ox.isNumber(arguments[0]) ? arguments[0] : Ox.isNumber(arguments[1]) ? arguments[1] : 0, bin = '', bits = mode < 1 ? [-mode] : Ox.map(Ox.range(8), function(b) { return mode & 1 << b ? b : null; }), done = 0; len = 4, str = ''; that.forEach(function(rgba, xy) { if (rgba[3] == 255) { var index = getIndex(xy); Ox.loop(3, function(c) { var i = index + c; Ox.forEach(bits, function(bit) { if (mode < 1) { bin += Ox.sum(Ox.range(8).map(function(bit) { return +!!(self.data[i] & 1 << bit); })) % 2; } else { bin += +!!(self.data[i] & 1 << bit); } // /*mode > 0 &&*/ Ox.print(bin) if (bin.length == 8) { str += Ox.char(parseInt(bin, 2)); bin = ''; if (str.length == len) { if (++done == 1) { len = Ox.decodeBase256(str); Ox.print(Ox.map(str, function(chr) { return Ox.pad(chr.charCodeAt(0).toString(2), 8); }).join(' ')) Ox.print("LEN", len, getCapacity(bits.length), bits, xy, c) Ox.print(Ox.range(index).map(function(i) { return that.pixel(i, 0).map(function(px, j) { return j < 4 ? Ox.pad(px.toString(2), 8) : null; }).join(',') }).join('\n')) if (len + 4 > getCapacity(bits.length)) { error('decode'); } str = ''; } else { return false; } } } }); }); if (done == 2) { return false; } } }); try { Ox.print("STR", str, str.length, str.charCodeAt(0)); if (deflate) { Ox.decodeDeflate(str, callback); } else { callback(Ox.decodeUTF8(str)); return str; } } catch (e) { error('decode'); } } that.encode = function(str) { var callback = arguments[arguments.length - 1], deflate = Ox.isBoolean(arguments[1]) ? arguments[1] : Ox.isBoolean(arguments[2]) ? arguments[2] : false, mode = Ox.isNumber(arguments[1]) ? arguments[1] : Ox.isNumber(arguments[2]) ? arguments[2] : 0, b = 0, bin, bits = mode < 1 ? [-mode] : Ox.map(Ox.range(8), function(bit) { return mode & 1 << bit ? bit : null; }), cap = getCapacity(bits.length), len; Ox.print("CAPACITY", cap); // Compress the string str = Ox[deflate ? 'encodeDeflate' : 'encodeUTF8'](str); Ox.print("STR", str, str.length, str.charCodeAt(0)); len = str.length; // Prefix the string with its length, as a four-byte value str = Ox.pad(Ox.encodeBase256(len), 4, Ox.char(0)) + str; str.length > cap && error('encode'); while (str.length < cap) { str += str.substr(4, len); } str = str.substr(0, Math.ceil(cap)); // Create an array of bit values bin = Ox.flatten(Ox.map(str, function(chr) { return Ox.map(Ox.range(8), function(i) { return chr.charCodeAt(0) >> 7 - i & 1; }); })); b = 0; that.forEach(function(rgba, xy) { if (rgba[3] == 255) { var index = getIndex(xy); Ox.loop(3, function(c) { var i = index + c; Ox.forEach(bits, function(bit) { if (( mode < 1 // If the number of bits set to 1, mod 2 ? Ox.sum(Ox.range(8).map(function(bit) { return +!!(self.data[i] & 1 << bit); })) % 2 // or the one bit in question : +!!(self.data[i] & 1 << bit) // is not equal to the data bit ) != bin[b++]) { // then flip the bit self.data[i] ^= 1 << bit; } }); /* if (mode < 1) { if (Ox.sum(Ox.range(8).map(function(bit) { return self.data[i] & 1 << bit; })) % 2 != bin[b++]) { // If the number of bits set to one, modulo 2, // is not equal to the data bit, flip one bit self.data[i] ^= 1 << bits[0]; } } else { Ox.forEach(bits, function(bit) { if (self.data[i] & 1 << bit != bin[b++]) { // If the bit is not equal to the data bit, // flip it self.data[i] ^= 1 << bit; } }); } */ }); } }); self.context.putImageData(self.imageData, 0, 0); callback(that); }; that.fillRect = function(x, y, w, h) { self.context.fillRect(x, y, w, h); return that; }; that.fillStyle = function() { if (arguments.length == 0) { return self.context.fillStyle; } else { self.context.fillStyle = arguments[0]; return that; } }; that.filter = function(filter, bias) { bias = bias || 0; var filterSize = Math.sqrt(filter.length), d = (filterSize - 1) / 2, imageData = self.context.createImageData(self.width, self.height), data = []; self.imageData = self.context.getImageData(0, 0, self.width, self.height); self.data = self.imageData.data; Ox.loop(0, self.data.length, 4, function(i) { var filterIndex = 0, xy = getXY(i); Ox.loop(3, function(c) { data[i + c] = 0; }); Ox.loop(-d, d + 1, function(x) { Ox.loop(-d, d + 1, function(y) { var pixelIndex = getIndex(xy[0] + x, xy[1] + y); Ox.loop(3, function(c) { data[i + c] += self.data[pixelIndex + c] * filter[filterIndex]; }); filterIndex++; }); }); }); Ox.loop(0, self.data.length, 4, function(i) { Ox.loop(4, function(c) { imageData.data[i + c] = c < 3 ? Ox.limit(Math.round(data[i + c] + bias), 0, 255) : self.data[i + c]; }); }); self.context.putImageData(imageData, 0, 0); self.imageData = imageData; self.data = data; return that; }; /*@ forEach Pixel-wise forEach function (fn) -> The image object fn Iterator function rgba <[n]> RGBA values xy <[n]> XY coordinates @*/ that.forEach = function(fn) { Ox.loop(0, self.data.length, 4, function(i) { return fn([ self.data[i], self.data[i + 1], self.data[i + 2], self.data[i + 3] ], getXY(i)); }) return that; }; that.hue = function(val) { return that.map(function(rgba) { var hsl = Ox.hsl([rgba[0], rgba[1], rgba[2]]), rgb; hsl[0] = (hsl[0] + val) % 360; rgb = Ox.rgb(hsl); return Ox.merge(rgb, rgba[3]); }); }; that.invert = function() { return that.map(function(rgba) { return [255 - rgba[0], 255 - rgba[1], 255 - rgba[2], rgba[3]]; }); }; that.lightness = function(val) { return setSL('l', val); }; /*@ map Pixel-wise map function (fn) -> The image object fn Iterator function rgba <[n]> RGBA values xy <[n]> XY coordinates @*/ that.map = function(fn) { self.imageData = self.context.getImageData(0, 0, self.width, self.height); self.data = self.imageData.data; Ox.loop(0, self.data.length, 4, function(i) { fn([ self.data[i], self.data[i + 1], self.data[i + 2], self.data[i + 3] ], getXY(i)).forEach(function(val, c) { self.data[i + c] = val; }); }) self.context.putImageData(self.imageData, 0, 0); return that; }; that.motionBlur = function() { return that.filter([ 0.2, 0.0, 0.0, 0.0, 0.0, 0.0, 0.2, 0.0, 0.0, 0.0, 0.0, 0.0, 0.2, 0.0, 0.0, 0.0, 0.0, 0.0, 0.2, 0.0, 0.0, 0.0, 0.0, 0.0, 0.2 ]); }; that.pixel = function(x, y, val) { var i = getIndex(x, y); if (!val) { return Ox.range(4).map(function(c) { return self.data[i + c]; }); } else { val.forEach(function(v, c) { self.data[i + c] = v; }); } } that.posterize = function() { return that.blur(3).map(function(rgba) { return [ Math.floor(rgba[0] / 64) * 64, Math.floor(rgba[1] / 64) * 64, Math.floor(rgba[2] / 64) * 64, rgba[3] ]; }); }; that.resize = function(width, height) { self.canvas.attr({ width: width, height: height }); return that; } that.saturation = function(val) { return setSL('s', val); }; that.sharpen = function(val) { return that.filter([ -1, -1, -1, -1, +9, -1, -1, -1, -1 ]); }; that.solarize = function() { return that.map(function(rgba) { return [ rgba[0] < 128 ? rgba[0] : 255 - rgba[0], rgba[1] < 128 ? rgba[1] : 255 - rgba[1], rgba[2] < 128 ? rgba[2] : 255 - rgba[2], rgba[3] ]; }); } that.src = function() { if (arguments.length == 0) { return self.canvas[0].toDataURL(); } else { var callback = arguments[1]; self.src = arguments[0]; self.image = new Image(); self.image.onload = function() { self.width = self.image.width; self.height = self.image.height; self.canvas.attr({ width: self.width, height: self.height }); self.context.drawImage(self.image, 0, 0); self.imageData = self.context.getImageData(0, 0, self.width, self.height); self.data = self.imageData.data; callback && callback(that); } self.image.src = self.src; return that; } }; that.toDataURL = that.src; }; callback(true); }