본문 바로가기
Javascript/Paint JS

[Vanilla-JS] 7. Saving the Image

by 닉우 2020. 8. 20.

이미지를 우클릭하면 아래와 같이 저장이 가능하다.

 

canvas는 pixel을 다루는 거니까 기본적으로 image를 만든다.

그래서 download와 save파트는 이미 내장되어 있다.


하지만 아래와 같이 저장했을 때 버그가 있다.

 

 

canvas의 배경색이 transparent(투명)로 저장이 된다.

canvas의 배경색을 설정하지 않아서 실제 pixel manipulator canvas에

background를 설정하지 않았다. ( HTML 의 background만 설정한거다.)

 

그래서 canvas가 load 되자마자 설정되도록 해야된다.


fillStyle을 black으로 만들기 전에

 이렇게 설정을 하면 기본적으로 canvas의 background는 default에 의해서 하얀 배경이 되는거다.


이러한 context menu를 원하지 않으면 addEventListener로 할 수 있다.


이제 우클릭을 해도 context menu가 뜨지 않는다.


이제 SAVE 버튼을 눌렀을 때 저장 할 수 있도록 설정해보자.

 

우리가 원하는건 그린 것들을 다 넣고, image 안에 이것들을 담아내는거다.

먼저 필요한게 canvas의 데이터를 image 처럼 얻는거다.

 

https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement/toDataURL

 

HTMLCanvasElement.toDataURL()

The HTMLCanvasElement.toDataURL() method returns a data URI containing a representation of the image in the format specified by the type parameter (defaults to PNG). The returned image is in a resolution of 96 dpi.

developer.mozilla.org

HTMLCanvasElement.toDataURL() 메소드는 기본적으로 PNG로 설정된 type parameter에 의해

지정된 포맷의 이미지 표현을 포함한 data URL을 반환한다.

 

이건 변경할 수 있다. png가 될 수도 있고, jpeg도 될 수 있고 (image format이라면 뭐든지)



이제 링크를 만들거다.

download 속성은 browser에게 이 링크로 가는 대신 URL을 다운로드 하라고 지시하는거다.

그래서 link는 이미지를 갖게 된다.


이제 클릭을 가짜로 만들면 된다.

하지만 클릭해도 아무런 반응이 없다.

그 이유는 image가 href로 되어야 하고 download는 그 이름을 가져야 된다.


href는 image(URL)가 되어야 하고 download는 이름을 정해주면 된다.


사진을 저정하고 열어보니 JPEG의 퀄리티가 안좋아서 PNG로 바꾸겠다.

저 부분을 지워주면 default가 png니까 png가 될 거다.

 

png로 해도 픽셀이 조금 깨지는데 그건 700x700 사이즈의 image라 그렇다.

초고화질을 원하면 canvas를 고품질로 만들어야 된다.

 


<소스코드>

 

<html>

<!DOCTYPE html>
<html lang="ko">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <link rel="stylesheet" href="./styles.css" />
    <title>PaintJS</title>
  </head>
  <body>
    <canvas id="jsCanvas" class="canvas"></canvas>
    <div class="controls">
      <div class="controls__range">
        <!-- 페인트 브러쉬의 사이즈를 컨트롤 -->
        <input
          type="range"
          id="jsRange"
          min="0.1"
          max="5.0"
          value="2.5"
          step="0.1"
        />
        <!-- value는 기본값을 말하고 step은 0.1씩 이동 -->
      </div>
      <div class="controls__btns">
        <button id="jsMode">Fill</button>
        <button id="jsSave">Save</button>
      </div>
      <div class="controls__colors" id="jsColors">
        <div
          class="controls__color jsColor"
          style="background-color: white;"
        ></div>
        <div
          class="controls__color jsColor"
          style="background-color: #2c2c2c;"
        ></div>
        <div
          class="controls__color jsColor"
          style="background-color: #ff3b30;"
        ></div>
        <div
          class="controls__color jsColor"
          style="background-color: #ff9500;"
        ></div>
        <div
          class="controls__color jsColor"
          style="background-color: #ffcc00;"
        ></div>
        <div
          class="controls__color jsColor"
          style="background-color: #4cd963;"
        ></div>
        <div
          class="controls__color jsColor"
          style="background-color: #5ac8fa;"
        ></div>
        <div
          class="controls__color jsColor"
          style="background-color: #0579ff;"
        ></div>
        <div
          class="controls__color jsColor"
          style="background-color: #5856d6;"
        ></div>
      </div>
    </div>
    <script src="./app.js"></script>
  </body>
</html>

 

<css>

@import "reset.css";

body {
  background-color: #f6f9fc;
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen,
    Ubuntu, Cantarell, "Open Sans", "Helvetica Neue", sans-serif;
  display: flex;
  flex-direction: column;
  align-items: center;
  padding: 50px 0px; /*  밑부분의 잉여공간을 줄여줬다. */
}

.canvas {
  width: 700px;
  height: 700px;
  background-color: white;
  border-radius: 15px;
  box-shadow: 0 4px 6px rgba(50, 50, 93, 0.11), 0 1px 3px rgba(0, 0, 0, 0.08);
}
.controls {
  margin-top: 50px; /* canvas와 controls의 거리를 두기위해 */
  display: flex;
  flex-direction: column;
  align-items: center;
}
.controls .controls__btns {
  margin-bottom: 30px;
}

