Wie zeichne ich ein Oval in HTML5-Leinwand?

81

Es scheint keine native Funktion zu geben, um eine ovale Form zu zeichnen. Auch ich suche nicht die Eiform.

Ist es möglich, ein Oval mit 2 Bezierkurven zu zeichnen? Hat das jemand erlebt?

Mein Zweck ist es, einige Augen zu zeichnen und tatsächlich benutze ich nur Bögen. Danke im Voraus.

Lösung

Also ändert scale () die Skalierung für alle nächsten Formen. Speichern () speichert die Einstellungen vor und Wiederherstellen wird verwendet, um die Einstellungen wiederherzustellen und neue Formen ohne Skalierung zu zeichnen.

Danke an 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();
Tammo
quelle
7
Sie können keine Ellipse mit Bezier-Kurven zeichnen, im Gegensatz zu einigen der folgenden Antworten. Sie können eine Ellipse mit Polynomkurven approximieren, aber nicht exakt reproduzieren.
Joe
Ich habe +1 gegeben, aber dies verzerrt die Linie zusammen mit dem Kreis, was für mich nicht funktioniert. Gute Infos.
Mwilcox
1
@mwilcox - wenn Sie es restore()vorher sagen stroke(), wird es die Linie nicht verzerren, wie Johnathan Hebert im Kommentar zu Steve Tranbys Antwort unten erwähnte. Auch dies ist unnötig und falsch zu verwenden closePath(). Solch eine missverstandene Methode ... stackoverflow.com/questions/10807230/…
Brian McCutchon

Antworten:

113

Aktualisierung:

  • Die Skalierungsmethode kann das Erscheinungsbild der Strichbreite beeinflussen
  • Durch die richtige Skalierungsmethode kann die Strichbreite intakt bleiben
  • Canvas verfügt über eine Ellipsenmethode, die Chrome jetzt unterstützt
  • aktualisierte Tests zu JSBin hinzugefügt

JSBin-Testbeispiel (aktualisiert, um die Antworten anderer zum Vergleich zu testen)

  • Bezier - Zeichnung oben links mit Rechteck und Breite / Höhe
  • Bezier mit Mitte - Zeichnung basierend auf Mitte und Breite / Höhe
  • Bögen und Skalierung - Zeichnen basierend auf Zeichnungskreis und Skalierung
  • Quadratische Kurven - Zeichnen Sie mit Quadraten
    • Test scheint nicht ganz gleich zu zeichnen, kann Implementierung sein
    • siehe oyophants Antwort
  • Canvas Ellipse - mit der W3C-Standardmethode ellipse ()
    • Test scheint nicht ganz gleich zu zeichnen, kann Implementierung sein
    • siehe Loktars Antwort

Original:

Wenn Sie ein symmetrisches Oval wünschen, können Sie jederzeit einen Kreis mit Radiusbreite erstellen und ihn dann auf die gewünschte Höhe skalieren ( Bearbeiten: Beachten Sie, dass dies das Erscheinungsbild der Strichbreite beeinflusst - siehe Antwort von acdameli). Wenn Sie jedoch die Ellipse vollständig steuern möchten Hier ist eine Möglichkeit, Bezierkurven zu verwenden.

<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>
Steve Tranby
quelle
1
Dies ist die allgemeinste Lösung, da scalesie Schlaganfälle verzerren wird (siehe Antwort von acdameli).
Trevor Burnham
4
ctx.stroke () kann durch ctx.fill () ersetzt werden, um eine gefüllte Form zu erhalten.
h3xStream
13
Skala verformen sich nicht Striche , wenn Sie sich skalieren, dann Pfad hinzufügen , mit Bogen oder was auch immer, dann zurückfahren zu den ursprünglichen, dann Schlaganfall den bestehenden Pfad
Johnathan Hebert
1
Nur eine Annäherung von 4 * (sqrt (2) -1) / 3 fand viele Stellen, an denen Googles Berechnung 0,55228475 ergibt. Eine schnelle Suche ergibt diese gute Ressource: whizkidtech.redprince.net/bezier/circle/kappa
Steve Tranby
1
Keine dumme Frage. Wenn Sie sich ein Rechteck vorstellen, das die Ellipse umschließt, dann ist x, y die obere linke Ecke und die Mitte befindet sich am Punkt {x + w / 2.0, y + h / 2.0}
Steve Tranby
45

Hier ist eine vereinfachte Version von Lösungen an anderer Stelle. Ich zeichne einen kanonischen Kreis, übersetze und skaliere und streiche dann.

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();
}
Deven Kalra
quelle
2
Das ist ziemlich elegant. Lassen contextSie das schwere Heben mit machen scale.
Adu
1
Beachten Sie die sehr wichtige Wiederherstellung vor dem Strich (andernfalls wird die Strichbreite verzerrt)
Jason S
Genau das, wonach ich gesucht habe, danke! Ich wusste, dass es anders als auf bezier möglich war, aber ich konnte die Waage nicht dazu bringen, selbst zu arbeiten.
@JasonS Nur neugierig, warum die Strichbreite verzerrt wird, wenn Sie nach dem Strich wiederherstellen?
OneMoreQuestion
17

