react-konva-image-editor icon indicating copy to clipboard operation
react-konva-image-editor copied to clipboard

๐Ÿ–ผ Image Editor based on React.js & Konva.js

react-konva-image-editor

Subject

  • (๊ตญ๋ฌธ) HTML5 Canvas ๊ธฐ๋ฐ˜ ์˜คํ”ˆ์†Œ์Šค ์ด๋ฏธ์ง€ ์—๋””ํ„ฐ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ๊ฐœ๋ฐœ
  • (์˜๋ฌธ) Opensource Image Editor Library Development based on HTML5 Canvas
  • ๋ณธ ํ”„๋กœ์ ํŠธ๋Š” 2021-2ํ•™๊ธฐ ๊ฒฝํฌ๋Œ€ํ•™๊ต ์บก์Šคํ†ค ๋””์ž์ธ 1 - ์‚ฐ์—…์ฒด ์ฃผ์ œ(๋ชจ๋ฐ”์ผ ์•ฑ๊ฐœ๋ฐœ ํ˜‘๋™์กฐํ•ฉ)๋ฅผ ๋ฐ”ํƒ•์œผ๋กœ ๊ฐœ๋ฐœํ–ˆ์Šต๋‹ˆ๋‹ค.

Members

  • ์ •์ข…์œค(@wormwlrm)

Abstract

HTML5๋ถ€ํ„ฐ ๋“ฑ์žฅํ•œ Canvas API๋ฅผ ์ด์šฉํ•˜๋ฉด ์›น์—์„œ๋„ ์ด๋ฏธ์ง€ ํŽธ์ง‘์„ ๋น„๋กฏํ•œ ๋‹ค์–‘ํ•œ ๊ทธ๋ž˜ํ”ฝ ๊ธฐ์ˆ ์„ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ๋‹ค. ํ•˜์ง€๋งŒ ๊ธฐ๋ณธ์ ์œผ๋กœ ์ œ๊ณตํ•˜๋Š” API์˜ ์ถ”์ƒํ™” ๋‹จ๊ณ„๊ฐ€ ๋‚ฎ๊ณ , ๊ทธ๋ž˜ํ”ฝ ๋„๋ฉ”์ธ์— ๋Œ€ํ•œ ์ง€์‹์„ ๋ณ„ ๋„๋กœ ํ•™์Šตํ•ด์•ผ ํ•œ๋‹ค๋Š” ๋‹จ์ ์ด ์žˆ๋‹ค. ๋”ฐ๋ผ์„œ ๋Œ€๋ถ€๋ถ„์˜ ๊ฒฝ์šฐ์—๋Š” Canvas API๋ฅผ ์ง์ ‘ ํ™œ์šฉํ•˜๊ธฐ๋ณด๋‹ค๋Š” ์™„์„ฑ ๋œ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ํ™œ์šฉํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ์„ ํƒํ•œ๋‹ค.

๋ณธ ํ”„๋กœ์ ํŠธ์—์„œ๋Š” ์ด๋ฏธ์ง€ ํŽธ์ง‘์— ์ดˆ์ ์„ ๋งž์ถ”์–ด, 2D ๊ทธ๋ž˜ํ”ฝ ๊ธฐ๋ฐ˜์˜ Canvas API๋ฅผ ํ™œ์šฉํ•œ ์˜คํ”ˆ์†Œ์Šค ์ด๋ฏธ์ง€ ์—๋””ํ„ฐ๋ฅผ ๊ตฌํ˜„ํ•œ๋‹ค. ์ด๋ฏธ์ง€ ์—๋””ํ„ฐ์—์„œ ์ œ๊ณตํ•˜๋Š” ๋‹ค์–‘ํ•œ ํŽธ์ง‘ ๊ธฐ๋Šฅ๋“ค์„ ์–ด๋– ํ•œ Canvas API์™€ ๋””์ž์ธ ํŒจํ„ด์œผ๋กœ ๊ตฌํ˜„ ๊ฐ€๋Šฅํ•œ์ง€๋ฅผ ์—ฐ๊ตฌํ•˜๊ณ , ์ด ๊ณผ์ •์—์„œ ๋งˆ์ฃผ์น˜๋Š” ํ•œ๊ณ„์ ๋“ค์„ ๊ทน๋ณตํ•˜๋Š” ๋ฐฉ์•ˆ์— ๋Œ€ํ•ด ์•Œ์•„๋ณธ๋‹ค.

Overview

๋ณ„๋„ ์ œ์ž‘ํ•œ ๋ฐ๋ชจ ํŽ˜์ด์ง€์—์„œ ๋™์ž‘ํ•˜๋Š” ์˜ˆ์‹œ๋ฅผ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๋ณธ ํ”„๋กœ์ ํŠธ์—์„œ๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์€ ๊ธฐ๋Šฅ์„ ์ง€์›ํ•ฉ๋‹ˆ๋‹ค. ๊ธฐ๋Šฅ์— ๋Œ€ํ•œ ์ž์„ธํ•œ ์„ค๋ช…์€ ์•„๋ž˜์—์„œ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

  • [x] ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ํ˜•ํƒœ๋กœ ํŒจํ‚ค์ง€ ๋ฆด๋ฆฌ์Šค
  • [x] ํ˜ธ์ŠคํŠธ ์˜ต์…˜ ์ œ๊ณต
  • [x] ์ด๋ฏธ์ง€/๋„ํ˜• ์ปดํฌ๋„ŒํŠธ ์ƒ์„ฑ ๋ฐ ํŽธ์ง‘
  • [x] ์ˆ˜์ • ๊ฐ€๋Šฅํ•œ ํ…์ŠคํŠธ ์ปดํฌ๋„ŒํŠธ ์ œ๊ณต
  • [x] ์ปดํฌ๋„ŒํŠธ ๋ณต์ œ/์‚ญ์ œ
  • [x] ์‹คํ–‰ ์ทจ์†Œ/๋‹ค์‹œ ์‹คํ–‰
  • [x] ๋“œ๋กœ์ž‰
  • [x] ์ด๋ฏธ์ง€ ์ €์žฅ
  • [x] ์บ”๋ฒ„์Šค ํ™•๋Œ€ ๋ฐ ์ถ•์†Œ
  • [x] Z-index ์กฐ์ •
  • [x] ์ด๋ฏธ์ง€ ์ €์žฅ
  • [x] ์ง๋ ฌํ™”๋ฅผ ์ด์šฉํ•œ ์ˆ˜์ • ๋‚ด์—ญ ์ €์žฅ ๋ฐ ๋ณต์› ๊ธฐ๋Šฅ

