HTML5 캔버스에서 타원을 그리는 방법?


81

타원형 모양을 그리는 기본 기능이없는 것 같습니다. 또한 나는 달걀 모양을 찾고 있지 않습니다.

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

7
아래 답변 중 일부와 달리 베 지어 곡선을 사용하여 타원을 그릴 수 없습니다. 다항식 곡선으로 타원을 근사 할 수는 있지만 정확하게 재현 할 수는 없습니다.
Joe

나는 +1을 주었지만 이것은 나를 위해 작동하지 않는 원과 함께 선을 왜곡합니다. 그래도 좋은 정보.
mwilcox

1
@mwilcox- Johnathan Hebert가 아래 Steve Tranby의 답변에 대한 의견에서 언급했듯이 restore()앞에 넣으면 stroke()라인이 왜곡되지 않습니다. 또한 이것은 불필요하고 closePath(). 이러한 오해 된 방법 ... stackoverflow.com/questions/10807230/…
Brian McCutchon 2013-08-02

답변:


114

업데이트 :

  • 크기 조절 방법이 획 너비 모양에 영향을 줄 수 있음
  • 스케일링 방법을 올바르게 수행하면 획 너비를 그대로 유지할 수 있습니다.
  • canvas에는 Chrome이 이제 지원하는 타원 메서드가 있습니다.
  • JSBin에 업데이트 된 테스트 추가

JSBin 테스트 예제 (비교를 위해 다른 사람의 답변을 테스트하도록 업데이트 됨)

  • 베 지어-직사각형 및 너비 / 높이를 포함하는 왼쪽 상단을 기준으로 그리기
  • 중심이있는 베 지어-중심 및 너비 / 높이를 기준으로 그리기
  • 호 및 스케일링-원 그리기 및 스케일링을 기반으로 그리기
  • 2 차 곡선-2 차로 그리기
    • 테스트는 똑같이 그려지지 않는 것 같습니다. 구현 일 수 있습니다.
    • 참조 oyophant의 대답을
  • Canvas Ellipse-W3C 표준 ellipse () 메서드 사용
    • 테스트는 똑같이 그려지지 않는 것 같습니다. 구현 일 수 있습니다.
    • 참조 Loktar의 대답을

실물:

대칭 타원을 원하면 항상 반경 너비의 원을 만든 다음 원하는 높이로 크기를 조정할 수 있습니다 ( 편집 : 획 너비 모양에 영향을 미침-acdameli의 답변 참조), 타원을 완전히 제어하려면 베 지어 곡선을 사용하는 한 가지 방법이 있습니다.

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

1
이것은 scale스트로크를 왜곡 하는 가장 일반적인 솔루션 입니다 (acdameli의 답변 참조).
Trevor Burnham 2011

4
ctx.stroke ()는 ctx.fill ()로 대체하여 채워진 모양을 가질 수 있습니다.
h3xStream

13
확장 할 수 방금 다음, 첫 번째 확장 아크 또는 무엇이든을 사용하여 경로에 추가 한 후 원본을 축소 아닌 경우 왜곡 스트로크 뇌졸중 기존 경로
조나단 허버트을

1
4 * (sqrt (2) -1) / 3의 근사치만으로도 Google의 계산 결과가 0.55228475 인 많은 곳을 찾았습니다. : 빠른 검색이 훌륭한 자원을 얻을 수 whizkidtech.redprince.net/bezier/circle/kappa
스티브 Tranby

1
어리석은 질문이 아닙니다. 타원을 둘러싸는 직사각형을 상상하면 x, y는 왼쪽 상단 모서리이고 중심은 점 {x + w / 2.0, y + h / 2.0}에 있습니다
Steve Tranby 2013

45

다음은 다른 솔루션의 단순화 된 버전입니다. 표준 원을 그리고 이동하고 크기를 조정 한 다음 스트로크합니다.

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

2
이것은 꽤 우아합니다. 하자 context와 무거운 작업을 수행 scale.
adu

1
스트로크 이전에 매우 중요한 복원에 유의하십시오 (그렇지 않으면 스트로크 너비가 왜곡됨)
Jason S

정확히 내가 찾던 것, 감사합니다! 베 지어 방식 외에는 가능하다는 것을 알았지 만 스스로 작업 할 수있는 규모를 얻을 수 없었습니다.

@JasonS 스트로크 후 복원하면 스트로크 너비가 왜곡되는 이유가 궁금하십니까?
OneMoreQuestion

17

이제 캔버스에 대한 네이티브 타원 함수가 있습니다. arc 함수와 매우 유사하지만 이제 두 개의 반경 값과 멋진 회전이 있습니다.

ellipse (x, y, radiusX, radiusY, rotation, startAngle, endAngle, 반 시계 방향)

라이브 데모

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

현재 Chrome에서만 작동하는 것 같습니다.


2
네, 이것의 구현은 느립니다.

@Epistemex 동의합니다. 시간이 지남에 따라 더 빨라질 것이라고 상상 / 희망합니다.
Loktar

developer.mozilla.org/en-US/docs/Web/API/… 에 따르면 현재 Opera에서도 지원됩니다
jazzpi

16

베 지어 곡선 접근 방식은 단순한 타원에 적합합니다. 더 많은 제어를 위해 루프를 사용하여 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


이 방법은 수학적으로 가장 정확한 타원을 생성합니다. 계산 비용 측면에서 다른 것과 어떻게 비교되는지 궁금합니다.
에릭

