oxjs/source/Ox.Image/Ox.Image.js

515 lines
18 KiB
JavaScript
Raw Normal View History

2011-06-19 15:40:53 +00:00
Ox.load.Image = function(options, callback) {
2011-09-08 21:39:07 +00:00
//@ 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)
@*/
2011-06-19 15:40:53 +00:00
Ox.Image = function() {
var self = {},
that = {};
self.callback = arguments[arguments.length - 1];
2011-06-19 15:40:53 +00:00
if (arguments.length == 2) {
self.src = arguments[0];
self.image = new Image();
self.image.onload = init;
self.image.src = self.src;
2011-06-19 15:40:53 +00:00
} else {
self.width = arguments[0];
self.height = arguments[1];
self.background = arguments.length == 4 ? arguments[2] : [0, 0, 0, 0];
init();
2011-06-19 15:40:53 +00:00
}
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) {
2011-09-08 12:27:40 +00:00
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;
2011-06-21 09:16:23 +00:00
}
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;
2011-06-21 09:16:23 +00:00
}
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);
2011-06-19 15:40:53 +00:00
}
self.imageData = self.context.getImageData(0, 0, self.width, self.height);
self.data = self.imageData.data;
self.callback(that);
}
2011-06-19 15:40:53 +00:00
function setSL(sl, d) {
var c = sl == 's' ? 1 : 2;
return that.map(function(rgba) {
2011-06-19 15:40:53 +00:00
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]);
2011-06-19 15:40:53 +00:00
});
}
2011-06-21 09:16:23 +00:00
that.blur = function(val) {
2011-09-08 21:39:07 +00: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);
2011-06-19 15:40:53 +00: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]);
}
2011-06-19 15:40:53 +00:00
});
}
2011-06-21 09:16:23 +00:00
that.contour = function(val) {
return that.filter([
2011-06-21 09:16:23 +00:00
+1, +1, +1,
+1, -7, +1,
+1, +1, +1
]);
};
2011-09-08 21:39:07 +00:00
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
});
});
}
2011-06-21 09:16:23 +00:00
that.edges = function(val) {
return that.filter([
2011-06-21 09:16:23 +00:00
-1, -1, -1,
-1, +8, -1,
-1, -1, -1
]).saturation(-1);
2011-06-21 09:16:23 +00:00
};
that.emboss = function(val) {
return that.filter([
2011-06-21 09:16:23 +00:00
-1, -1, +0,
-1, +0, +1,
+0, +1, +1
], 128).saturation(-1);
};
2011-09-08 21:39:07 +00:00
/*@
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]
2011-09-08 21:39:07 +00:00
: Ox.isBoolean(arguments[2]) ? arguments[2] : true,
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;
2011-09-08 21:39:07 +00:00
Ox.print("CAPACITY", cap)
// Compress the string
str = Ox[deflate ? 'encodeDeflate' : 'encodeUTF8'](str);
len = str.length;
// Prefix the string with its length, as a four-byte value
2011-09-08 21:39:07 +00:00
str = Ox.pad(Ox.encodeBase256(len), 4, '\u0000') + 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) {
2011-09-08 21:39:07 +00:00
// 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) {
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) {
2011-09-08 12:27:40 +00:00
return +!!(self.data[i] & 1 << bit);
})) % 2
// or the one bit in question
2011-09-08 12:27:40 +00:00
: +!!(self.data[i] & 1 << bit)
// is not equal to the data bit
) != bin[b++]) {
// then flip the bit
self.data[i] ^= 1 << bit;
}
2011-09-08 12:27:40 +00:00
});
});
}
});
self.context.putImageData(self.imageData, 0, 0);
callback(that);
2011-09-08 21:39:07 +00:00
return that;
};
2011-09-08 21:39:07 +00:00
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;
};
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;
2011-06-21 09:16:23 +00:00
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;
};
2011-06-19 15:40:53 +00:00
that.hue = function(val) {
return that.map(function(rgba) {
2011-06-19 15:40:53 +00:00
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]);
2011-06-19 15:40:53 +00:00
});
};
that.invert = function() {
return that.map(function(rgba) {
2011-06-19 15:40:53 +00:00
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);
2011-06-21 09:16:23 +00:00
return that;
};
that.motionBlur = function() {
return that.filter([
2011-06-21 09:16:23 +00:00
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) {
2011-09-08 12:27:40 +00:00
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;
});
}
}
2011-06-21 09:16:23 +00:00
that.posterize = function() {
return that.blur(3).map(function(rgba) {
2011-06-21 09:16:23 +00:00
return [
Math.floor(rgba[0] / 64) * 64,
Math.floor(rgba[1] / 64) * 64,
Math.floor(rgba[2] / 64) * 64,
rgba[3]
];
});
2011-06-19 15:40:53 +00:00
};
that.resize = function(width, height) {
self.canvas.attr({
width: width,
height: height
});
2011-06-21 09:16:23 +00:00
return that;
}
that.saturation = function(val) {
return setSL('s', val);
2011-06-21 09:16:23 +00:00
};
that.sharpen = function(val) {
return that.filter([
2011-06-21 09:16:23 +00:00
-1, -1, -1,
-1, +9, -1,
-1, -1, -1
]);
2011-06-19 15:40:53 +00:00
};
2011-06-21 09:16:23 +00:00
that.solarize = function() {
return that.map(function(rgba) {
2011-06-21 09:16:23 +00:00
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;
}
2011-06-19 15:40:53 +00:00
};
that.toDataURL = that.src;
2011-06-19 15:40:53 +00:00
};
callback(true);
}