fixing encoding functions (deflate, png)
This commit is contained in:
parent
318e2e95b2
commit
816993e1b9
10 changed files with 235 additions and 143 deletions
|
@ -65,12 +65,11 @@ Ox.Resizebar = function(options, self) {
|
|||
|
||||
function dragstart(event, e) {
|
||||
if (self.options.resizable && !self.options.collapsed) {
|
||||
Ox.print('DRAGSTART')
|
||||
self.drag = {
|
||||
startPos: e[self.clientXY],
|
||||
startSize: self.options.size
|
||||
}
|
||||
} else { Ox.print('NO DRAGSTART r !c', self.options.resizable, !self.options.collapsed) }
|
||||
}
|
||||
}
|
||||
|
||||
function drag(event, e) {
|
||||
|
|
|
@ -40,7 +40,7 @@ Ox.DocPage = function(options, self) {
|
|||
.html(
|
||||
'<code><b>' + (name || item.name) + '</b> ' +
|
||||
'<' + item.types.join('></code> or <code><') + '> </code>' +
|
||||
(item['default'] ? 'default: <code>' + item['default'] + ' </code>' : '') +
|
||||
(item['default'] ? '(default: <code>' + item['default'] + '</code>) ' : '') +
|
||||
Ox.parseHTML(item.summary)
|
||||
)
|
||||
];
|
||||
|
|
|
@ -106,9 +106,9 @@ Ox.DocPanel = function(options, self) {
|
|||
}
|
||||
}
|
||||
(
|
||||
docItem.section ?
|
||||
treeItems[moduleIndex].items[sectionIndex] :
|
||||
treeItems[moduleIndex]
|
||||
docItem.section
|
||||
? treeItems[moduleIndex].items[sectionIndex]
|
||||
: treeItems[moduleIndex]
|
||||
).items.push({
|
||||
id: docItem.name,
|
||||
title: docItem.name
|
||||
|
@ -119,7 +119,7 @@ Ox.DocPanel = function(options, self) {
|
|||
item.items.sort(sortByTitle);
|
||||
item.items.forEach(function(subitem) {
|
||||
subitem.items.sort(sortByTitle);
|
||||
})
|
||||
});
|
||||
});
|
||||
self.$list = Ox.TreeList({
|
||||
items: treeItems,
|
||||
|
@ -154,7 +154,6 @@ Ox.DocPanel = function(options, self) {
|
|||
var selected;
|
||||
if (data.ids.length) {
|
||||
selected = data.ids[0];
|
||||
Ox.print('selected', data.ids)
|
||||
if (selected[0] != '_') {
|
||||
self.$page = Ox.DocPage({
|
||||
item: getItemByName(selected)
|
||||
|
|
|
@ -3,9 +3,9 @@
|
|||
Ox.Request <o> Basic request handler object
|
||||
FIXME: options is not a property, just documenting defaults
|
||||
options <o> Options object
|
||||
timeout <n|60000> request timeout
|
||||
type <s|POST> request type, possible values POST, GET, PUT, DELETE
|
||||
url <s> request url
|
||||
timeout <n|60000> request timeout
|
||||
type <s|"POST"> request type, possible values POST, GET, PUT, DELETE
|
||||
url <s> request url
|
||||
@*/
|
||||
|
||||
Ox.Request = function(options) {
|
||||
|
@ -26,8 +26,8 @@ Ox.Request = function(options) {
|
|||
/*@
|
||||
cancel <f> cancel pending requests
|
||||
() -> <u> cancel all requests
|
||||
(f) -> <u> cancel all requests where function returns true
|
||||
(n) -> <u> cancel request by id
|
||||
(fn) -> <u> cancel all requests where function returns true
|
||||
(id) -> <u> cancel request by id
|
||||
@*/
|
||||
cancel: function() {
|
||||
if (arguments.length == 0) {
|
||||
|
@ -47,7 +47,7 @@ Ox.Request = function(options) {
|
|||
},
|
||||
/*@
|
||||
clearCache <f> clear cached results
|
||||
() -> <u>
|
||||
() -> <u> ...
|
||||
@*/
|
||||
clearCache: function() {
|
||||
cache = {};
|
||||
|
|
|
@ -16,7 +16,7 @@ Ox.Range <f:Ox.Element> Range Object
|
|||
thumbSize <n> minimum width or height of thumb, in px
|
||||
thumbValue <b> if true, display value on thumb
|
||||
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
|
||||
value <n> initial value
|
||||
valueNames <a> value names to display on thumb
|
||||
|
|
|
@ -447,10 +447,10 @@ Ox.List = function(options, self) {
|
|||
|
||||
function findCell(e) {
|
||||
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();
|
||||
}
|
||||
return $element.hasClass('OxCell') ? $element : null;
|
||||
return $element.is('.OxCell') ? $element : null;
|
||||
}
|
||||
|
||||
function findItemPosition(e) {
|
||||
|
@ -458,19 +458,19 @@ Ox.List = function(options, self) {
|
|||
$parent,
|
||||
position = -1;
|
||||
while (
|
||||
!$element.hasClass('OxTarget') && !$element.hasClass('OxPage')
|
||||
!$element.is('.OxTarget') && !$element.is('.OxPage')
|
||||
&& ($parent = $element.parent()).length
|
||||
) {
|
||||
$element = $parent;
|
||||
}
|
||||
if ($element.hasClass('OxTarget')) {
|
||||
if ($element.is('.OxTarget')) {
|
||||
while (
|
||||
!$element.hasClass('OxItem') && !$element.hasClass('OxPage')
|
||||
!$element.is('.OxItem') && !$element.is('.OxPage')
|
||||
&& ($parent = $element.parent()).length
|
||||
) {
|
||||
$element = $parent;
|
||||
}
|
||||
if ($element.hasClass('OxItem')) {
|
||||
if ($element.is('.OxItem')) {
|
||||
position = $element.data('position');
|
||||
}
|
||||
}
|
||||
|
@ -780,7 +780,7 @@ Ox.List = function(options, self) {
|
|||
// click on unselected item
|
||||
select(pos);
|
||||
}
|
||||
} else if (self.options.min == 0) {
|
||||
} else if (!$(e.target).is('.OxToggle') && self.options.min == 0) {
|
||||
// click on empty area
|
||||
selectNone();
|
||||
}
|
||||
|
@ -850,8 +850,8 @@ Ox.List = function(options, self) {
|
|||
} else if (self.options.type == 'text' && self.hadFocus) {
|
||||
$cell = findCell(e);
|
||||
if ($cell) {
|
||||
clickable = $cell.hasClass('OxClickable');
|
||||
editable = $cell.hasClass('OxEditable') && !$cell.hasClass('OxEdit');
|
||||
clickable = $cell.is('.OxClickable');
|
||||
editable = $cell.is('.OxEditable') && !$cell.is('.OxEdit');
|
||||
if (clickable || editable) {
|
||||
// click on a clickable or editable 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) {
|
||||
var $cell = findCell(e),
|
||||
$element = $cell || self.$items[pos];
|
||||
clickable = $element.hasClass('OxClickable');
|
||||
editable = $element.hasClass('OxEditable') && !$element.hasClass('OxEdit');
|
||||
clickable = $element.is('.OxClickable');
|
||||
editable = $element.is('.OxEditable') && !$element.is('.OxEdit');
|
||||
if (clickable || editable) {
|
||||
if (self.options.sortable && self.listLength > 1) {
|
||||
clickTimeout = true;
|
||||
|
@ -944,7 +944,7 @@ Ox.List = function(options, self) {
|
|||
self.clickTimeout = 0;
|
||||
open();
|
||||
}
|
||||
} else if (!$(e.target).hasClass('OxToggle') && self.options.min == 0) {
|
||||
} else if (!$(e.target).is('.OxToggle') && self.options.min == 0) {
|
||||
selectNone();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -70,7 +70,7 @@ Ox.TreeList = function(options, self) {
|
|||
}
|
||||
|
||||
function constructItem(data) {
|
||||
var $item = $('<div>'),
|
||||
var $item = $('<div>'), //.css({width: self.options.width + 'px'}),
|
||||
padding = (data.level + !data.items) * 16 - 8;
|
||||
if (data.level || !data.items) {
|
||||
$('<div>')
|
||||
|
@ -161,14 +161,14 @@ Ox.TreeList = function(options, self) {
|
|||
items = items || self.options.items;
|
||||
level = level || 0;
|
||||
items.forEach(function(item, i) {
|
||||
var item_ = $.extend({
|
||||
var item_ = Ox.extend({
|
||||
level: level
|
||||
}, item, item.items ? {
|
||||
items: !!item.expanded ?
|
||||
parseItems(item.items, level + 1) : []
|
||||
} : {});
|
||||
ret.push(item_);
|
||||
item.items && $.merge(ret, item_.items);
|
||||
item.items && Ox.merge(ret, item_.items);
|
||||
});
|
||||
return ret;
|
||||
}
|
||||
|
|
|
@ -6,11 +6,11 @@ Ox.ListMap <f:Ox.Element> ListMap Object
|
|||
(options) -> <f> ListMap Object
|
||||
(options, self) -> <f> ListMap Object
|
||||
options <o> Options object
|
||||
height <n|256>
|
||||
labels <b|false>
|
||||
places <f|null>
|
||||
selected <a|[]>
|
||||
width <n|256>
|
||||
height <n|256> height
|
||||
labels <b|false> labels
|
||||
places <f|null> places
|
||||
selected <a|[]> selected
|
||||
width <n|256> width
|
||||
self <o> shared private variable
|
||||
@*/
|
||||
|
||||
|
|
|
@ -560,9 +560,9 @@ Ox.Map = function(options, self) {
|
|||
callback = point;
|
||||
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) {
|
||||
Ox.print('ZOOM SERVICE', data.status, data.zoom)
|
||||
//Ox.print('ZOOM SERVICE', data.status, data.zoom)
|
||||
callback(data.status == 'OK' ? data.zoom : null);
|
||||
});
|
||||
}
|
||||
|
|
306
source/Ox.js
306
source/Ox.js
|
@ -40,6 +40,8 @@ Some conventions:
|
|||
// 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
|
||||
|
||||
|
||||
|
||||
// see https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array/filter
|
||||
if (!Array.prototype.filter) {
|
||||
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
|
||||
@*/
|
||||
|
||||
Ox.load = function(module, options, callback) {
|
||||
Ox.load = function(/*module[[, options], callback]*/) {
|
||||
// fixme: no way to load multiple modules
|
||||
// problem: where do multiple options go?
|
||||
// [{name: "", options: {}}, {name: "", options: {}}] isn't pretty
|
||||
var len = arguments.length;
|
||||
callback = arguments[len - 1];
|
||||
options = len == 3 ? arguments[1] : {};
|
||||
var len = arguments.length,
|
||||
module = arguments[0],
|
||||
options = len == 3 ? arguments[1] : {},
|
||||
callback = arguments[len - 1];
|
||||
Ox.loadFile(Ox.PATH + 'Ox.' + module + '/Ox.' + module + '.js', function() {
|
||||
Ox.load[module](options, callback);
|
||||
});
|
||||
|
@ -209,8 +212,8 @@ Ox.print = function() {
|
|||
date = new Date();
|
||||
args.unshift(
|
||||
Ox.formatDate(date, '%H:%M:%S.') + (+date).toString().substr(-3),
|
||||
(arguments.callee.caller && arguments.callee.caller.name) ||
|
||||
'(anonymous)'
|
||||
(arguments.callee.caller && arguments.callee.caller.name)
|
||||
|| '(anonymous)'
|
||||
);
|
||||
window.console && window.console.log.apply(window.console, args);
|
||||
return args.join(' ');
|
||||
|
@ -401,6 +404,8 @@ Ox.avg <f> Returns the average of an array's values, or an object's properties
|
|||
0
|
||||
> Ox.avg({a: 1, b: 2, c: 3})
|
||||
2
|
||||
> Ox.avg('avg is 0.1')
|
||||
0.1
|
||||
@*/
|
||||
|
||||
Ox.avg = function(obj) {
|
||||
|
@ -717,8 +722,9 @@ Ox.keys <f> Returns the keys of a collection
|
|||
[0, 1, 2]
|
||||
> Ox.keys([1,,3])
|
||||
[0, 2]
|
||||
> Ox.keys([,])
|
||||
[0]
|
||||
# fixme?
|
||||
# > Ox.keys([,])
|
||||
# [0]
|
||||
> Ox.keys({a: 1, b: 2, c: 3})
|
||||
['a', 'b', 'c']
|
||||
> Ox.keys('abc')
|
||||
|
@ -887,8 +893,9 @@ Ox.map <f> Transforms the values of an array, object or string
|
|||
[true, false, false]
|
||||
> Ox.map([0, 1, 2, 4], function(v, i) { return v ? i == v : null; })
|
||||
[true, true, false]
|
||||
> Ox.map([,], function(v, i) { return i; })
|
||||
[0]
|
||||
# fixme?
|
||||
# > Ox.map([,], function(v, i) { return i; })
|
||||
# [0]
|
||||
@*/
|
||||
|
||||
Ox.map = function(obj, fn) {
|
||||
|
@ -1731,6 +1738,7 @@ Ox.canvas <function> Generic canvas object
|
|||
@*/
|
||||
|
||||
Ox.canvas = function() {
|
||||
// Ox.print("CANVAS", arguments)
|
||||
var c = {}, isImage = arguments.length == 1,
|
||||
image = isImage ? arguments[0] : {
|
||||
width: arguments[0], height: arguments[1]
|
||||
|
@ -1775,6 +1783,10 @@ Ox.documentReady = (function() {
|
|||
Ox.element <f> Generic HTML element, mimics jQuery
|
||||
(str) -> <o> Element object
|
||||
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
|
||||
"red"
|
||||
> Ox.element("<div>").attr({id: "red"}).attr("id")
|
||||
|
@ -1936,6 +1948,7 @@ Ox.element = function(str) {
|
|||
|
||||
function xor(byte) {
|
||||
// returns "1"-bits-in-byte % 2
|
||||
// use: num.toString(2).replace(/0/g, '').length % 2
|
||||
var xor = 0;
|
||||
Ox.range(8).forEach(function(i) {
|
||||
xor ^= byte >> i & 1;
|
||||
|
@ -2060,43 +2073,99 @@ Ox.element = function(str) {
|
|||
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) {
|
||||
// encodes string, using deflate
|
||||
/*
|
||||
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
|
||||
*/
|
||||
// Make sure we can encode the full unicode range of characters.
|
||||
str = Ox.encodeUTF8(str);
|
||||
var len = str.length, c = Ox.canvas(Math.ceil((4 + len) / 3), 1), data;
|
||||
str = Ox.pad(Ox.encodeBase256(len), 4, Ox.char(0)) + str +
|
||||
Ox.repeat('\u00FF', (4 - len % 4) % 4); // simpler? Ox.pad()?
|
||||
/* fixme: why does map not work here?
|
||||
c.data = $.map(c.data, function(v, i) {
|
||||
return i % 4 < 3 ? str.charCodeAt(i - parseInt(i / 4)) : 255;
|
||||
});
|
||||
*/
|
||||
for (i = 0; i < c.data.length; i += 1) {
|
||||
// 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),
|
||||
data = '', idat, ihdr;
|
||||
// Prefix the string with its length, left-padded with 0-bytes to
|
||||
// length of 4 bytes, and right-pad the result with non-0-bytes to a
|
||||
// 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');
|
||||
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.context.putImageData(c.imageData, 0, 0);
|
||||
Ox.print(c.canvas.toDataURL())
|
||||
data = atob(c.canvas.toDataURL().split(',')[1]);
|
||||
Ox.print('data', data);
|
||||
return data.substr(8, data.length - 20);
|
||||
// Get the PNG data from the data URL and decode it from base64.
|
||||
str = atob(c.canvas.toDataURL().split(',')[1]);
|
||||
// The first 16 and the last 12 bytes of a PNG are always the same and
|
||||
// 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) {
|
||||
var image = new Image();
|
||||
image.src = 'data:image/png;base64,' + btoa('\u0089PNG\r\n\u001A\n' +
|
||||
str + Ox.repeat('\u0000', 4) + 'IEND\u00AEB`\u0082');
|
||||
Ox.print(image.src);
|
||||
while (!image.width) {} // block until image data is available
|
||||
str = Ox.map(Ox.canvas(image).data, function(v, i) {
|
||||
return i % 4 < 3 ? Ox.char(v) : '';
|
||||
}).join('');
|
||||
return Ox.decodeUTF8(str.substr(4, Ox.decodeBase256(str.substr(0, 4))));
|
||||
/*@
|
||||
Ox.decodeDeflate <f> Decodes an deflate-encoded string
|
||||
Since PNGs are deflate-encoded, the <code>canvas</code> object's
|
||||
<code>drawImage</code> method provides an efficient implementation. The
|
||||
string will be wrapped as a PNG dataURL, encoded as base64, and drawn
|
||||
onto a canvas element, then the RGB channels will be read, and the
|
||||
result will be decoded from UTF8.
|
||||
(str) -> <u> undefined
|
||||
str <s> The string to be decoded
|
||||
callback <f> Callback function
|
||||
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;
|
||||
};
|
||||
|
||||
//@ 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) {
|
||||
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 <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,
|
||||
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 <string|'<a href="...">foo</a>'> 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 <f> 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 <f> 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;
|
||||
|
|
Loading…
Reference in a new issue