Canvas 데이터 를 수정 가능한 상태로 저장하고 불러오기

오현재·2024년 1월 17일

Canvas API 는 Javascript 와 HTML 의 <canvas> 태그를 이용하여, 그래픽을 그릴 수 있게 하는 Javascript Web API 이다.

동작 방식을 간단하게 설명하면, <canvas> 요소 의 getContext() 라는 메소드 를 통해, 렌더링 컨텍스트 를 생성한다. 렌더링 컨텍스트그래픽이 그려질 수 있는 영역 이라고 생각하면 될 것 같다. 이러한 렌더링 컨텍스트그래픽에 관련한 다양한 기능을 제공 한다.

비유하자면 <canvas> 라는 비어 있는 책상위에, 렌더링 컨텍스트 라는 종이를 놓고 그 위에 그림을 그리는 것이라고 생각할 수 있다.

// 렌더링 컨텍스트가 생성되지 않은, 빈 상태의 <canvas>
const emptyCanvas = document.getElementById("canvas");
// getContext() 를 통해 생성된 렌더링 컨텍스트
const renderingContext = emptyCanvas.getContext("2d");

위와 같이 hi 라는 그래픽을 그려보았다. 이제는 저 hi 를 저장하고 싶은 데, 어디에 있는 어떤 데이터 에 접근해야 할지 모르겠다. 어떻게 할 수 있을까?


렌더링 컨텍스트

렌더링 컨텍스트 는 정확히 말하면 Canvas API 에 속해 있는 CanvasRenderingContext2D 라고 불리우는 인터페이스 (추상 클래스와 비슷한 개념) 이다. 앞서 말한 것 처럼 그래픽에 관련한 다양한 메서드 와 속성 을 제공 한다.

// getContext() 를 통해 생성된 렌더링 컨텍스트
const renderingContext = emptyCanvas.getContext("2d");

그 중 getImageData() 메서드 는 ImageData 라는 객체를 반환하는데, 이 객체를 통해 그래픽에 관한 정보 에 접근할 수 있다.

1. getImageData() 와 putImageData()

getImageData() 가 반환하는 ImageData<canvas> 에 깔려있는 그래픽 의 일부 영역 을 픽셀 데이터 로 표현한, 그래픽 에 관한 정보를 가지고 있는 객체 이다. 따라서 getImageData() 는 표현할 ImageData 의 영역에 대한 위치와 너비, 높이 를 파라미터 로써 받는다.

getImageData(startX, startY, width, height)

getImageData 가 반환한ImageData 객체 의 속성 에는 colorSpace, data, width, height 가 있다. 각각의 속성은 간단하게 설명하면, ImageData 의 color space, 픽셀 데이터, 너비, 높이 에 대한 값 을 가진다.

이러한 ImageData 객체는 렌더링 컨텍스트 의 메서드인 putImageData() 의 파라미터로 삽입 되어, 또 다른 렌더링 컨텍스트 를 가지는 <canvas> 영역에 그려질 수 있다.

이것을 코드로 표현하면 다음과 같다.

const canvas = document.getElementById("canvas");
const renderingContext = canvas.getContext("2d");

/** 
...
	렌더링 컨텍스트 에 그래픽 그리기
...
*/

// getImageData() 로 <canvas> 데이터 추출
const imageData = renderingContext.getImageData(
	0,
	0,
	this.canvas.width,
	this.canvas.height,
);

const otherCanvas = document.getElementById("other-canvas");
const otherRenderingContext = otherCanvas.getContext("2d");

// 다른 렌더링 컨텍스트에 추출한 <canvas> 데이터 삽입
otherRenderingContext.putImageData(imageData);

이것을 통해 getImageData() 에서 반환하는 ImageData 를 이용해 렌더링 컨텍스트 에 그린 그래픽을 저장하고, putImageData() 로 불러 와 <canvas> 영역에 그릴 수 있다 는 것을 확인했다.

그렇다면 ImageDatalocalStorage 에 저장하거나, 서버와 주고 받을 수 있도록 JSON 타입 으로 변환해보겠다.

const canvas = document.getElementById("canvas");
const renderingContext = canvas.getContext("2d");

const {colorSpace, data, width, height} = renderingContext.getImageData(
	0,
	0,
	this.canvas.width,
	this.canvas.height,
);

const stringfiedData = JSON.stringify(Array.from(data))

const stringfiedImageData = JSON.stringfy
({ data: stringfiedData, colorSpace, height, width })

위 의 과정 을 통해 JSON 으로 변환된 stringfiedImagedata 는 서버와 주고받을 수 있는 형태가 되었다.

하지만 조금 특이한 점이 있는데, data 속성 값 을 배열로 변환하여 JSON 타입 으로 변환하는 과정이 있다. 이는 data 속성 값 이 문자열이나 숫자 같은 원시값이 아니기 때문이다.

ImageData.data

앞서 ImageData 객체 에는 그래픽의 픽셀 데이터에 관한 정보가 담겨있다 라고 했었다. 그러한 픽셀 데이터는 ImageDatadata 속성에 담겨있다.

ImageData.dataUint8ClampedArray 타입 이며, 각 원소는 1차원 배열로 R,G,B,A 순서대로 있다. 1차원 배열 의 원소는 0 - 255 사이의 정수값을 갖는다.

예를 들어 렌더링 컨텍스트 의 기본 크기인 300px X 150px 의 ImageData 가 생성되었다면, ImageData.data 에는 4 x 300 x 150 = 180,000 개 의 원소 가 존재한다.

180,000 개의 정수값이니 4byte x 180,000 을 계산하면 무려 720kb 이다. 이것을 서버에 저장하고, 불러오는 데이터 로 생각한다면 확실히 무리 이다. 그렇다면 다른 방법이 없을 까? 찾아보았다.

2. toDataURL() 와 drawImage()

앞선 ImageData 의 문제점의 원인이 그래픽을 픽셀 단위로 표현한 것이었다면, 이 toDataURL()그래픽을 이미지 형태로 표현된 url 을 Base64 문자열 형태로 반환한다. 이는 Uint8ClampedArray 타입 데이터 의 길이보다 훨씬 작은 길이를 가진다.

변환된 Base64 문자열 을 가지고 있다면, 그 문자열을 새로운 Image 객체의 src 속성 에 할당하여, 또 다른 <canvas> 에 이미지 로 삽입할 수 있다. 설명이 이해가 잘 안되니 코드와 함께 간략하게 설명해보겠다.

const loadedCanvas = document.getElementById("canvas");
const renderingContext = canvas.getContext("2d");

// toDataURL() 로 <canvas> 에서 Base64 추출
const base64canvasData = document.getElementById("loaded-canvas")
.toDataURL();

// loadedCanvas 에 그려질 Image 객체 생성
const canvasImage = new Image();

// Image src 에 추출한 Base64 값 을 넣어줌
canvasImage.src = base64canvasData;

// Image 가 로드될 때, Image 를 loadedCanvas 의 렌더링 컨텍스트 의
// drawImage 메서드의 파라미터에 삽입하여 그래픽을 그려줌
canvasImage.onload = () => {
	renderingContext.
    drawImage(canvasImage, 0, 0, loadedCanvas.width, loadedCanvas.height);
};

profile
안녕하세요. 환영합니다. 프론트엔드 개발자 오현재입니다.

0개의 댓글