[Canvas] 이미지 에디터 만들기 4 : 이미지 중앙정렬 기능 구현

profile
마릴린벅시
2024. 8. 26. 18:20프론트엔드


완성 화면

⭐️ 이미지 중앙 정렬 하기의 문제

이미지를 중앙에 두는건 쉽다. (캔버스 사이즈 - 이미지사이즈)/2 로 계산해주면 끝.

function centerImage() {
    if (imageRef.current) {
      const width = imageRef.current.width() * imageRef.current.scaleX();
      const height = imageRef.current.height() * imageRef.current.scaleY();
      currentDegree.current = imageRef.current.rotation();
      imageRef.current.setAttrs({
        x: (CANVAS_SIZE - width) / 2,
        y: (CANVAS_SIZE - height) / 2,
      });
    }
  }

하지만 이렇게 하면 이미지를 회전하고 나서 중앙정렬 했을 때 이미지가 엉뚱한 곳으로 가는 문제가 있다.

자꾸 어디 가니

 


⭐️ 이미지 중앙 정렬 문제점 해결

나는 꼼수를 썼다.

이미지 중앙 정렬 직전에 현재

1. 현재 각도를 저장

2. 이미지 각도를 0으로 초기화

3. 이미지 중앙 정렬

4. 1번에서 저장한 각도로 다시 이미지 회전

  function centerImage() {
    if (imageRef.current) {
      const width = imageRef.current.width() * imageRef.current.scaleX();
      const height = imageRef.current.height() * imageRef.current.scaleY();
      // 1. 현재 각도를 저장
      currentDegree.current = imageRef.current.rotation();

      // 2. 이미지 각도를 0으로 초기화
      imageRef.current.setAttrs({
        rotation: 0,
      });

      // 3. 이미지 중앙 정렬
      imageRef.current.setAttrs({
        x: (CANVAS_SIZE - width) / 2,
        y: (CANVAS_SIZE - height) / 2,
      });

      // 4. 1번에서 저장한 각도로 다시 이미지 회전
      rotateAroundCenter(imageRef.current, currentDegree.current);
    }
  }

 

이제 이미지를 회전 한 뒤에도 정상적으로 중앙정렬이 된다.

 


⭐️ 이미지가 캔버스에 처음부터 중앙정렬 되도록 수정

캔버스가 화면에 처음 로드 될 때부터 이미지가 중앙에 정렬되는 것이 UX적으로 자연스러우니

코드를 조금만 수정해보자.

  useEffect(() => {
    if (!source) return;
    initialize();
    drawImage().then(() => {
      addTransformer();
      centerImage(); //=> 추가
    });
  }, [initialize, drawImage]);

 

⭐️ 이미지 정보 추출하기

마지막으로 캔버스에 올라간 이미지의 위치, 사이즈, 각도 등의 데이터를 받아오는 함수를 추가해보자.

이미지를 편집한 뒤, 해당 정보를 백엔드에 전달하여 추가 작업이 필요할 경우,

또는 이미지 편집 데이터를 저장해두었다가 사용자가 나중에 다시 접속했을 때

마지막으로 편집 된 상태 그대로 이미지를 보여줘야 할 경우 등등에 사용할 수 있다.

왠만한 정보는 기본적으로 Node데이터에 들어있다. 하지만 몇몇가지는 우리가 직접 계산해서 구해야 하는데..

import { useCallback, useEffect, useRef, useState } from 'react';
import Konva from 'konva';
import { Stage } from 'konva/lib/Stage';
import { Layer } from 'konva/lib/Layer';
import { Node } from 'konva/lib/Node';
import { Transformer } from 'konva/lib/shapes/Transformer';
import { getCanvasObjectData, rotateAroundCenter } from '../utils/image.utils';

const CANVAS_SIZE = 512;

export default function Canvas2() {
  const source = '/images/bottle.jpeg';
  const stageRef = useRef<Stage | null>(null);
  const layerRef = useRef<Layer | null>(null);
  const imageRef = useRef<Node | null>(null);
  const transformerRef = useRef<Transformer | null>(null);

  const [degree, setDegree] = useState(0);

  const currentDegree = useRef(0);

  //...이전 코드 생략

  function getData() {
    if (!imageRef.current) return;
    const data = getCanvasObjectData(imageRef.current);
    console.log('data', data);
  }

  //...다음 코드 생략

  return (
    <section id='canvas_container'>
      <div
        id='container'
        onMouseEnter={onFocus}
        onMouseLeave={onFocusOut}
        onTouchStart={onFocus}
      ></div>
      <div className='btn_container'>
        <button className='rotate_btn' onClick={rotateImage}>
          회전하기
        </button>
        <button className='rotate_btn center' onClick={centerImage}>
          중앙정렬
        </button>
        <button className='rotate_btn data' onClick={getData}>
          데이터 추출
        </button>
      </div>
    </section>
  );
}



export function degToRad(angle: number) {
  return (angle / 180) * Math.PI;
}


//이미지의 중앙 좌표를 추출하는 함수.
export function getCenter(shape: Node) {
  const angleRad = degToRad(shape.rotation() || 0);
  return {
    x:
      shape.x() +
      ((shape.width() * shape.scaleX()) / 2) * Math.cos(angleRad) +
      ((shape.height() * shape.scaleY()) / 2) * Math.sin(-angleRad),
    y:
      shape.y() +
      ((shape.height() * shape.scaleY()) / 2) * Math.cos(angleRad) +
      ((shape.width() * shape.scaleX()) / 2) * Math.sin(angleRad),
  };
}

export type CanvasObjType = {
  x: number;
  y: number;
  imgCenter: { x: number; y: number };
  scaleX: number;
  scaleY: number;
  width: number;
  height: number;
  rotation: number;
};

export function getCanvasObjectData(node: Node): CanvasObjType {
  const attrs = {
    x: node.x(),
    y: node.y(),
    width: node.width(),
    height: node.height(),
    rotation: node.rotation(),
  };
  const center = getCenter(node);
  return {
    ...attrs,
    imgCenter: center,
    scaleX: node.scaleX(),
    scaleY: node.scaleY(),
  };
}

이미지를 회전하고, 중앙정렬하고 지지고 볶고 난 후

해당 이미지의 중앙 x,y축을 구하는 함수를 추가했다. (getCenter)

이 데이터는 데이터를 기반으로 캔버스에 이미지를 다시 띄워야 할 때나

캔버스에 올려진 모양 그대로 스케일만 축소해서 다른 곳에 보여줘야 할 때 이 데이터가 필요하다.

 


 

⭐️ 최종 코드 깃헙에서 보기

아래의 레포를 clone받은 뒤 810f1f4커밋으로 체크아웃 하면 여기까지의 코드를 볼 수 있다.

https://github.com/seoulsaram/canvas-search

 

GitHub - seoulsaram/canvas-search

Contribute to seoulsaram/canvas-search development by creating an account on GitHub.

github.com

 

 

반응형