베 지어 곡선의 애니메이션 그리기


39

당신의 임무는 제어점이 주어진 베 지어 곡선을 그리는 것입니다. 유일한 기준은 실제로 초기 제어점에서 마지막 제어점까지 곡선을 그리는 방법을 보여 주어야한다는 것입니다.

기준

  • 결과는 애니메이션 처리되어야합니다. 예를 들어 드로잉 프로세스를 어떻게 든 보여야 합니다. 애니메이션을 수행하는 방식은 관련이 없으며을 생성 .gif하거나 창을 그리거나 ASCII 결과를 생성 할 수 있습니다 (각 드로우 후 화면을 지울 수 있음).
  • 최소한 64 개의 제어점을 지원해야합니다.
  • 이것은 인기 콘테스트이므로 더 많은 투표를 위해 프로그램에 몇 가지 기능을 추가 할 수 있습니다. (예를 들어 내 대답은 제어점을 그리고 이미지를 생성하는 방법에 대한 시각적 도움을줍니다)
  • 우승자는 가장 upvoted입니다 유효 칠일 마지막 유효 제출 후 대답.
  • 제출 한 내용이 유효하지 않습니다.

베 지어 곡선을 그리는 방법

우리가 100 번의 반복을 원한다고 가정하자 n곡선의 점 을 얻으려면 다음 알고리즘을 사용할 수 있습니다.

1. Take each adjanced control point, and draw a line between them
2. Divide this line by the number of iterations, and get the nth point based on this division.
3. Put the points you've got into a separate list. Let's call them "secondary" control points.
4. Repeat the whole process, but use these new "secondary" control points. As these points have one less points at each iteration eventually only one point will remain, and you can end the loop.
5. This will be nth point of the Bézier curve

기록 할 때 이해하기가 다소 어려울 수 있으므로 시각화에 도움이되는 몇 가지 이미지가 있습니다.

두 개의 제어점 (검은 점으로 표시)의 경우 한 줄만 검은 색 선으로 표시됩니다. 이 줄을 반복 횟수로 나누고 n점을 얻으면 곡선의 다음 점 (빨간색으로 표시)이 나타납니다.

2 점

세 개의 제어점의 경우 먼저 첫 번째와 두 번째 제어점 사이에서 선을 나눈 다음 두 번째와 세 번째 제어점 사이에서 선을 나눕니다. 포인트가 파란색 점으로 표시됩니다.

그런 다음 여전히 두 개의 점이 있으므로이 두 점 사이에 선을 그리고 (이미지에서 파란색) 곡선의 다음 점을 얻기 위해 다시 나눠야합니다.

3 점

더 많은 제어점을 추가하면 알고리즘은 동일하게 유지되지만 더 많은 반복이 수행됩니다. 다음은 네 가지 점에서 어떻게 보일 것입니다.

4 점

그리고 다섯 포인트 :

5 점

이 이미지를 생성하는 데 사용 된 솔루션을 확인하거나 Wikipedia에서 베 지어 곡선 생성에 대해 자세히 읽을 수 있습니다


"64 개 이상의 제어 지점을 지원해야합니다." 이것이 과도하지 않습니까? 6 개의 제어점이 충분하다고 생각합니다.
DavidC

@DavidCarraher : 알고리즘은 구현하기가 어렵지 않고 O(n^2)시간과 공간 에서 실행되므로 ( n제어점 수는 어디에서나 ) 64는 그렇게 과도해서는 안됩니다 (그리고 많은 제어로 결과는 정말 멋질 수 있습니다) 포인트들). 물론 선택한 언어에 실제로 기술적 인 제한이있어이를 몇도 이상으로 해결하는 것이 불가능하거나 매우 실용적이지 않다면 기꺼이 줄이십시오.
SztupY

1
뒤로 또는 끝없이 반복되는 대신 좋은 변형입니다. 마지막에 첫 번째 점을 버리고 다른 임의의 점을 선택하십시오. 선을 계속 연장하십시오.
QuentinUK

답변:


18

Mathematica에서 수 제어점을 세 줄로 제한하지 않고 재귀 적으로 수행하십시오.

