header wave

Post

프로젝트) 이미지 라벨링 사이트 -2

2022-10-06 AM 04/53
#javascript
#react
#study
#project
#redux

3) Display uploaded image

Redux store에 저장된 이미지 파일들을 디스플레이 해줘야 한다.

아래 구성도 처럼 ImageShow 컴포넌트에 이미지가 출력될 예정이다.

img.png

structure

ImageShow 컴포넌트의 기본 골조이다.

Redux 스토어에 저장된 image 파일들이 없다면 "Upload Your Image"란 메세지가 출력되고, 이미지들이 있다면 cavas에 이미지가 나와야 한다.

import { useEffect, useState } from "react";

// Redux
import { useSelector, useDispatch } from "react-redux";

// canvas global variable
let canvas, ctx;

const ImageShow = () => {
  const dispatch = useDispatch();

  const loaded_image = useSelector((state) => state.image);

  if (loaded_image.image.length === 0) {
    return <h1>Upload your image</h1>;
  } else {
    return (
      <>
        <canvas
          id="canvas"
          style={{
            maxWidth: "100%",
            maxHeight: "100%",
            overflow: "auto",
          }}
        ></canvas>
      </>
    );
  }
};

export default ImageShow;

html canvas가 dom에 로드된 이후 이미지를 canvas에 그려주는 로직이 실행되어야 한다.

왜냐하면 리액트는 컴포넌트가 생성될 때(componentDidMount), 업데이트될 때(componentDidupdate), 제거될 때(componentWillUnmount)의 생성주기를 가진다.

img.png

https://projects.wojtekmaj.pl/react-lifecycle-methods-diagram/

컴포넌트가 생성된 후, DOM요소의 Canvas에 이미지를 그리려면 document.getElementById를 통해 Canvas DOM 요소를 선택햐줘야 한다.

하지만 컴포넌트가 로딩되는 과정에서 document.getElementById로 DOM 요소를 선택하려고 한다면 아직 생기지도 않은 DOM 요소를 찾고 있으니 Error가 발생한다.

그래서 컴포넌트가 DOM으로 로드된 이후, DOM 요소를 선택해줘야 한다!

이를 위해서는 useEffect Hook을 사용하면 된다. (현재 함수형 컴포넌트를 작성 중)

useEffect를 통해서 컴포넌트가 로드된 이후 실행될 코드를 작성할 수 있다.

먼저 컴포넌트가 로드된 이후 DOM의 canvas Element를 선택해준다.

(canvas 변수는 useEffect 밖에서 전역변수로 정의했다.)

    // canvas def
    canvas = document.getElementById("canvas");

이후 canvas Element가 제대로 생성되었다면 canvas context를 만들어준다. (ctx도 전역변수이다.)

    if (canvas) {
      ctx = canvas.getContext("2d");
    }

canvas, ctx를 전역변수로 생성해둔 이유는 추후에 만들 drawing tool에서도 canvas, ctx 변수를 사용해야 하기 때문이다.

drawing tool들은 useEffect 밖에 정의될 계획인데, useEffect와 같은 변수를 공유하기 위해서는 전역변수를 사용하는 수 밖에 없다.

Canvas에 이미지를 그리기 위해서 FileReader API를 사용했다.

먼저 아래와 같이 canvas에 로드할 파일을 store에 저장된 image에서 선택해준다.

(currentIndex는 현재 이미지의 인덱스다. 길이 순으로 0,1,2,3... 이런 식으로 지정되어 있으며 이 값 또한 current.js란 store에 저장되어 있다. 추후 버튼을 통해서 index 변경과 함께, 이미지도 변경하기 위해서 redux를 통해 상태관리 할 수 있도록 만들었다.)

  file = loaded_image.image[currentIndex].file;

FileReader 객체를 불러온다.

  fr = new FileReader();

FileReader API가 성공적으로 파일을 불러오면 실행되는 onload란 메서드다.

여기에 이미지를 로드하는 이벤트(콜백함수)를 할당해주면 된다.

 fr.onload = createImage;

나는 createImage란 함수를 할당했다.

이제 FileReader로 파일을 읽어보자. readAsDataURL이란 메서드를 사용했다.

fr.readAsDataURL(file);

이 메서드를 사용하면 Redux에 업로드된 File 객체를 URL 형태로 바꿔준다.

img.png

FileReader.readAsDataURL

createImage function

createImage 함수 내에서 먼저 img란 이미지 객체를 만들어 준다.

이후 img.onload (이미지가 로드되면 실행되는 함수이다.)에 imageLoaded란 콜백함수를 할당해주고,

img.src에는 FileReader API로 불러온 값을 넣어준다.

(위의 fr.readAsDataURL로 변환된 값이 fr.result다.)

     function createImage() {
        img = new Image();
        img.onload = imageLoaded;
        img.src = fr.result;
      }

imageLoaded function

이미지 객체가 로드되면(img.onload) 실행되는 콜백함수다.

먼저 이미지가 그려질 canvas의 width, height를 재정의 해준다.

(나는 이미지의 실제 가로, 세로 높이로 canvas의 크기를 재조정했다).

이후 canvas context의 drawImage 메서드를 통해 img 객체를 그려준다.

      function imageLoaded() {
        // set canvas(image) width & height
        canvas.width = img.width;
        canvas.height = img.height;

        // drawing image to canvas
        ctx.drawImage(img, 0, 0);
      }

img.gif

이미지 로드