Es gibt jetzt eine native Ellipsenfunktion für Leinwand, die der Bogenfunktion sehr ähnlich ist, obwohl wir jetzt zwei Radiuswerte und eine Drehung haben, die fantastisch ist.

Ellipse (x, y, Radius X, Radius Y, Drehung, Startwinkel, Endwinkel, gegen den Uhrzeigersinn)

Live-Demo

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

Scheint derzeit nur in Chrome zu funktionieren

Loktar
quelle
2
Ja, die Implementierung ist langsam.
@Epistemex Einverstanden. Ich stelle mir vor / hoffe, dass es mit der Zeit schneller wird.
Loktar
Laut developer.mozilla.org/en-US/docs/Web/API/… wird es jetzt auch in Opera unterstützt
jazzpi
16

Der Bezierkurvenansatz eignet sich hervorragend für einfache Ovale. Für mehr Kontrolle können Sie eine Schleife verwenden, um eine Ellipse mit unterschiedlichen Werten für den x- und y-Radius (Radien, Radien?) Zu zeichnen.

Durch Hinzufügen eines Rotationswinkel-Parameters kann das Oval um einen beliebigen Winkel um seine Mitte gedreht werden. Partielle Ovale können gezeichnet werden, indem der Bereich (var i) geändert wird, über den die Schleife läuft.

Wenn Sie das Oval auf diese Weise rendern, können Sie die genaue x, y-Position aller Punkte auf der Linie bestimmen. Dies ist nützlich, wenn die Position anderer Objekte von der Position und Ausrichtung des Ovals abhängt.

Hier ist ein Beispiel für den 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);
    }
}

Ein interaktives Beispiel finden Sie hier: http://www.scienceprimer.com/draw-oval-html5-canvas

Andrew Staroscik
quelle
Diese Methode erzeugt die mathematisch korrekteste Ellipse. Ich frage mich, wie es in Bezug auf die Rechenkosten mit den anderen verglichen wird.
Eric
@Eric Ganz oben auf meinem Kopf sieht es so aus, als ob der Unterschied zwischen (Diese Methode: 12 Multiplikationen, 4 Additionen und 8 Triggerfunktionen) und (4 Cubic Beziers: 24 Multiplikationen und 24 Additionen) liegt. Unter der Annahme, dass die Leinwand einen iterativen Ansatz von De Casteljau verwendet, bin ich mir nicht sicher, wie sie tatsächlich in verschiedenen Browser-Leinwänden implementiert sind.
DerekR
versucht, diese 2 Ansätze in Chrome zu profilieren: 4 Cubic Beziers: ca. 0,3 ms Diese Methode: ca. 1,5 ms mit Schritt = 0,1 scheint ungefähr die gleichen 0,3 ms
sol0mka
Anscheinend wird CanvasRenderingContext2D.ellipse () nicht in allen Browsern unterstützt. Diese Routine funktioniert hervorragend, mit perfekter Genauigkeit und gut genug für Workshop-Vorlagen. Funktioniert sogar im Internet Explorer, seufz.
Zipzit
12

Sie benötigen 4 Bezierkurven (und eine magische Zahl), um eine Ellipse zuverlässig zu reproduzieren. Siehe hier:

www.tinaja.com/glib/ellipse4.pdf

Zwei Bezier reproduzieren eine Ellipse nicht genau. Um dies zu beweisen, probieren Sie einige der beiden oben genannten Bezier-Lösungen mit gleicher Höhe und Breite aus. Sie sollten sich idealerweise einem Kreis annähern, werden es aber nicht. Sie sehen immer noch oval aus, was beweist, dass sie nicht das tun, was sie sollen.

Hier ist etwas, das funktionieren sollte:

http://jsfiddle.net/BsPsj/

Hier ist der 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();


}
Alankar Misra
quelle
Diese Lösung funktioniert am besten für mich. Vier Parameter. Schnell. Einfach.
Oliver
Hallo, diese Lösung hat mir auch gefallen, aber als ich mit Ihrem Code herumgespielt habe und festgestellt habe, dass beim Anhängen einer Null links vom Parameterwert der Kreis schief ist. Dies wird verstärkt, wenn der Kreis größer wird. Wissen Sie, warum dies passieren kann? jsfiddle.net/BsPsj/187
alexcons
1
@alexcons vor einer 0 macht es zu einem oktalen Ganzzahlliteral.
Alankar Misra
11

Sie können auch versuchen, eine ungleichmäßige Skalierung zu verwenden. Sie können eine X- und Y-Skalierung bereitstellen. Stellen Sie also einfach die X- oder Y-Skalierung größer als die andere ein, zeichnen Sie einen Kreis und Sie haben eine Ellipse.

