Рисуем эллипс на canvas


Создатели API для отрисовки различных графических примитивов не предусмотрели наличие встроенной функции для подобных целей. К счастью, ее реализация не очень сложна и, столкнувшись с необходимостью рисовать эллипс на canvas, я придумал следующее решение.

function drawEllipse(ctx, x, y, a, b) {
   // Запоминаем положение системы координат (CК) и масштаб
  ctx.save();
  ctx.beginPath();
 
  // Переносим СК в центр будущего эллипса
  ctx.translate(x, y);
 
  /*
   * Масштабируем по х.
   * Теперь нарисованная окружность вытянется в a / b раз
   * и станет эллипсом
   */
 
  ctx.scale(a / b, 1);
 
  // Рисуем окружность, которая благодаря масштабированию станет эллипсом
  ctx.arc(0, 0, b, 0, Math.PI * 2, true);
 
  // Восстанавливаем СК и масштаб
  ctx.restore();
 
  ctx.closePath();
  ctx.stroke();
}


Как видно, функция drawEllipse принимает пять аргументов.

  • ctx – контекст отрисовки
  • x, y – координаты центра эллипса (точки O на картинке)
  • a – большая полуось («горизонтальный радиус»)
  • b – малая полуось («вертикальный радиус»)

Перенос системы координат в центр эллипса необходим для того, чтобы избежать смещения его центра вправо. Наша окружность растягивается не влево и вправо одновременно а только вправо. Что произошло бы, не перенеси мы систему координат, вам пояснит картинка.

Как видно, центр окружности (точка O), координаты которого указываются при в аргументах функции, не совпадает с центром нарисованного эллипса (точка O1).

Для удобства функцию drawEllipse можно добавить в прототип объекта CanvasRenderingContext2D.

CanvasRenderingContext2D.prototype.drawEllipse = function (x, y, a, b) {
  // Запоминаем положение системы координат (CК) и масштаб
  this.save();
  this.beginPath();
 
  // Переносим СК в центр будущего эллипса
  this.translate(x, y);
 
  /*
   * Масштабируем по х.
   * Теперь нарисованная окружность вытянется в a / b раз
   * и станет эллипсом
   */
 
  this.scale(a / b, 1);
 
  // Рисуем окружность, которая благодаря масштабированию станет эллипсом
  this.arc(0, 0, b, 0, Math.PI * 2, true);
 
  // Восстанавливаем СК и масштаб
  this.restore();
 
  this.closePath();
}

Тогда её можно вызывать следующим образом:

ctx.drawEllipse(x, y, a, b);


Здесь ctx — контекст отрисовки.

В этой реализации я убрал из тела функции метод stroke(), поэтому чтобы эллипс появился на холсте необходимо ещё дописать

ctx.stroke();


Если есть желание, то можно с лёгкостью реализовать отрисовку закрашенных эллипсов просто заеменив cxt.stroke() на ctx.fill(), можно реализовать отрисовку повёрнутых на заданный угол эллипсов при помощи метода rotate контекста отрисовки. Поигратся и порисовать эллипсы, если конечно у вас человеческий браузер, можно тут. Я поленился и не стал добавлять в демо всех описанных мной дополнительных возможностей.

Скачать исходники

  • SkullZ

    Большое спасибо автор.Я давно искал ответ на мой вопрос.Он тут 🙂

  • Сергей Грибовский

    Ерунда, потому что, если например мне нужно сделать clip изображения по эллипсу — то такой подход уже не проходит.

    • Сергей Грибовский, какой логичный вывод — ерунда.

  • Михаил

    спасибо за статью

  • Kostik

    Этот метод наиболее предпочтителен, имхо:
    http://www.williammalone.com/briefs/how-to-draw-ellipse-html5-canvas/

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

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

      • Kostik

        Вопрос в том, как построить путь для отсечения на основе эллипса ?