Motivation

์ด ํ”„๋กœ์ ํŠธ๋Š” ์‚ฐํ•™ ํ˜‘๋ ฅ ํ”„๋กœ์ ํŠธ์˜ ์ฃผ์ œ์˜ ์‘์šฉ์œผ๋กœ๋ถ€ํ„ฐ ์‹œ์ž‘ํ•˜์˜€์Šต๋‹ˆ๋‹ค. ๊ธฐ์กด ์ฃผ์ œ๋Š” ์ด๋ฏธ ๋ฐฐํฌ๋œ ์ด๋ฏธ์ง€ ํŽธ์ง‘ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ์›น ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ์ œ์ž‘ํ•˜๋Š” ๊ตฌํ˜„ ํ”„๋กœ์ ํŠธ์˜€์Šต๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ ์ด๋ฏธ ๊ตฌํ˜„๋œ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ๋‹จ์ˆœํžˆ ์ด์šฉํ•˜๋Š” ๊ฒƒ์€ ํฐ ์˜๋ฏธ๊ฐ€ ์—†๋‹ค๊ณ  ์ƒ๊ฐํ–ˆ๊ณ , ๋ณด๋‹ค ๋„์ „์ ์œผ๋กœ ์ ‘๊ทผํ•ด์„œ ๋‹ค๋ฅธ ์‚ฌ๋žŒ์ด ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ์ž์ฒด๋ฅผ ์ œ์ž‘ํ•ด๋ณด๊ณ  ์‹ถ๋‹ค๋Š” ์ƒ๊ฐ์ด ๋“ค์—ˆ์Šต๋‹ˆ๋‹ค. ์ด ๊ณผ์ •์—์„œ ์‚ฐ์—…์ฒด์˜ ํ—ˆ๋ฝ์„ ๊ตฌํ–ˆ๊ณ , ์›น ํ™˜๊ฒฝ์—์„œ ์ด๋ฏธ์ง€ ํŽธ์ง‘ ๊ธฐ๋Šฅ์ด ํฌํ•จ๋œ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์ง์ ‘ ์ œ์ž‘ํ•˜๊ธฐ๋กœ ํ–ˆ์Šต๋‹ˆ๋‹ค.

Considerations

  • ์ถ”์ƒํ™”: Canvas API์—์„œ ์ œ๊ณตํ•˜๋Š” ๊ธฐ๋ณธ ์ถ”์ƒํ™” ๋‹จ๊ณ„๊ฐ€ ๋‚ฎ์•„, ์‹ค์ œ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ๋ ˆ๋ฒจ์—์„œ ๊ธฐ๋Šฅ์„ ์“ฐ๊ธฐ์—๋Š” ์ƒ์‚ฐ์„ฑ์ด ๋‚ฎ์Šต๋‹ˆ๋‹ค. ๋”ฐ๋ผ์„œ 2D ๊ทธ๋ž˜ํ”ฝ์— ๋Œ€ํ•œ ๊ธฐ๋ณธ ์ถ”์ƒํ™”๋ฅผ ์ œ๊ณตํ•˜๋Š” ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ Konva.js๋ฅผ ํ™œ์šฉํ•˜์˜€์Šต๋‹ˆ๋‹ค.
  • ์ƒํƒœ ๊ด€๋ฆฌ: Canvas ์œ„์— ๊ทธ๋ ค์ง„ ์ด๋ฏธ์ง€, ๋„ํ˜• ๋ฐ ์ˆ˜์ • ์‚ฌํ•ญ๋“ค์„ ๊ฐ์ฒด์ง€ํ–ฅ์ ์œผ๋กœ ๊ด€๋ฆฌํ•˜๋ฉด์„œ ๋ฐ์ดํ„ฐ์™€ ๋ทฐ๋ฅผ ์ผ์น˜์‹œํ‚ค๊ณ ์ž React.js๋ฅผ ํ™œ์šฉํ•˜์˜€์Šต๋‹ˆ๋‹ค.
  • ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ: JavaScript ๋ชจ๋“ˆ ์‹œ์Šคํ…œ์— ์ ํ•ฉํ•œ ํ˜•ํƒœ์—ฌ์•ผ ํ•˜๊ณ , ๋ธŒ๋ผ์šฐ์ € ๋ฐ ํŒจํ‚ค์ง€ ๊ด€๋ฆฌ์ž๋ฅผ ํ†ตํ•ด ๋‹ค์šด๋กœ๋“œ ๊ฐ€๋Šฅํ•ด์•ผ ํ•˜๋ฏ€๋กœ Rollup.js ๋ฐ NPM์„ ์‚ฌ์šฉํ•จ์œผ๋กœ์จ ํ™œ์šฉํ•˜์˜€์Šต๋‹ˆ๋‹ค.

Architecture

๋ณธ ํ”„๋กœ์ ํŠธ์˜ ์•„ํ‚คํ…์ฒ˜๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค.