.controls__btns button {
  all: unset; /* 버튼에 기본적으로 적용된 스타일을 제거 */
  cursor: pointer;
  background-color: white;
  padding: 5px 0px;
  width: 80px;
  text-align: center;
  border-radius: 10px;
  box-shadow: 0 4px 6px rgba(50, 50, 93, 0.11), 0 1px 3px rgba(0, 0, 0, 0.08);
  border: 2px solid rgba(0, 0, 0, 0.2); /* 버튼에 회색 테두리 */
  color: rgba(0, 0, 0, 0.6);
  text-transform: uppercase; /* 전체 대문자로 */
  font-weight: 800;
  font-size: 12px;
}
.controls__btns button:active {
  transform: scale(0.98); /*  요소를 확대 또는 축소 */
}

.controls .controls__colors {
  display: flex; /* 일렬로 나열 */
}

.controls__colors .controls__color {
  /* 각각의 색상 박스들 */
  width: 50px;
  height: 50px;
  border-radius: 25px; /* 원형으로 만드려면 width의 반만큼 border-radius를 가지면 된다. */
  box-shadow: 0 4px 6px rgba(50, 50, 93, 0.11), 0 1px 3px rgba(0, 0, 0, 0.08);
}

.controls .controls__range {
  margin-bottom: 30px;
}

<js>

const canvas = document.getElementById("jsCanvas");
const ctx = canvas.getContext("2d");
const colors = document.getElementsByClassName("jsColor");
const range = document.getElementById("jsRange");
const mode = document.getElementById("jsMode");
const saveBtn = document.getElementById("jsSave");

const INITIAL_COLOR = "#2c2c2c";
const CANVAS_SIZE = 700;

canvas.width = CANVAS_SIZE;
canvas.height = CANVAS_SIZE; // css에서 설정한 캔버스 사이즈와 동일해야된다.

ctx.fillStyle = "white";
ctx.fillRect(0, 0, CANVAS_SIZE, CANVAS_SIZE);

ctx.strokeStyle = "INITIAL_COLOR";
ctx.fillStyle = "INITIAL_COLOR";
ctx.lineWidth = 2.5;

let painting = false; /* 기본값으로 false로 주고 클릭했을 때 true가 될거다. */
let filling = false; /* filling을 하고 있으면 그걸 나에게 말해줄 variable이 필요하다. */

function startPainting() {
  painting = true;
}

function stopPainting() {
  painting = false;
}

function onMouseMove(event) {
  const x = event.offsetX;
  const y = event.offsetY;
  if (!painting) {
    ctx.beginPath();
    ctx.moveTo(x, y);
  } else {
    ctx.lineTo(x, y);
    ctx.stroke();
  }
}
function handleColorClick(event) {
  const color = event.target.style.backgroundColor;
  ctx.strokeStyle = color;
  ctx.fillStyle = color;
}

function handleRangeChange(event) {
  const size = event.target.value;
  ctx.lineWidth = size;
}

function handleModeClick(event) {
  if (filling === true) {
    filling = false;
    mode.innerText = "Fill";
  } else {
    filling = true;
    mode.innerText = "Paint";
  }
}

function handleCanvasClick() {
  /* 코너에서부터 시작(0, 0)하고 캔버스보다 커야된다.*/
  if (filling) {
    ctx.fillRect(0, 0, CANVAS_SIZE, CANVAS_SIZE);
  }
}

function handleCM(event) {
  event.preventDefault();
}

function handleSaveClick() {
  const image = canvas.toDataURL();
  const link = document.createElement("a");
  link.href = image;
  link.download = "PaintJs[🎨]";
  link.click();
}

if (canvas) {
  canvas.addEventListener("mousemove", onMouseMove);
  canvas.addEventListener("mousedown", startPainting);
  canvas.addEventListener("mouseup", stopPainting);
  canvas.addEventListener("mouseleave", stopPainting);
  canvas.addEventListener("click", handleCanvasClick);
  canvas.addEventListener("contextmenu", handleCM);
}

Array.from(colors).forEach((color) =>
  color.addEventListener("click", handleColorClick)
);

if (range) {
  range.addEventListener("input", handleRangeChange);
}

if (mode) {
  mode.addEventListener("click", handleModeClick);
}

if (saveBtn) {
  saveBtn.addEventListener("click", handleSaveClick);
}

<실행화면>


 

 

 

 

 

 

 

 

 

 

 

 

 

 

※ 본 포스팅은 개인 공부 기록을 목적으로 남긴 글이며 본 사실과 다른 부분이 있다면 과감하게 지적 부탁드립니다.

'Javascript > Paint JS' 카테고리의 다른 글

[Vanilla-JS] 6. Filling Mode  (0) 2020.08.20
[Vanilla-JS] 5. Changing Color / Brush Size  (0) 2020.08.17
[Vanilla-JS] 4. 2D Context  (0) 2020.08.17
[Vanilla-JS] 3. Canvas Events  (0) 2020.08.13
[Vanilla-JS] 2. Styles part  (0) 2020.08.10

댓글