본문 바로가기

나의 플랫폼/안드로이드

[ Android ] 베지어(Bezier) 곡선 그리기

336x280(권장), 300x250(권장), 250x250, 200x200 크기의 광고 코드만 넣을 수 있습니다.
소프트웨어 쪽만 공부하던 저이기에 곡선 움직임을 그리기위해 열심히 구글링을 해봐도,

간단한 곡선하나 그리기가 힘들고 어려웠습니다.

열심히 조사하고, 읽어보고, 거의 한 일주일간 알아봤던 것 같네요.!!!

결국!!! 베지어 곡선을 그리는데 성공을 했습니다.
역시 포기 하지 않고, 읽어보고 실험을 해보니 어느 순간 머리 속으로 들어오더라구요.

자.. 뻘소리 그만하고, 소스를 보기 전에 베지어 곡선을 사용할 때 꼭 알아두어야 할 것이 있습니다.

1. 베지어 곡선은 여러 포인트로 곡선을 그린다. 적어도 포인트 3개 정도는 되야 곡선이 만들어집니다.
2. 베지어 곡선 공식에서 'u'라는 변수 사용하는데요. 이 변수는 0부터 1까지만 지정합니다. (소스에서는 mu)
    0은 곡선의 시작점을 나타내고, 1은 곡선의 끝점을 나타냅니다.
3. 절대 포인트 두 개 사이에서 곡선을 그린다는 생각은 버리십시요.( 제가 이렇게 생각했었음...)
   포인트 하나하나 위치가 곡선의 모양을 바꿔주는 거지 점 2개로 곡선을 그리는 게 아닙니다.  


위와 같은 생각을 한 이후로, 쉽게 베이지 곡선을 이해하고 사용할 수 있게되었습니다.

이해를 도와준 사이트!!
http://eunchul.com/Algorithms/BezierCurves/BezierCurves.htm

http://kimhn8312.cafe24.com/tt/entry/%BA%A3%C1%F6%BE%EE-%B0%EE%BC%B1-%B0%F8%BD%C4-%C7%C1%B7%CE%B1%D7%B7%A1%B9%D6?category=11&TSSESSIONkimhn8312cafe24comtt=68778ee31e9352403736507b3cd7879b 

소스는 ApiDemo에서 FingerPaint를 수정해서 만들어보았습니다.

클래스는 3가지로 되어있습니다.

PathPoint : 현재 위치 좌표를 나타내는 Beans 클래스
BezierCurveSampleActivity : 메뉴로 차원을 변경 시켜주고, 화면에 그려주는 Activity클래스
Bezier : 베지어 곡선을 설정하고 계산해 주는 클래스 


 PathPoint

package com.test.bezier;


public class PathPoint {

public double x;

public double y;

public double z;

public PathPoint() {

super();

}

public PathPoint(double x) {

super();

this.x = x;

}

public PathPoint(double x, double y) {

super();

this.x = x;

this.y = y;

}

public PathPoint(double x, double y, double z) {

super();

this.x = x;

this.y = y;

this.z = z;

}

}



 Bezier

package com.test.bezier;


