diff --git a/demos/image/index.html b/demos/image/index.html
new file mode 100644
index 00000000..1be83b10
--- /dev/null
+++ b/demos/image/index.html
@@ -0,0 +1,10 @@
+
+
+
+ OxJS Image Demo
+
+
+
+
+
\ No newline at end of file
diff --git a/demos/image/js/image.js b/demos/image/js/image.js
new file mode 100644
index 00000000..1d8eb083
--- /dev/null
+++ b/demos/image/js/image.js
@@ -0,0 +1,11 @@
+Ox.load('Image', function() {
+
+ Ox.Image('png/Netherlandish Proverbs.png', function(image) {
+ Ox.element('').attr({src: image.url()}).appendTo(Ox.element('body'));
+ image.saturation(-0.9);
+ Ox.element('').attr({src: image.url()}).appendTo(Ox.element('body'));
+ image.brightness(-0.5);
+ Ox.element('').attr({src: image.url()}).appendTo(Ox.element('body'));
+ });
+
+});
\ No newline at end of file
diff --git a/demos/image/png/Netherlandish Proverbs.png b/demos/image/png/Netherlandish Proverbs.png
new file mode 100644
index 00000000..5148cfe7
Binary files /dev/null and b/demos/image/png/Netherlandish Proverbs.png differ
diff --git a/source/Ox.Image/Ox.Image.js b/source/Ox.Image/Ox.Image.js
new file mode 100644
index 00000000..4ec1558f
--- /dev/null
+++ b/source/Ox.Image/Ox.Image.js
@@ -0,0 +1,110 @@
+Ox.load.Image = function(options, callback) {
+
+ Ox.Image = function() {
+
+ var self = {},
+ that = {};
+
+ if (arguments.length == 2) {
+ self.src = arguments[0];
+ self.callback = arguments[1];
+ } else {
+ self.width = arguments[0];
+ self.height = arguments[1];
+ self.callback = arguments[2];
+ }
+
+ self.channels = 'RGBA';
+
+ self.map = function(fn) {
+ var imageData = self.context.getImageData(0, 0, self.width, self.height),
+ i, n = imageData.data.length;
+ for (var i = 0; i < n; i += 4) {
+ var data = fn([
+ imageData.data[i], imageData.data[i + 1],
+ imageData.data[i + 2], imageData.data[i + 3]
+ ]);
+ for (var 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) {
+ 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]];
+ });
+ }
+
+ that.brightness = function(val) {
+ self.setSL('brightness', val);
+ };
+
+
+ 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.hue = function(val) {
+ self.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]];
+ });
+ };
+
+ that.invert = function() {
+ self.map(function(rgba) {
+ return [255 - rgba[0], 255 - rgba[1], 255 - rgba[2], rgba[3]];
+ });
+ };
+
+ that.saturation = function(val) {
+ self.setSL('saturation', val);
+ };
+
+ that.url = function() {
+ return self.canvas.toDataURL();
+ };
+
+ Ox.print(self);
+ if (self.src) {
+ self.image = new Image();
+ self.image.onload = init;
+ self.image.src = self.src;
+ } else {
+ init();
+ }
+
+ function init() {
+ if (self.image) {
+ self.width = self.image.width;
+ self.height = self.image.height;
+ }
+ self.canvas = Ox.element('