Skip to content

Commit 1dcfbcd

Browse files
tinchox5claude
andcommitted
fix(export): flatten jpeg/webp background by resolved format, not export name
normalizeExportOptions only injected the white background when the *export name* was 'jpeg'/'jpg'. But result.toBlob({type:'jpeg'}), result.toCanvas({format:'jpeg'}) and result.download({format:'jpeg'}) pass the image format in opts.format/opts.type while the export name is 'blob'/'canvas'/ 'download'. On a capture with no format (backgroundColor null) the transparent area was then encoded as black by JPEG. Resolve the real lossy format from the merged options and flatten white for jpeg/webp, matching createContext. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 7ee7958 commit 1dcfbcd

2 files changed

Lines changed: 54 additions & 2 deletions

File tree

__tests__/snapdom.backgroundColor.test.js

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,3 +40,47 @@ describe('snapdom.toJpg backgroundColor option', () => {
4040
expect(pixel[2]).toBeLessThan(30) // blue
4141
})
4242
})
43+
44+
describe('lossy format flattening at export time (no format on capture)', () => {
45+
let container
46+
47+
beforeEach(() => {
48+
container = document.createElement('div')
49+
container.style.width = '100px'
50+
container.style.height = '100px'
51+
container.style.background = 'transparent'
52+
document.body.appendChild(container)
53+
})
54+
55+
// The capture has no `format` (defaults to png, backgroundColor null). The lossy
56+
// format is requested only at export time via the result helper. White must still
57+
// be flattened in, otherwise JPEG encodes the transparent area as black.
58+
it('result.toBlob({ type: "jpeg" }) flattens white instead of black', async () => {
59+
const result = await snapdom(container)
60+
const blob = await result.toBlob({ type: 'jpeg' })
61+
expect(blob.type).toBe('image/jpeg')
62+
const img = new Image()
63+
img.src = URL.createObjectURL(blob)
64+
await img.decode()
65+
const canvas = document.createElement('canvas')
66+
canvas.width = img.naturalWidth
67+
canvas.height = img.naturalHeight
68+
const ctx = canvas.getContext('2d')
69+
ctx.drawImage(img, 0, 0)
70+
const pixel = ctx.getImageData(0, 0, 1, 1).data
71+
expect(pixel[0]).toBeGreaterThan(240)
72+
expect(pixel[1]).toBeGreaterThan(240)
73+
expect(pixel[2]).toBeGreaterThan(240)
74+
})
75+
76+
it('result.toCanvas({ format: "jpeg" }) flattens white instead of leaving transparent', async () => {
77+
const result = await snapdom(container)
78+
const canvas = await result.toCanvas({ format: 'jpeg' })
79+
const ctx = canvas.getContext('2d')
80+
const pixel = ctx.getImageData(0, 0, 1, 1).data
81+
expect(pixel[3]).toBe(255) // opaque
82+
expect(pixel[0]).toBeGreaterThan(240) // white
83+
expect(pixel[1]).toBeGreaterThan(240)
84+
expect(pixel[2]).toBeGreaterThan(240)
85+
})
86+
})

src/api/snapdom.js

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -157,10 +157,18 @@ snapdom.capture = async (el, context, _token) => {
157157
exportsMap.jpg = (ctx, opts) => exportsMap.jpeg(ctx, opts)
158158
}
159159

160-
// —— Normalizador para opciones por tipo (p.ej. JPEG: fondo blanco) ——
160+
// —— Normalizador para opciones por tipo (p.ej. JPEG/WebP: fondo blanco) ——
161161
function normalizeExportOptions(type, opts) {
162162
const next = { ...context, ...(opts || {}) }
163-
if (type === 'jpeg' || type === 'jpg') {
163+
// `type` aquí es el NOMBRE del export ('blob'/'canvas'/'download'/'jpeg'/…), no el formato
164+
// de imagen: en toBlob/toCanvas/download el formato viaja en opts.format/opts.type. Resolver
165+
// el formato real (jpg→jpeg) para aplanar el fondo igual que createContext (context.js:84),
166+
// o JPEG codificaría las zonas transparentes en negro.
167+
const lossy = (s) => s === 'jpeg' || s === 'jpg' || s === 'webp'
168+
const fmt = [type, next.format, next.type]
169+
.map(v => (typeof v === 'string' ? v.toLowerCase() : ''))
170+
.find(lossy)
171+
if (fmt) {
164172
const noBg = next.backgroundColor == null || next.backgroundColor === 'transparent'
165173
if (noBg) next.backgroundColor = '#ffffff'
166174
}

0 commit comments

Comments
 (0)