인페인팅

Inpainting

이미지의 일부 영역만 마스크로 지정해서, 그 부분만 새로 그리고 나머지는 그대로 두는 편집 방식.

다른 이름: Masked editingGenerative fill
···
html
<div class="wrap">
  <canvas id="cv"></canvas>
  <div class="panel">마스크 안쪽만 다시 생성</div>
</div>
css
.wrap{position:relative;width:100%;height:100%}
#cv{position:absolute;inset:0;width:100%;height:100%}
.panel{position:absolute;top:10px;left:10px;z-index:2;padding:6px 10px;border-radius:10px;
  background:rgba(13,13,18,0.55);backdrop-filter:blur(6px);border:1px solid rgba(255,255,255,0.15);
  font-size:11px;font-weight:700;color:#f1f0ec}
js
const cv = document.getElementById('cv'), ctx = cv.getContext('2d');
function fit() { const dpr = Math.min(devicePixelRatio || 1, 2); cv.width = innerWidth * dpr; cv.height = innerHeight * dpr; ctx.setTransform(dpr, 0, 0, dpr, 0, 0); }
addEventListener('resize', fit); fit();

function mulberry32(a) { return function () { a |= 0; a = a + 0x6D2B79F5 | 0; let t = Math.imul(a ^ a >>> 15, 1 | a); t = t + Math.imul(t ^ t >>> 7, 61 | t) ^ t; return ((t ^ t >>> 14) >>> 0) / 4294967296; }; }

function paintBase(w, h) {
  const r = mulberry32(5);
  const g = ctx.createLinearGradient(0, 0, 0, h);
  g.addColorStop(0, 'hsl(200 50% 62%)'); g.addColorStop(1, 'hsl(210 40% 32%)');
  ctx.fillStyle = g; ctx.fillRect(0, 0, w, h);
  ctx.beginPath(); ctx.arc(w * 0.18, h * 0.2, Math.min(w, h) * 0.08, 0, Math.PI * 2); ctx.fillStyle = 'hsl(48 85% 78%)'; ctx.fill();
  for (let l = 0; l < 2; l++) {
    ctx.beginPath(); ctx.moveTo(0, h);
    for (let i = 0; i <= 6; i++) { const x = w * i / 6; const y = h * (0.55 + l * 0.16) - r() * h * 0.1; ctx.lineTo(x, y); }
    ctx.lineTo(w, h); ctx.closePath();
    ctx.fillStyle = 'hsl(150 30% ' + (22 + l * 12) + '%)'; ctx.fill();
  }
}
function paintFill(mx, my, mw, mh, seed) {
  const r = mulberry32(seed);
  ctx.fillStyle = 'hsl(' + Math.floor(r() * 360) + ' 55% 45%)'; ctx.fillRect(mx, my, mw, mh);
  for (let i = 0; i < 6; i++) {
    ctx.beginPath(); ctx.arc(mx + r() * mw, my + r() * mh, Math.min(mw, mh) * (0.1 + r() * 0.15), 0, Math.PI * 2);
    ctx.fillStyle = 'hsl(' + Math.floor(r() * 360) + ' 70% 60%)'; ctx.fill();
  }
}
function render(seed) {
  const w = innerWidth, h = innerHeight;
  ctx.clearRect(0, 0, w, h);
  paintBase(w, h);
  const mx = w * 0.32, my = h * 0.32, mw = w * 0.38, mh = h * 0.4;
  ctx.save(); ctx.beginPath(); ctx.rect(mx, my, mw, mh); ctx.clip();
  paintFill(mx, my, mw, mh, seed);
  ctx.restore();
  ctx.setLineDash([6, 4]); ctx.strokeStyle = 'rgba(255,255,255,0.85)'; ctx.lineWidth = 2;
  ctx.strokeRect(mx, my, mw, mh); ctx.setLineDash([]);
}
const variants = [11, 22, 33];
let vi = 0;
function loop() { render(variants[vi]); vi = (vi + 1) % variants.length; setTimeout(loop, 1500); }
loop();

사용자가 지우거나 바꾸고 싶은 영역을 마스크(흰색/검은색 이진 영역)로 표시하면, 생성 과정의 매 스텝마다 마스크 바깥 픽셀은 원본 이미지의 값(그 스텝에 맞게 노이즈를 살짝 더한 값)으로 강제로 되돌려집니다. 그래서 실제로 진화하는 건 마스크 안쪽뿐입니다.

프롬프트는 이미지 전체가 아니라 마스크 안에 무엇이 들어가야 하는지를 설명하는 용도로 씁니다. 물체 제거·교체, 결과물의 결함 부분만 다시 그리기, 작은 영역을 다른 내용으로 바꾸기 등에 흔히 쓰입니다.

마스크 경계를 딱딱하게 자르면 안팎의 색감·질감이 어긋나는 이음매가 보이기 쉽습니다. 그래서 대부분의 도구는 마스크 가장자리를 부드럽게 페더링(feathering)해서 전환을 자연스럽게 만듭니다.

아래 데모는 실제 마스크 안내 생성 대신, 캔버스를 클리핑 영역으로 잘라 그 안쪽만 다른 패턴으로 다시 그리고 바깥은 전혀 다시 그리지 않는 절차적 시뮬레이션입니다.

언제 쓰나

이미지 전체를 다시 만들지 않고 특정 부분만 고치거나 바꾸고 싶을 때 씁니다. 프롬프트는 마스크 안 내용에 집중해서 씁니다.