Créer du bruit en HTML5
Au cours de ce tutoriel, nous allons voir comment bruiter une image sur une page web via HTML5 et Javascript, à travers trois méthodes différentes.
- Qu'est-ce que le bruit ?
- Mise en place du code
- Méthode par dessin
- Méthode par modification de l'image
- Méthode par motif
Qu'est-ce que le bruit ?
Avant de commencer, expliquons ce qu'on désigne par "bruit".
Si vous n'avez aucune notion de traitement du signal, lorsqu'on vous parle de bruit, vous pensez sûrement à des sons indésirables, quelque-chose, donc dont on se passerait bien.
Cette définition est toujours valable en traitement du signal. Le bruit, c'est une perturbation ajoutée sur le signal d'origine.
Voici un exemple de courbe bruitée :

On a donc une courbe sinusoïdale parfaite à la base, à laquelle on ajoute un bruit, une composante aléatoire inutile mais qui existe bel et bien. Qu'on le veuille ou non, il faut faire avec. Selon le niveau de bruit appliqué, on peut reconnaître (ou non) l'allure du signal d'origine.
C'est tout pour les notions théoriques, qui vous serviront peut-être un jour.
Quoiqu'il en soit, si on transpose cela sur l'image, le bruit, ce sont les parasites sur votre vieille télévision qui capte mal.
Un exemple en image :