๊ตฌ์กฐ๋„

  • Shapes Layer: ์บ”๋ฒ„์Šค์— ๊ทธ๋ ค์ง€๋Š” ๋ชจ๋“  ๋„ํ˜•, ์‚ฌ์šฉ์ž ์ƒํƒœ ๋“ฑ์„ ๊ด€๋ฆฌํ•ฉ๋‹ˆ๋‹ค. ๋˜ํ•œ ์บ”๋ฒ„์Šค์™€ ํŒจ๋„, ํˆด๋ฐ” ์‚ฌ์ด์˜ ์ƒํ˜ธ์ž‘์šฉ์„ ์ค‘์žฌํ•ฉ๋‹ˆ๋‹ค.
    • Snapshot: ํŠน์ •ํ•œ ์‚ฌ์šฉ์ž ์•ก์…˜(๋„ํ˜• ์ƒ์„ฑ, ์ด๋™, ์Šคํƒ€์ผ ์ˆ˜์ • ๋“ฑ)์ด ๋ฐœ์ƒํ•˜์—ฌ ํžˆ์Šคํ† ๋ฆฌ๋ฅผ ์ €์žฅํ•ด์•ผ ํ•  ๊ฒฝ์šฐ, ํ˜„์žฌ ๋„ํ˜•์„ ์Šค๋ƒ…์ƒท์œผ๋กœ ์ €์žฅํ•˜๊ณ  ํžˆ์Šคํ† ๋ฆฌ์— ์ถ”๊ฐ€ํ•ฉ๋‹ˆ๋‹ค.
  • History Layer: ์ €์žฅ๋œ ์Šค๋ƒ…์ƒท ๋ฐฐ์—ด์„ ๊ธฐ๋ฐ˜์œผ๋กœ ์‹คํ–‰ ์ทจ์†Œ ๋ฐ ๋‹ค์‹œ ์‹คํ–‰ ๊ธฐ๋Šฅ์„ ์ง€์›ํ•ฉ๋‹ˆ๋‹ค.
  • Canvas Layer: Shapes Layer์— ์ €์žฅ๋œ ๋„ํ˜•๋“ค์„ ์‹ค์ œ 2D ๊ทธ๋ž˜ํ”ฝ์œผ๋กœ ํ‘œํ˜„ํ•˜๊ณ , ์‚ฌ์šฉ์ž๋กœ๋ถ€ํ„ฐ ์ž…๋ ฅ๋ฐ›์€ ์ƒํ˜ธ์ž‘์šฉ์„ Shapes Layer์— ๋„˜๊ฒจ์ค๋‹ˆ๋‹ค.
  • Toolbar Layer: ์‚ฌ์šฉ์ž ์ƒํ˜ธ์ž‘์šฉ์„ ๋ช…์‹œ์ ์œผ๋กœ ์ž…๋ ฅ๋ฐ›๋Š” ์˜์—ญ์ž…๋‹ˆ๋‹ค.
  • Panel Layer: ํ˜„์žฌ ์„ ํƒ๋œ ๋„ํ˜•์˜ ์†์„ฑ์„ ํ‘œ์‹œํ•˜๋Š” ์˜์—ญ์ž…๋‹ˆ๋‹ค.

Features

๋ณธ ํ”„๋กœ์ ํŠธ์—์„œ๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์€ ๊ธฐ๋Šฅ์„ ์ง€์›ํ•ฉ๋‹ˆ๋‹ค.

๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ํ˜•ํƒœ๋กœ ํŒจํ‚ค์ง€ ๋ฆด๋ฆฌ์Šค

๋ฐ๋ชจ ํ”„๋กœ์ ํŠธ ํ™•์ธ์„ ์œ„ํ•ด ํ˜„์žฌ๊นŒ์ง€์˜ ๊ฐœ๋ฐœ ์‚ฌํ•ญ์„ node.js ํŒจํ‚ค์ง€ ๋งค๋‹ˆ์ €์ธ NPM์— ๋ฐฐํฌํ•œ ์ƒํƒœ์ž…๋‹ˆ๋‹ค. ๋”ฐ๋ผ์„œ ํ„ฐ๋ฏธ๋„์—์„œ ๋‹ค์Œ๊ณผ ๊ฐ™์€ ๋ช…๋ น์–ด๋ฅผ ์ž…๋ ฅํ•˜๋ฉด ์„ค์น˜๊ฐ€ ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค.

# for this package
$ npm install react-konva-image-editor

# for peer dependencies
$ npm install react react-dom

ํ˜ธ์ŠคํŠธ ์ธก์—์„œ ๋‹ค์Œ๊ณผ ๊ฐ™์ด ํ˜ธ์ถœํ•˜์—ฌ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

// App.js
import { Editor } from 'react-konva-image-editor';

return (
  <Editor />
);

ํ˜ธ์ŠคํŠธ ์˜ต์…˜ ์ œ๊ณต

๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์„ค์น˜ํ•˜์—ฌ ์‚ฌ์šฉํ•˜๋Š” ํ˜ธ์ŠคํŠธ์—์„œ ์—๋””ํ„ฐ ๋ ˆ์ด์•„์›ƒ์— ๋Œ€ํ•œ ์˜ต์…˜๊ฐ’์„ ์„ค์ •ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

const props = {
  width = window.innerHeight, // Number, ๋ช…์‹œ์ ์œผ๋กœ ๋„ˆ๋น„ ์„ค์ •
  height = 500, // Number, ๋ช…์‹œ์ ์œผ๋กœ ๋†’์ด ์„ค์ •
  responsive = false, // Boolean, ๋ฐ˜์‘ํ˜• ์„ค์ •
  aspectRatio = 1, // Number, ๋ฐ˜์‘ํ˜• ์„ค์ • ์‹œ ๋„ˆ๋น„์™€ ๋†’์ด ๋น„์œจ ์„ค์ •
}

