프로젝트 배경

보석 십자수(Diamond Painting)는 최근 많은 인기를 얻고 있는 취미입니다. 하지만 개인적인 사진(가족, 반려동물 등)을 보석 십자수로 만들고 싶어도 전문 서비스를 이용하면 비용이 많이 듭니다.

이 문제를 해결하기 위해, 누구나 무료로 자신만의 보석 십자수 도안을 만들 수 있는 웹 애플리케이션 Diastr를 개발했습니다.

기술 스택

핵심 기능 구현

1. 이미지 업로드 및 처리

사용자가 이미지를 업로드하면 Canvas API를 사용하여 이미지를 읽고 픽셀 데이터를 추출합니다.

const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
const img = new Image();

img.onload = function() {
  canvas.width = img.width;
  canvas.height = img.height;
  ctx.drawImage(img, 0, 0);
  const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
  // 픽셀 데이터 처리
};

2. 색상 매칭 시스템 (Delta E 2000)

보석 십자수용 보석 색상 팔레트와 업로드된 이미지의 픽셀 색상을 비교하여 가장 유사한 보석 색상을 찾습니다.

Delta E 2000 알고리즘은 두 색상 간의 차이를 계산하는 가장 정확한 방법 중 하나입니다.

function deltaE2000(lab1, lab2) {
  const L1 = lab1.L, a1 = lab1.a, b1 = lab1.b;
  const L2 = lab2.L, a2 = lab2.a, b2 = lab2.b;

  const kL = 1, kC = 1, kH = 1;

  const C1 = Math.sqrt(a1 * a1 + b1 * b1);
  const C2 = Math.sqrt(a2 * a2 + b2 * b2);
  const Cab = (C1 + C2) / 2;

  const G = 0.5 * (1 - Math.sqrt(Math.pow(Cab, 7) / (Math.pow(Cab, 7) + Math.pow(25, 7))));

  const a1p = a1 * (1 + G);
  const a2p = a2 * (1 + G);

  const C1p = Math.sqrt(a1p * a1p + b1 * b1);
  const C2p = Math.sqrt(a2p * a2p + b2 * b2);

  const h1p = Math.atan2(b1, a1p) * 180 / Math.PI;
  const h2p = Math.atan2(b2, a2p) * 180 / Math.PI;

  const dLp = L2 - L1;
  const dCp = C2p - C1p;
  const dhp = calculateDhp(h1p, h2p);

  const dHp = 2 * Math.sqrt(C1p * C2p) * Math.sin(dhp * Math.PI / 360);

  const Lp = (L1 + L2) / 2;
  const Cp = (C1p + C2p) / 2;

  const Hp = calculateHp(h1p, h2p, Cp);

  const T = 1 - 0.17 * Math.cos((Hp - 30) * Math.PI / 180)
           + 0.24 * Math.cos(2 * Hp * Math.PI / 180)
           + 0.32 * Math.cos((3 * Hp + 6) * Math.PI / 180)
           - 0.20 * Math.cos((4 * Hp - 63) * Math.PI / 180);

  const SL = 1 + (0.015 * Math.pow(Lp - 50, 2)) / Math.sqrt(20 + Math.pow(Lp - 50, 2));
  const SC = 1 + 0.045 * Cp;
  const SH = 1 + 0.015 * Cp * T;

  const RT = -2 * Math.sqrt(Math.pow(Cp, 7) / (Math.pow(Cp, 7) + Math.pow(25, 7)))
            * Math.sin(60 * Math.exp(-Math.pow((Hp - 275) / 25, 2)) * Math.PI / 180);

  const dE = Math.sqrt(
    Math.pow(dLp / (kL * SL), 2) +
    Math.pow(dCp / (kC * SC), 2) +
    Math.pow(dHp / (kH * SH), 2) +
    RT * (dCp / (kC * SC)) * (dHp / (kH * SH))
  );

  return dE;
}

3. 도안 생성

색상 매칭 결과를 기반으로 보석 십자수 도안을 생성하고 사용자에게 다운로드 옵션을 제공합니다.

개발 과정에서의 도전 과제

색상 공간 변환

RGB 색상 공간에서 LAB 색상 공간으로 변환해야 Delta E 2000 알고리즘을 적용할 수 있습니다.

function rgbToLab(r, g, b) {
  // RGB to XYZ
  let rNorm = r / 255, gNorm = g / 255, bNorm = b / 255;

  rNorm = rNorm > 0.04045 ? Math.pow((rNorm + 0.055) / 1.055, 2.4) : rNorm / 12.92;
  gNorm = gNorm > 0.04045 ? Math.pow((gNorm + 0.055) / 1.055, 2.4) : gNorm / 12.92;
  bNorm = bNorm > 0.04045 ? Math.pow((bNorm + 0.055) / 1.055, 2.4) : bNorm / 12.92;

  const x = rNorm * 0.4124564 + gNorm * 0.3575761 + bNorm * 0.1804375;
  const y = rNorm * 0.2126729 + gNorm * 0.7151522 + bNorm * 0.0721750;
  const z = rNorm * 0.0193339 + gNorm * 0.1191920 + bNorm * 0.9503041;

  // XYZ to LAB
  const xRef = 95.047, yRef = 100.000, zRef = 108.883;

  const fx = x / xRef > 0.008856 ? Math.pow(x / xRef, 1/3) : (7.787 * x / xRef) + 16/116;
  const fy = y / yRef > 0.008856 ? Math.pow(y / yRef, 1/3) : (7.787 * y / yRef) + 16/116;
  const fz = z / zRef > 0.008856 ? Math.pow(z / zRef, 1/3) : (7.787 * z / zRef) + 16/116;

  const L = 116 * fy - 16;
  const a = 500 * (fx - fy);
  const b = 200 * (fy - fz);

  return { L, a, b };
}

성능 최적화

대용량 이미지 처리 시 브라우저 성능 저하 문제가 있었습니다. 이를 해결하기 위해:

  1. 이미지 크기 제한 (최대 2000x2000 픽셀)
  2. Web Worker 사용하여 메인 스레드 차단 방지
  3. 색상 캐싱으로 중복 계산 제거

결론

Diastr를 통해 누구나 쉽게 자신만의 보석 십자수 도안을 만들 수 있게 되었습니다. Delta E 2000 알고리즘을 적용하여 정확한 색상 매칭을 구현했고, Canvas API를 활용하여 클라이언트 사이드에서 모든 처리를 수행합니다.

프로젝트는 https://diastr.dreamurl.biz/에서 사용해볼 수 있습니다.