@Eric 머리 꼭대기에서 (이 방법 : 곱셈 12 개, 더하기 4 개, 삼각 함수 8 개)와 (4 큐빅 베 지어 : 24 곱셈과 24 더하기) 사이의 차이 인 것 같습니다. 그리고 그것은 캔버스가 De Casteljau 반복적 접근 방식을 사용한다고 가정하고 있습니다. 실제로 다양한 브라우저 캔버스에서 어떻게 구현되는지 잘 모르겠습니다.
DerekR

4 입방 베지 : 약 0.3 밀리이 방법 단계가 동일한 것 = 0.3ms 대해 0.1 내지 약 1.5 밀리 크롬이 2 접근하려고 프로필
sol0mka

분명히 CanvasRenderingContext2D.ellipse ()는 모든 브라우저에서 지원되지 않습니다. 이 루틴은 훌륭하고 완벽하게 작동하며 워크샵 템플릿에 충분합니다 . Internet Explorer에서도 작동합니다.
zipzit

12

타원을 안정적으로 재현하려면 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();


}

이 솔루션은 저에게 가장 적합합니다. 4 개의 매개 변수. 빨리. 쉬운.
Oliver

안녕하세요,이 솔루션도 마음에 들었지만 코드를 가지고 놀았을 때 매개 변수 값의 왼쪽에 0을 추가 할 때 원이 비뚤어진 것을 발견했습니다. 이것은 원의 크기가 커짐에 따라 강조됩니다. 왜 이런 일이 발생할 수 있는지 알고 있습니까? jsfiddle.net/BsPsj/187
alexcons

1
@alexcons 앞에 0을 붙이면 8 진 정수 리터럴이됩니다.
Alankar Misra

11

비 균일 스케일링을 사용해 볼 수도 있습니다. X 및 Y 스케일링을 제공 할 수 있으므로 X 또는 Y 스케일링을 다른 것보다 크게 설정하고 원을 그리면 타원이 생깁니다.


9

나는 너무 일반적인 타원을 원하지 않고 더 큰 반축과 타원의 편심 데이터 만 가진 사람들 을 위해이 코드 (부분적으로 Andrew Staroscik에 의해 제시됨)를 약간 수정 했습니다 (궤도를 그리는 천문 자바 스크립트 장난감에 적합) 예를 들어).

이제 단계를 조정 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();
}

5

내 솔루션은 이들 모두와 약간 다릅니다. 가장 가까운 답은 위에서 가장 많이 득표 한 답이라고 생각하지만이 방법이 좀 더 깔끔하고 이해하기 더 쉽다고 생각합니다.

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를 사용하여 실패한 시도와 함께 바이올린에 몇 가지 사용 예제가 있습니다.


코드는 멋져 보이지만 동일한 높이와 너비 값을 전달하여 원을 그리는 데 사용하면 모서리가 매우 둥근 정사각형과 비슷한 모양이됩니다. 그런 경우에 진정한 서클을 얻을 수있는 방법이 있습니까?
Drew Noakes

이 경우 가장 좋은 방법은 arc () 메서드를 사용하는 것입니다. 그러면 여기에서 가장 많이 득표 한 답변이 수행되는 방식과 유사하게 호의 시작 및 끝 각도 위치를 정의 할 수 있습니다. 전체적으로도 최고의 답변이라고 생각합니다.
jaredwilli

4

위의 베 지어 곡선 솔루션이 마음에 듭니다. 배율도 선 너비에 영향을 미치므로 높이보다 넓은 타원을 그리려고하면 위쪽 및 아래쪽 "측면"이 왼쪽 및 오른쪽 "측면"보다 얇게 나타납니다.

좋은 예는 다음과 같습니다.

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

타원의 정점과 골짜기에있는 선의 너비가 왼쪽 및 오른쪽 정점 (apices?)의 절반 너비임을 알 수 있습니다.


5
스트로크 작업 직전에 스케일을 반전하면 스트로크 스케일링을 피할 수 있습니다. 따라서 마지막 줄의 두 번째 줄에 반대 스케일링을 추가하십시오ctx.scale(0.5, 1);
Johnathan Hebert


3

Chrome 및 Opera 는 canvas 2d 컨텍스트에 대해 타원 방법을 지원 하지만 IE, Edge, Firefox 및 Safari에서는 지원하지 않습니다.

JS로 ellipse 메소드를 구현하거나 타사 폴리 필을 사용할 수 있습니다.

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

타원 1

타원 2

타원 3

타원 4


오늘의 최고의 답변입니다. 네이티브 이클립스 함수는 아직 실험적이므로 폴백을 갖는 것이 절대적으로 굉장합니다. 감사!
walv

2

이것은 "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>

2

이를 통해 타원의 세그먼트를 그릴 수도 있습니다.

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/


2

아무도 간단한 방법을 사용하지 않았기 때문에 이에 quadraticCurveTo대한 해결책을 추가하고 있습니다. @Steve bezierCurveTo답변 에 있는 호출을 다음과 같이 바꾸십시오 .

  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. 타원은 약간 다르게 보입니다.


0

다음은 SVG의 타원 호와 동일한 값을 사용하는 함수입니다. X1 & Y1은 마지막 좌표, X2 & Y2는 끝 좌표, 반지름은 숫자 값, 시계 방향은 부울 값입니다. 또한 캔버스 컨텍스트가 이미 정의되었다고 가정합니다.

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

타원이 직사각형 안에 완전히 들어가도록하려면 실제로 다음과 같습니다.

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)';이 디자인으로 채워지지 .

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.