background: 'transparent' returns an alpha channel, so it needs output_format: 'png' or 'webp'. In the clear run, 76% of the icon's pixels came back fully transparent, with anti-aliased edges that sit cleanly on any background. opaque forces a filled background and auto lets the model choose (the JPEG run got white). Transparency is in preview on gpt-image-2 86 . Each response echoes the size, quality, background and output_format actually used: log those, not what you asked for.
<!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>
<p>output_format: <label><input type="radio" name="f" value="png" checked> png</label>
<label><input type="radio" name="f" value="webp"> webp</label> <label><input type="radio" name="f" value="jpeg"> jpeg</label>
<span id="out"></span></p>
<canvas id="c" width="600" height="220"></canvas>
<script>
// The "returned" icon: an open book with a small bird, drawn with transparent surroundings
const S = 180, icon = document.createElement('canvas');
icon.width = icon.height = S;
const g = icon.getContext('2d');
g.fillStyle = '#1f5f8b';
g.beginPath(); g.moveTo(20, 120); g.quadraticCurveTo(55, 100, 90, 120); g.lineTo(90, 150);
g.quadraticCurveTo(55, 130, 20, 150); g.closePath(); g.fill();
g.beginPath(); g.moveTo(160, 120); g.quadraticCurveTo(125, 100, 90, 120); g.lineTo(90, 150);
g.quadraticCurveTo(125, 130, 160, 150); g.closePath(); g.fill();
g.fillStyle = '#e09a10'; g.beginPath(); g.ellipse(95, 88, 26, 20, 0, 0, 7); g.fill(); // bird
g.beginPath(); g.arc(115, 70, 12, 0, 7); g.fill();
g.fillStyle = '#263238'; g.beginPath(); g.arc(119, 67, 2.5, 0, 7); g.fill();
g.fillStyle = '#b5452f'; g.beginPath(); g.moveTo(126, 70); g.lineTo(136, 73); g.lineTo(126, 76); g.fill();
// Count fully transparent pixels, as the chapter did for the clear run
const alpha = g.getImageData(0, 0, S, S).data;
let clear = 0;
for (let i = 3; i < alpha.length; i += 4) if (alpha[i] === 0) clear++;
const ctx = document.getElementById('c').getContext('2d');
function checker(x, y) {
for (let r = 0; r < S / 12; r++) for (let q = 0; q < S / 12; q++) {
ctx.fillStyle = (r + q) % 2 ? '#ddd' : '#fff'; ctx.fillRect(x + q * 12, y + r * 12, 12, 12);
}
}
function draw(format) {
ctx.clearRect(0, 0, 600, 220);
const backs = [['checkerboard', null], ['cream #fff3e0', '#fff3e0'], ['ink #263238', '#263238']];
backs.forEach(([name, color], k) => {
const x = 10 + k * 196, y = 10;
if (color) { ctx.fillStyle = color; ctx.fillRect(x, y, S, S); } else checker(x, y);
// JPEG has no alpha channel: the transparent area arrives filled (white, in the chapter's run)
if (format === 'jpeg') { ctx.fillStyle = '#ffffff'; ctx.fillRect(x, y, S, S); }
ctx.drawImage(icon, x, y);
ctx.fillStyle = '#263238'; ctx.fillText(name, x, 208);
});
document.getElementById('out').textContent = format === 'jpeg'
? '→ JPEG cannot carry alpha: request background "opaque" (or auto) with it.'
: `→ ${Math.round(clear / (S * S) * 100)}% of the pixels are fully transparent; edges are anti-aliased.`;
}
document.querySelectorAll('input[name=f]').forEach(r => r.onchange = () => draw(r.value));
draw('png');
</script>