C'est cet effet que nous allons essayer de reproduire.
Mise en place du code
Pour bien commencer, prenons notre page HTML avec simplement le canvas pour afficher l'image, et une référence au script qui s'occupera du bruit.
<!DOCTYPE html> <html> <head> </head> <body> <canvas id="canvas-bruit" width="200" height="200"></canvas> <script src="bruit.js"></script> </body> </html>
Pensez à changer le chemin vers le script Javascript en fonction de votre environnement.
Le canvas fera 200 pixels de large sur 200 de haut. J'ai volontairement choisi un petit canvas, car de trop grandes dimensions pourraient faire planter votre navigateur, selon la méthode utilisée.
Pour ce qui est du Javascript, commencez par prévoir l'utilisation de l'API requestAnimationFrame. Cette API permet de demander au navigateur de prévoir de nouvelles images de l'animation. Elle fonctionne comme setTimeout(), sauf que vous ne pouvez pas lui spécifier le délai. On lui passe donc en paramètre uniquement la fonction à appeler.
Rassurez-vous, pour utiliser cette API, il vous suffit de copier ce bout de code, qui permet de s'adapter peu importe le navigateur :
// API requestAnimationFrame var requestAnimFrame = window.requestAnimFrame = (function(){ return window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || window.oRequestAnimationFrame || window.msRequestAnimationFrame || function(callback){ window.setTimeout(callback,1000/60); }; })(); var cancelAnimFrame = window.cancelAnimFrame = (function(){ return window.cancelAnimationFrame || window.webkitCancelAnimationFrame || window.mozCancelAnimationFrame || window.oCancelAnimationFrame || window.msCancelAnimationFrame || function(){ window.clearTimeout.apply(window,arguments); } })();
Ensuite, voici le code Javascript de base que l'on utilisera :
// Variables globales var can,ctx; // On attend le chargement de la page document.addEventListener('DOMContentLoaded',function(){ // Une fois la page chargée, on va charger l'image qu'on va bruiter var img = new Image(); img.onload = function(){ // On récupère le canvas et son contexte. On pourrait aussi // vérifier si canvas est supporté par le navigateur, mais ce // n'est pas l'objet ici. can = document.getElementById('canvas-bruit'); ctx = can.getContext('2d'); // Mise en route de l'animation (function(){ // A chaque image de l'animation, on réaffiche notre photo ctx.drawImage(img,0,0); // A laquelle on ajoute du bruit bruit(); // Et enfin, on demande au navigateur de prévoir la prochaine // image de l'animation. requestAnimFrame(arguments.callee); })(); }; img.onerror = function(){ console.log('Unable to load ' + this.src); }; img.src = 'background.png'; });
Il consiste simplement à charger une image, puis met en route l'animation qui consiste à afficher l'image puis la bruiter.
Je ne détaille pas ce bout de code (il est normalement suffisamment commenté), sachez juste que ce qui nous intéresse, c'est la fonction bruit(), que nous allons écrire par la suite.
Pensez tout de même à changer le chemin de l'image de fond à utiliser.
Méthode par dessin
La première méthode est la plus simple : elle consiste à dessiner de petits rectangles partout sur l'image, avec une opacité plus ou moins élevée. On parcoure donc simplement l'image, et pour chaque pixel on dessine un rectangle par-dessus, qui sera plus ou moins visible.
En voici le code:
function bruit(){ ctx.fillStyle = 'white'; for(var x = 0 ; x < can.width ; x++){ for(var y = 0 ; y < can.height ; y++){ ctx.globalAlpha = Math.random() * 0.2; ctx.fillRect(x,y,1,1); } } ctx.globalAlpha = 1; }
Une fois toute l'image parcourue, on obtient un résultat plutôt correct. Cependant cette méthode a plusieurs inconvénients :
Tout d'abord il ne s'agit pas d'un vrai bruit. On a simplement dessiné des rectangles blancs par-dessus l'image.
Mais surtout, son principal inconvénient est qu'elle est extrêmement lente ! Imaginez, ici nous avons un canvas de 200 par 200, ce qui fait que pour chaque frame de votre animation, on doit dessiner 40000 petits rectangles ! Dans le cas d'une simple image avec un bruit fixe, cela peut être une bonne méthode, facile à mettre en place, mais pour une animation, c'est à proscrire. Vous risquez même de faire planter votre navigateur.
L'avantage est tout de même qu'on peut facilement définir une taille de pixel pour le bruit, ce qui est relativement inutile.
Méthode par modification de l'image
Cette deuxième méthode consiste à mettre en place un "vrai" bruit. Cette fois-ci, nous n'allons pas dessiner par-dessus l'image, nous allons directement modifier ses pixels.
Pour cela, nous avons besoin de la fonction getImageData(), qui permet de récupérer les données de l'image, c'est-à-dire ses 4 canaux :
- un canal rouge
- un canal vert
- un canal bleu
- un canal alpha, que nous n'allons pas modifier
Par conséquent, dans le cas présent, notre canvas fait 200 pixels par 200 pixels, ce qui fait un total de 400000 pixels. Avec quatre canaux par pixel, le tableau obtenu contiendra donc 1600000 cases. C'est vous dire à quel point il est immense !
Cependant, ici, nous n'avons pas besoin d'accéder à un pixel particulier. Il suffit donc de parcourir tout le tableau quatre à quatre, et de bruiter chacun des canaux rouge, vert et bleu. Ceux-ci contiennent des valeurs entre 0 et 255, il faut donc prendre garde à ne pas dépasser 255, et à ne pas donner des valeurs négatives.
La fonction getImageData() prend 4 paramètres, qui représentent le rectangle duquel vous souhaitez extraire les données. Vous pouvez donc tout à faire n'extraire qu'une partie de votre image.
Cela devrait être plus clair avec le code à l'appui :
function bruit(){ // On récupère de façon brute les données de l'image (pixels) var imageData = ctx.getImageData(0,0,can.width,can.height); // Et ensuite, on va parcourir chaque pixel et le bruiter for(var i = 0 ; i < imageData.data.length ; i += 4){ // Pour faire simple, on va bruiter tous les canaux de la même // façon, mais vous pouvez toujours ajouter un bruit différent // pour chaque canal. var bruit = ~~(Math.random() * 64 - 32); // On applique ensuite ce bruit, sans oublier de borner les // valeurs entre 0 et 255. imageData.data[i] = Math.min(255,Math.max(0,imageData.data[i] + bruit)); // bruitage du rouge imageData.data[i+1] = Math.min(255,Math.max(0,imageData.data[i+1] + bruit)); // bruitage du vert imageData.data[i+2] = Math.min(255,Math.max(0,imageData.data[i+2] + bruit)); // bruitage du bleu } ctx.putImageData(imageData,0,0); }
Le résultat obtenu est meilleur qu'avec la première méthode. On a cette fois-ci un vrai bruit. De plus, c'est nettement plus rapide. Cependant nous pouvons faire quelque-chose de plus rapide encore.
Sachez simplement qu'en utilisant getImageData(), vous avez accès aux pixels de votre canvas, et donc vous pouvez y effectuer tous types de traitements et de filtres. Vous pouvez par exemple passer votre image en noir et blanc, la flouter... Bref, vous pouvez faire ce que vous voulez, mais en sachant que pour une animation, c'est bien trop lent : on doit toujours parcourir tous les pixels. Cela dit, vous pouvez toujours vous amuser avec.
Méthode par motif
Cette troisième et dernière méthode est la plus rapide. Le résultat est semblable à celui de la première méthode.
Rappelez-vous de la première méthode : le problème de vitesse était dû au fait qu'on devait parcourir chaque pixel et dessiner par-dessus, le tout via Javascript, qui n'est pas aussi rapide qu'un langage compilé.
Imaginez maintenant qu'on crée un masque de bruit dès le début, et que l'on n'ait plus qu'à le dessiner par-dessus notre image de base. La plupart des traitements ne dépendent alors plus de Javascript, mais du navigateur.
C'est là l'astuce de cette méthode : on crée un motif de bruit, qu'on va redessiner partout sur l'image, et on va le décaler à chaque fois un peu plus pour donner une vraie impression de bruit qui ne soit pas statique.
Pour cela, commençons par créer ce motif. Il suffit simplement de créer un canvas temporaire (qui ne sera pas visible à l'écran), auquel on applique la première méthode (bruitage par le dessin), pour enfin créer un motif.
En voici le code :
var motifBruit = (function(){ // Création d'un canvas temporaire (qui ne sera pas affiché à l'écran) var canvasTmp = document.createElement('canvas'); var ctxTmp = canvasTmp.getContext('2d'); // On définit la taille du motif. Attention, le motif va être // répété, donc plus il est petit, plus ce sera visible. canvasTmp.width = 200; canvasTmp.height = 200; // Ensuite, on applique la première méthode à ce canvas for(var x = 0 ; x < canvasTmp.width ; x++){ for(var y = 0 ; y < canvasTmp.height ; y++){ ctxTmp.globalAlpha = Math.random() * 0.2; ctxTmp.fillRect(x,y,1,1); } } // Enfin, on crée le fameux motif var motif = ctxTmp.createPattern(canvasTmp,'repeat'); // Et on le stocke dans motifBruit return motif; })();
Maintenant que nous avons notre motif (dans la variable motifBruit), voyons comment faire pour le
décaler à chaque frame de l'animation.
Les motifs démarrent toujours selon l'origine (0,0) du contexte. Vous ne pouvez pas (encore) définir le point de départ du motif. Il faut donc déplacer l'origine du contexte. Rien de plus simple : la fonction translate() est là pour ça ! Il faut juste ne pas oublier de dessiner ensuite au bon endroit, puisque l'origine de notre repère a été décalée.
Voici donc le code final :
function bruit(){ // L'idée, ici, c'est de décaler l'origine du motif à chaque image. // Pour cela, on va translater l'origine du contexte. var decalageX = Math.floor(Math.random() * 200); var decalageY = Math.floor(Math.random() * 200); ctx.save(); // Translation du contexte ctx.translate(decalageX,decalageY); // Maintenant que le contexte n'a plus la même origine, on peut // dessiner un rectangle avec le motif voulu. // Attention : l'origine du contexte a été déplacée, il faut donc // en prendre compte lorsqu'on dessine le rectangle. ctx.fillStyle = motifBruit; ctx.fillRect(-decalageX,-decalageY,can.width,can.height); ctx.restore(); }
Cette méthode est la plus rapide des trois. On peut atteindre un nombre d'images par seconde élevé, contrairement aux autres. Elle ne donne pas un aussi bon résultat que la seconde, mais un résultat très correct tout de même.
Notez également qu'on aurait pu créer à l'avance une image contenant le masque de bruit, pour ensuite l'afficher au-dessus de notre image de fond. Cependant, pourquoi charger une image alors qu'on peut la créer rapidement via le code, et pour au final peu de valeur ajoutée ?
Au cours de ce tutoriel, nous avons donc pu voir trois méthodes pour créer ce fameux bruit. Chacune a ses avantages et inconvénients, mais pour des animations ou des jeux, préférez la troisième.
| Pseudo | Commentaires |
|---|---|
| Il n'y a aucun message | |
| Pseudo | Commentaires |



