Image Polygonal Lasso Tool 구현하기

유영석·2022년 3월 2일
4
post-thumbnail

프로젝트를 진행하면서 실시간으로 이미지를 정밀하게 깍아내는 기능이 필요했다.
이미지를 처리할때 매번 네트워크를 통해 백엔드에서 처리하다보니 반응속도가 느려졌고, 이는 곧 UX의 저하로 이어졌다. 이미지 처리를 백엔드에서 하려면 이러한 과정을 거치게 된다.

네트워크 요청 > 이미지 로딩 > 이미지 프로세싱 > 이미지 생성 > 이미지 저장 > 네트워크 응답

이미지를 프론트엔드에서 처리하면 이미지는 이미 로딩되어있기 때문에 많은 단계를 줄일 수 있다. 이는 곧 UX향상과 코스트 절감으로 연결된다.

네트워크 요청 > 이미지 로딩 > 이미지 프로세싱 > 이미지 생성 > 이미지 저장 > 네트워크 응답

또한 이미지는 클라우드 스토리지(S3 등)로 직접 업로드하여 백엔드에서 처리할 일을 굉장히 줄여줄 수 있다.
따라서 프론트엔드에서 빠르고 정밀하게 이미지를 처리할 수 있는 기능을 만들어야했는데, HTML5 canvas library인 fabric.js를 이용해 구현한 경험을 공유하고자 한다.

결과물부터 보면 다음과 같다. Demo link
Demo Preview

캔버스에 클릭 이벤트를 통해 생성되는 선(path)의 시작점과 끝점을 이어 다각형(polygon)으로 만들어 해당 부분을 이미지에서 제거한다.
이미지를 깍아내기 위한 로직은 다음과 같다.

  1. 이미지에서 클릭 이벤트를 통해 선을 생성한다.
    • Mouse click event(down > move > up)를 이용하여 선(path)을 그린다.
  2. 생성된 선의 시작점과 끝점을 이어 다각형으로 변환한다. 생성되었던 선은 삭제한다.
  3. 만들어진 다각형을 하얀색으로 채운다. (fill)
  4. 기존 이미지에 다각형을 겹쳐 새로운 이미지를 생성한다.
  5. 캔버스에 있는 오브젝트(원본 이미지, 다각형)를 제거하고 생성된 이미지를 캔버스에 추가한다.

핵심 로직 Demo code

const canvas = new fabric.Canvas(canvasRef.current);

const handleCreatedPath = (e) => {
  const path: fabric.Path = e.path;

  // 생성된 path를 이용하여 polygon을 만들어 해당 영역에 덮어쓸 준비를 한다.
  const polygon = new fabric.Polygon(
    path.path!
      .flat()
      .filter((v) => typeof v === 'number')
      .reduce((pre, curr, index) => {
        if (index % 2 === 0) pre.push({ x: curr });
        else pre[Math.floor(index / 2)].y = curr;

        return pre;
      }, []),
    { fill: 'white' }
  );

  // canvas에서 파란선을 제거하고 하얀 polygon만 남긴다.
  canvas.remove(path);
  canvas.add(polygon);

  // 기존 이미지는 제거하고 새로운 이미지를 추가한다.
  const newImage = canvas.toDataURL({});
  fabric.Image.fromURL(newImage, (image) => {
    canvas.remove(...canvas.getObjects());
    canvas.add(image);
  });
};

canvas.on('path:created', handleCreatedPath);

원래는 Rotate, vertical flip, horizontal flip까지 처리해야해서 머리 아팠는데 다 빼고보니 너무 간단해보인다...ㅠㅠ

위의 편집가능한 캔버스를 통하여 프론트엔드에서 이미지를 직접처리할 수 있는 기능을 만들어보았다. 위에서는 간단하게 그려진 부분의 이미지를 제거하는 기능만 만들었지만, 해당 부분만 추출하는 등으로 응용도 가능하다.
사용자에게 더 좋은 경험을 제공해주기 위해 위와 같은 꽤 복잡한 작업이라도 프론트엔드에서 하는 것이 옳다고 생각한다. 또한 이미지의 처리 비용까지 생각한다면 일석이조가 아닐까? 가능하다면 앞으로는 프론트엔드에서 더 많은 기능들을 처리해 보자 :)
이외에도 이미지의 선명도를 높이기 위해 opencv를 붙여 이미지 sharpening 기능도 제작했었는데, 관련된 포스팅은 다음 기회에 해보겠습니다.

profile
ENFP FE 개발자 :)

0개의 댓글