public class Bezier {

private PathPoint p1,p2,p3,p4; // 각각 의 조절 포인트 (4차원 까지만 지정)

private PathPoint[] arrPn; // n차원의 조절 포인트 저장

private double mu; /* 곡선의 시작과 끝을 알리는 변수 ( 0 부터 1사이  )

0 : 시작, 1: 끝*/

private PathPoint resultPoint; // 계산된 결과를 넘겨주는 포인트

public Bezier() {

super();

resultPoint = new PathPoint();

initResultPoint();

}

// 3차원 베지어 곡선 포인터 설정

public void setBezier3(PathPoint p1 , PathPoint p2 , PathPoint p3){

this.p1 = p1;

this.p2 = p2;

this.p3 = p3;

}

// 3차원 베지어 곡선 계산

public void nextBezier3(){

double mum1,mum12,mu2;

initResultPoint();

   mu2 = mu * mu;

   mum1 = 1 - mu;

   mum12 = mum1 * mum1;

   resultPoint.x = p1.x * mum12 + 2 * p2.x * mum1 * mu + p3.x * mu2;

   resultPoint.y = p1.y * mum12 + 2 * p2.y * mum1 * mu + p3.y * mu2;

   resultPoint.z = p1.z * mum12 + 2 * p2.z * mum1 * mu + p3.z * mu2;

}

// 4차원 베지어 곡선 포인터 설정

public void setBezier4(PathPoint p1 , PathPoint p2 , PathPoint p3 , PathPoint p4){

this.p1 = p1;

this.p2 = p2;

this.p3 = p3;

this.p4 = p4;

}

// 4차원 베지어 곡선 계산

public void nextBezier4(){

double mum1,mum13,mu3;

initResultPoint();

   mum1 = 1 - mu;

   mum13 = mum1 * mum1 * mum1;

   mu3 = mu * mu * mu;

   resultPoint.x = mum13*p1.x + 3*mu*mum1*mum1*p2.x + 3*mu*mu*mum1*p3.x + mu3*p4.x;

   resultPoint.y = mum13*p1.y + 3*mu*mum1*mum1*p2.y + 3*mu*mu*mum1*p3.y + mu3*p4.y;

   resultPoint.z = mum13*p1.z + 3*mu*mum1*mum1*p2.z + 3*mu*mu*mum1*p3.z + mu3*p4.z;

}

// N차원 베지어 곡선 포인터 설정

public void setBezierN(PathPoint[] arrPn){

this.arrPn = arrPn;

}

// N차원 베지어 곡선 계산

public void nextBezierN(){

int k,kn,nn,nkn;

   double blend,muk,munk;

   int n = arrPn.length-1;

   

   initResultPoint();

   muk = 1;

   munk = Math.pow(1-mu,(double)n);

   for (k=0;k<=n;k++)

   {

       nn = n;

       kn = k;

       nkn = n - k;

       blend = muk * munk;

       muk *= mu;

       munk /= (1-mu);

       while (nn >= 1) {

           blend *= nn;

           nn--;

           if (kn > 1) {

               blend /= (double)kn;

           kn--;

           }

           if (nkn > 1) {

               blend /= (double)nkn;

               nkn--;

           }

       }

       resultPoint.x += arrPn[k].x * blend;

       resultPoint.y += arrPn[k].y * blend;

       resultPoint.z += arrPn[k].z * blend;

   }

}
 

       // 결과값을 초기화

public void initResultPoint(){

resultPoint.x = 0.0;

resultPoint.y = 0.0;

resultPoint.z = 0.0;

}

    
 
       // u변수를 설정

public void setMu(double mu){

this.mu = mu;

}

        // 계산 결과값을 넘긴다.
        public PathPoint getResult(){

return resultPoint;

}

}


 
BezierCurveSampleActivity 

/*

 * Copyright (C) 2007 The Android Open Source Project

 *

 * Licensed under the Apache License, Version 2.0 (the "License");

 * you may not use this file except in compliance with the License.

 * You may obtain a copy of the License at

 *

 *      http://www.apache.org/licenses/LICENSE-2.0

 *

 * Unless required by applicable law or agreed to in writing, software

 * distributed under the License is distributed on an "AS IS" BASIS,

 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.

 * See the License for the specific language governing permissions and

 * limitations under the License.

 */


package com.test.bezier;


import android.app.Activity;

import android.content.Context;

import android.graphics.*;

import android.os.Bundle;

import android.os.Debug;

import android.util.Log;

import android.view.Display;

import android.view.Menu;

import android.view.MenuItem;

import android.view.View;

import android.view.WindowManager;


public class BezierCurveSampleActivity extends Activity {

private static final int POINT_COUNT = 5;            // n차원의 갯수 지정

private static final int DRAW_LINE_COUNT = 26;

private Bezier bezier;

private View   drawView;

private int mWidth;

private int mHeight;

private int menuSelectedId = Menu.FIRST;

    @Override

