Filtres d'image avec canevas

Ilmari Heikkinen

Introduction

L'élément canevas HTML5 peut être utilisé pour créer des filtres d'image. Ce que vous dessine une image sur un canevas, relisez les pixels du canevas, et appliquer votre filtre. Vous pouvez ensuite écrire le résultat (ou réutilisez simplement l'ancien).

Cela semble simple ? Très bien. Au boulot !

L'image de test d'origine
Image test d'origine

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;
};

Ensuite, nous avons besoin d'un moyen de filtrer les images. Que diriez-vous d'un filterImage qui prend un filtre et une image, et 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éé, écrire quelques filtres simples. Pour commencer, convertissons l'image en nuances 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;
};

Vous pouvez ajuster la luminosité en ajoutant 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;
};

Définir un seuil pour une image est également assez simple. Il suffit de comparer les nuances de gris d'un pixel sur la valeur du seuil, puis définissez la couleur en conséquence:

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 que vous prenez la somme pondérée d'un rectangle de pixels de l'image source et l'utiliser comme valeur de sortie. Les filtres de convolution permettent de flouter, d'améliorer la détection des 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. Voyez comment l'épaisseur est concentrée sur le pixel central. Pour maintenir la luminosité de l'image, la somme des valeurs de la matrice doit un seul.

Filters.filterImage(Filters.convolute, image,
[  0, -1,  0,
-1,  5, -1,
  0, -1,  0 ]
);

Voici un autre exemple de filtre convolution : le flou de zone. La boîte "floutage" génère la moyenne des valeurs en pixels dans la matrice de convolution. Pour cela, vous pouvez créer une matrice convolutive de taille NxN, chacune des pondérations est égale à 1 / (NxN). Ainsi, chacun des pixels à l'intérieur de contribue de manière égale à l'image de sortie et à la somme des la pondération est de 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. Pour écrivons un filtre Sobel. Un filtre de Sobel calcule la valeur et horizontaux de l'image, puis combine les images calculées trouver des bords dans l'image. Ici, nous implémentons le filtre Sobel en appliquant d'abord une échelle de gris à l'image, puis les dégradés horizontaux et verticaux, combiner les images en dégradé pour constituer l'image finale.

Concernant la terminologie, le terme "gradient" ici signifie que le changement en pixels à la position d'une image. Si un pixel a un élément voisin de gauche avec la valeur 20 et la valeur voisine droite de 50, le gradient horizontal c'est-à-dire 30. Le dégradé vertical a la même idée, mais utilise les voisins ci-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 à convolution n'attendent que vous les découvriez. Par exemple, essayez d'implémenter un filtre de Laplace. dans le jouet à convolution ci-dessus et voyons ce qu'il fait.

Conclusion

J'espère que ce petit article vous a été utile pour vous présenter les concepts de base de l'écriture de filtres d'image en JavaScript à l'aide du tag de canevas HTML. Je je vous invite à implémenter d'autres filtres d'image, amusant !

Pour améliorer les performances de vos filtres, vous pouvez généralement et les importer pour traiter l'image à l'aide de nuanceurs de fragments WebGL. Avec les nuanceurs, vous pouvez exécuter la plupart des filtres simples en temps réel, ce qui permet de les utiliser pour le post-traitement des vidéos et des animations.