From f3d1f151da861bd8205cbabb49e5b7dd4f437a5d Mon Sep 17 00:00:00 2001 From: Chris Lorenzo Date: Tue, 2 Jun 2026 16:48:24 -0400 Subject: [PATCH 1/4] fix(webgl): premultiply straight bitmaps when device ignores createImageBitmap option Older Safari/WebKit accepts the createImageBitmap `premultiplyAlpha: 'premultiply'` option but silently ignores it, returning straight (non-premultiplied) alpha. The WebGL upload path assumed ImageBitmaps were always premultiplied (hardcoding UNPACK_PREMULTIPLY_ALPHA_WEBGL = false), so straight pixels reached the GPU and produced edge ghosting on transparent images. Fix: unify TextureData.premultiplyAlpha to mean "WebGL should premultiply this source on upload." When a device is known not to honor the option, create the bitmap straight ('none') and let WebGL premultiply during texImage2D instead. - Add a startup probe (detectPremultiplyAlphaHonored) that uploads a known semi-transparent pixel and reads it back via a framebuffer to verify the option is actually honored, not just accepted. - Add `premultiplyAlphaHonored` config: undefined -> true (assume honored, no probe), null -> run the probe, boolean -> force the value. - Carry the per-source GL-premultiply intent from each loader (main-thread bitmap, ImageWorker, SVG ImageData fallback) through getTextureSource to the GL upload, which now honors it uniformly for bitmaps. - Add premultiply-alpha example for visual regression. Co-Authored-By: Claude Opus 4.7 --- examples/tests/premultiply-alpha.ts | 115 ++++++++++++++++++++ src/core/CoreTextureManager.ts | 69 ++++++++++-- src/core/Stage.ts | 2 + src/core/lib/ImageWorker.ts | 36 +++++- src/core/lib/textureSvg.ts | 5 +- src/core/lib/validateImageBitmap.ts | 89 +++++++++++++++ src/core/renderers/webgl/WebGlCtxTexture.ts | 6 +- src/core/textures/ImageTexture.ts | 32 ++++-- src/main-api/Renderer.ts | 25 +++++ 9 files changed, 356 insertions(+), 23 deletions(-) create mode 100644 examples/tests/premultiply-alpha.ts diff --git a/examples/tests/premultiply-alpha.ts b/examples/tests/premultiply-alpha.ts new file mode 100644 index 0000000..68934c6 --- /dev/null +++ b/examples/tests/premultiply-alpha.ts @@ -0,0 +1,115 @@ +import type { INode, NodeLoadedEventHandler } from '@lightningjs/renderer'; +import type { ExampleSettings } from '../common/ExampleSettings.js'; +import rocko from '../assets/rocko.png'; + +/** + * Premultiply-alpha ghosting test. + * + * A transparent PNG with antialiased edges is rendered with + * `premultiplyAlpha: true` (correct) and `premultiplyAlpha: false` (the state + * older Safari/WebKit silently lands in because it ignores the + * createImageBitmap `premultiplyAlpha: 'premultiply'` option). + * + * Each is shown over a light and a dark background so the edge halos produced + * by straight (non-premultiplied) alpha are visible: the `false` column should + * show fringing/ghosting around the silhouette, the `true` column should have + * clean edges. + */ +export async function automation(settings: ExampleSettings) { + await test(settings); + await settings.snapshot(); +} + +export default async function test({ renderer, testRoot }: ExampleSettings) { + const PADDING = 20; + const CELL = 220; + + testRoot.color = 0x808080ff; // neutral grey so both halo colors show + + renderer.createTextNode({ + text: 'Premultiply Alpha — left column = premultiply:true (correct), right = false (Safari bug)', + fontFamily: 'Ubuntu', + fontSize: 30, + color: 0xffffffff, + x: PADDING, + y: PADDING, + parent: testRoot, + }); + + // Surface what the startup probe detected on this device, if reachable. + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const honored = (renderer as any).stage?.txManager?.imageBitmapSupported + ?.premultiplyHonored; + renderer.createTextNode({ + text: `Device probe — createImageBitmap premultiplyAlpha honored: ${String( + honored, + )}`, + fontFamily: 'Ubuntu', + fontSize: 26, + color: 0xffff00ff, + x: PADDING, + y: PADDING + 40, + parent: testRoot, + }); + + const sizeToTexture: NodeLoadedEventHandler = (target, payload) => { + const { w, h } = payload.dimensions; + target.w = w; + target.h = h; + }; + + const rows: { label: string; bg: number }[] = [ + { label: 'over white', bg: 0xffffffff }, + { label: 'over black', bg: 0x000000ff }, + ]; + + const cols: { label: string; premultiplyAlpha: boolean }[] = [ + { label: 'premultiply: true', premultiplyAlpha: true }, + { label: 'premultiply: false', premultiplyAlpha: false }, + ]; + + const gridTop = PADDING + 90; + + for (let r = 0; r < rows.length; r++) { + const row = rows[r]!; + const y = gridTop + r * (CELL + PADDING + 30); + + for (let c = 0; c < cols.length; c++) { + const col = cols[c]!; + const x = PADDING + c * (CELL + PADDING); + + // Contrasting background panel + renderer.createNode({ + x, + y, + w: CELL, + h: CELL, + color: row.bg, + parent: testRoot, + }); + + // Transparent image on top, with the mode under test + renderer + .createNode({ + x, + y, + parent: testRoot, + texture: renderer.createTexture('ImageTexture', { + src: rocko, + premultiplyAlpha: col.premultiplyAlpha, + }), + }) + .once('loaded', sizeToTexture); + + renderer.createTextNode({ + text: `${col.label} — ${row.label}`, + fontFamily: 'Ubuntu', + fontSize: 22, + color: 0xffffffff, + x, + y: y + CELL + 4, + parent: testRoot, + }); + } + } +} diff --git a/src/core/CoreTextureManager.ts b/src/core/CoreTextureManager.ts index ef70e88..512872d 100644 --- a/src/core/CoreTextureManager.ts +++ b/src/core/CoreTextureManager.ts @@ -10,6 +10,7 @@ import { EventEmitter } from '../common/EventEmitter.js'; import type { Stage } from './Stage.js'; import { validateCreateImageBitmap, + detectPremultiplyAlphaHonored, type CreateImageBitmapSupport, } from './lib/validateImageBitmap.js'; import type { Platform } from './platforms/Platform.js'; @@ -46,6 +47,9 @@ export interface TextureManagerDebugInfo { export interface TextureManagerSettings { numImageWorkers: number; createImageBitmapSupport: 'auto' | 'basic' | 'options' | 'full'; + // Override for whether createImageBitmap honors premultiplyAlpha:'premultiply'. + // null = auto-detect via probe; boolean = force the value and skip the probe. + premultiplyAlphaHonored: boolean | null; maxRetryCount: number; } @@ -255,6 +259,7 @@ export class CoreTextureManager extends EventEmitter { basic: false, options: false, full: false, + premultiplyHonored: null as boolean | null, }; hasWorker = !!self.Worker; @@ -281,8 +286,12 @@ export class CoreTextureManager extends EventEmitter { constructor(stage: Stage, settings: TextureManagerSettings) { super(); - const { numImageWorkers, createImageBitmapSupport, maxRetryCount } = - settings; + const { + numImageWorkers, + createImageBitmapSupport, + premultiplyAlphaHonored, + maxRetryCount, + } = settings; this.stage = stage; this.platform = stage.platform; @@ -292,7 +301,7 @@ export class CoreTextureManager extends EventEmitter { if (createImageBitmapSupport === 'auto') { validateCreateImageBitmap(this.platform) .then((result) => { - this.initialize(result); + this.resolvePremultiplyAndInit(result, premultiplyAlphaHonored); }) .catch(() => { console.warn( @@ -304,11 +313,15 @@ export class CoreTextureManager extends EventEmitter { this.emit('initialized'); }); } else { - this.initialize({ - basic: createImageBitmapSupport === 'basic', - options: createImageBitmapSupport === 'options', - full: createImageBitmapSupport === 'full', - }); + this.resolvePremultiplyAndInit( + { + basic: createImageBitmapSupport === 'basic', + options: createImageBitmapSupport === 'options', + full: createImageBitmapSupport === 'full', + premultiplyHonored: null, + }, + premultiplyAlphaHonored, + ); } this.registerTextureType('ImageTexture', ImageTexture); @@ -325,11 +338,51 @@ export class CoreTextureManager extends EventEmitter { this.txConstructors[textureType] = textureClass; } + /** + * Resolve `premultiplyHonored` on the support object, then initialize. + * + * - boolean override -> use it directly, skip the probe + * - null -> run the detection probe (only meaningful when the options/full + * API exists, since that's the only path that passes the premultiply option) + */ + private resolvePremultiplyAndInit( + support: CreateImageBitmapSupport, + premultiplyAlphaHonored: boolean | null, + ): void { + if (premultiplyAlphaHonored !== null) { + support.premultiplyHonored = premultiplyAlphaHonored; + this.initialize(support); + return; + } + + if (support.options === false && support.full === false) { + support.premultiplyHonored = null; + this.initialize(support); + return; + } + + detectPremultiplyAlphaHonored(this.platform) + .then((honored) => { + support.premultiplyHonored = honored; + this.initialize(support); + }) + .catch(() => { + support.premultiplyHonored = null; + this.initialize(support); + }); + } + private initialize(support: CreateImageBitmapSupport) { this.hasCreateImageBitmap = support.basic || support.options || support.full; this.imageBitmapSupported = support; + if (support.premultiplyHonored === false) { + console.warn( + '[Lightning] createImageBitmap premultiplyAlpha:"premultiply" is not honored on this device — images may show alpha ghosting. GL-side premultiply fallback recommended.', + ); + } + if (this.hasCreateImageBitmap === false) { console.warn( '[Lightning] createImageBitmap is not supported on this browser. ImageTexture will be slower.', diff --git a/src/core/Stage.ts b/src/core/Stage.ts index 7f495e1..3b2249b 100644 --- a/src/core/Stage.ts +++ b/src/core/Stage.ts @@ -179,6 +179,7 @@ export class Stage { renderEngine, fontEngines, createImageBitmapSupport, + premultiplyAlphaHonored, platform, maxRetryCount, } = options; @@ -206,6 +207,7 @@ export class Stage { this.txManager = new CoreTextureManager(this, { numImageWorkers, createImageBitmapSupport, + premultiplyAlphaHonored: premultiplyAlphaHonored ?? null, maxRetryCount, }); diff --git a/src/core/lib/ImageWorker.ts b/src/core/lib/ImageWorker.ts index de15edc..fc38764 100644 --- a/src/core/lib/ImageWorker.ts +++ b/src/core/lib/ImageWorker.ts @@ -43,12 +43,14 @@ function createImageWorker() { options: { supportsOptionsCreateImageBitmap: boolean; supportsFullCreateImageBitmap: boolean; + premultiplyAlphaHonored: boolean; }, ): Promise { return new Promise(function (resolve, reject) { var supportsOptionsCreateImageBitmap = options.supportsOptionsCreateImageBitmap; var supportsFullCreateImageBitmap = options.supportsFullCreateImageBitmap; + var premultiplyAlphaHonored = options.premultiplyAlphaHonored; var xhr = new XMLHttpRequest(); xhr.open('GET', src, true); xhr.responseType = 'blob'; @@ -71,6 +73,17 @@ function createImageWorker() { ? premultiplyAlpha : hasAlphaChannel(blob.type); + // When the device ignores the createImageBitmap premultiply option, + // create a straight ('none') bitmap and let WebGL premultiply on + // upload. `premultiplyAlpha` in the resolved value means "WebGL should + // premultiply this source on upload". + var useGlPremultiply = + withAlphaChannel === true && premultiplyAlphaHonored === false; + var bitmapMode: 'premultiply' | 'none' = + withAlphaChannel === true && useGlPremultiply === false + ? 'premultiply' + : 'none'; + // createImageBitmap with crop and options if ( supportsFullCreateImageBitmap === true && @@ -78,12 +91,12 @@ function createImageWorker() { height !== null ) { createImageBitmap(blob, x || 0, y || 0, width, height, { - premultiplyAlpha: withAlphaChannel ? 'premultiply' : 'none', + premultiplyAlpha: bitmapMode, colorSpaceConversion: 'none', imageOrientation: 'none', }) .then(function (data) { - resolve({ data: data, premultiplyAlpha: withAlphaChannel }); + resolve({ data: data, premultiplyAlpha: useGlPremultiply }); }) .catch(function (error) { reject(error); @@ -94,22 +107,23 @@ function createImageWorker() { supportsFullCreateImageBitmap === false ) { // Fallback for browsers that do not support createImageBitmap with options - // this is supported for Chrome v50 to v52/54 that doesn't support options + // this is supported for Chrome v50 to v52/54 that doesn't support options. + // The browser default premultiplies, so WebGL must not premultiply again. createImageBitmap(blob) .then(function (data) { - resolve({ data: data, premultiplyAlpha: withAlphaChannel }); + resolve({ data: data, premultiplyAlpha: false }); }) .catch(function (error) { reject(error); }); } else { createImageBitmap(blob, { - premultiplyAlpha: withAlphaChannel ? 'premultiply' : 'none', + premultiplyAlpha: bitmapMode, colorSpaceConversion: 'none', imageOrientation: 'none', }) .then(function (data) { - resolve({ data: data, premultiplyAlpha: withAlphaChannel }); + resolve({ data: data, premultiplyAlpha: useGlPremultiply }); }) .catch(function (error) { reject(error); @@ -139,10 +153,13 @@ function createImageWorker() { // these will be set to true if the browser supports the createImageBitmap options or full var supportsOptionsCreateImageBitmap = false; var supportsFullCreateImageBitmap = false; + // set to false when the device is known to ignore the premultiply option + var premultiplyAlphaHonored = true; getImage(src, premultiplyAlpha, x, y, width, height, { supportsOptionsCreateImageBitmap, supportsFullCreateImageBitmap, + premultiplyAlphaHonored, }) .then(function (data) { // @ts-ignore ts has wrong postMessage signature @@ -240,6 +257,13 @@ export class ImageWorkerManager { ); } + if (createImageBitmapSupport.premultiplyHonored === false) { + workerCode = workerCode.replace( + 'var premultiplyAlphaHonored = true;', + 'var premultiplyAlphaHonored = false;', + ); + } + workerCode = workerCode.replace('"use strict";', ''); const blob: Blob = new Blob([workerCode], { type: 'application/javascript', diff --git a/src/core/lib/textureSvg.ts b/src/core/lib/textureSvg.ts index 25d236d..ce2868a 100644 --- a/src/core/lib/textureSvg.ts +++ b/src/core/lib/textureSvg.ts @@ -91,8 +91,11 @@ export const loadSvg = async ( } } + // getImageData returns straight (un-premultiplied) pixels, so WebGL must + // premultiply this source on upload (unlike the ImageBitmap path above, + // where the canvas is already premultiplied by createImageBitmap's default). return { data: ctx.getImageData(0, 0, physW, physH), - premultiplyAlpha: false, + premultiplyAlpha: true, }; }; diff --git a/src/core/lib/validateImageBitmap.ts b/src/core/lib/validateImageBitmap.ts index 70f7d4f..8c4abcf 100644 --- a/src/core/lib/validateImageBitmap.ts +++ b/src/core/lib/validateImageBitmap.ts @@ -4,6 +4,11 @@ export interface CreateImageBitmapSupport { basic: boolean; // Supports createImageBitmap(image) options: boolean; // Supports createImageBitmap(image, options) full: boolean; // Supports createImageBitmap(image, sx, sy, sw, sh, options) + // Whether `premultiplyAlpha: 'premultiply'` is actually HONORED (not just + // accepted without throwing). null = could not determine. Older Safari/WebKit + // accepts the option but ignores it, returning straight alpha — the source of + // the edge-ghosting bug on those devices. + premultiplyHonored: boolean | null; } export async function validateCreateImageBitmap( @@ -47,6 +52,7 @@ export async function validateCreateImageBitmap( basic: false, options: false, full: false, + premultiplyHonored: null, }; // Test basic createImageBitmap support @@ -83,5 +89,88 @@ export async function validateCreateImageBitmap( /* ignore */ } + // premultiplyHonored is resolved separately by the caller (it may be a + // forced override or an explicit opt-in to the probe), so it is left as its + // default (null) here. return support; } + +/** + * Determine whether `createImageBitmap(..., { premultiplyAlpha: 'premultiply' })` + * is actually honored by this browser. + * + * Strategy: feed a known straight-alpha pixel (255, 0, 0, 128) through + * createImageBitmap with 'premultiply', upload it to a WebGL texture with + * GL-side premultiply DISABLED (so we observe the bitmap's own state), then + * read the raw texel back via a framebuffer. + * + * - honored -> red comes back premultiplied (~128) + * - ignored -> red comes back straight (~255) [older Safari/WebKit] + * + * @returns true if honored, false if ignored, null if it couldn't be measured + * (no WebGL, createImageBitmap from ImageData unsupported, framebuffer + * incomplete, etc.) — caller should treat null as "unknown". + */ +export async function detectPremultiplyAlphaHonored( + platform: Platform, +): Promise { + let bitmap: ImageBitmap; + try { + // Straight (un-premultiplied) RGBA. ImageData is straight-alpha by spec. + const imageData = new ImageData( + new Uint8ClampedArray([255, 0, 0, 128]), + 1, + 1, + ); + bitmap = await platform.createImageBitmap(imageData, { + premultiplyAlpha: 'premultiply', + colorSpaceConversion: 'none', + imageOrientation: 'none', + }); + } catch (e) { + return null; + } + + const canvas = platform.createCanvas(); + canvas.width = 1; + canvas.height = 1; + const gl = (canvas.getContext('webgl') || + canvas.getContext('experimental-webgl')) as WebGLRenderingContext | null; + if (gl === null) { + bitmap.close?.(); + return null; + } + + const tex = gl.createTexture(); + gl.bindTexture(gl.TEXTURE_2D, tex); + // Critical: do NOT let GL premultiply. We want to observe whatever state the + // bitmap itself is in. + gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, false); + gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, false); + gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, bitmap); + + const fb = gl.createFramebuffer(); + gl.bindFramebuffer(gl.FRAMEBUFFER, fb); + gl.framebufferTexture2D( + gl.FRAMEBUFFER, + gl.COLOR_ATTACHMENT0, + gl.TEXTURE_2D, + tex, + 0, + ); + + let result: boolean | null = null; + if (gl.checkFramebufferStatus(gl.FRAMEBUFFER) === gl.FRAMEBUFFER_COMPLETE) { + const px = new Uint8Array(4); + gl.readPixels(0, 0, 1, 1, gl.RGBA, gl.UNSIGNED_BYTE, px); + // Straight red reads ~255; premultiplied red reads ~128. Split at the + // midpoint to tolerate rounding/colorspace drift. + result = px[0]! < 192; + } + + gl.deleteFramebuffer(fb); + gl.deleteTexture(tex); + bitmap.close?.(); + + return result; +} diff --git a/src/core/renderers/webgl/WebGlCtxTexture.ts b/src/core/renderers/webgl/WebGlCtxTexture.ts index de85d18..0fcf37d 100644 --- a/src/core/renderers/webgl/WebGlCtxTexture.ts +++ b/src/core/renderers/webgl/WebGlCtxTexture.ts @@ -188,9 +188,13 @@ export class WebGlCtxTexture extends CoreContextTexture { w = tdata.width; h = tdata.height; glw.bindTexture(this._nativeCtxTexture); + // `premultiplyAlpha` carries the source's GL-upload intent: true when the + // source pixels are straight and WebGL must premultiply (e.g. a straight + // bitmap produced when the device ignores the createImageBitmap + // premultiply option), false when the source is already premultiplied. glw.pixelStorei( glw.UNPACK_PREMULTIPLY_ALPHA_WEBGL, - isImageBitmap ? false : !!textureData.premultiplyAlpha, + !!textureData.premultiplyAlpha, ); glw.texImage2D(0, format, format, glw.UNSIGNED_BYTE, tdata); diff --git a/src/core/textures/ImageTexture.ts b/src/core/textures/ImageTexture.ts index 44904ab..8890a03 100644 --- a/src/core/textures/ImageTexture.ts +++ b/src/core/textures/ImageTexture.ts @@ -192,6 +192,18 @@ export class ImageTexture extends Texture { const hasAlphaChannel = premultiplyAlpha ?? blob.type.includes('image/png'); const imageBitmapSupported = this.txManager.imageBitmapSupported; + // When the device does NOT honor the createImageBitmap premultiply option + // (e.g. older Safari), request a straight ('none') bitmap and let WebGL + // premultiply on upload instead. `premultiplyAlpha` in the returned + // TextureData means "WebGL should premultiply this source on upload". + const useGlPremultiply = + hasAlphaChannel === true && + imageBitmapSupported.premultiplyHonored === false; + const bitmapMode: 'premultiply' | 'none' = + hasAlphaChannel === true && useGlPremultiply === false + ? 'premultiply' + : 'none'; + if (imageBitmapSupported.full === true && sw !== null && sh !== null) { // createImageBitmap with crop const bitmap = await this.platform.createImageBitmap( @@ -201,28 +213,29 @@ export class ImageTexture extends Texture { sw, sh, { - premultiplyAlpha: hasAlphaChannel ? 'premultiply' : 'none', + premultiplyAlpha: bitmapMode, colorSpaceConversion: 'none', imageOrientation: 'none', }, ); - return { data: bitmap, premultiplyAlpha: hasAlphaChannel }; + return { data: bitmap, premultiplyAlpha: useGlPremultiply }; } else if (imageBitmapSupported.basic === true) { // basic createImageBitmap without options or crop - // this is supported for Chrome v50 to v52/54 that doesn't support options + // this is supported for Chrome v50 to v52/54 that doesn't support options. + // The browser default premultiplies, so WebGL must not premultiply again. return { data: await this.platform.createImageBitmap(blob), - premultiplyAlpha: hasAlphaChannel, + premultiplyAlpha: false, }; } // default createImageBitmap without crop but with options const bitmap = await this.platform.createImageBitmap(blob, { - premultiplyAlpha: hasAlphaChannel ? 'premultiply' : 'none', + premultiplyAlpha: bitmapMode, colorSpaceConversion: 'none', imageOrientation: 'none', }); - return { data: bitmap, premultiplyAlpha: hasAlphaChannel }; + return { data: bitmap, premultiplyAlpha: useGlPremultiply }; } async loadImage(src: string) { @@ -274,9 +287,14 @@ export class ImageTexture extends Texture { }; } + // The loader computes whether WebGL should premultiply this source on + // upload (it depends on the source type and on whether createImageBitmap + // honored the premultiply option). Preserve it; fall back to the prop only + // when the loader didn't decide. return { data: resp.data, - premultiplyAlpha: this.props.premultiplyAlpha ?? true, + premultiplyAlpha: + resp.premultiplyAlpha ?? this.props.premultiplyAlpha ?? true, }; } diff --git a/src/main-api/Renderer.ts b/src/main-api/Renderer.ts index 5c978de..b82413c 100644 --- a/src/main-api/Renderer.ts +++ b/src/main-api/Renderer.ts @@ -458,6 +458,24 @@ export type RendererMainSettings = RendererRuntimeSettings & { */ createImageBitmapSupport: 'auto' | 'basic' | 'options' | 'full'; + /** + * Override for whether `createImageBitmap(..., { premultiplyAlpha: 'premultiply' })` + * is actually honored by the target device. + * + * @remarks + * Some older browsers (notably older Safari/WebKit) accept the + * `premultiplyAlpha: 'premultiply'` option without throwing but silently + * ignore it, returning straight (non-premultiplied) alpha. This causes edge + * "ghosting" on images with transparency. + * + * Leave `undefined`/`null` to auto-detect via a startup probe. Set to a + * boolean to skip the probe entirely and force the value — useful on known + * fleet devices where the probe is unnecessary overhead or unreliable. + * + * @defaultValue `null` (auto-detect) + */ + premultiplyAlphaHonored?: boolean | null; + /** * Provide an alternative platform abstraction layer * @@ -586,6 +604,12 @@ export class RendererMain extends EventEmitter { textureProcessingTimeLimit: settings.textureProcessingTimeLimit || 10, canvas: settings.canvas, createImageBitmapSupport: settings.createImageBitmapSupport || 'full', + // undefined -> true (assume honored, no probe); explicit null -> run the + // probe; explicit boolean -> force the value. + premultiplyAlphaHonored: + settings.premultiplyAlphaHonored === undefined + ? true + : settings.premultiplyAlphaHonored, platform: settings.platform || null, maxRetryCount: settings.maxRetryCount ?? 5, }; @@ -646,6 +670,7 @@ export class RendererMain extends EventEmitter { targetFPS: settings.targetFPS!, textureProcessingTimeLimit: settings.textureProcessingTimeLimit!, createImageBitmapSupport: settings.createImageBitmapSupport!, + premultiplyAlphaHonored: settings.premultiplyAlphaHonored ?? null, platform, maxRetryCount: settings.maxRetryCount ?? 5, }); From 1220c06187b55105939f7b465964eaabf29d6a48 Mon Sep 17 00:00:00 2001 From: Chris Lorenzo Date: Tue, 2 Jun 2026 21:01:28 -0400 Subject: [PATCH 2/4] fix up test for premultiply --- examples/tests/premultiply-alpha.ts | 8 ++++---- .../chromium-ci/premultiply-alpha-1.png | Bin 0 -> 74405 bytes 2 files changed, 4 insertions(+), 4 deletions(-) create mode 100644 visual-regression/certified-snapshots/chromium-ci/premultiply-alpha-1.png diff --git a/examples/tests/premultiply-alpha.ts b/examples/tests/premultiply-alpha.ts index 68934c6..b0249aa 100644 --- a/examples/tests/premultiply-alpha.ts +++ b/examples/tests/premultiply-alpha.ts @@ -21,13 +21,13 @@ export async function automation(settings: ExampleSettings) { } export default async function test({ renderer, testRoot }: ExampleSettings) { - const PADDING = 20; + const PADDING = 80; const CELL = 220; testRoot.color = 0x808080ff; // neutral grey so both halo colors show renderer.createTextNode({ - text: 'Premultiply Alpha — left column = premultiply:true (correct), right = false (Safari bug)', + text: 'Premultiply Alpha: left column = premultiply:true (correct), right = false (Safari bug)', fontFamily: 'Ubuntu', fontSize: 30, color: 0xffffffff, @@ -41,7 +41,7 @@ export default async function test({ renderer, testRoot }: ExampleSettings) { const honored = (renderer as any).stage?.txManager?.imageBitmapSupported ?.premultiplyHonored; renderer.createTextNode({ - text: `Device probe — createImageBitmap premultiplyAlpha honored: ${String( + text: `Device probe: createImageBitmap premultiplyAlpha honored: ${String( honored, )}`, fontFamily: 'Ubuntu', @@ -102,7 +102,7 @@ export default async function test({ renderer, testRoot }: ExampleSettings) { .once('loaded', sizeToTexture); renderer.createTextNode({ - text: `${col.label} — ${row.label}`, + text: `${col.label}, ${row.label}`, fontFamily: 'Ubuntu', fontSize: 22, color: 0xffffffff, diff --git a/visual-regression/certified-snapshots/chromium-ci/premultiply-alpha-1.png b/visual-regression/certified-snapshots/chromium-ci/premultiply-alpha-1.png new file mode 100644 index 0000000000000000000000000000000000000000..f539d4fb6d244f0c0144caeab16f427af7064a7c GIT binary patch literal 74405 zcmeFZZ=G;Gwb7~^;t zs2@^HAJEazNYUQQNq_dpK3K*Lpr6SRx-rno0dsQ3I*sbdC}9MW;%{VkX8aA95~=8h z9v@!XI@Cbmzuh7GHOR$2971ZsjBIr&4CWN)TpDX9)g16Icp0>yarX)BdqI;T3CZDZ z6?a$nP=lGv>Gq&?@0Hu!=PdsnA}jB($o@Ojag5M7{`;&TrUOky?!SX35IrN}zvC4K zCjb8oG{?d*`ky}@!XxbXpK&Q<)B%zIjeEh$k4FAKCsg?VPxxqK&;y9GT zrKhK7g4VozM8>L+9a`z~I7-99vPV^cMiOzl-oN59^>Kf;Qgk;VnhHRSh2hY48n#m( z&gk{CrNwiu3TV>t#TPNN^W|aN|G{zv6r=+EI`bv8^2W^?b^ZvQvo0slLrR}RE8d%e&f3? zUo$Bq<$AbUw=!+E{$yD`Uav(1a*-LP75v1<92Sr5S#5%L0t$6KMVq;?~N$<4KhE2fXhCm?1axC}k%*ioMis31HwEK#$FhctC zN1Y1YKu{c$B-CL=!qH9q>TmGX^+EkuV?#s4Ihl#rCHG^HMvIp=k3dq?I_Zp$m1dm=FR{Oz9dwWTV!bP&Z@RjA!uktFH z8(_V~pC7W}pS9!}Df3>ZXq@uS&dwtD%BzPI`M?Kt-vupABnkZbFNL5Lr-w+m_c3B~ zxyGL5o@L(DU(UvcTSf@3mz~ztC8Outo7DR@LJecFU#&68xh{R{HHcXi}zK5T@Y$mh;7;o}6u8tsOMhD2`PGoni|N;DaD z205EE<|fse_M7w_ErZ>=!-&qv4$Mgh2?Kl|EB;tuT;!q_XJ1unYh7KPX@8uSmR4jv zHu$6_&c6=b<8-a}MK+7Do10sdGybeziU3)FCesq?WEFb#H4L|ZgGn+oGkbPr1f`^8 zu$!5t084(iht%Z{L>*VNvOh(gHX@c2KIv2By>8(45iL}W7XNT{UIjkNr;rqeA$ur`3Bo(*VLHTppmfb;o!iY zm8AgsHk~-|0a5L5_ZWA-FTCtB)fGx`y8I>|7n7u6+O%(!Ll0dR7rQ+__uE6NESX== zcSPQfPpFxb8*v!Z=bCSYsbys50JQYGZL z!jUf1KGMj3>&P+*Xl(VrTW2~9&Z`}xU77RsY0A!~zdNWN5TPbUD00g)XQGmq)u(ql zW9WSnw@q-b{UPq^9^rC^fZ9@;P>kxiXc>Wh~p&ks1`W9 zOtR!0DrB!!1FBOTNJ_Zukh&Vq(O^NWdEcg?ud=Pkn^*TrPe5 z{Gi#&8O#xXex~Ke%pN9q2p=F6TNOAt?5KTSSa89yyf@Xux^{}=-n2?wrR2gJ4X&Q6 z5Wx8<@K-~D;E%fbI|&Q^CKf{=4H5=1kCS|3>SgHTKqBkJPccRkI8(9@YV+N4sSNB) z6fWBfi}+hHQcGP_0ElpYGc*`BD70~=5#Vb&X&c?0z7@X?{fqEGjyB z^Fky?HAe75ofF!^TlurQK1^KV`EFBbBc+LD+aB7eK=1Df4G3Z)oIJjyXhVsJu+RGX zFH0!|nT{9HA2fBW~rLo6VouO{U zsEehW#oq;?km--53&*bYJhx~_p6;KhDpAYhv-D6_Cdkb8mBH$>+Zy@qfgjD7s^Gm` zYkc~{6AJ^qrMQdV2}{*Mlm}7Qw@}}k>wOjZBu~IpVFmMB(>jlEm$fo|tntBoE%npn zm5B-UtQ4gE3u)6T6Y0WE7od=wyYk(L>vD-P^BNfs*NAh)2kNn8KgwCX5lg~&SIR%n zHnne0oDywUGhkLa8Q&^`A=#|K&*&XYm_D)Ua5uoFHT?zrH*|i50JM29Djc22jFM*% z2_e(b-o`Y>Y_&hSMM979eB6zE`#%c{3ztHRj+NgKy;(ntW=xJeRbe=iz@FlJWW&xGY-W{T)@DIAkm$di+)|8l-!(cOuQHXGA=xc*)3`|(ID?I2D^ zTDmh<7#?w~dRx{TO(l7~k$m+v-QVwiAEf4OdGTsuQ0lS6&+PhaQ%&qN#J6qXkMK$i zx*uwn!7%@NU*7CjgCJh60Cei*BbY-w@WI|aqsD<++~KzJ2zqPp6JG*3Ic!=nZ1?kR zI*#-k3&JKa^xRCHW_xG1*sxG<`M9g*|5Q9f(305C9@a<%VaGDMTkuBft7t~$zDY(o z8Sm3xs;MEedGhv-$}6j3X?L;LdOGn=X$v0-H*Hb zFgklB2%C@sW;EKl=EYxBPUtJT@0Pp^zQ;ql7}3N1AAJ{$T64N1xL-BR$LE_DG??@w zsyoij*olyB2r3@1XO$}?S-KOiS6g3qNfsdmNCtU zSO*{0L#_FMe8-`6{wbqP$bJ>LM#}#zIj;!0eB8WFd@+6cJZP#^0>PCiX4R|uF+&IW zD|Rfk@bu8Z9|l#*QnH^qc^ialF`6l4qfz>q5y!SkD$y231@fuAw_IFI!6^*MvrZ1K z?EgJym?hF(dsSnqPVB$zI#+D3o)|mPaFF9Tzhq2S3WWjuuazBfV8#>sw455VdTdcl z_dAMb?yp^WKpVwSdcvitx2E={<>u|26}q(;h3UvM*ZtXI!xsd0Gxj_n5$7{ps9>?2{9#qHqYl> zVhoW*>uWPXaUWW?(N(k<2j4`o*jYa;Q4o{=ouf-jP1fu~lzb`vO8&T9~s%a^o=%j7fes^mk&ing95gur^&65U#Q9;I&GJ}g z?3xF;@VF)1o`}HmjI_9@ayyt0v?u4F6a=v)|JcF2SWc`y(FU~NImydoQwrbgE2r^v zvM3*B8m&k-m3lp_lKAi3-%pfEgXm{SV#F*cI2NENl&+QGGs5Q*F`-KO>;zAOp*_#| zOfLM%52|zW+YkJ%O70%ApPR;o<%NlFKb+`g^i6BNpIDqAlbh=Gkp=f25gr8ZAYTnw zb(eWFX&ApT>D@(g@w|Gi@3LR3`XlG>)I{my{jqFkPRPqL`=8k$V~_uw=Q8Y&KRO8*Z9r#XxY63%@}`}uBCctcc12Lp#0fD z5X>G-w9o3M`y7QsO}X-g)>s)mHxj3t?$7fcW7PWLQnp$LW8#p*ZvdO@=|hp|7eQz- zi~1{LVTW$nn#SFs7tzKwZ8gCprqMVtr)qT89wop2R|_DFW<12F`&yA@#XCRyqqih? zGU#2v>zUyUqir&ddRQqxxmT+yx|>GP;XucSD4IARuWst6W`&^Mif;NwfPVtMGFB|^ zJ^=c6BMkf2{NOs^NhiTX)Ob}5Ma_2zV%DJ6u{-Q1ELwfg3%XoYqb0}MQ_7lP>aX2I zfSpBPt5LI#nZ@JNGz4gFK+B#Tk#i!!n3U*4U!lmDna?${hoS3=>?R%JhnlzK5ma~S z#|yMB`nIEyk>z~c{)(pXG=a)PMO;aP zZJ6SW>iZGtY{_u<1ac^mc1#O6Hn{GRi8Y7s8=PiO&ae;YNxu0aWd`0QF&uL zouZgjO=6^KcFY}1e_L-zK5-;r0I}v*Ei9&&qT)N_ri>CO;)2)ehNfC6Sa=%|N2P$n z@G6Gag2%0)P!rxy$FS-w?*sskT)%pfKNreyXzg2OA77T1)0+#Yhp@y@TS&b8d`9;A z8BGYkLn78VNw;R5fDw+#uZfRxrPm|M)lS5PiqS5yQ8a#ut3*KV5Zu>Y0;YXMK;=RE zR;!nVN#k2!NWs`H@P5G0KZb4#Q-+{*0GXX=mP1}Ie`KGaj4N)t@N=B4=IM{gpyFiO zfJ3T+-}&`%jJyg4+Pea)5lm+R5rTvr1!Ma;%b;{cV1Z2>S8r8qm8?m9a9=!P)V}I0 zfNWv@gy$9SY=1)zKs+`KQvJdH_l?Z#&ZneQI(#1%B*JLoxvZPFD3dMaM(1}VtFFSl zl~K#QB{r;A7mKY3)E0kF;U;K2hl5l!GDvI{M)x+{Nc?H7IGDTV)|@Uh8uop`HH$;? zGUZ4YKJ`{P)UzC zicj;OjNk>*Zgc^gZ88Mu^;GBY5V|f6+_v+F8j{GEzsscC{I1K4>>-ED9Gwz8Oa^EN z6V1p)8%}oPsX*#HO3g!nwr$`9V~NW5pvFX`Ys{h4;(fBl7%)A&Z=icu^K$nBY6cDk z5ZvZJVRNe>+tK;*trOd~O~cF4QDE_nHuph54V~p4`d$(}_XxMNoLq2s3q^psgB+H8 z4i;$j>;hLtI>alK3%=jkAsX_BRE41BSz4~mnF%4 zc`i;`{r1<%P^{!3_Ml`wJV2WxQli7qZG?!2OYdb0$xtq-*W_BvpvBbTyQnj+qULEJ zvo2OfxW|Xw(U&*DDm8WaF9$0!7L&kTDpLJal}$Ei@C>$5!nzk;-Q=9A2fmi1`q&t3 z3Vn8<8(|VM8D*N`UTSnR!d8QBR69F=JTsd6DC~f^dzw@ z<2xs|pciQX{ojzHjfZM`*t4~QHU)*fVBMiBq;yyhG=uE@{7ewTi#p%MsHmtDecLy+ zG^niZ7jDSsYTV7Jp+ZIJwdk(s-|3`?c69rvc;h>^u8`N_GLJC{HqK7J_6IpR%bhRy z9cMOA&@sW;>K@V#W&5srlLOg4W8#-CzQ58PSUpo57FpNUabM?t{8^B{|J}<1_|nIc z6^*rX=&11S!;$rWR3wG5+LiwFTS8>;RkO)oL{lIG{E*D>XhF1+VVz5gV!(+2R5!a*L_Yfu)K3P>J z5>`p^h`NE=42_E~URtNID2!C_Dh$M* zG%sR&`G@OJM#Qe}p0yL{OI&(b)8W(ShL%_ed*A&b_K&T8O(Je+$x-4iIN3hjTW{Tq zlXLHNBeQs-L22%y8(_5S*Bm*8Os&BO`)fP#KjgMeAe0QYIh+o1Uv|BUbkI21PYf3x zb`yHXb*Q!6$bC%)bPq9JUPuVZaYTWKL`AvByKTvIFphSmeyDgUk}6>Y!U+sYH}u*? zF@PF>=?AWDH%UJC_l+IUW0&O0VgL##3|^aE7p<^3w6ECnJ#qCI zl4GoR$Le=L%WuvMGtE~F2r&GcIMvC?COFsVp?KOLqq&2B;z0l2iZ^y)duV1K7sIg^ zhiJg&Ce(}TLwP2XHO5aGTte>}T@w^1cVGB}qALaPVRs@pDf8Se_F%UwqbJ_oK29cF~t@HO45*1CQXqkc*3cYg&yRrH7mO`OmvtONv#(Iv zINTjM$ol+W!2~D%Tnw$yQqpFY=V0|{1uX_RarGhVt)s~?XJs)XWMN?;GX28ktvTQ% zHO3_>&SMYpd9qa-#X>rXoeoCRIiI-yX-aB~#p-L>w-Y@5=)YT>Uf`ZY8{jQ@^LaMm zHaExY;kZ~KGjDp>x9Fg(FDrjBY&!7QR$1=c%lKy5ZD{(>c*lhP-5=CRjVr5vJA-%j z{(Kk26=xK^Gkskn%vKp&%*X2K_q=P;nhp6d3D`y(L&=+(UX%4wwJtUAA38a1kSamu zZfdVdPGIDL8#hUdSTQ%-Ld^szFw=dJHgGHOD$p$`M--)*sfph(c#c8w(&%HMOkzBx z4e8`$wFFP3U9L`6t<#J4u=SrevfLD$v}m z(7?VBa`}C`=uLFROW1zO7jl%!G&l7_L06L>k^!8b9gGfP=5-hU@}+UI>N1gor9f@S zlQCeEXeTj6;&x%I;5Zamvy=Ed>Urz@T!9#osQZ!J4dmfw|I|0$5Wph6SR84_iN+M` zFpi1{K72PhI~az}e}t}v4g1QD~%JGX16(R{6V zzV_eO#`ESItjV8NjBR)KBV6w4m*(JhVhZirIDd5vJ>`8qn{+9b=32$#3yL_O$@FLV z+(nu=tq49djF7mW(E-Iu@ZtXeP9g3uSK?_Ado$&8l?LfZiZDev#dr|>11@=-F+cIn zYg22Jq)lts3xfQvrCTc_qYEAFsxOi6!mh(EdjQt1ww_mfBC;eBSI={M2^H&U-q=$2 z5v7v5M5hBleyEIUv?zeP(dj{rE_yIn9KTF7ZyD&vtUyg>8uJ*xkVRwx5QePb(eP53NKOQFCfJ&$B^!9^`C^L8zCS!qM$6R zT2tDvPs^KRLjCLjemgJ*4+Tkp2o-U6$kH!ruuScu$6jfR7@5U+{hoj!eRg-@eh zai@Y#{gDfny=WbMq{<+}Xlh<`XV$~XPu5wi4O+I*vN;2&D~K!i)yYn83(GR;~FGqk=^qhGa#M(H88O-k`sYu&d4IlWf%R~P?kIG0Du zx}mooLl=pxiZxp%7AC#@RT~cwJ~RbtJ3wSXr_J7m^0yOs%yJQRI{D$f_4fUeQn zeaR6QN0yf%*cEJ?E?V!^D!`0lfxg?GsMreD`{FvIwNo@ym{la$R*Mg%ny6Q-rj`B) zLm%go4(g?POw}>GE@ZmLi_hl_Cthh_dq?O+y$m<`%vN0#??Bu6^PIVmB`yjFW4$%aBA{Zt$fZ8(j$o=L8hNLf3VE7rvlz#21RQw_0T zVu(@|l)#S04BQ4iB0%zWnBgLHeUx?z*_!-<6HgD4FGzv>%w@vpN0b)5tnp$&n?kJb zB_ilrQse(u3sB`>)Y`LCR-I7>K-e56vlc@R3~D;UnfxMbBh?yUylJdR(E&PGgyFGZ z2$^hohs5)1P7ZqM0Yh1E)x^_TFr9rf`V%%+NPW2-5-@M=G|byHUrxdzVDb%MS;bOZ z=&Pzy$CRQ7P(HKQunM8#7{n#BJ}B2HmFDPN4Aip()iZ!`{%LjGRn07j0!6n-L?mZ! z2fTGUl>jy(0jDc5;9MrYtm`wLVjfOHIw{h2R^cB&Wb~-J=e4w35K1}frRbtUeu#3A z@#k9c9)%}QDAxl<+&*keno3YnQNa$Gs^#gZBZ7?tYEf~69=T&oV$pBjTd{su#d7Y5tw+R^&7%;v?v* zO=ph@Bhu*`%D|9gK6DAa6c^YT#BA_t$5ub?&Id;V2X@2@!7;R|du16w(EJpuHOboY zxNMi`FoEW}-Bg^LtdatwvUr*byd7&jfZ`>%{VeB{xNeH0n(AyS?}1|Fa_7B~(gmay zxqULRqgWuECnE9lJsz5ptlrVzTu$ zbd!O7AFG_7Iz&it!_kvDjsuOyUh_O3scpFJDe1C7Y4=1ge@CIp=n-FH{Up<@zc%mr zdYwvSKn8esvJsP2<9U5536`oR*Cp`3SmLHGL$wzIPnH=SBH1IjJ|12e{SvsWIp80x z>zpT_bhW*GC3!plNe$-SehYLlc-(J)AYgw!;uGV%7~8CR<7oA$TcRtXJ6o4OJw*VP z28<}e=h7FJY=?Los3sy1+;c&?f%6JOR89mOt$-7BQi|Rvnk%)3c*Ly zue;}fFquYHME|^-)>JEI=;KKzlfV|7l1S5)!R*td*779SnJ)FxzbJ(;qbN0pTh{7#vm0Bl#7Gkz5@{78&QmbwDtIVu%Tm+X zzt;U!^89?&8??;tf1;<`%Rj|$I;CN=w}ls~_nPq?eCh&fO^ zVPp;$5;$IqHSx2i&zNgn`PaS#z}8y;Elp^Pu#cS8s9*kFtcUR&uv}?Q_q2AkHww@$ zdLUb-<~=wxn6jdnF_Me?WE6F>OZ|StV5Am)JkO@{dU|9t4C5mOk?Y~&@1Io!EG)G2 zqp|qkKwjq%lHlr*TwxMy_-r za&-~ujd+s{?8Op6M;2;;UDIS`@0$2(5ocef!`FY_`+6GBDVn?of?%${KP1oE*B+G$ zW#uZ+MT0M2ew>R};FD3>@lt7Sd+(50m6Z>-Gy*2=XfYYoDKR<)RrVyYD^Fs4QyW%d z8w)(x^@+;uyK89b)&SUiSwx~QJe|Nef9F~nBZk`y`@VAN*f$zI?cTwMrq|S@SP>5s zIi2|P{Z{$-yzw&D67^8BPVF7I^7c{|pJDJXrb1HR?lLSJB)6g{z4d{TNU=_i3+7D! z;DNbQjSlzk_oh_aD4Ry@#~Lc}EkbDZ*xe0*oZNK$=%Z}@m;;IU1*zA@{8%h-`u6fz zvY2j+bi!JSW^d7^T0V+e#N~xH1O;9ykLP3_v6vs*P<0y=C%86G2U;-U`5tvu;U
Is&C;85knel)25vaO<0aYCIZEIs0 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 literal 0 HcmV?d00001 From f1e17610c076bf946d7cb794e9c34c85433ac98f Mon Sep 17 00:00:00 2001 From: Chris Lorenzo Date: Tue, 2 Jun 2026 21:17:38 -0400 Subject: [PATCH 3/4] refactor(webgl): simplify premultiplyAlphaHonored config + probe by default MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Collapse the three-state config (undefined=assume-honored, null=probe, boolean=force) into two states on a plain optional boolean: premultiplyAlphaHonored?: boolean // omitted = probe; set = force This removes the confusing undefined-vs-null distinction and the double-normalization that was spread across Renderer and Stage. The probe now runs by default (when the option is omitted) instead of being opt-in. It is cheap — one 1x1 texture upload + readback at startup — and fails safe: any error resolves to null, which leaves the original behavior unchanged. On honored devices it resolves true (no change); on ignored devices it enables the GL-side premultiply fallback. So the fix now self-heals affected devices out of the box rather than requiring deployers to know their fleet. Also adds the previously-missing unit test for detectPremultiplyAlphaHonored (honored/ignored/null branches + GL-premultiply-off assertion). Co-Authored-By: Claude Opus 4.8 (1M context) --- src/core/CoreTextureManager.ts | 10 +- src/core/Stage.ts | 2 +- src/core/lib/validateImageBitmap.test.ts | 119 +++++++++++++++++++++++ src/main-api/Renderer.ts | 20 ++-- 4 files changed, 133 insertions(+), 18 deletions(-) create mode 100644 src/core/lib/validateImageBitmap.test.ts diff --git a/src/core/CoreTextureManager.ts b/src/core/CoreTextureManager.ts index 512872d..fd1eac8 100644 --- a/src/core/CoreTextureManager.ts +++ b/src/core/CoreTextureManager.ts @@ -48,8 +48,8 @@ export interface TextureManagerSettings { numImageWorkers: number; createImageBitmapSupport: 'auto' | 'basic' | 'options' | 'full'; // Override for whether createImageBitmap honors premultiplyAlpha:'premultiply'. - // null = auto-detect via probe; boolean = force the value and skip the probe. - premultiplyAlphaHonored: boolean | null; + // undefined = auto-detect via probe; boolean = force the value and skip it. + premultiplyAlphaHonored: boolean | undefined; maxRetryCount: number; } @@ -342,14 +342,14 @@ export class CoreTextureManager extends EventEmitter { * Resolve `premultiplyHonored` on the support object, then initialize. * * - boolean override -> use it directly, skip the probe - * - null -> run the detection probe (only meaningful when the options/full + * - undefined -> run the detection probe (only meaningful when the options/full * API exists, since that's the only path that passes the premultiply option) */ private resolvePremultiplyAndInit( support: CreateImageBitmapSupport, - premultiplyAlphaHonored: boolean | null, + premultiplyAlphaHonored: boolean | undefined, ): void { - if (premultiplyAlphaHonored !== null) { + if (premultiplyAlphaHonored !== undefined) { support.premultiplyHonored = premultiplyAlphaHonored; this.initialize(support); return; diff --git a/src/core/Stage.ts b/src/core/Stage.ts index 3b2249b..01d70e6 100644 --- a/src/core/Stage.ts +++ b/src/core/Stage.ts @@ -207,7 +207,7 @@ export class Stage { this.txManager = new CoreTextureManager(this, { numImageWorkers, createImageBitmapSupport, - premultiplyAlphaHonored: premultiplyAlphaHonored ?? null, + premultiplyAlphaHonored, maxRetryCount, }); diff --git a/src/core/lib/validateImageBitmap.test.ts b/src/core/lib/validateImageBitmap.test.ts new file mode 100644 index 0000000..bee51ba --- /dev/null +++ b/src/core/lib/validateImageBitmap.test.ts @@ -0,0 +1,119 @@ +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; +import { detectPremultiplyAlphaHonored } from './validateImageBitmap.js'; +import type { Platform } from '../platforms/Platform.js'; + +/** + * Minimal stand-in for the WebGL constants/methods the probe touches. The probe + * uploads a known straight pixel, reads it back, and infers whether + * createImageBitmap premultiplied it. `readbackRed` is what readPixels returns + * for the red channel: ~128 = premultiplied (honored), ~255 = straight (ignored). + */ +function createFakeGl(readbackRed: number, framebufferComplete = true) { + return { + TEXTURE_2D: 0x0de1, + UNPACK_PREMULTIPLY_ALPHA_WEBGL: 0x9241, + UNPACK_FLIP_Y_WEBGL: 0x9240, + RGBA: 0x1908, + UNSIGNED_BYTE: 0x1401, + FRAMEBUFFER: 0x8d40, + COLOR_ATTACHMENT0: 0x8ce0, + FRAMEBUFFER_COMPLETE: 0x8cd5, + createTexture: vi.fn(() => ({})), + bindTexture: vi.fn(), + pixelStorei: vi.fn(), + texImage2D: vi.fn(), + createFramebuffer: vi.fn(() => ({})), + bindFramebuffer: vi.fn(), + framebufferTexture2D: vi.fn(), + checkFramebufferStatus: vi.fn(() => (framebufferComplete ? 0x8cd5 : 0)), + readPixels: vi.fn( + ( + _x: number, + _y: number, + _w: number, + _h: number, + _format: number, + _type: number, + px: Uint8Array, + ) => { + px[0] = readbackRed; + px[1] = 0; + px[2] = 0; + px[3] = 128; + }, + ), + deleteFramebuffer: vi.fn(), + deleteTexture: vi.fn(), + }; +} + +function createPlatform(gl: object | null): Platform { + const close = vi.fn(); + return { + createImageBitmap: vi.fn(() => Promise.resolve({ close })), + createCanvas: vi.fn(() => ({ + width: 0, + height: 0, + getContext: vi.fn((type: string) => (type === 'webgl' ? gl : null)), + })), + } as unknown as Platform; +} + +describe('detectPremultiplyAlphaHonored', () => { + beforeEach(() => { + // node test env has no ImageData global; the probe constructs one. + (globalThis as unknown as { ImageData: unknown }).ImageData = class { + data: Uint8ClampedArray; + width: number; + height: number; + constructor(data: Uint8ClampedArray, width: number, height: number) { + this.data = data; + this.width = width; + this.height = height; + } + }; + }); + + afterEach(() => { + delete (globalThis as unknown as { ImageData?: unknown }).ImageData; + }); + + it('returns true when the bitmap reads back premultiplied (~128)', async () => { + const platform = createPlatform(createFakeGl(128)); + expect(await detectPremultiplyAlphaHonored(platform)).toBe(true); + }); + + it('returns false when the bitmap reads back straight (~255)', async () => { + const platform = createPlatform(createFakeGl(255)); + expect(await detectPremultiplyAlphaHonored(platform)).toBe(false); + }); + + it('returns null when createImageBitmap throws', async () => { + const platform = { + createImageBitmap: vi.fn(() => Promise.reject(new Error('unsupported'))), + createCanvas: vi.fn(), + } as unknown as Platform; + expect(await detectPremultiplyAlphaHonored(platform)).toBe(null); + }); + + it('returns null when no WebGL context is available', async () => { + const platform = createPlatform(null); + expect(await detectPremultiplyAlphaHonored(platform)).toBe(null); + }); + + it('returns null when the framebuffer is incomplete', async () => { + const platform = createPlatform(createFakeGl(128, false)); + expect(await detectPremultiplyAlphaHonored(platform)).toBe(null); + }); + + it('disables GL-side premultiply on the probe upload', async () => { + const gl = createFakeGl(128); + await detectPremultiplyAlphaHonored(createPlatform(gl)); + // The probe must observe the bitmap's own alpha state, so GL premultiply + // has to be off during the readback upload. + expect(gl.pixelStorei).toHaveBeenCalledWith( + gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, + false, + ); + }); +}); diff --git a/src/main-api/Renderer.ts b/src/main-api/Renderer.ts index b82413c..c2ce4ce 100644 --- a/src/main-api/Renderer.ts +++ b/src/main-api/Renderer.ts @@ -468,13 +468,13 @@ export type RendererMainSettings = RendererRuntimeSettings & { * ignore it, returning straight (non-premultiplied) alpha. This causes edge * "ghosting" on images with transparency. * - * Leave `undefined`/`null` to auto-detect via a startup probe. Set to a - * boolean to skip the probe entirely and force the value — useful on known - * fleet devices where the probe is unnecessary overhead or unreliable. + * Leave unset to auto-detect via a cheap startup probe. Set to a boolean to + * skip the probe entirely and force the value — useful on known fleet devices + * where the probe is unnecessary overhead or unreliable. * - * @defaultValue `null` (auto-detect) + * @defaultValue `undefined` (auto-detect via probe) */ - premultiplyAlphaHonored?: boolean | null; + premultiplyAlphaHonored?: boolean; /** * Provide an alternative platform abstraction layer @@ -604,12 +604,8 @@ export class RendererMain extends EventEmitter { textureProcessingTimeLimit: settings.textureProcessingTimeLimit || 10, canvas: settings.canvas, createImageBitmapSupport: settings.createImageBitmapSupport || 'full', - // undefined -> true (assume honored, no probe); explicit null -> run the - // probe; explicit boolean -> force the value. - premultiplyAlphaHonored: - settings.premultiplyAlphaHonored === undefined - ? true - : settings.premultiplyAlphaHonored, + // undefined -> probe; explicit boolean -> force the value. + premultiplyAlphaHonored: settings.premultiplyAlphaHonored, platform: settings.platform || null, maxRetryCount: settings.maxRetryCount ?? 5, }; @@ -670,7 +666,7 @@ export class RendererMain extends EventEmitter { targetFPS: settings.targetFPS!, textureProcessingTimeLimit: settings.textureProcessingTimeLimit!, createImageBitmapSupport: settings.createImageBitmapSupport!, - premultiplyAlphaHonored: settings.premultiplyAlphaHonored ?? null, + premultiplyAlphaHonored: settings.premultiplyAlphaHonored, platform, maxRetryCount: settings.maxRetryCount ?? 5, }); From c2487af59a5432a9067cd80ed97ea02acc6e7d7a Mon Sep 17 00:00:00 2001 From: Chris Lorenzo Date: Tue, 2 Jun 2026 21:45:35 -0400 Subject: [PATCH 4/4] refactor(webgl): default premultiplyAlphaHonored to true, 'auto' to probe Change the probe from default-on to opt-in via an explicit 'auto' sentinel, restoring the conservative default (assume honored, no probe) so existing behavior is unchanged out of the box: premultiplyAlphaHonored?: boolean | 'auto' undefined -> true (default: assume honored, no probe) 'auto' -> run the startup probe boolean -> force the value 'auto' is self-documenting where the previous null-vs-undefined sentinel was not. Renderer normalizes undefined -> true; Stage coerces the same default so it is safe when constructed directly. Co-Authored-By: Claude Opus 4.8 (1M context) --- src/core/CoreTextureManager.ts | 10 +++++----- src/core/Stage.ts | 3 ++- src/main-api/Renderer.ts | 19 ++++++++++++------- 3 files changed, 19 insertions(+), 13 deletions(-) diff --git a/src/core/CoreTextureManager.ts b/src/core/CoreTextureManager.ts index fd1eac8..d508511 100644 --- a/src/core/CoreTextureManager.ts +++ b/src/core/CoreTextureManager.ts @@ -48,8 +48,8 @@ export interface TextureManagerSettings { numImageWorkers: number; createImageBitmapSupport: 'auto' | 'basic' | 'options' | 'full'; // Override for whether createImageBitmap honors premultiplyAlpha:'premultiply'. - // undefined = auto-detect via probe; boolean = force the value and skip it. - premultiplyAlphaHonored: boolean | undefined; + // 'auto' = detect via probe; boolean = force the value and skip the probe. + premultiplyAlphaHonored: boolean | 'auto'; maxRetryCount: number; } @@ -342,14 +342,14 @@ export class CoreTextureManager extends EventEmitter { * Resolve `premultiplyHonored` on the support object, then initialize. * * - boolean override -> use it directly, skip the probe - * - undefined -> run the detection probe (only meaningful when the options/full + * - 'auto' -> run the detection probe (only meaningful when the options/full * API exists, since that's the only path that passes the premultiply option) */ private resolvePremultiplyAndInit( support: CreateImageBitmapSupport, - premultiplyAlphaHonored: boolean | undefined, + premultiplyAlphaHonored: boolean | 'auto', ): void { - if (premultiplyAlphaHonored !== undefined) { + if (premultiplyAlphaHonored !== 'auto') { support.premultiplyHonored = premultiplyAlphaHonored; this.initialize(support); return; diff --git a/src/core/Stage.ts b/src/core/Stage.ts index 01d70e6..016eee7 100644 --- a/src/core/Stage.ts +++ b/src/core/Stage.ts @@ -207,7 +207,8 @@ export class Stage { this.txManager = new CoreTextureManager(this, { numImageWorkers, createImageBitmapSupport, - premultiplyAlphaHonored, + // undefined -> true (default: assume honored, no probe) + premultiplyAlphaHonored: premultiplyAlphaHonored ?? true, maxRetryCount, }); diff --git a/src/main-api/Renderer.ts b/src/main-api/Renderer.ts index c2ce4ce..940ebcc 100644 --- a/src/main-api/Renderer.ts +++ b/src/main-api/Renderer.ts @@ -468,13 +468,14 @@ export type RendererMainSettings = RendererRuntimeSettings & { * ignore it, returning straight (non-premultiplied) alpha. This causes edge * "ghosting" on images with transparency. * - * Leave unset to auto-detect via a cheap startup probe. Set to a boolean to - * skip the probe entirely and force the value — useful on known fleet devices - * where the probe is unnecessary overhead or unreliable. + * Set to `'auto'` to detect via a cheap startup probe (one 1×1 texture + * upload + framebuffer readback). Set to a boolean to force the value. Leave + * unset to assume the option is honored — the default, which preserves + * existing behavior with no probe overhead. * - * @defaultValue `undefined` (auto-detect via probe) + * @defaultValue `true` (assume honored; no probe) */ - premultiplyAlphaHonored?: boolean; + premultiplyAlphaHonored?: boolean | 'auto'; /** * Provide an alternative platform abstraction layer @@ -604,8 +605,12 @@ export class RendererMain extends EventEmitter { textureProcessingTimeLimit: settings.textureProcessingTimeLimit || 10, canvas: settings.canvas, createImageBitmapSupport: settings.createImageBitmapSupport || 'full', - // undefined -> probe; explicit boolean -> force the value. - premultiplyAlphaHonored: settings.premultiplyAlphaHonored, + // undefined -> true (assume honored, no probe); 'auto' -> probe; + // explicit boolean -> force the value. + premultiplyAlphaHonored: + settings.premultiplyAlphaHonored === undefined + ? true + : settings.premultiplyAlphaHonored, platform: settings.platform || null, maxRetryCount: settings.maxRetryCount ?? 5, };