web-dev-qa-db-fra.com

effacement des régions circulaires de HTML5 Canvas

Il semble que la seule façon d'effacer une région d'un canevas est d'utiliser la commande clearRect () - j'ai besoin d'effacer un cercle (je masque des zones d'un canevas rempli, des lumières ponctuelles dans ce cas spécifique) et malgré toutes les tentatives ne semble pas possible.

J'ai essayé de dessiner un cercle avec une valeur alpha de 0 mais simplement rien n'apparaîtrait à moins que l'alpha soit plus élevé (ce qui est contraire au point: P) - je suppose parce qu'un context.fill () le dessine comme un ajout plutôt que comme un remplacement .

Avez-vous des suggestions sur la façon dont je pourrais (rapidement) effacer les cercles à des fins de masque?

33
David Burford

Utilisez .arc Pour créer un trait circulaire, puis utilisez .clip() pour en faire la zone de découpage actuelle.

Vous pouvez ensuite utiliser .clearRect() pour effacer tout le canevas, mais seule la zone découpée changera.

39
Alnitak

Si vous créez un jeu ou quelque chose où la compression de chaque bit de performance est importante, regardez comment j'ai fait cette réponse: Canvas - Remplissez un rectangle dans toutes les zones qui sont entièrement transparentes

Plus précisément, la modification de la réponse qui mène à ceci: http://jsfiddle.net/a2Age/2/

Les énormes avantages ici:

  • Pas d'utilisation de chemins (lent)
  • Pas d'utilisation de clips (lent)
  • Pas besoin de sauvegarder/restaurer (puisqu'il n'y a aucun moyen de réinitialiser une région de découpage sans effacer tous les états (1), cela signifie que vous devez également utiliser la sauvegarde/restauration)

(1) En fait, je m'en suis plaint et resetClip () a été placé dans la spécification officielle à cause de cela, mais il faudra un certain temps avant que les navigateurs ne l'implémentent.

Code

var ctx          = document.getElementById('canvas1').getContext('2d'),
    ambientLight = 0.1,
    intensity    = 1,
    radius       = 100,
    amb          = 'rgba(0,0,0,' + (1 - ambientLight) + ')';

addLight(ctx, intensity, amb, 200, 200, 0, 200, 200, radius); // First circle
addLight(ctx, intensity, amb, 250, 270, 0, 250, 270, radius); // Second circle
addLight(ctx, intensity, amb, 50, 370, 0, 50, 370, radius, 50); // Third!

ctx.fillStyle = amb;
ctx.globalCompositeOperation = 'xor';
ctx.fillRect(0, 0, 500, 500);

function addLight(ctx, intsy, amb, xStart, yStart, rStart, xEnd, yEnd, rEnd, xOff, yOff) {
  xOff = xOff || 0;
  yOff = yOff || 0;

  var g = ctx.createRadialGradient(xStart, yStart, rStart, xEnd, yEnd, rEnd);
  g.addColorStop(1, 'rgba(0,0,0,' + (1 - intsy) + ')');
  g.addColorStop(0, amb);
  ctx.fillStyle = g;
  ctx.fillRect(xStart - rEnd + xOff, yStart - rEnd + yOff, xEnd + rEnd, yEnd + rEnd);
}
canvas {
  border: 1px solid black;
  background-image: url('http://placekitten.com/500/500');
}
<canvas id="canvas1" width="500" height="500"></canvas>
16
Simon Sarris

Compte tenu des exigences, ces réponses sont bonnes. Mais disons que vous êtes comme moi et que vous avez des exigences supplémentaires:

  1. Vous souhaitez "effacer" une partie d'une forme qui peut être partiellement en dehors des limites de la forme que vous effacez.
  2. Vous souhaitez voir l'arrière-plan sous la forme au lieu de l'effacer.

Pour la première exigence, la solution consiste à utiliser context.globalCompositeOperation = 'destination-out' Le bleu est la première forme et le rouge est la deuxième forme. Comme vous pouvez le voir, destination-out Supprime la section de la première forme.

enter image description here

