Wydaje się, że nie ma natywnej funkcji rysowania owalnego kształtu. Ja też nie szukam kształtu jajka.
Czy można narysować owal za pomocą 2 krzywych beziera? Ktoś to doświadczył?
Moim celem jest przyciągnięcie kilku oczu i właściwie używam tylko łuków. Z góry dziękuję.
Rozwiązanie
Zatem scale () zmienia skalowanie dla wszystkich następnych kształtów. Save () zapisuje ustawienia wcześniej, a przywracanie służy do przywracania ustawień rysowania nowych kształtów bez skalowania.
Dzięki 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();
javascript
html
canvas
Tammo
źródło
źródło
restore()
wcześniejstroke()
, nie zniekształci to linii, o czym Johnathan Hebert wspomniał w komentarzu do odpowiedzi Steve'a Tranby'ego poniżej. Jest to również niepotrzebne i nieprawidłowe użycieclosePath()
. Taka niezrozumiana metoda ... stackoverflow.com/questions/10807230/…Odpowiedzi:
aktualizacje:
Przykład testowania JSBin (zaktualizowany, aby przetestować odpowiedzi innych w celu porównania)
Oryginalny:
Jeśli chcesz symetrycznego owalu, zawsze możesz utworzyć okrąg o promieniu szerokości, a następnie przeskalować go do żądanej wysokości ( edycja: zauważ, że wpłynie to na wygląd szerokości obrysu - zobacz odpowiedź acdameli), ale jeśli chcesz mieć pełną kontrolę nad elipsą oto jeden ze sposobów wykorzystania krzywych Beziera.
<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>
źródło
scale
zniekształca pociągnięcia (patrz odpowiedź acdameli).Oto uproszczona wersja rozwiązań w innym miejscu. Rysuję okrąg kanoniczny, tłumaczę i skaluję, a następnie głaszczę.
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(); }
źródło
context
ciężkie podnoszenie za pomocąscale
.Istnieje teraz natywna funkcja elipsy dla płótna, bardzo podobna do funkcji łuku, chociaż teraz mamy dwie wartości promienia i obrót, który jest niesamowity.
Live Demo
ctx.ellipse(100, 100, 10, 15, 0, 0, Math.PI*2); ctx.fill();
Wydaje się, że obecnie działa tylko w Chrome
źródło
Podejście krzywej Beziera doskonale nadaje się do prostych owali. Aby uzyskać większą kontrolę, możesz użyć pętli do narysowania elipsy z różnymi wartościami promienia xiy (promienie, promienie?).
Dodanie parametru RotationAngle umożliwia obracanie owalu wokół jego środka o dowolny kąt. Częściowe owale można narysować, zmieniając zakres (var i), w którym przebiega pętla.
Renderowanie owalu w ten sposób pozwala określić dokładne położenie x, y wszystkich punktów na prostej. Jest to przydatne, jeśli położenie innych obiektów zależy od lokalizacji i orientacji owalu.
Oto przykład kodu:
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); } }
Zobacz interaktywny przykład tutaj: http://www.scienceprimer.com/draw-oval-html5-canvas
źródło
Potrzebujesz 4 krzywych Beziera (i magicznej liczby), aby wiarygodnie odtworzyć elipsę. Spójrz tutaj:
www.tinaja.com/glib/ellipse4.pdf
Dwa beziery nie odtwarzają dokładnie elipsy. Aby to udowodnić, wypróbuj niektóre z dwóch powyższych rozwiązań beziera o równej wysokości i szerokości - powinny idealnie przybliżać okrąg, ale nie będą. Nadal będą wyglądać owalnie, co dowodzi, że nie robią tego, co do nich należy.
Oto coś, co powinno działać:
http://jsfiddle.net/BsPsj/
Oto kod:
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(); }
źródło
Możesz także spróbować użyć niejednolitego skalowania. Możesz zapewnić skalowanie X i Y, więc po prostu ustaw skalowanie X lub Y na większe niż inne i narysuj okrąg, a otrzymasz elipsę.
źródło
Zrobiłem małą adaptację tego kodu (częściowo przedstawionego przez Andrew Staroscika) dla ludzi, którzy nie chcą tak ogólnej elipsy i którzy mają tylko większą półosi i dane o ekscentryczności elipsy (dobre dla astronomicznych zabawek javascript do kreślenia orbit , na przykład).
Proszę bardzo, pamiętając, że można dostosować kroki,
i
aby uzyskać większą precyzję w rysunku:/* 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(); }
źródło
Moje rozwiązanie jest trochę inne niż wszystkie te. Myślę, że najbliższa odpowiedź to najczęściej głosowana powyżej odpowiedź, ale myślę, że ta metoda jest nieco czystsza i łatwiejsza do zrozumienia.
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(); }
A potem użyj tego w ten sposób:
bezierCurve(x + 60, y + 75, 80, 130);
W skrzypcach jest kilka przykładów użycia, a także nieudana próba wykonania jednego przy użyciu quadraticCurveTo.
źródło
Podoba mi się powyższe rozwiązanie krzywych Beziera. Zauważyłem, że skala wpływa również na szerokość linii, więc jeśli próbujesz narysować elipsę, która jest szersza niż wysoka, Twoje górne i dolne „boki” będą wyglądały na cieńsze niż lewe i prawe „boki” ...
dobrym przykładem byłoby:
ctx.lineWidth = 4; ctx.scale(1, 0.5); ctx.beginPath(); ctx.arc(20, 20, 10, 0, Math.PI * 2, false); ctx.stroke();
należy zauważyć, że szerokość linii na szczycie i dolinie elipsy jest o połowę mniejsza niż na lewym i prawym wierzchołku (wierzchołkach?).
źródło
ctx.scale(0.5, 1);
Tak, jest to możliwe z dwoma krzywymi Beziera - oto krótki samouczek / przykład: http://www.williammalone.com/briefs/how-to-draw-ellipse-html5-canvas/
źródło
Chrome i Opera obsługują elipsę metodę w kontekście kanwy 2D, ale IE, Edge, Firefox i Safari jej nie obsługują.
Metodę elipsy możemy zaimplementować w JS lub skorzystać z polyfillu innej firmy.
Przykład użycia:
ctx.ellipse(20, 21, 10, 10, 0, 0, Math.PI*2, true);
Możesz użyć płótna-5-polyfill aby zapewnić metodę elipsy.
Lub po prostu wklej kod js, aby zapewnić metodę elipsy:
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(); } }
źródło
Jest to inny sposób tworzenia kształtu podobnego do elipsy, chociaż wykorzystuje funkcję „fillRect ()”, której można użyć do zmiany argumentów w funkcji 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>
źródło
Dzięki temu możesz nawet narysować segmenty elipsy:
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/
źródło
Ponieważ nikt nie wymyślił podejścia używającego prostszego
quadraticCurveTo
, dodaję rozwiązanie. Wystarczy wymienićbezierCurveTo
połączenia w @ Steve'a odpowiedzi z tego:Możesz również usunąć
closePath
. Jednak owal wygląda nieco inaczej.źródło
Oto funkcja, którą napisałem, która używa tych samych wartości, co łuk elipsy w SVG. X1 i Y1 to ostatnie współrzędne, X2 i Y2 to końcowe współrzędne, promień to wartość liczbowa, a zgodnie z ruchem wskazówek zegara to wartość logiczna. Zakłada się również, że kontekst kanwy został już zdefiniowany.
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); }
źródło
Jeśli chcesz, aby elipsa w pełni mieściła się w prostokącie, to naprawdę wygląda tak:
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; }
Upewnij się, że nie jesteś
canvasContext.fillStyle = 'rgba(0,0,0,0)';
wypełniony tym projektem.źródło