Как нарисовать овал в холсте html5?

Кажется, что не существует нативной функции для рисования овальной формы. Также я не ищу форму яйца.

Можно ли нарисовать овал с 2 безьевыми кривыми? Кто-нибудь из вас это сделал?

Моя цель - нарисовать глаза и на самом деле ими пользоваться дугами. Спасибо заранее.

Решение

Таким образом, scale() изменяет масштаб для всех следующих фигур. Save() сохраняет настройки до и восстановления, используется для восстановления настроек для рисования новых фигур без масштабирования.

Благодаря 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();

Ответ 1

обновления:

  • метод масштабирования может влиять на ширину штриха.
  • метод масштабирования, сделанный справа, может сохранить неизменную длину хода
  • у canvas есть метод эллипса, который теперь поддерживает Chrome
  • добавлены обновленные тесты в JSBin

Пример тестирования JSBin (обновлен для проверки других ответов для сравнения)

  • Безье - розыгрыш, основанный на верхнем левом углу, содержащий прямоугольник и ширину/высоту.
  • Безье с Центром - рисование на основе центра и ширины/высоты
  • Дуги и масштабирование - рисование по кругу рисования и масштабированию
  • Квадратичные кривые - рисование с квадратикой
    • Тест, похоже, не совсем то же, может быть реализация
    • см. ответ оофанта.
  • Canvas Ellipse - с использованием стандартного метода эллипса W3C()
    • Тест, похоже, не совсем то же, может быть реализация
    • см. ответ от Loktar

Оригинал:

Если вам нужен симметричный овал, вы всегда можете создать круг радиуса ширины и затем масштабировать его до требуемой высоты (отредактируйте: обратите внимание, что это повлияет на вид ширины штриха - см. ответ acdamely), но если вы хотите полностью контролировать эллипса здесь одним способом, используя кривые Безье.

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

Ответ 2

Вот упрощенная версия решений в другом месте. Я рисую канонический круг, переводим и масштабируем, а затем обводя его.

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

Ответ 3

Подход кривой Безье отлично подходит для простых овалов. Для большего контроля вы можете использовать цикл для рисования эллипса с разными значениями радиуса x и y (радиусы, радиусы?).

Добавление параметра rotationAngle позволяет овалу поворачиваться вокруг его центра под любым углом. Частичные овалы можно нарисовать, изменив диапазон (var i), по которому проходит цикл.

Оказание овала таким образом позволяет вам определить точное местоположение x, y всех точек на линии. Это полезно, если положение других объектов зависит от местоположения и ориентации овала.

Вот пример кода:

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

См. интерактивный пример здесь: http://www.scienceprimer.com/draw-oval-html5-canvas

Ответ 4

Вы также можете попробовать использовать неравномерное масштабирование. Вы можете обеспечить масштабирование X и Y, поэтому просто установите масштаб X или Y больше, чем другой, и нарисуйте круг, и у вас есть эллипс.

Ответ 5

Для надежного воспроизведения эллипса вам понадобится 4 кривых безье (и волшебное число). См. Здесь:

www.tinaja.com/glib/ellipse4.pdf

Два безье не точно воспроизводят эллипс. Чтобы это доказать, попробуйте два из вышеперечисленных выше безьеров с одинаковой высотой и шириной - они должны идеально приближать круг, но они не будут. Они все равно будут выглядеть овальными, что доказывает, что они не делают то, что они должны.

Здесь что-то должно работать:

http://jsfiddle.net/BsPsj/

Здесь код:

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


}

Ответ 6

Я немного адаптировал этот код (частично представленный Эндрю Староскиком) для людей, которые не хотят иметь такой общий эллипс и у кого есть только большая полуось и данные об эксцентриситете эллипса (например, для астрономических javascript-игрушек для построения орбит).

Здесь вы поймете, что можно адаптировать шаги в i для большей точности в чертеже:

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

Ответ 7

В настоящее время существует встроенная функция ellipse для холста, очень похожая на функцию дуги, хотя теперь мы имеем два значения радиуса и поворот который является удивительным.

эллипс (x, y, радиус X, радиус Y, вращение, startAngle, endAngle, против часовой стрелки)

Live Demo

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

Кажется, что работает только в Chrome

Ответ 8

Мое решение немного отличается от всех этих. Ближайший, я думаю, самый голосующий ответ выше, хотя, но я думаю, что этот способ немного чище и легче понять.

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

И затем используйте его следующим образом:

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

В скрипте есть несколько примеров использования, а также неудачная попытка сделать это с помощью quadraticCurveTo.

Ответ 9

Мне нравится решение кривых Безье выше. Я заметил, что масштаб также влияет на ширину линии, поэтому, если вы пытаетесь нарисовать эллипс, который шире, чем высокий, ваши верхние и нижние "стороны" будут выглядеть тоньше, чем ваши левые и правые "стороны"...

хорошим примером может быть:

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

вы должны заметить, что ширина линии в пике и долине эллипса вдвое меньше ширины, чем на левой и правой вершинах (вершины?).

Ответ 11

Поддержка Chrome и Opera ellipse метод для контекста canvas 2d, но IE, Edge, Firefox и Safari не поддерживают его.

Мы можем реализовать метод эллипса JS или использовать сторонний polyfill.

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

Пример использования:

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

Вы можете использовать canvas-5-polyfill, чтобы обеспечить метод эллипса.

Или просто вставьте некоторый js-код, чтобы обеспечить метод эллипса:

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

Ответ 12

Поскольку никто не придумал подход, используя более простой quadraticCurveTo, я добавляю для этого решение. Просто замените вызовы bezierCurveTo в ответе @Steve следующим образом:

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

Вы также можете удалить closePath. Оваль выглядит немного иначе, хотя.

Ответ 13

Это еще один способ создания эллипсовидной формы, хотя он использует функцию fillRect(), которую можно использовать для изменения аргументов в функции 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>

Ответ 14

С помощью этого вы можете даже рисовать сегменты эллипса:

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/

Ответ 15

Здесь функция, которую я написал, использует те же значения, что и дуга эллипса в SVG. X1 и Y1 - последние координаты, X2 и Y2 - координаты конца, радиус - числовое значение, а по часовой стрелке - логическое значение. Он также предполагает, что ваш контекст canvas уже определен.

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

Ответ 16

Если вы хотите, чтобы эллипс полностью вписывался в прямоугольник, это действительно так:

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

Убедитесь, что ваш canvasContext.fillStyle = 'rgba(0,0,0,0)'; не заполняется этой конструкцией.