return (
  <Editor {...props} />
);

์ด๋ฏธ์ง€/๋„ํ˜• ์ปดํฌ๋„ŒํŠธ ์ƒ์„ฑ ๋ฐ ํŽธ์ง‘

https://user-images.githubusercontent.com/26682772/145718038-42747719-298d-45d5-b2c9-b1be1c23239e.mov

์บ”๋ฒ„์Šค์— ์ด๋ฏธ์ง€์™€ ๋„ํ˜• ์ธ์Šคํ„ด์Šค๋ฅผ ์ƒ์„ฑํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๊ฐ ๋„ํ˜•์€ ๋“œ๋ž˜๊ทธ ๊ฐ€๋Šฅํ•˜๋ฉฐ(Draggable), ์ž์ฒด์ ์œผ๋กœ ํšŒ์ „, ๋ฆฌ์‚ฌ์ด์ง•์ด ๊ฐ€๋Šฅ(Transformable)ํ•ฉ๋‹ˆ๋‹ค.

์ด๋ฏธ์ง€ ๋ฐ ๋„ํ˜• ์ธ์Šคํ„ด์Šค๋Š” Shape Layer์—์„œ ๊ด€๋ฆฌ๋˜๋ฉฐ, ๊ฐ ์ปดํฌ๋„ŒํŠธ์— ๋Œ€ํ•œ ์†์„ฑ ์ •์˜๋Š” components ํด๋”์— ์ •์˜๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค.

์ด๋ฏธ์ง€๋ฅผ ๋ถˆ๋Ÿฌ์˜ฌ ๋•Œ์—๋Š” ์ด๋ฏธ์ง€ ํŒŒ์ผ์„ Base64๋กœ ์ธ์ฝ”๋”ฉํ•˜์—ฌ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.

์‹คํ–‰ ์ทจ์†Œ/๋‹ค์‹œ ์‹คํ–‰

https://user-images.githubusercontent.com/26682772/145718069-e9ac7410-0165-4cb1-9f92-7eb81fc3daf0.mov

์‹คํ–‰ ์ทจ์†Œ์™€ ๋‹ค์‹œ ์‹คํ–‰ ๊ธฐ๋Šฅ์„ ์œ„ํ•ด ๋ฉ”๋ฉ˜ํ†  ํŒจํ„ด์„ ์ ์šฉํ–ˆ์Šต๋‹ˆ๋‹ค. ๋ฉ”๋ฉ˜ํ†  ํŒจํ„ด์€ ํžˆ์Šคํ† ๋ฆฌ๋ฅผ ์ €์žฅํ•˜๋Š” Caretaker ์—ญํ• , ๊ทธ๋ฆฌ๊ณ  ํžˆ์Šคํ† ๋ฆฌ๋ฅผ ๊ฐ€๋ฆฌํ‚ค๋Š” ์ธ๋ฑ์Šค๋ฅผ ๋„์›Œ์ฃผ๋Š” Originator ์—ญํ• ์œผ๋กœ ๊ตฌ์„ฑ๋ฉ๋‹ˆ๋‹ค.

์—ฌ๊ธฐ์„œ Caretaker ์—ญํ• ์„ ํ•˜๋Š” ๊ฒƒ์ด History Layer์ž…๋‹ˆ๋‹ค. ๋„ํ˜•์— ๋Œ€ํ•œ ์‚ฌ์šฉ์ž ์ƒํ˜ธ์ž‘์šฉ(๋“œ๋ž˜๊ทธ, ํšŒ์ „, ๋ฆฌ์‚ฌ์ด์ง• ๋“ฑ)์ด ๋ฐœ์ƒํ•  ๋•Œ๋งˆ๋‹ค History Layer๋Š” ํ˜„์žฌ Shapes Layer์˜ ์ธ์Šคํ„ด์Šค๋ฅผ ์Šค๋ƒ…์ƒท์„ ์ƒ์„ฑํ•˜๊ณ  ๋ฐฐ์—ด๋กœ ๊ด€๋ฆฌํ•ฉ๋‹ˆ๋‹ค.

History Layer์—๋Š” ํŠน์ • ์Šค๋ƒ…์ƒท์„ ๊ฐ€๋ฆฌํ‚ค๋Š” ์ธ๋ฑ์Šค๊ฐ€ ์ •์˜๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค. ๋”ฐ๋ผ์„œ ์‹คํ–‰ ์ทจ์†Œ ์ด๋ฒคํŠธ๊ฐ€ ๋ฐœ์ƒํ•  ๋•Œ์—๋Š” ์ธ๋ฑ์Šค๋ฅผ 1๋งŒํผ ๊ฐ์†Œ์‹œํ‚ค๋ฉฐ, ๋‹ค์‹œ ์‹คํ–‰ ์ด๋ฒคํŠธ๊ฐ€ ๋ฐœ์ƒํ•  ๋•Œ์—๋Š” ์ธ๋ฑ์Šค๋ฅผ 1๋งŒํผ ์ฆ๊ฐ€์‹œํ‚ต๋‹ˆ๋‹ค.

Shapes Layer๋Š” ํ˜„์žฌ History Layer์—์„œ ๊ฐ€๋ฆฌํ‚ค๋Š” ์Šค๋ƒ…์ƒท์„ ๊ฐ€์ ธ์˜ต๋‹ˆ๋‹ค. Canvas Layer๋Š” Shapes Layer์˜ ์ธ์Šคํ„ด์Šค ๋ฐฐ์—ด์— ์ข…์†์ ์ด๊ธฐ ๋•Œ๋ฌธ์—, ์ €์žฅ๋œ ์ธ์Šคํ„ด์Šค๋ฅผ ์บ”๋ฒ„์Šค์— ํ‘œ์‹œํ•ฉ๋‹ˆ๋‹ค.

