The decoded string
+ # Test with: Ox.decodeDeflate(Ox.encodeDeflate('foo'), alert)
+ @*/
+
+ 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(),
+ // The first 16 and the last 12 bytes of a PNG are always the same,
+ // the first 17 remaining bytes are part of the IHDR chunk, and the
+ // rest are IDAT chunks.
+ head = decodeHex('89 50 4E 47 0D 0A 1A 0A 00 00 00 0D 49 48 44 52'),
+ tail = decodeHex('00 00 00 00 49 45 4E 44 AE 42 60 82'),
+ data = '', ihdr = str.substr(0, 17), idat = str.substr(17), len;
+ while (idat) {
+ // Reinsert the IDAT chunk names
+ len = idat.substr(0, 4);
+ data += len + 'IDAT' + idat.substr(4, 8 + (len = Ox.decodeBase256(len)));
+ idat = idat.substr(8 + len);
+ }
+ // Unfortunately, we can't synchronously set the source of an image,
+ // draw it onto a canvas, and read its data.
+ image.onload = function() {
+ str = Ox.makeArray(Ox.canvas(image).data).map(function(v, i) {
+ // Read one character per RGB byte, ignore ALPHA.
+ return i % 4 < 3 ? Ox.char(v) : '';
+ }).join('');
+ callback(
+ // Parse the first 4 bytes as length and the next length bytes
+ // as an UTF8-encoded string.
+ Ox.decodeUTF8(str.substr(4, Ox.decodeBase256(str.substr(0, 4))))
+ );
+ }
+ image.onerror = function() {
+ throw new RangeError('Deflate codec can\'t decode data.')
+ }
+ image.src = 'data:image/png;base64,' + btoa(head + ihdr + data + tail);
}
/*@
@@ -2131,71 +2200,98 @@ Ox.element = function(str) {
return Ox.element('').html(str)[0].childNodes[0].nodeValue;
};
- //@ Ox.encodePNG Encodes a string into an image, returns a new image URL
+ /*@
+ Ox.encodePNG Encodes a string into an image, returns a new image
+ The string is compressed with deflate (by proxy of canvas), prefixed
+ with its length (four bytes), and encoded bitwise into the red, green
+ and blue bytes of all fully opaque pixels of the image, by flipping, if
+ necessary, the least significant bit, so that for every byte, the total
+ number of bits set to to 1, modulo 2, is the bit that we are encoding.
+ (img, src) -> An image into which the string has been encoded
+ img Any JavaScript PNG image object
+ str The string to be encoded
+ @*/
+
Ox.encodePNG = function(img, str) {
+ var c = Ox.canvas(img), i = 0;
+ // Compress the string
+ str = Ox.encodeDeflate(str);
+ // Prefix the string with its length, as a four-byte value
+ str = Ox.pad(Ox.encodeBase256(str.length), 4, Ox.char(0)) + str;
+ // Create an array of bit values
+ Ox.forEach(Ox.flatten(Ox.map(str, function(chr) {
+ return Ox.map(Ox.range(8), function(i) {
+ return chr.charCodeAt(0) >> 7 - i & 1;
+ });
+ })), function(bit) {
+ // Skip all pixels that are not fully opaque
+ while (i < c.data.length && i % 4 == 0 && c.data[i + 3] < 255) {
+ i += 4;
+ }
+ if (i == c.data.length) {
+ throw new RangeError('PNG codec can\'t encode data');
+ }
+ // If the number of bits set to one, modulo 2 is equal to the bit,
+ // do nothing, otherwise, flip the least significant bit.
+ c.data[i] += c.data[i].toString(2).replace(/0/g, '').length % 2
+ == bit ? 0 : c.data[i] % 2 ? -1 : 1;
+ i++;
+ });
+ c.context.putImageData(c.imageData, 0, 0);
+ img = new Image();
+ img.src = c.canvas.toDataURL();
+ return img;
/*
- the message is compressed with deflate (by proxy of canvas),
- then the string (four bytes length) + (length bytes message)
- is encoded bitwise into the r/g/b bytes of all opaque pixels
- by flipping, if necessary, the least significant bit, so that
- (number of "1"-bits of the byte) % 2 is the bit of the string
wishlist:
- only use deflate if it actually shortens the message
- - in deflate, strip and later re-insert the chunk types
- encode a decoy message into the least significant bit
(and flip the second least significant bit, if at all)
- write an extra png chunk containing some key
*/
- //str = Ox.encodeDeflate(str); currently broken
- str = Ox.encodeUTF8(str);
- var c = Ox.canvas(img), len = str.length, px = 0;
- if (len == 0 || len > cap(img.width, img.height)) {
- throwPNGError('en')
- }
- len = Ox.pad(Ox.encodeBase256(len), 4, Ox.char(0));
- Ox.forEach(Ox.map(len + str, function(byte) {
- return Ox.map(Ox.range(8), function(i) {
- return byte.charCodeAt(0) >> 7 - i & 1;
- }).join('');
- }).join(''), function(bit, i) {
- var index = parseInt((px = seek(c.data, px)) * 4 + i % 3),
- byte = c.data[index];
- c.data[index] = bit == xor(byte) ? byte :
- byte & 254 | !(byte & 1);
- px += i % 3 == 2;
- });
- c.context.putImageData(c.imageData, 0, 0);
- return c.canvas.toDataURL();
}
- //@ Ox.decodePNG Decodes an image, returns a string
- Ox.decodePNG = function(img) {
+ /*@
+ Ox.decodePNG Decodes an image, returns a string
+ For every red, green and blue byte of every fully opaque pixel of the
+ image, one bit, namely the number of bits of the byte set to one, modulo
+ 2, is being read, the result being the string, prefixed with its length
+ (four bytes), which is decompressed with deflate (by proxy of canvas).
+ (img, callback) -> undefined
+ img The image into which the string has been encoded
+ callback Callback function
+ str The decoded string
+ @*/
+
+ Ox.decodePNG = function(img, callback) {
var bits = '', data = Ox.canvas(img).data, flag = false, i = 0,
- len = 4, max = cap(img.width, img.height), px = 0, str = '';
- do {
- bits += xor(data[parseInt((px = seek(data, px)) * 4 + i % 3)]);
- px += i % 3 == 2;
+ len = 4, str = '';
+ while (len) {
+ // Skip all pixels that are not fully opaque
+ while (i < data.length && i % 4 == 0 && data[i + 3] < 255) {
+ i += 4;
+ }
+ if (i == data.length) {
+ break;
+ }
+ // Read the number of bits set to one, modulo 2
+ bits += data[i].toString(2).replace(/0/g, '').length % 2;
if (++i % 8 == 0) {
+ // Every 8 bits, add one byte
str += Ox.char(parseInt(bits, 2));
bits = '';
- len--;
- if (len == 0 && !flag) {
- len = Ox.decodeBase256(str);
- if (len <= 0 || len > max) {
- Ox.print(len);
- throwPNGError('de');
- }
- str = '';
+ // When length reaches 0 for the first time,
+ // decode the string and treat it as the new length
+ if (--len == 0 && !flag) {
flag = true;
+ len = Ox.decodeBase256(str);
+ str = '';
}
}
- } while (len);
+ }
try {
- //return Ox.decodeDeflate(str); currently broken
- return Ox.decodeUTF8(str);
- } catch(e) {
- Ox.print(e.toString());
- throwPNGError('de');
+ Ox.decodeDeflate(str, callback);
+ } catch (e) {
+ throw new RangeError('PNG codec can\'t decode image');
}
}
@@ -3354,7 +3450,7 @@ Ox.doc = (function() {
return indent;
}
function parseItem(str) {
- var matches = re.item(str);
+ var matches = re.item.exec(str);
// to tell a variable with default value, like
// name foo'> summary
// from a line of description with tags, like
@@ -3370,7 +3466,7 @@ Ox.doc = (function() {
}, parseType(matches[2])) : null;
}
function parseName(str) {
- var matches = re.usage(str);
+ var matches = re.usage.exec(str);
return matches ? matches[0] : str;
}
function parseNode(node) {
@@ -3515,7 +3611,7 @@ Ox.doc = (function() {
var match;
token.source = source.substr(token.offset, token.length);
if (token.type == 'comment' && (match =
- re.multiline(token.source) || re.singleline(token.source)
+ re.multiline.exec(token.source)|| re.singleline.exec(token.source)
)) {
blocks.push(match[1]);
tokens.push([]);
@@ -4016,11 +4112,11 @@ Ox.limit Limits a number by a given mininum and maximum
> Ox.limit(2, 1)
1
@*/
-Ox.limit = function(num, min, max) {
- // fixme: rewrite this
- var len = arguments.length;
- max = arguments[len - 1];
- min = len == 3 ? min : 0;
+Ox.limit = function(/*num[[, min], max]*/) {
+ var len = arguments.length,
+ num = arguments[0],
+ min = len == 3 ? arguments[1] : 0, // fixme: should be -Infinity
+ max = arguments[len - 1];
return Math.min(Math.max(num, min), max);
};
@@ -4373,25 +4469,23 @@ Ox.isValidEmail = function(str) {
Ox.pad Pad a string to a given length
> Ox.pad(1, 2)
"01"
- > Ox.pad("abc", 6, ".", "right")
+ > Ox.pad("abc", -6, ".")
"abc..."
- > Ox.pad("foobar", 3, ".", "right")
+ > Ox.pad("foobar", -3, ".")
"foo"
- > Ox.pad("abc", 6, "123456", "right")
+ > Ox.pad("abc", -6, "123456")
"abc123"
- > Ox.pad("abc", 6, "123456", "left")
+ > Ox.pad("abc", 6, "123456")
"456abc"
@*/
-Ox.pad = function(str, len, pad, pos) {
+Ox.pad = function(str, len, pad) {
// fixme: slighly obscure signature
// fixme: weird for negative numbers
- /*
- */
+ var pos = len / (len = Math.abs(len));
str = str.toString().substr(0, len);
pad = Ox.repeat(pad || '0', len - str.length);
- pos = pos || 'left';
- str = pos == 'left' ? pad + str : str + pad;
- str = pos == 'left' ?
+ str = pos == 1 ? pad + str : str + pad;
+ str = pos == 1 ?
str.substr(str.length - len, str.length) :
str.substr(0, len);
return str;