Multiple References

Composing from Multiple Reference Images

image also takes an array of up to 16 images, and the prompt can name them by position. compose.mjs is mask-edit.mjs without the mask and with image: [await png('window-edited.png'), await png('booknest-sign.png')] and the prompt "Hang the round sign from the second image on a bracket above the shop window in the first image. Keep the first image's style, colors and layout." Its usage reported 2,048 image input tokens, 57 text tokens and 196 output tokens: $0.0225. Sunburst kept the window, cart and covers and hung the sign, bird and lettering intact, on an iron bracket (the right-hand image in Multi-Turn Editing's figure). A mask, if you add one, applies to the first image only.

Two reference images named by position in the prompt, composed into one scene, with the tokens each input costsHTMLLive
<!doctype html>
<style>
  body { margin: 0; padding: 8px; background: #fafaf7; font: 12px system-ui, sans-serif; color: #263238; }
  canvas { display: block; max-width: 100%; }
</style>
<canvas id="c" width="600" height="300"></canvas>
<p><button id="play">Compose again</button> "Hang the round sign from the second image on a bracket above the shop window in the first image."</p>
<script>
  const ctx = document.getElementById('c').getContext('2d');
  function windowScene(g, x, y, s) {
    const sky = g.createLinearGradient(0, y, 0, y + s);
    sky.addColorStop(0, '#1f3f66');  sky.addColorStop(1, '#e09a10');
    g.fillStyle = sky;  g.fillRect(x, y, s, s);
    g.fillStyle = '#5b3f99';  g.fillRect(x + s * 0.12, y + s * 0.28, s * 0.76, s * 0.55);
    g.fillStyle = '#fff3e0';  g.fillRect(x + s * 0.2, y + s * 0.38, s * 0.6, s * 0.32);
    ['#1f5f8b', '#5b3f99', '#e09a10', '#3f7d3a', '#b5452f', '#2a9d8f'].forEach((c, i) => { g.fillStyle = c;  g.fillRect(x + s * (0.24 + i * 0.09), y + s * 0.48, s * 0.06, s * 0.18); });
    g.fillStyle = '#6b4f3a';  g.fillRect(x, y + s * 0.83, s, s * 0.17);
    g.fillStyle = '#8d6e63';  g.fillRect(x + s * 0.03, y + s * 0.75, s * 0.24, s * 0.1);        // the cart from the mask edit
  }
  function sign(g, cx, cy, r) {
    g.fillStyle = '#fff3e0';  g.beginPath();  g.arc(cx, cy, r, 0, 7);  g.fill();
    g.strokeStyle = '#1f5f8b';  g.lineWidth = r * 0.08;  g.stroke();
    g.fillStyle = '#1f5f8b';                                          // an open book forming a nest
    g.beginPath();  g.moveTo(cx - r * 0.55, cy + r * 0.1);  g.quadraticCurveTo(cx, cy + r * 0.55, cx + r * 0.55, cy + r * 0.1);
    g.quadraticCurveTo(cx, cy + r * 0.3, cx - r * 0.55, cy + r * 0.1);  g.fill();
    g.beginPath();  g.arc(cx + r * 0.05, cy - r * 0.12, r * 0.18, 0, 7);  g.fill();          // the bird
    g.font = `bold ${r * 0.24}px Georgia`;  g.textAlign = 'center';  g.fillText('BookNest', cx, cy - r * 0.45);
    g.textAlign = 'start';
  }
  let t = 0, raf;
  function draw() {
    ctx.clearRect(0, 0, 600, 300);
    ctx.font = '12px system-ui';  ctx.fillStyle = '#263238';
    windowScene(ctx, 10, 20, 140);  ctx.fillText('image[0]: window-edited.png', 10, 176);
    ctx.fillStyle = '#eee';  ctx.fillRect(10, 190, 140, 140);
    sign(ctx, 80, 250, 36);  ctx.font = '12px system-ui';  ctx.fillStyle = '#263238';  ctx.fillText('image[1]: booknest-sign.png', 10, 296);
    // Inputs flowing into the model
    const k = Math.min(1, t / 50);
    ctx.fillStyle = '#5b3f99';  ctx.beginPath();  ctx.roundRect(200, 110, 110, 70, 10);  ctx.fill();
    ctx.font = '12px system-ui';  ctx.fillStyle = '#fff';  ctx.fillText('gpt-image-2.5', 216, 140);  ctx.fillText('-sunburst', 216, 156);
    ctx.strokeStyle = '#8d6e63';  ctx.lineWidth = 2;
    ctx.beginPath();  ctx.moveTo(152, 90);  ctx.lineTo(152 + 46 * k, 90 + 40 * k);  ctx.moveTo(152, 250);  ctx.lineTo(152 + 46 * k, 250 - 80 * k);  ctx.stroke();
    // The composed result appears after the model "works"
    if (t > 60) {
      const a = Math.min(1, (t - 60) / 30);
      ctx.globalAlpha = a;
      windowScene(ctx, 350, 40, 200);
      ctx.strokeStyle = '#263238';  ctx.lineWidth = 3;                  // an iron bracket
      ctx.beginPath();  ctx.moveTo(380, 70);  ctx.lineTo(450, 70);  ctx.moveTo(430, 70);  ctx.lineTo(430, 76);  ctx.stroke();
      sign(ctx, 430, 100, 24);
      ctx.globalAlpha = 1;
      ctx.font = '12px system-ui';  ctx.fillStyle = '#263238';
      ctx.fillText('usage: 2,048 image + 57 text in, 196 out = $0.0225', 318, 262);
      ctx.fillText('(each 1024² input image costs 1,024 tokens)', 318, 280);
    }
    if (t < 95) { t++;  raf = requestAnimationFrame(draw); }
  }
  document.getElementById('play').onclick = () => { cancelAnimationFrame(raf);  t = 0;  draw(); };
  draw();
</script>