image module update

This commit is contained in:
rlx 2011-09-08 21:39:07 +00:00
parent 1b4591dcbe
commit 1e38ff6b9e
4 changed files with 188 additions and 152 deletions

View file

@ -8,6 +8,7 @@ Ox.load('UI', {
files: Ox.merge([ files: Ox.merge([
'Ox.js', 'Ox.js',
'Ox.Geo/Ox.Geo.js', 'Ox.Geo/Ox.Geo.js',
'Ox.Image/Ox.Image.js',
], files.filter(function(file) { ], files.filter(function(file) {
return Ox.endsWith(file, '.js'); return Ox.endsWith(file, '.js');
})), })),

View file

@ -6,14 +6,15 @@ Ox.load('Image', function() {
var body = Ox.element('body'), var body = Ox.element('body'),
select = Ox.element('<select>').appendTo(body); select = Ox.element('<select>').appendTo(body);
[ [
'Method...', 'blur(1)', 'blur(2)', 'blur(3)', 'Method...', 'blur(1)', 'blur(5)',
'channel("r")', 'channel("g")', 'channel("b")', 'channel("a")', 'channel("r")', 'channel("g")', 'channel("b")', 'channel("a")',
'channel("h")', 'channel("s")', 'channel("l")', 'channel("h")', 'channel("s")', 'channel("l")',
'contour()', 'edges()', 'emboss()', 'contour()', 'depth(1)', 'depth(4)', 'edges()', 'emboss()',
'encode("some secret stuff")', 'decode()', 'encode("secret")', 'decode()',
'encode("some secret stuff", true)', 'decode(true)', 'encode("secret", false)', 'decode(false)',
'encode("some secret stuff", 1)', 'decode(1)', 'encode("secret", 1)', 'decode(1)',
'encode("some secret stuff.", 15)', 'decode(15)', 'encode("secret", false, 15)', 'decode(false, 15)',
'encode("secret", 127)', 'decode(127)',
'hue(-60)', 'hue(60)', 'hue(-60)', 'hue(60)',
'invert()', 'invert()',
'lightness(-0.5)', 'lightness(0.5)', 'lightness(-0.5)', 'lightness(0.5)',

View file

@ -1,5 +1,17 @@
Ox.load.Image = function(options, callback) { Ox.load.Image = function(options, callback) {
//@ Image ------------------------------------------------------------------
/*@
Ox.Image <f> Generic image object
(src, callback) -> <u> undefined
(width, height, callback) -> <u> undefined
(width, height, background, callback) -> <u> undefined
src <s> Image source (local, remote or data URL)
width <n> Width in px
image <n> Height in px
background <[n]> Background color (RGB or RGBA)
@*/
Ox.Image = function() { Ox.Image = function() {
var self = {}, var self = {},
@ -81,25 +93,22 @@ Ox.load.Image = function(options, callback) {
} }
that.blur = function(val) { that.blur = function(val) {
return that.filter(val == 1 ? [ var filter = [],
0.0, 0.2, 0.0, size = val * 2 + 1;
0.2, 0.2, 0.2, sum = 0
0.0, 0.2, 0.0 Ox.loop(size, function(x) {
] : val == 2 ? [ Ox.loop(size, function(y) {
0.00, 1/21, 1/21, 1/21, 0.00, var isInCircle = +(Math.sqrt(
1/21, 1/21, 1/21, 1/21, 1/21, Math.pow(x - val, 2) + Math.pow(y - val, 2)
1/21, 1/21, 1/21, 1/21, 1/21, ) <= val);
1/21, 1/21, 1/21, 1/21, 1/21, sum += isInCircle;
0.00, 1/21, 1/21, 1/21, 0.00 filter.push(isInCircle)
] : [ });
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, filter = filter.map(function(val) {
1/37, 1/37, 1/37, 1/37, 1/37, 1/37, 1/37, return val / sum;
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, return that.filter(filter);
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) { that.channel = function(str) {
@ -133,6 +142,15 @@ Ox.load.Image = function(options, callback) {
]); ]);
}; };
that.depth = function(val) {
var pow = Math.pow(2, 8 - val);
return that.map(function(rgba) {
return rgba.map(function(v, i) {
return i < 3 ? Math.floor(v / pow) * pow : v
});
});
}
that.edges = function(val) { that.edges = function(val) {
return that.filter([ return that.filter([
-1, -1, -1, -1, -1, -1,
@ -149,79 +167,35 @@ Ox.load.Image = function(options, callback) {
], 128).saturation(-1); ], 128).saturation(-1);
}; };
that.decode = function() { /*@
var callback = arguments[arguments.length - 1], encode <f> Encodes a string into the image
deflate = Ox.isBoolean(arguments[0]) ? arguments[0] For most purposes, deflate and mode should be omitted, since the
: Ox.isBoolean(arguments[1]) ? arguments[1] : false, defaults make the existence of the message harder to detect. A valid
mode = Ox.isNumber(arguments[0]) ? arguments[0] use case for deflate and mode would be to encode, additionally to
: Ox.isNumber(arguments[1]) ? arguments[1] : 0, the secret string, a more easily detected decoy string: <code>
bin = '', image.encode(decoy, false, 1, function(image) { image.encode(secret,
bits = mode < 1 ? [-mode] : Ox.map(Ox.range(8), function(b) { -1, callback); })</code>.
return mode & 1 << b ? b : null; (str, callback) -> <o> The image object (unmodified)
}), (str, deflate, callback) -> <o> The image object (unmodified)
done = 0; len = 4, str = ''; (str, mode, callback) -> <o> The image object (unmodified)
that.forEach(function(rgba, xy) { (str, deflate, mode, callback) -> <o> The image object (unmodified)
if (rgba[3] == 255) { (str, mode, deflate, callback) -> <o> The image object (unmodified)
var index = getIndex(xy); str <s> The string to be encoded
Ox.loop(3, function(c) { callback <f> Callback function
var i = index + c; image <o> The image object (modified)
Ox.forEach(bits, function(bit) { deflate <b|true> If true, compress string with deflate
if (mode < 1) { mode <n|0> Encoding mode
bin += Ox.sum(Ox.range(8).map(function(bit) { If mode is between -7 and 0, the string will be encoded one bit
return +!!(self.data[i] & 1 << bit); per byte, as the number of bits within that byte set to 1,
})) % 2; modulo 2, by flipping, if necessary, the most (mode -7) to least
} else { (mode 0) significant bit. If mode is between 1 and 255, the
bin += +!!(self.data[i] & 1 << bit); string will be encoded bitwise into all bits per byte that, in
} mode, are set to 1.
// /*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) { that.encode = function(str) {
var callback = arguments[arguments.length - 1], var callback = arguments[arguments.length - 1],
deflate = Ox.isBoolean(arguments[1]) ? arguments[1] deflate = Ox.isBoolean(arguments[1]) ? arguments[1]
: Ox.isBoolean(arguments[2]) ? arguments[2] : false, : Ox.isBoolean(arguments[2]) ? arguments[2] : true,
mode = Ox.isNumber(arguments[1]) ? arguments[1] mode = Ox.isNumber(arguments[1]) ? arguments[1]
: Ox.isNumber(arguments[2]) ? arguments[2] : 0, : Ox.isNumber(arguments[2]) ? arguments[2] : 0,
b = 0, bin, b = 0, bin,
@ -229,13 +203,12 @@ Ox.load.Image = function(options, callback) {
return mode & 1 << bit ? bit : null; return mode & 1 << bit ? bit : null;
}), }),
cap = getCapacity(bits.length), len; cap = getCapacity(bits.length), len;
Ox.print("CAPACITY", cap); Ox.print("CAPACITY", cap)
// Compress the string // Compress the string
str = Ox[deflate ? 'encodeDeflate' : 'encodeUTF8'](str); str = Ox[deflate ? 'encodeDeflate' : 'encodeUTF8'](str);
Ox.print("STR", str, str.length, str.charCodeAt(0));
len = str.length; len = str.length;
// Prefix the string with its length, as a four-byte value // Prefix the string with its length, as a four-byte value
str = Ox.pad(Ox.encodeBase256(len), 4, Ox.char(0)) + str; str = Ox.pad(Ox.encodeBase256(len), 4, '\u0000') + str;
str.length > cap && error('encode'); str.length > cap && error('encode');
while (str.length < cap) { while (str.length < cap) {
str += str.substr(4, len); str += str.substr(4, len);
@ -249,6 +222,7 @@ Ox.load.Image = function(options, callback) {
})); }));
b = 0; b = 0;
that.forEach(function(rgba, xy) { that.forEach(function(rgba, xy) {
// If alpha is not 255, the RGB values may not be preserved
if (rgba[3] == 255) { if (rgba[3] == 255) {
var index = getIndex(xy); var index = getIndex(xy);
Ox.loop(3, function(c) { Ox.loop(3, function(c) {
@ -268,32 +242,70 @@ Ox.load.Image = function(options, callback) {
self.data[i] ^= 1 << 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); self.context.putImageData(self.imageData, 0, 0);
callback(that); callback(that);
return that;
}; };
that.decode = function() {
var callback = arguments[arguments.length - 1],
deflate = Ox.isBoolean(arguments[0]) ? arguments[0]
: Ox.isBoolean(arguments[1]) ? arguments[1] : true,
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) {
bin += mode < 1
? Ox.sum(Ox.range(8).map(function(bit) {
return +!!(self.data[i] & 1 << bit);
})) % 2
: +!!(self.data[i] & 1 << bit);
if (bin.length == 8) {
str += Ox.char(parseInt(bin, 2));
bin = '';
if (str.length == len) {
if (++done == 1) {
len = Ox.decodeBase256(str);
Ox.print("LEN", len)
len + 4 > getCapacity(bits.length)
&& error('decode');
str = '';
} else {
return false;
}
}
}
});
});
if (done == 2) {
return false;
}
}
});
try {
if (deflate) {
Ox.print('DEFLATE')
Ox.decodeDeflate(str, callback);
} else {
callback(Ox.decodeUTF8(str));
}
} catch (e) {
error('decode');
}
}
that.fillRect = function(x, y, w, h) { that.fillRect = function(x, y, w, h) {
self.context.fillRect(x, y, w, h); self.context.fillRect(x, y, w, h);
return that; return that;

View file

@ -1857,6 +1857,17 @@ Ox.element = function(str) {
return ret; return ret;
}, },
/*@ /*@
click <f> Binds a function to the click event
(callback) -> <o> This element
callback <f> Callback function
event <o> The DOM event
@*/
// fixme: why not a generic bind?
click: function(callback) {
this[0].onclick = callback;
return this;
},
/*@
css <f> Gets or sets a CSS attribute css <f> Gets or sets a CSS attribute
(key) -> <s> Value (key) -> <s> Value
(key, value) -> <o> This element (key, value) -> <o> This element
@ -1912,6 +1923,15 @@ Ox.element = function(str) {
return this; return this;
}, },
/*@ /*@
removeAttr <f> Removes an attribute
(key) -> <o> This element
key <s> The attribute
@*/
removeAttr: function(key) {
this[0].removeAttribute(key);
return this;
},
/*@
removeClass <f> Removes a class name removeClass <f> Removes a class name
(className) -> <o> This element (className) -> <o> This element
className <s> Class name className <s> Class name
@ -2092,13 +2112,12 @@ Ox.element = function(str) {
// Make sure we can encode the full unicode range of characters. // Make sure we can encode the full unicode range of characters.
str = Ox.encodeUTF8(str); str = Ox.encodeUTF8(str);
// We can only safely write to RGB, so we need 1 pixel for 3 bytes. // We can only safely write to RGB, so we need 1 pixel for 3 bytes.
var len = str.length, c = Ox.canvas(Math.ceil((4 + len) / 3), 1), // The string length may not be a multiple of 3, so we need to encode
data = '', idat; // the number of padding bytes (1 byte), the string, and non-0-bytes
// Prefix the string with its length, left-padded with 0-bytes to // as padding, so that the combined length becomes a multiple of 3.
// length of 4 bytes, and right-pad the result with non-0-bytes to a var len = 1 + str.length, c = Ox.canvas(Math.ceil(len / 3), 1),
// length that is a multiple of 3. data, idat, pad = (3 - len % 3) % 3;
str = Ox.pad(Ox.pad(Ox.encodeBase256(len), 4, '\u0000') + str, str = Ox.char(pad) + str + Ox.repeat('\u00FF', pad);
-Math.ceil((4 + len) / 3) * 3, '\u00FF');
Ox.loop(c.data.length, function(i) { Ox.loop(c.data.length, function(i) {
// Write character codes into RGB, and 255 into ALPHA // Write character codes into RGB, and 255 into ALPHA
c.data[i] = i % 4 < 3 ? str.charCodeAt(i - parseInt(i / 4)) : 255; c.data[i] = i % 4 < 3 ? str.charCodeAt(i - parseInt(i / 4)) : 255;
@ -2106,15 +2125,17 @@ Ox.element = function(str) {
c.context.putImageData(c.imageData, 0, 0); c.context.putImageData(c.imageData, 0, 0);
// Get the PNG data from the data URL and decode it from base64. // Get the PNG data from the data URL and decode it from base64.
str = atob(c.canvas.toDataURL().split(',')[1]); str = atob(c.canvas.toDataURL().split(',')[1]);
// The first 16 and the last 12 bytes of a PNG are always the same and // Discard bytes 0 to 15 (8 bytes PNG signature, 4 bytes IHDR length, 4
// can be discarded, the first 17 remaining bytes are part of the IHDR // bytes IHDR name), keep bytes 16 to 19 (width), discard bytes 20 to 29
// chunk, and the rest are IDAT chunks. // (4 bytes height, 5 bytes flags), keep bytes 29 to 32 (IHDR checksum),
data = Ox.sub(str, 16, 33); idat = Ox.sub(str, 33, -12); // keep the rest (IDAT chunks), discard the last 12 bytes (IEND chunk).
data = str.substr(16, 4) + str.substr(29, 4);
idat = str.substr(33, str.length - 45);
while (idat) { while (idat) {
// Each IDAT chunk consists of 4 bytes length, 4 bytes "IDAT" and // Each IDAT chunk is 4 bytes length, 4 bytes name, length bytes
// length bytes data, so we can optimize by removing each "IDAT". // data and 4 bytes checksum. We can discard the name parts.
len = idat.substr(0, 4); len = idat.substr(0, 4);
data += len + idat.substr(8, 12 + (len = Ox.decodeBase256(len))); data += len + idat.substr(8, 4 + (len = Ox.decodeBase256(len)));
idat = idat.substr(12 + len); idat = idat.substr(12 + len);
} }
callback && callback(data); callback && callback(data);
@ -2135,24 +2156,24 @@ Ox.element = function(str) {
@*/ @*/
Ox.decodeDeflate = function(str, callback) { Ox.decodeDeflate = function(str, callback) {
function decodeHex(str) {
return str.split(' ').map(function(v) {
return Ox.char(parseInt(v, 16));
}).join('');
}
var image = new Image(), var image = new Image(),
// The first 16 and the last 12 bytes of a PNG are always the same, // PNG file signature and IHDR chunk
// the first 17 remaining bytes are part of the IHDR chunk, and the data = '\u0089PNG\r\n\u001A\n\u0000\u0000\u0000\u000DIHDR'
// rest are IDAT chunks. + str.substr(0, 4) + '\u0000\u0000\u0000\u0001'
head = decodeHex('89 50 4E 47 0D 0A 1A 0A 00 00 00 0D 49 48 44 52'), + '\u0008\u0006\u0000\u0000\u0000' + str.substr(4, 4),
tail = decodeHex('00 00 00 00 49 45 4E 44 AE 42 60 82'), // IDAT chunks
data = '', ihdr = str.substr(0, 17), idat = str.substr(17), len; idat = str.substr(8), len;
function error() {
throw new RangeError('Deflate codec can\'t decode data.');
}
while (idat) { while (idat) {
// Reinsert the IDAT chunk names // Reinsert the IDAT chunk names
len = idat.substr(0, 4); len = idat.substr(0, 4);
data += len + 'IDAT' + idat.substr(4, 8 + (len = Ox.decodeBase256(len))); data += len + 'IDAT' + idat.substr(4, 4 + (len = Ox.decodeBase256(len)));
idat = idat.substr(8 + len); idat = idat.substr(8 + len);
} }
// IEND chunk
data += '\u0000\u0000\u0000\u0000IEND\u00AE\u0042\u0060\u0082';
// Unfortunately, we can't synchronously set the source of an image, // Unfortunately, we can't synchronously set the source of an image,
// draw it onto a canvas, and read its data. // draw it onto a canvas, and read its data.
image.onload = function() { image.onload = function() {
@ -2160,16 +2181,17 @@ Ox.element = function(str) {
// Read one character per RGB byte, ignore ALPHA. // Read one character per RGB byte, ignore ALPHA.
return i % 4 < 3 ? Ox.char(v) : ''; return i % 4 < 3 ? Ox.char(v) : '';
}).join(''); }).join('');
callback( try {
// Parse the first 4 bytes as length and the next length bytes // Parse the first byte as number of bytes to chop at the end,
// as an UTF8-encoded string. // and the rest, without these bytes, as an UTF8-encoded string.
Ox.decodeUTF8(str.substr(4, Ox.decodeBase256(str.substr(0, 4)))) str = Ox.decodeUTF8(str.substr(1, str.length - 1 - str.charCodeAt(0)))
); } catch (e) {
error();
} }
image.onerror = function() { callback(str);
throw new RangeError('Deflate codec can\'t decode data.')
} }
image.src = 'data:image/png;base64,' + btoa(head + ihdr + data + tail); image.onerror = error;
image.src = 'data:image/png;base64,' + btoa(data);
} }
/*@ /*@