move image encoding functions to image module

This commit is contained in:
rlx 2011-09-08 01:53:19 +00:00
parent 816993e1b9
commit 842d536dc8
4 changed files with 416 additions and 147 deletions

View file

@ -5,23 +5,60 @@ Ox.load('Image', function() {
Ox.Image('png/Lenna.png', function(image) {
var body = Ox.element('body'),
select = Ox.element('<select>').appendTo(body);
['Filter...', 'blur', 'channel', 'contour', 'invert', 'edges', 'emboss', 'motionBlur', 'posterize', 'sharpen', 'solarize'].forEach(function(filter) {
[
'Method...', 'blur(1)', 'blur(2)', 'blur(3)',
'channel("r")', 'channel("g")', 'channel("b")',
'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", 127)', 'decode(127)',
'hue(0)', 'hue(120)', 'hue(240)',
'invert()',
'lightness(-0.5)', 'lightness(0.5)',
'motionBlur()', 'posterize()',
'saturation(-0.5)', 'saturation(0.5)',
'sharpen()', 'solarize()', 'src("png/Lenna.png")'
].forEach(function(filter) {
Ox.element('<option>').html(filter).appendTo(select);
});
select[0].onchange = function() {
if (select[0].value[0] == select[0].value[0].toLowerCase()) {
Ox.element('#filter').attr({
src: image[select[0].value]().url()
});
var split = select[0].value.split('('),
fn = split[0],
args = split[1].length == 1 ? []
: split[1].split(')')[0].split(', ').map(function(v) {
return v == 'true' ? true
: v == 'false'? false
: v[0] == '"' ? v.split('"')[1]
: parseFloat(v);
});
if (fn == 'encode' || fn == 'src') {
image[fn].apply(null, Ox.merge(args, function(image) {
Ox.element('#filter').attr({
src: image.src()
});
}));
} else if (fn == 'decode') {
image[fn].apply(null, Ox.merge(args, function(str) {
alert(str);
}));
} else {
Ox.element('#filter').attr({
src: image[fn].apply(null, args).src()
});
}
select[0].selectedIndex = 0;
}
}
Ox.element('<br>').appendTo(body);
Ox.element('<img>').attr({
src: image.url()
src: image.src()
}).appendTo(body);
Ox.element('<img>').attr({
id: 'filter',
src: image.url()
src: image.src()
}).appendTo(body);
//Ox.element('<img>').attr({src: image.saturation(0.5).blur().url()}).appendTo(Ox.element('body'));
});

BIN
demos/image/png/Lenna.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 464 KiB

View file

@ -5,17 +5,43 @@ Ox.load.Image = function(options, callback) {
var self = {},
that = {};
self.callback = arguments[arguments.length - 1];
if (arguments.length == 2) {
self.src = arguments[0];
self.callback = arguments[1];
self.image = new Image();
self.image.onload = init;
self.image.src = self.src;
} else {
self.width = arguments[0];
self.height = arguments[1];
self.callback = arguments[2];
self.background = arguments.length == 4 ? arguments[2] : [0, 0, 0, 0];
init();
}
function getIndex(x, y) {
return (Ox.mod(x, self.width) + Ox.mod(y * self.width, self.width * self.height)) * 4;
function error(mode) {
throw new RangeError('PNG codec can\'t ' + mode + ' ' + (
mode == 'encode' ? 'data' : 'image'
));
}
function getCapacity(bpb) {
var capacity = 0;
that.forEach(function(rgba) {
capacity += rgba[3] == 255 ? bpb / 8 : 0;
});
return capacity;
}
function getIndex() {
if (arguments.length == 1) {
xy = arguments[0];
} else {
xy = [arguments[0], arguments[1]];
}
return (
Ox.mod(xy[0], self.width)
+ Ox.mod(xy[1] * self.width, self.width * self.height)
) * 4;
}
function getXY(index) {
@ -23,67 +49,39 @@ Ox.load.Image = function(options, callback) {
return [index % self.width, Math.floor(index / self.width)];
}
self.filter = function(filter, bias) {
bias = bias || 0;
var filterSize = Math.sqrt(filter.length),
d = (filterSize - 1) / 2,
imageData = self.context.getImageData(0, 0, self.width, self.height),
imageDataNew = self.context.createImageData(self.width, self.height),
data = [],
n = imageData.data.length,
xy, filterIndex, pixelIndex;
for (var i = 0; i < n; i += 4) {
for (var c = 0; c < 3; c++) {
data[i + c] = 0;
}
filterIndex = 0;
xy = getXY(i);
for (var x = -d; x <= d; x++) {
for (var y = -d; y <= d; y++) {
pixelIndex = getIndex(xy[0] + x, xy[1] + y);
for (var c = 0; c < 3; c++) {
data[i + c] += imageData.data[pixelIndex + c] * filter[filterIndex];
}
filterIndex++;
}
}
function init() {
if (self.image) {
self.width = self.image.width;
self.height = self.image.height;
}
for (var i = 0; i < n; i += 4) {
for (var c = 0; c < 3; c++) {
imageDataNew.data[i + c] = Ox.limit(Math.round(data[i + c] + bias), 0, 255)
}
imageDataNew.data[i + 3] = 255;
self.canvas = Ox.element('<canvas>').attr({
width: self.width,
height: self.height
});
self.context = self.canvas[0].getContext('2d');
if (self.image) {
self.context.drawImage(self.image, 0, 0);
} else if (Ox.sum(self.background)) {
that.fillStyle(self.background);
that.fillRect(0, 0, self.width, self.height);
}
self.context.putImageData(imageDataNew, 0, 0);
};
self.imageData = self.context.getImageData(0, 0, self.width, self.height);
self.data = self.imageData.data;
self.callback(that);
}
self.map = function(fn) {
var imageData = self.context.getImageData(0, 0, self.width, self.height),
i, n = imageData.data.length, c;
for (i = 0; i < n; i += 4) {
var data = fn([
imageData.data[i], imageData.data[i + 1],
imageData.data[i + 2], imageData.data[i + 3]
], getXY(i));
for (c = 0; c < 4; c++) {
imageData.data[i + c] = data[c];
};
}
self.context.putImageData(imageData, 0, 0);
};
self.setSL = function(sl, d) {
var c = sl == 'saturation' ? 1 : 2;
self.map(function(rgba) {
function setSL(sl, d) {
var c = sl == 's' ? 1 : 2;
return that.map(function(rgba) {
var hsl = Ox.hsl([rgba[0], rgba[1], rgba[2]]), rgb;
hsl[c] = d < 0 ? hsl[c] * (d + 1) : hsl[c] + (1 - hsl[c]) * d;
rgb = Ox.rgb(hsl);
return [rgb[0], rgb[1], rgb[2], rgba[3]];
return Ox.merge(rgb, rgba[3]);
});
}
that.blur = function(val) {
self.filter(val == 1 ? [
return that.filter(val == 1 ? [
0.0, 0.2, 0.0,
0.2, 0.2, 0.2,
0.0, 0.2, 0.0
@ -102,83 +100,320 @@ Ox.load.Image = function(options, callback) {
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
]);
return that;
};
that.brightness = function(val) {
self.setSL('brightness', val);
return that;
};
that.channel = function(channel) {
self.map(function(rgba) {
var hue = Ox.hsl([rgba[0], rgba[1], rgba[2]])[0],
rgb = Ox.rgb([hue, 1, 0.5]);
return [rgb[0], rgb[1], rgb[2], rgba[3]];
that.channel = function(c) {
c = c.toLowerCase();
return that.map(function(rgba) {
var i = ['r', 'g', 'b'].indexOf(c), rgb, val;
if (i > -1) {
return Ox.map(rgba, function(v, c) {
return c == i || c == 3 ? v : 0;
});
} else {
i = ['h', 's', 'l'].indexOf(c);
val = Ox.hsl([rgba[0], rgba[1], rgba[2]])[i];
rgb = i == 0
? Ox.rgb([val, 1, 0.5])
: Ox.map(Ox.range(3), function(v) {
return parseInt(val * 255);
});
return Ox.merge(rgb, rgba[3]);
}
});
return that;
}
that.contour = function(val) {
self.filter([
return that.filter([
+1, +1, +1,
+1, -7, +1,
+1, +1, +1
]);
return that;
};
that.edges = function(val) {
self.filter([
return that.filter([
-1, -1, -1,
-1, +8, -1,
-1, -1, -1
]);
that.saturation(-1);
return that;
]).saturation(-1);
};
that.emboss = function(val) {
self.filter([
return that.filter([
-1, -1, +0,
-1, +0, +1,
+0, +1, +1
], 128);
that.saturation(-1);
], 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(b) {
return self.data[i] & 1 << b;
})) % 2;
} else {
bin += +!!(self.data[i] & i << 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("LEN", len)
if (len + 4 > getCapacity(1)) {
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) {
var callback = arguments[arguments.length - 1],
deflate = Ox.isBoolean(arguments[1]) ? arguments[1]
: Ox.isBoolean(arguments[2]) ? arguments[2] : false,
mode = Ox.isNumber(arguments[1]) ? arguments[1]
: Ox.isNumber(arguments[2]) ? arguments[2] : 0,
b = 0, bin,
bits = mode < 1 ? [-mode] : Ox.map(Ox.range(8), function(bit) {
return mode & 1 << bit ? bit : null;
}),
cap = getCapacity(bits.length), len;
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.length > cap && error('encode');
while (str.length < cap) {
str += str.substr(4, len);
}
str = str.substr(0, Math.ceil(cap));
// Create an array of bit values
bin = Ox.flatten(Ox.map(str, function(chr) {
return Ox.map(Ox.range(8), function(i) {
return chr.charCodeAt(0) >> 7 - i & 1;
});
}));
b = 0;
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
? Ox.sum(Ox.range(8).map(function(bit) {
return self.data[i] & 1 << bit;
})) % 2
: self.data[i] & 1 << bit
) != bin[b++]) {
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);
};
that.fillRect = function(x, y, w, h) {
self.context.fillRect(x, y, w, h);
return that;
};
that.fillStyle = function() {
if (arguments.length == 0) {
return self.context.fillStyle;
} else {
self.context.fillStyle = arguments[0];
return that;
}
};
that.filter = function(filter, bias) {
bias = bias || 0;
var filterSize = Math.sqrt(filter.length),
d = (filterSize - 1) / 2,
imageData = self.context.createImageData(self.width, self.height),
data = [];
self.imageData = self.context.getImageData(0, 0, self.width, self.height);
self.data = self.imageData.data;
Ox.loop(0, self.data.length, 4, function(i) {
var filterIndex = 0,
xy = getXY(i);
Ox.loop(3, function(c) {
data[i + c] = 0;
});
Ox.loop(-d, d + 1, function(x) {
Ox.loop(-d, d + 1, function(y) {
var pixelIndex = getIndex(xy[0] + x, xy[1] + y);
Ox.loop(3, function(c) {
data[i + c] += self.data[pixelIndex + c] * filter[filterIndex];
});
filterIndex++;
});
});
});
Ox.loop(0, self.data.length, 4, function(i) {
Ox.loop(4, function(c) {
imageData.data[i + c] = c < 3
? Ox.limit(Math.round(data[i + c] + bias), 0, 255)
: self.data[i + c];
});
});
self.context.putImageData(imageData, 0, 0);
self.imageData = imageData;
self.data = data;
return that;
};
/*@
forEach <f> Pixel-wise forEach function
(fn) -> <o> The image object
fn <f> Iterator function
rgba <[n]> RGBA values
xy <[n]> XY coordinates
@*/
that.forEach = function(fn) {
Ox.loop(0, self.data.length, 4, function(i) {
return fn([
self.data[i], self.data[i + 1],
self.data[i + 2], self.data[i + 3]
], getXY(i));
})
return that;
};
that.hue = function(val) {
self.map(function(rgba) {
return that.map(function(rgba) {
var hsl = Ox.hsl([rgba[0], rgba[1], rgba[2]]), rgb;
hsl[0] = (hsl[0] + val) % 360;
rgb = Ox.rgb(hsl);
return [rgb[0], rgb[1], rgb[2], rgba[3]];
return Ox.merge(rgb, rgba[3]);
});
return that;
};
that.invert = function() {
self.map(function(rgba) {
return that.map(function(rgba) {
return [255 - rgba[0], 255 - rgba[1], 255 - rgba[2], rgba[3]];
});
};
that.lightness = function(val) {
return setSL('l', val);
};
/*@
map <f> Pixel-wise map function
(fn) -> <o> The image object
fn <f> Iterator function
rgba <[n]> RGBA values
xy <[n]> XY coordinates
@*/
that.map = function(fn) {
self.imageData = self.context.getImageData(0, 0, self.width, self.height);
self.data = self.imageData.data;
Ox.loop(0, self.data.length, 4, function(i) {
fn([
self.data[i], self.data[i + 1],
self.data[i + 2], self.data[i + 3]
], getXY(i)).forEach(function(val, c) {
self.data[i + c] = val;
});
})
self.context.putImageData(self.imageData, 0, 0);
return that;
};
that.motionBlur = function() {
self.filter([
return that.filter([
0.2, 0.0, 0.0, 0.0, 0.0,
0.0, 0.2, 0.0, 0.0, 0.0,
0.0, 0.0, 0.2, 0.0, 0.0,
0.0, 0.0, 0.0, 0.2, 0.0,
0.0, 0.0, 0.0, 0.0, 0.2
]);
return that;
};
that.pixel = function(x, y, val) {
var index = getIndex(x, y);
if (!val) {
return Ox.range(4).map(function(c) {
return self.data[i + c];
});
} else {
val.forEach(function(v, c) {
self.data[i + c] = v;
});
}
}
that.posterize = function() {
that.blur(3);
self.map(function(rgba) {
return that.blur(3).map(function(rgba) {
return [
Math.floor(rgba[0] / 64) * 64,
Math.floor(rgba[1] / 64) * 64,
@ -186,35 +421,8 @@ Ox.load.Image = function(options, callback) {
rgba[3]
];
});
return that;
};
that.saturation = function(val) {
self.setSL('saturation', val);
return that;
};
that.sharpen = function(val) {
self.filter([
-1, -1, -1,
-1, +9, -1,
-1, -1, -1
]);
return that;
};
that.solarize = function() {
self.map(function(rgba) {
return [
rgba[0] < 128 ? rgba[0] : 255 - rgba[0],
rgba[1] < 128 ? rgba[1] : 255 - rgba[1],
rgba[2] < 128 ? rgba[2] : 255 - rgba[2],
rgba[3]
];
});
return that;
}
that.resize = function(width, height) {
self.canvas.attr({
width: width,
@ -223,34 +431,54 @@ Ox.load.Image = function(options, callback) {
return that;
}
that.url = function() {
return self.canvas[0].toDataURL();
return that;
that.saturation = function(val) {
return setSL('s', val);
};
if (self.src) {
self.image = new Image();
self.image.onload = init;
self.image.src = self.src;
} else {
init();
that.sharpen = function(val) {
return that.filter([
-1, -1, -1,
-1, +9, -1,
-1, -1, -1
]);
};
that.solarize = function() {
return that.map(function(rgba) {
return [
rgba[0] < 128 ? rgba[0] : 255 - rgba[0],
rgba[1] < 128 ? rgba[1] : 255 - rgba[1],
rgba[2] < 128 ? rgba[2] : 255 - rgba[2],
rgba[3]
];
});
}
function init() {
if (self.image) {
self.width = self.image.width;
self.height = self.image.height;
that.src = function() {
if (arguments.length == 0) {
return self.canvas[0].toDataURL();
} else {
var callback = arguments[1];
self.src = arguments[0];
self.image = new Image();
self.image.onload = function() {
self.width = self.image.width;
self.height = self.image.height;
self.canvas.attr({
width: self.width,
height: self.height
});
self.context.drawImage(self.image, 0, 0);
self.imageData = self.context.getImageData(0, 0, self.width, self.height);
self.data = self.imageData.data;
callback && callback(that);
}
self.image.src = self.src;
return that;
}
self.canvas = Ox.element('<canvas>').attr({
width: self.width,
height: self.height
});
self.context = self.canvas[0].getContext('2d');
if (self.image) {
self.context.drawImage(self.image, 0, 0);
}
self.callback(that);
}
};
that.toDataURL = that.src;
};

View file

@ -309,12 +309,15 @@ Ox.flatten = function(arr) {
Ox.merge <f> Merges an array with one or more other arrays
> Ox.merge([1], [2, 3, 2], [1])
[1, 2, 3, 2, 1]
> Ox.merge(1, [2, 3, 2], 1)
[1, 2, 3, 2, 1]
@*/
Ox.merge = function(arr) {
arr = Ox.isArray(arr) ? arr : [arr];
Ox.forEach(Array.prototype.slice.call(arguments, 1), function(arg) {
Ox.forEach(arg, function(val) {
Ox.isArray(arg) ? Ox.forEach(arg, function(val) {
arr.push(val);
});
}) : arr.push(arg);
});
return arr;
};
@ -2082,14 +2085,15 @@ Ox.element = function(str) {
head, tail and chunk names are removed.
(str) -> <s> The encoded string
str <s> The string to be encoded
# Test with: Ox.decodeDeflate(Ox.encodeDeflate('foo'), alert)
@*/
Ox.encodeDeflate = function(str) {
Ox.encodeDeflate = function(str, callback) {
// 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, ihdr;
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.
@ -2105,7 +2109,7 @@ Ox.element = function(str) {
// 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);
data = 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".
@ -2113,7 +2117,8 @@ Ox.element = function(str) {
data += len + idat.substr(8, 12 + (len = Ox.decodeBase256(len)));
idat = idat.substr(12 + len);
}
return ihdr + data;
callback && callback(data);
return data;
}
/*@
@ -2127,7 +2132,6 @@ Ox.element = function(str) {
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) {