Voici un exemple de code:

  explosionCanvasCtx.fillStyle = "red"
  drawCircle(explosionCanvasCtx, projectile.radius, projectile.radius, projectile.radius)
  explosionCanvasCtx.fill()

  explosionCanvasCtx.globalCompositeOperation = 'destination-out' #see https://developer.mozilla.org/samples/canvas-tutorial/6_1_canvas_composite.html
  drawCircle(explosionCanvasCtx, projectile.radius + 20, projectile.radius, projectile.radius)
  explosionCanvasCtx.fill()

Voici le problème potentiel avec ceci: La deuxième fill() effacera tout en dessous, y compris l'arrière-plan. Parfois, vous souhaiterez effacer uniquement la première forme, mais vous souhaitez toujours voir les couches qui se trouvent en dessous.

La solution est de dessiner ceci sur un canevas temporaire puis drawImage pour dessiner le canevas temporaire sur votre canevas principal. Le code ressemblera à ceci:

  diameter = projectile.radius * 2
  console.log "<canvas width='" + diameter + "' height='" + diameter + "'></canvas>"
  explosionCanvas = $("<canvas width='" + diameter + "' height='" + diameter + "'></canvas>")
  explosionCanvasCtx = explosionCanvas[0].getContext("2d")

  explosionCanvasCtx.fillStyle = "red"
  drawCircle(explosionCanvasCtx, projectile.radius, projectile.radius, projectile.radius)
  explosionCanvasCtx.fill()

  explosionCanvasCtx.globalCompositeOperation = 'destination-out' #see https://developer.mozilla.org/samples/canvas-tutorial/6_1_canvas_composite.html
  durationPercent = (projectile.startDuration - projectile.duration) / projectile.startDuration
  drawCircle(explosionCanvasCtx, projectile.radius + 20, projectile.radius, projectile.radius)
  explosionCanvasCtx.fill()
  explosionCanvasCtx.globalCompositeOperation = 'source-over' #see https://developer.mozilla.org/samples/canvas-tutorial/6_1_canvas_composite.html

  ctx.drawImage(explosionCanvas[0], projectile.pos.x - projectile.radius, projectile.pos.y - projectile.radius) #center
9
Daniel Kaplan

Vous avez quelques options.

Tout d'abord, voici une fonction que nous utiliserons pour remplir un cercle.

var fillCircle = function(x, y, radius)
{
    context.beginPath();
    context.arc(x, y, radius, 0, 2 * Math.PI, false);
    context.fill();
};

clip()

var clearCircle = function(x, y, radius)
{
    context.beginPath();
    context.arc(x, y, radius, 0, 2 * Math.PI, false);
    context.clip();
    context.clearRect(x - radius - 1, y - radius - 1,
                      radius * 2 + 2, radius * 2 + 2);
};

Voir ceci sur jsFiddle .


globalCompositeOperation

var clearCircle = function(x, y, radius)
{
    context.save();
    context.globalCompositeOperation = 'destination-out';
    context.beginPath();
    context.arc(x, y, radius, 0, 2 * Math.PI, false);
    context.fill();
    context.restore();
};

Voir ceci sur jsFiddle .


Les deux ont donné le résultat souhaité à l'écran, mais les performances n'étaient pas suffisantes dans mon cas car je dessinais et effaçais beaucoup de cercles chaque image pour un effet. En fin de compte, j'ai trouvé une manière différente d'obtenir un effet similaire à ce que je voulais en dessinant simplement des lignes plus épaisses sur un arc, mais ce qui précède peut toujours être utile à quelqu'un ayant des exigences de performances différentes.

8
Drew Noakes

Où x = position gauche, y = position droite, r = rayon et ctx = votre toile:

function clearCircle( x , y , r ){
    for( var i = 0 ; i < Math.round( Math.PI * r ) ; i++ ){
        var angle = ( i / Math.round( Math.PI * r )) * 360;
        ctx.clearRect( x , y , Math.sin( angle * ( Math.PI / 180 )) * r , Math.cos( angle * ( Math.PI / 180 )) * r );
    }
}
0
Alex

Utilisez canvas.getContext("2d").arc(...) pour dessiner un cercle sur la zone avec la couleur d'arrière-plan?

var canvas = document.getElementById("myCanvas");
var context = canvas.getContext("2d");
context.arc(x, y, r, 0, 2*Math.PI, false);
context.fillStyle = "#FFFFFF";
context.fill();
0
Ozzy