Introduction
L'élément canevas HTML5 peut être utilisé pour créer des filtres d'image. Vous devez dessiner une image sur un canevas, lire les pixels du canevas et exécuter votre filtre dessus. Vous pouvez ensuite écrire le résultat sur un nouveau canevas (ou simplement réutiliser l'ancien).
Cela semble simple ? Très bien. Au boulot !
Traitement des pixels...
Commencez par récupérer les pixels de l'image:
Filters = {};
Filters.getPixels = function(img) {
var c = this.getCanvas(img.width, img.height);
var ctx = c.getContext('2d');
ctx.drawImage(img);
return ctx.getImageData(0,0,c.width,c.height);
};
Filters.getCanvas = function(w,h) {
var c = document.createElement('canvas');
c.width = w;
c.height = h;
return c;
};
Nous devons ensuite trouver un moyen de filtrer les images. Que diriez-vous d'une méthode filterImage
qui prend un filtre et une image, puis renvoie les pixels filtrés ?
Filters.filterImage = function(filter, image, var_args) {
var args = [this.getPixels(image)];
for (var i=2; i<arguments.length; i++) {
args.push(arguments[i]);
}
return filter.apply(null, args);
};
Exécuter des filtres simples
Maintenant que le pipeline de traitement des pixels est créé, il est temps d'écrire quelques filtres simples. Pour commencer, convertissons l'image en niveaux de gris.
Filters.grayscale = function(pixels, args) {
var d = pixels.data;
for (var i=0; i<d.length; i+=4) {
var r = d[i];
var g = d[i+1];
var b = d[i+2];
// CIE luminance for the RGB
// The human eye is bad at seeing red and blue, so we de-emphasize them.
var v = 0.2126*r + 0.7152*g + 0.0722*b;
d[i] = d[i+1] = d[i+2] = v
}
return pixels;
};
Pour ajuster la luminosité, ajoutez une valeur fixe aux pixels:
Filters.brightness = function(pixels, adjustment) {
var d = pixels.data;
for (var i=0; i<d.length; i+=4) {
d[i] += adjustment;
d[i+1] += adjustment;
d[i+2] += adjustment;
}
return pixels;
};
Le seuil d'une image est également très simple. Il vous suffit de comparer la valeur en niveaux de gris d'un pixel à la valeur du seuil et de définir la couleur en fonction de celle-ci:
Filters.threshold = function(pixels, threshold) {
var d = pixels.data;
for (var i=0; i<d.length; i+=4) {
var r = d[i];
var g = d[i+1];
var b = d[i+2];
var v = (0.2126*r + 0.7152*g + 0.0722*b >= threshold) ? 255 : 0;
d[i] = d[i+1] = d[i+2] = v
}
return pixels;
};
Convoluer des images
Les filtres de convolution sont des filtres génériques très utiles pour le traitement des images. L'idée de base est de prendre la somme pondérée d'un rectangle de pixels de l'image source et de l'utiliser comme valeur de sortie. Les filtres de convolution peuvent être utilisés pour flouter, affiner, gaufrer, détecter les bords et bien d'autres choses.
Filters.tmpCanvas = document.createElement('canvas');
Filters.tmpCtx = Filters.tmpCanvas.getContext('2d');
Filters.createImageData = function(w,h) {
return this.tmpCtx.createImageData(w,h);
};
Filters.convolute = function(pixels, weights, opaque) {
var side = Math.round(Math.sqrt(weights.length));
var halfSide = Math.floor(side/2);
var src = pixels.data;
var sw = pixels.width;
var sh = pixels.height;
// pad output by the convolution matrix
var w = sw;
var h = sh;
var output = Filters.createImageData(w, h);
var dst = output.data;
// go through the destination image pixels
var alphaFac = opaque ? 1 : 0;
for (var y=0; y<h; y++) {
for (var x=0; x<w; x++) {
var sy = y;
var sx = x;
var dstOff = (y*w+x)*4;
// calculate the weighed sum of the source image pixels that
// fall under the convolution matrix
var r=0, g=0, b=0, a=0;
for (var cy=0; cy<side; cy++) {
for (var cx=0; cx<side; cx++) {
var scy = sy + cy - halfSide;
var scx = sx + cx - halfSide;
if (scy >= 0 && scy < sh && scx >= 0 && scx < sw) {
var srcOff = (scy*sw+scx)*4;
var wt = weights[cy*side+cx];
r += src[srcOff] * wt;
g += src[srcOff+1] * wt;
b += src[srcOff+2] * wt;
a += src[srcOff+3] * wt;
}
}
}
dst[dstOff] = r;
dst[dstOff+1] = g;
dst[dstOff+2] = b;
dst[dstOff+3] = a + alphaFac*(255-a);
}
}
return output;
};
Voici un filtre d'amélioration de la netteté 3 x 3. Notez comment la pondération est concentrée sur le pixel central. Pour maintenir la luminosité de l'image, la somme des valeurs de la matrice doit être égale à 1.
Filters.filterImage(Filters.convolute, image,
[ 0, -1, 0,
-1, 5, -1,
0, -1, 0 ]
);
Voici un autre exemple de filtre de convolution, le floutage au cadre. Le floutage au cadre affiche la moyenne des valeurs de pixel dans la matrice de convolution. Pour ce faire, créez une matrice de convolution de taille NxN, où chacun des poids est égal à 1 / (NxN). De cette façon, chacun des pixels de la matrice contribue de manière égale à l'image de sortie, et la somme des poids est égale à un.
Filters.filterImage(Filters.convolute, image,
[ 1/9, 1/9, 1/9,
1/9, 1/9, 1/9,
1/9, 1/9, 1/9 ]
);
Nous pouvons créer des filtres d'image plus complexes en combinant des filtres existants. Par exemple, écrivons un filtre Sobel. Un filtre Sobel calcule les gradients verticaux et horizontaux de l'image, puis combine les images calculées pour trouver les bords de l'image. Ici, nous implémentons le filtre Sobel en mettant d'abord l'image à l'échelle de gris, puis en prenant les dégradés horizontal et vertical, et enfin en combinant les images en dégradé pour former l'image finale.
Dans le contexte terminologique, le terme "dégradé" désigne ici le changement de la valeur en pixels au niveau d'une position d'image. Si un pixel a un voisin de gauche avec une valeur de 20 et un voisin de droite avec une valeur de 50, le dégradé horizontal au niveau du pixel sera de 30. Le gradient vertical a la même idée mais utilise les voisins au-dessus et en dessous.
var grayscale = Filters.filterImage(Filter.grayscale, image);
// Note that ImageData values are clamped between 0 and 255, so we need
// to use a Float32Array for the gradient values because they
// range between -255 and 255.
var vertical = Filters.convoluteFloat32(grayscale,
[ -1, 0, 1,
-2, 0, 2,
-1, 0, 1 ]);
var horizontal = Filters.convoluteFloat32(grayscale,
[ -1, -2, -1,
0, 0, 0,
1, 2, 1 ]);
var final_image = Filters.createImageData(vertical.width, vertical.height);
for (var i=0; i<final_image.data.length; i+=4) {
// make the vertical gradient red
var v = Math.abs(vertical.data[i]);
final_image.data[i] = v;
// make the horizontal gradient green
var h = Math.abs(horizontal.data[i]);
final_image.data[i+1] = h;
// and mix in some blue for aesthetics
final_image.data[i+2] = (v+h)/4;
final_image.data[i+3] = 255; // opaque alpha
}
Il existe de nombreux autres filtres de convolution intéressants qui ne demandent qu'à être découverts. Par exemple, essayez d'implémenter un filtre Laplace dans le jouet de convolution ci-dessus et observez son fonctionnement.
Conclusion
J'espère que ce petit article vous a permis de découvrir les concepts de base de l'écriture de filtres d'image en JavaScript à l'aide de la balise de canevas HTML. Je vous encourage à implémenter d'autres filtres d'image. C'est très amusant !
Si vous avez besoin de meilleures performances pour vos filtres, vous pouvez généralement les importer de sorte qu'ils utilisent des nuanceurs de fragments WebGL pour traiter l'image. Avec les nuanceurs, vous pouvez exécuter la plupart des filtres simples en temps réel, ce qui vous permet de les utiliser pour le post-traitement des vidéos et des animations.