web-dev-qa-db-fra.com

Comment dessiner un ovale en toile html5?

Il ne semble pas y avoir de fonction native pour dessiner une forme ovale. Aussi, je ne cherche pas la forme d'oeuf.

Est-il possible de dessiner un ovale avec 2 courbes de Bézier? Quelqu'un d’expertencé avec ça?

Mon but est de dessiner des yeux et en fait, je suis en train d’utiliser des arcs ... Merci d’avance.

Solution

Donc scale () modifie la mise à l'échelle de toutes les formes suivantes . Save () enregistre les paramètres avant et la restauration est utilisée pour restaurer les paramètres afin de dessiner de nouvelles formes sans mise à l'échelle.

Merci à Jani

ctx.save();
ctx.scale(0.75, 1);
ctx.beginPath();
ctx.arc(20, 21, 10, 0, Math.PI*2, false);
ctx.stroke();
ctx.closePath();
ctx.restore();
72
Tammo

mises à jour:

  • la méthode de mise à l'échelle peut affecter l'apparence de la largeur du trait 
  • la méthode de mise à l'échelle bien faite peut garder la largeur de trait intacte
  • la toile possède une méthode d'ellipse que Chrome prend maintenant en charge 
  • ajouté des tests mis à jour à JSBin 

Exemple de test JSBin (mis à jour pour tester les réponses des autres à des fins de comparaison)

  • Bézier - dessiner basé sur le coin supérieur gauche contenant rect et largeur/hauteur
  • Bézier avec centre - dessiner en fonction du centre et de la largeur/hauteur
  • Arcs and Scaling - Dessin basé sur le cercle de dessin et la mise à l'échelle
  • Courbes quadratiques - dessiner avec des quadratiques
    • test semble ne pas tirer tout à fait la même chose, peut être mise en œuvre
    • voir oyophant's answer
  • Canvas Ellipse - Utilisation de la méthode standard ellipse () du W3C
    • test semble ne pas tirer tout à fait la même chose, peut être mise en œuvre
    • voir Loktar's answer

Original:

Si vous voulez un ovale symétrique, vous pouvez toujours créer un cercle de rayon, puis le redimensionner à la hauteur souhaitée (edit: notez que ceci affectera l'apparence de la largeur du trait - voir la réponse de voulez un contrôle total de l'ellipse, voici un moyen d'utiliser les courbes de Bézier. 

<canvas id="thecanvas" width="400" height="400"></canvas>

<script>
var canvas = document.getElementById('thecanvas');

if(canvas.getContext) 
{
  var ctx = canvas.getContext('2d');
  drawEllipse(ctx, 10, 10, 100, 60);
  drawEllipseByCenter(ctx, 60,40,20,10);
}

function drawEllipseByCenter(ctx, cx, cy, w, h) {
  drawEllipse(ctx, cx - w/2.0, cy - h/2.0, w, h);
}

function drawEllipse(ctx, x, y, w, h) {
  var kappa = .5522848,
      ox = (w / 2) * kappa, // control point offset horizontal
      oy = (h / 2) * kappa, // control point offset vertical
      xe = x + w,           // x-end
      ye = y + h,           // y-end
      xm = x + w / 2,       // x-middle
      ym = y + h / 2;       // y-middle

  ctx.beginPath();
  ctx.moveTo(x, ym);
  ctx.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y);
  ctx.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym);
  ctx.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye);
  ctx.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym);
  //ctx.closePath(); // not used correctly, see comments (use to close off open path)
  ctx.stroke();
}

</script>
105
Steve Tranby

Voici une version simplifiée des solutions ailleurs. Je dessine un cercle canonique, traduis et redimensionne puis je caresse.

function ellipse(context, cx, cy, rx, ry){
        context.save(); // save state
        context.beginPath();

        context.translate(cx-rx, cy-ry);
        context.scale(rx, ry);
        context.arc(1, 1, 1, 0, 2 * Math.PI, false);

        context.restore(); // restore to original state
        context.stroke();
}
43
Deven Kalra

L'approche de la courbe de Bézier est idéale pour les ovales simples. Pour plus de contrôle, vous pouvez utiliser une boucle pour dessiner une ellipse avec différentes valeurs pour les rayons x et y (rayons, rayons?). 