๋“œ๋กœ์ž‰

https://user-images.githubusercontent.com/26682772/145718089-751a0abc-ee86-4511-80ab-1543f2a21473.mov

๋“œ๋กœ์ž‰์€ ๊ธฐ๋ณธ์ ์œผ๋กœ ๋“œ๋ž˜๊ทธ ์•ค ๋“œ๋กญ๊ณผ ๋™์ผํ•œ ์ž…๋ ฅ์„ ๋ฐ›์ง€๋งŒ ๋‹ค๋ฅด๊ฒŒ ๋™์ž‘ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ๋”ฐ๋ผ์„œ ํˆด๋ฐ”์—์„œ ๋“œ๋กœ์ž‰ ๋ชจ๋“œ๋ฅผ ์„ ํƒํ•˜๊ฒŒ ๋˜๋ฉด, ์บ”๋ฒ„์Šค์— ์žˆ๋Š” ๋„ํ˜•๋“ค์€ ๋” ์ด์ƒ ๋“œ๋ž˜๊ทธ ๊ฐ€๋Šฅํ•˜์ง€ ์•Š๊ฒŒ ๋ฉ๋‹ˆ๋‹ค. ์ด๋Š” ๋„ํ˜• ์œ„์—์„œ ๋“œ๋กœ์ž‰์„ ์‹œ์ž‘ํ–ˆ์„ ๋•Œ, ๋„ํ˜•์ด ๋“œ๋ž˜๊ทธ ๋˜๋Š” ํ˜„์ƒ์„ ๋ง‰๊ธฐ ์œ„ํ•ด์„œ์ž…๋‹ˆ๋‹ค.

๋งˆ์šฐ์Šค ์ปค์„œ๋ฅผ ๋ˆ„๋ฅด๋ฉด onmousedown ์ด๋ฒคํŠธ๊ฐ€ ๋ฐœ์ƒํ•˜๊ฒŒ ๋˜๋Š”๋ฐ, ์ด๋•Œ x, y ์ขŒํ‘œ๋ฅผ ๊ตฌํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๋”ฐ๋ผ์„œ ์ด ์ขŒํ‘œ๋ฅผ ์ด์‚ฐ์ ์œผ๋กœ ๋ง๋ถ™์ด๋ฉด์„œ ๋ผ์ธ ์ปดํฌ๋„ŒํŠธ์˜ ๊ฒฝ๋กœ๋ฅผ ํ‘œ์‹œํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๋ผ์ธ ์ปดํฌ๋„ŒํŠธ ์—ญ์‹œ ๋„ํ˜• ์ธ์Šคํ„ด์Šค๋กœ ์ทจ๊ธ‰๋ฉ๋‹ˆ๋‹ค. ๋”ฐ๋ผ์„œ ๋“œ๋ž˜๊ทธ์™€ ๋ณ€ํ™˜์ด ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค.

ํ•˜์ง€๋งŒ ๋งˆ์šฐ์Šค๋ฅผ ๋–ผ๊ธฐ ์ „๊นŒ์ง€ ๋ผ์ธ ์ปดํฌ๋„ŒํŠธ๋Š” ์•„์ง ์ƒ์„ฑ๋œ ์ƒํƒœ๊ฐ€ ์•„๋‹ˆ์ง€๋งŒ, ํ˜„์žฌ๊นŒ์ง€์˜ ๊ฒฝ๋กœ๋ฅผ ์บ”๋ฒ„์Šค์— ํ‘œ์‹œํ•ด์ฃผ์–ด์•ผ ํ•  ํ•„์š”๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค. ๋”ฐ๋ผ์„œ ๋งˆ์šฐ์Šค๋ฅผ ๋–ผ๊ธฐ ์ „๊นŒ์ง€ ์ขŒํ‘œ ๋ฐฐ์—ด์„ ์ž„์‹œ๋กœ ์ €์žฅํ•˜๊ณ , ๋งˆ์šฐ์Šค๋ฅผ ๋—„ ๋•Œ ์ €์žฅ๋œ ์ขŒํ‘œ ๋ฐฐ์—ด์„ ์ด์šฉํ•ด Shape Layer์— ์ƒˆ ๋ผ์ธ ์ปดํฌ๋„ŒํŠธ๋ฅผ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค.

์ˆ˜์ • ๊ฐ€๋Šฅํ•œ ํ…์ŠคํŠธ ์ปดํฌ๋„ŒํŠธ ์ œ๊ณต

https://user-images.githubusercontent.com/26682772/145718104-01e7da02-e35f-49e8-8281-f1b8880ae069.mov

HTML5 ์ŠคํŽ™์— ๋”ฐ๋ฅด๋ฉด, ์บ”๋ฒ„์Šค์—์„œ ํ…์ŠคํŠธ๋ฅผ ๋ Œ๋”ํ•  ์ˆ˜๋Š” ์žˆ์ง€๋งŒ ์บ”๋ฒ„์Šค ๋‚ด์—์„œ ์ž…๋ ฅ๊ฐ’์„ ์ง์ ‘ ์ˆ˜์ •ํ•  ์ˆ˜ ์žˆ๋Š” ์ธํ’‹(input) ํ˜•ํƒœ๋กœ๋Š” ์ œ๊ณต๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ๋”ฐ๋ผ์„œ ํ•ด๋‹น ๊ธฐ๋Šฅ์ด ํ•„์š”ํ•œ ๊ฒฝ์šฐ๊ฐ€ ์žˆ๋‹ค๋ฉด Canvas API๋ฅผ ์ ์ ˆํžˆ ์šฐํšŒํ•˜์—ฌ์•ผ ํ•ฉ๋‹ˆ๋‹ค.

