oxjs/source/Ox.Image/Ox.Image.js
2011-09-08 12:27:40 +00:00

502 lines
18 KiB
JavaScript

Ox.load.Image = function(options, callback) {
Ox.Image = function() {
var self = {},
that = {};
self.callback = arguments[arguments.length - 1];
if (arguments.length == 2) {
self.src = arguments[0];
self.image = new Image();
self.image.onload = init;
self.image.src = self.src;
} else {
self.width = arguments[0];
self.height = arguments[1];
self.background = arguments.length == 4 ? arguments[2] : [0, 0, 0, 0];
init();
}
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 * 3/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) {
index /= 4;
return [index % self.width, Math.floor(index / self.width)];
}
function init() {
if (self.image) {
self.width = self.image.width;
self.height = self.image.height;
}
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.imageData = self.context.getImageData(0, 0, self.width, self.height);
self.data = self.imageData.data;
self.callback(that);
}
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 Ox.merge(rgb, rgba[3]);
});
}
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
]);
};
that.channel = function(str) {
str = str[0].toLowerCase();
return that.map(function(rgba) {
var i = ['r', 'g', 'b', 'a'].indexOf(str), rgb, val;
if (i > -1) {
return Ox.map(rgba, function(v, c) {
return str == 'a'
? (c < 3 ? rgba[3] : 255)
: (c == i || c == 3 ? v : 0);
});
} else {
i = ['h', 's', 'l'].indexOf(str);
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]);
}
});
}
that.contour = function(val) {
return that.filter([
+1, +1, +1,
+1, -7, +1,
+1, +1, +1
]);
};
that.edges = function(val) {
return that.filter([
-1, -1, -1,
-1, +8, -1,
-1, -1, -1
]).saturation(-1);
};
that.emboss = function(val) {
return that.filter([
-1, -1, +0,
-1, +0, +1,
+0, +1, +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(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');
}
}
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
// If the number of bits set to 1, mod 2
? Ox.sum(Ox.range(8).map(function(bit) {
return +!!(self.data[i] & 1 << bit);
})) % 2
// or the one bit in question
: +!!(self.data[i] & 1 << bit)
// is not equal to the data bit
) != bin[b++]) {
// then flip the bit
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) {
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 Ox.merge(rgb, rgba[3]);
});
};
that.invert = function() {
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() {
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
]);
};
that.pixel = function(x, y, val) {
var i = 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() {
return that.blur(3).map(function(rgba) {
return [
Math.floor(rgba[0] / 64) * 64,
Math.floor(rgba[1] / 64) * 64,
Math.floor(rgba[2] / 64) * 64,
rgba[3]
];
});
};
that.resize = function(width, height) {
self.canvas.attr({
width: width,
height: height
});
return that;
}
that.saturation = function(val) {
return setSL('s', val);
};
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]
];
});
}
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;
}
};
that.toDataURL = that.src;
};
callback(true);
}