I have recently started researching some computer vision, and in the process of discovering some interesting things I have been compiling some functions that allow me to manipulate images at a low level. I am not interested in replacing any of these functions with already existing, built-in functionality of canvas (but if there are some it is always interesting to learn about them), I am trying to get a bit more low-level here.
I usually do front-ends of feature sites, so I have only cursory experience with the creation of large libraries, and I am wondering whether the style of code and way of implementing it is acceptable and what could - and what should - be improved. I have tested these functions and they do what I intended, so to me it is not about how they function precisely (I am not a mathematician, and I'm sure mathematicians can figure out better and more clever ways to reach these results). By all means, tell me about better ways, but I am trying to see if this would work as a library. I am not afraid to change functions to use core canvas constructs, but my reason for building this was to learn to manipulate pixel based graphics myself, without someone who has written native code for me.
If you run the snippet you will see some use case examples (in order of code they are: normal, blur (radius of 2), grayscale, desaturate (50%) and colour channel (red) - although they may appear in different order depending on which one loads faster). I had to base64 encode an image as I couldn't find a way around cross-origin restrictions otherwise, but of course it works with local loading of images.
The end result would be that you could use this to load an image and apply some effects, and then insert it into the DOM with the following syntax:
Usage
var myImage = new ImageManipulation({
image: 'http::domain.tld/image.ext',
callback: function(Image){
// Manipulate the image on load and append to DOM
Image.Blur(2).Desaturate(55).DOM(document.body);
}
});
Code
"use strict";
function ImageManipulation(options){
var canvasSupport = document.createElement('canvas');
if(!(canvasSupport.getContext && canvasSupport.getContext('2d')))
throw('Initialisation failed: support for <canvas> is missing.');
this.src = options.image || false;
if(!this.src)
throw('Initialisation failed: no image in options.');
/* Canvas for input. */
this.input = document.createElement('canvas');
/* Canvas for output. */
this.output = document.createElement('canvas');
/* Canvas Context for input. */
this.inputContext = this.input.getContext('2d');
/* Canvas Context for output. */
this.outputContext = this.output.getContext('2d');
/* Defaults (changed on Init). */
this.width = 0;
this.height = 0;
this.ready = false;
/* Source should not be changed after Init(). */
this.source = false;
/* Manipulation should be done on resource after Init() */
this.resource = false;
this.image = document.createElement('img');
this.image .addEventListener('load', this.Init.bind(this));
this.image .addEventListener('error', function(){
throw('Initialisation failed: image loading returned error.')
});
/* options.development:Func - defaults to 'throw'. */
this.development = options.development && options.development instanceof Function
? options.development
: 'throw';
this.autoUpdate = options.autoUpdate || false;
this.callback = options.callback && options.callback instanceof Function
? options.callback
: function(){};
this.image .setAttribute('src', this.src);
// Returns this
return this;
}
ImageManipulation.prototype = {
/***********************************************************/
/********************** Core Functions *********************/
/***********************************************************/
/* Init() -> void
* gets called automatically. Initialises all values and resources. */
Init: function(){
if(this.ready)
return this.Throw("Init was ready. Cannot be called again.");
this.width = this.image.width;
this.height = this.image.height;
this.input.width = this.width;
this.input.height = this.height;
this.output.width = this.width;
this.output.height = this.height;
this.inputContext .drawImage(this.image, 0, 0);
this.source = this.inputContext.getImageData(0, 0, this.width, this.height);
this.resource = this.inputContext.getImageData(0, 0, this.width, this.height);
this.Support.Self = this;
this.ready = true;
this.callback(this);
},
/* Apply() -> this
* save the changes to the source. */
Apply: function(){
this.source = this.resource;
if(this.autoUpdate) this.Draw();
return this;
},
/* Draw() -> this
* update the canvas. */
Draw: function(){
this.outputContext.putImageData(new ImageData(
this.resource.data, this.width, this.height
), 0, 0);
return this;
},
/* Reset() -> this
* reset the image to the original values (undo all manipulations). */
Reset: function(){
this.resource = this.source;
if(this.autoUpdate) this.Draw();
return this;
},
/* DOM(element?:<dom>) -> <canvas>
* returns the <canvas> DOM element or appends it to the passed element. */
DOM: function(element){
this.Draw();
if(element){
element.appendChild(this.output);
return true;
} else {
return this.output;
}
},
/* Throw(message:String) -> this?
* internal error reporting function. */
Throw: function(message){
if(this.development === 'throw') throw(message);
else this.development(message);
return this;
},
/* Warn(message:String) -> this?
* internal warning reporting function. */
Warn: function(message){
if(console && console.log) console.log(message);
return this;
},
/* isReady() -> Bool
* return true or false and throws an error if a callee is passed. */
isReady: function(callee){
if(!this.ready){
if(callee){
this.Throw(callee + " called before ready. Use callback to ensure readiness.");
}
return false;
} else {
return this;
}
},
/***********************************************************/
/*********************** Manipulations *********************/
/***********************************************************/
/* Blur(radius:Int:%2=0) -> this
* Blurs by radius amount (radius needs to be even). */
Blur: function(radius){
this.isReady('Blur');
if(!radius || isNaN(radius))
return this.Throw("Blur @param radius:Int is required.");
if(radius%2)
return this.Throw("Blur @param radius:Int needs to be even.");
if(radius > 5)
this.Warn("Blur @param radius:Int is large and may cause slowdown.");
var blur = 1 / ((radius+1) * (radius+1));
for(var e = 0; e < this.source.data.length; e+=4){
var a = [0,0,0,255];
for(var y = -radius/2; y <= radius/2; y++){
for(var x = -radius/2; x <= radius/2; x++){
var position = e - (this.width * y + x) * 4;
for(var rgba = 0; rgba < 3; rgba++){
if(this.source.data[position+rgba])
a[rgba] += this.source.data[position+rgba] * blur;
else a[rgba] += 255 * blur;
}
}}
for(var rgba = 0; rgba < 4; rgba++){
this.resource.data[e+rgba] = a[rgba];
}
}
if(this.autoUpdate) this.Draw();
return this;
},
/* GrayScale([forHumans:Bool]) -> this
* Turn into grayscale. `forHumans` uses different values for human perception. */
GrayScale: function(forHumans){
this.isReady('GrayScale');
return this.Desaturate(100, forHumans);
},
/* Desaturate(percentage:Int, [forHumans:Bool]) -> this
* Equalise r, g and b values to the average by the passed percentage. */
Desaturate: function(percentage, forHumans){
this.isReady('Desaturate');
forHumans = forHumans
? [.3,.4,.3]
: [(1/3),(1/3),(1/3)];
for(var p = 0; p < this.source.data.length; p+=4){
var avg = 0;
for(var i = 0; i < 3; i++){
avg += this.source.data[p+i] * forHumans[i];
}
for(var i = 0; i < 3; i++){
this.resource.data[p + i] = Math.round(this.source.data[p + i] + (avg - this.source.data[p + i]) / 100 * percentage);
}
}
if(this.autoUpdate) this.Draw();
return this;
},
/* Channel(channel:Char:r,g,b,a) -> this
* Display channel in single color. Accepts `r`,`g`,`b` or `a`. */
Channel: function(channel){
this.isReady('Channel');
if(channel == 'r') channel = 0;
if(channel == 'g') channel = 1;
if(channel == 'b') channel = 2;
if(channel == 'a') channel = 3;
if(channel != 0 && channel != 1 && channel != 2 && channel != 3)
return this.Throw('Channel has to be either r, g, b or a');
for(var i = 0; i < this.source.data.length; i += 4){
var alpha = channel == 3 ? this.source.data[i+3] : 0;
this.resource.data[i] = channel == 0 ? this.source.data[i] : alpha;
this.resource.data[i+1] = channel == 1 ? this.source.data[i+1] : alpha;
this.resource.data[i+2] = channel == 2 ? this.source.data[i+2] : alpha;
this.resource.data[i+3] = 255;
}
if(this.autoUpdate) this.Draw();
return this;
},
/***********************************************************/
/********************* Support Functions *******************/
/***********************************************************/
Support: {
Self: false,
getPixelAtAxisFromPixel: function(id, angle){
var add, angle, addLine, Self;
Self = this.Self || {width: 0};
angle = angle%360;
if(angle%45) Self.Warn("getPixelAtAxisFromPixel @angle:Int[45*n] converted to Axis");
angle = Math.round(angle / 45) * 45;
switch(angle){
case 0: add = 1; break;
case 45: add = Self.width + 1; break;
case 90: add = Self.width; break;
case 135: add = Self.width - 1; break;
case 180: add = -1; break;
case 225: add = -Self.width - 1; break;
case 270: add = -Self.width; break;
case 315: add = -Self.width + 1; break;
case 360: add = 1; break;
default: add = 0;
}
add = id + add;
// Check whether there is a linedifference of more than 2 (edge-case)
id = Math.floor(id / Self.width);
addLine = Math.floor(add / Self.width);
if(addLine < id-1 || addLine > id+1){
return false;
} else {
return add;
}
}
}
};
/***********************************************************\
* Please note the below is for the example only and not *
* really relevant to comment on. *
\***********************************************************/
var image = "data:image/jpeg;base64,/9j/4QAYRXhpZgAASUkqAAgAAAAAAAAAAAAAAP/sABFEdWNreQABAAQAAABVAAD/4QMvaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wLwA8P3hwYWNrZXQgYmVnaW49Iu+7vyIgaWQ9Ilc1TTBNcENlaGlIenJlU3pOVGN6a2M5ZCI/PiA8eDp4bXBtZXRhIHhtbG5zOng9ImFkb2JlOm5zOm1ldGEvIiB4OnhtcHRrPSJBZG9iZSBYTVAgQ29yZSA1LjUtYzAyMSA3OS4xNTU3NzIsIDIwMTQvMDEvMTMtMTk6NDQ6MDAgICAgICAgICI+IDxyZGY6UkRGIHhtbG5zOnJkZj0iaHR0cDovL3d3dy53My5vcmcvMTk5OS8wMi8yMi1yZGYtc3ludGF4LW5zIyI+IDxyZGY6RGVzY3JpcHRpb24gcmRmOmFib3V0PSIiIHhtbG5zOnhtcE1NPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvbW0vIiB4bWxuczpzdFJlZj0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL3NUeXBlL1Jlc291cmNlUmVmIyIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bXBNTTpEb2N1bWVudElEPSJ4bXAuZGlkOjVEQjQ3QTMwMUVGNzExRTVBOTlCQTU4NjdCQUQwMjIyIiB4bXBNTTpJbnN0YW5jZUlEPSJ4bXAuaWlkOjU0OEVDODAwMUU5NTExRTVBOTlCQTU4NjdCQUQwMjIyIiB4bXA6Q3JlYXRvclRvb2w9IkFkb2JlIFBob3Rvc2hvcCBDQyAyMDE0IE1hY2ludG9zaCI+IDx4bXBNTTpEZXJpdmVkRnJvbSBzdFJlZjppbnN0YW5jZUlEPSJ4bXAuaWlkOjU0OEVDN0ZDMUU5NTExRTVBOTlCQTU4NjdCQUQwMjIyIiBzdFJlZjpkb2N1bWVudElEPSJ4bXAuZGlkOjU0OEVDN0ZEMUU5NTExRTVBOTlCQTU4NjdCQUQwMjIyIi8+IDwvcmRmOkRlc2NyaXB0aW9uPiA8L3JkZjpSREY+IDwveDp4bXBtZXRhPiA8P3hwYWNrZXQgZW5kPSJyIj8+/+4ADkFkb2JlAGTAAAAAAf/bAIQAAgEBAQEBAgEBAgMCAQID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"
new ImageManipulation({
image: image,
callback: function(Image){
Image.DOM(document.body);
}
});
new ImageManipulation({
image: image,
callback: function(Image){
Image.Blur(2).DOM(document.body);
}
});
new ImageManipulation({
image: image,
callback: function(Image){
Image.GrayScale().DOM(document.body);
}
});
new ImageManipulation({
image: image,
callback: function(Image){
Image.Desaturate(50).DOM(document.body);
}
});
new ImageManipulation({
image: image,
callback: function(Image){
Image.Channel('r').DOM(document.body);
}
});