์ˆ˜์ • ๊ฐ€๋Šฅํ•œ ํ…์ŠคํŠธ ์ปดํฌ๋„ŒํŠธ๋ฅผ ๋งŒ๋“ค๊ธฐ ์œ„ํ•ด์„œ๋Š” Canvas API์™€ ์™ธ๋ถ€ DOM ์—˜๋ฆฌ๋จผํŠธ ๊ฐ„ ์Šคํƒ€์ผ ๋ฐ ๋ฐ์ดํ„ฐ๋ฅผ ๋™๊ธฐํ™”ํ–ˆ์Šต๋‹ˆ๋‹ค. ์šฐ์„  ์บ”๋ฒ„์Šค์— ํ…์ŠคํŠธ๋ฅผ ๋‚˜ํƒ€๋‚ผ ์ˆ˜ ์žˆ๋Š” ํ…์ŠคํŠธ ์ปดํฌ๋„ŒํŠธ๋ฅผ ์ •์˜ํ•ฉ๋‹ˆ๋‹ค. ์ด ์—ญ์‹œ ๋„ํ˜• ์ธ์Šคํ„ด์Šค๋กœ ์ทจ๊ธ‰๋˜๋ฏ€๋กœ ๋“œ๋ž˜๊ทธ์™€ ๋ณ€ํ™˜์ด ๊ฐ€๋Šฅํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

๋งŒ์•ฝ ํ…์ŠคํŠธ ์ปดํฌ๋„ŒํŠธ์— ๋”๋ธ” ํด๋ฆญ์ด๋‚˜ enterํ‚ค ๋“ฑ์˜ ์ด๋ฒคํŠธ๊ฐ€ ๋ฐœ์ƒํ•˜๋ฉด ํ•ด๋‹น ์œ„์น˜์— <textarea> ์—˜๋ฆฌ๋จผํŠธ๋ฅผ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค. ์ด ๋•Œ ์บ”๋ฒ„์Šค์˜ ์คŒ, ํ…์ŠคํŠธ ์ปดํฌ๋„ŒํŠธ์˜ ํšŒ์ „, ์ค„๋ฐ”๊ฟˆ์„ ๊ณ ๋ คํ•˜์—ฌ ์–ด์ƒ‰ํ•จ์ด ์—†๋„๋ก ์Šคํƒ€์ผ์„ ์ ์ ˆํžˆ ์กฐ์ •ํ•ด์ค๋‹ˆ๋‹ค.

enterํ‚ค ์ž…๋ ฅ ์‹œ ๊ฐ’์„ ์ €์žฅํ•˜๊ณ , shift + enterํ‚ค๋ฅผ ๋ˆ„๋ฅผ ๋• ์ค„๋ฐ”๊ฟˆ์ด ์ผ์–ด๋‚˜์•ผ ํ•ฉ๋‹ˆ๋‹ค. ํฌ์ปค์Šค๋ฅผ ์žƒ์—ˆ์„ ๋•Œ์—๋Š” ์ž๋™์œผ๋กœ <textarea> ๊ฐ€ ์‚ญ์ œ๋˜๋„๋ก ํ•ฉ๋‹ˆ๋‹ค.

์บ”๋ฒ„์Šค ํ™•๋Œ€ ๋ฐ ์ถ•์†Œ

https://user-images.githubusercontent.com/26682772/145718305-dbe41202-d872-4d92-a5ea-47dfa597e871.mov

์คŒ ๋ฐฐ์œจ์„ ์ง€์ •ํ•˜๋Š” ๋ณ€์ˆ˜๋ฅผ ์„ ์–ธํ•˜๊ณ , ์ด๋ฅผ ์บ”๋ฒ„์Šค ๋ ˆ์ด์–ด์— ์ ์šฉํ•˜๊ณ  ์ ์ ˆํžˆ ๋ฆฌ์‚ฌ์ด์ง•ํ•ฉ๋‹ˆ๋‹ค.

์ด ๋•Œ ๊ธฐ์กด์˜ ๋„ํ˜• ์ธ์Šคํ„ด์Šค ๋ฐฐ์น˜์— ์˜ํ–ฅ์ด ๊ฐ€์ง€ ์•Š๊ฒŒ ํ•ฉ๋‹ˆ๋‹ค.

ํ•œํŽธ ์บ”๋ฒ„์Šค ์˜์—ญ์ด ๋ณด์—ฌ์ง€๋Š” ํ™”๋ฉด๋ณด๋‹ค ํด ๊ฒฝ์šฐ ์Šคํฌ๋กค ๊ฐ€๋Šฅํ•˜๊ฒŒ ๋งŒ๋“ญ๋‹ˆ๋‹ค.

Z-index ์กฐ์ •

https://user-images.githubusercontent.com/26682772/145718128-7024a879-ed43-436a-a834-608cfa0cccbd.mov

๋„ํ˜• ๊ฐ„ z-index ์กฐ์ •์€ Shape Layer์˜ ๋„ํ˜• ์ธ์Šคํ„ด์Šค ๋ฐฐ์—ด์˜ ์ˆœ์„œ๋ฅผ ๋ฐ”๊พธ๋Š” ๋ฐฉ์‹์œผ๋กœ ๊ตฌํ˜„ ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค.

์ด๋ฏธ์ง€ ์ €์žฅ ๋ฐ ์ง๋ ฌํ™” ๊ธฐ๋ฐ˜ ์ €์žฅ ๋ฐ ๋ณต์› ๊ธฐ๋Šฅ

https://user-images.githubusercontent.com/26682772/145718140-2ecfcfc6-2a85-4e88-a688-b5feb4a2a89a.mov