ctrlPts = {{0, 0}, {1, 1}, {2, 0}, {1, -1}, {0, 0}};
getLines[x_List] := Partition[x, 2, 1];
partLine[{a_, b_}, t_] := t a + (1 - t) b;
f[pts_, t_] := NestList[partLine[#, t] & /@ getLines@# &, pts, Length@pts- 1]

계산보다 더 많은 것을 보여줍니다! :

color[x_] := ColorData[5, "ColorList"][[x[[1]]]]
Animate[Graphics[{PointSize[.03], ,
        MapIndexed[{color@#2, Point/@ #1,Line@#1} &, f[ctrlPts,t]],
        Red, Point@Last@f[ctrlPts, t],
        Line@Flatten[(Last@f[ctrlPts, #]) & /@ Range[0, t, 1/50], 1]}], {t, 0, 1}]

계산 부분에 대한 코드 해부 (종류) :

getLines[x_List] := Partition[x, 2, 1]; (* Takes a list of points { pt1, pt2, pt...} 
                                           as input and returns {{pt1,pt2}, {pt2,pt3} ...}
                                           which are the endpoints for the successive
                                           lines between the input points *)

partLine[{a_, b_}, t_] := t a + (1 - t) b;(* Takes two points and a "time" as input 
                                             and calculates
                                             a linear interpolation between them*)

f[pts_, t_] := NestList[partLine[#, t] & /@ getLines@# &, pts, Length@pts- 1]
                                          (* This is where the meat is :) 
     Takes a list of n points and a "time" as input.
     Operates recursively on the previous result n-1 times, preserving all the results 
     Each time it does the following with the list {pt1, pt2, pt3 ...} received:
           1) Generates a list {{pt1, pt2}, {pt2, pt3}, {pt3, pt4} ...}
           2) For each of those sublists calculate the linear interpolation at time "t",
              thus effectively reducing the number of input points by 1 in each round.
     So the end result is a list of lists resembling:
     {{pt1, pt2, pt3 ...}, {t*pt1+(1-t)*pt2, t*pt2+(1-t)*pt3,..}, {t*pt12+(1-t)*pt23, ..},..}
                             --------------   ---------------         
                                  pt12              pt23
     And all those are the points you see in the following image:

여기에 이미지 설명을 입력하십시오

몇 줄이 더 있으면 애니메이션이 실행되는 동안 제어점을 대화식으로 끌 수 있습니다.

Manipulate[
 Animate[Graphics[{
    PointSize[.03], Point@pts,
    MapIndexed[{color@#2, Point /@ #1, Line@#1} &, f[pts, t]],
    Red, Point@Last@f[pts, t], Line@Flatten[(Last@f[pts, #]) & /@ Range[0, t, 1/50], 1]}, 
   PlotRange -> {{0, 2}, {-1, 1}}], {t, 0, 1}],
  {{pts, ctrlPts}, Locator}]

녹색 점을 드래그하면 다음과 같습니다.

여기에 이미지 설명을 입력하십시오

BTW, 수정없이 3D로 동일한 코드 실행 (매스 매 티카 괴짜의 경우는 만 교체 할 필요 Graphics[]에 의해 Graphics3D[]제 3 컨트롤 포인트 목록에 좌표 추가)

여기에 이미지 설명을 입력하십시오

NB :

물론 베 지어를 그리기위한 몇 가지 기본 요소가 있기 때문에 Mathematica에서는이 kludge가 필요하지 않습니다.

Graphics[{BSplineCurve[ctrlPts], Green, Line[ctrlPts], Red,  Point[ctrlPts]}]

Mathematica 그래픽


4
거대한 도서관에 크게 의존하지 않는 수학 솔루션을 보는 것이 좋습니다
izabera

3
Instructions for testing this answer without Mathematica installed: 1) Download http://pastebin.com/qU9rztdf and save it as *.CDF 2) Dowload the free CDF environment from Wolfram Research at https://www.wolfram.com/cdf-player/ (not a small file) 3) "Alt + Left-Click" on Windows or "CMD + Left-Click" on Mac for creating/deleting control points 4) Control points can also be dragged!
Dr. belisarius

15

파이썬

확실히 가장 효율적이거나 아름다운 코드는 아니지만 재미있게 작성했습니다. 다음으로 실행

$> python thisscript.py

제어점은 현재 무작위로 생성되지만 stdin 또는 파일 입력을 허용하도록 확장하는 것은 쉽지 않습니다.

  • 제어점을 표시합니다
  • 반복을 보여줍니다
  • 각 반복마다 다른 색상

보너스로 며칠이 아닌 몇 시간 동안 엔터테인먼트를 제공 할 수있는 무한 모드가 있습니다!

결과 GIF를 저장하려면 Matplotlib이 필요하고 ImageMagick이 필요합니다. 최대 64 개의 제어점에서 테스트되었습니다 (많은 점에서 매우 느리게 실행됩니다!)

예제 출력 gif

import matplotlib
matplotlib.use('GTkAgg')
import pylab as pl
from math import sin,cos,pi
from random import random
import os
import time

class Point:
    def __init__(self,x,y):
        self.x=x
        self.y=y
    def __repr__(self):
        return "[{:.3f},{:.3f}]".format(self.x,self.y)
    def __str__(self):
        return self.__repr__()

class Path:
    def __init__(self,points):
        self.points=points
    def interpolate(self,u): # interpolate the path, resulting in another path with one point less in length
        pts = [None]*(len(self.points)-1)
        for i in range(1,len(self.points)):
            x = self.points[i-1].x + u*( self.points[i].x - self.points[i-1].x)
            if (self.points[i-1].x == self.points[i].x): # vertical line
                y = self.points[i-1].y + u*( self.points[i].y - self.points[i-1].y)
            else:
                y = self.points[i-1].y + (self.points[i].y - self.points[i-1].y)/(self.points[i].x - self.points[i-1].x)*( x - self.points[i-1].x)
            pts[i-1] = Point(x,y)
        return Path(pts)

    def interpolate_all(self,u): # interpolate all the paths
        paths = [None]*len(self.points)
        paths[0]=self
        for i in range(1,len(paths)):
            paths[i] = paths[i-1].interpolate(u)
        return paths

    def draw(self,ax,color,*args,**kwargs):
        x = [ p.x for p in self.points]
        y = [ p.y for p in self.points]
        ax.plot(x,y,*args,color=color,**kwargs)
        ax.scatter([p.x for p in self.points], [p.y for p in self.points],color=color)  

    def __str__(self):
        return str(self.points)

def bezier(path,ustep,ax,makeGif):
    if makeGif:
        os.system("mkdir -p tempgif")
    u=0.
    x=[] # x coordinate list for the bezier path point
    y=[] # y coordinate list for the bezier path point
    pl.ion()
    pl.show()
    n=0
    while u < 1.+ustep: # and not u <= 1.0 to get rid of rounding errors 
        ax.cla()
        paths = path.interpolate_all(u)
        x.append(paths[-1].points[0].x)
        y.append(paths[-1].points[0].y)
        u+=ustep
        for i in range(len(paths)):
            color = pl.cm.jet(1.*i/len(paths)) # <-- change colormap here for other colors, for a list of available maps go to http://wiki.scipy.org/Cookbook/Matplotlib/Show_colormaps
            paths[i].draw(ax,color=color,lw=2) # draw all the paths
        ax.plot(x,y,color='red',lw=5) # draw the bezier curve itself
        pl.draw()
        if makeGif:
            pl.savefig("tempgif/bezier_{:05d}.png".format(n))
            n+=1
    if makeGif:
        print("Creating your GIF, this can take a while...")
        os.system("convert -delay 5 -loop 0 tempgif/*.png "+makeGif)
        os.system("rm -r tempgif/")
        print("Done.")
    pl.ioff()

def getPtsOnCircle(R,n):
    x = [None]*(n+1)
    y = [None]*(n+1)
    for i in range(n+1):
        x[i] = R*cos(i*2.*pi/n)
        y[i] = R*sin(i*2.*pi/n)
    return x,y

def getRndPts(n):
    x = [ random() for i in range(n) ]
    y = [ random() for i in range(n) ]
    return x,y

def run(ax,x,y,makeGif=False):
    ctrlpoints = [ Point(px,py) for px,py in zip(x,y) ]
    path = Path(ctrlpoints)
    bezier(path,0.01,ax,makeGif) # 0.01 is the step size in the interval [0,1]

def endless_mode(ax):
    while True:
        x,y = getRndPts( int(5+random()*10) )
        run(ax,x,y)
        pl.draw()
        time.sleep(0.5) # pause for a moment to gaze upon the finished bezier curve
def main():
    fig = pl.figure(figsize=(6,6)) # <-- adjust here for figure size
    fig.subplots_adjust(0,0,1,1)
    ax = fig.add_subplot(111)
    ax.get_xaxis().set_visible(False)
    ax.get_yaxis().set_visible(False)
    opt = raw_input("[s]ingle run or [e]ndless mode? ")
    if opt=='s':
        gifname = raw_input("Name of output GIF (leave blank for no GIF): ")
        x,y = getRndPts(15)
        run(ax,x,y,gifname if gifname != "" else False)
        pl.show()
    elif opt=='e':
        endless_mode(ax)
    else:
        print("Invalid input: "+opt)

main()

인상적이었습니다. 기타 참고 사항 : PyGTK 는 2.6 및 2.7에서만 사용할 수 있습니다. 그리고 어디에도 언급되어 있지는 않지만, 파이썬 인터프리터가 가장 잘 보이는 시스템에 설치된 GTK + 버전은 2.x 여야합니다.
primo

11

추신

를 생성하려면 gif'방출 페이지'가 true로 정의되어 있는지 확인하십시오.

gs -sDEVICE=png16 -g500x500 -o bezan%03d.png bezanim.ps
convert bezan*.png bezan.gif

추가 기능 :

  • 구성 가능한 경계 상자
  • 2 개의 생성기 : 랜덤 포인트, 랜덤 순열 랜덤 플랫 베 지어
  • "오버레이"애니메이션 (아무것도 지우지 않는 곳).
  • 선택적 '쇼 페이지'( gifs 생성에 사용 , 화면 미리보기에 생략)

고스트 스크립트와 더 큰 점 집합을 사용하면 각 점에서 선이 수렴되는 것을 볼 수 있으므로 화면 미리보기가 생성 된 이미지와 매우 다릅니다.

다음을 사용하여 스케일링 된 간단한 포인트 세트 png48:

간단한 포인트 세트

오버레이가있는 간단한 세트 :

오버레이가있는 간단한 세트

많은 포인트, 오버레이 애니메이션 :

오버레이 애니메이션

비 오버레이 :

오버레이되지 않은

암호:

%!
/iterations 100 def
%/Xdim 612 def
%/Ydim 792 def
/Xdim 400 def
/Ydim 400 def
/scalepage {
    100 100 translate
    %1 5 scale % "tron"?
    1 3 dup dup scale div currentlinewidth mul setlinewidth
} def scalepage
/gen 2 def  % 0:rand points  1:rand permuted bezier
            % 2:special list
/genN 6 def % number of generated points
/overlay true def
/emitpages false def
/emitpartials false def
/.setlanguagelevel where { pop 2 .setlanguagelevel } if

/pairs { % array-of-points  .  array-of-pairs-of-points
    [ exch 0 1 2 index length 2 sub { % A i
        2 copy 2 getinterval % A i A_[i..i+1]
        3 1 roll pop % A_[i..i+1] A
    } for pop ]
} def

/drawpairs {
    gsave
    dup 1 exch length B length div sub setgray
    {
        aload pop
        aload pop moveto
        aload pop lineto
        stroke
        %flushpage
    } forall
    %flushpage
    %emitpartials { copypage } if
    grestore
} def

/points { % array-of-pairs  .  array-of-points
    [ exch { % pair
        [ exch
        aload pop % p0 p1
        aload pop 3 2 roll aload pop % p1x p1y p0x p0y
        exch % p1x p1y p0y p0x
        4 3 roll % p1y p0y p0x p1x
        exch % p1y p0y p1x p0x
        2 copy sub n mul add exch pop % p1y p0y p0x+n(p1x-p0x)
        3 1 roll % p0x+n(p1x-p0x) p1y p0y
        2 copy sub n mul add exch pop % p0x+n(p1x-p0x) p0y+n(p1y-p0y)
        ]
    } forall ]
} def

/drawpoints {
    gsave
    dup length B length div setgray
    {
        newpath
        aload pop 2 copy moveto currentlinewidth 3 mul 0 360 arc fill
        %flushpage
    } forall
    %flushpage
    emitpartials { copypage } if
    grestore
} def

/anim {
    /B exch def
    /N exch def
    /Bp B pairs def
    Bp drawpairs
    1 0 0 setrgbcolor
    B 0 get aload pop moveto
    0 1 N div 1 { /n exch def
        B
        {
            dup length 1 eq { exit } if
            dup drawpoints
            pairs dup drawpairs
            points
        } loop
        aload pop
        aload pop
        2 copy
        gsave
            newpath
            2 copy moveto currentlinewidth 3 mul 0 360 arc fill
        grestore
        lineto currentpoint stroke 2 copy moveto
        gsave
            count dup 1 add copy
            3 1 roll moveto
            2 idiv 1 sub {
                lineto
            } repeat
            pop
            stroke
        grestore
        emitpages {
            currentpoint
            currentrgbcolor
            overlay { copypage }{ showpage scalepage } ifelse
            setrgbcolor
            moveto
        }{
            flushpage 10000 { 239587 23984 div pop } repeat 
            flushpage 4000 { 239587 23984 div pop } repeat
            overlay not {
                erasepage Bp drawpairs
            } if
            %flushpage
        } ifelse
    } for
    moveto N { lineto } repeat stroke
} def

% "main":

iterations
[
    { [ genN { [ rand Xdim mod rand Ydim mod ] } repeat ] }
    {
        40 setflat
        rand Xdim mod rand Ydim mod moveto
        genN 1 sub 3 div ceiling cvi
            { 3 { rand Xdim mod rand Ydim mod } repeat curveto } repeat
        flattenpath
        [{2 array astore}dup{}{}pathforall]
        [exch dup 0 get exch 1 1 index length 1 sub getinterval {
            rand 2 mod 0 eq { exch } if
        } forall]
        0 genN getinterval
    }
    {
        [
            [10 10]
            [100 10]
            [100 100]
            [10 100]
            [10 40]
            [100 40]
            [130 10]
            [50 50]
            [80 50]
            [110 30]
            [20 50]
            [70 50]
            [60 50]
            [10 10]
            [40 50]
            [30 50]
            [10 30]
            [90 50]
            [10 50]
            [120 20]
            [10 20]
        ] 0 genN getinterval
    }
] gen get exec

newpath anim

stroke

오버레이 애니메이션은 현대 미술 용처럼 보입니다
SztupY

꽤 야생입니다! 고스트 스크립트로 미리 볼 때 스트로보 스코프 효과로 인해 지우기가보기 어려웠으므로 제거하려고했습니다. 그러나 오버레이를 사용하더라도 고스트 스크립트 미리보기는 여전히 "깜박"입니다. 아마도 간질에 적합하지 않습니다. :(
luser droog

제어점이 적은 이미지를 몇 개 추가해야합니다. 무슨 일이 일어나고 있는지 알기가 어렵습니다.
primo 2019

예. 좋은 생각.
luser droog

9

루비 + RMagick

추가 기능 :

  • 제어점을 표시합니다
  • 각 반복을 보여줍니다
  • 반복마다 다른 색상을 사용합니다

STDIN에서 포인트를 보내려면 :

$ echo "300 400 400 300 300 100 100 100 200 400" | ./draw.rb

결과는 다음과 result.gif같습니다.

5 점

다음은 12 + 1 포인트의 또 다른 런입니다.

$ echo "100 100 200 200 300 100 400 200 300 300 400 400 300 500 200 400 100 500 0   400 100 300 0   200 100 100" | ./draw.rb

13 점

암호

이것은 골프도 아니고 읽기도 쉽지 않습니다. 죄송합니다.

draw.rb

#!/usr/bin/env ruby
require 'rubygems'
require 'bundler/setup'
Bundler.require(:default)

ITERATIONS = 100

points = ARGF.read.split.map(&:to_i).each_slice(2).to_a
result = []

def draw_line(draw,points)
  points.each_cons(2) do |a,b|
    draw.line(*a, *b)
  end
end

def draw_dots(draw,points,r)
  points.each do |x,y|
    draw.ellipse(x,y,r,r,0,360)
  end
end

canvas = Magick::ImageList.new

0.upto(ITERATIONS) do |i|
  canvas.new_image(512, 512)

  draw = Magick::Draw.new

  draw.stroke('black')
  draw.stroke_width(points.length.to_f/2)
  draw_line(draw,points)
  draw_dots(draw,points,points.length)

  it = points.dup
  while it.length>1
    next_it = []
    it.each_cons(2) do |a,b|
      next_it << [b[0]+(a[0]-b[0]).to_f/ITERATIONS * i, b[1]+(a[1]-b[1]).to_f/ITERATIONS * i]
    end
    draw.stroke("hsl(#{360/points.length.to_f*next_it.length},100,100)")
    draw.fill("hsl(#{360/points.length.to_f*next_it.length},100,100)")
    draw.stroke_width(next_it.length.to_f/2)
    draw_line(draw,next_it)
    draw_dots(draw,next_it,next_it.length)
    it = next_it
  end

  result << it.first

  draw.stroke("hsl(0,100,100)")
  draw.fill("hsl(0,100,100)")
  draw.stroke_width(points.length)
  draw_line(draw,result)
  draw_dots(draw,[it.first],points.length*2)

  draw.draw(canvas)
end

canvas.write('result.gif')

Gemfile

source "https://rubygems.org"
gem 'rmagick', '2.13.2'

8

자바

안티 그레인 지오메트리 의 적응 형 세분화 알고리즘 사용 .

코드는 대화식이며 사용자는 마우스로 4 개의 노드를 드래그 할 수 있습니다.

public class SplineAnimation extends JPanel implements ActionListener, MouseInputListener {
    int     CANVAS_SIZE             = 512;
    double  m_distance_tolerance    = 1;
    double  m_angle_tolerance       = 1;
    int     curve_recursion_limit   = 1000;

    public static void main(String[] args) {
        JFrame f = new JFrame();
        f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

        f.setContentPane(new SplineAnimation());

        f.pack();
        f.setResizable(false);
        f.setLocationRelativeTo(null);
        f.setVisible(true);
    }

    // Graphics.
    BufferedImage   im      = new BufferedImage(CANVAS_SIZE, CANVAS_SIZE, BufferedImage.TYPE_INT_ARGB);
    Graphics2D      imageG  = im.createGraphics();
    Graphics2D      g       = null;
    Ellipse2D       dot     = new Ellipse2D.Double();
    Line2D          line    = new Line2D.Double();
    Path2D          path    = new Path2D.Double();

    // State.
    Point2D[]       pts     = {new Point2D.Double(10, 10), new Point2D.Double(CANVAS_SIZE / 8, CANVAS_SIZE - 10), new Point2D.Double(CANVAS_SIZE - 10, CANVAS_SIZE - 10), new Point2D.Double(CANVAS_SIZE / 2, 10)};
    double          phase   = 0;
    private int     dragPt;
    private double  f;
    private int     n       = 0;

    public SplineAnimation() {
        super(null);
        setPreferredSize(new Dimension(CANVAS_SIZE, CANVAS_SIZE));
        setOpaque(false);

        // Prepare stuff.
        imageG.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
        imageG.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
        imageG.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS, RenderingHints.VALUE_FRACTIONALMETRICS_ON);
        imageG.setRenderingHint(RenderingHints.KEY_ALPHA_INTERPOLATION, RenderingHints.VALUE_ALPHA_INTERPOLATION_QUALITY);
        imageG.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_PURE);
        imageG.setStroke(new BasicStroke(1, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND));
        imageG.translate(0.5, 0.5);
        addMouseListener(this);
        addMouseMotionListener(this);

        // Animate!
        new javax.swing.Timer(16, this).start();
    }

    @Override
    public void paintComponent(Graphics g) {
        this.g = (Graphics2D)getGraphics();
    }

    @Override
    public void actionPerformed(ActionEvent e) {
        // Drawable yet?
        if (g == null)
            return;

        // Clear.
        imageG.setColor(Color.WHITE);
        imageG.fillRect(0, 0, CANVAS_SIZE + 1, CANVAS_SIZE + 1);

        // Update state.
        f = 0.5 - 0.495 * Math.cos(phase += Math.PI / 100);

        // Render.
        path.reset();
        path.moveTo(pts[0].getX(), pts[0].getY());
        recursive_bezier(0, pts[0].getX(), pts[0].getY(), pts[1].getX(), pts[1].getY(), pts[2].getX(), pts[2].getY(), pts[3].getX(), pts[3].getY());
        path.lineTo(pts[3].getX(), pts[3].getY());

        imageG.setPaint(Color.BLACK);
        imageG.draw(path);
        g.drawImage(im, 0, 0, null);

//      if (phase > Math.PI)
//          System.exit(0);
//      save("Bezier" + n++ + ".png");
    }

    private void save(String filename) {
        paint(im.getGraphics());
        try {
            ImageIO.write(im, "PNG", new File(filename));
        }
        catch (IOException e) {}
    }

    // Modified algorithm from Anti Grain Geometry.
    // http://www.antigrain.com/research/adaptive_bezier/index.html
    private void recursive_bezier(int level, double x1, double y1, double x2, double y2, double x3, double y3, double x4,
            double y4) {
        if (level > curve_recursion_limit)
            return;

        // Calculate all the mid-points of the line segments
        // ----------------------
        double x12 = x1 + (x2 - x1) * f;
        double y12 = y1 + (y2 - y1) * f;
        double x23 = x2 + (x3 - x2) * f;
        double y23 = y2 + (y3 - y2) * f;
        double x34 = x3 + (x4 - x3) * f;
        double y34 = y3 + (y4 - y3) * f;
        double x123 = x12 + (x23 - x12) * f;
        double y123 = y12 + (y23 - y12) * f;
        double x234 = x23 + (x34 - x23) * f;
        double y234 = y23 + (y34 - y23) * f;
        double x1234 = x123 + (x234 - x123) * f;
        double y1234 = y123 + (y234 - y123) * f;

        if (level > 0) // Enforce subdivision first time
        {
            // Try to approximate the full cubic curve by a single straight line
            // ------------------
            double dx = x4 - x1;
            double dy = y4 - y1;

            double d2 = Math.abs((x2 - x4) * dy - (y2 - y4) * dx);
            double d3 = Math.abs((x3 - x4) * dy - (y3 - y4) * dx);

            double da1, da2;

            if ((d2 + d3) * (d2 + d3) <= m_distance_tolerance * (dx * dx + dy * dy)) {
                // If the curvature doesn't exceed the distance_tolerance
                // value we tend to finish subdivisions.
                // ----------------------

                // Angle & Cusp Condition
                // ----------------------
                double a23 = Math.atan2(y3 - y2, x3 - x2);
                da1 = Math.abs(a23 - Math.atan2(y2 - y1, x2 - x1));
                da2 = Math.abs(Math.atan2(y4 - y3, x4 - x3) - a23);
                if (da1 >= Math.PI)
                    da1 = 2 * Math.PI - da1;
                if (da2 >= Math.PI)
                    da2 = 2 * Math.PI - da2;

                if (da1 + da2 < m_angle_tolerance) {
                    // Finally we can stop the recursion
                    // ----------------------
                    path.lineTo(x1234, y1234);
                    return;
                }
            }
        }

        // Continue subdivision
        // ----------------------
        recursive_bezier(level + 1, x1, y1, x12, y12, x123, y123, x1234, y1234);
        recursive_bezier(level + 1, x1234, y1234, x234, y234, x34, y34, x4, y4);

        // Draw the frame.
        float c = 1 - (float)Math.pow(1 - Math.sqrt(Math.min(f, 1 - f)), level * 0.2);
        imageG.setPaint(new Color(1, c, c));
        line.setLine(x1, y1, x2, y2);
        imageG.draw(line);
        line.setLine(x2, y2, x3, y3);
        imageG.draw(line);
        line.setLine(x3, y3, x4, y4);
        imageG.draw(line);
        line.setLine(x12, y12, x23, y23);
        imageG.draw(line);
        line.setLine(x23, y23, x34, y34);
        imageG.draw(line);
        node(level + 1, x1234, y1234, level == 0? Color.BLUE : Color.GRAY);
    }

    private void node(int level, double x, double y, Color color) {
        double r = 20 * Math.pow(0.8, level);
        double r2 = r / 2;
        dot.setFrame(x - r2, y - r2, r, r);
        imageG.setPaint(color);
        imageG.fill(dot);
    }

    @Override
    public void mouseClicked(MouseEvent e) {}

    @Override
    public void mouseEntered(MouseEvent e) {}

    @Override
    public void mouseExited(MouseEvent e) {}

    @Override
    public void mousePressed(MouseEvent e) {
        Point mouse = e.getPoint();

        // Find the closest point;
        double minDist = Double.MAX_VALUE;
        for (int i = 0; i < pts.length; i++) {
            double dist = mouse.distanceSq(pts[i]);
            if (minDist > dist) {
                minDist = dist;
                dragPt = i;
            }
        }
    }

    @Override
    public void mouseReleased(MouseEvent e) {}

    @Override
    public void mouseDragged(MouseEvent e) {
        pts[dragPt] = e.getPoint();
    }

    @Override
    public void mouseMoved(MouseEvent e) {}
}

여기에 이미지 설명을 입력하십시오


성스러운! 대단해. +1
luser droog

6

HTML5 + 자바 스크립트 + CSS

그래서 오래 전에 그것을했습니다 (파일의 마지막 수정 날짜는 2012 년 9 월 21 일입니다). 내가 그것을 유지 다행입니다. 불행히도 현재 상태에서 4 개의 제어점 만 지원하지만 현재 노력하고 있습니다.

편집 : UI는 4 개의 제어점 만 지원하지만 기본 기능 ( animateConstruction)은 임의의 수의 제어점을 지원합니다. 코드가 매우 비효율적이므로 10 이상을 수행하지 않는 것이 좋습니다. (25로 시도했지만 작업 관리자를 사용하여 탭을 종료해야했습니다) 이것이 유효한 제출로 간주되면 코드를 개정 할 계획이 아닙니다.

참고 : 나는 당시 순진한 취미가였습니다. 세미콜론 부족 및 사용을 포함하여 여러 수준에서 코드가 잘못되었습니다 eval.

사용

코드를 .html 파일로 저장하고 Chrome 또는 JSfiddle 에서 엽니 다.
4 개 이하의 제어점이 필요하면 오른쪽에 매개 변수를 입력 한 다음 "건설 모드"를 선택하고 왼쪽 하단에서 "애니메이션"을 누르십시오.
더 많은 제어점이 필요하면 animateConstruction함수를 호출하십시오 . 인수로 좌표 배열 (2 항목 배열)을 사용합니다. (예 :) animateConstruction([[0,0],[500,0],[0,500]]. 그리기 영역은 500x500이며 좌표계는 HTML 캔버스 요소를 따릅니다 (왼쪽 상단, x 축은 오른쪽, y 축은 아래쪽)
바이올린의 경우 왼쪽 아래에 텍스트 상자를 추가했습니다. 세미콜론으로 구분 된 좌표를 입력하고 (기본값은 예) Go를 누릅니다.

피들 버전의 차이점

  • 텍스트 상자
  • 기본 애니메이션 단계가 100으로 감소
  • 보조 커브는 기본적으로 꺼져 있습니다

암호

<html>
<head>
<style>
span.h{
    display: inline-block;
    text-align: center;
    text-decoration: underline;
    font: bold 1em Arial;
}

input[type="color"]{
    -webkit-appearance: button-bevel;
    vertical-align: -7px;
    width: 21px;
    height: 27px;
}

input[type="color"][disabled]{background: #FFF}

td{position:relative; padding:1px; text-align:center}
table[class] td{text-align:left}
td.t{padding:1px 5px; width:46px;}
table input[type="checkbox"]{visibility:hidden}
tr:hover input[type="checkbox"]{visibility:visible}
</style>
<script type='text/javascript'>
function Bezier(c){
    if(c.length==2) return function(t){return [c[0][0]+t*(c[1][0]-c[0][0]),c[0][1]+t*(c[1][1]-c[0][1])]}
    else return function(t){return Bezier([Bezier(c.slice(0,-1))(t),Bezier(c.slice(1))(t)])(t)}
}

function Bezier2(f1,f2){
    return function(t){return Bezier([f1(t),f2(t)])(t)}
}

//============================================
var c = null
var settings = {'guide':{'show':[true,true,true,true], 'color':['#EEEEEE','#00FF00','#0000FF','#FF00FF'], 'width':[10,1,1,1]}, 'curve':{'show':[true,true,true,true], 'color':['#EEEEEE','#00FF00','#0000FF','#FF00FF'], 'width':[10,3,3,3]}, 'main':{'show':true, 'color':'#FF0000', 'width':10}, 'sample': 100, 'steps':200, 'stepTime':10, 'mode':'Bezier', 'coords':[[0,500],[125,450],[125,0],[500,0]]}
var itv = 0

window.addEventListener('load',function(){
    c = $('c').getContext('2d')
    c.lineCap = 'round'
    c.lineJoin = 'round'
    draw(settings.coords,1)
},true)

function get(k,i){
    var t = settings
    if(k.constructor == Array) k.forEach(function(e){t = t[e]})
    return t.length>i ? t[i] : t.slice(-1)[0]
}

function frame(coords){
    c.strokeStyle = settings.curve.color[0]
    c.lineWidth = settings.guide.width[0]
    c.beginPath()
    c.moveTo.apply(c,coords[0])
    coords.slice(1).forEach(function(e){c.lineTo.apply(c,e)})
    c.stroke()
}

function transf(c){
    var t = []
    c.forEach(function(e){t.push([e[0]+5,e[1]+5])})
    return t
}
//============================================
function drawBezier(coords,t){
    if(t===undefined) t = 1
    coords = transf(coords)
    c.clearRect(0,0,510,510)
    frame(coords)
    c.beginPath()
    c.strokeStyle = settings.main.color
    c.lineWidth = settings.main.width
    c.moveTo.apply(c,coords[0])
    for(var i=0;i<=t*settings.sample;i++) c.lineTo.apply(c,Bezier(coords)(i/settings.sample))
    c.stroke()
}

function animateBezier(coords){
    var s = settings.steps
    var cur = ($('t').value==1 ? ($('t').value=$('T').innerHTML=(0).toFixed(3))*1 : $('t').value*s)+1
    var b = drawBezier(coords,$('t').value*1)
    itv = setInterval(function(){
        $("T").innerHTML = ($("t").value = cur/s).toFixed(3)
        drawBezier(coords,cur++/s,b)
        if(cur>s) clearInterval(itv)
    },settings.stepTime)
}
//============================================
function drawBezier2(coords,t){
    if(t===undefined) t = 1
    c.beginPath()
    c.strokeStyle = get(['curve','color'],coords.length-1)
    c.lineWidth = get(['curve','width'],coords.length-1)
    c.moveTo.apply(c,coords[0])
    for(var i=0;i<=t*100;i++) c.lineTo.apply(c,Bezier(coords)(i/100))
    c.stroke()
}

function drawConstruction(coords,t,B){
    coords = transf(coords)
    if(t===undefined) t = 0.5
    var b = B===undefined ? [[]] : B
    coords.forEach(function(e){b[0].push(function(t){return e})})
    c.clearRect(0,0,510,510)
    frame(coords)
    for(var i=1;i<coords.length;i++){
        if(B===undefined) b.push([])
        with(c){
            for(var j=0;j<coords.length-i;j++){
                if(B===undefined) b[i].push(Bezier2(b[i-1][j],b[i-1][j+1]))
                if(i!=coords.length-1 && get(['curve','show'],i-1) || i==coords.length-1 && settings.main.show){
                    strokeStyle = i==coords.length-1?settings.main.color:get(['curve','color'],i-1)
                    lineWidth = i==coords.length-1?settings.main.width:get(['curve','width'],i-1)
                    beginPath()
                    moveTo.apply(c,b[i][j](0))
                    for(var k=0;k<=t*settings.sample;k++) lineTo.apply(c,b[i][j](k/settings.sample))
                    stroke()
                }
                if(i && i!=coords.length-1 && get(['guide','show'],i)){
                    strokeStyle = i==coords.length-1?settings.main.color:get(['guide','color'],i)
                    lineWidth = i==coords.length-1?settings.main.width:get(['guide','width'],i)
                    beginPath()
                    if(i!=coords.length-1) arc.apply(c,b[i][j](t).concat([settings.curve.width[0]/2,0,2*Math.PI]))
                    stroke()
                }
            }
            if(i && i!=coords.length-1 && get(['guide','show'],i)){
                beginPath()
                moveTo.apply(c,b[i][0](t))
                for(var j=1;j<coords.length-i;j++) lineTo.apply(c,b[i][j](t))
                stroke()
            }
        }
    }
    return b
}

function animateConstruction(coords){
    var s = settings.steps
    var cur = ($('t').value==1 ? ($('t').value=$('T').innerHTML=(0).toFixed(3))*1 : $('t').value*s)+1
    var b = drawConstruction(coords,$('t').value*1)
    itv = setInterval(function(){
        $("T").innerHTML = ($("t").value = cur/s).toFixed(3)
        drawConstruction(coords,cur++/s,b)
        if(cur>s) clearInterval(itv)
    },settings.stepTime)
}
//============================================
function draw(coords,t){clearInterval(itv); return window['draw'+settings.mode](coords,t)}
function animate(coords){clearInterval(itv); return window['animate'+settings.mode](coords);}
//============================================
function $(id){return document.getElementById(id)}
function v(o,p){
    for(var i in o){
        var k = (p||[]).concat([i]).join('-')
        var t
        if((t = o[i].constructor) == Object || t == Array) v(o[i],[k])
        else if(t = $(k)){
            if(t.type=='checkbox') t.checked = o[i]
            else if(t.type=='radio'){
                for(var j=0, t=document.getElementsByName(t.name); j<t.length; j++) if(t[j].value == o[i]){
                    t[j].checked = true
                    break
                }
            }else t.value = o[i]
        }else if(t = $((i==0?'x':'y') + p[0].slice(-1))) t.value = o[i]
    }
}

document.addEventListener('load',function(){
    v(settings)
    $('t').setAttribute('step',1/settings.steps)
    var t = document.getElementsByTagName('input')
    for(i=0;i<t.length;i++) t[i].addEventListener('change',function(){
        var t
        if((t=this.id.split('-')).length > 1){
            var t1 = function(T){
                var t = 'settings'
                T.forEach(function(e){t += '[' + (isNaN(e)?'"'+e+'"':e) +']'})
                eval(t + '=' + (this.type=='text'?this.value:(this.type=='checkbox'?this.checked:'"'+this.value+'"')))
                $(T.join('-')).value = this.value
            }
            t1.call(this,t)
            if(t[0]=='curve' && t[1]=='color' && $('u').checked==true) t1.call(this,['guide'].concat(t.slice(1)))
        }else if(this.id == 'u'){
            for(i=0;t=$('guide-color-'+i);i++){
                t.disabled = this.checked
                t.value = settings.guide.color[i] = this.checked?settings.curve.color[i]:t.value
            }
        }else if(this.id == 't'){
            $('T').innerHTML = (this.value*1).toFixed(3)
            draw(settings.coords,this.value*1)
        }else if(t = /([xy])(\d+)/.exec(this.id)) settings.coords[t[2]*1][t[1]=='x'?0:1] = this.value*1
        else settings[this.id] = this.value
        if(this.id == 'steps') $("t").setAttribute("step",1/settings.steps)
    },true)
},true)
</script>
</head>
<body>
<canvas style='float:left' width='510' height='510' id='c'>
</canvas>
<div style='padding-left:550px; font-family:Arial'>
<span class='h' style='width:123px'>Control Points</span><br />
(<input type='text' id='x0' size='3' maxlength='3' />,<input type='text' id='y0' size='3' maxlength='3' />)<br />
(<input type='text' id='x1' size='3' maxlength='3' />,<input type='text' id='y1' size='3' maxlength='3' />)<br />
(<input type='text' id='x2' size='3' maxlength='3' />,<input type='text' id='y2' size='3' maxlength='3' />)<br />
(<input type='text' id='x3' size='3' maxlength='3' />,<input type='text' id='y3' size='3' maxlength='3' />)<br /><br />
<span class='h' style='width:200px'>Appearance</span><br />
<span style='font-weight:bold'>Guide lines</span><br />
<input type='checkbox' checked='checked' id='u' onchange='' /> Use curve colors<br />
<table style='border-collapse:collapse'>
<tr><td><input type='checkbox' id='guide-show-0' /></td><td><input type='color' id='guide-color-0' disabled='disabled' /></td><td class='t'>Frame</td><td><input type='text' id='guide-width-0' size='2' maxlength='2' /></td></tr>
<tr><td><input type='checkbox' id='guide-show-1' /></td><td><input type='color' id='guide-color-1' disabled='disabled' /></td><td class='t'>1</td><td><input type='text' id='guide-width-1' size='2' maxlength='2' /></td></tr>
<tr><td><input type='checkbox' id='guide-show-2' /></td><td><input type='color' id='guide-color-2' disabled='disabled' /></td><td class='t'>2</td><td><input type='text' id='guide-width-2' size='2' maxlength='2' /></td></tr>
<tr><td><input type='checkbox' id='guide-show-3' /></td><td><input type='color' id='guide-color-3' disabled='disabled' /></td><td class='t'>3</td><td><input type='text' id='guide-width-3' size='2' maxlength='2' /></td></tr>
</table>
<span style='font-weight:bold'>Curves</span>
<table style='border-collapse:collapse'>
<tr><td><input type='checkbox' id='curve-show-0' /></td><td><input type='color' id='curve-color-0' /></td><td class='t'>1</td><td><input type='text' id='curve-width-0' size='2' maxlength='2' /></td></td></tr>
<tr><td><input type='checkbox' id='curve-show-1' /></td><td><input type='color' id='curve-color-1' /></td><td class='t'>2</td><td><input type='text' id='curve-width-1' size='2' maxlength='2' /></td></td></tr>
<tr><td><input type='checkbox' id='curve-show-2' /></td><td><input type='color' id='curve-color-2' /></td><td class='t'>3</td><td><input type='text' id='curve-width-2' size='2' maxlength='2' /></td></td></tr>
<tr><td><input type='checkbox' id='curve-show-3' /></td><td><input type='color' id='curve-color-3' /></td><td class='t'>4</td><td><input type='text' id='curve-width-3' size='2' maxlength='2' /></td></td></tr>
<tr><td><input type='checkbox' id='main-show' /></td><td><input type='color' id='main-color' /></td><td class='t'>Main</td><td><input type='text' id='main-width' size='2' maxlength='2' /></td></td></tr>
</table><br />
<span class='h' style='width:300px'>Graphing & Animation</span><br />
<table class>
<tr><td>Sample points:</td><td><input type='text' id='sample' /></td></tr>
<tr><td>Animation steps:</td><td><input type='text' id='steps' /></td></tr>
<tr><td>Step time:</td><td><input type='text' id='stepTime' />ms</td></tr>
</table>
<div style='position:absolute; top:526px; left:8px; width:510px; height:100px;'>
<input type='range' id='t' max='1' min='0' style='width:450px' value='1' />&nbsp;&nbsp;&nbsp;<span id='T' style='vertical-align: 6px'>1.000</span><br />
<input type='button' onclick='draw(settings.coords,$("t").value*1)' value='Draw' /><input type='button' onclick='animate(settings.coords)' value='Animate' />
<input type='radio' id='mode' name='mode' value='Bezier' />Basic Mode <input type='radio' id='mode' name='mode' value='Construction' />Construction Mode
</div>
</body>
</html>

쉽게 테스트 할 수 있도록이 jsfiddle을 호환 가능하게 만들어야합니다. Chrome에서 제어 지점을 설정할 수는 없지만 좋은 해결책입니다.
SztupY


6

펄 + 펄

use strict;
use Image::Magick;

sub point
{
    my ($image, $f, $x, $y, $r) = @_;
    $image->Draw(fill => $f, primitive => 'circle', points => $x . ',' . $y . ',' . ($x + $r) . ',' . $y);
}

sub line
{
    my ($image, $f, $x1, $y1, $x2, $y2, $w) = @_;
    $image->Draw(fill => 'transparent', stroke => $f, primitive => 'line', strokewidth => $w, points => "$x1,$y1,$x2,$y2");
}

sub colorize
{
    my $i = shift;
    return ((sin($i * 6) + 0.5) * 255) . ',' . ((sin($i * 6 + 2) + 0.5) * 255) . ',' . ((sin($i * 6 + 4) + 0.5) * 255);
}

sub eval_bezier
{
    my $p = shift;
    my @x = @_;
    my @y;
    for my $i (0 .. $#x - 1)
    {
        $y[$i] = $x[$i] * (1 - $p) + $x[$i + 1] * $p;
    }
    return @y;
}

sub render_bezier
{
    my (%args) = @_;
    my $seq = $args{sequence};
    for my $q (0 .. $args{frames} - 1)
    {
        my $p = $q / ($args{frames} - 1);
        my @x = @{$args{xpoints}};
        my @y = @{$args{ypoints}};
        my $amt = @x;
        my $image = Image::Magick->new(size => $args{size});
        $image->ReadImage('xc:black');
        for my $i (0 .. $amt - 1)
        {
            for my $j (0 .. $#x - 1)
            {
                line($image, 'rgba(' . (colorize $i / $amt). ', 0.5)', $x[$j], $y[$j], $x[$j+1], $y[$j+1], 4 * 0.88 ** $i)
            }
            for my $j (0 .. $#x)
            {
                point($image, 'rgba(' . (colorize $i / $amt). ', 1.0)', $x[$j], $y[$j], 4 * 0.88 ** $i);
            }
            @x = eval_bezier $p, @x;
            @y = eval_bezier $p, @y;
        }
        my ($ox, $oy) = ($x[0], $y[0]);
        for my $q (0 .. $q)
        {
            my $p = $q / ($args{frames} - 1);
            my @x = @{$args{xpoints}};
            my @y = @{$args{ypoints}};
            for (0 .. $amt - 2)
            {
                @x = eval_bezier $p, @x;
                @y = eval_bezier $p, @y;
            }
            line($image, 'rgba(255, 255, 255, 1.0)', $x[0], $y[0], $ox, $oy, 2);
            ($ox, $oy) = ($x[0], $y[0]);
        }
        push @$seq, $image;
    }
}

my @x = (10,190,40,190);
my @y = (190,30,10,110);

my $gif = Image::Magick->new;
render_bezier(xpoints => \@x, ypoints => \@y, sequence => $gif, size => '200x200', frames => 70);
$gif->Write(filename => 'output.gif');

출력 예 :

이 이미지 앨범 에서 다른 출력을 볼 수 있습니다

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