Jani Hartikainen
quelle
9

Ich habe eine kleine Anpassung dieses Codes (teilweise von Andrew Staroscik vorgestellt) für Personen vorgenommen, die keine so allgemeine Ellipse wollen und nur die größere Halbachse und die Exzentrizitätsdaten der Ellipse haben (gut für astronomische Javascript-Spielzeuge, um Umlaufbahnen zu zeichnen , zum Beispiel).

Denken Sie daran, dass Sie die Schritte anpassen können, um ieine präzisere Zeichnung zu erzielen:

/* 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();
}
Girardi
quelle
5

Meine Lösung ist etwas anders als alle diese. Am nächsten ist meiner Meinung nach die am häufigsten gewählte Antwort, aber ich denke, dieser Weg ist ein bisschen sauberer und leichter zu verstehen.

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

Und dann benutze es so:

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

Es gibt einige Anwendungsbeispiele in der Geige, zusammen mit einem fehlgeschlagenen Versuch, eines mit quadraticCurveTo zu erstellen.

jaredwilli
quelle
Der Code sieht gut aus, aber wenn Sie damit einen Kreis zeichnen, indem Sie dieselben Werte für Höhe und Breite übergeben, erhalten Sie eine Form, die einem Quadrat mit sehr abgerundeten Ecken ähnelt. Gibt es in einem solchen Fall eine Möglichkeit, einen echten Kreis zu bilden?
Drew Noakes
Ich denke, die beste Option in diesem Fall wäre die Verwendung der arc () -Methode, mit der Sie dann eine Start- und Endwinkelposition für den Bogen definieren können, ähnlich wie hier die Antwort mit der höchsten Abstimmung erfolgt. Ich denke, es ist auch die beste Antwort insgesamt.
Jaredwilli
4

Ich mag die Bezier-Kurvenlösung oben. Ich habe festgestellt, dass die Skalierung auch die Linienbreite beeinflusst. Wenn Sie also versuchen, eine Ellipse zu zeichnen, die breiter als hoch ist, erscheinen Ihre oberen und unteren "Seiten" dünner als Ihre linken und rechten "Seiten" ...

Ein gutes Beispiel wäre:

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

Sie sollten beachten, dass die Breite der Linie am Gipfel und im Tal der Ellipse halb so breit ist wie an der linken und rechten Spitze (Spitzen?).

acdameli
quelle
5
Sie können die Strichskalierung vermeiden, wenn Sie die Skalierung direkt vor der Strichoperation umkehren. Fügen Sie also die entgegengesetzte Skalierung als vorletzte Zeile hinzuctx.scale(0.5, 1);
Johnathan Hebert,
3

Chrome und Opera unterstützen die Ellipsenmethode für den 2D-Canvas-Kontext, IE, Edge, Firefox und Safari unterstützen sie jedoch nicht.

Wir können die Ellipsenmethode von JS implementieren oder eine Polyfüllung von Drittanbietern verwenden.

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

Anwendungsbeispiel:

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

Sie können eine Canvas-5-Polyfüllung verwenden , um eine Ellipsenmethode bereitzustellen.

Oder fügen Sie einfach einen js-Code ein, um die Ellipsenmethode bereitzustellen:

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

cuixiping
quelle
die beste Antwort für heute. Da die native Eclipse-Funktion noch experimentell ist, ist es absolut fantastisch, einen Fallback zu haben. Vielen Dank!
Walv
2

Dies ist eine weitere Möglichkeit, eine ellipsenähnliche Form zu erstellen, obwohl die Funktion "fillRect ()" verwendet wird. Dies kann verwendet werden, um die Argumente in der Funktion fillRect () zu ändern.

<!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>
aspYou
quelle
2

Damit können Sie sogar Segmente einer Ellipse zeichnen:

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/


quelle
2

Da niemand einen Ansatz mit dem Einfacheren gefunden hat, füge quadraticCurveToich eine Lösung dafür hinzu. Ersetzen Sie einfach die bezierCurveToAnrufe in der Antwort von @ Steve durch Folgendes :

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

Sie können auch die entfernen closePath. Das Oval sieht allerdings etwas anders aus.

Oyophant
quelle
0

Hier ist eine Funktion, die ich geschrieben habe und die dieselben Werte wie der Ellipsenbogen in SVG verwendet. X1 & Y1 sind die letzten Koordinaten, X2 & Y2 sind die Endkoordinaten, der Radius ist ein Zahlenwert und im Uhrzeigersinn ein Boolescher Wert. Es wird auch davon ausgegangen, dass Ihr Canvas-Kontext bereits definiert wurde.

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);
}
Gewähren
quelle
0

Wenn Sie möchten, dass die Ellipse vollständig in ein Rechteck passt, ist dies wirklich so:

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

Stellen Sie sicher, dass Sie canvasContext.fillStyle = 'rgba(0,0,0,0)';dieses Design nicht ausfüllen.

StackSlave
quelle