diff --git a/packages/effects-core/src/asset-manager.ts b/packages/effects-core/src/asset-manager.ts index c27aab2f..14e8c1fa 100644 --- a/packages/effects-core/src/asset-manager.ts +++ b/packages/effects-core/src/asset-manager.ts @@ -5,7 +5,7 @@ import { passRenderLevel } from './pass-render-level'; import type { PrecompileOptions } from './plugin-system'; import { PluginSystem } from './plugin-system'; import type { JSONValue } from './downloader'; -import { Downloader, loadWebPOptional, loadImage, loadVideo, loadMedia, loadAVIFOptional } from './downloader'; +import { Downloader, loadWebPOptional, loadImage, loadVideo, loadMedia, loadAVIFOptional, closeImageBitMap } from './downloader'; import type { ImageLike, SceneLoadOptions } from './scene'; import { Scene } from './scene'; import type { Disposable } from './utils'; @@ -37,6 +37,10 @@ export class AssetManager implements Disposable { * TextureSource 来源 */ private sourceFrom: Record = {}; + /** + * Texture 选项,用于创建 ImageBitmap + */ + private imageBitmapOptions: Record = {}; /** * 自定义文本缓存,随页面销毁而销毁 */ @@ -217,11 +221,13 @@ export class AssetManager implements Disposable { private async processJSON (json: JSONValue) { const jsonScene = getStandardJSON(json); - const { plugins = [] } = jsonScene; + const { plugins = [], textures } = jsonScene; const pluginSystem = new PluginSystem(plugins); await pluginSystem.processRawJSON(jsonScene, this.options); + this.assignImageBitmapOptions(textures); + return { jsonScene, pluginSystem, @@ -356,8 +362,8 @@ export class AssetManager implements Disposable { } const { url, image } = avifURL - ? await loadAVIFOptional(imageURL, avifURL) - : await loadWebPOptional(imageURL, webpURL); + ? await loadAVIFOptional(imageURL, avifURL, this.imageBitmapOptions[id]) + : await loadWebPOptional(imageURL, webpURL, this.imageBitmapOptions[id]); this.sourceFrom[id] = { url, type: TextureSourceType.image }; @@ -471,6 +477,21 @@ export class AssetManager implements Disposable { } } + private assignImageBitmapOptions (textures: spec.SerializedTextureSource[] = []) { + textures.forEach(texture => { + if (!(texture instanceof Texture || 'mipmaps' in texture)) { + const { source } = texture; + + if (isObject(source)) { + this.imageBitmapOptions[source.id as string] = { + imageOrientation: texture.flipY ? 'flipY' : 'none', + premultiplyAlpha: texture.premultiplyAlpha ? 'premultiply' : 'none', + }; + } + } + }); + } + private removeTimer (id: number) { const index = this.timers.indexOf(id); @@ -485,6 +506,8 @@ export class AssetManager implements Disposable { if (this.timers.length) { this.timers.map(id => window.clearTimeout(id)); } + + closeImageBitMap(Object.keys(this.assets).map(key => this.assets[key])); this.assets = {}; this.sourceFrom = {}; this.timers = []; @@ -508,6 +531,7 @@ function createTextureOptionsBySource ( }; } else if ( image instanceof HTMLImageElement || + image instanceof ImageBitmap || isCanvas(image as HTMLCanvasElement) ) { return { diff --git a/packages/effects-core/src/config.ts b/packages/effects-core/src/config.ts index 42b86e38..9852def4 100644 --- a/packages/effects-core/src/config.ts +++ b/packages/effects-core/src/config.ts @@ -5,6 +5,8 @@ export const RENDER_PREFER_LOOKUP_TEXTURE = 'lookup_texture'; export const TEMPLATE_USE_OFFSCREEN_CANVAS = 'offscreen_canvas'; // 后处理配置相关 export const POST_PROCESS_SETTINGS = 'post_process_settings'; +// 加载图片时是否使用 ImageBitmap +export const LOAD_PREFER_IMAGE_BITMAP = 'load_image_bitmap'; const config: Record> = {}; diff --git a/packages/effects-core/src/downloader.ts b/packages/effects-core/src/downloader.ts index 4b960726..4164aa0c 100644 --- a/packages/effects-core/src/downloader.ts +++ b/packages/effects-core/src/downloader.ts @@ -1,3 +1,4 @@ +import { LOAD_PREFER_IMAGE_BITMAP, getConfig } from './config'; import { isAndroid } from './utils'; type SuccessHandler = (data: T) => void; @@ -109,20 +110,20 @@ let avifFailed = false; * @param png - PNG 图片文件的 URL * @param webp - WebP 图片文件的 URL */ -export async function loadWebPOptional (png: string, webp?: string) { +export async function loadWebPOptional (png: string, webp?: string, options?: ImageBitmapOptions) { if (webPFailed || !webp) { - const image = await loadImage(png); + const image = await loadImageBitmap(png, options); return { image, url: png }; } try { - const image = await loadImage(webp); + const image = await loadImageBitmap(webp, options); return { image, url: webp }; } catch (_) { webPFailed = true; - const image = await loadImage(png); + const image = await loadImageBitmap(png, options); return { image, url: png }; } @@ -133,20 +134,20 @@ export async function loadWebPOptional (png: string, webp?: string) { * @param png - PNG 图片文件的 URL * @param avif - AVIF 图片文件的 URL */ -export async function loadAVIFOptional (png: string, avif?: string) { +export async function loadAVIFOptional (png: string, avif?: string, options?: ImageBitmapOptions) { if (avifFailed || !avif) { - const image = await loadImage(png); + const image = await loadImageBitmap(png, options); return { image, url: png }; } try { - const image = await loadImage(avif); + const image = await loadImageBitmap(avif, options); return { image, url: avif }; } catch (_) { avifFailed = true; - const image = await loadImage(png); + const image = await loadImageBitmap(png, options); return { image, url: png }; } @@ -284,3 +285,46 @@ export async function loadMedia (url: string | string[], loadFn: (url: string) = return loadFn(url); } + +const imageBitMapAvailable = typeof createImageBitmap === 'function'; + +/** + * 异步加载一个图片文件,如果支持 ImageBitmap 则返回 ImageBitmap 对象 + * @param source + * @param options + * @returns + */ +export async function loadImageBitmap ( + source: string | Blob | HTMLImageElement, + options?: ImageBitmapOptions, +): Promise { + if (imageBitMapAvailable && getConfig(LOAD_PREFER_IMAGE_BITMAP)) { + let blob: Blob | HTMLImageElement; + + if (typeof source === 'string') { + blob = await loadBlob(source); + } else if (source instanceof Blob) { + blob = source; + } else { + return loadImage(source); + } + + return createImageBitmap(blob, options); + } else { + return loadImage(source); + } +} + +/** + * 关闭 ImageBitMap,释放内存 + * @param imgs + */ +export function closeImageBitMap (imgs: any) { + if (imageBitMapAvailable) { + if (imgs instanceof ImageBitmap) { + imgs.close(); + } else if (imgs instanceof Array) { + imgs.forEach(closeImageBitMap); + } + } +} diff --git a/packages/effects-core/src/scene.ts b/packages/effects-core/src/scene.ts index 9e8d3fce..4498b5fe 100644 --- a/packages/effects-core/src/scene.ts +++ b/packages/effects-core/src/scene.ts @@ -4,7 +4,7 @@ import type { PluginSystem } from './plugin-system'; import type { PickEnum } from './utils'; import { isObject } from './utils'; -export type ImageLike = spec.HTMLImageLike | ArrayBuffer | Texture; +export type ImageLike = spec.HTMLImageLike | ArrayBuffer | Texture | ImageBitmap; export type SceneRenderLevel = PickEnum; /** diff --git a/packages/effects-core/src/template-image.ts b/packages/effects-core/src/template-image.ts index 0d5003d0..1ba13ad3 100644 --- a/packages/effects-core/src/template-image.ts +++ b/packages/effects-core/src/template-image.ts @@ -32,7 +32,7 @@ export async function combineImageTemplate ( template?: spec.TemplateContent, variables?: spec.TemplateVariables, ) { - let image; + let image: HTMLImageElement; if (typeof url === 'string') { image = await loadImage(url);