간단한 곡선하나 그리기가 힘들고 어려웠습니다.
열심히 조사하고, 읽어보고, 거의 한 일주일간 알아봤던 것 같네요.!!!
결국!!! 베지어 곡선을 그리는데 성공을 했습니다.
역시 포기 하지 않고, 읽어보고 실험을 해보니 어느 순간 머리 속으로 들어오더라구요.
자.. 뻘소리 그만하고, 소스를 보기 전에 베지어 곡선을 사용할 때 꼭 알아두어야 할 것이 있습니다.
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) >
베지어 계산 공식은 이해 못했지만, 그릴수는 있게 되었습니다.
혹시 지금 소스에 관해서 궁금하신 점이 있으시면 언제든지 댓글 달아주세요^^
많은 분들에게 이 소스가 도움이 되었으면 좋겠네요.
그럼 오늘도 즐코딩!!!
소스 :
'나의 플랫폼 > 안드로이드' 카테고리의 다른 글
[ Android ] 지나쳐 온 Activity 제거 방법 (0) | 2011.08.04 |
---|---|
[ Android ] Camera 호출 후 이미지 Crop하기 예제 (3) | 2011.07.29 |
[ Android ] 이미지 버튼 만들기 (0) | 2011.07.25 |
[ Android ] 잠금 화면 상태 확인 (0) | 2011.07.25 |
[ Android ] 전체 화면 사용 시 (0) | 2011.07.25 |