Outpainting

아웃페인팅

Extending the canvas beyond the original image and generating new content in the added margins that continues it naturally.

Also known as: Generative fill outwardCanvas extension
···
html
<div class="wrap"><canvas id="cv"></canvas></div>
css
.wrap{position:relative;width:100%;height:100%}
#cv{position:absolute;inset:0;width:100%;height:100%}
js
const cv = document.getElementById('cv'), ctx = cv.getContext('2d');
const big = document.createElement('canvas');
const bctx = big.getContext('2d');
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 paintFull(w, h) {
  const r = mulberry32(9);
  const g = bctx.createLinearGradient(0, 0, 0, h);
  g.addColorStop(0, 'hsl(255 45% 55%)'); g.addColorStop(1, 'hsl(230 40% 22%)');
  bctx.fillStyle = g; bctx.fillRect(0, 0, w, h);
  bctx.beginPath(); bctx.arc(w * 0.62, h * 0.2, Math.min(w, h) * 0.07, 0, Math.PI * 2); bctx.fillStyle = 'hsl(50 85% 78%)'; bctx.fill();
  for (let l = 0; l < 3; l++) {
    bctx.beginPath(); bctx.moveTo(0, h);
    for (let i = 0; i <= 10; i++) { const x = w * i / 10; const y = h * (0.55 + l * 0.12) - r() * h * 0.1; bctx.lineTo(x, y); }
    bctx.lineTo(w, h); bctx.closePath();
    bctx.fillStyle = 'hsl(255 30% ' + (16 + l * 10) + '%)'; bctx.fill();
  }
}
function rebuild() {
  const dpr = Math.min(devicePixelRatio || 1, 2);
  cv.width = innerWidth * dpr; cv.height = innerHeight * dpr; ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
  big.width = innerWidth; big.height = innerHeight;
  paintFull(innerWidth, innerHeight);
}
addEventListener('resize', rebuild); rebuild();

const core = { x: 0.32, y: 0.3, w: 0.36, h: 0.4 };
function rectAt(g) {
  const cx = core.x + core.w / 2, cy = core.y + core.h / 2;
  const cw = core.w + (1 - core.w) * g, ch = core.h + (1 - core.h) * g;
  return { x: cx - cw / 2, y: cy - ch / 2, w: cw, h: ch };
}
function render(g) {
  const w = innerWidth, h = innerHeight;
  ctx.clearRect(0, 0, w, h);
  ctx.fillStyle = 'rgba(255,255,255,0.05)';
  for (let y = 0; y < h; y += 14) for (let x = 0; x < w; x += 14) ctx.fillRect(x, y, 2, 2);
  const r = rectAt(g);
  const rx = r.x * w, ry = r.y * h, rw = r.w * w, rh = r.h * h;
  ctx.save(); ctx.beginPath(); ctx.rect(rx, ry, rw, rh); ctx.clip();
  ctx.drawImage(big, 0, 0, w, h);
  ctx.restore();
  ctx.setLineDash([6, 4]); ctx.strokeStyle = 'rgba(255,255,255,0.75)'; ctx.lineWidth = 1.5;
  ctx.strokeRect(rx, ry, rw, rh); ctx.setLineDash([]);
  const cr = { x: core.x * w, y: core.y * h, w: core.w * w, h: core.h * h };
  ctx.strokeStyle = '#ffffff'; ctx.lineWidth = 2; ctx.strokeRect(cr.x, cr.y, cr.w, cr.h);
}
let g = 0, dir = 1;
function loop() {
  render(g);
  g += dir * 0.012;
  if (g >= 1) { g = 1; dir = 0; setTimeout(() => { dir = -1; }, 1000); }
  if (g <= 0 && dir === -1) { g = 0; dir = 0; setTimeout(() => { dir = 1; }, 800); }
  setTimeout(loop, 30);
}
loop();

You can think of this as inpainting turned inside out. The newly added canvas area becomes the mask, and edge pixels near the original are used as conditioning so the new content continues its perspective, lighting, and texture. The original interior itself is left untouched, exactly as in inpainting.

Extending a little at a time and treating the result as the new "original" for the next extension lets a scene grow well past its original frame — common for building panoramas or widening a portrait photo into a landscape background.

The farther new content gets from the original, though, the more its consistency with that original anchor tends to drift. Stitching many tiles together means style can drift a little each time; overall uniformity is not guaranteed.

The demo below is a simulation, not real generation — it pre-renders one large scene once and simply reveals more of it outward from the fixed central "original" region, which is why that central region stays pixel-for-pixel unchanged throughout.

When to use

Use it to widen an existing composition or change its aspect ratio. Extending very far from the original is worth checking visually for style drift as you go.