L'ajout d'un paramètre rotationAngle permet à l'ovale d'être pivoté autour de son centre de n'importe quel angle. Des ovales partiels peuvent être dessinés en modifiant la plage (var i) sur laquelle la boucle s'exécute. 

Le rendu de l'ovale de cette manière vous permet de déterminer l'emplacement x, y exact de tous les points de la ligne. Ceci est utile si la position d'autres objets dépend de l'emplacement et de l'orientation de l'ovale. 

Voici un exemple de code:

for (var i = 0 * Math.PI; i < 2 * Math.PI; i += 0.01 ) {
    xPos = centerX - (radiusX * Math.sin(i)) * Math.sin(rotationAngle * Math.PI) + (radiusY * Math.cos(i)) * Math.cos(rotationAngle * Math.PI);
    yPos = centerY + (radiusY * Math.cos(i)) * Math.sin(rotationAngle * Math.PI) + (radiusX * Math.sin(i)) * Math.cos(rotationAngle * Math.PI);

    if (i == 0) {
        cxt.moveTo(xPos, yPos);
    } else {
        cxt.lineTo(xPos, yPos);
    }
}

Voir un exemple interactif ici: http://www.scienceprimer.com/draw-oval-html5-canvas

15
Andrew Staroscik

Vous avez besoin de 4 courbes de Bézier (et d'un nombre magique) pour reproduire de manière fiable une ellipse. Vois ici:

www.tinaja.com/glib/ellipse4.pdf

Deux beziers ne reproduisent pas exactement une ellipse. Pour le prouver, essayez l'une des deux solutions de Bézier ci-dessus avec une hauteur et une largeur égales - elles devraient idéalement se rapprocher d'un cercle mais ne le feront pas. Ils auront toujours l'air ovale, ce qui prouvera qu'ils ne font pas ce qu'ils sont censés faire. 

Voici quelque chose qui devrait fonctionner:

http://jsfiddle.net/BsPsj/

Voici le code:

function ellipse(cx, cy, w, h){
    var ctx = canvas.getContext('2d');
    ctx.beginPath();
    var lx = cx - w/2,
        rx = cx + w/2,
        ty = cy - h/2,
        by = cy + h/2;
    var magic = 0.551784;
    var xmagic = magic*w/2;
    var ymagic = h*magic/2;
    ctx.moveTo(cx,ty);
    ctx.bezierCurveTo(cx+xmagic,ty,rx,cy-ymagic,rx,cy);
    ctx.bezierCurveTo(rx,cy+ymagic,cx+xmagic,by,cx,by);
    ctx.bezierCurveTo(cx-xmagic,by,lx,cy+ymagic,lx,cy);
    ctx.bezierCurveTo(lx,cy-ymagic,cx-xmagic,ty,cx,ty);
    ctx.stroke();


}
11
Alankar Misra

Vous pouvez également essayer d'utiliser une mise à l'échelle non uniforme. Vous pouvez fournir une mise à l'échelle X et Y, il vous suffit donc de définir une échelle X ou Y plus grande que l'autre, de tracer un cercle et d'obtenir une ellipse.

11
Jani Hartikainen

J'ai fait une petite adaptation de ce code (présenté en partie par Andrew Staroscik) pour peoplo qui ne veut pas d'une ellipse aussi générale et qui n'a que le plus grand demi-axe et les données d'excentricité de l'ellipse (bon pour le javascript astronomique jouets pour tracer des orbites, par exemple).

Voilà, en vous rappelant qu’on peut adapter les étapes dans i pour avoir une plus grande précision dans le dessin:

/* draw ellipse
 * x0,y0 = center of the ellipse
 * a = greater semi-axis
 * exc = ellipse excentricity (exc = 0 for circle, 0 < exc < 1 for ellipse, exc > 1 for hyperbole)
 */
function drawEllipse(ctx, x0, y0, a, exc, lineWidth, color)
{
    x0 += a * exc;
    var r = a * (1 - exc*exc)/(1 + exc),
        x = x0 + r,
        y = y0;
    ctx.beginPath();
    ctx.moveTo(x, y);
    var i = 0.01 * Math.PI;
    var twoPi = 2 * Math.PI;
    while (i < twoPi) {
        r = a * (1 - exc*exc)/(1 + exc * Math.cos(i));
        x = x0 + r * Math.cos(i);
        y = y0 + r * Math.sin(i);
        ctx.lineTo(x, y);
        i += 0.01;
    }
    ctx.lineWidth = lineWidth;
    ctx.strokeStyle = color;
    ctx.closePath();
    ctx.stroke();
}
8
Girardi

Il existe maintenant une fonction ellipse native pour canvas, très similaire à la fonction arc bien que nous ayons maintenant deux valeurs de rayon et une rotation qui soit impressionnante.

ellipse (x, y, rayon X, rayon Y, rotation, angle de départ, angle de fin, sens inverse des aiguilles d'une montre) 

Démo en direct

ctx.ellipse(100, 100, 10, 15, 0, 0, Math.PI*2);
ctx.fill();

Ne semble fonctionner que dans Chrome actuellement

8
Loktar

Ma solution est un peu différente de toutes. Je pense que la plus proche est la réponse la plus votée ci-dessus, mais je pense que cette façon de procéder est un peu plus propre et plus facile à comprendre.

http://jsfiddle.net/jaredwilli/CZeEG/4/

    function bezierCurve(centerX, centerY, width, height) {
    con.beginPath();
    con.moveTo(centerX, centerY - height / 2);

    con.bezierCurveTo(
        centerX + width / 2, centerY - height / 2,
        centerX + width / 2, centerY + height / 2,
        centerX, centerY + height / 2
    );
    con.bezierCurveTo(
        centerX - width / 2, centerY + height / 2,
        centerX - width / 2, centerY - height / 2,
        centerX, centerY - height / 2
    );

    con.fillStyle = 'white';
    con.fill();
    con.closePath();
}

Et puis utilisez-le comme ceci:

bezierCurve(x + 60, y + 75, 80, 130);

Il existe quelques exemples d’utilisation dans le violon, ainsi qu’une tentative infructueuse d’en faire un en utilisant quadraticCurveTo.

4
jaredwilli

J'aime la solution des courbes de Bézier ci-dessus. J'ai remarqué que l'échelle affectait également la largeur de trait. Ainsi, si vous essayez de dessiner une ellipse plus large que haute, les "côtés" supérieur et inférieur apparaissent plus minces que les "côtés" gauche et droit ...

un bon exemple serait:

ctx.lineWidth = 4;
ctx.scale(1, 0.5);
ctx.beginPath();
ctx.arc(20, 20, 10, 0, Math.PI * 2, false);
ctx.stroke();

vous remarquerez que la largeur de la ligne au sommet et la vallée de l'ellipse sont deux fois moins larges que celles des sommets gauche et droit.

3
acdameli

Chrome et Opera prennent en charge la méthode ellipse pour le contexte canvas 2d, mais IE, Edge, Firefox et Safari ne la prennent pas en charge.

Nous pouvons implémenter la méthode ellipse par JS ou utiliser un polyfill tiers.

ellipse(x, y, radiusX, radiusY, rotation, startAngle, endAngle, anticlockwise)

Exemple d'utilisation:

ctx.ellipse(20, 21, 10, 10, 0, 0, Math.PI*2, true);

Vous pouvez utiliser un fichier canvas-5-polyfill pour fournir une méthode ellipse.

Ou simplement coller du code js pour fournir la méthode ellipse:

if (CanvasRenderingContext2D.prototype.ellipse == undefined) {
  CanvasRenderingContext2D.prototype.ellipse = function(x, y, radiusX, radiusY,
        rotation, startAngle, endAngle, antiClockwise) {
    this.save();
    this.translate(x, y);
    this.rotate(rotation);
    this.scale(radiusX, radiusY);
    this.arc(0, 0, 1, startAngle, endAngle, antiClockwise);
    this.restore();
  }
}

ellipse 1

ellipse 2

ellipse 3

ellipse 4

2
cuixiping

Oui, c'est possible avec deux courbes de Bezier - voici un bref tutoriel/exemple: http://www.williammalone.com/briefs/how-to-draw-ellipse-html5-canvas/

2
tagawa

Étant donné que personne n’a proposé une approche utilisant la plus simple quadraticCurveTo, j’ajoute une solution pour cela. Remplacez simplement les appels bezierCurveTo dans le answer de @ Steve par ceci:

  ctx.quadraticCurveTo(x,y,xm,y);
  ctx.quadraticCurveTo(xe,y,xe,ym);
  ctx.quadraticCurveTo(xe,ye,xm,ye);
  ctx.quadraticCurveTo(x,ye,x,ym);

Vous pouvez également supprimer la closePath. L'ovale est légèrement différent cependant.

1
oyophant

Avec cela, vous pouvez même dessiner des segments d'une ellipse:

function ellipse(color, lineWidth, x, y, stretchX, stretchY, startAngle, endAngle) {
    for (var angle = startAngle; angle < endAngle; angle += Math.PI / 180) {
        ctx.beginPath()
        ctx.moveTo(x, y)
        ctx.lineTo(x + Math.cos(angle) * stretchX, y + Math.sin(angle) * stretchY)
        ctx.lineWidth = lineWidth
        ctx.strokeStyle = color
        ctx.stroke()
        ctx.closePath()
    }
}

http://jsfiddle.net/FazAe/1/

1
super

C’est une autre façon de créer une forme semblable à une ellipse, bien qu’elle utilise la fonction "fillRect ()", elle peut être utilisée pour modifier les arguments de la fonction fillRect ().

<!DOCTYPE html>
<html lang="en">
<head>
    <title>Sine and cosine functions</title>
</head>
<body>
<canvas id="trigCan" width="400" height="400"></canvas>

<script type="text/javascript">
var canvas = document.getElementById("trigCan"), ctx = canvas.getContext('2d');
for (var i = 0; i < 360; i++) {
    var x = Math.sin(i), y = Math.cos(i);
    ctx.stroke();
    ctx.fillRect(50 * 2 * x * 2 / 5 + 200, 40 * 2 * y / 4 + 200, 10, 10, true);
}
</script>
</body>
</html>
1
aspYou

Voici une fonction que j'ai écrite qui utilise les mêmes valeurs que l'arc d'ellipse en SVG. X1 et Y1 sont les dernières coordonnées, X2 et Y2 sont les coordonnées de fin, le rayon est une valeur numérique et dans le sens des aiguilles d'une montre, une valeur booléenne. Cela suppose également que votre contexte de toile a déjà été défini.

function ellipse(x1, y1, x2, y2, radius, clockwise) {

var cBx = (x1 + x2) / 2;    //get point between xy1 and xy2
var cBy = (y1 + y2) / 2;
var aB = Math.atan2(y1 - y2, x1 - x2);  //get angle to bulge point in radians
if (clockwise) { aB += (90 * (Math.PI / 180)); }
else { aB -= (90 * (Math.PI / 180)); }
var op_side = Math.sqrt(Math.pow(x1 - x2, 2) + Math.pow(y1 - y2, 2)) / 2;
var adj_side = Math.sqrt(Math.pow(radius, 2) - Math.pow(op_side, 2));

if (isNaN(adj_side)) {
    adj_side = Math.sqrt(Math.pow(op_side, 2) - Math.pow(radius, 2));
}

var Cx = cBx + (adj_side * Math.cos(aB));            
var Cy = cBy + (adj_side * Math.sin(aB));
var startA = Math.atan2(y1 - Cy, x1 - Cx);       //get start/end angles in radians
var endA = Math.atan2(y2 - Cy, x2 - Cx);
var mid = (startA + endA) / 2;
var Mx = Cx + (radius * Math.cos(mid));
var My = Cy + (radius * Math.sin(mid));
context.arc(Cx, Cy, radius, startA, endA, clockwise);
}
0
Grant

Si vous voulez que l'ellipse s'intègre parfaitement à l'intérieur d'un rectangle, procédez comme suit:

function ellipse(canvasContext, x, y, width, height){
  var z = canvasContext, X = Math.round(x), Y = Math.round(y), wd = Math.round(width), ht = Math.round(height), h6 = Math.round(ht/6);
  var y2 = Math.round(Y+ht/2), xw = X+wd, ym = Y-h6, yp = Y+ht+h6, cs = cards, c = this.card;
  z.beginPath(); z.moveTo(X, y2); z.bezierCurveTo(X, ym, xw, ym, xw, y2); z.bezierCurveTo(xw, yp, X, yp, X, y2); z.fill(); z.stroke();
  return z;
}

Assurez-vous que votre canvasContext.fillStyle = 'rgba(0,0,0,0)'; pour aucun remplissage avec cette conception.

0
PHPglue