์ด๋ฏธ์ง€ ์ €์žฅ ๊ธฐ๋Šฅ์€ ํ˜„์žฌ ๋ Œ๋”๋œ ํ™”๋ฉด์„ ์ด๋ฏธ์ง€ ํŒŒ์ผ๋กœ ์ €์žฅํ•  ์ˆ˜ ์žˆ๋Š” Canvas API์˜ toDataURL() ๋ฉ”์„œ๋“œ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค.

๋„ํ˜•์„ ์ธ์Šคํ„ด์Šคํ™”ํ–ˆ๊ธฐ ๋•Œ๋ฌธ์—, ํ˜„์žฌ๊นŒ์ง€์˜ ํŽธ์ง‘ ๋‚ด์—ญ์ด ๋‹ด๊ธด Shape Layer์˜ ๋„ํ˜• ์ธ์Šคํ„ด์Šค ๋ฐฐ์—ด์„ ์ง๋ ฌํ™”ํ•˜์—ฌ JSON ํŒŒ์ผ๋กœ ์ €์žฅํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์ดํ›„ JSON ํŒŒ์ผ์„ ๋ถˆ๋Ÿฌ์˜ฌ ๋•Œ, Shape Layer ๋ฐฐ์—ด์— ์ ์ ˆํžˆ ๋งค์นญ์‹œํ‚ค๋Š” ๊ธฐ๋Šฅ๊นŒ์ง€ ๊ตฌํ˜„๋œ ์ƒํƒœ์ž…๋‹ˆ๋‹ค.

ํ™œ์šฉ ์˜ˆ์‹œ

ํ™œ์šฉ

๋ณธ ํ”„๋กœ์ ํŠธ๋Š” ํ˜„์žฌ๊นŒ์ง€์˜ ์บ”๋ฒ„์Šค์— ๊ทธ๋ ค์ง„ ๋„ํ˜• ์ƒํƒœ๋ฅผ JSON ํฌ๋งท์œผ๋กœ ์ง๋ ฌํ™”ํ•  ์ˆ˜ ์žˆ๊ณ , ์ด๋ฅผ ํŒŒ์ผ๋กœ ๋‹ค์‹œ ๋ถˆ๋Ÿฌ์˜ฌ ์ˆ˜ ์žˆ๋‹ค๋Š” ์ ์—์„œ ๋…์ฐฝ์„ฑ์ด ์žˆ๋‹ค. ์ด๋ฅผ ์„ค๋ช…ํ•˜๊ธฐ ์œ„ํ•ด, ๊ด€๋ จ ์˜คํ”ˆ์†Œ์Šค์ธ NHN Toast UI Image Editor์™€ ๋น„๊ตํ•ด ํ•ด๋‹น ๊ธฐ๋Šฅ์„ ์„ค๋ช…ํ•œ๋‹ค.

NHN์˜ Toast UI Image Editor๋Š” ๊ฒฐ๊ณผ๋ฌผ๋กœ ์ด๋ฏธ์ง€ ํŒŒ์ผ๋งŒ ์ถ”์ถœ(export) ํ•  ์ˆ˜ ์žˆ๋‹ค. ๋”ฐ๋ผ์„œ ํ˜„์žฌ ์บ”๋ฒ„์Šค๊ฐ€ ๊ทธ๋ ค์ ธ ์žˆ๋Š” ์›น ๋ธŒ๋ผ์šฐ์ €๋ฅผ ๋‹ซ๊ฒŒ ๋˜๋ฉด ๋ฉ”๋ชจ๋ฆฌ์— ์˜ฌ๋ผ๊ฐ€ ์žˆ๋Š” ๋ฐ์ดํ„ฐ๋“ค์ด ๋ชจ๋‘ ์‚ฌ๋ผ์ง€๊ธฐ ๋•Œ๋ฌธ์—, ํ•ด๋‹น ๊ฒฐ๊ณผ๋ฌผ์„ ๋‹ค์‹œ ๋ณต๊ตฌํ•˜๊ฑฐ๋‚˜ ์ˆ˜์ •ํ•  ์ˆ˜ ์—†๋‹ค.

ํ•œํŽธ ๋ณธ ํ”„๋กœ์ ํŠธ์—์„œ๋Š” ๋ฐ์ดํ„ฐ ์ง๋ ฌํ™” ๊ธฐ๋Šฅ์„ ์ง€์›ํ•˜๊ธฐ ๋•Œ๋ฌธ์—, ์‚ฌ์šฉ์ž์˜ ํ˜„์žฌ ์ž‘์—… ๋‚ด์—ญ์„ JSON ํŒŒ์ผ๋กœ ์ €์žฅํ•  ์ˆ˜ ์žˆ๋‹ค. ๋”ฐ๋ผ์„œ ํ•ด๋‹น JSON ํŒŒ์ผ์„ ์—ญ์ง๋ ฌํ™”ํ•˜๋ฉด์„œ ์ž‘์—… ๋‚ด์—ญ์„ ๋ณต๊ตฌํ•  ์ˆ˜ ์žˆ๋‹ค. ๋งŒ์•ฝ JSON ํŒŒ์ผ์„ ๋‹ค๋ฅธ ์‚ฌ์šฉ์ž์—๊ฒŒ ๊ณต์œ ํ•  ์ˆ˜ ์žˆ๋‹ค๋ฉด, ๋™์ผํ•œ ์ž‘์—… ๋‚ด์—ญ์„ ์—ฌ๋Ÿฌ ์‚ฌ์šฉ์ž์—๊ฒŒ ์ œ๊ณตํ•  ์ˆ˜๋„ ์žˆ๋‹ค. ์ด ๊ธฐ๋Šฅ์„ ํ˜ธ์ŠคํŠธ ์ชฝ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ๋“ฑ์— ์ €์žฅํ•ด๋‘˜ ์ˆ˜ ์žˆ๋‹ค๋ฉด ์ด ์žฅ์ ์ด ๊ทน๋Œ€ํ™”๋œ๋‹ค. ์‚ฌ์šฉ์ž๋Š” ์›๊ฒฉ ์„œ๋ฒ„์— ๋ณธ์ธ์˜ ์ž‘์—… ๋‚ด์—ญ์„ ์ €์žฅํ•ด๋‘๊ณ  ์–ธ์ œ๋“  ๋ถˆ๋Ÿฌ์˜ฌ ์ˆ˜ ์žˆ๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค.

