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([
'Ox.js',
'Ox.Geo/Ox.Geo.js',
'Ox.Image/Ox.Image.js',
], files.filter(function(file) {
return Ox.endsWith(file, '.js');
})),

View file

@ -6,14 +6,15 @@ Ox.load('Image', function() {
var body = Ox.element('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("h")', 'channel("s")', 'channel("l")',
'contour()', 'edges()', 'emboss()',
'encode("some secret stuff")', 'decode()',
'encode("some secret stuff", true)', 'decode(true)',
'encode("some secret stuff", 1)', 'decode(1)',
'encode("some secret stuff.", 15)', 'decode(15)',
'contour()', 'depth(1)', 'depth(4)', 'edges()', 'emboss()',
'encode("secret")', 'decode()',
'encode("secret", false)', 'decode(false)',
'encode("secret", 1)', 'decode(1)',
'encode("secret", false, 15)', 'decode(false, 15)',
'encode("secret", 127)', 'decode(127)',
'hue(-60)', 'hue(60)',
'invert()',
'lightness(-0.5)', 'lightness(0.5)',

View file

@ -1,5 +1,17 @@
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() {
var self = {},
@ -81,25 +93,22 @@ Ox.load.Image = function(options, callback) {
}
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
]);
var filter = [],
size = val * 2 + 1;
sum = 0
Ox.loop(size, function(x) {
Ox.loop(size, function(y) {
var isInCircle = +(Math.sqrt(
Math.pow(x - val, 2) + Math.pow(y - val, 2)
) <= val);
sum += isInCircle;
filter.push(isInCircle)
});
});
filter = filter.map(function(val) {
return val / sum;
})
return that.filter(filter);
};
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) {
return that.filter([
-1, -1, -1,
@ -149,79 +167,35 @@ Ox.load.Image = function(options, callback) {
], 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');
}
}
/*@
encode <f> Encodes a string into the image
For most purposes, deflate and mode should be omitted, since the
defaults make the existence of the message harder to detect. A valid
use case for deflate and mode would be to encode, additionally to
the secret string, a more easily detected decoy string: <code>
image.encode(decoy, false, 1, function(image) { image.encode(secret,
-1, callback); })</code>.
(str, callback) -> <o> The image object (unmodified)
(str, deflate, callback) -> <o> The image object (unmodified)
(str, mode, callback) -> <o> The image object (unmodified)
(str, deflate, mode, callback) -> <o> The image object (unmodified)
(str, mode, deflate, callback) -> <o> The image object (unmodified)
str <s> The string to be encoded
callback <f> Callback function
image <o> The image object (modified)
deflate <b|true> If true, compress string with deflate
mode <n|0> Encoding mode
If mode is between -7 and 0, the string will be encoded one bit
per byte, as the number of bits within that byte set to 1,
modulo 2, by flipping, if necessary, the most (mode -7) to least
(mode 0) significant bit. If mode is between 1 and 255, the
string will be encoded bitwise into all bits per byte that, in
mode, are set to 1.
@*/
that.encode = function(str) {
var callback = arguments[arguments.length - 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]
: Ox.isNumber(arguments[2]) ? arguments[2] : 0,
b = 0, bin,
@ -229,13 +203,12 @@ Ox.load.Image = function(options, callback) {
return mode & 1 << bit ? bit : null;
}),
cap = getCapacity(bits.length), len;
Ox.print("CAPACITY", cap);
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 = Ox.pad(Ox.encodeBase256(len), 4, '\u0000') + str;
str.length > cap && error('encode');
while (str.length < cap) {
str += str.substr(4, len);
@ -249,6 +222,7 @@ Ox.load.Image = function(options, callback) {
}));
b = 0;
that.forEach(function(rgba, xy) {
// If alpha is not 255, the RGB values may not be preserved
if (rgba[3] == 255) {
var index = getIndex(xy);
Ox.loop(3, function(c) {
@ -268,32 +242,70 @@ Ox.load.Image = function(options, callback) {
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);
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) {
self.context.fillRect(x, y, w, h);
return that;

View file

@ -1857,6 +1857,17 @@ Ox.element = function(str) {
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
(key) -> <s> Value
(key, value) -> <o> This element
@ -1912,6 +1923,15 @@ Ox.element = function(str) {
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
(className) -> <o> This element
className <s> Class name
@ -2092,13 +2112,12 @@ Ox.element = function(str) {
// Make sure we can encode the full unicode range of characters.
str = Ox.encodeUTF8(str);
// 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;
// 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');
// The string length may not be a multiple of 3, so we need to encode
// the number of padding bytes (1 byte), the string, and non-0-bytes
// as padding, so that the combined length becomes a multiple of 3.
var len = 1 + str.length, c = Ox.canvas(Math.ceil(len / 3), 1),
data, idat, pad = (3 - len % 3) % 3;
str = Ox.char(pad) + str + Ox.repeat('\u00FF', pad);
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;
@ -2106,15 +2125,17 @@ Ox.element = function(str) {
c.context.putImageData(c.imageData, 0, 0);
// 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.
data = Ox.sub(str, 16, 33); idat = Ox.sub(str, 33, -12);
// Discard bytes 0 to 15 (8 bytes PNG signature, 4 bytes IHDR length, 4
// bytes IHDR name), keep bytes 16 to 19 (width), discard bytes 20 to 29
// (4 bytes height, 5 bytes flags), keep bytes 29 to 32 (IHDR checksum),
// 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) {
// Each IDAT chunk consists of 4 bytes length, 4 bytes "IDAT" and
// length bytes data, so we can optimize by removing each "IDAT".
// Each IDAT chunk is 4 bytes length, 4 bytes name, length bytes
// data and 4 bytes checksum. We can discard the name parts.
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);
}
callback && callback(data);
@ -2135,24 +2156,24 @@ Ox.element = function(str) {
@*/
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;
// PNG file signature and IHDR chunk
data = '\u0089PNG\r\n\u001A\n\u0000\u0000\u0000\u000DIHDR'
+ str.substr(0, 4) + '\u0000\u0000\u0000\u0001'
+ '\u0008\u0006\u0000\u0000\u0000' + str.substr(4, 4),
// IDAT chunks
idat = str.substr(8), len;
function error() {
throw new RangeError('Deflate codec can\'t decode data.');
}
while (idat) {
// Reinsert the IDAT chunk names
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);
}
// IEND chunk
data += '\u0000\u0000\u0000\u0000IEND\u00AE\u0042\u0060\u0082';
// Unfortunately, we can't synchronously set the source of an image,
// draw it onto a canvas, and read its data.
image.onload = function() {
@ -2160,16 +2181,17 @@ Ox.element = function(str) {
// 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))))
);
try {
// Parse the first byte as number of bytes to chop at the end,
// and the rest, without these bytes, as an UTF8-encoded string.
str = Ox.decodeUTF8(str.substr(1, str.length - 1 - str.charCodeAt(0)))
} catch (e) {
error();
}
callback(str);
}
image.onerror = function() {
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);
}
/*@