거의 알고리즘 일기장

svg drawing 구현과 문제, 그리고 simplify 방법 본문

web

svg drawing 구현과 문제, 그리고 simplify 방법

건우권 2023. 4. 29. 17:13

https://kunkunwoo.tistory.com/307

 

React Native를 이용한 사이드 프로젝트 만들기 -8. 오랜만의 수정 또 수정 (with 오늘의 그림일기)

예전에 만든 그림일기라는 프로젝트가 있었다. rn 시작할때 만든 프로젝트였는데, 최근에 기억이 나 들어가보니 생각보다 사람들이 써줘서 약 1년? 만에 조금 손보았다. https://kunkunwoo.tistory.com/253

kunkunwoo.tistory.com

 

최근 오늘의 그림일기라는 프로젝트를 리뉴얼하면서 기존의 webview를 이용한 raster drawing 방식에서 svg drawing 방식으로 바꾸었다. react-native-draw라는 모듈을 이용하였는데, 몇몇 문제가 있어 모듈을 살펴볼 일이 좀 있었다.

 

살펴보다보니 이 svg drawing의 방식 자체의 흥미가 들어서 간단하게 작은 웹 프로젝트를 만들어 실험해보았다.


실습 (간단한 drawing)

svg drawing canvas 자체를 만드는 건 간단하다!! html canvas에서 drawing을 만들때와 동일하다! 

(물론, 이벤트의 좌표를 svg 좌표로 매핑하는 과정이 있기는 하다.)

//...
//mouse down
this.originSvgCanvas.addEventListener(
  'mousedown',
  this.mouseDown.bind(this),
);

//mouse move
this.originSvgCanvas.addEventListener(
  'mousemove',
  this.mouseMove.bind(this),
);

//mouse up
this.originSvgCanvas.addEventListener('mouseup', this.mouseUp.bind(this));
//...

이런 형태로
mouse down -> drawing 시작 

mouse move -> drawing

mouse up -> drawing 끝

 

이런 로직만 짜주면 된다.

그러면 이렇게 바로 그릴수있음


문제?

이 방식에는 문제가 있다. 

?

바로 정점의 수가 너무 많다 (퍼포먼스의 문제)

내가 그린 간단한 선 그림을 한번 보자. 시각적으로 보기 편하게 각 정점마다 빨간 점을 추가적으로 그려주었다.

엄청나게 많은 정점수가 보일것이다. 이게 퍼포먼스에 문제를 끼친다.

 

그래서 이 문제를 해결하기위해 chatgpt 군에게 물어보았다.

나의 친구

Laplacian smoothing -> 곡선을 부드럽게

Ramer-Douglas-Peucker -> 정점의 수를 제거

Visvalingam -> 정점의 수를 제거

 

필자는 Laplacian smoothing + Ramer-Douglas-Peucker를 조합해 정점의 수를 제거하고 곡선을 부드럽게 하기로 했다.


Laplacian smoothing 알고리즘 적용

Laplacian smoothing 방법은 gpt 군이 설명한대로 제일 간단하게 곡선을 부드럽게 만드는 방법이다.

 

가중치와 얼마나 반복할지를 정하고
(인접한 점 (prev, next) 의 점의 합  - 현재의 점 *2 ) * 가중치를 더해주는 방식이다.

// laplacian smoothing
const laplacianSmooth = (
  points: Point[],
  iterations: number,
  weight: number,
): Point[] => {
  for (let i = 0; i < iterations; i++) {
    // 첫번째 점은 고정
    const newPoints: Point[] = [points[0]!];

    for (let j = 1; j < points.length - 1; j++) {
      const prev = points[j - 1]!;
      const curr = points[j]!;
      const next = points[j + 1]!;

      const newX = curr.x + weight * (prev.x + next.x - 2 * curr.x);
      const newY = curr.y + weight * (prev.y + next.y - 2 * curr.y);

      newPoints.push({ x: newX, y: newY });
    }

    newPoints.push(points[points.length - 1]!);
    points = newPoints;
  }

  return points;
};

 

적용하면 이런식으로 선이 부드러워진다.

Laplacian smoothing 알고리즘을 적용한후

하지만, 정점의 수의 차이는 없다.

이제 퍼포먼스에서 가장 중요한 정점의 수를 줄여보자.


Ramer-Douglas-Peucker 알고리즘 적용

이 방법은 허용치를 정해놓고, 허용치보다 큰 거리를 가진 점을 유지하며 다른 점들을 제거함으로써 주어진 점들을 단순화한다.

재귀적으로 배열을 두 부분으로 나눕니다.
각 부분의 시작점과 끝점 사이에 있는 점들 중에서 선분과 가장 멀리 떨어진 점을 찾습니다.
거리가 epsilon보다 크면, 해당 점을 기준으로 배열을 두 부분으로 나누고, 이 과정을 각 부분에 대해 반복합니다.
거리가 epsilon보다 작으면, 해당 점을 제거하고 시작점과 끝점만 남깁니다.
// 라머-도플러스-피커 알고리즘
const ramerDouglasPeucker = (points: Point[], epsilon: number): Point[] => {
  if (points.length < 3) return points;

  let maxDist = 0;
  let index = 0;

  for (let i = 1; i < points.length - 1; i++) {
    const dist = distanceToSegment(
      points[i]!,
      points[0]!,
      points[points.length - 1]!,
    );
    if (dist > maxDist) {
      maxDist = dist;
      index = i;
    }
  }

  if (maxDist > epsilon) {
    const firstHalf = points.slice(0, index + 1);
    const secondHalf = points.slice(index);
    const result1 = ramerDouglasPeucker(firstHalf, epsilon);
    const result2 = ramerDouglasPeucker(secondHalf, epsilon);

    return [...result1.slice(0, -1), ...result2];
  } else {
    return [points[0]!, points[points.length - 1]!];
  }
};

적용하게 되면 점이 꽤나 줄어든다.

Ramer-Douglas-Peucker 알고리즘 적용
꽤나 줄어듬

자세한 내용을 적지는 않겠지만, 여기서 L으로 그렸던 path들을 Q(2차 베지어)로 바꾸면 곡선에 대한 표현들이 괜찮아진다.


그러면 그림일기에서 쓴 라이브러리(react-native-draw)에서는 무슨 알고리즘을 썼을까??

여기서는 graphics gem 1권에 나온 AN ALGORITHM FOR AUTOMATICALLY FITTING DIGITIZED CURVES(612 ~ 625) 의 알고리즘을 사용하였다.

추가적인 휴리스틱 과정이 있고 꽤나 복잡하다.

관심이 있으면 아래의 link와 책을 보기 바란다. (책은 graphics gem 1 pdf 라고 구글에 치면 바로나온다.)

https://github.com/luncheon/simplify-svg-path/blob/main/index.ts

 

GitHub - luncheon/simplify-svg-path: Extracts `Path#simplify()` from Paper.js.

Extracts `Path#simplify()` from Paper.js. Contribute to luncheon/simplify-svg-path development by creating an account on GitHub.

github.com


reference

https://github.com/BenJeau/react-native-draw

 

GitHub - BenJeau/react-native-draw: SVG based data-driven React Native drawing component 🎨

SVG based data-driven React Native drawing component 🎨 - GitHub - BenJeau/react-native-draw: SVG based data-driven React Native drawing component 🎨

github.com

https://www.youtube.com/watch?v=jvPPXbo87ds&t=2891s

+ chatgpt

반응형
Comments