Conclusion & Limitations

  • HTML5์˜ Canvas API๋ฅผ ํ™œ์šฉํ•˜์—ฌ ์ด๋ฏธ์ง€ ์—๋””ํ„ฐ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์ œ์ž‘ํ•˜๊ณ , ๊ฐ ๊ธฐ๋Šฅ๋“ค์˜ ๊ตฌํ˜„ ๋ฐฉ๋ฒ•๊ณผ ํ•œ๊ณ„์ ์— ๋Œ€ํ•ด ์—ฐ๊ตฌํ•˜๊ณ  ์†Œ์Šค ์ฝ”๋“œ๋ฅผ ๊ณต๊ฐœํ–ˆ์Šต๋‹ˆ๋‹ค.
  • ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ๋ ˆ๋ฒจ์—์„œ ์ ํ•ฉํ•œ ๊ณ ์ˆ˜์ค€์˜ ์ถ”์ƒํ™”๋ฅผ ์ œ๊ณตํ•˜๋Š” ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ํ™œ์šฉํ•˜๊ณ , ์ƒํ™ฉ์— ๋งž๋Š” ๋””์ž์ธ ํŒจํ„ด์„ ์‚ฌ์šฉํ•จ์œผ๋กœ์„œ ์ด๋ฏธ์ง€ ์—๋””ํ„ฐ์˜ ๊ธฐ๋Šฅ์„ ๊ตฌํ˜„ํ•  ์ˆ˜ ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค.
  • Canvas API์˜ ๋ฒ”์œ„๋ฅผ ๋„˜์–ด์„œ๋Š” ์š”๊ตฌ์‚ฌํ•ญ์— ๋Œ€ํ•ด์„œ๋Š” ๊ธฐ์ˆ ์ ์œผ๋กœ ์šฐํšŒํ•˜๋ฉด์„œ๋„ ์‚ฌ์šฉ์ž ๊ฒฝํ—˜์„ ํ•ด์น˜์ง€ ์•Š์•˜๋‹ค๋Š” ์ ์—์„œ ์˜์˜๊ฐ€ ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค.
  • ๋‹ค๋งŒ ์‹œ๊ฐ„์  ์—ฌ์œ  ์ƒ ๊ธฐ๋Šฅ์„ฑ ๋‹ค์–‘ํ™” ๋ฐ ์‚ฌ์šฉ์ž ํŽธ์˜๋ฅผ ์œ„ํ•œ ๋ถ€๊ฐ€์ ์ธ ๊ธฐ๋Šฅ๋“ค(ํ‚ค๋ณด๋“œ ๋‹จ์ถ•ํ‚ค ์ง€์› ๋“ฑ)์— ๋Œ€ํ•ด์„œ๋Š” ๊ตฌํ˜„์ด ์™„๋ฃŒ๋˜์ง€ ์•Š์€ ๋ถ€๋ถ„์ด ์žˆ์Šต๋‹ˆ๋‹ค.
  • ๋˜ํ•œ ๋ณต์žกํ•œ ์ƒํƒœ ๊ด€๋ฆฌ ๋ฐ UI ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ์ ์šฉ์œผ๋กœ ์ธํ•ด ์ฝ”๋“œ ๊ฐ€๋…์„ฑ ๋ฐ ์„ฑ๋Šฅ ์ตœ์ ํ™” ๋ฉด์—์„œ ๋‹ค์†Œ ๋ถ€์กฑํ•œ ๋ถ€๋ถ„์ด ์žˆ์Šต๋‹ˆ๋‹ค.
  • ํ–ฅํ›„ ๊ธฐ๋Šฅ ์ถ”๊ฐ€ ๋ฐ ์œ ์ง€๋ณด์ˆ˜๋ฅผ ํ†ตํ•ด ์„ฑ๋Šฅ๊ณผ ํŽธ์˜๋ฅผ ๊ฐœ์„ ํ•ด ๋‚˜๊ฐˆ ์˜ˆ์ •์ž…๋‹ˆ๋‹ค.

References

ํ”„๋กœ์ ํŠธ ๊ฐœ๋ฐœ ์ค‘ ์ฐธ๊ณ ํ•œ ์ž๋ฃŒ์ž…๋‹ˆ๋‹ค.

Papers

Others

๋ณด๊ณ ์„œ

ํ•ด๋‹น ๋…ผ๋ฌธ์€ 2021 ํ•œ๊ตญ์ •๋ณด๊ณผํ•™ํšŒ ํ•™๋ถ€์ƒ ๋…ผ๋ฌธ๊ฒฝ์ง„๋Œ€ํšŒ ๋ณธ์„  ์ง„์ถœ์ž‘์ž…๋‹ˆ๋‹ค.

  • [x] ์ตœ์ข…๋ณด๊ณ ์„œ
  • [x] ํ•œ๊ตญ์ •๋ณด๊ณผํ•™ํšŒ ๋…ผ๋ฌธ
  • [x] ํ•œ๊ตญ์ •๋ณด๊ณผํ•™ํšŒ ํ•™๋ถ€์ƒ๊ฒฝ์ง„๋Œ€ํšŒ ๋ฐœํ‘œ์˜์ƒ