3. 서명 유효성 검증
유효한 서명이란 무엇일까요? 사람의 생김새가 모두 다르듯 서명도 모두 제각각입니다.
그렇다면 반대로 유효하지 않은 서명은 무엇을 기준으로 판단해야 할까요? 기능 명세서엔 명시되지 않았지만 아래의 두 기준으로 정리해 보았습니다.
- 너무 적게(또는 작게) 그려서 충분히 서명으로 인식되지 않은 이미지
- 가장자리에 치우쳐 그려저서 청약서에 합성했을때 서명란에서 벗어나는 이미지
유의미한 서명으로 인지하기 어려운 부적합한 입력의 예
다른 이유도 아니고 서명이 제대로 그려지지 않아 서류 접수 후 청약이 거절되는 일은 막아야 합니다. 그렇다면 유저가 유효하지 않은 서명을 그렸을때 유저에게 안내해주어야 합니다.
3-1. 어떻게 서명이 적게 그려졌는지 알 수 있을까?
서명 이미지의 크기로 판단하면 작은 점을 사방팔방 찍은 이미지의 가장자리가 크게 잡혀 유효하지 않습니다.
다행히 서명이 단색의 선으로 그려진다는 점에 착안하여, 그려진 검은 픽셀과 그려지지 않은 투명한 알파 채널 투명도(alpha channel transparency)가 0인 픽셀의 비례를 구하여 그려지지 않은 픽셀이 몇 퍼센트 이하일 경우를 걸러낼 수 있었습니다.
Canvas API에서는 정적으로 픽셀을 분석하고 조작(manipulation)할 수 있도록 getImageData() 메서드를 제공합니다.
// Syntax
getImageData(sx, sy, sw, sh)
getImageData(sx, sy, sw, sh, settings)
- sx - 추출될 이미지의 좌상단 x축 좌표값
- sy - 추출될 이미지의 좌상단 y축 좌표값
- sw - 추출될 이미지의 너비로써 양수일 경우 오른쪽 방향으로, 음수일 경우 왼쪽 방향으로 영역을 잡습니다.
- sh - 위와 마찬가지로 양수일 경우 아래, 음수일 경우 위로 높이 영역을 잡습니다.
서명이 그려진 캔버스의 컨텍스트(ctx)에서 위 메소드를 사용하면 ImageData라는 객체를 반환하는데 이 객체의 프로퍼티인 ImageData.data라는 Uint8ClampedArray 자료형의 객체를 사용합니다.
Uint8ClampedArray란 형식화 배열(TypedArray)의 한 종류로써 0–255로 고정된 8비트 부호(sign) 없는 정수의 배열입니다.
콘솔에 찍어보면 0부터 255까지의 값이 이미지 픽셀 수의 4배수로 배열 가득 들어차있습니다.
Uint8ClampedArray 형식화 배열 속 RGBA 값의 연속
여기서 흥미로운 것은 이 값들이 연속된 RGBA(red, green, blue, alpha) 순서로 이루어져있다는 점입니다. 분석할 이미지의 좌측 상단에서부터 우측 하단까지 횡 방향으로 픽셀 단위 한 줄씩 1차원 배열로 픽셀의 RGBA 값을 나타냅니다.
ImageData.data
A Uint8ClampedArray representing a one-dimensional array containing the data in the RGBA order, with integer values between 0 and 255 (inclusive). The order goes by rows from the top-left pixel to the bottom-right.
따라서 네 개의 인덱스를 하나의 픽셀로 보고, RGB의 alpha 값이 불투명에 가까운 픽셀은 서명(전경)으로, alpha값이 0에 가까운 투명한 픽셀은 바탕(배경)으로 판독하도록 만들어봅니다.
픽셀을 분석하고 리사이징하는 변수와 함수들은 같은 네임스페이스를 공유하고 인스턴스 단위로 묶어놓도록 class를 사용했습니다. 또한 전경 대비 배경 비율을 가독성 좋게 참조하기 위해 메소드 대신 getter로 만들었습니다.
안녕하세요, 저는 한국신용데이터의 뱅킹팀 웹 프론트엔드 개발 담당 이원국(Lee)입니다.
저희 캐시노트 뱅킹팀은 소상공인 금융 환경을 혁신적으로 개선하기 위해 끊임없이 고민하고 노력합니다. 이번에는 ‘노란우산공제’라는 소상공인 대상 공제 상품을 비대면으로 간편 가입할 수 있는 서비스를 런칭했습니다.
이번 프로젝트를 통해 많은 것을 공부하고 배워나갔습니다. 그중 서비스 가입 퍼널 중 전자 서명 입력 모듈이 특히 흥미로웠기에 기술 블로그를 통해 공유드립니다.
하나의 포스트로 정리하기엔 내용이 많아 두 편에 나누어 설명드리고자 합니다. 이번 파트에선 Canvas API를 사용하여 픽셀 데이터를 만들고, 분석, 편집하여 가공하는 과정을, 다음 파트에선 Broadcast Channel API를 사용해서 다른 웹뷰 컨텍스트끼리 데이터를 공유하는 과정을 설명하겠습니다.
무엇을 만들었나요?
캐시노트 앱을 사용하는 모바일 기기에서 고객이 쉽게 청약서와 청약 관련 제반 문서에 자필 서명을 할 수 있도록 돕는 디지털 자필 서명 모듈을 만들었습니다.
왜 만들었나요?
실제 협약 기관에서 공제 상품을 청약하는 과정을 디지털화 및 비대면, 간소화하여 더 많은 사장님들이 공제 혜택을 드리기 위해 만들어졌습니다.
저희 캐시노트에서는 유저가 입력한 청약 정보와, 홈택스 간편 인증 모듈을 통해 취합된 정보를 통해 전자 문서를 생성하고, 서명란에 자필 서명을 합성하여 PDF 및 바이너리 이미지 데이터로써 청약서 양식을 완성시켜 청약 과정에서의 고객 경험을 개선하고 있습니다.
어떻게 만들었나요?
JavaScript의 Canvas API를 사용하여 사용자가 서명을 그릴 수 있도록 하고, alpha값을 가지는 PNG 이미지를 추출하여 바이너리 데이터를 안전하게 난독화하여 서버에 올려보내어, 청약 서류에서 합성하는 프로세스로 구성되어 있습니다.
Canvas API를 통해 그림을 그리는 것은 간단했지만, 유효한 서명을 어떻게 판단할지, 엣지 케이스는 없을지, 어떻게 하면 서명 절차를 좀 더 feasible하게 만들 수 있는지가 고민이었습니다.
배울 수 있었던 것들
브라우저에서 그림을 그리는 방법들 🎨
웹 프론트엔드의 무대는 브라우저입니다. 브라우저에서 그림을 그리는 방법은 아래와 같습니다.
SVG
SVG는 도형을 그리는 벡터 API입니다. 그려진 각 도형은 이벤트를 바인딩할 수 있는 객체로 존재합니다. 벡터 방식이기 때문에 래스터화 된 캔버스보다 확대했을때도 품질이 유지됩니다.
CSS
문서의 DOM 요소들을 스타일링합니다. HTML, JS와 함께 웹에서 빼먹고 이야기할 수 없는 필수 요소입니다. DOM 요소를 이용하거나 가상(pseudo) 요소를 시각적으로 꾸미는 역할을 합니다. 캔버스 영역에는 스타일을 바인딩할 수 있는 객체가 없기 때문에 Canvas API 내부적으로 CSS 스타일을 활용할 수는 없습니다.
DOM animation
CSS 또는 JavaScript를 사용해서 엘리먼트를 그리거나 이동하는 등 DOM Manipulation을 통해 애니메이션을 구현할 수 있습니다. 경우에 따라 Canvas로 그리는 것보다 더 부드러운 애니메이션을 얻을 수 있지만 이는 구현하는 브라우저마다 다릅니다.
Canvas API
Canvas는 Flash나 Java와 같은 플러그인을 사용하지 않고 웹 브라우저에서 개발자가 원하는 요소를 그릴 수 있도록 하기 위해 만들어졌습니다. 우리가 잘 아는 Apple에서 대시보드 위젯용으로 만들었고, 이후 모든 브라우저 벤더에서 채택하여 현재는 HTML5 공식 사양 중 하나가 되었습니다.
언제 Canvas API를 사용해야 할까요?
Canvas는 다른 방법보다 저수준으로 이미지를 제어하고, 메모리를 덜 차지하지만, 구현체를 만들기 위해 일반적으로 더 많은 코드를 작성하고 관리해야 합니다.
어도비 일러스트레이터 등을 사용한 벡터 기반의 기존 도형이 있는 경우 SVG를 사용합니다. 애니메이션을 적용하는 큰 정적 영역이 있거나, 3D 변형을 사용하는 경우 CSS 또는 DOM 애니메이션을 사용합니다.
차트, 그래프, 동적 다이어그램, 게임 등을 구현하기 위해선 캔버스는 좋은 선택이 될 수 있습니다. 또한 캔버스에서 벡터, 객체를 사용할 수 있도록 많은 라이브러리가 존재합니다. OpenGL을 사용한 WebGL도 캔버스를 활용하고 있습니다.
캔버스는 앞서 언급했듯이 이미 HTML 공식 사용이 된지 오래인지라, 브라우저 하위 호환성은 걱정하지 않아도 된다고 볼 수 있습니다.
Canvas API를 사용한 이유
개발에 앞서 요구된 스펙과 해결해야 하는 문제를 정의하는 것이 가장 중요하다고 생각합니다. 서명 기능에 요구되는 스펙은 아래와 같이 정리할 수 있습니다.
위 내용을 다시 정리하면 아래와 같습니다.
제가 아는 지식 내에선 Canvas API를 활용하는 방법이 가장 쉽고 빠를 것으로 생각되었습니다.
서명 모듈 만들기 ⚙️
TLDR;
요구된 기능 명세
어떻게 만들었나?
1. 초기화
1-1. 컴포넌트 위계 및 Prop 설정
interface Props {
fullyOpened: boolean;
renderId: string;
placeholder?: string;
opened: boolean;
onSubmit: (encodedSignature: string) => void;
}
export default function SignPad({
placeholder,
renderId,
fullyOpened,
opened,
onSubmit,
}: Props) {
const heap = React.useRef(new Map());
const [isDirty, setIsDirty] = React.useState(false);
// ...
}
Props
Context
{
// ...
React.useEffect(() => { // (1)
if (!opened && isDirty) {
setIsDirty(false);
}
}, [isDirty, opened]);
React.useEffect(() => { // (2)
heap.current.clear();
const dom = document.getElementById(renderId) as HTMLCanvasElement;
heap.current.set('node', dom);
heap.current.set('ctx', dom.getContext('2d'));
}, [placeholder, renderId]);
// ...
}
heap.current 속 Map 객체의 프로퍼티를 지워줍니다.
1-2. 캔버스 노드 및 컨텍스트
{
// ...
React.useEffect(() => {
// (1)
const node = heap.current.get('node') as HTMLCanvasElement;
const ctx = heap.current.get('ctx') as CanvasRenderingContext2D;
if (!node || !fullyOpened) return;
// (2)
const standardWidth = window.innerWidth - 40;
const standardHeight = 180;
const scale = 2;
// (3)
node.style.width = `${standardWidth}px`;
node.style.height = `${standardHeight}px`;
node.width = standardWidth * scale;
node.height = standardHeight * scale;
ctx.imageSmoothingEnabled = true;
ctx.imageSmoothingQuality = 'high';
ctx.lineWidth = 4;
ctx.lineCap = 'round';
ctx.lineJoin = 'round';
// (4)
if (!isDirty) {
ctx.font = '36px Pretendard Arial';
ctx.fillStyle = '#3F4A56';
ctx.fillText(
placeholder,
(node.width - node.width / 2) / 2,
node.height / 2,
);
}
// (5)
node.addEventListener('touchstart', () => {
if (!isDirty) {
setIsDirty(true);
}
});
node.addEventListener('touchend', () => {
ctx.stroke();
ctx.beginPath();
});
node.addEventListener('touchmove', draw(scale));
}, [draw, fullyOpened, isDirty, placeholder, renderId]);
// ...
}
-node.style.with, node.style.height에 앞서 지정한 너비를 할당합니다.
-node.width, node.height는 캔버스에 표시되는 픽셀 단위로 디스플레이 크기의 2배수로 지정하여 더 많은 픽셀을 그려 높은 해상도의 서명 이미지를 얻도록 합니다.
– touchstart - 유저가 서명 영역에 터치를 시작하면 isDirty 플래그를 true로 변환합니다.
– touchend - 유저의 터치 이벤트가 끝나는 좌표까지 선을 그려주고 CanvasRenderingContext2D.beginPath() 메소드로 서브 패스(sub-path)를 초기화해줍니다.
– touchmove - 실제 유저의 터치 경로가 선으로 그려지는 함수 draw()를 호출하는 부분입니다. draw() 함수는 픽셀 단위 대비 디스플레이 단위 비례를 나타내는 상수 scale을 인자로 받고 있습니다.
2. 입력
2-1. 서명 그리기
유저의 터치 이벤트에 바인딩 될 draw() 함수와 캔버스를 비워주는 clear() 함수로 입력을 제어합니다.
renderId가 변할 때마다 컨텍스트가 바뀌기 때문에 renderId를 dependency로 하는 useCallback 훅을 사용했습니다. 계산이 많은 무거운 로직이 아닌 경우 Memorization이 오히려 Overhead라는 의견이 분분하지만 이 주제는 다음에 다뤄보겠습니다.
draw() 함수의 역할을 간단히 정리하면 아래와 같습니다.
2-2. 왜 쓰로틀링(Throttling)을 사용하지 않았나요?
보통 터치 스크린은 60~120Hz의 주사율로 터치 입력을 인식합니다 (하지만 디스플레이 출력은 대부분 60Hz입니다. 출처 ). wheel, mousewheel, touchmove, pointermove, mousemove와 같은 지속적인 이벤트 디스패칭은 보통 쓰로틀링이라는 빈도수를 임의로 제한하는 방법으로 성능 부하를 완화합니다.
Touch Move Event Dispatched와 Paint의 지표를 살펴보면 약 100ms당 약 5번의 리페인트와 터치 이벤트가 트리거 되었음을 볼 수 있습니다.
이에 반해 약 40ms의 레이턴시를 설정하여 쓰로틀링을 걸어본 결과 성능 지표는 아래와 같습니다.
터치 이벤트의 발생 빈도수는 다를것이 없지만 레이아웃, 렌더링 관련 지표(Paint, Composite)가 다소 완화되어 보입니다.
유의미한 성능 개선 효과라고 하기 어려운 결과를 얻은 반면, 이미지의 곡선 부분의 품질은 급격히 떨어집니다. 그럼에도 브라우저 메인 스레드의 평균 CPU Usage는 사용 여부와 관계 없이 7–8%대로 별 차이가 없었습니다. (Webkit 기준 amd64 iOS Simulator 환경)
모바일 웹뷰에서 구현되는 만큼 디스플레이 폼팩터와 서명 모듈의 입력부 영역도 작아서 쓰로틀링을 적용하는 것보단 더 많은 이벤트 입력으로 서명을 비교적 부드럽고 정교하게 그려내는 편이 더 낫다는 판단으로 쓰로틀링을 사용하지 않았습니다.
디스플레이 주사율보다 잦은 이벤트 발생으로 인한 불필요한 콜스택을 줄이기 위해 requestAnimationFrame()을 사용해 보았습니다. (참조) 하지만 FPS(Frame Per Second)의 개선은 없거나 미미한 정도로 보였습니다.
단, 자바스크립트 이벤트 루프에서 앞서 언급한 함수는 chromium 브라우저와 webkit 브라우저가 작동 시점이 달라서 확증적으로 차이가 없다고 말씀드리긴 이릅니다.
관심 있으신 분들은 JSConf 세션 영상을 참고하세요.
2-3. 서명 지우기
const clear = () => {if (!isDirty) return;
const node = heap.current.get('node') as HTMLCanvasElement;
const ctx = heap.current.get('ctx') as CanvasRenderingContext2D;
ctx.clearRect(0, 0, node.width, node.height);
setIsDirty(false);
};
@wonkooklee
ctx.clearRect() 내장 메소드를 사용하면 캔버스 내부에 그려진 픽셀들을 정리할 수 있습니다.
3. 서명 유효성 검증
유효한 서명이란 무엇일까요? 사람의 생김새가 모두 다르듯 서명도 모두 제각각입니다.
그렇다면 반대로 유효하지 않은 서명은 무엇을 기준으로 판단해야 할까요? 기능 명세서엔 명시되지 않았지만 아래의 두 기준으로 정리해 보았습니다.
다른 이유도 아니고 서명이 제대로 그려지지 않아 서류 접수 후 청약이 거절되는 일은 막아야 합니다. 그렇다면 유저가 유효하지 않은 서명을 그렸을때 유저에게 안내해주어야 합니다.
3-1. 어떻게 서명이 적게 그려졌는지 알 수 있을까?
서명 이미지의 크기로 판단하면 작은 점을 사방팔방 찍은 이미지의 가장자리가 크게 잡혀 유효하지 않습니다.
다행히 서명이 단색의 선으로 그려진다는 점에 착안하여, 그려진 검은 픽셀과 그려지지 않은 투명한 알파 채널 투명도(alpha channel transparency)가 0인 픽셀의 비례를 구하여 그려지지 않은 픽셀이 몇 퍼센트 이하일 경우를 걸러낼 수 있었습니다.
Canvas API에서는 정적으로 픽셀을 분석하고 조작(manipulation)할 수 있도록 getImageData() 메서드를 제공합니다.
서명이 그려진 캔버스의 컨텍스트(ctx)에서 위 메소드를 사용하면 ImageData라는 객체를 반환하는데 이 객체의 프로퍼티인 ImageData.data라는 Uint8ClampedArray 자료형의 객체를 사용합니다.
콘솔에 찍어보면 0부터 255까지의 값이 이미지 픽셀 수의 4배수로 배열 가득 들어차있습니다.
여기서 흥미로운 것은 이 값들이 연속된 RGBA(red, green, blue, alpha) 순서로 이루어져있다는 점입니다. 분석할 이미지의 좌측 상단에서부터 우측 하단까지 횡 방향으로 픽셀 단위 한 줄씩 1차원 배열로 픽셀의 RGBA 값을 나타냅니다.
따라서 네 개의 인덱스를 하나의 픽셀로 보고, RGB의 alpha 값이 불투명에 가까운 픽셀은 서명(전경)으로, alpha값이 0에 가까운 투명한 픽셀은 바탕(배경)으로 판독하도록 만들어봅니다.
픽셀을 분석하고 리사이징하는 변수와 함수들은 같은 네임스페이스를 공유하고 인스턴스 단위로 묶어놓도록 class를 사용했습니다. 또한 전경 대비 배경 비율을 가독성 좋게 참조하기 위해 메소드 대신 getter로 만들었습니다.
3-2. Uint8ClampedArray 자료형의 RGBA 배열로 그려진 서명과 배경의 비율 구하기
get entityAndBackgroundRatio() {let entity = 0; // (1)
let background = 0; // (2)
for (let i = 0; i < this.#imageData.length; i += 4) { // (3)
if (this.#imageData[i + 3] > this.entityThreshold) {
entity += 1;
} else {
background += 1;
}
}
return {
entity,
background,
ratio: (entity / background) * 100, // (4)
};
}
3-3. Pixel Processing Visualization
이해를 돕기 위해 imageData.data 프로퍼티에 담긴 픽셀 데이터를 인덱스 순서로 파싱하여 글자를 이루는 픽셀의 수를 찾아내는 과정을 시각화 하였습니다. (아래 링크)
GitHub - wonkooklee/pixel-manipulation-with-canvas: With the ImageData object we can directly read…
With the ImageData object we can directly read and write a data array to manipulate pixel data. Install dependencies…
github.com
실제 프로세스는 불과 몇 ms만에 완료되지만 과정의 시각화를 위해 의도적으로 지연을 발생시켰습니다. 자세한 코드는 위 repository의 코드를 참조하세요.
서명 이미지는 투명도(Alpha Channel)를 가지는 PNG 확장자를 사용합니다. 따라서 불투명한 픽셀은 전경(서명), 투명한 픽셀은 배경으로 분리될 수 있습니다.
3-4. 서명 이미지 가장자리 좌표 구하기
결국 우리가 하고싶었던 것은 이미지 크롭 후 리사이징입니다. 서명 이미지를 크롭하려면 어디까지 이미지를 잘라내야 하는지 상하좌우 각 가장자리의 불투명한 픽셀 좌표를 구해야합니다.
제가 생각했던 잘라낼 이미지 가장자리 구하는 방법은 아래와 같습니다.
위 논리를 코드로 바꾸어 아래의 함수를 만들었습니다.
그리고 위 과정 또한 설명을 돕기 위해 간단한 예시를 만들었습니다.
3-5. 이미지 자르기
위 단계를 통해 이미지가 크롭될 위치와 너비, 높이가 정해졌습니다.
이제 이미지를 잘라봅시다.
getCroppedImage() {
const coords = this.getCroppingCoordinates();
if (!coords) return null;
const { top, bottom, left, right } = coords;
if (
right - left < this.validMinimumImageLength ||
bottom - top < this.validMinimumImageLength ||
this.entityAndBackgroundRatio?.ratio < this.validMinimumPixelRate
) {
throw new PixelProcessorError({
errorCode: 'notEnoughSize',
message:
'The given image is too small to recognize as a valid signature.',
});
}
const virtualCanvasElement = document.createElement('canvas');
virtualCanvasElement.width = right - left + this.offset;
virtualCanvasElement.height = bottom - top + this.offset;
const virtualCtx = virtualCanvasElement.getContext(
'2d',
this.renderingOptions,
) as CanvasRenderingContext2D;
this.imageRatio = (bottom - top) / (right - left);
const extracted = this.#canvasContext!.getImageData(
left - (this.offset ? this.offset / 2 : 0),
top - (this.offset ? this.offset / 2 : 0),
right - left + this.offset,
bottom - top + this.offset,
);
virtualCtx.putImageData(extracted, 0, 0);
const dataUrl = virtualCanvasElement.toDataURL('image/png');
virtualCanvasElement.remove();
return dataUrl;
}
– 크롭될 이미지의 폭 또는 높이가 기준(validMinimumImageLength)보다 작은 경우
– 서명 이미지의 배경 대비 전경 비율이 기준(validMinimumPixelRate)보다 적은 경우
추출에 사용된 캔버스 요소는 remove() 메소드를 사용해서 detach합니다.
4. 이미지 리사이징
이미지의 장축을 지정된 크기로 리사이징하여 서버에 균일한 크기의 이미지만 전송될 수 있도록 합니다.
주요 코드는 앞서 설명드린 절차와 비슷하여 간단히 설명하겠습니다.
resizeImage(lengthLimit: number) {
if (
lengthLimit * this.imageRatio < 1 ||
lengthLimit / this.imageRatio < 1
) {
throw new PixelProcessorError({
errorCode: 'wrongArgumentPassed',
message:
'The given length limit is too small. The shorter length must be greater than 1.',
});
}
return (callback: (resizedBase64')[1];
void callback(dataExceptMimeType);
virtualCanvasElement.remove();
img.remove();
};
img.addEventListener('load', loadEventHandler);
};
}
Consumption
const node = heap.current.get('node') as HTMLCanvasElement;const controller = new MySignature(node, 60, 0.8, 60, 10);
const handleResult = controller.resizeImage(280);
handleResult(onSubmit);
마땅한 이름이 생각나지 않아 모듈에 MySignature라는 이름을 붙여보았습니다.
초기화 함수는 아래의 순서로 인자를 받습니다.
여담: CPU Canvas vs. GPU Canvas
캔버스를 사용할때 브라우저는 휴리스틱(Heuristics)에 따라 메인 메모리에 캔버스 데이터를 저장하며 CPU를 활용하여 렌더링할 수도 있고, GPU를 사용하여 캔버스를 구성하고 작동시킬 수도 있습니다. 편의상 전자는 CPU 캔버스, 후자는 GPU 캔버스라 칭하겠습니다.
GPU 캔버스는 하드웨어 가속을 사용하여 CPU 캔버스에 비해 더 나은 퍼포먼스를 보인다고 합니다. 하지만 언제나 더 나은 성능을 기대한다고 보장할 순 없으며, 상황에 따라 CPU 캔버스가 유리한 경우도 있습니다. 바로 이번 서명 모듈과 같이 빈번하게 getImageData(), putImageData()를 호출해서 픽셀 데이터를 조작해야 하는 경우가 그렇습니다.
이 경우 GPU 캔버스를 사용하는 경우, 캔버스 버퍼(buffer)를 연산하기 위해 다시 CPU로 데이터를 readback해야 하는데, 이러한 상황이 빈번하게 발생되는 경우 CPU에서 전적으로 캔버스를 제어하는 것이 성능상 비용이 더 적다고 합니다.
하지만 어느 하드웨어 리소스를 사용할지는 브라우저 휴리스틱을 통해 결정되기 때문에 적극적으로 사용하기는 어렵다고 합니다. Figma에서 캔버스 대신 WebGL을 사용한 이유도 캔버스가 GPU 하드웨어 가속(GPU hardware acceleration)을 통한 성능을 보장하기 어렵기 때문이라고 하네요.
HTMLCanvasElement.getContext('2d', { willReadFrequently: true });아무튼 이번 사례와 같이 빈번한 데이터의 read/write가 필요한 경우 캔버스 노드의 최초 getContext() 메소드 호출시 willReadFrequently 옵션을 true로 설정해서 메모리를 최적화 할 수 있다고 합니다.
To Be Continued
여기까지가 Canvas API를 이용하여 유저의 입력을 받아 서명을 만들고, 검증하며, 이미지로 추출하는 과정입니다.
Part2에선 어떻게 Broadcast ChannelAPI를 활용해서 웹뷰 브라우저 컨텍스트끼리 seamless하게 데이터를 주고 받을 수 있었는지 설명하겠습니다.
긴 글 읽어주셔서 감사합니다.
Acknowledgements
이번 프로젝트에서 팀으로 함께 호흡을 맞췄던 Jordan, Nicky, Scarlet께도 많이 배울 수 있어 감사하다고 말씀드리고 싶네요. 팀의 일원으로써 제 역할을 다 하고 있는지 여전히 고민스럽지만 항상 잘 대해주시고 많은 가르침을 주시는 뱅킹팀, KCD 구성원 분들께 항상 감사한 마음입니다.
저희 팀 특성상 현업의 이야기를 공개하기 쉽지 않은데, 이렇게 공유할 수 있는 기회를 마련해주신 CTO Oliver와 피플팀 분들께도 감사드립니다.
References