fixing encoding functions (deflate, png)

This commit is contained in:
rlx 2011-09-05 23:34:29 +00:00
commit 816993e1b9
10 changed files with 235 additions and 143 deletions

View file

@ -65,12 +65,11 @@ Ox.Resizebar = function(options, self) {
function dragstart(event, e) { function dragstart(event, e) {
if (self.options.resizable && !self.options.collapsed) { if (self.options.resizable && !self.options.collapsed) {
Ox.print('DRAGSTART')
self.drag = { self.drag = {
startPos: e[self.clientXY], startPos: e[self.clientXY],
startSize: self.options.size startSize: self.options.size
} }
} else { Ox.print('NO DRAGSTART r !c', self.options.resizable, !self.options.collapsed) } }
} }
function drag(event, e) { function drag(event, e) {

View file

@ -40,7 +40,7 @@ Ox.DocPage = function(options, self) {
.html( .html(
'<code><b>' + (name || item.name) + '</b> ' + '<code><b>' + (name || item.name) + '</b> ' +
'&lt;' + item.types.join('&gt;</code> or <code>&lt;') + '&gt; </code>' + '&lt;' + item.types.join('&gt;</code> or <code>&lt;') + '&gt; </code>' +
(item['default'] ? 'default: <code>' + item['default'] + ' </code>' : '') + (item['default'] ? '(default: <code>' + item['default'] + '</code>) ' : '') +
Ox.parseHTML(item.summary) Ox.parseHTML(item.summary)
) )
]; ];

View file

@ -106,9 +106,9 @@ Ox.DocPanel = function(options, self) {
} }
} }
( (
docItem.section ? docItem.section
treeItems[moduleIndex].items[sectionIndex] : ? treeItems[moduleIndex].items[sectionIndex]
treeItems[moduleIndex] : treeItems[moduleIndex]
).items.push({ ).items.push({
id: docItem.name, id: docItem.name,
title: docItem.name title: docItem.name
@ -119,7 +119,7 @@ Ox.DocPanel = function(options, self) {
item.items.sort(sortByTitle); item.items.sort(sortByTitle);
item.items.forEach(function(subitem) { item.items.forEach(function(subitem) {
subitem.items.sort(sortByTitle); subitem.items.sort(sortByTitle);
}) });
}); });
self.$list = Ox.TreeList({ self.$list = Ox.TreeList({
items: treeItems, items: treeItems,
@ -154,7 +154,6 @@ Ox.DocPanel = function(options, self) {
var selected; var selected;
if (data.ids.length) { if (data.ids.length) {
selected = data.ids[0]; selected = data.ids[0];
Ox.print('selected', data.ids)
if (selected[0] != '_') { if (selected[0] != '_') {
self.$page = Ox.DocPage({ self.$page = Ox.DocPage({
item: getItemByName(selected) item: getItemByName(selected)

View file

@ -3,9 +3,9 @@
Ox.Request <o> Basic request handler object Ox.Request <o> Basic request handler object
FIXME: options is not a property, just documenting defaults FIXME: options is not a property, just documenting defaults
options <o> Options object options <o> Options object
timeout <n|60000> request timeout timeout <n|60000> request timeout
type <s|POST> request type, possible values POST, GET, PUT, DELETE type <s|"POST"> request type, possible values POST, GET, PUT, DELETE
url <s> request url url <s> request url
@*/ @*/
Ox.Request = function(options) { Ox.Request = function(options) {
@ -26,8 +26,8 @@ Ox.Request = function(options) {
/*@ /*@
cancel <f> cancel pending requests cancel <f> cancel pending requests
() -> <u> cancel all requests () -> <u> cancel all requests
(f) -> <u> cancel all requests where function returns true (fn) -> <u> cancel all requests where function returns true
(n) -> <u> cancel request by id (id) -> <u> cancel request by id
@*/ @*/
cancel: function() { cancel: function() {
if (arguments.length == 0) { if (arguments.length == 0) {
@ -47,7 +47,7 @@ Ox.Request = function(options) {
}, },
/*@ /*@
clearCache <f> clear cached results clearCache <f> clear cached results
() -> <u> () -> <u> ...
@*/ @*/
clearCache: function() { clearCache: function() {
cache = {}; cache = {};

View file

@ -16,7 +16,7 @@ Ox.Range <f:Ox.Element> Range Object
thumbSize <n> minimum width or height of thumb, in px thumbSize <n> minimum width or height of thumb, in px
thumbValue <b> if true, display value on thumb thumbValue <b> if true, display value on thumb
trackGradient <a> colors trackGradient <a> colors
trackImages <s> or <a> one or multiple track background image URLs trackImages <s|[s]> one or multiple track background image URLs
trackStep <n> 0 (scroll here) or step when clicking track trackStep <n> 0 (scroll here) or step when clicking track
value <n> initial value value <n> initial value
valueNames <a> value names to display on thumb valueNames <a> value names to display on thumb

View file

@ -447,10 +447,10 @@ Ox.List = function(options, self) {
function findCell(e) { function findCell(e) {
var $element = $(e.target); var $element = $(e.target);
while (!$element.hasClass('OxCell') && !$element.hasClass('OxPage') && !$element.is('body')) { while (!$element.is('.OxCell') && !$element.is('.OxPage') && !$element.is('body')) {
$element = $element.parent(); $element = $element.parent();
} }
return $element.hasClass('OxCell') ? $element : null; return $element.is('.OxCell') ? $element : null;
} }
function findItemPosition(e) { function findItemPosition(e) {
@ -458,19 +458,19 @@ Ox.List = function(options, self) {
$parent, $parent,
position = -1; position = -1;
while ( while (
!$element.hasClass('OxTarget') && !$element.hasClass('OxPage') !$element.is('.OxTarget') && !$element.is('.OxPage')
&& ($parent = $element.parent()).length && ($parent = $element.parent()).length
) { ) {
$element = $parent; $element = $parent;
} }
if ($element.hasClass('OxTarget')) { if ($element.is('.OxTarget')) {
while ( while (
!$element.hasClass('OxItem') && !$element.hasClass('OxPage') !$element.is('.OxItem') && !$element.is('.OxPage')
&& ($parent = $element.parent()).length && ($parent = $element.parent()).length
) { ) {
$element = $parent; $element = $parent;
} }
if ($element.hasClass('OxItem')) { if ($element.is('.OxItem')) {
position = $element.data('position'); position = $element.data('position');
} }
} }
@ -780,7 +780,7 @@ Ox.List = function(options, self) {
// click on unselected item // click on unselected item
select(pos); select(pos);
} }
} else if (self.options.min == 0) { } else if (!$(e.target).is('.OxToggle') && self.options.min == 0) {
// click on empty area // click on empty area
selectNone(); selectNone();
} }
@ -850,8 +850,8 @@ Ox.List = function(options, self) {
} else if (self.options.type == 'text' && self.hadFocus) { } else if (self.options.type == 'text' && self.hadFocus) {
$cell = findCell(e); $cell = findCell(e);
if ($cell) { if ($cell) {
clickable = $cell.hasClass('OxClickable'); clickable = $cell.is('.OxClickable');
editable = $cell.hasClass('OxEditable') && !$cell.hasClass('OxEdit'); editable = $cell.is('.OxEditable') && !$cell.is('.OxEdit');
if (clickable || editable) { if (clickable || editable) {
// click on a clickable or editable cell // click on a clickable or editable cell
triggerClickEvent(clickable ? 'click' : 'edit', self.$items[pos], $cell); triggerClickEvent(clickable ? 'click' : 'edit', self.$items[pos], $cell);
@ -904,8 +904,8 @@ Ox.List = function(options, self) {
} else if (self.options.type == 'text' && hadFocus) { } else if (self.options.type == 'text' && hadFocus) {
var $cell = findCell(e), var $cell = findCell(e),
$element = $cell || self.$items[pos]; $element = $cell || self.$items[pos];
clickable = $element.hasClass('OxClickable'); clickable = $element.is('.OxClickable');
editable = $element.hasClass('OxEditable') && !$element.hasClass('OxEdit'); editable = $element.is('.OxEditable') && !$element.is('.OxEdit');
if (clickable || editable) { if (clickable || editable) {
if (self.options.sortable && self.listLength > 1) { if (self.options.sortable && self.listLength > 1) {
clickTimeout = true; clickTimeout = true;
@ -944,7 +944,7 @@ Ox.List = function(options, self) {
self.clickTimeout = 0; self.clickTimeout = 0;
open(); open();
} }
} else if (!$(e.target).hasClass('OxToggle') && self.options.min == 0) { } else if (!$(e.target).is('.OxToggle') && self.options.min == 0) {
selectNone(); selectNone();
} }
} }

View file

@ -70,7 +70,7 @@ Ox.TreeList = function(options, self) {
} }
function constructItem(data) { function constructItem(data) {
var $item = $('<div>'), var $item = $('<div>'), //.css({width: self.options.width + 'px'}),
padding = (data.level + !data.items) * 16 - 8; padding = (data.level + !data.items) * 16 - 8;
if (data.level || !data.items) { if (data.level || !data.items) {
$('<div>') $('<div>')
@ -161,14 +161,14 @@ Ox.TreeList = function(options, self) {
items = items || self.options.items; items = items || self.options.items;
level = level || 0; level = level || 0;
items.forEach(function(item, i) { items.forEach(function(item, i) {
var item_ = $.extend({ var item_ = Ox.extend({
level: level level: level
}, item, item.items ? { }, item, item.items ? {
items: !!item.expanded ? items: !!item.expanded ?
parseItems(item.items, level + 1) : [] parseItems(item.items, level + 1) : []
} : {}); } : {});
ret.push(item_); ret.push(item_);
item.items && $.merge(ret, item_.items); item.items && Ox.merge(ret, item_.items);
}); });
return ret; return ret;
} }

View file

@ -6,11 +6,11 @@ Ox.ListMap <f:Ox.Element> ListMap Object
(options) -> <f> ListMap Object (options) -> <f> ListMap Object
(options, self) -> <f> ListMap Object (options, self) -> <f> ListMap Object
options <o> Options object options <o> Options object
height <n|256> height <n|256> height
labels <b|false> labels <b|false> labels
places <f|null> places <f|null> places
selected <a|[]> selected <a|[]> selected
width <n|256> width <n|256> width
self <o> shared private variable self <o> shared private variable
@*/ @*/

View file

@ -560,9 +560,9 @@ Ox.Map = function(options, self) {
callback = point; callback = point;
point = self.map.getCenter(); point = self.map.getCenter();
} }
Ox.print('CALLING ZOOM SERVICE', point.lat(), point.lng()) //Ox.print('CALLING ZOOM SERVICE', point.lat(), point.lng())
self.maxZoomService.getMaxZoomAtLatLng(point, function(data) { self.maxZoomService.getMaxZoomAtLatLng(point, function(data) {
Ox.print('ZOOM SERVICE', data.status, data.zoom) //Ox.print('ZOOM SERVICE', data.status, data.zoom)
callback(data.status == 'OK' ? data.zoom : null); callback(data.status == 'OK' ? data.zoom : null);
}); });
} }

View file

@ -40,6 +40,8 @@ Some conventions:
// todo: check http://ejohn.org/blog/ecmascript-5-strict-mode-json-and-more/ // todo: check http://ejohn.org/blog/ecmascript-5-strict-mode-json-and-more/
// also see https://github.com/tlrobinson/narwhal/blob/master/lib/util.js // also see https://github.com/tlrobinson/narwhal/blob/master/lib/util.js
// see https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array/filter // see https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array/filter
if (!Array.prototype.filter) { if (!Array.prototype.filter) {
Array.prototype.filter = function(fn, that) { Array.prototype.filter = function(fn, that) {
@ -182,13 +184,14 @@ Ox.load <f> Loads a module
success <b> If true, the module has been loaded successfully success <b> If true, the module has been loaded successfully
@*/ @*/
Ox.load = function(module, options, callback) { Ox.load = function(/*module[[, options], callback]*/) {
// fixme: no way to load multiple modules // fixme: no way to load multiple modules
// problem: where do multiple options go? // problem: where do multiple options go?
// [{name: "", options: {}}, {name: "", options: {}}] isn't pretty // [{name: "", options: {}}, {name: "", options: {}}] isn't pretty
var len = arguments.length; var len = arguments.length,
callback = arguments[len - 1]; module = arguments[0],
options = len == 3 ? arguments[1] : {}; options = len == 3 ? arguments[1] : {},
callback = arguments[len - 1];
Ox.loadFile(Ox.PATH + 'Ox.' + module + '/Ox.' + module + '.js', function() { Ox.loadFile(Ox.PATH + 'Ox.' + module + '/Ox.' + module + '.js', function() {
Ox.load[module](options, callback); Ox.load[module](options, callback);
}); });
@ -209,8 +212,8 @@ Ox.print = function() {
date = new Date(); date = new Date();
args.unshift( args.unshift(
Ox.formatDate(date, '%H:%M:%S.') + (+date).toString().substr(-3), Ox.formatDate(date, '%H:%M:%S.') + (+date).toString().substr(-3),
(arguments.callee.caller && arguments.callee.caller.name) || (arguments.callee.caller && arguments.callee.caller.name)
'(anonymous)' || '(anonymous)'
); );
window.console && window.console.log.apply(window.console, args); window.console && window.console.log.apply(window.console, args);
return args.join(' '); return args.join(' ');
@ -401,6 +404,8 @@ Ox.avg <f> Returns the average of an array's values, or an object's properties
0 0
> Ox.avg({a: 1, b: 2, c: 3}) > Ox.avg({a: 1, b: 2, c: 3})
2 2
> Ox.avg('avg is 0.1')
0.1
@*/ @*/
Ox.avg = function(obj) { Ox.avg = function(obj) {
@ -717,8 +722,9 @@ Ox.keys <f> Returns the keys of a collection
[0, 1, 2] [0, 1, 2]
> Ox.keys([1,,3]) > Ox.keys([1,,3])
[0, 2] [0, 2]
> Ox.keys([,]) # fixme?
[0] # > Ox.keys([,])
# [0]
> Ox.keys({a: 1, b: 2, c: 3}) > Ox.keys({a: 1, b: 2, c: 3})
['a', 'b', 'c'] ['a', 'b', 'c']
> Ox.keys('abc') > Ox.keys('abc')
@ -887,8 +893,9 @@ Ox.map <f> Transforms the values of an array, object or string
[true, false, false] [true, false, false]
> Ox.map([0, 1, 2, 4], function(v, i) { return v ? i == v : null; }) > Ox.map([0, 1, 2, 4], function(v, i) { return v ? i == v : null; })
[true, true, false] [true, true, false]
> Ox.map([,], function(v, i) { return i; }) # fixme?
[0] # > Ox.map([,], function(v, i) { return i; })
# [0]
@*/ @*/
Ox.map = function(obj, fn) { Ox.map = function(obj, fn) {
@ -1731,6 +1738,7 @@ Ox.canvas <function> Generic canvas object
@*/ @*/
Ox.canvas = function() { Ox.canvas = function() {
// Ox.print("CANVAS", arguments)
var c = {}, isImage = arguments.length == 1, var c = {}, isImage = arguments.length == 1,
image = isImage ? arguments[0] : { image = isImage ? arguments[0] : {
width: arguments[0], height: arguments[1] width: arguments[0], height: arguments[1]
@ -1775,6 +1783,10 @@ Ox.documentReady = (function() {
Ox.element <f> Generic HTML element, mimics jQuery Ox.element <f> Generic HTML element, mimics jQuery
(str) -> <o> Element object (str) -> <o> Element object
str <s> Tagname ('<tagname>') or selector ('tagname', '.classname', '#id') str <s> Tagname ('<tagname>') or selector ('tagname', '.classname', '#id')
> Ox.element("<div>").addClass("red").hasClass("red")
true
> Ox.element("<div>").addClass("red").removeClass("red").hasClass("red")
false
> Ox.element("<div>").addClass("red").addClass("red")[0].className > Ox.element("<div>").addClass("red").addClass("red")[0].className
"red" "red"
> Ox.element("<div>").attr({id: "red"}).attr("id") > Ox.element("<div>").attr({id: "red"}).attr("id")
@ -1936,6 +1948,7 @@ Ox.element = function(str) {
function xor(byte) { function xor(byte) {
// returns "1"-bits-in-byte % 2 // returns "1"-bits-in-byte % 2
// use: num.toString(2).replace(/0/g, '').length % 2
var xor = 0; var xor = 0;
Ox.range(8).forEach(function(i) { Ox.range(8).forEach(function(i) {
xor ^= byte >> i & 1; xor ^= byte >> i & 1;
@ -2060,43 +2073,99 @@ Ox.element = function(str) {
return num; return num;
} }
//@ Ox.encodeDeflate <f> (undocumented) /*@
Ox.encodeDeflate <f> Encodes a string, using deflate
Since PNGs are deflate-encoded, the <code>canvas</code> object's
<code>toDataURL</code> method provides an efficient implementation.
The string is encoded as UTF-8 and written to the RGB channels of a
canvas element, then the PNG dataURL is decoded from base64, and some
head, tail and chunk names are removed.
(str) -> <s> The encoded string
str <s> The string to be encoded
@*/
Ox.encodeDeflate = function(str) { Ox.encodeDeflate = function(str) {
// encodes string, using deflate // Make sure we can encode the full unicode range of characters.
/*
in fact, the string is written to the rgb channels of a canvas element,
then the dataURL is decoded from base64, and some head and tail cut off
*/
str = Ox.encodeUTF8(str); str = Ox.encodeUTF8(str);
var len = str.length, c = Ox.canvas(Math.ceil((4 + len) / 3), 1), data; // We can only safely write to RGB, so we need 1 pixel for 3 bytes.
str = Ox.pad(Ox.encodeBase256(len), 4, Ox.char(0)) + str + var len = str.length, c = Ox.canvas(Math.ceil((4 + len) / 3), 1),
Ox.repeat('\u00FF', (4 - len % 4) % 4); // simpler? Ox.pad()? data = '', idat, ihdr;
/* fixme: why does map not work here? // Prefix the string with its length, left-padded with 0-bytes to
c.data = $.map(c.data, function(v, i) { // length of 4 bytes, and right-pad the result with non-0-bytes to a
return i % 4 < 3 ? str.charCodeAt(i - parseInt(i / 4)) : 255; // length that is a multiple of 3.
}); str = Ox.pad(Ox.pad(Ox.encodeBase256(len), 4, '\u0000') + str,
*/ -Math.ceil((4 + len) / 3) * 3, '\u00FF');
for (i = 0; i < c.data.length; i += 1) { Ox.loop(c.data.length, function(i) {
// 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;
} });
c.context.putImageData(c.imageData, 0, 0); c.context.putImageData(c.imageData, 0, 0);
Ox.print(c.canvas.toDataURL()) // Get the PNG data from the data URL and decode it from base64.
data = atob(c.canvas.toDataURL().split(',')[1]); str = atob(c.canvas.toDataURL().split(',')[1]);
Ox.print('data', data); // The first 16 and the last 12 bytes of a PNG are always the same and
return data.substr(8, data.length - 20); // can be discarded, the first 17 remaining bytes are part of the IHDR
// chunk, and the rest are IDAT chunks.
ihdr = Ox.sub(str, 16, 33); idat = Ox.sub(str, 33, -12);
while (idat) {
// Each IDAT chunk consists of 4 bytes length, 4 bytes "IDAT" and
// length bytes data, so we can optimize by removing each "IDAT".
len = idat.substr(0, 4);
data += len + idat.substr(8, 12 + (len = Ox.decodeBase256(len)));
idat = idat.substr(12 + len);
}
return ihdr + data;
} }
//@ Ox.decodeDeflate <f> (undocumented) /*@
Ox.decodeDeflate = function(str) { Ox.decodeDeflate <f> Decodes an deflate-encoded string
var image = new Image(); Since PNGs are deflate-encoded, the <code>canvas</code> object's
image.src = 'data:image/png;base64,' + btoa('\u0089PNG\r\n\u001A\n' + <code>drawImage</code> method provides an efficient implementation. The
str + Ox.repeat('\u0000', 4) + 'IEND\u00AEB`\u0082'); string will be wrapped as a PNG dataURL, encoded as base64, and drawn
Ox.print(image.src); onto a canvas element, then the RGB channels will be read, and the
while (!image.width) {} // block until image data is available result will be decoded from UTF8.
str = Ox.map(Ox.canvas(image).data, function(v, i) { (str) -> <u> undefined
return i % 4 < 3 ? Ox.char(v) : ''; str <s> The string to be decoded
}).join(''); callback <f> Callback function
return Ox.decodeUTF8(str.substr(4, Ox.decodeBase256(str.substr(0, 4)))); str <s> 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('<div>').html(str)[0].childNodes[0].nodeValue; return Ox.element('<div>').html(str)[0].childNodes[0].nodeValue;
}; };
//@ Ox.encodePNG <f> Encodes a string into an image, returns a new image URL /*@
Ox.encodePNG <f> 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) -> <e> An image into which the string has been encoded
img <e> Any JavaScript PNG image object
str <s> The string to be encoded
@*/
Ox.encodePNG = function(img, str) { 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: wishlist:
- only use deflate if it actually shortens the message - 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 - encode a decoy message into the least significant bit
(and flip the second least significant bit, if at all) (and flip the second least significant bit, if at all)
- write an extra png chunk containing some key - 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 <f> Decodes an image, returns a string /*@
Ox.decodePNG = function(img) { Ox.decodePNG <f> 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) -> <u> undefined
img <e> The image into which the string has been encoded
callback <f> Callback function
str <s> The decoded string
@*/
Ox.decodePNG = function(img, callback) {
var bits = '', data = Ox.canvas(img).data, flag = false, i = 0, var bits = '', data = Ox.canvas(img).data, flag = false, i = 0,
len = 4, max = cap(img.width, img.height), px = 0, str = ''; len = 4, str = '';
do { while (len) {
bits += xor(data[parseInt((px = seek(data, px)) * 4 + i % 3)]); // Skip all pixels that are not fully opaque
px += i % 3 == 2; 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) { if (++i % 8 == 0) {
// Every 8 bits, add one byte
str += Ox.char(parseInt(bits, 2)); str += Ox.char(parseInt(bits, 2));
bits = ''; bits = '';
len--; // When length reaches 0 for the first time,
if (len == 0 && !flag) { // decode the string and treat it as the new length
len = Ox.decodeBase256(str); if (--len == 0 && !flag) {
if (len <= 0 || len > max) {
Ox.print(len);
throwPNGError('de');
}
str = '';
flag = true; flag = true;
len = Ox.decodeBase256(str);
str = '';
} }
} }
} while (len); }
try { try {
//return Ox.decodeDeflate(str); currently broken Ox.decodeDeflate(str, callback);
return Ox.decodeUTF8(str); } catch (e) {
} catch(e) { throw new RangeError('PNG codec can\'t decode image');
Ox.print(e.toString());
throwPNGError('de');
} }
} }
@ -3354,7 +3450,7 @@ Ox.doc = (function() {
return indent; return indent;
} }
function parseItem(str) { function parseItem(str) {
var matches = re.item(str); var matches = re.item.exec(str);
// to tell a variable with default value, like // to tell a variable with default value, like
// name <string|'<a href="...">foo</a>'> summary // name <string|'<a href="...">foo</a>'> summary
// from a line of description with tags, like // from a line of description with tags, like
@ -3370,7 +3466,7 @@ Ox.doc = (function() {
}, parseType(matches[2])) : null; }, parseType(matches[2])) : null;
} }
function parseName(str) { function parseName(str) {
var matches = re.usage(str); var matches = re.usage.exec(str);
return matches ? matches[0] : str; return matches ? matches[0] : str;
} }
function parseNode(node) { function parseNode(node) {
@ -3515,7 +3611,7 @@ Ox.doc = (function() {
var match; var match;
token.source = source.substr(token.offset, token.length); token.source = source.substr(token.offset, token.length);
if (token.type == 'comment' && (match = 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]); blocks.push(match[1]);
tokens.push([]); tokens.push([]);
@ -4016,11 +4112,11 @@ Ox.limit <f> Limits a number by a given mininum and maximum
> Ox.limit(2, 1) > Ox.limit(2, 1)
1 1
@*/ @*/
Ox.limit = function(num, min, max) { Ox.limit = function(/*num[[, min], max]*/) {
// fixme: rewrite this var len = arguments.length,
var len = arguments.length; num = arguments[0],
max = arguments[len - 1]; min = len == 3 ? arguments[1] : 0, // fixme: should be -Infinity
min = len == 3 ? min : 0; max = arguments[len - 1];
return Math.min(Math.max(num, min), max); return Math.min(Math.max(num, min), max);
}; };
@ -4373,25 +4469,23 @@ Ox.isValidEmail = function(str) {
Ox.pad <f> Pad a string to a given length Ox.pad <f> Pad a string to a given length
> Ox.pad(1, 2) > Ox.pad(1, 2)
"01" "01"
> Ox.pad("abc", 6, ".", "right") > Ox.pad("abc", -6, ".")
"abc..." "abc..."
> Ox.pad("foobar", 3, ".", "right") > Ox.pad("foobar", -3, ".")
"foo" "foo"
> Ox.pad("abc", 6, "123456", "right") > Ox.pad("abc", -6, "123456")
"abc123" "abc123"
> Ox.pad("abc", 6, "123456", "left") > Ox.pad("abc", 6, "123456")
"456abc" "456abc"
@*/ @*/
Ox.pad = function(str, len, pad, pos) { Ox.pad = function(str, len, pad) {
// fixme: slighly obscure signature // fixme: slighly obscure signature
// fixme: weird for negative numbers // fixme: weird for negative numbers
/* var pos = len / (len = Math.abs(len));
*/
str = str.toString().substr(0, len); str = str.toString().substr(0, len);
pad = Ox.repeat(pad || '0', len - str.length); pad = Ox.repeat(pad || '0', len - str.length);
pos = pos || 'left'; str = pos == 1 ? pad + str : str + pad;
str = pos == 'left' ? pad + str : str + pad; str = pos == 1 ?
str = pos == 'left' ?
str.substr(str.length - len, str.length) : str.substr(str.length - len, str.length) :
str.substr(0, len); str.substr(0, len);
return str; return str;