    protected void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);

        drawView = new MyView(this);

        setContentView(drawView);

        

        // 화면 사이즈 

        Display display = ((WindowManager)getSystemService(WINDOW_SERVICE)).getDefaultDisplay();

        mWidth = display.getWidth();

        mHeight = display.getHeight();

        

        // 베지어 곡선 초기화 (기본 3차원)

        bezier = new Bezier();

        settingBezier3();

        

        mPaint = new Paint();

        mPaint.setAntiAlias(true);

        mPaint.setDither(true);

        mPaint.setColor(0xFFFF0000);

        mPaint.setStyle(Paint.Style.STROKE);

        mPaint.setStrokeJoin(Paint.Join.ROUND);

        mPaint.setStrokeCap(Paint.Cap.ROUND);

        mPaint.setStrokeWidth(12);

    }

    

    private Paint       mPaint;

    

    public void colorChanged(int color) {

        mPaint.setColor(color);

    }


    public class MyView extends View {

        

        public MyView(Context c) {

            super(c);

        }


        @Override

        protected void onSizeChanged(int w, int h, int oldw, int oldh) {

            super.onSizeChanged(w, h, oldw, oldh);

        }

        

        @Override

        protected void onDraw(Canvas canvas) {

            canvas.drawColor(0xFFAAAAAA);

            // 선 간격

            double muGap = 1.0/DRAW_LINE_COUNT;

            

            PathPoint startP = new PathPoint();

            PathPoint endP = new PathPoint();

            // mu값이 1이 되었을 때가 목표점에 도달했다는 의미

            for(double mu = 0; mu <= 1 ; mu += muGap ){

            // 선 시작점

            startP.x = bezier.getResult().x;

            startP.y = bezier.getResult().y;

           

            // 베지어 곡선 다음 위치를 얻는다.

            bezier.setMu(mu);

            switch (menuSelectedId) {

            case BEZIER3_MENU_ID:

            bezier.nextBezier3();            

break;

            case BEZIER4_MENU_ID:

            bezier.nextBezier4();

break;

            case BEZIERN_MENU_ID:

            bezier.nextBezierN();

break;

}

           

            // 선 끝점

            endP.x = bezier.getResult().x;

            endP.y = bezier.getResult().y;

            canvas.drawLine((float)startP.x, (float)startP.y, (float)endP.x, (float)endP.y, mPaint);

            }

        }

    }

    

    private static final int BEZIER3_MENU_ID = Menu.FIRST;

    private static final int BEZIER4_MENU_ID = Menu.FIRST + 1;

    private static final int BEZIERN_MENU_ID = Menu.FIRST + 2;

    

    @Override

    public boolean onCreateOptionsMenu(Menu menu) {

        super.onCreateOptionsMenu(menu);

        

        menu.add(0, BEZIER3_MENU_ID, 0, "3개 점");

        menu.add(0, BEZIER4_MENU_ID, 0, "4개 점");

        menu.add(0, BEZIERN_MENU_ID, 0, "n개 점");


        return true;

    }

    

    @Override

    public boolean onPrepareOptionsMenu(Menu menu) {

        super.onPrepareOptionsMenu(menu);

        return true;

    }

    

    @Override

    public boolean onOptionsItemSelected(MenuItem item) {

        mPaint.setXfermode(null);

        mPaint.setAlpha(0xFF);

        

        menuSelectedId = item.getItemId();

        

        switch (item.getItemId()) {

            case BEZIER3_MENU_ID:

            settingBezier3();

            drawView.invalidate();

                return true;

            case BEZIER4_MENU_ID:

            settingBezier4();

            drawView.invalidate();

                return true;

            case BEZIERN_MENU_ID:

            settingBezierN();

            drawView.invalidate();

                return true;

        }

        return super.onOptionsItemSelected(item);

    }

    

    private void settingBezier3(){

    PathPoint[] arrP3 = new PathPoint[3];

   

    for(int index = 0 ; index < arrP3.length ; index++){

    PathPoint p = new PathPoint();

    p.x = getRandomDoubleNum(0, mWidth);

    p.y = getRandomDoubleNum(0, mHeight);

   

    arrP3[index] = p;

//     p.z = getRandomDoubleNum(0, 1);    canvas는 2차원이라 ...

    }

   

    bezier.setBezier3(arrP3[0], arrP3[1], arrP3[2]);

    }

    

    private void settingBezier4(){

    PathPoint[] arrP4 = new PathPoint[4];

   

    for(int index = 0 ; index < arrP4.length ; index++){

    PathPoint p = new PathPoint();

    p.x = getRandomDoubleNum(0, mWidth);

    p.y = getRandomDoubleNum(0, mHeight);

//     p.z = getRandomDoubleNum(0, 1);    canvas는 2차원이라 ...

   

    arrP4[index] = p;

    }

   

    bezier.setBezier4(arrP4[0], arrP4[1], arrP4[2], arrP4[3]);

    }

    private void settingBezierN(){

    PathPoint[] arrPn = new PathPoint[POINT_COUNT];

   

    for(int index = 0 ; index < arrPn.length ; index++){

    PathPoint p = new PathPoint();

    p.x = getRandomDoubleNum(0, mWidth);

    p.y = getRandomDoubleNum(0, mHeight);

//     p.z = getRandomDoubleNum(0, 1);    canvas는 2차원이라 ...

   

    arrPn[index] = p;

    }

   

    bezier.setBezierN(arrPn);

    }

    private double getRandomDoubleNum(double min, double max){

Double randomNum = ( Math.random() * (max - min + 1) ) + min ;

return  randomNum;

}

}


 

메뉴 부분은 대부분 아실꺼라 생각해서 주석을 달지 않았습니다.

결과 화면



                      < 3차원 >                                                          < 4차원 >                                            
          < n차원(현재 5) >                                                                     
베지어 계산 공식은 이해 못했지만, 그릴수는 있게 되었습니다.

혹시 지금 소스에 관해서 궁금하신 점이 있으시면 언제든지 댓글 달아주세요^^

 많은 분들에게 이 소스가 도움이 되었으면 좋겠네요.

그럼 오늘도 즐코딩!!! 

소스 :